summaryrefslogtreecommitdiff
path: root/enigma/src
diff options
context:
space:
mode:
Diffstat (limited to 'enigma/src')
-rw-r--r--enigma/src/main/java/cuchaz/enigma/ClassProvider.java10
-rw-r--r--enigma/src/main/java/cuchaz/enigma/Enigma.java139
-rw-r--r--enigma/src/main/java/cuchaz/enigma/EnigmaProfile.java131
-rw-r--r--enigma/src/main/java/cuchaz/enigma/EnigmaProject.java288
-rw-r--r--enigma/src/main/java/cuchaz/enigma/EnigmaServices.java20
-rw-r--r--enigma/src/main/java/cuchaz/enigma/ProgressListener.java19
-rw-r--r--enigma/src/main/java/cuchaz/enigma/analysis/Access.java43
-rw-r--r--enigma/src/main/java/cuchaz/enigma/analysis/BuiltinPlugin.java156
-rw-r--r--enigma/src/main/java/cuchaz/enigma/analysis/ClassCache.java126
-rw-r--r--enigma/src/main/java/cuchaz/enigma/analysis/ClassImplementationsTreeNode.java72
-rw-r--r--enigma/src/main/java/cuchaz/enigma/analysis/ClassInheritanceTreeNode.java72
-rw-r--r--enigma/src/main/java/cuchaz/enigma/analysis/ClassReferenceTreeNode.java94
-rw-r--r--enigma/src/main/java/cuchaz/enigma/analysis/EntryReference.java140
-rw-r--r--enigma/src/main/java/cuchaz/enigma/analysis/FieldReferenceTreeNode.java83
-rw-r--r--enigma/src/main/java/cuchaz/enigma/analysis/IndexSimpleVerifier.java154
-rw-r--r--enigma/src/main/java/cuchaz/enigma/analysis/IndexTreeBuilder.java74
-rw-r--r--enigma/src/main/java/cuchaz/enigma/analysis/InterpreterPair.java130
-rw-r--r--enigma/src/main/java/cuchaz/enigma/analysis/MethodImplementationsTreeNode.java85
-rw-r--r--enigma/src/main/java/cuchaz/enigma/analysis/MethodInheritanceTreeNode.java95
-rw-r--r--enigma/src/main/java/cuchaz/enigma/analysis/MethodNodeWithAction.java19
-rw-r--r--enigma/src/main/java/cuchaz/enigma/analysis/MethodReferenceTreeNode.java113
-rw-r--r--enigma/src/main/java/cuchaz/enigma/analysis/ReferenceTargetType.java74
-rw-r--r--enigma/src/main/java/cuchaz/enigma/analysis/ReferenceTreeNode.java20
-rw-r--r--enigma/src/main/java/cuchaz/enigma/analysis/index/BridgeMethodIndex.java156
-rw-r--r--enigma/src/main/java/cuchaz/enigma/analysis/index/EntryIndex.java102
-rw-r--r--enigma/src/main/java/cuchaz/enigma/analysis/index/IndexClassVisitor.java40
-rw-r--r--enigma/src/main/java/cuchaz/enigma/analysis/index/IndexReferenceVisitor.java180
-rw-r--r--enigma/src/main/java/cuchaz/enigma/analysis/index/InheritanceIndex.java127
-rw-r--r--enigma/src/main/java/cuchaz/enigma/analysis/index/JarIndex.java170
-rw-r--r--enigma/src/main/java/cuchaz/enigma/analysis/index/JarIndexer.java28
-rw-r--r--enigma/src/main/java/cuchaz/enigma/analysis/index/PackageVisibilityIndex.java147
-rw-r--r--enigma/src/main/java/cuchaz/enigma/analysis/index/ReferenceIndex.java148
-rw-r--r--enigma/src/main/java/cuchaz/enigma/api/EnigmaPlugin.java5
-rw-r--r--enigma/src/main/java/cuchaz/enigma/api/EnigmaPluginContext.java9
-rw-r--r--enigma/src/main/java/cuchaz/enigma/api/service/EnigmaService.java4
-rw-r--r--enigma/src/main/java/cuchaz/enigma/api/service/EnigmaServiceContext.java11
-rw-r--r--enigma/src/main/java/cuchaz/enigma/api/service/EnigmaServiceFactory.java5
-rw-r--r--enigma/src/main/java/cuchaz/enigma/api/service/EnigmaServiceType.java29
-rw-r--r--enigma/src/main/java/cuchaz/enigma/api/service/JarIndexerService.java10
-rw-r--r--enigma/src/main/java/cuchaz/enigma/api/service/NameProposalService.java12
-rw-r--r--enigma/src/main/java/cuchaz/enigma/api/service/ObfuscationTestService.java9
-rw-r--r--enigma/src/main/java/cuchaz/enigma/bytecode/translators/AsmObjectTranslator.java46
-rw-r--r--enigma/src/main/java/cuchaz/enigma/bytecode/translators/LocalVariableFixVisitor.java126
-rw-r--r--enigma/src/main/java/cuchaz/enigma/bytecode/translators/SourceFixVisitor.java39
-rw-r--r--enigma/src/main/java/cuchaz/enigma/bytecode/translators/TranslationAnnotationVisitor.java51
-rw-r--r--enigma/src/main/java/cuchaz/enigma/bytecode/translators/TranslationClassVisitor.java102
-rw-r--r--enigma/src/main/java/cuchaz/enigma/bytecode/translators/TranslationFieldVisitor.java33
-rw-r--r--enigma/src/main/java/cuchaz/enigma/bytecode/translators/TranslationMethodVisitor.java145
-rw-r--r--enigma/src/main/java/cuchaz/enigma/bytecode/translators/TranslationSignatureVisitor.java129
-rw-r--r--enigma/src/main/java/cuchaz/enigma/source/Decompiler.java5
-rw-r--r--enigma/src/main/java/cuchaz/enigma/source/DecompilerService.java11
-rw-r--r--enigma/src/main/java/cuchaz/enigma/source/Decompilers.java9
-rw-r--r--enigma/src/main/java/cuchaz/enigma/source/Source.java11
-rw-r--r--enigma/src/main/java/cuchaz/enigma/source/SourceIndex.java172
-rw-r--r--enigma/src/main/java/cuchaz/enigma/source/SourceRemapper.java62
-rw-r--r--enigma/src/main/java/cuchaz/enigma/source/SourceSettings.java11
-rw-r--r--enigma/src/main/java/cuchaz/enigma/source/Token.java72
-rw-r--r--enigma/src/main/java/cuchaz/enigma/source/cfr/CfrDecompiler.java108
-rw-r--r--enigma/src/main/java/cuchaz/enigma/source/cfr/CfrSource.java38
-rw-r--r--enigma/src/main/java/cuchaz/enigma/source/cfr/EnigmaDumper.java433
-rw-r--r--enigma/src/main/java/cuchaz/enigma/source/procyon/EntryParser.java49
-rw-r--r--enigma/src/main/java/cuchaz/enigma/source/procyon/ProcyonDecompiler.java84
-rw-r--r--enigma/src/main/java/cuchaz/enigma/source/procyon/ProcyonSource.java49
-rw-r--r--enigma/src/main/java/cuchaz/enigma/source/procyon/index/SourceIndexClassVisitor.java95
-rw-r--r--enigma/src/main/java/cuchaz/enigma/source/procyon/index/SourceIndexMethodVisitor.java218
-rw-r--r--enigma/src/main/java/cuchaz/enigma/source/procyon/index/SourceIndexVisitor.java40
-rw-r--r--enigma/src/main/java/cuchaz/enigma/source/procyon/index/TokenFactory.java46
-rw-r--r--enigma/src/main/java/cuchaz/enigma/source/procyon/transformers/AddJavadocsAstTransform.java134
-rw-r--r--enigma/src/main/java/cuchaz/enigma/source/procyon/transformers/DropImportAstTransform.java33
-rw-r--r--enigma/src/main/java/cuchaz/enigma/source/procyon/transformers/DropVarModifiersAstTransform.java37
-rw-r--r--enigma/src/main/java/cuchaz/enigma/source/procyon/transformers/InvalidIdentifierFix.java29
-rw-r--r--enigma/src/main/java/cuchaz/enigma/source/procyon/transformers/Java8Generics.java107
-rw-r--r--enigma/src/main/java/cuchaz/enigma/source/procyon/transformers/ObfuscatedEnumSwitchRewriterTransform.java414
-rw-r--r--enigma/src/main/java/cuchaz/enigma/source/procyon/transformers/RemoveObjectCasts.java39
-rw-r--r--enigma/src/main/java/cuchaz/enigma/source/procyon/transformers/VarargsFixer.java197
-rw-r--r--enigma/src/main/java/cuchaz/enigma/source/procyon/typeloader/CachingClasspathTypeLoader.java33
-rw-r--r--enigma/src/main/java/cuchaz/enigma/source/procyon/typeloader/CachingTypeLoader.java38
-rw-r--r--enigma/src/main/java/cuchaz/enigma/source/procyon/typeloader/CompiledSourceTypeLoader.java140
-rw-r--r--enigma/src/main/java/cuchaz/enigma/source/procyon/typeloader/NoRetryMetadataSystem.java38
-rw-r--r--enigma/src/main/java/cuchaz/enigma/source/procyon/typeloader/SynchronizedTypeLoader.java20
-rw-r--r--enigma/src/main/java/cuchaz/enigma/translation/LocalNameGenerator.java44
-rw-r--r--enigma/src/main/java/cuchaz/enigma/translation/MappingTranslator.java24
-rw-r--r--enigma/src/main/java/cuchaz/enigma/translation/ProposingTranslator.java51
-rw-r--r--enigma/src/main/java/cuchaz/enigma/translation/SignatureUpdater.java92
-rw-r--r--enigma/src/main/java/cuchaz/enigma/translation/Translatable.java9
-rw-r--r--enigma/src/main/java/cuchaz/enigma/translation/TranslationDirection.java36
-rw-r--r--enigma/src/main/java/cuchaz/enigma/translation/Translator.java61
-rw-r--r--enigma/src/main/java/cuchaz/enigma/translation/VoidTranslator.java10
-rw-r--r--enigma/src/main/java/cuchaz/enigma/translation/mapping/AccessModifier.java25
-rw-r--r--enigma/src/main/java/cuchaz/enigma/translation/mapping/EntryMap.java24
-rw-r--r--enigma/src/main/java/cuchaz/enigma/translation/mapping/EntryMapping.java75
-rw-r--r--enigma/src/main/java/cuchaz/enigma/translation/mapping/EntryRemapper.java104
-rw-r--r--enigma/src/main/java/cuchaz/enigma/translation/mapping/EntryResolver.java41
-rw-r--r--enigma/src/main/java/cuchaz/enigma/translation/mapping/IllegalNameException.java39
-rw-r--r--enigma/src/main/java/cuchaz/enigma/translation/mapping/IndexEntryResolver.java227
-rw-r--r--enigma/src/main/java/cuchaz/enigma/translation/mapping/MappingDelta.java54
-rw-r--r--enigma/src/main/java/cuchaz/enigma/translation/mapping/MappingOperations.java71
-rw-r--r--enigma/src/main/java/cuchaz/enigma/translation/mapping/MappingPair.java32
-rw-r--r--enigma/src/main/java/cuchaz/enigma/translation/mapping/MappingValidator.java75
-rw-r--r--enigma/src/main/java/cuchaz/enigma/translation/mapping/MappingsChecker.java99
-rw-r--r--enigma/src/main/java/cuchaz/enigma/translation/mapping/NameValidator.java50
-rw-r--r--enigma/src/main/java/cuchaz/enigma/translation/mapping/ResolutionStrategy.java6
-rw-r--r--enigma/src/main/java/cuchaz/enigma/translation/mapping/VoidEntryResolver.java27
-rw-r--r--enigma/src/main/java/cuchaz/enigma/translation/mapping/serde/LfPrintWriter.java16
-rw-r--r--enigma/src/main/java/cuchaz/enigma/translation/mapping/serde/MappingFileNameFormat.java10
-rw-r--r--enigma/src/main/java/cuchaz/enigma/translation/mapping/serde/MappingFormat.java65
-rw-r--r--enigma/src/main/java/cuchaz/enigma/translation/mapping/serde/MappingHelper.java51
-rw-r--r--enigma/src/main/java/cuchaz/enigma/translation/mapping/serde/MappingParseException.java39
-rw-r--r--enigma/src/main/java/cuchaz/enigma/translation/mapping/serde/MappingSaveParameters.java16
-rw-r--r--enigma/src/main/java/cuchaz/enigma/translation/mapping/serde/MappingsReader.java12
-rw-r--r--enigma/src/main/java/cuchaz/enigma/translation/mapping/serde/MappingsWriter.java16
-rw-r--r--enigma/src/main/java/cuchaz/enigma/translation/mapping/serde/RawEntryMapping.java30
-rw-r--r--enigma/src/main/java/cuchaz/enigma/translation/mapping/serde/enigma/EnigmaFormat.java9
-rw-r--r--enigma/src/main/java/cuchaz/enigma/translation/mapping/serde/enigma/EnigmaMappingsReader.java322
-rw-r--r--enigma/src/main/java/cuchaz/enigma/translation/mapping/serde/enigma/EnigmaMappingsWriter.java318
-rw-r--r--enigma/src/main/java/cuchaz/enigma/translation/mapping/serde/proguard/ProguardMappingsReader.java135
-rw-r--r--enigma/src/main/java/cuchaz/enigma/translation/mapping/serde/srg/SrgMappingsWriter.java119
-rw-r--r--enigma/src/main/java/cuchaz/enigma/translation/mapping/serde/tiny/TinyMappingsReader.java116
-rw-r--r--enigma/src/main/java/cuchaz/enigma/translation/mapping/serde/tiny/TinyMappingsWriter.java149
-rw-r--r--enigma/src/main/java/cuchaz/enigma/translation/mapping/serde/tinyv2/TinyV2Reader.java296
-rw-r--r--enigma/src/main/java/cuchaz/enigma/translation/mapping/serde/tinyv2/TinyV2Writer.java172
-rw-r--r--enigma/src/main/java/cuchaz/enigma/translation/mapping/tree/DeltaTrackingTree.java110
-rw-r--r--enigma/src/main/java/cuchaz/enigma/translation/mapping/tree/EntryTree.java26
-rw-r--r--enigma/src/main/java/cuchaz/enigma/translation/mapping/tree/EntryTreeNode.java40
-rw-r--r--enigma/src/main/java/cuchaz/enigma/translation/mapping/tree/HashEntryTree.java188
-rw-r--r--enigma/src/main/java/cuchaz/enigma/translation/mapping/tree/HashTreeNode.java75
-rw-r--r--enigma/src/main/java/cuchaz/enigma/translation/representation/AccessFlags.java116
-rw-r--r--enigma/src/main/java/cuchaz/enigma/translation/representation/Lambda.java105
-rw-r--r--enigma/src/main/java/cuchaz/enigma/translation/representation/MethodDescriptor.java132
-rw-r--r--enigma/src/main/java/cuchaz/enigma/translation/representation/Signature.java98
-rw-r--r--enigma/src/main/java/cuchaz/enigma/translation/representation/TypeDescriptor.java268
-rw-r--r--enigma/src/main/java/cuchaz/enigma/translation/representation/entry/ClassDefEntry.java93
-rw-r--r--enigma/src/main/java/cuchaz/enigma/translation/representation/entry/ClassEntry.java214
-rw-r--r--enigma/src/main/java/cuchaz/enigma/translation/representation/entry/DefEntry.java7
-rw-r--r--enigma/src/main/java/cuchaz/enigma/translation/representation/entry/Entry.java107
-rw-r--r--enigma/src/main/java/cuchaz/enigma/translation/representation/entry/FieldDefEntry.java71
-rw-r--r--enigma/src/main/java/cuchaz/enigma/translation/representation/entry/FieldEntry.java96
-rw-r--r--enigma/src/main/java/cuchaz/enigma/translation/representation/entry/LocalVariableDefEntry.java51
-rw-r--r--enigma/src/main/java/cuchaz/enigma/translation/representation/entry/LocalVariableEntry.java93
-rw-r--r--enigma/src/main/java/cuchaz/enigma/translation/representation/entry/MethodDefEntry.java71
-rw-r--r--enigma/src/main/java/cuchaz/enigma/translation/representation/entry/MethodEntry.java105
-rw-r--r--enigma/src/main/java/cuchaz/enigma/translation/representation/entry/ParentedEntry.java81
-rw-r--r--enigma/src/main/java/cuchaz/enigma/utils/I18n.java95
-rw-r--r--enigma/src/main/java/cuchaz/enigma/utils/Pair.java26
-rw-r--r--enigma/src/main/java/cuchaz/enigma/utils/Utils.java92
-rw-r--r--enigma/src/main/resources/META-INF/services/cuchaz.enigma.api.EnigmaPlugin1
-rw-r--r--enigma/src/main/resources/lang/en_us.json164
-rw-r--r--enigma/src/main/resources/lang/fr_fr.json164
-rw-r--r--enigma/src/main/resources/lang/zh_cn.json118
-rw-r--r--enigma/src/main/resources/profile.json20
-rw-r--r--enigma/src/test/java/cuchaz/enigma/PackageVisibilityIndexTest.java53
-rw-r--r--enigma/src/test/java/cuchaz/enigma/TestDeobfed.java106
-rw-r--r--enigma/src/test/java/cuchaz/enigma/TestDeobfuscator.java41
-rw-r--r--enigma/src/test/java/cuchaz/enigma/TestEntryFactory.java49
-rw-r--r--enigma/src/test/java/cuchaz/enigma/TestInnerClasses.java85
-rw-r--r--enigma/src/test/java/cuchaz/enigma/TestJarIndexConstructorReferences.java124
-rw-r--r--enigma/src/test/java/cuchaz/enigma/TestJarIndexInheritanceTree.java227
-rw-r--r--enigma/src/test/java/cuchaz/enigma/TestJarIndexLoneClass.java157
-rw-r--r--enigma/src/test/java/cuchaz/enigma/TestMethodDescriptor.java247
-rw-r--r--enigma/src/test/java/cuchaz/enigma/TestTokensConstructors.java137
-rw-r--r--enigma/src/test/java/cuchaz/enigma/TestTranslator.java155
-rw-r--r--enigma/src/test/java/cuchaz/enigma/TestTypeDescriptor.java243
-rw-r--r--enigma/src/test/java/cuchaz/enigma/TokenChecker.java65
-rw-r--r--enigma/src/test/java/cuchaz/enigma/inputs/Keep.java18
-rw-r--r--enigma/src/test/java/cuchaz/enigma/inputs/constructors/BaseClass.java26
-rw-r--r--enigma/src/test/java/cuchaz/enigma/inputs/constructors/Caller.java58
-rw-r--r--enigma/src/test/java/cuchaz/enigma/inputs/constructors/DefaultConstructable.java16
-rw-r--r--enigma/src/test/java/cuchaz/enigma/inputs/constructors/SubClass.java39
-rw-r--r--enigma/src/test/java/cuchaz/enigma/inputs/constructors/SubSubClass.java22
-rw-r--r--enigma/src/test/java/cuchaz/enigma/inputs/inheritanceTree/BaseClass.java32
-rw-r--r--enigma/src/test/java/cuchaz/enigma/inputs/inheritanceTree/SubclassA.java22
-rw-r--r--enigma/src/test/java/cuchaz/enigma/inputs/inheritanceTree/SubclassB.java41
-rw-r--r--enigma/src/test/java/cuchaz/enigma/inputs/inheritanceTree/SubsubclassAA.java35
-rw-r--r--enigma/src/test/java/cuchaz/enigma/inputs/innerClasses/A_Anonymous.java25
-rw-r--r--enigma/src/test/java/cuchaz/enigma/inputs/innerClasses/B_AnonymousWithScopeArgs.java24
-rw-r--r--enigma/src/test/java/cuchaz/enigma/inputs/innerClasses/C_ConstructorArgs.java31
-rw-r--r--enigma/src/test/java/cuchaz/enigma/inputs/innerClasses/D_Simple.java19
-rw-r--r--enigma/src/test/java/cuchaz/enigma/inputs/innerClasses/E_AnonymousWithOuterAccess.java32
-rw-r--r--enigma/src/test/java/cuchaz/enigma/inputs/innerClasses/F_ClassTree.java30
-rw-r--r--enigma/src/test/java/cuchaz/enigma/inputs/loneClass/LoneClass.java25
-rw-r--r--enigma/src/test/java/cuchaz/enigma/inputs/packageAccess/Base.java7
-rw-r--r--enigma/src/test/java/cuchaz/enigma/inputs/packageAccess/SamePackageChild.java12
-rw-r--r--enigma/src/test/java/cuchaz/enigma/inputs/packageAccess/sub/OtherPackageChild.java14
-rw-r--r--enigma/src/test/java/cuchaz/enigma/inputs/translation/A_Basic.java33
-rw-r--r--enigma/src/test/java/cuchaz/enigma/inputs/translation/B_BaseClass.java26
-rw-r--r--enigma/src/test/java/cuchaz/enigma/inputs/translation/C_SubClass.java28
-rw-r--r--enigma/src/test/java/cuchaz/enigma/inputs/translation/D_AnonymousTesting.java29
-rw-r--r--enigma/src/test/java/cuchaz/enigma/inputs/translation/E_Bridges.java32
-rw-r--r--enigma/src/test/java/cuchaz/enigma/inputs/translation/F_ObjectMethods.java31
-rw-r--r--enigma/src/test/java/cuchaz/enigma/inputs/translation/G_OuterClass.java36
-rw-r--r--enigma/src/test/java/cuchaz/enigma/inputs/translation/H_NamelessClass.java40
-rw-r--r--enigma/src/test/java/cuchaz/enigma/inputs/translation/I_Generics.java35
-rw-r--r--enigma/src/test/java/cuchaz/enigma/translation/mapping/TestComments.java39
-rw-r--r--enigma/src/test/java/cuchaz/enigma/translation/mapping/TestTinyV2InnerClasses.java37
-rw-r--r--enigma/src/test/java/cuchaz/enigma/translation/mapping/TestV2Main.java23
-rw-r--r--enigma/src/test/resources/comments/test.mapping18
-rw-r--r--enigma/src/test/resources/proguard-build.conf6
-rw-r--r--enigma/src/test/resources/proguard-test.conf8
-rw-r--r--enigma/src/test/resources/tinyV2InnerClasses/c.mapping2
-rw-r--r--enigma/src/test/resources/tinyV2InnerClasses/cuchaz/enigma/Dad.mapping5
-rw-r--r--enigma/src/test/resources/translation.mappings41
201 files changed, 15472 insertions, 0 deletions
diff --git a/enigma/src/main/java/cuchaz/enigma/ClassProvider.java b/enigma/src/main/java/cuchaz/enigma/ClassProvider.java
new file mode 100644
index 0000000..2b91379
--- /dev/null
+++ b/enigma/src/main/java/cuchaz/enigma/ClassProvider.java
@@ -0,0 +1,10 @@
1package cuchaz.enigma;
2
3import org.objectweb.asm.tree.ClassNode;
4
5import javax.annotation.Nullable;
6
7public interface ClassProvider {
8 @Nullable
9 ClassNode getClassNode(String name);
10}
diff --git a/enigma/src/main/java/cuchaz/enigma/Enigma.java b/enigma/src/main/java/cuchaz/enigma/Enigma.java
new file mode 100644
index 0000000..73c9a09
--- /dev/null
+++ b/enigma/src/main/java/cuchaz/enigma/Enigma.java
@@ -0,0 +1,139 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 * <p>
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11
12package cuchaz.enigma;
13
14import com.google.common.base.Preconditions;
15import com.google.common.collect.ImmutableListMultimap;
16import cuchaz.enigma.analysis.ClassCache;
17import cuchaz.enigma.analysis.index.JarIndex;
18import cuchaz.enigma.api.EnigmaPlugin;
19import cuchaz.enigma.api.EnigmaPluginContext;
20import cuchaz.enigma.api.service.EnigmaService;
21import cuchaz.enigma.api.service.EnigmaServiceFactory;
22import cuchaz.enigma.api.service.EnigmaServiceType;
23import cuchaz.enigma.api.service.JarIndexerService;
24import cuchaz.enigma.utils.Utils;
25import org.objectweb.asm.Opcodes;
26
27import java.io.IOException;
28import java.nio.file.Path;
29import java.util.List;
30import java.util.ServiceLoader;
31
32public class Enigma {
33 public static final String NAME = "Enigma";
34 public static final String VERSION;
35 public static final String URL = "https://fabricmc.net";
36 public static final int ASM_VERSION = Opcodes.ASM8;
37
38 private final EnigmaProfile profile;
39 private final EnigmaServices services;
40
41 private Enigma(EnigmaProfile profile, EnigmaServices services) {
42 this.profile = profile;
43 this.services = services;
44 }
45
46 public static Enigma create() {
47 return new Builder().build();
48 }
49
50 public static Builder builder() {
51 return new Builder();
52 }
53
54 public EnigmaProject openJar(Path path, ProgressListener progress) throws IOException {
55 ClassCache classCache = ClassCache.of(path);
56 JarIndex jarIndex = classCache.index(progress);
57
58 services.get(JarIndexerService.TYPE).forEach(indexer -> indexer.acceptJar(classCache, jarIndex));
59
60 return new EnigmaProject(this, classCache, jarIndex, Utils.zipSha1(path));
61 }
62
63 public EnigmaProfile getProfile() {
64 return profile;
65 }
66
67 public EnigmaServices getServices() {
68 return services;
69 }
70
71 public static class Builder {
72 private EnigmaProfile profile = EnigmaProfile.EMPTY;
73 private Iterable<EnigmaPlugin> plugins = ServiceLoader.load(EnigmaPlugin.class);
74
75 private Builder() {
76 }
77
78 public Builder setProfile(EnigmaProfile profile) {
79 Preconditions.checkNotNull(profile, "profile cannot be null");
80 this.profile = profile;
81 return this;
82 }
83
84 public Builder setPlugins(Iterable<EnigmaPlugin> plugins) {
85 Preconditions.checkNotNull(plugins, "plugins cannot be null");
86 this.plugins = plugins;
87 return this;
88 }
89
90 public Enigma build() {
91 PluginContext pluginContext = new PluginContext(profile);
92 for (EnigmaPlugin plugin : plugins) {
93 plugin.init(pluginContext);
94 }
95
96 EnigmaServices services = pluginContext.buildServices();
97 return new Enigma(profile, services);
98 }
99 }
100
101 private static class PluginContext implements EnigmaPluginContext {
102 private final EnigmaProfile profile;
103
104 private final ImmutableListMultimap.Builder<EnigmaServiceType<?>, EnigmaService> services = ImmutableListMultimap.builder();
105
106 PluginContext(EnigmaProfile profile) {
107 this.profile = profile;
108 }
109
110 @Override
111 public <T extends EnigmaService> void registerService(String id, EnigmaServiceType<T> serviceType, EnigmaServiceFactory<T> factory) {
112 List<EnigmaProfile.Service> serviceProfiles = profile.getServiceProfiles(serviceType);
113
114 for (EnigmaProfile.Service serviceProfile : serviceProfiles) {
115 if (serviceProfile.matches(id)) {
116 T service = factory.create(serviceProfile::getArgument);
117 services.put(serviceType, service);
118 break;
119 }
120 }
121 }
122
123 EnigmaServices buildServices() {
124 return new EnigmaServices(services.build());
125 }
126 }
127
128 static {
129 String version = null;
130
131 try {
132 version = Utils.readResourceToString("/version.txt");
133 } catch (Throwable t) {
134 version = "Unknown Version";
135 }
136
137 VERSION = version;
138 }
139}
diff --git a/enigma/src/main/java/cuchaz/enigma/EnigmaProfile.java b/enigma/src/main/java/cuchaz/enigma/EnigmaProfile.java
new file mode 100644
index 0000000..daf2727
--- /dev/null
+++ b/enigma/src/main/java/cuchaz/enigma/EnigmaProfile.java
@@ -0,0 +1,131 @@
1package cuchaz.enigma;
2
3import com.google.common.collect.ImmutableMap;
4import com.google.gson.Gson;
5import com.google.gson.GsonBuilder;
6import com.google.gson.JsonDeserializationContext;
7import com.google.gson.JsonDeserializer;
8import com.google.gson.JsonElement;
9import com.google.gson.JsonObject;
10import com.google.gson.JsonParseException;
11import com.google.gson.annotations.SerializedName;
12import com.google.gson.reflect.TypeToken;
13import cuchaz.enigma.api.service.EnigmaServiceType;
14import cuchaz.enigma.translation.mapping.serde.MappingFileNameFormat;
15import cuchaz.enigma.translation.mapping.serde.MappingSaveParameters;
16
17import javax.annotation.Nullable;
18import java.io.BufferedReader;
19import java.io.IOException;
20import java.io.InputStreamReader;
21import java.io.Reader;
22import java.lang.reflect.Type;
23import java.nio.charset.StandardCharsets;
24import java.nio.file.Files;
25import java.nio.file.Path;
26import java.util.Collections;
27import java.util.List;
28import java.util.Map;
29import java.util.Optional;
30
31public final class EnigmaProfile {
32 public static final EnigmaProfile EMPTY = new EnigmaProfile(new ServiceContainer(ImmutableMap.of()));
33
34 private static final MappingSaveParameters DEFAULT_MAPPING_SAVE_PARAMETERS = new MappingSaveParameters(MappingFileNameFormat.BY_DEOBF);
35 private static final Gson GSON = new GsonBuilder()
36 .registerTypeAdapter(ServiceContainer.class, (JsonDeserializer<ServiceContainer>) EnigmaProfile::loadServiceContainer)
37 .create();
38 private static final Type SERVICE_LIST_TYPE = new TypeToken<List<Service>>() {
39 }.getType();
40
41 @SerializedName("services")
42 private final ServiceContainer serviceProfiles;
43
44 @SerializedName("mapping_save_parameters")
45 private final MappingSaveParameters mappingSaveParameters = null;
46
47 private EnigmaProfile(ServiceContainer serviceProfiles) {
48 this.serviceProfiles = serviceProfiles;
49 }
50
51 public static EnigmaProfile read(@Nullable Path file) throws IOException {
52 if (file != null) {
53 try (BufferedReader reader = Files.newBufferedReader(file)) {
54 return EnigmaProfile.parse(reader);
55 }
56 } else {
57 try (BufferedReader reader = new BufferedReader(new InputStreamReader(EnigmaProfile.class.getResourceAsStream("/profile.json"), StandardCharsets.UTF_8))) {
58 return EnigmaProfile.parse(reader);
59 } catch (IOException ex) {
60 System.err.println("Failed to load default profile, will use empty profile: " + ex.getMessage());
61 return EnigmaProfile.EMPTY;
62 }
63 }
64 }
65
66 public static EnigmaProfile parse(Reader reader) {
67 return GSON.fromJson(reader, EnigmaProfile.class);
68 }
69
70 private static ServiceContainer loadServiceContainer(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
71 if (!json.isJsonObject()) {
72 throw new JsonParseException("services must be an Object!");
73 }
74
75 JsonObject object = json.getAsJsonObject();
76
77 ImmutableMap.Builder<String, List<Service>> builder = ImmutableMap.builder();
78
79 for (Map.Entry<String, JsonElement> entry : object.entrySet()) {
80 JsonElement value = entry.getValue();
81 if (value.isJsonObject()) {
82 builder.put(entry.getKey(), Collections.singletonList(GSON.fromJson(value, Service.class)));
83 } else if (value.isJsonArray()) {
84 builder.put(entry.getKey(), GSON.fromJson(value, SERVICE_LIST_TYPE));
85 } else {
86 throw new JsonParseException(String.format("Don't know how to convert %s to a list of service!", value));
87 }
88 }
89
90 return new ServiceContainer(builder.build());
91 }
92
93 public List<Service> getServiceProfiles(EnigmaServiceType<?> serviceType) {
94 return serviceProfiles.get(serviceType.key);
95 }
96
97 public MappingSaveParameters getMappingSaveParameters() {
98 //noinspection ConstantConditions
99 return mappingSaveParameters == null ? EnigmaProfile.DEFAULT_MAPPING_SAVE_PARAMETERS : mappingSaveParameters;
100 }
101
102 public static class Service {
103 private final String id;
104 private final Map<String, String> args;
105
106 Service(String id, Map<String, String> args) {
107 this.id = id;
108 this.args = args;
109 }
110
111 public boolean matches(String id) {
112 return this.id.equals(id);
113 }
114
115 public Optional<String> getArgument(String key) {
116 return args != null ? Optional.ofNullable(args.get(key)) : Optional.empty();
117 }
118 }
119
120 static final class ServiceContainer {
121 private final Map<String, List<Service>> services;
122
123 ServiceContainer(Map<String, List<Service>> services) {
124 this.services = services;
125 }
126
127 List<Service> get(String key) {
128 return services.getOrDefault(key, Collections.emptyList());
129 }
130 }
131}
diff --git a/enigma/src/main/java/cuchaz/enigma/EnigmaProject.java b/enigma/src/main/java/cuchaz/enigma/EnigmaProject.java
new file mode 100644
index 0000000..43ea14e
--- /dev/null
+++ b/enigma/src/main/java/cuchaz/enigma/EnigmaProject.java
@@ -0,0 +1,288 @@
1package cuchaz.enigma;
2
3import com.google.common.base.Functions;
4import com.google.common.base.Preconditions;
5import cuchaz.enigma.analysis.ClassCache;
6import cuchaz.enigma.analysis.EntryReference;
7import cuchaz.enigma.analysis.index.JarIndex;
8import cuchaz.enigma.api.service.NameProposalService;
9import cuchaz.enigma.bytecode.translators.SourceFixVisitor;
10import cuchaz.enigma.bytecode.translators.TranslationClassVisitor;
11import cuchaz.enigma.source.*;
12import cuchaz.enigma.translation.ProposingTranslator;
13import cuchaz.enigma.translation.Translator;
14import cuchaz.enigma.translation.mapping.*;
15import cuchaz.enigma.translation.mapping.tree.DeltaTrackingTree;
16import cuchaz.enigma.translation.mapping.tree.EntryTree;
17import cuchaz.enigma.translation.representation.entry.ClassEntry;
18import cuchaz.enigma.translation.representation.entry.Entry;
19import cuchaz.enigma.translation.representation.entry.LocalVariableEntry;
20import cuchaz.enigma.translation.representation.entry.MethodEntry;
21import cuchaz.enigma.utils.I18n;
22
23import org.objectweb.asm.ClassWriter;
24import org.objectweb.asm.tree.ClassNode;
25
26import java.io.*;
27import java.nio.file.Files;
28import java.nio.file.Path;
29import java.util.Collection;
30import java.util.Map;
31import java.util.Objects;
32import java.util.concurrent.atomic.AtomicInteger;
33import java.util.jar.JarEntry;
34import java.util.jar.JarOutputStream;
35import java.util.stream.Collectors;
36
37public class EnigmaProject {
38 private final Enigma enigma;
39
40 private final ClassCache classCache;
41 private final JarIndex jarIndex;
42 private final byte[] jarChecksum;
43
44 private EntryRemapper mapper;
45
46 public EnigmaProject(Enigma enigma, ClassCache classCache, JarIndex jarIndex, byte[] jarChecksum) {
47 Preconditions.checkArgument(jarChecksum.length == 20);
48 this.enigma = enigma;
49 this.classCache = classCache;
50 this.jarIndex = jarIndex;
51 this.jarChecksum = jarChecksum;
52
53 this.mapper = EntryRemapper.empty(jarIndex);
54 }
55
56 public void setMappings(EntryTree<EntryMapping> mappings) {
57 if (mappings != null) {
58 mapper = EntryRemapper.mapped(jarIndex, mappings);
59 } else {
60 mapper = EntryRemapper.empty(jarIndex);
61 }
62 }
63
64 public Enigma getEnigma() {
65 return enigma;
66 }
67
68 public ClassCache getClassCache() {
69 return classCache;
70 }
71
72 public JarIndex getJarIndex() {
73 return jarIndex;
74 }
75
76 public byte[] getJarChecksum() {
77 return jarChecksum;
78 }
79
80 public EntryRemapper getMapper() {
81 return mapper;
82 }
83
84 public void dropMappings(ProgressListener progress) {
85 DeltaTrackingTree<EntryMapping> mappings = mapper.getObfToDeobf();
86
87 Collection<Entry<?>> dropped = dropMappings(mappings, progress);
88 for (Entry<?> entry : dropped) {
89 mappings.trackChange(entry);
90 }
91 }
92
93 private Collection<Entry<?>> dropMappings(EntryTree<EntryMapping> mappings, ProgressListener progress) {
94 // drop mappings that don't match the jar
95 MappingsChecker checker = new MappingsChecker(jarIndex, mappings);
96 MappingsChecker.Dropped dropped = checker.dropBrokenMappings(progress);
97
98 Map<Entry<?>, String> droppedMappings = dropped.getDroppedMappings();
99 for (Map.Entry<Entry<?>, String> mapping : droppedMappings.entrySet()) {
100 System.out.println("WARNING: Couldn't find " + mapping.getKey() + " (" + mapping.getValue() + ") in jar. Mapping was dropped.");
101 }
102
103 return droppedMappings.keySet();
104 }
105
106 public Decompiler createDecompiler(DecompilerService decompilerService) {
107 return decompilerService.create(name -> {
108 ClassNode node = this.getClassCache().getClassNode(name);
109
110 if (node == null) {
111 return null;
112 }
113
114 ClassNode fixedNode = new ClassNode();
115 node.accept(new SourceFixVisitor(Enigma.ASM_VERSION, fixedNode, getJarIndex()));
116 return fixedNode;
117 }, new SourceSettings(true, true));
118 }
119
120 public boolean isRenamable(Entry<?> obfEntry) {
121 if (obfEntry instanceof MethodEntry) {
122 // HACKHACK: Object methods are not obfuscated identifiers
123 MethodEntry obfMethodEntry = (MethodEntry) obfEntry;
124 String name = obfMethodEntry.getName();
125 String sig = obfMethodEntry.getDesc().toString();
126 if (name.equals("clone") && sig.equals("()Ljava/lang/Object;")) {
127 return false;
128 } else if (name.equals("equals") && sig.equals("(Ljava/lang/Object;)Z")) {
129 return false;
130 } else if (name.equals("finalize") && sig.equals("()V")) {
131 return false;
132 } else if (name.equals("getClass") && sig.equals("()Ljava/lang/Class;")) {
133 return false;
134 } else if (name.equals("hashCode") && sig.equals("()I")) {
135 return false;
136 } else if (name.equals("notify") && sig.equals("()V")) {
137 return false;
138 } else if (name.equals("notifyAll") && sig.equals("()V")) {
139 return false;
140 } else if (name.equals("toString") && sig.equals("()Ljava/lang/String;")) {
141 return false;
142 } else if (name.equals("wait") && sig.equals("()V")) {
143 return false;
144 } else if (name.equals("wait") && sig.equals("(J)V")) {
145 return false;
146 } else if (name.equals("wait") && sig.equals("(JI)V")) {
147 return false;
148 }
149 } else if (obfEntry instanceof LocalVariableEntry && !((LocalVariableEntry) obfEntry).isArgument()) {
150 return false;
151 }
152
153 return this.jarIndex.getEntryIndex().hasEntry(obfEntry);
154 }
155
156 public boolean isRenamable(EntryReference<Entry<?>, Entry<?>> obfReference) {
157 return obfReference.isNamed() && isRenamable(obfReference.getNameableEntry());
158 }
159
160 public JarExport exportRemappedJar(ProgressListener progress) {
161 Collection<ClassEntry> classEntries = jarIndex.getEntryIndex().getClasses();
162
163 NameProposalService[] nameProposalServices = getEnigma().getServices().get(NameProposalService.TYPE).toArray(new NameProposalService[0]);
164 Translator deobfuscator = nameProposalServices.length == 0 ? mapper.getDeobfuscator() : new ProposingTranslator(mapper, nameProposalServices);
165
166 AtomicInteger count = new AtomicInteger();
167 progress.init(classEntries.size(), I18n.translate("progress.classes.deobfuscating"));
168
169 Map<String, ClassNode> compiled = classEntries.parallelStream()
170 .map(entry -> {
171 ClassEntry translatedEntry = deobfuscator.translate(entry);
172 progress.step(count.getAndIncrement(), translatedEntry.toString());
173
174 ClassNode node = classCache.getClassNode(entry.getFullName());
175 if (node != null) {
176 ClassNode translatedNode = new ClassNode();
177 node.accept(new TranslationClassVisitor(deobfuscator, Enigma.ASM_VERSION, new SourceFixVisitor(Enigma.ASM_VERSION, translatedNode, jarIndex)));
178 return translatedNode;
179 }
180
181 return null;
182 })
183 .filter(Objects::nonNull)
184 .collect(Collectors.toMap(n -> n.name, Functions.identity()));
185
186 return new JarExport(jarIndex, compiled);
187 }
188
189 public static final class JarExport {
190 private final JarIndex jarIndex;
191 private final Map<String, ClassNode> compiled;
192
193 JarExport(JarIndex jarIndex, Map<String, ClassNode> compiled) {
194 this.jarIndex = jarIndex;
195 this.compiled = compiled;
196 }
197
198 public void write(Path path, ProgressListener progress) throws IOException {
199 progress.init(this.compiled.size(), I18n.translate("progress.jar.writing"));
200
201 try (JarOutputStream out = new JarOutputStream(Files.newOutputStream(path))) {
202 AtomicInteger count = new AtomicInteger();
203
204 for (ClassNode node : this.compiled.values()) {
205 progress.step(count.getAndIncrement(), node.name);
206
207 String entryName = node.name.replace('.', '/') + ".class";
208
209 ClassWriter writer = new ClassWriter(0);
210 node.accept(writer);
211
212 out.putNextEntry(new JarEntry(entryName));
213 out.write(writer.toByteArray());
214 out.closeEntry();
215 }
216 }
217 }
218
219 public SourceExport decompile(ProgressListener progress, DecompilerService decompilerService) {
220 Collection<ClassNode> classes = this.compiled.values().stream()
221 .filter(classNode -> classNode.name.indexOf('$') == -1)
222 .collect(Collectors.toList());
223
224 progress.init(classes.size(), I18n.translate("progress.classes.decompiling"));
225
226 //create a common instance outside the loop as mappings shouldn't be changing while this is happening
227 Decompiler decompiler = decompilerService.create(compiled::get, new SourceSettings(false, false));
228
229 AtomicInteger count = new AtomicInteger();
230
231 Collection<ClassSource> decompiled = classes.parallelStream()
232 .map(translatedNode -> {
233 progress.step(count.getAndIncrement(), translatedNode.name);
234
235 String source = decompileClass(translatedNode, decompiler);
236 return new ClassSource(translatedNode.name, source);
237 })
238 .collect(Collectors.toList());
239
240 return new SourceExport(decompiled);
241 }
242
243 private String decompileClass(ClassNode translatedNode, Decompiler decompiler) {
244 return decompiler.getSource(translatedNode.name).asString();
245 }
246 }
247
248 public static final class SourceExport {
249 private final Collection<ClassSource> decompiled;
250
251 SourceExport(Collection<ClassSource> decompiled) {
252 this.decompiled = decompiled;
253 }
254
255 public void write(Path path, ProgressListener progress) throws IOException {
256 progress.init(decompiled.size(), I18n.translate("progress.sources.writing"));
257
258 int count = 0;
259 for (ClassSource source : decompiled) {
260 progress.step(count++, source.name);
261
262 Path sourcePath = source.resolvePath(path);
263 source.writeTo(sourcePath);
264 }
265 }
266 }
267
268 private static class ClassSource {
269 private final String name;
270 private final String source;
271
272 ClassSource(String name, String source) {
273 this.name = name;
274 this.source = source;
275 }
276
277 void writeTo(Path path) throws IOException {
278 Files.createDirectories(path.getParent());
279 try (BufferedWriter writer = Files.newBufferedWriter(path)) {
280 writer.write(source);
281 }
282 }
283
284 Path resolvePath(Path root) {
285 return root.resolve(name.replace('.', '/') + ".java");
286 }
287 }
288}
diff --git a/enigma/src/main/java/cuchaz/enigma/EnigmaServices.java b/enigma/src/main/java/cuchaz/enigma/EnigmaServices.java
new file mode 100644
index 0000000..df3b7bb
--- /dev/null
+++ b/enigma/src/main/java/cuchaz/enigma/EnigmaServices.java
@@ -0,0 +1,20 @@
1package cuchaz.enigma;
2
3import com.google.common.collect.ImmutableListMultimap;
4import cuchaz.enigma.api.service.EnigmaService;
5import cuchaz.enigma.api.service.EnigmaServiceType;
6
7import java.util.List;
8
9public final class EnigmaServices {
10 private final ImmutableListMultimap<EnigmaServiceType<?>, EnigmaService> services;
11
12 EnigmaServices(ImmutableListMultimap<EnigmaServiceType<?>, EnigmaService> services) {
13 this.services = services;
14 }
15
16 @SuppressWarnings("unchecked")
17 public <T extends EnigmaService> List<T> get(EnigmaServiceType<T> type) {
18 return (List<T>) services.get(type);
19 }
20}
diff --git a/enigma/src/main/java/cuchaz/enigma/ProgressListener.java b/enigma/src/main/java/cuchaz/enigma/ProgressListener.java
new file mode 100644
index 0000000..6da3b81
--- /dev/null
+++ b/enigma/src/main/java/cuchaz/enigma/ProgressListener.java
@@ -0,0 +1,19 @@
1package cuchaz.enigma;
2
3public interface ProgressListener {
4 static ProgressListener none() {
5 return new ProgressListener() {
6 @Override
7 public void init(int totalWork, String title) {
8 }
9
10 @Override
11 public void step(int numDone, String message) {
12 }
13 };
14 }
15
16 void init(int totalWork, String title);
17
18 void step(int numDone, String message);
19}
diff --git a/enigma/src/main/java/cuchaz/enigma/analysis/Access.java b/enigma/src/main/java/cuchaz/enigma/analysis/Access.java
new file mode 100644
index 0000000..82ca669
--- /dev/null
+++ b/enigma/src/main/java/cuchaz/enigma/analysis/Access.java
@@ -0,0 +1,43 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 * <p>
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11
12package cuchaz.enigma.analysis;
13
14import cuchaz.enigma.translation.representation.AccessFlags;
15
16import java.lang.reflect.Modifier;
17
18public enum Access {
19
20 PUBLIC, PROTECTED, PACKAGE, PRIVATE;
21
22 public static Access get(AccessFlags flags) {
23 return get(flags.getFlags());
24 }
25
26 public static Access get(int modifiers) {
27 boolean isPublic = Modifier.isPublic(modifiers);
28 boolean isProtected = Modifier.isProtected(modifiers);
29 boolean isPrivate = Modifier.isPrivate(modifiers);
30
31 if (isPublic && !isProtected && !isPrivate) {
32 return PUBLIC;
33 } else if (!isPublic && isProtected && !isPrivate) {
34 return PROTECTED;
35 } else if (!isPublic && !isProtected && isPrivate) {
36 return PRIVATE;
37 } else if (!isPublic && !isProtected && !isPrivate) {
38 return PACKAGE;
39 }
40 // assume public by default
41 return PUBLIC;
42 }
43}
diff --git a/enigma/src/main/java/cuchaz/enigma/analysis/BuiltinPlugin.java b/enigma/src/main/java/cuchaz/enigma/analysis/BuiltinPlugin.java
new file mode 100644
index 0000000..4685b39
--- /dev/null
+++ b/enigma/src/main/java/cuchaz/enigma/analysis/BuiltinPlugin.java
@@ -0,0 +1,156 @@
1package cuchaz.enigma.analysis;
2
3import cuchaz.enigma.Enigma;
4import cuchaz.enigma.api.EnigmaPlugin;
5import cuchaz.enigma.api.EnigmaPluginContext;
6import cuchaz.enigma.api.service.JarIndexerService;
7import cuchaz.enigma.api.service.NameProposalService;
8import cuchaz.enigma.source.DecompilerService;
9import cuchaz.enigma.source.Decompilers;
10import cuchaz.enigma.translation.representation.TypeDescriptor;
11import cuchaz.enigma.translation.representation.entry.ClassEntry;
12import cuchaz.enigma.translation.representation.entry.Entry;
13import cuchaz.enigma.translation.representation.entry.FieldEntry;
14import cuchaz.enigma.utils.Pair;
15import org.objectweb.asm.ClassReader;
16import org.objectweb.asm.ClassVisitor;
17import org.objectweb.asm.FieldVisitor;
18import org.objectweb.asm.MethodVisitor;
19import org.objectweb.asm.Opcodes;
20import org.objectweb.asm.tree.AbstractInsnNode;
21import org.objectweb.asm.tree.FieldInsnNode;
22import org.objectweb.asm.tree.InsnList;
23import org.objectweb.asm.tree.LdcInsnNode;
24import org.objectweb.asm.tree.MethodInsnNode;
25import org.objectweb.asm.tree.MethodNode;
26import org.objectweb.asm.tree.analysis.Analyzer;
27import org.objectweb.asm.tree.analysis.Frame;
28import org.objectweb.asm.tree.analysis.SourceInterpreter;
29import org.objectweb.asm.tree.analysis.SourceValue;
30
31import java.util.ArrayList;
32import java.util.HashMap;
33import java.util.HashSet;
34import java.util.List;
35import java.util.Map;
36import java.util.Optional;
37import java.util.Set;
38
39public final class BuiltinPlugin implements EnigmaPlugin {
40
41 public BuiltinPlugin() {
42 }
43
44 @Override
45 public void init(EnigmaPluginContext ctx) {
46 registerEnumNamingService(ctx);
47 registerDecompilerServices(ctx);
48 }
49
50 private void registerEnumNamingService(EnigmaPluginContext ctx) {
51 final Map<Entry<?>, String> names = new HashMap<>();
52 final EnumFieldNameFindingVisitor visitor = new EnumFieldNameFindingVisitor(names);
53
54 ctx.registerService("enigma:enum_initializer_indexer", JarIndexerService.TYPE, ctx1 -> (classCache, jarIndex) -> classCache.visit(() -> visitor, ClassReader.SKIP_FRAMES));
55 ctx.registerService("enigma:enum_name_proposer", NameProposalService.TYPE, ctx1 -> (obfEntry, remapper) -> Optional.ofNullable(names.get(obfEntry)));
56 }
57
58 private void registerDecompilerServices(EnigmaPluginContext ctx) {
59 ctx.registerService("enigma:procyon", DecompilerService.TYPE, ctx1 -> Decompilers.PROCYON);
60 ctx.registerService("enigma:cfr", DecompilerService.TYPE, ctx1 -> Decompilers.CFR);
61 }
62
63 private static final class EnumFieldNameFindingVisitor extends ClassVisitor {
64
65 private ClassEntry clazz;
66 private String className;
67 private final Map<Entry<?>, String> mappings;
68 private final Set<Pair<String, String>> enumFields = new HashSet<>();
69 private final List<MethodNode> classInits = new ArrayList<>();
70
71 EnumFieldNameFindingVisitor(Map<Entry<?>, String> mappings) {
72 super(Enigma.ASM_VERSION);
73 this.mappings = mappings;
74 }
75
76 @Override
77 public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) {
78 super.visit(version, access, name, signature, superName, interfaces);
79 this.className = name;
80 this.clazz = new ClassEntry(name);
81 this.enumFields.clear();
82 this.classInits.clear();
83 }
84
85 @Override
86 public FieldVisitor visitField(int access, String name, String descriptor, String signature, Object value) {
87 if ((access & Opcodes.ACC_ENUM) != 0) {
88 if (!enumFields.add(new Pair<>(name, descriptor))) {
89 throw new IllegalArgumentException("Found two enum fields with the same name \"" + name + "\" and desc \"" + descriptor + "\"!");
90 }
91 }
92 return super.visitField(access, name, descriptor, signature, value);
93 }
94
95 @Override
96 public MethodVisitor visitMethod(int access, String name, String descriptor, String signature, String[] exceptions) {
97 if ("<clinit>".equals(name)) {
98 MethodNode node = new MethodNode(api, access, name, descriptor, signature, exceptions);
99 classInits.add(node);
100 return node;
101 }
102 return super.visitMethod(access, name, descriptor, signature, exceptions);
103 }
104
105 @Override
106 public void visitEnd() {
107 super.visitEnd();
108 try {
109 collectResults();
110 } catch (Exception ex) {
111 throw new RuntimeException(ex);
112 }
113 }
114
115 private void collectResults() throws Exception {
116 String owner = className;
117 Analyzer<SourceValue> analyzer = new Analyzer<>(new SourceInterpreter());
118
119 for (MethodNode mn : classInits) {
120 Frame<SourceValue>[] frames = analyzer.analyze(className, mn);
121
122 InsnList instrs = mn.instructions;
123 for (int i = 1; i < instrs.size(); i++) {
124 AbstractInsnNode instr1 = instrs.get(i - 1);
125 AbstractInsnNode instr2 = instrs.get(i);
126 String s = null;
127
128 if (instr2.getOpcode() == Opcodes.PUTSTATIC
129 && ((FieldInsnNode) instr2).owner.equals(owner)
130 && enumFields.contains(new Pair<>(((FieldInsnNode) instr2).name, ((FieldInsnNode) instr2).desc))
131 && instr1.getOpcode() == Opcodes.INVOKESPECIAL
132 && "<init>".equals(((MethodInsnNode) instr1).name)) {
133
134 for (int j = 0; j < frames[i - 1].getStackSize(); j++) {
135 SourceValue sv = frames[i - 1].getStack(j);
136 for (AbstractInsnNode ci : sv.insns) {
137 if (ci instanceof LdcInsnNode && ((LdcInsnNode) ci).cst instanceof String) {
138 //if (s == null || !s.equals(((LdcInsnNode) ci).cst)) {
139 if (s == null) {
140 s = (String) (((LdcInsnNode) ci).cst);
141 // stringsFound++;
142 }
143 }
144 }
145 }
146 }
147
148 if (s != null) {
149 mappings.put(new FieldEntry(clazz, ((FieldInsnNode) instr2).name, new TypeDescriptor(((FieldInsnNode) instr2).desc)), s);
150 }
151 // report otherwise?
152 }
153 }
154 }
155 }
156}
diff --git a/enigma/src/main/java/cuchaz/enigma/analysis/ClassCache.java b/enigma/src/main/java/cuchaz/enigma/analysis/ClassCache.java
new file mode 100644
index 0000000..a3d24e3
--- /dev/null
+++ b/enigma/src/main/java/cuchaz/enigma/analysis/ClassCache.java
@@ -0,0 +1,126 @@
1package cuchaz.enigma.analysis;
2
3import com.google.common.cache.Cache;
4import com.google.common.cache.CacheBuilder;
5import com.google.common.collect.ImmutableSet;
6import cuchaz.enigma.ClassProvider;
7import cuchaz.enigma.Enigma;
8import cuchaz.enigma.ProgressListener;
9import cuchaz.enigma.analysis.index.JarIndex;
10import cuchaz.enigma.bytecode.translators.LocalVariableFixVisitor;
11import org.objectweb.asm.ClassReader;
12import org.objectweb.asm.ClassVisitor;
13import org.objectweb.asm.tree.ClassNode;
14
15import javax.annotation.Nullable;
16import java.io.IOException;
17import java.nio.file.FileSystem;
18import java.nio.file.FileSystems;
19import java.nio.file.Files;
20import java.nio.file.Path;
21import java.util.concurrent.ExecutionException;
22import java.util.concurrent.TimeUnit;
23import java.util.function.Supplier;
24
25public final class ClassCache implements AutoCloseable, ClassProvider {
26 private final FileSystem fileSystem;
27 private final ImmutableSet<String> classNames;
28
29 private final Cache<String, ClassNode> nodeCache = CacheBuilder.newBuilder()
30 .maximumSize(128)
31 .expireAfterAccess(1, TimeUnit.MINUTES)
32 .build();
33
34 private ClassCache(FileSystem fileSystem, ImmutableSet<String> classNames) {
35 this.fileSystem = fileSystem;
36 this.classNames = classNames;
37 }
38
39 public static ClassCache of(Path jarPath) throws IOException {
40 FileSystem fileSystem = FileSystems.newFileSystem(jarPath, (ClassLoader) null);
41 ImmutableSet<String> classNames = collectClassNames(fileSystem);
42
43 return new ClassCache(fileSystem, classNames);
44 }
45
46 private static ImmutableSet<String> collectClassNames(FileSystem fileSystem) throws IOException {
47 ImmutableSet.Builder<String> classNames = ImmutableSet.builder();
48 for (Path root : fileSystem.getRootDirectories()) {
49 Files.walk(root).map(Path::toString)
50 .forEach(path -> {
51 if (path.endsWith(".class")) {
52 String name = path.substring(1, path.length() - ".class".length());
53 classNames.add(name);
54 }
55 });
56 }
57
58 return classNames.build();
59 }
60
61 @Nullable
62 @Override
63 public ClassNode getClassNode(String name) {
64 if (!classNames.contains(name)) {
65 return null;
66 }
67
68 try {
69 return nodeCache.get(name, () -> parseNode(name));
70 } catch (ExecutionException e) {
71 throw new RuntimeException(e);
72 }
73 }
74
75 private ClassNode parseNode(String name) throws IOException {
76 ClassReader reader = getReader(name);
77
78 ClassNode node = new ClassNode();
79
80 LocalVariableFixVisitor visitor = new LocalVariableFixVisitor(Enigma.ASM_VERSION, node);
81 reader.accept(visitor, 0);
82
83 return node;
84 }
85
86 private ClassReader getReader(String name) throws IOException {
87 Path path = fileSystem.getPath(name + ".class");
88 byte[] bytes = Files.readAllBytes(path);
89 return new ClassReader(bytes);
90 }
91
92 public int getClassCount() {
93 return classNames.size();
94 }
95
96 public void visit(Supplier<ClassVisitor> visitorSupplier, int readFlags) {
97 for (String className : classNames) {
98 ClassVisitor visitor = visitorSupplier.get();
99
100 ClassNode cached = nodeCache.getIfPresent(className);
101 if (cached != null) {
102 cached.accept(visitor);
103 continue;
104 }
105
106 try {
107 ClassReader reader = getReader(className);
108 reader.accept(visitor, readFlags);
109 } catch (IOException e) {
110 System.out.println("Failed to visit class " + className);
111 e.printStackTrace();
112 }
113 }
114 }
115
116 @Override
117 public void close() throws IOException {
118 this.fileSystem.close();
119 }
120
121 public JarIndex index(ProgressListener progress) {
122 JarIndex index = JarIndex.empty();
123 index.indexJar(this, progress);
124 return index;
125 }
126}
diff --git a/enigma/src/main/java/cuchaz/enigma/analysis/ClassImplementationsTreeNode.java b/enigma/src/main/java/cuchaz/enigma/analysis/ClassImplementationsTreeNode.java
new file mode 100644
index 0000000..0fc44ca
--- /dev/null
+++ b/enigma/src/main/java/cuchaz/enigma/analysis/ClassImplementationsTreeNode.java
@@ -0,0 +1,72 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 * <p>
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11
12package cuchaz.enigma.analysis;
13
14import com.google.common.collect.Lists;
15import cuchaz.enigma.analysis.index.InheritanceIndex;
16import cuchaz.enigma.analysis.index.JarIndex;
17import cuchaz.enigma.translation.Translator;
18import cuchaz.enigma.translation.representation.entry.ClassEntry;
19import cuchaz.enigma.translation.representation.entry.MethodEntry;
20
21import javax.swing.tree.DefaultMutableTreeNode;
22import java.util.Collection;
23import java.util.List;
24
25public class ClassImplementationsTreeNode extends DefaultMutableTreeNode {
26 private final Translator translator;
27 private final ClassEntry entry;
28
29 public ClassImplementationsTreeNode(Translator translator, ClassEntry entry) {
30 this.translator = translator;
31 this.entry = entry;
32 }
33
34 public static ClassImplementationsTreeNode findNode(ClassImplementationsTreeNode node, MethodEntry entry) {
35 // is this the node?
36 if (node.entry.equals(entry.getParent())) {
37 return node;
38 }
39
40 // recurse
41 for (int i = 0; i < node.getChildCount(); i++) {
42 ClassImplementationsTreeNode foundNode = findNode((ClassImplementationsTreeNode) node.getChildAt(i), entry);
43 if (foundNode != null) {
44 return foundNode;
45 }
46 }
47 return null;
48 }
49
50 public ClassEntry getClassEntry() {
51 return this.entry;
52 }
53
54 @Override
55 public String toString() {
56 return translator.translate(entry).toString();
57 }
58
59 public void load(JarIndex index) {
60 // get all method implementations
61 List<ClassImplementationsTreeNode> nodes = Lists.newArrayList();
62 InheritanceIndex inheritanceIndex = index.getInheritanceIndex();
63
64 Collection<ClassEntry> inheritors = inheritanceIndex.getChildren(entry);
65 for (ClassEntry inheritor : inheritors) {
66 nodes.add(new ClassImplementationsTreeNode(translator, inheritor));
67 }
68
69 // add them to this node
70 nodes.forEach(this::add);
71 }
72}
diff --git a/enigma/src/main/java/cuchaz/enigma/analysis/ClassInheritanceTreeNode.java b/enigma/src/main/java/cuchaz/enigma/analysis/ClassInheritanceTreeNode.java
new file mode 100644
index 0000000..7904c5f
--- /dev/null
+++ b/enigma/src/main/java/cuchaz/enigma/analysis/ClassInheritanceTreeNode.java
@@ -0,0 +1,72 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 * <p>
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11
12package cuchaz.enigma.analysis;
13
14import com.google.common.collect.Lists;
15import cuchaz.enigma.analysis.index.InheritanceIndex;
16import cuchaz.enigma.translation.Translator;
17import cuchaz.enigma.translation.representation.entry.ClassEntry;
18
19import javax.swing.tree.DefaultMutableTreeNode;
20import java.util.List;
21
22public class ClassInheritanceTreeNode extends DefaultMutableTreeNode {
23 private final Translator translator;
24 private final ClassEntry obfClassEntry;
25
26 public ClassInheritanceTreeNode(Translator translator, String obfClassName) {
27 this.translator = translator;
28 this.obfClassEntry = new ClassEntry(obfClassName);
29 }
30
31 public static ClassInheritanceTreeNode findNode(ClassInheritanceTreeNode node, ClassEntry entry) {
32 // is this the node?
33 if (node.getObfClassName().equals(entry.getFullName())) {
34 return node;
35 }
36
37 // recurse
38 for (int i = 0; i < node.getChildCount(); i++) {
39 ClassInheritanceTreeNode foundNode = findNode((ClassInheritanceTreeNode) node.getChildAt(i), entry);
40 if (foundNode != null) {
41 return foundNode;
42 }
43 }
44 return null;
45 }
46
47 public String getObfClassName() {
48 return this.obfClassEntry.getFullName();
49 }
50
51 @Override
52 public String toString() {
53 return translator.translate(obfClassEntry).getFullName();
54 }
55
56 public void load(InheritanceIndex ancestries, boolean recurse) {
57 // get all the child nodes
58 List<ClassInheritanceTreeNode> nodes = Lists.newArrayList();
59 for (ClassEntry inheritor : ancestries.getChildren(this.obfClassEntry)) {
60 nodes.add(new ClassInheritanceTreeNode(translator, inheritor.getFullName()));
61 }
62
63 // add them to this node
64 nodes.forEach(this::add);
65
66 if (recurse) {
67 for (ClassInheritanceTreeNode node : nodes) {
68 node.load(ancestries, true);
69 }
70 }
71 }
72}
diff --git a/enigma/src/main/java/cuchaz/enigma/analysis/ClassReferenceTreeNode.java b/enigma/src/main/java/cuchaz/enigma/analysis/ClassReferenceTreeNode.java
new file mode 100644
index 0000000..90d8a6c
--- /dev/null
+++ b/enigma/src/main/java/cuchaz/enigma/analysis/ClassReferenceTreeNode.java
@@ -0,0 +1,94 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 * <p>
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11
12package cuchaz.enigma.analysis;
13
14import com.google.common.collect.Sets;
15import cuchaz.enigma.analysis.index.JarIndex;
16import cuchaz.enigma.analysis.index.ReferenceIndex;
17import cuchaz.enigma.translation.Translator;
18import cuchaz.enigma.translation.representation.entry.ClassEntry;
19import cuchaz.enigma.translation.representation.entry.Entry;
20import cuchaz.enigma.translation.representation.entry.MethodDefEntry;
21
22import javax.swing.tree.DefaultMutableTreeNode;
23import javax.swing.tree.TreeNode;
24import java.util.Set;
25
26public class ClassReferenceTreeNode extends DefaultMutableTreeNode
27 implements ReferenceTreeNode<ClassEntry, MethodDefEntry> {
28
29 private Translator deobfuscatingTranslator;
30 private ClassEntry entry;
31 private EntryReference<ClassEntry, MethodDefEntry> reference;
32
33 public ClassReferenceTreeNode(Translator deobfuscatingTranslator, ClassEntry entry) {
34 this.deobfuscatingTranslator = deobfuscatingTranslator;
35 this.entry = entry;
36 this.reference = null;
37 }
38
39 public ClassReferenceTreeNode(Translator deobfuscatingTranslator, EntryReference<ClassEntry, MethodDefEntry> reference) {
40 this.deobfuscatingTranslator = deobfuscatingTranslator;
41 this.entry = reference.entry;
42 this.reference = reference;
43 }
44
45 @Override
46 public ClassEntry getEntry() {
47 return this.entry;
48 }
49
50 @Override
51 public EntryReference<ClassEntry, MethodDefEntry> getReference() {
52 return this.reference;
53 }
54
55 @Override
56 public String toString() {
57 if (this.reference != null) {
58 return String.format("%s", this.deobfuscatingTranslator.translate(this.reference.context));
59 }
60 return this.deobfuscatingTranslator.translate(this.entry).getFullName();
61 }
62
63 public void load(JarIndex index, boolean recurse) {
64 ReferenceIndex referenceIndex = index.getReferenceIndex();
65
66 // get all the child nodes
67 for (EntryReference<ClassEntry, MethodDefEntry> reference : referenceIndex.getReferencesToClass(this.entry)) {
68 add(new ClassReferenceTreeNode(this.deobfuscatingTranslator, reference));
69 }
70
71 if (recurse && this.children != null) {
72 for (Object child : this.children) {
73 if (child instanceof ClassReferenceTreeNode) {
74 ClassReferenceTreeNode node = (ClassReferenceTreeNode) child;
75
76 // don't recurse into ancestor
77 Set<Entry<?>> ancestors = Sets.newHashSet();
78 TreeNode n = node;
79 while (n.getParent() != null) {
80 n = n.getParent();
81 if (n instanceof ClassReferenceTreeNode) {
82 ancestors.add(((ClassReferenceTreeNode) n).getEntry());
83 }
84 }
85 if (ancestors.contains(node.getEntry())) {
86 continue;
87 }
88
89 node.load(index, true);
90 }
91 }
92 }
93 }
94}
diff --git a/enigma/src/main/java/cuchaz/enigma/analysis/EntryReference.java b/enigma/src/main/java/cuchaz/enigma/analysis/EntryReference.java
new file mode 100644
index 0000000..320f945
--- /dev/null
+++ b/enigma/src/main/java/cuchaz/enigma/analysis/EntryReference.java
@@ -0,0 +1,140 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 * <p>
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11
12package cuchaz.enigma.analysis;
13
14import cuchaz.enigma.translation.Translatable;
15import cuchaz.enigma.translation.Translator;
16import cuchaz.enigma.translation.mapping.EntryMapping;
17import cuchaz.enigma.translation.mapping.EntryResolver;
18import cuchaz.enigma.translation.mapping.EntryMap;
19import cuchaz.enigma.translation.representation.entry.ClassEntry;
20import cuchaz.enigma.translation.representation.entry.Entry;
21import cuchaz.enigma.translation.representation.entry.MethodEntry;
22
23import java.util.Arrays;
24import java.util.List;
25import java.util.Objects;
26
27public class EntryReference<E extends Entry<?>, C extends Entry<?>> implements Translatable {
28
29 private static final List<String> CONSTRUCTOR_NON_NAMES = Arrays.asList("this", "super", "static");
30 public E entry;
31 public C context;
32 public ReferenceTargetType targetType;
33
34 private boolean sourceName;
35
36 public EntryReference(E entry, String sourceName) {
37 this(entry, sourceName, null);
38 }
39
40 public EntryReference(E entry, String sourceName, C context) {
41 this(entry, sourceName, context, ReferenceTargetType.none());
42 }
43
44 public EntryReference(E entry, String sourceName, C context, ReferenceTargetType targetType) {
45 if (entry == null) {
46 throw new IllegalArgumentException("Entry cannot be null!");
47 }
48
49 this.entry = entry;
50 this.context = context;
51 this.targetType = targetType;
52
53 this.sourceName = sourceName != null && !sourceName.isEmpty();
54 if (entry instanceof MethodEntry && ((MethodEntry) entry).isConstructor() && CONSTRUCTOR_NON_NAMES.contains(sourceName)) {
55 this.sourceName = false;
56 }
57 }
58
59 public EntryReference(E entry, C context, EntryReference<E, C> other) {
60 this.entry = entry;
61 this.context = context;
62 this.sourceName = other.sourceName;
63 this.targetType = other.targetType;
64 }
65
66 public ClassEntry getLocationClassEntry() {
67 if (context != null) {
68 return context.getContainingClass();
69 }
70 return entry.getContainingClass();
71 }
72
73 public boolean isNamed() {
74 return this.sourceName;
75 }
76
77 public Entry<?> getNameableEntry() {
78 if (entry instanceof MethodEntry && ((MethodEntry) entry).isConstructor()) {
79 // renaming a constructor really means renaming the class
80 return entry.getContainingClass();
81 }
82 return entry;
83 }
84
85 public String getNameableName() {
86 return getNameableEntry().getName();
87 }
88
89 @Override
90 public int hashCode() {
91 if (context != null) {
92 return Objects.hash(entry.hashCode(), context.hashCode());
93 }
94 return entry.hashCode();
95 }
96
97 @Override
98 public boolean equals(Object other) {
99 return other instanceof EntryReference && equals((EntryReference<?, ?>) other);
100 }
101
102 public boolean equals(EntryReference<?, ?> other) {
103 // check entry first
104 boolean isEntrySame = entry.equals(other.entry);
105 if (!isEntrySame) {
106 return false;
107 }
108
109 // check caller
110 if (context == null && other.context == null) {
111 return true;
112 } else if (context != null && other.context != null) {
113 return context.equals(other.context);
114 }
115 return false;
116 }
117
118 @Override
119 public String toString() {
120 StringBuilder buf = new StringBuilder();
121 buf.append(entry);
122
123 if (context != null) {
124 buf.append(" called from ");
125 buf.append(context);
126 }
127
128 if (targetType != null && targetType.getKind() != ReferenceTargetType.Kind.NONE) {
129 buf.append(" on target of type ");
130 buf.append(targetType);
131 }
132
133 return buf.toString();
134 }
135
136 @Override
137 public Translatable translate(Translator translator, EntryResolver resolver, EntryMap<EntryMapping> mappings) {
138 return new EntryReference<>(translator.translate(entry), translator.translate(context), this);
139 }
140}
diff --git a/enigma/src/main/java/cuchaz/enigma/analysis/FieldReferenceTreeNode.java b/enigma/src/main/java/cuchaz/enigma/analysis/FieldReferenceTreeNode.java
new file mode 100644
index 0000000..4beab7f
--- /dev/null
+++ b/enigma/src/main/java/cuchaz/enigma/analysis/FieldReferenceTreeNode.java
@@ -0,0 +1,83 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 * <p>
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11
12package cuchaz.enigma.analysis;
13
14import cuchaz.enigma.analysis.index.JarIndex;
15import cuchaz.enigma.analysis.index.ReferenceIndex;
16import cuchaz.enigma.translation.Translator;
17import cuchaz.enigma.translation.representation.entry.FieldEntry;
18import cuchaz.enigma.translation.representation.entry.MethodDefEntry;
19import cuchaz.enigma.translation.representation.entry.MethodEntry;
20
21import javax.swing.tree.DefaultMutableTreeNode;
22
23public class FieldReferenceTreeNode extends DefaultMutableTreeNode implements ReferenceTreeNode<FieldEntry, MethodDefEntry> {
24
25 private final Translator translator;
26 private FieldEntry entry;
27 private EntryReference<FieldEntry, MethodDefEntry> reference;
28
29 public FieldReferenceTreeNode(Translator translator, FieldEntry entry) {
30 this.translator = translator;
31 this.entry = entry;
32 this.reference = null;
33 }
34
35 private FieldReferenceTreeNode(Translator translator, EntryReference<FieldEntry, MethodDefEntry> reference) {
36 this.translator = translator;
37 this.entry = reference.entry;
38 this.reference = reference;
39 }
40
41 @Override
42 public FieldEntry getEntry() {
43 return this.entry;
44 }
45
46 @Override
47 public EntryReference<FieldEntry, MethodDefEntry> getReference() {
48 return this.reference;
49 }
50
51 @Override
52 public String toString() {
53 if (this.reference != null) {
54 return String.format("%s", translator.translate(this.reference.context));
55 }
56 return translator.translate(entry).toString();
57 }
58
59 public void load(JarIndex index, boolean recurse) {
60 ReferenceIndex referenceIndex = index.getReferenceIndex();
61
62 // get all the child nodes
63 if (this.reference == null) {
64 for (EntryReference<FieldEntry, MethodDefEntry> reference : referenceIndex.getReferencesToField(this.entry)) {
65 add(new FieldReferenceTreeNode(translator, reference));
66 }
67 } else {
68 for (EntryReference<MethodEntry, MethodDefEntry> reference : referenceIndex.getReferencesToMethod(this.reference.context)) {
69 add(new MethodReferenceTreeNode(translator, reference));
70 }
71 }
72
73 if (recurse && children != null) {
74 for (Object node : children) {
75 if (node instanceof MethodReferenceTreeNode) {
76 ((MethodReferenceTreeNode) node).load(index, true, false);
77 } else if (node instanceof FieldReferenceTreeNode) {
78 ((FieldReferenceTreeNode) node).load(index, true);
79 }
80 }
81 }
82 }
83}
diff --git a/enigma/src/main/java/cuchaz/enigma/analysis/IndexSimpleVerifier.java b/enigma/src/main/java/cuchaz/enigma/analysis/IndexSimpleVerifier.java
new file mode 100644
index 0000000..ec8f323
--- /dev/null
+++ b/enigma/src/main/java/cuchaz/enigma/analysis/IndexSimpleVerifier.java
@@ -0,0 +1,154 @@
1package cuchaz.enigma.analysis;
2
3import cuchaz.enigma.Enigma;
4import cuchaz.enigma.analysis.index.EntryIndex;
5import cuchaz.enigma.analysis.index.InheritanceIndex;
6import cuchaz.enigma.translation.representation.AccessFlags;
7import cuchaz.enigma.translation.representation.entry.ClassDefEntry;
8import cuchaz.enigma.translation.representation.entry.ClassEntry;
9import org.objectweb.asm.Type;
10import org.objectweb.asm.tree.analysis.BasicValue;
11import org.objectweb.asm.tree.analysis.SimpleVerifier;
12
13import java.util.Set;
14
15public class IndexSimpleVerifier extends SimpleVerifier {
16 private static final Type OBJECT_TYPE = Type.getType("Ljava/lang/Object;");
17 private final EntryIndex entryIndex;
18 private final InheritanceIndex inheritanceIndex;
19
20 public IndexSimpleVerifier(EntryIndex entryIndex, InheritanceIndex inheritanceIndex) {
21 super(Enigma.ASM_VERSION, null, null, null, false);
22 this.entryIndex = entryIndex;
23 this.inheritanceIndex = inheritanceIndex;
24 }
25
26 @Override
27 protected boolean isSubTypeOf(BasicValue value, BasicValue expected) {
28 Type expectedType = expected.getType();
29 Type type = value.getType();
30 switch (expectedType.getSort()) {
31 case Type.INT:
32 case Type.FLOAT:
33 case Type.LONG:
34 case Type.DOUBLE:
35 return type.equals(expectedType);
36 case Type.ARRAY:
37 case Type.OBJECT:
38 if (type.equals(NULL_TYPE)) {
39 return true;
40 } else if (type.getSort() == Type.OBJECT || type.getSort() == Type.ARRAY) {
41 if (isAssignableFrom(expectedType, type)) {
42 return true;
43 } else if (isInterface(expectedType)) {
44 return isAssignableFrom(OBJECT_TYPE, type);
45 } else {
46 return false;
47 }
48 } else {
49 return false;
50 }
51 default:
52 throw new AssertionError();
53 }
54 }
55
56 @Override
57 protected boolean isInterface(Type type) {
58 AccessFlags classAccess = entryIndex.getClassAccess(new ClassEntry(type.getInternalName()));
59 if (classAccess != null) {
60 return classAccess.isInterface();
61 }
62
63 Class<?> clazz = getClass(type);
64 if (clazz != null) {
65 return clazz.isInterface();
66 }
67
68 return false;
69 }
70
71 @Override
72 protected Type getSuperClass(Type type) {
73 ClassDefEntry definition = entryIndex.getDefinition(new ClassEntry(type.getInternalName()));
74 if (definition != null) {
75 return Type.getType('L' + definition.getSuperClass().getFullName() + ';');
76 }
77
78 Class<?> clazz = getClass(type);
79 if (clazz != null) {
80 return Type.getType(clazz.getSuperclass());
81 }
82
83 return OBJECT_TYPE;
84 }
85
86 @Override
87 protected boolean isAssignableFrom(Type type1, Type type2) {
88 if (type1.equals(type2)) {
89 return true;
90 }
91
92 if (type2.equals(NULL_TYPE)) {
93 return true;
94 }
95
96 if (type1.getSort() == Type.ARRAY) {
97 return type2.getSort() == Type.ARRAY && isAssignableFrom(Type.getType(type1.getDescriptor().substring(1)), Type.getType(type2.getDescriptor().substring(1)));
98 }
99
100 if (type2.getSort() == Type.ARRAY) {
101 return type1.equals(OBJECT_TYPE);
102 }
103
104 if (type1.getSort() == Type.OBJECT && type2.getSort() == Type.OBJECT) {
105 if (type1.equals(OBJECT_TYPE)) {
106 return true;
107 }
108
109 ClassEntry class1 = new ClassEntry(type1.getInternalName());
110 ClassEntry class2 = new ClassEntry(type2.getInternalName());
111
112 if (entryIndex.hasClass(class1) && entryIndex.hasClass(class2)) {
113 return inheritanceIndex.getAncestors(class2).contains(class1);
114 }
115
116 Class<?> class1Class = getClass(Type.getType('L' + class1.getFullName() + ';'));
117 Class<?> class2Class = getClass(Type.getType('L' + class2.getFullName() + ';'));
118
119 if (class1Class == null) {
120 return true; // missing classes to find out
121 }
122
123 if (class2Class != null) {
124 return class1Class.isAssignableFrom(class2Class);
125 }
126
127 if (entryIndex.hasClass(class2)) {
128 Set<ClassEntry> ancestors = inheritanceIndex.getAncestors(class2);
129
130 for (ClassEntry ancestorEntry : ancestors) {
131 Class<?> ancestor = getClass(Type.getType('L' + ancestorEntry.getFullName() + ';'));
132 if (ancestor == null || class1Class.isAssignableFrom(ancestor)) {
133 return true; // assignable, or missing classes to find out
134 }
135 }
136
137 return false;
138 }
139
140 return true; // missing classes to find out
141 }
142
143 return false;
144 }
145
146 @Override
147 protected final Class<?> getClass(Type type) {
148 try {
149 return Class.forName(type.getSort() == Type.ARRAY ? type.getDescriptor().replace('/', '.') : type.getClassName(), false, null);
150 } catch (ClassNotFoundException e) {
151 return null;
152 }
153 }
154}
diff --git a/enigma/src/main/java/cuchaz/enigma/analysis/IndexTreeBuilder.java b/enigma/src/main/java/cuchaz/enigma/analysis/IndexTreeBuilder.java
new file mode 100644
index 0000000..0c2dfd7
--- /dev/null
+++ b/enigma/src/main/java/cuchaz/enigma/analysis/IndexTreeBuilder.java
@@ -0,0 +1,74 @@
1package cuchaz.enigma.analysis;
2
3import com.google.common.collect.Lists;
4import cuchaz.enigma.analysis.index.JarIndex;
5import cuchaz.enigma.translation.Translator;
6import cuchaz.enigma.translation.mapping.EntryResolver;
7import cuchaz.enigma.translation.mapping.ResolutionStrategy;
8import cuchaz.enigma.translation.representation.entry.ClassEntry;
9import cuchaz.enigma.translation.representation.entry.MethodEntry;
10
11import java.util.Collection;
12import java.util.List;
13
14public class IndexTreeBuilder {
15 private final JarIndex index;
16
17 public IndexTreeBuilder(JarIndex index) {
18 this.index = index;
19 }
20
21 public ClassInheritanceTreeNode buildClassInheritance(Translator translator, ClassEntry obfClassEntry) {
22 // get the root node
23 List<String> ancestry = Lists.newArrayList();
24 ancestry.add(obfClassEntry.getFullName());
25 for (ClassEntry classEntry : index.getInheritanceIndex().getAncestors(obfClassEntry)) {
26 ancestry.add(classEntry.getFullName());
27 }
28
29 ClassInheritanceTreeNode rootNode = new ClassInheritanceTreeNode(translator, ancestry.get(ancestry.size() - 1));
30
31 // expand all children recursively
32 rootNode.load(index.getInheritanceIndex(), true);
33
34 return rootNode;
35 }
36
37 public ClassImplementationsTreeNode buildClassImplementations(Translator translator, ClassEntry obfClassEntry) {
38 if (index.getInheritanceIndex().isParent(obfClassEntry)) {
39 ClassImplementationsTreeNode node = new ClassImplementationsTreeNode(translator, obfClassEntry);
40 node.load(index);
41 return node;
42 }
43 return null;
44 }
45
46 public MethodInheritanceTreeNode buildMethodInheritance(Translator translator, MethodEntry obfMethodEntry) {
47 MethodEntry resolvedEntry = index.getEntryResolver().resolveFirstEntry(obfMethodEntry, ResolutionStrategy.RESOLVE_ROOT);
48
49 // make a root node at the base
50 MethodInheritanceTreeNode rootNode = new MethodInheritanceTreeNode(
51 translator, resolvedEntry,
52 index.getEntryIndex().hasMethod(resolvedEntry)
53 );
54
55 // expand the full tree
56 rootNode.load(index);
57
58 return rootNode;
59 }
60
61 public List<MethodImplementationsTreeNode> buildMethodImplementations(Translator translator, MethodEntry obfMethodEntry) {
62 EntryResolver resolver = index.getEntryResolver();
63 Collection<MethodEntry> resolvedEntries = resolver.resolveEntry(obfMethodEntry, ResolutionStrategy.RESOLVE_ROOT);
64
65 List<MethodImplementationsTreeNode> nodes = Lists.newArrayList();
66 for (MethodEntry resolvedEntry : resolvedEntries) {
67 MethodImplementationsTreeNode node = new MethodImplementationsTreeNode(translator, resolvedEntry);
68 node.load(index);
69 nodes.add(node);
70 }
71
72 return nodes;
73 }
74}
diff --git a/enigma/src/main/java/cuchaz/enigma/analysis/InterpreterPair.java b/enigma/src/main/java/cuchaz/enigma/analysis/InterpreterPair.java
new file mode 100644
index 0000000..fad10bf
--- /dev/null
+++ b/enigma/src/main/java/cuchaz/enigma/analysis/InterpreterPair.java
@@ -0,0 +1,130 @@
1package cuchaz.enigma.analysis;
2
3import cuchaz.enigma.Enigma;
4import org.objectweb.asm.Type;
5import org.objectweb.asm.tree.AbstractInsnNode;
6import org.objectweb.asm.tree.analysis.AnalyzerException;
7import org.objectweb.asm.tree.analysis.Interpreter;
8import org.objectweb.asm.tree.analysis.Value;
9
10import java.util.List;
11import java.util.Objects;
12import java.util.stream.Collectors;
13
14public class InterpreterPair<V extends Value, W extends Value> extends Interpreter<InterpreterPair.PairValue<V, W>> {
15 private final Interpreter<V> left;
16 private final Interpreter<W> right;
17
18 public InterpreterPair(Interpreter<V> left, Interpreter<W> right) {
19 super(Enigma.ASM_VERSION);
20 this.left = left;
21 this.right = right;
22 }
23
24 @Override
25 public PairValue<V, W> newValue(Type type) {
26 return pair(
27 left.newValue(type),
28 right.newValue(type)
29 );
30 }
31
32 @Override
33 public PairValue<V, W> newOperation(AbstractInsnNode insn) throws AnalyzerException {
34 return pair(
35 left.newOperation(insn),
36 right.newOperation(insn)
37 );
38 }
39
40 @Override
41 public PairValue<V, W> copyOperation(AbstractInsnNode insn, PairValue<V, W> value) throws AnalyzerException {
42 return pair(
43 left.copyOperation(insn, value.left),
44 right.copyOperation(insn, value.right)
45 );
46 }
47
48 @Override
49 public PairValue<V, W> unaryOperation(AbstractInsnNode insn, PairValue<V, W> value) throws AnalyzerException {
50 return pair(
51 left.unaryOperation(insn, value.left),
52 right.unaryOperation(insn, value.right)
53 );
54 }
55
56 @Override
57 public PairValue<V, W> binaryOperation(AbstractInsnNode insn, PairValue<V, W> value1, PairValue<V, W> value2) throws AnalyzerException {
58 return pair(
59 left.binaryOperation(insn, value1.left, value2.left),
60 right.binaryOperation(insn, value1.right, value2.right)
61 );
62 }
63
64 @Override
65 public PairValue<V, W> ternaryOperation(AbstractInsnNode insn, PairValue<V, W> value1, PairValue<V, W> value2, PairValue<V, W> value3) throws AnalyzerException {
66 return pair(
67 left.ternaryOperation(insn, value1.left, value2.left, value3.left),
68 right.ternaryOperation(insn, value1.right, value2.right, value3.right)
69 );
70 }
71
72 @Override
73 public PairValue<V, W> naryOperation(AbstractInsnNode insn, List<? extends PairValue<V, W>> values) throws AnalyzerException {
74 return pair(
75 left.naryOperation(insn, values.stream().map(v -> v.left).collect(Collectors.toList())),
76 right.naryOperation(insn, values.stream().map(v -> v.right).collect(Collectors.toList()))
77 );
78 }
79
80 @Override
81 public void returnOperation(AbstractInsnNode insn, PairValue<V, W> value, PairValue<V, W> expected) throws AnalyzerException {
82 left.returnOperation(insn, value.left, expected.left);
83 right.returnOperation(insn, value.right, expected.right);
84 }
85
86 @Override
87 public PairValue<V, W> merge(PairValue<V, W> value1, PairValue<V, W> value2) {
88 return pair(
89 left.merge(value1.left, value2.left),
90 right.merge(value1.right, value2.right)
91 );
92 }
93
94 private PairValue<V, W> pair(V left, W right) {
95 if (left == null && right == null) {
96 return null;
97 }
98
99 return new PairValue<>(left, right);
100 }
101
102 public static final class PairValue<V extends Value, W extends Value> implements Value {
103 public final V left;
104 public final W right;
105
106 public PairValue(V left, W right) {
107 if (left == null && right == null) {
108 throw new IllegalArgumentException("should use null rather than pair of nulls");
109 }
110
111 this.left = left;
112 this.right = right;
113 }
114
115 @Override
116 public boolean equals(Object o) {
117 return o instanceof InterpreterPair.PairValue && Objects.equals(left, ((PairValue) o).left) && Objects.equals(right, ((PairValue) o).right);
118 }
119
120 @Override
121 public int hashCode() {
122 return left.hashCode() * 31 + right.hashCode();
123 }
124
125 @Override
126 public int getSize() {
127 return (left == null ? right : left).getSize();
128 }
129 }
130}
diff --git a/enigma/src/main/java/cuchaz/enigma/analysis/MethodImplementationsTreeNode.java b/enigma/src/main/java/cuchaz/enigma/analysis/MethodImplementationsTreeNode.java
new file mode 100644
index 0000000..b09f7ac
--- /dev/null
+++ b/enigma/src/main/java/cuchaz/enigma/analysis/MethodImplementationsTreeNode.java
@@ -0,0 +1,85 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 * <p>
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11
12package cuchaz.enigma.analysis;
13
14import com.google.common.collect.Lists;
15import cuchaz.enigma.analysis.index.EntryIndex;
16import cuchaz.enigma.analysis.index.InheritanceIndex;
17import cuchaz.enigma.analysis.index.JarIndex;
18import cuchaz.enigma.translation.Translator;
19import cuchaz.enigma.translation.representation.entry.ClassEntry;
20import cuchaz.enigma.translation.representation.entry.MethodEntry;
21
22import javax.swing.tree.DefaultMutableTreeNode;
23import java.util.Collection;
24import java.util.List;
25
26public class MethodImplementationsTreeNode extends DefaultMutableTreeNode {
27
28 private final Translator translator;
29 private MethodEntry entry;
30
31 public MethodImplementationsTreeNode(Translator translator, MethodEntry entry) {
32 this.translator = translator;
33 if (entry == null) {
34 throw new IllegalArgumentException("Entry cannot be null!");
35 }
36
37 this.entry = entry;
38 }
39
40 public static MethodImplementationsTreeNode findNode(MethodImplementationsTreeNode node, MethodEntry entry) {
41 // is this the node?
42 if (node.getMethodEntry().equals(entry)) {
43 return node;
44 }
45
46 // recurse
47 for (int i = 0; i < node.getChildCount(); i++) {
48 MethodImplementationsTreeNode foundNode = findNode((MethodImplementationsTreeNode) node.getChildAt(i), entry);
49 if (foundNode != null) {
50 return foundNode;
51 }
52 }
53 return null;
54 }
55
56 public MethodEntry getMethodEntry() {
57 return this.entry;
58 }
59
60 @Override
61 public String toString() {
62 MethodEntry translatedEntry = translator.translate(entry);
63 String className = translatedEntry.getParent().getFullName();
64 String methodName = translatedEntry.getName();
65 return className + "." + methodName + "()";
66 }
67
68 public void load(JarIndex index) {
69 // get all method implementations
70 List<MethodImplementationsTreeNode> nodes = Lists.newArrayList();
71 EntryIndex entryIndex = index.getEntryIndex();
72 InheritanceIndex inheritanceIndex = index.getInheritanceIndex();
73
74 Collection<ClassEntry> descendants = inheritanceIndex.getDescendants(entry.getParent());
75 for (ClassEntry inheritor : descendants) {
76 MethodEntry methodEntry = entry.withParent(inheritor);
77 if (entryIndex.hasMethod(methodEntry)) {
78 nodes.add(new MethodImplementationsTreeNode(translator, methodEntry));
79 }
80 }
81
82 // add them to this node
83 nodes.forEach(this::add);
84 }
85}
diff --git a/enigma/src/main/java/cuchaz/enigma/analysis/MethodInheritanceTreeNode.java b/enigma/src/main/java/cuchaz/enigma/analysis/MethodInheritanceTreeNode.java
new file mode 100644
index 0000000..e77b5cc
--- /dev/null
+++ b/enigma/src/main/java/cuchaz/enigma/analysis/MethodInheritanceTreeNode.java
@@ -0,0 +1,95 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 * <p>
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11
12package cuchaz.enigma.analysis;
13
14import cuchaz.enigma.analysis.index.EntryIndex;
15import cuchaz.enigma.analysis.index.InheritanceIndex;
16import cuchaz.enigma.analysis.index.JarIndex;
17import cuchaz.enigma.translation.Translator;
18import cuchaz.enigma.translation.representation.entry.ClassEntry;
19import cuchaz.enigma.translation.representation.entry.MethodEntry;
20
21import javax.swing.tree.DefaultMutableTreeNode;
22
23public class MethodInheritanceTreeNode extends DefaultMutableTreeNode {
24
25 private final Translator translator;
26 private MethodEntry entry;
27 private boolean implemented;
28
29 public MethodInheritanceTreeNode(Translator translator, MethodEntry entry, boolean implemented) {
30 this.translator = translator;
31 this.entry = entry;
32 this.implemented = implemented;
33 }
34
35 public static MethodInheritanceTreeNode findNode(MethodInheritanceTreeNode node, MethodEntry entry) {
36 // is this the node?
37 if (node.getMethodEntry().equals(entry)) {
38 return node;
39 }
40
41 // recurse
42 for (int i = 0; i < node.getChildCount(); i++) {
43 MethodInheritanceTreeNode foundNode = findNode((MethodInheritanceTreeNode) node.getChildAt(i), entry);
44 if (foundNode != null) {
45 return foundNode;
46 }
47 }
48 return null;
49 }
50
51 public MethodEntry getMethodEntry() {
52 return this.entry;
53 }
54
55 public boolean isImplemented() {
56 return this.implemented;
57 }
58
59 @Override
60 public String toString() {
61 MethodEntry translatedEntry = translator.translate(entry);
62 String className = translatedEntry.getContainingClass().getFullName();
63
64 if (!this.implemented) {
65 return className;
66 } else {
67 String methodName = translatedEntry.getName();
68 return className + "." + methodName + "()";
69 }
70 }
71
72 /**
73 * Returns true if there is sub-node worthy to display.
74 */
75 public boolean load(JarIndex index) {
76 // get all the child nodes
77 EntryIndex entryIndex = index.getEntryIndex();
78 InheritanceIndex inheritanceIndex = index.getInheritanceIndex();
79
80 boolean ret = false;
81 for (ClassEntry inheritorEntry : inheritanceIndex.getChildren(this.entry.getParent())) {
82 MethodEntry methodEntry = new MethodEntry(inheritorEntry, this.entry.getName(), this.entry.getDesc());
83
84 MethodInheritanceTreeNode node = new MethodInheritanceTreeNode(translator, methodEntry, entryIndex.hasMethod(methodEntry));
85 boolean childOverride = node.load(index);
86
87 if (childOverride || node.implemented) {
88 this.add(node);
89 ret = true;
90 }
91 }
92
93 return ret;
94 }
95}
diff --git a/enigma/src/main/java/cuchaz/enigma/analysis/MethodNodeWithAction.java b/enigma/src/main/java/cuchaz/enigma/analysis/MethodNodeWithAction.java
new file mode 100644
index 0000000..8117103
--- /dev/null
+++ b/enigma/src/main/java/cuchaz/enigma/analysis/MethodNodeWithAction.java
@@ -0,0 +1,19 @@
1package cuchaz.enigma.analysis;
2
3import org.objectweb.asm.tree.MethodNode;
4
5import java.util.function.Consumer;
6
7public class MethodNodeWithAction extends MethodNode {
8 private final Consumer<MethodNode> action;
9
10 public MethodNodeWithAction(int api, int access, String name, String descriptor, String signature, String[] exceptions, Consumer<MethodNode> action) {
11 super(api, access, name, descriptor, signature, exceptions);
12 this.action = action;
13 }
14
15 @Override
16 public void visitEnd() {
17 action.accept(this);
18 }
19}
diff --git a/enigma/src/main/java/cuchaz/enigma/analysis/MethodReferenceTreeNode.java b/enigma/src/main/java/cuchaz/enigma/analysis/MethodReferenceTreeNode.java
new file mode 100644
index 0000000..8995eb5
--- /dev/null
+++ b/enigma/src/main/java/cuchaz/enigma/analysis/MethodReferenceTreeNode.java
@@ -0,0 +1,113 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 * <p>
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11
12package cuchaz.enigma.analysis;
13
14import com.google.common.collect.Sets;
15import cuchaz.enigma.analysis.index.JarIndex;
16import cuchaz.enigma.analysis.index.ReferenceIndex;
17import cuchaz.enigma.translation.Translator;
18import cuchaz.enigma.translation.mapping.EntryResolver;
19import cuchaz.enigma.translation.representation.entry.Entry;
20import cuchaz.enigma.translation.representation.entry.MethodDefEntry;
21import cuchaz.enigma.translation.representation.entry.MethodEntry;
22
23import javax.swing.tree.DefaultMutableTreeNode;
24import javax.swing.tree.TreeNode;
25import java.util.ArrayList;
26import java.util.Collection;
27import java.util.Set;
28
29public class MethodReferenceTreeNode extends DefaultMutableTreeNode implements ReferenceTreeNode<MethodEntry, MethodDefEntry> {
30
31 private final Translator translator;
32 private MethodEntry entry;
33 private EntryReference<MethodEntry, MethodDefEntry> reference;
34
35 public MethodReferenceTreeNode(Translator translator, MethodEntry entry) {
36 this.translator = translator;
37 this.entry = entry;
38 this.reference = null;
39 }
40
41 public MethodReferenceTreeNode(Translator translator, EntryReference<MethodEntry, MethodDefEntry> reference) {
42 this.translator = translator;
43 this.entry = reference.entry;
44 this.reference = reference;
45 }
46
47 @Override
48 public MethodEntry getEntry() {
49 return this.entry;
50 }
51
52 @Override
53 public EntryReference<MethodEntry, MethodDefEntry> getReference() {
54 return this.reference;
55 }
56
57 @Override
58 public String toString() {
59 if (this.reference != null) {
60 return String.format("%s", translator.translate(this.reference.context));
61 }
62 return translator.translate(this.entry).getName();
63 }
64
65 public void load(JarIndex index, boolean recurse, boolean recurseMethod) {
66 // get all the child nodes
67 Collection<EntryReference<MethodEntry, MethodDefEntry>> references = getReferences(index, recurseMethod);
68
69 for (EntryReference<MethodEntry, MethodDefEntry> reference : references) {
70 add(new MethodReferenceTreeNode(translator, reference));
71 }
72
73 if (recurse && this.children != null) {
74 for (Object child : this.children) {
75 if (child instanceof MethodReferenceTreeNode) {
76 MethodReferenceTreeNode node = (MethodReferenceTreeNode) child;
77
78 // don't recurse into ancestor
79 Set<Entry<?>> ancestors = Sets.newHashSet();
80 TreeNode n = node;
81 while (n.getParent() != null) {
82 n = n.getParent();
83 if (n instanceof MethodReferenceTreeNode) {
84 ancestors.add(((MethodReferenceTreeNode) n).getEntry());
85 }
86 }
87 if (ancestors.contains(node.getEntry())) {
88 continue;
89 }
90
91 node.load(index, true, false);
92 }
93 }
94 }
95 }
96
97 private Collection<EntryReference<MethodEntry, MethodDefEntry>> getReferences(JarIndex index, boolean recurseMethod) {
98 ReferenceIndex referenceIndex = index.getReferenceIndex();
99
100 if (recurseMethod) {
101 Collection<EntryReference<MethodEntry, MethodDefEntry>> references = new ArrayList<>();
102
103 EntryResolver entryResolver = index.getEntryResolver();
104 for (MethodEntry methodEntry : entryResolver.resolveEquivalentMethods(entry)) {
105 references.addAll(referenceIndex.getReferencesToMethod(methodEntry));
106 }
107
108 return references;
109 } else {
110 return referenceIndex.getReferencesToMethod(entry);
111 }
112 }
113}
diff --git a/enigma/src/main/java/cuchaz/enigma/analysis/ReferenceTargetType.java b/enigma/src/main/java/cuchaz/enigma/analysis/ReferenceTargetType.java
new file mode 100644
index 0000000..5b19d18
--- /dev/null
+++ b/enigma/src/main/java/cuchaz/enigma/analysis/ReferenceTargetType.java
@@ -0,0 +1,74 @@
1package cuchaz.enigma.analysis;
2
3import cuchaz.enigma.translation.representation.entry.ClassEntry;
4
5public abstract class ReferenceTargetType {
6 private static final None NONE = new None();
7 private static final Uninitialized UNINITIALIZED = new Uninitialized();
8
9 public abstract Kind getKind();
10
11 public static None none() {
12 return NONE;
13 }
14
15 public static Uninitialized uninitialized() {
16 return UNINITIALIZED;
17 }
18
19 public static ClassType classType(ClassEntry name) {
20 return new ClassType(name);
21 }
22
23 public enum Kind {
24 NONE,
25 UNINITIALIZED,
26 CLASS_TYPE
27 }
28
29 public static class None extends ReferenceTargetType {
30 @Override
31 public Kind getKind() {
32 return Kind.NONE;
33 }
34
35 @Override
36 public String toString() {
37 return "(none)";
38 }
39 }
40
41 public static class Uninitialized extends ReferenceTargetType {
42 @Override
43 public Kind getKind() {
44 return Kind.UNINITIALIZED;
45 }
46
47 @Override
48 public String toString() {
49 return "(uninitialized)";
50 }
51 }
52
53 public static class ClassType extends ReferenceTargetType {
54 private final ClassEntry entry;
55
56 private ClassType(ClassEntry entry) {
57 this.entry = entry;
58 }
59
60 public ClassEntry getEntry() {
61 return entry;
62 }
63
64 @Override
65 public Kind getKind() {
66 return Kind.CLASS_TYPE;
67 }
68
69 @Override
70 public String toString() {
71 return entry.toString();
72 }
73 }
74}
diff --git a/enigma/src/main/java/cuchaz/enigma/analysis/ReferenceTreeNode.java b/enigma/src/main/java/cuchaz/enigma/analysis/ReferenceTreeNode.java
new file mode 100644
index 0000000..c0a3a75
--- /dev/null
+++ b/enigma/src/main/java/cuchaz/enigma/analysis/ReferenceTreeNode.java
@@ -0,0 +1,20 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 * <p>
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11
12package cuchaz.enigma.analysis;
13
14import cuchaz.enigma.translation.representation.entry.Entry;
15
16public interface ReferenceTreeNode<E extends Entry<?>, C extends Entry<?>> {
17 E getEntry();
18
19 EntryReference<E, C> getReference();
20}
diff --git a/enigma/src/main/java/cuchaz/enigma/analysis/index/BridgeMethodIndex.java b/enigma/src/main/java/cuchaz/enigma/analysis/index/BridgeMethodIndex.java
new file mode 100644
index 0000000..a4b1aac
--- /dev/null
+++ b/enigma/src/main/java/cuchaz/enigma/analysis/index/BridgeMethodIndex.java
@@ -0,0 +1,156 @@
1package cuchaz.enigma.analysis.index;
2
3import com.google.common.collect.Maps;
4import cuchaz.enigma.translation.representation.AccessFlags;
5import cuchaz.enigma.translation.representation.MethodDescriptor;
6import cuchaz.enigma.translation.representation.TypeDescriptor;
7import cuchaz.enigma.translation.representation.entry.ClassEntry;
8import cuchaz.enigma.translation.representation.entry.MethodDefEntry;
9import cuchaz.enigma.translation.representation.entry.MethodEntry;
10
11import javax.annotation.Nullable;
12import java.util.*;
13
14public class BridgeMethodIndex implements JarIndexer {
15 private final EntryIndex entryIndex;
16 private final InheritanceIndex inheritanceIndex;
17 private final ReferenceIndex referenceIndex;
18
19 private final Map<MethodEntry, MethodEntry> bridgeToSpecialized = Maps.newHashMap();
20 private final Map<MethodEntry, MethodEntry> specializedToBridge = Maps.newHashMap();
21
22 public BridgeMethodIndex(EntryIndex entryIndex, InheritanceIndex inheritanceIndex, ReferenceIndex referenceIndex) {
23 this.entryIndex = entryIndex;
24 this.inheritanceIndex = inheritanceIndex;
25 this.referenceIndex = referenceIndex;
26 }
27
28 public void findBridgeMethods() {
29 // look for access and bridged methods
30 for (MethodEntry methodEntry : entryIndex.getMethods()) {
31 MethodDefEntry methodDefEntry = (MethodDefEntry) methodEntry;
32
33 AccessFlags access = methodDefEntry.getAccess();
34 if (access == null || !access.isSynthetic()) {
35 continue;
36 }
37
38 indexSyntheticMethod(methodDefEntry, access);
39 }
40 }
41
42 @Override
43 public void processIndex(JarIndex index) {
44 Map<MethodEntry, MethodEntry> copiedAccessToBridge = new HashMap<>(specializedToBridge);
45
46 for (Map.Entry<MethodEntry, MethodEntry> entry : copiedAccessToBridge.entrySet()) {
47 MethodEntry specializedEntry = entry.getKey();
48 MethodEntry bridgeEntry = entry.getValue();
49 if (bridgeEntry.getName().equals(specializedEntry.getName())) {
50 continue;
51 }
52
53 MethodEntry renamedSpecializedEntry = specializedEntry.withName(bridgeEntry.getName());
54 specializedToBridge.put(renamedSpecializedEntry, specializedToBridge.get(specializedEntry));
55 }
56 }
57
58 private void indexSyntheticMethod(MethodDefEntry syntheticMethod, AccessFlags access) {
59 MethodEntry specializedMethod = findSpecializedMethod(syntheticMethod);
60 if (specializedMethod == null) {
61 return;
62 }
63
64 if (access.isBridge() || isPotentialBridge(syntheticMethod, specializedMethod)) {
65 bridgeToSpecialized.put(syntheticMethod, specializedMethod);
66 specializedToBridge.put(specializedMethod, syntheticMethod);
67 }
68 }
69
70 private MethodEntry findSpecializedMethod(MethodEntry method) {
71 // we want to find all compiler-added methods that directly call another with no processing
72
73 // get all the methods that we call
74 final Collection<MethodEntry> referencedMethods = referenceIndex.getMethodsReferencedBy(method);
75
76 // is there just one?
77 if (referencedMethods.size() != 1) {
78 return null;
79 }
80
81 return referencedMethods.stream().findFirst().orElse(null);
82 }
83
84 private boolean isPotentialBridge(MethodDefEntry bridgeMethod, MethodEntry specializedMethod) {
85 // Bridge methods only exist for inheritance purposes, if we're private, final, or static, we cannot be inherited
86 AccessFlags bridgeAccess = bridgeMethod.getAccess();
87 if (bridgeAccess.isPrivate() || bridgeAccess.isFinal() || bridgeAccess.isStatic()) {
88 return false;
89 }
90
91 MethodDescriptor bridgeDesc = bridgeMethod.getDesc();
92 MethodDescriptor specializedDesc = specializedMethod.getDesc();
93 List<TypeDescriptor> bridgeArguments = bridgeDesc.getArgumentDescs();
94 List<TypeDescriptor> specializedArguments = specializedDesc.getArgumentDescs();
95
96 // A bridge method will always have the same number of arguments
97 if (bridgeArguments.size() != specializedArguments.size()) {
98 return false;
99 }
100
101 // Check that all argument types are bridge-compatible
102 for (int i = 0; i < bridgeArguments.size(); i++) {
103 if (!areTypesBridgeCompatible(bridgeArguments.get(i), specializedArguments.get(i))) {
104 return false;
105 }
106 }
107
108 // Check that the return type is bridge-compatible
109 return areTypesBridgeCompatible(bridgeDesc.getReturnDesc(), specializedDesc.getReturnDesc());
110 }
111
112 private boolean areTypesBridgeCompatible(TypeDescriptor bridgeDesc, TypeDescriptor specializedDesc) {
113 if (bridgeDesc.equals(specializedDesc)) {
114 return true;
115 }
116
117 // Either the descs will be equal, or they are both types and different through a generic
118 if (bridgeDesc.isType() && specializedDesc.isType()) {
119 ClassEntry bridgeType = bridgeDesc.getTypeEntry();
120 ClassEntry accessedType = specializedDesc.getTypeEntry();
121
122 // If the given types are completely unrelated to each other, this can't be bridge compatible
123 InheritanceIndex.Relation relation = inheritanceIndex.computeClassRelation(accessedType, bridgeType);
124 return relation != InheritanceIndex.Relation.UNRELATED;
125 }
126
127 return false;
128 }
129
130 public boolean isBridgeMethod(MethodEntry entry) {
131 return bridgeToSpecialized.containsKey(entry);
132 }
133
134 public boolean isSpecializedMethod(MethodEntry entry) {
135 return specializedToBridge.containsKey(entry);
136 }
137
138 @Nullable
139 public MethodEntry getBridgeFromSpecialized(MethodEntry specialized) {
140 return specializedToBridge.get(specialized);
141 }
142
143 public MethodEntry getSpecializedFromBridge(MethodEntry bridge) {
144 return bridgeToSpecialized.get(bridge);
145 }
146
147 /** Includes "renamed specialized -> bridge" entries. */
148 public Map<MethodEntry, MethodEntry> getSpecializedToBridge() {
149 return Collections.unmodifiableMap(specializedToBridge);
150 }
151
152 /** Only "bridge -> original name" entries. **/
153 public Map<MethodEntry, MethodEntry> getBridgeToSpecialized() {
154 return Collections.unmodifiableMap(bridgeToSpecialized);
155 }
156}
diff --git a/enigma/src/main/java/cuchaz/enigma/analysis/index/EntryIndex.java b/enigma/src/main/java/cuchaz/enigma/analysis/index/EntryIndex.java
new file mode 100644
index 0000000..9a2726e
--- /dev/null
+++ b/enigma/src/main/java/cuchaz/enigma/analysis/index/EntryIndex.java
@@ -0,0 +1,102 @@
1package cuchaz.enigma.analysis.index;
2
3import cuchaz.enigma.translation.representation.AccessFlags;
4import cuchaz.enigma.translation.representation.entry.*;
5
6import javax.annotation.Nullable;
7import java.util.Collection;
8import java.util.HashMap;
9import java.util.Map;
10
11public class EntryIndex implements JarIndexer {
12 private Map<ClassEntry, AccessFlags> classes = new HashMap<>();
13 private Map<FieldEntry, AccessFlags> fields = new HashMap<>();
14 private Map<MethodEntry, AccessFlags> methods = new HashMap<>();
15 private Map<ClassEntry, ClassDefEntry> definitions = new HashMap<>();
16
17 @Override
18 public void indexClass(ClassDefEntry classEntry) {
19 definitions.put(classEntry, classEntry);
20 classes.put(classEntry, classEntry.getAccess());
21 }
22
23 @Override
24 public void indexMethod(MethodDefEntry methodEntry) {
25 methods.put(methodEntry, methodEntry.getAccess());
26 }
27
28 @Override
29 public void indexField(FieldDefEntry fieldEntry) {
30 fields.put(fieldEntry, fieldEntry.getAccess());
31 }
32
33 public boolean hasClass(ClassEntry entry) {
34 return classes.containsKey(entry);
35 }
36
37 public boolean hasMethod(MethodEntry entry) {
38 return methods.containsKey(entry);
39 }
40
41 public boolean hasField(FieldEntry entry) {
42 return fields.containsKey(entry);
43 }
44
45 public boolean hasEntry(Entry<?> entry) {
46 if (entry instanceof ClassEntry) {
47 return hasClass((ClassEntry) entry);
48 } else if (entry instanceof MethodEntry) {
49 return hasMethod((MethodEntry) entry);
50 } else if (entry instanceof FieldEntry) {
51 return hasField((FieldEntry) entry);
52 } else if (entry instanceof LocalVariableEntry) {
53 return hasMethod(((LocalVariableEntry) entry).getParent());
54 }
55
56 return false;
57 }
58
59 @Nullable
60 public AccessFlags getMethodAccess(MethodEntry entry) {
61 return methods.get(entry);
62 }
63
64 @Nullable
65 public AccessFlags getFieldAccess(FieldEntry entry) {
66 return fields.get(entry);
67 }
68
69 @Nullable
70 public AccessFlags getClassAccess(ClassEntry entry) {
71 return classes.get(entry);
72 }
73
74 @Nullable
75 public AccessFlags getEntryAccess(Entry<?> entry) {
76 if (entry instanceof MethodEntry) {
77 return getMethodAccess((MethodEntry) entry);
78 } else if (entry instanceof FieldEntry) {
79 return getFieldAccess((FieldEntry) entry);
80 } else if (entry instanceof LocalVariableEntry) {
81 return getMethodAccess(((LocalVariableEntry) entry).getParent());
82 }
83
84 return null;
85 }
86
87 public ClassDefEntry getDefinition(ClassEntry entry) {
88 return definitions.get(entry);
89 }
90
91 public Collection<ClassEntry> getClasses() {
92 return classes.keySet();
93 }
94
95 public Collection<MethodEntry> getMethods() {
96 return methods.keySet();
97 }
98
99 public Collection<FieldEntry> getFields() {
100 return fields.keySet();
101 }
102}
diff --git a/enigma/src/main/java/cuchaz/enigma/analysis/index/IndexClassVisitor.java b/enigma/src/main/java/cuchaz/enigma/analysis/index/IndexClassVisitor.java
new file mode 100644
index 0000000..f9cb23c
--- /dev/null
+++ b/enigma/src/main/java/cuchaz/enigma/analysis/index/IndexClassVisitor.java
@@ -0,0 +1,40 @@
1package cuchaz.enigma.analysis.index;
2
3import cuchaz.enigma.translation.representation.entry.ClassDefEntry;
4import cuchaz.enigma.translation.representation.entry.FieldDefEntry;
5import cuchaz.enigma.translation.representation.entry.MethodDefEntry;
6import org.objectweb.asm.ClassVisitor;
7import org.objectweb.asm.FieldVisitor;
8import org.objectweb.asm.MethodVisitor;
9
10public class IndexClassVisitor extends ClassVisitor {
11 private final JarIndexer indexer;
12 private ClassDefEntry classEntry;
13
14 public IndexClassVisitor(JarIndex indexer, int api) {
15 super(api);
16 this.indexer = indexer;
17 }
18
19 @Override
20 public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) {
21 classEntry = ClassDefEntry.parse(access, name, signature, superName, interfaces);
22 indexer.indexClass(classEntry);
23
24 super.visit(version, access, name, signature, superName, interfaces);
25 }
26
27 @Override
28 public FieldVisitor visitField(int access, String name, String desc, String signature, Object value) {
29 indexer.indexField(FieldDefEntry.parse(classEntry, access, name, desc, signature));
30
31 return super.visitField(access, name, desc, signature, value);
32 }
33
34 @Override
35 public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) {
36 indexer.indexMethod(MethodDefEntry.parse(classEntry, access, name, desc, signature));
37
38 return super.visitMethod(access, name, desc, signature, exceptions);
39 }
40}
diff --git a/enigma/src/main/java/cuchaz/enigma/analysis/index/IndexReferenceVisitor.java b/enigma/src/main/java/cuchaz/enigma/analysis/index/IndexReferenceVisitor.java
new file mode 100644
index 0000000..f3d419e
--- /dev/null
+++ b/enigma/src/main/java/cuchaz/enigma/analysis/index/IndexReferenceVisitor.java
@@ -0,0 +1,180 @@
1package cuchaz.enigma.analysis.index;
2
3import cuchaz.enigma.analysis.IndexSimpleVerifier;
4import cuchaz.enigma.analysis.InterpreterPair;
5import cuchaz.enigma.analysis.MethodNodeWithAction;
6import cuchaz.enigma.analysis.ReferenceTargetType;
7import cuchaz.enigma.translation.representation.AccessFlags;
8import cuchaz.enigma.translation.representation.Lambda;
9import cuchaz.enigma.translation.representation.MethodDescriptor;
10import cuchaz.enigma.translation.representation.Signature;
11import cuchaz.enigma.translation.representation.entry.*;
12import org.objectweb.asm.*;
13import org.objectweb.asm.tree.AbstractInsnNode;
14import org.objectweb.asm.tree.FieldInsnNode;
15import org.objectweb.asm.tree.InvokeDynamicInsnNode;
16import org.objectweb.asm.tree.MethodInsnNode;
17import org.objectweb.asm.tree.analysis.*;
18
19import java.util.List;
20import java.util.stream.Collectors;
21
22public class IndexReferenceVisitor extends ClassVisitor {
23 private final JarIndexer indexer;
24 private final EntryIndex entryIndex;
25 private final InheritanceIndex inheritanceIndex;
26 private ClassEntry classEntry;
27 private String className;
28
29 public IndexReferenceVisitor(JarIndexer indexer, EntryIndex entryIndex, InheritanceIndex inheritanceIndex, int api) {
30 super(api);
31 this.indexer = indexer;
32 this.entryIndex = entryIndex;
33 this.inheritanceIndex = inheritanceIndex;
34 }
35
36 @Override
37 public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) {
38 classEntry = new ClassEntry(name);
39 className = name;
40 }
41
42 @Override
43 public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) {
44 MethodDefEntry entry = new MethodDefEntry(classEntry, name, new MethodDescriptor(desc), Signature.createSignature(signature), new AccessFlags(access));
45 return new MethodNodeWithAction(api, access, name, desc, signature, exceptions, methodNode -> {
46 try {
47 new Analyzer<>(new MethodInterpreter(entry, indexer, entryIndex, inheritanceIndex)).analyze(className, methodNode);
48 } catch (AnalyzerException e) {
49 throw new RuntimeException(e);
50 }
51 });
52 }
53
54 private static class MethodInterpreter extends InterpreterPair<BasicValue, SourceValue> {
55 private final MethodDefEntry callerEntry;
56 private JarIndexer indexer;
57
58 public MethodInterpreter(MethodDefEntry callerEntry, JarIndexer indexer, EntryIndex entryIndex, InheritanceIndex inheritanceIndex) {
59 super(new IndexSimpleVerifier(entryIndex, inheritanceIndex), new SourceInterpreter());
60 this.callerEntry = callerEntry;
61 this.indexer = indexer;
62 }
63
64 @Override
65 public PairValue<BasicValue, SourceValue> newOperation(AbstractInsnNode insn) throws AnalyzerException {
66 if (insn.getOpcode() == Opcodes.GETSTATIC) {
67 FieldInsnNode field = (FieldInsnNode) insn;
68 indexer.indexFieldReference(callerEntry, FieldEntry.parse(field.owner, field.name, field.desc), ReferenceTargetType.none());
69 }
70
71 return super.newOperation(insn);
72 }
73
74 @Override
75 public PairValue<BasicValue, SourceValue> unaryOperation(AbstractInsnNode insn, PairValue<BasicValue, SourceValue> value) throws AnalyzerException {
76 if (insn.getOpcode() == Opcodes.PUTSTATIC) {
77 FieldInsnNode field = (FieldInsnNode) insn;
78 indexer.indexFieldReference(callerEntry, FieldEntry.parse(field.owner, field.name, field.desc), ReferenceTargetType.none());
79 }
80
81 if (insn.getOpcode() == Opcodes.GETFIELD) {
82 FieldInsnNode field = (FieldInsnNode) insn;
83 indexer.indexFieldReference(callerEntry, FieldEntry.parse(field.owner, field.name, field.desc), getReferenceTargetType(value, insn));
84 }
85
86 return super.unaryOperation(insn, value);
87 }
88
89
90 @Override
91 public PairValue<BasicValue, SourceValue> binaryOperation(AbstractInsnNode insn, PairValue<BasicValue, SourceValue> value1, PairValue<BasicValue, SourceValue> value2) throws AnalyzerException {
92 if (insn.getOpcode() == Opcodes.PUTFIELD) {
93 FieldInsnNode field = (FieldInsnNode) insn;
94 FieldEntry fieldEntry = FieldEntry.parse(field.owner, field.name, field.desc);
95 indexer.indexFieldReference(callerEntry, fieldEntry, ReferenceTargetType.none());
96 }
97
98 return super.binaryOperation(insn, value1, value2);
99 }
100
101 @Override
102 public PairValue<BasicValue, SourceValue> naryOperation(AbstractInsnNode insn, List<? extends PairValue<BasicValue, SourceValue>> values) throws AnalyzerException {
103 if (insn.getOpcode() == Opcodes.INVOKEINTERFACE || insn.getOpcode() == Opcodes.INVOKESPECIAL || insn.getOpcode() == Opcodes.INVOKEVIRTUAL) {
104 MethodInsnNode methodInsn = (MethodInsnNode) insn;
105 indexer.indexMethodReference(callerEntry, MethodEntry.parse(methodInsn.owner, methodInsn.name, methodInsn.desc), getReferenceTargetType(values.get(0), insn));
106 }
107
108 if (insn.getOpcode() == Opcodes.INVOKESTATIC) {
109 MethodInsnNode methodInsn = (MethodInsnNode) insn;
110 indexer.indexMethodReference(callerEntry, MethodEntry.parse(methodInsn.owner, methodInsn.name, methodInsn.desc), ReferenceTargetType.none());
111 }
112
113 if (insn.getOpcode() == Opcodes.INVOKEDYNAMIC) {
114 InvokeDynamicInsnNode invokeDynamicInsn = (InvokeDynamicInsnNode) insn;
115 List<AbstractInsnNode> args = values.stream().map(v -> v.right.insns.stream().findFirst().orElseThrow(AssertionError::new)).collect(Collectors.toList());
116
117 if ("java/lang/invoke/LambdaMetafactory".equals(invokeDynamicInsn.bsm.getOwner()) && "metafactory".equals(invokeDynamicInsn.bsm.getName())) {
118 Type samMethodType = (Type) invokeDynamicInsn.bsmArgs[0];
119 Handle implMethod = (Handle) invokeDynamicInsn.bsmArgs[1];
120 Type instantiatedMethodType = (Type) invokeDynamicInsn.bsmArgs[2];
121
122 ReferenceTargetType targetType;
123 if (implMethod.getTag() != Opcodes.H_GETSTATIC && implMethod.getTag() != Opcodes.H_PUTFIELD && implMethod.getTag() != Opcodes.H_INVOKESTATIC) {
124 if (instantiatedMethodType.getArgumentTypes().length < Type.getArgumentTypes(implMethod.getDesc()).length) {
125 targetType = getReferenceTargetType(values.get(0), insn);
126 } else {
127 targetType = ReferenceTargetType.none(); // no "this" argument
128 }
129 } else {
130 targetType = ReferenceTargetType.none();
131 }
132
133 indexer.indexLambda(callerEntry, new Lambda(
134 invokeDynamicInsn.name,
135 new MethodDescriptor(invokeDynamicInsn.desc),
136 new MethodDescriptor(samMethodType.getDescriptor()),
137 getHandleEntry(implMethod),
138 new MethodDescriptor(instantiatedMethodType.getDescriptor())
139 ), targetType);
140 }
141 }
142
143 return super.naryOperation(insn, values);
144 }
145
146 private ReferenceTargetType getReferenceTargetType(PairValue<BasicValue, SourceValue> target, AbstractInsnNode insn) throws AnalyzerException {
147 if (target.left == BasicValue.UNINITIALIZED_VALUE) {
148 return ReferenceTargetType.uninitialized();
149 }
150
151 if (target.left.getType().getSort() == Type.OBJECT) {
152 return ReferenceTargetType.classType(new ClassEntry(target.left.getType().getInternalName()));
153 }
154
155 if (target.left.getType().getSort() == Type.ARRAY) {
156 return ReferenceTargetType.classType(new ClassEntry("java/lang/Object"));
157 }
158
159 throw new AnalyzerException(insn, "called method on or accessed field of non-object type");
160 }
161
162 private static ParentedEntry<?> getHandleEntry(Handle handle) {
163 switch (handle.getTag()) {
164 case Opcodes.H_GETFIELD:
165 case Opcodes.H_GETSTATIC:
166 case Opcodes.H_PUTFIELD:
167 case Opcodes.H_PUTSTATIC:
168 return FieldEntry.parse(handle.getOwner(), handle.getName(), handle.getDesc());
169 case Opcodes.H_INVOKEINTERFACE:
170 case Opcodes.H_INVOKESPECIAL:
171 case Opcodes.H_INVOKESTATIC:
172 case Opcodes.H_INVOKEVIRTUAL:
173 case Opcodes.H_NEWINVOKESPECIAL:
174 return MethodEntry.parse(handle.getOwner(), handle.getName(), handle.getDesc());
175 }
176
177 throw new RuntimeException("Invalid handle tag " + handle.getTag());
178 }
179 }
180}
diff --git a/enigma/src/main/java/cuchaz/enigma/analysis/index/InheritanceIndex.java b/enigma/src/main/java/cuchaz/enigma/analysis/index/InheritanceIndex.java
new file mode 100644
index 0000000..1ab2abd
--- /dev/null
+++ b/enigma/src/main/java/cuchaz/enigma/analysis/index/InheritanceIndex.java
@@ -0,0 +1,127 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 * <p>
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11
12package cuchaz.enigma.analysis.index;
13
14import com.google.common.collect.HashMultimap;
15import com.google.common.collect.Multimap;
16import com.google.common.collect.Sets;
17import cuchaz.enigma.translation.representation.entry.ClassDefEntry;
18import cuchaz.enigma.translation.representation.entry.ClassEntry;
19
20import java.util.Collection;
21import java.util.HashSet;
22import java.util.LinkedList;
23import java.util.Set;
24
25public class InheritanceIndex implements JarIndexer {
26 private final EntryIndex entryIndex;
27
28 private Multimap<ClassEntry, ClassEntry> classParents = HashMultimap.create();
29 private Multimap<ClassEntry, ClassEntry> classChildren = HashMultimap.create();
30
31 public InheritanceIndex(EntryIndex entryIndex) {
32 this.entryIndex = entryIndex;
33 }
34
35 @Override
36 public void indexClass(ClassDefEntry classEntry) {
37 if (classEntry.isJre()) {
38 return;
39 }
40
41 ClassEntry superClass = classEntry.getSuperClass();
42 if (superClass != null && !superClass.getName().equals("java/lang/Object")) {
43 indexParent(classEntry, superClass);
44 }
45
46 for (ClassEntry interfaceEntry : classEntry.getInterfaces()) {
47 indexParent(classEntry, interfaceEntry);
48 }
49 }
50
51 private void indexParent(ClassEntry childEntry, ClassEntry parentEntry) {
52 classParents.put(childEntry, parentEntry);
53 classChildren.put(parentEntry, childEntry);
54 }
55
56 public Collection<ClassEntry> getParents(ClassEntry classEntry) {
57 return classParents.get(classEntry);
58 }
59
60 public Collection<ClassEntry> getChildren(ClassEntry classEntry) {
61 return classChildren.get(classEntry);
62 }
63
64 public Collection<ClassEntry> getDescendants(ClassEntry classEntry) {
65 Collection<ClassEntry> descendants = new HashSet<>();
66
67 LinkedList<ClassEntry> descendantQueue = new LinkedList<>();
68 descendantQueue.push(classEntry);
69
70 while (!descendantQueue.isEmpty()) {
71 ClassEntry descendant = descendantQueue.pop();
72 Collection<ClassEntry> children = getChildren(descendant);
73
74 children.forEach(descendantQueue::push);
75 descendants.addAll(children);
76 }
77
78 return descendants;
79 }
80
81 public Set<ClassEntry> getAncestors(ClassEntry classEntry) {
82 Set<ClassEntry> ancestors = Sets.newHashSet();
83
84 LinkedList<ClassEntry> ancestorQueue = new LinkedList<>();
85 ancestorQueue.push(classEntry);
86
87 while (!ancestorQueue.isEmpty()) {
88 ClassEntry ancestor = ancestorQueue.pop();
89 Collection<ClassEntry> parents = getParents(ancestor);
90
91 parents.forEach(ancestorQueue::push);
92 ancestors.addAll(parents);
93 }
94
95 return ancestors;
96 }
97
98 public Relation computeClassRelation(ClassEntry classEntry, ClassEntry potentialAncestor) {
99 if (potentialAncestor.getName().equals("java/lang/Object")) return Relation.RELATED;
100 if (!entryIndex.hasClass(classEntry)) return Relation.UNKNOWN;
101
102 for (ClassEntry ancestor : getAncestors(classEntry)) {
103 if (potentialAncestor.equals(ancestor)) {
104 return Relation.RELATED;
105 } else if (!entryIndex.hasClass(ancestor)) {
106 return Relation.UNKNOWN;
107 }
108 }
109
110 return Relation.UNRELATED;
111 }
112
113 public boolean isParent(ClassEntry classEntry) {
114 return classChildren.containsKey(classEntry);
115 }
116
117 public boolean hasParents(ClassEntry classEntry) {
118 Collection<ClassEntry> parents = classParents.get(classEntry);
119 return parents != null && !parents.isEmpty();
120 }
121
122 public enum Relation {
123 RELATED,
124 UNRELATED,
125 UNKNOWN
126 }
127}
diff --git a/enigma/src/main/java/cuchaz/enigma/analysis/index/JarIndex.java b/enigma/src/main/java/cuchaz/enigma/analysis/index/JarIndex.java
new file mode 100644
index 0000000..de3f5f5
--- /dev/null
+++ b/enigma/src/main/java/cuchaz/enigma/analysis/index/JarIndex.java
@@ -0,0 +1,170 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 * <p>
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11
12package cuchaz.enigma.analysis.index;
13
14import com.google.common.collect.HashMultimap;
15import com.google.common.collect.Multimap;
16import cuchaz.enigma.Enigma;
17import cuchaz.enigma.ProgressListener;
18import cuchaz.enigma.analysis.ClassCache;
19import cuchaz.enigma.analysis.ReferenceTargetType;
20import cuchaz.enigma.translation.mapping.EntryResolver;
21import cuchaz.enigma.translation.mapping.IndexEntryResolver;
22import cuchaz.enigma.translation.representation.Lambda;
23import cuchaz.enigma.translation.representation.entry.*;
24import cuchaz.enigma.utils.I18n;
25
26import org.objectweb.asm.ClassReader;
27
28import java.util.Arrays;
29import java.util.Collection;
30
31public class JarIndex implements JarIndexer {
32 private final EntryIndex entryIndex;
33 private final InheritanceIndex inheritanceIndex;
34 private final ReferenceIndex referenceIndex;
35 private final BridgeMethodIndex bridgeMethodIndex;
36 private final PackageVisibilityIndex packageVisibilityIndex;
37 private final EntryResolver entryResolver;
38
39 private final Collection<JarIndexer> indexers;
40
41 private final Multimap<String, MethodDefEntry> methodImplementations = HashMultimap.create();
42
43 public JarIndex(EntryIndex entryIndex, InheritanceIndex inheritanceIndex, ReferenceIndex referenceIndex, BridgeMethodIndex bridgeMethodIndex, PackageVisibilityIndex packageVisibilityIndex) {
44 this.entryIndex = entryIndex;
45 this.inheritanceIndex = inheritanceIndex;
46 this.referenceIndex = referenceIndex;
47 this.bridgeMethodIndex = bridgeMethodIndex;
48 this.packageVisibilityIndex = packageVisibilityIndex;
49 this.indexers = Arrays.asList(entryIndex, inheritanceIndex, referenceIndex, bridgeMethodIndex, packageVisibilityIndex);
50 this.entryResolver = new IndexEntryResolver(this);
51 }
52
53 public static JarIndex empty() {
54 EntryIndex entryIndex = new EntryIndex();
55 InheritanceIndex inheritanceIndex = new InheritanceIndex(entryIndex);
56 ReferenceIndex referenceIndex = new ReferenceIndex();
57 BridgeMethodIndex bridgeMethodIndex = new BridgeMethodIndex(entryIndex, inheritanceIndex, referenceIndex);
58 PackageVisibilityIndex packageVisibilityIndex = new PackageVisibilityIndex();
59 return new JarIndex(entryIndex, inheritanceIndex, referenceIndex, bridgeMethodIndex, packageVisibilityIndex);
60 }
61
62 public void indexJar(ClassCache classCache, ProgressListener progress) {
63 progress.init(4, I18n.translate("progress.jar.indexing"));
64
65 progress.step(1, I18n.translate("progress.jar.indexing.entries"));
66 classCache.visit(() -> new IndexClassVisitor(this, Enigma.ASM_VERSION), ClassReader.SKIP_CODE);
67
68 progress.step(2, I18n.translate("progress.jar.indexing.references"));
69 classCache.visit(() -> new IndexReferenceVisitor(this, entryIndex, inheritanceIndex, Enigma.ASM_VERSION), 0);
70
71 progress.step(3, I18n.translate("progress.jar.indexing.methods"));
72 bridgeMethodIndex.findBridgeMethods();
73
74 progress.step(4, I18n.translate("progress.jar.indexing.process"));
75 processIndex(this);
76 }
77
78 @Override
79 public void processIndex(JarIndex index) {
80 indexers.forEach(indexer -> indexer.processIndex(index));
81 }
82
83 @Override
84 public void indexClass(ClassDefEntry classEntry) {
85 if (classEntry.isJre()) {
86 return;
87 }
88
89 for (ClassEntry interfaceEntry : classEntry.getInterfaces()) {
90 if (classEntry.equals(interfaceEntry)) {
91 throw new IllegalArgumentException("Class cannot be its own interface! " + classEntry);
92 }
93 }
94
95 indexers.forEach(indexer -> indexer.indexClass(classEntry));
96 }
97
98 @Override
99 public void indexField(FieldDefEntry fieldEntry) {
100 if (fieldEntry.getParent().isJre()) {
101 return;
102 }
103
104 indexers.forEach(indexer -> indexer.indexField(fieldEntry));
105 }
106
107 @Override
108 public void indexMethod(MethodDefEntry methodEntry) {
109 if (methodEntry.getParent().isJre()) {
110 return;
111 }
112
113 indexers.forEach(indexer -> indexer.indexMethod(methodEntry));
114
115 if (!methodEntry.isConstructor()) {
116 methodImplementations.put(methodEntry.getParent().getFullName(), methodEntry);
117 }
118 }
119
120 @Override
121 public void indexMethodReference(MethodDefEntry callerEntry, MethodEntry referencedEntry, ReferenceTargetType targetType) {
122 if (callerEntry.getParent().isJre()) {
123 return;
124 }
125
126 indexers.forEach(indexer -> indexer.indexMethodReference(callerEntry, referencedEntry, targetType));
127 }
128
129 @Override
130 public void indexFieldReference(MethodDefEntry callerEntry, FieldEntry referencedEntry, ReferenceTargetType targetType) {
131 if (callerEntry.getParent().isJre()) {
132 return;
133 }
134
135 indexers.forEach(indexer -> indexer.indexFieldReference(callerEntry, referencedEntry, targetType));
136 }
137
138 @Override
139 public void indexLambda(MethodDefEntry callerEntry, Lambda lambda, ReferenceTargetType targetType) {
140 if (callerEntry.getParent().isJre()) {
141 return;
142 }
143
144 indexers.forEach(indexer -> indexer.indexLambda(callerEntry, lambda, targetType));
145 }
146
147 public EntryIndex getEntryIndex() {
148 return entryIndex;
149 }
150
151 public InheritanceIndex getInheritanceIndex() {
152 return this.inheritanceIndex;
153 }
154
155 public ReferenceIndex getReferenceIndex() {
156 return referenceIndex;
157 }
158
159 public BridgeMethodIndex getBridgeMethodIndex() {
160 return bridgeMethodIndex;
161 }
162
163 public PackageVisibilityIndex getPackageVisibilityIndex() {
164 return packageVisibilityIndex;
165 }
166
167 public EntryResolver getEntryResolver() {
168 return entryResolver;
169 }
170}
diff --git a/enigma/src/main/java/cuchaz/enigma/analysis/index/JarIndexer.java b/enigma/src/main/java/cuchaz/enigma/analysis/index/JarIndexer.java
new file mode 100644
index 0000000..f17e7c9
--- /dev/null
+++ b/enigma/src/main/java/cuchaz/enigma/analysis/index/JarIndexer.java
@@ -0,0 +1,28 @@
1package cuchaz.enigma.analysis.index;
2
3import cuchaz.enigma.analysis.ReferenceTargetType;
4import cuchaz.enigma.translation.representation.Lambda;
5import cuchaz.enigma.translation.representation.entry.*;
6
7public interface JarIndexer {
8 default void indexClass(ClassDefEntry classEntry) {
9 }
10
11 default void indexField(FieldDefEntry fieldEntry) {
12 }
13
14 default void indexMethod(MethodDefEntry methodEntry) {
15 }
16
17 default void indexMethodReference(MethodDefEntry callerEntry, MethodEntry referencedEntry, ReferenceTargetType targetType) {
18 }
19
20 default void indexFieldReference(MethodDefEntry callerEntry, FieldEntry referencedEntry, ReferenceTargetType targetType) {
21 }
22
23 default void indexLambda(MethodDefEntry callerEntry, Lambda lambda, ReferenceTargetType targetType) {
24 }
25
26 default void processIndex(JarIndex index) {
27 }
28}
diff --git a/enigma/src/main/java/cuchaz/enigma/analysis/index/PackageVisibilityIndex.java b/enigma/src/main/java/cuchaz/enigma/analysis/index/PackageVisibilityIndex.java
new file mode 100644
index 0000000..64de5f3
--- /dev/null
+++ b/enigma/src/main/java/cuchaz/enigma/analysis/index/PackageVisibilityIndex.java
@@ -0,0 +1,147 @@
1package cuchaz.enigma.analysis.index;
2
3import com.google.common.collect.HashMultimap;
4import com.google.common.collect.Lists;
5import com.google.common.collect.Maps;
6import com.google.common.collect.Sets;
7import cuchaz.enigma.analysis.EntryReference;
8import cuchaz.enigma.analysis.ReferenceTargetType;
9import cuchaz.enigma.translation.representation.AccessFlags;
10import cuchaz.enigma.translation.representation.entry.*;
11
12import java.util.*;
13
14public class PackageVisibilityIndex implements JarIndexer {
15 private static boolean requiresSamePackage(AccessFlags entryAcc, EntryReference ref, InheritanceIndex inheritanceIndex) {
16 if (entryAcc.isPublic()) {
17 return false;
18 }
19
20 if (entryAcc.isProtected()) {
21 ClassEntry contextClass = ref.context.getContainingClass();
22 ClassEntry referencedClass = ref.entry.getContainingClass();
23
24 if (!inheritanceIndex.getAncestors(contextClass).contains(referencedClass)) {
25 return true; // access to protected member not in superclass
26 }
27
28 if (ref.targetType.getKind() == ReferenceTargetType.Kind.NONE) {
29 return false; // access to superclass or static superclass member
30 }
31
32 // access to instance member only valid if target's class assignable to context class
33 return !(ref.targetType.getKind() == ReferenceTargetType.Kind.UNINITIALIZED ||
34 ((ReferenceTargetType.ClassType) ref.targetType).getEntry().equals(contextClass) ||
35 inheritanceIndex.getAncestors(((ReferenceTargetType.ClassType) ref.targetType).getEntry()).contains(contextClass));
36 }
37
38 return true;
39 }
40
41 private final HashMultimap<ClassEntry, ClassEntry> connections = HashMultimap.create();
42 private final List<Set<ClassEntry>> partitions = Lists.newArrayList();
43 private final Map<ClassEntry, Set<ClassEntry>> classPartitions = Maps.newHashMap();
44
45 private void addConnection(ClassEntry classA, ClassEntry classB) {
46 if (classA != classB) {
47 connections.put(classA, classB);
48 connections.put(classB, classA);
49 }
50 }
51
52 private void buildPartition(Set<ClassEntry> unassignedClasses, Set<ClassEntry> partition, ClassEntry member) {
53 for (ClassEntry connected : connections.get(member)) {
54 if (unassignedClasses.remove(connected)) {
55 partition.add(connected);
56 buildPartition(unassignedClasses, partition, connected);
57 }
58 }
59 }
60
61 private void addConnections(EntryIndex entryIndex, ReferenceIndex referenceIndex, InheritanceIndex inheritanceIndex) {
62 for (FieldEntry entry : entryIndex.getFields()) {
63 AccessFlags entryAcc = entryIndex.getFieldAccess(entry);
64 if (!entryAcc.isPublic() && !entryAcc.isPrivate()) {
65 for (EntryReference<FieldEntry, MethodDefEntry> ref : referenceIndex.getReferencesToField(entry)) {
66 if (requiresSamePackage(entryAcc, ref, inheritanceIndex)) {
67 addConnection(ref.entry.getContainingClass(), ref.context.getContainingClass());
68 }
69 }
70 }
71 }
72
73 for (MethodEntry entry : entryIndex.getMethods()) {
74 AccessFlags entryAcc = entryIndex.getMethodAccess(entry);
75 if (!entryAcc.isPublic() && !entryAcc.isPrivate()) {
76 for (EntryReference<MethodEntry, MethodDefEntry> ref : referenceIndex.getReferencesToMethod(entry)) {
77 if (requiresSamePackage(entryAcc, ref, inheritanceIndex)) {
78 addConnection(ref.entry.getContainingClass(), ref.context.getContainingClass());
79 }
80 }
81 }
82 }
83
84 for (ClassEntry entry : entryIndex.getClasses()) {
85 AccessFlags entryAcc = entryIndex.getClassAccess(entry);
86 if (!entryAcc.isPublic() && !entryAcc.isPrivate()) {
87 for (EntryReference<ClassEntry, FieldDefEntry> ref : referenceIndex.getFieldTypeReferencesToClass(entry)) {
88 if (requiresSamePackage(entryAcc, ref, inheritanceIndex)) {
89 addConnection(ref.entry.getContainingClass(), ref.context.getContainingClass());
90 }
91 }
92
93 for (EntryReference<ClassEntry, MethodDefEntry> ref : referenceIndex.getMethodTypeReferencesToClass(entry)) {
94 if (requiresSamePackage(entryAcc, ref, inheritanceIndex)) {
95 addConnection(ref.entry.getContainingClass(), ref.context.getContainingClass());
96 }
97 }
98 }
99
100 for (ClassEntry parent : inheritanceIndex.getParents(entry)) {
101 AccessFlags parentAcc = entryIndex.getClassAccess(parent);
102 if (parentAcc != null && !parentAcc.isPublic() && !parentAcc.isPrivate()) {
103 addConnection(entry, parent);
104 }
105 }
106
107 ClassEntry outerClass = entry.getOuterClass();
108 if (outerClass != null) {
109 addConnection(entry, outerClass);
110 }
111 }
112 }
113
114 private void addPartitions(EntryIndex entryIndex) {
115 Set<ClassEntry> unassignedClasses = Sets.newHashSet(entryIndex.getClasses());
116 while (!unassignedClasses.isEmpty()) {
117 Iterator<ClassEntry> iterator = unassignedClasses.iterator();
118 ClassEntry initialEntry = iterator.next();
119 iterator.remove();
120
121 HashSet<ClassEntry> partition = Sets.newHashSet();
122 partition.add(initialEntry);
123 buildPartition(unassignedClasses, partition, initialEntry);
124 partitions.add(partition);
125 for (ClassEntry entry : partition) {
126 classPartitions.put(entry, partition);
127 }
128 }
129 }
130
131 public Collection<Set<ClassEntry>> getPartitions() {
132 return partitions;
133 }
134
135 public Set<ClassEntry> getPartition(ClassEntry classEntry) {
136 return classPartitions.get(classEntry);
137 }
138
139 @Override
140 public void processIndex(JarIndex index) {
141 EntryIndex entryIndex = index.getEntryIndex();
142 ReferenceIndex referenceIndex = index.getReferenceIndex();
143 InheritanceIndex inheritanceIndex = index.getInheritanceIndex();
144 addConnections(entryIndex, referenceIndex, inheritanceIndex);
145 addPartitions(entryIndex);
146 }
147}
diff --git a/enigma/src/main/java/cuchaz/enigma/analysis/index/ReferenceIndex.java b/enigma/src/main/java/cuchaz/enigma/analysis/index/ReferenceIndex.java
new file mode 100644
index 0000000..b6797c2
--- /dev/null
+++ b/enigma/src/main/java/cuchaz/enigma/analysis/index/ReferenceIndex.java
@@ -0,0 +1,148 @@
1package cuchaz.enigma.analysis.index;
2
3import com.google.common.collect.HashMultimap;
4import com.google.common.collect.Multimap;
5import cuchaz.enigma.analysis.EntryReference;
6import cuchaz.enigma.analysis.ReferenceTargetType;
7import cuchaz.enigma.translation.mapping.ResolutionStrategy;
8import cuchaz.enigma.translation.representation.Lambda;
9import cuchaz.enigma.translation.representation.MethodDescriptor;
10import cuchaz.enigma.translation.representation.TypeDescriptor;
11import cuchaz.enigma.translation.representation.entry.*;
12
13import java.util.Collection;
14import java.util.Map;
15
16public class ReferenceIndex implements JarIndexer {
17 private Multimap<MethodEntry, MethodEntry> methodReferences = HashMultimap.create();
18
19 private Multimap<MethodEntry, EntryReference<MethodEntry, MethodDefEntry>> referencesToMethods = HashMultimap.create();
20 private Multimap<ClassEntry, EntryReference<ClassEntry, MethodDefEntry>> referencesToClasses = HashMultimap.create();
21 private Multimap<FieldEntry, EntryReference<FieldEntry, MethodDefEntry>> referencesToFields = HashMultimap.create();
22 private Multimap<ClassEntry, EntryReference<ClassEntry, FieldDefEntry>> fieldTypeReferences = HashMultimap.create();
23 private Multimap<ClassEntry, EntryReference<ClassEntry, MethodDefEntry>> methodTypeReferences = HashMultimap.create();
24
25 @Override
26 public void indexMethod(MethodDefEntry methodEntry) {
27 indexMethodDescriptor(methodEntry, methodEntry.getDesc());
28 }
29
30 private void indexMethodDescriptor(MethodDefEntry entry, MethodDescriptor descriptor) {
31 for (TypeDescriptor typeDescriptor : descriptor.getArgumentDescs()) {
32 indexMethodTypeDescriptor(entry, typeDescriptor);
33 }
34 indexMethodTypeDescriptor(entry, descriptor.getReturnDesc());
35 }
36
37 private void indexMethodTypeDescriptor(MethodDefEntry method, TypeDescriptor typeDescriptor) {
38 if (typeDescriptor.isType()) {
39 ClassEntry referencedClass = typeDescriptor.getTypeEntry();
40 methodTypeReferences.put(referencedClass, new EntryReference<>(referencedClass, referencedClass.getName(), method));
41 } else if (typeDescriptor.isArray()) {
42 indexMethodTypeDescriptor(method, typeDescriptor.getArrayType());
43 }
44 }
45
46 @Override
47 public void indexField(FieldDefEntry fieldEntry) {
48 indexFieldTypeDescriptor(fieldEntry, fieldEntry.getDesc());
49 }
50
51 private void indexFieldTypeDescriptor(FieldDefEntry field, TypeDescriptor typeDescriptor) {
52 if (typeDescriptor.isType()) {
53 ClassEntry referencedClass = typeDescriptor.getTypeEntry();
54 fieldTypeReferences.put(referencedClass, new EntryReference<>(referencedClass, referencedClass.getName(), field));
55 } else if (typeDescriptor.isArray()) {
56 indexFieldTypeDescriptor(field, typeDescriptor.getArrayType());
57 }
58 }
59
60 @Override
61 public void indexMethodReference(MethodDefEntry callerEntry, MethodEntry referencedEntry, ReferenceTargetType targetType) {
62 referencesToMethods.put(referencedEntry, new EntryReference<>(referencedEntry, referencedEntry.getName(), callerEntry, targetType));
63 methodReferences.put(callerEntry, referencedEntry);
64
65 if (referencedEntry.isConstructor()) {
66 ClassEntry referencedClass = referencedEntry.getParent();
67 referencesToClasses.put(referencedClass, new EntryReference<>(referencedClass, referencedEntry.getName(), callerEntry, targetType));
68 }
69 }
70
71 @Override
72 public void indexFieldReference(MethodDefEntry callerEntry, FieldEntry referencedEntry, ReferenceTargetType targetType) {
73 referencesToFields.put(referencedEntry, new EntryReference<>(referencedEntry, referencedEntry.getName(), callerEntry, targetType));
74 }
75
76 @Override
77 public void indexLambda(MethodDefEntry callerEntry, Lambda lambda, ReferenceTargetType targetType) {
78 if (lambda.getImplMethod() instanceof MethodEntry) {
79 indexMethodReference(callerEntry, (MethodEntry) lambda.getImplMethod(), targetType);
80 } else {
81 indexFieldReference(callerEntry, (FieldEntry) lambda.getImplMethod(), targetType);
82 }
83
84 indexMethodDescriptor(callerEntry, lambda.getInvokedType());
85 indexMethodDescriptor(callerEntry, lambda.getSamMethodType());
86 indexMethodDescriptor(callerEntry, lambda.getInstantiatedMethodType());
87 }
88
89 @Override
90 public void processIndex(JarIndex index) {
91 methodReferences = remapReferences(index, methodReferences);
92 referencesToMethods = remapReferencesTo(index, referencesToMethods);
93 referencesToClasses = remapReferencesTo(index, referencesToClasses);
94 referencesToFields = remapReferencesTo(index, referencesToFields);
95 fieldTypeReferences = remapReferencesTo(index, fieldTypeReferences);
96 methodTypeReferences = remapReferencesTo(index, methodTypeReferences);
97 }
98
99 private <K extends Entry<?>, V extends Entry<?>> Multimap<K, V> remapReferences(JarIndex index, Multimap<K, V> multimap) {
100 final int keySetSize = multimap.keySet().size();
101 Multimap<K, V> resolved = HashMultimap.create(multimap.keySet().size(), keySetSize == 0 ? 0 : multimap.size() / keySetSize);
102 for (Map.Entry<K, V> entry : multimap.entries()) {
103 resolved.put(remap(index, entry.getKey()), remap(index, entry.getValue()));
104 }
105 return resolved;
106 }
107
108 private <E extends Entry<?>, C extends Entry<?>> Multimap<E, EntryReference<E, C>> remapReferencesTo(JarIndex index, Multimap<E, EntryReference<E, C>> multimap) {
109 final int keySetSize = multimap.keySet().size();
110 Multimap<E, EntryReference<E, C>> resolved = HashMultimap.create(keySetSize, keySetSize == 0 ? 0 : multimap.size() / keySetSize);
111 for (Map.Entry<E, EntryReference<E, C>> entry : multimap.entries()) {
112 resolved.put(remap(index, entry.getKey()), remap(index, entry.getValue()));
113 }
114 return resolved;
115 }
116
117 private <E extends Entry<?>> E remap(JarIndex index, E entry) {
118 return index.getEntryResolver().resolveFirstEntry(entry, ResolutionStrategy.RESOLVE_CLOSEST);
119 }
120
121 private <E extends Entry<?>, C extends Entry<?>> EntryReference<E, C> remap(JarIndex index, EntryReference<E, C> reference) {
122 return index.getEntryResolver().resolveFirstReference(reference, ResolutionStrategy.RESOLVE_CLOSEST);
123 }
124
125 public Collection<MethodEntry> getMethodsReferencedBy(MethodEntry entry) {
126 return methodReferences.get(entry);
127 }
128
129 public Collection<EntryReference<FieldEntry, MethodDefEntry>> getReferencesToField(FieldEntry entry) {
130 return referencesToFields.get(entry);
131 }
132
133 public Collection<EntryReference<ClassEntry, MethodDefEntry>> getReferencesToClass(ClassEntry entry) {
134 return referencesToClasses.get(entry);
135 }
136
137 public Collection<EntryReference<MethodEntry, MethodDefEntry>> getReferencesToMethod(MethodEntry entry) {
138 return referencesToMethods.get(entry);
139 }
140
141 public Collection<EntryReference<ClassEntry, FieldDefEntry>> getFieldTypeReferencesToClass(ClassEntry entry) {
142 return fieldTypeReferences.get(entry);
143 }
144
145 public Collection<EntryReference<ClassEntry, MethodDefEntry>> getMethodTypeReferencesToClass(ClassEntry entry) {
146 return methodTypeReferences.get(entry);
147 }
148}
diff --git a/enigma/src/main/java/cuchaz/enigma/api/EnigmaPlugin.java b/enigma/src/main/java/cuchaz/enigma/api/EnigmaPlugin.java
new file mode 100644
index 0000000..bdd6015
--- /dev/null
+++ b/enigma/src/main/java/cuchaz/enigma/api/EnigmaPlugin.java
@@ -0,0 +1,5 @@
1package cuchaz.enigma.api;
2
3public interface EnigmaPlugin {
4 void init(EnigmaPluginContext ctx);
5}
diff --git a/enigma/src/main/java/cuchaz/enigma/api/EnigmaPluginContext.java b/enigma/src/main/java/cuchaz/enigma/api/EnigmaPluginContext.java
new file mode 100644
index 0000000..a59051a
--- /dev/null
+++ b/enigma/src/main/java/cuchaz/enigma/api/EnigmaPluginContext.java
@@ -0,0 +1,9 @@
1package cuchaz.enigma.api;
2
3import cuchaz.enigma.api.service.EnigmaService;
4import cuchaz.enigma.api.service.EnigmaServiceFactory;
5import cuchaz.enigma.api.service.EnigmaServiceType;
6
7public interface EnigmaPluginContext {
8 <T extends EnigmaService> void registerService(String id, EnigmaServiceType<T> serviceType, EnigmaServiceFactory<T> factory);
9}
diff --git a/enigma/src/main/java/cuchaz/enigma/api/service/EnigmaService.java b/enigma/src/main/java/cuchaz/enigma/api/service/EnigmaService.java
new file mode 100644
index 0000000..526dda7
--- /dev/null
+++ b/enigma/src/main/java/cuchaz/enigma/api/service/EnigmaService.java
@@ -0,0 +1,4 @@
1package cuchaz.enigma.api.service;
2
3public interface EnigmaService {
4}
diff --git a/enigma/src/main/java/cuchaz/enigma/api/service/EnigmaServiceContext.java b/enigma/src/main/java/cuchaz/enigma/api/service/EnigmaServiceContext.java
new file mode 100644
index 0000000..9e433fb
--- /dev/null
+++ b/enigma/src/main/java/cuchaz/enigma/api/service/EnigmaServiceContext.java
@@ -0,0 +1,11 @@
1package cuchaz.enigma.api.service;
2
3import java.util.Optional;
4
5public interface EnigmaServiceContext<T extends EnigmaService> {
6 static <T extends EnigmaService> EnigmaServiceContext<T> empty() {
7 return key -> Optional.empty();
8 }
9
10 Optional<String> getArgument(String key);
11}
diff --git a/enigma/src/main/java/cuchaz/enigma/api/service/EnigmaServiceFactory.java b/enigma/src/main/java/cuchaz/enigma/api/service/EnigmaServiceFactory.java
new file mode 100644
index 0000000..7c10ac2
--- /dev/null
+++ b/enigma/src/main/java/cuchaz/enigma/api/service/EnigmaServiceFactory.java
@@ -0,0 +1,5 @@
1package cuchaz.enigma.api.service;
2
3public interface EnigmaServiceFactory<T extends EnigmaService> {
4 T create(EnigmaServiceContext<T> ctx);
5}
diff --git a/enigma/src/main/java/cuchaz/enigma/api/service/EnigmaServiceType.java b/enigma/src/main/java/cuchaz/enigma/api/service/EnigmaServiceType.java
new file mode 100644
index 0000000..358828f
--- /dev/null
+++ b/enigma/src/main/java/cuchaz/enigma/api/service/EnigmaServiceType.java
@@ -0,0 +1,29 @@
1package cuchaz.enigma.api.service;
2
3public final class EnigmaServiceType<T extends EnigmaService> {
4 public final String key;
5
6 private EnigmaServiceType(String key) {
7 this.key = key;
8 }
9
10 public static <T extends EnigmaService> EnigmaServiceType<T> create(String key) {
11 return new EnigmaServiceType<>(key);
12 }
13
14 @Override
15 public int hashCode() {
16 return key.hashCode();
17 }
18
19 @Override
20 public boolean equals(Object obj) {
21 if (obj == this) return true;
22
23 if (obj instanceof EnigmaServiceType) {
24 return ((EnigmaServiceType) obj).key.equals(key);
25 }
26
27 return false;
28 }
29}
diff --git a/enigma/src/main/java/cuchaz/enigma/api/service/JarIndexerService.java b/enigma/src/main/java/cuchaz/enigma/api/service/JarIndexerService.java
new file mode 100644
index 0000000..0cda199
--- /dev/null
+++ b/enigma/src/main/java/cuchaz/enigma/api/service/JarIndexerService.java
@@ -0,0 +1,10 @@
1package cuchaz.enigma.api.service;
2
3import cuchaz.enigma.analysis.ClassCache;
4import cuchaz.enigma.analysis.index.JarIndex;
5
6public interface JarIndexerService extends EnigmaService {
7 EnigmaServiceType<JarIndexerService> TYPE = EnigmaServiceType.create("jar_indexer");
8
9 void acceptJar(ClassCache classCache, JarIndex jarIndex);
10}
diff --git a/enigma/src/main/java/cuchaz/enigma/api/service/NameProposalService.java b/enigma/src/main/java/cuchaz/enigma/api/service/NameProposalService.java
new file mode 100644
index 0000000..4c357db
--- /dev/null
+++ b/enigma/src/main/java/cuchaz/enigma/api/service/NameProposalService.java
@@ -0,0 +1,12 @@
1package cuchaz.enigma.api.service;
2
3import cuchaz.enigma.translation.mapping.EntryRemapper;
4import cuchaz.enigma.translation.representation.entry.Entry;
5
6import java.util.Optional;
7
8public interface NameProposalService extends EnigmaService {
9 EnigmaServiceType<NameProposalService> TYPE = EnigmaServiceType.create("name_proposal");
10
11 Optional<String> proposeName(Entry<?> obfEntry, EntryRemapper remapper);
12}
diff --git a/enigma/src/main/java/cuchaz/enigma/api/service/ObfuscationTestService.java b/enigma/src/main/java/cuchaz/enigma/api/service/ObfuscationTestService.java
new file mode 100644
index 0000000..af0cf30
--- /dev/null
+++ b/enigma/src/main/java/cuchaz/enigma/api/service/ObfuscationTestService.java
@@ -0,0 +1,9 @@
1package cuchaz.enigma.api.service;
2
3import cuchaz.enigma.translation.representation.entry.Entry;
4
5public interface ObfuscationTestService extends EnigmaService {
6 EnigmaServiceType<ObfuscationTestService> TYPE = EnigmaServiceType.create("obfuscation_test");
7
8 boolean testDeobfuscated(Entry<?> entry);
9}
diff --git a/enigma/src/main/java/cuchaz/enigma/bytecode/translators/AsmObjectTranslator.java b/enigma/src/main/java/cuchaz/enigma/bytecode/translators/AsmObjectTranslator.java
new file mode 100644
index 0000000..1a2b47f
--- /dev/null
+++ b/enigma/src/main/java/cuchaz/enigma/bytecode/translators/AsmObjectTranslator.java
@@ -0,0 +1,46 @@
1package cuchaz.enigma.bytecode.translators;
2
3import cuchaz.enigma.translation.Translator;
4import cuchaz.enigma.translation.representation.MethodDescriptor;
5import cuchaz.enigma.translation.representation.TypeDescriptor;
6import cuchaz.enigma.translation.representation.entry.ClassEntry;
7import cuchaz.enigma.translation.representation.entry.MethodEntry;
8import org.objectweb.asm.Handle;
9import org.objectweb.asm.Type;
10
11public class AsmObjectTranslator {
12 public static Type translateType(Translator translator, Type type) {
13 String descString = type.getDescriptor();
14 switch (type.getSort()) {
15 case Type.OBJECT: {
16 ClassEntry classEntry = new ClassEntry(type.getInternalName());
17 return Type.getObjectType(translator.translate(classEntry).getFullName());
18 }
19 case Type.ARRAY: {
20 TypeDescriptor descriptor = new TypeDescriptor(descString);
21 return Type.getType(translator.translate(descriptor).toString());
22 }
23 case Type.METHOD: {
24 MethodDescriptor descriptor = new MethodDescriptor(descString);
25 return Type.getMethodType(translator.translate(descriptor).toString());
26 }
27 }
28 return type;
29 }
30
31 public static Handle translateHandle(Translator translator, Handle handle) {
32 MethodEntry entry = new MethodEntry(new ClassEntry(handle.getOwner()), handle.getName(), new MethodDescriptor(handle.getDesc()));
33 MethodEntry translatedMethod = translator.translate(entry);
34 ClassEntry ownerClass = translatedMethod.getParent();
35 return new Handle(handle.getTag(), ownerClass.getFullName(), translatedMethod.getName(), translatedMethod.getDesc().toString(), handle.isInterface());
36 }
37
38 public static Object translateValue(Translator translator, Object value) {
39 if (value instanceof Type) {
40 return translateType(translator, (Type) value);
41 } else if (value instanceof Handle) {
42 return translateHandle(translator, (Handle) value);
43 }
44 return value;
45 }
46}
diff --git a/enigma/src/main/java/cuchaz/enigma/bytecode/translators/LocalVariableFixVisitor.java b/enigma/src/main/java/cuchaz/enigma/bytecode/translators/LocalVariableFixVisitor.java
new file mode 100644
index 0000000..cfd8fbe
--- /dev/null
+++ b/enigma/src/main/java/cuchaz/enigma/bytecode/translators/LocalVariableFixVisitor.java
@@ -0,0 +1,126 @@
1package cuchaz.enigma.bytecode.translators;
2
3import com.google.common.base.CharMatcher;
4import cuchaz.enigma.translation.LocalNameGenerator;
5import cuchaz.enigma.translation.representation.TypeDescriptor;
6import cuchaz.enigma.translation.representation.entry.ClassDefEntry;
7import cuchaz.enigma.translation.representation.entry.MethodDefEntry;
8import org.objectweb.asm.ClassVisitor;
9import org.objectweb.asm.Label;
10import org.objectweb.asm.MethodVisitor;
11import org.objectweb.asm.Opcodes;
12
13import java.util.HashMap;
14import java.util.List;
15import java.util.Map;
16
17public class LocalVariableFixVisitor extends ClassVisitor {
18 private ClassDefEntry ownerEntry;
19
20 public LocalVariableFixVisitor(int api, ClassVisitor visitor) {
21 super(api, visitor);
22 }
23
24 @Override
25 public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) {
26 ownerEntry = ClassDefEntry.parse(access, name, signature, superName, interfaces);
27 super.visit(version, access, name, signature, superName, interfaces);
28 }
29
30 @Override
31 public MethodVisitor visitMethod(int access, String name, String descriptor, String signature, String[] exceptions) {
32 MethodDefEntry methodEntry = MethodDefEntry.parse(ownerEntry, access, name, descriptor, signature);
33 return new Method(api, methodEntry, super.visitMethod(access, name, descriptor, signature, exceptions));
34 }
35
36 private class Method extends MethodVisitor {
37 private final MethodDefEntry methodEntry;
38 private final Map<Integer, String> parameterNames = new HashMap<>();
39 private final Map<Integer, Integer> parameterIndices = new HashMap<>();
40 private boolean hasParameterTable;
41 private int parameterIndex = 0;
42
43 Method(int api, MethodDefEntry methodEntry, MethodVisitor visitor) {
44 super(api, visitor);
45 this.methodEntry = methodEntry;
46
47 int lvIndex = methodEntry.getAccess().isStatic() ? 0 : 1;
48 List<TypeDescriptor> parameters = methodEntry.getDesc().getArgumentDescs();
49 for (int parameterIndex = 0; parameterIndex < parameters.size(); parameterIndex++) {
50 TypeDescriptor param = parameters.get(parameterIndex);
51 parameterIndices.put(lvIndex, parameterIndex);
52 lvIndex += param.getSize();
53 }
54 }
55
56 @Override
57 public void visitParameter(String name, int access) {
58 hasParameterTable = true;
59 super.visitParameter(fixParameterName(parameterIndex, name), fixParameterAccess(parameterIndex, access));
60 parameterIndex++;
61 }
62
63 @Override
64 public void visitLocalVariable(String name, String desc, String signature, Label start, Label end, int index) {
65 if (index == 0 && !methodEntry.getAccess().isStatic()) {
66 name = "this";
67 } else if (parameterIndices.containsKey(index)) {
68 name = fixParameterName(parameterIndices.get(index), name);
69 } else if (isInvalidName(name)) {
70 name = LocalNameGenerator.generateLocalVariableName(index, new TypeDescriptor(desc));
71 }
72
73 super.visitLocalVariable(name, desc, signature, start, end, index);
74 }
75
76 private boolean isInvalidName(String name) {
77 return name == null || name.isEmpty() || !CharMatcher.ascii().matchesAllOf(name);
78 }
79
80 @Override
81 public void visitEnd() {
82 if (!hasParameterTable) {
83 List<TypeDescriptor> arguments = methodEntry.getDesc().getArgumentDescs();
84 for (int argumentIndex = 0; argumentIndex < arguments.size(); argumentIndex++) {
85 super.visitParameter(fixParameterName(argumentIndex, null), fixParameterAccess(argumentIndex, 0));
86 }
87 }
88
89 super.visitEnd();
90 }
91
92 private String fixParameterName(int index, String name) {
93 if (parameterNames.get(index) != null) {
94 return parameterNames.get(index); // to make sure that LVT names are consistent with parameter table names
95 }
96
97 if (isInvalidName(name)) {
98 List<TypeDescriptor> arguments = methodEntry.getDesc().getArgumentDescs();
99 name = LocalNameGenerator.generateArgumentName(index, arguments.get(index), arguments);
100 }
101
102 if (index == 0 && ownerEntry.getAccess().isEnum() && methodEntry.getName().equals("<init>")) {
103 name = "name";
104 }
105
106 if (index == 1 && ownerEntry.getAccess().isEnum() && methodEntry.getName().equals("<init>")) {
107 name = "ordinal";
108 }
109
110 parameterNames.put(index, name);
111 return name;
112 }
113
114 private int fixParameterAccess(int index, int access) {
115 if (index == 0 && ownerEntry.getAccess().isEnum() && methodEntry.getName().equals("<init>")) {
116 access |= Opcodes.ACC_SYNTHETIC;
117 }
118
119 if (index == 1 && ownerEntry.getAccess().isEnum() && methodEntry.getName().equals("<init>")) {
120 access |= Opcodes.ACC_SYNTHETIC;
121 }
122
123 return access;
124 }
125 }
126}
diff --git a/enigma/src/main/java/cuchaz/enigma/bytecode/translators/SourceFixVisitor.java b/enigma/src/main/java/cuchaz/enigma/bytecode/translators/SourceFixVisitor.java
new file mode 100644
index 0000000..2b750ea
--- /dev/null
+++ b/enigma/src/main/java/cuchaz/enigma/bytecode/translators/SourceFixVisitor.java
@@ -0,0 +1,39 @@
1package cuchaz.enigma.bytecode.translators;
2
3import cuchaz.enigma.analysis.index.BridgeMethodIndex;
4import cuchaz.enigma.analysis.index.JarIndex;
5import cuchaz.enigma.translation.representation.entry.ClassDefEntry;
6import cuchaz.enigma.translation.representation.entry.MethodDefEntry;
7import org.objectweb.asm.ClassVisitor;
8import org.objectweb.asm.MethodVisitor;
9import org.objectweb.asm.Opcodes;
10
11public class SourceFixVisitor extends ClassVisitor {
12 private final JarIndex index;
13 private ClassDefEntry ownerEntry;
14
15 public SourceFixVisitor(int api, ClassVisitor visitor, JarIndex index) {
16 super(api, visitor);
17 this.index = index;
18 }
19
20 @Override
21 public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) {
22 ownerEntry = ClassDefEntry.parse(access, name, signature, superName, interfaces);
23 super.visit(version, access, name, signature, superName, interfaces);
24 }
25
26 @Override
27 public MethodVisitor visitMethod(int access, String name, String descriptor, String signature, String[] exceptions) {
28 MethodDefEntry methodEntry = MethodDefEntry.parse(ownerEntry, access, name, descriptor, signature);
29
30 BridgeMethodIndex bridgeIndex = index.getBridgeMethodIndex();
31 if (bridgeIndex.isBridgeMethod(methodEntry)) {
32 access |= Opcodes.ACC_BRIDGE;
33 } else if (bridgeIndex.isSpecializedMethod(methodEntry)) {
34 name = bridgeIndex.getBridgeFromSpecialized(methodEntry).getName();
35 }
36
37 return super.visitMethod(access, name, descriptor, signature, exceptions);
38 }
39}
diff --git a/enigma/src/main/java/cuchaz/enigma/bytecode/translators/TranslationAnnotationVisitor.java b/enigma/src/main/java/cuchaz/enigma/bytecode/translators/TranslationAnnotationVisitor.java
new file mode 100644
index 0000000..cb843ad
--- /dev/null
+++ b/enigma/src/main/java/cuchaz/enigma/bytecode/translators/TranslationAnnotationVisitor.java
@@ -0,0 +1,51 @@
1package cuchaz.enigma.bytecode.translators;
2
3import cuchaz.enigma.translation.Translator;
4import cuchaz.enigma.translation.representation.TypeDescriptor;
5import cuchaz.enigma.translation.representation.entry.ClassEntry;
6import cuchaz.enigma.translation.representation.entry.FieldEntry;
7import org.objectweb.asm.AnnotationVisitor;
8
9public class TranslationAnnotationVisitor extends AnnotationVisitor {
10 private final Translator translator;
11 private final ClassEntry annotationEntry;
12
13 public TranslationAnnotationVisitor(Translator translator, ClassEntry annotationEntry, int api, AnnotationVisitor av) {
14 super(api, av);
15 this.translator = translator;
16 this.annotationEntry = annotationEntry;
17 }
18
19 @Override
20 public void visit(String name, Object value) {
21 super.visit(name, AsmObjectTranslator.translateValue(translator, value));
22 }
23
24 @Override
25 public AnnotationVisitor visitArray(String name) {
26 return new TranslationAnnotationVisitor(translator, annotationEntry, api, super.visitArray(name));
27 }
28
29 @Override
30 public AnnotationVisitor visitAnnotation(String name, String desc) {
31 TypeDescriptor type = new TypeDescriptor(desc);
32 if (name != null) {
33 FieldEntry annotationField = translator.translate(new FieldEntry(annotationEntry, name, type));
34 return super.visitAnnotation(annotationField.getName(), annotationField.getDesc().toString());
35 } else {
36 return super.visitAnnotation(null, translator.translate(type).toString());
37 }
38 }
39
40 @Override
41 public void visitEnum(String name, String desc, String value) {
42 TypeDescriptor type = new TypeDescriptor(desc);
43 FieldEntry enumField = translator.translate(new FieldEntry(type.getTypeEntry(), value, type));
44 if (name != null) {
45 FieldEntry annotationField = translator.translate(new FieldEntry(annotationEntry, name, type));
46 super.visitEnum(annotationField.getName(), annotationField.getDesc().toString(), enumField.getName());
47 } else {
48 super.visitEnum(null, translator.translate(type).toString(), enumField.getName());
49 }
50 }
51}
diff --git a/enigma/src/main/java/cuchaz/enigma/bytecode/translators/TranslationClassVisitor.java b/enigma/src/main/java/cuchaz/enigma/bytecode/translators/TranslationClassVisitor.java
new file mode 100644
index 0000000..e4c41d3
--- /dev/null
+++ b/enigma/src/main/java/cuchaz/enigma/bytecode/translators/TranslationClassVisitor.java
@@ -0,0 +1,102 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 * <p>
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11
12package cuchaz.enigma.bytecode.translators;
13
14import cuchaz.enigma.translation.Translator;
15import cuchaz.enigma.translation.representation.MethodDescriptor;
16import cuchaz.enigma.translation.representation.TypeDescriptor;
17import cuchaz.enigma.translation.representation.entry.*;
18import org.objectweb.asm.*;
19
20import java.util.Arrays;
21
22public class TranslationClassVisitor extends ClassVisitor {
23 private final Translator translator;
24
25 private ClassDefEntry obfClassEntry;
26
27 public TranslationClassVisitor(Translator translator, int api, ClassVisitor cv) {
28 super(api, cv);
29 this.translator = translator;
30 }
31
32 @Override
33 public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) {
34 obfClassEntry = ClassDefEntry.parse(access, name, signature, superName, interfaces);
35
36 ClassDefEntry translatedEntry = translator.translate(obfClassEntry);
37 String translatedSuper = translatedEntry.getSuperClass() != null ? translatedEntry.getSuperClass().getFullName() : null;
38 String[] translatedInterfaces = Arrays.stream(translatedEntry.getInterfaces()).map(ClassEntry::getFullName).toArray(String[]::new);
39
40 super.visit(version, translatedEntry.getAccess().getFlags(), translatedEntry.getFullName(), translatedEntry.getSignature().toString(), translatedSuper, translatedInterfaces);
41 }
42
43 @Override
44 public FieldVisitor visitField(int access, String name, String desc, String signature, Object value) {
45 FieldDefEntry entry = FieldDefEntry.parse(obfClassEntry, access, name, desc, signature);
46 FieldDefEntry translatedEntry = translator.translate(entry);
47 FieldVisitor fv = super.visitField(translatedEntry.getAccess().getFlags(), translatedEntry.getName(), translatedEntry.getDesc().toString(), translatedEntry.getSignature().toString(), value);
48 return new TranslationFieldVisitor(translator, translatedEntry, api, fv);
49 }
50
51 @Override
52 public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) {
53 MethodDefEntry entry = MethodDefEntry.parse(obfClassEntry, access, name, desc, signature);
54 MethodDefEntry translatedEntry = translator.translate(entry);
55 String[] translatedExceptions = new String[exceptions.length];
56 for (int i = 0; i < exceptions.length; i++) {
57 translatedExceptions[i] = translator.translate(new ClassEntry(exceptions[i])).getFullName();
58 }
59 MethodVisitor mv = super.visitMethod(translatedEntry.getAccess().getFlags(), translatedEntry.getName(), translatedEntry.getDesc().toString(), translatedEntry.getSignature().toString(), translatedExceptions);
60 return new TranslationMethodVisitor(translator, obfClassEntry, entry, api, mv);
61 }
62
63 @Override
64 public void visitInnerClass(String name, String outerName, String innerName, int access) {
65 ClassDefEntry classEntry = ClassDefEntry.parse(access, name, obfClassEntry.getSignature().toString(), null, new String[0]);
66 ClassDefEntry translatedEntry = translator.translate(classEntry);
67 ClassEntry translatedOuterClass = translatedEntry.getOuterClass();
68 if (translatedOuterClass == null) {
69 throw new IllegalStateException("Translated inner class did not have outer class");
70 }
71
72 // Anonymous classes do not specify an outer or inner name. As we do not translate from the given parameter, ignore if the input is null
73 String translatedName = translatedEntry.getFullName();
74 String translatedOuterName = outerName != null ? translatedOuterClass.getFullName() : null;
75 String translatedInnerName = innerName != null ? translatedEntry.getName() : null;
76 super.visitInnerClass(translatedName, translatedOuterName, translatedInnerName, translatedEntry.getAccess().getFlags());
77 }
78
79 @Override
80 public void visitOuterClass(String owner, String name, String desc) {
81 if (desc != null) {
82 MethodEntry translatedEntry = translator.translate(new MethodEntry(new ClassEntry(owner), name, new MethodDescriptor(desc)));
83 super.visitOuterClass(translatedEntry.getParent().getFullName(), translatedEntry.getName(), translatedEntry.getDesc().toString());
84 } else {
85 super.visitOuterClass(owner, name, desc);
86 }
87 }
88
89 @Override
90 public AnnotationVisitor visitAnnotation(String desc, boolean visible) {
91 TypeDescriptor translatedDesc = translator.translate(new TypeDescriptor(desc));
92 AnnotationVisitor av = super.visitAnnotation(translatedDesc.toString(), visible);
93 return new TranslationAnnotationVisitor(translator, translatedDesc.getTypeEntry(), api, av);
94 }
95
96 @Override
97 public AnnotationVisitor visitTypeAnnotation(int typeRef, TypePath typePath, String desc, boolean visible) {
98 TypeDescriptor translatedDesc = translator.translate(new TypeDescriptor(desc));
99 AnnotationVisitor av = super.visitTypeAnnotation(typeRef, typePath, translatedDesc.toString(), visible);
100 return new TranslationAnnotationVisitor(translator, translatedDesc.getTypeEntry(), api, av);
101 }
102}
diff --git a/enigma/src/main/java/cuchaz/enigma/bytecode/translators/TranslationFieldVisitor.java b/enigma/src/main/java/cuchaz/enigma/bytecode/translators/TranslationFieldVisitor.java
new file mode 100644
index 0000000..28fc199
--- /dev/null
+++ b/enigma/src/main/java/cuchaz/enigma/bytecode/translators/TranslationFieldVisitor.java
@@ -0,0 +1,33 @@
1package cuchaz.enigma.bytecode.translators;
2
3import cuchaz.enigma.translation.Translator;
4import cuchaz.enigma.translation.representation.TypeDescriptor;
5import cuchaz.enigma.translation.representation.entry.FieldDefEntry;
6import org.objectweb.asm.AnnotationVisitor;
7import org.objectweb.asm.FieldVisitor;
8import org.objectweb.asm.TypePath;
9
10public class TranslationFieldVisitor extends FieldVisitor {
11 private final FieldDefEntry fieldEntry;
12 private final Translator translator;
13
14 public TranslationFieldVisitor(Translator translator, FieldDefEntry fieldEntry, int api, FieldVisitor fv) {
15 super(api, fv);
16 this.translator = translator;
17 this.fieldEntry = fieldEntry;
18 }
19
20 @Override
21 public AnnotationVisitor visitAnnotation(String desc, boolean visible) {
22 TypeDescriptor typeDesc = translator.translate(new TypeDescriptor(desc));
23 AnnotationVisitor av = super.visitAnnotation(typeDesc.toString(), visible);
24 return new TranslationAnnotationVisitor(translator, typeDesc.getTypeEntry(), api, av);
25 }
26
27 @Override
28 public AnnotationVisitor visitTypeAnnotation(int typeRef, TypePath typePath, String desc, boolean visible) {
29 TypeDescriptor typeDesc = translator.translate(new TypeDescriptor(desc));
30 AnnotationVisitor av = super.visitAnnotation(typeDesc.toString(), visible);
31 return new TranslationAnnotationVisitor(translator, typeDesc.getTypeEntry(), api, av);
32 }
33}
diff --git a/enigma/src/main/java/cuchaz/enigma/bytecode/translators/TranslationMethodVisitor.java b/enigma/src/main/java/cuchaz/enigma/bytecode/translators/TranslationMethodVisitor.java
new file mode 100644
index 0000000..a82df1b
--- /dev/null
+++ b/enigma/src/main/java/cuchaz/enigma/bytecode/translators/TranslationMethodVisitor.java
@@ -0,0 +1,145 @@
1package cuchaz.enigma.bytecode.translators;
2
3import cuchaz.enigma.translation.Translator;
4import cuchaz.enigma.translation.representation.MethodDescriptor;
5import cuchaz.enigma.translation.representation.Signature;
6import cuchaz.enigma.translation.representation.TypeDescriptor;
7import cuchaz.enigma.translation.representation.entry.*;
8import org.objectweb.asm.*;
9
10public class TranslationMethodVisitor extends MethodVisitor {
11 private final MethodDefEntry methodEntry;
12 private final Translator translator;
13
14 private int parameterIndex = 0;
15 private int parameterLvIndex;
16
17 public TranslationMethodVisitor(Translator translator, ClassDefEntry ownerEntry, MethodDefEntry methodEntry, int api, MethodVisitor mv) {
18 super(api, mv);
19 this.translator = translator;
20 this.methodEntry = methodEntry;
21
22 parameterLvIndex = methodEntry.getAccess().isStatic() ? 0 : 1;
23 }
24
25 @Override
26 public void visitParameter(String name, int access) {
27 name = translateVariableName(parameterLvIndex, name);
28 parameterLvIndex += methodEntry.getDesc().getArgumentDescs().get(parameterIndex++).getSize();
29
30 super.visitParameter(name, access);
31 }
32
33 @Override
34 public void visitFieldInsn(int opcode, String owner, String name, String desc) {
35 FieldEntry entry = new FieldEntry(new ClassEntry(owner), name, new TypeDescriptor(desc));
36 FieldEntry translatedEntry = translator.translate(entry);
37 super.visitFieldInsn(opcode, translatedEntry.getParent().getFullName(), translatedEntry.getName(), translatedEntry.getDesc().toString());
38 }
39
40 @Override
41 public void visitMethodInsn(int opcode, String owner, String name, String desc, boolean itf) {
42 MethodEntry entry = new MethodEntry(new ClassEntry(owner), name, new MethodDescriptor(desc));
43 MethodEntry translatedEntry = translator.translate(entry);
44 super.visitMethodInsn(opcode, translatedEntry.getParent().getFullName(), translatedEntry.getName(), translatedEntry.getDesc().toString(), itf);
45 }
46
47 @Override
48 public void visitFrame(int type, int localCount, Object[] locals, int stackCount, Object[] stack) {
49 Object[] translatedLocals = this.getTranslatedFrame(locals, localCount);
50 Object[] translatedStack = this.getTranslatedFrame(stack, stackCount);
51 super.visitFrame(type, localCount, translatedLocals, stackCount, translatedStack);
52 }
53
54 private Object[] getTranslatedFrame(Object[] array, int count) {
55 if (array == null) {
56 return null;
57 }
58 for (int i = 0; i < count; i++) {
59 Object object = array[i];
60 if (object instanceof String) {
61 String type = (String) object;
62 array[i] = translator.translate(new ClassEntry(type)).getFullName();
63 }
64 }
65 return array;
66 }
67
68 @Override
69 public AnnotationVisitor visitAnnotation(String desc, boolean visible) {
70 TypeDescriptor typeDesc = translator.translate(new TypeDescriptor(desc));
71 AnnotationVisitor av = super.visitAnnotation(typeDesc.toString(), visible);
72 return new TranslationAnnotationVisitor(translator, typeDesc.getTypeEntry(), api, av);
73 }
74
75 @Override
76 public AnnotationVisitor visitParameterAnnotation(int parameter, String desc, boolean visible) {
77 TypeDescriptor typeDesc = translator.translate(new TypeDescriptor(desc));
78 AnnotationVisitor av = super.visitParameterAnnotation(parameter, typeDesc.toString(), visible);
79 return new TranslationAnnotationVisitor(translator, typeDesc.getTypeEntry(), api, av);
80 }
81
82 @Override
83 public AnnotationVisitor visitTypeAnnotation(int typeRef, TypePath typePath, String desc, boolean visible) {
84 TypeDescriptor typeDesc = translator.translate(new TypeDescriptor(desc));
85 AnnotationVisitor av = super.visitTypeAnnotation(typeRef, typePath, typeDesc.toString(), visible);
86 return new TranslationAnnotationVisitor(translator, typeDesc.getTypeEntry(), api, av);
87 }
88
89 @Override
90 public void visitTypeInsn(int opcode, String type) {
91 ClassEntry translatedEntry = translator.translate(new ClassEntry(type));
92 super.visitTypeInsn(opcode, translatedEntry.getFullName());
93 }
94
95 @Override
96 public void visitInvokeDynamicInsn(String name, String desc, Handle bsm, Object... bsmArgs) {
97 MethodDescriptor translatedMethodDesc = translator.translate(new MethodDescriptor(desc));
98 Object[] translatedBsmArgs = new Object[bsmArgs.length];
99 for (int i = 0; i < bsmArgs.length; i++) {
100 translatedBsmArgs[i] = AsmObjectTranslator.translateValue(translator, bsmArgs[i]);
101 }
102 super.visitInvokeDynamicInsn(name, translatedMethodDesc.toString(), AsmObjectTranslator.translateHandle(translator, bsm), translatedBsmArgs);
103 }
104
105 @Override
106 public void visitLdcInsn(Object cst) {
107 super.visitLdcInsn(AsmObjectTranslator.translateValue(translator, cst));
108 }
109
110 @Override
111 public void visitMultiANewArrayInsn(String desc, int dims) {
112 super.visitMultiANewArrayInsn(translator.translate(new TypeDescriptor(desc)).toString(), dims);
113 }
114
115 @Override
116 public void visitTryCatchBlock(Label start, Label end, Label handler, String type) {
117 if (type != null) {
118 ClassEntry translatedEntry = translator.translate(new ClassEntry(type));
119 super.visitTryCatchBlock(start, end, handler, translatedEntry.getFullName());
120 } else {
121 super.visitTryCatchBlock(start, end, handler, type);
122 }
123 }
124
125 @Override
126 public void visitLocalVariable(String name, String desc, String signature, Label start, Label end, int index) {
127 signature = translator.translate(Signature.createTypedSignature(signature)).toString();
128 name = translateVariableName(index, name);
129 desc = translator.translate(new TypeDescriptor(desc)).toString();
130
131 super.visitLocalVariable(name, desc, signature, start, end, index);
132 }
133
134 private String translateVariableName(int index, String name) {
135 LocalVariableEntry entry = new LocalVariableEntry(methodEntry, index, "", true,null);
136 LocalVariableEntry translatedEntry = translator.translate(entry);
137 String translatedName = translatedEntry.getName();
138
139 if (!translatedName.isEmpty()) {
140 return translatedName;
141 }
142
143 return name;
144 }
145}
diff --git a/enigma/src/main/java/cuchaz/enigma/bytecode/translators/TranslationSignatureVisitor.java b/enigma/src/main/java/cuchaz/enigma/bytecode/translators/TranslationSignatureVisitor.java
new file mode 100644
index 0000000..bc4a2f0
--- /dev/null
+++ b/enigma/src/main/java/cuchaz/enigma/bytecode/translators/TranslationSignatureVisitor.java
@@ -0,0 +1,129 @@
1package cuchaz.enigma.bytecode.translators;
2
3import cuchaz.enigma.Enigma;
4import org.objectweb.asm.signature.SignatureVisitor;
5
6import java.util.Stack;
7import java.util.function.Function;
8
9public class TranslationSignatureVisitor extends SignatureVisitor {
10 private final Function<String, String> remapper;
11
12 private final SignatureVisitor sv;
13 private final Stack<String> classStack = new Stack<>();
14
15 public TranslationSignatureVisitor(Function<String, String> remapper, SignatureVisitor sv) {
16 super(Enigma.ASM_VERSION);
17 this.remapper = remapper;
18 this.sv = sv;
19 }
20
21 @Override
22 public void visitClassType(String name) {
23 classStack.push(name);
24 String translatedEntry = this.remapper.apply(name);
25 this.sv.visitClassType(translatedEntry);
26 }
27
28 @Override
29 public void visitInnerClassType(String name) {
30 String lastClass = classStack.pop();
31 if (!name.startsWith(lastClass+"$")){//todo see if there's a way to base this on whether there were type params or not
32 name = lastClass+"$"+name;
33 }
34 String translatedEntry = this.remapper.apply(name);
35 if (translatedEntry.contains("/")){
36 translatedEntry = translatedEntry.substring(translatedEntry.lastIndexOf("/")+1);
37 }
38 if (translatedEntry.contains("$")){
39 translatedEntry = translatedEntry.substring(translatedEntry.lastIndexOf("$")+1);
40 }
41 this.sv.visitInnerClassType(translatedEntry);
42 }
43
44 @Override
45 public void visitFormalTypeParameter(String name) {
46 this.sv.visitFormalTypeParameter(name);
47 }
48
49 @Override
50 public void visitTypeVariable(String name) {
51 this.sv.visitTypeVariable(name);
52 }
53
54 @Override
55 public SignatureVisitor visitArrayType() {
56 this.sv.visitArrayType();
57 return this;
58 }
59
60 @Override
61 public void visitBaseType(char descriptor) {
62 this.sv.visitBaseType(descriptor);
63 }
64
65 @Override
66 public SignatureVisitor visitClassBound() {
67 this.sv.visitClassBound();
68 return this;
69 }
70
71 @Override
72 public SignatureVisitor visitExceptionType() {
73 this.sv.visitExceptionType();
74 return this;
75 }
76
77 @Override
78 public SignatureVisitor visitInterface() {
79 this.sv.visitInterface();
80 return this;
81 }
82
83 @Override
84 public SignatureVisitor visitInterfaceBound() {
85 this.sv.visitInterfaceBound();
86 return this;
87 }
88
89 @Override
90 public SignatureVisitor visitParameterType() {
91 this.sv.visitParameterType();
92 return this;
93 }
94
95 @Override
96 public SignatureVisitor visitReturnType() {
97 this.sv.visitReturnType();
98 return this;
99 }
100
101 @Override
102 public SignatureVisitor visitSuperclass() {
103 this.sv.visitSuperclass();
104 return this;
105 }
106
107 @Override
108 public void visitTypeArgument() {
109 this.sv.visitTypeArgument();
110 }
111
112 @Override
113 public SignatureVisitor visitTypeArgument(char wildcard) {
114 this.sv.visitTypeArgument(wildcard);
115 return this;
116 }
117
118 @Override
119 public void visitEnd() {
120 this.sv.visitEnd();
121 if (!classStack.empty())
122 classStack.pop();
123 }
124
125 @Override
126 public String toString() {
127 return this.sv.toString();
128 }
129}
diff --git a/enigma/src/main/java/cuchaz/enigma/source/Decompiler.java b/enigma/src/main/java/cuchaz/enigma/source/Decompiler.java
new file mode 100644
index 0000000..c9666d5
--- /dev/null
+++ b/enigma/src/main/java/cuchaz/enigma/source/Decompiler.java
@@ -0,0 +1,5 @@
1package cuchaz.enigma.source;
2
3public interface Decompiler {
4 Source getSource(String className);
5}
diff --git a/enigma/src/main/java/cuchaz/enigma/source/DecompilerService.java b/enigma/src/main/java/cuchaz/enigma/source/DecompilerService.java
new file mode 100644
index 0000000..377ccbc
--- /dev/null
+++ b/enigma/src/main/java/cuchaz/enigma/source/DecompilerService.java
@@ -0,0 +1,11 @@
1package cuchaz.enigma.source;
2
3import cuchaz.enigma.ClassProvider;
4import cuchaz.enigma.api.service.EnigmaService;
5import cuchaz.enigma.api.service.EnigmaServiceType;
6
7public interface DecompilerService extends EnigmaService {
8 EnigmaServiceType<DecompilerService> TYPE = EnigmaServiceType.create("decompiler");
9
10 Decompiler create(ClassProvider classProvider, SourceSettings settings);
11}
diff --git a/enigma/src/main/java/cuchaz/enigma/source/Decompilers.java b/enigma/src/main/java/cuchaz/enigma/source/Decompilers.java
new file mode 100644
index 0000000..7d154a6
--- /dev/null
+++ b/enigma/src/main/java/cuchaz/enigma/source/Decompilers.java
@@ -0,0 +1,9 @@
1package cuchaz.enigma.source;
2
3import cuchaz.enigma.source.cfr.CfrDecompiler;
4import cuchaz.enigma.source.procyon.ProcyonDecompiler;
5
6public class Decompilers {
7 public static final DecompilerService PROCYON = ProcyonDecompiler::new;
8 public static final DecompilerService CFR = CfrDecompiler::new;
9}
diff --git a/enigma/src/main/java/cuchaz/enigma/source/Source.java b/enigma/src/main/java/cuchaz/enigma/source/Source.java
new file mode 100644
index 0000000..43c4de0
--- /dev/null
+++ b/enigma/src/main/java/cuchaz/enigma/source/Source.java
@@ -0,0 +1,11 @@
1package cuchaz.enigma.source;
2
3import cuchaz.enigma.translation.mapping.EntryRemapper;
4
5public interface Source {
6 String asString();
7
8 Source addJavadocs(EntryRemapper remapper);
9
10 SourceIndex index();
11}
diff --git a/enigma/src/main/java/cuchaz/enigma/source/SourceIndex.java b/enigma/src/main/java/cuchaz/enigma/source/SourceIndex.java
new file mode 100644
index 0000000..178ce20
--- /dev/null
+++ b/enigma/src/main/java/cuchaz/enigma/source/SourceIndex.java
@@ -0,0 +1,172 @@
1package cuchaz.enigma.source;
2
3import com.google.common.collect.HashMultimap;
4import com.google.common.collect.Lists;
5import com.google.common.collect.Maps;
6import com.google.common.collect.Multimap;
7import cuchaz.enigma.analysis.EntryReference;
8import cuchaz.enigma.translation.mapping.EntryResolver;
9import cuchaz.enigma.translation.mapping.ResolutionStrategy;
10import cuchaz.enigma.translation.representation.entry.Entry;
11
12import java.util.Collection;
13import java.util.List;
14import java.util.Map;
15import java.util.TreeMap;
16import java.util.stream.Collectors;
17
18public class SourceIndex {
19 private String source;
20 private List<Integer> lineOffsets;
21 private final TreeMap<Token, EntryReference<Entry<?>, Entry<?>>> tokenToReference;
22 private final Multimap<EntryReference<Entry<?>, Entry<?>>, Token> referenceToTokens;
23 private final Map<Entry<?>, Token> declarationToToken;
24
25 public SourceIndex() {
26 tokenToReference = new TreeMap<>();
27 referenceToTokens = HashMultimap.create();
28 declarationToToken = Maps.newHashMap();
29 }
30
31 public SourceIndex(String source) {
32 this();
33 setSource(source);
34 }
35
36 public void setSource(String source) {
37 this.source = source;
38 lineOffsets = Lists.newArrayList();
39 lineOffsets.add(0);
40
41 for (int i = 0; i < this.source.length(); i++) {
42 if (this.source.charAt(i) == '\n') {
43 lineOffsets.add(i + 1);
44 }
45 }
46 }
47
48 public String getSource() {
49 return source;
50 }
51
52 public int getLineNumber(int position) {
53 int line = 0;
54
55 for (int offset : lineOffsets) {
56 if (offset > position) {
57 break;
58 }
59
60 line++;
61 }
62
63 return line;
64 }
65
66 public int getColumnNumber(int position) {
67 return position - lineOffsets.get(getLineNumber(position) - 1) + 1;
68 }
69
70 public int getPosition(int line, int column) {
71 return lineOffsets.get(line - 1) + column - 1;
72 }
73
74 public Iterable<Entry<?>> declarations() {
75 return declarationToToken.keySet();
76 }
77
78 public Iterable<Token> declarationTokens() {
79 return declarationToToken.values();
80 }
81
82 public Token getDeclarationToken(Entry<?> entry) {
83 return declarationToToken.get(entry);
84 }
85
86 public void addDeclaration(Token token, Entry<?> deobfEntry) {
87 if (token != null) {
88 EntryReference<Entry<?>, Entry<?>> reference = new EntryReference<>(deobfEntry, token.text);
89 tokenToReference.put(token, reference);
90 referenceToTokens.put(reference, token);
91 declarationToToken.put(deobfEntry, token);
92 }
93 }
94
95 public Iterable<EntryReference<Entry<?>, Entry<?>>> references() {
96 return referenceToTokens.keySet();
97 }
98
99 public EntryReference<Entry<?>, Entry<?>> getReference(Token token) {
100 if (token == null) {
101 return null;
102 }
103
104 return tokenToReference.get(token);
105 }
106
107 public Iterable<Token> referenceTokens() {
108 return tokenToReference.keySet();
109 }
110
111 public Token getReferenceToken(int pos) {
112 Token token = tokenToReference.floorKey(new Token(pos, pos, null));
113
114 if (token != null && token.contains(pos)) {
115 return token;
116 }
117
118 return null;
119 }
120
121 public Collection<Token> getReferenceTokens(EntryReference<Entry<?>, Entry<?>> deobfReference) {
122 return referenceToTokens.get(deobfReference);
123 }
124
125 public void addReference(Token token, Entry<?> deobfEntry, Entry<?> deobfContext) {
126 if (token != null) {
127 EntryReference<Entry<?>, Entry<?>> deobfReference = new EntryReference<>(deobfEntry, token.text, deobfContext);
128 tokenToReference.put(token, deobfReference);
129 referenceToTokens.put(deobfReference, token);
130 }
131 }
132
133 public void resolveReferences(EntryResolver resolver) {
134 // resolve all the classes in the source references
135 for (Token token : Lists.newArrayList(referenceToTokens.values())) {
136 EntryReference<Entry<?>, Entry<?>> reference = tokenToReference.get(token);
137 EntryReference<Entry<?>, Entry<?>> resolvedReference = resolver.resolveFirstReference(reference, ResolutionStrategy.RESOLVE_CLOSEST);
138
139 // replace the reference
140 tokenToReference.replace(token, resolvedReference);
141
142 Collection<Token> tokens = referenceToTokens.removeAll(reference);
143 referenceToTokens.putAll(resolvedReference, tokens);
144 }
145 }
146
147 public SourceIndex remapTo(SourceRemapper.Result result) {
148 SourceIndex remapped = new SourceIndex(result.getSource());
149
150 for (Map.Entry<Entry<?>, Token> entry : declarationToToken.entrySet()) {
151 remapped.declarationToToken.put(entry.getKey(), result.getRemappedToken(entry.getValue()));
152 }
153
154 for (Map.Entry<EntryReference<Entry<?>, Entry<?>>, Collection<Token>> entry : referenceToTokens.asMap().entrySet()) {
155 EntryReference<Entry<?>, Entry<?>> reference = entry.getKey();
156 Collection<Token> oldTokens = entry.getValue();
157
158 Collection<Token> newTokens = oldTokens
159 .stream()
160 .map(result::getRemappedToken)
161 .collect(Collectors.toList());
162
163 remapped.referenceToTokens.putAll(reference, newTokens);
164 }
165
166 for (Map.Entry<Token, EntryReference<Entry<?>, Entry<?>>> entry : tokenToReference.entrySet()) {
167 remapped.tokenToReference.put(result.getRemappedToken(entry.getKey()), entry.getValue());
168 }
169
170 return remapped;
171 }
172}
diff --git a/enigma/src/main/java/cuchaz/enigma/source/SourceRemapper.java b/enigma/src/main/java/cuchaz/enigma/source/SourceRemapper.java
new file mode 100644
index 0000000..b5f8006
--- /dev/null
+++ b/enigma/src/main/java/cuchaz/enigma/source/SourceRemapper.java
@@ -0,0 +1,62 @@
1package cuchaz.enigma.source;
2
3import java.util.HashMap;
4import java.util.Map;
5
6public class SourceRemapper {
7 private final String source;
8 private final Iterable<Token> tokens;
9
10 public SourceRemapper(String source, Iterable<Token> tokens) {
11 this.source = source;
12 this.tokens = tokens;
13 }
14
15 public Result remap(Remapper remapper) {
16 StringBuffer remappedSource = new StringBuffer(source);
17 Map<Token, Token> remappedTokens = new HashMap<>();
18
19 int accumulatedOffset = 0;
20 for (Token token : tokens) {
21 Token movedToken = token.move(accumulatedOffset);
22
23 String remappedName = remapper.remap(token, movedToken);
24 if (remappedName != null) {
25 accumulatedOffset += movedToken.getRenameOffset(remappedName);
26 movedToken.rename(remappedSource, remappedName);
27 }
28
29 if (!token.equals(movedToken)) {
30 remappedTokens.put(token, movedToken);
31 }
32 }
33
34 return new Result(remappedSource.toString(), remappedTokens);
35 }
36
37 public static class Result {
38 private final String remappedSource;
39 private final Map<Token, Token> remappedTokens;
40
41 Result(String remappedSource, Map<Token, Token> remappedTokens) {
42 this.remappedSource = remappedSource;
43 this.remappedTokens = remappedTokens;
44 }
45
46 public String getSource() {
47 return remappedSource;
48 }
49
50 public Token getRemappedToken(Token token) {
51 return remappedTokens.getOrDefault(token, token);
52 }
53
54 public boolean isEmpty() {
55 return remappedTokens.isEmpty();
56 }
57 }
58
59 public interface Remapper {
60 String remap(Token token, Token movedToken);
61 }
62}
diff --git a/enigma/src/main/java/cuchaz/enigma/source/SourceSettings.java b/enigma/src/main/java/cuchaz/enigma/source/SourceSettings.java
new file mode 100644
index 0000000..f6c68e9
--- /dev/null
+++ b/enigma/src/main/java/cuchaz/enigma/source/SourceSettings.java
@@ -0,0 +1,11 @@
1package cuchaz.enigma.source;
2
3public class SourceSettings {
4 public final boolean removeImports;
5 public final boolean removeVariableFinal;
6
7 public SourceSettings(boolean removeImports, boolean removeVariableFinal) {
8 this.removeImports = removeImports;
9 this.removeVariableFinal = removeVariableFinal;
10 }
11}
diff --git a/enigma/src/main/java/cuchaz/enigma/source/Token.java b/enigma/src/main/java/cuchaz/enigma/source/Token.java
new file mode 100644
index 0000000..fad733f
--- /dev/null
+++ b/enigma/src/main/java/cuchaz/enigma/source/Token.java
@@ -0,0 +1,72 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 * <p>
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11
12package cuchaz.enigma.source;
13
14public class Token implements Comparable<Token> {
15
16 public int start;
17 public int end;
18 public String text;
19
20 public Token(int start, int end, String text) {
21 this.start = start;
22 this.end = end;
23 this.text = text;
24 }
25
26 public int getRenameOffset(String to) {
27 int length = this.end - this.start;
28 return to.length() - length;
29 }
30
31 public void rename(StringBuffer source, String to) {
32 int oldEnd = this.end;
33 this.text = to;
34 this.end = this.start + to.length();
35
36 source.replace(start, oldEnd, to);
37 }
38
39 public Token move(int offset) {
40 Token token = new Token(this.start + offset, this.end + offset, null);
41 token.text = text;
42 return token;
43 }
44
45 public boolean contains(int pos) {
46 return pos >= start && pos <= end;
47 }
48
49 @Override
50 public int compareTo(Token other) {
51 return start - other.start;
52 }
53
54 @Override
55 public boolean equals(Object other) {
56 return other instanceof Token && equals((Token) other);
57 }
58
59 @Override
60 public int hashCode() {
61 return start * 37 + end;
62 }
63
64 public boolean equals(Token other) {
65 return start == other.start && end == other.end && text.equals(other.text);
66 }
67
68 @Override
69 public String toString() {
70 return String.format("[%d,%d]", start, end);
71 }
72}
diff --git a/enigma/src/main/java/cuchaz/enigma/source/cfr/CfrDecompiler.java b/enigma/src/main/java/cuchaz/enigma/source/cfr/CfrDecompiler.java
new file mode 100644
index 0000000..9e37f16
--- /dev/null
+++ b/enigma/src/main/java/cuchaz/enigma/source/cfr/CfrDecompiler.java
@@ -0,0 +1,108 @@
1package cuchaz.enigma.source.cfr;
2
3import com.google.common.io.ByteStreams;
4import cuchaz.enigma.ClassProvider;
5import cuchaz.enigma.source.Decompiler;
6import cuchaz.enigma.source.Source;
7import cuchaz.enigma.source.SourceSettings;
8import org.benf.cfr.reader.apiunreleased.ClassFileSource2;
9import org.benf.cfr.reader.apiunreleased.JarContent;
10import org.benf.cfr.reader.bytecode.analysis.parse.utils.Pair;
11import org.benf.cfr.reader.entities.ClassFile;
12import org.benf.cfr.reader.mapping.MappingFactory;
13import org.benf.cfr.reader.mapping.ObfuscationMapping;
14import org.benf.cfr.reader.relationship.MemberNameResolver;
15import org.benf.cfr.reader.state.DCCommonState;
16import org.benf.cfr.reader.state.TypeUsageCollectingDumper;
17import org.benf.cfr.reader.util.AnalysisType;
18import org.benf.cfr.reader.util.CannotLoadClassException;
19import org.benf.cfr.reader.util.collections.ListFactory;
20import org.benf.cfr.reader.util.getopt.Options;
21import org.benf.cfr.reader.util.getopt.OptionsImpl;
22import org.objectweb.asm.ClassWriter;
23import org.objectweb.asm.tree.ClassNode;
24
25import java.io.IOException;
26import java.io.InputStream;
27import java.util.Collection;
28import java.util.HashMap;
29import java.util.Map;
30
31
32public class CfrDecompiler implements Decompiler {
33 private final DCCommonState state;
34
35 public CfrDecompiler(ClassProvider classProvider, SourceSettings sourceSettings) {
36 Map<String, String> options = new HashMap<>();
37
38 state = new DCCommonState(OptionsImpl.getFactory().create(options), new ClassFileSource2() {
39 @Override
40 public JarContent addJarContent(String s, AnalysisType analysisType) {
41 return null;
42 }
43
44 @Override
45 public void informAnalysisRelativePathDetail(String usePath, String classFilePath) {
46
47 }
48
49 @Override
50 public Collection<String> addJar(String jarPath) {
51 return null;
52 }
53
54 @Override
55 public String getPossiblyRenamedPath(String path) {
56 return path;
57 }
58
59 @Override
60 public Pair<byte[], String> getClassFileContent(String path) {
61 ClassNode node = classProvider.getClassNode(path.substring(0, path.lastIndexOf('.')));
62
63 if (node == null) {
64 try (InputStream classResource = CfrDecompiler.class.getClassLoader().getResourceAsStream(path)) {
65 if (classResource != null) {
66 return new Pair<>(ByteStreams.toByteArray(classResource), path);
67 }
68 } catch (IOException ignored) {}
69
70 return null;
71 }
72
73 ClassWriter cw = new ClassWriter(0);
74 node.accept(cw);
75 return new Pair<>(cw.toByteArray(), path);
76 }
77 });
78 }
79
80 @Override
81 public Source getSource(String className) {
82 DCCommonState state = this.state;
83 Options options = state.getOptions();
84
85 ObfuscationMapping mapping = MappingFactory.get(options, state);
86 state = new DCCommonState(state, mapping);
87 ClassFile tree = state.getClassFileMaybePath(className);
88
89 state.configureWith(tree);
90
91 // To make sure we're analysing the cached version
92 try {
93 tree = state.getClassFile(tree.getClassType());
94 } catch (CannotLoadClassException ignored) {}
95
96 if (options.getOption(OptionsImpl.DECOMPILE_INNER_CLASSES)) {
97 tree.loadInnerClasses(state);
98 }
99
100 if (options.getOption(OptionsImpl.RENAME_DUP_MEMBERS)) {
101 MemberNameResolver.resolveNames(state, ListFactory.newList(state.getClassCache().getLoadedTypes()));
102 }
103
104 TypeUsageCollectingDumper typeUsageCollector = new TypeUsageCollectingDumper(options, tree);
105 tree.analyseTop(state, typeUsageCollector);
106 return new CfrSource(tree, state, typeUsageCollector.getRealTypeUsageInformation());
107 }
108}
diff --git a/enigma/src/main/java/cuchaz/enigma/source/cfr/CfrSource.java b/enigma/src/main/java/cuchaz/enigma/source/cfr/CfrSource.java
new file mode 100644
index 0000000..d4f2da6
--- /dev/null
+++ b/enigma/src/main/java/cuchaz/enigma/source/cfr/CfrSource.java
@@ -0,0 +1,38 @@
1package cuchaz.enigma.source.cfr;
2
3import cuchaz.enigma.source.Source;
4import cuchaz.enigma.source.SourceIndex;
5import cuchaz.enigma.translation.mapping.EntryRemapper;
6import org.benf.cfr.reader.entities.ClassFile;
7import org.benf.cfr.reader.state.DCCommonState;
8import org.benf.cfr.reader.state.TypeUsageInformation;
9
10public class CfrSource implements Source {
11 private final ClassFile tree;
12 private final SourceIndex index;
13 private final String string;
14
15 public CfrSource(ClassFile tree, DCCommonState state, TypeUsageInformation typeUsages) {
16 this.tree = tree;
17
18 EnigmaDumper dumper = new EnigmaDumper(typeUsages);
19 tree.dump(state.getObfuscationMapping().wrap(dumper));
20 index = dumper.getIndex();
21 string = dumper.getString();
22 }
23
24 @Override
25 public String asString() {
26 return string;
27 }
28
29 @Override
30 public Source addJavadocs(EntryRemapper remapper) {
31 return this; // TODO
32 }
33
34 @Override
35 public SourceIndex index() {
36 return index;
37 }
38}
diff --git a/enigma/src/main/java/cuchaz/enigma/source/cfr/EnigmaDumper.java b/enigma/src/main/java/cuchaz/enigma/source/cfr/EnigmaDumper.java
new file mode 100644
index 0000000..e05a6ff
--- /dev/null
+++ b/enigma/src/main/java/cuchaz/enigma/source/cfr/EnigmaDumper.java
@@ -0,0 +1,433 @@
1package cuchaz.enigma.source.cfr;
2
3import cuchaz.enigma.source.Token;
4import cuchaz.enigma.source.SourceIndex;
5import cuchaz.enigma.translation.representation.MethodDescriptor;
6import cuchaz.enigma.translation.representation.TypeDescriptor;
7import cuchaz.enigma.translation.representation.entry.*;
8import org.benf.cfr.reader.bytecode.analysis.types.*;
9import org.benf.cfr.reader.bytecode.analysis.variables.NamedVariable;
10import org.benf.cfr.reader.entities.Field;
11import org.benf.cfr.reader.entities.Method;
12import org.benf.cfr.reader.mapping.NullMapping;
13import org.benf.cfr.reader.mapping.ObfuscationMapping;
14import org.benf.cfr.reader.state.TypeUsageInformation;
15import org.benf.cfr.reader.util.collections.SetFactory;
16import org.benf.cfr.reader.util.output.DelegatingDumper;
17import org.benf.cfr.reader.util.output.Dumpable;
18import org.benf.cfr.reader.util.output.Dumper;
19import org.benf.cfr.reader.util.output.TypeContext;
20
21import java.util.Set;
22import java.util.stream.Collectors;
23
24public class EnigmaDumper implements Dumper {
25 private int outputCount = 0;
26 private int indent;
27 private boolean atStart = true;
28 private boolean pendingCR = false;
29 private final StringBuilder sb = new StringBuilder();
30 private final TypeUsageInformation typeUsageInformation;
31 private final Set<JavaTypeInstance> emitted = SetFactory.newSet();
32 private final SourceIndex index = new SourceIndex();
33 private int position;
34
35
36 public EnigmaDumper(TypeUsageInformation typeUsageInformation) {
37 this.typeUsageInformation = typeUsageInformation;
38 }
39
40 private void append(String s) {
41 sb.append(s);
42 position += s.length();
43 }
44
45 private String getDesc(JavaTypeInstance type) {
46 if (!type.isUsableType() && type != RawJavaType.VOID) {
47 throw new IllegalArgumentException(type.toString());
48 }
49
50 if (type instanceof JavaGenericBaseInstance) {
51 return getDesc(type.getDeGenerifiedType());
52 }
53
54 if (type instanceof JavaRefTypeInstance) {
55 return "L" + type.getRawName().replace('.', '/') + ";";
56 }
57
58 if (type instanceof JavaArrayTypeInstance) {
59 return "[" + getDesc(((JavaArrayTypeInstance) type).removeAnArrayIndirection());
60 }
61
62 if (type instanceof RawJavaType) {
63 switch ((RawJavaType) type) {
64 case BOOLEAN:
65 return "Z";
66 case BYTE:
67 return "B";
68 case CHAR:
69 return "C";
70 case SHORT:
71 return "S";
72 case INT:
73 return "I";
74 case LONG:
75 return "J";
76 case FLOAT:
77 return "F";
78 case DOUBLE:
79 return "D";
80 case VOID:
81 return "V";
82 default:
83 throw new AssertionError();
84 }
85 }
86
87 throw new AssertionError();
88 }
89
90 private MethodEntry getMethodEntry(MethodPrototype method) {
91 if (method == null || method.getClassType() == null) {
92 return null;
93 }
94
95 MethodDescriptor desc = new MethodDescriptor(
96 method.getArgs().stream().map(type -> new TypeDescriptor(getDesc(type))).collect(Collectors.toList()),
97 new TypeDescriptor(method.getName().equals("<init>") || method.getName().equals("<clinit>") ? "V" : getDesc(method.getReturnType()))
98 );
99
100 return new MethodEntry(getClassEntry(method.getClassType()), method.getName(), desc);
101 }
102
103 private LocalVariableEntry getParameterEntry(MethodPrototype method, int parameterIndex, String name) {
104 int variableIndex = method.isInstanceMethod() ? 1 : 0;
105 for (int i = 0; i < parameterIndex; i++) {
106 variableIndex += method.getArgs().get(i).getStackType().getComputationCategory();
107 }
108
109 return new LocalVariableEntry(getMethodEntry(method), variableIndex, name, true, null);
110 }
111
112 private FieldEntry getFieldEntry(JavaTypeInstance owner, String name, JavaTypeInstance type) {
113 return new FieldEntry(getClassEntry(owner), name, new TypeDescriptor(getDesc(type)));
114 }
115
116 private ClassEntry getClassEntry(JavaTypeInstance type) {
117 return new ClassEntry(type.getRawName().replace('.', '/'));
118 }
119
120 @Override
121 public Dumper beginBlockComment(boolean inline) {
122 print("/*").newln();
123 return this;
124 }
125
126 @Override
127 public Dumper endBlockComment() {
128 print(" */").newln();
129 return this;
130 }
131
132 @Override
133 public Dumper label(String s, boolean inline) {
134 processPendingCR();
135 append(s);
136 append(":");
137 return this;
138 }
139
140 @Override
141 public Dumper comment(String s) {
142 append("// ");
143 append(s);
144 append("\n");
145 return this;
146 }
147
148 @Override
149 public void enqueuePendingCarriageReturn() {
150 pendingCR = true;
151 }
152
153 @Override
154 public Dumper removePendingCarriageReturn() {
155 pendingCR = false;
156 return this;
157 }
158
159 private void processPendingCR() {
160 if (pendingCR) {
161 append("\n");
162 atStart = true;
163 pendingCR = false;
164 }
165 }
166
167 @Override
168 public Dumper identifier(String s, Object ref, boolean defines) {
169 return print(s);
170 }
171
172 @Override
173 public Dumper methodName(String name, MethodPrototype method, boolean special, boolean defines) {
174 doIndent();
175 Token token = new Token(position, position + name.length(), name);
176 Entry<?> entry = getMethodEntry(method);
177
178 if (entry != null) {
179 if (defines) {
180 index.addDeclaration(token, entry);
181 } else {
182 index.addReference(token, entry, null);
183 }
184 }
185
186 return identifier(name, null, defines);
187 }
188
189 @Override
190 public Dumper parameterName(String name, MethodPrototype method, int index, boolean defines) {
191 doIndent();
192 Token token = new Token(position, position + name.length(), name);
193 Entry<?> entry = getParameterEntry(method, index, name);
194
195 if (entry != null) {
196 if (defines) {
197 this.index.addDeclaration(token, entry);
198 } else {
199 this.index.addReference(token, entry, null);
200 }
201 }
202
203 return identifier(name, null, defines);
204 }
205
206 @Override
207 public Dumper variableName(String name, NamedVariable variable, boolean defines) {
208 return identifier(name, null, defines);
209 }
210
211 @Override
212 public Dumper packageName(JavaRefTypeInstance t) {
213 String s = t.getPackageName();
214
215 if (!s.isEmpty()) {
216 keyword("package ").print(s).endCodeln().newln();
217 }
218
219 return this;
220 }
221
222 @Override
223 public Dumper fieldName(String name, Field field, JavaTypeInstance owner, boolean hiddenDeclaration, boolean defines) {
224 doIndent();
225 Token token = new Token(position, position + name.length(), name);
226 Entry<?> entry = field == null ? null : getFieldEntry(owner, name, field.getJavaTypeInstance());
227
228 if (entry != null) {
229 if (defines) {
230 index.addDeclaration(token, entry);
231 } else {
232 index.addReference(token, entry, null);
233 }
234 }
235
236 identifier(name, null, defines);
237 return this;
238 }
239
240 @Override
241 public Dumper print(String s) {
242 processPendingCR();
243 doIndent();
244 append(s);
245 atStart = s.endsWith("\n");
246 outputCount++;
247 return this;
248 }
249
250 @Override
251 public Dumper print(char c) {
252 return print(String.valueOf(c));
253 }
254
255 @Override
256 public Dumper newln() {
257 append("\n");
258 atStart = true;
259 outputCount++;
260 return this;
261 }
262
263 @Override
264 public Dumper endCodeln() {
265 append(";\n");
266 atStart = true;
267 outputCount++;
268 return this;
269 }
270
271 @Override
272 public Dumper keyword(String s) {
273 print(s);
274 return this;
275 }
276
277 @Override
278 public Dumper operator(String s) {
279 print(s);
280 return this;
281 }
282
283 @Override
284 public Dumper separator(String s) {
285 print(s);
286 return this;
287 }
288
289 @Override
290 public Dumper literal(String s, Object o) {
291 print(s);
292 return this;
293 }
294
295 private void doIndent() {
296 if (!atStart) return;
297 String indents = " ";
298
299 for (int x = 0; x < indent; ++x) {
300 append(indents);
301 }
302
303 atStart = false;
304 }
305
306 @Override
307 public void indent(int diff) {
308 indent += diff;
309 }
310
311 @Override
312 public Dumper dump(Dumpable d) {
313 if (d == null) {
314 keyword("null");
315 return this;
316 }
317
318 d.dump(this);
319 return this;
320 }
321
322 @Override
323 public TypeUsageInformation getTypeUsageInformation() {
324 return typeUsageInformation;
325 }
326
327 @Override
328 public ObfuscationMapping getObfuscationMapping() {
329 return NullMapping.INSTANCE;
330 }
331
332 @Override
333 public String toString() {
334 return sb.toString();
335 }
336
337 @Override
338 public void addSummaryError(Method method, String s) {}
339
340 @Override
341 public void close() {
342 }
343
344 @Override
345 public boolean canEmitClass(JavaTypeInstance type) {
346 return emitted.add(type);
347 }
348
349 @Override
350 public int getOutputCount() {
351 return outputCount;
352 }
353
354 @Override
355 public Dumper dump(JavaTypeInstance type) {
356 return dump(type, TypeContext.None, false);
357 }
358
359 @Override
360 public Dumper dump(JavaTypeInstance type, boolean defines) {
361 return dump(type, TypeContext.None, false);
362 }
363
364 @Override
365 public Dumper dump(JavaTypeInstance type, TypeContext context) {
366 return dump(type, context, false);
367 }
368
369 private Dumper dump(JavaTypeInstance type, TypeContext context, boolean defines) {
370 doIndent();
371 if (type instanceof JavaRefTypeInstance) {
372 int start = position;
373 type.dumpInto(this, typeUsageInformation, TypeContext.None);
374 int end = position;
375 Token token = new Token(start, end, sb.toString().substring(start, end));
376
377 if (defines) {
378 index.addDeclaration(token, getClassEntry(type));
379 } else {
380 index.addReference(token, getClassEntry(type), null);
381 }
382
383 return this;
384 }
385
386 type.dumpInto(this, typeUsageInformation, context);
387 return this;
388 }
389
390 @Override
391 public Dumper withTypeUsageInformation(TypeUsageInformation innerclassTypeUsageInformation) {
392 return new WithTypeUsageInformationDumper(this, innerclassTypeUsageInformation);
393 }
394
395 public SourceIndex getIndex() {
396 index.setSource(getString());
397 return index;
398 }
399
400 public String getString() {
401 return sb.toString();
402 }
403
404 public static class WithTypeUsageInformationDumper extends DelegatingDumper {
405 private final TypeUsageInformation typeUsageInformation;
406
407 WithTypeUsageInformationDumper(Dumper delegate, TypeUsageInformation typeUsageInformation) {
408 super(delegate);
409 this.typeUsageInformation = typeUsageInformation;
410 }
411
412 @Override
413 public TypeUsageInformation getTypeUsageInformation() {
414 return typeUsageInformation;
415 }
416
417 @Override
418 public Dumper dump(JavaTypeInstance javaTypeInstance) {
419 return dump(javaTypeInstance, TypeContext.None);
420 }
421
422 @Override
423 public Dumper dump(JavaTypeInstance javaTypeInstance, TypeContext typeContext) {
424 javaTypeInstance.dumpInto(this, typeUsageInformation, typeContext);
425 return this;
426 }
427
428 @Override
429 public Dumper withTypeUsageInformation(TypeUsageInformation innerclassTypeUsageInformation) {
430 return new WithTypeUsageInformationDumper(delegate, innerclassTypeUsageInformation);
431 }
432 }
433}
diff --git a/enigma/src/main/java/cuchaz/enigma/source/procyon/EntryParser.java b/enigma/src/main/java/cuchaz/enigma/source/procyon/EntryParser.java
new file mode 100644
index 0000000..2fae61a
--- /dev/null
+++ b/enigma/src/main/java/cuchaz/enigma/source/procyon/EntryParser.java
@@ -0,0 +1,49 @@
1package cuchaz.enigma.source.procyon;
2
3import com.strobel.assembler.metadata.FieldDefinition;
4import com.strobel.assembler.metadata.MethodDefinition;
5import com.strobel.assembler.metadata.TypeDefinition;
6import com.strobel.assembler.metadata.TypeReference;
7import cuchaz.enigma.translation.representation.AccessFlags;
8import cuchaz.enigma.translation.representation.MethodDescriptor;
9import cuchaz.enigma.translation.representation.Signature;
10import cuchaz.enigma.translation.representation.TypeDescriptor;
11import cuchaz.enigma.translation.representation.entry.ClassDefEntry;
12import cuchaz.enigma.translation.representation.entry.ClassEntry;
13import cuchaz.enigma.translation.representation.entry.FieldDefEntry;
14import cuchaz.enigma.translation.representation.entry.MethodDefEntry;
15
16public class EntryParser {
17 public static FieldDefEntry parse(FieldDefinition definition) {
18 ClassEntry owner = parse(definition.getDeclaringType());
19 TypeDescriptor descriptor = new TypeDescriptor(definition.getErasedSignature());
20 Signature signature = Signature.createTypedSignature(definition.getSignature());
21 AccessFlags access = new AccessFlags(definition.getModifiers());
22 return new FieldDefEntry(owner, definition.getName(), descriptor, signature, access, null);
23 }
24
25 public static ClassDefEntry parse(TypeDefinition def) {
26 String name = def.getInternalName();
27 Signature signature = Signature.createSignature(def.getSignature());
28 AccessFlags access = new AccessFlags(def.getModifiers());
29 ClassEntry superClass = def.getBaseType() != null ? parse(def.getBaseType()) : null;
30 ClassEntry[] interfaces = def.getExplicitInterfaces().stream().map(EntryParser::parse).toArray(ClassEntry[]::new);
31 return new ClassDefEntry(name, signature, access, superClass, interfaces);
32 }
33
34 public static ClassEntry parse(TypeReference typeReference) {
35 return new ClassEntry(typeReference.getInternalName());
36 }
37
38 public static MethodDefEntry parse(MethodDefinition definition) {
39 ClassEntry classEntry = parse(definition.getDeclaringType());
40 MethodDescriptor descriptor = new MethodDescriptor(definition.getErasedSignature());
41 Signature signature = Signature.createSignature(definition.getSignature());
42 AccessFlags access = new AccessFlags(definition.getModifiers());
43 return new MethodDefEntry(classEntry, definition.getName(), descriptor, signature, access, null);
44 }
45
46 public static TypeDescriptor parseTypeDescriptor(TypeReference type) {
47 return new TypeDescriptor(type.getErasedSignature());
48 }
49}
diff --git a/enigma/src/main/java/cuchaz/enigma/source/procyon/ProcyonDecompiler.java b/enigma/src/main/java/cuchaz/enigma/source/procyon/ProcyonDecompiler.java
new file mode 100644
index 0000000..3cbd680
--- /dev/null
+++ b/enigma/src/main/java/cuchaz/enigma/source/procyon/ProcyonDecompiler.java
@@ -0,0 +1,84 @@
1package cuchaz.enigma.source.procyon;
2
3import com.strobel.assembler.metadata.ITypeLoader;
4import com.strobel.assembler.metadata.MetadataSystem;
5import com.strobel.assembler.metadata.TypeDefinition;
6import com.strobel.assembler.metadata.TypeReference;
7import com.strobel.decompiler.DecompilerContext;
8import com.strobel.decompiler.DecompilerSettings;
9import com.strobel.decompiler.languages.java.BraceStyle;
10import com.strobel.decompiler.languages.java.JavaFormattingOptions;
11import com.strobel.decompiler.languages.java.ast.AstBuilder;
12import com.strobel.decompiler.languages.java.ast.CompilationUnit;
13import com.strobel.decompiler.languages.java.ast.InsertParenthesesVisitor;
14import cuchaz.enigma.ClassProvider;
15import cuchaz.enigma.source.Source;
16import cuchaz.enigma.source.Decompiler;
17import cuchaz.enigma.source.SourceSettings;
18import cuchaz.enigma.source.procyon.transformers.*;
19import cuchaz.enigma.source.procyon.typeloader.CompiledSourceTypeLoader;
20import cuchaz.enigma.source.procyon.typeloader.NoRetryMetadataSystem;
21import cuchaz.enigma.source.procyon.typeloader.SynchronizedTypeLoader;
22
23public class ProcyonDecompiler implements Decompiler {
24 private final SourceSettings settings;
25 private final DecompilerSettings decompilerSettings;
26 private final MetadataSystem metadataSystem;
27
28 public ProcyonDecompiler(ClassProvider classProvider, SourceSettings settings) {
29 ITypeLoader typeLoader = new SynchronizedTypeLoader(new CompiledSourceTypeLoader(classProvider));
30
31 metadataSystem = new NoRetryMetadataSystem(typeLoader);
32 metadataSystem.setEagerMethodLoadingEnabled(true);
33
34 decompilerSettings = DecompilerSettings.javaDefaults();
35 decompilerSettings.setMergeVariables(getSystemPropertyAsBoolean("enigma.mergeVariables", true));
36 decompilerSettings.setForceExplicitImports(getSystemPropertyAsBoolean("enigma.forceExplicitImports", true));
37 decompilerSettings.setForceExplicitTypeArguments(getSystemPropertyAsBoolean("enigma.forceExplicitTypeArguments", true));
38 decompilerSettings.setShowDebugLineNumbers(getSystemPropertyAsBoolean("enigma.showDebugLineNumbers", false));
39 decompilerSettings.setShowSyntheticMembers(getSystemPropertyAsBoolean("enigma.showSyntheticMembers", false));
40 decompilerSettings.setTypeLoader(typeLoader);
41
42 JavaFormattingOptions formattingOptions = decompilerSettings.getJavaFormattingOptions();
43 formattingOptions.ClassBraceStyle = BraceStyle.EndOfLine;
44 formattingOptions.InterfaceBraceStyle = BraceStyle.EndOfLine;
45 formattingOptions.EnumBraceStyle = BraceStyle.EndOfLine;
46
47 this.settings = settings;
48 }
49
50 @Override
51 public Source getSource(String className) {
52 TypeReference type = metadataSystem.lookupType(className);
53 if (type == null) {
54 throw new Error(String.format("Unable to find desc: %s", className));
55 }
56
57 TypeDefinition resolvedType = type.resolve();
58
59 DecompilerContext context = new DecompilerContext();
60 context.setCurrentType(resolvedType);
61 context.setSettings(decompilerSettings);
62
63 AstBuilder builder = new AstBuilder(context);
64 builder.addType(resolvedType);
65 builder.runTransformations(null);
66 CompilationUnit source = builder.getCompilationUnit();
67
68 new ObfuscatedEnumSwitchRewriterTransform(context).run(source);
69 new VarargsFixer(context).run(source);
70 new RemoveObjectCasts(context).run(source);
71 new Java8Generics().run(source);
72 new InvalidIdentifierFix().run(source);
73 if (settings.removeImports) DropImportAstTransform.INSTANCE.run(source);
74 if (settings.removeVariableFinal) DropVarModifiersAstTransform.INSTANCE.run(source);
75 source.acceptVisitor(new InsertParenthesesVisitor(), null);
76
77 return new ProcyonSource(source, decompilerSettings);
78 }
79
80 private static boolean getSystemPropertyAsBoolean(String property, boolean defValue) {
81 String value = System.getProperty(property);
82 return value == null ? defValue : Boolean.parseBoolean(value);
83 }
84}
diff --git a/enigma/src/main/java/cuchaz/enigma/source/procyon/ProcyonSource.java b/enigma/src/main/java/cuchaz/enigma/source/procyon/ProcyonSource.java
new file mode 100644
index 0000000..53c8c70
--- /dev/null
+++ b/enigma/src/main/java/cuchaz/enigma/source/procyon/ProcyonSource.java
@@ -0,0 +1,49 @@
1package cuchaz.enigma.source.procyon;
2
3import com.strobel.decompiler.DecompilerSettings;
4import com.strobel.decompiler.PlainTextOutput;
5import com.strobel.decompiler.languages.java.JavaOutputVisitor;
6import com.strobel.decompiler.languages.java.ast.CompilationUnit;
7import cuchaz.enigma.source.Source;
8import cuchaz.enigma.source.SourceIndex;
9import cuchaz.enigma.source.procyon.index.SourceIndexVisitor;
10import cuchaz.enigma.source.procyon.transformers.AddJavadocsAstTransform;
11import cuchaz.enigma.translation.mapping.EntryRemapper;
12
13import java.io.StringWriter;
14
15public class ProcyonSource implements Source {
16 private final DecompilerSettings settings;
17 private final CompilationUnit tree;
18 private String string;
19
20 public ProcyonSource(CompilationUnit tree, DecompilerSettings settings) {
21 this.settings = settings;
22 this.tree = tree;
23 }
24
25 @Override
26 public SourceIndex index() {
27 SourceIndex index = new SourceIndex(asString());
28 tree.acceptVisitor(new SourceIndexVisitor(), index);
29 return index;
30 }
31
32 @Override
33 public String asString() {
34 if (string == null) {
35 StringWriter writer = new StringWriter();
36 tree.acceptVisitor(new JavaOutputVisitor(new PlainTextOutput(writer), settings), null);
37 string = writer.toString();
38 }
39
40 return string;
41 }
42
43 @Override
44 public Source addJavadocs(EntryRemapper remapper) {
45 CompilationUnit remappedTree = (CompilationUnit) tree.clone();
46 new AddJavadocsAstTransform(remapper).run(remappedTree);
47 return new ProcyonSource(remappedTree, settings);
48 }
49}
diff --git a/enigma/src/main/java/cuchaz/enigma/source/procyon/index/SourceIndexClassVisitor.java b/enigma/src/main/java/cuchaz/enigma/source/procyon/index/SourceIndexClassVisitor.java
new file mode 100644
index 0000000..f6eeb15
--- /dev/null
+++ b/enigma/src/main/java/cuchaz/enigma/source/procyon/index/SourceIndexClassVisitor.java
@@ -0,0 +1,95 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 * <p>
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11
12package cuchaz.enigma.source.procyon.index;
13
14import com.strobel.assembler.metadata.FieldDefinition;
15import com.strobel.assembler.metadata.MethodDefinition;
16import com.strobel.assembler.metadata.TypeDefinition;
17import com.strobel.assembler.metadata.TypeReference;
18import com.strobel.decompiler.languages.TextLocation;
19import com.strobel.decompiler.languages.java.ast.*;
20import cuchaz.enigma.source.SourceIndex;
21import cuchaz.enigma.source.procyon.EntryParser;
22import cuchaz.enigma.translation.representation.entry.*;
23
24public class SourceIndexClassVisitor extends SourceIndexVisitor {
25 private ClassDefEntry classEntry;
26
27 public SourceIndexClassVisitor(ClassDefEntry classEntry) {
28 this.classEntry = classEntry;
29 }
30
31 @Override
32 public Void visitTypeDeclaration(TypeDeclaration node, SourceIndex index) {
33 // is this this class, or a subtype?
34 TypeDefinition def = node.getUserData(Keys.TYPE_DEFINITION);
35 ClassDefEntry classEntry = EntryParser.parse(def);
36 if (!classEntry.equals(this.classEntry)) {
37 // it's a subtype, recurse
38 index.addDeclaration(TokenFactory.createToken(index, node.getNameToken()), classEntry);
39 return node.acceptVisitor(new SourceIndexClassVisitor(classEntry), index);
40 }
41
42 return visitChildren(node, index);
43 }
44
45 @Override
46 public Void visitSimpleType(SimpleType node, SourceIndex index) {
47 TypeReference ref = node.getUserData(Keys.TYPE_REFERENCE);
48 if (node.getIdentifierToken().getStartLocation() != TextLocation.EMPTY) {
49 ClassEntry classEntry = new ClassEntry(ref.getInternalName());
50 index.addReference(TokenFactory.createToken(index, node.getIdentifierToken()), classEntry, this.classEntry);
51 }
52
53 return visitChildren(node, index);
54 }
55
56 @Override
57 public Void visitMethodDeclaration(MethodDeclaration node, SourceIndex index) {
58 MethodDefinition def = node.getUserData(Keys.METHOD_DEFINITION);
59 MethodDefEntry methodEntry = EntryParser.parse(def);
60 AstNode tokenNode = node.getNameToken();
61 if (methodEntry.isConstructor() && methodEntry.getName().equals("<clinit>")) {
62 // for static initializers, check elsewhere for the token node
63 tokenNode = node.getModifiers().firstOrNullObject();
64 }
65 index.addDeclaration(TokenFactory.createToken(index, tokenNode), methodEntry);
66 return node.acceptVisitor(new SourceIndexMethodVisitor(methodEntry), index);
67 }
68
69 @Override
70 public Void visitConstructorDeclaration(ConstructorDeclaration node, SourceIndex index) {
71 MethodDefinition def = node.getUserData(Keys.METHOD_DEFINITION);
72 MethodDefEntry methodEntry = EntryParser.parse(def);
73 index.addDeclaration(TokenFactory.createToken(index, node.getNameToken()), methodEntry);
74 return node.acceptVisitor(new SourceIndexMethodVisitor(methodEntry), index);
75 }
76
77 @Override
78 public Void visitFieldDeclaration(FieldDeclaration node, SourceIndex index) {
79 FieldDefinition def = node.getUserData(Keys.FIELD_DEFINITION);
80 FieldDefEntry fieldEntry = EntryParser.parse(def);
81 assert (node.getVariables().size() == 1);
82 VariableInitializer variable = node.getVariables().firstOrNullObject();
83 index.addDeclaration(TokenFactory.createToken(index, variable.getNameToken()), fieldEntry);
84 return visitChildren(node, index);
85 }
86
87 @Override
88 public Void visitEnumValueDeclaration(EnumValueDeclaration node, SourceIndex index) {
89 // treat enum declarations as field declarations
90 FieldDefinition def = node.getUserData(Keys.FIELD_DEFINITION);
91 FieldDefEntry fieldEntry = EntryParser.parse(def);
92 index.addDeclaration(TokenFactory.createToken(index, node.getNameToken()), fieldEntry);
93 return visitChildren(node, index);
94 }
95}
diff --git a/enigma/src/main/java/cuchaz/enigma/source/procyon/index/SourceIndexMethodVisitor.java b/enigma/src/main/java/cuchaz/enigma/source/procyon/index/SourceIndexMethodVisitor.java
new file mode 100644
index 0000000..0e8bc51
--- /dev/null
+++ b/enigma/src/main/java/cuchaz/enigma/source/procyon/index/SourceIndexMethodVisitor.java
@@ -0,0 +1,218 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 * <p>
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11
12package cuchaz.enigma.source.procyon.index;
13
14import com.google.common.collect.HashMultimap;
15import com.google.common.collect.Multimap;
16import com.strobel.assembler.metadata.*;
17import com.strobel.decompiler.ast.Variable;
18import com.strobel.decompiler.languages.TextLocation;
19import com.strobel.decompiler.languages.java.ast.*;
20import cuchaz.enigma.source.SourceIndex;
21import cuchaz.enigma.source.procyon.EntryParser;
22import cuchaz.enigma.translation.representation.MethodDescriptor;
23import cuchaz.enigma.translation.representation.TypeDescriptor;
24import cuchaz.enigma.translation.representation.entry.*;
25
26import java.lang.Error;
27import java.util.HashMap;
28import java.util.Map;
29
30public class SourceIndexMethodVisitor extends SourceIndexVisitor {
31 private final MethodDefEntry methodEntry;
32
33 private Multimap<String, Identifier> unmatchedIdentifier = HashMultimap.create();
34 private Map<String, Entry<?>> identifierEntryCache = new HashMap<>();
35
36 public SourceIndexMethodVisitor(MethodDefEntry methodEntry) {
37 this.methodEntry = methodEntry;
38 }
39
40 @Override
41 public Void visitInvocationExpression(InvocationExpression node, SourceIndex index) {
42 MemberReference ref = node.getUserData(Keys.MEMBER_REFERENCE);
43
44 // get the behavior entry
45 ClassEntry classEntry = new ClassEntry(ref.getDeclaringType().getInternalName());
46 MethodEntry methodEntry = null;
47 if (ref instanceof MethodReference) {
48 methodEntry = new MethodEntry(classEntry, ref.getName(), new MethodDescriptor(ref.getErasedSignature()));
49 }
50 if (methodEntry != null) {
51 // get the node for the token
52 AstNode tokenNode = null;
53 if (node.getTarget() instanceof MemberReferenceExpression) {
54 tokenNode = ((MemberReferenceExpression) node.getTarget()).getMemberNameToken();
55 } else if (node.getTarget() instanceof SuperReferenceExpression) {
56 tokenNode = node.getTarget();
57 } else if (node.getTarget() instanceof ThisReferenceExpression) {
58 tokenNode = node.getTarget();
59 }
60 if (tokenNode != null) {
61 index.addReference(TokenFactory.createToken(index, tokenNode), methodEntry, this.methodEntry);
62 }
63 }
64
65 // Check for identifier
66 node.getArguments().stream().filter(expression -> expression instanceof IdentifierExpression)
67 .forEach(expression -> this.checkIdentifier((IdentifierExpression) expression, index));
68 return visitChildren(node, index);
69 }
70
71 @Override
72 public Void visitMemberReferenceExpression(MemberReferenceExpression node, SourceIndex index) {
73 MemberReference ref = node.getUserData(Keys.MEMBER_REFERENCE);
74 if (ref instanceof FieldReference) {
75 // make sure this is actually a field
76 String erasedSignature = ref.getErasedSignature();
77 if (erasedSignature.indexOf('(') >= 0) {
78 throw new Error("Expected a field here! got " + ref);
79 }
80
81 ClassEntry classEntry = new ClassEntry(ref.getDeclaringType().getInternalName());
82 FieldEntry fieldEntry = new FieldEntry(classEntry, ref.getName(), new TypeDescriptor(erasedSignature));
83 index.addReference(TokenFactory.createToken(index, node.getMemberNameToken()), fieldEntry, this.methodEntry);
84 }
85
86 return visitChildren(node, index);
87 }
88
89 @Override
90 public Void visitSimpleType(SimpleType node, SourceIndex index) {
91 TypeReference ref = node.getUserData(Keys.TYPE_REFERENCE);
92 if (node.getIdentifierToken().getStartLocation() != TextLocation.EMPTY) {
93 ClassEntry classEntry = new ClassEntry(ref.getInternalName());
94 index.addReference(TokenFactory.createToken(index, node.getIdentifierToken()), classEntry, this.methodEntry);
95 }
96
97 return visitChildren(node, index);
98 }
99
100 @Override
101 public Void visitParameterDeclaration(ParameterDeclaration node, SourceIndex index) {
102 ParameterDefinition def = node.getUserData(Keys.PARAMETER_DEFINITION);
103 int parameterIndex = def.getSlot();
104
105 if (parameterIndex >= 0) {
106 MethodDefEntry ownerMethod = methodEntry;
107 if (def.getMethod() instanceof MethodDefinition) {
108 ownerMethod = EntryParser.parse((MethodDefinition) def.getMethod());
109 }
110
111 TypeDescriptor parameterType = EntryParser.parseTypeDescriptor(def.getParameterType());
112 LocalVariableDefEntry localVariableEntry = new LocalVariableDefEntry(ownerMethod, parameterIndex, node.getName(), true, parameterType, null);
113 Identifier identifier = node.getNameToken();
114 // cache the argument entry and the identifier
115 identifierEntryCache.put(identifier.getName(), localVariableEntry);
116 index.addDeclaration(TokenFactory.createToken(index, identifier), localVariableEntry);
117 }
118
119 return visitChildren(node, index);
120 }
121
122 @Override
123 public Void visitIdentifierExpression(IdentifierExpression node, SourceIndex index) {
124 MemberReference ref = node.getUserData(Keys.MEMBER_REFERENCE);
125 if (ref != null) {
126 ClassEntry classEntry = new ClassEntry(ref.getDeclaringType().getInternalName());
127 FieldEntry fieldEntry = new FieldEntry(classEntry, ref.getName(), new TypeDescriptor(ref.getErasedSignature()));
128 index.addReference(TokenFactory.createToken(index, node.getIdentifierToken()), fieldEntry, this.methodEntry);
129 } else
130 this.checkIdentifier(node, index);
131 return visitChildren(node, index);
132 }
133
134 private void checkIdentifier(IdentifierExpression node, SourceIndex index) {
135 if (identifierEntryCache.containsKey(node.getIdentifier())) // If it's in the argument cache, create a token!
136 index.addDeclaration(TokenFactory.createToken(index, node.getIdentifierToken()), identifierEntryCache.get(node.getIdentifier()));
137 else
138 unmatchedIdentifier.put(node.getIdentifier(), node.getIdentifierToken()); // Not matched actually, put it!
139 }
140
141 private void addDeclarationToUnmatched(String key, SourceIndex index) {
142 Entry<?> entry = identifierEntryCache.get(key);
143
144 // This cannot happened in theory
145 if (entry == null)
146 return;
147 for (Identifier identifier : unmatchedIdentifier.get(key))
148 index.addDeclaration(TokenFactory.createToken(index, identifier), entry);
149 unmatchedIdentifier.removeAll(key);
150 }
151
152 @Override
153 public Void visitObjectCreationExpression(ObjectCreationExpression node, SourceIndex index) {
154 MemberReference ref = node.getUserData(Keys.MEMBER_REFERENCE);
155 if (ref != null && node.getType() instanceof SimpleType) {
156 SimpleType simpleTypeNode = (SimpleType) node.getType();
157 ClassEntry classEntry = new ClassEntry(ref.getDeclaringType().getInternalName());
158 MethodEntry constructorEntry = new MethodEntry(classEntry, "<init>", new MethodDescriptor(ref.getErasedSignature()));
159 index.addReference(TokenFactory.createToken(index, simpleTypeNode.getIdentifierToken()), constructorEntry, this.methodEntry);
160 }
161
162 return visitChildren(node, index);
163 }
164
165 @Override
166 public Void visitVariableDeclaration(VariableDeclarationStatement node, SourceIndex index) {
167 AstNodeCollection<VariableInitializer> variables = node.getVariables();
168
169 // Single assignation
170 if (variables.size() == 1) {
171 VariableInitializer initializer = variables.firstOrNullObject();
172 if (initializer != null && node.getType() instanceof SimpleType) {
173 Identifier identifier = initializer.getNameToken();
174 Variable variable = initializer.getUserData(Keys.VARIABLE);
175 if (variable != null) {
176 VariableDefinition originalVariable = variable.getOriginalVariable();
177 if (originalVariable != null) {
178 int variableIndex = originalVariable.getSlot();
179 if (variableIndex >= 0) {
180 MethodDefEntry ownerMethod = EntryParser.parse(originalVariable.getDeclaringMethod());
181 TypeDescriptor variableType = EntryParser.parseTypeDescriptor(originalVariable.getVariableType());
182 LocalVariableDefEntry localVariableEntry = new LocalVariableDefEntry(ownerMethod, variableIndex, initializer.getName(), false, variableType, null);
183 identifierEntryCache.put(identifier.getName(), localVariableEntry);
184 addDeclarationToUnmatched(identifier.getName(), index);
185 index.addDeclaration(TokenFactory.createToken(index, identifier), localVariableEntry);
186 }
187 }
188 }
189 }
190 }
191 return visitChildren(node, index);
192 }
193
194 @Override
195 public Void visitMethodGroupExpression(MethodGroupExpression node, SourceIndex index) {
196 MemberReference ref = node.getUserData(Keys.MEMBER_REFERENCE);
197
198 if (ref instanceof MethodReference) {
199 // get the behavior entry
200 ClassEntry classEntry = new ClassEntry(ref.getDeclaringType().getInternalName());
201 MethodEntry methodEntry = new MethodEntry(classEntry, ref.getName(), new MethodDescriptor(ref.getErasedSignature()));
202
203 // get the node for the token
204 AstNode methodNameToken = node.getMethodNameToken();
205 AstNode targetToken = node.getTarget();
206
207 if (methodNameToken != null) {
208 index.addReference(TokenFactory.createToken(index, methodNameToken), methodEntry, this.methodEntry);
209 }
210
211 if (targetToken != null && !(targetToken instanceof ThisReferenceExpression)) {
212 index.addReference(TokenFactory.createToken(index, targetToken), methodEntry.getParent(), this.methodEntry);
213 }
214 }
215
216 return visitChildren(node, index);
217 }
218}
diff --git a/enigma/src/main/java/cuchaz/enigma/source/procyon/index/SourceIndexVisitor.java b/enigma/src/main/java/cuchaz/enigma/source/procyon/index/SourceIndexVisitor.java
new file mode 100644
index 0000000..dad505f
--- /dev/null
+++ b/enigma/src/main/java/cuchaz/enigma/source/procyon/index/SourceIndexVisitor.java
@@ -0,0 +1,40 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 * <p>
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11
12package cuchaz.enigma.source.procyon.index;
13
14import com.strobel.assembler.metadata.TypeDefinition;
15import com.strobel.decompiler.languages.java.ast.AstNode;
16import com.strobel.decompiler.languages.java.ast.DepthFirstAstVisitor;
17import com.strobel.decompiler.languages.java.ast.Keys;
18import com.strobel.decompiler.languages.java.ast.TypeDeclaration;
19import cuchaz.enigma.source.SourceIndex;
20import cuchaz.enigma.source.procyon.EntryParser;
21import cuchaz.enigma.translation.representation.entry.ClassDefEntry;
22
23public class SourceIndexVisitor extends DepthFirstAstVisitor<SourceIndex, Void> {
24 @Override
25 public Void visitTypeDeclaration(TypeDeclaration node, SourceIndex index) {
26 TypeDefinition def = node.getUserData(Keys.TYPE_DEFINITION);
27 ClassDefEntry classEntry = EntryParser.parse(def);
28 index.addDeclaration(TokenFactory.createToken(index, node.getNameToken()), classEntry);
29
30 return node.acceptVisitor(new SourceIndexClassVisitor(classEntry), index);
31 }
32
33 @Override
34 protected Void visitChildren(AstNode node, SourceIndex index) {
35 for (final AstNode child : node.getChildren()) {
36 child.acceptVisitor(this, index);
37 }
38 return null;
39 }
40}
diff --git a/enigma/src/main/java/cuchaz/enigma/source/procyon/index/TokenFactory.java b/enigma/src/main/java/cuchaz/enigma/source/procyon/index/TokenFactory.java
new file mode 100644
index 0000000..dc36865
--- /dev/null
+++ b/enigma/src/main/java/cuchaz/enigma/source/procyon/index/TokenFactory.java
@@ -0,0 +1,46 @@
1package cuchaz.enigma.source.procyon.index;
2
3import com.strobel.decompiler.languages.Region;
4import com.strobel.decompiler.languages.java.ast.AstNode;
5import com.strobel.decompiler.languages.java.ast.ConstructorDeclaration;
6import com.strobel.decompiler.languages.java.ast.Identifier;
7import com.strobel.decompiler.languages.java.ast.TypeDeclaration;
8import cuchaz.enigma.source.Token;
9import cuchaz.enigma.source.SourceIndex;
10
11import java.util.regex.Pattern;
12
13public class TokenFactory {
14 private static final Pattern ANONYMOUS_INNER = Pattern.compile("\\$\\d+$");
15
16 public static Token createToken(SourceIndex index, AstNode node) {
17 String name = node instanceof Identifier ? ((Identifier) node).getName() : "";
18 Region region = node.getRegion();
19
20 if (region.getBeginLine() == 0) {
21 System.err.println("Got bad region from Procyon for node " + node);
22 return null;
23 }
24
25 int start = index.getPosition(region.getBeginLine(), region.getBeginColumn());
26 int end = index.getPosition(region.getEndLine(), region.getEndColumn());
27 String text = index.getSource().substring(start, end);
28 Token token = new Token(start, end, text);
29
30 boolean isAnonymousInner =
31 node instanceof Identifier &&
32 name.indexOf('$') >= 0 && node.getParent() instanceof ConstructorDeclaration &&
33 name.lastIndexOf('$') >= 0 &&
34 !ANONYMOUS_INNER.matcher(name).matches();
35
36 if (isAnonymousInner) {
37 TypeDeclaration type = node.getParent().getParent() instanceof TypeDeclaration ? (TypeDeclaration) node.getParent().getParent() : null;
38 if (type != null) {
39 name = type.getName();
40 token.end = token.start + name.length();
41 }
42 }
43
44 return token;
45 }
46}
diff --git a/enigma/src/main/java/cuchaz/enigma/source/procyon/transformers/AddJavadocsAstTransform.java b/enigma/src/main/java/cuchaz/enigma/source/procyon/transformers/AddJavadocsAstTransform.java
new file mode 100644
index 0000000..70fc8c6
--- /dev/null
+++ b/enigma/src/main/java/cuchaz/enigma/source/procyon/transformers/AddJavadocsAstTransform.java
@@ -0,0 +1,134 @@
1package cuchaz.enigma.source.procyon.transformers;
2
3import com.google.common.base.Function;
4import com.google.common.base.Strings;
5import com.strobel.assembler.metadata.ParameterDefinition;
6import com.strobel.decompiler.languages.java.ast.*;
7import com.strobel.decompiler.languages.java.ast.transforms.IAstTransform;
8import cuchaz.enigma.source.procyon.EntryParser;
9import cuchaz.enigma.translation.mapping.EntryMapping;
10import cuchaz.enigma.translation.mapping.EntryRemapper;
11import cuchaz.enigma.translation.mapping.ResolutionStrategy;
12import cuchaz.enigma.translation.representation.entry.*;
13
14import java.util.ArrayList;
15import java.util.Collections;
16import java.util.List;
17import java.util.Objects;
18import java.util.stream.Stream;
19
20public final class AddJavadocsAstTransform implements IAstTransform {
21
22 private final EntryRemapper remapper;
23
24 public AddJavadocsAstTransform(EntryRemapper remapper) {
25 this.remapper = remapper;
26 }
27
28 @Override
29 public void run(AstNode compilationUnit) {
30 compilationUnit.acceptVisitor(new Visitor(remapper), null);
31 }
32
33 static class Visitor extends DepthFirstAstVisitor<Void, Void> {
34
35 private final EntryRemapper remapper;
36
37 Visitor(EntryRemapper remapper) {
38 this.remapper = remapper;
39 }
40
41 private <T extends AstNode> void addDoc(T node, Function<T, Entry<?>> retriever) {
42 final Comment[] comments = getComments(node, retriever);
43 if (comments != null) {
44 node.insertChildrenBefore(node.getFirstChild(), Roles.COMMENT, comments);
45 }
46 }
47
48 private <T extends AstNode> Comment[] getComments(T node, Function<T, Entry<?>> retriever) {
49 final EntryMapping mapping = remapper.getDeobfMapping(retriever.apply(node));
50 final String docs = mapping == null ? null : Strings.emptyToNull(mapping.getJavadoc());
51 return docs == null ? null : Stream.of(docs.split("\\R")).map(st -> new Comment(st,
52 CommentType.Documentation)).toArray(Comment[]::new);
53 }
54
55 private Comment[] getParameterComments(ParameterDeclaration node, Function<ParameterDeclaration, Entry<?>> retriever) {
56 final EntryMapping mapping = remapper.getDeobfMapping(retriever.apply(node));
57 final Comment[] ret = getComments(node, retriever);
58 if (ret != null) {
59 final String paramPrefix = "@param " + mapping.getTargetName() + " ";
60 final String indent = Strings.repeat(" ", paramPrefix.length());
61 ret[0].setContent(paramPrefix + ret[0].getContent());
62 for (int i = 1; i < ret.length; i++) {
63 ret[i].setContent(indent + ret[i].getContent());
64 }
65 }
66 return ret;
67 }
68
69 private void visitMethod(AstNode node) {
70 final MethodDefEntry methodDefEntry = EntryParser.parse(node.getUserData(Keys.METHOD_DEFINITION));
71 final Comment[] baseComments = getComments(node, $ -> methodDefEntry);
72 List<Comment> comments = new ArrayList<>();
73 if (baseComments != null)
74 Collections.addAll(comments, baseComments);
75
76 for (ParameterDeclaration dec : node.getChildrenByRole(Roles.PARAMETER)) {
77 ParameterDefinition def = dec.getUserData(Keys.PARAMETER_DEFINITION);
78 final Comment[] paramComments = getParameterComments(dec, $ -> new LocalVariableDefEntry(methodDefEntry, def.getSlot(), def.getName(),
79 true,
80 EntryParser.parseTypeDescriptor(def.getParameterType()), null));
81 if (paramComments != null)
82 Collections.addAll(comments, paramComments);
83 }
84
85 if (!comments.isEmpty()) {
86 if (remapper.getObfResolver().resolveEntry(methodDefEntry, ResolutionStrategy.RESOLVE_ROOT).stream().noneMatch(e -> Objects.equals(e, methodDefEntry))) {
87 comments.add(0, new Comment("{@inheritDoc}", CommentType.Documentation));
88 }
89 final AstNode oldFirst = node.getFirstChild();
90 for (Comment comment : comments) {
91 node.insertChildBefore(oldFirst, comment, Roles.COMMENT);
92 }
93 }
94 }
95
96 @Override
97 protected Void visitChildren(AstNode node, Void data) {
98 for (final AstNode child : node.getChildren()) {
99 child.acceptVisitor(this, data);
100 }
101 return null;
102 }
103
104 @Override
105 public Void visitMethodDeclaration(MethodDeclaration node, Void data) {
106 visitMethod(node);
107 return super.visitMethodDeclaration(node, data);
108 }
109
110 @Override
111 public Void visitConstructorDeclaration(ConstructorDeclaration node, Void data) {
112 visitMethod(node);
113 return super.visitConstructorDeclaration(node, data);
114 }
115
116 @Override
117 public Void visitFieldDeclaration(FieldDeclaration node, Void data) {
118 addDoc(node, dec -> EntryParser.parse(dec.getUserData(Keys.FIELD_DEFINITION)));
119 return super.visitFieldDeclaration(node, data);
120 }
121
122 @Override
123 public Void visitTypeDeclaration(TypeDeclaration node, Void data) {
124 addDoc(node, dec -> EntryParser.parse(dec.getUserData(Keys.TYPE_DEFINITION)));
125 return super.visitTypeDeclaration(node, data);
126 }
127
128 @Override
129 public Void visitEnumValueDeclaration(EnumValueDeclaration node, Void data) {
130 addDoc(node, dec -> EntryParser.parse(dec.getUserData(Keys.FIELD_DEFINITION)));
131 return super.visitEnumValueDeclaration(node, data);
132 }
133 }
134}
diff --git a/enigma/src/main/java/cuchaz/enigma/source/procyon/transformers/DropImportAstTransform.java b/enigma/src/main/java/cuchaz/enigma/source/procyon/transformers/DropImportAstTransform.java
new file mode 100644
index 0000000..39e599d
--- /dev/null
+++ b/enigma/src/main/java/cuchaz/enigma/source/procyon/transformers/DropImportAstTransform.java
@@ -0,0 +1,33 @@
1package cuchaz.enigma.source.procyon.transformers;
2
3import com.strobel.decompiler.languages.java.ast.AstNode;
4import com.strobel.decompiler.languages.java.ast.DepthFirstAstVisitor;
5import com.strobel.decompiler.languages.java.ast.ImportDeclaration;
6import com.strobel.decompiler.languages.java.ast.PackageDeclaration;
7import com.strobel.decompiler.languages.java.ast.transforms.IAstTransform;
8
9public final class DropImportAstTransform implements IAstTransform {
10 public static final DropImportAstTransform INSTANCE = new DropImportAstTransform();
11
12 private DropImportAstTransform() {
13 }
14
15 @Override
16 public void run(AstNode compilationUnit) {
17 compilationUnit.acceptVisitor(new Visitor(), null);
18 }
19
20 static class Visitor extends DepthFirstAstVisitor<Void, Void> {
21 @Override
22 public Void visitPackageDeclaration(PackageDeclaration node, Void data) {
23 node.remove();
24 return null;
25 }
26
27 @Override
28 public Void visitImportDeclaration(ImportDeclaration node, Void data) {
29 node.remove();
30 return null;
31 }
32 }
33}
diff --git a/enigma/src/main/java/cuchaz/enigma/source/procyon/transformers/DropVarModifiersAstTransform.java b/enigma/src/main/java/cuchaz/enigma/source/procyon/transformers/DropVarModifiersAstTransform.java
new file mode 100644
index 0000000..b8c087b
--- /dev/null
+++ b/enigma/src/main/java/cuchaz/enigma/source/procyon/transformers/DropVarModifiersAstTransform.java
@@ -0,0 +1,37 @@
1package cuchaz.enigma.source.procyon.transformers;
2
3import com.strobel.decompiler.languages.java.ast.*;
4import com.strobel.decompiler.languages.java.ast.transforms.IAstTransform;
5
6import javax.lang.model.element.Modifier;
7
8public final class DropVarModifiersAstTransform implements IAstTransform {
9 public static final DropVarModifiersAstTransform INSTANCE = new DropVarModifiersAstTransform();
10
11 private DropVarModifiersAstTransform() {
12 }
13
14 @Override
15 public void run(AstNode compilationUnit) {
16 compilationUnit.acceptVisitor(new Visitor(), null);
17 }
18
19 static class Visitor extends DepthFirstAstVisitor<Void, Void> {
20 @Override
21 public Void visitParameterDeclaration(ParameterDeclaration node, Void data) {
22 for (JavaModifierToken modifierToken : node.getChildrenByRole(EntityDeclaration.MODIFIER_ROLE)) {
23 if (modifierToken.getModifier() == Modifier.FINAL) {
24 modifierToken.remove();
25 }
26 }
27
28 return null;
29 }
30
31 @Override
32 public Void visitVariableDeclaration(VariableDeclarationStatement node, Void data) {
33 node.removeModifier(Modifier.FINAL);
34 return null;
35 }
36 }
37}
diff --git a/enigma/src/main/java/cuchaz/enigma/source/procyon/transformers/InvalidIdentifierFix.java b/enigma/src/main/java/cuchaz/enigma/source/procyon/transformers/InvalidIdentifierFix.java
new file mode 100644
index 0000000..34d95fa
--- /dev/null
+++ b/enigma/src/main/java/cuchaz/enigma/source/procyon/transformers/InvalidIdentifierFix.java
@@ -0,0 +1,29 @@
1package cuchaz.enigma.source.procyon.transformers;
2
3import com.strobel.decompiler.languages.java.ast.AstNode;
4import com.strobel.decompiler.languages.java.ast.DepthFirstAstVisitor;
5import com.strobel.decompiler.languages.java.ast.Identifier;
6import com.strobel.decompiler.languages.java.ast.transforms.IAstTransform;
7
8/**
9 * Created by Thiakil on 13/07/2018.
10 */
11public class InvalidIdentifierFix implements IAstTransform {
12 @Override
13 public void run(AstNode compilationUnit) {
14 compilationUnit.acceptVisitor(new Visitor(), null);
15 }
16
17 class Visitor extends DepthFirstAstVisitor<Void,Void>{
18 @Override
19 public Void visitIdentifier(Identifier node, Void data) {
20 super.visitIdentifier(node, data);
21 if (node.getName().equals("do") || node.getName().equals("if")){
22 Identifier newIdentifier = Identifier.create(node.getName() + "_", node.getStartLocation());
23 newIdentifier.copyUserDataFrom(node);
24 node.replaceWith(newIdentifier);
25 }
26 return null;
27 }
28 }
29}
diff --git a/enigma/src/main/java/cuchaz/enigma/source/procyon/transformers/Java8Generics.java b/enigma/src/main/java/cuchaz/enigma/source/procyon/transformers/Java8Generics.java
new file mode 100644
index 0000000..8accfc7
--- /dev/null
+++ b/enigma/src/main/java/cuchaz/enigma/source/procyon/transformers/Java8Generics.java
@@ -0,0 +1,107 @@
1package cuchaz.enigma.source.procyon.transformers;
2
3import com.strobel.assembler.metadata.BuiltinTypes;
4import com.strobel.assembler.metadata.CommonTypeReferences;
5import com.strobel.assembler.metadata.Flags;
6import com.strobel.assembler.metadata.IGenericInstance;
7import com.strobel.assembler.metadata.IMemberDefinition;
8import com.strobel.assembler.metadata.JvmType;
9import com.strobel.assembler.metadata.MemberReference;
10import com.strobel.assembler.metadata.MethodDefinition;
11import com.strobel.assembler.metadata.TypeDefinition;
12import com.strobel.assembler.metadata.TypeReference;
13import com.strobel.decompiler.languages.java.ast.ArrayCreationExpression;
14import com.strobel.decompiler.languages.java.ast.AstNode;
15import com.strobel.decompiler.languages.java.ast.AstNodeCollection;
16import com.strobel.decompiler.languages.java.ast.AstType;
17import com.strobel.decompiler.languages.java.ast.CastExpression;
18import com.strobel.decompiler.languages.java.ast.ComposedType;
19import com.strobel.decompiler.languages.java.ast.DepthFirstAstVisitor;
20import com.strobel.decompiler.languages.java.ast.Expression;
21import com.strobel.decompiler.languages.java.ast.Identifier;
22import com.strobel.decompiler.languages.java.ast.InvocationExpression;
23import com.strobel.decompiler.languages.java.ast.Keys;
24import com.strobel.decompiler.languages.java.ast.MemberReferenceExpression;
25import com.strobel.decompiler.languages.java.ast.ObjectCreationExpression;
26import com.strobel.decompiler.languages.java.ast.Roles;
27import com.strobel.decompiler.languages.java.ast.SimpleType;
28import com.strobel.decompiler.languages.java.ast.WildcardType;
29import com.strobel.decompiler.languages.java.ast.transforms.IAstTransform;
30
31/**
32 * Created by Thiakil on 12/07/2018.
33 */
34public class Java8Generics implements IAstTransform {
35
36 @Override
37 public void run(AstNode compilationUnit) {
38 compilationUnit.acceptVisitor(new Visitor(), null);
39 }
40
41 static class Visitor extends DepthFirstAstVisitor<Void,Void>{
42
43 @Override
44 public Void visitInvocationExpression(InvocationExpression node, Void data) {
45 super.visitInvocationExpression(node, data);
46 if (node.getTarget() instanceof MemberReferenceExpression){
47 MemberReferenceExpression referenceExpression = (MemberReferenceExpression) node.getTarget();
48 if (referenceExpression.getTypeArguments().stream().map(t->{
49 TypeReference tr = t.toTypeReference();
50 if (tr.getDeclaringType() != null){//ensure that inner types are resolved so we can get the TypeDefinition below
51 TypeReference resolved = tr.resolve();
52 if (resolved != null)
53 return resolved;
54 }
55 return tr;
56 }).anyMatch(t -> t.isWildcardType() || (t instanceof TypeDefinition && ((TypeDefinition) t).isAnonymous()))) {
57 //these are invalid for invocations, let the compiler work it out
58 referenceExpression.getTypeArguments().clear();
59 } else if (referenceExpression.getTypeArguments().stream().allMatch(t->t.toTypeReference().equals(CommonTypeReferences.Object))){
60 //all are <Object>, thereby redundant and/or bad
61 referenceExpression.getTypeArguments().clear();
62 }
63 }
64 return null;
65 }
66
67 @Override
68 public Void visitObjectCreationExpression(ObjectCreationExpression node, Void data) {
69 super.visitObjectCreationExpression(node, data);
70 AstType type = node.getType();
71 if (type instanceof SimpleType && !((SimpleType) type).getTypeArguments().isEmpty()){
72 SimpleType simpleType = (SimpleType) type;
73 AstNodeCollection<AstType> typeArguments = simpleType.getTypeArguments();
74 if (typeArguments.size() == 1 && typeArguments.firstOrNullObject().toTypeReference().equals(CommonTypeReferences.Object)){
75 //all are <Object>, thereby redundant and/or bad
76 typeArguments.firstOrNullObject().getChildByRole(Roles.IDENTIFIER).replaceWith(Identifier.create(""));
77 }
78 }
79 return null;
80 }
81
82 @Override
83 public Void visitCastExpression(CastExpression node, Void data) {
84 boolean doReplace = false;
85 TypeReference typeReference = node.getType().toTypeReference();
86 if (typeReference.isArray() && typeReference.getElementType().isGenericType()){
87 doReplace = true;
88 } else if (typeReference.isGenericType()) {
89 Expression target = node.getExpression();
90 if (typeReference instanceof IGenericInstance && ((IGenericInstance)typeReference).getTypeArguments().stream().anyMatch(t->t.isWildcardType())){
91 doReplace = true;
92 } else if (target instanceof InvocationExpression) {
93 InvocationExpression invocationExpression = (InvocationExpression)target;
94 if (invocationExpression.getTarget() instanceof MemberReferenceExpression && !((MemberReferenceExpression) invocationExpression.getTarget()).getTypeArguments().isEmpty()) {
95 ((MemberReferenceExpression) invocationExpression.getTarget()).getTypeArguments().clear();
96 doReplace = true;
97 }
98 }
99 }
100 super.visitCastExpression(node, data);
101 if (doReplace){
102 node.replaceWith(node.getExpression());
103 }
104 return null;
105 }
106 }
107}
diff --git a/enigma/src/main/java/cuchaz/enigma/source/procyon/transformers/ObfuscatedEnumSwitchRewriterTransform.java b/enigma/src/main/java/cuchaz/enigma/source/procyon/transformers/ObfuscatedEnumSwitchRewriterTransform.java
new file mode 100644
index 0000000..32bb72f
--- /dev/null
+++ b/enigma/src/main/java/cuchaz/enigma/source/procyon/transformers/ObfuscatedEnumSwitchRewriterTransform.java
@@ -0,0 +1,414 @@
1/*
2 * Originally:
3 * EnumSwitchRewriterTransform.java
4 *
5 * Copyright (c) 2013 Mike Strobel
6 *
7 * This source code is based on Mono.Cecil from Jb Evain, Copyright (c) Jb Evain;
8 * and ILSpy/ICSharpCode from SharpDevelop, Copyright (c) AlphaSierraPapa.
9 *
10 * This source code is subject to terms and conditions of the Apache License, Version 2.0.
11 * A copy of the license can be found in the License.html file at the root of this distribution.
12 * By using this source code in any fashion, you are agreeing to be bound by the terms of the
13 * Apache License, Version 2.0.
14 *
15 * You must not remove this notice, or any other, from this software.
16 */
17
18package cuchaz.enigma.source.procyon.transformers;
19
20import com.strobel.assembler.metadata.BuiltinTypes;
21import com.strobel.assembler.metadata.FieldDefinition;
22import com.strobel.assembler.metadata.MethodDefinition;
23import com.strobel.assembler.metadata.TypeDefinition;
24import com.strobel.assembler.metadata.TypeReference;
25import com.strobel.core.SafeCloseable;
26import com.strobel.core.VerifyArgument;
27import com.strobel.decompiler.DecompilerContext;
28import com.strobel.decompiler.languages.java.ast.AssignmentExpression;
29import com.strobel.decompiler.languages.java.ast.AstBuilder;
30import com.strobel.decompiler.languages.java.ast.AstNode;
31import com.strobel.decompiler.languages.java.ast.CaseLabel;
32import com.strobel.decompiler.languages.java.ast.ContextTrackingVisitor;
33import com.strobel.decompiler.languages.java.ast.Expression;
34import com.strobel.decompiler.languages.java.ast.IdentifierExpression;
35import com.strobel.decompiler.languages.java.ast.IndexerExpression;
36import com.strobel.decompiler.languages.java.ast.InvocationExpression;
37import com.strobel.decompiler.languages.java.ast.Keys;
38import com.strobel.decompiler.languages.java.ast.MemberReferenceExpression;
39import com.strobel.decompiler.languages.java.ast.PrimitiveExpression;
40import com.strobel.decompiler.languages.java.ast.SwitchSection;
41import com.strobel.decompiler.languages.java.ast.SwitchStatement;
42import com.strobel.decompiler.languages.java.ast.TypeDeclaration;
43import com.strobel.decompiler.languages.java.ast.TypeReferenceExpression;
44import com.strobel.decompiler.languages.java.ast.transforms.IAstTransform;
45
46import java.util.ArrayList;
47import java.util.IdentityHashMap;
48import java.util.LinkedHashMap;
49import java.util.List;
50import java.util.Map;
51
52/**
53 * Copy of {@link com.strobel.decompiler.languages.java.ast.transforms.EnumSwitchRewriterTransform} modified to:
54 * - Not rely on a field containing "$SwitchMap$" (Proguard strips it)
55 * - Ignore classes *with* SwitchMap$ names (so the original can handle it)
56 * - Ignores inner synthetics that are not package private
57 */
58@SuppressWarnings("Duplicates")
59public class ObfuscatedEnumSwitchRewriterTransform implements IAstTransform {
60 private final DecompilerContext _context;
61
62 public ObfuscatedEnumSwitchRewriterTransform(final DecompilerContext context) {
63 _context = VerifyArgument.notNull(context, "context");
64 }
65
66 @Override
67 public void run(final AstNode compilationUnit) {
68 compilationUnit.acceptVisitor(new Visitor(_context), null);
69 }
70
71 private final static class Visitor extends ContextTrackingVisitor<Void> {
72 private final static class SwitchMapInfo {
73 final String enclosingType;
74 final Map<String, List<SwitchStatement>> switches = new LinkedHashMap<>();
75 final Map<String, Map<Integer, Expression>> mappings = new LinkedHashMap<>();
76
77 TypeDeclaration enclosingTypeDeclaration;
78
79 SwitchMapInfo(final String enclosingType) {
80 this.enclosingType = enclosingType;
81 }
82 }
83
84 private final Map<String, SwitchMapInfo> _switchMaps = new LinkedHashMap<>();
85 private boolean _isSwitchMapWrapper;
86
87 protected Visitor(final DecompilerContext context) {
88 super(context);
89 }
90
91 @Override
92 public Void visitTypeDeclaration(final TypeDeclaration typeDeclaration, final Void p) {
93 final boolean oldIsSwitchMapWrapper = _isSwitchMapWrapper;
94 final TypeDefinition typeDefinition = typeDeclaration.getUserData(Keys.TYPE_DEFINITION);
95 final boolean isSwitchMapWrapper = isSwitchMapWrapper(typeDefinition);
96
97 if (isSwitchMapWrapper) {
98 final String internalName = typeDefinition.getInternalName();
99
100 SwitchMapInfo info = _switchMaps.get(internalName);
101
102 if (info == null) {
103 _switchMaps.put(internalName, info = new SwitchMapInfo(internalName));
104 }
105
106 info.enclosingTypeDeclaration = typeDeclaration;
107 }
108
109 _isSwitchMapWrapper = isSwitchMapWrapper;
110
111 try {
112 super.visitTypeDeclaration(typeDeclaration, p);
113 }
114 finally {
115 _isSwitchMapWrapper = oldIsSwitchMapWrapper;
116 }
117
118 rewrite();
119
120 return null;
121 }
122
123 @Override
124 public Void visitSwitchStatement(final SwitchStatement node, final Void data) {
125 final Expression test = node.getExpression();
126
127 if (test instanceof IndexerExpression) {
128 final IndexerExpression indexer = (IndexerExpression) test;
129 final Expression array = indexer.getTarget();
130 final Expression argument = indexer.getArgument();
131
132 if (!(array instanceof MemberReferenceExpression)) {
133 return super.visitSwitchStatement(node, data);
134 }
135
136 final MemberReferenceExpression arrayAccess = (MemberReferenceExpression) array;
137 final Expression arrayOwner = arrayAccess.getTarget();
138 final String mapName = arrayAccess.getMemberName();
139
140 if (mapName == null || mapName.startsWith("$SwitchMap$") || !(arrayOwner instanceof TypeReferenceExpression)) {
141 return super.visitSwitchStatement(node, data);
142 }
143
144 final TypeReferenceExpression enclosingTypeExpression = (TypeReferenceExpression) arrayOwner;
145 final TypeReference enclosingType = enclosingTypeExpression.getType().getUserData(Keys.TYPE_REFERENCE);
146
147 if (!isSwitchMapWrapper(enclosingType) || !(argument instanceof InvocationExpression)) {
148 return super.visitSwitchStatement(node, data);
149 }
150
151 final InvocationExpression invocation = (InvocationExpression) argument;
152 final Expression invocationTarget = invocation.getTarget();
153
154 if (!(invocationTarget instanceof MemberReferenceExpression)) {
155 return super.visitSwitchStatement(node, data);
156 }
157
158 final MemberReferenceExpression memberReference = (MemberReferenceExpression) invocationTarget;
159
160 if (!"ordinal".equals(memberReference.getMemberName())) {
161 return super.visitSwitchStatement(node, data);
162 }
163
164 final String enclosingTypeName = enclosingType.getInternalName();
165
166 SwitchMapInfo info = _switchMaps.get(enclosingTypeName);
167
168 if (info == null) {
169 _switchMaps.put(enclosingTypeName, info = new SwitchMapInfo(enclosingTypeName));
170
171 final TypeDefinition resolvedType = enclosingType.resolve();
172
173 if (resolvedType != null) {
174 AstBuilder astBuilder = context.getUserData(Keys.AST_BUILDER);
175
176 if (astBuilder == null) {
177 astBuilder = new AstBuilder(context);
178 }
179
180 try (final SafeCloseable importSuppression = astBuilder.suppressImports()) {
181 final TypeDeclaration declaration = astBuilder.createType(resolvedType);
182
183 declaration.acceptVisitor(this, data);
184 }
185 }
186 }
187
188 List<SwitchStatement> switches = info.switches.get(mapName);
189
190 if (switches == null) {
191 info.switches.put(mapName, switches = new ArrayList<>());
192 }
193
194 switches.add(node);
195 }
196
197 return super.visitSwitchStatement(node, data);
198 }
199
200 @Override
201 public Void visitAssignmentExpression(final AssignmentExpression node, final Void data) {
202 final TypeDefinition currentType = context.getCurrentType();
203 final MethodDefinition currentMethod = context.getCurrentMethod();
204
205 if (_isSwitchMapWrapper &&
206 currentType != null &&
207 currentMethod != null &&
208 currentMethod.isTypeInitializer()) {
209
210 final Expression left = node.getLeft();
211 final Expression right = node.getRight();
212
213 if (left instanceof IndexerExpression &&
214 right instanceof PrimitiveExpression) {
215
216 String mapName = null;
217
218 final Expression array = ((IndexerExpression) left).getTarget();
219 final Expression argument = ((IndexerExpression) left).getArgument();
220
221 if (array instanceof MemberReferenceExpression) {
222 mapName = ((MemberReferenceExpression) array).getMemberName();
223 }
224 else if (array instanceof IdentifierExpression) {
225 mapName = ((IdentifierExpression) array).getIdentifier();
226 }
227
228 if (mapName == null || mapName.startsWith("$SwitchMap$")) {
229 return super.visitAssignmentExpression(node, data);
230 }
231
232 if (!(argument instanceof InvocationExpression)) {
233 return super.visitAssignmentExpression(node, data);
234 }
235
236 final InvocationExpression invocation = (InvocationExpression) argument;
237 final Expression invocationTarget = invocation.getTarget();
238
239 if (!(invocationTarget instanceof MemberReferenceExpression)) {
240 return super.visitAssignmentExpression(node, data);
241 }
242
243 final MemberReferenceExpression memberReference = (MemberReferenceExpression) invocationTarget;
244 final Expression memberTarget = memberReference.getTarget();
245
246 if (!(memberTarget instanceof MemberReferenceExpression) || !"ordinal".equals(memberReference.getMemberName())) {
247 return super.visitAssignmentExpression(node, data);
248 }
249
250 final MemberReferenceExpression outerMemberReference = (MemberReferenceExpression) memberTarget;
251 final Expression outerMemberTarget = outerMemberReference.getTarget();
252
253 if (!(outerMemberTarget instanceof TypeReferenceExpression)) {
254 return super.visitAssignmentExpression(node, data);
255 }
256
257 final String enclosingType = currentType.getInternalName();
258
259 SwitchMapInfo info = _switchMaps.get(enclosingType);
260
261 if (info == null) {
262 _switchMaps.put(enclosingType, info = new SwitchMapInfo(enclosingType));
263
264 AstBuilder astBuilder = context.getUserData(Keys.AST_BUILDER);
265
266 if (astBuilder == null) {
267 astBuilder = new AstBuilder(context);
268 }
269
270 info.enclosingTypeDeclaration = astBuilder.createType(currentType);
271 }
272
273 final PrimitiveExpression value = (PrimitiveExpression) right;
274
275 assert value.getValue() instanceof Integer;
276
277 Map<Integer, Expression> mapping = info.mappings.get(mapName);
278
279 if (mapping == null) {
280 info.mappings.put(mapName, mapping = new LinkedHashMap<>());
281 }
282
283 final IdentifierExpression enumValue = new IdentifierExpression( Expression.MYSTERY_OFFSET, outerMemberReference.getMemberName());
284
285 enumValue.putUserData(Keys.MEMBER_REFERENCE, outerMemberReference.getUserData(Keys.MEMBER_REFERENCE));
286
287 mapping.put(((Number) value.getValue()).intValue(), enumValue);
288 }
289 }
290
291 return super.visitAssignmentExpression(node, data);
292 }
293
294 private void rewrite() {
295 if (_switchMaps.isEmpty()) {
296 return;
297 }
298
299 for (final SwitchMapInfo info : _switchMaps.values()) {
300 rewrite(info);
301 }
302
303 //
304 // Remove switch map type wrappers that are no longer referenced.
305 //
306
307 outer:
308 for (final SwitchMapInfo info : _switchMaps.values()) {
309 for (final String mapName : info.switches.keySet()) {
310 final List<SwitchStatement> switches = info.switches.get(mapName);
311
312 if (switches != null && !switches.isEmpty()) {
313 continue outer;
314 }
315 }
316
317 final TypeDeclaration enclosingTypeDeclaration = info.enclosingTypeDeclaration;
318
319 if (enclosingTypeDeclaration != null) {
320 enclosingTypeDeclaration.remove();
321 }
322 }
323 }
324
325 private void rewrite(final SwitchMapInfo info) {
326 if (info.switches.isEmpty()) {
327 return;
328 }
329
330 for (final String mapName : info.switches.keySet()) {
331 final List<SwitchStatement> switches = info.switches.get(mapName);
332 final Map<Integer, Expression> mappings = info.mappings.get(mapName);
333
334 if (switches != null && mappings != null) {
335 for (int i = 0; i < switches.size(); i++) {
336 if (rewriteSwitch(switches.get(i), mappings)) {
337 switches.remove(i--);
338 }
339 }
340 }
341 }
342 }
343
344 private boolean rewriteSwitch(final SwitchStatement s, final Map<Integer, Expression> mappings) {
345 final Map<Expression, Expression> replacements = new IdentityHashMap<>();
346
347 for (final SwitchSection section : s.getSwitchSections()) {
348 for (final CaseLabel caseLabel : section.getCaseLabels()) {
349 final Expression expression = caseLabel.getExpression();
350
351 if (expression.isNull()) {
352 continue;
353 }
354
355 if (expression instanceof PrimitiveExpression) {
356 final Object value = ((PrimitiveExpression) expression).getValue();
357
358 if (value instanceof Integer) {
359 final Expression replacement = mappings.get(value);
360
361 if (replacement != null) {
362 replacements.put(expression, replacement);
363 continue;
364 }
365 }
366 }
367
368 //
369 // If we can't rewrite all cases, we abort.
370 //
371
372 return false;
373 }
374 }
375
376 final IndexerExpression indexer = (IndexerExpression) s.getExpression();
377 final InvocationExpression argument = (InvocationExpression) indexer.getArgument();
378 final MemberReferenceExpression memberReference = (MemberReferenceExpression) argument.getTarget();
379 final Expression newTest = memberReference.getTarget();
380
381 newTest.remove();
382 indexer.replaceWith(newTest);
383
384 for (final Map.Entry<Expression, Expression> entry : replacements.entrySet()) {
385 entry.getKey().replaceWith(entry.getValue().clone());
386 }
387
388 return true;
389 }
390
391 private static boolean isSwitchMapWrapper(final TypeReference type) {
392 if (type == null) {
393 return false;
394 }
395
396 final TypeDefinition definition = type instanceof TypeDefinition ? (TypeDefinition) type
397 : type.resolve();
398
399 if (definition == null || !definition.isSynthetic() || !definition.isInnerClass() || !definition.isPackagePrivate()) {
400 return false;
401 }
402
403 for (final FieldDefinition field : definition.getDeclaredFields()) {
404 if (!field.getName().startsWith("$SwitchMap$") &&
405 BuiltinTypes.Integer.makeArrayType().equals(field.getFieldType())) {
406
407 return true;
408 }
409 }
410
411 return false;
412 }
413 }
414} \ No newline at end of file
diff --git a/enigma/src/main/java/cuchaz/enigma/source/procyon/transformers/RemoveObjectCasts.java b/enigma/src/main/java/cuchaz/enigma/source/procyon/transformers/RemoveObjectCasts.java
new file mode 100644
index 0000000..cf0376f
--- /dev/null
+++ b/enigma/src/main/java/cuchaz/enigma/source/procyon/transformers/RemoveObjectCasts.java
@@ -0,0 +1,39 @@
1package cuchaz.enigma.source.procyon.transformers;
2
3import com.strobel.assembler.metadata.BuiltinTypes;
4import com.strobel.decompiler.DecompilerContext;
5import com.strobel.decompiler.languages.java.ast.AstNode;
6import com.strobel.decompiler.languages.java.ast.CastExpression;
7import com.strobel.decompiler.languages.java.ast.ContextTrackingVisitor;
8import com.strobel.decompiler.languages.java.ast.transforms.IAstTransform;
9
10/**
11 * Created by Thiakil on 11/07/2018.
12 */
13public class RemoveObjectCasts implements IAstTransform {
14 private final DecompilerContext _context;
15
16 public RemoveObjectCasts(DecompilerContext context) {
17 _context = context;
18 }
19
20 @Override
21 public void run(AstNode compilationUnit) {
22 compilationUnit.acceptVisitor(new Visitor(_context), null);
23 }
24
25 private final static class Visitor extends ContextTrackingVisitor<Void>{
26
27 protected Visitor(DecompilerContext context) {
28 super(context);
29 }
30
31 @Override
32 public Void visitCastExpression(CastExpression node, Void data) {
33 if (node.getType().toTypeReference().equals(BuiltinTypes.Object)){
34 node.replaceWith(node.getExpression());
35 }
36 return super.visitCastExpression(node, data);
37 }
38 }
39}
diff --git a/enigma/src/main/java/cuchaz/enigma/source/procyon/transformers/VarargsFixer.java b/enigma/src/main/java/cuchaz/enigma/source/procyon/transformers/VarargsFixer.java
new file mode 100644
index 0000000..d3ddaab
--- /dev/null
+++ b/enigma/src/main/java/cuchaz/enigma/source/procyon/transformers/VarargsFixer.java
@@ -0,0 +1,197 @@
1package cuchaz.enigma.source.procyon.transformers;
2
3import com.strobel.assembler.metadata.MemberReference;
4import com.strobel.assembler.metadata.MetadataFilters;
5import com.strobel.assembler.metadata.MetadataHelper;
6import com.strobel.assembler.metadata.MethodBinder;
7import com.strobel.assembler.metadata.MethodDefinition;
8import com.strobel.assembler.metadata.MethodReference;
9import com.strobel.assembler.metadata.TypeReference;
10import com.strobel.core.StringUtilities;
11import com.strobel.core.VerifyArgument;
12import com.strobel.decompiler.DecompilerContext;
13import com.strobel.decompiler.languages.java.ast.ArrayCreationExpression;
14import com.strobel.decompiler.languages.java.ast.ArrayInitializerExpression;
15import com.strobel.decompiler.languages.java.ast.AstNode;
16import com.strobel.decompiler.languages.java.ast.AstNodeCollection;
17import com.strobel.decompiler.languages.java.ast.CastExpression;
18import com.strobel.decompiler.languages.java.ast.ContextTrackingVisitor;
19import com.strobel.decompiler.languages.java.ast.DepthFirstAstVisitor;
20import com.strobel.decompiler.languages.java.ast.Expression;
21import com.strobel.decompiler.languages.java.ast.InvocationExpression;
22import com.strobel.decompiler.languages.java.ast.JavaResolver;
23import com.strobel.decompiler.languages.java.ast.Keys;
24import com.strobel.decompiler.languages.java.ast.MemberReferenceExpression;
25import com.strobel.decompiler.languages.java.ast.ObjectCreationExpression;
26import com.strobel.decompiler.languages.java.ast.transforms.IAstTransform;
27import com.strobel.decompiler.semantics.ResolveResult;
28
29import java.util.ArrayList;
30import java.util.List;
31
32/**
33 * Created by Thiakil on 12/07/2018.
34 */
35public class VarargsFixer implements IAstTransform {
36 private final DecompilerContext _context;
37
38 public VarargsFixer(final DecompilerContext context) {
39 _context = VerifyArgument.notNull(context, "context");
40 }
41
42 @Override
43 public void run(AstNode compilationUnit) {
44 compilationUnit.acceptVisitor(new Visitor(_context), null);
45 }
46
47 class Visitor extends ContextTrackingVisitor<Void> {
48 private final JavaResolver _resolver;
49 protected Visitor(DecompilerContext context) {
50 super(context);
51 _resolver = new JavaResolver(context);
52 }
53
54 //remove `new Object[0]` on varagrs as the normal tranformer doesnt do them
55 @Override
56 public Void visitInvocationExpression(InvocationExpression node, Void data) {
57 super.visitInvocationExpression(node, data);
58 MemberReference definition = node.getUserData(Keys.MEMBER_REFERENCE);
59 if (definition instanceof MethodDefinition && ((MethodDefinition) definition).isVarArgs()){
60 AstNodeCollection<Expression> arguments = node.getArguments();
61 Expression lastParam = arguments.lastOrNullObject();
62 if (!lastParam.isNull() && lastParam instanceof ArrayCreationExpression){
63 ArrayCreationExpression varargArray = (ArrayCreationExpression)lastParam;
64 if (varargArray.getInitializer().isNull() || varargArray.getInitializer().getElements().isEmpty()){
65 lastParam.remove();
66 } else {
67 for (Expression e : varargArray.getInitializer().getElements()){
68 arguments.insertBefore(varargArray, e.clone());
69 }
70 varargArray.remove();
71 }
72 }
73 }
74 return null;
75 }
76
77 //applies the vararg transform to object creation
78 @Override
79 public Void visitObjectCreationExpression(ObjectCreationExpression node, Void data) {
80 super.visitObjectCreationExpression(node, data);
81 final AstNodeCollection<Expression> arguments = node.getArguments();
82 final Expression lastArgument = arguments.lastOrNullObject();
83
84 Expression arrayArg = lastArgument;
85
86 if (arrayArg instanceof CastExpression)
87 arrayArg = ((CastExpression) arrayArg).getExpression();
88
89 if (arrayArg == null ||
90 arrayArg.isNull() ||
91 !(arrayArg instanceof ArrayCreationExpression &&
92 node.getTarget() instanceof MemberReferenceExpression)) {
93
94 return null;
95 }
96
97 final ArrayCreationExpression newArray = (ArrayCreationExpression) arrayArg;
98 final MemberReferenceExpression target = (MemberReferenceExpression) node.getTarget();
99
100 if (!newArray.getAdditionalArraySpecifiers().hasSingleElement()) {
101 return null;
102 }
103
104 final MethodReference method = (MethodReference) node.getUserData(Keys.MEMBER_REFERENCE);
105
106 if (method == null) {
107 return null;
108 }
109
110 final MethodDefinition resolved = method.resolve();
111
112 if (resolved == null || !resolved.isVarArgs()) {
113 return null;
114 }
115
116 final List<MethodReference> candidates;
117 final Expression invocationTarget = target.getTarget();
118
119 if (invocationTarget == null || invocationTarget.isNull()) {
120 candidates = MetadataHelper.findMethods(
121 context.getCurrentType(),
122 MetadataFilters.matchName(resolved.getName())
123 );
124 }
125 else {
126 final ResolveResult targetResult = _resolver.apply(invocationTarget);
127
128 if (targetResult == null || targetResult.getType() == null) {
129 return null;
130 }
131
132 candidates = MetadataHelper.findMethods(
133 targetResult.getType(),
134 MetadataFilters.matchName(resolved.getName())
135 );
136 }
137
138 final List<TypeReference> argTypes = new ArrayList<>();
139
140 for (final Expression argument : arguments) {
141 final ResolveResult argResult = _resolver.apply(argument);
142
143 if (argResult == null || argResult.getType() == null) {
144 return null;
145 }
146
147 argTypes.add(argResult.getType());
148 }
149
150 final MethodBinder.BindResult c1 = MethodBinder.selectMethod(candidates, argTypes);
151
152 if (c1.isFailure() || c1.isAmbiguous()) {
153 return null;
154 }
155
156 argTypes.remove(argTypes.size() - 1);
157
158 final ArrayInitializerExpression initializer = newArray.getInitializer();
159 final boolean hasElements = !initializer.isNull() && !initializer.getElements().isEmpty();
160
161 if (hasElements) {
162 for (final Expression argument : initializer.getElements()) {
163 final ResolveResult argResult = _resolver.apply(argument);
164
165 if (argResult == null || argResult.getType() == null) {
166 return null;
167 }
168
169 argTypes.add(argResult.getType());
170 }
171 }
172
173 final MethodBinder.BindResult c2 = MethodBinder.selectMethod(candidates, argTypes);
174
175 if (c2.isFailure() ||
176 c2.isAmbiguous() ||
177 !StringUtilities.equals(c2.getMethod().getErasedSignature(), c1.getMethod().getErasedSignature())) {
178
179 return null;
180 }
181
182 lastArgument.remove();
183
184 if (!hasElements) {
185 lastArgument.remove();
186 return null;
187 }
188
189 for (final Expression newArg : initializer.getElements()) {
190 newArg.remove();
191 arguments.add(newArg);
192 }
193
194 return null;
195 }
196 }
197}
diff --git a/enigma/src/main/java/cuchaz/enigma/source/procyon/typeloader/CachingClasspathTypeLoader.java b/enigma/src/main/java/cuchaz/enigma/source/procyon/typeloader/CachingClasspathTypeLoader.java
new file mode 100644
index 0000000..e702956
--- /dev/null
+++ b/enigma/src/main/java/cuchaz/enigma/source/procyon/typeloader/CachingClasspathTypeLoader.java
@@ -0,0 +1,33 @@
1package cuchaz.enigma.source.procyon.typeloader;
2
3import com.strobel.assembler.metadata.Buffer;
4import com.strobel.assembler.metadata.ClasspathTypeLoader;
5import com.strobel.assembler.metadata.ITypeLoader;
6
7/**
8 * Caching version of {@link ClasspathTypeLoader}
9 */
10public class CachingClasspathTypeLoader extends CachingTypeLoader {
11 private static ITypeLoader extraClassPathLoader = null;
12
13 public static void setExtraClassPathLoader(ITypeLoader loader){
14 extraClassPathLoader = loader;
15 }
16
17 private final ITypeLoader classpathLoader = new ClasspathTypeLoader();
18
19 @Override
20 protected byte[] doLoad(String className) {
21 Buffer parentBuf = new Buffer();
22 if (classpathLoader.tryLoadType(className, parentBuf)) {
23 return parentBuf.array();
24 }
25 if (extraClassPathLoader != null){
26 parentBuf.reset();
27 if (extraClassPathLoader.tryLoadType(className, parentBuf)){
28 return parentBuf.array();
29 }
30 }
31 return EMPTY_ARRAY;//need to return *something* as null means no store
32 }
33}
diff --git a/enigma/src/main/java/cuchaz/enigma/source/procyon/typeloader/CachingTypeLoader.java b/enigma/src/main/java/cuchaz/enigma/source/procyon/typeloader/CachingTypeLoader.java
new file mode 100644
index 0000000..5be5ddd
--- /dev/null
+++ b/enigma/src/main/java/cuchaz/enigma/source/procyon/typeloader/CachingTypeLoader.java
@@ -0,0 +1,38 @@
1package cuchaz.enigma.source.procyon.typeloader;
2
3import com.google.common.collect.Maps;
4import com.strobel.assembler.metadata.Buffer;
5import com.strobel.assembler.metadata.ITypeLoader;
6
7import java.util.Map;
8
9/**
10 * Common cache functions
11 */
12public abstract class CachingTypeLoader implements ITypeLoader {
13 protected static final byte[] EMPTY_ARRAY = {};
14
15 private final Map<String, byte[]> cache = Maps.newHashMap();
16
17 protected abstract byte[] doLoad(String className);
18
19 @Override
20 public boolean tryLoadType(String className, Buffer out) {
21
22 // check the cache
23 byte[] data = this.cache.computeIfAbsent(className, this::doLoad);
24
25 if (data == EMPTY_ARRAY) {
26 return false;
27 }
28
29 out.reset(data.length);
30 System.arraycopy(data, 0, out.array(), out.position(), data.length);
31 out.position(0);
32 return true;
33 }
34
35 public void clearCache() {
36 this.cache.clear();
37 }
38}
diff --git a/enigma/src/main/java/cuchaz/enigma/source/procyon/typeloader/CompiledSourceTypeLoader.java b/enigma/src/main/java/cuchaz/enigma/source/procyon/typeloader/CompiledSourceTypeLoader.java
new file mode 100644
index 0000000..e703d3b
--- /dev/null
+++ b/enigma/src/main/java/cuchaz/enigma/source/procyon/typeloader/CompiledSourceTypeLoader.java
@@ -0,0 +1,140 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 * <p>
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11
12package cuchaz.enigma.source.procyon.typeloader;
13
14import com.google.common.collect.Lists;
15import com.strobel.assembler.metadata.Buffer;
16import com.strobel.assembler.metadata.ITypeLoader;
17import cuchaz.enigma.ClassProvider;
18import cuchaz.enigma.translation.representation.entry.ClassEntry;
19import org.objectweb.asm.ClassVisitor;
20import org.objectweb.asm.ClassWriter;
21import org.objectweb.asm.Opcodes;
22import org.objectweb.asm.tree.AbstractInsnNode;
23import org.objectweb.asm.tree.ClassNode;
24import org.objectweb.asm.tree.MethodInsnNode;
25import org.objectweb.asm.tree.MethodNode;
26
27import java.util.Collection;
28import java.util.LinkedList;
29import java.util.List;
30import java.util.function.Function;
31
32public class CompiledSourceTypeLoader extends CachingTypeLoader {
33 //Store one instance as the classpath shouldn't change during load
34 private static final ITypeLoader CLASSPATH_TYPE_LOADER = new CachingClasspathTypeLoader();
35
36 private final ClassProvider compiledSource;
37 private final LinkedList<Function<ClassVisitor, ClassVisitor>> visitors = new LinkedList<>();
38
39 public CompiledSourceTypeLoader(ClassProvider compiledSource) {
40 this.compiledSource = compiledSource;
41 }
42
43 public void addVisitor(Function<ClassVisitor, ClassVisitor> visitor) {
44 this.visitors.addFirst(visitor);
45 }
46
47 @Override
48 protected byte[] doLoad(String className) {
49 byte[] data = loadType(className);
50 if (data == null) {
51 return loadClasspath(className);
52 }
53
54 return data;
55 }
56
57 private byte[] loadClasspath(String name) {
58 Buffer parentBuf = new Buffer();
59 if (CLASSPATH_TYPE_LOADER.tryLoadType(name, parentBuf)) {
60 return parentBuf.array();
61 }
62 return EMPTY_ARRAY;
63 }
64
65 private byte[] loadType(String className) {
66 ClassEntry entry = new ClassEntry(className);
67
68 // find the class in the jar
69 ClassNode node = findClassNode(entry);
70 if (node == null) {
71 // couldn't find it
72 return null;
73 }
74
75 removeRedundantClassCalls(node);
76
77 ClassWriter writer = new ClassWriter(0);
78
79 ClassVisitor visitor = writer;
80 for (Function<ClassVisitor, ClassVisitor> visitorFunction : this.visitors) {
81 visitor = visitorFunction.apply(visitor);
82 }
83
84 node.accept(visitor);
85
86 // we have a transformed class!
87 return writer.toByteArray();
88 }
89
90 private void removeRedundantClassCalls(ClassNode node) {
91 // remove <obj>.getClass() calls that are seemingly injected
92 // DUP
93 // INVOKEVIRTUAL java/lang/Object.getClass ()Ljava/lang/Class;
94 // POP
95 for (MethodNode methodNode : node.methods) {
96 AbstractInsnNode insnNode = methodNode.instructions.getFirst();
97 while (insnNode != null) {
98 if (insnNode instanceof MethodInsnNode && insnNode.getOpcode() == Opcodes.INVOKEVIRTUAL) {
99 MethodInsnNode methodInsnNode = (MethodInsnNode) insnNode;
100 if (methodInsnNode.name.equals("getClass") && methodInsnNode.owner.equals("java/lang/Object") && methodInsnNode.desc.equals("()Ljava/lang/Class;")) {
101 AbstractInsnNode previous = methodInsnNode.getPrevious();
102 AbstractInsnNode next = methodInsnNode.getNext();
103 if (previous.getOpcode() == Opcodes.DUP && next.getOpcode() == Opcodes.POP) {
104 insnNode = previous.getPrevious();//reset the iterator so it gets the new next instruction
105 methodNode.instructions.remove(previous);
106 methodNode.instructions.remove(methodInsnNode);
107 methodNode.instructions.remove(next);
108 }
109 }
110 }
111 insnNode = insnNode.getNext();
112 }
113 }
114 }
115
116 private ClassNode findClassNode(ClassEntry entry) {
117 // try to find the class in the jar
118 for (String className : getClassNamesToTry(entry)) {
119 ClassNode node = compiledSource.getClassNode(className);
120 if (node != null) {
121 return node;
122 }
123 }
124
125 // didn't find it ;_;
126 return null;
127 }
128
129 private Collection<String> getClassNamesToTry(ClassEntry entry) {
130 List<String> classNamesToTry = Lists.newArrayList();
131 classNamesToTry.add(entry.getFullName());
132
133 ClassEntry outerClass = entry.getOuterClass();
134 if (outerClass != null) {
135 classNamesToTry.addAll(getClassNamesToTry(outerClass));
136 }
137
138 return classNamesToTry;
139 }
140}
diff --git a/enigma/src/main/java/cuchaz/enigma/source/procyon/typeloader/NoRetryMetadataSystem.java b/enigma/src/main/java/cuchaz/enigma/source/procyon/typeloader/NoRetryMetadataSystem.java
new file mode 100644
index 0000000..c4732b0
--- /dev/null
+++ b/enigma/src/main/java/cuchaz/enigma/source/procyon/typeloader/NoRetryMetadataSystem.java
@@ -0,0 +1,38 @@
1package cuchaz.enigma.source.procyon.typeloader;
2
3import com.strobel.assembler.metadata.ITypeLoader;
4import com.strobel.assembler.metadata.MetadataSystem;
5import com.strobel.assembler.metadata.TypeDefinition;
6import com.strobel.assembler.metadata.TypeReference;
7
8import java.util.Collections;
9import java.util.Set;
10import java.util.concurrent.ConcurrentHashMap;
11
12public final class NoRetryMetadataSystem extends MetadataSystem {
13 private final Set<String> failedTypes = Collections.newSetFromMap(new ConcurrentHashMap<>());
14
15 public NoRetryMetadataSystem(final ITypeLoader typeLoader) {
16 super(typeLoader);
17 }
18
19 @Override
20 protected synchronized TypeDefinition resolveType(final String descriptor, final boolean mightBePrimitive) {
21 if (failedTypes.contains(descriptor)) {
22 return null;
23 }
24
25 final TypeDefinition result = super.resolveType(descriptor, mightBePrimitive);
26
27 if (result == null) {
28 failedTypes.add(descriptor);
29 }
30
31 return result;
32 }
33
34 @Override
35 public synchronized TypeDefinition resolve(final TypeReference type) {
36 return super.resolve(type);
37 }
38}
diff --git a/enigma/src/main/java/cuchaz/enigma/source/procyon/typeloader/SynchronizedTypeLoader.java b/enigma/src/main/java/cuchaz/enigma/source/procyon/typeloader/SynchronizedTypeLoader.java
new file mode 100644
index 0000000..86c6ecc
--- /dev/null
+++ b/enigma/src/main/java/cuchaz/enigma/source/procyon/typeloader/SynchronizedTypeLoader.java
@@ -0,0 +1,20 @@
1package cuchaz.enigma.source.procyon.typeloader;
2
3import com.strobel.assembler.metadata.Buffer;
4import com.strobel.assembler.metadata.ITypeLoader;
5
6/**
7 * Typeloader with synchronized tryLoadType method
8 */
9public class SynchronizedTypeLoader implements ITypeLoader {
10 private final ITypeLoader delegate;
11
12 public SynchronizedTypeLoader(ITypeLoader delegate) {
13 this.delegate = delegate;
14 }
15
16 @Override
17 public synchronized boolean tryLoadType(String internalName, Buffer buffer) {
18 return delegate.tryLoadType(internalName, buffer);
19 }
20}
diff --git a/enigma/src/main/java/cuchaz/enigma/translation/LocalNameGenerator.java b/enigma/src/main/java/cuchaz/enigma/translation/LocalNameGenerator.java
new file mode 100644
index 0000000..18c966c
--- /dev/null
+++ b/enigma/src/main/java/cuchaz/enigma/translation/LocalNameGenerator.java
@@ -0,0 +1,44 @@
1package cuchaz.enigma.translation;
2
3import cuchaz.enigma.translation.mapping.NameValidator;
4import cuchaz.enigma.translation.representation.TypeDescriptor;
5
6import java.util.Collection;
7import java.util.Locale;
8
9public class LocalNameGenerator {
10 public static String generateArgumentName(int index, TypeDescriptor desc, Collection<TypeDescriptor> arguments) {
11 boolean uniqueType = arguments.stream().filter(desc::equals).count() <= 1;
12 String translatedName;
13 int nameIndex = index + 1;
14 StringBuilder nameBuilder = new StringBuilder(getTypeName(desc));
15 if (!uniqueType || NameValidator.isReserved(nameBuilder.toString())) {
16 nameBuilder.append(nameIndex);
17 }
18 translatedName = nameBuilder.toString();
19 return translatedName;
20 }
21
22 public static String generateLocalVariableName(int index, TypeDescriptor desc) {
23 int nameIndex = index + 1;
24 return getTypeName(desc) + nameIndex;
25 }
26
27 private static String getTypeName(TypeDescriptor desc) {
28 // Unfortunately each of these have different name getters, so they have different code paths
29 if (desc.isPrimitive()) {
30 TypeDescriptor.Primitive argCls = desc.getPrimitive();
31 return argCls.name().toLowerCase(Locale.ROOT);
32 } else if (desc.isArray()) {
33 // List types would require this whole block again, so just go with aListx
34 return "arr";
35 } else if (desc.isType()) {
36 String typeName = desc.getTypeEntry().getSimpleName().replace("$", "");
37 typeName = typeName.substring(0, 1).toLowerCase(Locale.ROOT) + typeName.substring(1);
38 return typeName;
39 } else {
40 System.err.println("Encountered invalid argument type descriptor " + desc.toString());
41 return "var";
42 }
43 }
44}
diff --git a/enigma/src/main/java/cuchaz/enigma/translation/MappingTranslator.java b/enigma/src/main/java/cuchaz/enigma/translation/MappingTranslator.java
new file mode 100644
index 0000000..529d0ed
--- /dev/null
+++ b/enigma/src/main/java/cuchaz/enigma/translation/MappingTranslator.java
@@ -0,0 +1,24 @@
1package cuchaz.enigma.translation;
2
3import cuchaz.enigma.translation.mapping.EntryMapping;
4import cuchaz.enigma.translation.mapping.EntryResolver;
5import cuchaz.enigma.translation.mapping.EntryMap;
6
7public class MappingTranslator implements Translator {
8 private final EntryMap<EntryMapping> mappings;
9 private final EntryResolver resolver;
10
11 public MappingTranslator(EntryMap<EntryMapping> mappings, EntryResolver resolver) {
12 this.mappings = mappings;
13 this.resolver = resolver;
14 }
15
16 @SuppressWarnings("unchecked")
17 @Override
18 public <T extends Translatable> T translate(T translatable) {
19 if (translatable == null) {
20 return null;
21 }
22 return (T) translatable.translate(this, resolver, mappings);
23 }
24}
diff --git a/enigma/src/main/java/cuchaz/enigma/translation/ProposingTranslator.java b/enigma/src/main/java/cuchaz/enigma/translation/ProposingTranslator.java
new file mode 100644
index 0000000..97866e9
--- /dev/null
+++ b/enigma/src/main/java/cuchaz/enigma/translation/ProposingTranslator.java
@@ -0,0 +1,51 @@
1package cuchaz.enigma.translation;
2
3import cuchaz.enigma.api.service.NameProposalService;
4import cuchaz.enigma.translation.mapping.EntryRemapper;
5import cuchaz.enigma.translation.mapping.ResolutionStrategy;
6import cuchaz.enigma.translation.representation.entry.Entry;
7
8import java.util.Arrays;
9import java.util.Optional;
10
11public class ProposingTranslator implements Translator {
12 private final EntryRemapper mapper;
13 private final NameProposalService[] nameProposalServices;
14
15 public ProposingTranslator(EntryRemapper mapper, NameProposalService[] nameProposalServices) {
16 this.mapper = mapper;
17 this.nameProposalServices = nameProposalServices;
18 }
19
20 @Override
21 @SuppressWarnings("unchecked")
22 public <T extends Translatable> T translate(T translatable) {
23 if (translatable == null) {
24 return null;
25 }
26
27 T deobfuscated = mapper.deobfuscate(translatable);
28
29 if (translatable instanceof Entry && ((Entry) deobfuscated).getName().equals(((Entry<?>) translatable).getName())) {
30 return mapper.getObfResolver()
31 .resolveEntry((Entry<?>) translatable, ResolutionStrategy.RESOLVE_ROOT)
32 .stream()
33 .map(this::proposeName)
34 .filter(Optional::isPresent)
35 .map(Optional::get)
36 .findFirst()
37 .map(newName -> (T) ((Entry) deobfuscated).withName(newName))
38 .orElse(deobfuscated);
39 }
40
41 return deobfuscated;
42 }
43
44 private Optional<String> proposeName(Entry<?> entry) {
45 return Arrays.stream(nameProposalServices)
46 .map(service -> service.proposeName(entry, mapper))
47 .filter(Optional::isPresent)
48 .map(Optional::get)
49 .findFirst();
50 }
51}
diff --git a/enigma/src/main/java/cuchaz/enigma/translation/SignatureUpdater.java b/enigma/src/main/java/cuchaz/enigma/translation/SignatureUpdater.java
new file mode 100644
index 0000000..3783053
--- /dev/null
+++ b/enigma/src/main/java/cuchaz/enigma/translation/SignatureUpdater.java
@@ -0,0 +1,92 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 * <p>
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11
12package cuchaz.enigma.translation;
13
14import com.google.common.collect.Lists;
15
16import java.io.IOException;
17import java.io.StringReader;
18import java.util.List;
19
20public class SignatureUpdater {
21
22 public static String update(String signature, ClassNameUpdater updater) {
23 try {
24 StringBuilder buf = new StringBuilder();
25
26 // read the signature character-by-character
27 StringReader reader = new StringReader(signature);
28 int i;
29 while ((i = reader.read()) != -1) {
30 char c = (char) i;
31
32 // does this character start a class name?
33 if (c == 'L') {
34 // update the class name and add it to the buffer
35 buf.append('L');
36 String className = readClass(reader);
37 if (className == null) {
38 throw new IllegalArgumentException("Malformed signature: " + signature);
39 }
40 buf.append(updater.update(className));
41 buf.append(';');
42 } else {
43 // copy the character into the buffer
44 buf.append(c);
45 }
46 }
47
48 return buf.toString();
49 } catch (IOException ex) {
50 // I'm pretty sure a StringReader will never throw one of these
51 throw new Error(ex);
52 }
53 }
54
55 private static String readClass(StringReader reader) throws IOException {
56 // read all the characters in the buffer until we hit a ';'
57 // remember to treat generics correctly
58 StringBuilder buf = new StringBuilder();
59 int depth = 0;
60 int i;
61 while ((i = reader.read()) != -1) {
62 char c = (char) i;
63
64 if (c == '<') {
65 depth++;
66 } else if (c == '>') {
67 depth--;
68 } else if (depth == 0) {
69 if (c == ';') {
70 return buf.toString();
71 } else {
72 buf.append(c);
73 }
74 }
75 }
76
77 return null;
78 }
79
80 public static List<String> getClasses(String signature) {
81 final List<String> classNames = Lists.newArrayList();
82 update(signature, className -> {
83 classNames.add(className);
84 return className;
85 });
86 return classNames;
87 }
88
89 public interface ClassNameUpdater {
90 String update(String className);
91 }
92}
diff --git a/enigma/src/main/java/cuchaz/enigma/translation/Translatable.java b/enigma/src/main/java/cuchaz/enigma/translation/Translatable.java
new file mode 100644
index 0000000..0370ef1
--- /dev/null
+++ b/enigma/src/main/java/cuchaz/enigma/translation/Translatable.java
@@ -0,0 +1,9 @@
1package cuchaz.enigma.translation;
2
3import cuchaz.enigma.translation.mapping.EntryMapping;
4import cuchaz.enigma.translation.mapping.EntryResolver;
5import cuchaz.enigma.translation.mapping.EntryMap;
6
7public interface Translatable {
8 Translatable translate(Translator translator, EntryResolver resolver, EntryMap<EntryMapping> mappings);
9}
diff --git a/enigma/src/main/java/cuchaz/enigma/translation/TranslationDirection.java b/enigma/src/main/java/cuchaz/enigma/translation/TranslationDirection.java
new file mode 100644
index 0000000..2ecb30b
--- /dev/null
+++ b/enigma/src/main/java/cuchaz/enigma/translation/TranslationDirection.java
@@ -0,0 +1,36 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 * <p>
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11
12package cuchaz.enigma.translation;
13
14public enum TranslationDirection {
15
16 DEOBFUSCATING {
17 @Override
18 public <T> T choose(T deobfChoice, T obfChoice) {
19 if (deobfChoice == null) {
20 return obfChoice;
21 }
22 return deobfChoice;
23 }
24 },
25 OBFUSCATING {
26 @Override
27 public <T> T choose(T deobfChoice, T obfChoice) {
28 if (obfChoice == null) {
29 return deobfChoice;
30 }
31 return obfChoice;
32 }
33 };
34
35 public abstract <T> T choose(T deobfChoice, T obfChoice);
36}
diff --git a/enigma/src/main/java/cuchaz/enigma/translation/Translator.java b/enigma/src/main/java/cuchaz/enigma/translation/Translator.java
new file mode 100644
index 0000000..c70141f
--- /dev/null
+++ b/enigma/src/main/java/cuchaz/enigma/translation/Translator.java
@@ -0,0 +1,61 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 * <p>
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11
12package cuchaz.enigma.translation;
13
14import com.google.common.collect.HashMultimap;
15import com.google.common.collect.Multimap;
16
17import java.util.Collection;
18import java.util.HashMap;
19import java.util.Map;
20import java.util.Set;
21import java.util.stream.Collectors;
22
23public interface Translator {
24 <T extends Translatable> T translate(T translatable);
25
26 default <T extends Translatable> Collection<T> translate(Collection<T> translatable) {
27 return translatable.stream()
28 .map(this::translate)
29 .collect(Collectors.toList());
30 }
31
32 default <T extends Translatable> Set<T> translate(Set<T> translatable) {
33 return translatable.stream()
34 .map(this::translate)
35 .collect(Collectors.toSet());
36 }
37
38 default <T extends Translatable, V> Map<T, V> translateKeys(Map<T, V> translatable) {
39 Map<T, V> result = new HashMap<>(translatable.size());
40 for (Map.Entry<T, V> entry : translatable.entrySet()) {
41 result.put(translate(entry.getKey()), entry.getValue());
42 }
43 return result;
44 }
45
46 default <K extends Translatable, V extends Translatable> Map<K, V> translate(Map<K, V> translatable) {
47 Map<K, V> result = new HashMap<>(translatable.size());
48 for (Map.Entry<K, V> entry : translatable.entrySet()) {
49 result.put(translate(entry.getKey()), translate(entry.getValue()));
50 }
51 return result;
52 }
53
54 default <K extends Translatable, V extends Translatable> Multimap<K, V> translate(Multimap<K, V> translatable) {
55 Multimap<K, V> result = HashMultimap.create(translatable.size(), 1);
56 for (Map.Entry<K, Collection<V>> entry : translatable.asMap().entrySet()) {
57 result.putAll(translate(entry.getKey()), translate(entry.getValue()));
58 }
59 return result;
60 }
61}
diff --git a/enigma/src/main/java/cuchaz/enigma/translation/VoidTranslator.java b/enigma/src/main/java/cuchaz/enigma/translation/VoidTranslator.java
new file mode 100644
index 0000000..c010833
--- /dev/null
+++ b/enigma/src/main/java/cuchaz/enigma/translation/VoidTranslator.java
@@ -0,0 +1,10 @@
1package cuchaz.enigma.translation;
2
3public enum VoidTranslator implements Translator {
4 INSTANCE;
5
6 @Override
7 public <T extends Translatable> T translate(T translatable) {
8 return translatable;
9 }
10}
diff --git a/enigma/src/main/java/cuchaz/enigma/translation/mapping/AccessModifier.java b/enigma/src/main/java/cuchaz/enigma/translation/mapping/AccessModifier.java
new file mode 100644
index 0000000..5b79b79
--- /dev/null
+++ b/enigma/src/main/java/cuchaz/enigma/translation/mapping/AccessModifier.java
@@ -0,0 +1,25 @@
1package cuchaz.enigma.translation.mapping;
2
3import cuchaz.enigma.translation.representation.AccessFlags;
4
5public enum AccessModifier {
6 UNCHANGED, PUBLIC, PROTECTED, PRIVATE;
7
8 public String getFormattedName() {
9 return "ACC:" + super.toString();
10 }
11
12 public AccessFlags transform(AccessFlags access) {
13 switch (this) {
14 case PUBLIC:
15 return access.setPublic();
16 case PROTECTED:
17 return access.setProtected();
18 case PRIVATE:
19 return access.setPrivate();
20 case UNCHANGED:
21 default:
22 return access;
23 }
24 }
25}
diff --git a/enigma/src/main/java/cuchaz/enigma/translation/mapping/EntryMap.java b/enigma/src/main/java/cuchaz/enigma/translation/mapping/EntryMap.java
new file mode 100644
index 0000000..e1a3253
--- /dev/null
+++ b/enigma/src/main/java/cuchaz/enigma/translation/mapping/EntryMap.java
@@ -0,0 +1,24 @@
1package cuchaz.enigma.translation.mapping;
2
3import cuchaz.enigma.translation.representation.entry.Entry;
4
5import javax.annotation.Nullable;
6import java.util.stream.Stream;
7
8public interface EntryMap<T> {
9 void insert(Entry<?> entry, T value);
10
11 @Nullable
12 T remove(Entry<?> entry);
13
14 @Nullable
15 T get(Entry<?> entry);
16
17 default boolean contains(Entry<?> entry) {
18 return get(entry) != null;
19 }
20
21 Stream<Entry<?>> getAllEntries();
22
23 boolean isEmpty();
24}
diff --git a/enigma/src/main/java/cuchaz/enigma/translation/mapping/EntryMapping.java b/enigma/src/main/java/cuchaz/enigma/translation/mapping/EntryMapping.java
new file mode 100644
index 0000000..c607817
--- /dev/null
+++ b/enigma/src/main/java/cuchaz/enigma/translation/mapping/EntryMapping.java
@@ -0,0 +1,75 @@
1package cuchaz.enigma.translation.mapping;
2
3import javax.annotation.Nonnull;
4import javax.annotation.Nullable;
5
6public class EntryMapping {
7 private final String targetName;
8 private final AccessModifier accessModifier;
9 private final @Nullable String javadoc;
10
11 public EntryMapping(@Nonnull String targetName) {
12 this(targetName, AccessModifier.UNCHANGED);
13 }
14
15 public EntryMapping(@Nonnull String targetName, @Nullable String javadoc) {
16 this(targetName, AccessModifier.UNCHANGED, javadoc);
17 }
18
19 public EntryMapping(@Nonnull String targetName, AccessModifier accessModifier) {
20 this(targetName, accessModifier, null);
21 }
22
23 public EntryMapping(@Nonnull String targetName, AccessModifier accessModifier, @Nullable String javadoc) {
24 this.targetName = targetName;
25 this.accessModifier = accessModifier;
26 this.javadoc = javadoc;
27 }
28
29 @Nonnull
30 public String getTargetName() {
31 return targetName;
32 }
33
34 @Nonnull
35 public AccessModifier getAccessModifier() {
36 if (accessModifier == null) {
37 return AccessModifier.UNCHANGED;
38 }
39 return accessModifier;
40 }
41
42 @Nullable
43 public String getJavadoc() {
44 return javadoc;
45 }
46
47 public EntryMapping withName(String newName) {
48 return new EntryMapping(newName, accessModifier, javadoc);
49 }
50
51 public EntryMapping withModifier(AccessModifier newModifier) {
52 return new EntryMapping(targetName, newModifier, javadoc);
53 }
54
55 public EntryMapping withDocs(String newDocs) {
56 return new EntryMapping(targetName, accessModifier, newDocs);
57 }
58
59 @Override
60 public boolean equals(Object obj) {
61 if (obj == this) return true;
62
63 if (obj instanceof EntryMapping) {
64 EntryMapping mapping = (EntryMapping) obj;
65 return mapping.targetName.equals(targetName) && mapping.accessModifier.equals(accessModifier);
66 }
67
68 return false;
69 }
70
71 @Override
72 public int hashCode() {
73 return targetName.hashCode() + accessModifier.hashCode() * 31;
74 }
75}
diff --git a/enigma/src/main/java/cuchaz/enigma/translation/mapping/EntryRemapper.java b/enigma/src/main/java/cuchaz/enigma/translation/mapping/EntryRemapper.java
new file mode 100644
index 0000000..1dd7eac
--- /dev/null
+++ b/enigma/src/main/java/cuchaz/enigma/translation/mapping/EntryRemapper.java
@@ -0,0 +1,104 @@
1package cuchaz.enigma.translation.mapping;
2
3import cuchaz.enigma.analysis.index.JarIndex;
4import cuchaz.enigma.translation.MappingTranslator;
5import cuchaz.enigma.translation.Translatable;
6import cuchaz.enigma.translation.Translator;
7import cuchaz.enigma.translation.mapping.tree.DeltaTrackingTree;
8import cuchaz.enigma.translation.mapping.tree.EntryTree;
9import cuchaz.enigma.translation.mapping.tree.HashEntryTree;
10import cuchaz.enigma.translation.representation.entry.Entry;
11
12import javax.annotation.Nullable;
13import java.util.Collection;
14import java.util.stream.Stream;
15
16public class EntryRemapper {
17 private final DeltaTrackingTree<EntryMapping> obfToDeobf;
18
19 private final EntryResolver obfResolver;
20 private final Translator deobfuscator;
21
22 private final MappingValidator validator;
23
24 private EntryRemapper(JarIndex jarIndex, EntryTree<EntryMapping> obfToDeobf) {
25 this.obfToDeobf = new DeltaTrackingTree<>(obfToDeobf);
26
27 this.obfResolver = jarIndex.getEntryResolver();
28
29 this.deobfuscator = new MappingTranslator(obfToDeobf, obfResolver);
30
31 this.validator = new MappingValidator(obfToDeobf, deobfuscator, jarIndex);
32 }
33
34 public static EntryRemapper mapped(JarIndex index, EntryTree<EntryMapping> obfToDeobf) {
35 return new EntryRemapper(index, obfToDeobf);
36 }
37
38 public static EntryRemapper empty(JarIndex index) {
39 return new EntryRemapper(index, new HashEntryTree<>());
40 }
41
42 public <E extends Entry<?>> void mapFromObf(E obfuscatedEntry, @Nullable EntryMapping deobfMapping) {
43 mapFromObf(obfuscatedEntry, deobfMapping, true);
44 }
45
46 public <E extends Entry<?>> void mapFromObf(E obfuscatedEntry, @Nullable EntryMapping deobfMapping, boolean renaming) {
47 Collection<E> resolvedEntries = obfResolver.resolveEntry(obfuscatedEntry, renaming ? ResolutionStrategy.RESOLVE_ROOT : ResolutionStrategy.RESOLVE_CLOSEST);
48
49 if (renaming && deobfMapping != null) {
50 for (E resolvedEntry : resolvedEntries) {
51 validator.validateRename(resolvedEntry, deobfMapping.getTargetName());
52 }
53 }
54
55 for (E resolvedEntry : resolvedEntries) {
56 obfToDeobf.insert(resolvedEntry, deobfMapping);
57 }
58 }
59
60 public void removeByObf(Entry<?> obfuscatedEntry) {
61 mapFromObf(obfuscatedEntry, null);
62 }
63
64 @Nullable
65 public EntryMapping getDeobfMapping(Entry<?> entry) {
66 return obfToDeobf.get(entry);
67 }
68
69 public boolean hasDeobfMapping(Entry<?> obfEntry) {
70 return obfToDeobf.contains(obfEntry);
71 }
72
73 public <T extends Translatable> T deobfuscate(T translatable) {
74 return deobfuscator.translate(translatable);
75 }
76
77 public Translator getDeobfuscator() {
78 return deobfuscator;
79 }
80
81 public Stream<Entry<?>> getObfEntries() {
82 return obfToDeobf.getAllEntries();
83 }
84
85 public Collection<Entry<?>> getObfChildren(Entry<?> obfuscatedEntry) {
86 return obfToDeobf.getChildren(obfuscatedEntry);
87 }
88
89 public DeltaTrackingTree<EntryMapping> getObfToDeobf() {
90 return obfToDeobf;
91 }
92
93 public MappingDelta<EntryMapping> takeMappingDelta() {
94 return obfToDeobf.takeDelta();
95 }
96
97 public boolean isDirty() {
98 return obfToDeobf.isDirty();
99 }
100
101 public EntryResolver getObfResolver() {
102 return obfResolver;
103 }
104}
diff --git a/enigma/src/main/java/cuchaz/enigma/translation/mapping/EntryResolver.java b/enigma/src/main/java/cuchaz/enigma/translation/mapping/EntryResolver.java
new file mode 100644
index 0000000..521f72d
--- /dev/null
+++ b/enigma/src/main/java/cuchaz/enigma/translation/mapping/EntryResolver.java
@@ -0,0 +1,41 @@
1package cuchaz.enigma.translation.mapping;
2
3import com.google.common.collect.Streams;
4import cuchaz.enigma.analysis.EntryReference;
5import cuchaz.enigma.translation.representation.entry.Entry;
6import cuchaz.enigma.translation.representation.entry.MethodEntry;
7
8import java.util.Collection;
9import java.util.Set;
10import java.util.stream.Collectors;
11
12public interface EntryResolver {
13 <E extends Entry<?>> Collection<E> resolveEntry(E entry, ResolutionStrategy strategy);
14
15 default <E extends Entry<?>> E resolveFirstEntry(E entry, ResolutionStrategy strategy) {
16 return resolveEntry(entry, strategy).stream().findFirst().orElse(entry);
17 }
18
19 default <E extends Entry<?>, C extends Entry<?>> Collection<EntryReference<E, C>> resolveReference(EntryReference<E, C> reference, ResolutionStrategy strategy) {
20 Collection<E> entry = resolveEntry(reference.entry, strategy);
21 if (reference.context != null) {
22 Collection<C> context = resolveEntry(reference.context, strategy);
23 return Streams.zip(entry.stream(), context.stream(), (e, c) -> new EntryReference<>(e, c, reference))
24 .collect(Collectors.toList());
25 } else {
26 return entry.stream()
27 .map(e -> new EntryReference<>(e, null, reference))
28 .collect(Collectors.toList());
29 }
30 }
31
32 default <E extends Entry<?>, C extends Entry<?>> EntryReference<E, C> resolveFirstReference(EntryReference<E, C> reference, ResolutionStrategy strategy) {
33 E entry = resolveFirstEntry(reference.entry, strategy);
34 C context = resolveFirstEntry(reference.context, strategy);
35 return new EntryReference<>(entry, context, reference);
36 }
37
38 Set<Entry<?>> resolveEquivalentEntries(Entry<?> entry);
39
40 Set<MethodEntry> resolveEquivalentMethods(MethodEntry methodEntry);
41}
diff --git a/enigma/src/main/java/cuchaz/enigma/translation/mapping/IllegalNameException.java b/enigma/src/main/java/cuchaz/enigma/translation/mapping/IllegalNameException.java
new file mode 100644
index 0000000..a7f83cd
--- /dev/null
+++ b/enigma/src/main/java/cuchaz/enigma/translation/mapping/IllegalNameException.java
@@ -0,0 +1,39 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 * <p>
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11
12package cuchaz.enigma.translation.mapping;
13
14public class IllegalNameException extends RuntimeException {
15
16 private String name;
17 private String reason;
18
19 public IllegalNameException(String name, String reason) {
20 this.name = name;
21 this.reason = reason;
22 }
23
24 public String getReason() {
25 return this.reason;
26 }
27
28 @Override
29 public String getMessage() {
30 StringBuilder buf = new StringBuilder();
31 buf.append("Illegal name: ");
32 buf.append(this.name);
33 if (this.reason != null) {
34 buf.append(" because ");
35 buf.append(this.reason);
36 }
37 return buf.toString();
38 }
39}
diff --git a/enigma/src/main/java/cuchaz/enigma/translation/mapping/IndexEntryResolver.java b/enigma/src/main/java/cuchaz/enigma/translation/mapping/IndexEntryResolver.java
new file mode 100644
index 0000000..78231dd
--- /dev/null
+++ b/enigma/src/main/java/cuchaz/enigma/translation/mapping/IndexEntryResolver.java
@@ -0,0 +1,227 @@
1package cuchaz.enigma.translation.mapping;
2
3import com.google.common.collect.Sets;
4import cuchaz.enigma.analysis.IndexTreeBuilder;
5import cuchaz.enigma.analysis.MethodImplementationsTreeNode;
6import cuchaz.enigma.analysis.MethodInheritanceTreeNode;
7import cuchaz.enigma.analysis.index.BridgeMethodIndex;
8import cuchaz.enigma.analysis.index.EntryIndex;
9import cuchaz.enigma.analysis.index.InheritanceIndex;
10import cuchaz.enigma.analysis.index.JarIndex;
11import cuchaz.enigma.translation.VoidTranslator;
12import cuchaz.enigma.translation.representation.AccessFlags;
13import cuchaz.enigma.translation.representation.entry.ClassEntry;
14import cuchaz.enigma.translation.representation.entry.Entry;
15import cuchaz.enigma.translation.representation.entry.MethodEntry;
16
17import javax.annotation.Nullable;
18import java.util.*;
19import java.util.stream.Collectors;
20
21public class IndexEntryResolver implements EntryResolver {
22 private final EntryIndex entryIndex;
23 private final InheritanceIndex inheritanceIndex;
24 private final BridgeMethodIndex bridgeMethodIndex;
25
26 private final IndexTreeBuilder treeBuilder;
27
28 public IndexEntryResolver(JarIndex index) {
29 this.entryIndex = index.getEntryIndex();
30 this.inheritanceIndex = index.getInheritanceIndex();
31 this.bridgeMethodIndex = index.getBridgeMethodIndex();
32
33 this.treeBuilder = new IndexTreeBuilder(index);
34 }
35
36 @Override
37 @SuppressWarnings("unchecked")
38 public <E extends Entry<?>> Collection<E> resolveEntry(E entry, ResolutionStrategy strategy) {
39 if (entry == null) {
40 return Collections.emptySet();
41 }
42
43 Entry<ClassEntry> classChild = getClassChild(entry);
44 if (classChild != null && !(classChild instanceof ClassEntry)) {
45 AccessFlags access = entryIndex.getEntryAccess(classChild);
46
47 // If we're looking for the closest and this entry exists, we're done looking
48 if (strategy == ResolutionStrategy.RESOLVE_CLOSEST && access != null) {
49 return Collections.singleton(entry);
50 }
51
52 if (access == null || !access.isPrivate()) {
53 Collection<Entry<ClassEntry>> resolvedChildren = resolveChildEntry(classChild, strategy);
54 if (!resolvedChildren.isEmpty()) {
55 return resolvedChildren.stream()
56 .map(resolvedChild -> (E) entry.replaceAncestor(classChild, resolvedChild))
57 .collect(Collectors.toList());
58 }
59 }
60 }
61
62 return Collections.singleton(entry);
63 }
64
65 @Nullable
66 private Entry<ClassEntry> getClassChild(Entry<?> entry) {
67 if (entry instanceof ClassEntry) {
68 return null;
69 }
70
71 // get the entry in the hierarchy that is the child of a class
72 List<Entry<?>> ancestry = entry.getAncestry();
73 for (int i = ancestry.size() - 1; i > 0; i--) {
74 Entry<?> child = ancestry.get(i);
75 Entry<ClassEntry> cast = child.castParent(ClassEntry.class);
76 if (cast != null && !(cast instanceof ClassEntry)) {
77 // we found the entry which is a child of a class, we are now able to resolve the owner of this entry
78 return cast;
79 }
80 }
81
82 return null;
83 }
84
85 private Set<Entry<ClassEntry>> resolveChildEntry(Entry<ClassEntry> entry, ResolutionStrategy strategy) {
86 ClassEntry ownerClass = entry.getParent();
87
88 if (entry instanceof MethodEntry) {
89 MethodEntry bridgeMethod = bridgeMethodIndex.getBridgeFromSpecialized((MethodEntry) entry);
90 if (bridgeMethod != null && ownerClass.equals(bridgeMethod.getParent())) {
91 Set<Entry<ClassEntry>> resolvedBridge = resolveChildEntry(bridgeMethod, strategy);
92 if (!resolvedBridge.isEmpty()) {
93 return resolvedBridge;
94 } else {
95 return Collections.singleton(bridgeMethod);
96 }
97 }
98 }
99
100 Set<Entry<ClassEntry>> resolvedEntries = new HashSet<>();
101
102 for (ClassEntry parentClass : inheritanceIndex.getParents(ownerClass)) {
103 Entry<ClassEntry> parentEntry = entry.withParent(parentClass);
104
105 if (strategy == ResolutionStrategy.RESOLVE_ROOT) {
106 resolvedEntries.addAll(resolveRoot(parentEntry, strategy));
107 } else {
108 resolvedEntries.addAll(resolveClosest(parentEntry, strategy));
109 }
110 }
111
112 return resolvedEntries;
113 }
114
115 private Collection<Entry<ClassEntry>> resolveRoot(Entry<ClassEntry> entry, ResolutionStrategy strategy) {
116 // When resolving root, we want to first look for the lowest entry before returning ourselves
117 Set<Entry<ClassEntry>> parentResolution = resolveChildEntry(entry, strategy);
118
119 if (parentResolution.isEmpty()) {
120 AccessFlags parentAccess = entryIndex.getEntryAccess(entry);
121 if (parentAccess != null && !parentAccess.isPrivate()) {
122 return Collections.singleton(entry);
123 }
124 }
125
126 return parentResolution;
127 }
128
129 private Collection<Entry<ClassEntry>> resolveClosest(Entry<ClassEntry> entry, ResolutionStrategy strategy) {
130 // When resolving closest, we want to first check if we exist before looking further down
131 AccessFlags parentAccess = entryIndex.getEntryAccess(entry);
132 if (parentAccess != null && !parentAccess.isPrivate()) {
133 return Collections.singleton(entry);
134 } else {
135 return resolveChildEntry(entry, strategy);
136 }
137 }
138
139 @Override
140 public Set<Entry<?>> resolveEquivalentEntries(Entry<?> entry) {
141 MethodEntry relevantMethod = entry.findAncestor(MethodEntry.class);
142 if (relevantMethod == null || !entryIndex.hasMethod(relevantMethod)) {
143 return Collections.singleton(entry);
144 }
145
146 Set<MethodEntry> equivalentMethods = resolveEquivalentMethods(relevantMethod);
147 Set<Entry<?>> equivalentEntries = new HashSet<>(equivalentMethods.size());
148
149 for (MethodEntry equivalentMethod : equivalentMethods) {
150 Entry<?> equivalentEntry = entry.replaceAncestor(relevantMethod, equivalentMethod);
151 equivalentEntries.add(equivalentEntry);
152 }
153
154 return equivalentEntries;
155 }
156
157 @Override
158 public Set<MethodEntry> resolveEquivalentMethods(MethodEntry methodEntry) {
159 AccessFlags access = entryIndex.getMethodAccess(methodEntry);
160 if (access == null) {
161 throw new IllegalArgumentException("Could not find method " + methodEntry);
162 }
163
164 if (!canInherit(methodEntry, access)) {
165 return Collections.singleton(methodEntry);
166 }
167
168 Set<MethodEntry> methodEntries = Sets.newHashSet();
169 resolveEquivalentMethods(methodEntries, treeBuilder.buildMethodInheritance(VoidTranslator.INSTANCE, methodEntry));
170 return methodEntries;
171 }
172
173 private void resolveEquivalentMethods(Set<MethodEntry> methodEntries, MethodInheritanceTreeNode node) {
174 MethodEntry methodEntry = node.getMethodEntry();
175 if (methodEntries.contains(methodEntry)) {
176 return;
177 }
178
179 AccessFlags flags = entryIndex.getMethodAccess(methodEntry);
180 if (flags != null && canInherit(methodEntry, flags)) {
181 // collect the entry
182 methodEntries.add(methodEntry);
183 }
184
185 // look at bridge methods!
186 MethodEntry bridgedMethod = bridgeMethodIndex.getBridgeFromSpecialized(methodEntry);
187 while (bridgedMethod != null) {
188 methodEntries.addAll(resolveEquivalentMethods(bridgedMethod));
189 bridgedMethod = bridgeMethodIndex.getBridgeFromSpecialized(bridgedMethod);
190 }
191
192 // look at interface methods too
193 for (MethodImplementationsTreeNode implementationsNode : treeBuilder.buildMethodImplementations(VoidTranslator.INSTANCE, methodEntry)) {
194 resolveEquivalentMethods(methodEntries, implementationsNode);
195 }
196
197 // recurse
198 for (int i = 0; i < node.getChildCount(); i++) {
199 resolveEquivalentMethods(methodEntries, (MethodInheritanceTreeNode) node.getChildAt(i));
200 }
201 }
202
203 private void resolveEquivalentMethods(Set<MethodEntry> methodEntries, MethodImplementationsTreeNode node) {
204 MethodEntry methodEntry = node.getMethodEntry();
205 AccessFlags flags = entryIndex.getMethodAccess(methodEntry);
206 if (flags != null && !flags.isPrivate() && !flags.isStatic()) {
207 // collect the entry
208 methodEntries.add(methodEntry);
209 }
210
211 // look at bridge methods!
212 MethodEntry bridgedMethod = bridgeMethodIndex.getBridgeFromSpecialized(methodEntry);
213 while (bridgedMethod != null) {
214 methodEntries.addAll(resolveEquivalentMethods(bridgedMethod));
215 bridgedMethod = bridgeMethodIndex.getBridgeFromSpecialized(bridgedMethod);
216 }
217
218 // recurse
219 for (int i = 0; i < node.getChildCount(); i++) {
220 resolveEquivalentMethods(methodEntries, (MethodImplementationsTreeNode) node.getChildAt(i));
221 }
222 }
223
224 private boolean canInherit(MethodEntry entry, AccessFlags access) {
225 return !entry.isConstructor() && !access.isPrivate() && !access.isStatic() && !access.isFinal();
226 }
227}
diff --git a/enigma/src/main/java/cuchaz/enigma/translation/mapping/MappingDelta.java b/enigma/src/main/java/cuchaz/enigma/translation/mapping/MappingDelta.java
new file mode 100644
index 0000000..1407bb6
--- /dev/null
+++ b/enigma/src/main/java/cuchaz/enigma/translation/mapping/MappingDelta.java
@@ -0,0 +1,54 @@
1package cuchaz.enigma.translation.mapping;
2
3import cuchaz.enigma.translation.Translatable;
4import cuchaz.enigma.translation.Translator;
5import cuchaz.enigma.translation.mapping.tree.EntryTree;
6import cuchaz.enigma.translation.mapping.tree.EntryTreeNode;
7import cuchaz.enigma.translation.mapping.tree.HashEntryTree;
8import cuchaz.enigma.translation.representation.entry.Entry;
9
10import java.util.stream.Stream;
11
12public class MappingDelta<T> implements Translatable {
13 public static final Object PLACEHOLDER = new Object();
14
15 private final EntryTree<T> baseMappings;
16
17 private final EntryTree<Object> changes;
18
19 public MappingDelta(EntryTree<T> baseMappings, EntryTree<Object> changes) {
20 this.baseMappings = baseMappings;
21 this.changes = changes;
22 }
23
24 public MappingDelta(EntryTree<T> baseMappings) {
25 this(baseMappings, new HashEntryTree<>());
26 }
27
28 public static <T> MappingDelta<T> added(EntryTree<T> mappings) {
29 EntryTree<Object> changes = new HashEntryTree<>();
30 mappings.getAllEntries().forEach(entry -> changes.insert(entry, PLACEHOLDER));
31
32 return new MappingDelta<>(new HashEntryTree<>(), changes);
33 }
34
35 public EntryTree<T> getBaseMappings() {
36 return baseMappings;
37 }
38
39 public EntryTree<?> getChanges() {
40 return changes;
41 }
42
43 public Stream<Entry<?>> getChangedRoots() {
44 return changes.getRootNodes().map(EntryTreeNode::getEntry);
45 }
46
47 @Override
48 public MappingDelta<T> translate(Translator translator, EntryResolver resolver, EntryMap<EntryMapping> mappings) {
49 return new MappingDelta<>(
50 translator.translate(baseMappings),
51 translator.translate(changes)
52 );
53 }
54}
diff --git a/enigma/src/main/java/cuchaz/enigma/translation/mapping/MappingOperations.java b/enigma/src/main/java/cuchaz/enigma/translation/mapping/MappingOperations.java
new file mode 100644
index 0000000..2c03748
--- /dev/null
+++ b/enigma/src/main/java/cuchaz/enigma/translation/mapping/MappingOperations.java
@@ -0,0 +1,71 @@
1package cuchaz.enigma.translation.mapping;
2
3import cuchaz.enigma.translation.mapping.tree.EntryTree;
4import cuchaz.enigma.translation.mapping.tree.EntryTreeNode;
5import cuchaz.enigma.translation.mapping.tree.HashEntryTree;
6import cuchaz.enigma.translation.MappingTranslator;
7import cuchaz.enigma.translation.Translator;
8import cuchaz.enigma.translation.representation.entry.ClassEntry;
9import cuchaz.enigma.translation.representation.entry.Entry;
10import cuchaz.enigma.translation.representation.entry.FieldEntry;
11import cuchaz.enigma.translation.representation.entry.MethodEntry;
12
13import java.util.HashSet;
14import java.util.Set;
15
16public class MappingOperations {
17 public static EntryTree<EntryMapping> invert(EntryTree<EntryMapping> mappings) {
18 Translator translator = new MappingTranslator(mappings, VoidEntryResolver.INSTANCE);
19 EntryTree<EntryMapping> result = new HashEntryTree<>();
20
21 for (EntryTreeNode<EntryMapping> node : mappings) {
22 Entry<?> leftEntry = node.getEntry();
23 EntryMapping leftMapping = node.getValue();
24
25 if (!(leftEntry instanceof ClassEntry || leftEntry instanceof MethodEntry || leftEntry instanceof FieldEntry)) {
26 result.insert(translator.translate(leftEntry), leftMapping);
27 continue;
28 }
29
30 Entry<?> rightEntry = translator.translate(leftEntry);
31
32 result.insert(rightEntry, leftMapping == null ? null : leftMapping.withName(leftEntry.getName()));
33 }
34
35 return result;
36 }
37
38 public static EntryTree<EntryMapping> compose(EntryTree<EntryMapping> left, EntryTree<EntryMapping> right, boolean keepLeftOnly, boolean keepRightOnly) {
39 Translator leftTranslator = new MappingTranslator(left, VoidEntryResolver.INSTANCE);
40 EntryTree<EntryMapping> result = new HashEntryTree<>();
41 Set<Entry<?>> addedMappings = new HashSet<>();
42
43 for (EntryTreeNode<EntryMapping> node : left) {
44 Entry<?> leftEntry = node.getEntry();
45 EntryMapping leftMapping = node.getValue();
46
47 Entry<?> rightEntry = leftTranslator.translate(leftEntry);
48
49 EntryMapping rightMapping = right.get(rightEntry);
50 if (rightMapping != null) {
51 result.insert(leftEntry, rightMapping);
52 addedMappings.add(rightEntry);
53 } else if (keepLeftOnly) {
54 result.insert(leftEntry, leftMapping);
55 }
56 }
57
58 if (keepRightOnly) {
59 Translator leftInverseTranslator = new MappingTranslator(invert(left), VoidEntryResolver.INSTANCE);
60 for (EntryTreeNode<EntryMapping> node : right) {
61 Entry<?> rightEntry = node.getEntry();
62 EntryMapping rightMapping = node.getValue();
63
64 if (!addedMappings.contains(rightEntry)) {
65 result.insert(leftInverseTranslator.translate(rightEntry), rightMapping);
66 }
67 }
68 }
69 return result;
70 }
71}
diff --git a/enigma/src/main/java/cuchaz/enigma/translation/mapping/MappingPair.java b/enigma/src/main/java/cuchaz/enigma/translation/mapping/MappingPair.java
new file mode 100644
index 0000000..5d39e3d
--- /dev/null
+++ b/enigma/src/main/java/cuchaz/enigma/translation/mapping/MappingPair.java
@@ -0,0 +1,32 @@
1package cuchaz.enigma.translation.mapping;
2
3import cuchaz.enigma.translation.representation.entry.Entry;
4
5import javax.annotation.Nullable;
6
7public class MappingPair<E extends Entry<?>, M> {
8 private final E entry;
9 private M mapping;
10
11 public MappingPair(E entry, @Nullable M mapping) {
12 this.entry = entry;
13 this.mapping = mapping;
14 }
15
16 public MappingPair(E entry) {
17 this(entry, null);
18 }
19
20 public E getEntry() {
21 return entry;
22 }
23
24 @Nullable
25 public M getMapping() {
26 return mapping;
27 }
28
29 public void setMapping(M mapping) {
30 this.mapping = mapping;
31 }
32}
diff --git a/enigma/src/main/java/cuchaz/enigma/translation/mapping/MappingValidator.java b/enigma/src/main/java/cuchaz/enigma/translation/mapping/MappingValidator.java
new file mode 100644
index 0000000..ae615da
--- /dev/null
+++ b/enigma/src/main/java/cuchaz/enigma/translation/mapping/MappingValidator.java
@@ -0,0 +1,75 @@
1package cuchaz.enigma.translation.mapping;
2
3import cuchaz.enigma.analysis.index.InheritanceIndex;
4import cuchaz.enigma.analysis.index.JarIndex;
5import cuchaz.enigma.translation.Translator;
6import cuchaz.enigma.translation.mapping.tree.EntryTree;
7import cuchaz.enigma.translation.representation.entry.ClassEntry;
8import cuchaz.enigma.translation.representation.entry.Entry;
9
10import java.util.Collection;
11import java.util.HashSet;
12import java.util.stream.Collectors;
13
14public class MappingValidator {
15 private final EntryTree<EntryMapping> obfToDeobf;
16 private final Translator deobfuscator;
17 private final JarIndex index;
18
19 public MappingValidator(EntryTree<EntryMapping> obfToDeobf, Translator deobfuscator, JarIndex index) {
20 this.obfToDeobf = obfToDeobf;
21 this.deobfuscator = deobfuscator;
22 this.index = index;
23 }
24
25 public void validateRename(Entry<?> entry, String name) throws IllegalNameException {
26 Collection<Entry<?>> equivalentEntries = index.getEntryResolver().resolveEquivalentEntries(entry);
27 for (Entry<?> equivalentEntry : equivalentEntries) {
28 equivalentEntry.validateName(name);
29 validateUnique(equivalentEntry, name);
30 }
31 }
32
33 private void validateUnique(Entry<?> entry, String name) {
34 ClassEntry containingClass = entry.getContainingClass();
35 Collection<ClassEntry> relatedClasses = getRelatedClasses(containingClass);
36
37 for (ClassEntry relatedClass : relatedClasses) {
38 Entry<?> relatedEntry = entry.replaceAncestor(containingClass, relatedClass);
39 Entry<?> translatedEntry = deobfuscator.translate(relatedEntry);
40
41 Collection<Entry<?>> translatedSiblings = obfToDeobf.getSiblings(relatedEntry).stream()
42 .map(deobfuscator::translate)
43 .collect(Collectors.toList());
44
45 if (!isUnique(translatedEntry, translatedSiblings, name)) {
46 Entry<?> parent = translatedEntry.getParent();
47 if (parent != null) {
48 throw new IllegalNameException(name, "Name is not unique in " + parent + "!");
49 } else {
50 throw new IllegalNameException(name, "Name is not unique!");
51 }
52 }
53 }
54 }
55
56 private Collection<ClassEntry> getRelatedClasses(ClassEntry classEntry) {
57 InheritanceIndex inheritanceIndex = index.getInheritanceIndex();
58
59 Collection<ClassEntry> relatedClasses = new HashSet<>();
60 relatedClasses.add(classEntry);
61 relatedClasses.addAll(inheritanceIndex.getChildren(classEntry));
62 relatedClasses.addAll(inheritanceIndex.getAncestors(classEntry));
63
64 return relatedClasses;
65 }
66
67 private boolean isUnique(Entry<?> entry, Collection<Entry<?>> siblings, String name) {
68 for (Entry<?> sibling : siblings) {
69 if (entry.canConflictWith(sibling) && sibling.getName().equals(name)) {
70 return false;
71 }
72 }
73 return true;
74 }
75}
diff --git a/enigma/src/main/java/cuchaz/enigma/translation/mapping/MappingsChecker.java b/enigma/src/main/java/cuchaz/enigma/translation/mapping/MappingsChecker.java
new file mode 100644
index 0000000..5d9794f
--- /dev/null
+++ b/enigma/src/main/java/cuchaz/enigma/translation/mapping/MappingsChecker.java
@@ -0,0 +1,99 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 * <p>
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11
12package cuchaz.enigma.translation.mapping;
13
14import cuchaz.enigma.ProgressListener;
15import cuchaz.enigma.analysis.index.JarIndex;
16import cuchaz.enigma.translation.mapping.tree.EntryTree;
17import cuchaz.enigma.translation.mapping.tree.EntryTreeNode;
18import cuchaz.enigma.translation.representation.entry.ClassEntry;
19import cuchaz.enigma.translation.representation.entry.Entry;
20import cuchaz.enigma.translation.representation.entry.FieldEntry;
21import cuchaz.enigma.translation.representation.entry.LocalVariableEntry;
22import cuchaz.enigma.translation.representation.entry.MethodEntry;
23
24import java.util.Collection;
25import java.util.HashMap;
26import java.util.Map;
27import java.util.stream.Collectors;
28
29public class MappingsChecker {
30 private final JarIndex index;
31 private final EntryTree<EntryMapping> mappings;
32
33 public MappingsChecker(JarIndex index, EntryTree<EntryMapping> mappings) {
34 this.index = index;
35 this.mappings = mappings;
36 }
37
38 public Dropped dropBrokenMappings(ProgressListener progress) {
39 Dropped dropped = new Dropped();
40
41 Collection<Entry<?>> obfEntries = mappings.getAllEntries()
42 .filter(e -> e instanceof ClassEntry || e instanceof MethodEntry || e instanceof FieldEntry || e instanceof LocalVariableEntry)
43 .collect(Collectors.toList());
44
45 progress.init(obfEntries.size(), "Checking for dropped mappings");
46
47 int steps = 0;
48 for (Entry<?> entry : obfEntries) {
49 progress.step(steps++, entry.toString());
50 tryDropEntry(dropped, entry);
51 }
52
53 dropped.apply(mappings);
54
55 return dropped;
56 }
57
58 private void tryDropEntry(Dropped dropped, Entry<?> entry) {
59 if (shouldDropEntry(entry)) {
60 EntryMapping mapping = mappings.get(entry);
61 if (mapping != null) {
62 dropped.drop(entry, mapping);
63 }
64 }
65 }
66
67 private boolean shouldDropEntry(Entry<?> entry) {
68 if (!index.getEntryIndex().hasEntry(entry)) {
69 return true;
70 }
71 Collection<Entry<?>> resolvedEntries = index.getEntryResolver().resolveEntry(entry, ResolutionStrategy.RESOLVE_ROOT);
72 return !resolvedEntries.contains(entry);
73 }
74
75 public static class Dropped {
76 private final Map<Entry<?>, String> droppedMappings = new HashMap<>();
77
78 public void drop(Entry<?> entry, EntryMapping mapping) {
79 droppedMappings.put(entry, mapping.getTargetName());
80 }
81
82 void apply(EntryTree<EntryMapping> mappings) {
83 for (Entry<?> entry : droppedMappings.keySet()) {
84 EntryTreeNode<EntryMapping> node = mappings.findNode(entry);
85 if (node == null) {
86 continue;
87 }
88
89 for (Entry<?> childEntry : node.getChildrenRecursively()) {
90 mappings.remove(childEntry);
91 }
92 }
93 }
94
95 public Map<Entry<?>, String> getDroppedMappings() {
96 return droppedMappings;
97 }
98 }
99}
diff --git a/enigma/src/main/java/cuchaz/enigma/translation/mapping/NameValidator.java b/enigma/src/main/java/cuchaz/enigma/translation/mapping/NameValidator.java
new file mode 100644
index 0000000..74ba633
--- /dev/null
+++ b/enigma/src/main/java/cuchaz/enigma/translation/mapping/NameValidator.java
@@ -0,0 +1,50 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 * <p>
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11
12package cuchaz.enigma.translation.mapping;
13
14import java.util.Arrays;
15import java.util.List;
16import java.util.regex.Pattern;
17
18public class NameValidator {
19 private static final Pattern IDENTIFIER_PATTERN;
20 private static final Pattern CLASS_PATTERN;
21 private static final List<String> ILLEGAL_IDENTIFIERS = Arrays.asList(
22 "abstract", "continue", "for", "new", "switch", "assert", "default", "goto", "package", "synchronized",
23 "boolean", "do", "if", "private", "this", "break", "double", "implements", "protected", "throw", "byte",
24 "else", "import", "public", "throws", "case", "enum", "instanceof", "return", "transient", "catch",
25 "extends", "int", "short", "try", "char", "final", "interface", "static", "void", "class", "finally",
26 "long", "strictfp", "volatile", "const", "float", "native", "super", "while", "_"
27 );
28
29 static {
30 String identifierRegex = "[A-Za-z_<][A-Za-z0-9_>]*";
31 IDENTIFIER_PATTERN = Pattern.compile(identifierRegex);
32 CLASS_PATTERN = Pattern.compile(String.format("^(%s(\\.|/))*(%s)$", identifierRegex, identifierRegex));
33 }
34
35 public static void validateClassName(String name) {
36 if (!CLASS_PATTERN.matcher(name).matches() || ILLEGAL_IDENTIFIERS.contains(name)) {
37 throw new IllegalNameException(name, "This doesn't look like a legal class name");
38 }
39 }
40
41 public static void validateIdentifier(String name) {
42 if (!IDENTIFIER_PATTERN.matcher(name).matches() || ILLEGAL_IDENTIFIERS.contains(name)) {
43 throw new IllegalNameException(name, "This doesn't look like a legal identifier");
44 }
45 }
46
47 public static boolean isReserved(String name) {
48 return ILLEGAL_IDENTIFIERS.contains(name);
49 }
50}
diff --git a/enigma/src/main/java/cuchaz/enigma/translation/mapping/ResolutionStrategy.java b/enigma/src/main/java/cuchaz/enigma/translation/mapping/ResolutionStrategy.java
new file mode 100644
index 0000000..1c28e02
--- /dev/null
+++ b/enigma/src/main/java/cuchaz/enigma/translation/mapping/ResolutionStrategy.java
@@ -0,0 +1,6 @@
1package cuchaz.enigma.translation.mapping;
2
3public enum ResolutionStrategy {
4 RESOLVE_ROOT,
5 RESOLVE_CLOSEST
6}
diff --git a/enigma/src/main/java/cuchaz/enigma/translation/mapping/VoidEntryResolver.java b/enigma/src/main/java/cuchaz/enigma/translation/mapping/VoidEntryResolver.java
new file mode 100644
index 0000000..2eab55f
--- /dev/null
+++ b/enigma/src/main/java/cuchaz/enigma/translation/mapping/VoidEntryResolver.java
@@ -0,0 +1,27 @@
1package cuchaz.enigma.translation.mapping;
2
3import cuchaz.enigma.translation.representation.entry.Entry;
4import cuchaz.enigma.translation.representation.entry.MethodEntry;
5
6import java.util.Collection;
7import java.util.Collections;
8import java.util.Set;
9
10public enum VoidEntryResolver implements EntryResolver {
11 INSTANCE;
12
13 @Override
14 public <E extends Entry<?>> Collection<E> resolveEntry(E entry, ResolutionStrategy strategy) {
15 return Collections.singleton(entry);
16 }
17
18 @Override
19 public Set<Entry<?>> resolveEquivalentEntries(Entry<?> entry) {
20 return Collections.singleton(entry);
21 }
22
23 @Override
24 public Set<MethodEntry> resolveEquivalentMethods(MethodEntry methodEntry) {
25 return Collections.singleton(methodEntry);
26 }
27}
diff --git a/enigma/src/main/java/cuchaz/enigma/translation/mapping/serde/LfPrintWriter.java b/enigma/src/main/java/cuchaz/enigma/translation/mapping/serde/LfPrintWriter.java
new file mode 100644
index 0000000..441949c
--- /dev/null
+++ b/enigma/src/main/java/cuchaz/enigma/translation/mapping/serde/LfPrintWriter.java
@@ -0,0 +1,16 @@
1package cuchaz.enigma.translation.mapping.serde;
2
3import java.io.PrintWriter;
4import java.io.Writer;
5
6public class LfPrintWriter extends PrintWriter {
7 public LfPrintWriter(Writer out) {
8 super(out);
9 }
10
11 @Override
12 public void println() {
13 // https://stackoverflow.com/a/14749004
14 write('\n');
15 }
16}
diff --git a/enigma/src/main/java/cuchaz/enigma/translation/mapping/serde/MappingFileNameFormat.java b/enigma/src/main/java/cuchaz/enigma/translation/mapping/serde/MappingFileNameFormat.java
new file mode 100644
index 0000000..9bc013c
--- /dev/null
+++ b/enigma/src/main/java/cuchaz/enigma/translation/mapping/serde/MappingFileNameFormat.java
@@ -0,0 +1,10 @@
1package cuchaz.enigma.translation.mapping.serde;
2
3import com.google.gson.annotations.SerializedName;
4
5public enum MappingFileNameFormat {
6 @SerializedName("by_obf")
7 BY_OBF,
8 @SerializedName("by_deobf")
9 BY_DEOBF
10}
diff --git a/enigma/src/main/java/cuchaz/enigma/translation/mapping/serde/MappingFormat.java b/enigma/src/main/java/cuchaz/enigma/translation/mapping/serde/MappingFormat.java
new file mode 100644
index 0000000..ca275eb
--- /dev/null
+++ b/enigma/src/main/java/cuchaz/enigma/translation/mapping/serde/MappingFormat.java
@@ -0,0 +1,65 @@
1package cuchaz.enigma.translation.mapping.serde;
2
3import cuchaz.enigma.ProgressListener;
4import cuchaz.enigma.translation.mapping.EntryMapping;
5import cuchaz.enigma.translation.mapping.MappingDelta;
6import cuchaz.enigma.translation.mapping.serde.enigma.EnigmaMappingsReader;
7import cuchaz.enigma.translation.mapping.serde.enigma.EnigmaMappingsWriter;
8import cuchaz.enigma.translation.mapping.serde.proguard.ProguardMappingsReader;
9import cuchaz.enigma.translation.mapping.serde.srg.SrgMappingsWriter;
10import cuchaz.enigma.translation.mapping.serde.tiny.TinyMappingsReader;
11import cuchaz.enigma.translation.mapping.serde.tiny.TinyMappingsWriter;
12import cuchaz.enigma.translation.mapping.serde.tinyv2.TinyV2Reader;
13import cuchaz.enigma.translation.mapping.serde.tinyv2.TinyV2Writer;
14import cuchaz.enigma.translation.mapping.tree.EntryTree;
15
16import javax.annotation.Nullable;
17import java.io.IOException;
18import java.nio.file.Path;
19
20public enum MappingFormat {
21 ENIGMA_FILE(EnigmaMappingsWriter.FILE, EnigmaMappingsReader.FILE),
22 ENIGMA_DIRECTORY(EnigmaMappingsWriter.DIRECTORY, EnigmaMappingsReader.DIRECTORY),
23 ENIGMA_ZIP(EnigmaMappingsWriter.ZIP, EnigmaMappingsReader.ZIP),
24 TINY_V2(new TinyV2Writer("intermediary", "named"), new TinyV2Reader()),
25 TINY_FILE(TinyMappingsWriter.INSTANCE, TinyMappingsReader.INSTANCE),
26 SRG_FILE(SrgMappingsWriter.INSTANCE, null),
27 PROGUARD(null, ProguardMappingsReader.INSTANCE);
28
29
30 private final MappingsWriter writer;
31 private final MappingsReader reader;
32
33 MappingFormat(MappingsWriter writer, MappingsReader reader) {
34 this.writer = writer;
35 this.reader = reader;
36 }
37
38 public void write(EntryTree<EntryMapping> mappings, Path path, ProgressListener progressListener, MappingSaveParameters saveParameters) {
39 write(mappings, MappingDelta.added(mappings), path, progressListener, saveParameters);
40 }
41
42 public void write(EntryTree<EntryMapping> mappings, MappingDelta<EntryMapping> delta, Path path, ProgressListener progressListener, MappingSaveParameters saveParameters) {
43 if (writer == null) {
44 throw new IllegalStateException(name() + " does not support writing");
45 }
46 writer.write(mappings, delta, path, progressListener, saveParameters);
47 }
48
49 public EntryTree<EntryMapping> read(Path path, ProgressListener progressListener, MappingSaveParameters saveParameters) throws IOException, MappingParseException {
50 if (reader == null) {
51 throw new IllegalStateException(name() + " does not support reading");
52 }
53 return reader.read(path, progressListener, saveParameters);
54 }
55
56 @Nullable
57 public MappingsWriter getWriter() {
58 return writer;
59 }
60
61 @Nullable
62 public MappingsReader getReader() {
63 return reader;
64 }
65}
diff --git a/enigma/src/main/java/cuchaz/enigma/translation/mapping/serde/MappingHelper.java b/enigma/src/main/java/cuchaz/enigma/translation/mapping/serde/MappingHelper.java
new file mode 100644
index 0000000..7c8f6cc
--- /dev/null
+++ b/enigma/src/main/java/cuchaz/enigma/translation/mapping/serde/MappingHelper.java
@@ -0,0 +1,51 @@
1package cuchaz.enigma.translation.mapping.serde;
2
3public final class MappingHelper {
4 private static final String TO_ESCAPE = "\\\n\r\0\t";
5 private static final String ESCAPED = "\\nr0t";
6
7 public static String escape(String raw) {
8 StringBuilder builder = new StringBuilder(raw.length() + 1);
9 for (int i = 0; i < raw.length(); i++) {
10 final char c = raw.charAt(i);
11 final int r = TO_ESCAPE.indexOf(c);
12 if (r < 0) {
13 builder.append(c);
14 } else {
15 builder.append('\\').append(ESCAPED.charAt(r));
16 }
17 }
18 return builder.toString();
19 }
20
21 public static String unescape(String str) {
22 int pos = str.indexOf('\\');
23 if (pos < 0) return str;
24
25 StringBuilder ret = new StringBuilder(str.length() - 1);
26 int start = 0;
27
28 do {
29 ret.append(str, start, pos);
30 pos++;
31 int type;
32
33 if (pos >= str.length()) {
34 throw new RuntimeException("incomplete escape sequence at the end");
35 } else if ((type = ESCAPED.indexOf(str.charAt(pos))) < 0) {
36 throw new RuntimeException("invalid escape character: \\" + str.charAt(pos));
37 } else {
38 ret.append(TO_ESCAPE.charAt(type));
39 }
40
41 start = pos + 1;
42 } while ((pos = str.indexOf('\\', start)) >= 0);
43
44 ret.append(str, start, str.length());
45
46 return ret.toString();
47 }
48
49 private MappingHelper() {
50 }
51}
diff --git a/enigma/src/main/java/cuchaz/enigma/translation/mapping/serde/MappingParseException.java b/enigma/src/main/java/cuchaz/enigma/translation/mapping/serde/MappingParseException.java
new file mode 100644
index 0000000..9d04b97
--- /dev/null
+++ b/enigma/src/main/java/cuchaz/enigma/translation/mapping/serde/MappingParseException.java
@@ -0,0 +1,39 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 * <p>
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11
12package cuchaz.enigma.translation.mapping.serde;
13
14import java.io.File;
15import java.util.function.Supplier;
16
17public class MappingParseException extends Exception {
18
19 private int line;
20 private String message;
21 private String filePath;
22
23 public MappingParseException(File file, int line, String message) {
24 this.line = line;
25 this.message = message;
26 filePath = file.getAbsolutePath();
27 }
28
29 public MappingParseException(Supplier<String> filenameProvider, int line, String message) {
30 this.line = line;
31 this.message = message;
32 filePath = filenameProvider.get();
33 }
34
35 @Override
36 public String getMessage() {
37 return "Line " + line + ": " + message + " in file " + filePath;
38 }
39}
diff --git a/enigma/src/main/java/cuchaz/enigma/translation/mapping/serde/MappingSaveParameters.java b/enigma/src/main/java/cuchaz/enigma/translation/mapping/serde/MappingSaveParameters.java
new file mode 100644
index 0000000..e78fd95
--- /dev/null
+++ b/enigma/src/main/java/cuchaz/enigma/translation/mapping/serde/MappingSaveParameters.java
@@ -0,0 +1,16 @@
1package cuchaz.enigma.translation.mapping.serde;
2
3import com.google.gson.annotations.SerializedName;
4
5public class MappingSaveParameters {
6 @SerializedName("file_name_format")
7 private final MappingFileNameFormat fileNameFormat;
8
9 public MappingSaveParameters(MappingFileNameFormat fileNameFormat) {
10 this.fileNameFormat = fileNameFormat;
11 }
12
13 public MappingFileNameFormat getFileNameFormat() {
14 return fileNameFormat;
15 }
16}
diff --git a/enigma/src/main/java/cuchaz/enigma/translation/mapping/serde/MappingsReader.java b/enigma/src/main/java/cuchaz/enigma/translation/mapping/serde/MappingsReader.java
new file mode 100644
index 0000000..2f01375
--- /dev/null
+++ b/enigma/src/main/java/cuchaz/enigma/translation/mapping/serde/MappingsReader.java
@@ -0,0 +1,12 @@
1package cuchaz.enigma.translation.mapping.serde;
2
3import cuchaz.enigma.ProgressListener;
4import cuchaz.enigma.translation.mapping.EntryMapping;
5import cuchaz.enigma.translation.mapping.tree.EntryTree;
6
7import java.io.IOException;
8import java.nio.file.Path;
9
10public interface MappingsReader {
11 EntryTree<EntryMapping> read(Path path, ProgressListener progress, MappingSaveParameters saveParameters) throws MappingParseException, IOException;
12}
diff --git a/enigma/src/main/java/cuchaz/enigma/translation/mapping/serde/MappingsWriter.java b/enigma/src/main/java/cuchaz/enigma/translation/mapping/serde/MappingsWriter.java
new file mode 100644
index 0000000..68a8dbb
--- /dev/null
+++ b/enigma/src/main/java/cuchaz/enigma/translation/mapping/serde/MappingsWriter.java
@@ -0,0 +1,16 @@
1package cuchaz.enigma.translation.mapping.serde;
2
3import cuchaz.enigma.ProgressListener;
4import cuchaz.enigma.translation.mapping.EntryMapping;
5import cuchaz.enigma.translation.mapping.MappingDelta;
6import cuchaz.enigma.translation.mapping.tree.EntryTree;
7
8import java.nio.file.Path;
9
10public interface MappingsWriter {
11 void write(EntryTree<EntryMapping> mappings, MappingDelta<EntryMapping> delta, Path path, ProgressListener progress, MappingSaveParameters saveParameters);
12
13 default void write(EntryTree<EntryMapping> mappings, Path path, ProgressListener progress, MappingSaveParameters saveParameters) {
14 write(mappings, MappingDelta.added(mappings), path, progress, saveParameters);
15 }
16}
diff --git a/enigma/src/main/java/cuchaz/enigma/translation/mapping/serde/RawEntryMapping.java b/enigma/src/main/java/cuchaz/enigma/translation/mapping/serde/RawEntryMapping.java
new file mode 100644
index 0000000..79587a0
--- /dev/null
+++ b/enigma/src/main/java/cuchaz/enigma/translation/mapping/serde/RawEntryMapping.java
@@ -0,0 +1,30 @@
1package cuchaz.enigma.translation.mapping.serde;
2
3import cuchaz.enigma.translation.mapping.AccessModifier;
4import cuchaz.enigma.translation.mapping.EntryMapping;
5
6import java.util.ArrayList;
7import java.util.List;
8
9public final class RawEntryMapping {
10 private final String targetName;
11 private final AccessModifier access;
12 private List<String> javadocs = new ArrayList<>();
13
14 public RawEntryMapping(String targetName) {
15 this(targetName, null);
16 }
17
18 public RawEntryMapping(String targetName, AccessModifier access) {
19 this.access = access;
20 this.targetName = targetName;
21 }
22
23 public void addJavadocLine(String line) {
24 javadocs.add(line);
25 }
26
27 public EntryMapping bake() {
28 return new EntryMapping(targetName, access, javadocs.isEmpty() ? null : String.join("\n", javadocs));
29 }
30}
diff --git a/enigma/src/main/java/cuchaz/enigma/translation/mapping/serde/enigma/EnigmaFormat.java b/enigma/src/main/java/cuchaz/enigma/translation/mapping/serde/enigma/EnigmaFormat.java
new file mode 100644
index 0000000..210d328
--- /dev/null
+++ b/enigma/src/main/java/cuchaz/enigma/translation/mapping/serde/enigma/EnigmaFormat.java
@@ -0,0 +1,9 @@
1package cuchaz.enigma.translation.mapping.serde.enigma;
2
3public class EnigmaFormat {
4 public static final String COMMENT = "COMMENT";
5 public static final String CLASS = "CLASS";
6 public static final String FIELD = "FIELD";
7 public static final String METHOD = "METHOD";
8 public static final String PARAMETER = "ARG";
9}
diff --git a/enigma/src/main/java/cuchaz/enigma/translation/mapping/serde/enigma/EnigmaMappingsReader.java b/enigma/src/main/java/cuchaz/enigma/translation/mapping/serde/enigma/EnigmaMappingsReader.java
new file mode 100644
index 0000000..30de85f
--- /dev/null
+++ b/enigma/src/main/java/cuchaz/enigma/translation/mapping/serde/enigma/EnigmaMappingsReader.java
@@ -0,0 +1,322 @@
1package cuchaz.enigma.translation.mapping.serde.enigma;
2
3import com.google.common.base.Charsets;
4import cuchaz.enigma.ProgressListener;
5import cuchaz.enigma.translation.mapping.serde.MappingParseException;
6import cuchaz.enigma.translation.mapping.AccessModifier;
7import cuchaz.enigma.translation.mapping.EntryMapping;
8import cuchaz.enigma.translation.mapping.MappingPair;
9import cuchaz.enigma.translation.mapping.serde.MappingSaveParameters;
10import cuchaz.enigma.translation.mapping.serde.MappingHelper;
11import cuchaz.enigma.translation.mapping.serde.MappingsReader;
12import cuchaz.enigma.translation.mapping.serde.RawEntryMapping;
13import cuchaz.enigma.translation.mapping.tree.EntryTree;
14import cuchaz.enigma.translation.mapping.tree.HashEntryTree;
15import cuchaz.enigma.translation.representation.MethodDescriptor;
16import cuchaz.enigma.translation.representation.TypeDescriptor;
17import cuchaz.enigma.translation.representation.entry.*;
18import cuchaz.enigma.utils.I18n;
19
20import javax.annotation.Nullable;
21import java.io.IOException;
22import java.nio.file.FileSystem;
23import java.nio.file.FileSystems;
24import java.nio.file.Files;
25import java.nio.file.Path;
26import java.util.ArrayDeque;
27import java.util.Arrays;
28import java.util.Deque;
29import java.util.List;
30import java.util.Locale;
31import java.util.stream.Collectors;
32
33public enum EnigmaMappingsReader implements MappingsReader {
34 FILE {
35 @Override
36 public EntryTree<EntryMapping> read(Path path, ProgressListener progress, MappingSaveParameters saveParameters) throws IOException, MappingParseException {
37 progress.init(1, I18n.translate("progress.mappings.enigma_file.loading"));
38
39 EntryTree<EntryMapping> mappings = new HashEntryTree<>();
40 readFile(path, mappings);
41
42 progress.step(1, I18n.translate("progress.mappings.enigma_file.done"));
43
44 return mappings;
45 }
46 },
47 DIRECTORY {
48 @Override
49 public EntryTree<EntryMapping> read(Path root, ProgressListener progress, MappingSaveParameters saveParameters) throws IOException, MappingParseException {
50 EntryTree<EntryMapping> mappings = new HashEntryTree<>();
51
52 List<Path> files = Files.walk(root)
53 .filter(f -> !Files.isDirectory(f))
54 .filter(f -> f.toString().endsWith(".mapping"))
55 .collect(Collectors.toList());
56
57 progress.init(files.size(), I18n.translate("progress.mappings.enigma_directory.loading"));
58 int step = 0;
59
60 for (Path file : files) {
61 progress.step(step++, root.relativize(file).toString());
62 if (Files.isHidden(file)) {
63 continue;
64 }
65 readFile(file, mappings);
66 }
67
68 return mappings;
69 }
70 },
71 ZIP {
72 @Override
73 public EntryTree<EntryMapping> read(Path zip, ProgressListener progress, MappingSaveParameters saveParameters) throws MappingParseException, IOException {
74 try (FileSystem fs = FileSystems.newFileSystem(zip, (ClassLoader) null)) {
75 return DIRECTORY.read(fs.getPath("/"), progress, saveParameters);
76 }
77 }
78 };
79
80 protected void readFile(Path path, EntryTree<EntryMapping> mappings) throws IOException, MappingParseException {
81 List<String> lines = Files.readAllLines(path, Charsets.UTF_8);
82 Deque<MappingPair<?, RawEntryMapping>> mappingStack = new ArrayDeque<>();
83
84 for (int lineNumber = 0; lineNumber < lines.size(); lineNumber++) {
85 String line = lines.get(lineNumber);
86 int indentation = countIndentation(line);
87
88 line = formatLine(line);
89 if (line == null) {
90 continue;
91 }
92
93 cleanMappingStack(indentation, mappingStack, mappings);
94
95 try {
96 MappingPair<?, RawEntryMapping> pair = parseLine(mappingStack.peek(), line);
97 if (pair != null) {
98 mappingStack.push(pair);
99 if (pair.getMapping() != null) {
100
101 }
102 }
103 } catch (Throwable t) {
104 t.printStackTrace();
105 throw new MappingParseException(path::toString, lineNumber, t.toString());
106 }
107 }
108
109 // Clean up rest
110 cleanMappingStack(0, mappingStack, mappings);
111 }
112
113 private void cleanMappingStack(int indentation, Deque<MappingPair<?, RawEntryMapping>> mappingStack, EntryTree<EntryMapping> mappings) {
114 while (indentation < mappingStack.size()) {
115 MappingPair<?, RawEntryMapping> pair = mappingStack.pop();
116 if (pair.getMapping() != null) {
117 mappings.insert(pair.getEntry(), pair.getMapping().bake());
118 }
119 }
120 }
121
122 @Nullable
123 private String formatLine(String line) {
124 line = stripComment(line);
125 line = line.trim();
126
127 if (line.isEmpty()) {
128 return null;
129 }
130
131 return line;
132 }
133
134 private String stripComment(String line) {
135 //Dont support comments on javadoc lines
136 if (line.trim().startsWith(EnigmaFormat.COMMENT)) {
137 return line;
138 }
139
140 int commentPos = line.indexOf('#');
141 if (commentPos >= 0) {
142 return line.substring(0, commentPos);
143 }
144 return line;
145 }
146
147 private int countIndentation(String line) {
148 int indent = 0;
149 for (int i = 0; i < line.length(); i++) {
150 if (line.charAt(i) != '\t') {
151 break;
152 }
153 indent++;
154 }
155 return indent;
156 }
157
158 private MappingPair<?, RawEntryMapping> parseLine(@Nullable MappingPair<?, RawEntryMapping> parent, String line) {
159 String[] tokens = line.trim().split("\\s");
160 String keyToken = tokens[0].toUpperCase(Locale.ROOT);
161 Entry<?> parentEntry = parent == null ? null : parent.getEntry();
162
163 switch (keyToken) {
164 case EnigmaFormat.CLASS:
165 return parseClass(parentEntry, tokens);
166 case EnigmaFormat.FIELD:
167 return parseField(parentEntry, tokens);
168 case EnigmaFormat.METHOD:
169 return parseMethod(parentEntry, tokens);
170 case EnigmaFormat.PARAMETER:
171 return parseArgument(parentEntry, tokens);
172 case EnigmaFormat.COMMENT:
173 readJavadoc(parent, tokens);
174 return null;
175 default:
176 throw new RuntimeException("Unknown token '" + keyToken + "'");
177 }
178 }
179
180 private void readJavadoc(MappingPair<?, RawEntryMapping> parent, String[] tokens) {
181 if (parent == null)
182 throw new IllegalStateException("Javadoc has no parent!");
183 // Empty string to concat
184 String jdLine = tokens.length > 1 ? String.join(" ", Arrays.copyOfRange(tokens,1,tokens.length)) : "";
185 if (parent.getMapping() == null) {
186 parent.setMapping(new RawEntryMapping(parent.getEntry().getName(), AccessModifier.UNCHANGED));
187 }
188 parent.getMapping().addJavadocLine(MappingHelper.unescape(jdLine));
189 }
190
191 private MappingPair<ClassEntry, RawEntryMapping> parseClass(@Nullable Entry<?> parent, String[] tokens) {
192 String obfuscatedName = ClassEntry.getInnerName(tokens[1]);
193 ClassEntry obfuscatedEntry;
194 if (parent instanceof ClassEntry) {
195 obfuscatedEntry = new ClassEntry((ClassEntry) parent, obfuscatedName);
196 } else {
197 obfuscatedEntry = new ClassEntry(obfuscatedName);
198 }
199
200 String mapping = null;
201 AccessModifier modifier = AccessModifier.UNCHANGED;
202
203 if (tokens.length == 3) {
204 AccessModifier parsedModifier = parseModifier(tokens[2]);
205 if (parsedModifier != null) {
206 modifier = parsedModifier;
207 mapping = obfuscatedName;
208 } else {
209 mapping = tokens[2];
210 }
211 } else if (tokens.length == 4) {
212 mapping = tokens[2];
213 modifier = parseModifier(tokens[3]);
214 }
215
216 if (mapping != null) {
217 return new MappingPair<>(obfuscatedEntry, new RawEntryMapping(mapping, modifier));
218 } else {
219 return new MappingPair<>(obfuscatedEntry);
220 }
221 }
222
223 private MappingPair<FieldEntry, RawEntryMapping> parseField(@Nullable Entry<?> parent, String[] tokens) {
224 if (!(parent instanceof ClassEntry)) {
225 throw new RuntimeException("Field must be a child of a class!");
226 }
227
228 ClassEntry ownerEntry = (ClassEntry) parent;
229
230 String obfuscatedName = tokens[1];
231 String mapping = obfuscatedName;
232 AccessModifier modifier = AccessModifier.UNCHANGED;
233 TypeDescriptor descriptor;
234
235 if (tokens.length == 3) {
236 mapping = tokens[1];
237 descriptor = new TypeDescriptor(tokens[2]);
238 } else if (tokens.length == 4) {
239 AccessModifier parsedModifier = parseModifier(tokens[3]);
240 if (parsedModifier != null) {
241 descriptor = new TypeDescriptor(tokens[2]);
242 modifier = parsedModifier;
243 } else {
244 mapping = tokens[2];
245 descriptor = new TypeDescriptor(tokens[3]);
246 }
247 } else if (tokens.length == 5) {
248 descriptor = new TypeDescriptor(tokens[3]);
249 mapping = tokens[2];
250 modifier = parseModifier(tokens[4]);
251 } else {
252 throw new RuntimeException("Invalid field declaration");
253 }
254
255 FieldEntry obfuscatedEntry = new FieldEntry(ownerEntry, obfuscatedName, descriptor);
256 if (mapping != null) {
257 return new MappingPair<>(obfuscatedEntry, new RawEntryMapping(mapping, modifier));
258 } else {
259 return new MappingPair<>(obfuscatedEntry);
260 }
261 }
262
263 private MappingPair<MethodEntry, RawEntryMapping> parseMethod(@Nullable Entry<?> parent, String[] tokens) {
264 if (!(parent instanceof ClassEntry)) {
265 throw new RuntimeException("Method must be a child of a class!");
266 }
267
268 ClassEntry ownerEntry = (ClassEntry) parent;
269
270 String obfuscatedName = tokens[1];
271 String mapping = null;
272 AccessModifier modifier = AccessModifier.UNCHANGED;
273 MethodDescriptor descriptor;
274
275 if (tokens.length == 3) {
276 descriptor = new MethodDescriptor(tokens[2]);
277 } else if (tokens.length == 4) {
278 AccessModifier parsedModifier = parseModifier(tokens[3]);
279 if (parsedModifier != null) {
280 modifier = parsedModifier;
281 mapping = obfuscatedName;
282 descriptor = new MethodDescriptor(tokens[2]);
283 } else {
284 mapping = tokens[2];
285 descriptor = new MethodDescriptor(tokens[3]);
286 }
287 } else if (tokens.length == 5) {
288 mapping = tokens[2];
289 modifier = parseModifier(tokens[4]);
290 descriptor = new MethodDescriptor(tokens[3]);
291 } else {
292 throw new RuntimeException("Invalid method declaration");
293 }
294
295 MethodEntry obfuscatedEntry = new MethodEntry(ownerEntry, obfuscatedName, descriptor);
296 if (mapping != null) {
297 return new MappingPair<>(obfuscatedEntry, new RawEntryMapping(mapping, modifier));
298 } else {
299 return new MappingPair<>(obfuscatedEntry);
300 }
301 }
302
303 private MappingPair<LocalVariableEntry, RawEntryMapping> parseArgument(@Nullable Entry<?> parent, String[] tokens) {
304 if (!(parent instanceof MethodEntry)) {
305 throw new RuntimeException("Method arg must be a child of a method!");
306 }
307
308 MethodEntry ownerEntry = (MethodEntry) parent;
309 LocalVariableEntry obfuscatedEntry = new LocalVariableEntry(ownerEntry, Integer.parseInt(tokens[1]), "", true, null);
310 String mapping = tokens[2];
311
312 return new MappingPair<>(obfuscatedEntry, new RawEntryMapping(mapping));
313 }
314
315 @Nullable
316 private AccessModifier parseModifier(String token) {
317 if (token.startsWith("ACC:")) {
318 return AccessModifier.valueOf(token.substring(4));
319 }
320 return null;
321 }
322}
diff --git a/enigma/src/main/java/cuchaz/enigma/translation/mapping/serde/enigma/EnigmaMappingsWriter.java b/enigma/src/main/java/cuchaz/enigma/translation/mapping/serde/enigma/EnigmaMappingsWriter.java
new file mode 100644
index 0000000..7570d4b
--- /dev/null
+++ b/enigma/src/main/java/cuchaz/enigma/translation/mapping/serde/enigma/EnigmaMappingsWriter.java
@@ -0,0 +1,318 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11
12package cuchaz.enigma.translation.mapping.serde.enigma;
13
14import java.io.IOException;
15import java.io.PrintWriter;
16import java.net.URI;
17import java.net.URISyntaxException;
18import java.nio.file.DirectoryStream;
19import java.nio.file.FileSystem;
20import java.nio.file.FileSystems;
21import java.nio.file.Files;
22import java.nio.file.Path;
23import java.nio.file.Paths;
24import java.util.ArrayList;
25import java.util.Collection;
26import java.util.Collections;
27import java.util.Objects;
28import java.util.concurrent.atomic.AtomicInteger;
29import java.util.stream.Collectors;
30import java.util.stream.Stream;
31
32import cuchaz.enigma.ProgressListener;
33import cuchaz.enigma.translation.MappingTranslator;
34import cuchaz.enigma.translation.Translator;
35import cuchaz.enigma.translation.mapping.AccessModifier;
36import cuchaz.enigma.translation.mapping.EntryMapping;
37import cuchaz.enigma.translation.mapping.MappingDelta;
38import cuchaz.enigma.translation.mapping.serde.MappingFileNameFormat;
39import cuchaz.enigma.translation.mapping.serde.MappingSaveParameters;
40import cuchaz.enigma.translation.mapping.VoidEntryResolver;
41import cuchaz.enigma.translation.mapping.serde.LfPrintWriter;
42import cuchaz.enigma.translation.mapping.serde.MappingHelper;
43import cuchaz.enigma.translation.mapping.serde.MappingsWriter;
44import cuchaz.enigma.translation.mapping.tree.EntryTree;
45import cuchaz.enigma.translation.mapping.tree.EntryTreeNode;
46import cuchaz.enigma.translation.representation.entry.ClassEntry;
47import cuchaz.enigma.translation.representation.entry.Entry;
48import cuchaz.enigma.translation.representation.entry.FieldEntry;
49import cuchaz.enigma.translation.representation.entry.LocalVariableEntry;
50import cuchaz.enigma.translation.representation.entry.MethodEntry;
51import cuchaz.enigma.utils.I18n;
52
53public enum EnigmaMappingsWriter implements MappingsWriter {
54 FILE {
55 @Override
56 public void write(EntryTree<EntryMapping> mappings, MappingDelta<EntryMapping> delta, Path path, ProgressListener progress, MappingSaveParameters saveParameters) {
57 Collection<ClassEntry> classes = mappings.getRootNodes()
58 .filter(entry -> entry instanceof ClassEntry)
59 .map(entry -> (ClassEntry) entry)
60 .collect(Collectors.toList());
61
62 progress.init(classes.size(), I18n.translate("progress.mappings.enigma_file.writing"));
63
64 int steps = 0;
65 try (PrintWriter writer = new LfPrintWriter(Files.newBufferedWriter(path))) {
66 for (ClassEntry classEntry : classes) {
67 progress.step(steps++, classEntry.getFullName());
68 writeRoot(writer, mappings, classEntry);
69 }
70 } catch (IOException e) {
71 e.printStackTrace();
72 }
73 }
74 },
75 DIRECTORY {
76 @Override
77 public void write(EntryTree<EntryMapping> mappings, MappingDelta<EntryMapping> delta, Path path, ProgressListener progress, MappingSaveParameters saveParameters) {
78 Collection<ClassEntry> changedClasses = delta.getChangedRoots()
79 .filter(entry -> entry instanceof ClassEntry)
80 .map(entry -> (ClassEntry) entry)
81 .collect(Collectors.toList());
82
83 applyDeletions(path, changedClasses, mappings, delta.getBaseMappings(), saveParameters.getFileNameFormat());
84
85 progress.init(changedClasses.size(), I18n.translate("progress.mappings.enigma_directory.writing"));
86
87 AtomicInteger steps = new AtomicInteger();
88
89 Translator translator = new MappingTranslator(mappings, VoidEntryResolver.INSTANCE);
90 changedClasses.parallelStream().forEach(classEntry -> {
91 progress.step(steps.getAndIncrement(), classEntry.getFullName());
92
93 try {
94 ClassEntry fileEntry = classEntry;
95 if (saveParameters.getFileNameFormat() == MappingFileNameFormat.BY_DEOBF) {
96 fileEntry = translator.translate(fileEntry);
97 }
98
99 Path classPath = resolve(path, fileEntry);
100 Files.createDirectories(classPath.getParent());
101 Files.deleteIfExists(classPath);
102
103 try (PrintWriter writer = new LfPrintWriter(Files.newBufferedWriter(classPath))) {
104 writeRoot(writer, mappings, classEntry);
105 }
106 } catch (Throwable t) {
107 System.err.println("Failed to write class '" + classEntry.getFullName() + "'");
108 t.printStackTrace();
109 }
110 });
111 }
112
113 private void applyDeletions(Path root, Collection<ClassEntry> changedClasses, EntryTree<EntryMapping> mappings, EntryTree<EntryMapping> oldMappings, MappingFileNameFormat fileNameFormat) {
114 Translator oldMappingTranslator = new MappingTranslator(oldMappings, VoidEntryResolver.INSTANCE);
115
116 Stream<ClassEntry> deletedClassStream = changedClasses.stream()
117 .filter(e -> !Objects.equals(oldMappings.get(e), mappings.get(e)));
118
119 if (fileNameFormat == MappingFileNameFormat.BY_DEOBF) {
120 deletedClassStream = deletedClassStream.map(oldMappingTranslator::translate);
121 }
122
123 Collection<ClassEntry> deletedClasses = deletedClassStream.collect(Collectors.toList());
124
125 for (ClassEntry classEntry : deletedClasses) {
126 try {
127 Files.deleteIfExists(resolve(root, classEntry));
128 } catch (IOException e) {
129 System.err.println("Failed to delete deleted class '" + classEntry + "'");
130 e.printStackTrace();
131 }
132 }
133
134 for (ClassEntry classEntry : deletedClasses) {
135 String packageName = classEntry.getPackageName();
136 if (packageName != null) {
137 Path packagePath = Paths.get(packageName);
138 try {
139 deleteDeadPackages(root, packagePath);
140 } catch (IOException e) {
141 System.err.println("Failed to delete dead package '" + packageName + "'");
142 e.printStackTrace();
143 }
144 }
145 }
146 }
147
148 private void deleteDeadPackages(Path root, Path packagePath) throws IOException {
149 for (int i = packagePath.getNameCount() - 1; i >= 0; i--) {
150 Path subPath = packagePath.subpath(0, i + 1);
151 Path packagePart = root.resolve(subPath);
152 if (isEmpty(packagePart)) {
153 Files.deleteIfExists(packagePart);
154 }
155 }
156 }
157
158 private boolean isEmpty(Path path) {
159 try (DirectoryStream<Path> stream = Files.newDirectoryStream(path)) {
160 return !stream.iterator().hasNext();
161 } catch (IOException e) {
162 return false;
163 }
164 }
165
166 private Path resolve(Path root, ClassEntry classEntry) {
167 return root.resolve(classEntry.getFullName() + ".mapping");
168 }
169 },
170 ZIP {
171 @Override
172 public void write(EntryTree<EntryMapping> mappings, MappingDelta<EntryMapping> delta, Path zip, ProgressListener progress, MappingSaveParameters saveParameters) {
173 try (FileSystem fs = FileSystems.newFileSystem(new URI("jar:file", null, zip.toUri().getPath(), ""), Collections.singletonMap("create", "true"))) {
174 DIRECTORY.write(mappings, delta, fs.getPath("/"), progress, saveParameters);
175 } catch (IOException e) {
176 e.printStackTrace();
177 } catch (URISyntaxException e) {
178 throw new RuntimeException("Unexpected error creating URI for " + zip, e);
179 }
180 }
181 };
182
183 protected void writeRoot(PrintWriter writer, EntryTree<EntryMapping> mappings, ClassEntry classEntry) {
184 Collection<Entry<?>> children = groupChildren(mappings.getChildren(classEntry));
185
186 EntryMapping classEntryMapping = mappings.get(classEntry);
187
188 writer.println(writeClass(classEntry, classEntryMapping).trim());
189 if (classEntryMapping != null && classEntryMapping.getJavadoc() != null) {
190 writeDocs(writer, classEntryMapping, 0);
191 }
192
193 for (Entry<?> child : children) {
194 writeEntry(writer, mappings, child, 1);
195 }
196
197 }
198
199 private void writeDocs(PrintWriter writer, EntryMapping mapping, int depth) {
200 String jd = mapping.getJavadoc();
201 if (jd != null) {
202 for (String line : jd.split("\\R")) {
203 writer.println(indent(EnigmaFormat.COMMENT + " " + MappingHelper.escape(line), depth + 1));
204 }
205 }
206 }
207
208 protected void writeEntry(PrintWriter writer, EntryTree<EntryMapping> mappings, Entry<?> entry, int depth) {
209 EntryTreeNode<EntryMapping> node = mappings.findNode(entry);
210 if (node == null) {
211 return;
212 }
213
214 EntryMapping mapping = node.getValue();
215
216 if (entry instanceof ClassEntry) {
217 String line = writeClass((ClassEntry) entry, mapping);
218 writer.println(indent(line, depth));
219 } else if (entry instanceof MethodEntry) {
220 String line = writeMethod((MethodEntry) entry, mapping);
221 writer.println(indent(line, depth));
222 } else if (entry instanceof FieldEntry) {
223 String line = writeField((FieldEntry) entry, mapping);
224 writer.println(indent(line, depth));
225 } else if (entry instanceof LocalVariableEntry && mapping != null) {
226 String line = writeArgument((LocalVariableEntry) entry, mapping);
227 writer.println(indent(line, depth));
228 }
229 if (mapping != null && mapping.getJavadoc() != null) {
230 writeDocs(writer, mapping, depth);
231 }
232
233 Collection<Entry<?>> children = groupChildren(node.getChildren());
234 for (Entry<?> child : children) {
235 writeEntry(writer, mappings, child, depth + 1);
236 }
237 }
238
239 private Collection<Entry<?>> groupChildren(Collection<Entry<?>> children) {
240 Collection<Entry<?>> result = new ArrayList<>(children.size());
241
242 children.stream().filter(e -> e instanceof FieldEntry)
243 .map(e -> (FieldEntry) e)
244 .sorted()
245 .forEach(result::add);
246
247 children.stream().filter(e -> e instanceof MethodEntry)
248 .map(e -> (MethodEntry) e)
249 .sorted()
250 .forEach(result::add);
251
252 children.stream().filter(e -> e instanceof LocalVariableEntry)
253 .map(e -> (LocalVariableEntry) e)
254 .sorted()
255 .forEach(result::add);
256
257 children.stream().filter(e -> e instanceof ClassEntry)
258 .map(e -> (ClassEntry) e)
259 .sorted()
260 .forEach(result::add);
261
262 return result;
263 }
264
265 protected String writeClass(ClassEntry entry, EntryMapping mapping) {
266 StringBuilder builder = new StringBuilder(EnigmaFormat.CLASS +" ");
267 builder.append(entry.getName()).append(' ');
268 writeMapping(builder, mapping);
269
270 return builder.toString();
271 }
272
273 protected String writeMethod(MethodEntry entry, EntryMapping mapping) {
274 StringBuilder builder = new StringBuilder(EnigmaFormat.METHOD + " ");
275 builder.append(entry.getName()).append(' ');
276 if (mapping != null && !mapping.getTargetName().equals(entry.getName())) {
277 writeMapping(builder, mapping);
278 }
279
280 builder.append(entry.getDesc().toString());
281
282 return builder.toString();
283 }
284
285 protected String writeField(FieldEntry entry, EntryMapping mapping) {
286 StringBuilder builder = new StringBuilder(EnigmaFormat.FIELD + " ");
287 builder.append(entry.getName()).append(' ');
288 if (mapping != null && !mapping.getTargetName().equals(entry.getName())) {
289 writeMapping(builder, mapping);
290 }
291
292 builder.append(entry.getDesc().toString());
293
294 return builder.toString();
295 }
296
297 protected String writeArgument(LocalVariableEntry entry, EntryMapping mapping) {
298 return EnigmaFormat.PARAMETER + " " + entry.getIndex() + ' ' + mapping.getTargetName();
299 }
300
301 private void writeMapping(StringBuilder builder, EntryMapping mapping) {
302 if (mapping != null) {
303 builder.append(mapping.getTargetName()).append(' ');
304 if (mapping.getAccessModifier() != AccessModifier.UNCHANGED) {
305 builder.append(mapping.getAccessModifier().getFormattedName()).append(' ');
306 }
307 }
308 }
309
310 private String indent(String line, int depth) {
311 StringBuilder builder = new StringBuilder();
312 for (int i = 0; i < depth; i++) {
313 builder.append("\t");
314 }
315 builder.append(line.trim());
316 return builder.toString();
317 }
318}
diff --git a/enigma/src/main/java/cuchaz/enigma/translation/mapping/serde/proguard/ProguardMappingsReader.java b/enigma/src/main/java/cuchaz/enigma/translation/mapping/serde/proguard/ProguardMappingsReader.java
new file mode 100644
index 0000000..034fb41
--- /dev/null
+++ b/enigma/src/main/java/cuchaz/enigma/translation/mapping/serde/proguard/ProguardMappingsReader.java
@@ -0,0 +1,135 @@
1package cuchaz.enigma.translation.mapping.serde.proguard;
2
3import cuchaz.enigma.ProgressListener;
4import cuchaz.enigma.translation.mapping.MappingOperations;
5import cuchaz.enigma.translation.mapping.serde.MappingParseException;
6import cuchaz.enigma.translation.mapping.EntryMapping;
7import cuchaz.enigma.translation.mapping.serde.MappingSaveParameters;
8import cuchaz.enigma.translation.mapping.serde.MappingsReader;
9import cuchaz.enigma.translation.mapping.tree.EntryTree;
10import cuchaz.enigma.translation.mapping.tree.HashEntryTree;
11import cuchaz.enigma.translation.representation.MethodDescriptor;
12import cuchaz.enigma.translation.representation.TypeDescriptor;
13import cuchaz.enigma.translation.representation.entry.ClassEntry;
14import cuchaz.enigma.translation.representation.entry.FieldEntry;
15import cuchaz.enigma.translation.representation.entry.MethodEntry;
16
17import java.io.IOException;
18import java.nio.charset.StandardCharsets;
19import java.nio.file.Files;
20import java.nio.file.Path;
21import java.util.regex.Matcher;
22import java.util.regex.Pattern;
23
24public class ProguardMappingsReader implements MappingsReader {
25 public static final ProguardMappingsReader INSTANCE = new ProguardMappingsReader();
26 private static final String NAME = "[a-zA-Z0-9_\\-.$<>]+";
27 private static final String TYPE = NAME + "(?:\\[])*";
28 private static final String TYPE_LIST = "|(?:(?:" + TYPE + ",)*" + TYPE + ")";
29 private static final Pattern CLASS = Pattern.compile("(" + NAME + ") -> (" + NAME + "):");
30 private static final Pattern FIELD = Pattern.compile(" {4}(" + TYPE + ") (" + NAME + ") -> (" + NAME + ")");
31 private static final Pattern METHOD = Pattern.compile(" {4}(?:[0-9]+:[0-9]+:)?(" + TYPE + ") (" + NAME + ")\\((" + TYPE_LIST + ")\\) -> (" + NAME + ")");
32
33 public ProguardMappingsReader() {}
34
35 @Override
36 public EntryTree<EntryMapping> read(Path path, ProgressListener progress, MappingSaveParameters saveParameters) throws MappingParseException, IOException {
37 EntryTree<EntryMapping> mappings = new HashEntryTree<>();
38
39 int lineNumber = 0;
40 ClassEntry currentClass = null;
41 for (String line : Files.readAllLines(path, StandardCharsets.UTF_8)) {
42 lineNumber++;
43
44 if (line.startsWith("#") || line.isEmpty()) {
45 continue;
46 }
47
48 Matcher classMatcher = CLASS.matcher(line);
49 Matcher fieldMatcher = FIELD.matcher(line);
50 Matcher methodMatcher = METHOD.matcher(line);
51
52 if (classMatcher.matches()) {
53 String name = classMatcher.group(1);
54 String targetName = classMatcher.group(2);
55
56 mappings.insert(currentClass = new ClassEntry(name.replace('.', '/')), new EntryMapping(ClassEntry.getInnerName(targetName.replace('.', '/'))));
57 } else if (fieldMatcher.matches()) {
58 String type = fieldMatcher.group(1);
59 String name = fieldMatcher.group(2);
60 String targetName = fieldMatcher.group(3);
61
62 if (currentClass == null) {
63 throw new MappingParseException(path::toString, lineNumber, "field mapping not inside class: " + line);
64 }
65
66 mappings.insert(new FieldEntry(currentClass, name, new TypeDescriptor(getDescriptor(type))), new EntryMapping(targetName));
67 } else if (methodMatcher.matches()) {
68 String returnType = methodMatcher.group(1);
69 String name = methodMatcher.group(2);
70 String[] parameterTypes = methodMatcher.group(3).isEmpty() ? new String[0] : methodMatcher.group(3).split(",");
71 String targetName = methodMatcher.group(4);
72
73 if (currentClass == null) {
74 throw new MappingParseException(path::toString, lineNumber, "method mapping not inside class: " + line);
75 }
76
77 mappings.insert(new MethodEntry(currentClass, name, new MethodDescriptor(getDescriptor(returnType, parameterTypes))), new EntryMapping(targetName));
78 } else {
79 throw new MappingParseException(path::toString, lineNumber, "invalid mapping line: " + line);
80 }
81 }
82
83 return MappingOperations.invert(mappings);
84 }
85
86 private String getDescriptor(String type) {
87 StringBuilder descriptor = new StringBuilder();
88
89 while (type.endsWith("[]")) {
90 descriptor.append("[");
91 type = type.substring(0, type.length() - 2);
92 }
93
94 switch (type) {
95 case "byte":
96 return descriptor + "B";
97 case "char":
98 return descriptor + "C";
99 case "short":
100 return descriptor + "S";
101 case "int":
102 return descriptor + "I";
103 case "long":
104 return descriptor + "J";
105 case "float":
106 return descriptor + "F";
107 case "double":
108 return descriptor + "D";
109 case "boolean":
110 return descriptor + "Z";
111 case "void":
112 return descriptor + "V";
113 }
114
115 descriptor.append("L");
116 descriptor.append(type.replace('.', '/'));
117 descriptor.append(";");
118
119 return descriptor.toString();
120 }
121
122 private String getDescriptor(String returnType, String[] parameterTypes) {
123 StringBuilder descriptor = new StringBuilder();
124 descriptor.append('(');
125
126 for (String parameterType : parameterTypes) {
127 descriptor.append(getDescriptor(parameterType));
128 }
129
130 descriptor.append(')');
131 descriptor.append(getDescriptor(returnType));
132
133 return descriptor.toString();
134 }
135}
diff --git a/enigma/src/main/java/cuchaz/enigma/translation/mapping/serde/srg/SrgMappingsWriter.java b/enigma/src/main/java/cuchaz/enigma/translation/mapping/serde/srg/SrgMappingsWriter.java
new file mode 100644
index 0000000..9c9f103
--- /dev/null
+++ b/enigma/src/main/java/cuchaz/enigma/translation/mapping/serde/srg/SrgMappingsWriter.java
@@ -0,0 +1,119 @@
1package cuchaz.enigma.translation.mapping.serde.srg;
2
3import com.google.common.collect.Lists;
4import cuchaz.enigma.ProgressListener;
5import cuchaz.enigma.translation.MappingTranslator;
6import cuchaz.enigma.translation.Translator;
7import cuchaz.enigma.translation.mapping.EntryMapping;
8import cuchaz.enigma.translation.mapping.MappingDelta;
9import cuchaz.enigma.translation.mapping.serde.MappingSaveParameters;
10import cuchaz.enigma.translation.mapping.VoidEntryResolver;
11import cuchaz.enigma.translation.mapping.serde.LfPrintWriter;
12import cuchaz.enigma.translation.mapping.serde.MappingsWriter;
13import cuchaz.enigma.translation.mapping.tree.EntryTree;
14import cuchaz.enigma.translation.mapping.tree.EntryTreeNode;
15import cuchaz.enigma.translation.representation.entry.ClassEntry;
16import cuchaz.enigma.translation.representation.entry.Entry;
17import cuchaz.enigma.translation.representation.entry.FieldEntry;
18import cuchaz.enigma.translation.representation.entry.MethodEntry;
19import cuchaz.enigma.utils.I18n;
20
21import java.io.IOException;
22import java.io.PrintWriter;
23import java.nio.file.Files;
24import java.nio.file.Path;
25import java.util.ArrayList;
26import java.util.Collection;
27import java.util.Comparator;
28import java.util.List;
29import java.util.stream.Collectors;
30
31public enum SrgMappingsWriter implements MappingsWriter {
32 INSTANCE;
33
34 @Override
35 public void write(EntryTree<EntryMapping> mappings, MappingDelta<EntryMapping> delta, Path path, ProgressListener progress, MappingSaveParameters saveParameters) {
36 try {
37 Files.deleteIfExists(path);
38 Files.createFile(path);
39 } catch (IOException e) {
40 e.printStackTrace();
41 }
42
43 List<String> classLines = new ArrayList<>();
44 List<String> fieldLines = new ArrayList<>();
45 List<String> methodLines = new ArrayList<>();
46
47 Collection<Entry<?>> rootEntries = Lists.newArrayList(mappings).stream()
48 .map(EntryTreeNode::getEntry)
49 .collect(Collectors.toList());
50 progress.init(rootEntries.size(), I18n.translate("progress.mappings.srg_file.generating"));
51
52 int steps = 0;
53 for (Entry<?> entry : sorted(rootEntries)) {
54 progress.step(steps++, entry.getName());
55 writeEntry(classLines, fieldLines, methodLines, mappings, entry);
56 }
57
58 progress.init(3, I18n.translate("progress.mappings.srg_file.writing"));
59 try (PrintWriter writer = new LfPrintWriter(Files.newBufferedWriter(path))) {
60 progress.step(0, I18n.translate("type.classes"));
61 classLines.forEach(writer::println);
62 progress.step(1, I18n.translate("type.fields"));
63 fieldLines.forEach(writer::println);
64 progress.step(2, I18n.translate("type.methods"));
65 methodLines.forEach(writer::println);
66 } catch (IOException e) {
67 e.printStackTrace();
68 }
69 }
70
71 private void writeEntry(List<String> classes, List<String> fields, List<String> methods, EntryTree<EntryMapping> mappings, Entry<?> entry) {
72 EntryTreeNode<EntryMapping> node = mappings.findNode(entry);
73 if (node == null) {
74 return;
75 }
76
77 Translator translator = new MappingTranslator(mappings, VoidEntryResolver.INSTANCE);
78 if (entry instanceof ClassEntry) {
79 classes.add(generateClassLine((ClassEntry) entry, translator));
80 } else if (entry instanceof FieldEntry) {
81 fields.add(generateFieldLine((FieldEntry) entry, translator));
82 } else if (entry instanceof MethodEntry) {
83 methods.add(generateMethodLine((MethodEntry) entry, translator));
84 }
85
86 for (Entry<?> child : sorted(node.getChildren())) {
87 writeEntry(classes, fields, methods, mappings, child);
88 }
89 }
90
91 private String generateClassLine(ClassEntry sourceEntry, Translator translator) {
92 ClassEntry targetEntry = translator.translate(sourceEntry);
93 return "CL: " + sourceEntry.getFullName() + " " + targetEntry.getFullName();
94 }
95
96 private String generateMethodLine(MethodEntry sourceEntry, Translator translator) {
97 MethodEntry targetEntry = translator.translate(sourceEntry);
98 return "MD: " + describeMethod(sourceEntry) + " " + describeMethod(targetEntry);
99 }
100
101 private String describeMethod(MethodEntry entry) {
102 return entry.getParent().getFullName() + "/" + entry.getName() + " " + entry.getDesc();
103 }
104
105 private String generateFieldLine(FieldEntry sourceEntry, Translator translator) {
106 FieldEntry targetEntry = translator.translate(sourceEntry);
107 return "FD: " + describeField(sourceEntry) + " " + describeField(targetEntry);
108 }
109
110 private String describeField(FieldEntry entry) {
111 return entry.getParent().getFullName() + "/" + entry.getName();
112 }
113
114 private Collection<Entry<?>> sorted(Iterable<Entry<?>> iterable) {
115 ArrayList<Entry<?>> sorted = Lists.newArrayList(iterable);
116 sorted.sort(Comparator.comparing(Entry::getName));
117 return sorted;
118 }
119}
diff --git a/enigma/src/main/java/cuchaz/enigma/translation/mapping/serde/tiny/TinyMappingsReader.java b/enigma/src/main/java/cuchaz/enigma/translation/mapping/serde/tiny/TinyMappingsReader.java
new file mode 100644
index 0000000..f3c66df
--- /dev/null
+++ b/enigma/src/main/java/cuchaz/enigma/translation/mapping/serde/tiny/TinyMappingsReader.java
@@ -0,0 +1,116 @@
1package cuchaz.enigma.translation.mapping.serde.tiny;
2
3import com.google.common.base.Charsets;
4import cuchaz.enigma.ProgressListener;
5import cuchaz.enigma.translation.mapping.serde.MappingParseException;
6import cuchaz.enigma.translation.mapping.EntryMapping;
7import cuchaz.enigma.translation.mapping.MappingPair;
8import cuchaz.enigma.translation.mapping.serde.MappingSaveParameters;
9import cuchaz.enigma.translation.mapping.serde.MappingsReader;
10import cuchaz.enigma.translation.mapping.tree.EntryTree;
11import cuchaz.enigma.translation.mapping.tree.HashEntryTree;
12import cuchaz.enigma.translation.representation.MethodDescriptor;
13import cuchaz.enigma.translation.representation.TypeDescriptor;
14import cuchaz.enigma.translation.representation.entry.ClassEntry;
15import cuchaz.enigma.translation.representation.entry.FieldEntry;
16import cuchaz.enigma.translation.representation.entry.LocalVariableEntry;
17import cuchaz.enigma.translation.representation.entry.MethodEntry;
18import cuchaz.enigma.utils.I18n;
19
20import java.io.IOException;
21import java.nio.file.Files;
22import java.nio.file.Path;
23import java.util.List;
24
25public enum TinyMappingsReader implements MappingsReader {
26 INSTANCE;
27
28 @Override
29 public EntryTree<EntryMapping> read(Path path, ProgressListener progress, MappingSaveParameters saveParameters) throws IOException, MappingParseException {
30 return read(path, Files.readAllLines(path, Charsets.UTF_8), progress);
31 }
32
33 private EntryTree<EntryMapping> read(Path path, List<String> lines, ProgressListener progress) throws MappingParseException {
34 EntryTree<EntryMapping> mappings = new HashEntryTree<>();
35 lines.remove(0);
36
37 progress.init(lines.size(), I18n.translate("progress.mappings.tiny_file.loading"));
38
39 for (int lineNumber = 0; lineNumber < lines.size(); lineNumber++) {
40 progress.step(lineNumber, "");
41
42 String line = lines.get(lineNumber);
43
44 if (line.trim().startsWith("#")) {
45 continue;
46 }
47
48 try {
49 MappingPair<?, EntryMapping> mapping = parseLine(line);
50 mappings.insert(mapping.getEntry(), mapping.getMapping());
51 } catch (Throwable t) {
52 t.printStackTrace();
53 throw new MappingParseException(path::toString, lineNumber, t.toString());
54 }
55 }
56
57 return mappings;
58 }
59
60 private MappingPair<?, EntryMapping> parseLine(String line) {
61 String[] tokens = line.split("\t");
62
63 String key = tokens[0];
64 switch (key) {
65 case "CLASS":
66 return parseClass(tokens);
67 case "FIELD":
68 return parseField(tokens);
69 case "METHOD":
70 return parseMethod(tokens);
71 case "MTH-ARG":
72 return parseArgument(tokens);
73 default:
74 throw new RuntimeException("Unknown token '" + key + "'!");
75 }
76 }
77
78 private MappingPair<ClassEntry, EntryMapping> parseClass(String[] tokens) {
79 ClassEntry obfuscatedEntry = new ClassEntry(tokens[1]);
80 String mapping = tokens[2];
81 if (mapping.indexOf('$') > 0) {
82 // inner classes should map to only the final part
83 mapping = mapping.substring(mapping.lastIndexOf('$') + 1);
84 }
85 return new MappingPair<>(obfuscatedEntry, new EntryMapping(mapping));
86 }
87
88 private MappingPair<FieldEntry, EntryMapping> parseField(String[] tokens) {
89 ClassEntry ownerClass = new ClassEntry(tokens[1]);
90 TypeDescriptor descriptor = new TypeDescriptor(tokens[2]);
91
92 FieldEntry obfuscatedEntry = new FieldEntry(ownerClass, tokens[3], descriptor);
93 String mapping = tokens[4];
94 return new MappingPair<>(obfuscatedEntry, new EntryMapping(mapping));
95 }
96
97 private MappingPair<MethodEntry, EntryMapping> parseMethod(String[] tokens) {
98 ClassEntry ownerClass = new ClassEntry(tokens[1]);
99 MethodDescriptor descriptor = new MethodDescriptor(tokens[2]);
100
101 MethodEntry obfuscatedEntry = new MethodEntry(ownerClass, tokens[3], descriptor);
102 String mapping = tokens[4];
103 return new MappingPair<>(obfuscatedEntry, new EntryMapping(mapping));
104 }
105
106 private MappingPair<LocalVariableEntry, EntryMapping> parseArgument(String[] tokens) {
107 ClassEntry ownerClass = new ClassEntry(tokens[1]);
108 MethodDescriptor ownerDescriptor = new MethodDescriptor(tokens[2]);
109 MethodEntry ownerMethod = new MethodEntry(ownerClass, tokens[3], ownerDescriptor);
110 int variableIndex = Integer.parseInt(tokens[4]);
111
112 String mapping = tokens[5];
113 LocalVariableEntry obfuscatedEntry = new LocalVariableEntry(ownerMethod, variableIndex, "", true, null);
114 return new MappingPair<>(obfuscatedEntry, new EntryMapping(mapping));
115 }
116}
diff --git a/enigma/src/main/java/cuchaz/enigma/translation/mapping/serde/tiny/TinyMappingsWriter.java b/enigma/src/main/java/cuchaz/enigma/translation/mapping/serde/tiny/TinyMappingsWriter.java
new file mode 100644
index 0000000..4f78e6f
--- /dev/null
+++ b/enigma/src/main/java/cuchaz/enigma/translation/mapping/serde/tiny/TinyMappingsWriter.java
@@ -0,0 +1,149 @@
1package cuchaz.enigma.translation.mapping.serde.tiny;
2
3import com.google.common.base.Joiner;
4import com.google.common.collect.Lists;
5import cuchaz.enigma.ProgressListener;
6import cuchaz.enigma.translation.MappingTranslator;
7import cuchaz.enigma.translation.Translator;
8import cuchaz.enigma.translation.mapping.EntryMapping;
9import cuchaz.enigma.translation.mapping.MappingDelta;
10import cuchaz.enigma.translation.mapping.serde.MappingSaveParameters;
11import cuchaz.enigma.translation.mapping.VoidEntryResolver;
12import cuchaz.enigma.translation.mapping.serde.MappingsWriter;
13import cuchaz.enigma.translation.mapping.tree.EntryTree;
14import cuchaz.enigma.translation.mapping.tree.EntryTreeNode;
15import cuchaz.enigma.translation.representation.entry.ClassEntry;
16import cuchaz.enigma.translation.representation.entry.Entry;
17import cuchaz.enigma.translation.representation.entry.FieldEntry;
18import cuchaz.enigma.translation.representation.entry.MethodEntry;
19
20import java.io.BufferedWriter;
21import java.io.IOException;
22import java.io.Writer;
23import java.nio.charset.StandardCharsets;
24import java.nio.file.Files;
25import java.nio.file.Path;
26import java.util.Comparator;
27import java.util.HashSet;
28import java.util.Set;
29
30public class TinyMappingsWriter implements MappingsWriter {
31 private static final String VERSION_CONSTANT = "v1";
32 private static final Joiner TAB_JOINER = Joiner.on('\t');
33
34 //Possibly add a gui or a way to select the namespaces when exporting from the gui
35 public static final TinyMappingsWriter INSTANCE = new TinyMappingsWriter("intermediary", "named");
36
37 // HACK: as of enigma 0.13.1, some fields seem to appear duplicated?
38 private final Set<String> writtenLines = new HashSet<>();
39 private final String nameObf;
40 private final String nameDeobf;
41
42 public TinyMappingsWriter(String nameObf, String nameDeobf) {
43 this.nameObf = nameObf;
44 this.nameDeobf = nameDeobf;
45 }
46
47 @Override
48 public void write(EntryTree<EntryMapping> mappings, MappingDelta<EntryMapping> delta, Path path, ProgressListener progress, MappingSaveParameters saveParameters) {
49 try {
50 Files.deleteIfExists(path);
51 Files.createFile(path);
52 } catch (IOException e) {
53 e.printStackTrace();
54 }
55
56 try (BufferedWriter writer = Files.newBufferedWriter(path, StandardCharsets.UTF_8)) {
57 writeLine(writer, new String[]{VERSION_CONSTANT, nameObf, nameDeobf});
58
59 Lists.newArrayList(mappings).stream()
60 .map(EntryTreeNode::getEntry).sorted(Comparator.comparing(Object::toString))
61 .forEach(entry -> writeEntry(writer, mappings, entry));
62 } catch (IOException e) {
63 e.printStackTrace();
64 }
65 }
66
67 private void writeEntry(Writer writer, EntryTree<EntryMapping> mappings, Entry<?> entry) {
68 EntryTreeNode<EntryMapping> node = mappings.findNode(entry);
69 if (node == null) {
70 return;
71 }
72
73 Translator translator = new MappingTranslator(mappings, VoidEntryResolver.INSTANCE);
74
75 EntryMapping mapping = mappings.get(entry);
76 if (mapping != null && !entry.getName().equals(mapping.getTargetName())) {
77 if (entry instanceof ClassEntry) {
78 writeClass(writer, (ClassEntry) entry, translator);
79 } else if (entry instanceof FieldEntry) {
80 writeLine(writer, serializeEntry(entry, mapping.getTargetName()));
81 } else if (entry instanceof MethodEntry) {
82 writeLine(writer, serializeEntry(entry, mapping.getTargetName()));
83 }
84 }
85
86 writeChildren(writer, mappings, node);
87 }
88
89 private void writeChildren(Writer writer, EntryTree<EntryMapping> mappings, EntryTreeNode<EntryMapping> node) {
90 node.getChildren().stream()
91 .filter(e -> e instanceof FieldEntry).sorted()
92 .forEach(child -> writeEntry(writer, mappings, child));
93
94 node.getChildren().stream()
95 .filter(e -> e instanceof MethodEntry).sorted()
96 .forEach(child -> writeEntry(writer, mappings, child));
97
98 node.getChildren().stream()
99 .filter(e -> e instanceof ClassEntry).sorted()
100 .forEach(child -> writeEntry(writer, mappings, child));
101 }
102
103 private void writeClass(Writer writer, ClassEntry entry, Translator translator) {
104 ClassEntry translatedEntry = translator.translate(entry);
105
106 String obfClassName = entry.getFullName();
107 String deobfClassName = translatedEntry.getFullName();
108 writeLine(writer, new String[]{"CLASS", obfClassName, deobfClassName});
109 }
110
111 private void writeLine(Writer writer, String[] data) {
112 try {
113 String line = TAB_JOINER.join(data) + "\n";
114 if (writtenLines.add(line)) {
115 writer.write(line);
116 }
117 } catch (IOException e) {
118 throw new RuntimeException(e);
119 }
120 }
121
122 private String[] serializeEntry(Entry<?> entry, String... extraFields) {
123 String[] data = null;
124
125 if (entry instanceof FieldEntry) {
126 data = new String[4 + extraFields.length];
127 data[0] = "FIELD";
128 data[1] = entry.getContainingClass().getFullName();
129 data[2] = ((FieldEntry) entry).getDesc().toString();
130 data[3] = entry.getName();
131 } else if (entry instanceof MethodEntry) {
132 data = new String[4 + extraFields.length];
133 data[0] = "METHOD";
134 data[1] = entry.getContainingClass().getFullName();
135 data[2] = ((MethodEntry) entry).getDesc().toString();
136 data[3] = entry.getName();
137 } else if (entry instanceof ClassEntry) {
138 data = new String[2 + extraFields.length];
139 data[0] = "CLASS";
140 data[1] = ((ClassEntry) entry).getFullName();
141 }
142
143 if (data != null) {
144 System.arraycopy(extraFields, 0, data, data.length - extraFields.length, extraFields.length);
145 }
146
147 return data;
148 }
149}
diff --git a/enigma/src/main/java/cuchaz/enigma/translation/mapping/serde/tinyv2/TinyV2Reader.java b/enigma/src/main/java/cuchaz/enigma/translation/mapping/serde/tinyv2/TinyV2Reader.java
new file mode 100644
index 0000000..dc25569
--- /dev/null
+++ b/enigma/src/main/java/cuchaz/enigma/translation/mapping/serde/tinyv2/TinyV2Reader.java
@@ -0,0 +1,296 @@
1package cuchaz.enigma.translation.mapping.serde.tinyv2;
2
3import cuchaz.enigma.ProgressListener;
4import cuchaz.enigma.translation.mapping.serde.MappingParseException;
5import cuchaz.enigma.translation.mapping.EntryMapping;
6import cuchaz.enigma.translation.mapping.MappingPair;
7import cuchaz.enigma.translation.mapping.serde.MappingSaveParameters;
8import cuchaz.enigma.translation.mapping.serde.MappingsReader;
9import cuchaz.enigma.translation.mapping.serde.RawEntryMapping;
10import cuchaz.enigma.translation.mapping.tree.EntryTree;
11import cuchaz.enigma.translation.mapping.tree.HashEntryTree;
12import cuchaz.enigma.translation.representation.MethodDescriptor;
13import cuchaz.enigma.translation.representation.TypeDescriptor;
14import cuchaz.enigma.translation.representation.entry.ClassEntry;
15import cuchaz.enigma.translation.representation.entry.Entry;
16import cuchaz.enigma.translation.representation.entry.FieldEntry;
17import cuchaz.enigma.translation.representation.entry.LocalVariableEntry;
18import cuchaz.enigma.translation.representation.entry.MethodEntry;
19
20import java.io.IOException;
21import java.nio.charset.StandardCharsets;
22import java.nio.file.Files;
23import java.nio.file.Path;
24import java.util.BitSet;
25import java.util.List;
26
27public final class TinyV2Reader implements MappingsReader {
28
29 private static final String MINOR_VERSION = "0";
30 // 0 indent
31 private static final int IN_HEADER = 0;
32 private static final int IN_CLASS = IN_HEADER + 1;
33 // 1 indent
34 private static final int IN_METHOD = IN_CLASS + 1;
35 private static final int IN_FIELD = IN_METHOD + 1;
36 // 2 indent
37 private static final int IN_PARAMETER = IN_FIELD + 1;
38 // general properties
39 private static final int STATE_SIZE = IN_PARAMETER + 1;
40 private static final int[] INDENT_CLEAR_START = {IN_HEADER, IN_METHOD, IN_PARAMETER, STATE_SIZE};
41
42 @Override
43 public EntryTree<EntryMapping> read(Path path, ProgressListener progress, MappingSaveParameters saveParameters) throws IOException, MappingParseException {
44 return read(path, Files.readAllLines(path, StandardCharsets.UTF_8), progress);
45 }
46
47 private EntryTree<EntryMapping> read(Path path, List<String> lines, ProgressListener progress) throws MappingParseException {
48 EntryTree<EntryMapping> mappings = new HashEntryTree<>();
49
50 progress.init(lines.size(), "progress.mappings.tiny_v2.loading");
51
52 BitSet state = new BitSet(STATE_SIZE);
53 @SuppressWarnings({"unchecked", "rawtypes"})
54 MappingPair<? extends Entry<?>, RawEntryMapping>[] holds = new MappingPair[STATE_SIZE];
55 boolean escapeNames = false;
56
57 for (int lineNumber = 0; lineNumber < lines.size(); lineNumber++) {
58 try {
59 progress.step(lineNumber, "");
60 String line = lines.get(lineNumber);
61
62 int indent = 0;
63 while (line.charAt(indent) == '\t')
64 indent++;
65
66 String[] parts = line.substring(indent).split("\t", -1);
67 if (parts.length == 0 || indent >= INDENT_CLEAR_START.length)
68 throw new IllegalArgumentException("Invalid format");
69
70 // clean and register stuff in stack
71 for (int i = INDENT_CLEAR_START[indent]; i < STATE_SIZE; i++) {
72 state.clear(i);
73 if (holds[i] != null) {
74 RawEntryMapping mapping = holds[i].getMapping();
75 if (mapping != null) {
76 EntryMapping baked = mapping.bake();
77 if (baked != null) {
78 mappings.insert(holds[i].getEntry(), baked);
79 }
80 }
81 holds[i] = null;
82 }
83 }
84
85 switch (indent) {
86 case 0:
87 switch (parts[0]) {
88 case "tiny": // header
89 if (lineNumber != 0) {
90 throw new IllegalArgumentException("Header can only be on the first line");
91 }
92 if (parts.length < 5) {
93 throw new IllegalArgumentException("Not enough header columns, needs at least 5");
94 }
95 if (!"2".equals(parts[1]) || !MINOR_VERSION.equals(parts[2])) {
96 throw new IllegalArgumentException("Unsupported TinyV2 version, requires major " + "2" + " and minor " + MINOR_VERSION + "");
97 }
98 state.set(IN_HEADER);
99 break;
100 case "c": // class
101 state.set(IN_CLASS);
102 holds[IN_CLASS] = parseClass(parts, escapeNames);
103 break;
104 default:
105 unsupportKey(parts);
106 }
107
108 break;
109 case 1:
110 if (state.get(IN_HEADER)) {
111 if (parts[0].equals("esacpe-names")) {
112 escapeNames = true;
113 }
114
115 break;
116 }
117
118 if (state.get(IN_CLASS)) {
119 switch (parts[0]) {
120 case "m": // method
121 state.set(IN_METHOD);
122 holds[IN_METHOD] = parseMethod(holds[IN_CLASS], parts, escapeNames);
123 break;
124 case "f": // field
125 state.set(IN_FIELD);
126 holds[IN_FIELD] = parseField(holds[IN_CLASS], parts, escapeNames);
127 break;
128 case "c": // class javadoc
129 addJavadoc(holds[IN_CLASS], parts);
130 break;
131 default:
132 unsupportKey(parts);
133 }
134 break;
135 }
136
137 unsupportKey(parts);
138 case 2:
139 if (state.get(IN_METHOD)) {
140 switch (parts[0]) {
141 case "p": // parameter
142 state.set(IN_PARAMETER);
143 holds[IN_PARAMETER] = parseArgument(holds[IN_METHOD], parts, escapeNames);
144 break;
145 case "v": // local variable
146 // TODO add local var mapping
147 break;
148 case "c": // method javadoc
149 addJavadoc(holds[IN_METHOD], parts);
150 break;
151 default:
152 unsupportKey(parts);
153 }
154 break;
155 }
156
157 if (state.get(IN_FIELD)) {
158 switch (parts[0]) {
159 case "c": // field javadoc
160 addJavadoc(holds[IN_FIELD], parts);
161 break;
162 default:
163 unsupportKey(parts);
164 }
165 break;
166 }
167 unsupportKey(parts);
168 case 3:
169 if (state.get(IN_PARAMETER)) {
170 switch (parts[0]) {
171 case "c":
172 addJavadoc(holds[IN_PARAMETER], parts);
173 break;
174 default:
175 unsupportKey(parts);
176 }
177 break;
178 }
179 unsupportKey(parts);
180 default:
181 unsupportKey(parts);
182 }
183
184 } catch (Throwable t) {
185 t.printStackTrace();
186 throw new MappingParseException(path::toString, lineNumber + 1, t.toString());
187 }
188 }
189
190 return mappings;
191 }
192
193 private void unsupportKey(String[] parts) {
194 throw new IllegalArgumentException("Unsupported key " + parts[0]);
195 }
196
197 private void addJavadoc(MappingPair<? extends Entry, RawEntryMapping> pair, String[] parts) {
198 if (parts.length != 2) {
199 throw new IllegalArgumentException("Invalid javadoc declaration");
200 }
201
202 addJavadoc(pair, parts[1]);
203 }
204
205 private MappingPair<ClassEntry, RawEntryMapping> parseClass(String[] tokens, boolean escapeNames) {
206 ClassEntry obfuscatedEntry = new ClassEntry(unescapeOpt(tokens[1], escapeNames));
207 if (tokens.length <= 2)
208 return new MappingPair<>(obfuscatedEntry);
209 String token2 = unescapeOpt(tokens[2], escapeNames);
210 String mapping = token2.substring(token2.lastIndexOf('$') + 1);
211 return new MappingPair<>(obfuscatedEntry, new RawEntryMapping(mapping));
212 }
213
214 private MappingPair<FieldEntry, RawEntryMapping> parseField(MappingPair<? extends Entry, RawEntryMapping> parent, String[] tokens, boolean escapeNames) {
215 ClassEntry ownerClass = (ClassEntry) parent.getEntry();
216 TypeDescriptor descriptor = new TypeDescriptor(unescapeOpt(tokens[1], escapeNames));
217
218 FieldEntry obfuscatedEntry = new FieldEntry(ownerClass, unescapeOpt(tokens[2], escapeNames), descriptor);
219 if (tokens.length <= 3)
220 return new MappingPair<>(obfuscatedEntry);
221 String mapping = unescapeOpt(tokens[3], escapeNames);
222 return new MappingPair<>(obfuscatedEntry, new RawEntryMapping(mapping));
223 }
224
225 private MappingPair<MethodEntry, RawEntryMapping> parseMethod(MappingPair<? extends Entry, RawEntryMapping> parent, String[] tokens, boolean escapeNames) {
226 ClassEntry ownerClass = (ClassEntry) parent.getEntry();
227 MethodDescriptor descriptor = new MethodDescriptor(unescapeOpt(tokens[1], escapeNames));
228
229 MethodEntry obfuscatedEntry = new MethodEntry(ownerClass, unescapeOpt(tokens[2], escapeNames), descriptor);
230 if (tokens.length <= 3)
231 return new MappingPair<>(obfuscatedEntry);
232 String mapping = unescapeOpt(tokens[3], escapeNames);
233 return new MappingPair<>(obfuscatedEntry, new RawEntryMapping(mapping));
234 }
235
236
237
238 private void addJavadoc(MappingPair<? extends Entry, RawEntryMapping> pair, String javadoc) {
239 RawEntryMapping mapping = pair.getMapping();
240 if (mapping == null) {
241 throw new IllegalArgumentException("Javadoc requires a mapping in enigma!");
242 }
243 mapping.addJavadocLine(unescape(javadoc));
244 }
245
246
247
248 private MappingPair<LocalVariableEntry, RawEntryMapping> parseArgument(MappingPair<? extends Entry, RawEntryMapping> parent, String[] tokens, boolean escapeNames) {
249 MethodEntry ownerMethod = (MethodEntry) parent.getEntry();
250 int variableIndex = Integer.parseInt(tokens[1]);
251
252 // tokens[2] is the useless obf name
253
254 LocalVariableEntry obfuscatedEntry = new LocalVariableEntry(ownerMethod, variableIndex, "", true, null);
255 if (tokens.length <= 3)
256 return new MappingPair<>(obfuscatedEntry);
257 String mapping = unescapeOpt(tokens[3], escapeNames);
258 return new MappingPair<>(obfuscatedEntry, new RawEntryMapping(mapping));
259 }
260
261 private static final String TO_ESCAPE = "\\\n\r\0\t";
262 private static final String ESCAPED = "\\nr0t";
263
264 private static String unescapeOpt(String raw, boolean escapedStrings) {
265 return escapedStrings ? unescape(raw) : raw;
266 }
267
268 private static String unescape(String str) {
269 // copied from matcher, lazy!
270 int pos = str.indexOf('\\');
271 if (pos < 0) return str;
272
273 StringBuilder ret = new StringBuilder(str.length() - 1);
274 int start = 0;
275
276 do {
277 ret.append(str, start, pos);
278 pos++;
279 int type;
280
281 if (pos >= str.length()) {
282 throw new RuntimeException("incomplete escape sequence at the end");
283 } else if ((type = ESCAPED.indexOf(str.charAt(pos))) < 0) {
284 throw new RuntimeException("invalid escape character: \\" + str.charAt(pos));
285 } else {
286 ret.append(TO_ESCAPE.charAt(type));
287 }
288
289 start = pos + 1;
290 } while ((pos = str.indexOf('\\', start)) >= 0);
291
292 ret.append(str, start, str.length());
293
294 return ret.toString();
295 }
296}
diff --git a/enigma/src/main/java/cuchaz/enigma/translation/mapping/serde/tinyv2/TinyV2Writer.java b/enigma/src/main/java/cuchaz/enigma/translation/mapping/serde/tinyv2/TinyV2Writer.java
new file mode 100644
index 0000000..5546b2f
--- /dev/null
+++ b/enigma/src/main/java/cuchaz/enigma/translation/mapping/serde/tinyv2/TinyV2Writer.java
@@ -0,0 +1,172 @@
1package cuchaz.enigma.translation.mapping.serde.tinyv2;
2
3import com.google.common.base.Strings;
4import cuchaz.enigma.ProgressListener;
5import cuchaz.enigma.translation.mapping.EntryMap;
6import cuchaz.enigma.translation.mapping.EntryMapping;
7import cuchaz.enigma.translation.mapping.MappingDelta;
8import cuchaz.enigma.translation.mapping.serde.MappingSaveParameters;
9import cuchaz.enigma.translation.mapping.serde.LfPrintWriter;
10import cuchaz.enigma.translation.mapping.serde.MappingHelper;
11import cuchaz.enigma.translation.mapping.serde.MappingsWriter;
12import cuchaz.enigma.translation.mapping.tree.EntryTree;
13import cuchaz.enigma.translation.mapping.tree.EntryTreeNode;
14import cuchaz.enigma.translation.representation.entry.ClassEntry;
15import cuchaz.enigma.translation.representation.entry.Entry;
16import cuchaz.enigma.translation.representation.entry.FieldEntry;
17import cuchaz.enigma.translation.representation.entry.LocalVariableEntry;
18import cuchaz.enigma.translation.representation.entry.MethodEntry;
19
20import java.io.IOException;
21import java.io.PrintWriter;
22import java.nio.file.Files;
23import java.nio.file.Path;
24import java.util.Deque;
25import java.util.LinkedList;
26import java.util.List;
27import java.util.stream.Collectors;
28import java.util.stream.StreamSupport;
29
30public final class TinyV2Writer implements MappingsWriter {
31
32 private static final String MINOR_VERSION = "0";
33 private final String obfHeader;
34 private final String deobfHeader;
35
36 public TinyV2Writer(String obfHeader, String deobfHeader) {
37 this.obfHeader = obfHeader;
38 this.deobfHeader = deobfHeader;
39 }
40
41 @Override
42 public void write(EntryTree<EntryMapping> mappings, MappingDelta<EntryMapping> delta, Path path, ProgressListener progress, MappingSaveParameters parameters) {
43 List<EntryTreeNode<EntryMapping>> classes = StreamSupport.stream(mappings.spliterator(), false).filter(node -> node.getEntry() instanceof ClassEntry).collect(Collectors.toList());
44
45 try (PrintWriter writer = new LfPrintWriter(Files.newBufferedWriter(path))) {
46 writer.println("tiny\t2\t" + MINOR_VERSION + "\t" + obfHeader + "\t" + deobfHeader);
47
48 // no escape names
49
50 for (EntryTreeNode<EntryMapping> node : classes) {
51 writeClass(writer, node, mappings);
52 }
53 } catch (IOException ex) {
54 ex.printStackTrace(); // TODO add some better logging system
55 }
56 }
57
58 private void writeClass(PrintWriter writer, EntryTreeNode<EntryMapping> node, EntryMap<EntryMapping> tree) {
59 writer.print("c\t");
60 ClassEntry classEntry = (ClassEntry) node.getEntry();
61 String fullName = classEntry.getFullName();
62 writer.print(fullName);
63 Deque<String> parts = new LinkedList<>();
64 do {
65 EntryMapping mapping = tree.get(classEntry);
66 if (mapping != null) {
67 parts.addFirst(mapping.getTargetName());
68 } else {
69 parts.addFirst(classEntry.getName());
70 }
71 classEntry = classEntry.getOuterClass();
72 } while (classEntry != null);
73
74 String mappedName = String.join("$", parts);
75
76 writer.print("\t");
77
78 writer.print(mappedName); // todo escaping when we have v2 fixed later
79
80 writer.println();
81
82 writeComment(writer, node.getValue(), 1);
83
84 for (EntryTreeNode<EntryMapping> child : node.getChildNodes()) {
85 Entry entry = child.getEntry();
86 if (entry instanceof FieldEntry) {
87 writeField(writer, child);
88 } else if (entry instanceof MethodEntry) {
89 writeMethod(writer, child);
90 }
91 }
92 }
93
94 private void writeMethod(PrintWriter writer, EntryTreeNode<EntryMapping> node) {
95 writer.print(indent(1));
96 writer.print("m\t");
97 writer.print(((MethodEntry) node.getEntry()).getDesc().toString());
98 writer.print("\t");
99 writer.print(node.getEntry().getName());
100 writer.print("\t");
101 EntryMapping mapping = node.getValue();
102 if (mapping == null) {
103 writer.println(node.getEntry().getName()); // todo fix v2 name inference
104 } else {
105 writer.println(mapping.getTargetName());
106
107 writeComment(writer, mapping, 2);
108 }
109
110 for (EntryTreeNode<EntryMapping> child : node.getChildNodes()) {
111 Entry entry = child.getEntry();
112 if (entry instanceof LocalVariableEntry) {
113 writeParameter(writer, child);
114 }
115 // TODO write actual local variables
116 }
117 }
118
119 private void writeField(PrintWriter writer, EntryTreeNode<EntryMapping> node) {
120 if (node.getValue() == null)
121 return; // Shortcut
122
123 writer.print(indent(1));
124 writer.print("f\t");
125 writer.print(((FieldEntry) node.getEntry()).getDesc().toString());
126 writer.print("\t");
127 writer.print(node.getEntry().getName());
128 writer.print("\t");
129 EntryMapping mapping = node.getValue();
130 if (mapping == null) {
131 writer.println(node.getEntry().getName()); // todo fix v2 name inference
132 } else {
133 writer.println(mapping.getTargetName());
134
135 writeComment(writer, mapping, 2);
136 }
137 }
138
139 private void writeParameter(PrintWriter writer, EntryTreeNode<EntryMapping> node) {
140 if (node.getValue() == null)
141 return; // Shortcut
142
143 writer.print(indent(2));
144 writer.print("p\t");
145 writer.print(((LocalVariableEntry) node.getEntry()).getIndex());
146 writer.print("\t");
147 writer.print(node.getEntry().getName());
148 writer.print("\t");
149 EntryMapping mapping = node.getValue();
150 if (mapping == null) {
151 writer.println(); // todo ???
152 } else {
153 writer.println(mapping.getTargetName());
154
155 writeComment(writer, mapping, 3);
156 }
157 }
158
159 private void writeComment(PrintWriter writer, EntryMapping mapping, int indent) {
160 if (mapping != null && mapping.getJavadoc() != null) {
161 writer.print(indent(indent));
162 writer.print("c\t");
163 writer.print(MappingHelper.escape(mapping.getJavadoc()));
164 writer.println();
165 }
166 }
167
168 private String indent(int level) {
169 return Strings.repeat("\t", level);
170 }
171
172}
diff --git a/enigma/src/main/java/cuchaz/enigma/translation/mapping/tree/DeltaTrackingTree.java b/enigma/src/main/java/cuchaz/enigma/translation/mapping/tree/DeltaTrackingTree.java
new file mode 100644
index 0000000..255fa5f
--- /dev/null
+++ b/enigma/src/main/java/cuchaz/enigma/translation/mapping/tree/DeltaTrackingTree.java
@@ -0,0 +1,110 @@
1package cuchaz.enigma.translation.mapping.tree;
2
3import cuchaz.enigma.translation.Translator;
4import cuchaz.enigma.translation.mapping.EntryMap;
5import cuchaz.enigma.translation.mapping.EntryMapping;
6import cuchaz.enigma.translation.mapping.EntryResolver;
7import cuchaz.enigma.translation.mapping.MappingDelta;
8import cuchaz.enigma.translation.representation.entry.Entry;
9
10import javax.annotation.Nullable;
11import java.util.Collection;
12import java.util.Iterator;
13import java.util.stream.Stream;
14
15public class DeltaTrackingTree<T> implements EntryTree<T> {
16 private final EntryTree<T> delegate;
17
18 private EntryTree<T> deltaReference;
19 private EntryTree<Object> changes = new HashEntryTree<>();
20
21 public DeltaTrackingTree(EntryTree<T> delegate) {
22 this.delegate = delegate;
23 this.deltaReference = new HashEntryTree<>(delegate);
24 }
25
26 public DeltaTrackingTree() {
27 this(new HashEntryTree<>());
28 }
29
30 @Override
31 public void insert(Entry<?> entry, T value) {
32 trackChange(entry);
33 delegate.insert(entry, value);
34 }
35
36 @Nullable
37 @Override
38 public T remove(Entry<?> entry) {
39 trackChange(entry);
40 return delegate.remove(entry);
41 }
42
43 public void trackChange(Entry<?> entry) {
44 changes.insert(entry, MappingDelta.PLACEHOLDER);
45 }
46
47 @Nullable
48 @Override
49 public T get(Entry<?> entry) {
50 return delegate.get(entry);
51 }
52
53 @Override
54 public Collection<Entry<?>> getChildren(Entry<?> entry) {
55 return delegate.getChildren(entry);
56 }
57
58 @Override
59 public Collection<Entry<?>> getSiblings(Entry<?> entry) {
60 return delegate.getSiblings(entry);
61 }
62
63 @Nullable
64 @Override
65 public EntryTreeNode<T> findNode(Entry<?> entry) {
66 return delegate.findNode(entry);
67 }
68
69 @Override
70 public Stream<EntryTreeNode<T>> getRootNodes() {
71 return delegate.getRootNodes();
72 }
73
74 @Override
75 public DeltaTrackingTree<T> translate(Translator translator, EntryResolver resolver, EntryMap<EntryMapping> mappings) {
76 DeltaTrackingTree<T> translatedTree = new DeltaTrackingTree<>(delegate.translate(translator, resolver, mappings));
77 translatedTree.changes = changes.translate(translator, resolver, mappings);
78 return translatedTree;
79 }
80
81 @Override
82 public Stream<Entry<?>> getAllEntries() {
83 return delegate.getAllEntries();
84 }
85
86 @Override
87 public boolean isEmpty() {
88 return delegate.isEmpty();
89 }
90
91 @Override
92 public Iterator<EntryTreeNode<T>> iterator() {
93 return delegate.iterator();
94 }
95
96 public MappingDelta<T> takeDelta() {
97 MappingDelta<T> delta = new MappingDelta<>(deltaReference, changes);
98 resetDelta();
99 return delta;
100 }
101
102 private void resetDelta() {
103 deltaReference = new HashEntryTree<>(delegate);
104 changes = new HashEntryTree<>();
105 }
106
107 public boolean isDirty() {
108 return !changes.isEmpty();
109 }
110}
diff --git a/enigma/src/main/java/cuchaz/enigma/translation/mapping/tree/EntryTree.java b/enigma/src/main/java/cuchaz/enigma/translation/mapping/tree/EntryTree.java
new file mode 100644
index 0000000..daaefcc
--- /dev/null
+++ b/enigma/src/main/java/cuchaz/enigma/translation/mapping/tree/EntryTree.java
@@ -0,0 +1,26 @@
1package cuchaz.enigma.translation.mapping.tree;
2
3import cuchaz.enigma.translation.Translatable;
4import cuchaz.enigma.translation.Translator;
5import cuchaz.enigma.translation.mapping.EntryMap;
6import cuchaz.enigma.translation.mapping.EntryMapping;
7import cuchaz.enigma.translation.mapping.EntryResolver;
8import cuchaz.enigma.translation.representation.entry.Entry;
9
10import javax.annotation.Nullable;
11import java.util.Collection;
12import java.util.stream.Stream;
13
14public interface EntryTree<T> extends EntryMap<T>, Iterable<EntryTreeNode<T>>, Translatable {
15 Collection<Entry<?>> getChildren(Entry<?> entry);
16
17 Collection<Entry<?>> getSiblings(Entry<?> entry);
18
19 @Nullable
20 EntryTreeNode<T> findNode(Entry<?> entry);
21
22 Stream<EntryTreeNode<T>> getRootNodes();
23
24 @Override
25 EntryTree<T> translate(Translator translator, EntryResolver resolver, EntryMap<EntryMapping> mappings);
26}
diff --git a/enigma/src/main/java/cuchaz/enigma/translation/mapping/tree/EntryTreeNode.java b/enigma/src/main/java/cuchaz/enigma/translation/mapping/tree/EntryTreeNode.java
new file mode 100644
index 0000000..affcd50
--- /dev/null
+++ b/enigma/src/main/java/cuchaz/enigma/translation/mapping/tree/EntryTreeNode.java
@@ -0,0 +1,40 @@
1package cuchaz.enigma.translation.mapping.tree;
2
3import cuchaz.enigma.translation.representation.entry.Entry;
4
5import javax.annotation.Nullable;
6import java.util.ArrayList;
7import java.util.Collection;
8import java.util.stream.Collectors;
9
10public interface EntryTreeNode<T> {
11 @Nullable
12 T getValue();
13
14 Entry<?> getEntry();
15
16 boolean isEmpty();
17
18 Collection<Entry<?>> getChildren();
19
20 Collection<? extends EntryTreeNode<T>> getChildNodes();
21
22 default Collection<? extends EntryTreeNode<T>> getNodesRecursively() {
23 Collection<EntryTreeNode<T>> nodes = new ArrayList<>();
24 nodes.add(this);
25 for (EntryTreeNode<T> node : getChildNodes()) {
26 nodes.addAll(node.getNodesRecursively());
27 }
28 return nodes;
29 }
30
31 default Collection<Entry<?>> getChildrenRecursively() {
32 return getNodesRecursively().stream()
33 .map(EntryTreeNode::getEntry)
34 .collect(Collectors.toList());
35 }
36
37 default boolean hasValue() {
38 return getValue() != null;
39 }
40}
diff --git a/enigma/src/main/java/cuchaz/enigma/translation/mapping/tree/HashEntryTree.java b/enigma/src/main/java/cuchaz/enigma/translation/mapping/tree/HashEntryTree.java
new file mode 100644
index 0000000..570941c
--- /dev/null
+++ b/enigma/src/main/java/cuchaz/enigma/translation/mapping/tree/HashEntryTree.java
@@ -0,0 +1,188 @@
1package cuchaz.enigma.translation.mapping.tree;
2
3import cuchaz.enigma.translation.Translator;
4import cuchaz.enigma.translation.mapping.EntryMap;
5import cuchaz.enigma.translation.mapping.EntryMapping;
6import cuchaz.enigma.translation.mapping.EntryResolver;
7import cuchaz.enigma.translation.representation.entry.Entry;
8
9import javax.annotation.Nullable;
10import java.util.*;
11import java.util.function.Function;
12import java.util.stream.Stream;
13import java.util.stream.StreamSupport;
14
15public class HashEntryTree<T> implements EntryTree<T> {
16 private final Map<Entry<?>, HashTreeNode<T>> root = new HashMap<>();
17
18 public HashEntryTree() {
19 }
20
21 public HashEntryTree(EntryTree<T> tree) {
22 for (EntryTreeNode<T> node : tree) {
23 insert(node.getEntry(), node.getValue());
24 }
25 }
26
27 @Override
28 public void insert(Entry<?> entry, T value) {
29 List<HashTreeNode<T>> path = computePath(entry, true);
30 path.get(path.size() - 1).putValue(value);
31 if (value == null) {
32 removeDeadAlong(path);
33 }
34 }
35
36 @Override
37 @Nullable
38 public T remove(Entry<?> entry) {
39 List<HashTreeNode<T>> path = computePath(entry, false);
40 if (path.isEmpty()) {
41 return null;
42 }
43
44 T value = path.get(path.size() - 1).removeValue();
45
46 removeDeadAlong(path);
47
48 return value;
49 }
50
51 @Override
52 @Nullable
53 public T get(Entry<?> entry) {
54 HashTreeNode<T> node = findNode(entry);
55 if (node == null) {
56 return null;
57 }
58 return node.getValue();
59 }
60
61 @Override
62 public boolean contains(Entry<?> entry) {
63 return get(entry) != null;
64 }
65
66 @Override
67 public Collection<Entry<?>> getChildren(Entry<?> entry) {
68 HashTreeNode<T> leaf = findNode(entry);
69 if (leaf == null) {
70 return Collections.emptyList();
71 }
72 return leaf.getChildren();
73 }
74
75 @Override
76 public Collection<Entry<?>> getSiblings(Entry<?> entry) {
77 Entry<?> parent = entry.getParent();
78 if (parent == null) {
79 return getSiblings(entry, root.keySet());
80 }
81 return getSiblings(entry, getChildren(parent));
82 }
83
84 private Collection<Entry<?>> getSiblings(Entry<?> entry, Collection<Entry<?>> generation) {
85 Set<Entry<?>> siblings = new HashSet<>(generation);
86 siblings.remove(entry);
87 return siblings;
88 }
89
90 @Override
91 @Nullable
92 public HashTreeNode<T> findNode(Entry<?> target) {
93 List<Entry<?>> parentChain = target.getAncestry();
94 if (parentChain.isEmpty()) {
95 return null;
96 }
97
98 HashTreeNode<T> node = root.get(parentChain.get(0));
99 for (int i = 1; i < parentChain.size(); i++) {
100 if (node == null) {
101 return null;
102 }
103 node = node.getChild(parentChain.get(i));
104 }
105
106 return node;
107 }
108
109 private List<HashTreeNode<T>> computePath(Entry<?> target, boolean make) {
110 List<Entry<?>> ancestry = target.getAncestry();
111 if (ancestry.isEmpty()) {
112 return Collections.emptyList();
113 }
114
115 List<HashTreeNode<T>> path = new ArrayList<>(ancestry.size());
116
117 Entry<?> rootEntry = ancestry.get(0);
118 HashTreeNode<T> node = make ? root.computeIfAbsent(rootEntry, HashTreeNode::new) : root.get(rootEntry);
119 if (node == null) {
120 return Collections.emptyList();
121 }
122
123 path.add(node);
124
125 for (int i = 1; i < ancestry.size(); i++) {
126 Entry<?> ancestor = ancestry.get(i);
127 node = make ? node.computeChild(ancestor) : node.getChild(ancestor);
128 if (node == null) {
129 return Collections.emptyList();
130 }
131
132 path.add(node);
133 }
134
135 return path;
136 }
137
138 private void removeDeadAlong(List<HashTreeNode<T>> path) {
139 for (int i = path.size() - 1; i >= 0; i--) {
140 HashTreeNode<T> node = path.get(i);
141 if (node.isEmpty()) {
142 if (i > 0) {
143 HashTreeNode<T> parentNode = path.get(i - 1);
144 parentNode.remove(node.getEntry());
145 } else {
146 root.remove(node.getEntry());
147 }
148 } else {
149 break;
150 }
151 }
152 }
153
154 @Override
155 public Iterator<EntryTreeNode<T>> iterator() {
156 Collection<EntryTreeNode<T>> nodes = new ArrayList<>();
157 for (EntryTreeNode<T> node : root.values()) {
158 nodes.addAll(node.getNodesRecursively());
159 }
160 return nodes.iterator();
161 }
162
163 @Override
164 public Stream<Entry<?>> getAllEntries() {
165 return StreamSupport.stream(spliterator(), false)
166 .filter(EntryTreeNode::hasValue)
167 .map(EntryTreeNode::getEntry);
168 }
169
170 @Override
171 public Stream<EntryTreeNode<T>> getRootNodes() {
172 return root.values().stream().map(Function.identity());
173 }
174
175 @Override
176 public boolean isEmpty() {
177 return root.isEmpty();
178 }
179
180 @Override
181 public HashEntryTree<T> translate(Translator translator, EntryResolver resolver, EntryMap<EntryMapping> mappings) {
182 HashEntryTree<T> translatedTree = new HashEntryTree<>();
183 for (EntryTreeNode<T> node : this) {
184 translatedTree.insert(translator.translate(node.getEntry()), node.getValue());
185 }
186 return translatedTree;
187 }
188}
diff --git a/enigma/src/main/java/cuchaz/enigma/translation/mapping/tree/HashTreeNode.java b/enigma/src/main/java/cuchaz/enigma/translation/mapping/tree/HashTreeNode.java
new file mode 100644
index 0000000..0a990bd
--- /dev/null
+++ b/enigma/src/main/java/cuchaz/enigma/translation/mapping/tree/HashTreeNode.java
@@ -0,0 +1,75 @@
1package cuchaz.enigma.translation.mapping.tree;
2
3import cuchaz.enigma.translation.representation.entry.Entry;
4
5import javax.annotation.Nonnull;
6import javax.annotation.Nullable;
7import java.util.Collection;
8import java.util.HashMap;
9import java.util.Iterator;
10import java.util.Map;
11
12public class HashTreeNode<T> implements EntryTreeNode<T>, Iterable<HashTreeNode<T>> {
13 private final Entry<?> entry;
14 private final Map<Entry<?>, HashTreeNode<T>> children = new HashMap<>();
15 private T value;
16
17 HashTreeNode(Entry<?> entry) {
18 this.entry = entry;
19 }
20
21 void putValue(T value) {
22 this.value = value;
23 }
24
25 T removeValue() {
26 T value = this.value;
27 this.value = null;
28 return value;
29 }
30
31 @Nullable
32 HashTreeNode<T> getChild(Entry<?> entry) {
33 return children.get(entry);
34 }
35
36 @Nonnull
37 HashTreeNode<T> computeChild(Entry<?> entry) {
38 return children.computeIfAbsent(entry, HashTreeNode::new);
39 }
40
41 void remove(Entry<?> entry) {
42 children.remove(entry);
43 }
44
45 @Override
46 @Nullable
47 public T getValue() {
48 return value;
49 }
50
51 @Override
52 public Entry<?> getEntry() {
53 return entry;
54 }
55
56 @Override
57 public boolean isEmpty() {
58 return children.isEmpty() && value == null;
59 }
60
61 @Override
62 public Collection<Entry<?>> getChildren() {
63 return children.keySet();
64 }
65
66 @Override
67 public Collection<? extends EntryTreeNode<T>> getChildNodes() {
68 return children.values();
69 }
70
71 @Override
72 public Iterator<HashTreeNode<T>> iterator() {
73 return children.values().iterator();
74 }
75}
diff --git a/enigma/src/main/java/cuchaz/enigma/translation/representation/AccessFlags.java b/enigma/src/main/java/cuchaz/enigma/translation/representation/AccessFlags.java
new file mode 100644
index 0000000..b280eef
--- /dev/null
+++ b/enigma/src/main/java/cuchaz/enigma/translation/representation/AccessFlags.java
@@ -0,0 +1,116 @@
1package cuchaz.enigma.translation.representation;
2
3import cuchaz.enigma.analysis.Access;
4import org.objectweb.asm.Opcodes;
5
6import java.lang.reflect.Modifier;
7
8public class AccessFlags {
9 public static final AccessFlags PRIVATE = new AccessFlags(Opcodes.ACC_PRIVATE);
10 public static final AccessFlags PUBLIC = new AccessFlags(Opcodes.ACC_PUBLIC);
11
12 private int flags;
13
14 public AccessFlags(int flags) {
15 this.flags = flags;
16 }
17
18 public boolean isPrivate() {
19 return Modifier.isPrivate(this.flags);
20 }
21
22 public boolean isProtected() {
23 return Modifier.isProtected(this.flags);
24 }
25
26 public boolean isPublic() {
27 return Modifier.isPublic(this.flags);
28 }
29
30 public boolean isSynthetic() {
31 return (this.flags & Opcodes.ACC_SYNTHETIC) != 0;
32 }
33
34 public boolean isStatic() {
35 return Modifier.isStatic(this.flags);
36 }
37
38 public boolean isEnum() {
39 return (flags & Opcodes.ACC_ENUM) != 0;
40 }
41
42 public boolean isBridge() {
43 return (flags & Opcodes.ACC_BRIDGE) != 0;
44 }
45
46 public boolean isFinal() {
47 return (flags & Opcodes.ACC_FINAL) != 0;
48 }
49
50 public boolean isInterface() {
51 return (flags & Opcodes.ACC_INTERFACE) != 0;
52 }
53
54 public AccessFlags setPrivate() {
55 this.setVisibility(Opcodes.ACC_PRIVATE);
56 return this;
57 }
58
59 public AccessFlags setProtected() {
60 this.setVisibility(Opcodes.ACC_PROTECTED);
61 return this;
62 }
63
64 public AccessFlags setPublic() {
65 this.setVisibility(Opcodes.ACC_PUBLIC);
66 return this;
67 }
68
69 public AccessFlags setBridge() {
70 flags |= Opcodes.ACC_BRIDGE;
71 return this;
72 }
73
74 @Deprecated
75 public AccessFlags setBridged() {
76 return setBridge();
77 }
78
79 public void setVisibility(int visibility) {
80 this.resetVisibility();
81 this.flags |= visibility;
82 }
83
84 private void resetVisibility() {
85 this.flags &= ~(Opcodes.ACC_PRIVATE | Opcodes.ACC_PROTECTED | Opcodes.ACC_PUBLIC);
86 }
87
88 public int getFlags() {
89 return this.flags;
90 }
91
92 @Override
93 public boolean equals(Object obj) {
94 return obj instanceof AccessFlags && ((AccessFlags) obj).flags == flags;
95 }
96
97 @Override
98 public int hashCode() {
99 return flags;
100 }
101
102 @Override
103 public String toString() {
104 StringBuilder builder = new StringBuilder(Access.get(this).toString().toLowerCase());
105 if (isStatic()) {
106 builder.append(" static");
107 }
108 if (isSynthetic()) {
109 builder.append(" synthetic");
110 }
111 if (isBridge()) {
112 builder.append(" bridge");
113 }
114 return builder.toString();
115 }
116}
diff --git a/enigma/src/main/java/cuchaz/enigma/translation/representation/Lambda.java b/enigma/src/main/java/cuchaz/enigma/translation/representation/Lambda.java
new file mode 100644
index 0000000..ad9389c
--- /dev/null
+++ b/enigma/src/main/java/cuchaz/enigma/translation/representation/Lambda.java
@@ -0,0 +1,105 @@
1package cuchaz.enigma.translation.representation;
2
3import cuchaz.enigma.translation.Translatable;
4import cuchaz.enigma.translation.Translator;
5import cuchaz.enigma.translation.mapping.EntryMap;
6import cuchaz.enigma.translation.mapping.EntryMapping;
7import cuchaz.enigma.translation.mapping.EntryResolver;
8import cuchaz.enigma.translation.mapping.ResolutionStrategy;
9import cuchaz.enigma.translation.representation.entry.ClassEntry;
10import cuchaz.enigma.translation.representation.entry.MethodEntry;
11import cuchaz.enigma.translation.representation.entry.ParentedEntry;
12
13import java.util.Objects;
14
15public class Lambda implements Translatable {
16 private final String invokedName;
17 private final MethodDescriptor invokedType;
18 private final MethodDescriptor samMethodType;
19 private final ParentedEntry<?> implMethod;
20 private final MethodDescriptor instantiatedMethodType;
21
22 public Lambda(String invokedName, MethodDescriptor invokedType, MethodDescriptor samMethodType, ParentedEntry<?> implMethod, MethodDescriptor instantiatedMethodType) {
23 this.invokedName = invokedName;
24 this.invokedType = invokedType;
25 this.samMethodType = samMethodType;
26 this.implMethod = implMethod;
27 this.instantiatedMethodType = instantiatedMethodType;
28 }
29
30 @Override
31 public Lambda translate(Translator translator, EntryResolver resolver, EntryMap<EntryMapping> mappings) {
32 MethodEntry samMethod = new MethodEntry(getInterface(), invokedName, samMethodType);
33 EntryMapping samMethodMapping = resolveMapping(resolver, mappings, samMethod);
34
35 return new Lambda(
36 samMethodMapping != null ? samMethodMapping.getTargetName() : invokedName,
37 invokedType.translate(translator, resolver, mappings),
38 samMethodType.translate(translator, resolver, mappings),
39 implMethod.translate(translator, resolver, mappings),
40 instantiatedMethodType.translate(translator, resolver, mappings)
41 );
42 }
43
44 private EntryMapping resolveMapping(EntryResolver resolver, EntryMap<EntryMapping> mappings, MethodEntry methodEntry) {
45 for (MethodEntry entry : resolver.resolveEntry(methodEntry, ResolutionStrategy.RESOLVE_ROOT)) {
46 EntryMapping mapping = mappings.get(entry);
47 if (mapping != null) {
48 return mapping;
49 }
50 }
51 return null;
52 }
53
54 public ClassEntry getInterface() {
55 return invokedType.getReturnDesc().getTypeEntry();
56 }
57
58 public String getInvokedName() {
59 return invokedName;
60 }
61
62 public MethodDescriptor getInvokedType() {
63 return invokedType;
64 }
65
66 public MethodDescriptor getSamMethodType() {
67 return samMethodType;
68 }
69
70 public ParentedEntry<?> getImplMethod() {
71 return implMethod;
72 }
73
74 public MethodDescriptor getInstantiatedMethodType() {
75 return instantiatedMethodType;
76 }
77
78 @Override
79 public boolean equals(Object o) {
80 if (this == o) return true;
81 if (o == null || getClass() != o.getClass()) return false;
82 Lambda lambda = (Lambda) o;
83 return Objects.equals(invokedName, lambda.invokedName) &&
84 Objects.equals(invokedType, lambda.invokedType) &&
85 Objects.equals(samMethodType, lambda.samMethodType) &&
86 Objects.equals(implMethod, lambda.implMethod) &&
87 Objects.equals(instantiatedMethodType, lambda.instantiatedMethodType);
88 }
89
90 @Override
91 public int hashCode() {
92 return Objects.hash(invokedName, invokedType, samMethodType, implMethod, instantiatedMethodType);
93 }
94
95 @Override
96 public String toString() {
97 return "Lambda{" +
98 "invokedName='" + invokedName + '\'' +
99 ", invokedType=" + invokedType +
100 ", samMethodType=" + samMethodType +
101 ", implMethod=" + implMethod +
102 ", instantiatedMethodType=" + instantiatedMethodType +
103 '}';
104 }
105}
diff --git a/enigma/src/main/java/cuchaz/enigma/translation/representation/MethodDescriptor.java b/enigma/src/main/java/cuchaz/enigma/translation/representation/MethodDescriptor.java
new file mode 100644
index 0000000..03c8686
--- /dev/null
+++ b/enigma/src/main/java/cuchaz/enigma/translation/representation/MethodDescriptor.java
@@ -0,0 +1,132 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 * <p>
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11
12package cuchaz.enigma.translation.representation;
13
14import com.google.common.collect.Lists;
15import cuchaz.enigma.translation.Translatable;
16import cuchaz.enigma.translation.Translator;
17import cuchaz.enigma.translation.mapping.EntryMapping;
18import cuchaz.enigma.translation.mapping.EntryResolver;
19import cuchaz.enigma.translation.mapping.EntryMap;
20import cuchaz.enigma.translation.representation.entry.ClassEntry;
21
22import java.util.ArrayList;
23import java.util.List;
24import java.util.Objects;
25import java.util.function.Function;
26
27public class MethodDescriptor implements Translatable {
28
29 private List<TypeDescriptor> argumentDescs;
30 private TypeDescriptor returnDesc;
31
32 public MethodDescriptor(String desc) {
33 try {
34 this.argumentDescs = Lists.newArrayList();
35 int i = 0;
36 while (i < desc.length()) {
37 char c = desc.charAt(i);
38 if (c == '(') {
39 assert (this.argumentDescs.isEmpty());
40 assert (this.returnDesc == null);
41 i++;
42 } else if (c == ')') {
43 i++;
44 break;
45 } else {
46 String type = TypeDescriptor.parseFirst(desc.substring(i));
47 this.argumentDescs.add(new TypeDescriptor(type));
48 i += type.length();
49 }
50 }
51 this.returnDesc = new TypeDescriptor(TypeDescriptor.parseFirst(desc.substring(i)));
52 } catch (Exception ex) {
53 throw new IllegalArgumentException("Unable to parse method descriptor: " + desc, ex);
54 }
55 }
56
57 public MethodDescriptor(List<TypeDescriptor> argumentDescs, TypeDescriptor returnDesc) {
58 this.argumentDescs = argumentDescs;
59 this.returnDesc = returnDesc;
60 }
61
62 public List<TypeDescriptor> getArgumentDescs() {
63 return this.argumentDescs;
64 }
65
66 public TypeDescriptor getReturnDesc() {
67 return this.returnDesc;
68 }
69
70 @Override
71 public String toString() {
72 StringBuilder buf = new StringBuilder();
73 buf.append("(");
74 for (TypeDescriptor desc : this.argumentDescs) {
75 buf.append(desc);
76 }
77 buf.append(")");
78 buf.append(this.returnDesc);
79 return buf.toString();
80 }
81
82 public Iterable<TypeDescriptor> types() {
83 List<TypeDescriptor> descs = Lists.newArrayList();
84 descs.addAll(this.argumentDescs);
85 descs.add(this.returnDesc);
86 return descs;
87 }
88
89 @Override
90 public boolean equals(Object other) {
91 return other instanceof MethodDescriptor && equals((MethodDescriptor) other);
92 }
93
94 public boolean equals(MethodDescriptor other) {
95 return this.argumentDescs.equals(other.argumentDescs) && this.returnDesc.equals(other.returnDesc);
96 }
97
98 @Override
99 public int hashCode() {
100 return Objects.hash(this.argumentDescs.hashCode(), this.returnDesc.hashCode());
101 }
102
103 public boolean hasClass(ClassEntry classEntry) {
104 for (TypeDescriptor desc : types()) {
105 if (desc.containsType() && desc.getTypeEntry().equals(classEntry)) {
106 return true;
107 }
108 }
109 return false;
110 }
111
112 public MethodDescriptor remap(Function<String, String> remapper) {
113 List<TypeDescriptor> argumentDescs = new ArrayList<>(this.argumentDescs.size());
114 for (TypeDescriptor desc : this.argumentDescs) {
115 argumentDescs.add(desc.remap(remapper));
116 }
117 return new MethodDescriptor(argumentDescs, returnDesc.remap(remapper));
118 }
119
120 @Override
121 public MethodDescriptor translate(Translator translator, EntryResolver resolver, EntryMap<EntryMapping> mappings) {
122 List<TypeDescriptor> translatedArguments = new ArrayList<>(argumentDescs.size());
123 for (TypeDescriptor argument : argumentDescs) {
124 translatedArguments.add(translator.translate(argument));
125 }
126 return new MethodDescriptor(translatedArguments, translator.translate(returnDesc));
127 }
128
129 public boolean canConflictWith(MethodDescriptor descriptor) {
130 return descriptor.argumentDescs.equals(argumentDescs);
131 }
132}
diff --git a/enigma/src/main/java/cuchaz/enigma/translation/representation/Signature.java b/enigma/src/main/java/cuchaz/enigma/translation/representation/Signature.java
new file mode 100644
index 0000000..424088a
--- /dev/null
+++ b/enigma/src/main/java/cuchaz/enigma/translation/representation/Signature.java
@@ -0,0 +1,98 @@
1package cuchaz.enigma.translation.representation;
2
3import cuchaz.enigma.bytecode.translators.TranslationSignatureVisitor;
4import cuchaz.enigma.translation.Translatable;
5import cuchaz.enigma.translation.Translator;
6import cuchaz.enigma.translation.mapping.EntryMap;
7import cuchaz.enigma.translation.mapping.EntryMapping;
8import cuchaz.enigma.translation.mapping.EntryResolver;
9import cuchaz.enigma.translation.representation.entry.ClassEntry;
10import org.objectweb.asm.signature.SignatureReader;
11import org.objectweb.asm.signature.SignatureVisitor;
12import org.objectweb.asm.signature.SignatureWriter;
13
14import java.util.function.Function;
15import java.util.regex.Pattern;
16
17public class Signature implements Translatable {
18 private static final Pattern OBJECT_PATTERN = Pattern.compile(".*:Ljava/lang/Object;:.*");
19
20 private final String signature;
21 private final boolean isType;
22
23 private Signature(String signature, boolean isType) {
24 if (signature != null && OBJECT_PATTERN.matcher(signature).matches()) {
25 signature = signature.replaceAll(":Ljava/lang/Object;:", "::");
26 }
27
28 this.signature = signature;
29 this.isType = isType;
30 }
31
32 public static Signature createTypedSignature(String signature) {
33 if (signature != null && !signature.isEmpty()) {
34 return new Signature(signature, true);
35 }
36 return new Signature(null, true);
37 }
38
39 public static Signature createSignature(String signature) {
40 if (signature != null && !signature.isEmpty()) {
41 return new Signature(signature, false);
42 }
43 return new Signature(null, false);
44 }
45
46 public String getSignature() {
47 return signature;
48 }
49
50 public boolean isType() {
51 return isType;
52 }
53
54 public Signature remap(Function<String, String> remapper) {
55 if (signature == null) {
56 return this;
57 }
58 SignatureWriter writer = new SignatureWriter();
59 SignatureVisitor visitor = new TranslationSignatureVisitor(remapper, writer);
60 if (isType) {
61 new SignatureReader(signature).acceptType(visitor);
62 } else {
63 new SignatureReader(signature).accept(visitor);
64 }
65 return new Signature(writer.toString(), isType);
66 }
67
68 @Override
69 public boolean equals(Object obj) {
70 if (obj instanceof Signature) {
71 Signature other = (Signature) obj;
72 return (other.signature == null && signature == null || other.signature != null
73 && signature != null && other.signature.equals(signature))
74 && other.isType == this.isType;
75 }
76 return false;
77 }
78
79 @Override
80 public int hashCode() {
81 int hash = (isType ? 1 : 0) << 16;
82 if (signature != null) {
83 hash |= signature.hashCode();
84 }
85
86 return hash;
87 }
88
89 @Override
90 public String toString() {
91 return signature;
92 }
93
94 @Override
95 public Translatable translate(Translator translator, EntryResolver resolver, EntryMap<EntryMapping> mappings) {
96 return remap(name -> translator.translate(new ClassEntry(name)).getFullName());
97 }
98}
diff --git a/enigma/src/main/java/cuchaz/enigma/translation/representation/TypeDescriptor.java b/enigma/src/main/java/cuchaz/enigma/translation/representation/TypeDescriptor.java
new file mode 100644
index 0000000..f7ba849
--- /dev/null
+++ b/enigma/src/main/java/cuchaz/enigma/translation/representation/TypeDescriptor.java
@@ -0,0 +1,268 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 * <p>
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11
12package cuchaz.enigma.translation.representation;
13
14import com.google.common.base.Preconditions;
15import com.google.common.collect.Maps;
16import cuchaz.enigma.translation.Translatable;
17import cuchaz.enigma.translation.Translator;
18import cuchaz.enigma.translation.mapping.EntryMapping;
19import cuchaz.enigma.translation.mapping.EntryResolver;
20import cuchaz.enigma.translation.mapping.EntryMap;
21import cuchaz.enigma.translation.representation.entry.ClassEntry;
22
23import java.util.Map;
24import java.util.function.Function;
25
26public class TypeDescriptor implements Translatable {
27
28 protected final String desc;
29
30 public TypeDescriptor(String desc) {
31 Preconditions.checkNotNull(desc, "Desc cannot be null");
32
33 // don't deal with generics
34 // this is just for raw jvm types
35 if (desc.charAt(0) == 'T' || desc.indexOf('<') >= 0 || desc.indexOf('>') >= 0) {
36 throw new IllegalArgumentException("don't use with generic types or templates: " + desc);
37 }
38
39 this.desc = desc;
40 }
41
42 public static String parseFirst(String in) {
43
44 if (in == null || in.length() <= 0) {
45 throw new IllegalArgumentException("No desc to parse, input is empty!");
46 }
47
48 // read one desc from the input
49
50 char c = in.charAt(0);
51
52 // first check for void
53 if (c == 'V') {
54 return "V";
55 }
56
57 // then check for primitives
58 Primitive primitive = Primitive.get(c);
59 if (primitive != null) {
60 return in.substring(0, 1);
61 }
62
63 // then check for classes
64 if (c == 'L') {
65 return readClass(in);
66 }
67
68 // then check for templates
69 if (c == 'T') {
70 return readClass(in);
71 }
72
73 // then check for arrays
74 int dim = countArrayDimension(in);
75 if (dim > 0) {
76 String arrayType = TypeDescriptor.parseFirst(in.substring(dim));
77 return in.substring(0, dim + arrayType.length());
78 }
79
80 throw new IllegalArgumentException("don't know how to parse: " + in);
81 }
82
83 private static int countArrayDimension(String in) {
84 int i = 0;
85 while (i < in.length() && in.charAt(i) == '[')
86 i++;
87 return i;
88 }
89
90 private static String readClass(String in) {
91 // read all the characters in the buffer until we hit a ';'
92 // include the parameters too
93 StringBuilder buf = new StringBuilder();
94 int depth = 0;
95 for (int i = 0; i < in.length(); i++) {
96 char c = in.charAt(i);
97 buf.append(c);
98
99 if (c == '<') {
100 depth++;
101 } else if (c == '>') {
102 depth--;
103 } else if (depth == 0 && c == ';') {
104 return buf.toString();
105 }
106 }
107 return null;
108 }
109
110 public static TypeDescriptor of(String name) {
111 return new TypeDescriptor("L" + name + ";");
112 }
113
114 @Override
115 public String toString() {
116 return this.desc;
117 }
118
119 public boolean isVoid() {
120 return this.desc.length() == 1 && this.desc.charAt(0) == 'V';
121 }
122
123 public boolean isPrimitive() {
124 return this.desc.length() == 1 && Primitive.get(this.desc.charAt(0)) != null;
125 }
126
127 public Primitive getPrimitive() {
128 if (!isPrimitive()) {
129 throw new IllegalStateException("not a primitive");
130 }
131 return Primitive.get(this.desc.charAt(0));
132 }
133
134 public boolean isType() {
135 return this.desc.charAt(0) == 'L' && this.desc.charAt(this.desc.length() - 1) == ';';
136 }
137
138 public ClassEntry getTypeEntry() {
139 if (isType()) {
140 String name = this.desc.substring(1, this.desc.length() - 1);
141
142 int pos = name.indexOf('<');
143 if (pos >= 0) {
144 // remove the parameters from the class name
145 name = name.substring(0, pos);
146 }
147
148 return new ClassEntry(name);
149
150 } else if (isArray() && getArrayType().isType()) {
151 return getArrayType().getTypeEntry();
152 } else {
153 throw new IllegalStateException("desc doesn't have a class");
154 }
155 }
156
157 public boolean isArray() {
158 return this.desc.charAt(0) == '[';
159 }
160
161 public int getArrayDimension() {
162 if (!isArray()) {
163 throw new IllegalStateException("not an array");
164 }
165 return countArrayDimension(this.desc);
166 }
167
168 public TypeDescriptor getArrayType() {
169 if (!isArray()) {
170 throw new IllegalStateException("not an array");
171 }
172 return new TypeDescriptor(this.desc.substring(getArrayDimension()));
173 }
174
175 public boolean containsType() {
176 return isType() || (isArray() && getArrayType().containsType());
177 }
178
179 @Override
180 public boolean equals(Object other) {
181 return other instanceof TypeDescriptor && equals((TypeDescriptor) other);
182 }
183
184 public boolean equals(TypeDescriptor other) {
185 return this.desc.equals(other.desc);
186 }
187
188 @Override
189 public int hashCode() {
190 return this.desc.hashCode();
191 }
192
193 public TypeDescriptor remap(Function<String, String> remapper) {
194 String desc = this.desc;
195 if (isType() || (isArray() && containsType())) {
196 String replacedName = remapper.apply(this.getTypeEntry().getFullName());
197 if (replacedName != null) {
198 if (this.isType()) {
199 desc = "L" + replacedName + ";";
200 } else {
201 desc = getArrayPrefix(this.getArrayDimension()) + "L" + replacedName + ";";
202 }
203 }
204 }
205 return new TypeDescriptor(desc);
206 }
207
208 private static String getArrayPrefix(int dimension) {
209 StringBuilder buf = new StringBuilder();
210 for (int i = 0; i < dimension; i++) {
211 buf.append("[");
212 }
213 return buf.toString();
214 }
215
216 public int getSize() {
217 switch (desc.charAt(0)) {
218 case 'J':
219 case 'D':
220 if (desc.length() == 1) {
221 return 2;
222 } else {
223 return 1;
224 }
225 default:
226 return 1;
227 }
228 }
229
230 @Override
231 public Translatable translate(Translator translator, EntryResolver resolver, EntryMap<EntryMapping> mappings) {
232 return remap(name -> translator.translate(new ClassEntry(name)).getFullName());
233 }
234
235 public enum Primitive {
236 BYTE('B'),
237 CHARACTER('C'),
238 SHORT('S'),
239 INTEGER('I'),
240 LONG('J'),
241 FLOAT('F'),
242 DOUBLE('D'),
243 BOOLEAN('Z');
244
245 private static final Map<Character, Primitive> lookup;
246
247 static {
248 lookup = Maps.newTreeMap();
249 for (Primitive val : values()) {
250 lookup.put(val.getCode(), val);
251 }
252 }
253
254 private char code;
255
256 Primitive(char code) {
257 this.code = code;
258 }
259
260 public static Primitive get(char code) {
261 return lookup.get(code);
262 }
263
264 public char getCode() {
265 return this.code;
266 }
267 }
268}
diff --git a/enigma/src/main/java/cuchaz/enigma/translation/representation/entry/ClassDefEntry.java b/enigma/src/main/java/cuchaz/enigma/translation/representation/entry/ClassDefEntry.java
new file mode 100644
index 0000000..6930765
--- /dev/null
+++ b/enigma/src/main/java/cuchaz/enigma/translation/representation/entry/ClassDefEntry.java
@@ -0,0 +1,93 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 * <p>
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11
12package cuchaz.enigma.translation.representation.entry;
13
14import com.google.common.base.Preconditions;
15import cuchaz.enigma.translation.Translator;
16import cuchaz.enigma.translation.mapping.EntryMapping;
17import cuchaz.enigma.translation.representation.AccessFlags;
18import cuchaz.enigma.translation.representation.Signature;
19
20import javax.annotation.Nullable;
21import java.util.Arrays;
22
23public class ClassDefEntry extends ClassEntry implements DefEntry<ClassEntry> {
24 private final AccessFlags access;
25 private final Signature signature;
26 private final ClassEntry superClass;
27 private final ClassEntry[] interfaces;
28
29 public ClassDefEntry(String className, Signature signature, AccessFlags access, @Nullable ClassEntry superClass, ClassEntry[] interfaces) {
30 this(getOuterClass(className), getInnerName(className), signature, access, superClass, interfaces, null);
31 }
32
33 public ClassDefEntry(ClassEntry parent, String className, Signature signature, AccessFlags access, @Nullable ClassEntry superClass, ClassEntry[] interfaces) {
34 this(parent, className, signature, access, superClass, interfaces, null);
35 }
36
37 public ClassDefEntry(ClassEntry parent, String className, Signature signature, AccessFlags access, @Nullable ClassEntry superClass,
38 ClassEntry[] interfaces, String javadocs) {
39 super(parent, className, javadocs);
40 Preconditions.checkNotNull(signature, "Class signature cannot be null");
41 Preconditions.checkNotNull(access, "Class access cannot be null");
42
43 this.signature = signature;
44 this.access = access;
45 this.superClass = superClass;
46 this.interfaces = interfaces != null ? interfaces : new ClassEntry[0];
47 }
48
49 public static ClassDefEntry parse(int access, String name, String signature, String superName, String[] interfaces) {
50 ClassEntry superClass = superName != null ? new ClassEntry(superName) : null;
51 ClassEntry[] interfaceClasses = Arrays.stream(interfaces).map(ClassEntry::new).toArray(ClassEntry[]::new);
52 return new ClassDefEntry(name, Signature.createSignature(signature), new AccessFlags(access), superClass, interfaceClasses);
53 }
54
55 public Signature getSignature() {
56 return signature;
57 }
58
59 @Override
60 public AccessFlags getAccess() {
61 return access;
62 }
63
64 @Nullable
65 public ClassEntry getSuperClass() {
66 return superClass;
67 }
68
69 public ClassEntry[] getInterfaces() {
70 return interfaces;
71 }
72
73 @Override
74 public ClassDefEntry translate(Translator translator, @Nullable EntryMapping mapping) {
75 Signature translatedSignature = translator.translate(signature);
76 String translatedName = mapping != null ? mapping.getTargetName() : name;
77 AccessFlags translatedAccess = mapping != null ? mapping.getAccessModifier().transform(access) : access;
78 ClassEntry translatedSuper = translator.translate(superClass);
79 ClassEntry[] translatedInterfaces = Arrays.stream(interfaces).map(translator::translate).toArray(ClassEntry[]::new);
80 String docs = mapping != null ? mapping.getJavadoc() : null;
81 return new ClassDefEntry(parent, translatedName, translatedSignature, translatedAccess, translatedSuper, translatedInterfaces, docs);
82 }
83
84 @Override
85 public ClassDefEntry withName(String name) {
86 return new ClassDefEntry(parent, name, signature, access, superClass, interfaces, javadocs);
87 }
88
89 @Override
90 public ClassDefEntry withParent(ClassEntry parent) {
91 return new ClassDefEntry(parent, name, signature, access, superClass, interfaces, javadocs);
92 }
93}
diff --git a/enigma/src/main/java/cuchaz/enigma/translation/representation/entry/ClassEntry.java b/enigma/src/main/java/cuchaz/enigma/translation/representation/entry/ClassEntry.java
new file mode 100644
index 0000000..7d4b2ba
--- /dev/null
+++ b/enigma/src/main/java/cuchaz/enigma/translation/representation/entry/ClassEntry.java
@@ -0,0 +1,214 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 * <p>
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11
12package cuchaz.enigma.translation.representation.entry;
13
14import cuchaz.enigma.translation.mapping.IllegalNameException;
15import cuchaz.enigma.translation.Translator;
16import cuchaz.enigma.translation.mapping.EntryMapping;
17import cuchaz.enigma.translation.mapping.NameValidator;
18import cuchaz.enigma.translation.representation.TypeDescriptor;
19
20import javax.annotation.Nonnull;
21import javax.annotation.Nullable;
22import java.util.List;
23import java.util.Objects;
24
25public class ClassEntry extends ParentedEntry<ClassEntry> implements Comparable<ClassEntry> {
26 private final String fullName;
27
28 public ClassEntry(String className) {
29 this(getOuterClass(className), getInnerName(className), null);
30 }
31
32 public ClassEntry(@Nullable ClassEntry parent, String className) {
33 this(parent, className, null);
34 }
35
36 public ClassEntry(@Nullable ClassEntry parent, String className, @Nullable String javadocs) {
37 super(parent, className, javadocs);
38 if (parent != null) {
39 fullName = parent.getFullName() + "$" + name;
40 } else {
41 fullName = name;
42 }
43
44 if (parent == null && className.indexOf('.') >= 0) {
45 throw new IllegalArgumentException("Class name must be in JVM format. ie, path/to/package/class$inner : " + className);
46 }
47 }
48
49 @Override
50 public Class<ClassEntry> getParentType() {
51 return ClassEntry.class;
52 }
53
54 @Override
55 public String getName() {
56 return this.name;
57 }
58
59 public String getFullName() {
60 return fullName;
61 }
62
63 @Override
64 public ClassEntry translate(Translator translator, @Nullable EntryMapping mapping) {
65 if (name.charAt(0) == '[') {
66 String translatedName = translator.translate(new TypeDescriptor(name)).toString();
67 return new ClassEntry(parent, translatedName);
68 }
69
70 String translatedName = mapping != null ? mapping.getTargetName() : name;
71 String docs = mapping != null ? mapping.getJavadoc() : null;
72 return new ClassEntry(parent, translatedName, docs);
73 }
74
75 @Override
76 public ClassEntry getContainingClass() {
77 return this;
78 }
79
80 @Override
81 public int hashCode() {
82 return fullName.hashCode();
83 }
84
85 @Override
86 public boolean equals(Object other) {
87 return other instanceof ClassEntry && equals((ClassEntry) other);
88 }
89
90 public boolean equals(ClassEntry other) {
91 return other != null && Objects.equals(parent, other.parent) && this.name.equals(other.name);
92 }
93
94 @Override
95 public boolean canConflictWith(Entry<?> entry) {
96 return true;
97 }
98
99 @Override
100 public void validateName(String name) throws IllegalNameException {
101 NameValidator.validateClassName(name);
102 }
103
104 @Override
105 public ClassEntry withName(String name) {
106 return new ClassEntry(parent, name, javadocs);
107 }
108
109 @Override
110 public ClassEntry withParent(ClassEntry parent) {
111 return new ClassEntry(parent, name, javadocs);
112 }
113
114 @Override
115 public String toString() {
116 return getFullName();
117 }
118
119 public String getPackageName() {
120 return getPackageName(fullName);
121 }
122
123 public String getSimpleName() {
124 int packagePos = name.lastIndexOf('/');
125 if (packagePos > 0) {
126 return name.substring(packagePos + 1);
127 }
128 return name;
129 }
130
131 public boolean isInnerClass() {
132 return parent != null;
133 }
134
135 @Nullable
136 public ClassEntry getOuterClass() {
137 return parent;
138 }
139
140 @Nonnull
141 public ClassEntry getOutermostClass() {
142 if (parent == null) {
143 return this;
144 }
145 return parent.getOutermostClass();
146 }
147
148 public ClassEntry buildClassEntry(List<ClassEntry> classChain) {
149 assert (classChain.contains(this));
150 StringBuilder buf = new StringBuilder();
151 for (ClassEntry chainEntry : classChain) {
152 if (buf.length() == 0) {
153 buf.append(chainEntry.getFullName());
154 } else {
155 buf.append("$");
156 buf.append(chainEntry.getSimpleName());
157 }
158
159 if (chainEntry == this) {
160 break;
161 }
162 }
163 return new ClassEntry(buf.toString());
164 }
165
166 public boolean isJre() {
167 String packageName = getPackageName();
168 return packageName != null && (packageName.startsWith("java") || packageName.startsWith("javax"));
169 }
170
171 public static String getPackageName(String name) {
172 int pos = name.lastIndexOf('/');
173 if (pos > 0) {
174 return name.substring(0, pos);
175 }
176 return null;
177 }
178
179 @Nullable
180 public static ClassEntry getOuterClass(String name) {
181 int index = name.lastIndexOf('$');
182 if (index >= 0) {
183 return new ClassEntry(name.substring(0, index));
184 }
185 return null;
186 }
187
188 public static String getInnerName(String name) {
189 int innerClassPos = name.lastIndexOf('$');
190 if (innerClassPos > 0) {
191 return name.substring(innerClassPos + 1);
192 }
193 return name;
194 }
195
196 @Override
197 public String getSourceRemapName() {
198 ClassEntry outerClass = getOuterClass();
199 if (outerClass != null) {
200 return outerClass.getSourceRemapName() + "." + name;
201 }
202 return getSimpleName();
203 }
204
205 @Override
206 public int compareTo(ClassEntry entry) {
207 String fullName = getFullName();
208 String otherFullName = entry.getFullName();
209 if (fullName.length() != otherFullName.length()) {
210 return fullName.length() - otherFullName.length();
211 }
212 return fullName.compareTo(otherFullName);
213 }
214}
diff --git a/enigma/src/main/java/cuchaz/enigma/translation/representation/entry/DefEntry.java b/enigma/src/main/java/cuchaz/enigma/translation/representation/entry/DefEntry.java
new file mode 100644
index 0000000..82536c7
--- /dev/null
+++ b/enigma/src/main/java/cuchaz/enigma/translation/representation/entry/DefEntry.java
@@ -0,0 +1,7 @@
1package cuchaz.enigma.translation.representation.entry;
2
3import cuchaz.enigma.translation.representation.AccessFlags;
4
5public interface DefEntry<P extends Entry<?>> extends Entry<P> {
6 AccessFlags getAccess();
7}
diff --git a/enigma/src/main/java/cuchaz/enigma/translation/representation/entry/Entry.java b/enigma/src/main/java/cuchaz/enigma/translation/representation/entry/Entry.java
new file mode 100644
index 0000000..40bff31
--- /dev/null
+++ b/enigma/src/main/java/cuchaz/enigma/translation/representation/entry/Entry.java
@@ -0,0 +1,107 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 * <p>
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11
12package cuchaz.enigma.translation.representation.entry;
13
14import cuchaz.enigma.translation.mapping.IllegalNameException;
15import cuchaz.enigma.translation.Translatable;
16import cuchaz.enigma.translation.mapping.NameValidator;
17
18import javax.annotation.Nullable;
19import java.util.ArrayList;
20import java.util.List;
21
22public interface Entry<P extends Entry<?>> extends Translatable {
23 String getName();
24
25 String getJavadocs();
26
27 default String getSourceRemapName() {
28 return getName();
29 }
30
31 @Nullable
32 P getParent();
33
34 Class<P> getParentType();
35
36 Entry<P> withName(String name);
37
38 Entry<P> withParent(P parent);
39
40 boolean canConflictWith(Entry<?> entry);
41
42 @Nullable
43 default ClassEntry getContainingClass() {
44 P parent = getParent();
45 if (parent == null) {
46 return null;
47 }
48 if (parent instanceof ClassEntry) {
49 return (ClassEntry) parent;
50 }
51 return parent.getContainingClass();
52 }
53
54 default List<Entry<?>> getAncestry() {
55 P parent = getParent();
56 List<Entry<?>> entries = new ArrayList<>();
57 if (parent != null) {
58 entries.addAll(parent.getAncestry());
59 }
60 entries.add(this);
61 return entries;
62 }
63
64 @Nullable
65 @SuppressWarnings("unchecked")
66 default <E extends Entry<?>> E findAncestor(Class<E> type) {
67 List<Entry<?>> ancestry = getAncestry();
68 for (int i = ancestry.size() - 1; i >= 0; i--) {
69 Entry<?> ancestor = ancestry.get(i);
70 if (type.isAssignableFrom(ancestor.getClass())) {
71 return (E) ancestor;
72 }
73 }
74 return null;
75 }
76
77 @SuppressWarnings("unchecked")
78 default <E extends Entry<?>> Entry<P> replaceAncestor(E target, E replacement) {
79 if (replacement.equals(target)) {
80 return this;
81 }
82
83 if (equals(target)) {
84 return (Entry<P>) replacement;
85 }
86
87 P parent = getParent();
88 if (parent == null) {
89 return this;
90 }
91
92 return withParent((P) parent.replaceAncestor(target, replacement));
93 }
94
95 default void validateName(String name) throws IllegalNameException {
96 NameValidator.validateIdentifier(name);
97 }
98
99 @SuppressWarnings("unchecked")
100 @Nullable
101 default <C extends Entry<?>> Entry<C> castParent(Class<C> parentType) {
102 if (parentType.equals(getParentType())) {
103 return (Entry<C>) this;
104 }
105 return null;
106 }
107}
diff --git a/enigma/src/main/java/cuchaz/enigma/translation/representation/entry/FieldDefEntry.java b/enigma/src/main/java/cuchaz/enigma/translation/representation/entry/FieldDefEntry.java
new file mode 100644
index 0000000..f9282b2
--- /dev/null
+++ b/enigma/src/main/java/cuchaz/enigma/translation/representation/entry/FieldDefEntry.java
@@ -0,0 +1,71 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 * <p>
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11
12package cuchaz.enigma.translation.representation.entry;
13
14import com.google.common.base.Preconditions;
15import cuchaz.enigma.translation.Translator;
16import cuchaz.enigma.translation.mapping.EntryMapping;
17import cuchaz.enigma.translation.representation.AccessFlags;
18import cuchaz.enigma.translation.representation.Signature;
19import cuchaz.enigma.translation.representation.TypeDescriptor;
20
21import javax.annotation.Nullable;
22
23public class FieldDefEntry extends FieldEntry implements DefEntry<ClassEntry> {
24 private final AccessFlags access;
25 private final Signature signature;
26
27 public FieldDefEntry(ClassEntry owner, String name, TypeDescriptor desc, Signature signature, AccessFlags access) {
28 this(owner, name, desc, signature, access, null);
29 }
30
31 public FieldDefEntry(ClassEntry owner, String name, TypeDescriptor desc, Signature signature, AccessFlags access, String javadocs) {
32 super(owner, name, desc, javadocs);
33 Preconditions.checkNotNull(access, "Field access cannot be null");
34 Preconditions.checkNotNull(signature, "Field signature cannot be null");
35 this.access = access;
36 this.signature = signature;
37 }
38
39 public static FieldDefEntry parse(ClassEntry owner, int access, String name, String desc, String signature) {
40 return new FieldDefEntry(owner, name, new TypeDescriptor(desc), Signature.createTypedSignature(signature), new AccessFlags(access), null);
41 }
42
43 @Override
44 public AccessFlags getAccess() {
45 return access;
46 }
47
48 public Signature getSignature() {
49 return signature;
50 }
51
52 @Override
53 public FieldDefEntry translate(Translator translator, @Nullable EntryMapping mapping) {
54 TypeDescriptor translatedDesc = translator.translate(desc);
55 Signature translatedSignature = translator.translate(signature);
56 String translatedName = mapping != null ? mapping.getTargetName() : name;
57 AccessFlags translatedAccess = mapping != null ? mapping.getAccessModifier().transform(access) : access;
58 String docs = mapping != null ? mapping.getJavadoc() : null;
59 return new FieldDefEntry(parent, translatedName, translatedDesc, translatedSignature, translatedAccess, docs);
60 }
61
62 @Override
63 public FieldDefEntry withName(String name) {
64 return new FieldDefEntry(parent, name, desc, signature, access, javadocs);
65 }
66
67 @Override
68 public FieldDefEntry withParent(ClassEntry owner) {
69 return new FieldDefEntry(owner, this.name, this.desc, signature, access, javadocs);
70 }
71}
diff --git a/enigma/src/main/java/cuchaz/enigma/translation/representation/entry/FieldEntry.java b/enigma/src/main/java/cuchaz/enigma/translation/representation/entry/FieldEntry.java
new file mode 100644
index 0000000..0e47878
--- /dev/null
+++ b/enigma/src/main/java/cuchaz/enigma/translation/representation/entry/FieldEntry.java
@@ -0,0 +1,96 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 * <p>
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11
12package cuchaz.enigma.translation.representation.entry;
13
14import com.google.common.base.Preconditions;
15import cuchaz.enigma.translation.Translator;
16import cuchaz.enigma.translation.mapping.EntryMapping;
17import cuchaz.enigma.translation.representation.TypeDescriptor;
18
19import javax.annotation.Nullable;
20import java.util.Objects;
21
22public class FieldEntry extends ParentedEntry<ClassEntry> implements Comparable<FieldEntry> {
23 protected final TypeDescriptor desc;
24
25 public FieldEntry(ClassEntry parent, String name, TypeDescriptor desc) {
26 this(parent, name, desc, null);
27 }
28
29 public FieldEntry(ClassEntry parent, String name, TypeDescriptor desc, String javadocs) {
30 super(parent, name, javadocs);
31
32 Preconditions.checkNotNull(parent, "Owner cannot be null");
33 Preconditions.checkNotNull(desc, "Field descriptor cannot be null");
34
35 this.desc = desc;
36 }
37
38 public static FieldEntry parse(String owner, String name, String desc) {
39 return new FieldEntry(new ClassEntry(owner), name, new TypeDescriptor(desc), null);
40 }
41
42 @Override
43 public Class<ClassEntry> getParentType() {
44 return ClassEntry.class;
45 }
46
47 public TypeDescriptor getDesc() {
48 return this.desc;
49 }
50
51 @Override
52 public FieldEntry withName(String name) {
53 return new FieldEntry(parent, name, desc, null);
54 }
55
56 @Override
57 public FieldEntry withParent(ClassEntry parent) {
58 return new FieldEntry(parent, this.name, this.desc, null);
59 }
60
61 @Override
62 protected FieldEntry translate(Translator translator, @Nullable EntryMapping mapping) {
63 String translatedName = mapping != null ? mapping.getTargetName() : name;
64 String docs = mapping != null ? mapping.getJavadoc() : null;
65 return new FieldEntry(parent, translatedName, translator.translate(desc), docs);
66 }
67
68 @Override
69 public int hashCode() {
70 return Objects.hash(this.parent, this.name, this.desc);
71 }
72
73 @Override
74 public boolean equals(Object other) {
75 return other instanceof FieldEntry && equals((FieldEntry) other);
76 }
77
78 public boolean equals(FieldEntry other) {
79 return this.parent.equals(other.parent) && name.equals(other.name) && desc.equals(other.desc);
80 }
81
82 @Override
83 public boolean canConflictWith(Entry<?> entry) {
84 return entry instanceof FieldEntry && ((FieldEntry) entry).parent.equals(parent);
85 }
86
87 @Override
88 public String toString() {
89 return this.parent.getFullName() + "." + this.name + ":" + this.desc;
90 }
91
92 @Override
93 public int compareTo(FieldEntry entry) {
94 return (name + desc.toString()).compareTo(entry.name + entry.desc.toString());
95 }
96}
diff --git a/enigma/src/main/java/cuchaz/enigma/translation/representation/entry/LocalVariableDefEntry.java b/enigma/src/main/java/cuchaz/enigma/translation/representation/entry/LocalVariableDefEntry.java
new file mode 100644
index 0000000..aad4236
--- /dev/null
+++ b/enigma/src/main/java/cuchaz/enigma/translation/representation/entry/LocalVariableDefEntry.java
@@ -0,0 +1,51 @@
1package cuchaz.enigma.translation.representation.entry;
2
3import com.google.common.base.Preconditions;
4import cuchaz.enigma.translation.Translator;
5import cuchaz.enigma.translation.mapping.EntryMapping;
6import cuchaz.enigma.translation.representation.TypeDescriptor;
7
8import javax.annotation.Nullable;
9
10/**
11 * TypeDescriptor...
12 * Created by Thog
13 * 19/10/2016
14 */
15public class LocalVariableDefEntry extends LocalVariableEntry {
16 protected final TypeDescriptor desc;
17
18 public LocalVariableDefEntry(MethodEntry ownerEntry, int index, String name, boolean parameter, TypeDescriptor desc, String javadoc) {
19 super(ownerEntry, index, name, parameter, javadoc);
20 Preconditions.checkNotNull(desc, "Variable desc cannot be null");
21
22 this.desc = desc;
23 }
24
25 public TypeDescriptor getDesc() {
26 return desc;
27 }
28
29 @Override
30 public LocalVariableDefEntry translate(Translator translator, @Nullable EntryMapping mapping) {
31 TypeDescriptor translatedDesc = translator.translate(desc);
32 String translatedName = mapping != null ? mapping.getTargetName() : name;
33 String javadoc = mapping != null ? mapping.getJavadoc() : javadocs;
34 return new LocalVariableDefEntry(parent, index, translatedName, parameter, translatedDesc, javadoc);
35 }
36
37 @Override
38 public LocalVariableDefEntry withName(String name) {
39 return new LocalVariableDefEntry(parent, index, name, parameter, desc, javadocs);
40 }
41
42 @Override
43 public LocalVariableDefEntry withParent(MethodEntry entry) {
44 return new LocalVariableDefEntry(entry, index, name, parameter, desc, javadocs);
45 }
46
47 @Override
48 public String toString() {
49 return this.parent + "(" + this.index + ":" + this.name + ":" + this.desc + ")";
50 }
51}
diff --git a/enigma/src/main/java/cuchaz/enigma/translation/representation/entry/LocalVariableEntry.java b/enigma/src/main/java/cuchaz/enigma/translation/representation/entry/LocalVariableEntry.java
new file mode 100644
index 0000000..3f98f03
--- /dev/null
+++ b/enigma/src/main/java/cuchaz/enigma/translation/representation/entry/LocalVariableEntry.java
@@ -0,0 +1,93 @@
1package cuchaz.enigma.translation.representation.entry;
2
3import com.google.common.base.Preconditions;
4import cuchaz.enigma.translation.Translator;
5import cuchaz.enigma.translation.mapping.EntryMapping;
6
7import javax.annotation.Nullable;
8import java.util.Objects;
9
10/**
11 * TypeDescriptor...
12 * Created by Thog
13 * 19/10/2016
14 */
15public class LocalVariableEntry extends ParentedEntry<MethodEntry> implements Comparable<LocalVariableEntry> {
16
17 protected final int index;
18 protected final boolean parameter;
19
20 public LocalVariableEntry(MethodEntry parent, int index, String name, boolean parameter, String javadoc) {
21 super(parent, name, javadoc);
22
23 Preconditions.checkNotNull(parent, "Variable owner cannot be null");
24 Preconditions.checkArgument(index >= 0, "Index must be positive");
25
26 this.index = index;
27 this.parameter = parameter;
28 }
29
30 @Override
31 public Class<MethodEntry> getParentType() {
32 return MethodEntry.class;
33 }
34
35 public boolean isArgument() {
36 return this.parameter;
37 }
38
39 public int getIndex() {
40 return index;
41 }
42
43 @Override
44 public String getName() {
45 return this.name;
46 }
47
48 @Override
49 public LocalVariableEntry translate(Translator translator, @Nullable EntryMapping mapping) {
50 String translatedName = mapping != null ? mapping.getTargetName() : name;
51 String javadoc = mapping != null ? mapping.getJavadoc() : null;
52 return new LocalVariableEntry(parent, index, translatedName, parameter, javadoc);
53 }
54
55 @Override
56 public LocalVariableEntry withName(String name) {
57 return new LocalVariableEntry(parent, index, name, parameter, javadocs);
58 }
59
60 @Override
61 public LocalVariableEntry withParent(MethodEntry parent) {
62 return new LocalVariableEntry(parent, index, name, parameter, javadocs);
63 }
64
65 @Override
66 public int hashCode() {
67 return Objects.hash(this.parent, this.index);
68 }
69
70 @Override
71 public boolean equals(Object other) {
72 return other instanceof LocalVariableEntry && equals((LocalVariableEntry) other);
73 }
74
75 public boolean equals(LocalVariableEntry other) {
76 return this.parent.equals(other.parent) && this.index == other.index;
77 }
78
79 @Override
80 public boolean canConflictWith(Entry<?> entry) {
81 return entry instanceof LocalVariableEntry && ((LocalVariableEntry) entry).parent.equals(parent);
82 }
83
84 @Override
85 public String toString() {
86 return this.parent + "(" + this.index + ":" + this.name + ")";
87 }
88
89 @Override
90 public int compareTo(LocalVariableEntry entry) {
91 return Integer.compare(index, entry.index);
92 }
93}
diff --git a/enigma/src/main/java/cuchaz/enigma/translation/representation/entry/MethodDefEntry.java b/enigma/src/main/java/cuchaz/enigma/translation/representation/entry/MethodDefEntry.java
new file mode 100644
index 0000000..4e75a5c
--- /dev/null
+++ b/enigma/src/main/java/cuchaz/enigma/translation/representation/entry/MethodDefEntry.java
@@ -0,0 +1,71 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 * <p>
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11
12package cuchaz.enigma.translation.representation.entry;
13
14import com.google.common.base.Preconditions;
15import cuchaz.enigma.translation.Translator;
16import cuchaz.enigma.translation.mapping.EntryMapping;
17import cuchaz.enigma.translation.representation.AccessFlags;
18import cuchaz.enigma.translation.representation.MethodDescriptor;
19import cuchaz.enigma.translation.representation.Signature;
20
21import javax.annotation.Nullable;
22
23public class MethodDefEntry extends MethodEntry implements DefEntry<ClassEntry> {
24 private final AccessFlags access;
25 private final Signature signature;
26
27 public MethodDefEntry(ClassEntry owner, String name, MethodDescriptor descriptor, Signature signature, AccessFlags access) {
28 this(owner, name, descriptor, signature, access, null);
29 }
30
31 public MethodDefEntry(ClassEntry owner, String name, MethodDescriptor descriptor, Signature signature, AccessFlags access, String docs) {
32 super(owner, name, descriptor, docs);
33 Preconditions.checkNotNull(access, "Method access cannot be null");
34 Preconditions.checkNotNull(signature, "Method signature cannot be null");
35 this.access = access;
36 this.signature = signature;
37 }
38
39 public static MethodDefEntry parse(ClassEntry owner, int access, String name, String desc, String signature) {
40 return new MethodDefEntry(owner, name, new MethodDescriptor(desc), Signature.createSignature(signature), new AccessFlags(access), null);
41 }
42
43 @Override
44 public AccessFlags getAccess() {
45 return access;
46 }
47
48 public Signature getSignature() {
49 return signature;
50 }
51
52 @Override
53 public MethodDefEntry translate(Translator translator, @Nullable EntryMapping mapping) {
54 MethodDescriptor translatedDesc = translator.translate(descriptor);
55 Signature translatedSignature = translator.translate(signature);
56 String translatedName = mapping != null ? mapping.getTargetName() : name;
57 AccessFlags translatedAccess = mapping != null ? mapping.getAccessModifier().transform(access) : access;
58 String docs = mapping != null ? mapping.getJavadoc() : null;
59 return new MethodDefEntry(parent, translatedName, translatedDesc, translatedSignature, translatedAccess, docs);
60 }
61
62 @Override
63 public MethodDefEntry withName(String name) {
64 return new MethodDefEntry(parent, name, descriptor, signature, access, javadocs);
65 }
66
67 @Override
68 public MethodDefEntry withParent(ClassEntry parent) {
69 return new MethodDefEntry(new ClassEntry(parent.getFullName()), name, descriptor, signature, access, javadocs);
70 }
71}
diff --git a/enigma/src/main/java/cuchaz/enigma/translation/representation/entry/MethodEntry.java b/enigma/src/main/java/cuchaz/enigma/translation/representation/entry/MethodEntry.java
new file mode 100644
index 0000000..086a5c1
--- /dev/null
+++ b/enigma/src/main/java/cuchaz/enigma/translation/representation/entry/MethodEntry.java
@@ -0,0 +1,105 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 * <p>
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11
12package cuchaz.enigma.translation.representation.entry;
13
14import com.google.common.base.Preconditions;
15import cuchaz.enigma.translation.Translator;
16import cuchaz.enigma.translation.mapping.EntryMapping;
17import cuchaz.enigma.translation.representation.MethodDescriptor;
18
19import javax.annotation.Nullable;
20import java.util.Objects;
21
22public class MethodEntry extends ParentedEntry<ClassEntry> implements Comparable<MethodEntry> {
23
24 protected final MethodDescriptor descriptor;
25
26 public MethodEntry(ClassEntry parent, String name, MethodDescriptor descriptor) {
27 this(parent, name, descriptor, null);
28 }
29
30 public MethodEntry(ClassEntry parent, String name, MethodDescriptor descriptor, String javadocs) {
31 super(parent, name, javadocs);
32
33 Preconditions.checkNotNull(parent, "Parent cannot be null");
34 Preconditions.checkNotNull(descriptor, "Method descriptor cannot be null");
35
36 this.descriptor = descriptor;
37 }
38
39 public static MethodEntry parse(String owner, String name, String desc) {
40 return new MethodEntry(new ClassEntry(owner), name, new MethodDescriptor(desc), null);
41 }
42
43 @Override
44 public Class<ClassEntry> getParentType() {
45 return ClassEntry.class;
46 }
47
48 public MethodDescriptor getDesc() {
49 return this.descriptor;
50 }
51
52 public boolean isConstructor() {
53 return name.equals("<init>") || name.equals("<clinit>");
54 }
55
56 @Override
57 public MethodEntry translate(Translator translator, @Nullable EntryMapping mapping) {
58 String translatedName = mapping != null ? mapping.getTargetName() : name;
59 String docs = mapping != null ? mapping.getJavadoc() : null;
60 return new MethodEntry(parent, translatedName, translator.translate(descriptor), docs);
61 }
62
63 @Override
64 public MethodEntry withName(String name) {
65 return new MethodEntry(parent, name, descriptor, javadocs);
66 }
67
68 @Override
69 public MethodEntry withParent(ClassEntry parent) {
70 return new MethodEntry(new ClassEntry(parent.getFullName()), name, descriptor, javadocs);
71 }
72
73 @Override
74 public int hashCode() {
75 return Objects.hash(this.parent, this.name, this.descriptor);
76 }
77
78 @Override
79 public boolean equals(Object other) {
80 return other instanceof MethodEntry && equals((MethodEntry) other);
81 }
82
83 public boolean equals(MethodEntry other) {
84 return this.parent.equals(other.getParent()) && this.name.equals(other.getName()) && this.descriptor.equals(other.getDesc());
85 }
86
87 @Override
88 public boolean canConflictWith(Entry<?> entry) {
89 if (entry instanceof MethodEntry) {
90 MethodEntry methodEntry = (MethodEntry) entry;
91 return methodEntry.parent.equals(parent) && methodEntry.descriptor.canConflictWith(descriptor);
92 }
93 return false;
94 }
95
96 @Override
97 public String toString() {
98 return this.parent.getFullName() + "." + this.name + this.descriptor;
99 }
100
101 @Override
102 public int compareTo(MethodEntry entry) {
103 return (name + descriptor.toString()).compareTo(entry.name + entry.descriptor.toString());
104 }
105}
diff --git a/enigma/src/main/java/cuchaz/enigma/translation/representation/entry/ParentedEntry.java b/enigma/src/main/java/cuchaz/enigma/translation/representation/entry/ParentedEntry.java
new file mode 100644
index 0000000..95be22c
--- /dev/null
+++ b/enigma/src/main/java/cuchaz/enigma/translation/representation/entry/ParentedEntry.java
@@ -0,0 +1,81 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 * <p>
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11
12package cuchaz.enigma.translation.representation.entry;
13
14import com.google.common.base.Preconditions;
15import cuchaz.enigma.translation.Translator;
16import cuchaz.enigma.translation.mapping.EntryMap;
17import cuchaz.enigma.translation.mapping.EntryMapping;
18import cuchaz.enigma.translation.mapping.EntryResolver;
19import cuchaz.enigma.translation.mapping.ResolutionStrategy;
20
21import javax.annotation.Nullable;
22
23public abstract class ParentedEntry<P extends Entry<?>> implements Entry<P> {
24 protected final P parent;
25 protected final String name;
26 protected final @Nullable String javadocs;
27
28 protected ParentedEntry(P parent, String name, String javadocs) {
29 this.parent = parent;
30 this.name = name;
31 this.javadocs = javadocs;
32
33 Preconditions.checkNotNull(name, "Name cannot be null");
34 }
35
36 @Override
37 public abstract ParentedEntry<P> withParent(P parent);
38
39 @Override
40 public abstract ParentedEntry<P> withName(String name);
41
42 protected abstract ParentedEntry<P> translate(Translator translator, @Nullable EntryMapping mapping);
43
44 @Override
45 public String getName() {
46 return name;
47 }
48
49 @Override
50 @Nullable
51 public P getParent() {
52 return parent;
53 }
54
55 @Nullable
56 @Override
57 public String getJavadocs() {
58 return javadocs;
59 }
60
61 @Override
62 public ParentedEntry<P> translate(Translator translator, EntryResolver resolver, EntryMap<EntryMapping> mappings) {
63 P parent = getParent();
64 EntryMapping mapping = resolveMapping(resolver, mappings);
65 if (parent == null) {
66 return translate(translator, mapping);
67 }
68 P translatedParent = translator.translate(parent);
69 return withParent(translatedParent).translate(translator, mapping);
70 }
71
72 private EntryMapping resolveMapping(EntryResolver resolver, EntryMap<EntryMapping> mappings) {
73 for (ParentedEntry<P> entry : resolver.resolveEntry(this, ResolutionStrategy.RESOLVE_ROOT)) {
74 EntryMapping mapping = mappings.get(entry);
75 if (mapping != null) {
76 return mapping;
77 }
78 }
79 return null;
80 }
81}
diff --git a/enigma/src/main/java/cuchaz/enigma/utils/I18n.java b/enigma/src/main/java/cuchaz/enigma/utils/I18n.java
new file mode 100644
index 0000000..e18532b
--- /dev/null
+++ b/enigma/src/main/java/cuchaz/enigma/utils/I18n.java
@@ -0,0 +1,95 @@
1package cuchaz.enigma.utils;
2
3import java.io.BufferedReader;
4import java.io.IOException;
5import java.io.InputStream;
6import java.io.InputStreamReader;
7import java.nio.charset.StandardCharsets;
8import java.util.ArrayList;
9import java.util.Collections;
10import java.util.Map;
11import java.util.stream.Stream;
12
13import com.google.common.collect.ImmutableList;
14import com.google.common.collect.Maps;
15import com.google.common.reflect.ClassPath;
16import com.google.common.reflect.ClassPath.ResourceInfo;
17import com.google.gson.Gson;
18
19public class I18n {
20 public static final String DEFAULT_LANGUAGE = "en_us";
21 private static final Gson GSON = new Gson();
22 private static Map<String, String> translations = Maps.newHashMap();
23 private static Map<String, String> defaultTranslations = Maps.newHashMap();
24 private static Map<String, String> languageNames = Maps.newHashMap();
25
26 static {
27 defaultTranslations = load(DEFAULT_LANGUAGE);
28 translations = defaultTranslations;
29 }
30
31 @SuppressWarnings("unchecked")
32 public static Map<String, String> load(String language) {
33 try (InputStream inputStream = I18n.class.getResourceAsStream("/lang/" + language + ".json")) {
34 if (inputStream != null) {
35 try (BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream, StandardCharsets.UTF_8))) {
36 return GSON.fromJson(reader, Map.class);
37 }
38 }
39 } catch (IOException e) {
40 e.printStackTrace();
41 }
42 return Collections.emptyMap();
43 }
44
45 public static String translate(String key) {
46 String value = translations.get(key);
47 if (value != null) {
48 return value;
49 }
50 value = defaultTranslations.get(key);
51 if (value != null) {
52 return value;
53 }
54 return key;
55 }
56
57 public static String getLanguageName(String language) {
58 return languageNames.get(language);
59 }
60
61 public static void setLanguage(String language) {
62 translations = load(language);
63 }
64
65 public static ArrayList<String> getAvailableLanguages() {
66 ArrayList<String> list = new ArrayList<String>();
67
68 try {
69 ImmutableList<ResourceInfo> resources = ClassPath.from(Thread.currentThread().getContextClassLoader()).getResources().asList();
70 Stream<ResourceInfo> dirStream = resources.stream();
71 dirStream.forEach(context -> {
72 String file = context.getResourceName();
73 if (file.startsWith("lang/") && file.endsWith(".json")) {
74 String fileName = file.substring(5, file.length() - 5);
75 list.add(fileName);
76 loadLanguageName(fileName);
77 }
78 });
79 } catch (IOException e) {
80 e.printStackTrace();
81 }
82 return list;
83 }
84
85 private static void loadLanguageName(String fileName) {
86 try (InputStream stream = Thread.currentThread().getContextClassLoader().getResourceAsStream("lang/" + fileName + ".json")) {
87 try (BufferedReader reader = new BufferedReader(new InputStreamReader(stream, StandardCharsets.UTF_8))) {
88 Map<?, ?> map = GSON.fromJson(reader, Map.class);
89 languageNames.put(fileName, map.get("language").toString());
90 }
91 } catch (IOException e) {
92 e.printStackTrace();
93 }
94 }
95}
diff --git a/enigma/src/main/java/cuchaz/enigma/utils/Pair.java b/enigma/src/main/java/cuchaz/enigma/utils/Pair.java
new file mode 100644
index 0000000..bf02cef
--- /dev/null
+++ b/enigma/src/main/java/cuchaz/enigma/utils/Pair.java
@@ -0,0 +1,26 @@
1package cuchaz.enigma.utils;
2
3import java.util.Objects;
4
5public class Pair<A, B> {
6 public final A a;
7 public final B b;
8
9 public Pair(A a, B b) {
10 this.a = a;
11 this.b = b;
12 }
13
14 @Override
15 public int hashCode() {
16 return Objects.hashCode(a) * 31 +
17 Objects.hashCode(b);
18 }
19
20 @Override
21 public boolean equals(Object o) {
22 return o instanceof Pair &&
23 Objects.equals(a, ((Pair<?, ?>) o).a) &&
24 Objects.equals(b, ((Pair<?, ?>) o).b);
25 }
26}
diff --git a/enigma/src/main/java/cuchaz/enigma/utils/Utils.java b/enigma/src/main/java/cuchaz/enigma/utils/Utils.java
new file mode 100644
index 0000000..2664099
--- /dev/null
+++ b/enigma/src/main/java/cuchaz/enigma/utils/Utils.java
@@ -0,0 +1,92 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 * <p>
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11
12package cuchaz.enigma.utils;
13
14import com.google.common.io.CharStreams;
15
16import java.io.IOException;
17import java.io.InputStream;
18import java.io.InputStreamReader;
19import java.nio.charset.StandardCharsets;
20import java.nio.file.Files;
21import java.nio.file.Path;
22import java.security.MessageDigest;
23import java.security.NoSuchAlgorithmException;
24import java.util.Collections;
25import java.util.Comparator;
26import java.util.List;
27import java.util.Locale;
28import java.util.stream.Collectors;
29import java.util.zip.ZipEntry;
30import java.util.zip.ZipFile;
31
32public class Utils {
33 public static String readStreamToString(InputStream in) throws IOException {
34 return CharStreams.toString(new InputStreamReader(in, StandardCharsets.UTF_8));
35 }
36
37 public static String readResourceToString(String path) throws IOException {
38 InputStream in = Utils.class.getResourceAsStream(path);
39 if (in == null) {
40 throw new IllegalArgumentException("Resource not found! " + path);
41 }
42 return readStreamToString(in);
43 }
44
45 public static void delete(Path path) throws IOException {
46 if (Files.exists(path)) {
47 for (Path p : Files.walk(path).sorted(Comparator.reverseOrder()).collect(Collectors.toList())) {
48 Files.delete(p);
49 }
50 }
51 }
52
53 public static byte[] zipSha1(Path path) throws IOException {
54 MessageDigest digest;
55 try {
56 digest = MessageDigest.getInstance("SHA-1");
57 } catch (NoSuchAlgorithmException e) {
58 // Algorithm guaranteed to be supported
59 throw new RuntimeException(e);
60 }
61 try (ZipFile zip = new ZipFile(path.toFile())) {
62 List<? extends ZipEntry> entries = Collections.list(zip.entries());
63 // only compare classes (some implementations may not generate directory entries)
64 entries.removeIf(entry -> !entry.getName().toLowerCase(Locale.ROOT).endsWith(".class"));
65 // different implementations may add zip entries in a different order
66 entries.sort(Comparator.comparing(ZipEntry::getName));
67 byte[] buffer = new byte[8192];
68 for (ZipEntry entry : entries) {
69 digest.update(entry.getName().getBytes(StandardCharsets.UTF_8));
70 try (InputStream in = zip.getInputStream(entry)) {
71 int n;
72 while ((n = in.read(buffer)) != -1) {
73 digest.update(buffer, 0, n);
74 }
75 }
76 }
77 }
78 return digest.digest();
79 }
80
81 public static boolean isBlank(String input) {
82 if (input == null) {
83 return true;
84 }
85 for (int i = 0; i < input.length(); i++) {
86 if (!Character.isWhitespace(input.charAt(i))) {
87 return false;
88 }
89 }
90 return true;
91 }
92}
diff --git a/enigma/src/main/resources/META-INF/services/cuchaz.enigma.api.EnigmaPlugin b/enigma/src/main/resources/META-INF/services/cuchaz.enigma.api.EnigmaPlugin
new file mode 100644
index 0000000..136a3e7
--- /dev/null
+++ b/enigma/src/main/resources/META-INF/services/cuchaz.enigma.api.EnigmaPlugin
@@ -0,0 +1 @@
cuchaz.enigma.analysis.BuiltinPlugin
diff --git a/enigma/src/main/resources/lang/en_us.json b/enigma/src/main/resources/lang/en_us.json
new file mode 100644
index 0000000..dbf4b93
--- /dev/null
+++ b/enigma/src/main/resources/lang/en_us.json
@@ -0,0 +1,164 @@
1{
2 "language": "English",
3
4 "mapping_format.enigma_file": "Enigma File",
5 "mapping_format.enigma_directory": "Enigma Directory",
6 "mapping_format.enigma_zip": "Enigma ZIP",
7 "mapping_format.tiny_v2": "Tiny v2",
8 "mapping_format.tiny_file": "Tiny File",
9 "mapping_format.srg_file": "SRG File",
10 "mapping_format.proguard": "Proguard",
11 "type.methods": "Methods",
12 "type.fields": "Fields",
13 "type.parameters": "Parameters",
14 "type.classes": "Classes",
15
16 "menu.file": "File",
17 "menu.file.jar.open": "Open Jar...",
18 "menu.file.jar.close": "Close Jar",
19 "menu.file.mappings.open": "Open Mappings...",
20 "menu.file.mappings.save": "Save Mappings",
21 "menu.file.mappings.save_as": "Save Mappings As...",
22 "menu.file.mappings.close": "Close Mappings",
23 "menu.file.mappings.drop": "Drop Invalid Mappings",
24 "menu.file.export.source": "Export Source...",
25 "menu.file.export.jar": "Export Jar...",
26 "menu.file.stats": "Mapping Stats...",
27 "menu.file.stats.title": "Choose Included Members",
28 "menu.file.stats.generate": "Generate Stats",
29 "menu.file.exit": "Exit",
30 "menu.decompiler": "Decompiler",
31 "menu.view": "View",
32 "menu.view.themes": "Themes",
33 "menu.view.themes.default": "Default",
34 "menu.view.themes.darcula": "Darcula",
35 "menu.view.themes.system": "System",
36 "menu.view.themes.none": "None (JVM Default)",
37 "menu.view.languages": "Languages",
38 "menu.view.scale": "Scale",
39 "menu.view.scale.custom": "Custom...",
40 "menu.view.scale.custom.title": "Custom Scale",
41 "menu.view.change.title": "Changes",
42 "menu.view.change.summary": "Changes will be applied after the next restart.",
43 "menu.view.change.ok": "Ok",
44 "menu.view.search": "Search",
45 "menu.collab": "Collab",
46 "menu.collab.connect": "Connect to server",
47 "menu.collab.connect.error": "Error connecting to server",
48 "menu.collab.disconnect": "Disconnect",
49 "menu.collab.server.start": "Start server",
50 "menu.collab.server.start.error": "Error starting server",
51 "menu.collab.server.stop": "Stop server",
52 "menu.help": "Help",
53 "menu.help.about": "About",
54 "menu.help.about.title": "%s - About",
55 "menu.help.about.ok": "Ok",
56 "menu.help.github": "Github Page",
57
58 "popup_menu.rename": "Rename",
59 "popup_menu.javadoc": "Edit Javadoc",
60 "popup_menu.inheritance": "Show Inheritance",
61 "popup_menu.implementations": "Show Implementations",
62 "popup_menu.calls": "Show Calls (All Implementations)",
63 "popup_menu.calls.specific": "Show Calls (Specific)",
64 "popup_menu.declaration": "Go to Declaration",
65 "popup_menu.back": "Go back",
66 "popup_menu.forward": "Go forward",
67 "popup_menu.mark_deobfuscated": "Mark as deobfuscated",
68 "popup_menu.reset_obfuscated": "Reset to obfuscated",
69 "popup_menu.zoom.in": "Zoom in",
70 "popup_menu.zoom.out": "Zoom out",
71 "popup_menu.zoom.reset": "Reset zoom",
72
73 "info_panel.classes.obfuscated": "Obfuscated Classes",
74 "info_panel.classes.deobfuscated": "De-obfuscated Classes",
75 "info_panel.identifier": "Identifier Info",
76 "info_panel.identifier.none": "No identifier selected",
77 "info_panel.identifier.variable": "Variable",
78 "info_panel.identifier.field": "Field",
79 "info_panel.identifier.method": "Method",
80 "info_panel.identifier.constructor": "Constructor",
81 "info_panel.identifier.class": "Class",
82 "info_panel.identifier.type_descriptor": "TypeDescriptor",
83 "info_panel.identifier.method_descriptor": "MethodDescriptor",
84 "info_panel.identifier.modifier": "Modifier",
85 "info_panel.identifier.index": "Index",
86 "info_panel.editor.class.decompiling": "(decompiling...)",
87 "info_panel.editor.class.not_found": "Unable to find class:",
88 "info_panel.tree.inheritance": "Inheritance",
89 "info_panel.tree.implementations": "Implementations",
90 "info_panel.tree.calls": "Call Graph",
91
92 "log_panel.messages": "Messages",
93 "log_panel.users": "Users",
94
95 "progress.operation": "%s - Operation in progress",
96 "progress.jar.indexing": "Indexing jar",
97 "progress.jar.indexing.entries": "Entries...",
98 "progress.jar.indexing.references": "Entry references...",
99 "progress.jar.indexing.methods": "Bridge methods...",
100 "progress.jar.indexing.process": "Processing...",
101 "progress.jar.writing": "Writing jar...",
102 "progress.sources.writing": "Writing sources...",
103 "progress.classes.deobfuscating": "Deobfuscating classes...",
104 "progress.classes.decompiling": "Decompiling classes...",
105 "progress.mappings.enigma_file.loading": "Loading mapping file",
106 "progress.mappings.enigma_file.done": "Done!",
107 "progress.mappings.enigma_file.writing": "Writing classes",
108 "progress.mappings.enigma_directory.loading": "Loading mapping files",
109 "progress.mappings.enigma_directory.writing": "Writing classes",
110 "progress.mappings.tiny_file.loading": "Loading mapping file",
111 "progress.mappings.tiny_v2.loading": "Loading mapping file",
112 "progress.mappings.srg_file.generating": "Generating mappings",
113 "progress.mappings.srg_file.writing": "Writing mappings",
114 "progress.stats": "Generating stats",
115 "progress.stats.data": "Generating data",
116
117 "javadocs.edit": "Edit Javadocs",
118 "javadocs.instruction": "Edit javadocs here.",
119 "javadocs.cancel": "Cancel",
120 "javadocs.save": "Save",
121
122 "prompt.close.title": "Save your changes?",
123 "prompt.close.summary": "Your mappings have not been saved yet. Do you want to save?",
124 "prompt.close.save": "Save and close",
125 "prompt.close.discard": "Discard changes",
126 "prompt.close.cancel": "Cancel",
127 "prompt.open": "Open",
128 "prompt.cancel": "Cancel",
129 "prompt.connect.title": "Connect to server",
130 "prompt.connect.username": "Username",
131 "prompt.connect.ip": "IP",
132 "prompt.port": "Port",
133 "prompt.port.nan": "Port is not a number",
134 "prompt.port.invalid": "Port is out of range, should be between 0-65535",
135 "prompt.password": "Password",
136 "prompt.password.too_long": "Password is too long, it must be at most 255 characters.",
137 "prompt.create_server.title": "Create server",
138
139 "disconnect.disconnected": "Disconnected",
140 "disconnect.server_closed": "Server closed",
141 "disconnect.wrong_jar": "Jar checksums don't match (you have the wrong jar)!",
142 "disconnect.wrong_password": "Incorrect password",
143 "disconnect.username_taken": "Username is taken",
144
145 "message.chat.text": "%s: %s",
146 "message.connect.text": "[+] %s",
147 "message.disconnect.text": "[-] %s",
148 "message.edit_docs.text": "%s edited docs for %s",
149 "message.mark_deobf.text": "%s marked %s as deobfuscated",
150 "message.remove_mapping.text": "%s removed mappings for %s",
151 "message.rename.text": "%s renamed %s to %s",
152
153 "status.disconnected": "Disconnected.",
154 "status.connected": "Connected.",
155 "status.connected_user_count": "Connected (%d users).",
156 "status.ready": "Ready.",
157
158 "crash.title": "%s - Crash Report",
159 "crash.summary": "%s has crashed! =(",
160 "crash.export": "Export",
161 "crash.ignore": "Ignore",
162 "crash.exit": "Exit",
163 "crash.exit.warning": "If you choose exit, you will lose any unsaved work."
164}
diff --git a/enigma/src/main/resources/lang/fr_fr.json b/enigma/src/main/resources/lang/fr_fr.json
new file mode 100644
index 0000000..d169b9a
--- /dev/null
+++ b/enigma/src/main/resources/lang/fr_fr.json
@@ -0,0 +1,164 @@
1{
2 "language": "Français",
3
4 "mapping_format.enigma_file": "Fichier Enigma",
5 "mapping_format.enigma_directory": "Répertoire Enigma",
6 "mapping_format.enigma_zip": "ZIP Enigma",
7 "mapping_format.tiny_v2": "Tiny v2",
8 "mapping_format.tiny_file": "Fichier Tiny",
9 "mapping_format.srg_file": "Fichier SRG",
10 "mapping_format.proguard": "Proguard",
11 "type.methods": "Méthodes",
12 "type.fields": "Champs",
13 "type.parameters": "Paramètres",
14 "type.classes": "Classes",
15
16 "menu.file": "Fichier",
17 "menu.file.jar.open": "Ouvrir le jar...",
18 "menu.file.jar.close": "Fermer le jar",
19 "menu.file.mappings.open": "Ouvrir les mappings...",
20 "menu.file.mappings.save": "Enregistrer les mappings",
21 "menu.file.mappings.save_as": "Enregistrer les mappings sous...",
22 "menu.file.mappings.close": "Fermer les mappings",
23 "menu.file.mappings.drop": "Supprimer les mappings invalides",
24 "menu.file.export.source": "Exporter la source...",
25 "menu.file.export.jar": "Exporter le jar...",
26 "menu.file.stats": "Statistiques des mappings...",
27 "menu.file.stats.title": "Choisir les membres inclus",
28 "menu.file.stats.generate": "Générer les statistiques",
29 "menu.file.exit": "Quitter",
30 "menu.decompiler": "Décompilateur",
31 "menu.view": "Affichage",
32 "menu.view.themes": "Thèmes",
33 "menu.view.themes.default": "Par défaut",
34 "menu.view.themes.darcula": "Darcula",
35 "menu.view.themes.system": "Système",
36 "menu.view.themes.none": "Aucun (JVM par défaut)",
37 "menu.view.languages": "Langues",
38 "menu.view.scale": "Échelle",
39 "menu.view.scale.custom": "Personnalisée...",
40 "menu.view.scale.custom.title": "Échelle personnalisée",
41 "menu.view.change.title": "Modifications",
42 "menu.view.change.summary": "Les modifications seront appliquées lors du prochain redémarrage.",
43 "menu.view.change.ok": "Ok",
44 "menu.view.search": "Rechercher",
45 "menu.collab": "Collab",
46 "menu.collab.connect": "Se connecter à un serveur",
47 "menu.collab.connect.error": "Erreur lors de la connexion au serveur",
48 "menu.collab.disconnect": "Se déconnecter",
49 "menu.collab.server.start": "Démarrer le serveur",
50 "menu.collab.server.start.error": "Erreur lors du démarrage du serveur",
51 "menu.collab.server.stop": "Arrêter le serveur",
52 "menu.help": "Aide",
53 "menu.help.about": "À propos",
54 "menu.help.about.title": "%s - À propos",
55 "menu.help.about.ok": "Ok",
56 "menu.help.github": "Page Github",
57
58 "popup_menu.rename": "Renommer",
59 "popup_menu.javadoc": "Éditer le Javadoc",
60 "popup_menu.inheritance": "Afficher l'héritage",
61 "popup_menu.implementations": "Afficher les implémentations",
62 "popup_menu.calls": "Afficher les appels (tous)",
63 "popup_menu.calls.specific": "Afficher les appels (spécifiques)",
64 "popup_menu.declaration": "Aller à la déclaration",
65 "popup_menu.back": "Annuler",
66 "popup_menu.forward": "Restaurer",
67 "popup_menu.mark_deobfuscated": "Marquer comme déobfusqué",
68 "popup_menu.reset_obfuscated": "Réinitialiser à obfusqué",
69 "popup_menu.zoom.in": "Zoomer",
70 "popup_menu.zoom.out": "Dézoomer",
71 "popup_menu.zoom.reset": "Réinitialiser le zoom",
72
73 "info_panel.classes.obfuscated": "Classes obfusquées",
74 "info_panel.classes.deobfuscated": "Classes déobfusquées",
75 "info_panel.identifier": "Informations sur l'identifiant",
76 "info_panel.identifier.none": "Aucun identifiant sélectionné",
77 "info_panel.identifier.variable": "Variable",
78 "info_panel.identifier.field": "Champ",
79 "info_panel.identifier.method": "Méthode",
80 "info_panel.identifier.constructor": "Constructeur",
81 "info_panel.identifier.class": "Classe",
82 "info_panel.identifier.type_descriptor": "Descripteur de type",
83 "info_panel.identifier.method_descriptor": "Descripteur de méthode",
84 "info_panel.identifier.modifier": "Modificateur",
85 "info_panel.identifier.index": "Index",
86 "info_panel.editor.class.decompiling": "(décompilation...)",
87 "info_panel.editor.class.not_found": "Impossible de trouver la classe :",
88 "info_panel.tree.inheritance": "Héritage",
89 "info_panel.tree.implementations": "Implémentations",
90 "info_panel.tree.calls": "Graphique des appels",
91
92 "log_panel.messages": "Messages",
93 "log_panel.users": "Utilisateurs",
94
95 "progress.operation": "%s - Opération en cours",
96 "progress.jar.indexing": "Indexation du jar",
97 "progress.jar.indexing.entries": "Entrées...",
98 "progress.jar.indexing.references": "Références des entrées...",
99 "progress.jar.indexing.methods": "Mise en place des méthodes...",
100 "progress.jar.indexing.process": "Traitement...",
101 "progress.jar.writing": "Écriture du jar...",
102 "progress.sources.writing": "Écriture des sources...",
103 "progress.classes.deobfuscating": "Déobfuscation des classes...",
104 "progress.classes.decompiling": "Décompilation des classes...",
105 "progress.mappings.enigma_file.loading": "Chargement du fichier de mappings",
106 "progress.mappings.enigma_file.done": "Terminé !",
107 "progress.mappings.enigma_file.writing": "Écriture des classes",
108 "progress.mappings.enigma_directory.loading": "Chargement des fichiers de mappings",
109 "progress.mappings.enigma_directory.writing": "Écriture des classes",
110 "progress.mappings.tiny_file.loading": "Chargement du fichier de mappings",
111 "progress.mappings.tiny_v2.loading": "Chargement du fichier de mappings",
112 "progress.mappings.srg_file.generating": "Génération des mappings",
113 "progress.mappings.srg_file.writing": "Écriture des mappings",
114 "progress.stats": "Génération des statistiques",
115 "progress.stats.data": "Génération des données",
116
117 "javadocs.edit": "Éditer les Javadocs",
118 "javadocs.instruction": "Éditer les Javadocs ici.",
119 "javadocs.cancel": "Annuler",
120 "javadocs.save": "Enregistrer",
121
122 "prompt.close.title": "Enregistrer les modifications ?",
123 "prompt.close.summary": "Vos mappings n'ont pas encore été enregistrés. Souhaitez-vous enregistrer ?",
124 "prompt.close.save": "Enregistrer et fermer",
125 "prompt.close.discard": "Annuler les modifications",
126 "prompt.close.cancel": "Annuler",
127 "prompt.open": "Ouvrir",
128 "prompt.cancel": "Annuler",
129 "prompt.connect.title": "Se connecter à un serveur",
130 "prompt.connect.username": "Nom d'utilisateur",
131 "prompt.connect.ip": "IP",
132 "prompt.port": "Port",
133 "prompt.port.nan": "Le port n'est pas un nombre",
134 "prompt.port.invalid": "Le port est hors de portée. Il doit être compris entre 0 et 65535.",
135 "prompt.password": "Mot de passe",
136 "prompt.password.too_long": "Le mot de passe est trop long. Il ne doit pas dépasser 255 caractères.",
137 "prompt.create_server.title": "Créer un serveur",
138
139 "disconnect.disconnected": "Déconnecté",
140 "disconnect.server_closed": "Serveur fermé",
141 "disconnect.wrong_jar": "Les sommes de contrôle du jar ne correspondent pas (vous avez le mauvais jar) !",
142 "disconnect.wrong_password": "Mot de passe incorrect",
143 "disconnect.username_taken": "Le nom d'utilisateur est déjà pris",
144
145 "message.chat.text": "%s : %s",
146 "message.connect.text": "[+] %s",
147 "message.disconnect.text": "[-] %s",
148 "message.edit_docs.text": "%s a édité les javadocs de %s",
149 "message.mark_deobf.text": "%s a marqué %s comme déobfusqué",
150 "message.remove_mapping.text": "%s a supprimé les mappings de %s",
151 "message.rename.text": "%s a renommé %s en %s",
152
153 "status.disconnected": "Déconnecté.",
154 "status.connected": "Connecté.",
155 "status.connected_user_count": "Connecté (%d utilisateurs).",
156 "status.ready": "Prêt.",
157
158 "crash.title": "%s - Rapport de plantage",
159 "crash.summary": "%s a planté ! =(",
160 "crash.export": "Exporter",
161 "crash.ignore": "Ignorer",
162 "crash.exit": "Quitter",
163 "crash.exit.warning": "Si vous choisissez Quitter, vous perdrez tout travail non sauvegardé."
164}
diff --git a/enigma/src/main/resources/lang/zh_cn.json b/enigma/src/main/resources/lang/zh_cn.json
new file mode 100644
index 0000000..f3f503a
--- /dev/null
+++ b/enigma/src/main/resources/lang/zh_cn.json
@@ -0,0 +1,118 @@
1{
2 "language": "Chinese",
3
4 "mapping_format.enigma_file": "Enigma 文件",
5 "mapping_format.enigma_directory": "Enigma 目录",
6 "mapping_format.enigma_zip": "Enigma ZIP",
7 "mapping_format.tiny_v2": "Tiny v2",
8 "mapping_format.tiny_file": "Tiny File",
9 "mapping_format.srg_file": "SRG File",
10 "mapping_format.proguard": "Proguard",
11 "type.methods": "方法",
12 "type.fields": "字段",
13 "type.parameters": "参数",
14 "type.classes": "类",
15
16 "menu.file": "文件",
17 "menu.file.jar.open": "打开 Jar...",
18 "menu.file.jar.close": "关闭 Jar",
19 "menu.file.mappings.open": "打开映射...",
20 "menu.file.mappings.save": "保存映射",
21 "menu.file.mappings.save_as": "将映射另存为...",
22 "menu.file.mappings.close": "关闭映射",
23 "menu.file.mappings.drop": "删除无效映射",
24 "menu.file.export.source": "导出源码...",
25 "menu.file.export.jar": "导出Jar...",
26 "menu.file.stats": "映射统计范围...",
27 "menu.file.stats.title": "选择包括的成员",
28 "menu.file.stats.generate": "生成统计范围",
29 "menu.file.exit": "退出",
30 "menu.decompiler": "反编译",
31 "menu.view": "查看",
32 "menu.view.themes": "主题",
33 "menu.view.themes.default": "Default",
34 "menu.view.themes.darcula": "Darcula",
35 "menu.view.themes.system": "System",
36 "menu.view.themes.none": "None (JVM Default)",
37 "menu.view.languages": "语言",
38 "menu.view.languages.title": "更改语言",
39 "menu.view.languages.summary": "新语言将在下次重新启动后应用.",
40 "menu.view.languages.ok": "确定",
41 "menu.view.search": "搜索",
42 "menu.help": "帮助",
43 "menu.help.about": "关于",
44 "menu.help.about.title": "%s - 关于",
45 "menu.help.about.ok": "确定",
46 "menu.help.github": "GitHub 页面",
47
48 "popup_menu.rename": "改名",
49 "popup_menu.javadoc": "编辑注释",
50 "popup_menu.inheritance": "显示继承",
51 "popup_menu.implementations": "显示实现",
52 "popup_menu.calls": "显示 Calls",
53 "popup_menu.calls.specific": "显示 Calls (具体)",
54 "popup_menu.declaration": "Go to Declaration",
55 "popup_menu.back": "Go back",
56 "popup_menu.forward": "Go forward",
57 "popup_menu.mark_deobfuscated": "标记为反混淆",
58 "popup_menu.reset_obfuscated": "重置混淆",
59
60 "info_panel.classes.obfuscated": "混淆类",
61 "info_panel.classes.deobfuscated": "反混淆类",
62 "info_panel.identifier": "标识符信息",
63 "info_panel.identifier.none": "未选择标识符",
64 "info_panel.identifier.variable": "变量",
65 "info_panel.identifier.field": "字段",
66 "info_panel.identifier.method": "方法",
67 "info_panel.identifier.constructor": "构造器",
68 "info_panel.identifier.class": "类",
69 "info_panel.identifier.type_descriptor": "类型描述符",
70 "info_panel.identifier.method_descriptor": "方法描述符",
71 "info_panel.identifier.modifier": "修饰语",
72 "info_panel.identifier.index": "索引",
73 "info_panel.editor.class.decompiling": "(反编译中...)",
74 "info_panel.editor.class.not_found": "找不到类:",
75 "info_panel.tree.inheritance": "继承",
76 "info_panel.tree.implementations": "实现",
77 "info_panel.tree.calls": "调用图",
78
79 "progress.operation": "%s - 进行中",
80 "progress.jar.indexing": "索引jar",
81 "progress.jar.indexing.entries": "条目...",
82 "progress.jar.indexing.references": "条目引用...",
83 "progress.jar.indexing.methods": "桥接方法...",
84 "progress.jar.indexing.process": "处理中...",
85 "progress.jar.writing": "写出jar中...",
86 "progress.sources.writing": "写出源码中...",
87 "progress.classes.deobfuscating": "反混淆类中...",
88 "progress.classes.decompiling": "反编译类中...",
89 "progress.mappings.enigma_file.loading": "加载映射文件",
90 "progress.mappings.enigma_file.done": "完成!",
91 "progress.mappings.enigma_file.writing": "写出类",
92 "progress.mappings.enigma_directory.loading": "加载映射文件",
93 "progress.mappings.enigma_directory.writing": "写出类",
94 "progress.mappings.tiny_file.loading": "加载映射文件",
95 "progress.mappings.tiny_v2.loading": "加载映射文件",
96 "progress.mappings.srg_file.generating": "生成映射",
97 "progress.mappings.srg_file.writing": "写出映射",
98 "progress.stats": "生成统计范围",
99 "progress.stats.data": "生成数据",
100
101 "javadocs.edit": "编辑注释",
102 "javadocs.instruction": "在此处编辑编辑注释.",
103 "javadocs.cancel": "取消",
104 "javadocs.save": "保存",
105
106 "prompt.close.title": "保存更改?",
107 "prompt.close.summary": "您的映射尚未保存。你想保存吗?",
108 "prompt.close.save": "保存并关闭",
109 "prompt.close.discard": "放弃更改",
110 "prompt.close.cancel": "取消",
111
112 "crash.title": "%s - 崩溃报告",
113 "crash.summary": "%s 已经崩溃! =(",
114 "crash.export": "输出",
115 "crash.ignore": "忽略",
116 "crash.exit": "退出",
117 "crash.exit.warning": "如果选择退出,将丢失所有未保存的工作."
118}
diff --git a/enigma/src/main/resources/profile.json b/enigma/src/main/resources/profile.json
new file mode 100644
index 0000000..e1af4cd
--- /dev/null
+++ b/enigma/src/main/resources/profile.json
@@ -0,0 +1,20 @@
1{
2 "services": {
3 "jar_indexer": [
4 {
5 "id": "enigma:enum_initializer_indexer"
6 },
7 {
8 "id": "enigma:specialized_bridge_method_indexer"
9 }
10 ],
11 "name_proposal": [
12 {
13 "id": "enigma:enum_name_proposer"
14 },
15 {
16 "id": "enigma:specialized_method_name_proposer"
17 }
18 ]
19 }
20} \ No newline at end of file
diff --git a/enigma/src/test/java/cuchaz/enigma/PackageVisibilityIndexTest.java b/enigma/src/test/java/cuchaz/enigma/PackageVisibilityIndexTest.java
new file mode 100644
index 0000000..1dc9748
--- /dev/null
+++ b/enigma/src/test/java/cuchaz/enigma/PackageVisibilityIndexTest.java
@@ -0,0 +1,53 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11
12package cuchaz.enigma;
13
14import cuchaz.enigma.analysis.ClassCache;
15import cuchaz.enigma.analysis.index.JarIndex;
16import cuchaz.enigma.analysis.index.PackageVisibilityIndex;
17import cuchaz.enigma.translation.representation.entry.ClassEntry;
18import org.junit.Test;
19
20import java.nio.file.Paths;
21
22import static cuchaz.enigma.TestEntryFactory.newClass;
23import static org.hamcrest.MatcherAssert.assertThat;
24import static org.hamcrest.Matchers.contains;
25import static org.hamcrest.Matchers.containsInAnyOrder;
26
27public class PackageVisibilityIndexTest {
28
29 private static final ClassEntry KEEP = newClass("cuchaz/enigma/inputs/Keep");
30 private static final ClassEntry BASE = newClass("a");
31 private static final ClassEntry SAME_PACKAGE_CHILD = newClass("b");
32 private static final ClassEntry SAME_PACKAGE_CHILD_INNER = newClass("b$a");
33 private static final ClassEntry OTHER_PACKAGE_CHILD = newClass("c");
34 private static final ClassEntry OTHER_PACKAGE_CHILD_INNER = newClass("c$a");
35 private final JarIndex jarIndex;
36
37 public PackageVisibilityIndexTest() throws Exception {
38 ClassCache classCache = ClassCache.of(Paths.get("build/test-obf/packageAccess.jar"));
39 jarIndex = classCache.index(ProgressListener.none());
40 }
41
42 @Test
43 public void test() {
44 PackageVisibilityIndex visibilityIndex = jarIndex.getPackageVisibilityIndex();
45 assertThat(visibilityIndex.getPartition(BASE), containsInAnyOrder(BASE, SAME_PACKAGE_CHILD, SAME_PACKAGE_CHILD_INNER));
46 System.out.println(visibilityIndex.getPartitions());
47 assertThat(visibilityIndex.getPartitions(), containsInAnyOrder(
48 containsInAnyOrder(BASE, SAME_PACKAGE_CHILD, SAME_PACKAGE_CHILD_INNER),
49 containsInAnyOrder(OTHER_PACKAGE_CHILD, OTHER_PACKAGE_CHILD_INNER),
50 contains(KEEP)
51 ));
52 }
53}
diff --git a/enigma/src/test/java/cuchaz/enigma/TestDeobfed.java b/enigma/src/test/java/cuchaz/enigma/TestDeobfed.java
new file mode 100644
index 0000000..494d959
--- /dev/null
+++ b/enigma/src/test/java/cuchaz/enigma/TestDeobfed.java
@@ -0,0 +1,106 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11
12package cuchaz.enigma;
13
14import cuchaz.enigma.analysis.ClassCache;
15import cuchaz.enigma.analysis.index.JarIndex;
16import cuchaz.enigma.source.Decompiler;
17import cuchaz.enigma.source.Decompilers;
18import cuchaz.enigma.source.SourceSettings;
19import org.junit.BeforeClass;
20import org.junit.Test;
21
22import java.nio.file.Files;
23import java.nio.file.Path;
24import java.nio.file.Paths;
25
26import static cuchaz.enigma.TestEntryFactory.newClass;
27import static org.hamcrest.MatcherAssert.assertThat;
28import static org.hamcrest.Matchers.containsInAnyOrder;
29
30public class TestDeobfed {
31 private static Enigma enigma;
32 private static ClassCache classCache;
33 private static JarIndex index;
34
35 @BeforeClass
36 public static void beforeClass() throws Exception {
37 enigma = Enigma.create();
38
39 Path obf = Paths.get("build/test-obf/translation.jar");
40 Path deobf = Paths.get("build/test-deobf/translation.jar");
41 Files.createDirectories(deobf.getParent());
42 EnigmaProject project = enigma.openJar(obf, ProgressListener.none());
43 project.exportRemappedJar(ProgressListener.none()).write(deobf, ProgressListener.none());
44
45 classCache = ClassCache.of(deobf);
46 index = classCache.index(ProgressListener.none());
47 }
48
49 @Test
50 public void obfEntries() {
51 assertThat(index.getEntryIndex().getClasses(), containsInAnyOrder(
52 newClass("cuchaz/enigma/inputs/Keep"),
53 newClass("a"),
54 newClass("b"),
55 newClass("c"),
56 newClass("d"),
57 newClass("d$1"),
58 newClass("e"),
59 newClass("f"),
60 newClass("g"),
61 newClass("g$a"),
62 newClass("g$a$a"),
63 newClass("g$b"),
64 newClass("g$b$a"),
65 newClass("h"),
66 newClass("h$a"),
67 newClass("h$a$a"),
68 newClass("h$b"),
69 newClass("h$b$a"),
70 newClass("h$b$a$a"),
71 newClass("h$b$a$b"),
72 newClass("i"),
73 newClass("i$a"),
74 newClass("i$b")
75 ));
76 }
77
78 @Test
79 public void decompile() {
80 EnigmaProject project = new EnigmaProject(enigma, classCache, index, new byte[20]);
81 Decompiler decompiler = Decompilers.PROCYON.create(project.getClassCache(), new SourceSettings(false, false));
82
83 decompiler.getSource("a");
84 decompiler.getSource("b");
85 decompiler.getSource("c");
86 decompiler.getSource("d");
87 decompiler.getSource("d$1");
88 decompiler.getSource("e");
89 decompiler.getSource("f");
90 decompiler.getSource("g");
91 decompiler.getSource("g$a");
92 decompiler.getSource("g$a$a");
93 decompiler.getSource("g$b");
94 decompiler.getSource("g$b$a");
95 decompiler.getSource("h");
96 decompiler.getSource("h$a");
97 decompiler.getSource("h$a$a");
98 decompiler.getSource("h$b");
99 decompiler.getSource("h$b$a");
100 decompiler.getSource("h$b$a$a");
101 decompiler.getSource("h$b$a$b");
102 decompiler.getSource("i");
103 decompiler.getSource("i$a");
104 decompiler.getSource("i$b");
105 }
106}
diff --git a/enigma/src/test/java/cuchaz/enigma/TestDeobfuscator.java b/enigma/src/test/java/cuchaz/enigma/TestDeobfuscator.java
new file mode 100644
index 0000000..6619d26
--- /dev/null
+++ b/enigma/src/test/java/cuchaz/enigma/TestDeobfuscator.java
@@ -0,0 +1,41 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11
12package cuchaz.enigma;
13
14import cuchaz.enigma.source.Decompiler;
15import cuchaz.enigma.source.Decompilers;
16import cuchaz.enigma.source.SourceSettings;
17import org.junit.Test;
18
19import java.io.IOException;
20import java.nio.file.Paths;
21
22public class TestDeobfuscator {
23 private EnigmaProject openProject() throws IOException {
24 Enigma enigma = Enigma.create();
25 return enigma.openJar(Paths.get("build/test-obf/loneClass.jar"), ProgressListener.none());
26 }
27
28 @Test
29 public void loadJar()
30 throws Exception {
31 openProject();
32 }
33
34 @Test
35 public void decompileClass() throws Exception {
36 EnigmaProject project = openProject();
37 Decompiler decompiler = Decompilers.PROCYON.create(project.getClassCache(), new SourceSettings(false, false));
38
39 decompiler.getSource("a").asString();
40 }
41}
diff --git a/enigma/src/test/java/cuchaz/enigma/TestEntryFactory.java b/enigma/src/test/java/cuchaz/enigma/TestEntryFactory.java
new file mode 100644
index 0000000..9e1425a
--- /dev/null
+++ b/enigma/src/test/java/cuchaz/enigma/TestEntryFactory.java
@@ -0,0 +1,49 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11
12package cuchaz.enigma;
13
14import cuchaz.enigma.analysis.EntryReference;
15import cuchaz.enigma.translation.representation.*;
16import cuchaz.enigma.translation.representation.entry.ClassEntry;
17import cuchaz.enigma.translation.representation.entry.FieldEntry;
18import cuchaz.enigma.translation.representation.entry.MethodEntry;
19
20public class TestEntryFactory {
21
22 public static ClassEntry newClass(String name) {
23 return new ClassEntry(name);
24 }
25
26 public static FieldEntry newField(String className, String fieldName, String fieldType) {
27 return newField(newClass(className), fieldName, fieldType);
28 }
29
30 public static FieldEntry newField(ClassEntry classEntry, String fieldName, String fieldType) {
31 return new FieldEntry(classEntry, fieldName, new TypeDescriptor(fieldType));
32 }
33
34 public static MethodEntry newMethod(String className, String methodName, String methodSignature) {
35 return newMethod(newClass(className), methodName, methodSignature);
36 }
37
38 public static MethodEntry newMethod(ClassEntry classEntry, String methodName, String methodSignature) {
39 return new MethodEntry(classEntry, methodName, new MethodDescriptor(methodSignature));
40 }
41
42 public static EntryReference<FieldEntry, MethodEntry> newFieldReferenceByMethod(FieldEntry fieldEntry, String callerClassName, String callerName, String callerSignature) {
43 return new EntryReference<>(fieldEntry, "", newMethod(callerClassName, callerName, callerSignature));
44 }
45
46 public static EntryReference<MethodEntry, MethodEntry> newBehaviorReferenceByMethod(MethodEntry methodEntry, String callerClassName, String callerName, String callerSignature) {
47 return new EntryReference<>(methodEntry, "", newMethod(callerClassName, callerName, callerSignature));
48 }
49}
diff --git a/enigma/src/test/java/cuchaz/enigma/TestInnerClasses.java b/enigma/src/test/java/cuchaz/enigma/TestInnerClasses.java
new file mode 100644
index 0000000..85c72f8
--- /dev/null
+++ b/enigma/src/test/java/cuchaz/enigma/TestInnerClasses.java
@@ -0,0 +1,85 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11
12package cuchaz.enigma;
13
14import cuchaz.enigma.analysis.ClassCache;
15import cuchaz.enigma.analysis.index.JarIndex;
16import cuchaz.enigma.source.Decompiler;
17import cuchaz.enigma.source.Decompilers;
18import cuchaz.enigma.source.SourceSettings;
19import cuchaz.enigma.translation.representation.entry.ClassEntry;
20import org.junit.Test;
21
22import java.nio.file.Paths;
23
24import static cuchaz.enigma.TestEntryFactory.newClass;
25import static org.hamcrest.MatcherAssert.assertThat;
26import static org.hamcrest.Matchers.is;
27
28public class TestInnerClasses {
29
30 private static final ClassEntry SimpleOuter = newClass("d");
31 private static final ClassEntry SimpleInner = newClass("d$a");
32 private static final ClassEntry ConstructorArgsOuter = newClass("c");
33 private static final ClassEntry ConstructorArgsInner = newClass("c$a");
34 private static final ClassEntry ClassTreeRoot = newClass("f");
35 private static final ClassEntry ClassTreeLevel1 = newClass("f$a");
36 private static final ClassEntry ClassTreeLevel2 = newClass("f$a$a");
37 private static final ClassEntry ClassTreeLevel3 = newClass("f$a$a$a");
38 private final JarIndex index;
39 private final Decompiler decompiler;
40
41 public TestInnerClasses() throws Exception {
42 ClassCache classCache = ClassCache.of(Paths.get("build/test-obf/innerClasses.jar"));
43 index = classCache.index(ProgressListener.none());
44 decompiler = Decompilers.PROCYON.create(classCache, new SourceSettings(false, false));
45 }
46
47 @Test
48 public void simple() {
49 decompile(SimpleOuter);
50 }
51
52 @Test
53 public void constructorArgs() {
54 decompile(ConstructorArgsOuter);
55 }
56
57 @Test
58 public void classTree() {
59
60 // root level
61 assertThat(index.getEntryIndex().hasClass(ClassTreeRoot), is(true));
62
63 // level 1
64 ClassEntry fullClassEntry = new ClassEntry(ClassTreeRoot.getName()
65 + "$" + ClassTreeLevel1.getSimpleName());
66 assertThat(index.getEntryIndex().hasClass(fullClassEntry), is(true));
67
68 // level 2
69 fullClassEntry = new ClassEntry(ClassTreeRoot.getName()
70 + "$" + ClassTreeLevel1.getSimpleName()
71 + "$" + ClassTreeLevel2.getSimpleName());
72 assertThat(index.getEntryIndex().hasClass(fullClassEntry), is(true));
73
74 // level 3
75 fullClassEntry = new ClassEntry(ClassTreeRoot.getName()
76 + "$" + ClassTreeLevel1.getSimpleName()
77 + "$" + ClassTreeLevel2.getSimpleName()
78 + "$" + ClassTreeLevel3.getSimpleName());
79 assertThat(index.getEntryIndex().hasClass(fullClassEntry), is(true));
80 }
81
82 private void decompile(ClassEntry classEntry) {
83 decompiler.getSource(classEntry.getName());
84 }
85}
diff --git a/enigma/src/test/java/cuchaz/enigma/TestJarIndexConstructorReferences.java b/enigma/src/test/java/cuchaz/enigma/TestJarIndexConstructorReferences.java
new file mode 100644
index 0000000..48975c8
--- /dev/null
+++ b/enigma/src/test/java/cuchaz/enigma/TestJarIndexConstructorReferences.java
@@ -0,0 +1,124 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11
12package cuchaz.enigma;
13
14import cuchaz.enigma.analysis.ClassCache;
15import cuchaz.enigma.analysis.EntryReference;
16import cuchaz.enigma.analysis.index.JarIndex;
17import cuchaz.enigma.translation.representation.entry.ClassEntry;
18import cuchaz.enigma.translation.representation.entry.MethodDefEntry;
19import cuchaz.enigma.translation.representation.entry.MethodEntry;
20import org.junit.Test;
21
22import java.nio.file.Paths;
23import java.util.Collection;
24
25import static cuchaz.enigma.TestEntryFactory.*;
26import static org.hamcrest.MatcherAssert.assertThat;
27import static org.hamcrest.Matchers.*;
28
29public class TestJarIndexConstructorReferences {
30
31 private JarIndex index;
32
33 private ClassEntry baseClass = newClass("a");
34 private ClassEntry subClass = newClass("d");
35 private ClassEntry subsubClass = newClass("e");
36 private ClassEntry defaultClass = newClass("c");
37 private ClassEntry callerClass = newClass("b");
38
39 public TestJarIndexConstructorReferences() throws Exception {
40 ClassCache classCache = ClassCache.of(Paths.get("build/test-obf/constructors.jar"));
41 index = classCache.index(ProgressListener.none());
42 }
43
44 @Test
45 public void obfEntries() {
46 assertThat(index.getEntryIndex().getClasses(), containsInAnyOrder(newClass("cuchaz/enigma/inputs/Keep"), baseClass,
47 subClass, subsubClass, defaultClass, callerClass));
48 }
49
50 @Test
51 @SuppressWarnings("unchecked")
52 public void baseDefault() {
53 MethodEntry source = newMethod(baseClass, "<init>", "()V");
54 Collection<EntryReference<MethodEntry, MethodDefEntry>> references = index.getReferenceIndex().getReferencesToMethod(source);
55 assertThat(references, containsInAnyOrder(
56 newBehaviorReferenceByMethod(source, callerClass.getName(), "a", "()V"),
57 newBehaviorReferenceByMethod(source, subClass.getName(), "<init>", "()V"),
58 newBehaviorReferenceByMethod(source, subClass.getName(), "<init>", "(III)V")
59 ));
60 }
61
62 @Test
63 @SuppressWarnings("unchecked")
64 public void baseInt() {
65 MethodEntry source = newMethod(baseClass, "<init>", "(I)V");
66 assertThat(index.getReferenceIndex().getReferencesToMethod(source), containsInAnyOrder(
67 newBehaviorReferenceByMethod(source, callerClass.getName(), "b", "()V")
68 ));
69 }
70
71 @Test
72 @SuppressWarnings("unchecked")
73 public void subDefault() {
74 MethodEntry source = newMethod(subClass, "<init>", "()V");
75 assertThat(index.getReferenceIndex().getReferencesToMethod(source), containsInAnyOrder(
76 newBehaviorReferenceByMethod(source, callerClass.getName(), "c", "()V"),
77 newBehaviorReferenceByMethod(source, subClass.getName(), "<init>", "(I)V")
78 ));
79 }
80
81 @Test
82 @SuppressWarnings("unchecked")
83 public void subInt() {
84 MethodEntry source = newMethod(subClass, "<init>", "(I)V");
85 assertThat(index.getReferenceIndex().getReferencesToMethod(source), containsInAnyOrder(
86 newBehaviorReferenceByMethod(source, callerClass.getName(), "d", "()V"),
87 newBehaviorReferenceByMethod(source, subClass.getName(), "<init>", "(II)V"),
88 newBehaviorReferenceByMethod(source, subsubClass.getName(), "<init>", "(I)V")
89 ));
90 }
91
92 @Test
93 @SuppressWarnings("unchecked")
94 public void subIntInt() {
95 MethodEntry source = newMethod(subClass, "<init>", "(II)V");
96 assertThat(index.getReferenceIndex().getReferencesToMethod(source), containsInAnyOrder(
97 newBehaviorReferenceByMethod(source, callerClass.getName(), "e", "()V")
98 ));
99 }
100
101 @Test
102 public void subIntIntInt() {
103 MethodEntry source = newMethod(subClass, "<init>", "(III)V");
104 assertThat(index.getReferenceIndex().getReferencesToMethod(source), is(empty()));
105 }
106
107 @Test
108 @SuppressWarnings("unchecked")
109 public void subsubInt() {
110 MethodEntry source = newMethod(subsubClass, "<init>", "(I)V");
111 assertThat(index.getReferenceIndex().getReferencesToMethod(source), containsInAnyOrder(
112 newBehaviorReferenceByMethod(source, callerClass.getName(), "f", "()V")
113 ));
114 }
115
116 @Test
117 @SuppressWarnings("unchecked")
118 public void defaultConstructable() {
119 MethodEntry source = newMethod(defaultClass, "<init>", "()V");
120 assertThat(index.getReferenceIndex().getReferencesToMethod(source), containsInAnyOrder(
121 newBehaviorReferenceByMethod(source, callerClass.getName(), "g", "()V")
122 ));
123 }
124}
diff --git a/enigma/src/test/java/cuchaz/enigma/TestJarIndexInheritanceTree.java b/enigma/src/test/java/cuchaz/enigma/TestJarIndexInheritanceTree.java
new file mode 100644
index 0000000..76e379c
--- /dev/null
+++ b/enigma/src/test/java/cuchaz/enigma/TestJarIndexInheritanceTree.java
@@ -0,0 +1,227 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11
12package cuchaz.enigma;
13
14import cuchaz.enigma.analysis.ClassCache;
15import cuchaz.enigma.analysis.EntryReference;
16import cuchaz.enigma.analysis.index.EntryIndex;
17import cuchaz.enigma.analysis.index.InheritanceIndex;
18import cuchaz.enigma.analysis.index.JarIndex;
19import cuchaz.enigma.translation.mapping.EntryResolver;
20import cuchaz.enigma.translation.mapping.IndexEntryResolver;
21import cuchaz.enigma.translation.representation.AccessFlags;
22import cuchaz.enigma.translation.representation.entry.ClassEntry;
23import cuchaz.enigma.translation.representation.entry.FieldEntry;
24import cuchaz.enigma.translation.representation.entry.MethodDefEntry;
25import cuchaz.enigma.translation.representation.entry.MethodEntry;
26import org.junit.Test;
27import org.objectweb.asm.Opcodes;
28
29import java.nio.file.Paths;
30import java.util.Collection;
31
32import static cuchaz.enigma.TestEntryFactory.*;
33import static org.hamcrest.MatcherAssert.assertThat;
34import static org.hamcrest.Matchers.*;
35
36public class TestJarIndexInheritanceTree {
37
38 private JarIndex index;
39
40 private ClassEntry baseClass = newClass("a");
41 private ClassEntry subClassA = newClass("b");
42 private ClassEntry subClassAA = newClass("d");
43 private ClassEntry subClassB = newClass("c");
44 private FieldEntry nameField = newField(baseClass, "a", "Ljava/lang/String;");
45 private FieldEntry numThingsField = newField(subClassB, "a", "I");
46
47 public TestJarIndexInheritanceTree()
48 throws Exception {
49 ClassCache classCache = ClassCache.of(Paths.get("build/test-obf/inheritanceTree.jar"));
50 index = classCache.index(ProgressListener.none());
51 }
52
53 @Test
54 public void obfEntries() {
55 assertThat(index.getEntryIndex().getClasses(), containsInAnyOrder(
56 newClass("cuchaz/enigma/inputs/Keep"), baseClass, subClassA, subClassAA, subClassB
57 ));
58 }
59
60 @Test
61 public void translationIndex() {
62
63 InheritanceIndex index = this.index.getInheritanceIndex();
64
65 // base class
66 assertThat(index.getParents(baseClass), is(empty()));
67 assertThat(index.getAncestors(baseClass), is(empty()));
68 assertThat(index.getChildren(baseClass), containsInAnyOrder(subClassA, subClassB
69 ));
70
71 // subclass a
72 assertThat(index.getParents(subClassA), contains(baseClass));
73 assertThat(index.getAncestors(subClassA), containsInAnyOrder(baseClass));
74 assertThat(index.getChildren(subClassA), contains(subClassAA));
75
76 // subclass aa
77 assertThat(index.getParents(subClassAA), contains(subClassA));
78 assertThat(index.getAncestors(subClassAA), containsInAnyOrder(subClassA, baseClass));
79 assertThat(index.getChildren(subClassAA), is(empty()));
80
81 // subclass b
82 assertThat(index.getParents(subClassB), contains(baseClass));
83 assertThat(index.getAncestors(subClassB), containsInAnyOrder(baseClass));
84 assertThat(index.getChildren(subClassB), is(empty()));
85 }
86
87 @Test
88 public void access() {
89 assertThat(index.getEntryIndex().getFieldAccess(nameField), is(new AccessFlags(Opcodes.ACC_PRIVATE)));
90 assertThat(index.getEntryIndex().getFieldAccess(numThingsField), is(new AccessFlags(Opcodes.ACC_PRIVATE)));
91 }
92
93 @Test
94 public void relatedMethodImplementations() {
95
96 Collection<MethodEntry> entries;
97
98 EntryResolver resolver = new IndexEntryResolver(index);
99 // getName()
100 entries = resolver.resolveEquivalentMethods(newMethod(baseClass, "a", "()Ljava/lang/String;"));
101 assertThat(entries, containsInAnyOrder(
102 newMethod(baseClass, "a", "()Ljava/lang/String;"),
103 newMethod(subClassAA, "a", "()Ljava/lang/String;")
104 ));
105 entries = resolver.resolveEquivalentMethods(newMethod(subClassAA, "a", "()Ljava/lang/String;"));
106 assertThat(entries, containsInAnyOrder(
107 newMethod(baseClass, "a", "()Ljava/lang/String;"),
108 newMethod(subClassAA, "a", "()Ljava/lang/String;")
109 ));
110
111 // doBaseThings()
112 entries = resolver.resolveEquivalentMethods(newMethod(baseClass, "a", "()V"));
113 assertThat(entries, containsInAnyOrder(
114 newMethod(baseClass, "a", "()V"),
115 newMethod(subClassAA, "a", "()V"),
116 newMethod(subClassB, "a", "()V")
117 ));
118 entries = resolver.resolveEquivalentMethods(newMethod(subClassAA, "a", "()V"));
119 assertThat(entries, containsInAnyOrder(
120 newMethod(baseClass, "a", "()V"),
121 newMethod(subClassAA, "a", "()V"),
122 newMethod(subClassB, "a", "()V")
123 ));
124 entries = resolver.resolveEquivalentMethods(newMethod(subClassB, "a", "()V"));
125 assertThat(entries, containsInAnyOrder(
126 newMethod(baseClass, "a", "()V"),
127 newMethod(subClassAA, "a", "()V"),
128 newMethod(subClassB, "a", "()V")
129 ));
130
131 // doBThings
132 entries = resolver.resolveEquivalentMethods(newMethod(subClassB, "b", "()V"));
133 assertThat(entries, containsInAnyOrder(newMethod(subClassB, "b", "()V")));
134 }
135
136 @Test
137 @SuppressWarnings("unchecked")
138 public void fieldReferences() {
139 Collection<EntryReference<FieldEntry, MethodDefEntry>> references;
140
141 // name
142 references = index.getReferenceIndex().getReferencesToField(nameField);
143 assertThat(references, containsInAnyOrder(
144 newFieldReferenceByMethod(nameField, baseClass.getName(), "<init>", "(Ljava/lang/String;)V"),
145 newFieldReferenceByMethod(nameField, baseClass.getName(), "a", "()Ljava/lang/String;")
146 ));
147
148 // numThings
149 references = index.getReferenceIndex().getReferencesToField(numThingsField);
150 assertThat(references, containsInAnyOrder(
151 newFieldReferenceByMethod(numThingsField, subClassB.getName(), "<init>", "()V"),
152 newFieldReferenceByMethod(numThingsField, subClassB.getName(), "b", "()V")
153 ));
154 }
155
156 @Test
157 @SuppressWarnings("unchecked")
158 public void behaviorReferences() {
159
160 MethodEntry source;
161 Collection<EntryReference<MethodEntry, MethodDefEntry>> references;
162
163 // baseClass constructor
164 source = newMethod(baseClass, "<init>", "(Ljava/lang/String;)V");
165 references = index.getReferenceIndex().getReferencesToMethod(source);
166 assertThat(references, containsInAnyOrder(
167 newBehaviorReferenceByMethod(source, subClassA.getName(), "<init>", "(Ljava/lang/String;)V"),
168 newBehaviorReferenceByMethod(source, subClassB.getName(), "<init>", "()V")
169 ));
170
171 // subClassA constructor
172 source = newMethod(subClassA, "<init>", "(Ljava/lang/String;)V");
173 references = index.getReferenceIndex().getReferencesToMethod(source);
174 assertThat(references, containsInAnyOrder(
175 newBehaviorReferenceByMethod(source, subClassAA.getName(), "<init>", "()V")
176 ));
177
178 // baseClass.getName()
179 source = newMethod(baseClass, "a", "()Ljava/lang/String;");
180 references = index.getReferenceIndex().getReferencesToMethod(source);
181 assertThat(references, containsInAnyOrder(
182 newBehaviorReferenceByMethod(source, subClassAA.getName(), "a", "()Ljava/lang/String;"),
183 newBehaviorReferenceByMethod(source, subClassB.getName(), "a", "()V")
184 ));
185
186 // subclassAA.getName()
187 source = newMethod(subClassAA, "a", "()Ljava/lang/String;");
188 references = index.getReferenceIndex().getReferencesToMethod(source);
189 assertThat(references, containsInAnyOrder(
190 newBehaviorReferenceByMethod(source, subClassAA.getName(), "a", "()V")
191 ));
192 }
193
194 @Test
195 public void containsEntries() {
196 EntryIndex entryIndex = index.getEntryIndex();
197 // classes
198 assertThat(entryIndex.hasClass(baseClass), is(true));
199 assertThat(entryIndex.hasClass(subClassA), is(true));
200 assertThat(entryIndex.hasClass(subClassAA), is(true));
201 assertThat(entryIndex.hasClass(subClassB), is(true));
202
203 // fields
204 assertThat(entryIndex.hasField(nameField), is(true));
205 assertThat(entryIndex.hasField(numThingsField), is(true));
206
207 // methods
208 // getName()
209 assertThat(entryIndex.hasMethod(newMethod(baseClass, "a", "()Ljava/lang/String;")), is(true));
210 assertThat(entryIndex.hasMethod(newMethod(subClassA, "a", "()Ljava/lang/String;")), is(false));
211 assertThat(entryIndex.hasMethod(newMethod(subClassAA, "a", "()Ljava/lang/String;")), is(true));
212 assertThat(entryIndex.hasMethod(newMethod(subClassB, "a", "()Ljava/lang/String;")), is(false));
213
214 // doBaseThings()
215 assertThat(entryIndex.hasMethod(newMethod(baseClass, "a", "()V")), is(true));
216 assertThat(entryIndex.hasMethod(newMethod(subClassA, "a", "()V")), is(false));
217 assertThat(entryIndex.hasMethod(newMethod(subClassAA, "a", "()V")), is(true));
218 assertThat(entryIndex.hasMethod(newMethod(subClassB, "a", "()V")), is(true));
219
220 // doBThings()
221 assertThat(entryIndex.hasMethod(newMethod(baseClass, "b", "()V")), is(false));
222 assertThat(entryIndex.hasMethod(newMethod(subClassA, "b", "()V")), is(false));
223 assertThat(entryIndex.hasMethod(newMethod(subClassAA, "b", "()V")), is(false));
224 assertThat(entryIndex.hasMethod(newMethod(subClassB, "b", "()V")), is(true));
225
226 }
227}
diff --git a/enigma/src/test/java/cuchaz/enigma/TestJarIndexLoneClass.java b/enigma/src/test/java/cuchaz/enigma/TestJarIndexLoneClass.java
new file mode 100644
index 0000000..103c366
--- /dev/null
+++ b/enigma/src/test/java/cuchaz/enigma/TestJarIndexLoneClass.java
@@ -0,0 +1,157 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11
12package cuchaz.enigma;
13
14import cuchaz.enigma.analysis.*;
15import cuchaz.enigma.analysis.index.EntryIndex;
16import cuchaz.enigma.analysis.index.InheritanceIndex;
17import cuchaz.enigma.analysis.index.JarIndex;
18import cuchaz.enigma.translation.VoidTranslator;
19import cuchaz.enigma.translation.representation.AccessFlags;
20import cuchaz.enigma.translation.representation.entry.ClassEntry;
21import cuchaz.enigma.translation.representation.entry.FieldEntry;
22import cuchaz.enigma.translation.representation.entry.MethodDefEntry;
23import cuchaz.enigma.translation.representation.entry.MethodEntry;
24import org.junit.Test;
25
26import java.nio.file.Paths;
27import java.util.Collection;
28import java.util.List;
29
30import static cuchaz.enigma.TestEntryFactory.*;
31import static org.hamcrest.MatcherAssert.assertThat;
32import static org.hamcrest.Matchers.*;
33
34public class TestJarIndexLoneClass {
35
36 private JarIndex index;
37
38 public TestJarIndexLoneClass() throws Exception {
39 ClassCache classCache = ClassCache.of(Paths.get("build/test-obf/loneClass.jar"));
40 index = classCache.index(ProgressListener.none());
41 }
42
43 @Test
44 public void obfEntries() {
45 assertThat(index.getEntryIndex().getClasses(), containsInAnyOrder(
46 newClass("cuchaz/enigma/inputs/Keep"),
47 newClass("a")
48 ));
49 }
50
51 @Test
52 public void translationIndex() {
53 InheritanceIndex inheritanceIndex = index.getInheritanceIndex();
54 assertThat(inheritanceIndex.getParents(new ClassEntry("a")), is(empty()));
55 assertThat(inheritanceIndex.getParents(new ClassEntry("cuchaz/enigma/inputs/Keep")), is(empty()));
56 assertThat(inheritanceIndex.getAncestors(new ClassEntry("a")), is(empty()));
57 assertThat(inheritanceIndex.getAncestors(new ClassEntry("cuchaz/enigma/inputs/Keep")), is(empty()));
58 assertThat(inheritanceIndex.getChildren(new ClassEntry("a")), is(empty()));
59 assertThat(inheritanceIndex.getChildren(new ClassEntry("cuchaz/enigma/inputs/Keep")), is(empty()));
60 }
61
62 @Test
63 public void access() {
64 EntryIndex entryIndex = index.getEntryIndex();
65 assertThat(entryIndex.getFieldAccess(newField("a", "a", "Ljava/lang/String;")), is(AccessFlags.PRIVATE));
66 assertThat(entryIndex.getMethodAccess(newMethod("a", "a", "()Ljava/lang/String;")), is(AccessFlags.PUBLIC));
67 assertThat(entryIndex.getFieldAccess(newField("a", "b", "Ljava/lang/String;")), is(nullValue()));
68 assertThat(entryIndex.getFieldAccess(newField("a", "a", "LFoo;")), is(nullValue()));
69 }
70
71 @Test
72 public void classInheritance() {
73 IndexTreeBuilder treeBuilder = new IndexTreeBuilder(index);
74 ClassInheritanceTreeNode node = treeBuilder.buildClassInheritance(VoidTranslator.INSTANCE, newClass("a"));
75 assertThat(node, is(not(nullValue())));
76 assertThat(node.getObfClassName(), is("a"));
77 assertThat(node.getChildCount(), is(0));
78 }
79
80 @Test
81 public void methodInheritance() {
82 IndexTreeBuilder treeBuilder = new IndexTreeBuilder(index);
83 MethodEntry source = newMethod("a", "a", "()Ljava/lang/String;");
84 MethodInheritanceTreeNode node = treeBuilder.buildMethodInheritance(VoidTranslator.INSTANCE, source);
85 assertThat(node, is(not(nullValue())));
86 assertThat(node.getMethodEntry(), is(source));
87 assertThat(node.getChildCount(), is(0));
88 }
89
90 @Test
91 public void classImplementations() {
92 IndexTreeBuilder treeBuilder = new IndexTreeBuilder(index);
93 ClassImplementationsTreeNode node = treeBuilder.buildClassImplementations(VoidTranslator.INSTANCE, newClass("a"));
94 assertThat(node, is(nullValue()));
95 }
96
97 @Test
98 public void methodImplementations() {
99 IndexTreeBuilder treeBuilder = new IndexTreeBuilder(index);
100 MethodEntry source = newMethod("a", "a", "()Ljava/lang/String;");
101
102 List<MethodImplementationsTreeNode> nodes = treeBuilder.buildMethodImplementations(VoidTranslator.INSTANCE, source);
103 assertThat(nodes, hasSize(1));
104 assertThat(nodes.get(0).getMethodEntry(), is(source));
105 }
106
107 @Test
108 public void relatedMethodImplementations() {
109 Collection<MethodEntry> entries = index.getEntryResolver().resolveEquivalentMethods(newMethod("a", "a", "()Ljava/lang/String;"));
110 assertThat(entries, containsInAnyOrder(
111 newMethod("a", "a", "()Ljava/lang/String;")
112 ));
113 }
114
115 @Test
116 @SuppressWarnings("unchecked")
117 public void fieldReferences() {
118 FieldEntry source = newField("a", "a", "Ljava/lang/String;");
119 Collection<EntryReference<FieldEntry, MethodDefEntry>> references = index.getReferenceIndex().getReferencesToField(source);
120 assertThat(references, containsInAnyOrder(
121 newFieldReferenceByMethod(source, "a", "<init>", "(Ljava/lang/String;)V"),
122 newFieldReferenceByMethod(source, "a", "a", "()Ljava/lang/String;")
123 ));
124 }
125
126 @Test
127 public void behaviorReferences() {
128 assertThat(index.getReferenceIndex().getReferencesToMethod(newMethod("a", "a", "()Ljava/lang/String;")), is(empty()));
129 }
130
131 @Test
132 public void interfaces() {
133 assertThat(index.getInheritanceIndex().getParents(new ClassEntry("a")), is(empty()));
134 }
135
136 @Test
137 public void implementingClasses() {
138 assertThat(index.getInheritanceIndex().getChildren(new ClassEntry("a")), is(empty()));
139 }
140
141 @Test
142 public void isInterface() {
143 assertThat(index.getInheritanceIndex().isParent(new ClassEntry("a")), is(false));
144 }
145
146 @Test
147 public void testContains() {
148 EntryIndex entryIndex = index.getEntryIndex();
149 assertThat(entryIndex.hasClass(newClass("a")), is(true));
150 assertThat(entryIndex.hasClass(newClass("b")), is(false));
151 assertThat(entryIndex.hasField(newField("a", "a", "Ljava/lang/String;")), is(true));
152 assertThat(entryIndex.hasField(newField("a", "b", "Ljava/lang/String;")), is(false));
153 assertThat(entryIndex.hasField(newField("a", "a", "LFoo;")), is(false));
154 assertThat(entryIndex.hasMethod(newMethod("a", "a", "()Ljava/lang/String;")), is(true));
155 assertThat(entryIndex.hasMethod(newMethod("a", "b", "()Ljava/lang/String;")), is(false));
156 }
157}
diff --git a/enigma/src/test/java/cuchaz/enigma/TestMethodDescriptor.java b/enigma/src/test/java/cuchaz/enigma/TestMethodDescriptor.java
new file mode 100644
index 0000000..a73880d
--- /dev/null
+++ b/enigma/src/test/java/cuchaz/enigma/TestMethodDescriptor.java
@@ -0,0 +1,247 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11
12package cuchaz.enigma;
13
14import cuchaz.enigma.translation.representation.MethodDescriptor;
15import cuchaz.enigma.translation.representation.TypeDescriptor;
16import org.junit.Test;
17
18import static org.hamcrest.MatcherAssert.assertThat;
19import static org.hamcrest.Matchers.*;
20
21public class TestMethodDescriptor {
22
23 @Test
24 public void easiest() {
25 final MethodDescriptor sig = new MethodDescriptor("()V");
26 assertThat(sig.getArgumentDescs(), is(empty()));
27 assertThat(sig.getReturnDesc(), is(new TypeDescriptor("V")));
28 }
29
30 @Test
31 public void primitives() {
32 {
33 final MethodDescriptor sig = new MethodDescriptor("(I)V");
34 assertThat(sig.getArgumentDescs(), contains(
35 new TypeDescriptor("I")
36 ));
37 assertThat(sig.getReturnDesc(), is(new TypeDescriptor("V")));
38 }
39 {
40 final MethodDescriptor sig = new MethodDescriptor("(I)I");
41 assertThat(sig.getArgumentDescs(), contains(
42 new TypeDescriptor("I")
43 ));
44 assertThat(sig.getReturnDesc(), is(new TypeDescriptor("I")));
45 }
46 {
47 final MethodDescriptor sig = new MethodDescriptor("(IBCJ)Z");
48 assertThat(sig.getArgumentDescs(), contains(
49 new TypeDescriptor("I"),
50 new TypeDescriptor("B"),
51 new TypeDescriptor("C"),
52 new TypeDescriptor("J")
53 ));
54 assertThat(sig.getReturnDesc(), is(new TypeDescriptor("Z")));
55 }
56 }
57
58 @Test
59 public void classes() {
60 {
61 final MethodDescriptor sig = new MethodDescriptor("([LFoo;)V");
62 assertThat(sig.getArgumentDescs().size(), is(1));
63 assertThat(sig.getArgumentDescs().get(0), is(new TypeDescriptor("[LFoo;")));
64 assertThat(sig.getReturnDesc(), is(new TypeDescriptor("V")));
65 }
66 {
67 final MethodDescriptor sig = new MethodDescriptor("(LFoo;)LBar;");
68 assertThat(sig.getArgumentDescs(), contains(
69 new TypeDescriptor("LFoo;")
70 ));
71 assertThat(sig.getReturnDesc(), is(new TypeDescriptor("LBar;")));
72 }
73 {
74 final MethodDescriptor sig = new MethodDescriptor("(LFoo;LMoo;LZoo;)LBar;");
75 assertThat(sig.getArgumentDescs(), contains(
76 new TypeDescriptor("LFoo;"),
77 new TypeDescriptor("LMoo;"),
78 new TypeDescriptor("LZoo;")
79 ));
80 assertThat(sig.getReturnDesc(), is(new TypeDescriptor("LBar;")));
81 }
82 }
83
84 @Test
85 public void arrays() {
86 {
87 final MethodDescriptor sig = new MethodDescriptor("([I)V");
88 assertThat(sig.getArgumentDescs(), contains(
89 new TypeDescriptor("[I")
90 ));
91 assertThat(sig.getReturnDesc(), is(new TypeDescriptor("V")));
92 }
93 {
94 final MethodDescriptor sig = new MethodDescriptor("([I)[J");
95 assertThat(sig.getArgumentDescs(), contains(
96 new TypeDescriptor("[I")
97 ));
98 assertThat(sig.getReturnDesc(), is(new TypeDescriptor("[J")));
99 }
100 {
101 final MethodDescriptor sig = new MethodDescriptor("([I[Z[F)[D");
102 assertThat(sig.getArgumentDescs(), contains(
103 new TypeDescriptor("[I"),
104 new TypeDescriptor("[Z"),
105 new TypeDescriptor("[F")
106 ));
107 assertThat(sig.getReturnDesc(), is(new TypeDescriptor("[D")));
108 }
109 }
110
111 @Test
112 public void mixed() {
113 {
114 final MethodDescriptor sig = new MethodDescriptor("(I[JLFoo;)Z");
115 assertThat(sig.getArgumentDescs(), contains(
116 new TypeDescriptor("I"),
117 new TypeDescriptor("[J"),
118 new TypeDescriptor("LFoo;")
119 ));
120 assertThat(sig.getReturnDesc(), is(new TypeDescriptor("Z")));
121 }
122 {
123 final MethodDescriptor sig = new MethodDescriptor("(III)[LFoo;");
124 assertThat(sig.getArgumentDescs(), contains(
125 new TypeDescriptor("I"),
126 new TypeDescriptor("I"),
127 new TypeDescriptor("I")
128 ));
129 assertThat(sig.getReturnDesc(), is(new TypeDescriptor("[LFoo;")));
130 }
131 }
132
133 @Test
134 public void replaceClasses() {
135 {
136 final MethodDescriptor oldSig = new MethodDescriptor("()V");
137 final MethodDescriptor sig = oldSig.remap(s -> null);
138 assertThat(sig.getArgumentDescs(), is(empty()));
139 assertThat(sig.getReturnDesc(), is(new TypeDescriptor("V")));
140 }
141 {
142 final MethodDescriptor oldSig = new MethodDescriptor("(IJLFoo;)V");
143 final MethodDescriptor sig = oldSig.remap(s -> null);
144 assertThat(sig.getArgumentDescs(), contains(
145 new TypeDescriptor("I"),
146 new TypeDescriptor("J"),
147 new TypeDescriptor("LFoo;")
148 ));
149 assertThat(sig.getReturnDesc(), is(new TypeDescriptor("V")));
150 }
151 {
152 final MethodDescriptor oldSig = new MethodDescriptor("(LFoo;LBar;)LMoo;");
153 final MethodDescriptor sig = oldSig.remap(s -> {
154 if (s.equals("Foo")) {
155 return "Bar";
156 }
157 return null;
158 });
159 assertThat(sig.getArgumentDescs(), contains(
160 new TypeDescriptor("LBar;"),
161 new TypeDescriptor("LBar;")
162 ));
163 assertThat(sig.getReturnDesc(), is(new TypeDescriptor("LMoo;")));
164 }
165 {
166 final MethodDescriptor oldSig = new MethodDescriptor("(LFoo;LBar;)LMoo;");
167 final MethodDescriptor sig = oldSig.remap(s -> {
168 if (s.equals("Moo")) {
169 return "Cow";
170 }
171 return null;
172 });
173 assertThat(sig.getArgumentDescs(), contains(
174 new TypeDescriptor("LFoo;"),
175 new TypeDescriptor("LBar;")
176 ));
177 assertThat(sig.getReturnDesc(), is(new TypeDescriptor("LCow;")));
178 }
179 }
180
181 @Test
182 public void replaceArrayClasses() {
183 {
184 final MethodDescriptor oldSig = new MethodDescriptor("([LFoo;)[[[LBar;");
185 final MethodDescriptor sig = oldSig.remap(s -> {
186 if (s.equals("Foo")) {
187 return "Food";
188 } else if (s.equals("Bar")) {
189 return "Beer";
190 }
191 return null;
192 });
193 assertThat(sig.getArgumentDescs(), contains(
194 new TypeDescriptor("[LFood;")
195 ));
196 assertThat(sig.getReturnDesc(), is(new TypeDescriptor("[[[LBeer;")));
197 }
198 }
199
200 @Test
201 public void equals() {
202
203 // base
204 assertThat(new MethodDescriptor("()V"), is(new MethodDescriptor("()V")));
205
206 // arguments
207 assertThat(new MethodDescriptor("(I)V"), is(new MethodDescriptor("(I)V")));
208 assertThat(new MethodDescriptor("(ZIZ)V"), is(new MethodDescriptor("(ZIZ)V")));
209 assertThat(new MethodDescriptor("(LFoo;)V"), is(new MethodDescriptor("(LFoo;)V")));
210 assertThat(new MethodDescriptor("(LFoo;LBar;)V"), is(new MethodDescriptor("(LFoo;LBar;)V")));
211 assertThat(new MethodDescriptor("([I)V"), is(new MethodDescriptor("([I)V")));
212 assertThat(new MethodDescriptor("([[D[[[J)V"), is(new MethodDescriptor("([[D[[[J)V")));
213
214 assertThat(new MethodDescriptor("()V"), is(not(new MethodDescriptor("(I)V"))));
215 assertThat(new MethodDescriptor("(I)V"), is(not(new MethodDescriptor("()V"))));
216 assertThat(new MethodDescriptor("(IJ)V"), is(not(new MethodDescriptor("(JI)V"))));
217 assertThat(new MethodDescriptor("([[Z)V"), is(not(new MethodDescriptor("([[LFoo;)V"))));
218 assertThat(new MethodDescriptor("(LFoo;LBar;)V"), is(not(new MethodDescriptor("(LFoo;LCow;)V"))));
219 assertThat(new MethodDescriptor("([LFoo;LBar;)V"), is(not(new MethodDescriptor("(LFoo;LCow;)V"))));
220
221 // return desc
222 assertThat(new MethodDescriptor("()I"), is(new MethodDescriptor("()I")));
223 assertThat(new MethodDescriptor("()Z"), is(new MethodDescriptor("()Z")));
224 assertThat(new MethodDescriptor("()[D"), is(new MethodDescriptor("()[D")));
225 assertThat(new MethodDescriptor("()[[[Z"), is(new MethodDescriptor("()[[[Z")));
226 assertThat(new MethodDescriptor("()LFoo;"), is(new MethodDescriptor("()LFoo;")));
227 assertThat(new MethodDescriptor("()[LFoo;"), is(new MethodDescriptor("()[LFoo;")));
228
229 assertThat(new MethodDescriptor("()I"), is(not(new MethodDescriptor("()Z"))));
230 assertThat(new MethodDescriptor("()Z"), is(not(new MethodDescriptor("()I"))));
231 assertThat(new MethodDescriptor("()[D"), is(not(new MethodDescriptor("()[J"))));
232 assertThat(new MethodDescriptor("()[[[Z"), is(not(new MethodDescriptor("()[[Z"))));
233 assertThat(new MethodDescriptor("()LFoo;"), is(not(new MethodDescriptor("()LBar;"))));
234 assertThat(new MethodDescriptor("()[LFoo;"), is(not(new MethodDescriptor("()[LBar;"))));
235 }
236
237 @Test
238 public void testToString() {
239 assertThat(new MethodDescriptor("()V").toString(), is("()V"));
240 assertThat(new MethodDescriptor("(I)V").toString(), is("(I)V"));
241 assertThat(new MethodDescriptor("(ZIZ)V").toString(), is("(ZIZ)V"));
242 assertThat(new MethodDescriptor("(LFoo;)V").toString(), is("(LFoo;)V"));
243 assertThat(new MethodDescriptor("(LFoo;LBar;)V").toString(), is("(LFoo;LBar;)V"));
244 assertThat(new MethodDescriptor("([I)V").toString(), is("([I)V"));
245 assertThat(new MethodDescriptor("([[D[[[J)V").toString(), is("([[D[[[J)V"));
246 }
247}
diff --git a/enigma/src/test/java/cuchaz/enigma/TestTokensConstructors.java b/enigma/src/test/java/cuchaz/enigma/TestTokensConstructors.java
new file mode 100644
index 0000000..0398de4
--- /dev/null
+++ b/enigma/src/test/java/cuchaz/enigma/TestTokensConstructors.java
@@ -0,0 +1,137 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11
12package cuchaz.enigma;
13
14import cuchaz.enigma.translation.representation.entry.MethodEntry;
15import org.junit.Test;
16
17import java.nio.file.Paths;
18
19import static cuchaz.enigma.TestEntryFactory.newBehaviorReferenceByMethod;
20import static cuchaz.enigma.TestEntryFactory.newMethod;
21import static org.hamcrest.MatcherAssert.assertThat;
22import static org.hamcrest.Matchers.*;
23
24public class TestTokensConstructors extends TokenChecker {
25
26 public TestTokensConstructors()
27 throws Exception {
28 super(Paths.get("build/test-obf/constructors.jar"));
29 }
30
31 @Test
32 public void baseDeclarations() {
33 assertThat(getDeclarationToken(newMethod("a", "<init>", "()V")), is("a"));
34 assertThat(getDeclarationToken(newMethod("a", "<init>", "(I)V")), is("a"));
35 }
36
37 @Test
38 public void subDeclarations() {
39 assertThat(getDeclarationToken(newMethod("d", "<init>", "()V")), is("d"));
40 assertThat(getDeclarationToken(newMethod("d", "<init>", "(I)V")), is("d"));
41 assertThat(getDeclarationToken(newMethod("d", "<init>", "(II)V")), is("d"));
42 assertThat(getDeclarationToken(newMethod("d", "<init>", "(III)V")), is("d"));
43 }
44
45 @Test
46 public void subsubDeclarations() {
47 assertThat(getDeclarationToken(newMethod("e", "<init>", "(I)V")), is("e"));
48 }
49
50 @Test
51 public void defaultDeclarations() {
52 assertThat(getDeclarationToken(newMethod("c", "<init>", "()V")), nullValue());
53 }
54
55 @Test
56 public void baseDefaultReferences() {
57 MethodEntry source = newMethod("a", "<init>", "()V");
58 assertThat(
59 getReferenceTokens(newBehaviorReferenceByMethod(source, "b", "a", "()V")),
60 containsInAnyOrder("a")
61 );
62 assertThat(
63 getReferenceTokens(newBehaviorReferenceByMethod(source, "d", "<init>", "()V")),
64 is(empty()) // implicit call, not decompiled to token
65 );
66 assertThat(
67 getReferenceTokens(newBehaviorReferenceByMethod(source, "d", "<init>", "(III)V")),
68 is(empty()) // implicit call, not decompiled to token
69 );
70 }
71
72 @Test
73 public void baseIntReferences() {
74 MethodEntry source = newMethod("a", "<init>", "(I)V");
75 assertThat(
76 getReferenceTokens(newBehaviorReferenceByMethod(source, "b", "b", "()V")),
77 containsInAnyOrder("a")
78 );
79 }
80
81 @Test
82 public void subDefaultReferences() {
83 MethodEntry source = newMethod("d", "<init>", "()V");
84 assertThat(
85 getReferenceTokens(newBehaviorReferenceByMethod(source, "b", "c", "()V")),
86 containsInAnyOrder("d")
87 );
88 assertThat(
89 getReferenceTokens(newBehaviorReferenceByMethod(source, "d", "<init>", "(I)V")),
90 containsInAnyOrder("this")
91 );
92 }
93
94 @Test
95 public void subIntReferences() {
96 MethodEntry source = newMethod("d", "<init>", "(I)V");
97 assertThat(getReferenceTokens(
98 newBehaviorReferenceByMethod(source, "b", "d", "()V")),
99 containsInAnyOrder("d")
100 );
101 assertThat(getReferenceTokens(
102 newBehaviorReferenceByMethod(source, "d", "<init>", "(II)V")),
103 containsInAnyOrder("this")
104 );
105 assertThat(getReferenceTokens(
106 newBehaviorReferenceByMethod(source, "e", "<init>", "(I)V")),
107 containsInAnyOrder("super")
108 );
109 }
110
111 @Test
112 public void subIntIntReferences() {
113 MethodEntry source = newMethod("d", "<init>", "(II)V");
114 assertThat(
115 getReferenceTokens(newBehaviorReferenceByMethod(source, "b", "e", "()V")),
116 containsInAnyOrder("d")
117 );
118 }
119
120 @Test
121 public void subsubIntReferences() {
122 MethodEntry source = newMethod("e", "<init>", "(I)V");
123 assertThat(
124 getReferenceTokens(newBehaviorReferenceByMethod(source, "b", "f", "()V")),
125 containsInAnyOrder("e")
126 );
127 }
128
129 @Test
130 public void defaultConstructableReferences() {
131 MethodEntry source = newMethod("c", "<init>", "()V");
132 assertThat(
133 getReferenceTokens(newBehaviorReferenceByMethod(source, "b", "g", "()V")),
134 containsInAnyOrder("c")
135 );
136 }
137}
diff --git a/enigma/src/test/java/cuchaz/enigma/TestTranslator.java b/enigma/src/test/java/cuchaz/enigma/TestTranslator.java
new file mode 100644
index 0000000..a420afe
--- /dev/null
+++ b/enigma/src/test/java/cuchaz/enigma/TestTranslator.java
@@ -0,0 +1,155 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11
12package cuchaz.enigma;
13
14import cuchaz.enigma.translation.representation.entry.Entry;
15import org.junit.BeforeClass;
16import org.junit.Test;
17
18import static cuchaz.enigma.TestEntryFactory.*;
19
20public class TestTranslator {
21
22 @BeforeClass
23 public static void beforeClass()
24 throws Exception {
25 //TODO FIx
26 //deobfuscator = new Enigma(new JarFile("build/test-obf/translation.jar"));
27 //try (InputStream in = TestTranslator.class.getResourceAsStream("/cuchaz/enigma/resources/translation.mappings")) {
28 // mappings = new MappingsJsonReader().read(new InputStreamReader(in));
29 // deobfuscator.setMappings(mappings);
30 // deobfTranslator = deobfuscator.getTranslator(TranslationDirection.Deobfuscating);
31 // obfTranslator = deobfuscator.getTranslator(TranslationDirection.Obfuscating);
32 //}
33 }
34
35 @Test
36 public void basicClasses() {
37 assertMapping(newClass("a"), newClass("deobf/A_Basic"));
38 assertMapping(newClass("b"), newClass("deobf/B_BaseClass"));
39 assertMapping(newClass("c"), newClass("deobf/C_SubClass"));
40 }
41
42 @Test
43 public void basicFields() {
44 assertMapping(newField("a", "a", "I"), newField("deobf/A_Basic", "f1", "I"));
45 assertMapping(newField("a", "a", "F"), newField("deobf/A_Basic", "f2", "F"));
46 assertMapping(newField("a", "a", "Ljava/lang/String;"), newField("deobf/A_Basic", "f3", "Ljava/lang/String;"));
47 }
48
49 @Test
50 public void basicMethods() {
51 assertMapping(newMethod("a", "a", "()V"), newMethod("deobf/A_Basic", "m1", "()V"));
52 assertMapping(newMethod("a", "a", "()I"), newMethod("deobf/A_Basic", "m2", "()I"));
53 assertMapping(newMethod("a", "a", "(I)V"), newMethod("deobf/A_Basic", "m3", "(I)V"));
54 assertMapping(newMethod("a", "a", "(I)I"), newMethod("deobf/A_Basic", "m4", "(I)I"));
55 }
56
57 // TODO: basic constructors
58
59 @Test
60 public void inheritanceFields() {
61 assertMapping(newField("b", "a", "I"), newField("deobf/B_BaseClass", "f1", "I"));
62 assertMapping(newField("b", "a", "C"), newField("deobf/B_BaseClass", "f2", "C"));
63 assertMapping(newField("c", "b", "I"), newField("deobf/C_SubClass", "f3", "I"));
64 assertMapping(newField("c", "c", "I"), newField("deobf/C_SubClass", "f4", "I"));
65 }
66
67 @Test
68 public void inheritanceFieldsShadowing() {
69 assertMapping(newField("c", "b", "C"), newField("deobf/C_SubClass", "f2", "C"));
70 }
71
72 @Test
73 public void inheritanceFieldsBySubClass() {
74 assertMapping(newField("c", "a", "I"), newField("deobf/C_SubClass", "f1", "I"));
75 // NOTE: can't reference b.C by subclass since it's shadowed
76 }
77
78 @Test
79 public void inheritanceMethods() {
80 assertMapping(newMethod("b", "a", "()I"), newMethod("deobf/B_BaseClass", "m1", "()I"));
81 assertMapping(newMethod("b", "b", "()I"), newMethod("deobf/B_BaseClass", "m2", "()I"));
82 assertMapping(newMethod("c", "c", "()I"), newMethod("deobf/C_SubClass", "m3", "()I"));
83 }
84
85 @Test
86 public void inheritanceMethodsOverrides() {
87 assertMapping(newMethod("c", "a", "()I"), newMethod("deobf/C_SubClass", "m1", "()I"));
88 }
89
90 @Test
91 public void inheritanceMethodsBySubClass() {
92 assertMapping(newMethod("c", "b", "()I"), newMethod("deobf/C_SubClass", "m2", "()I"));
93 }
94
95 @Test
96 public void innerClasses() {
97
98 // classes
99 assertMapping(newClass("g"), newClass("deobf/G_OuterClass"));
100 assertMapping(newClass("g$a"), newClass("deobf/G_OuterClass$A_InnerClass"));
101 assertMapping(newClass("g$a$a"), newClass("deobf/G_OuterClass$A_InnerClass$A_InnerInnerClass"));
102 assertMapping(newClass("g$b"), newClass("deobf/G_OuterClass$b"));
103 assertMapping(newClass("g$b$a"), newClass("deobf/G_OuterClass$b$A_NamedInnerClass"));
104
105 // fields
106 assertMapping(newField("g$a", "a", "I"), newField("deobf/G_OuterClass$A_InnerClass", "f1", "I"));
107 assertMapping(newField("g$a", "a", "Ljava/lang/String;"), newField("deobf/G_OuterClass$A_InnerClass", "f2", "Ljava/lang/String;"));
108 assertMapping(newField("g$a$a", "a", "I"), newField("deobf/G_OuterClass$A_InnerClass$A_InnerInnerClass", "f3", "I"));
109 assertMapping(newField("g$b$a", "a", "I"), newField("deobf/G_OuterClass$b$A_NamedInnerClass", "f4", "I"));
110
111 // methods
112 assertMapping(newMethod("g$a", "a", "()V"), newMethod("deobf/G_OuterClass$A_InnerClass", "m1", "()V"));
113 assertMapping(newMethod("g$a$a", "a", "()V"), newMethod("deobf/G_OuterClass$A_InnerClass$A_InnerInnerClass", "m2", "()V"));
114 }
115
116 @Test
117 public void namelessClass() {
118 assertMapping(newClass("h"), newClass("h"));
119 }
120
121 @Test
122 public void testGenerics() {
123
124 // classes
125 assertMapping(newClass("i"), newClass("deobf/I_Generics"));
126 assertMapping(newClass("i$a"), newClass("deobf/I_Generics$A_Type"));
127 assertMapping(newClass("i$b"), newClass("deobf/I_Generics$B_Generic"));
128
129 // fields
130 assertMapping(newField("i", "a", "Ljava/util/List;"), newField("deobf/I_Generics", "f1", "Ljava/util/List;"));
131 assertMapping(newField("i", "b", "Ljava/util/List;"), newField("deobf/I_Generics", "f2", "Ljava/util/List;"));
132 assertMapping(newField("i", "a", "Ljava/util/Map;"), newField("deobf/I_Generics", "f3", "Ljava/util/Map;"));
133 assertMapping(newField("i$b", "a", "Ljava/lang/Object;"), newField("deobf/I_Generics$B_Generic", "f4", "Ljava/lang/Object;"));
134 assertMapping(newField("i", "a", "Li$b;"), newField("deobf/I_Generics", "f5", "Ldeobf/I_Generics$B_Generic;"));
135 assertMapping(newField("i", "b", "Li$b;"), newField("deobf/I_Generics", "f6", "Ldeobf/I_Generics$B_Generic;"));
136
137 // methods
138 assertMapping(newMethod("i$b", "a", "()Ljava/lang/Object;"), newMethod("deobf/I_Generics$B_Generic", "m1", "()Ljava/lang/Object;"));
139 }
140
141 private void assertMapping(Entry<?> obf, Entry<?> deobf) {
142 //assertThat(deobfTranslator.translateEntry(obf), is(deobf));
143 //assertThat(obfTranslator.translateEntry(deobf), is(obf));
144
145 //String deobfName = deobfTranslator.translate(obf);
146 //if (deobfName != null) {
147 // assertThat(deobfName, is(deobf.getName()));
148 //}
149
150 //String obfName = obfTranslator.translate(deobf);
151 //if (obfName != null) {
152 // assertThat(obfName, is(obf.getName()));
153 //}
154 }
155}
diff --git a/enigma/src/test/java/cuchaz/enigma/TestTypeDescriptor.java b/enigma/src/test/java/cuchaz/enigma/TestTypeDescriptor.java
new file mode 100644
index 0000000..b9ebe55
--- /dev/null
+++ b/enigma/src/test/java/cuchaz/enigma/TestTypeDescriptor.java
@@ -0,0 +1,243 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11
12package cuchaz.enigma;
13
14import cuchaz.enigma.translation.representation.TypeDescriptor;
15import org.junit.Test;
16
17import static cuchaz.enigma.TestEntryFactory.newClass;
18import static org.hamcrest.MatcherAssert.assertThat;
19import static org.hamcrest.Matchers.is;
20import static org.hamcrest.Matchers.not;
21
22public class TestTypeDescriptor {
23
24 @Test
25 public void isVoid() {
26 assertThat(new TypeDescriptor("V").isVoid(), is(true));
27 assertThat(new TypeDescriptor("Z").isVoid(), is(false));
28 assertThat(new TypeDescriptor("B").isVoid(), is(false));
29 assertThat(new TypeDescriptor("C").isVoid(), is(false));
30 assertThat(new TypeDescriptor("I").isVoid(), is(false));
31 assertThat(new TypeDescriptor("J").isVoid(), is(false));
32 assertThat(new TypeDescriptor("F").isVoid(), is(false));
33 assertThat(new TypeDescriptor("D").isVoid(), is(false));
34 assertThat(new TypeDescriptor("LFoo;").isVoid(), is(false));
35 assertThat(new TypeDescriptor("[I").isVoid(), is(false));
36 }
37
38 @Test
39 public void isPrimitive() {
40 assertThat(new TypeDescriptor("V").isPrimitive(), is(false));
41 assertThat(new TypeDescriptor("Z").isPrimitive(), is(true));
42 assertThat(new TypeDescriptor("B").isPrimitive(), is(true));
43 assertThat(new TypeDescriptor("C").isPrimitive(), is(true));
44 assertThat(new TypeDescriptor("I").isPrimitive(), is(true));
45 assertThat(new TypeDescriptor("J").isPrimitive(), is(true));
46 assertThat(new TypeDescriptor("F").isPrimitive(), is(true));
47 assertThat(new TypeDescriptor("D").isPrimitive(), is(true));
48 assertThat(new TypeDescriptor("LFoo;").isPrimitive(), is(false));
49 assertThat(new TypeDescriptor("[I").isPrimitive(), is(false));
50 }
51
52 @Test
53 public void getPrimitive() {
54 assertThat(new TypeDescriptor("Z").getPrimitive(), is(TypeDescriptor.Primitive.BOOLEAN));
55 assertThat(new TypeDescriptor("B").getPrimitive(), is(TypeDescriptor.Primitive.BYTE));
56 assertThat(new TypeDescriptor("C").getPrimitive(), is(TypeDescriptor.Primitive.CHARACTER));
57 assertThat(new TypeDescriptor("I").getPrimitive(), is(TypeDescriptor.Primitive.INTEGER));
58 assertThat(new TypeDescriptor("J").getPrimitive(), is(TypeDescriptor.Primitive.LONG));
59 assertThat(new TypeDescriptor("F").getPrimitive(), is(TypeDescriptor.Primitive.FLOAT));
60 assertThat(new TypeDescriptor("D").getPrimitive(), is(TypeDescriptor.Primitive.DOUBLE));
61 }
62
63 @Test
64 public void isClass() {
65 assertThat(new TypeDescriptor("V").isType(), is(false));
66 assertThat(new TypeDescriptor("Z").isType(), is(false));
67 assertThat(new TypeDescriptor("B").isType(), is(false));
68 assertThat(new TypeDescriptor("C").isType(), is(false));
69 assertThat(new TypeDescriptor("I").isType(), is(false));
70 assertThat(new TypeDescriptor("J").isType(), is(false));
71 assertThat(new TypeDescriptor("F").isType(), is(false));
72 assertThat(new TypeDescriptor("D").isType(), is(false));
73 assertThat(new TypeDescriptor("LFoo;").isType(), is(true));
74 assertThat(new TypeDescriptor("[I").isType(), is(false));
75 }
76
77 @Test
78 public void getClassEntry() {
79 assertThat(new TypeDescriptor("LFoo;").getTypeEntry(), is(newClass("Foo")));
80 assertThat(new TypeDescriptor("Ljava/lang/String;").getTypeEntry(), is(newClass("java/lang/String")));
81 }
82
83 @Test
84 public void getArrayClassEntry() {
85 assertThat(new TypeDescriptor("[LFoo;").getTypeEntry(), is(newClass("Foo")));
86 assertThat(new TypeDescriptor("[[[Ljava/lang/String;").getTypeEntry(), is(newClass("java/lang/String")));
87 }
88
89 @Test
90 public void isArray() {
91 assertThat(new TypeDescriptor("V").isArray(), is(false));
92 assertThat(new TypeDescriptor("Z").isArray(), is(false));
93 assertThat(new TypeDescriptor("B").isArray(), is(false));
94 assertThat(new TypeDescriptor("C").isArray(), is(false));
95 assertThat(new TypeDescriptor("I").isArray(), is(false));
96 assertThat(new TypeDescriptor("J").isArray(), is(false));
97 assertThat(new TypeDescriptor("F").isArray(), is(false));
98 assertThat(new TypeDescriptor("D").isArray(), is(false));
99 assertThat(new TypeDescriptor("LFoo;").isArray(), is(false));
100 assertThat(new TypeDescriptor("[I").isArray(), is(true));
101 }
102
103 @Test
104 public void getArrayDimension() {
105 assertThat(new TypeDescriptor("[I").getArrayDimension(), is(1));
106 assertThat(new TypeDescriptor("[[I").getArrayDimension(), is(2));
107 assertThat(new TypeDescriptor("[[[I").getArrayDimension(), is(3));
108 }
109
110 @Test
111 public void getArrayType() {
112 assertThat(new TypeDescriptor("[I").getArrayType(), is(new TypeDescriptor("I")));
113 assertThat(new TypeDescriptor("[[I").getArrayType(), is(new TypeDescriptor("I")));
114 assertThat(new TypeDescriptor("[[[I").getArrayType(), is(new TypeDescriptor("I")));
115 assertThat(new TypeDescriptor("[Ljava/lang/String;").getArrayType(), is(new TypeDescriptor("Ljava/lang/String;")));
116 }
117
118 @Test
119 public void hasClass() {
120 assertThat(new TypeDescriptor("LFoo;").containsType(), is(true));
121 assertThat(new TypeDescriptor("Ljava/lang/String;").containsType(), is(true));
122 assertThat(new TypeDescriptor("[LBar;").containsType(), is(true));
123 assertThat(new TypeDescriptor("[[[LCat;").containsType(), is(true));
124
125 assertThat(new TypeDescriptor("V").containsType(), is(false));
126 assertThat(new TypeDescriptor("[I").containsType(), is(false));
127 assertThat(new TypeDescriptor("[[[I").containsType(), is(false));
128 assertThat(new TypeDescriptor("Z").containsType(), is(false));
129 }
130
131 @Test
132 public void parseVoid() {
133 final String answer = "V";
134 assertThat(TypeDescriptor.parseFirst("V"), is(answer));
135 assertThat(TypeDescriptor.parseFirst("VVV"), is(answer));
136 assertThat(TypeDescriptor.parseFirst("VIJ"), is(answer));
137 assertThat(TypeDescriptor.parseFirst("V[I"), is(answer));
138 assertThat(TypeDescriptor.parseFirst("VLFoo;"), is(answer));
139 assertThat(TypeDescriptor.parseFirst("V[LFoo;"), is(answer));
140 }
141
142 @Test
143 public void parsePrimitive() {
144 final String answer = "I";
145 assertThat(TypeDescriptor.parseFirst("I"), is(answer));
146 assertThat(TypeDescriptor.parseFirst("III"), is(answer));
147 assertThat(TypeDescriptor.parseFirst("IJZ"), is(answer));
148 assertThat(TypeDescriptor.parseFirst("I[I"), is(answer));
149 assertThat(TypeDescriptor.parseFirst("ILFoo;"), is(answer));
150 assertThat(TypeDescriptor.parseFirst("I[LFoo;"), is(answer));
151 }
152
153 @Test
154 public void parseClass() {
155 {
156 final String answer = "LFoo;";
157 assertThat(TypeDescriptor.parseFirst("LFoo;"), is(answer));
158 assertThat(TypeDescriptor.parseFirst("LFoo;I"), is(answer));
159 assertThat(TypeDescriptor.parseFirst("LFoo;JZ"), is(answer));
160 assertThat(TypeDescriptor.parseFirst("LFoo;[I"), is(answer));
161 assertThat(TypeDescriptor.parseFirst("LFoo;LFoo;"), is(answer));
162 assertThat(TypeDescriptor.parseFirst("LFoo;[LFoo;"), is(answer));
163 }
164 {
165 final String answer = "Ljava/lang/String;";
166 assertThat(TypeDescriptor.parseFirst("Ljava/lang/String;"), is(answer));
167 assertThat(TypeDescriptor.parseFirst("Ljava/lang/String;I"), is(answer));
168 assertThat(TypeDescriptor.parseFirst("Ljava/lang/String;JZ"), is(answer));
169 assertThat(TypeDescriptor.parseFirst("Ljava/lang/String;[I"), is(answer));
170 assertThat(TypeDescriptor.parseFirst("Ljava/lang/String;LFoo;"), is(answer));
171 assertThat(TypeDescriptor.parseFirst("Ljava/lang/String;[LFoo;"), is(answer));
172 }
173 }
174
175 @Test
176 public void parseArray() {
177 {
178 final String answer = "[I";
179 assertThat(TypeDescriptor.parseFirst("[I"), is(answer));
180 assertThat(TypeDescriptor.parseFirst("[III"), is(answer));
181 assertThat(TypeDescriptor.parseFirst("[IJZ"), is(answer));
182 assertThat(TypeDescriptor.parseFirst("[I[I"), is(answer));
183 assertThat(TypeDescriptor.parseFirst("[ILFoo;"), is(answer));
184 }
185 {
186 final String answer = "[[I";
187 assertThat(TypeDescriptor.parseFirst("[[I"), is(answer));
188 assertThat(TypeDescriptor.parseFirst("[[III"), is(answer));
189 assertThat(TypeDescriptor.parseFirst("[[IJZ"), is(answer));
190 assertThat(TypeDescriptor.parseFirst("[[I[I"), is(answer));
191 assertThat(TypeDescriptor.parseFirst("[[ILFoo;"), is(answer));
192 }
193 {
194 final String answer = "[LFoo;";
195 assertThat(TypeDescriptor.parseFirst("[LFoo;"), is(answer));
196 assertThat(TypeDescriptor.parseFirst("[LFoo;II"), is(answer));
197 assertThat(TypeDescriptor.parseFirst("[LFoo;JZ"), is(answer));
198 assertThat(TypeDescriptor.parseFirst("[LFoo;[I"), is(answer));
199 assertThat(TypeDescriptor.parseFirst("[LFoo;LFoo;"), is(answer));
200 }
201 }
202
203 @Test
204 public void equals() {
205 assertThat(new TypeDescriptor("V"), is(new TypeDescriptor("V")));
206 assertThat(new TypeDescriptor("Z"), is(new TypeDescriptor("Z")));
207 assertThat(new TypeDescriptor("B"), is(new TypeDescriptor("B")));
208 assertThat(new TypeDescriptor("C"), is(new TypeDescriptor("C")));
209 assertThat(new TypeDescriptor("I"), is(new TypeDescriptor("I")));
210 assertThat(new TypeDescriptor("J"), is(new TypeDescriptor("J")));
211 assertThat(new TypeDescriptor("F"), is(new TypeDescriptor("F")));
212 assertThat(new TypeDescriptor("D"), is(new TypeDescriptor("D")));
213 assertThat(new TypeDescriptor("LFoo;"), is(new TypeDescriptor("LFoo;")));
214 assertThat(new TypeDescriptor("[I"), is(new TypeDescriptor("[I")));
215 assertThat(new TypeDescriptor("[[[I"), is(new TypeDescriptor("[[[I")));
216 assertThat(new TypeDescriptor("[LFoo;"), is(new TypeDescriptor("[LFoo;")));
217
218 assertThat(new TypeDescriptor("V"), is(not(new TypeDescriptor("I"))));
219 assertThat(new TypeDescriptor("I"), is(not(new TypeDescriptor("J"))));
220 assertThat(new TypeDescriptor("I"), is(not(new TypeDescriptor("LBar;"))));
221 assertThat(new TypeDescriptor("I"), is(not(new TypeDescriptor("[I"))));
222 assertThat(new TypeDescriptor("LFoo;"), is(not(new TypeDescriptor("LBar;"))));
223 assertThat(new TypeDescriptor("[I"), is(not(new TypeDescriptor("[Z"))));
224 assertThat(new TypeDescriptor("[[[I"), is(not(new TypeDescriptor("[I"))));
225 assertThat(new TypeDescriptor("[LFoo;"), is(not(new TypeDescriptor("[LBar;"))));
226 }
227
228 @Test
229 public void testToString() {
230 assertThat(new TypeDescriptor("V").toString(), is("V"));
231 assertThat(new TypeDescriptor("Z").toString(), is("Z"));
232 assertThat(new TypeDescriptor("B").toString(), is("B"));
233 assertThat(new TypeDescriptor("C").toString(), is("C"));
234 assertThat(new TypeDescriptor("I").toString(), is("I"));
235 assertThat(new TypeDescriptor("J").toString(), is("J"));
236 assertThat(new TypeDescriptor("F").toString(), is("F"));
237 assertThat(new TypeDescriptor("D").toString(), is("D"));
238 assertThat(new TypeDescriptor("LFoo;").toString(), is("LFoo;"));
239 assertThat(new TypeDescriptor("[I").toString(), is("[I"));
240 assertThat(new TypeDescriptor("[[[I").toString(), is("[[[I"));
241 assertThat(new TypeDescriptor("[LFoo;").toString(), is("[LFoo;"));
242 }
243}
diff --git a/enigma/src/test/java/cuchaz/enigma/TokenChecker.java b/enigma/src/test/java/cuchaz/enigma/TokenChecker.java
new file mode 100644
index 0000000..96fc6da
--- /dev/null
+++ b/enigma/src/test/java/cuchaz/enigma/TokenChecker.java
@@ -0,0 +1,65 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11
12package cuchaz.enigma;
13
14import com.google.common.collect.Lists;
15import cuchaz.enigma.analysis.ClassCache;
16import cuchaz.enigma.analysis.EntryReference;
17import cuchaz.enigma.source.SourceIndex;
18import cuchaz.enigma.source.*;
19import cuchaz.enigma.source.Token;
20import cuchaz.enigma.translation.representation.entry.Entry;
21
22import java.io.IOException;
23import java.nio.file.Path;
24import java.util.Collection;
25import java.util.List;
26
27public class TokenChecker {
28 private final Decompiler decompiler;
29
30 protected TokenChecker(Path path) throws IOException {
31 ClassCache classCache = ClassCache.of(path);
32 decompiler = Decompilers.PROCYON.create(classCache, new SourceSettings(false, false));
33 }
34
35 protected String getDeclarationToken(Entry<?> entry) {
36 // decompile the class
37 Source source = decompiler.getSource(entry.getContainingClass().getFullName());
38 // DEBUG
39 // tree.acceptVisitor( new TreeDumpVisitor( new File( "tree." + entry.getClassName().replace( '/', '.' ) + ".txt" ) ), null );
40 String string = source.asString();
41 SourceIndex index = source.index();
42
43 // get the token value
44 Token token = index.getDeclarationToken(entry);
45 if (token == null) {
46 return null;
47 }
48 return string.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 Source source = decompiler.getSource(reference.context.getContainingClass().getFullName());
55 String string = source.asString();
56 SourceIndex index = source.index();
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(string.substring(token.start, token.end));
62 }
63 return values;
64 }
65}
diff --git a/enigma/src/test/java/cuchaz/enigma/inputs/Keep.java b/enigma/src/test/java/cuchaz/enigma/inputs/Keep.java
new file mode 100644
index 0000000..4dbe8e2
--- /dev/null
+++ b/enigma/src/test/java/cuchaz/enigma/inputs/Keep.java
@@ -0,0 +1,18 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11
12package cuchaz.enigma.inputs;
13
14public class Keep {
15 public static void main(String[] args) {
16 System.out.println("Keep me!");
17 }
18}
diff --git a/enigma/src/test/java/cuchaz/enigma/inputs/constructors/BaseClass.java b/enigma/src/test/java/cuchaz/enigma/inputs/constructors/BaseClass.java
new file mode 100644
index 0000000..f07e1f8
--- /dev/null
+++ b/enigma/src/test/java/cuchaz/enigma/inputs/constructors/BaseClass.java
@@ -0,0 +1,26 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11
12package cuchaz.enigma.inputs.constructors;
13
14// a
15public class BaseClass {
16
17 // <init>()V
18 public BaseClass() {
19 System.out.println("Default constructor");
20 }
21
22 // <init>(I)V
23 public BaseClass(int i) {
24 System.out.println("Int constructor " + i);
25 }
26}
diff --git a/enigma/src/test/java/cuchaz/enigma/inputs/constructors/Caller.java b/enigma/src/test/java/cuchaz/enigma/inputs/constructors/Caller.java
new file mode 100644
index 0000000..71439fd
--- /dev/null
+++ b/enigma/src/test/java/cuchaz/enigma/inputs/constructors/Caller.java
@@ -0,0 +1,58 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11
12package cuchaz.enigma.inputs.constructors;
13
14// b
15public class Caller {
16
17 // a()V
18 public void callBaseDefault() {
19 // a.<init>()V
20 System.out.println(new BaseClass());
21 }
22
23 // b()V
24 public void callBaseInt() {
25 // a.<init>(I)V
26 System.out.println(new BaseClass(5));
27 }
28
29 // c()V
30 public void callSubDefault() {
31 // d.<init>()V
32 System.out.println(new SubClass());
33 }
34
35 // d()V
36 public void callSubInt() {
37 // d.<init>(I)V
38 System.out.println(new SubClass(6));
39 }
40
41 // e()V
42 public void callSubIntInt() {
43 // d.<init>(II)V
44 System.out.println(new SubClass(4, 2));
45 }
46
47 // f()V
48 public void callSubSubInt() {
49 // e.<init>(I)V
50 System.out.println(new SubSubClass(3));
51 }
52
53 // g()V
54 public void callDefaultConstructable() {
55 // c.<init>()V
56 System.out.println(new DefaultConstructable());
57 }
58}
diff --git a/enigma/src/test/java/cuchaz/enigma/inputs/constructors/DefaultConstructable.java b/enigma/src/test/java/cuchaz/enigma/inputs/constructors/DefaultConstructable.java
new file mode 100644
index 0000000..c3d4170
--- /dev/null
+++ b/enigma/src/test/java/cuchaz/enigma/inputs/constructors/DefaultConstructable.java
@@ -0,0 +1,16 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11
12package cuchaz.enigma.inputs.constructors;
13
14public class DefaultConstructable {
15 // only default constructor
16}
diff --git a/enigma/src/test/java/cuchaz/enigma/inputs/constructors/SubClass.java b/enigma/src/test/java/cuchaz/enigma/inputs/constructors/SubClass.java
new file mode 100644
index 0000000..bc56b3b
--- /dev/null
+++ b/enigma/src/test/java/cuchaz/enigma/inputs/constructors/SubClass.java
@@ -0,0 +1,39 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11
12package cuchaz.enigma.inputs.constructors;
13
14// d extends a
15public class SubClass extends BaseClass {
16
17 // <init>()V
18 public SubClass() {
19 // a.<init>()V
20 }
21
22 // <init>(I)V
23 public SubClass(int num) {
24 // <init>()V
25 this();
26 System.out.println("SubClass " + num);
27 }
28
29 // <init>(II)V
30 public SubClass(int a, int b) {
31 // <init>(I)V
32 this(a + b);
33 }
34
35 // <init>(III)V
36 public SubClass(int a, int b, int c) {
37 // a.<init>()V
38 }
39}
diff --git a/enigma/src/test/java/cuchaz/enigma/inputs/constructors/SubSubClass.java b/enigma/src/test/java/cuchaz/enigma/inputs/constructors/SubSubClass.java
new file mode 100644
index 0000000..87b69d3
--- /dev/null
+++ b/enigma/src/test/java/cuchaz/enigma/inputs/constructors/SubSubClass.java
@@ -0,0 +1,22 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11
12package cuchaz.enigma.inputs.constructors;
13
14// e extends d
15public class SubSubClass extends SubClass {
16
17 // <init>(I)V
18 public SubSubClass(int i) {
19 // c.<init>(I)V
20 super(i);
21 }
22}
diff --git a/enigma/src/test/java/cuchaz/enigma/inputs/inheritanceTree/BaseClass.java b/enigma/src/test/java/cuchaz/enigma/inputs/inheritanceTree/BaseClass.java
new file mode 100644
index 0000000..b9c4929
--- /dev/null
+++ b/enigma/src/test/java/cuchaz/enigma/inputs/inheritanceTree/BaseClass.java
@@ -0,0 +1,32 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11
12package cuchaz.enigma.inputs.inheritanceTree;
13
14// a
15public abstract class BaseClass {
16
17 // a
18 private String name;
19
20 // <init>(Ljava/lang/String;)V
21 protected BaseClass(String name) {
22 this.name = name;
23 }
24
25 // a()Ljava/lang/String;
26 public String getName() {
27 return name;
28 }
29
30 // a()V
31 public abstract void doBaseThings();
32}
diff --git a/enigma/src/test/java/cuchaz/enigma/inputs/inheritanceTree/SubclassA.java b/enigma/src/test/java/cuchaz/enigma/inputs/inheritanceTree/SubclassA.java
new file mode 100644
index 0000000..50e963c
--- /dev/null
+++ b/enigma/src/test/java/cuchaz/enigma/inputs/inheritanceTree/SubclassA.java
@@ -0,0 +1,22 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11
12package cuchaz.enigma.inputs.inheritanceTree;
13
14// b extends a
15public abstract class SubclassA extends BaseClass {
16
17 // <init>(Ljava/lang/String;)V
18 protected SubclassA(String name) {
19 // call to a.<init>(Ljava/lang/String)V
20 super(name);
21 }
22}
diff --git a/enigma/src/test/java/cuchaz/enigma/inputs/inheritanceTree/SubclassB.java b/enigma/src/test/java/cuchaz/enigma/inputs/inheritanceTree/SubclassB.java
new file mode 100644
index 0000000..d0dd664
--- /dev/null
+++ b/enigma/src/test/java/cuchaz/enigma/inputs/inheritanceTree/SubclassB.java
@@ -0,0 +1,41 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11
12package cuchaz.enigma.inputs.inheritanceTree;
13
14// c extends a
15public class SubclassB extends BaseClass {
16
17 // a
18 private int numThings;
19
20 // <init>()V
21 protected SubclassB() {
22 // a.<init>(Ljava/lang/String;)V
23 super("B");
24
25 // access to a
26 numThings = 4;
27 }
28
29 @Override
30 // a()V
31 public void doBaseThings() {
32 // call to a.a()Ljava/lang/String;
33 System.out.println("Base things by B! " + getName());
34 }
35
36 // b()V
37 public void doBThings() {
38 // access to a
39 System.out.println("" + numThings + " B things!");
40 }
41}
diff --git a/enigma/src/test/java/cuchaz/enigma/inputs/inheritanceTree/SubsubclassAA.java b/enigma/src/test/java/cuchaz/enigma/inputs/inheritanceTree/SubsubclassAA.java
new file mode 100644
index 0000000..c584570
--- /dev/null
+++ b/enigma/src/test/java/cuchaz/enigma/inputs/inheritanceTree/SubsubclassAA.java
@@ -0,0 +1,35 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11
12package cuchaz.enigma.inputs.inheritanceTree;
13
14// d extends b
15public class SubsubclassAA extends SubclassA {
16
17 protected SubsubclassAA() {
18 // call to b.<init>(Ljava/lang/String;)V
19 super("AA");
20 }
21
22 @Override
23 // a()Ljava/lang/String;
24 public String getName() {
25 // call to b.a()Ljava/lang/String;
26 return "subsub" + super.getName();
27 }
28
29 @Override
30 // a()V
31 public void doBaseThings() {
32 // call to d.a()Ljava/lang/String;
33 System.out.println("Base things by " + getName());
34 }
35}
diff --git a/enigma/src/test/java/cuchaz/enigma/inputs/innerClasses/A_Anonymous.java b/enigma/src/test/java/cuchaz/enigma/inputs/innerClasses/A_Anonymous.java
new file mode 100644
index 0000000..f652d87
--- /dev/null
+++ b/enigma/src/test/java/cuchaz/enigma/inputs/innerClasses/A_Anonymous.java
@@ -0,0 +1,25 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11
12package cuchaz.enigma.inputs.innerClasses;
13
14public class A_Anonymous {
15
16 public void foo() {
17 Runnable runnable = new Runnable() {
18 @Override
19 public void run() {
20 // don't care
21 }
22 };
23 runnable.run();
24 }
25}
diff --git a/enigma/src/test/java/cuchaz/enigma/inputs/innerClasses/B_AnonymousWithScopeArgs.java b/enigma/src/test/java/cuchaz/enigma/inputs/innerClasses/B_AnonymousWithScopeArgs.java
new file mode 100644
index 0000000..d1b7601
--- /dev/null
+++ b/enigma/src/test/java/cuchaz/enigma/inputs/innerClasses/B_AnonymousWithScopeArgs.java
@@ -0,0 +1,24 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11
12package cuchaz.enigma.inputs.innerClasses;
13
14public class B_AnonymousWithScopeArgs {
15
16 public static void foo(final D_Simple arg) {
17 System.out.println(new Object() {
18 @Override
19 public String toString() {
20 return arg.toString();
21 }
22 });
23 }
24}
diff --git a/enigma/src/test/java/cuchaz/enigma/inputs/innerClasses/C_ConstructorArgs.java b/enigma/src/test/java/cuchaz/enigma/inputs/innerClasses/C_ConstructorArgs.java
new file mode 100644
index 0000000..94061fa
--- /dev/null
+++ b/enigma/src/test/java/cuchaz/enigma/inputs/innerClasses/C_ConstructorArgs.java
@@ -0,0 +1,31 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11
12package cuchaz.enigma.inputs.innerClasses;
13
14@SuppressWarnings("unused")
15public class C_ConstructorArgs {
16
17 Inner i;
18
19 public void foo() {
20 i = new Inner(5);
21 }
22
23 class Inner {
24
25 private int a;
26
27 public Inner(int a) {
28 this.a = a;
29 }
30 }
31}
diff --git a/enigma/src/test/java/cuchaz/enigma/inputs/innerClasses/D_Simple.java b/enigma/src/test/java/cuchaz/enigma/inputs/innerClasses/D_Simple.java
new file mode 100644
index 0000000..71b3a6d
--- /dev/null
+++ b/enigma/src/test/java/cuchaz/enigma/inputs/innerClasses/D_Simple.java
@@ -0,0 +1,19 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11
12package cuchaz.enigma.inputs.innerClasses;
13
14public class D_Simple {
15
16 class Inner {
17 // nothing to do
18 }
19}
diff --git a/enigma/src/test/java/cuchaz/enigma/inputs/innerClasses/E_AnonymousWithOuterAccess.java b/enigma/src/test/java/cuchaz/enigma/inputs/innerClasses/E_AnonymousWithOuterAccess.java
new file mode 100644
index 0000000..976ec42
--- /dev/null
+++ b/enigma/src/test/java/cuchaz/enigma/inputs/innerClasses/E_AnonymousWithOuterAccess.java
@@ -0,0 +1,32 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11
12package cuchaz.enigma.inputs.innerClasses;
13
14public class E_AnonymousWithOuterAccess {
15
16 // reproduction of error case documented at:
17 // https://bitbucket.org/cuchaz/enigma/issue/61/stackoverflowerror-when-deobfuscating
18
19 public Object makeInner() {
20 outerMethod();
21 return new Object() {
22 @Override
23 public String toString() {
24 return outerMethod();
25 }
26 };
27 }
28
29 private String outerMethod() {
30 return "foo";
31 }
32}
diff --git a/enigma/src/test/java/cuchaz/enigma/inputs/innerClasses/F_ClassTree.java b/enigma/src/test/java/cuchaz/enigma/inputs/innerClasses/F_ClassTree.java
new file mode 100644
index 0000000..b1de3c9
--- /dev/null
+++ b/enigma/src/test/java/cuchaz/enigma/inputs/innerClasses/F_ClassTree.java
@@ -0,0 +1,30 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11
12package cuchaz.enigma.inputs.innerClasses;
13
14public class F_ClassTree {
15
16 public class Level1 {
17
18 public int f1;
19
20 public class Level2 {
21
22 public int f2;
23
24 public class Level3 {
25
26 public int f3;
27 }
28 }
29 }
30}
diff --git a/enigma/src/test/java/cuchaz/enigma/inputs/loneClass/LoneClass.java b/enigma/src/test/java/cuchaz/enigma/inputs/loneClass/LoneClass.java
new file mode 100644
index 0000000..ddc4e31
--- /dev/null
+++ b/enigma/src/test/java/cuchaz/enigma/inputs/loneClass/LoneClass.java
@@ -0,0 +1,25 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11
12package cuchaz.enigma.inputs.loneClass;
13
14public class LoneClass {
15
16 private String name;
17
18 public LoneClass(String name) {
19 this.name = name;
20 }
21
22 public String getName() {
23 return name;
24 }
25}
diff --git a/enigma/src/test/java/cuchaz/enigma/inputs/packageAccess/Base.java b/enigma/src/test/java/cuchaz/enigma/inputs/packageAccess/Base.java
new file mode 100644
index 0000000..6f5fe30
--- /dev/null
+++ b/enigma/src/test/java/cuchaz/enigma/inputs/packageAccess/Base.java
@@ -0,0 +1,7 @@
1package cuchaz.enigma.inputs.packageAccess;
2
3public class Base {
4 protected int make() {
5 return 42;
6 }
7}
diff --git a/enigma/src/test/java/cuchaz/enigma/inputs/packageAccess/SamePackageChild.java b/enigma/src/test/java/cuchaz/enigma/inputs/packageAccess/SamePackageChild.java
new file mode 100644
index 0000000..cf0f657
--- /dev/null
+++ b/enigma/src/test/java/cuchaz/enigma/inputs/packageAccess/SamePackageChild.java
@@ -0,0 +1,12 @@
1package cuchaz.enigma.inputs.packageAccess;
2
3public class SamePackageChild extends Base {
4
5 class Inner {
6 final int value;
7
8 Inner() {
9 value = SamePackageChild.this.make(); // no synthetic method
10 }
11 }
12}
diff --git a/enigma/src/test/java/cuchaz/enigma/inputs/packageAccess/sub/OtherPackageChild.java b/enigma/src/test/java/cuchaz/enigma/inputs/packageAccess/sub/OtherPackageChild.java
new file mode 100644
index 0000000..19fb19c
--- /dev/null
+++ b/enigma/src/test/java/cuchaz/enigma/inputs/packageAccess/sub/OtherPackageChild.java
@@ -0,0 +1,14 @@
1package cuchaz.enigma.inputs.packageAccess.sub;
2
3import cuchaz.enigma.inputs.packageAccess.Base;
4
5public class OtherPackageChild extends Base {
6
7 class Inner {
8 final int value;
9
10 Inner() {
11 value = OtherPackageChild.this.make(); // synthetic method call
12 }
13 }
14}
diff --git a/enigma/src/test/java/cuchaz/enigma/inputs/translation/A_Basic.java b/enigma/src/test/java/cuchaz/enigma/inputs/translation/A_Basic.java
new file mode 100644
index 0000000..26f3718
--- /dev/null
+++ b/enigma/src/test/java/cuchaz/enigma/inputs/translation/A_Basic.java
@@ -0,0 +1,33 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11
12package cuchaz.enigma.inputs.translation;
13
14public class A_Basic {
15
16 public int one;
17 public float two;
18 public String three;
19
20 public void m1() {
21 }
22
23 public int m2() {
24 return 42;
25 }
26
27 public void m3(int a1) {
28 }
29
30 public int m4(int a1) {
31 return 5; // chosen by fair die roll, guaranteed to be random
32 }
33}
diff --git a/enigma/src/test/java/cuchaz/enigma/inputs/translation/B_BaseClass.java b/enigma/src/test/java/cuchaz/enigma/inputs/translation/B_BaseClass.java
new file mode 100644
index 0000000..fd7f6e7
--- /dev/null
+++ b/enigma/src/test/java/cuchaz/enigma/inputs/translation/B_BaseClass.java
@@ -0,0 +1,26 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11
12package cuchaz.enigma.inputs.translation;
13
14public class B_BaseClass {
15
16 public int f1;
17 public char f2;
18
19 public int m1() {
20 return 5;
21 }
22
23 public int m2() {
24 return 42;
25 }
26}
diff --git a/enigma/src/test/java/cuchaz/enigma/inputs/translation/C_SubClass.java b/enigma/src/test/java/cuchaz/enigma/inputs/translation/C_SubClass.java
new file mode 100644
index 0000000..9d74e44
--- /dev/null
+++ b/enigma/src/test/java/cuchaz/enigma/inputs/translation/C_SubClass.java
@@ -0,0 +1,28 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11
12package cuchaz.enigma.inputs.translation;
13
14public class C_SubClass extends B_BaseClass {
15
16 public char f2; // shadows B_BaseClass.f2
17 public int f3;
18 public int f4;
19
20 @Override
21 public int m1() {
22 return 32;
23 }
24
25 public int m3() {
26 return 7;
27 }
28}
diff --git a/enigma/src/test/java/cuchaz/enigma/inputs/translation/D_AnonymousTesting.java b/enigma/src/test/java/cuchaz/enigma/inputs/translation/D_AnonymousTesting.java
new file mode 100644
index 0000000..99c83bb
--- /dev/null
+++ b/enigma/src/test/java/cuchaz/enigma/inputs/translation/D_AnonymousTesting.java
@@ -0,0 +1,29 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11
12package cuchaz.enigma.inputs.translation;
13
14import java.util.ArrayList;
15import java.util.List;
16
17public class D_AnonymousTesting {
18
19 public List<Object> getObjs() {
20 List<Object> objs = new ArrayList<Object>();
21 objs.add(new Object() {
22 @Override
23 public String toString() {
24 return "Object!";
25 }
26 });
27 return objs;
28 }
29}
diff --git a/enigma/src/test/java/cuchaz/enigma/inputs/translation/E_Bridges.java b/enigma/src/test/java/cuchaz/enigma/inputs/translation/E_Bridges.java
new file mode 100644
index 0000000..0b8cf2a
--- /dev/null
+++ b/enigma/src/test/java/cuchaz/enigma/inputs/translation/E_Bridges.java
@@ -0,0 +1,32 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11
12package cuchaz.enigma.inputs.translation;
13
14import java.util.Iterator;
15
16public class E_Bridges implements Iterator<Object> {
17
18 @Override
19 public boolean hasNext() {
20 return false;
21 }
22
23 @Override
24 public String next() {
25 // the compiler will generate a bridge for this method
26 return "foo";
27 }
28
29 @Override
30 public void remove() {
31 }
32}
diff --git a/enigma/src/test/java/cuchaz/enigma/inputs/translation/F_ObjectMethods.java b/enigma/src/test/java/cuchaz/enigma/inputs/translation/F_ObjectMethods.java
new file mode 100644
index 0000000..8a92792
--- /dev/null
+++ b/enigma/src/test/java/cuchaz/enigma/inputs/translation/F_ObjectMethods.java
@@ -0,0 +1,31 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11
12package cuchaz.enigma.inputs.translation;
13
14@SuppressWarnings("FinalizeCalledExplicitly")
15public class F_ObjectMethods {
16
17 public void callEmAll()
18 throws Throwable {
19 clone();
20 equals(this);
21 finalize();
22 getClass();
23 hashCode();
24 notify();
25 notifyAll();
26 toString();
27 wait();
28 wait(0);
29 wait(0, 0);
30 }
31}
diff --git a/enigma/src/test/java/cuchaz/enigma/inputs/translation/G_OuterClass.java b/enigma/src/test/java/cuchaz/enigma/inputs/translation/G_OuterClass.java
new file mode 100644
index 0000000..a1e6a85
--- /dev/null
+++ b/enigma/src/test/java/cuchaz/enigma/inputs/translation/G_OuterClass.java
@@ -0,0 +1,36 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11
12package cuchaz.enigma.inputs.translation;
13
14public class G_OuterClass {
15
16 public class A_InnerClass {
17
18 public int f1;
19 public String f2;
20
21 public void m1() {}
22
23 public class A_InnerInnerClass {
24
25 public int f3;
26
27 public void m2() {}
28 }
29 }
30
31 public class B_NamelessClass {
32 public class A_NamedInnerClass {
33 public int f4;
34 }
35 }
36}
diff --git a/enigma/src/test/java/cuchaz/enigma/inputs/translation/H_NamelessClass.java b/enigma/src/test/java/cuchaz/enigma/inputs/translation/H_NamelessClass.java
new file mode 100644
index 0000000..013c55a
--- /dev/null
+++ b/enigma/src/test/java/cuchaz/enigma/inputs/translation/H_NamelessClass.java
@@ -0,0 +1,40 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11
12package cuchaz.enigma.inputs.translation;
13
14public class H_NamelessClass {
15
16 public class A_InnerClass {
17
18 public int f1;
19 public String f2;
20
21 public void m1() {}
22
23 public class A_InnerInnerClass {
24
25 public int f3;
26
27 public void m2() {}
28 }
29 }
30
31 public class B_NamelessClass {
32 public class A_NamedInnerClass {
33 public int f4;
34
35 public class A_AnotherInnerClass {}
36
37 public class B_YetAnotherInnerClass {}
38 }
39 }
40}
diff --git a/enigma/src/test/java/cuchaz/enigma/inputs/translation/I_Generics.java b/enigma/src/test/java/cuchaz/enigma/inputs/translation/I_Generics.java
new file mode 100644
index 0000000..fd2ebdd
--- /dev/null
+++ b/enigma/src/test/java/cuchaz/enigma/inputs/translation/I_Generics.java
@@ -0,0 +1,35 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11
12package cuchaz.enigma.inputs.translation;
13
14import java.util.List;
15import java.util.Map;
16
17public class I_Generics {
18
19 public List<Integer> f1;
20 public List<A_Type> f2;
21 public Map<A_Type, A_Type> f3;
22 public B_Generic<Integer> f5;
23 public B_Generic<A_Type> f6;
24
25 public class A_Type {
26 }
27
28 public class B_Generic<T> {
29 public T f4;
30
31 public T m1() {
32 return null;
33 }
34 }
35}
diff --git a/enigma/src/test/java/cuchaz/enigma/translation/mapping/TestComments.java b/enigma/src/test/java/cuchaz/enigma/translation/mapping/TestComments.java
new file mode 100644
index 0000000..e831943
--- /dev/null
+++ b/enigma/src/test/java/cuchaz/enigma/translation/mapping/TestComments.java
@@ -0,0 +1,39 @@
1package cuchaz.enigma.translation.mapping;
2
3import java.io.IOException;
4import java.net.URISyntaxException;
5import java.nio.file.Path;
6import java.nio.file.Paths;
7
8import cuchaz.enigma.ProgressListener;
9import cuchaz.enigma.translation.mapping.serde.MappingFileNameFormat;
10import cuchaz.enigma.translation.mapping.serde.MappingParseException;
11import cuchaz.enigma.translation.mapping.serde.MappingSaveParameters;
12import cuchaz.enigma.translation.mapping.serde.enigma.EnigmaMappingsReader;
13import cuchaz.enigma.translation.mapping.serde.tinyv2.TinyV2Writer;
14import cuchaz.enigma.translation.mapping.tree.EntryTree;
15import org.junit.Test;
16
17public class TestComments {
18 private static Path DIRECTORY;
19
20 static {
21 try {
22 DIRECTORY = Paths.get(TestTinyV2InnerClasses.class.getResource("/comments/").toURI());
23 } catch (URISyntaxException e) {
24 throw new RuntimeException(e);
25 }
26 }
27
28 @Test
29 public void testParseAndWrite() throws IOException, MappingParseException {
30 ProgressListener progressListener = ProgressListener.none();
31 MappingSaveParameters params = new MappingSaveParameters(MappingFileNameFormat.BY_DEOBF);
32 EntryTree<EntryMapping> mappings = EnigmaMappingsReader.DIRECTORY.read(
33 DIRECTORY, progressListener, params);
34
35 new TinyV2Writer("intermediary", "named")
36 .write(mappings, DIRECTORY.resolve("convertedtiny.tiny"), progressListener, params);
37 }
38
39} \ No newline at end of file
diff --git a/enigma/src/test/java/cuchaz/enigma/translation/mapping/TestTinyV2InnerClasses.java b/enigma/src/test/java/cuchaz/enigma/translation/mapping/TestTinyV2InnerClasses.java
new file mode 100644
index 0000000..65941e5
--- /dev/null
+++ b/enigma/src/test/java/cuchaz/enigma/translation/mapping/TestTinyV2InnerClasses.java
@@ -0,0 +1,37 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11
12package cuchaz.enigma.translation.mapping;
13
14import cuchaz.enigma.Enigma;
15import cuchaz.enigma.EnigmaProject;
16import cuchaz.enigma.ProgressListener;
17import cuchaz.enigma.translation.mapping.serde.enigma.EnigmaMappingsReader;
18
19import java.nio.file.Path;
20import java.nio.file.Paths;
21
22public final class TestTinyV2InnerClasses {
23 private Path jar;
24 private Path mappings;
25
26 public TestTinyV2InnerClasses() throws Exception {
27 jar = Paths.get("build/test-obf/innerClasses.jar");
28 mappings = Paths.get(TestTinyV2InnerClasses.class.getResource("/tinyV2InnerClasses/").toURI());
29 }
30
31// @Test
32 public void testMappings() throws Exception {
33 EnigmaProject project = Enigma.create().openJar(jar, ProgressListener.none());
34 project.setMappings(EnigmaMappingsReader.DIRECTORY.read(mappings, ProgressListener.none(), project.getEnigma().getProfile().getMappingSaveParameters()));
35
36 }
37}
diff --git a/enigma/src/test/java/cuchaz/enigma/translation/mapping/TestV2Main.java b/enigma/src/test/java/cuchaz/enigma/translation/mapping/TestV2Main.java
new file mode 100644
index 0000000..6e4d7b9
--- /dev/null
+++ b/enigma/src/test/java/cuchaz/enigma/translation/mapping/TestV2Main.java
@@ -0,0 +1,23 @@
1package cuchaz.enigma.translation.mapping;
2
3import cuchaz.enigma.ProgressListener;
4import cuchaz.enigma.translation.mapping.serde.MappingFileNameFormat;
5import cuchaz.enigma.translation.mapping.serde.MappingSaveParameters;
6import cuchaz.enigma.translation.mapping.serde.enigma.EnigmaMappingsReader;
7import cuchaz.enigma.translation.mapping.serde.tinyv2.TinyV2Writer;
8import cuchaz.enigma.translation.mapping.tree.EntryTree;
9
10import java.nio.file.Path;
11import java.nio.file.Paths;
12
13public final class TestV2Main {
14 public static void main(String... args) throws Exception {
15 Path path = Paths.get(TestV2Main.class.getResource("/tinyV2InnerClasses/").toURI());
16
17 MappingSaveParameters parameters = new MappingSaveParameters(MappingFileNameFormat.BY_DEOBF);
18
19 EntryTree<EntryMapping> tree = EnigmaMappingsReader.DIRECTORY.read(path, ProgressListener.none(), parameters);
20
21 new TinyV2Writer("obf", "deobf").write(tree, Paths.get("currentYarn.tiny"), ProgressListener.none(), parameters);
22 }
23}
diff --git a/enigma/src/test/resources/comments/test.mapping b/enigma/src/test/resources/comments/test.mapping
new file mode 100644
index 0000000..d134558
--- /dev/null
+++ b/enigma/src/test/resources/comments/test.mapping
@@ -0,0 +1,18 @@
1CLASS net/minecraft/class_1158 net/minecraft/util/math/Quaternion
2 COMMENT it circel
3 COMMENT next line
4 FIELD field_21493 IDENTITY Lnet/minecraft/class_1158;
5 COMMENT moar comment thing
6 COMMENT near field
7 METHOD foo bar (FFFF)V
8 COMMENT method comment
9 COMMENT second line
10 COMMENT third line
11 ARG 1 b
12 COMMENT arg comment
13 CLASS old new
14 COMMENT inner comment
15 FIELD field_19263 iterator Lnet/minecraft/class_3980;
16 METHOD tryAdvance (Ljava/util/function/Consumer;)Z
17 ARG 1 consumer
18 COMMENT very inner comment \ No newline at end of file
diff --git a/enigma/src/test/resources/proguard-build.conf b/enigma/src/test/resources/proguard-build.conf
new file mode 100644
index 0000000..691d8a2
--- /dev/null
+++ b/enigma/src/test/resources/proguard-build.conf
@@ -0,0 +1,6 @@
1-dontoptimize
2-dontobfuscate
3-dontwarn
4-keep class cuchaz.enigma.Main { static void main(java.lang.String[]); }
5-keep class cuchaz.enigma.command.Main { static void main(java.lang.String[]); }
6-keep class de.sciss.syntaxpane.** { *; }
diff --git a/enigma/src/test/resources/proguard-test.conf b/enigma/src/test/resources/proguard-test.conf
new file mode 100644
index 0000000..9411d26
--- /dev/null
+++ b/enigma/src/test/resources/proguard-test.conf
@@ -0,0 +1,8 @@
1-overloadaggressively
2-repackageclasses
3-allowaccessmodification
4-dontoptimize
5-dontshrink
6-keepparameternames
7-keepattributes
8-keep class cuchaz.enigma.inputs.Keep
diff --git a/enigma/src/test/resources/tinyV2InnerClasses/c.mapping b/enigma/src/test/resources/tinyV2InnerClasses/c.mapping
new file mode 100644
index 0000000..f9b0442
--- /dev/null
+++ b/enigma/src/test/resources/tinyV2InnerClasses/c.mapping
@@ -0,0 +1,2 @@
1CLASS c
2 CLASS a Kid
diff --git a/enigma/src/test/resources/tinyV2InnerClasses/cuchaz/enigma/Dad.mapping b/enigma/src/test/resources/tinyV2InnerClasses/cuchaz/enigma/Dad.mapping
new file mode 100644
index 0000000..8d43ba9
--- /dev/null
+++ b/enigma/src/test/resources/tinyV2InnerClasses/cuchaz/enigma/Dad.mapping
@@ -0,0 +1,5 @@
1CLASS f cuchaz/enigma/Dad
2 CLASS a One
3 CLASS a Two
4 CLASS a
5 FIELD a value I
diff --git a/enigma/src/test/resources/translation.mappings b/enigma/src/test/resources/translation.mappings
new file mode 100644
index 0000000..c08765c
--- /dev/null
+++ b/enigma/src/test/resources/translation.mappings
@@ -0,0 +1,41 @@
1CLASS a deobf/A_Basic
2 FIELD a f1 I
3 FIELD a f2 F
4 FIELD a f3 Ljava/lang/String;
5 METHOD a m1 ()V
6 METHOD a m2 ()I
7 METHOD a m3 (I)V
8 METHOD a m4 (I)I
9CLASS b deobf/B_BaseClass
10 FIELD a f1 I
11 FIELD a f2 C
12 METHOD a m1 ()I
13 METHOD b m2 ()I
14CLASS c deobf/C_SubClass
15 FIELD b f2 C
16 FIELD b f3 I
17 FIELD c f4 I
18 METHOD a m1 ()I
19 METHOD c m3 ()I
20CLASS g deobf/G_OuterClass
21 CLASS g$a A_InnerClass
22 CLASS g$a$a A_InnerInnerClass
23 FIELD a f3 I
24 METHOD a m2 ()V
25 FIELD a f1 I
26 FIELD a f2 Ljava/lang/String;
27 METHOD a m1 ()V
28 CLASS g$b
29 CLASS g$b$a A_NamedInnerClass
30 FIELD a f4 I
31CLASS h
32CLASS i deobf/I_Generics
33 CLASS i$a A_Type
34 CLASS i$b B_Generic
35 FIELD a f4 Ljava/lang/Object;
36 METHOD a m1 ()Ljava/lang/Object;
37 FIELD a f1 Ljava/util/List;
38 FIELD b f2 Ljava/util/List;
39 FIELD a f3 Ljava/util/Map;
40 FIELD a f5 Li$b;
41 FIELD b f6 Li$b;