Compare commits
32 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 2cd112cd3d | |||
| 43358643be | |||
| 1f0d3dac0f | |||
| da95a8ae17 | |||
| da3ac6bff0 | |||
| bdbeaff8f0 | |||
| b1f48f1db1 | |||
| 5b09378614 | |||
| b64c93160b | |||
| 58c4f56a71 | |||
| 3d11d1fa87 | |||
| 0d158592e4 | |||
| 0c253f9a1f | |||
| f565178c8c | |||
| f6d13f1860 | |||
| d58c9ac926 | |||
| 7f9d51b9b1 | |||
| 432e49df03 | |||
| b530c234f3 | |||
| 181dcf7b4f | |||
| dc4dcb2bd0 | |||
| 74c396448e | |||
| cb9693a9d1 | |||
| 5d13acc6f3 | |||
| c04dddfa81 | |||
| 1bb645d676 | |||
| ecb597a461 | |||
| 46cd3b5597 | |||
| d523f1b15e | |||
| 8030c2f84e | |||
| 47224dc599 | |||
| d492628bfe |
@@ -8,12 +8,12 @@ jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v5
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Set up JDK
|
||||
uses: actions/setup-java@v4
|
||||
uses: actions/setup-java@v5
|
||||
with:
|
||||
distribution: temurin
|
||||
java-version: 21
|
||||
@@ -54,7 +54,7 @@ jobs:
|
||||
build-win-bundle:
|
||||
runs-on: windows-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v5
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
|
||||
@@ -14,10 +14,10 @@ jobs:
|
||||
|
||||
runs-on: ${{ matrix.os }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v5
|
||||
|
||||
- name: Set up JDK
|
||||
uses: actions/setup-java@v4
|
||||
uses: actions/setup-java@v5
|
||||
with:
|
||||
distribution: temurin
|
||||
java-version: 21
|
||||
|
||||
@@ -25,7 +25,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v5
|
||||
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@v3
|
||||
|
||||
@@ -13,7 +13,7 @@ jobs:
|
||||
build-release-win-bundle:
|
||||
runs-on: windows-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v5
|
||||
|
||||
- name: Set up JDK
|
||||
uses: oracle-actions/setup-java@v1
|
||||
@@ -21,7 +21,7 @@ jobs:
|
||||
release: 24
|
||||
|
||||
- name: Set jadx version
|
||||
uses: actions/github-script@v7
|
||||
uses: actions/github-script@v8
|
||||
with:
|
||||
script: |
|
||||
const jadxVersion = context.ref.split('/').pop().substring(1)
|
||||
@@ -45,16 +45,16 @@ jobs:
|
||||
needs: build-release-win-bundle
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v5
|
||||
|
||||
- name: Set up JDK
|
||||
uses: actions/setup-java@v4
|
||||
uses: actions/setup-java@v5
|
||||
with:
|
||||
distribution: temurin
|
||||
java-version: 21
|
||||
|
||||
- name: Set jadx version and release name
|
||||
uses: actions/github-script@v7
|
||||
uses: actions/github-script@v8
|
||||
with:
|
||||
script: |
|
||||
const jadxVersion = context.ref.split('/').pop().substring(1)
|
||||
@@ -69,7 +69,7 @@ jobs:
|
||||
JADX_BUILD_JAVA_VERSION: 11
|
||||
|
||||
- name: Download Windows JRE bundle
|
||||
uses: actions/download-artifact@v4
|
||||
uses: actions/download-artifact@v5
|
||||
with:
|
||||
name: ${{ format('jadx-gui-{0}-with-jre-win', env.JADX_VERSION) }}
|
||||
path: ${{ format('build/jadx-gui-{0}-with-jre-win', env.JADX_VERSION) }}
|
||||
|
||||
@@ -3,7 +3,7 @@ plugins {
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation("org.jetbrains.kotlin:kotlin-gradle-plugin:2.1.21")
|
||||
implementation("org.jetbrains.kotlin:kotlin-gradle-plugin:2.2.0")
|
||||
|
||||
implementation("org.openrewrite:plugin:6.19.1")
|
||||
}
|
||||
|
||||
@@ -20,7 +20,7 @@ dependencies {
|
||||
testImplementation("ch.qos.logback:logback-classic:1.5.18")
|
||||
testImplementation("org.assertj:assertj-core:3.27.3")
|
||||
|
||||
testImplementation("org.junit.jupiter:junit-jupiter:5.12.2")
|
||||
testImplementation("org.junit.jupiter:junit-jupiter:5.13.3")
|
||||
testRuntimeOnly("org.junit.platform:junit-platform-launcher")
|
||||
|
||||
testCompileOnly("org.jetbrains:annotations:26.0.2")
|
||||
|
||||
@@ -7,10 +7,10 @@ repositories {
|
||||
}
|
||||
|
||||
dependencies {
|
||||
rewrite("org.openrewrite.recipe:rewrite-testing-frameworks:3.8.0")
|
||||
rewrite("org.openrewrite.recipe:rewrite-logging-frameworks:3.8.0")
|
||||
rewrite("org.openrewrite.recipe:rewrite-migrate-java:3.9.0")
|
||||
rewrite("org.openrewrite.recipe:rewrite-static-analysis:2.9.0")
|
||||
rewrite("org.openrewrite.recipe:rewrite-testing-frameworks:3.13.0")
|
||||
rewrite("org.openrewrite.recipe:rewrite-logging-frameworks:3.11.0")
|
||||
rewrite("org.openrewrite.recipe:rewrite-migrate-java:3.13.0")
|
||||
rewrite("org.openrewrite.recipe:rewrite-static-analysis:2.12.0")
|
||||
}
|
||||
|
||||
tasks {
|
||||
|
||||
Vendored
BIN
Binary file not shown.
+2
-2
@@ -1,7 +1,7 @@
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
distributionSha256Sum=61ad310d3c7d3e5da131b76bbf22b5a4c0786e9d892dae8c1658d4b484de3caa
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.14-bin.zip
|
||||
distributionSha256Sum=bd71102213493060956ec229d946beee57158dbd89d0e62b91bca0fa2c5f3531
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.3-bin.zip
|
||||
networkTimeout=10000
|
||||
validateDistributionUrl=true
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
|
||||
@@ -114,7 +114,7 @@ case "$( uname )" in #(
|
||||
NONSTOP* ) nonstop=true ;;
|
||||
esac
|
||||
|
||||
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
|
||||
CLASSPATH="\\\"\\\""
|
||||
|
||||
|
||||
# Determine the Java command to use to start the JVM.
|
||||
@@ -213,7 +213,7 @@ DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
|
||||
set -- \
|
||||
"-Dorg.gradle.appname=$APP_BASE_NAME" \
|
||||
-classpath "$CLASSPATH" \
|
||||
org.gradle.wrapper.GradleWrapperMain \
|
||||
-jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \
|
||||
"$@"
|
||||
|
||||
# Stop when "xargs" is not available.
|
||||
|
||||
Vendored
+2
-2
@@ -70,11 +70,11 @@ goto fail
|
||||
:execute
|
||||
@rem Setup the command line
|
||||
|
||||
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
|
||||
set CLASSPATH=
|
||||
|
||||
|
||||
@rem Execute Gradle
|
||||
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
|
||||
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %*
|
||||
|
||||
:end
|
||||
@rem End local scope for the variables with windows NT shell
|
||||
|
||||
@@ -4,7 +4,7 @@ plugins {
|
||||
id("application")
|
||||
|
||||
// use shadow only for application scripts, jar will be copied from jadx-gui
|
||||
id("com.gradleup.shadow") version "8.3.6"
|
||||
id("com.gradleup.shadow") version "8.3.8"
|
||||
}
|
||||
|
||||
dependencies {
|
||||
@@ -33,6 +33,7 @@ application {
|
||||
mainClass.set("jadx.cli.JadxCLI")
|
||||
applicationDefaultJvmArgs =
|
||||
listOf(
|
||||
"-XX:+IgnoreUnrecognizedVMOptions",
|
||||
"-Xms256M",
|
||||
"-XX:MaxRAMPercentage=70.0",
|
||||
// disable zip checks (#1962)
|
||||
|
||||
@@ -11,6 +11,7 @@ import jadx.api.JadxDecompiler;
|
||||
import jadx.api.impl.AnnotatedCodeWriter;
|
||||
import jadx.api.impl.NoOpCodeCache;
|
||||
import jadx.api.impl.SimpleCodeWriter;
|
||||
import jadx.api.usage.impl.EmptyUsageInfoCache;
|
||||
import jadx.cli.LogHelper.LogLevelEnum;
|
||||
import jadx.cli.plugins.JadxFilesGetter;
|
||||
import jadx.core.utils.exceptions.JadxArgsValidateException;
|
||||
@@ -57,6 +58,7 @@ public class JadxCLI {
|
||||
LogHelper.setLogLevelsForLoadingStage();
|
||||
JadxArgs jadxArgs = cliArgs.toJadxArgs();
|
||||
jadxArgs.setCodeCache(new NoOpCodeCache());
|
||||
jadxArgs.setUsageInfoCache(new EmptyUsageInfoCache());
|
||||
jadxArgs.setPluginLoader(new JadxExternalPluginsLoader());
|
||||
jadxArgs.setFilesGetter(JadxFilesGetter.INSTANCE);
|
||||
initCodeWriterProvider(jadxArgs);
|
||||
|
||||
@@ -3,5 +3,5 @@ plugins {
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation("io.get-coursier.util:directories-jni:0.1.3")
|
||||
implementation("io.get-coursier.util:directories-jni:0.1.4")
|
||||
}
|
||||
|
||||
@@ -81,12 +81,15 @@ public class JadxCommonFiles {
|
||||
}
|
||||
|
||||
/**
|
||||
* Return JNI or Foreign implementation
|
||||
* Return JNI, Foreign or PowerShell implementation
|
||||
*/
|
||||
private static Windows getWinDirs() {
|
||||
Windows defSup = Windows.getDefaultSupplier().get();
|
||||
if (defSup instanceof WindowsPowerShell) {
|
||||
return new WindowsJni();
|
||||
if (JadxSystemInfo.IS_AMD64) {
|
||||
// JNI library compiled for x86-64
|
||||
return new WindowsJni();
|
||||
}
|
||||
}
|
||||
return defSup;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
package jadx.commons.app;
|
||||
|
||||
import java.util.Locale;
|
||||
|
||||
public class JadxSystemInfo {
|
||||
public static final String JAVA_VM = System.getProperty("java.vm.name", "?");
|
||||
public static final String JAVA_VER = System.getProperty("java.version", "?");
|
||||
|
||||
public static final String OS_NAME = System.getProperty("os.name", "?");
|
||||
public static final String OS_ARCH = System.getProperty("os.arch", "?");
|
||||
public static final String OS_VERSION = System.getProperty("os.version", "?");
|
||||
|
||||
private static final String OS_NAME_LOWER = OS_NAME.toLowerCase(Locale.ENGLISH);
|
||||
public static final boolean IS_WINDOWS = OS_NAME_LOWER.startsWith("windows");
|
||||
public static final boolean IS_MAC = OS_NAME_LOWER.startsWith("mac");
|
||||
public static final boolean IS_LINUX = !IS_WINDOWS && !IS_MAC;
|
||||
public static final boolean IS_UNIX = !IS_WINDOWS;
|
||||
|
||||
private static final String OS_ARCH_LOWER = OS_NAME.toLowerCase(Locale.ENGLISH);
|
||||
public static final boolean IS_AMD64 = OS_ARCH_LOWER.equals("amd64");
|
||||
public static final boolean IS_ARM64 = OS_ARCH_LOWER.equals("aarch64");
|
||||
|
||||
private JadxSystemInfo() {
|
||||
}
|
||||
}
|
||||
@@ -2,14 +2,17 @@ package jadx.zip;
|
||||
|
||||
import java.io.Closeable;
|
||||
import java.io.IOException;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
public class ZipContent implements Closeable {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(ZipContent.class);
|
||||
|
||||
private final IZipParser zipParser;
|
||||
private final List<IZipEntry> entries;
|
||||
private final Map<String, IZipEntry> entriesMap;
|
||||
@@ -17,7 +20,19 @@ public class ZipContent implements Closeable {
|
||||
public ZipContent(IZipParser zipParser, List<IZipEntry> entries) {
|
||||
this.zipParser = zipParser;
|
||||
this.entries = entries;
|
||||
this.entriesMap = entries.stream().collect(Collectors.toMap(IZipEntry::getName, Function.identity()));
|
||||
this.entriesMap = buildNameMap(zipParser, entries);
|
||||
}
|
||||
|
||||
private static Map<String, IZipEntry> buildNameMap(IZipParser zipParser, List<IZipEntry> entries) {
|
||||
Map<String, IZipEntry> map = new HashMap<>(entries.size());
|
||||
for (IZipEntry entry : entries) {
|
||||
String name = entry.getName();
|
||||
IZipEntry prevEntry = map.put(name, entry);
|
||||
if (prevEntry != null) {
|
||||
LOG.warn("Found duplicate entry: {} in {}", name, zipParser);
|
||||
}
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
public List<IZipEntry> getEntries() {
|
||||
|
||||
@@ -8,7 +8,7 @@ dependencies {
|
||||
|
||||
implementation("com.google.code.gson:gson:2.13.1")
|
||||
|
||||
testImplementation("org.apache.commons:commons-lang3:3.17.0")
|
||||
testImplementation("org.apache.commons:commons-lang3:3.18.0")
|
||||
|
||||
testImplementation(project(":jadx-plugins:jadx-dex-input"))
|
||||
testRuntimeOnly(project(":jadx-plugins:jadx-smali-input"))
|
||||
|
||||
@@ -4,31 +4,44 @@ import java.util.HashMap;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
|
||||
import jadx.api.resources.ResourceContentType;
|
||||
import jadx.core.utils.exceptions.JadxRuntimeException;
|
||||
|
||||
public enum ResourceType {
|
||||
CODE(".dex", ".jar", ".class"),
|
||||
XML(".xml"),
|
||||
ARSC(".arsc"),
|
||||
APK(".apk", ".apkm", ".apks"),
|
||||
FONT(".ttf", ".ttc", ".otf"),
|
||||
IMG(".png", ".gif", ".jpg", ".webp", ".bmp", ".tiff"),
|
||||
ARCHIVE(".zip", ".rar", ".7zip", ".7z", ".arj", ".tar", ".gzip", ".bzip", ".bzip2", ".cab", ".cpio", ".ar", ".gz", ".tgz", ".bz2"),
|
||||
VIDEOS(".mp4", ".mkv", ".webm", ".avi", ".flv", ".3gp"),
|
||||
SOUNDS(".aac", ".ogg", ".opus", ".mp3", ".wav", ".wma", ".mid", ".midi"),
|
||||
JSON(".json"),
|
||||
TEXT(".txt", ".ini", ".conf", ".yaml", ".properties", ".js"),
|
||||
HTML(".html"),
|
||||
LIB(".so"),
|
||||
MANIFEST,
|
||||
UNKNOWN;
|
||||
import static jadx.api.resources.ResourceContentType.CONTENT_BINARY;
|
||||
import static jadx.api.resources.ResourceContentType.CONTENT_TEXT;
|
||||
import static jadx.api.resources.ResourceContentType.CONTENT_UNKNOWN;
|
||||
|
||||
public enum ResourceType {
|
||||
CODE(CONTENT_BINARY, ".dex", ".jar", ".class"),
|
||||
XML(CONTENT_TEXT, ".xml"),
|
||||
ARSC(CONTENT_TEXT, ".arsc"),
|
||||
APK(CONTENT_BINARY, ".apk", ".apkm", ".apks"),
|
||||
FONT(CONTENT_BINARY, ".ttf", ".ttc", ".otf"),
|
||||
IMG(CONTENT_BINARY, ".png", ".gif", ".jpg", ".webp", ".bmp", ".tiff"),
|
||||
ARCHIVE(CONTENT_BINARY, ".zip", ".rar", ".7zip", ".7z", ".arj", ".tar", ".gzip", ".bzip", ".bzip2", ".cab", ".cpio", ".ar", ".gz",
|
||||
".tgz", ".bz2"),
|
||||
VIDEOS(CONTENT_BINARY, ".mp4", ".mkv", ".webm", ".avi", ".flv", ".3gp"),
|
||||
SOUNDS(CONTENT_BINARY, ".aac", ".ogg", ".opus", ".mp3", ".wav", ".wma", ".mid", ".midi"),
|
||||
JSON(CONTENT_TEXT, ".json"),
|
||||
TEXT(CONTENT_TEXT, ".txt", ".ini", ".conf", ".yaml", ".properties", ".js"),
|
||||
HTML(CONTENT_TEXT, ".html"),
|
||||
LIB(CONTENT_BINARY, ".so"),
|
||||
MANIFEST(CONTENT_TEXT),
|
||||
UNKNOWN_BIN(CONTENT_BINARY, ".bin"),
|
||||
UNKNOWN(CONTENT_UNKNOWN);
|
||||
|
||||
private final ResourceContentType contentType;
|
||||
private final String[] exts;
|
||||
|
||||
ResourceType(String... exts) {
|
||||
ResourceType(ResourceContentType contentType, String... exts) {
|
||||
this.contentType = contentType;
|
||||
this.exts = exts;
|
||||
}
|
||||
|
||||
public ResourceContentType getContentType() {
|
||||
return contentType;
|
||||
}
|
||||
|
||||
public String[] getExts() {
|
||||
return exts;
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.nio.charset.Charset;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
@@ -229,9 +230,13 @@ public final class ResourcesLoader implements IResourcesLoader {
|
||||
}
|
||||
|
||||
public static ICodeInfo loadToCodeWriter(InputStream is) throws IOException {
|
||||
return loadToCodeWriter(is, StandardCharsets.UTF_8);
|
||||
}
|
||||
|
||||
public static ICodeInfo loadToCodeWriter(InputStream is, Charset charset) throws IOException {
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream(READ_BUFFER_SIZE);
|
||||
copyStream(is, baos);
|
||||
return new SimpleCodeInfo(baos.toString(StandardCharsets.UTF_8));
|
||||
return new SimpleCodeInfo(baos.toString(charset));
|
||||
}
|
||||
|
||||
private synchronized BinaryXMLParser loadBinaryXmlParser() {
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
package jadx.api.resources;
|
||||
|
||||
public enum ResourceContentType {
|
||||
CONTENT_TEXT,
|
||||
CONTENT_BINARY,
|
||||
CONTENT_NONE,
|
||||
CONTENT_UNKNOWN,
|
||||
}
|
||||
@@ -1,10 +1,11 @@
|
||||
package jadx.core.dex.attributes.nodes;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.EnumMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import jadx.api.CommentsLevel;
|
||||
@@ -30,10 +31,10 @@ public class JadxCommentsAttr implements IJadxAttribute {
|
||||
return newAttr;
|
||||
}
|
||||
|
||||
private final Map<CommentsLevel, List<String>> comments = new EnumMap<>(CommentsLevel.class);
|
||||
private final Map<CommentsLevel, Set<String>> comments = new EnumMap<>(CommentsLevel.class);
|
||||
|
||||
public void add(CommentsLevel level, String comment) {
|
||||
comments.computeIfAbsent(level, l -> new ArrayList<>()).add(comment);
|
||||
comments.computeIfAbsent(level, l -> new HashSet<>()).add(comment);
|
||||
}
|
||||
|
||||
public List<String> formatAndFilter(CommentsLevel level) {
|
||||
@@ -47,12 +48,11 @@ public class JadxCommentsAttr implements IJadxAttribute {
|
||||
return e.getValue().stream()
|
||||
.map(v -> "JADX " + levelName + ": " + v);
|
||||
})
|
||||
.distinct()
|
||||
.sorted()
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
public Map<CommentsLevel, List<String>> getComments() {
|
||||
public Map<CommentsLevel, Set<String>> getComments() {
|
||||
return comments;
|
||||
}
|
||||
|
||||
|
||||
@@ -150,6 +150,8 @@ public class ClassNode extends NotificationAttrNode
|
||||
IUsageInfoData usageInfoData = root.getArgs().getUsageInfoCache().get(root);
|
||||
if (usageInfoData != null) {
|
||||
usageInfoData.applyForClass(this);
|
||||
} else {
|
||||
LOG.warn("Can't restore usage data for class: {}", this);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@ import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.regex.Matcher;
|
||||
|
||||
import jadx.api.ICodeWriter;
|
||||
import jadx.api.impl.SimpleCodeWriter;
|
||||
@@ -30,6 +31,7 @@ import static jadx.core.codegen.MethodGen.FallbackOption.BLOCK_DUMP;
|
||||
public class DotGraphVisitor extends AbstractVisitor {
|
||||
|
||||
private static final String NL = "\\l";
|
||||
private static final String NLQR = Matcher.quoteReplacement(NL);
|
||||
private static final boolean PRINT_DOMINATORS = false;
|
||||
private static final boolean PRINT_DOMINATORS_INFO = false;
|
||||
|
||||
@@ -324,7 +326,7 @@ public class DotGraphVisitor extends AbstractVisitor {
|
||||
.replace("\"", "\\\"")
|
||||
.replace("-", "\\-")
|
||||
.replace("|", "\\|")
|
||||
.replaceAll("\\R", NL);
|
||||
.replaceAll("\\R", NLQR);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,7 +31,8 @@ public class AndroidGradleGenerator implements IExportGradleGenerator {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(AndroidGradleGenerator.class);
|
||||
private static final Pattern ILLEGAL_GRADLE_CHARS = Pattern.compile("[/\\\\:>\"?*|]");
|
||||
|
||||
private static final ApplicationParams UNKNOWN_APP_PARAMS = new ApplicationParams("UNKNOWN", 0, 0, 0, "UNKNOWN", "UNKNOWN", "UNKNOWN");
|
||||
private static final ApplicationParams UNKNOWN_APP_PARAMS =
|
||||
new ApplicationParams("UNKNOWN", 0, 0, 0, 0, "UNKNOWN", "UNKNOWN", "UNKNOWN");
|
||||
|
||||
private final RootNode root;
|
||||
private final File projectDir;
|
||||
@@ -107,6 +108,7 @@ public class AndroidGradleGenerator implements IExportGradleGenerator {
|
||||
if (exportApp) {
|
||||
attrs.add(AppAttribute.APPLICATION_LABEL);
|
||||
attrs.add(AppAttribute.TARGET_SDK_VERSION);
|
||||
attrs.add(AppAttribute.COMPILE_SDK_VERSION);
|
||||
attrs.add(AppAttribute.VERSION_NAME);
|
||||
attrs.add(AppAttribute.VERSION_CODE);
|
||||
}
|
||||
@@ -160,6 +162,7 @@ public class AndroidGradleGenerator implements IExportGradleGenerator {
|
||||
TemplateFile tmpl = TemplateFile.fromResources("/export/android/app.build.gradle.tmpl");
|
||||
tmpl.add("applicationId", appPackage);
|
||||
tmpl.add("minSdkVersion", minSdkVersion);
|
||||
tmpl.add("compileSdkVersion", applicationParams.getCompileSdkVersion());
|
||||
tmpl.add("targetSdkVersion", applicationParams.getTargetSdkVersion());
|
||||
tmpl.add("versionCode", applicationParams.getVersionCode());
|
||||
tmpl.add("versionName", applicationParams.getVersionName());
|
||||
@@ -174,6 +177,7 @@ public class AndroidGradleGenerator implements IExportGradleGenerator {
|
||||
TemplateFile tmpl = TemplateFile.fromResources("/export/android/lib.build.gradle.tmpl");
|
||||
tmpl.add("packageId", pkg);
|
||||
tmpl.add("minSdkVersion", minSdkVersion);
|
||||
tmpl.add("compileSdkVersion", applicationParams.getCompileSdkVersion());
|
||||
tmpl.add("additionalOptions", genAdditionalAndroidPluginOptions(minSdkVersion));
|
||||
|
||||
tmpl.save(new File(baseDir, "build.gradle"));
|
||||
|
||||
@@ -65,7 +65,7 @@ public class DecompilerScheduler implements IDecompileScheduler {
|
||||
mergedBatch = new ArrayList<>(MERGED_BATCH_SIZE);
|
||||
}
|
||||
} else {
|
||||
List<JavaClass> batch = new ArrayList<>(depsSize + 1);
|
||||
List<JavaClass> batch = new ArrayList<>();
|
||||
for (JavaClass dep : cls.getDependencies()) {
|
||||
JavaClass topDep = dep.getTopParentClass();
|
||||
if (!added.contains(topDep)) {
|
||||
@@ -75,7 +75,7 @@ public class DecompilerScheduler implements IDecompileScheduler {
|
||||
}
|
||||
batch.sort(cmpDepSize);
|
||||
batch.add(cls);
|
||||
result.add(batch);
|
||||
result.add(Utils.lockList(batch));
|
||||
}
|
||||
}
|
||||
if (!mergedBatch.isEmpty()) {
|
||||
|
||||
@@ -9,6 +9,7 @@ import org.jetbrains.annotations.Nullable;
|
||||
import jadx.api.JadxArgs;
|
||||
import jadx.api.args.IntegerFormat;
|
||||
import jadx.core.deobf.NameMapper;
|
||||
import jadx.core.utils.exceptions.JadxRuntimeException;
|
||||
|
||||
public class StringUtils {
|
||||
private static final StringUtils DEFAULT_INSTANCE = new StringUtils(new JadxArgs());
|
||||
@@ -386,58 +387,90 @@ public class StringUtils {
|
||||
return new SimpleDateFormat("HH:mm:ss").format(new Date());
|
||||
}
|
||||
|
||||
private String toFormatString(long l) {
|
||||
private String formatNumber(long number, int bytesLen, boolean cast) {
|
||||
String numStr;
|
||||
if (integerFormat.isHexadecimal()) {
|
||||
return "0x" + Long.toHexString(l);
|
||||
String hexStr = Long.toHexString(number);
|
||||
if (number < 0) {
|
||||
// cut leading 'f' for negative numbers to match number type length
|
||||
int len = hexStr.length();
|
||||
numStr = "0x" + hexStr.substring(len - bytesLen * 2, len);
|
||||
// force cast, because unsigned negative numbers are bigger
|
||||
// than signed max value allowed by compiler
|
||||
cast = true;
|
||||
} else {
|
||||
numStr = "0x" + hexStr;
|
||||
}
|
||||
} else {
|
||||
numStr = Long.toString(number);
|
||||
}
|
||||
return Long.toString(l);
|
||||
if (bytesLen == 8 && (number == Long.MIN_VALUE || Math.abs(number) >= Integer.MAX_VALUE)) {
|
||||
// force cast for long values bigger than min/max int
|
||||
// to resolve compiler error: "integer number too large"
|
||||
cast = true;
|
||||
}
|
||||
if (cast) {
|
||||
if (bytesLen == 8) {
|
||||
return numStr + 'L';
|
||||
}
|
||||
return getCastStr(bytesLen) + numStr;
|
||||
}
|
||||
return numStr;
|
||||
}
|
||||
|
||||
public String formatShort(long l, boolean cast) {
|
||||
if (l == Short.MAX_VALUE) {
|
||||
return "Short.MAX_VALUE";
|
||||
private static String getCastStr(int bytesLen) {
|
||||
switch (bytesLen) {
|
||||
case 1:
|
||||
return "(byte) ";
|
||||
case 2:
|
||||
return "(short) ";
|
||||
case 4:
|
||||
return "(int) ";
|
||||
case 8:
|
||||
return "(long) ";
|
||||
default:
|
||||
throw new JadxRuntimeException("Unexpected number type length: " + bytesLen);
|
||||
}
|
||||
if (l == Short.MIN_VALUE) {
|
||||
return "Short.MIN_VALUE";
|
||||
}
|
||||
String str = toFormatString(l);
|
||||
return cast ? "(short) " + str : str;
|
||||
}
|
||||
|
||||
public String formatByte(long l, boolean cast) {
|
||||
if (l == Byte.MAX_VALUE) {
|
||||
return "Byte.MAX_VALUE";
|
||||
return formatNumber(l, 1, cast);
|
||||
}
|
||||
|
||||
public String formatShort(long l, boolean cast) {
|
||||
if (integerFormat == IntegerFormat.AUTO) {
|
||||
switch ((short) l) {
|
||||
case Short.MAX_VALUE:
|
||||
return "Short.MAX_VALUE";
|
||||
case Short.MIN_VALUE:
|
||||
return "Short.MIN_VALUE";
|
||||
}
|
||||
}
|
||||
if (l == Byte.MIN_VALUE) {
|
||||
return "Byte.MIN_VALUE";
|
||||
}
|
||||
String str = toFormatString(l);
|
||||
return cast ? "(byte) " + str : str;
|
||||
return formatNumber(l, 2, cast);
|
||||
}
|
||||
|
||||
public String formatInteger(long l, boolean cast) {
|
||||
if (l == Integer.MAX_VALUE) {
|
||||
return "Integer.MAX_VALUE";
|
||||
if (integerFormat == IntegerFormat.AUTO) {
|
||||
switch ((int) l) {
|
||||
case Integer.MAX_VALUE:
|
||||
return "Integer.MAX_VALUE";
|
||||
case Integer.MIN_VALUE:
|
||||
return "Integer.MIN_VALUE";
|
||||
}
|
||||
}
|
||||
if (l == Integer.MIN_VALUE) {
|
||||
return "Integer.MIN_VALUE";
|
||||
}
|
||||
String str = toFormatString(l);
|
||||
return cast ? "(int) " + str : str;
|
||||
return formatNumber(l, 4, cast);
|
||||
}
|
||||
|
||||
public String formatLong(long l, boolean cast) {
|
||||
if (l == Long.MAX_VALUE) {
|
||||
return "Long.MAX_VALUE";
|
||||
if (integerFormat == IntegerFormat.AUTO) {
|
||||
if (l == Long.MAX_VALUE) {
|
||||
return "Long.MAX_VALUE";
|
||||
}
|
||||
if (l == Long.MIN_VALUE) {
|
||||
return "Long.MIN_VALUE";
|
||||
}
|
||||
}
|
||||
if (l == Long.MIN_VALUE) {
|
||||
return "Long.MIN_VALUE";
|
||||
}
|
||||
String str = toFormatString(l);
|
||||
if (cast || Math.abs(l) >= Integer.MAX_VALUE) {
|
||||
return str + 'L';
|
||||
}
|
||||
return str;
|
||||
return formatNumber(l, 8, cast);
|
||||
}
|
||||
|
||||
public static String formatDouble(double d) {
|
||||
|
||||
@@ -61,6 +61,7 @@ public class AndroidManifestParser {
|
||||
String applicationLabel = null;
|
||||
Integer minSdkVersion = null;
|
||||
Integer targetSdkVersion = null;
|
||||
Integer compileSdkVersion = null;
|
||||
Integer versionCode = null;
|
||||
String versionName = null;
|
||||
String mainActivity = null;
|
||||
@@ -89,6 +90,14 @@ public class AndroidManifestParser {
|
||||
targetSdkVersion = minSdkVersion;
|
||||
}
|
||||
}
|
||||
if (parseAttrs.contains(AppAttribute.COMPILE_SDK_VERSION)) {
|
||||
String stringCompileSdk = usesSdk.getAttribute("android:compileSdkVersion");
|
||||
if (!stringCompileSdk.isEmpty()) {
|
||||
compileSdkVersion = Integer.valueOf(stringCompileSdk);
|
||||
} else {
|
||||
compileSdkVersion = targetSdkVersion;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (manifest != null) {
|
||||
if (parseAttrs.contains(AppAttribute.VERSION_CODE)) {
|
||||
@@ -105,8 +114,8 @@ public class AndroidManifestParser {
|
||||
application = getApplicationName();
|
||||
}
|
||||
|
||||
return new ApplicationParams(applicationLabel, minSdkVersion, targetSdkVersion, versionCode,
|
||||
versionName, mainActivity, application);
|
||||
return new ApplicationParams(applicationLabel, minSdkVersion, targetSdkVersion, compileSdkVersion,
|
||||
versionCode, versionName, mainActivity, application);
|
||||
}
|
||||
|
||||
private String getApplicationLabel() {
|
||||
|
||||
@@ -3,6 +3,7 @@ package jadx.core.utils.android;
|
||||
public enum AppAttribute {
|
||||
APPLICATION_LABEL,
|
||||
MIN_SDK_VERSION,
|
||||
COMPILE_SDK_VERSION,
|
||||
TARGET_SDK_VERSION,
|
||||
VERSION_CODE,
|
||||
VERSION_NAME,
|
||||
|
||||
@@ -8,16 +8,18 @@ public class ApplicationParams {
|
||||
private final String applicationLabel;
|
||||
private final Integer minSdkVersion;
|
||||
private final Integer targetSdkVersion;
|
||||
private final Integer compileSdkVersion;
|
||||
private final Integer versionCode;
|
||||
private final String versionName;
|
||||
private final String mainActivity;
|
||||
private final String application;
|
||||
|
||||
public ApplicationParams(String applicationLabel, Integer minSdkVersion, Integer targetSdkVersion, Integer versionCode,
|
||||
String versionName, String mainActivity, String application) {
|
||||
public ApplicationParams(String applicationLabel, Integer minSdkVersion, Integer targetSdkVersion, Integer compileSdkVersion,
|
||||
Integer versionCode, String versionName, String mainActivity, String application) {
|
||||
this.applicationLabel = applicationLabel;
|
||||
this.minSdkVersion = minSdkVersion;
|
||||
this.targetSdkVersion = targetSdkVersion;
|
||||
this.compileSdkVersion = compileSdkVersion;
|
||||
this.versionCode = versionCode;
|
||||
this.versionName = versionName;
|
||||
this.mainActivity = mainActivity;
|
||||
@@ -36,6 +38,10 @@ public class ApplicationParams {
|
||||
return targetSdkVersion;
|
||||
}
|
||||
|
||||
public Integer getCompileSdkVersion() {
|
||||
return compileSdkVersion;
|
||||
}
|
||||
|
||||
public Integer getVersionCode() {
|
||||
return versionCode;
|
||||
}
|
||||
|
||||
@@ -65,7 +65,7 @@ public class BinaryXMLParser extends CommonBinaryParser {
|
||||
resourceIds = null;
|
||||
is = new ParserStream(inputStream);
|
||||
if (!isBinaryXml()) {
|
||||
return ResourcesLoader.loadToCodeWriter(inputStream);
|
||||
return ResourcesLoader.loadToCodeWriter(is);
|
||||
}
|
||||
nsMapGenerated = new HashSet<>();
|
||||
nsMap = new HashMap<>();
|
||||
|
||||
@@ -9,7 +9,7 @@ import java.nio.charset.StandardCharsets;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
public class ParserStream {
|
||||
public class ParserStream extends InputStream {
|
||||
|
||||
protected static final Charset STRING_CHARSET_UTF16 = StandardCharsets.UTF_16LE;
|
||||
protected static final Charset STRING_CHARSET_UTF8 = StandardCharsets.UTF_8;
|
||||
@@ -88,7 +88,8 @@ public class ParserStream {
|
||||
return arr;
|
||||
}
|
||||
|
||||
public void skip(long count) throws IOException {
|
||||
@Override
|
||||
public long skip(long count) throws IOException {
|
||||
readPos += count;
|
||||
long pos = input.skip(count);
|
||||
while (pos < count) {
|
||||
@@ -98,6 +99,7 @@ public class ParserStream {
|
||||
}
|
||||
pos += skipped;
|
||||
}
|
||||
return pos;
|
||||
}
|
||||
|
||||
public void checkInt8(int expected, String error) throws IOException {
|
||||
@@ -140,14 +142,16 @@ public class ParserStream {
|
||||
checkPos(expectedOffset, error);
|
||||
}
|
||||
|
||||
public void mark(int len) throws IOException {
|
||||
@Override
|
||||
public void mark(int len) {
|
||||
if (!input.markSupported()) {
|
||||
throw new IOException("Mark not supported for input stream " + input.getClass());
|
||||
throw new RuntimeException("Mark not supported for input stream " + input.getClass());
|
||||
}
|
||||
input.mark(len);
|
||||
markPos = readPos;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reset() throws IOException {
|
||||
input.reset();
|
||||
readPos = markPos;
|
||||
@@ -172,6 +176,16 @@ public class ParserStream {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int read() throws IOException {
|
||||
return input.read();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int read(@NotNull byte[] b, int off, int len) throws IOException {
|
||||
return input.read(b, off, len);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "pos: 0x" + Long.toHexString(readPos);
|
||||
|
||||
@@ -3,12 +3,13 @@ plugins {
|
||||
}
|
||||
|
||||
android {
|
||||
compileSdkVersion 30
|
||||
buildToolsVersion "30.0.2"
|
||||
namespace "{{applicationId}}"
|
||||
compileSdkVersion {{compileSdkVersion}}
|
||||
|
||||
defaultConfig {
|
||||
applicationId '{{applicationId}}'
|
||||
minSdkVersion {{minSdkVersion}}
|
||||
//noinspection ExpiringTargetSdkVersion,ExpiredTargetSdkVersion
|
||||
targetSdkVersion {{targetSdkVersion}}
|
||||
versionCode {{versionCode}}
|
||||
versionName "{{versionName}}"
|
||||
|
||||
@@ -4,7 +4,7 @@ buildscript {
|
||||
mavenCentral()
|
||||
}
|
||||
dependencies {
|
||||
classpath 'com.android.tools.build:gradle:4.2.2'
|
||||
classpath 'com.android.tools.build:gradle:8.10.1'
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@ plugins {
|
||||
|
||||
android {
|
||||
namespace '{{packageId}}'
|
||||
compileSdk 30
|
||||
compileSdk {{compileSdkVersion}}
|
||||
|
||||
defaultConfig {
|
||||
minSdk {{minSdkVersion}}
|
||||
|
||||
@@ -17,6 +17,7 @@ public class TemplateFileTest {
|
||||
tmpl.add("versionCode", 3);
|
||||
tmpl.add("versionName", "1.2.3");
|
||||
tmpl.add("additionalOptions", "useLibrary 'org.apache.http.legacy'");
|
||||
tmpl.add("compileSdkVersion", 4);
|
||||
String res = tmpl.build();
|
||||
System.out.println(res);
|
||||
|
||||
@@ -24,5 +25,6 @@ public class TemplateFileTest {
|
||||
assertThat(res).contains("targetSdkVersion 2");
|
||||
assertThat(res).contains("versionCode 3");
|
||||
assertThat(res).contains("versionName \"1.2.3\"");
|
||||
assertThat(res).contains("compileSdkVersion 4");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,57 @@
|
||||
package jadx.tests.integration.arith;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import jadx.api.args.IntegerFormat;
|
||||
import jadx.tests.api.IntegrationTest;
|
||||
|
||||
import static jadx.tests.api.utils.assertj.JadxAssertions.assertThat;
|
||||
|
||||
public class TestNumbersFormat extends IntegrationTest {
|
||||
|
||||
@SuppressWarnings({ "FieldCanBeLocal", "UnusedAssignment", "unused" })
|
||||
public static class TestCls {
|
||||
private Object obj;
|
||||
|
||||
public void test() {
|
||||
obj = new byte[] { 0, -1, -0xA, (byte) 0xff, Byte.MIN_VALUE, Byte.MAX_VALUE };
|
||||
obj = new short[] { 0, -1, -0xA, (short) 0xffff, Short.MIN_VALUE, Short.MAX_VALUE };
|
||||
obj = new int[] { 0, -1, -0xA, 0xffff_ffff, Integer.MIN_VALUE, Integer.MAX_VALUE };
|
||||
obj = new long[] { 0, -1, -0xA, 0xffff_ffff_ffff_ffffL, Long.MIN_VALUE, Long.MAX_VALUE };
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test() {
|
||||
getArgs().setIntegerFormat(IntegerFormat.AUTO);
|
||||
assertThat(getClassNode(TestCls.class))
|
||||
.code()
|
||||
.containsOne("new byte[]{0, -1, -10, -1, -128, 127}")
|
||||
.containsOne("new short[]{0, -1, -10, -1, Short.MIN_VALUE, Short.MAX_VALUE}")
|
||||
.containsOne("new int[]{0, -1, -10, -1, Integer.MIN_VALUE, Integer.MAX_VALUE}")
|
||||
.containsOne("new long[]{0, -1, -10, -1, Long.MIN_VALUE, Long.MAX_VALUE}");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDecimalFormat() {
|
||||
getArgs().setIntegerFormat(IntegerFormat.DECIMAL);
|
||||
assertThat(getClassNode(TestCls.class))
|
||||
.code()
|
||||
.containsOne("new byte[]{0, -1, -10, -1, -128, 127}")
|
||||
.containsOne("new short[]{0, -1, -10, -1, -32768, 32767}")
|
||||
.containsOne("new int[]{0, -1, -10, -1, -2147483648, 2147483647}")
|
||||
.containsOne("new long[]{0, -1, -10, -1, -9223372036854775808L, 9223372036854775807L}");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testHexFormat() {
|
||||
getArgs().setIntegerFormat(IntegerFormat.HEXADECIMAL);
|
||||
assertThat(getClassNode(TestCls.class))
|
||||
.code()
|
||||
.containsOne("new byte[]{0x0, (byte) 0xff, (byte) 0xf6, (byte) 0xff, (byte) 0x80, 0x7f}")
|
||||
.containsOne("new short[]{0x0, (short) 0xffff, (short) 0xfff6, (short) 0xffff, (short) 0x8000, 0x7fff}")
|
||||
.containsOne("new int[]{0x0, (int) 0xffffffff, (int) 0xfffffff6, (int) 0xffffffff, (int) 0x80000000, 0x7fffffff}")
|
||||
.containsOne(
|
||||
"new long[]{0x0, 0xffffffffffffffffL, 0xfffffffffffffff6L, 0xffffffffffffffffL, 0x8000000000000000L, 0x7fffffffffffffffL}");
|
||||
}
|
||||
}
|
||||
@@ -3,7 +3,8 @@ package jadx.tests.integration.arith;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import jadx.tests.api.IntegrationTest;
|
||||
import jadx.tests.api.utils.assertj.JadxAssertions;
|
||||
|
||||
import static jadx.tests.api.utils.assertj.JadxAssertions.assertThat;
|
||||
|
||||
public class TestSpecialValues extends IntegrationTest {
|
||||
|
||||
@@ -11,7 +12,6 @@ public class TestSpecialValues extends IntegrationTest {
|
||||
|
||||
public void test() {
|
||||
shorts(Short.MIN_VALUE, Short.MAX_VALUE);
|
||||
bytes(Byte.MIN_VALUE, Byte.MAX_VALUE);
|
||||
ints(Integer.MIN_VALUE, Integer.MAX_VALUE);
|
||||
longs(Long.MIN_VALUE, Long.MAX_VALUE);
|
||||
|
||||
@@ -25,9 +25,6 @@ public class TestSpecialValues extends IntegrationTest {
|
||||
private void shorts(short... v) {
|
||||
}
|
||||
|
||||
private void bytes(byte... v) {
|
||||
}
|
||||
|
||||
private void ints(int... v) {
|
||||
}
|
||||
|
||||
@@ -43,14 +40,13 @@ public class TestSpecialValues extends IntegrationTest {
|
||||
|
||||
@Test
|
||||
public void test() {
|
||||
JadxAssertions.assertThat(getClassNode(TestCls.class))
|
||||
assertThat(getClassNode(TestCls.class))
|
||||
.code()
|
||||
.containsOne(
|
||||
"Float.NaN, Float.NEGATIVE_INFINITY, Float.POSITIVE_INFINITY, Float.MIN_VALUE, Float.MAX_VALUE, Float.MIN_NORMAL")
|
||||
.containsOne("Double.NaN, Double.NEGATIVE_INFINITY, Double.POSITIVE_INFINITY, "
|
||||
+ "Double.MIN_VALUE, Double.MAX_VALUE, Double.MIN_NORMAL")
|
||||
.containsOne("Short.MIN_VALUE, Short.MAX_VALUE")
|
||||
.containsOne("Byte.MIN_VALUE, Byte.MAX_VALUE")
|
||||
.containsOne("Integer.MIN_VALUE, Integer.MAX_VALUE")
|
||||
.containsOne("Long.MIN_VALUE, Long.MAX_VALUE");
|
||||
}
|
||||
|
||||
@@ -2,7 +2,6 @@ package jadx.tests.integration.arith;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import jadx.NotYetImplemented;
|
||||
import jadx.tests.api.IntegrationTest;
|
||||
|
||||
import static jadx.tests.api.utils.assertj.JadxAssertions.assertThat;
|
||||
@@ -15,7 +14,6 @@ public class TestSpecialValues2 extends IntegrationTest {
|
||||
}
|
||||
}
|
||||
|
||||
@NotYetImplemented("Constant value replace")
|
||||
@Test
|
||||
public void test() {
|
||||
noDebugInfo();
|
||||
|
||||
@@ -1,71 +0,0 @@
|
||||
package jadx.tests.integration.arrays;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import jadx.tests.api.IntegrationTest;
|
||||
import jadx.tests.api.utils.assertj.JadxAssertions;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
public class TestRedundantType extends IntegrationTest {
|
||||
|
||||
public static class TestCls {
|
||||
|
||||
public byte[] method() {
|
||||
return new byte[] { 10, 11, 12 };
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test() {
|
||||
JadxAssertions.assertThat(getClassNode(TestCls.class))
|
||||
.code()
|
||||
.contains("return new byte[]{10, 11, 12};");
|
||||
}
|
||||
|
||||
public static class TestByte {
|
||||
|
||||
public byte[] method() {
|
||||
byte[] arr = new byte[50];
|
||||
arr[10] = 126;
|
||||
arr[20] = 127;
|
||||
arr[30] = (byte) 128;
|
||||
arr[40] = (byte) 129;
|
||||
return arr;
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testByte() {
|
||||
JadxAssertions.assertThat(getClassNode(TestByte.class))
|
||||
.code()
|
||||
.contains("arr[10] = 126")
|
||||
.contains("arr[20] = Byte.MAX_VALUE")
|
||||
.contains("arr[30] = Byte.MIN_VALUE")
|
||||
.contains("arr[40] = -127");
|
||||
assertThat(new TestByte().method()[40]).isEqualTo((byte) -127);
|
||||
}
|
||||
|
||||
public static class TestShort {
|
||||
|
||||
public short[] method() {
|
||||
short[] arr = new short[50];
|
||||
arr[10] = 32766;
|
||||
arr[20] = 32767;
|
||||
arr[30] = (short) 32768;
|
||||
arr[40] = (short) 32769;
|
||||
return arr;
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testShort() {
|
||||
JadxAssertions.assertThat(getClassNode(TestShort.class))
|
||||
.code()
|
||||
.contains("arr[10] = 32766")
|
||||
.contains("arr[20] = Short.MAX_VALUE")
|
||||
.contains("arr[30] = Short.MIN_VALUE")
|
||||
.contains("arr[40] = -32767");
|
||||
assertThat(new TestShort().method()[40]).isEqualTo((short) -32767);
|
||||
}
|
||||
}
|
||||
@@ -21,7 +21,7 @@ public class TestCast extends IntegrationTest {
|
||||
}
|
||||
|
||||
public void test3(boolean a) {
|
||||
write(a ? 0 : Byte.MAX_VALUE);
|
||||
write(a ? 0 : (byte) 127);
|
||||
}
|
||||
|
||||
public void test4(boolean a) {
|
||||
@@ -49,7 +49,7 @@ public class TestCast extends IntegrationTest {
|
||||
.code()
|
||||
.contains("write(a ? (byte) 0 : (byte) 1);")
|
||||
.contains("write(a ? (byte) 0 : this.myByte);")
|
||||
.contains("write(a ? (byte) 0 : Byte.MAX_VALUE);")
|
||||
.contains("write(a ? (byte) 0 : (byte) 127);")
|
||||
.contains("write(a ? (short) 0 : (short) 1);")
|
||||
.contains("write(a ? this.myShort : (short) 0);")
|
||||
.contains("write(a ? Short.MIN_VALUE : (short) 0);");
|
||||
|
||||
@@ -3,7 +3,8 @@ package jadx.tests.integration.types;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import jadx.tests.api.IntegrationTest;
|
||||
import jadx.tests.api.utils.assertj.JadxAssertions;
|
||||
|
||||
import static jadx.tests.api.utils.assertj.JadxAssertions.assertThat;
|
||||
|
||||
public class TestTypeResolver9 extends IntegrationTest {
|
||||
|
||||
@@ -13,13 +14,13 @@ public class TestTypeResolver9 extends IntegrationTest {
|
||||
}
|
||||
|
||||
public int test2(byte[] array, int offset) {
|
||||
return (array[offset] * 128) + (array[offset + 1] & 0xFF);
|
||||
return array[offset] * 128 + (array[offset + 1] & 0xFF);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test() {
|
||||
JadxAssertions.assertThat(getClassNode(TestCls.class))
|
||||
assertThat(getClassNode(TestCls.class))
|
||||
.code()
|
||||
.containsOne("return 16777216 * b;")
|
||||
.doesNotContain("Byte.MIN_VALUE");
|
||||
|
||||
+10
-10
@@ -3,7 +3,7 @@ plugins {
|
||||
id("application")
|
||||
id("jadx-library")
|
||||
id("edu.sc.seis.launch4j") version "3.0.6"
|
||||
id("com.gradleup.shadow") version "8.3.6"
|
||||
id("com.gradleup.shadow") version "8.3.8"
|
||||
id("org.beryx.runtime") version "1.13.1"
|
||||
}
|
||||
|
||||
@@ -23,8 +23,8 @@ dependencies {
|
||||
implementation("com.fifesoft:autocomplete:3.3.2")
|
||||
|
||||
// use KtLint for format and check jadx scripts
|
||||
implementation("com.pinterest.ktlint:ktlint-rule-engine:1.5.0")
|
||||
implementation("com.pinterest.ktlint:ktlint-ruleset-standard:1.5.0")
|
||||
implementation("com.pinterest.ktlint:ktlint-rule-engine:1.7.0")
|
||||
implementation("com.pinterest.ktlint:ktlint-ruleset-standard:1.7.0")
|
||||
|
||||
implementation("org.jcommander:jcommander:2.0")
|
||||
implementation("ch.qos.logback:logback-classic:1.5.18")
|
||||
@@ -35,18 +35,18 @@ dependencies {
|
||||
implementation("hu.kazocsaba:image-viewer:1.2.3")
|
||||
implementation("com.twelvemonkeys.imageio:imageio-webp:3.12.0") // WebP support for image viewer
|
||||
|
||||
implementation("com.formdev:flatlaf:3.6")
|
||||
implementation("com.formdev:flatlaf-intellij-themes:3.6")
|
||||
implementation("com.formdev:flatlaf-extras:3.6")
|
||||
implementation("com.formdev:flatlaf:3.6.1")
|
||||
implementation("com.formdev:flatlaf-intellij-themes:3.6.1")
|
||||
implementation("com.formdev:flatlaf-extras:3.6.1")
|
||||
|
||||
implementation("com.google.code.gson:gson:2.13.1")
|
||||
implementation("org.apache.commons:commons-lang3:3.17.0")
|
||||
implementation("org.apache.commons:commons-lang3:3.18.0")
|
||||
implementation("org.apache.commons:commons-text:1.13.1")
|
||||
implementation("commons-io:commons-io:2.19.0")
|
||||
implementation("commons-io:commons-io:2.20.0")
|
||||
|
||||
implementation("io.reactivex.rxjava3:rxjava:3.1.10")
|
||||
implementation("io.reactivex.rxjava3:rxjava:3.1.11")
|
||||
implementation("com.github.akarnokd:rxjava3-swing:3.1.1")
|
||||
implementation("com.android.tools.build:apksig:8.10.0")
|
||||
implementation("com.android.tools.build:apksig:8.11.1")
|
||||
implementation("io.github.skylot:jdwp:2.0.0")
|
||||
|
||||
// Library for hex viewing data
|
||||
|
||||
@@ -9,6 +9,8 @@ import org.slf4j.LoggerFactory;
|
||||
|
||||
import jadx.cli.JCommanderWrapper;
|
||||
import jadx.cli.LogHelper;
|
||||
import jadx.commons.app.JadxSystemInfo;
|
||||
import jadx.core.Jadx;
|
||||
import jadx.core.utils.files.FileUtils;
|
||||
import jadx.gui.logs.LogCollector;
|
||||
import jadx.gui.settings.JadxSettings;
|
||||
@@ -17,7 +19,6 @@ import jadx.gui.ui.MainWindow;
|
||||
import jadx.gui.ui.dialog.ExceptionDialog;
|
||||
import jadx.gui.utils.LafManager;
|
||||
import jadx.gui.utils.NLS;
|
||||
import jadx.gui.utils.SystemInfo;
|
||||
|
||||
public class JadxGUI {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(JadxGUI.class);
|
||||
@@ -67,10 +68,10 @@ public class JadxGUI {
|
||||
|
||||
private static void printSystemInfo() {
|
||||
if (LOG.isDebugEnabled()) {
|
||||
LOG.debug("Starting jadx-gui. Version: '{}'. JVM: {} {}. OS: {} {}",
|
||||
SystemInfo.JADX_VERSION,
|
||||
SystemInfo.JAVA_VM, SystemInfo.JAVA_VER,
|
||||
SystemInfo.OS_NAME, SystemInfo.OS_VERSION);
|
||||
LOG.debug("Starting jadx-gui. Version: '{}'. JVM: {} {}. OS: {}, version: {}, arch: {}",
|
||||
Jadx.getVersion(),
|
||||
JadxSystemInfo.JAVA_VM, JadxSystemInfo.JAVA_VER,
|
||||
JadxSystemInfo.OS_NAME, JadxSystemInfo.OS_VERSION, JadxSystemInfo.OS_ARCH);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -104,6 +104,14 @@ public class BackgroundExecutor {
|
||||
return execute(new SimpleTask(title, Collections.singletonList(backgroundRunnable)));
|
||||
}
|
||||
|
||||
public void startLoading(Runnable backgroundRunnable, Runnable onFinishUiRunnable) {
|
||||
execute(new SimpleTask(NLS.str("progress.load"), backgroundRunnable, onFinishUiRunnable));
|
||||
}
|
||||
|
||||
public void startLoading(Runnable backgroundRunnable) {
|
||||
execute(new SimpleTask(NLS.str("progress.load"), backgroundRunnable));
|
||||
}
|
||||
|
||||
private synchronized void reset() {
|
||||
taskQueueExecutor = (ThreadPoolExecutor) Executors.newFixedThreadPool(1, Utils.simpleThreadFactory("bg"));
|
||||
taskRunning.clear();
|
||||
|
||||
@@ -16,12 +16,12 @@ import org.slf4j.LoggerFactory;
|
||||
|
||||
import ch.qos.logback.classic.Level;
|
||||
|
||||
import jadx.commons.app.JadxSystemInfo;
|
||||
import jadx.core.utils.exceptions.JadxRuntimeException;
|
||||
import jadx.gui.jobs.BackgroundExecutor;
|
||||
import jadx.gui.logs.LogOptions;
|
||||
import jadx.gui.treemodel.JRoot;
|
||||
import jadx.gui.ui.MainWindow;
|
||||
import jadx.gui.utils.SystemInfo;
|
||||
import jadx.gui.utils.UiUtils;
|
||||
|
||||
public class QuarkManager {
|
||||
@@ -132,7 +132,7 @@ public class QuarkManager {
|
||||
}
|
||||
}
|
||||
List<String> cmd = new ArrayList<>();
|
||||
if (SystemInfo.IS_WINDOWS) {
|
||||
if (JadxSystemInfo.IS_WINDOWS) {
|
||||
cmd.add("python");
|
||||
cmd.add("-m");
|
||||
cmd.add("venv");
|
||||
@@ -197,7 +197,7 @@ public class QuarkManager {
|
||||
}
|
||||
|
||||
private Path getVenvPath(String cmd) {
|
||||
if (SystemInfo.IS_WINDOWS) {
|
||||
if (JadxSystemInfo.IS_WINDOWS) {
|
||||
return VENV_PATH.resolve("Scripts").resolve(cmd + ".exe");
|
||||
}
|
||||
return VENV_PATH.resolve("bin").resolve(cmd);
|
||||
|
||||
@@ -8,6 +8,8 @@ import jadx.api.JadxDecompiler;
|
||||
import jadx.api.JavaClass;
|
||||
import jadx.api.JavaPackage;
|
||||
import jadx.core.dex.nodes.PackageNode;
|
||||
import jadx.core.utils.exceptions.InvalidDataException;
|
||||
import jadx.gui.search.providers.ResourceFilter;
|
||||
import jadx.gui.treemodel.JClass;
|
||||
import jadx.gui.treemodel.JResource;
|
||||
import jadx.gui.ui.MainWindow;
|
||||
@@ -26,6 +28,7 @@ public class SearchSettings {
|
||||
private Pattern regexPattern;
|
||||
private ISearchMethod searchMethod;
|
||||
private JavaPackage searchPackage;
|
||||
private ResourceFilter resourceFilter;
|
||||
|
||||
public SearchSettings(String searchString) {
|
||||
this.searchString = searchString;
|
||||
@@ -49,6 +52,11 @@ public class SearchSettings {
|
||||
searchPackage = pkg.getJavaNode();
|
||||
}
|
||||
searchMethod = ISearchMethod.build(this);
|
||||
try {
|
||||
resourceFilter = ResourceFilter.parse(resFilterStr);
|
||||
} catch (InvalidDataException e) {
|
||||
return "Invalid resource file filter: " + e.getMessage();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -112,14 +120,14 @@ public class SearchSettings {
|
||||
return searchMethod;
|
||||
}
|
||||
|
||||
public String getResFilterStr() {
|
||||
return resFilterStr;
|
||||
}
|
||||
|
||||
public void setResFilterStr(String resFilterStr) {
|
||||
this.resFilterStr = resFilterStr;
|
||||
}
|
||||
|
||||
public ResourceFilter getResourceFilter() {
|
||||
return resourceFilter;
|
||||
}
|
||||
|
||||
public int getResSizeLimit() {
|
||||
return resSizeLimit;
|
||||
}
|
||||
|
||||
@@ -23,6 +23,9 @@ public final class MethodSearchProvider extends BaseSearchProvider {
|
||||
|
||||
@Override
|
||||
public @Nullable JNode next(Cancelable cancelable) {
|
||||
if (classes.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
while (true) {
|
||||
if (cancelable.isCanceled()) {
|
||||
return null;
|
||||
|
||||
@@ -0,0 +1,102 @@
|
||||
package jadx.gui.search.providers;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.EnumSet;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import jadx.api.resources.ResourceContentType;
|
||||
import jadx.core.utils.Utils;
|
||||
import jadx.core.utils.exceptions.InvalidDataException;
|
||||
|
||||
import static jadx.api.resources.ResourceContentType.CONTENT_BINARY;
|
||||
import static jadx.api.resources.ResourceContentType.CONTENT_TEXT;
|
||||
|
||||
public class ResourceFilter {
|
||||
|
||||
private static final ResourceFilter ANY = new ResourceFilter(Set.of(), Set.of());
|
||||
|
||||
private static final String VAR_TEXT = "$TEXT";
|
||||
private static final String VAR_BIN = "$BIN";
|
||||
|
||||
public static final String DEFAULT_STR = VAR_TEXT;
|
||||
|
||||
public static ResourceFilter parse(String filterStr) {
|
||||
String str = filterStr.trim();
|
||||
if (str.isEmpty() || str.equals("*")) {
|
||||
return ANY;
|
||||
}
|
||||
Set<ResourceContentType> contentTypes = EnumSet.noneOf(ResourceContentType.class);
|
||||
Set<String> extSet = new LinkedHashSet<>();
|
||||
String[] parts = filterStr.split("[|, ]");
|
||||
for (String part : parts) {
|
||||
if (part.isEmpty()) {
|
||||
continue;
|
||||
}
|
||||
if (part.startsWith("$")) {
|
||||
switch (part) {
|
||||
case VAR_TEXT:
|
||||
contentTypes.add(CONTENT_TEXT);
|
||||
break;
|
||||
case VAR_BIN:
|
||||
contentTypes.add(CONTENT_BINARY);
|
||||
break;
|
||||
default:
|
||||
throw new InvalidDataException("Unknown var name: " + part);
|
||||
}
|
||||
} else {
|
||||
extSet.add(part);
|
||||
}
|
||||
}
|
||||
return new ResourceFilter(contentTypes, extSet);
|
||||
}
|
||||
|
||||
public static String format(ResourceFilter filter) {
|
||||
if (filter.isAnyFile()) {
|
||||
return "*";
|
||||
}
|
||||
List<String> list = new ArrayList<>();
|
||||
Set<ResourceContentType> types = filter.getContentTypes();
|
||||
if (types.contains(CONTENT_TEXT)) {
|
||||
list.add(VAR_TEXT);
|
||||
}
|
||||
if (types.contains(CONTENT_BINARY)) {
|
||||
list.add(VAR_BIN);
|
||||
}
|
||||
list.addAll(filter.getExtSet());
|
||||
return Utils.listToString(list, "|");
|
||||
}
|
||||
|
||||
public static String withContentType(String filterStr, Set<ResourceContentType> contentTypes) {
|
||||
ResourceFilter filter = parse(filterStr);
|
||||
return format(new ResourceFilter(contentTypes, filter.getExtSet()));
|
||||
}
|
||||
|
||||
private final boolean anyFile;
|
||||
private final Set<ResourceContentType> contentTypes;
|
||||
private final Set<String> extSet;
|
||||
|
||||
private ResourceFilter(Set<ResourceContentType> contentTypes, Set<String> extSet) {
|
||||
this.anyFile = contentTypes.isEmpty() && extSet.isEmpty();
|
||||
this.contentTypes = contentTypes.isEmpty() ? Set.of() : contentTypes;
|
||||
this.extSet = extSet.isEmpty() ? Set.of() : extSet;
|
||||
}
|
||||
|
||||
public boolean isAnyFile() {
|
||||
return anyFile;
|
||||
}
|
||||
|
||||
public Set<ResourceContentType> getContentTypes() {
|
||||
return contentTypes;
|
||||
}
|
||||
|
||||
public Set<String> getExtSet() {
|
||||
return extSet;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return format(this);
|
||||
}
|
||||
}
|
||||
@@ -4,8 +4,6 @@ import java.util.ArrayDeque;
|
||||
import java.util.Collections;
|
||||
import java.util.Deque;
|
||||
import java.util.Enumeration;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
import javax.swing.tree.TreeNode;
|
||||
|
||||
@@ -16,6 +14,7 @@ import org.slf4j.LoggerFactory;
|
||||
import jadx.api.ResourceFile;
|
||||
import jadx.api.ResourceType;
|
||||
import jadx.api.plugins.utils.CommonFileUtils;
|
||||
import jadx.api.resources.ResourceContentType;
|
||||
import jadx.api.utils.CodeUtils;
|
||||
import jadx.gui.jobs.Cancelable;
|
||||
import jadx.gui.search.ISearchProvider;
|
||||
@@ -32,10 +31,9 @@ public class ResourceSearchProvider implements ISearchProvider {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(ResourceSearchProvider.class);
|
||||
|
||||
private final SearchSettings searchSettings;
|
||||
private final Set<String> extSet;
|
||||
private final SearchDialog searchDialog;
|
||||
private final ResourceFilter resourceFilter;
|
||||
private final int sizeLimit;
|
||||
private boolean anyExt;
|
||||
|
||||
/**
|
||||
* Resources queue for process. Using UI nodes to reuse loading cache
|
||||
@@ -48,7 +46,7 @@ public class ResourceSearchProvider implements ISearchProvider {
|
||||
|
||||
public ResourceSearchProvider(MainWindow mw, SearchSettings searchSettings, SearchDialog searchDialog) {
|
||||
this.searchSettings = searchSettings;
|
||||
this.extSet = buildAllowedFilesExtensions(searchSettings.getResFilterStr());
|
||||
this.resourceFilter = searchSettings.getResourceFilter();
|
||||
this.sizeLimit = searchSettings.getResSizeLimit() * 1024 * 1024;
|
||||
this.searchDialog = searchDialog;
|
||||
JResource activeResource = searchSettings.getActiveResource();
|
||||
@@ -95,12 +93,20 @@ public class ResourceSearchProvider implements ISearchProvider {
|
||||
if (newPos == -1) {
|
||||
return null;
|
||||
}
|
||||
int lineStart = 1 + CodeUtils.getNewLinePosBefore(content, newPos);
|
||||
int lineEnd = CodeUtils.getNewLinePosAfter(content, newPos);
|
||||
int end = lineEnd == -1 ? content.length() : lineEnd;
|
||||
String line = content.substring(lineStart, end);
|
||||
this.pos = end;
|
||||
return new JResSearchNode(resNode, line.trim(), newPos);
|
||||
if (resNode.getContentType() == ResourceContentType.CONTENT_TEXT) {
|
||||
int lineStart = 1 + CodeUtils.getNewLinePosBefore(content, newPos);
|
||||
int lineEnd = CodeUtils.getNewLinePosAfter(content, newPos);
|
||||
int end = lineEnd == -1 ? content.length() : lineEnd;
|
||||
String line = content.substring(lineStart, end);
|
||||
this.pos = end;
|
||||
return new JResSearchNode(resNode, line.trim(), newPos);
|
||||
} else {
|
||||
int start = Math.max(0, newPos - 30);
|
||||
int end = Math.min(newPos + 50, content.length());
|
||||
String line = content.substring(start, end);
|
||||
this.pos = newPos + searchString.length() + 1;
|
||||
return new JResSearchNode(resNode, line, newPos);
|
||||
}
|
||||
}
|
||||
|
||||
private @Nullable JResource getNextResFile(Cancelable cancelable) {
|
||||
@@ -167,41 +173,41 @@ public class ResourceSearchProvider implements ISearchProvider {
|
||||
return deque;
|
||||
}
|
||||
|
||||
private Set<String> buildAllowedFilesExtensions(String srhResourceFileExt) {
|
||||
String str = srhResourceFileExt.trim();
|
||||
if (str.isEmpty() || str.equals("*")) {
|
||||
anyExt = true;
|
||||
return Collections.emptySet();
|
||||
private boolean shouldProcess(JResource resNode) {
|
||||
if (resNode.getResFile().getType() == ResourceType.ARSC) {
|
||||
// don't check the size of generated resource table, it will also skip all subfiles
|
||||
return resourceFilter.isAnyFile()
|
||||
|| resourceFilter.getContentTypes().contains(ResourceContentType.CONTENT_TEXT)
|
||||
|| resourceFilter.getExtSet().contains("xml");
|
||||
}
|
||||
Set<String> set = new HashSet<>();
|
||||
for (String extStr : str.split("[|.]")) {
|
||||
String ext = extStr.trim();
|
||||
if (!ext.isEmpty()) {
|
||||
anyExt = ext.equals("*");
|
||||
if (anyExt) {
|
||||
break;
|
||||
}
|
||||
set.add(ext);
|
||||
}
|
||||
if (!isAllowedFileType(resNode)) {
|
||||
return false;
|
||||
}
|
||||
return set;
|
||||
return isAllowedFileSize(resNode);
|
||||
}
|
||||
|
||||
private boolean shouldProcess(JResource resNode) {
|
||||
private boolean isAllowedFileType(JResource resNode) {
|
||||
ResourceFile resFile = resNode.getResFile();
|
||||
if (resFile.getType() == ResourceType.ARSC) {
|
||||
// don't check size of generated resource table, it will also skip all sub files
|
||||
return anyExt || extSet.contains("xml");
|
||||
if (resourceFilter.isAnyFile()) {
|
||||
return true;
|
||||
}
|
||||
if (!anyExt) {
|
||||
String fileExt = CommonFileUtils.getFileExtension(resFile.getOriginalName());
|
||||
if (fileExt == null) {
|
||||
return false;
|
||||
}
|
||||
if (!extSet.contains(fileExt)) {
|
||||
return false;
|
||||
}
|
||||
ResourceContentType resContentType = resNode.getContentType();
|
||||
if (resourceFilter.getContentTypes().contains(resContentType)) {
|
||||
return true;
|
||||
}
|
||||
String fileExt = CommonFileUtils.getFileExtension(resFile.getOriginalName());
|
||||
if (fileExt != null && resourceFilter.getExtSet().contains(fileExt)) {
|
||||
return true;
|
||||
}
|
||||
if (resContentType == ResourceContentType.CONTENT_UNKNOWN
|
||||
&& resourceFilter.getContentTypes().contains(ResourceContentType.CONTENT_BINARY)) {
|
||||
// treat unknown file type as binary
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private boolean isAllowedFileSize(JResource resNode) {
|
||||
if (sizeLimit <= 0) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -51,7 +51,7 @@ public class TabStateViewAdapter {
|
||||
viewState.setPreviewTab(tvs.isPreviewTab());
|
||||
return viewState;
|
||||
} catch (Exception e) {
|
||||
LOG.error("Failed to load tab state: " + tvs, e);
|
||||
LOG.error("Failed to load tab state: {}", tvs, e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@ import java.util.Objects;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import jadx.api.data.impl.JadxCodeData;
|
||||
import jadx.gui.search.providers.ResourceFilter;
|
||||
|
||||
public class ProjectData {
|
||||
private int projectVersion = 2;
|
||||
@@ -22,7 +23,7 @@ public class ProjectData {
|
||||
private @Nullable String cacheDir; // don't use relative path adapter
|
||||
private boolean enableLiveReload = false;
|
||||
private List<String> searchHistory = new ArrayList<>();
|
||||
private String searchResourcesFilter = "*";
|
||||
private String searchResourcesFilter = ResourceFilter.DEFAULT_STR;
|
||||
private int searchResourcesSizeLimit = 0; // in MB
|
||||
|
||||
protected Map<String, String> pluginOptions = new HashMap<>();
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package jadx.gui.treemodel;
|
||||
|
||||
import java.nio.file.Path;
|
||||
import java.util.Objects;
|
||||
|
||||
import javax.swing.Icon;
|
||||
import javax.swing.JPopupMenu;
|
||||
@@ -15,11 +16,15 @@ public class JInputFile extends JNode {
|
||||
private final Path filePath;
|
||||
|
||||
public JInputFile(Path filePath) {
|
||||
this.filePath = filePath;
|
||||
this.filePath = Objects.requireNonNull(filePath);
|
||||
}
|
||||
|
||||
@Override
|
||||
public JPopupMenu onTreePopupMenu(MainWindow mainWindow) {
|
||||
return buildInputFilePopupMenu(mainWindow, filePath);
|
||||
}
|
||||
|
||||
public static JPopupMenu buildInputFilePopupMenu(MainWindow mainWindow, Path filePath) {
|
||||
JPopupMenu menu = new JPopupMenu();
|
||||
menu.add(new SimpleMenuItem(NLS.str("popup.add_files"), mainWindow::addFiles));
|
||||
menu.add(new SimpleMenuItem(NLS.str("popup.remove"), () -> mainWindow.removeInput(filePath)));
|
||||
@@ -46,4 +51,22 @@ public class JInputFile extends JNode {
|
||||
public String getTooltip() {
|
||||
return filePath.normalize().toAbsolutePath().toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return filePath.hashCode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (o == null || getClass() != o.getClass()) {
|
||||
return false;
|
||||
}
|
||||
return ((JInputFile) o).filePath.equals(filePath);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "JInputFile{" + filePath + '}';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,7 +17,12 @@ public class JInputFiles extends JNode {
|
||||
|
||||
public JInputFiles(List<Path> files) {
|
||||
for (Path file : files) {
|
||||
add(new JInputFile(file));
|
||||
String fileName = file.getFileName().toString();
|
||||
if (fileName.endsWith(".smali")) {
|
||||
add(new JInputSmaliFile(file));
|
||||
} else {
|
||||
add(new JInputFile(file));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,99 @@
|
||||
package jadx.gui.treemodel;
|
||||
|
||||
import java.nio.file.Path;
|
||||
import java.util.Objects;
|
||||
|
||||
import javax.swing.Icon;
|
||||
import javax.swing.JPopupMenu;
|
||||
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import jadx.api.ICodeInfo;
|
||||
import jadx.api.impl.SimpleCodeInfo;
|
||||
import jadx.core.utils.exceptions.JadxRuntimeException;
|
||||
import jadx.core.utils.files.FileUtils;
|
||||
import jadx.gui.ui.MainWindow;
|
||||
import jadx.gui.ui.codearea.AbstractCodeArea;
|
||||
import jadx.gui.ui.codearea.CodeContentPanel;
|
||||
import jadx.gui.ui.panel.ContentPanel;
|
||||
import jadx.gui.ui.tab.TabbedPane;
|
||||
import jadx.gui.utils.Icons;
|
||||
|
||||
public class JInputSmaliFile extends JEditableNode {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(JInputSmaliFile.class);
|
||||
|
||||
private final Path filePath;
|
||||
|
||||
public JInputSmaliFile(Path filePath) {
|
||||
this.filePath = Objects.requireNonNull(filePath);
|
||||
}
|
||||
|
||||
@Override
|
||||
public JPopupMenu onTreePopupMenu(MainWindow mainWindow) {
|
||||
return JInputFile.buildInputFilePopupMenu(mainWindow, filePath);
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable ContentPanel getContentPanel(TabbedPane tabbedPane) {
|
||||
return new CodeContentPanel(tabbedPane, this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getSyntaxName() {
|
||||
return AbstractCodeArea.SYNTAX_STYLE_SMALI;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ICodeInfo getCodeInfo() {
|
||||
try {
|
||||
return new SimpleCodeInfo(FileUtils.readFile(filePath));
|
||||
} catch (Exception e) {
|
||||
throw new JadxRuntimeException("Failed to read file: " + filePath.toAbsolutePath(), e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void save(String newContent) {
|
||||
try {
|
||||
FileUtils.writeFile(filePath, newContent);
|
||||
LOG.debug("File saved: {}", filePath.toAbsolutePath());
|
||||
} catch (Exception e) {
|
||||
throw new JadxRuntimeException("Failed to write file: " + filePath.toAbsolutePath(), e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public JClass getJParent() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Icon getIcon() {
|
||||
return Icons.FILE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String makeString() {
|
||||
return filePath.getFileName().toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getTooltip() {
|
||||
return filePath.normalize().toAbsolutePath().toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return filePath.hashCode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (o == null || getClass() != o.getClass()) {
|
||||
return false;
|
||||
}
|
||||
return ((JInputSmaliFile) o).filePath.equals(filePath);
|
||||
}
|
||||
}
|
||||
@@ -17,6 +17,7 @@ import jadx.api.ICodeInfo;
|
||||
import jadx.api.JavaNode;
|
||||
import jadx.api.gui.tree.ITreeNode;
|
||||
import jadx.api.metadata.ICodeNodeRef;
|
||||
import jadx.api.resources.ResourceContentType;
|
||||
import jadx.core.utils.ListUtils;
|
||||
import jadx.gui.ui.MainWindow;
|
||||
import jadx.gui.ui.panel.ContentPanel;
|
||||
@@ -56,6 +57,10 @@ public abstract class JNode extends DefaultMutableTreeNode implements ITreeNode,
|
||||
return ICodeInfo.EMPTY;
|
||||
}
|
||||
|
||||
public ResourceContentType getContentType() {
|
||||
return ResourceContentType.CONTENT_TEXT;
|
||||
}
|
||||
|
||||
public boolean isEditable() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
package jadx.gui.treemodel;
|
||||
|
||||
import java.nio.charset.Charset;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
@@ -17,11 +19,13 @@ import jadx.api.ResourceFile;
|
||||
import jadx.api.ResourceType;
|
||||
import jadx.api.ResourcesLoader;
|
||||
import jadx.api.impl.SimpleCodeInfo;
|
||||
import jadx.api.resources.ResourceContentType;
|
||||
import jadx.core.utils.ListUtils;
|
||||
import jadx.core.utils.Utils;
|
||||
import jadx.core.xmlgen.ResContainer;
|
||||
import jadx.gui.jobs.SimpleTask;
|
||||
import jadx.gui.ui.MainWindow;
|
||||
import jadx.gui.ui.codearea.AbstractCodeArea;
|
||||
import jadx.gui.ui.codearea.BinaryContentPanel;
|
||||
import jadx.gui.ui.codearea.CodeContentPanel;
|
||||
import jadx.gui.ui.panel.ContentPanel;
|
||||
@@ -157,16 +161,17 @@ public class JResource extends JLoadableNode {
|
||||
switch (resFile.getType()) {
|
||||
case IMG:
|
||||
return new ImagePanel(tabbedPane, this);
|
||||
case LIB:
|
||||
case CODE:
|
||||
return new BinaryContentPanel(tabbedPane, this, false);
|
||||
case FONT:
|
||||
return new FontPanel(tabbedPane, this);
|
||||
}
|
||||
if (getSyntaxByExtension(resFile.getDeobfName()) == null) {
|
||||
return new BinaryContentPanel(tabbedPane, this);
|
||||
if (getContentType() == ResourceContentType.CONTENT_BINARY) {
|
||||
return new BinaryContentPanel(tabbedPane, this, false);
|
||||
}
|
||||
return new CodeContentPanel(tabbedPane, this);
|
||||
if (getSyntaxByExtension(resFile.getDeobfName()) != null) {
|
||||
return new CodeContentPanel(tabbedPane, this);
|
||||
}
|
||||
// unknown file type, show both text and binary
|
||||
return new BinaryContentPanel(tabbedPane, this, true);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -185,13 +190,18 @@ public class JResource extends JLoadableNode {
|
||||
return codeInfo;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ResourceContentType getContentType() {
|
||||
if (type == JResType.FILE) {
|
||||
return resFile.getType().getContentType();
|
||||
}
|
||||
return ResourceContentType.CONTENT_NONE;
|
||||
}
|
||||
|
||||
private ICodeInfo loadContent() {
|
||||
if (resFile == null || type != JResType.FILE) {
|
||||
return ICodeInfo.EMPTY;
|
||||
}
|
||||
if (!isSupportedForView(resFile.getType())) {
|
||||
return ICodeInfo.EMPTY;
|
||||
}
|
||||
ResContainer rc = resFile.loadContent();
|
||||
if (rc == null) {
|
||||
return ICodeInfo.EMPTY;
|
||||
@@ -216,11 +226,20 @@ public class JResource extends JLoadableNode {
|
||||
|
||||
case RES_LINK:
|
||||
try {
|
||||
return ResourcesLoader.decodeStream(rc.getResLink(), (size, is) -> {
|
||||
ResourceFile resourceFile = rc.getResLink();
|
||||
return ResourcesLoader.decodeStream(resourceFile, (size, is) -> {
|
||||
// TODO: check size before loading
|
||||
if (size > 10 * 1024 * 1024L) {
|
||||
return new SimpleCodeInfo("File too large for view");
|
||||
}
|
||||
return ResourcesLoader.loadToCodeWriter(is);
|
||||
Charset charset;
|
||||
if (resourceFile.getType().getContentType() == ResourceContentType.CONTENT_TEXT) {
|
||||
charset = StandardCharsets.UTF_8;
|
||||
} else {
|
||||
// force one byte charset for binary data to have the same offsets as in a byte array
|
||||
charset = StandardCharsets.US_ASCII;
|
||||
}
|
||||
return ResourcesLoader.loadToCodeWriter(is, charset);
|
||||
});
|
||||
} catch (Exception e) {
|
||||
return new SimpleCodeInfo("Failed to load resource file:\n" + Utils.getStackTrace(e));
|
||||
@@ -257,6 +276,7 @@ public class JResource extends JLoadableNode {
|
||||
|
||||
private static final Map<String, String> EXTENSION_TO_FILE_SYNTAX = jadx.core.utils.Utils.newConstStringMap(
|
||||
"java", SyntaxConstants.SYNTAX_STYLE_JAVA,
|
||||
"smali", AbstractCodeArea.SYNTAX_STYLE_SMALI,
|
||||
"js", SyntaxConstants.SYNTAX_STYLE_JAVASCRIPT,
|
||||
"ts", SyntaxConstants.SYNTAX_STYLE_TYPESCRIPT,
|
||||
"json", SyntaxConstants.SYNTAX_STYLE_JSON,
|
||||
|
||||
@@ -7,11 +7,12 @@ import java.awt.event.InputEvent;
|
||||
import java.awt.event.MouseEvent;
|
||||
import java.awt.event.MouseWheelEvent;
|
||||
|
||||
import jadx.gui.utils.UiUtils;
|
||||
import jadx.commons.app.JadxSystemInfo;
|
||||
|
||||
public class JadxEventQueue extends EventQueue {
|
||||
|
||||
private static final boolean IS_X_TOOLKIT = UiUtils.isXToolkit();
|
||||
private static final boolean IS_X_TOOLKIT = JadxSystemInfo.IS_LINUX
|
||||
&& "sun.awt.X11.XToolkit".equals(Toolkit.getDefaultToolkit().getClass().getName());
|
||||
|
||||
public static void register() {
|
||||
if (IS_X_TOOLKIT) {
|
||||
|
||||
@@ -2,6 +2,7 @@ package jadx.gui.ui;
|
||||
|
||||
import java.awt.BorderLayout;
|
||||
import java.awt.Component;
|
||||
import java.awt.Desktop;
|
||||
import java.awt.Dimension;
|
||||
import java.awt.DisplayMode;
|
||||
import java.awt.Font;
|
||||
@@ -83,6 +84,7 @@ import jadx.api.plugins.events.JadxEvents;
|
||||
import jadx.api.plugins.events.types.ReloadProject;
|
||||
import jadx.api.plugins.events.types.ReloadSettingsWindow;
|
||||
import jadx.api.plugins.utils.CommonFileUtils;
|
||||
import jadx.commons.app.JadxSystemInfo;
|
||||
import jadx.core.Jadx;
|
||||
import jadx.core.dex.nodes.ClassNode;
|
||||
import jadx.core.dex.nodes.FieldNode;
|
||||
@@ -171,7 +173,6 @@ import jadx.gui.utils.Icons;
|
||||
import jadx.gui.utils.LafManager;
|
||||
import jadx.gui.utils.Link;
|
||||
import jadx.gui.utils.NLS;
|
||||
import jadx.gui.utils.SystemInfo;
|
||||
import jadx.gui.utils.UiUtils;
|
||||
import jadx.gui.utils.dbg.UIWatchDog;
|
||||
import jadx.gui.utils.fileswatcher.LiveReloadWorker;
|
||||
@@ -990,13 +991,15 @@ public class MainWindow extends JFrame {
|
||||
ContentPanel panel = tabbedPane.getSelectedContentPanel();
|
||||
if (panel instanceof AbstractCodeContentPanel) {
|
||||
AbstractCodeArea codeArea = ((AbstractCodeContentPanel) panel).getCodeArea();
|
||||
String preferText = codeArea.getSelectedText();
|
||||
if (StringUtils.isEmpty(preferText)) {
|
||||
preferText = codeArea.getWordUnderCaret();
|
||||
}
|
||||
if (!StringUtils.isEmpty(preferText)) {
|
||||
SearchDialog.searchText(MainWindow.this, preferText);
|
||||
return;
|
||||
if (codeArea != null) {
|
||||
String preferText = codeArea.getSelectedText();
|
||||
if (StringUtils.isEmpty(preferText)) {
|
||||
preferText = codeArea.getWordUnderCaret();
|
||||
}
|
||||
if (!StringUtils.isEmpty(preferText)) {
|
||||
SearchDialog.searchText(MainWindow.this, preferText);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
SearchDialog.search(MainWindow.this, SearchDialog.SearchPreset.TEXT);
|
||||
@@ -1214,7 +1217,7 @@ public class MainWindow extends JFrame {
|
||||
JadxGuiAction forwardVariantAction = new JadxGuiAction(ActionModel.FORWARD_V, navController::navForward);
|
||||
JadxGuiAction quarkAction = new JadxGuiAction(ActionModel.QUARK,
|
||||
() -> new QuarkDialog(MainWindow.this).setVisible(true));
|
||||
JadxGuiAction openDeviceAction = new JadxGuiAction(ActionModel.OPEN_DEVICE,
|
||||
JadxGuiAction debuggerAction = new JadxGuiAction(ActionModel.OPEN_DEVICE,
|
||||
() -> new ADBDialog(MainWindow.this).setVisible(true));
|
||||
|
||||
JMenu file = new JadxMenu(NLS.str("menu.file"), shortcutsController);
|
||||
@@ -1274,12 +1277,12 @@ public class MainWindow extends JFrame {
|
||||
tools.add(resetCacheAction);
|
||||
tools.add(deobfMenuItem);
|
||||
tools.add(quarkAction);
|
||||
tools.add(openDeviceAction);
|
||||
tools.add(debuggerAction);
|
||||
|
||||
JMenu help = new JadxMenu(NLS.str("menu.help"), shortcutsController);
|
||||
help.setMnemonic(KeyEvent.VK_H);
|
||||
help.add(showLogAction);
|
||||
if (SystemInfo.IS_UNIX && !SystemInfo.IS_MAC) {
|
||||
if (JadxSystemInfo.IS_LINUX) {
|
||||
help.add(new JadxGuiAction(ActionModel.CREATE_DESKTOP_ENTRY, this::createDesktopEntry));
|
||||
}
|
||||
if (Jadx.isDevVersion()) {
|
||||
@@ -1295,7 +1298,13 @@ public class MainWindow extends JFrame {
|
||||
uiWatchDog.setState(UIWatchDog.onStart());
|
||||
help.add(uiWatchDog);
|
||||
}
|
||||
help.add(aboutAction);
|
||||
|
||||
if (JadxSystemInfo.IS_MAC) {
|
||||
System.setProperty("apple.laf.useScreenMenuBar", "true");
|
||||
Desktop.getDesktop().setAboutHandler(e -> aboutAction.actionPerformed(null));
|
||||
} else {
|
||||
help.add(aboutAction);
|
||||
}
|
||||
|
||||
menuBar = new JadxMenuBar();
|
||||
menuBar.add(file);
|
||||
@@ -1342,7 +1351,7 @@ public class MainWindow extends JFrame {
|
||||
toolbar.addSeparator();
|
||||
toolbar.add(deobfToggleBtn);
|
||||
toolbar.add(quarkAction);
|
||||
toolbar.add(openDeviceAction);
|
||||
toolbar.add(debuggerAction);
|
||||
toolbar.addSeparator();
|
||||
toolbar.add(showLogAction);
|
||||
toolbar.addSeparator();
|
||||
@@ -1377,6 +1386,7 @@ public class MainWindow extends JFrame {
|
||||
decompileAllAction.setEnabled(loaded);
|
||||
deobfAction.setEnabled(loaded);
|
||||
quarkAction.setEnabled(loaded);
|
||||
debuggerAction.setEnabled(loaded);
|
||||
resetCacheAction.setEnabled(loaded);
|
||||
return false;
|
||||
});
|
||||
|
||||
@@ -41,7 +41,7 @@ public enum ActionModel {
|
||||
Shortcut.keyboard(KeyEvent.VK_P, UiUtils.ctrlButton() | KeyEvent.SHIFT_DOWN_MASK)),
|
||||
EXIT(MENU_TOOLBAR, "file.exit", "file.exit", "ui/exit",
|
||||
Shortcut.none()),
|
||||
SYNC(MENU_TOOLBAR, "menu.sync", "menu.sync", "ui/selectWeb",
|
||||
SYNC(MENU_TOOLBAR, "menu.sync", "menu.sync", "ui/locate",
|
||||
Shortcut.keyboard(KeyEvent.VK_T, UiUtils.ctrlButton())),
|
||||
TEXT_SEARCH(MENU_TOOLBAR, "menu.text_search", "menu.text_search", "ui/find",
|
||||
Shortcut.keyboard(KeyEvent.VK_F, UiUtils.ctrlButton() | KeyEvent.SHIFT_DOWN_MASK)),
|
||||
|
||||
@@ -78,7 +78,9 @@ public final class FridaAction extends JNodeAction {
|
||||
}
|
||||
|
||||
private String generateMethodSnippet(JMethod jMth) {
|
||||
return getMethodSnippet(jMth.getJavaMethod(), jMth.getJParent());
|
||||
String classSnippet = generateClassSnippet(jMth.getJParent());
|
||||
String methodSnippet = getMethodSnippet(jMth.getJavaMethod(), jMth.getJParent());
|
||||
return String.format("%s\n%s", classSnippet, methodSnippet);
|
||||
}
|
||||
|
||||
private String generateMethodSnippet(JavaMethod javaMethod, JClass jc) {
|
||||
@@ -115,17 +117,14 @@ public final class FridaAction extends JNodeAction {
|
||||
logArgs = ": " + argNames.stream().map(arg -> arg + "=${" + arg + "}").collect(Collectors.joining(", "));
|
||||
}
|
||||
String shortClassName = mth.getParentClass().getAlias();
|
||||
String classSnippet = generateClassSnippet(jc);
|
||||
if (methodInfo.isConstructor() || methodInfo.getReturnType() == ArgType.VOID) {
|
||||
// no return value
|
||||
return classSnippet + "\n"
|
||||
+ shortClassName + "[\"" + methodName + "\"]" + overload + ".implementation = function (" + args + ") {\n"
|
||||
return shortClassName + "[\"" + methodName + "\"]" + overload + ".implementation = function (" + args + ") {\n"
|
||||
+ " console.log(`" + shortClassName + "." + newMethodName + " is called" + logArgs + "`);\n"
|
||||
+ " this[\"" + methodName + "\"](" + args + ");\n"
|
||||
+ "};";
|
||||
}
|
||||
return classSnippet + "\n"
|
||||
+ shortClassName + "[\"" + methodName + "\"]" + overload + ".implementation = function (" + args + ") {\n"
|
||||
return shortClassName + "[\"" + methodName + "\"]" + overload + ".implementation = function (" + args + ") {\n"
|
||||
+ " console.log(`" + shortClassName + "." + newMethodName + " is called" + logArgs + "`);\n"
|
||||
+ " let result = this[\"" + methodName + "\"](" + args + ");\n"
|
||||
+ " console.log(`" + shortClassName + "." + newMethodName + " result=${result}`);\n"
|
||||
@@ -137,7 +136,7 @@ public final class FridaAction extends JNodeAction {
|
||||
JavaClass javaClass = jc.getCls();
|
||||
String rawClassName = StringEscapeUtils.escapeEcmaScript(javaClass.getRawName());
|
||||
String shortClassName = javaClass.getName();
|
||||
return String.format("let %s = Java.use(\"%s\");", shortClassName, rawClassName);
|
||||
return String.format("var %s = Java.use(\"%s\");", shortClassName, rawClassName);
|
||||
}
|
||||
|
||||
private void showMethodSelectionDialog(JClass jc) {
|
||||
@@ -150,6 +149,8 @@ public final class FridaAction extends JNodeAction {
|
||||
|
||||
private String generateClassAllMethodSnippet(JClass jc, List<JavaMethod> methodList) {
|
||||
StringBuilder result = new StringBuilder();
|
||||
String classSnippet = generateClassSnippet(jc);
|
||||
result.append(classSnippet).append("\n");
|
||||
for (JavaMethod javaMethod : methodList) {
|
||||
result.append(generateMethodSnippet(javaMethod, jc)).append("\n");
|
||||
}
|
||||
|
||||
@@ -479,12 +479,7 @@ public abstract class AbstractCodeArea extends RSyntaxTextArea {
|
||||
}
|
||||
|
||||
public void dispose() {
|
||||
// code area reference can still be used somewhere in UI objects,
|
||||
// reset node reference to allow to GC jadx objects tree
|
||||
node = null;
|
||||
contentPanel = null;
|
||||
|
||||
// also clear internals
|
||||
// clear internals
|
||||
try {
|
||||
setIgnoreRepaint(true);
|
||||
setText("");
|
||||
@@ -513,6 +508,10 @@ public abstract class AbstractCodeArea extends RSyntaxTextArea {
|
||||
} catch (Throwable e) {
|
||||
LOG.debug("Error on code area dispose", e);
|
||||
}
|
||||
// code area reference can still be used somewhere in UI objects,
|
||||
// reset node reference to allow to GC jadx objects tree
|
||||
node = null;
|
||||
contentPanel = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -2,6 +2,8 @@ package jadx.gui.ui.codearea;
|
||||
|
||||
import java.awt.Component;
|
||||
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import jadx.gui.treemodel.JNode;
|
||||
import jadx.gui.ui.panel.ContentPanel;
|
||||
import jadx.gui.ui.tab.TabbedPane;
|
||||
@@ -16,7 +18,15 @@ public abstract class AbstractCodeContentPanel extends ContentPanel {
|
||||
super(panel, jnode);
|
||||
}
|
||||
|
||||
public abstract AbstractCodeArea getCodeArea();
|
||||
public abstract @Nullable AbstractCodeArea getCodeArea();
|
||||
|
||||
public abstract Component getChildrenComponent();
|
||||
|
||||
public void scrollToPos(int pos) {
|
||||
AbstractCodeArea codeArea = getCodeArea();
|
||||
if (codeArea != null) {
|
||||
codeArea.requestFocus();
|
||||
codeArea.scrollToPos(pos);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,7 +3,6 @@ package jadx.gui.ui.codearea;
|
||||
import java.awt.BorderLayout;
|
||||
import java.awt.Component;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
|
||||
import javax.swing.JSplitPane;
|
||||
import javax.swing.JTabbedPane;
|
||||
@@ -14,14 +13,15 @@ import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import jadx.api.ResourcesLoader;
|
||||
import jadx.core.utils.exceptions.JadxException;
|
||||
import jadx.api.resources.ResourceContentType;
|
||||
import jadx.gui.jobs.BackgroundExecutor;
|
||||
import jadx.gui.settings.JadxSettings;
|
||||
import jadx.gui.settings.LineNumbersMode;
|
||||
import jadx.gui.treemodel.JNode;
|
||||
import jadx.gui.treemodel.JResource;
|
||||
import jadx.gui.ui.hexviewer.HexPreviewPanel;
|
||||
import jadx.gui.ui.tab.TabbedPane;
|
||||
import jadx.gui.utils.NLS;
|
||||
import jadx.gui.utils.UiUtils;
|
||||
|
||||
public class BinaryContentPanel extends AbstractCodeContentPanel {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(BinaryContentPanel.class);
|
||||
@@ -29,10 +29,6 @@ public class BinaryContentPanel extends AbstractCodeContentPanel {
|
||||
private final transient HexPreviewPanel hexPreviewPanel;
|
||||
private final transient JTabbedPane areaTabbedPane;
|
||||
|
||||
public BinaryContentPanel(TabbedPane panel, JNode jnode) {
|
||||
this(panel, jnode, true);
|
||||
}
|
||||
|
||||
public BinaryContentPanel(TabbedPane panel, JNode jnode, boolean supportsText) {
|
||||
super(panel, jnode);
|
||||
setLayout(new BorderLayout());
|
||||
@@ -51,33 +47,26 @@ public class BinaryContentPanel extends AbstractCodeContentPanel {
|
||||
SwingUtilities.invokeLater(this::loadSelectedPanel);
|
||||
}
|
||||
|
||||
private void loadToHexView(JNode binaryNode) {
|
||||
private void loadHexView() {
|
||||
if (hexPreviewPanel.isDataLoaded()) {
|
||||
return;
|
||||
}
|
||||
AtomicReference<byte[]> bytesRef = new AtomicReference<>();
|
||||
getMainWindow().getBackgroundExecutor().execute(NLS.str("progress.load"),
|
||||
() -> {
|
||||
byte[] bytes = null;
|
||||
if (binaryNode instanceof JResource) {
|
||||
JResource jResource = (JResource) binaryNode;
|
||||
try {
|
||||
bytes = ResourcesLoader.decodeStream(jResource.getResFile(), (size, is) -> is.readAllBytes());
|
||||
} catch (JadxException e) {
|
||||
LOG.error("Failed to directly load resource binary data {}: {}", jResource.getName(), e.getMessage());
|
||||
}
|
||||
}
|
||||
if (bytes == null) {
|
||||
bytes = binaryNode.getCodeInfo().getCodeStr().getBytes(StandardCharsets.UTF_8);
|
||||
}
|
||||
bytesRef.set(bytes);
|
||||
},
|
||||
taskStatus -> {
|
||||
byte[] bytes = bytesRef.get();
|
||||
if (bytes != null) {
|
||||
hexPreviewPanel.setData(bytes);
|
||||
}
|
||||
});
|
||||
UiUtils.notUiThreadGuard();
|
||||
byte[] bytes = getNodeBytes();
|
||||
UiUtils.uiRunAndWait(() -> hexPreviewPanel.setData(bytes));
|
||||
}
|
||||
|
||||
private byte[] getNodeBytes() {
|
||||
JNode binaryNode = getNode();
|
||||
if (binaryNode instanceof JResource) {
|
||||
JResource jResource = (JResource) binaryNode;
|
||||
try {
|
||||
return ResourcesLoader.decodeStream(jResource.getResFile(), (size, is) -> is.readAllBytes());
|
||||
} catch (Exception e) {
|
||||
LOG.error("Failed to directly load resource binary data {}: {}", jResource.getName(), e.getMessage());
|
||||
}
|
||||
}
|
||||
return binaryNode.getCodeInfo().getCodeStr().getBytes(StandardCharsets.US_ASCII);
|
||||
}
|
||||
|
||||
private JTabbedPane buildTabbedPane() {
|
||||
@@ -96,12 +85,13 @@ public class BinaryContentPanel extends AbstractCodeContentPanel {
|
||||
}
|
||||
|
||||
private void loadSelectedPanel() {
|
||||
BackgroundExecutor bgExec = getMainWindow().getBackgroundExecutor();
|
||||
Component codePanel = getSelectedPanel();
|
||||
if (codePanel instanceof CodeArea) {
|
||||
CodeArea codeArea = (CodeArea) codePanel;
|
||||
codeArea.load();
|
||||
bgExec.startLoading(codeArea::load);
|
||||
} else {
|
||||
loadToHexView(getNode());
|
||||
bgExec.startLoading(this::loadHexView);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -114,6 +104,19 @@ public class BinaryContentPanel extends AbstractCodeContentPanel {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void scrollToPos(int pos) {
|
||||
BackgroundExecutor bgExec = getMainWindow().getBackgroundExecutor();
|
||||
if (getNode().getContentType() == ResourceContentType.CONTENT_TEXT) {
|
||||
areaTabbedPane.setSelectedComponent(textCodePanel);
|
||||
AbstractCodeArea codeArea = textCodePanel.getCodeArea();
|
||||
bgExec.startLoading(codeArea::load, () -> codeArea.scrollToPos(pos));
|
||||
} else {
|
||||
areaTabbedPane.setSelectedComponent(hexPreviewPanel);
|
||||
bgExec.startLoading(this::loadHexView, () -> hexPreviewPanel.scrollToOffset(pos));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Component getChildrenComponent() {
|
||||
return getSelectedPanel();
|
||||
|
||||
@@ -38,6 +38,7 @@ import javax.swing.tree.TreeSelectionModel;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import jadx.commons.app.JadxSystemInfo;
|
||||
import jadx.core.utils.StringUtils;
|
||||
import jadx.core.utils.exceptions.JadxRuntimeException;
|
||||
import jadx.gui.device.debugger.DbgUtils;
|
||||
@@ -48,7 +49,6 @@ import jadx.gui.device.protocol.ADBDeviceInfo;
|
||||
import jadx.gui.ui.MainWindow;
|
||||
import jadx.gui.ui.panel.IDebugController;
|
||||
import jadx.gui.utils.NLS;
|
||||
import jadx.gui.utils.SystemInfo;
|
||||
import jadx.gui.utils.UiUtils;
|
||||
|
||||
public class ADBDialog extends JDialog implements ADB.DeviceStateListener, ADB.JDWPProcessListener {
|
||||
@@ -208,7 +208,7 @@ public class ADBDialog extends JDialog implements ADB.DeviceStateListener, ADB.J
|
||||
}
|
||||
|
||||
private void detectADBPath() {
|
||||
boolean isWinOS = SystemInfo.IS_WINDOWS;
|
||||
boolean isWinOS = JadxSystemInfo.IS_WINDOWS;
|
||||
String slash = isWinOS ? "\\" : "/";
|
||||
String adbName = isWinOS ? "adb.exe" : "adb";
|
||||
String sdkPath = System.getenv("ANDROID_HOME");
|
||||
|
||||
@@ -3,7 +3,6 @@ package jadx.gui.ui.dialog;
|
||||
import java.awt.BorderLayout;
|
||||
import java.awt.Container;
|
||||
import java.awt.Dimension;
|
||||
import java.awt.Font;
|
||||
import java.net.URL;
|
||||
|
||||
import javax.swing.BorderFactory;
|
||||
@@ -29,21 +28,16 @@ public class AboutDialog extends JDialog {
|
||||
}
|
||||
|
||||
public final void initUI() {
|
||||
Font font = new Font("Serif", Font.BOLD, 13);
|
||||
|
||||
URL logoURL = getClass().getResource("/logos/jadx-logo-48px.png");
|
||||
Icon logo = new ImageIcon(logoURL, "jadx logo");
|
||||
Icon logo = new ImageIcon(logoURL, "JADX logo");
|
||||
|
||||
JLabel name = new JLabel("jadx", logo, SwingConstants.CENTER);
|
||||
name.setFont(font);
|
||||
JLabel name = new JLabel("JADX", logo, SwingConstants.CENTER);
|
||||
name.setAlignmentX(0.5f);
|
||||
|
||||
JLabel desc = new JLabel("Dex to Java decompiler");
|
||||
desc.setFont(font);
|
||||
desc.setAlignmentX(0.5f);
|
||||
|
||||
JLabel version = new JLabel("jadx version: " + JadxDecompiler.getVersion());
|
||||
version.setFont(font);
|
||||
JLabel version = new JLabel("JADX version: " + JadxDecompiler.getVersion());
|
||||
version.setAlignmentX(0.5f);
|
||||
|
||||
String javaVm = System.getProperty("java.vm.name");
|
||||
@@ -52,12 +46,10 @@ public class AboutDialog extends JDialog {
|
||||
javaVm = javaVm == null ? "" : javaVm;
|
||||
|
||||
JLabel javaVmLabel = new JLabel("Java VM: " + javaVm);
|
||||
javaVmLabel.setFont(font);
|
||||
javaVmLabel.setAlignmentX(0.5f);
|
||||
|
||||
javaVer = javaVer == null ? "" : javaVer;
|
||||
JLabel javaVerLabel = new JLabel("Java version: " + javaVer);
|
||||
javaVerLabel.setFont(font);
|
||||
javaVerLabel.setAlignmentX(0.5f);
|
||||
|
||||
JPanel textPane = new JPanel();
|
||||
|
||||
@@ -5,6 +5,7 @@ import java.awt.Color;
|
||||
import java.awt.Component;
|
||||
import java.awt.Dimension;
|
||||
import java.awt.Font;
|
||||
import java.awt.Rectangle;
|
||||
import java.awt.event.ActionEvent;
|
||||
import java.awt.event.KeyAdapter;
|
||||
import java.awt.event.KeyEvent;
|
||||
@@ -48,6 +49,7 @@ import org.slf4j.LoggerFactory;
|
||||
|
||||
import ch.qos.logback.classic.Level;
|
||||
|
||||
import jadx.api.JavaNode;
|
||||
import jadx.gui.logs.LogOptions;
|
||||
import jadx.gui.treemodel.JNode;
|
||||
import jadx.gui.treemodel.JResSearchNode;
|
||||
@@ -184,9 +186,12 @@ public abstract class CommonSearchDialog extends JFrame {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
Set<String> uniqueRefs = new HashSet<>();
|
||||
for (JNode node : resultsModel.rows) {
|
||||
String codeNodeRef = node.getJavaNode().getCodeNodeRef().toString();
|
||||
if (uniqueRefs.add(codeNodeRef)) {
|
||||
sb.append(codeNodeRef).append("\n");
|
||||
JavaNode javaNode = node.getJavaNode();
|
||||
if (javaNode != null) {
|
||||
String codeNodeRef = javaNode.getCodeNodeRef().toString();
|
||||
if (uniqueRefs.add(codeNodeRef)) {
|
||||
sb.append(codeNodeRef).append("\n");
|
||||
}
|
||||
}
|
||||
}
|
||||
UiUtils.copyToClipboard(sb.toString());
|
||||
@@ -381,6 +386,15 @@ public abstract class CommonSearchDialog extends JFrame {
|
||||
public Object getValueAt(int row, int column) {
|
||||
return model.getValueAt(row, column);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getScrollableUnitIncrement(Rectangle visibleRect, int orientation, int direction) {
|
||||
// ResultsTable only has two wide columns, the default increment is way too fast
|
||||
if (orientation == SwingConstants.HORIZONTAL) {
|
||||
return 30;
|
||||
}
|
||||
return super.getScrollableUnitIncrement(visibleRect, orientation, direction);
|
||||
}
|
||||
}
|
||||
|
||||
protected static final class ResultsModel extends AbstractTableModel {
|
||||
|
||||
@@ -32,6 +32,7 @@ import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import jadx.api.JadxDecompiler;
|
||||
import jadx.commons.app.JadxSystemInfo;
|
||||
import jadx.core.utils.exceptions.JadxRuntimeException;
|
||||
import jadx.gui.settings.JadxSettings;
|
||||
import jadx.gui.settings.JadxSettingsAdapter;
|
||||
@@ -66,11 +67,11 @@ public class ExceptionDialog extends JDialog {
|
||||
|
||||
Map<String, String> details = new LinkedHashMap<>();
|
||||
details.put("Jadx version", JadxDecompiler.getVersion());
|
||||
details.put("Java version", System.getProperty("java.version", "?"));
|
||||
details.put("Java VM", String.format("%s %s", System.getProperty("java.vm.vendor", "?"),
|
||||
System.getProperty("java.vm.name", "?")));
|
||||
details.put("Platform", String.format("%s (%s %s)", System.getProperty("os.name", "?"),
|
||||
System.getProperty("os.version", "?"), System.getProperty("os.arch", "?")));
|
||||
details.put("Java version", JadxSystemInfo.JAVA_VER);
|
||||
details.put("Java VM", String.format("%s %s",
|
||||
System.getProperty("java.vm.vendor", "?"), System.getProperty("java.vm.name", "?")));
|
||||
details.put("Platform", String.format("%s (%s %s)",
|
||||
JadxSystemInfo.OS_NAME, JadxSystemInfo.OS_VERSION, JadxSystemInfo.OS_ARCH));
|
||||
Runtime runtime = Runtime.getRuntime();
|
||||
details.put("Max heap size", String.format("%d MB", runtime.maxMemory() / (1024 * 1024)));
|
||||
|
||||
|
||||
@@ -4,6 +4,9 @@ import java.awt.BorderLayout;
|
||||
import java.awt.Dimension;
|
||||
import java.awt.FlowLayout;
|
||||
import java.awt.Insets;
|
||||
import java.awt.event.ComponentAdapter;
|
||||
import java.awt.event.ComponentEvent;
|
||||
import java.awt.event.ItemListener;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.EnumSet;
|
||||
@@ -49,6 +52,7 @@ import io.reactivex.rxjava3.schedulers.Schedulers;
|
||||
|
||||
import jadx.api.JavaClass;
|
||||
import jadx.api.JavaPackage;
|
||||
import jadx.api.resources.ResourceContentType;
|
||||
import jadx.core.dex.nodes.PackageNode;
|
||||
import jadx.core.utils.ListUtils;
|
||||
import jadx.core.utils.StringUtils;
|
||||
@@ -63,6 +67,7 @@ import jadx.gui.search.providers.CommentSearchProvider;
|
||||
import jadx.gui.search.providers.FieldSearchProvider;
|
||||
import jadx.gui.search.providers.MergedSearchProvider;
|
||||
import jadx.gui.search.providers.MethodSearchProvider;
|
||||
import jadx.gui.search.providers.ResourceFilter;
|
||||
import jadx.gui.search.providers.ResourceSearchProvider;
|
||||
import jadx.gui.treemodel.JClass;
|
||||
import jadx.gui.treemodel.JNode;
|
||||
@@ -78,6 +83,7 @@ import jadx.gui.utils.UiUtils;
|
||||
import jadx.gui.utils.cache.ValueCache;
|
||||
import jadx.gui.utils.layout.WrapLayout;
|
||||
import jadx.gui.utils.rx.RxUtils;
|
||||
import jadx.gui.utils.ui.DocumentUpdateListener;
|
||||
|
||||
import static jadx.gui.ui.dialog.SearchDialog.SearchOptions.ACTIVE_TAB;
|
||||
import static jadx.gui.ui.dialog.SearchDialog.SearchOptions.CLASS;
|
||||
@@ -316,15 +322,56 @@ public class SearchDialog extends CommonSearchDialog {
|
||||
searchPackagePanel
|
||||
.setPreferredSize(new Dimension(Math.max(packageField.getPreferredSize().width, minPanelSize.width), minPanelSize.height));
|
||||
|
||||
resExtField = new JTextField();
|
||||
resExtField = new JTextField(30);
|
||||
TextStandardActions.attach(resExtField);
|
||||
resExtField.putClientProperty(FlatClientProperties.TEXT_FIELD_SHOW_CLEAR_BUTTON, true);
|
||||
resExtField.setToolTipText(NLS.str("preferences.res_file_ext"));
|
||||
resExtField.setText(mainWindow.getProject().getSearchResourcesFilter());
|
||||
String resFilterStr = mainWindow.getProject().getSearchResourcesFilter();
|
||||
resExtField.setText(resFilterStr);
|
||||
|
||||
JPanel resExtFilePanel = new JPanel(new BorderLayout());
|
||||
ResourceFilter resFilter = ResourceFilter.parse(resFilterStr);
|
||||
|
||||
JCheckBox textResBox = new JCheckBox(NLS.str("search_dialog.res_text"));
|
||||
textResBox.setSelected(resFilter.getContentTypes().contains(ResourceContentType.CONTENT_TEXT));
|
||||
JCheckBox binResBox = new JCheckBox(NLS.str("search_dialog.res_binary"));
|
||||
binResBox.setSelected(resFilter.getContentTypes().contains(ResourceContentType.CONTENT_BINARY));
|
||||
|
||||
ItemListener resContentTypeListener = ev -> {
|
||||
try {
|
||||
Set<ResourceContentType> contentTypes = EnumSet.noneOf(ResourceContentType.class);
|
||||
if (textResBox.isSelected()) {
|
||||
contentTypes.add(ResourceContentType.CONTENT_TEXT);
|
||||
}
|
||||
if (binResBox.isSelected()) {
|
||||
contentTypes.add(ResourceContentType.CONTENT_BINARY);
|
||||
}
|
||||
String newStr = ResourceFilter.withContentType(resExtField.getText(), contentTypes);
|
||||
if (!newStr.equals(resExtField.getText())) {
|
||||
resExtField.setText(newStr);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
// ignore
|
||||
}
|
||||
};
|
||||
textResBox.addItemListener(resContentTypeListener);
|
||||
binResBox.addItemListener(resContentTypeListener);
|
||||
|
||||
resExtField.getDocument().addDocumentListener(new DocumentUpdateListener(ev -> UiUtils.uiRun(() -> {
|
||||
try {
|
||||
ResourceFilter filter = ResourceFilter.parse(resExtField.getText());
|
||||
textResBox.setSelected(filter.getContentTypes().contains(ResourceContentType.CONTENT_TEXT));
|
||||
binResBox.setSelected(filter.getContentTypes().contains(ResourceContentType.CONTENT_BINARY));
|
||||
} catch (Exception e) {
|
||||
// ignore
|
||||
}
|
||||
})));
|
||||
|
||||
JPanel resExtFilePanel = new JPanel();
|
||||
resExtFilePanel.setLayout(new BoxLayout(resExtFilePanel, BoxLayout.LINE_AXIS));
|
||||
resExtFilePanel.setBorder(BorderFactory.createTitledBorder(NLS.str("preferences.res_file_ext")));
|
||||
resExtFilePanel.add(resExtField, BorderLayout.CENTER);
|
||||
resExtFilePanel.add(resExtField);
|
||||
resExtFilePanel.add(textResBox);
|
||||
resExtFilePanel.add(binResBox);
|
||||
resExtFilePanel.setPreferredSize(calcMinSizeForTitledBorder(resExtFilePanel));
|
||||
|
||||
resSizeLimit = new JSpinner(new SpinnerNumberModel(mainWindow.getProject().getSearchResourcesSizeLimit(), 0, Integer.MAX_VALUE, 1));
|
||||
@@ -347,6 +394,8 @@ public class SearchDialog extends CommonSearchDialog {
|
||||
boolean resSearch = searchOptions.contains(RESOURCE);
|
||||
resExtFilePanel.setVisible(resSearch);
|
||||
sizeLimitPanel.setVisible(resSearch);
|
||||
optionsPanel.revalidate();
|
||||
optionsPanel.repaint();
|
||||
});
|
||||
|
||||
JPanel searchPane = new JPanel();
|
||||
@@ -367,6 +416,13 @@ public class SearchDialog extends CommonSearchDialog {
|
||||
contentPanel.add(buttonPane, BorderLayout.PAGE_END);
|
||||
getContentPane().add(contentPanel);
|
||||
|
||||
addComponentListener(new ComponentAdapter() {
|
||||
@Override
|
||||
public void componentResized(ComponentEvent e) {
|
||||
optionsPanel.revalidate();
|
||||
optionsPanel.repaint();
|
||||
}
|
||||
});
|
||||
setLocationRelativeTo(null);
|
||||
setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);
|
||||
}
|
||||
@@ -500,7 +556,7 @@ public class SearchDialog extends CommonSearchDialog {
|
||||
searchEmitter.getFlowable()));
|
||||
}
|
||||
searchDisposable = searchEvents
|
||||
.debounce(50, TimeUnit.MILLISECONDS)
|
||||
.debounce(100, TimeUnit.MILLISECONDS)
|
||||
.observeOn(Schedulers.from(searchBackgroundExecutor))
|
||||
.subscribe(t -> this.search(searchField.getText()));
|
||||
|
||||
@@ -551,10 +607,11 @@ public class SearchDialog extends CommonSearchDialog {
|
||||
|
||||
SearchTask newSearchTask = new SearchTask(mainWindow, this::addSearchResult, this::searchFinished);
|
||||
if (!buildSearch(newSearchTask, text, searchSettings)) {
|
||||
UiUtils.highlightAsErrorField(searchField, true);
|
||||
return null;
|
||||
}
|
||||
// save search settings
|
||||
mainWindow.getProject().setSearchResourcesFilter(searchSettings.getResFilterStr());
|
||||
mainWindow.getProject().setSearchResourcesFilter(resExtField.getText().trim());
|
||||
mainWindow.getProject().setSearchResourcesSizeLimit(searchSettings.getResSizeLimit());
|
||||
return newSearchTask;
|
||||
}
|
||||
|
||||
@@ -135,6 +135,12 @@ public class HexPreviewPanel extends JPanel {
|
||||
}
|
||||
}
|
||||
|
||||
public void scrollToOffset(int pos) {
|
||||
hexCodeArea.setSelection(pos, pos + 1);
|
||||
hexCodeArea.setActiveCaretPosition(pos);
|
||||
hexCodeArea.centerOnPosition(hexCodeArea.getActiveCaretPosition());
|
||||
}
|
||||
|
||||
public void enableUpdate() {
|
||||
CodeAreaCaretListener caretMovedListener = (CodeAreaCaretPosition caretPosition) -> updateValues();
|
||||
hexCodeArea.addCaretMovedListener(caretMovedListener);
|
||||
|
||||
@@ -3,6 +3,8 @@ package jadx.gui.ui.panel;
|
||||
import javax.swing.JPanel;
|
||||
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import jadx.gui.settings.JadxSettings;
|
||||
import jadx.gui.treemodel.JClass;
|
||||
@@ -12,7 +14,7 @@ import jadx.gui.ui.tab.TabbedPane;
|
||||
import jadx.gui.ui.tab.TabsController;
|
||||
|
||||
public abstract class ContentPanel extends JPanel {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(ContentPanel.class);
|
||||
private static final long serialVersionUID = 3237031760631677822L;
|
||||
|
||||
protected TabbedPane tabbedPane;
|
||||
@@ -41,6 +43,10 @@ public abstract class ContentPanel extends JPanel {
|
||||
return node;
|
||||
}
|
||||
|
||||
public void scrollToPos(int pos) {
|
||||
LOG.warn("ContentPanel.scrollToPos method not implemented, class: {}", getClass().getSimpleName());
|
||||
}
|
||||
|
||||
/**
|
||||
* Allows to show a tool tip on the tab e.g. for displaying a long path of the
|
||||
* selected entry inside the APK file.
|
||||
|
||||
@@ -63,4 +63,6 @@ public interface ITabStatesListener {
|
||||
default void onTabSave(TabBlueprint blueprint, EditorViewState viewState) {
|
||||
}
|
||||
|
||||
default void onTabPreviewChange(TabBlueprint blueprint) {
|
||||
}
|
||||
}
|
||||
|
||||
@@ -79,4 +79,9 @@ public class LogTabStates implements ITabStatesListener {
|
||||
public void onTabVisibilityChange(TabBlueprint blueprint) {
|
||||
LOG.debug("onTabVisibilityChange: blueprint={}", blueprint);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTabPreviewChange(TabBlueprint blueprint) {
|
||||
LOG.debug("onTabPreviewChange: blueprint={}", blueprint);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ import jadx.gui.treemodel.JNode;
|
||||
|
||||
public class TabBlueprint {
|
||||
private final JNode node;
|
||||
private boolean created;
|
||||
private boolean pinned;
|
||||
private boolean bookmarked;
|
||||
private boolean hidden;
|
||||
@@ -19,6 +20,14 @@ public class TabBlueprint {
|
||||
return node;
|
||||
}
|
||||
|
||||
public boolean isCreated() {
|
||||
return created;
|
||||
}
|
||||
|
||||
public void setCreated(boolean created) {
|
||||
this.created = created;
|
||||
}
|
||||
|
||||
public boolean isPinned() {
|
||||
return pinned;
|
||||
}
|
||||
|
||||
@@ -64,7 +64,13 @@ public class TabComponent extends JPanel {
|
||||
}
|
||||
|
||||
private Font getLabelFont() {
|
||||
return tabsController.getMainWindow().getSettings().getFont().deriveFont(Font.BOLD);
|
||||
Font font = tabsController.getMainWindow().getSettings().getFont();
|
||||
int style = font.getStyle();
|
||||
style |= Font.BOLD;
|
||||
if (getBlueprint().isPreviewTab()) {
|
||||
style ^= Font.ITALIC; // flip italic bit to distinguish preview
|
||||
}
|
||||
return font.deriveFont(style);
|
||||
}
|
||||
|
||||
private void init() {
|
||||
@@ -75,14 +81,12 @@ public class TabComponent extends JPanel {
|
||||
icon = new OverlayIcon(node.getIcon());
|
||||
|
||||
label = new NodeLabel(buildTabTitle(node), node.disableHtml());
|
||||
makeLabelFont();
|
||||
String toolTip = contentPanel.getTabTooltip();
|
||||
if (toolTip != null) {
|
||||
setToolTipText(toolTip);
|
||||
}
|
||||
label.setBorder(BorderFactory.createEmptyBorder(0, 0, 0, 10));
|
||||
label.setIcon(icon);
|
||||
updateBookmarkIcon();
|
||||
if (node instanceof JEditableNode) {
|
||||
((JEditableNode) node).addChangeListener(c -> label.setText(buildTabTitle(node)));
|
||||
}
|
||||
@@ -122,6 +126,9 @@ public class TabComponent extends JPanel {
|
||||
menu.show(e.getComponent(), e.getX(), e.getY());
|
||||
} else if (SwingUtilities.isLeftMouseButton(e)) {
|
||||
tabsController.selectTab(node);
|
||||
if (e.getClickCount() == 2) {
|
||||
tabsController.setTabPreview(node, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -129,11 +136,18 @@ public class TabComponent extends JPanel {
|
||||
addListenerForDnd();
|
||||
|
||||
add(label);
|
||||
updateCloseOrPinButton();
|
||||
setBorder(BorderFactory.createEmptyBorder(0, 0, 0, 0));
|
||||
|
||||
update();
|
||||
}
|
||||
|
||||
public void updateCloseOrPinButton() {
|
||||
public void update() {
|
||||
updateCloseOrPinButton();
|
||||
updateBookmarkIcon();
|
||||
updateFont();
|
||||
}
|
||||
|
||||
private void updateCloseOrPinButton() {
|
||||
if (getBlueprint().isPinned()) {
|
||||
if (closeBtn.isShowing()) {
|
||||
remove(closeBtn);
|
||||
@@ -151,7 +165,7 @@ public class TabComponent extends JPanel {
|
||||
}
|
||||
}
|
||||
|
||||
public void updateBookmarkIcon() {
|
||||
private void updateBookmarkIcon() {
|
||||
icon.clear();
|
||||
|
||||
if (getBlueprint().isBookmarked()) {
|
||||
@@ -174,14 +188,8 @@ public class TabComponent extends JPanel {
|
||||
tabsController.setTabBookmarked(getNode(), bookmarked);
|
||||
}
|
||||
|
||||
private void makeLabelFont() {
|
||||
boolean previewTab = getBlueprint().isPreviewTab();
|
||||
if (previewTab) {
|
||||
Font newLabelFont = new Font(label.getFont().getName(), Font.ITALIC, label.getFont().getSize());
|
||||
label.setFont(newLabelFont);
|
||||
} else {
|
||||
label.setFont(getLabelFont());
|
||||
}
|
||||
private void updateFont() {
|
||||
label.setFont(getLabelFont());
|
||||
}
|
||||
|
||||
private void addListenerForDnd() {
|
||||
@@ -342,9 +350,10 @@ public class TabComponent extends JPanel {
|
||||
}
|
||||
|
||||
public TabBlueprint getBlueprint() {
|
||||
TabBlueprint blueprint = tabsController.getTabByNode(contentPanel.getNode());
|
||||
JNode node = contentPanel.getNode();
|
||||
TabBlueprint blueprint = tabsController.getTabByNode(node);
|
||||
if (blueprint == null) {
|
||||
throw new JadxRuntimeException("TabComponent does not have a corresponding TabBlueprint");
|
||||
throw new JadxRuntimeException("TabComponent does not have a corresponding TabBlueprint, node: " + node);
|
||||
}
|
||||
return blueprint;
|
||||
}
|
||||
|
||||
@@ -86,12 +86,7 @@ public class TabbedPane extends JTabbedPane implements ITabStatesListener {
|
||||
int index = getSelectedIndex();
|
||||
int maxIndex = getTabCount() - 1;
|
||||
index += direction;
|
||||
// switch between first tab <-> last tab
|
||||
if (index < 0) {
|
||||
index = maxIndex;
|
||||
} else if (index > maxIndex) {
|
||||
index = 0;
|
||||
}
|
||||
index = Math.max(0, Math.min(maxIndex, index));
|
||||
try {
|
||||
setSelectedIndex(index);
|
||||
} catch (IndexOutOfBoundsException e) {
|
||||
@@ -225,26 +220,21 @@ public class TabbedPane extends JTabbedPane implements ITabStatesListener {
|
||||
}
|
||||
|
||||
private @Nullable ContentPanel showCode(JumpPosition jumpPos) {
|
||||
ContentPanel contentPanel = getContentPanel(jumpPos.getNode());
|
||||
if (contentPanel != null) {
|
||||
selectTab(contentPanel);
|
||||
scrollToPos(contentPanel, jumpPos.getPos());
|
||||
JNode jumpNode = jumpPos.getNode();
|
||||
ContentPanel contentPanel = getContentPanel(jumpNode);
|
||||
if (contentPanel == null) {
|
||||
return null;
|
||||
}
|
||||
selectTab(contentPanel);
|
||||
int pos = jumpPos.getPos();
|
||||
if (pos < 0) {
|
||||
LOG.warn("Invalid jump: {}", jumpPos, new JadxRuntimeException());
|
||||
pos = 0;
|
||||
}
|
||||
contentPanel.scrollToPos(pos);
|
||||
return contentPanel;
|
||||
}
|
||||
|
||||
private void scrollToPos(ContentPanel contentPanel, int pos) {
|
||||
if (pos == 0) {
|
||||
LOG.warn("Ignore zero jump!", new JadxRuntimeException());
|
||||
return;
|
||||
}
|
||||
if (contentPanel instanceof AbstractCodeContentPanel) {
|
||||
AbstractCodeArea codeArea = ((AbstractCodeContentPanel) contentPanel).getCodeArea();
|
||||
codeArea.requestFocus();
|
||||
codeArea.scrollToPos(pos);
|
||||
}
|
||||
}
|
||||
|
||||
public void selectTab(ContentPanel contentPanel) {
|
||||
controller.selectTab(contentPanel.getNode());
|
||||
}
|
||||
@@ -259,7 +249,7 @@ public class TabbedPane extends JTabbedPane implements ITabStatesListener {
|
||||
} else {
|
||||
selectTab(panel);
|
||||
}
|
||||
ClassCodeContentPanel codePane = ((ClassCodeContentPanel) panel);
|
||||
ClassCodeContentPanel codePane = (ClassCodeContentPanel) panel;
|
||||
codePane.showSmaliPane();
|
||||
SmaliArea smaliArea = (SmaliArea) codePane.getSmaliCodeArea();
|
||||
if (debugMode) {
|
||||
@@ -272,7 +262,10 @@ public class TabbedPane extends JTabbedPane implements ITabStatesListener {
|
||||
public @Nullable JumpPosition getCurrentPosition() {
|
||||
ContentPanel selectedCodePanel = getSelectedContentPanel();
|
||||
if (selectedCodePanel instanceof AbstractCodeContentPanel) {
|
||||
return ((AbstractCodeContentPanel) selectedCodePanel).getCodeArea().getCurrentPosition();
|
||||
AbstractCodeArea codeArea = ((AbstractCodeContentPanel) selectedCodePanel).getCodeArea();
|
||||
if (codeArea != null) {
|
||||
return codeArea.getCurrentPosition();
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
@@ -414,10 +407,15 @@ public class TabbedPane extends JTabbedPane implements ITabStatesListener {
|
||||
if (blueprint.isHidden()) {
|
||||
return;
|
||||
}
|
||||
ContentPanel newPanel = blueprint.getNode().getContentPanel(this);
|
||||
JNode node = blueprint.getNode();
|
||||
ContentPanel newPanel = node.getContentPanel(this);
|
||||
if (newPanel != null) {
|
||||
if (node != newPanel.getNode()) {
|
||||
throw new JadxRuntimeException("Incorrect node found in content panel");
|
||||
}
|
||||
FocusManager.listen(newPanel);
|
||||
addContentPanel(newPanel);
|
||||
blueprint.setCreated(true);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -444,13 +442,28 @@ public class TabbedPane extends JTabbedPane implements ITabStatesListener {
|
||||
|
||||
@Override
|
||||
public void onTabClose(TabBlueprint blueprint) {
|
||||
ContentPanel contentPanel = getTabByNode(blueprint.getNode());
|
||||
if (contentPanel == null) {
|
||||
ContentPanel contentPanelToClose = getTabByNode(blueprint.getNode());
|
||||
if (contentPanelToClose == null) {
|
||||
return;
|
||||
}
|
||||
tabsMap.remove(contentPanel.getNode());
|
||||
remove(contentPanel);
|
||||
contentPanel.dispose();
|
||||
|
||||
ContentPanel currentContentPanel = getSelectedContentPanel();
|
||||
if (currentContentPanel == contentPanelToClose) {
|
||||
if (lastTab != null && lastTab.getNode() != null) {
|
||||
selectTab(lastTab);
|
||||
} else if (getTabCount() > 1) {
|
||||
int removalIdx = indexOfComponent(contentPanelToClose);
|
||||
if (removalIdx > 0) { // select left tab
|
||||
setSelectedIndex(removalIdx - 1);
|
||||
} else if (removalIdx == 0) { // select right tab
|
||||
setSelectedIndex(removalIdx + 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
tabsMap.remove(contentPanelToClose.getNode());
|
||||
remove(contentPanelToClose);
|
||||
contentPanelToClose.dispose();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -467,9 +480,13 @@ public class TabbedPane extends JTabbedPane implements ITabStatesListener {
|
||||
if (tabComponent == null) {
|
||||
return;
|
||||
}
|
||||
boolean restoreSelection = contentPanel == getSelectedContentPanel();
|
||||
remove(contentPanel);
|
||||
add(contentPanel, position);
|
||||
setTabComponentAt(position, tabComponent);
|
||||
if (restoreSelection) {
|
||||
setSelectedIndex(position);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -478,7 +495,7 @@ public class TabbedPane extends JTabbedPane implements ITabStatesListener {
|
||||
if (tabComponent == null) {
|
||||
return;
|
||||
}
|
||||
tabComponent.updateCloseOrPinButton();
|
||||
tabComponent.update();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -487,7 +504,7 @@ public class TabbedPane extends JTabbedPane implements ITabStatesListener {
|
||||
if (tabComponent == null) {
|
||||
return;
|
||||
}
|
||||
tabComponent.updateBookmarkIcon();
|
||||
tabComponent.update();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -500,6 +517,15 @@ public class TabbedPane extends JTabbedPane implements ITabStatesListener {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTabPreviewChange(TabBlueprint blueprint) {
|
||||
TabComponent tabComponent = getTabComponentByNode(blueprint.getNode());
|
||||
if (tabComponent == null) {
|
||||
return;
|
||||
}
|
||||
tabComponent.update();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTabRestore(TabBlueprint blueprint, EditorViewState viewState) {
|
||||
ContentPanel contentPanel = getTabByNode(blueprint.getNode());
|
||||
|
||||
@@ -77,6 +77,10 @@ public class TabsController {
|
||||
blueprint = newBlueprint;
|
||||
}
|
||||
setTabHiddenInternal(blueprint, hidden);
|
||||
if (!blueprint.isCreated()) {
|
||||
LOG.warn("No content panel for node: {}", node);
|
||||
closeTabForce(blueprint);
|
||||
}
|
||||
return blueprint;
|
||||
}
|
||||
|
||||
@@ -277,6 +281,18 @@ public class TabsController {
|
||||
}
|
||||
}
|
||||
|
||||
public void setTabPreview(JNode node, boolean isPreview) {
|
||||
TabBlueprint blueprint = getTabByNode(node);
|
||||
setTabPreviewInternal(blueprint, isPreview);
|
||||
}
|
||||
|
||||
private void setTabPreviewInternal(TabBlueprint blueprint, boolean isPreview) {
|
||||
if (blueprint != null && blueprint.isPreviewTab() != isPreview) {
|
||||
blueprint.setPreviewTab(isPreview);
|
||||
listeners.forEach(l -> l.onTabPreviewChange(blueprint));
|
||||
}
|
||||
}
|
||||
|
||||
private void removeTabIfNotReferenced(TabBlueprint blueprint) {
|
||||
if (blueprint.isHidden() && !blueprint.isReferenced()) {
|
||||
tabsMap.remove(blueprint.getNode());
|
||||
|
||||
@@ -13,6 +13,8 @@ import javax.swing.JTextArea;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import jadx.commons.app.JadxSystemInfo;
|
||||
|
||||
import static java.awt.Desktop.Action;
|
||||
|
||||
public class Link extends JLabel {
|
||||
@@ -61,13 +63,13 @@ public class Link extends JLabel {
|
||||
}
|
||||
}
|
||||
try {
|
||||
if (SystemInfo.IS_WINDOWS) {
|
||||
if (JadxSystemInfo.IS_WINDOWS) {
|
||||
new ProcessBuilder()
|
||||
.command(new String[] { "rundll32", "url.dll,FileProtocolHandler", url })
|
||||
.start();
|
||||
return;
|
||||
}
|
||||
if (SystemInfo.IS_MAC) {
|
||||
if (JadxSystemInfo.IS_MAC) {
|
||||
new ProcessBuilder()
|
||||
.command(new String[] { "open", url })
|
||||
.start();
|
||||
|
||||
@@ -1,24 +0,0 @@
|
||||
package jadx.gui.utils;
|
||||
|
||||
import java.util.Locale;
|
||||
|
||||
import jadx.api.JadxDecompiler;
|
||||
|
||||
public class SystemInfo {
|
||||
public static final String JADX_VERSION = JadxDecompiler.getVersion();
|
||||
|
||||
public static final String JAVA_VM = System.getProperty("java.vm.name");
|
||||
public static final String JAVA_VER = System.getProperty("java.version");
|
||||
|
||||
public static final String OS_NAME = System.getProperty("os.name");
|
||||
public static final String OS_VERSION = System.getProperty("os.version");
|
||||
|
||||
private static final String LOWER_OS_NAME = OS_NAME.toLowerCase(Locale.ENGLISH);
|
||||
public static final boolean IS_WINDOWS = LOWER_OS_NAME.startsWith("windows");
|
||||
public static final boolean IS_MAC = LOWER_OS_NAME.startsWith("mac");
|
||||
public static final boolean IS_LINUX = LOWER_OS_NAME.startsWith("linux");
|
||||
public static final boolean IS_UNIX = !IS_WINDOWS;
|
||||
|
||||
private SystemInfo() {
|
||||
}
|
||||
}
|
||||
@@ -45,6 +45,7 @@ import org.slf4j.LoggerFactory;
|
||||
import com.formdev.flatlaf.extras.FlatSVGIcon;
|
||||
|
||||
import jadx.commons.app.JadxCommonEnv;
|
||||
import jadx.commons.app.JadxSystemInfo;
|
||||
import jadx.core.dex.info.AccessInfo;
|
||||
import jadx.core.dex.instructions.args.ArgType;
|
||||
import jadx.core.utils.StringUtils;
|
||||
@@ -278,7 +279,7 @@ public class UiUtils {
|
||||
@SuppressWarnings("deprecation")
|
||||
@MagicConstant(flagsFromClass = InputEvent.class)
|
||||
private static int getCtrlButton() {
|
||||
if (SystemInfo.IS_MAC) {
|
||||
if (JadxSystemInfo.IS_MAC) {
|
||||
return Toolkit.getDefaultToolkit().getMenuShortcutKeyMask();
|
||||
} else {
|
||||
return InputEvent.CTRL_DOWN_MASK;
|
||||
@@ -444,12 +445,6 @@ public class UiUtils {
|
||||
}
|
||||
}
|
||||
|
||||
public static boolean isXToolkit() {
|
||||
return SystemInfo.IS_UNIX
|
||||
&& !SystemInfo.IS_MAC
|
||||
&& "sun.awt.X11.XToolkit".equals(Toolkit.getDefaultToolkit().getClass().getName());
|
||||
}
|
||||
|
||||
@TestOnly
|
||||
public static void debugTimer(int periodInSeconds, Runnable action) {
|
||||
if (!LOG.isDebugEnabled()) {
|
||||
|
||||
@@ -8,7 +8,7 @@ import java.util.Set;
|
||||
|
||||
import javax.swing.KeyStroke;
|
||||
|
||||
import jadx.gui.utils.SystemInfo;
|
||||
import jadx.commons.app.JadxSystemInfo;
|
||||
|
||||
public class Shortcut {
|
||||
private static final Set<Integer> FORBIDDEN_KEY_CODES = new HashSet<>(List.of(
|
||||
@@ -88,7 +88,7 @@ public class Shortcut {
|
||||
public KeyStroke toKeyStroke() {
|
||||
return isKeyboard()
|
||||
? KeyStroke.getKeyStroke(keyCode, modifiers,
|
||||
modifiers != 0 && SystemInfo.IS_MAC)
|
||||
modifiers != 0 && JadxSystemInfo.IS_MAC)
|
||||
: null;
|
||||
}
|
||||
|
||||
|
||||
@@ -179,6 +179,8 @@ search_dialog.resource=Ressource
|
||||
search_dialog.keep_open=Offen halten
|
||||
search_dialog.tip_searching=Suchen…
|
||||
search_dialog.limit_package=Begrenzung auf Paket:
|
||||
#search_dialog.res_text=Text
|
||||
#search_dialog.res_binary=Binary
|
||||
search_dialog.package_not_found=Kein passendes Paket gefunden
|
||||
search_dialog.copy=alles kopieren
|
||||
|
||||
|
||||
@@ -179,6 +179,8 @@ search_dialog.resource=Resource
|
||||
search_dialog.keep_open=Keep open
|
||||
search_dialog.tip_searching=Searching
|
||||
search_dialog.limit_package=Limit to package:
|
||||
search_dialog.res_text=Text
|
||||
search_dialog.res_binary=Binary
|
||||
search_dialog.package_not_found=No matching package found
|
||||
search_dialog.copy=Copy All
|
||||
|
||||
|
||||
@@ -179,6 +179,8 @@ search_dialog.regex=Regex
|
||||
#search_dialog.keep_open=Keep open
|
||||
#search_dialog.tip_searching=Searching
|
||||
#search_dialog.limit_package=Limit to package:
|
||||
#search_dialog.res_text=Text
|
||||
#search_dialog.res_binary=Binary
|
||||
#search_dialog.package_not_found=No matching package found
|
||||
search_dialog.copy=copiar todo
|
||||
|
||||
|
||||
@@ -179,6 +179,8 @@ search_dialog.resource=Sumber daya
|
||||
search_dialog.keep_open=Tetap terbuka
|
||||
search_dialog.tip_searching=Mencari
|
||||
#search_dialog.limit_package=Limit to package:
|
||||
#search_dialog.res_text=Text
|
||||
#search_dialog.res_binary=Binary
|
||||
#search_dialog.package_not_found=No matching package found
|
||||
search_dialog.copy=salin semua
|
||||
|
||||
|
||||
@@ -179,6 +179,8 @@ search_dialog.resource=리소스
|
||||
search_dialog.keep_open=열어 두기
|
||||
search_dialog.tip_searching=검색 중...
|
||||
#search_dialog.limit_package=Limit to package:
|
||||
#search_dialog.res_text=Text
|
||||
#search_dialog.res_binary=Binary
|
||||
#search_dialog.package_not_found=No matching package found
|
||||
search_dialog.copy=모두 복사
|
||||
|
||||
|
||||
@@ -179,6 +179,8 @@ search_dialog.resource=Recursos
|
||||
search_dialog.keep_open=Manter aberto
|
||||
search_dialog.tip_searching=Buscando
|
||||
#search_dialog.limit_package=Limit to package:
|
||||
#search_dialog.res_text=Text
|
||||
#search_dialog.res_binary=Binary
|
||||
#search_dialog.package_not_found=No matching package found
|
||||
search_dialog.copy=skopiuj wszystko
|
||||
|
||||
|
||||
@@ -179,6 +179,8 @@ search_dialog.resource=Ресурсы
|
||||
search_dialog.keep_open=Оставлять поиск открытым
|
||||
search_dialog.tip_searching=Поиск...
|
||||
#search_dialog.limit_package=Limit to package:
|
||||
#search_dialog.res_text=Text
|
||||
#search_dialog.res_binary=Binary
|
||||
#search_dialog.package_not_found=No matching package found
|
||||
search_dialog.copy=скопировать все
|
||||
|
||||
|
||||
@@ -179,6 +179,8 @@ search_dialog.resource=资源
|
||||
search_dialog.keep_open=保持窗口
|
||||
search_dialog.tip_searching=搜索中…
|
||||
search_dialog.limit_package=限制package:
|
||||
#search_dialog.res_text=Text
|
||||
#search_dialog.res_binary=Binary
|
||||
search_dialog.package_not_found=没有找到匹配的package
|
||||
search_dialog.copy=复制全部
|
||||
|
||||
|
||||
@@ -179,6 +179,8 @@ search_dialog.resource=資源
|
||||
search_dialog.keep_open=保持開啟
|
||||
search_dialog.tip_searching=正在搜尋
|
||||
search_dialog.limit_package=限制至套件:
|
||||
#search_dialog.res_text=Text
|
||||
#search_dialog.res_binary=Binary
|
||||
search_dialog.package_not_found=找不到符合的套件
|
||||
search_dialog.copy=複製全部
|
||||
|
||||
|
||||
Regular → Executable
+2
-2
@@ -1,5 +1,5 @@
|
||||
<!-- Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. -->
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<rect x="2.5" y="2.5" width="11" height="11" rx="1.5" fill="#2F2936" stroke="#B589EC"/>
|
||||
<path d="M5.64488 11H4.54688V4.70001H5.97338L7.90388 9.24951L8.01188 9.60051L8.11538 9.24951L10.0054 4.70001H11.4499V11H10.3474V6.51801L10.3609 6.27951L8.40338 11H7.57988L5.63138 6.31101L5.64488 6.51801V11Z" fill="#B589EC"/>
|
||||
<rect x="1" y="1" width="14" height="14" rx="1.5" fill="#FAF5FF" stroke="#834DF0"/>
|
||||
<path d="M5.64488 11H4.54688V4.70001H5.97338L7.90388 9.24951L8.01188 9.60051L8.11538 9.24951L10.0054 4.70001H11.4499V11H10.3474V6.51801L10.3609 6.27951L8.40338 11H7.57988L5.63138 6.31101L5.64488 6.51801V11Z" fill="#834DF0"/>
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 543 B After Width: | Height: | Size: 539 B |
+10
@@ -0,0 +1,10 @@
|
||||
<!-- Copyright 2000-2021 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. -->
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16">
|
||||
<g fill="none" fill-rule="evenodd">
|
||||
<path fill="#6E6E6E" d="M8,15 C4.13400675,15 1,11.8659932 1,8 C1,4.13400675 4.13400675,1 8,1 C11.8659932,1 15,4.13400675 15,8 C15,11.8659932 11.8659932,15 8,15 Z M8,13.5 C11.0375661,13.5 13.5,11.0375661 13.5,8 C13.5,4.96243388 11.0375661,2.5 8,2.5 C4.96243388,2.5 2.5,4.96243388 2.5,8 C2.5,11.0375661 4.96243388,13.5 8,13.5 Z"/>
|
||||
<rect width="2" height="4" x="7" y="2" fill="#6E6E6E"/>
|
||||
<rect width="4" height="2" x="2" y="7" fill="#6E6E6E"/>
|
||||
<rect width="4" height="2" x="10" y="7" fill="#6E6E6E"/>
|
||||
<rect width="2" height="4" x="7" y="10" fill="#6E6E6E"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 876 B |
@@ -5,7 +5,7 @@ plugins {
|
||||
dependencies {
|
||||
compileOnly(project(":jadx-core"))
|
||||
|
||||
implementation("com.android.tools.build:aapt2-proto:8.10.0-12782657")
|
||||
implementation("com.android.tools.build:aapt2-proto:8.11.1-12782657")
|
||||
implementation("com.google.protobuf:protobuf-java") {
|
||||
version {
|
||||
require("3.25.3") // version 4 conflict with `aapt2-proto`
|
||||
|
||||
@@ -4,7 +4,10 @@ import jadx.api.plugins.JadxPlugin;
|
||||
import jadx.api.plugins.JadxPluginContext;
|
||||
import jadx.api.plugins.JadxPluginInfo;
|
||||
import jadx.api.plugins.resources.IResourcesLoader;
|
||||
import jadx.plugins.input.aab.factories.ProtoAppDependenciesResContainerFactory;
|
||||
import jadx.plugins.input.aab.factories.ProtoAssetsConfigResContainerFactory;
|
||||
import jadx.plugins.input.aab.factories.ProtoBundleConfigResContainerFactory;
|
||||
import jadx.plugins.input.aab.factories.ProtoNativeConfigResContainerFactory;
|
||||
import jadx.plugins.input.aab.factories.ProtoTableResContainerFactory;
|
||||
import jadx.plugins.input.aab.factories.ProtoXmlResContainerFactory;
|
||||
|
||||
@@ -28,5 +31,8 @@ public class AabInputPlugin implements JadxPlugin {
|
||||
resourcesLoader.addResContainerFactory(new ProtoTableResContainerFactory(tableParserProvider));
|
||||
resourcesLoader.addResContainerFactory(new ProtoXmlResContainerFactory());
|
||||
resourcesLoader.addResContainerFactory(new ProtoBundleConfigResContainerFactory());
|
||||
resourcesLoader.addResContainerFactory(new ProtoAssetsConfigResContainerFactory());
|
||||
resourcesLoader.addResContainerFactory(new ProtoNativeConfigResContainerFactory());
|
||||
resourcesLoader.addResContainerFactory(new ProtoAppDependenciesResContainerFactory());
|
||||
}
|
||||
}
|
||||
|
||||
+28
@@ -0,0 +1,28 @@
|
||||
package jadx.plugins.input.aab.factories;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import com.android.bundle.AppDependenciesOuterClass;
|
||||
|
||||
import jadx.api.ICodeInfo;
|
||||
import jadx.api.ResourceFile;
|
||||
import jadx.api.impl.SimpleCodeInfo;
|
||||
import jadx.api.plugins.resources.IResContainerFactory;
|
||||
import jadx.core.xmlgen.ResContainer;
|
||||
|
||||
public class ProtoAppDependenciesResContainerFactory implements IResContainerFactory {
|
||||
|
||||
@Override
|
||||
public @Nullable ResContainer create(ResourceFile resFile, InputStream inputStream) throws IOException {
|
||||
if (!resFile.getOriginalName().endsWith("BUNDLE-METADATA/com.android.tools.build.libraries/dependencies.pb")) {
|
||||
return null;
|
||||
}
|
||||
|
||||
AppDependenciesOuterClass.AppDependencies appDependencies = AppDependenciesOuterClass.AppDependencies.parseFrom(inputStream);
|
||||
ICodeInfo content = new SimpleCodeInfo(appDependencies.toString());
|
||||
return ResContainer.textResource(resFile.getDeobfName(), content);
|
||||
}
|
||||
}
|
||||
+28
@@ -0,0 +1,28 @@
|
||||
package jadx.plugins.input.aab.factories;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import com.android.bundle.Files;
|
||||
|
||||
import jadx.api.ICodeInfo;
|
||||
import jadx.api.ResourceFile;
|
||||
import jadx.api.impl.SimpleCodeInfo;
|
||||
import jadx.api.plugins.resources.IResContainerFactory;
|
||||
import jadx.core.xmlgen.ResContainer;
|
||||
|
||||
public class ProtoAssetsConfigResContainerFactory implements IResContainerFactory {
|
||||
|
||||
@Override
|
||||
public @Nullable ResContainer create(ResourceFile resFile, InputStream inputStream) throws IOException {
|
||||
if (!resFile.getOriginalName().endsWith("assets.pb")) {
|
||||
return null;
|
||||
}
|
||||
|
||||
Files.Assets assetsConfig = Files.Assets.parseFrom(inputStream);
|
||||
ICodeInfo content = new SimpleCodeInfo(assetsConfig.toString());
|
||||
return ResContainer.textResource(resFile.getDeobfName(), content);
|
||||
}
|
||||
}
|
||||
+28
@@ -0,0 +1,28 @@
|
||||
package jadx.plugins.input.aab.factories;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import com.android.bundle.Files;
|
||||
|
||||
import jadx.api.ICodeInfo;
|
||||
import jadx.api.ResourceFile;
|
||||
import jadx.api.impl.SimpleCodeInfo;
|
||||
import jadx.api.plugins.resources.IResContainerFactory;
|
||||
import jadx.core.xmlgen.ResContainer;
|
||||
|
||||
public class ProtoNativeConfigResContainerFactory implements IResContainerFactory {
|
||||
|
||||
@Override
|
||||
public @Nullable ResContainer create(ResourceFile resFile, InputStream inputStream) throws IOException {
|
||||
if (!resFile.getOriginalName().endsWith("native.pb")) {
|
||||
return null;
|
||||
}
|
||||
|
||||
Files.NativeLibraries nativeConfig = Files.NativeLibraries.parseFrom(inputStream);
|
||||
ICodeInfo content = new SimpleCodeInfo(nativeConfig.toString());
|
||||
return ResContainer.textResource(resFile.getDeobfName(), content);
|
||||
}
|
||||
}
|
||||
+1
@@ -28,6 +28,7 @@ public class ProtoTableResContainerFactory implements IResContainerFactory {
|
||||
if (parser == null) {
|
||||
return null;
|
||||
}
|
||||
parser.decode(inputStream);
|
||||
return parser.decodeFiles();
|
||||
}
|
||||
}
|
||||
|
||||
+4
-6
@@ -58,16 +58,14 @@ public class ResTableProtoParser extends CommonProtoParser implements IResTableP
|
||||
}
|
||||
|
||||
private void parse(Package p) {
|
||||
String name = p.getPackageName();
|
||||
resStorage.setAppPackage(name);
|
||||
parse(name, p.getTypeList());
|
||||
}
|
||||
String packageName = p.getPackageName();
|
||||
resStorage.setAppPackage(packageName);
|
||||
List<Type> types = p.getTypeList();
|
||||
|
||||
private void parse(String packageName, List<Type> types) {
|
||||
for (Type type : types) {
|
||||
String typeName = type.getName();
|
||||
for (Entry entry : type.getEntryList()) {
|
||||
int id = entry.getEntryId().getId();
|
||||
int id = p.getPackageId().getId() << 24 | type.getTypeId().getId() << 16 | entry.getEntryId().getId();
|
||||
String entryName = entry.getName();
|
||||
for (ConfigValue configValue : entry.getConfigValueList()) {
|
||||
String config = parse(configValue.getConfig());
|
||||
|
||||
@@ -9,7 +9,7 @@ dependencies {
|
||||
implementation("org.jetbrains.kotlinx:kotlinx-metadata-jvm:0.9.0")
|
||||
|
||||
testImplementation(project.project(":jadx-core").sourceSets.getByName("test").output)
|
||||
testImplementation("org.apache.commons:commons-lang3:3.17.0")
|
||||
testImplementation("org.apache.commons:commons-lang3:3.18.0")
|
||||
|
||||
testRuntimeOnly(project(":jadx-plugins:jadx-smali-input"))
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user