diff --git a/jadx-core/src/main/java/jadx/api/JadxArgs.java b/jadx-core/src/main/java/jadx/api/JadxArgs.java index 0d678a770..7957224eb 100644 --- a/jadx-core/src/main/java/jadx/api/JadxArgs.java +++ b/jadx-core/src/main/java/jadx/api/JadxArgs.java @@ -4,7 +4,6 @@ import java.io.Closeable; import java.io.File; import java.nio.file.Path; import java.util.ArrayList; -import java.util.Collections; import java.util.EnumSet; import java.util.HashMap; import java.util.HashSet; @@ -245,8 +244,12 @@ public class JadxArgs implements Closeable { return inputFiles; } + public void addInputFile(File inputFile) { + this.inputFiles.add(inputFile); + } + public void setInputFile(File inputFile) { - this.inputFiles = Collections.singletonList(inputFile); + addInputFile(inputFile); } public void setInputFiles(List inputFiles) { diff --git a/jadx-core/src/main/java/jadx/core/dex/info/ClassInfo.java b/jadx-core/src/main/java/jadx/core/dex/info/ClassInfo.java index 8df8fca43..d2122b4bb 100644 --- a/jadx-core/src/main/java/jadx/core/dex/info/ClassInfo.java +++ b/jadx-core/src/main/java/jadx/core/dex/info/ClassInfo.java @@ -23,9 +23,9 @@ public final class ClassInfo implements Comparable { @Nullable private ClassAliasInfo alias; - private ClassInfo(RootNode root, ArgType type) { + private ClassInfo(RootNode root, ArgType type, boolean canBeInner) { this.type = type; - splitAndApplyNames(root, type, root.getArgs().isMoveInnerClasses()); + splitAndApplyNames(root, type, canBeInner); } public static ClassInfo fromType(RootNode root, ArgType type) { @@ -34,7 +34,8 @@ public final class ClassInfo implements Comparable { if (cls != null) { return cls; } - ClassInfo newClsInfo = new ClassInfo(root, clsType); + boolean canBeInner = root.getArgs().isMoveInnerClasses(); + ClassInfo newClsInfo = new ClassInfo(root, clsType, canBeInner); return root.getInfoStorage().putCls(newClsInfo); } @@ -42,6 +43,10 @@ public final class ClassInfo implements Comparable { return fromType(root, ArgType.object(clsName)); } + public static ClassInfo fromNameWithoutCache(RootNode root, String fullClsName, boolean canBeInner) { + return new ClassInfo(root, ArgType.object(fullClsName), canBeInner); + } + private static ArgType checkClassType(ArgType type) { if (type == null) { throw new JadxRuntimeException("Null class type"); diff --git a/jadx-core/src/main/java/jadx/core/dex/nodes/ClassNode.java b/jadx-core/src/main/java/jadx/core/dex/nodes/ClassNode.java index 5f95fbb43..65d57eef8 100644 --- a/jadx-core/src/main/java/jadx/core/dex/nodes/ClassNode.java +++ b/jadx-core/src/main/java/jadx/core/dex/nodes/ClassNode.java @@ -624,32 +624,36 @@ public class ClassNode extends NotificationAttrNode */ @Override public void rename(String newName) { - int lastDot = newName.lastIndexOf('.'); - if (lastDot == -1) { + if (newName.indexOf('.') == -1) { clsInfo.changeShortName(newName); return; } - if (clsInfo.isInner()) { - addWarn("Can't change package for inner class: " + this + " to " + newName); - return; - } + // full name provided + ClassInfo newClsInfo = ClassInfo.fromNameWithoutCache(root, newName, clsInfo.isInner()); // change class package - String newPkg = newName.substring(0, lastDot); - String newShortName = newName.substring(lastDot + 1); - if (changeClassNodePackage(newPkg)) { - clsInfo.changePkgAndName(newPkg, newShortName); - } else { + String newPkg = newClsInfo.getPackage(); + String newShortName = newClsInfo.getShortName(); + if (clsInfo.isInner()) { + if (!newPkg.equals(clsInfo.getPackage())) { + addWarn("Can't change package for inner class: " + this + " to " + newName); + } clsInfo.changeShortName(newShortName); + } else { + if (changeClassNodePackage(newPkg)) { + clsInfo.changePkgAndName(newPkg, newShortName); + } else { + clsInfo.changeShortName(newShortName); + } } } private boolean changeClassNodePackage(String fullPkg) { - if (clsInfo.isInner()) { - throw new JadxRuntimeException("Can't change package for inner class: " + clsInfo); - } if (fullPkg.equals(clsInfo.getAliasPkg())) { return false; } + if (clsInfo.isInner()) { + throw new JadxRuntimeException("Can't change package for inner class: " + clsInfo); + } root.removeClsFromPackage(packageNode, this); packageNode = PackageNode.getForClass(root, fullPkg, this); root.sortPackages(); diff --git a/jadx-plugins/jadx-rename-mappings/build.gradle.kts b/jadx-plugins/jadx-rename-mappings/build.gradle.kts index 6ba365152..3f3c472cd 100644 --- a/jadx-plugins/jadx-rename-mappings/build.gradle.kts +++ b/jadx-plugins/jadx-rename-mappings/build.gradle.kts @@ -9,4 +9,7 @@ dependencies { exclude("org.ow2.asm:asm") exclude("net.fabricmc:tiny-remapper") } + + testRuntimeOnly(project(":jadx-plugins:jadx-dex-input")) + testRuntimeOnly(project(":jadx-plugins:jadx-smali-input")) } diff --git a/jadx-plugins/jadx-rename-mappings/src/test/java/jadx/plugins/mappings/BaseRenameMappingsTest.java b/jadx-plugins/jadx-rename-mappings/src/test/java/jadx/plugins/mappings/BaseRenameMappingsTest.java new file mode 100644 index 000000000..341166b64 --- /dev/null +++ b/jadx-plugins/jadx-rename-mappings/src/test/java/jadx/plugins/mappings/BaseRenameMappingsTest.java @@ -0,0 +1,58 @@ +package jadx.plugins.mappings; + +import java.io.File; +import java.net.URL; +import java.nio.file.Path; +import java.util.List; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.io.TempDir; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import jadx.api.JadxArgs; +import jadx.api.JavaClass; +import jadx.api.plugins.loader.JadxBasePluginLoader; +import jadx.core.plugins.files.SingleDirFilesGetter; + +import static org.assertj.core.api.Assertions.assertThat; + +public class BaseRenameMappingsTest { + private static final Logger LOG = LoggerFactory.getLogger(BaseRenameMappingsTest.class); + + @TempDir + Path testDir; + + Path outputDir; + + JadxArgs jadxArgs; + + String testResDir = ""; + + @BeforeEach + public void setUp() { + outputDir = testDir.resolve("output"); + jadxArgs = new JadxArgs(); + jadxArgs.setOutDir(outputDir.toFile()); + jadxArgs.setFilesGetter(new SingleDirFilesGetter(testDir)); + jadxArgs.setPluginLoader(new JadxBasePluginLoader()); + } + + public File loadResourceFile(String fileName) { + String path = testResDir + File.separator + fileName; + try { + URL resource = getClass().getClassLoader().getResource(path); + assertThat(resource).isNotNull(); + return new File(resource.getFile()); + } catch (Exception e) { + throw new RuntimeException("Failed to load resource file: " + path, e); + } + } + + public void printClassesCode(List classes) { + LOG.debug("Printing code for {} classes:", classes.size()); + for (JavaClass jCls : classes) { + LOG.debug("Class: {}\n{}\n---\n", jCls.getFullName(), jCls.getCode()); + } + } +} diff --git a/jadx-plugins/jadx-rename-mappings/src/test/java/jadx/plugins/mappings/TestInnerClassRename.java b/jadx-plugins/jadx-rename-mappings/src/test/java/jadx/plugins/mappings/TestInnerClassRename.java new file mode 100644 index 000000000..c9f13ebe3 --- /dev/null +++ b/jadx-plugins/jadx-rename-mappings/src/test/java/jadx/plugins/mappings/TestInnerClassRename.java @@ -0,0 +1,33 @@ +package jadx.plugins.mappings; + +import java.util.List; + +import org.junit.jupiter.api.Test; + +import jadx.api.JadxDecompiler; +import jadx.api.JavaClass; + +import static org.assertj.core.api.Assertions.assertThat; + +class TestInnerClassRename extends BaseRenameMappingsTest { + + @Test + public void test() { + testResDir = "inner-cls-rename"; + jadxArgs.getInputFiles().add(loadResourceFile("base.smali")); + jadxArgs.getInputFiles().add(loadResourceFile("inner.smali")); + jadxArgs.setUserRenamesMappingsPath(loadResourceFile("enigma.mapping").toPath()); + try (JadxDecompiler jadx = new JadxDecompiler(jadxArgs)) { + jadx.load(); + List classes = jadx.getClasses(); + printClassesCode(classes); + assertThat(classes).hasSize(1); + JavaClass baseCls = classes.get(0); + assertThat(baseCls.getName()).isEqualTo("BaseCls"); + List innerClasses = baseCls.getInnerClasses(); + assertThat(innerClasses).hasSize(1); + assertThat(innerClasses.get(0).getName()).isEqualTo("RenamedInner"); + assertThat(baseCls.getCode()).contains("class RenamedInner {"); + } + } +} diff --git a/jadx-plugins/jadx-rename-mappings/src/test/resources/inner-cls-rename/base.smali b/jadx-plugins/jadx-rename-mappings/src/test/resources/inner-cls-rename/base.smali new file mode 100644 index 000000000..4558c2bed --- /dev/null +++ b/jadx-plugins/jadx-rename-mappings/src/test/resources/inner-cls-rename/base.smali @@ -0,0 +1,2 @@ +.class Ljadx/test/BaseCls; +.super Ljava/lang/Object; diff --git a/jadx-plugins/jadx-rename-mappings/src/test/resources/inner-cls-rename/enigma.mapping b/jadx-plugins/jadx-rename-mappings/src/test/resources/inner-cls-rename/enigma.mapping new file mode 100644 index 000000000..2fab2db61 --- /dev/null +++ b/jadx-plugins/jadx-rename-mappings/src/test/resources/inner-cls-rename/enigma.mapping @@ -0,0 +1,2 @@ +CLASS jadx/test/BaseCls + CLASS Inner RenamedInner diff --git a/jadx-plugins/jadx-rename-mappings/src/test/resources/inner-cls-rename/inner.smali b/jadx-plugins/jadx-rename-mappings/src/test/resources/inner-cls-rename/inner.smali new file mode 100644 index 000000000..34213bce3 --- /dev/null +++ b/jadx-plugins/jadx-rename-mappings/src/test/resources/inner-cls-rename/inner.smali @@ -0,0 +1,2 @@ +.class Ljadx/test/BaseCls$Inner; +.super Ljava/lang/Object; diff --git a/jadx-plugins/jadx-rename-mappings/src/test/resources/logback-test.xml b/jadx-plugins/jadx-rename-mappings/src/test/resources/logback-test.xml new file mode 100644 index 000000000..3fcc38c08 --- /dev/null +++ b/jadx-plugins/jadx-rename-mappings/src/test/resources/logback-test.xml @@ -0,0 +1,13 @@ + + + + + %d{HH:mm:ss} %-5level - %msg%n + + + + + + + +