mirror of
https://github.com/skylot/jadx.git
synced 2024-11-23 12:50:02 +00:00
* initial setup for data class and metadata parsing * bug fix & comments * better version using plugin system * added tests * ignore getters test fix * logs & imports * reverted accidental changes * moved util classes to plugin, spotless apply * move test and some other minor fixes --------- Co-authored-by: Skylot <skylot@gmail.com>
This commit is contained in:
parent
3474f0da04
commit
ccdbb1d62c
11
README.md
11
README.md
@ -154,6 +154,17 @@ Plugin options (-P<name>=<value>):
|
||||
2) java-convert: Convert .class, .jar and .aar files to dex
|
||||
- java-convert.mode - convert mode, values: [dx, d8, both], default: both
|
||||
- java-convert.d8-desugar - use desugar in d8, values: [yes, no], default: no
|
||||
3) kotlin-metadata: Use kotlin.Metadata annotation for code generation
|
||||
- kotlin-metadata.class-alias - rename class alias, values: [yes, no], default: yes
|
||||
- kotlin-metadata.method-args - rename function arguments, values: [yes, no], default: yes
|
||||
- kotlin-metadata.fields - rename fields, values: [yes, no], default: yes
|
||||
- kotlin-metadata.companion - rename companion object, values: [yes, no], default: yes
|
||||
- kotlin-metadata.data-class - add data class modifier, values: [yes, no], default: yes
|
||||
- kotlin-metadata.to-string - rename fields using toString, values: [yes, no], default: yes
|
||||
- kotlin-metadata.getters - rename simple getters to field names, values: [yes, no], default: yes
|
||||
4) rename-mappings: various mappings support
|
||||
- rename-mappings.format - mapping format, values: [auto, TINY, TINY_2, ENIGMA, ENIGMA_DIR, MCP, SRG, TSRG, TSRG2, PROGUARD], default: auto
|
||||
- rename-mappings.invert - invert mapping, values: [yes, no], default: no
|
||||
|
||||
Examples:
|
||||
jadx -d out classes.dex
|
||||
|
@ -9,6 +9,8 @@ dependencies {
|
||||
runtimeOnly(project(':jadx-plugins:jadx-java-input'))
|
||||
runtimeOnly(project(':jadx-plugins:jadx-java-convert'))
|
||||
runtimeOnly(project(':jadx-plugins:jadx-smali-input'))
|
||||
runtimeOnly(project(':jadx-plugins:jadx-rename-mappings'))
|
||||
runtimeOnly(project(':jadx-plugins:jadx-kotlin-metadata'))
|
||||
runtimeOnly(project(':jadx-plugins:jadx-script:jadx-script-plugin'))
|
||||
|
||||
implementation 'com.beust:jcommander:1.82'
|
||||
|
@ -157,9 +157,6 @@ public class JadxCLIArgs {
|
||||
@Parameter(names = { "--deobf-use-sourcename" }, description = "use source file name as class name alias")
|
||||
protected boolean deobfuscationUseSourceNameAsAlias = false;
|
||||
|
||||
@Parameter(names = { "--deobf-parse-kotlin-metadata" }, description = "parse kotlin metadata to class and package names")
|
||||
protected boolean deobfuscationParseKotlinMetadata = false;
|
||||
|
||||
@Parameter(
|
||||
names = { "--deobf-res-name-source" },
|
||||
description = "better name source for resources:"
|
||||
@ -305,7 +302,6 @@ public class JadxCLIArgs {
|
||||
args.setDeobfuscationMinLength(deobfuscationMinLength);
|
||||
args.setDeobfuscationMaxLength(deobfuscationMaxLength);
|
||||
args.setUseSourceNameAsClassAlias(deobfuscationUseSourceNameAsAlias);
|
||||
args.setParseKotlinMetadata(deobfuscationParseKotlinMetadata);
|
||||
args.setUseKotlinMethodsForVarNames(useKotlinMethodsForVarNames);
|
||||
args.setResourceNameSource(resourceNameSource);
|
||||
args.setEscapeUnicode(escapeUnicode);
|
||||
@ -443,10 +439,6 @@ public class JadxCLIArgs {
|
||||
return deobfuscationUseSourceNameAsAlias;
|
||||
}
|
||||
|
||||
public boolean isDeobfuscationParseKotlinMetadata() {
|
||||
return deobfuscationParseKotlinMetadata;
|
||||
}
|
||||
|
||||
public ResourceNameSource getResourceNameSource() {
|
||||
return resourceNameSource;
|
||||
}
|
||||
|
@ -18,7 +18,6 @@ dependencies {
|
||||
testRuntimeOnly(project(':jadx-plugins:jadx-java-convert'))
|
||||
testRuntimeOnly(project(':jadx-plugins:jadx-java-input'))
|
||||
testRuntimeOnly(project(':jadx-plugins:jadx-raung-input'))
|
||||
testRuntimeOnly(project(':jadx-plugins:jadx-rename-mappings'))
|
||||
|
||||
testImplementation 'org.eclipse.jdt:ecj:3.33.0'
|
||||
testImplementation 'tools.profiler:async-profiler:2.9'
|
||||
|
@ -89,7 +89,6 @@ public class JadxArgs implements Closeable {
|
||||
|
||||
private boolean deobfuscationOn = false;
|
||||
private boolean useSourceNameAsClassAlias = false;
|
||||
private boolean parseKotlinMetadata = false;
|
||||
|
||||
private File generatedRenamesMappingFile = null;
|
||||
private GeneratedRenamesMappingFileMode generatedRenamesMappingFileMode = GeneratedRenamesMappingFileMode.getDefault();
|
||||
@ -404,14 +403,6 @@ public class JadxArgs implements Closeable {
|
||||
this.useSourceNameAsClassAlias = useSourceNameAsClassAlias;
|
||||
}
|
||||
|
||||
public boolean isParseKotlinMetadata() {
|
||||
return parseKotlinMetadata;
|
||||
}
|
||||
|
||||
public void setParseKotlinMetadata(boolean parseKotlinMetadata) {
|
||||
this.parseKotlinMetadata = parseKotlinMetadata;
|
||||
}
|
||||
|
||||
public int getDeobfuscationMinLength() {
|
||||
return deobfuscationMinLength;
|
||||
}
|
||||
@ -640,7 +631,7 @@ public class JadxArgs implements Closeable {
|
||||
+ inlineAnonymousClasses + inlineMethods + moveInnerClasses + allowInlineKotlinLambda
|
||||
+ deobfuscationOn + deobfuscationMinLength + deobfuscationMaxLength
|
||||
+ resourceNameSource
|
||||
+ parseKotlinMetadata + useKotlinMethodsForVarNames
|
||||
+ useKotlinMethodsForVarNames
|
||||
+ insertDebugLines + extractFinally
|
||||
+ debugInfo + useSourceNameAsClassAlias + escapeUnicode + replaceConsts
|
||||
+ respectBytecodeAccModifiers + fsCaseSensitive + renameFlags
|
||||
@ -668,7 +659,6 @@ public class JadxArgs implements Closeable {
|
||||
+ ", generatedRenamesMappingFileMode=" + generatedRenamesMappingFileMode
|
||||
+ ", resourceNameSource=" + resourceNameSource
|
||||
+ ", useSourceNameAsClassAlias=" + useSourceNameAsClassAlias
|
||||
+ ", parseKotlinMetadata=" + parseKotlinMetadata
|
||||
+ ", useKotlinMethodsForVarNames=" + useKotlinMethodsForVarNames
|
||||
+ ", insertDebugLines=" + insertDebugLines
|
||||
+ ", extractFinally=" + extractFinally
|
||||
|
@ -164,6 +164,10 @@ public class AccessInfo {
|
||||
return (accFlags & AccessFlags.MODULE) != 0;
|
||||
}
|
||||
|
||||
public boolean isData() {
|
||||
return (accFlags & AccessFlags.DATA) != 0;
|
||||
}
|
||||
|
||||
public AFType getType() {
|
||||
return type;
|
||||
}
|
||||
@ -220,6 +224,9 @@ public class AccessInfo {
|
||||
code.append("strict ");
|
||||
}
|
||||
if (showHidden) {
|
||||
if (isData()) {
|
||||
code.append("/* data */ ");
|
||||
}
|
||||
if (isModuleInfo()) {
|
||||
code.append("/* module-info */ ");
|
||||
}
|
||||
|
@ -1,25 +0,0 @@
|
||||
package jadx.core.dex.visitors.rename;
|
||||
|
||||
import jadx.core.dex.attributes.AFlag;
|
||||
import jadx.core.dex.nodes.ClassNode;
|
||||
import jadx.core.dex.nodes.RootNode;
|
||||
import jadx.core.utils.kotlin.ClsAliasPair;
|
||||
import jadx.core.utils.kotlin.KotlinMetadataUtils;
|
||||
|
||||
public class KotlinMetadataRename {
|
||||
|
||||
public static void process(RootNode root) {
|
||||
if (root.getArgs().isParseKotlinMetadata()) {
|
||||
for (ClassNode cls : root.getClasses()) {
|
||||
if (cls.contains(AFlag.DONT_RENAME)) {
|
||||
continue;
|
||||
}
|
||||
ClsAliasPair kotlinCls = KotlinMetadataUtils.getClassAlias(cls);
|
||||
if (kotlinCls != null) {
|
||||
cls.rename(kotlinCls.getName());
|
||||
cls.getPackageNode().rename(kotlinCls.getPkg());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -40,7 +40,6 @@ public class RenameVisitor extends AbstractVisitor {
|
||||
}
|
||||
|
||||
private void process(RootNode root) {
|
||||
KotlinMetadataRename.process(root);
|
||||
SourceFileRename.process(root);
|
||||
|
||||
UserRenames.apply(root);
|
||||
|
@ -1,24 +0,0 @@
|
||||
package jadx.core.utils.kotlin;
|
||||
|
||||
public class ClsAliasPair {
|
||||
private final String pkg;
|
||||
private final String name;
|
||||
|
||||
public ClsAliasPair(String pkg, String name) {
|
||||
this.pkg = pkg;
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public String getPkg() {
|
||||
return pkg;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return pkg + '.' + name;
|
||||
}
|
||||
}
|
@ -1,115 +0,0 @@
|
||||
package jadx.core.utils.kotlin;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import jadx.api.plugins.input.data.annotations.EncodedType;
|
||||
import jadx.api.plugins.input.data.annotations.EncodedValue;
|
||||
import jadx.api.plugins.input.data.annotations.IAnnotation;
|
||||
import jadx.core.deobf.NameMapper;
|
||||
import jadx.core.dex.attributes.nodes.RenameReasonAttr;
|
||||
import jadx.core.dex.info.ClassInfo;
|
||||
import jadx.core.dex.nodes.ClassNode;
|
||||
import jadx.core.utils.Utils;
|
||||
|
||||
// TODO: parse data from d1 (protobuf encoded) to get original method names and other useful info
|
||||
public class KotlinMetadataUtils {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(KotlinMetadataUtils.class);
|
||||
|
||||
private static final String KOTLIN_METADATA_ANNOTATION = "Lkotlin/Metadata;";
|
||||
private static final String KOTLIN_METADATA_D2_PARAMETER = "d2";
|
||||
|
||||
/**
|
||||
* Try to get class info from Kotlin Metadata annotation
|
||||
*/
|
||||
@Nullable
|
||||
public static ClsAliasPair getClassAlias(ClassNode cls) {
|
||||
IAnnotation metadataAnnotation = cls.getAnnotation(KOTLIN_METADATA_ANNOTATION);
|
||||
List<EncodedValue> d2Param = getParamAsList(metadataAnnotation, KOTLIN_METADATA_D2_PARAMETER);
|
||||
if (d2Param == null || d2Param.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
EncodedValue firstValue = d2Param.get(0);
|
||||
if (firstValue == null || firstValue.getType() != EncodedType.ENCODED_STRING) {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
String rawClassName = ((String) firstValue.getValue()).trim();
|
||||
if (rawClassName.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
String clsName = Utils.cleanObjectName(rawClassName);
|
||||
ClsAliasPair alias = splitAndCheckClsName(cls, clsName);
|
||||
if (alias != null) {
|
||||
RenameReasonAttr.forNode(cls).append("from Kotlin metadata");
|
||||
return alias;
|
||||
}
|
||||
} catch (Exception e) {
|
||||
LOG.error("Failed to parse kotlin metadata", e);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
// Don't use ClassInfo facility to not pollute class into cache
|
||||
private static ClsAliasPair splitAndCheckClsName(ClassNode originCls, String fullClsName) {
|
||||
if (!NameMapper.isValidFullIdentifier(fullClsName)) {
|
||||
return null;
|
||||
}
|
||||
String pkg;
|
||||
String name;
|
||||
int dot = fullClsName.lastIndexOf('.');
|
||||
if (dot == -1) {
|
||||
pkg = "";
|
||||
name = fullClsName;
|
||||
} else {
|
||||
pkg = fullClsName.substring(0, dot);
|
||||
name = fullClsName.substring(dot + 1);
|
||||
}
|
||||
ClassInfo originClsInfo = originCls.getClassInfo();
|
||||
String originName = originClsInfo.getShortName();
|
||||
if (originName.equals(name)
|
||||
|| name.contains("$")
|
||||
|| !NameMapper.isValidIdentifier(name)
|
||||
|| countPkgParts(originClsInfo.getPackage()) != countPkgParts(pkg)
|
||||
|| pkg.startsWith("java.")) {
|
||||
return null;
|
||||
}
|
||||
ClassNode newClsNode = originCls.root().resolveClass(fullClsName);
|
||||
if (newClsNode != null) {
|
||||
// class with alias name already exist
|
||||
return null;
|
||||
}
|
||||
return new ClsAliasPair(pkg, name);
|
||||
}
|
||||
|
||||
private static int countPkgParts(String pkg) {
|
||||
if (pkg.isEmpty()) {
|
||||
return 0;
|
||||
}
|
||||
int count = 1;
|
||||
int pos = 0;
|
||||
while (true) {
|
||||
pos = pkg.indexOf('.', pos);
|
||||
if (pos == -1) {
|
||||
return count;
|
||||
}
|
||||
pos++;
|
||||
count++;
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private static List<EncodedValue> getParamAsList(IAnnotation annotation, String paramName) {
|
||||
if (annotation == null) {
|
||||
return null;
|
||||
}
|
||||
EncodedValue encodedValue = annotation.getValues().get(paramName);
|
||||
if (encodedValue == null || encodedValue.getType() != EncodedType.ENCODED_ARRAY) {
|
||||
return null;
|
||||
}
|
||||
return (List<EncodedValue>) encodedValue.getValue();
|
||||
}
|
||||
}
|
10
jadx-core/src/main/java/jadx/core/utils/log/LogExt.kt
Normal file
10
jadx-core/src/main/java/jadx/core/utils/log/LogExt.kt
Normal file
@ -0,0 +1,10 @@
|
||||
package jadx.core.utils.log
|
||||
|
||||
import org.slf4j.Logger
|
||||
import org.slf4j.LoggerFactory
|
||||
|
||||
inline val <reified T : Any> T.LOG: Logger get() = LoggerFactory.getLogger(T::class.java)
|
||||
|
||||
inline fun <reified T : Any, R> T.runCatchingLog(msg: String? = null, block: () -> R) =
|
||||
runCatching(block)
|
||||
.onFailure { LOG.error(msg.orEmpty(), it) }
|
@ -1,49 +0,0 @@
|
||||
package jadx.tests.integration.deobf;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import jadx.tests.api.SmaliTest;
|
||||
|
||||
import static jadx.tests.api.utils.assertj.JadxAssertions.assertThat;
|
||||
|
||||
public class TestKotlinMetadata extends SmaliTest {
|
||||
// @formatter:off
|
||||
/*
|
||||
@file:JvmName("TestKotlinMetadata")
|
||||
class TestMetaData {
|
||||
|
||||
@JvmField
|
||||
val id = 1
|
||||
|
||||
@JvmName("makeTwo")
|
||||
fun double(x: Int): Int {
|
||||
return 2 * x
|
||||
}
|
||||
}
|
||||
*/
|
||||
// @formatter:on
|
||||
|
||||
@Test
|
||||
public void test() {
|
||||
prepareArgs(true);
|
||||
assertThat(getClassNodeFromSmali())
|
||||
.code()
|
||||
.containsOne("class TestMetaData {")
|
||||
.containsOne("reason: from Kotlin metadata");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIgnoreMetadata() {
|
||||
prepareArgs(false);
|
||||
assertThat(getClassNodeFromSmali())
|
||||
.code()
|
||||
.containsOne("class C0000TestKotlinMetadata {");
|
||||
}
|
||||
|
||||
private void prepareArgs(boolean parseKotlinMetadata) {
|
||||
enableDeobfuscation();
|
||||
args.setDeobfuscationMinLength(100); // rename everything
|
||||
getArgs().setParseKotlinMetadata(parseKotlinMetadata);
|
||||
disableCompilation();
|
||||
}
|
||||
}
|
@ -1,73 +0,0 @@
|
||||
.class public final Ldeobf/TestKotlinMetadata;
|
||||
.super Ljava/lang/Object;
|
||||
.source "TestMetaData.kt"
|
||||
|
||||
|
||||
# annotations
|
||||
.annotation runtime Lkotlin/Metadata;
|
||||
bv = {
|
||||
0x1,
|
||||
0x0,
|
||||
0x3
|
||||
}
|
||||
d1 = {
|
||||
"\u0000\u0014\n\u0002\u0018\u0002\n\u0002\u0010\u0000\n\u0002\u0008\u0002\n\u0002\u0010\u0008\n\u0002\u0008\u0004\u0018\u00002\u00020\u0001B\u0005\u00a2\u0006\u0002\u0010\u0002J\u0015\u0010\u0005\u001a\u00020\u00042\u0006\u0010\u0006\u001a\u00020\u0004H\u0007\u00a2\u0006\u0002\u0008\u0007R\u0010\u0010\u0003\u001a\u00020\u00048\u0006X\u0087D\u00a2\u0006\u0002\n\u0000\u00a8\u0006\u0008"
|
||||
}
|
||||
d2 = {
|
||||
"Ljadx/TestMetaData;",
|
||||
"",
|
||||
"()V",
|
||||
"id",
|
||||
"",
|
||||
"double",
|
||||
"x",
|
||||
"makeTwo",
|
||||
"test"
|
||||
}
|
||||
k = 0x1
|
||||
mv = {
|
||||
0x1,
|
||||
0x4,
|
||||
0x0
|
||||
}
|
||||
.end annotation
|
||||
|
||||
|
||||
# instance fields
|
||||
.field public final id:I
|
||||
.annotation build Lkotlin/jvm/JvmField;
|
||||
.end annotation
|
||||
.end field
|
||||
|
||||
|
||||
# direct methods
|
||||
.method public constructor <init>()V
|
||||
.registers 2
|
||||
|
||||
.prologue
|
||||
.line 4
|
||||
invoke-direct {p0}, Ljava/lang/Object;-><init>()V
|
||||
|
||||
.line 7
|
||||
const/4 v0, 0x1
|
||||
|
||||
iput v0, p0, Ldeobf/TestKotlinMetadata;->id:I
|
||||
|
||||
return-void
|
||||
.end method
|
||||
|
||||
|
||||
# virtual methods
|
||||
.method public final makeTwo(I)I
|
||||
.registers 3
|
||||
.param p1, "x" # I
|
||||
.annotation build Lkotlin/jvm/JvmName;
|
||||
name = "makeTwo"
|
||||
.end annotation
|
||||
|
||||
.prologue
|
||||
.line 11
|
||||
mul-int/lit8 v0, p1, 0x2
|
||||
|
||||
return v0
|
||||
.end method
|
@ -362,10 +362,6 @@ public class JadxSettings extends JadxCLIArgs {
|
||||
this.deobfuscationUseSourceNameAsAlias = deobfuscationUseSourceNameAsAlias;
|
||||
}
|
||||
|
||||
public void setDeobfuscationParseKotlinMetadata(boolean deobfuscationParseKotlinMetadata) {
|
||||
this.deobfuscationParseKotlinMetadata = deobfuscationParseKotlinMetadata;
|
||||
}
|
||||
|
||||
public void setUseKotlinMethodsForVarNames(JadxArgs.UseKotlinMethodsForVarNames useKotlinMethodsForVarNames) {
|
||||
this.useKotlinMethodsForVarNames = useKotlinMethodsForVarNames;
|
||||
}
|
||||
|
@ -322,19 +322,11 @@ public class JadxSettingsWindow extends JDialog {
|
||||
needReload();
|
||||
});
|
||||
|
||||
JCheckBox deobfKotlinMetadata = new JCheckBox();
|
||||
deobfKotlinMetadata.setSelected(settings.isDeobfuscationParseKotlinMetadata());
|
||||
deobfKotlinMetadata.addItemListener(e -> {
|
||||
settings.setDeobfuscationParseKotlinMetadata(e.getStateChange() == ItemEvent.SELECTED);
|
||||
needReload();
|
||||
});
|
||||
|
||||
SettingsGroup group = new SettingsGroup(NLS.str("preferences.rename"));
|
||||
group.addRow(NLS.str("preferences.rename_case"), renameCaseSensitive);
|
||||
group.addRow(NLS.str("preferences.rename_valid"), renameValid);
|
||||
group.addRow(NLS.str("preferences.rename_printable"), renamePrintable);
|
||||
group.addRow(NLS.str("preferences.deobfuscation_source_alias"), deobfSourceAlias);
|
||||
group.addRow(NLS.str("preferences.deobfuscation_kotlin_metadata"), deobfKotlinMetadata);
|
||||
return group;
|
||||
}
|
||||
|
||||
|
@ -204,7 +204,6 @@ preferences.generated_renames_mapping_file_mode=Umgang mit Map-Dateien
|
||||
preferences.deobfuscation_min_len=Minimale Namenlänge
|
||||
preferences.deobfuscation_max_len=Maximale Namenlänge
|
||||
preferences.deobfuscation_source_alias=Quelldateiname als Klassennamen-Alias verwenden
|
||||
preferences.deobfuscation_kotlin_metadata=Kotlin-Metadaten nach Klassen- und Paketnamen analysieren
|
||||
#preferences.deobfuscation_res_name_source=Better resources name source
|
||||
preferences.save=Speichern
|
||||
preferences.cancel=Abbrechen
|
||||
|
@ -204,7 +204,6 @@ preferences.generated_renames_mapping_file_mode=Map file handle mode
|
||||
preferences.deobfuscation_min_len=Minimum name length
|
||||
preferences.deobfuscation_max_len=Maximum name length
|
||||
preferences.deobfuscation_source_alias=Use source file name as class name alias
|
||||
preferences.deobfuscation_kotlin_metadata=Parse Kotlin metadata for class and package names
|
||||
preferences.deobfuscation_res_name_source=Better resources name source
|
||||
preferences.save=Save
|
||||
preferences.cancel=Cancel
|
||||
|
@ -204,7 +204,6 @@ preferences.deobfuscation_on=Activar desobfuscación
|
||||
preferences.deobfuscation_min_len=Longitud mínima del nombre
|
||||
preferences.deobfuscation_max_len=Longitud máxima del nombre
|
||||
preferences.deobfuscation_source_alias=Usar el nombre del source como alias para la clase
|
||||
preferences.deobfuscation_kotlin_metadata=Parse Kotlin metadatos para nombres de clase y paquete
|
||||
#preferences.deobfuscation_res_name_source=Better resources name source
|
||||
preferences.save=Guardar
|
||||
preferences.cancel=Cancelar
|
||||
|
@ -204,7 +204,6 @@ preferences.generated_renames_mapping_file_mode=맵 파일 처리 모드
|
||||
preferences.deobfuscation_min_len=최소 이름 길이
|
||||
preferences.deobfuscation_max_len=최대 이름 길이
|
||||
preferences.deobfuscation_source_alias=소스 파일 이름을 클래스 이름 별칭으로 사용
|
||||
preferences.deobfuscation_kotlin_metadata=클래스 및 패키지 이름에 대한 Kotlin 메타 데이터 파싱
|
||||
preferences.deobfuscation_res_name_source=더 나은 리소스 이름 소스
|
||||
preferences.save=저장
|
||||
preferences.cancel=취소
|
||||
|
@ -204,7 +204,6 @@ preferences.deobfuscation_on=Ativar desofuscação
|
||||
preferences.deobfuscation_min_len=Tamanho mínimo do nome
|
||||
preferences.deobfuscation_max_len=Tamanho máximo do nome
|
||||
preferences.deobfuscation_source_alias=Utilizar nome do arquivo como apelido da classe
|
||||
preferences.deobfuscation_kotlin_metadata=Parsear metadados do kotlin para nome de classes e pacotes
|
||||
preferences.deobfuscation_res_name_source=Melhora nome da fonte dos recursos
|
||||
preferences.save=Salvar
|
||||
preferences.cancel=Cancelar
|
||||
|
@ -204,7 +204,6 @@ preferences.deobfuscation_on=Включить деобфускацию
|
||||
preferences.deobfuscation_min_len=Минимальная длина имени
|
||||
preferences.deobfuscation_max_len=Максимальная длина имени
|
||||
preferences.deobfuscation_source_alias=Иcпользовать атрибут SOURCE
|
||||
preferences.deobfuscation_kotlin_metadata=Использовать метаданные Kotlin
|
||||
preferences.deobfuscation_res_name_source=Расшифровка имен ресурсов
|
||||
preferences.save=Сохранить
|
||||
preferences.cancel=Отмена
|
||||
|
@ -204,7 +204,6 @@ preferences.generated_renames_mapping_file_mode=映射文件句柄模式
|
||||
preferences.deobfuscation_min_len=最小命名长度
|
||||
preferences.deobfuscation_max_len=最大命名长度
|
||||
preferences.deobfuscation_source_alias=使用资源名作为类的别名
|
||||
preferences.deobfuscation_kotlin_metadata=解析Kotlin元数据以获得类名和包名
|
||||
preferences.deobfuscation_res_name_source=更好的资源名称来源
|
||||
preferences.save=保存
|
||||
preferences.cancel=取消
|
||||
|
@ -204,7 +204,6 @@ preferences.generated_renames_mapping_file_mode=Map 檔案處理模式
|
||||
preferences.deobfuscation_min_len=最小名稱長度
|
||||
preferences.deobfuscation_max_len=最大名稱長度
|
||||
preferences.deobfuscation_source_alias=將原始檔案名稱作為類別別名
|
||||
preferences.deobfuscation_kotlin_metadata=剖析 Kotlin 中繼資料來取得類別及套件名稱
|
||||
preferences.deobfuscation_res_name_source=較佳的資源名稱來源
|
||||
preferences.save=儲存
|
||||
preferences.cancel=取消
|
||||
|
@ -22,6 +22,7 @@ public class AccessFlags {
|
||||
public static final int MODULE = 0x8000;
|
||||
public static final int CONSTRUCTOR = 0x10000;
|
||||
public static final int DECLARED_SYNCHRONIZED = 0x20000;
|
||||
public static final int DATA = 0x40000;
|
||||
|
||||
public static boolean hasFlag(int flags, int flagValue) {
|
||||
return (flags & flagValue) != 0;
|
||||
@ -85,6 +86,9 @@ public class AccessFlags {
|
||||
if (hasFlag(flags, ENUM)) {
|
||||
code.append("enum ");
|
||||
}
|
||||
if (hasFlag(flags, DATA)) {
|
||||
code.append("data ");
|
||||
}
|
||||
break;
|
||||
}
|
||||
if (hasFlag(flags, SYNTHETIC)) {
|
||||
|
14
jadx-plugins/jadx-kotlin-metadata/build.gradle.kts
Normal file
14
jadx-plugins/jadx-kotlin-metadata/build.gradle.kts
Normal file
@ -0,0 +1,14 @@
|
||||
plugins {
|
||||
id("jadx-library")
|
||||
}
|
||||
|
||||
dependencies {
|
||||
api(project(":jadx-core"))
|
||||
|
||||
implementation("org.jetbrains.kotlinx:kotlinx-metadata-jvm:0.6.0")
|
||||
|
||||
testImplementation(project(":jadx-core").dependencyProject.sourceSets.test.get().output)
|
||||
testImplementation("org.apache.commons:commons-lang3:3.12.0")
|
||||
|
||||
testRuntimeOnly(project(":jadx-plugins:jadx-smali-input"))
|
||||
}
|
@ -0,0 +1,66 @@
|
||||
package jadx.plugins.kotlin.metadata
|
||||
|
||||
import jadx.api.plugins.options.OptionDescription
|
||||
import jadx.api.plugins.options.impl.BaseOptionsParser
|
||||
import jadx.api.plugins.options.impl.JadxOptionDescription
|
||||
import jadx.plugins.kotlin.metadata.KotlinMetadataPlugin.Companion.PLUGIN_ID
|
||||
|
||||
class KotlinMetadataOptions : BaseOptionsParser() {
|
||||
var isClassAlias: Boolean = true
|
||||
private set
|
||||
var isMethodArgs: Boolean = true
|
||||
private set
|
||||
var isFields: Boolean = true
|
||||
private set
|
||||
var isCompanion: Boolean = true
|
||||
private set
|
||||
var isDataClass: Boolean = true
|
||||
private set
|
||||
var isToString: Boolean = true
|
||||
private set
|
||||
var isGetters: Boolean = true
|
||||
private set
|
||||
|
||||
override fun parseOptions() {
|
||||
isClassAlias = getBooleanOption(CLASS_ALIAS_OPT, true)
|
||||
isMethodArgs = getBooleanOption(METHOD_ARGS_OPT, true)
|
||||
isFields = getBooleanOption(FIELDS_OPT, true)
|
||||
isCompanion = getBooleanOption(COMPANION_OPT, true)
|
||||
isDataClass = getBooleanOption(DATA_CLASS_OPT, true)
|
||||
isToString = getBooleanOption(TO_STRING_OPT, true)
|
||||
isGetters = getBooleanOption(GETTERS_OPT, true)
|
||||
}
|
||||
|
||||
override fun getOptionsDescriptions(): List<OptionDescription> {
|
||||
return listOf(
|
||||
JadxOptionDescription.booleanOption(CLASS_ALIAS_OPT, "rename class alias", true)
|
||||
.withFlag(OptionDescription.OptionFlag.PER_PROJECT),
|
||||
JadxOptionDescription.booleanOption(METHOD_ARGS_OPT, "rename function arguments", true)
|
||||
.withFlag(OptionDescription.OptionFlag.PER_PROJECT),
|
||||
JadxOptionDescription.booleanOption(FIELDS_OPT, "rename fields", true)
|
||||
.withFlag(OptionDescription.OptionFlag.PER_PROJECT),
|
||||
JadxOptionDescription.booleanOption(COMPANION_OPT, "rename companion object", true)
|
||||
.withFlag(OptionDescription.OptionFlag.PER_PROJECT),
|
||||
JadxOptionDescription.booleanOption(DATA_CLASS_OPT, "add data class modifier", true)
|
||||
.withFlag(OptionDescription.OptionFlag.PER_PROJECT),
|
||||
JadxOptionDescription.booleanOption(TO_STRING_OPT, "rename fields using toString", true)
|
||||
.withFlag(OptionDescription.OptionFlag.PER_PROJECT),
|
||||
JadxOptionDescription.booleanOption(GETTERS_OPT, "rename simple getters to field names", true)
|
||||
.withFlag(OptionDescription.OptionFlag.PER_PROJECT),
|
||||
)
|
||||
}
|
||||
|
||||
override fun toString(): String {
|
||||
return "KotlinMetadataOptions(isClassAlias=$isClassAlias, isMethodArgs=$isMethodArgs, isFields=$isFields, isCompanion=$isCompanion, isDataClass=$isDataClass, isToString=$isToString, isGetters=$isGetters)"
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val CLASS_ALIAS_OPT = "$PLUGIN_ID.class-alias"
|
||||
const val METHOD_ARGS_OPT = "$PLUGIN_ID.method-args"
|
||||
const val FIELDS_OPT = "$PLUGIN_ID.fields"
|
||||
const val COMPANION_OPT = "$PLUGIN_ID.companion"
|
||||
const val DATA_CLASS_OPT = "$PLUGIN_ID.data-class"
|
||||
const val TO_STRING_OPT = "$PLUGIN_ID.to-string"
|
||||
const val GETTERS_OPT = "$PLUGIN_ID.getters"
|
||||
}
|
||||
}
|
@ -0,0 +1,27 @@
|
||||
package jadx.plugins.kotlin.metadata
|
||||
|
||||
import jadx.api.plugins.JadxPlugin
|
||||
import jadx.api.plugins.JadxPluginContext
|
||||
import jadx.api.plugins.JadxPluginInfo
|
||||
import jadx.plugins.kotlin.metadata.pass.KotlinMetadataDecompilePass
|
||||
import jadx.plugins.kotlin.metadata.pass.KotlinMetadataPreparePass
|
||||
|
||||
class KotlinMetadataPlugin : JadxPlugin {
|
||||
|
||||
private val options = KotlinMetadataOptions()
|
||||
|
||||
override fun getPluginInfo(): JadxPluginInfo {
|
||||
return JadxPluginInfo(PLUGIN_ID, "Kotlin Metadata", "Use kotlin.Metadata annotation for code generation")
|
||||
}
|
||||
|
||||
override fun init(context: JadxPluginContext) {
|
||||
context.registerOptions(options)
|
||||
context.addPass(KotlinMetadataPreparePass(options))
|
||||
context.addPass(KotlinMetadataDecompilePass(options))
|
||||
context.registerInputsHashSupplier { options.toString() }
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val PLUGIN_ID = "kotlin-metadata"
|
||||
}
|
||||
}
|
@ -0,0 +1,12 @@
|
||||
package jadx.plugins.kotlin.metadata.model
|
||||
|
||||
object KotlinMetadataConsts {
|
||||
const val KOTLIN_METADATA_ANNOTATION = "Lkotlin/Metadata;"
|
||||
const val KOTLIN_METADATA_K_PARAMETER = "k"
|
||||
const val KOTLIN_METADATA_D1_PARAMETER = "d1"
|
||||
const val KOTLIN_METADATA_D2_PARAMETER = "d2"
|
||||
const val KOTLIN_METADATA_MV_PARAMETER = "mv"
|
||||
const val KOTLIN_METADATA_XS_PARAMETER = "xs"
|
||||
const val KOTLIN_METADATA_PN_PARAMETER = "pn"
|
||||
const val KOTLIN_METADATA_XI_PARAMETER = "xi"
|
||||
}
|
@ -0,0 +1,38 @@
|
||||
package jadx.plugins.kotlin.metadata.model
|
||||
|
||||
import jadx.core.dex.instructions.args.RegisterArg
|
||||
import jadx.core.dex.nodes.ClassNode
|
||||
import jadx.core.dex.nodes.FieldNode
|
||||
import jadx.core.dex.nodes.MethodNode
|
||||
|
||||
data class ClassAliasRename(
|
||||
val pkg: String,
|
||||
val name: String,
|
||||
)
|
||||
|
||||
data class MethodArgRename(
|
||||
val rArg: RegisterArg,
|
||||
val alias: String,
|
||||
)
|
||||
|
||||
data class FieldRename(
|
||||
val field: FieldNode,
|
||||
val alias: String,
|
||||
)
|
||||
|
||||
data class CompanionRename(
|
||||
val field: FieldNode,
|
||||
val cls: ClassNode,
|
||||
val hide: Boolean,
|
||||
)
|
||||
|
||||
data class ToStringRename(
|
||||
val cls: ClassNode,
|
||||
val clsAlias: String?,
|
||||
val fields: List<FieldRename>,
|
||||
)
|
||||
|
||||
data class MethodRename(
|
||||
val mth: MethodNode,
|
||||
val alias: String,
|
||||
)
|
@ -0,0 +1,140 @@
|
||||
package jadx.plugins.kotlin.metadata.pass
|
||||
|
||||
import jadx.api.plugins.input.data.AccessFlags
|
||||
import jadx.api.plugins.pass.JadxPassInfo
|
||||
import jadx.api.plugins.pass.impl.OrderedJadxPassInfo
|
||||
import jadx.api.plugins.pass.types.JadxDecompilePass
|
||||
import jadx.core.dex.attributes.AFlag
|
||||
import jadx.core.dex.attributes.nodes.RenameReasonAttr
|
||||
import jadx.core.dex.nodes.ClassNode
|
||||
import jadx.core.dex.nodes.MethodNode
|
||||
import jadx.core.dex.nodes.RootNode
|
||||
import jadx.plugins.kotlin.metadata.KotlinMetadataOptions
|
||||
import jadx.plugins.kotlin.metadata.utils.KmClassWrapper
|
||||
import jadx.plugins.kotlin.metadata.utils.KmClassWrapper.Companion.getWrapper
|
||||
|
||||
class KotlinMetadataDecompilePass(
|
||||
private val options: KotlinMetadataOptions,
|
||||
) : JadxDecompilePass {
|
||||
|
||||
override fun getInfo(): JadxPassInfo {
|
||||
return OrderedJadxPassInfo(
|
||||
"KotlinMetadataDecompile",
|
||||
"Use kotlin.Metadata annotation perform various renames",
|
||||
)
|
||||
.before("CodeRenameVisitor")
|
||||
}
|
||||
|
||||
override fun init(root: RootNode) {
|
||||
}
|
||||
|
||||
override fun visit(cls: ClassNode): Boolean {
|
||||
cls.innerClasses.forEach(::visit)
|
||||
|
||||
val wrapper = cls.getWrapper() ?: return false
|
||||
if (options.isMethodArgs) renameMethodArgs(wrapper)
|
||||
if (options.isFields) renameFields(wrapper)
|
||||
if (options.isCompanion) renameCompanion(wrapper)
|
||||
if (options.isDataClass) fixDataClass(wrapper)
|
||||
if (options.isToString) renameToString(wrapper)
|
||||
if (options.isGetters) renameGetters(wrapper)
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
override fun visit(mth: MethodNode?) { /* no op */
|
||||
}
|
||||
|
||||
private fun renameMethodArgs(wrapper: KmClassWrapper) {
|
||||
val args = wrapper.getMethodArgs()
|
||||
args.forEach { (_, list) ->
|
||||
list.forEach { (rArg, alias) ->
|
||||
// TODO comment not being added ?
|
||||
RenameReasonAttr.forNode(rArg).append(METADATA_REASON)
|
||||
rArg.name = alias
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun renameFields(wrapper: KmClassWrapper) {
|
||||
val fields = wrapper.getFields()
|
||||
fields.forEach { (field, alias) ->
|
||||
if (AFlag.DONT_RENAME !in field) {
|
||||
RenameReasonAttr.forNode(field).append(METADATA_REASON)
|
||||
field.rename(alias)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun renameCompanion(wrapper: KmClassWrapper) {
|
||||
val companion = wrapper.getCompanion()
|
||||
companion?.run {
|
||||
if (AFlag.DONT_RENAME !in field) {
|
||||
RenameReasonAttr.forNode(field).append(METADATA_REASON)
|
||||
field.rename(COMPANION_FIELD)
|
||||
}
|
||||
if (AFlag.DONT_RENAME !in cls) {
|
||||
RenameReasonAttr.forNode(cls).append(METADATA_REASON)
|
||||
cls.rename(COMPANION_CLASS)
|
||||
}
|
||||
|
||||
if (hide) {
|
||||
field.add(AFlag.DONT_GENERATE)
|
||||
cls.add(AFlag.DONT_GENERATE)
|
||||
cls.add(AFlag.DONT_INLINE)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun fixDataClass(wrapper: KmClassWrapper) {
|
||||
val isData = wrapper.isDataClass()
|
||||
wrapper.cls.run {
|
||||
if (isData != accessFlags.isData) {
|
||||
accessFlags = accessFlags.run {
|
||||
if (isData) {
|
||||
add(AccessFlags.DATA)
|
||||
} else {
|
||||
remove(AccessFlags.DATA)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun renameToString(wrapper: KmClassWrapper) {
|
||||
val toString = wrapper.parseToString()
|
||||
toString?.run {
|
||||
clsAlias?.let { alias ->
|
||||
if (AFlag.DONT_RENAME !in cls) {
|
||||
RenameReasonAttr.forNode(cls).append(TO_STRING_REASON)
|
||||
cls.rename(alias)
|
||||
}
|
||||
}
|
||||
|
||||
fields.forEach { (field, alias) ->
|
||||
if (AFlag.DONT_RENAME !in field) {
|
||||
RenameReasonAttr.forNode(field).append(TO_STRING_REASON)
|
||||
field.rename(alias)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun renameGetters(wrapper: KmClassWrapper) {
|
||||
val getters = wrapper.getGetters()
|
||||
getters.forEach { (mth, alias) ->
|
||||
if (AFlag.DONT_RENAME !in mth) {
|
||||
RenameReasonAttr.forNode(mth).append(GETTER_REASON)
|
||||
mth.rename(alias)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val METADATA_REASON = "from kotlin metadata"
|
||||
private const val COMPANION_FIELD = "INSTANCE"
|
||||
private const val COMPANION_CLASS = "Companion"
|
||||
private const val TO_STRING_REASON = "from toString"
|
||||
private const val GETTER_REASON = "from getter"
|
||||
}
|
||||
}
|
@ -0,0 +1,39 @@
|
||||
package jadx.plugins.kotlin.metadata.pass
|
||||
|
||||
import jadx.api.plugins.pass.JadxPassInfo
|
||||
import jadx.api.plugins.pass.impl.OrderedJadxPassInfo
|
||||
import jadx.api.plugins.pass.types.JadxPreparePass
|
||||
import jadx.core.dex.attributes.AFlag
|
||||
import jadx.core.dex.nodes.RootNode
|
||||
import jadx.plugins.kotlin.metadata.KotlinMetadataOptions
|
||||
import jadx.plugins.kotlin.metadata.utils.KotlinMetadataUtils
|
||||
|
||||
class KotlinMetadataPreparePass(
|
||||
private val options: KotlinMetadataOptions,
|
||||
) : JadxPreparePass {
|
||||
|
||||
override fun getInfo(): JadxPassInfo {
|
||||
return OrderedJadxPassInfo(
|
||||
"KotlinMetadataPrepare",
|
||||
"Use kotlin.Metadata annotation to rename class & package",
|
||||
)
|
||||
.before("RenameVisitor")
|
||||
}
|
||||
|
||||
override fun init(root: RootNode) {
|
||||
if (options.isClassAlias) {
|
||||
for (cls in root.classes) {
|
||||
if (cls.contains(AFlag.DONT_RENAME)) {
|
||||
continue
|
||||
}
|
||||
|
||||
// rename class & package
|
||||
val kotlinCls = KotlinMetadataUtils.getAlias(cls)
|
||||
if (kotlinCls != null) {
|
||||
cls.rename(kotlinCls.name)
|
||||
cls.packageNode.rename(kotlinCls.pkg)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,41 @@
|
||||
package jadx.plugins.kotlin.metadata.utils
|
||||
|
||||
import jadx.core.dex.nodes.ClassNode
|
||||
import kotlinx.metadata.KmClass
|
||||
import kotlinx.metadata.jvm.KotlinClassMetadata
|
||||
|
||||
// don't expose kotlinx.metadata.* types ?
|
||||
class KmClassWrapper private constructor(
|
||||
val cls: ClassNode,
|
||||
private val kmCls: KmClass,
|
||||
) {
|
||||
|
||||
fun getMethodArgs() =
|
||||
KotlinMetadataUtils.mapMethodArgs(cls, kmCls)
|
||||
|
||||
fun getFields() =
|
||||
KotlinMetadataUtils.mapFields(cls, kmCls)
|
||||
|
||||
fun getCompanion() =
|
||||
KotlinMetadataUtils.mapCompanion(cls, kmCls)
|
||||
|
||||
fun isDataClass() =
|
||||
KotlinUtils.isDataClass(kmCls)
|
||||
|
||||
// does not require metadata, may be useful for plain java ?
|
||||
fun parseToString() =
|
||||
KotlinUtils.parseToString(cls)
|
||||
|
||||
// does not require metadata, may be useful for plain java ?
|
||||
fun getGetters() =
|
||||
KotlinUtils.findGetters(cls)
|
||||
|
||||
companion object {
|
||||
|
||||
fun ClassNode.getWrapper(): KmClassWrapper? {
|
||||
val metadata = getKotlinClassMetadata()
|
||||
val kmCls = (metadata as? KotlinClassMetadata.Class)?.toKmClass() ?: return null
|
||||
return KmClassWrapper(this, kmCls)
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,10 @@
|
||||
package jadx.plugins.kotlin.metadata.utils
|
||||
|
||||
import kotlinx.metadata.KmFunction
|
||||
import kotlinx.metadata.KmProperty
|
||||
import kotlinx.metadata.jvm.fieldSignature
|
||||
import kotlinx.metadata.jvm.signature
|
||||
|
||||
inline val KmFunction.shortId: String? get() = signature?.asString()
|
||||
|
||||
inline val KmProperty.shortId: String? get() = fieldSignature?.asString()
|
@ -0,0 +1,72 @@
|
||||
@file:Suppress("UNCHECKED_CAST")
|
||||
|
||||
package jadx.plugins.kotlin.metadata.utils
|
||||
|
||||
import jadx.api.plugins.input.data.annotations.EncodedType
|
||||
import jadx.api.plugins.input.data.annotations.EncodedValue
|
||||
import jadx.api.plugins.input.data.annotations.IAnnotation
|
||||
import jadx.core.dex.nodes.ClassNode
|
||||
import jadx.plugins.kotlin.metadata.model.KotlinMetadataConsts
|
||||
import kotlinx.metadata.jvm.KotlinClassMetadata
|
||||
import kotlinx.metadata.jvm.Metadata
|
||||
|
||||
fun ClassNode.getMetadata(): Metadata? {
|
||||
val annotation: IAnnotation? = getAnnotation(KotlinMetadataConsts.KOTLIN_METADATA_ANNOTATION)
|
||||
|
||||
return annotation?.run {
|
||||
val k = getParamAsInt(KotlinMetadataConsts.KOTLIN_METADATA_K_PARAMETER)
|
||||
val mvArray = getParamAsIntArray(KotlinMetadataConsts.KOTLIN_METADATA_MV_PARAMETER)
|
||||
val d1Array = getParamAsStringArray(KotlinMetadataConsts.KOTLIN_METADATA_D1_PARAMETER)
|
||||
val d2Array = getParamAsStringArray(KotlinMetadataConsts.KOTLIN_METADATA_D2_PARAMETER)
|
||||
val xs = getParamAsString(KotlinMetadataConsts.KOTLIN_METADATA_XS_PARAMETER)
|
||||
val pn = getParamAsString(KotlinMetadataConsts.KOTLIN_METADATA_PN_PARAMETER)
|
||||
val xi = getParamAsInt(KotlinMetadataConsts.KOTLIN_METADATA_XI_PARAMETER)
|
||||
|
||||
Metadata(
|
||||
kind = k,
|
||||
metadataVersion = mvArray,
|
||||
data1 = d1Array,
|
||||
data2 = d2Array,
|
||||
extraString = xs,
|
||||
packageName = pn,
|
||||
extraInt = xi,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun IAnnotation.getParamsAsList(paramName: String): List<EncodedValue>? {
|
||||
val encodedValue = values[paramName]
|
||||
?.takeIf { it.type == EncodedType.ENCODED_ARRAY && it.value is List<*> }
|
||||
return encodedValue?.value?.let { it as List<EncodedValue> }
|
||||
}
|
||||
|
||||
private fun IAnnotation.getParamAsStringArray(paramName: String): Array<String>? {
|
||||
return getParamsAsList(paramName)
|
||||
?.map<EncodedValue, Any?>(EncodedValue::getValue)
|
||||
?.onEach { if (it != null && it !is String) /* TODO is this valid ? */ return@onEach }
|
||||
?.map { "$it" }
|
||||
?.toTypedArray()
|
||||
}
|
||||
|
||||
private fun IAnnotation.getParamAsIntArray(paramName: String): IntArray? {
|
||||
return getParamsAsList(paramName)
|
||||
?.map<EncodedValue, Any?>(EncodedValue::getValue)
|
||||
?.map { it as Int }
|
||||
?.toIntArray()
|
||||
}
|
||||
|
||||
private fun IAnnotation.getParamAsInt(paramName: String): Int? {
|
||||
val encodedValue = values[paramName]
|
||||
?.takeIf { it.type == EncodedType.ENCODED_INT && it.value is Int }
|
||||
return encodedValue?.value?.let { it as Int }
|
||||
}
|
||||
|
||||
private fun IAnnotation.getParamAsString(paramName: String): String? {
|
||||
val encodedValue = values[paramName]
|
||||
?.takeIf { it.type == EncodedType.ENCODED_STRING && it.value is String }
|
||||
return encodedValue?.value?.let { it as String }
|
||||
}
|
||||
|
||||
fun ClassNode.getKotlinClassMetadata(): KotlinClassMetadata? {
|
||||
return getMetadata()?.let(KotlinClassMetadata::read)
|
||||
}
|
@ -0,0 +1,143 @@
|
||||
package jadx.plugins.kotlin.metadata.utils
|
||||
|
||||
import jadx.core.deobf.NameMapper
|
||||
import jadx.core.dex.attributes.nodes.RenameReasonAttr
|
||||
import jadx.core.dex.nodes.ClassNode
|
||||
import jadx.core.dex.nodes.MethodNode
|
||||
import jadx.core.utils.Utils
|
||||
import jadx.core.utils.log.LOG
|
||||
import jadx.plugins.kotlin.metadata.model.ClassAliasRename
|
||||
import jadx.plugins.kotlin.metadata.model.CompanionRename
|
||||
import jadx.plugins.kotlin.metadata.model.FieldRename
|
||||
import jadx.plugins.kotlin.metadata.model.MethodArgRename
|
||||
import kotlinx.metadata.KmClass
|
||||
|
||||
object KotlinMetadataUtils {
|
||||
|
||||
@JvmStatic
|
||||
fun getAlias(cls: ClassNode): ClassAliasRename? {
|
||||
val annotation = cls.getMetadata() ?: return null
|
||||
return getClassAlias(cls, annotation)
|
||||
}
|
||||
|
||||
/**
|
||||
* Try to get class info from Kotlin Metadata annotation
|
||||
*/
|
||||
private fun getClassAlias(cls: ClassNode, annotation: Metadata): ClassAliasRename? {
|
||||
val firstValue = annotation.data2.getOrNull(0) ?: return null
|
||||
|
||||
try {
|
||||
val clsName = firstValue.trim()
|
||||
.takeUnless(String::isEmpty)
|
||||
?.let(Utils::cleanObjectName)
|
||||
?: return null
|
||||
|
||||
val alias = splitAndCheckClsName(cls, clsName)
|
||||
if (alias != null) {
|
||||
RenameReasonAttr.forNode(cls).append("from Kotlin metadata")
|
||||
return alias
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
LOG.error("Failed to parse kotlin metadata", e)
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
// Don't use ClassInfo facility to not pollute class into cache
|
||||
private fun splitAndCheckClsName(originCls: ClassNode, fullClsName: String): ClassAliasRename? {
|
||||
if (!NameMapper.isValidFullIdentifier(fullClsName)) {
|
||||
return null
|
||||
}
|
||||
val pkg: String
|
||||
val name: String
|
||||
val dot = fullClsName.lastIndexOf('.')
|
||||
if (dot == -1) {
|
||||
pkg = ""
|
||||
name = fullClsName
|
||||
} else {
|
||||
pkg = fullClsName.substring(0, dot)
|
||||
name = fullClsName.substring(dot + 1)
|
||||
}
|
||||
val originClsInfo = originCls.classInfo
|
||||
val originName = originClsInfo.shortName
|
||||
if (originName == name || name.contains("$") ||
|
||||
!NameMapper.isValidIdentifier(name) ||
|
||||
countPkgParts(originClsInfo.getPackage()) != countPkgParts(pkg) || pkg.startsWith("java.")
|
||||
) {
|
||||
return null
|
||||
}
|
||||
val newClsNode = originCls.root().resolveClass(fullClsName)
|
||||
return if (newClsNode != null) {
|
||||
// class with alias name already exist
|
||||
null
|
||||
} else {
|
||||
ClassAliasRename(pkg, name)
|
||||
}
|
||||
}
|
||||
|
||||
private fun countPkgParts(pkg: String): Int {
|
||||
if (pkg.isEmpty()) {
|
||||
return 0
|
||||
}
|
||||
var count = 1
|
||||
var pos = 0
|
||||
while (true) {
|
||||
pos = pkg.indexOf('.', pos)
|
||||
if (pos == -1) {
|
||||
return count
|
||||
}
|
||||
pos++
|
||||
count++
|
||||
}
|
||||
}
|
||||
|
||||
fun mapMethodArgs(cls: ClassNode, kmCls: KmClass): Map<MethodNode, List<MethodArgRename>> {
|
||||
return buildMap {
|
||||
kmCls.functions.forEach { kmFunction ->
|
||||
val node: MethodNode = cls.searchMethodByShortId(kmFunction.shortId) ?: return@forEach
|
||||
|
||||
val argCount = node.argTypes.size
|
||||
val paramCount = kmFunction.valueParameters.size
|
||||
if (argCount == paramCount) {
|
||||
// requires arg registers to be loaded, is this necessary ?
|
||||
val aliasList = node.argRegs.zip(kmFunction.valueParameters).map { (rArg, kmValueParameter) ->
|
||||
MethodArgRename(rArg = rArg, alias = kmValueParameter.name)
|
||||
}
|
||||
put(node, aliasList)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun mapFields(cls: ClassNode, kmCls: KmClass): List<FieldRename> {
|
||||
return kmCls.properties.mapNotNull { kmProperty ->
|
||||
val node = cls.searchFieldByShortId(kmProperty.shortId) ?: return@mapNotNull null
|
||||
FieldRename(field = node, alias = kmProperty.name)
|
||||
}
|
||||
}
|
||||
|
||||
fun mapCompanion(cls: ClassNode, kmCls: KmClass): CompanionRename? {
|
||||
val compName = kmCls.companionObject ?: return null
|
||||
val compField = cls.fields.firstOrNull {
|
||||
it.name == compName && it.accessFlags.run { isStatic && isFinal && isPublic }
|
||||
} ?: return null
|
||||
|
||||
if (compField.type.isObject) {
|
||||
val compType = compField.type.`object`
|
||||
val compCls = cls.innerClasses.firstOrNull {
|
||||
it.classInfo.makeRawFullName() == compType
|
||||
} ?: return null
|
||||
|
||||
val isOnlyInit = compField.useIn.size == 1 && compField.useIn[0].methodInfo.isClassInit
|
||||
val isEmpty = compCls.run { methods.all { it.isConstructor } && fields.isEmpty() }
|
||||
|
||||
return CompanionRename(
|
||||
field = compField,
|
||||
cls = compCls,
|
||||
hide = isOnlyInit && isEmpty,
|
||||
)
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
}
|
@ -0,0 +1,96 @@
|
||||
package jadx.plugins.kotlin.metadata.utils
|
||||
|
||||
import jadx.core.Consts
|
||||
import jadx.core.dex.info.FieldInfo
|
||||
import jadx.core.dex.instructions.IndexInsnNode
|
||||
import jadx.core.dex.instructions.InsnType
|
||||
import jadx.core.dex.instructions.InvokeNode
|
||||
import jadx.core.dex.instructions.args.PrimitiveType
|
||||
import jadx.core.dex.nodes.ClassNode
|
||||
import jadx.core.dex.nodes.FieldNode
|
||||
import jadx.core.dex.nodes.MethodNode
|
||||
import jadx.plugins.kotlin.metadata.model.MethodRename
|
||||
import jadx.plugins.kotlin.metadata.model.ToStringRename
|
||||
import kotlinx.metadata.Flag
|
||||
import kotlinx.metadata.KmClass
|
||||
import java.util.Locale
|
||||
|
||||
object KotlinUtils {
|
||||
|
||||
fun isDataClass(kmCls: KmClass): Boolean {
|
||||
return Flag.Class.IS_DATA(kmCls.flags)
|
||||
}
|
||||
|
||||
fun parseToString(cls: ClassNode): ToStringRename? {
|
||||
val mthToString = cls.searchMethodByShortId(Consts.MTH_TOSTRING_SIGNATURE)
|
||||
?: return null
|
||||
|
||||
return ToStringParser.parse(mthToString)
|
||||
}
|
||||
|
||||
fun findGetters(cls: ClassNode): List<MethodRename> {
|
||||
return cls.fields.filter(FieldNode::isInstance).mapNotNull { field ->
|
||||
val mth = getFieldGetterMethod(cls, field.fieldInfo)
|
||||
?: return@mapNotNull null
|
||||
MethodRename(
|
||||
mth = mth,
|
||||
alias = getGetterAlias(field.alias),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun getFieldGetterMethod(cls: ClassNode, field: FieldInfo): MethodNode? {
|
||||
return cls.methods.firstOrNull {
|
||||
it.returnType == field.type &&
|
||||
it.argTypes.isEmpty() &&
|
||||
it.insnsCount == 3 &&
|
||||
it.sVars.size == 2 &&
|
||||
(it.sVars[1].assignInsn as? IndexInsnNode)?.index == field
|
||||
}
|
||||
}
|
||||
|
||||
private fun getGetterAlias(fieldAlias: String): String {
|
||||
val capitalized = fieldAlias.replaceFirstChar {
|
||||
if (it.isLowerCase()) it.titlecase(Locale.ROOT) else it.toString()
|
||||
}
|
||||
return "get$capitalized"
|
||||
}
|
||||
|
||||
// untested & overly complicated
|
||||
fun parseDefaultMethods(cls: ClassNode): List<MethodRename> {
|
||||
val possibleMthList = cls.methods.filter {
|
||||
it.accessFlags.isStatic && it.accessFlags.isSynthetic &&
|
||||
it.argTypes.run {
|
||||
size > 3 &&
|
||||
first().isObject && first().`object` == cls.fullName &&
|
||||
get(size - 2).isPrimitive && get(size - 2).primitiveType == PrimitiveType.INT &&
|
||||
last().isObject && last().`object` == Consts.CLASS_OBJECT
|
||||
}
|
||||
}
|
||||
val insnList = possibleMthList.filter {
|
||||
it.exitBlock.run {
|
||||
iDom != null && iDom.instructions.firstOrNull()?.type == InsnType.RETURN
|
||||
iDom.iDom != null
|
||||
} &&
|
||||
it.exitBlock.iDom.iDom.run {
|
||||
instructions.firstOrNull() is InvokeNode
|
||||
}
|
||||
}
|
||||
|
||||
val remapped = insnList.mapNotNull {
|
||||
val insn = it.exitBlock.iDom.iDom.instructions.first() as InvokeNode
|
||||
cls.searchMethodByShortId(insn.callMth.shortId)?.run { it to this }
|
||||
}
|
||||
|
||||
return remapped.map { (defaultMethod, originalMethod) ->
|
||||
MethodRename(
|
||||
mth = defaultMethod,
|
||||
alias = getDefaultMethodAlias(originalMethod.alias),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun getDefaultMethodAlias(alias: String): String {
|
||||
return "$alias\$default"
|
||||
}
|
||||
}
|
@ -0,0 +1,147 @@
|
||||
package jadx.plugins.kotlin.metadata.utils
|
||||
|
||||
import jadx.core.Consts
|
||||
import jadx.core.dex.info.FieldInfo
|
||||
import jadx.core.dex.instructions.ConstStringNode
|
||||
import jadx.core.dex.instructions.IndexInsnNode
|
||||
import jadx.core.dex.instructions.InsnType
|
||||
import jadx.core.dex.instructions.InvokeNode
|
||||
import jadx.core.dex.instructions.InvokeType
|
||||
import jadx.core.dex.instructions.args.InsnWrapArg
|
||||
import jadx.core.dex.instructions.args.RegisterArg
|
||||
import jadx.core.dex.instructions.mods.ConstructorInsn
|
||||
import jadx.core.dex.nodes.BlockNode
|
||||
import jadx.core.dex.nodes.InsnNode
|
||||
import jadx.core.dex.nodes.MethodNode
|
||||
import jadx.core.utils.BlockUtils
|
||||
import jadx.core.utils.log.LOG
|
||||
import jadx.plugins.kotlin.metadata.model.FieldRename
|
||||
import jadx.plugins.kotlin.metadata.model.ToStringRename
|
||||
|
||||
class ToStringParser private constructor(mthToString: MethodNode) {
|
||||
private var isStarted = false
|
||||
private var isFirstProcessed = false
|
||||
private var isFinished = false
|
||||
private var pendingAlias: String? = null
|
||||
private var clsAlias: String? = null
|
||||
private val list: MutableList<Pair<String, FieldInfo>> = mutableListOf()
|
||||
val isSuccess: Boolean get() = isStarted && isFinished
|
||||
|
||||
init {
|
||||
val blocks: List<BlockNode> = BlockUtils.buildSimplePath(mthToString.enterBlock)
|
||||
blocks.forEach { block ->
|
||||
block.instructions.forEach { insn ->
|
||||
process(insn)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun process(insn: InsnNode) {
|
||||
if (!isStarted) {
|
||||
isStarted = isStartStringBuilder(insn)
|
||||
return
|
||||
}
|
||||
if (isFinished) {
|
||||
return
|
||||
}
|
||||
|
||||
if (isAppendInvoke(insn)) {
|
||||
val arg = insn.getArg(1)
|
||||
|
||||
// invoke with const string
|
||||
if (arg.isInsnWrap && arg is InsnWrapArg && arg.wrapInsn.type == InsnType.CONST_STR) {
|
||||
val constStr: String? = (arg.wrapInsn as ConstStringNode).string
|
||||
handleString(requireNotNull(constStr) { "Failed to get const String" })
|
||||
}
|
||||
|
||||
// invoke with register
|
||||
if (arg.isRegister && arg is RegisterArg) {
|
||||
val assign = arg.sVar.assignInsn
|
||||
// basic argument
|
||||
if (assign is IndexInsnNode) {
|
||||
val info: FieldInfo? = (arg.sVar.assignInsn as IndexInsnNode).index as? FieldInfo
|
||||
handleFieldInfo(requireNotNull(info) { "Failed to get FieldInfo from index" })
|
||||
}
|
||||
|
||||
// string formatted argument, for rare cases like Arrays.toString(...)
|
||||
if (assign is InvokeNode && assign.invokeType == InvokeType.STATIC && assign.argsCount == 1) {
|
||||
val prevArg = assign.getArg(0)
|
||||
if (prevArg.isRegister && prevArg is RegisterArg) {
|
||||
if (prevArg.sVar.assignInsn is IndexInsnNode) {
|
||||
val info: FieldInfo? = (prevArg.sVar.assignInsn as IndexInsnNode).index as? FieldInfo
|
||||
handleFieldInfo(requireNotNull(info) { "Failed to get nested FieldInfo from index" })
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
isFinished = isToString(insn)
|
||||
}
|
||||
|
||||
private fun handleString(string: String) {
|
||||
if (pendingAlias != null) {
|
||||
LOG.warn("Skipping pending alias: '$pendingAlias'")
|
||||
}
|
||||
if (!isFirstProcessed) {
|
||||
clsAlias = string.substringBefore('(')
|
||||
pendingAlias = string
|
||||
.substringAfter('(')
|
||||
.substringBeforeLast('=')
|
||||
isFirstProcessed = true
|
||||
} else {
|
||||
pendingAlias = string
|
||||
.substringAfter(", ")
|
||||
.substringBeforeLast('=')
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleFieldInfo(fieldInfo: FieldInfo) {
|
||||
list.add(requireNotNull(pendingAlias) { "No pending alias found" } to fieldInfo)
|
||||
pendingAlias = null
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
||||
fun parse(mth: MethodNode): ToStringRename? {
|
||||
val parser =
|
||||
kotlin.runCatching { ToStringParser(mth) }.getOrNull()
|
||||
if (parser?.isSuccess != true) return null
|
||||
|
||||
val cls = mth.parentClass
|
||||
return ToStringRename(
|
||||
cls = cls,
|
||||
clsAlias = parser.clsAlias,
|
||||
fields = parser.list.mapNotNull { (alias, fieldInfo) ->
|
||||
val field = cls.searchField(fieldInfo)
|
||||
?: return@mapNotNull null
|
||||
FieldRename(
|
||||
field = field,
|
||||
alias = alias,
|
||||
)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
private fun isStartStringBuilder(inst: InsnNode): Boolean {
|
||||
return inst is ConstructorInsn &&
|
||||
inst.isNewInstance &&
|
||||
inst.callMth.declClass.fullName == Consts.CLASS_STRING_BUILDER
|
||||
}
|
||||
|
||||
private fun isAppendInvoke(inst: InsnNode): Boolean {
|
||||
return inst is InvokeNode &&
|
||||
inst.callMth.declClass.fullName == Consts.CLASS_STRING_BUILDER &&
|
||||
inst.callMth.name == "append" &&
|
||||
inst.argsCount == 2
|
||||
}
|
||||
|
||||
private fun isToString(inst: InsnNode): Boolean {
|
||||
return inst is InvokeNode &&
|
||||
inst.callMth.declClass.fullName == Consts.CLASS_STRING_BUILDER &&
|
||||
inst.callMth.shortId == Consts.MTH_TOSTRING_SIGNATURE
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1 @@
|
||||
jadx.plugins.kotlin.metadata.KotlinMetadataPlugin
|
@ -0,0 +1,165 @@
|
||||
package jadx.plugins.kotlin.metadata.tests
|
||||
|
||||
import jadx.plugins.kotlin.metadata.KotlinMetadataOptions.Companion.CLASS_ALIAS_OPT
|
||||
import jadx.plugins.kotlin.metadata.KotlinMetadataOptions.Companion.COMPANION_OPT
|
||||
import jadx.plugins.kotlin.metadata.KotlinMetadataOptions.Companion.DATA_CLASS_OPT
|
||||
import jadx.plugins.kotlin.metadata.KotlinMetadataOptions.Companion.FIELDS_OPT
|
||||
import jadx.plugins.kotlin.metadata.KotlinMetadataOptions.Companion.GETTERS_OPT
|
||||
import jadx.plugins.kotlin.metadata.KotlinMetadataOptions.Companion.METHOD_ARGS_OPT
|
||||
import jadx.plugins.kotlin.metadata.KotlinMetadataOptions.Companion.TO_STRING_OPT
|
||||
import jadx.tests.api.SmaliTest
|
||||
import jadx.tests.api.utils.assertj.JadxAssertions.assertThat
|
||||
import jadx.tests.api.utils.assertj.JadxCodeAssertions
|
||||
import org.junit.jupiter.api.Test
|
||||
|
||||
class TestKotlinMetadata : SmaliTest() {
|
||||
// @formatter:off
|
||||
/*
|
||||
package deobf
|
||||
|
||||
data class DataClassSample(
|
||||
val name: String,
|
||||
private val id: Int,
|
||||
) {
|
||||
var inner: Short = 3
|
||||
|
||||
companion object {
|
||||
fun getTag(): String {
|
||||
return "TAG"
|
||||
}
|
||||
}
|
||||
}
|
||||
*/
|
||||
// @formatter:on
|
||||
|
||||
@Test
|
||||
fun testMethodArgs() {
|
||||
setupArgs { this[METHOD_ARGS_OPT] = true }
|
||||
assertThatClass()
|
||||
.containsOne("public boolean equals(Object other) {")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testIgnoreMethodArgs() {
|
||||
setupArgs()
|
||||
assertThatClass()
|
||||
.containsOne("public boolean equals(Object obj) {")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testFields() {
|
||||
setupArgs { this[FIELDS_OPT] = true }
|
||||
assertThatClass()
|
||||
.containsOne("private final String name;")
|
||||
.containsOne("private final int id;")
|
||||
.containsOne("private short inner;")
|
||||
.countString(3, "reason: from kotlin metadata")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testIgnoreFields() {
|
||||
setupArgs()
|
||||
assertThatClass()
|
||||
.containsOne("private final String a;")
|
||||
.containsOne("private final int b;")
|
||||
.containsOne("private short c;")
|
||||
.countString(0, "reason: from kotlin metadata")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testCompanion() {
|
||||
setupArgs { this[COMPANION_OPT] = true }
|
||||
assertThatClass()
|
||||
.containsOne("public static final Companion INSTANCE = new Companion(null);")
|
||||
.containsOne("public static final class Companion {")
|
||||
.countString(2, "reason: from kotlin metadata")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testIgnoreCompanion() {
|
||||
setupArgs()
|
||||
assertThatClass()
|
||||
.containsOne("public static final b d = new b(null);")
|
||||
.containsOne("public static final class b {")
|
||||
.countString(0, "reason: from kotlin metadata")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testDataClass() {
|
||||
setupArgs { this[DATA_CLASS_OPT] = true }
|
||||
assertThatClass()
|
||||
.containsOne("/* data */")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testIgnoreDataClass() {
|
||||
setupArgs()
|
||||
assertThatClass()
|
||||
.countString(0, "/* data */")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testToString() {
|
||||
setupArgs { this[TO_STRING_OPT] = true }
|
||||
assertThatClass()
|
||||
.containsOne("public final class DataClassSample {")
|
||||
.containsOne("private final String name;")
|
||||
.containsOne("private final int id;")
|
||||
.countString(3, "reason: from toString")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testIgnoreToString() {
|
||||
setupArgs()
|
||||
assertThatClass()
|
||||
.containsOne("public final class a {")
|
||||
.containsOne("private final String a;")
|
||||
.containsOne("private final int b;")
|
||||
.countString(0, "reason: from toString")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testGetters() {
|
||||
setupArgs { this[GETTERS_OPT] = true }
|
||||
assertThatClass()
|
||||
.containsOne("public final String getA() {")
|
||||
.countString(1, "reason: from getter")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testGettersAlias() {
|
||||
setupArgs {
|
||||
this[FIELDS_OPT] = true
|
||||
this[GETTERS_OPT] = true
|
||||
}
|
||||
assertThatClass()
|
||||
.containsOne("public final String getName() {")
|
||||
.countString(1, "reason: from getter")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testIgnoreGetters() {
|
||||
setupArgs()
|
||||
assertThatClass()
|
||||
.countString(0, "reason: from getter")
|
||||
}
|
||||
|
||||
private fun setupArgs(builder: MutableMap<String, Boolean>.() -> Unit = {}) {
|
||||
val allOff = mutableMapOf(
|
||||
CLASS_ALIAS_OPT to false,
|
||||
METHOD_ARGS_OPT to false,
|
||||
FIELDS_OPT to false,
|
||||
COMPANION_OPT to false,
|
||||
DATA_CLASS_OPT to false,
|
||||
TO_STRING_OPT to false,
|
||||
GETTERS_OPT to false,
|
||||
)
|
||||
args.pluginOptions = allOff.apply(builder).mapValues {
|
||||
if (it.value) "yes" else "no"
|
||||
}
|
||||
}
|
||||
|
||||
private fun assertThatClass(): JadxCodeAssertions =
|
||||
assertThat(getClassNodeFromSmaliFiles("deobf", "TestKotlinMetadata", "a"))
|
||||
.code()
|
||||
}
|
@ -0,0 +1,65 @@
|
||||
.class public final Ldeobf/a$b;
|
||||
.super Ljava/lang/Object;
|
||||
.source "SourceFile"
|
||||
|
||||
|
||||
# annotations
|
||||
.annotation system Ldalvik/annotation/EnclosingClass;
|
||||
value = Ldeobf/a;
|
||||
.end annotation
|
||||
|
||||
.annotation system Ldalvik/annotation/InnerClass;
|
||||
accessFlags = 0x19
|
||||
name = "b"
|
||||
.end annotation
|
||||
|
||||
|
||||
.annotation runtime Lkotlin/Metadata;
|
||||
d1 = {
|
||||
"\u0000\u0010\n\u0002\u0018\u0002\n\u0002\u0010\u0000\n\u0002\u0010\u000e\n\u0002\u0008\u0004\u0008\u0086\u0003\u0018\u00002\u00020\u0001B\t\u0008\u0002\u00a2\u0006\u0004\u0008\u0004\u0010\u0005J\u0006\u0010\u0003\u001a\u00020\u0002\u00a8\u0006\u0006"
|
||||
}
|
||||
d2 = {
|
||||
"Ldeobf/DataClassSample$Companion;",
|
||||
"",
|
||||
"",
|
||||
"a",
|
||||
"<init>",
|
||||
"()V",
|
||||
"app_release"
|
||||
}
|
||||
k = 0x1
|
||||
mv = {
|
||||
0x1,
|
||||
0x8,
|
||||
0x0
|
||||
}
|
||||
.end annotation
|
||||
|
||||
|
||||
# direct methods
|
||||
.method private constructor <init>()V
|
||||
.registers 1
|
||||
|
||||
invoke-direct {p0}, Ljava/lang/Object;-><init>()V
|
||||
|
||||
return-void
|
||||
.end method
|
||||
|
||||
.method public synthetic constructor <init>(Lkotlin/jvm/internal/DefaultConstructorMarker;)V
|
||||
.registers 2
|
||||
|
||||
.line 1
|
||||
invoke-direct {p0}, Ldeobf/a$b;-><init>()V
|
||||
|
||||
return-void
|
||||
.end method
|
||||
|
||||
|
||||
# virtual methods
|
||||
.method public final a()Ljava/lang/String;
|
||||
.registers 2
|
||||
|
||||
const-string v0, "TAG"
|
||||
|
||||
return-object v0
|
||||
.end method
|
@ -0,0 +1,216 @@
|
||||
.class public final Ldeobf/a;
|
||||
.super Ljava/lang/Object;
|
||||
.source "SourceFile"
|
||||
|
||||
|
||||
# annotations
|
||||
.annotation system Ldalvik/annotation/MemberClasses;
|
||||
value = {
|
||||
Ldeobf/a$b;
|
||||
}
|
||||
.end annotation
|
||||
|
||||
.annotation runtime Lkotlin/Metadata;
|
||||
d1 = {
|
||||
"\u0000&\n\u0002\u0018\u0002\n\u0002\u0010\u0000\n\u0002\u0010\u000e\n\u0000\n\u0002\u0010\u0008\n\u0002\u0008\u0002\n\u0002\u0010\u000b\n\u0002\u0008\u0008\n\u0002\u0010\n\n\u0002\u0008\u000b\u0008\u0086\u0008\u0018\u0000 \u00192\u00020\u0001:\u0001\u001aB\u0017\u0012\u0006\u0010\u000c\u001a\u00020\u0002\u0012\u0006\u0010\u000f\u001a\u00020\u0004\u00a2\u0006\u0004\u0008\u0017\u0010\u0018J\t\u0010\u0003\u001a\u00020\u0002H\u00d6\u0001J\t\u0010\u0005\u001a\u00020\u0004H\u00d6\u0001J\u0013\u0010\u0008\u001a\u00020\u00072\u0008\u0010\u0006\u001a\u0004\u0018\u00010\u0001H\u00d6\u0003R\u0017\u0010\u000c\u001a\u00020\u00028\u0006\u00a2\u0006\u000c\n\u0004\u0008\t\u0010\n\u001a\u0004\u0008\t\u0010\u000bR\u0014\u0010\u000f\u001a\u00020\u00048\u0002X\u0082\u0004\u00a2\u0006\u0006\n\u0004\u0008\r\u0010\u000eR\"\u0010\u0016\u001a\u00020\u00108\u0006@\u0006X\u0086\u000e\u00a2\u0006\u0012\n\u0004\u0008\u0011\u0010\u0012\u001a\u0004\u0008\u0013\u0010\u0014\"\u0004\u0008\r\u0010\u0015\u00a8\u0006\u001b"
|
||||
}
|
||||
d2 = {
|
||||
"Ldeobf/DataClassSample;",
|
||||
"",
|
||||
"",
|
||||
"toString",
|
||||
"",
|
||||
"hashCode",
|
||||
"other",
|
||||
"",
|
||||
"equals",
|
||||
"a",
|
||||
"Ljava/lang/String;",
|
||||
"()Ljava/lang/String;",
|
||||
"name",
|
||||
"b",
|
||||
"I",
|
||||
"id",
|
||||
"",
|
||||
"c",
|
||||
"S",
|
||||
"getInner",
|
||||
"()S",
|
||||
"(S)V",
|
||||
"inner",
|
||||
"<init>",
|
||||
"(Ljava/lang/String;I)V",
|
||||
"d",
|
||||
"Companion",
|
||||
"app_release"
|
||||
}
|
||||
k = 0x1
|
||||
mv = {
|
||||
0x1,
|
||||
0x8,
|
||||
0x0
|
||||
}
|
||||
.end annotation
|
||||
|
||||
# static fields
|
||||
.field public static final d:Ldeobf/a$b;
|
||||
|
||||
|
||||
# instance fields
|
||||
.field private final a:Ljava/lang/String;
|
||||
|
||||
.field private final b:I
|
||||
|
||||
.field private c:S
|
||||
|
||||
|
||||
# direct methods
|
||||
.method static constructor <clinit>()V
|
||||
.registers 2
|
||||
|
||||
new-instance v0, Ldeobf/a$b;
|
||||
|
||||
const/4 v1, 0x0
|
||||
|
||||
invoke-direct {v0, v1}, Ldeobf/a$b;-><init>(Lkotlin/jvm/internal/DefaultConstructorMarker;)V
|
||||
|
||||
sput-object v0, Ldeobf/a;->d:Ldeobf/a$b;
|
||||
|
||||
return-void
|
||||
.end method
|
||||
|
||||
.method public constructor <init>(Ljava/lang/String;I)V
|
||||
.registers 4
|
||||
|
||||
const-string v0, "name"
|
||||
|
||||
invoke-static {p1, v0}, Lkotlin/jvm/internal/Intrinsics;->checkNotNullParameter(Ljava/lang/Object;Ljava/lang/String;)V
|
||||
|
||||
invoke-direct {p0}, Ljava/lang/Object;-><init>()V
|
||||
|
||||
iput-object p1, p0, Ldeobf/a;->a:Ljava/lang/String;
|
||||
|
||||
iput p2, p0, Ldeobf/a;->b:I
|
||||
|
||||
const/4 p1, 0x3
|
||||
|
||||
iput-short p1, p0, Ldeobf/a;->c:S
|
||||
|
||||
return-void
|
||||
.end method
|
||||
|
||||
|
||||
# virtual methods
|
||||
.method public final a()Ljava/lang/String;
|
||||
.registers 2
|
||||
|
||||
iget-object v0, p0, Ldeobf/a;->a:Ljava/lang/String;
|
||||
|
||||
return-object v0
|
||||
.end method
|
||||
|
||||
.method public final b(S)V
|
||||
.registers 2
|
||||
|
||||
iput-short p1, p0, Ldeobf/a;->c:S
|
||||
|
||||
return-void
|
||||
.end method
|
||||
|
||||
.method public equals(Ljava/lang/Object;)Z
|
||||
.registers 6
|
||||
|
||||
const/4 v0, 0x1
|
||||
|
||||
if-ne p0, p1, :cond_4
|
||||
|
||||
return v0
|
||||
|
||||
:cond_4
|
||||
instance-of v1, p1, Ldeobf/a;
|
||||
|
||||
const/4 v2, 0x0
|
||||
|
||||
if-nez v1, :cond_a
|
||||
|
||||
return v2
|
||||
|
||||
:cond_a
|
||||
check-cast p1, Ldeobf/a;
|
||||
|
||||
iget-object v1, p0, Ldeobf/a;->a:Ljava/lang/String;
|
||||
|
||||
iget-object v3, p1, Ldeobf/a;->a:Ljava/lang/String;
|
||||
|
||||
invoke-static {v1, v3}, Lkotlin/jvm/internal/Intrinsics;->areEqual(Ljava/lang/Object;Ljava/lang/Object;)Z
|
||||
|
||||
move-result v1
|
||||
|
||||
if-nez v1, :cond_17
|
||||
|
||||
return v2
|
||||
|
||||
:cond_17
|
||||
iget v1, p0, Ldeobf/a;->b:I
|
||||
|
||||
iget p1, p1, Ldeobf/a;->b:I
|
||||
|
||||
if-eq v1, p1, :cond_1e
|
||||
|
||||
return v2
|
||||
|
||||
:cond_1e
|
||||
return v0
|
||||
.end method
|
||||
|
||||
.method public hashCode()I
|
||||
.registers 3
|
||||
|
||||
iget-object v0, p0, Ldeobf/a;->a:Ljava/lang/String;
|
||||
|
||||
invoke-virtual {v0}, Ljava/lang/String;->hashCode()I
|
||||
|
||||
move-result v0
|
||||
|
||||
mul-int/lit8 v0, v0, 0x1f
|
||||
|
||||
iget v1, p0, Ldeobf/a;->b:I
|
||||
|
||||
add-int/2addr v0, v1
|
||||
|
||||
return v0
|
||||
.end method
|
||||
|
||||
.method public toString()Ljava/lang/String;
|
||||
.registers 5
|
||||
|
||||
iget-object v0, p0, Ldeobf/a;->a:Ljava/lang/String;
|
||||
|
||||
iget v1, p0, Ldeobf/a;->b:I
|
||||
|
||||
new-instance v2, Ljava/lang/StringBuilder;
|
||||
|
||||
invoke-direct {v2}, Ljava/lang/StringBuilder;-><init>()V
|
||||
|
||||
const-string v3, "DataClassSample(name="
|
||||
|
||||
invoke-virtual {v2, v3}, Ljava/lang/StringBuilder;->append(Ljava/lang/String;)Ljava/lang/StringBuilder;
|
||||
|
||||
invoke-virtual {v2, v0}, Ljava/lang/StringBuilder;->append(Ljava/lang/String;)Ljava/lang/StringBuilder;
|
||||
|
||||
const-string v0, ", id="
|
||||
|
||||
invoke-virtual {v2, v0}, Ljava/lang/StringBuilder;->append(Ljava/lang/String;)Ljava/lang/StringBuilder;
|
||||
|
||||
invoke-virtual {v2, v1}, Ljava/lang/StringBuilder;->append(I)Ljava/lang/StringBuilder;
|
||||
|
||||
const-string v0, ")"
|
||||
|
||||
invoke-virtual {v2, v0}, Ljava/lang/StringBuilder;->append(Ljava/lang/String;)Ljava/lang/StringBuilder;
|
||||
|
||||
invoke-virtual {v2}, Ljava/lang/StringBuilder;->toString()Ljava/lang/String;
|
||||
|
||||
move-result-object v0
|
||||
|
||||
return-object v0
|
||||
.end method
|
@ -15,6 +15,7 @@ include("jadx-plugins:jadx-raung-input")
|
||||
include("jadx-plugins:jadx-smali-input")
|
||||
include("jadx-plugins:jadx-java-convert")
|
||||
include("jadx-plugins:jadx-rename-mappings")
|
||||
include("jadx-plugins:jadx-kotlin-metadata")
|
||||
|
||||
include("jadx-plugins:jadx-script:jadx-script-plugin")
|
||||
include("jadx-plugins:jadx-script:jadx-script-runtime")
|
||||
|
Loading…
Reference in New Issue
Block a user