Compare commits

..

32 Commits

Author SHA1 Message Date
dependabot[bot] 2cd112cd3d build(deps): bump actions/github-script from 7 to 8 (#2628)
Bumps [actions/github-script](https://github.com/actions/github-script) from 7 to 8.
- [Release notes](https://github.com/actions/github-script/releases)
- [Commits](https://github.com/actions/github-script/compare/v7...v8)

---
updated-dependencies:
- dependency-name: actions/github-script
  dependency-version: '8'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-09-08 19:20:27 +01:00
dependabot[bot] 43358643be build(deps): bump actions/setup-java from 4 to 5 (PR #2614)
Bumps [actions/setup-java](https://github.com/actions/setup-java) from 4 to 5.
- [Release notes](https://github.com/actions/setup-java/releases)
- [Commits](https://github.com/actions/setup-java/compare/v4...v5)

---
updated-dependencies:
- dependency-name: actions/setup-java
  dependency-version: '5'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-08-29 20:41:47 +01:00
dependabot[bot] 1f0d3dac0f build(deps): bump actions/download-artifact from 4 to 5 (#2602)
Bumps [actions/download-artifact](https://github.com/actions/download-artifact) from 4 to 5.
- [Release notes](https://github.com/actions/download-artifact/releases)
- [Commits](https://github.com/actions/download-artifact/compare/v4...v5)

---
updated-dependencies:
- dependency-name: actions/download-artifact
  dependency-version: '5'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-08-12 09:52:54 +01:00
dependabot[bot] da95a8ae17 build(deps): bump actions/checkout from 4 to 5 (#2603)
Bumps [actions/checkout](https://github.com/actions/checkout) from 4 to 5.
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/checkout/compare/v4...v5)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-version: '5'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-08-12 09:51:04 +01:00
Andrei Kudryavtsev da3ac6bff0 fix(gui): various tabs related fixes (PR #2595)
* 1. Fix tab rendering when preview state changed

* 2. Fix selected tab after closing currently active tab

* 3. Fix tab selection when pinning currently active tab

* 4. Make current preview tab permanent on double click

* 5. Fix preview tab font not reloading on settings change
2025-08-04 18:09:22 +01:00
beaverxsheet bdbeaff8f0 fix: use proper newlines when generating CFG (PR #2592)
fixing new lines in the cfg
2025-08-04 18:03:11 +01:00
Jan S. b1f48f1db1 fix(gui): fix NullPointerException in copyAllSearchResults (#2580) (PR #2581) 2025-07-24 18:37:47 +01:00
Skylot 5b09378614 fix: use 'dev.dirs' JNI implementation only on x86-64 (#2578) 2025-07-23 19:26:14 +01:00
Skylot b64c93160b refactor: move system info class to common app lib 2025-07-23 19:24:26 +01:00
Skylot 58c4f56a71 feat(gui): allow view and edit input smali files 2025-07-19 22:16:19 +01:00
Skylot 3d11d1fa87 fix(gui): resolve NPE on code area close 2025-07-19 22:14:30 +01:00
Skylot 0d158592e4 chore: update gradle 2025-07-19 19:14:24 +01:00
Skylot 0c253f9a1f chore: update dependencies 2025-07-19 19:08:18 +01:00
Loyie King f565178c8c feat(aab): enhance AAB parsing (PR #2557)
* fix(aab): resources.pb always NPE

* fix(aab): resources.pb shows wrong resources id

* feat(aab): add parser for native.pb, assets.pb, dependencies.pb

* apply code formating

---------

Co-authored-by: Skylot <118523+skylot@users.noreply.github.com>
2025-07-07 21:08:12 +01:00
Skylot f6d13f1860 fix(cli): add missing '-XX:+IgnoreUnrecognizedVMOptions' JVM flag (#2554) 2025-07-06 17:30:50 +01:00
TinyServal d58c9ac926 fix(gui): minor UI fixes (PR #2549)
* fix(gui): use system menu bar on macOS

* fix(gui): font consistency in AboutDialog

* fix(gui): fix horizontal scrolling speed in CommonSearchDialog

* fix(gui): slightly increase debounce timer to reduce UI flickering

* fix(gui): disable tab scroll wrap-around behavior
2025-07-02 20:37:16 +01:00
Skylot 7f9d51b9b1 fix(zip): allow to load zip with duplicate names in entries 2025-06-29 20:44:17 +01:00
Skylot 432e49df03 fix(gui): disable debugger action if project not loaded (#2547) 2025-06-29 19:48:10 +01:00
Skylot b530c234f3 chore: update gradle 2025-06-29 19:48:10 +01:00
Skylot 181dcf7b4f chore: update dependencies 2025-06-29 19:48:10 +01:00
xiaojye dc4dcb2bd0 refactor(gui): improve menu icon, such as 'Select in Tree' and 'Go to AndroidManifest.xml' buttons (PR #2543) 2025-06-26 20:48:23 +01:00
Jan S. 74c396448e fix(gui): prevent IndexOutOfBoundsException in MethodSearchProvider (PR #2542) 2025-06-20 22:37:27 +01:00
Skylot cb9693a9d1 fix: correct hex format for negative numbers (#2531) 2025-06-19 22:49:59 +01:00
Mart Lintz 5d13acc6f3 chore(export): add missing ExpiringTargetSdkVersion in #2533 (PR #2538) 2025-06-17 22:38:02 +01:00
Skylot c04dddfa81 fix(cli): improve memory usage in jadx-cli (#2524) 2025-06-16 21:00:01 +01:00
Jan S. 1bb645d676 fix: correct loading for plain text XML files (PR #2537) 2025-06-16 19:26:35 +01:00
Jan S. ecb597a461 chore(export): disable targetSDKVersion checks in AndroidStudio (PR #2533)
chore: disable targetSDKVersion warnings when opening exported project in AndroidStudio
2025-06-11 23:13:02 +03:00
Akexorcist 46cd3b5597 feat(export): use compileSdkVersion from manifest, update AGP to 8.10.1, other improvements (PR #2528)
* chore(core): export to android project with AGP 8.10.1

* fix code format

* fix export template test

---------

Co-authored-by: Skylot <118523+skylot@users.noreply.github.com>
2025-06-07 20:09:29 +01:00
Jeroen Beckers d523f1b15e fix(gui): use var instead let for class variable in Frida script (PR #2527)
Co-authored-by: Jeroen Beckers <dauntless@dauntless.be>
2025-06-07 19:53:19 +01:00
skylot 8030c2f84e feat(gui): new search options to search in text or binary resources (PR #2526)
* search in text or binary resources

* load matching tab for scroll to pos in binary panel, treat unknown files as binary in search
2025-06-07 19:23:08 +01:00
Ruffalo Lavoisier 47224dc599 fix(gui): remove duplicate class of Frida snippet (PR #2525)
fix duplicate class of frida snippet
2025-06-03 17:52:06 +01:00
Yaroslav d492628bfe fix(gui): workaround for wrap layout repaint issue in search dialog (PR #2521)
* fix(gui): fixed incorrectly handles full window repaint in search dialog

* fix: fix spotless check
2025-06-02 21:45:46 +01:00
101 changed files with 1118 additions and 453 deletions
+3 -3
View File
@@ -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
+2 -2
View File
@@ -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
+1 -1
View File
@@ -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
+6 -6
View File
@@ -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) }}
+1 -1
View File
@@ -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 {
Binary file not shown.
+2 -2
View File
@@ -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
Vendored
+2 -2
View File
@@ -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
View File
@@ -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
+2 -1
View File
@@ -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() {
+1 -1
View File
@@ -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
View File
@@ -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
+6 -5
View File
@@ -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=複製全部
+2 -2
View File
@@ -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
View File
@@ -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

+1 -1
View File
@@ -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());
}
}
@@ -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);
}
}
@@ -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);
}
}
@@ -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);
}
}
@@ -28,6 +28,7 @@ public class ProtoTableResContainerFactory implements IResContainerFactory {
if (parser == null) {
return null;
}
parser.decode(inputStream);
return parser.decodeFiles();
}
}
@@ -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