feat(cli): implement config file load and save (#1731)

This commit is contained in:
Skylot
2025-12-15 19:48:37 +00:00
parent 1aa16c4664
commit 3e709d6693
38 changed files with 1781 additions and 920 deletions
+11 -2
View File
@@ -172,7 +172,16 @@ options:
--log-level - set log level, values: quiet, progress, error, warn, info, debug, default: progress
-v, --verbose - verbose output (set --log-level to DEBUG)
-q, --quiet - turn off output (set --log-level to QUIET)
--disable-plugins - comma separated list of plugin ids to disable, default:
--disable-plugins - comma separated list of plugin ids to disable
--config <config-ref> - load configuration from file, <config-ref> can be:
path to '.json' file
short name - uses file with this name from config directory
'none' - to disable config loading
--save-config <config-ref> - save current options into configuration file and exit, <config-ref> can be:
empty - for default config
path to '.json' file
short name - file will be saved in config directory
--print-files - print files and directories used by jadx (config, cache, temp)
--version - print jadx version
-h, --help - print this help
@@ -193,7 +202,7 @@ Plugin options (-P<name>=<value>):
kotlin-smap: Use kotlin.SourceDebugExtension annotation for rename class alias
- kotlin-smap.class-alias-source-dbg - rename class alias from SourceDebugExtension, values: [yes, no], default: no
rename-mappings: various mappings support
- rename-mappings.format - mapping format, values: [AUTO, TINY_FILE, TINY_2_FILE, ENIGMA_FILE, ENIGMA_DIR, SRG_FILE, XSRG_FILE, JAM_FILE, CSRG_FILE, TSRG_FILE, TSRG_2_FILE, PROGUARD_FILE, INTELLIJ_MIGRATION_MAP_FILE, RECAF_SIMPLE_FILE, JOBF_FILE], default: AUTO
- rename-mappings.format - mapping format, values: [AUTO, TINY_FILE, TINY_2_FILE, ENIGMA_FILE, ENIGMA_DIR, PROGUARD_FILE, SRG_FILE, XSRG_FILE, JAM_FILE, CSRG_FILE, TSRG_FILE, TSRG_2_FILE, INTELLIJ_MIGRATION_MAP_FILE, RECAF_SIMPLE_FILE, JOBF_FILE], default: AUTO
- rename-mappings.invert - invert mapping on load, values: [yes, no], default: no
smali-input: Load .smali files
- smali-input.api-level - Android API level, default: 27
+1
View File
@@ -27,6 +27,7 @@ dependencies {
implementation("org.jcommander:jcommander:2.0")
implementation("ch.qos.logback:logback-classic:1.5.21")
implementation("com.google.code.gson:gson:2.13.2")
}
application {
@@ -41,12 +41,12 @@ public class JCommanderWrapper {
public boolean parse(String[] args) {
try {
jc.parse(args);
String[] fixedArgs = fixArgsForEmptySaveConfig(args);
jc.parse(fixedArgs);
applyFiles(argsObj);
return true;
} catch (ParameterException e) {
System.err.println("Arguments parse error: " + e.getMessage());
printUsage();
return false;
}
}
@@ -96,6 +96,41 @@ public class JCommanderWrapper {
return value;
}
/**
* Workaround to allow empty value (i.e. zero arity) for '--save-config' option
* Insert empty string arg if another option start right after this one, or it is a last one.
*/
private String[] fixArgsForEmptySaveConfig(String[] args) {
int len = args.length;
for (int i = 0; i < len; i++) {
String arg = args[i];
if (arg.equals("--save-config")) {
int next = i + 1;
if (next == len) {
return insertEmptyArg(args, next, true);
}
if (next < len) {
String nextArg = args[next];
if (nextArg.startsWith("-")) {
return insertEmptyArg(args, next, false);
}
}
break;
}
}
return args;
}
private static String[] insertEmptyArg(String[] args, int i, boolean add) {
List<String> strings = new ArrayList<>(Arrays.asList(args));
if (add) {
strings.add("");
} else {
strings.add(i, "");
}
return strings.toArray(new String[0]);
}
public void printUsage() {
LogHelper.setLogLevel(LogHelper.LogLevelEnum.ERROR); // mute logger while printing help
@@ -182,7 +217,9 @@ public class JCommanderWrapper {
}
if (addDefaults) {
String defaultValue = getDefaultValue(args, f);
if (defaultValue != null && !description.contains("(default)")) {
if (defaultValue != null
&& !defaultValue.isEmpty()
&& !description.contains("(default)")) {
opt.append(", default: ").append(defaultValue);
}
}
+11 -10
View File
@@ -13,6 +13,7 @@ import jadx.api.impl.NoOpCodeCache;
import jadx.api.impl.SimpleCodeWriter;
import jadx.api.usage.impl.EmptyUsageInfoCache;
import jadx.cli.LogHelper.LogLevelEnum;
import jadx.cli.config.JadxConfigAdapter;
import jadx.cli.plugins.JadxFilesGetter;
import jadx.core.utils.exceptions.JadxArgsValidateException;
import jadx.plugins.tools.JadxExternalPluginsLoader;
@@ -35,15 +36,17 @@ public class JadxCLI {
public static int execute(String[] args, @Nullable Consumer<JadxArgs> argsMod) {
try {
JadxCLIArgs cliArgs = new JadxCLIArgs();
if (cliArgs.processArgs(args)) {
JadxArgs jadxArgs = buildArgs(cliArgs);
if (argsMod != null) {
argsMod.accept(jadxArgs);
}
return runSave(jadxArgs, cliArgs);
JadxCLIArgs cliArgs = JadxCLIArgs.processArgs(args,
new JadxCLIArgs(),
new JadxConfigAdapter<>(JadxCLIArgs.class, "cli"));
if (cliArgs == null) {
return 0;
}
return 0;
JadxArgs jadxArgs = buildArgs(cliArgs);
if (argsMod != null) {
argsMod.accept(jadxArgs);
}
return runSave(jadxArgs, cliArgs);
} catch (JadxArgsValidateException e) {
LOG.error("Incorrect arguments: {}", e.getMessage());
return 1;
@@ -54,8 +57,6 @@ public class JadxCLI {
}
private static JadxArgs buildArgs(JadxCLIArgs cliArgs) {
LogHelper.initLogLevel(cliArgs);
LogHelper.applyLogLevels();
JadxArgs jadxArgs = cliArgs.toJadxArgs();
jadxArgs.setCodeCache(new NoOpCodeCache());
jadxArgs.setUsageInfoCache(new EmptyUsageInfoCache());
+321 -10
View File
@@ -31,22 +31,32 @@ import jadx.api.args.IntegerFormat;
import jadx.api.args.ResourceNameSource;
import jadx.api.args.UseSourceNameAsClassNameAlias;
import jadx.api.args.UserRenamesMappingsMode;
import jadx.cli.config.IJadxConfig;
import jadx.cli.config.JadxConfigAdapter;
import jadx.cli.config.JadxConfigExclude;
import jadx.commons.app.JadxCommonFiles;
import jadx.commons.app.JadxTempFiles;
import jadx.core.deobf.conditions.DeobfWhitelist;
import jadx.core.export.ExportGradleType;
import jadx.core.utils.exceptions.JadxArgsValidateException;
import jadx.core.utils.exceptions.JadxRuntimeException;
import jadx.core.utils.files.FileUtils;
public class JadxCLIArgs {
public class JadxCLIArgs implements IJadxConfig {
@JadxConfigExclude
@Parameter(description = "<input files> (.apk, .dex, .jar, .class, .smali, .zip, .aar, .arsc, .aab, .xapk, .apkm, .jadx.kts)")
protected List<String> files = Collections.emptyList();
@JadxConfigExclude
@Parameter(names = { "-d", "--output-dir" }, description = "output directory")
protected String outDir;
@JadxConfigExclude
@Parameter(names = { "-ds", "--output-dir-src" }, description = "output directory for sources")
protected String outDirSrc;
@JadxConfigExclude
@Parameter(names = { "-dr", "--output-dir-res" }, description = "output directory for resources")
protected String outDirRes;
@@ -59,9 +69,11 @@ public class JadxCLIArgs {
@Parameter(names = { "-j", "--threads-count" }, description = "processing threads count")
protected int threadsCount = JadxArgs.DEFAULT_THREADS_COUNT;
@JadxConfigExclude
@Parameter(names = { "--single-class" }, description = "decompile a single class, full name, raw or alias")
protected String singleClass = null;
@JadxConfigExclude
@Parameter(names = { "--single-class-output" }, description = "file or dir for write if decompile a single class")
protected String singleClassOutput = null;
@@ -166,6 +178,7 @@ public class JadxCLIArgs {
)
protected String deobfuscationWhitelistStr = DeobfWhitelist.DEFAULT_STR;
@JadxConfigExclude
@Parameter(
names = { "--deobf-cfg-file" },
description = "deobfuscation mappings file used for JADX auto-generated names (in the JOBF file format),"
@@ -241,7 +254,7 @@ public class JadxCLIArgs {
+ "\n 'printable' - remove non-printable chars from identifiers,"
+ "\nor single 'none' - to disable all renames"
+ "\nor single 'all' - to enable all (default)",
converter = RenameConverter.class
listConverter = RenameConverter.class
)
protected Set<RenameEnum> renameFlags = EnumSet.allOf(RenameEnum.class);
@@ -287,27 +300,106 @@ public class JadxCLIArgs {
)
protected LogHelper.LogLevelEnum logLevel = LogHelper.LogLevelEnum.PROGRESS;
@JadxConfigExclude
@Parameter(names = { "-v", "--verbose" }, description = "verbose output (set --log-level to DEBUG)")
protected boolean verbose = false;
@JadxConfigExclude
@Parameter(names = { "-q", "--quiet" }, description = "turn off output (set --log-level to QUIET)")
protected boolean quiet = false;
@JadxConfigExclude
@Parameter(names = { "--disable-plugins" }, description = "comma separated list of plugin ids to disable")
protected String disablePlugins = "";
@JadxConfigExclude
@Parameter(
names = { "--config" },
defaultValueDescription = "<config-ref>",
description = "load configuration from file, <config-ref> can be:"
+ "\n path to '.json' file"
+ "\n short name - uses file with this name from config directory"
+ "\n 'none' - to disable config loading"
)
protected String config = "";
@JadxConfigExclude
@Parameter(
names = { "--save-config" },
defaultValueDescription = "<config-ref>",
description = "save current options into configuration file and exit, <config-ref> can be:"
+ "\n empty - for default config"
+ "\n path to '.json' file"
+ "\n short name - file will be saved in config directory"
)
protected String saveConfig = null;
@JadxConfigExclude
@Parameter(names = { "--print-files" }, description = "print files and directories used by jadx (config, cache, temp)")
protected boolean printFiles = false;
@JadxConfigExclude
@Parameter(names = { "--version" }, description = "print jadx version")
protected boolean printVersion = false;
@JadxConfigExclude
@Parameter(names = { "-h", "--help" }, description = "print this help", help = true)
protected boolean printHelp = false;
@DynamicParameter(names = "-P", description = "Plugin options", hidden = true)
protected Map<String, String> pluginOptions = new HashMap<>();
/**
* Obsolete method without config support,
* prefer {@link #processArgs(String[], JadxCLIArgs, JadxConfigAdapter)}
*/
public boolean processArgs(String[] args) {
JCommanderWrapper jcw = new JCommanderWrapper(this);
return jcw.parse(args) && process(jcw);
return processArgs(args, this, null) != null;
}
public static <T extends JadxCLIArgs> @Nullable T processArgs(
String[] args, T argsObj, @Nullable JadxConfigAdapter<T> configAdapter) {
JCommanderWrapper jcw = new JCommanderWrapper(argsObj);
if (!jcw.parse(args)) {
return null;
}
applyArgs(argsObj);
// process commands and early exit flags
if (!argsObj.process(jcw)) {
return null;
}
if (configAdapter != null) {
if (argsObj.printFiles) {
printFilesAndDirs(configAdapter.getDefaultConfigFileName());
return null;
}
if (!argsObj.config.equalsIgnoreCase("none")) {
// load config file and merge with command line args
configAdapter.useConfigRef(argsObj.config);
T configObj = configAdapter.load();
if (configObj != null) {
jcw.overrideProvided(configObj);
argsObj = configObj;
}
}
}
// verify result object
argsObj.verify();
applyArgs(argsObj);
// save config if requested
if (argsObj.saveConfig != null) {
saveConfig(argsObj, configAdapter);
return null;
}
return argsObj;
}
private static <T extends JadxCLIArgs> void applyArgs(T argsObj) {
// apply log levels
LogHelper.initLogLevel(argsObj);
LogHelper.applyLogLevels();
}
public boolean process(JCommanderWrapper jcw) {
@@ -322,9 +414,7 @@ public class JadxCLIArgs {
System.out.println(JadxDecompiler.getVersion());
return false;
}
if (threadsCount <= 0) {
throw new JadxArgsValidateException("Threads count must be positive, got: " + threadsCount);
}
// unknown options added to 'files', run checks
for (String fileName : files) {
if (fileName.startsWith("-")) {
throw new JadxArgsValidateException("Unknown option: " + fileName);
@@ -333,6 +423,29 @@ public class JadxCLIArgs {
return true;
}
private static void printFilesAndDirs(String defaultConfigFileName) {
System.out.println("Files and directories used by jadx:");
System.out.println(" - default config file: " + JadxCommonFiles.getConfigDir().resolve(defaultConfigFileName).toAbsolutePath());
System.out.println(" - config directory: " + JadxCommonFiles.getConfigDir().toAbsolutePath());
System.out.println(" - cache directory: " + JadxCommonFiles.getCacheDir().toAbsolutePath());
System.out.println(" - temp directory: " + JadxTempFiles.getTempRootDir().getParent().toAbsolutePath());
}
public void verify() {
if (threadsCount <= 0) {
throw new JadxArgsValidateException("Threads count must be positive, got: " + threadsCount);
}
}
private static <T extends JadxCLIArgs> void saveConfig(T argsObj, @Nullable JadxConfigAdapter<T> configAdapter) {
if (configAdapter == null) {
throw new JadxRuntimeException("Config adapter set to null, can't save config");
}
configAdapter.useConfigRef(argsObj.saveConfig);
configAdapter.save(argsObj);
System.out.println("Config saved to " + configAdapter.getConfigPath().toAbsolutePath());
}
public JadxArgs toJadxArgs() {
JadxArgs args = new JadxArgs();
args.setInputFiles(files.stream().map(FileUtils::toFile).collect(Collectors.toList()));
@@ -383,7 +496,7 @@ public class JadxCLIArgs {
args.setAllowInlineKotlinLambda(allowInlineKotlinLambda);
args.setExtractFinally(extractFinally);
args.setRestoreSwitchOverString(restoreSwitchOverString);
args.setRenameFlags(renameFlags);
args.setRenameFlags(buildEnumSetForRenameFlags());
args.setFsCaseSensitive(fsCaseSensitive);
args.setCommentsLevel(commentsLevel);
args.setIntegerFormat(integerFormat);
@@ -394,6 +507,12 @@ public class JadxCLIArgs {
return args;
}
private EnumSet<RenameEnum> buildEnumSetForRenameFlags() {
EnumSet<RenameEnum> set = EnumSet.noneOf(RenameEnum.class);
set.addAll(renameFlags);
return set;
}
public List<String> getFiles() {
return files;
}
@@ -426,14 +545,26 @@ public class JadxCLIArgs {
return skipResources;
}
public void setSkipResources(boolean skipResources) {
this.skipResources = skipResources;
}
public boolean isSkipSources() {
return skipSources;
}
public void setSkipSources(boolean skipSources) {
this.skipSources = skipSources;
}
public int getThreadsCount() {
return threadsCount;
}
public void setThreadsCount(int threadsCount) {
this.threadsCount = threadsCount;
}
public boolean isFallbackMode() {
return fallbackMode;
}
@@ -442,82 +573,170 @@ public class JadxCLIArgs {
return useDx;
}
public void setUseDx(boolean useDx) {
this.useDx = useDx;
}
public DecompilationMode getDecompilationMode() {
return decompilationMode;
}
public void setDecompilationMode(DecompilationMode decompilationMode) {
this.decompilationMode = decompilationMode;
}
public boolean isShowInconsistentCode() {
return showInconsistentCode;
}
public void setShowInconsistentCode(boolean showInconsistentCode) {
this.showInconsistentCode = showInconsistentCode;
}
public boolean isUseImports() {
return useImports;
}
public void setUseImports(boolean useImports) {
this.useImports = useImports;
}
public boolean isDebugInfo() {
return debugInfo;
}
public void setDebugInfo(boolean debugInfo) {
this.debugInfo = debugInfo;
}
public boolean isAddDebugLines() {
return addDebugLines;
}
public void setAddDebugLines(boolean addDebugLines) {
this.addDebugLines = addDebugLines;
}
public boolean isInlineAnonymousClasses() {
return inlineAnonymousClasses;
}
public void setInlineAnonymousClasses(boolean inlineAnonymousClasses) {
this.inlineAnonymousClasses = inlineAnonymousClasses;
}
public boolean isInlineMethods() {
return inlineMethods;
}
public void setInlineMethods(boolean inlineMethods) {
this.inlineMethods = inlineMethods;
}
public boolean isMoveInnerClasses() {
return moveInnerClasses;
}
public void setMoveInnerClasses(boolean moveInnerClasses) {
this.moveInnerClasses = moveInnerClasses;
}
public boolean isAllowInlineKotlinLambda() {
return allowInlineKotlinLambda;
}
public void setAllowInlineKotlinLambda(boolean allowInlineKotlinLambda) {
this.allowInlineKotlinLambda = allowInlineKotlinLambda;
}
public boolean isExtractFinally() {
return extractFinally;
}
public void setExtractFinally(boolean extractFinally) {
this.extractFinally = extractFinally;
}
public boolean isRestoreSwitchOverString() {
return restoreSwitchOverString;
}
public void setRestoreSwitchOverString(boolean restoreSwitchOverString) {
this.restoreSwitchOverString = restoreSwitchOverString;
}
public Path getUserRenamesMappingsPath() {
return userRenamesMappingsPath;
}
public void setUserRenamesMappingsPath(Path userRenamesMappingsPath) {
this.userRenamesMappingsPath = userRenamesMappingsPath;
}
public UserRenamesMappingsMode getUserRenamesMappingsMode() {
return userRenamesMappingsMode;
}
public void setUserRenamesMappingsMode(UserRenamesMappingsMode userRenamesMappingsMode) {
this.userRenamesMappingsMode = userRenamesMappingsMode;
}
public boolean isDeobfuscationOn() {
return deobfuscationOn;
}
public void setDeobfuscationOn(boolean deobfuscationOn) {
this.deobfuscationOn = deobfuscationOn;
}
public int getDeobfuscationMinLength() {
return deobfuscationMinLength;
}
public void setDeobfuscationMinLength(int deobfuscationMinLength) {
this.deobfuscationMinLength = deobfuscationMinLength;
}
public int getDeobfuscationMaxLength() {
return deobfuscationMaxLength;
}
public void setDeobfuscationMaxLength(int deobfuscationMaxLength) {
this.deobfuscationMaxLength = deobfuscationMaxLength;
}
public String getDeobfuscationWhitelistStr() {
return deobfuscationWhitelistStr;
}
public void setDeobfuscationWhitelistStr(String deobfuscationWhitelistStr) {
this.deobfuscationWhitelistStr = deobfuscationWhitelistStr;
}
public String getGeneratedRenamesMappingFile() {
return generatedRenamesMappingFile;
}
public void setGeneratedRenamesMappingFile(String generatedRenamesMappingFile) {
this.generatedRenamesMappingFile = generatedRenamesMappingFile;
}
public GeneratedRenamesMappingFileMode getGeneratedRenamesMappingFileMode() {
return generatedRenamesMappingFileMode;
}
public void setGeneratedRenamesMappingFileMode(GeneratedRenamesMappingFileMode generatedRenamesMappingFileMode) {
this.generatedRenamesMappingFileMode = generatedRenamesMappingFileMode;
}
public int getSourceNameRepeatLimit() {
return sourceNameRepeatLimit;
}
public void setSourceNameRepeatLimit(int sourceNameRepeatLimit) {
this.sourceNameRepeatLimit = sourceNameRepeatLimit;
}
public UseSourceNameAsClassNameAlias getUseSourceNameAsClassNameAlias() {
if (useSourceNameAsClassNameAlias != null) {
return useSourceNameAsClassNameAlias;
@@ -529,8 +748,8 @@ public class JadxCLIArgs {
}
}
public int getSourceNameRepeatLimit() {
return sourceNameRepeatLimit;
public void setUseSourceNameAsClassNameAlias(UseSourceNameAsClassNameAlias useSourceNameAsClassNameAlias) {
this.useSourceNameAsClassNameAlias = useSourceNameAsClassNameAlias;
}
/**
@@ -541,50 +760,98 @@ public class JadxCLIArgs {
return getUseSourceNameAsClassNameAlias().toBoolean();
}
public void setDeobfuscationUseSourceNameAsAlias(Boolean deobfuscationUseSourceNameAsAlias) {
this.deobfuscationUseSourceNameAsAlias = deobfuscationUseSourceNameAsAlias;
}
public ResourceNameSource getResourceNameSource() {
return resourceNameSource;
}
public void setResourceNameSource(ResourceNameSource resourceNameSource) {
this.resourceNameSource = resourceNameSource;
}
public UseKotlinMethodsForVarNames getUseKotlinMethodsForVarNames() {
return useKotlinMethodsForVarNames;
}
public void setUseKotlinMethodsForVarNames(UseKotlinMethodsForVarNames useKotlinMethodsForVarNames) {
this.useKotlinMethodsForVarNames = useKotlinMethodsForVarNames;
}
public IntegerFormat getIntegerFormat() {
return integerFormat;
}
public void setIntegerFormat(IntegerFormat integerFormat) {
this.integerFormat = integerFormat;
}
public int getTypeUpdatesLimitCount() {
return typeUpdatesLimitCount;
}
public void setTypeUpdatesLimitCount(int typeUpdatesLimitCount) {
this.typeUpdatesLimitCount = typeUpdatesLimitCount;
}
public boolean isEscapeUnicode() {
return escapeUnicode;
}
public void setEscapeUnicode(boolean escapeUnicode) {
this.escapeUnicode = escapeUnicode;
}
public boolean isCfgOutput() {
return cfgOutput;
}
public void setCfgOutput(boolean cfgOutput) {
this.cfgOutput = cfgOutput;
}
public boolean isRawCfgOutput() {
return rawCfgOutput;
}
public void setRawCfgOutput(boolean rawCfgOutput) {
this.rawCfgOutput = rawCfgOutput;
}
public boolean isReplaceConsts() {
return replaceConsts;
}
public void setReplaceConsts(boolean replaceConsts) {
this.replaceConsts = replaceConsts;
}
public boolean isRespectBytecodeAccessModifiers() {
return respectBytecodeAccessModifiers;
}
public void setRespectBytecodeAccessModifiers(boolean respectBytecodeAccessModifiers) {
this.respectBytecodeAccessModifiers = respectBytecodeAccessModifiers;
}
public boolean isExportAsGradleProject() {
return exportAsGradleProject;
}
public void setExportAsGradleProject(boolean exportAsGradleProject) {
this.exportAsGradleProject = exportAsGradleProject;
}
public boolean isSkipXmlPrettyPrint() {
return skipXmlPrettyPrint;
}
public void setSkipXmlPrettyPrint(boolean skipXmlPrettyPrint) {
this.skipXmlPrettyPrint = skipXmlPrettyPrint;
}
public boolean isRenameCaseSensitive() {
return renameFlags.contains(RenameEnum.CASE);
}
@@ -601,26 +868,70 @@ public class JadxCLIArgs {
return fsCaseSensitive;
}
public void setFsCaseSensitive(boolean fsCaseSensitive) {
this.fsCaseSensitive = fsCaseSensitive;
}
public boolean isUseHeadersForDetectResourceExtensions() {
return useHeadersForDetectResourceExtensions;
}
public void setUseHeadersForDetectResourceExtensions(boolean useHeadersForDetectResourceExtensions) {
this.useHeadersForDetectResourceExtensions = useHeadersForDetectResourceExtensions;
}
public CommentsLevel getCommentsLevel() {
return commentsLevel;
}
public void setCommentsLevel(CommentsLevel commentsLevel) {
this.commentsLevel = commentsLevel;
}
public LogHelper.LogLevelEnum getLogLevel() {
return logLevel;
}
public void setLogLevel(LogHelper.LogLevelEnum logLevel) {
this.logLevel = logLevel;
}
public Map<String, String> getPluginOptions() {
return pluginOptions;
}
public void setPluginOptions(Map<String, String> pluginOptions) {
this.pluginOptions = pluginOptions;
}
public String getDisablePlugins() {
return disablePlugins;
}
public void setDisablePlugins(String disablePlugins) {
this.disablePlugins = disablePlugins;
}
public void setExportGradleType(@Nullable ExportGradleType exportGradleType) {
this.exportGradleType = exportGradleType;
}
public void setOutputFormat(String outputFormat) {
this.outputFormat = outputFormat;
}
public Set<RenameEnum> getRenameFlags() {
return renameFlags;
}
public void setRenameFlags(Set<RenameEnum> renameFlags) {
this.renameFlags = renameFlags;
}
public String getConfig() {
return config;
}
static class RenameConverter implements IStringConverter<Set<RenameEnum>> {
private final String paramName;
@@ -43,10 +43,9 @@ public class LogHelper {
return null;
}
if (args.quiet) {
return LogLevelEnum.QUIET;
}
if (args.verbose) {
return LogLevelEnum.DEBUG;
args.logLevel = LogLevelEnum.QUIET;
} else if (args.verbose) {
args.logLevel = LogLevelEnum.DEBUG;
}
return args.logLevel;
}
@@ -0,0 +1,7 @@
package jadx.cli.config;
/**
* Marker interface for jadx config objects
*/
public interface IJadxConfig {
}
@@ -0,0 +1,114 @@
package jadx.cli.config;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.function.Consumer;
import org.jetbrains.annotations.Nullable;
import com.google.gson.ExclusionStrategy;
import com.google.gson.FieldAttributes;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.stream.JsonReader;
import com.google.gson.stream.JsonWriter;
import jadx.commons.app.JadxCommonFiles;
import jadx.core.utils.GsonUtils;
import jadx.core.utils.exceptions.JadxArgsValidateException;
import jadx.core.utils.exceptions.JadxRuntimeException;
import static java.nio.file.StandardOpenOption.CREATE;
import static java.nio.file.StandardOpenOption.TRUNCATE_EXISTING;
import static java.nio.file.StandardOpenOption.WRITE;
public class JadxConfigAdapter<T extends IJadxConfig> {
private static final ExclusionStrategy GSON_EXCLUSION_STRATEGY = new ExclusionStrategy() {
@Override
public boolean shouldSkipField(FieldAttributes f) {
return f.getAnnotation(JadxConfigExclude.class) != null;
}
@Override
public boolean shouldSkipClass(Class<?> clazz) {
return false;
}
};
private final Class<T> configCls;
private final String defaultConfigFileName;
private final Gson gson;
private Path configPath;
public JadxConfigAdapter(Class<T> configCls, String defaultConfigName) {
this(configCls, defaultConfigName, gsonBuilder -> {
});
}
public JadxConfigAdapter(Class<T> configCls, String defaultConfigName, Consumer<GsonBuilder> applyGsonOptions) {
this.configCls = configCls;
this.defaultConfigFileName = defaultConfigName + ".json";
GsonBuilder gsonBuilder = GsonUtils.defaultGsonBuilder();
gsonBuilder.setExclusionStrategies(GSON_EXCLUSION_STRATEGY);
applyGsonOptions.accept(gsonBuilder);
this.gson = gsonBuilder.create();
}
public void useConfigRef(String configRef) {
this.configPath = resolveConfigRef(configRef);
}
public Path getConfigPath() {
return configPath;
}
public String getDefaultConfigFileName() {
return defaultConfigFileName;
}
public @Nullable T load() {
if (!Files.isRegularFile(configPath)) {
// file not found
return null;
}
try (JsonReader reader = gson.newJsonReader(Files.newBufferedReader(configPath))) {
return gson.fromJson(reader, configCls);
} catch (Exception e) {
throw new JadxRuntimeException("Failed to load config file: " + configPath, e);
}
}
public void save(T configObject) {
try (JsonWriter writer = gson.newJsonWriter(
Files.newBufferedWriter(configPath, StandardCharsets.UTF_8, WRITE, CREATE, TRUNCATE_EXISTING))) {
gson.toJson(configObject, configCls, writer);
} catch (Exception e) {
throw new JadxRuntimeException("Failed to save config file: " + configPath, e);
}
}
public String objectToJsonString(T configObject) {
return gson.toJson(configObject, configCls);
}
public T jsonStringToObject(String jsonStr) {
return gson.fromJson(jsonStr, configCls);
}
private Path resolveConfigRef(String configRef) {
if (configRef == null || configRef.isEmpty()) {
// use default config file
return JadxCommonFiles.getConfigDir().resolve(defaultConfigFileName);
}
if (configRef.contains("/") || configRef.contains("\\")) {
if (!configRef.toLowerCase().endsWith(".json")) {
throw new JadxArgsValidateException("Config file extension should be '.json'");
}
return Path.of(configRef);
}
// treat as a short name
return JadxCommonFiles.getConfigDir().resolve(configRef + ".json");
}
}
@@ -0,0 +1,11 @@
package jadx.cli.config;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface JadxConfigExclude {
}
@@ -18,7 +18,9 @@ public class JadxTempFiles {
String jadxTmpDir = System.getenv("JADX_TMP_DIR");
Path dir;
if (jadxTmpDir != null) {
dir = Files.createTempDirectory(Paths.get(jadxTmpDir), JADX_TMP_INSTANCE_PREFIX);
Path customTmpRootDir = Paths.get(jadxTmpDir);
Files.createDirectories(customTmpRootDir);
dir = Files.createTempDirectory(customTmpRootDir, JADX_TMP_INSTANCE_PREFIX);
} else {
dir = Files.createTempDirectory(JADX_TMP_INSTANCE_PREFIX);
}
@@ -4,13 +4,13 @@ import java.lang.reflect.Type;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.InstanceCreator;
import com.google.gson.JsonDeserializationContext;
import com.google.gson.JsonDeserializer;
import com.google.gson.JsonElement;
import com.google.gson.JsonParseException;
import com.google.gson.JsonSerializationContext;
import com.google.gson.JsonSerializer;
import com.google.gson.Strictness;
public class GsonUtils {
@@ -21,15 +21,11 @@ public class GsonUtils {
public static GsonBuilder defaultGsonBuilder() {
return new GsonBuilder()
.disableJdkUnsafe()
.disableInnerClassSerialization()
.setStrictness(Strictness.STRICT)
.setPrettyPrinting();
}
public static void fillObjectFromJsonString(GsonBuilder builder, Object obj, String jsonStr) {
Class<?> type = obj.getClass();
Gson gson = builder.registerTypeAdapter(type, (InstanceCreator<?>) t -> obj).create();
gson.fromJson(jsonStr, type);
}
public static <T> InterfaceReplace<T> interfaceReplace(Class<T> replaceCls) {
return new InterfaceReplace<>(replaceCls);
}
@@ -45,7 +45,7 @@ public class AndroidResourcesUtils {
addResourceFields(resCls, resStorage, true);
return resCls;
}
LOG.info("Can't find 'R' class in app package: {}", appPackage);
LOG.debug("Can't find 'R' class in app package: {}", appPackage);
List<ClassNode> candidates = root.searchClassByShortName("R");
if (candidates.size() == 1) {
ClassNode resClsCandidate = candidates.get(0);
+9 -13
View File
@@ -7,14 +7,14 @@ import javax.swing.SwingUtilities;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import jadx.cli.JCommanderWrapper;
import jadx.cli.LogHelper;
import jadx.cli.JadxCLIArgs;
import jadx.cli.config.JadxConfigAdapter;
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;
import jadx.gui.settings.JadxSettingsAdapter;
import jadx.gui.settings.JadxSettingsData;
import jadx.gui.ui.MainWindow;
import jadx.gui.ui.dialog.ExceptionDialog;
import jadx.gui.utils.LafManager;
@@ -25,20 +25,16 @@ public class JadxGUI {
public static void main(String[] args) {
try {
JadxSettings cliArgs = new JadxSettings();
JCommanderWrapper jcw = new JCommanderWrapper(cliArgs);
if (!jcw.parse(args) || !cliArgs.process(jcw)) {
JadxConfigAdapter<JadxSettingsData> configAdapter = JadxSettings.buildConfigAdapter();
JadxSettingsData settingsData = JadxCLIArgs.processArgs(args, new JadxSettingsData(), configAdapter);
if (settingsData == null) {
return;
}
LogHelper.initLogLevel(cliArgs);
LogHelper.applyLogLevels();
JadxSettings settings = new JadxSettings(configAdapter);
settings.loadSettingsData(settingsData);
LogCollector.register();
printSystemInfo();
JadxSettings settings = JadxSettingsAdapter.load();
// overwrite loaded settings by command line arguments
jcw.overrideProvided(settings);
LafManager.init(settings);
NLS.setLocale(settings.getLangLocale());
ExceptionDialog.registerUncaughtExceptionHandler();
@@ -0,0 +1,48 @@
package jadx.gui.settings;
import java.awt.Font;
import org.jetbrains.annotations.Nullable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import jadx.core.utils.Utils;
import jadx.gui.utils.FontUtils;
public class FontLoader {
private static final Logger LOG = LoggerFactory.getLogger(FontLoader.class);
private final Font defaultFont;
private Font font;
public FontLoader(Font defaultFont) {
this.defaultFont = defaultFont;
this.font = defaultFont;
}
public Font getFont() {
return font;
}
public void setFont(@Nullable Font font) {
this.font = Utils.getOrElse(font, defaultFont);
}
public void load(String fontStr) {
if (fontStr == null || fontStr.isEmpty()) {
font = defaultFont;
} else {
try {
font = FontUtils.loadByStr(fontStr);
} catch (Exception e) {
LOG.warn("Failed to load font: {}, reset to default", fontStr, e);
font = defaultFont;
}
}
}
public String getFontStr() {
return FontUtils.convertToStr(font);
}
}
@@ -0,0 +1,14 @@
package jadx.gui.settings;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* Annotation to mark fields excluded from config export/copy actions.
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface JadxConfigExcludeExport {
}
@@ -0,0 +1,24 @@
package jadx.gui.settings;
import com.beust.jcommander.Parameter;
import jadx.cli.JadxCLIArgs;
import jadx.cli.config.JadxConfigExclude;
public class JadxGUIArgs extends JadxCLIArgs {
@JadxConfigExclude
@Parameter(
names = { "-sc", "--select-class" },
description = "GUI: Open the selected class and show the decompiled code"
)
private String cmdSelectClass = null;
public String getCmdSelectClass() {
return cmdSelectClass;
}
public void setCmdSelectClass(String cmdSelectClass) {
this.cmdSelectClass = cmdSelectClass;
}
}
@@ -36,6 +36,7 @@ import jadx.core.utils.exceptions.JadxRuntimeException;
import jadx.core.utils.files.FileUtils;
import jadx.gui.cache.manager.CacheManager;
import jadx.gui.settings.data.ProjectData;
import jadx.gui.settings.data.SaveOptionEnum;
import jadx.gui.settings.data.TabViewState;
import jadx.gui.ui.MainWindow;
import jadx.gui.ui.codearea.EditorViewState;
@@ -267,7 +268,7 @@ public class JadxProject {
private void changed() {
JadxSettings settings = mainWindow.getSettings();
if (settings != null && settings.getSaveOption() == JadxSettings.SAVEOPTION.ALWAYS) {
if (settings != null && settings.getSaveOption() == SaveOptionEnum.ALWAYS) {
save();
} else {
saved = false;
File diff suppressed because it is too large Load Diff
@@ -1,108 +0,0 @@
package jadx.gui.settings;
import java.awt.Rectangle;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.lang.reflect.Modifier;
import java.nio.file.Path;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.gson.ExclusionStrategy;
import com.google.gson.FieldAttributes;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonObject;
import jadx.core.utils.GsonUtils;
import jadx.gui.utils.PathTypeAdapter;
import jadx.gui.utils.RectangleTypeAdapter;
public class JadxSettingsAdapter {
private static final Logger LOG = LoggerFactory.getLogger(JadxSettingsAdapter.class);
private static final JadxSettingsStorage STORAGE = new JadxSettingsStorage();
private static final ExclusionStrategy EXCLUDE_FIELDS = new ExclusionStrategy() {
@Override
public boolean shouldSkipField(FieldAttributes f) {
return JadxSettings.SKIP_FIELDS.contains(f.getName())
|| f.hasModifier(Modifier.PUBLIC)
|| f.hasModifier(Modifier.TRANSIENT)
|| f.hasModifier(Modifier.STATIC)
|| f.getAnnotation(GsonExclude.class) != null;
}
@Override
public boolean shouldSkipClass(Class<?> clazz) {
return false;
}
};
private static final Gson GSON = makeGsonBuilder().create();
private JadxSettingsAdapter() {
}
public static JadxSettings load() {
try {
JadxSettings settings = fromString(STORAGE.load());
if (settings == null) {
LOG.debug("Created new settings.");
settings = JadxSettings.makeDefault();
} else {
settings.fixOnLoad();
}
return settings;
} catch (Exception e) {
LOG.error("Error load settings. Settings will reset", e);
return new JadxSettings();
}
}
public static void store(JadxSettings settings) {
try {
STORAGE.save(makeString(settings));
} catch (Exception e) {
LOG.error("Error store settings", e);
}
}
public static JadxSettings fromString(String jsonSettings) {
if (jsonSettings == null) {
return null;
}
return GSON.fromJson(jsonSettings, JadxSettings.class);
}
public static String makeString(JadxSettings settings) {
return GSON.toJson(settings);
}
public static JsonObject makeJsonObject(JadxSettings settings) {
return GSON.toJsonTree(settings).getAsJsonObject();
}
public static void fill(JadxSettings settings, String jsonStr) {
GsonUtils.fillObjectFromJsonString(makeGsonBuilder(), settings, jsonStr);
}
private static GsonBuilder makeGsonBuilder() {
return GsonUtils.defaultGsonBuilder()
.setExclusionStrategies(EXCLUDE_FIELDS)
.registerTypeHierarchyAdapter(Path.class, PathTypeAdapter.singleton())
.registerTypeHierarchyAdapter(Rectangle.class, RectangleTypeAdapter.singleton());
}
/**
* Annotation for specifying fields that should not be saved/loaded
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface GsonExclude {
}
}
@@ -0,0 +1,481 @@
package jadx.gui.settings;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.swing.JFrame;
import org.jetbrains.annotations.Nullable;
import jadx.cli.LogHelper;
import jadx.gui.cache.code.CodeCacheMode;
import jadx.gui.cache.usage.UsageCacheMode;
import jadx.gui.settings.data.SaveOptionEnum;
import jadx.gui.ui.action.ActionModel;
import jadx.gui.ui.tab.dnd.TabDndGhostType;
import jadx.gui.utils.LafManager;
import jadx.gui.utils.LangLocale;
import jadx.gui.utils.NLS;
import jadx.gui.utils.shortcut.Shortcut;
/**
* Data class to hold all jadx-gui settings.
* Also inherit all options from jadx-cli.
* Serialized/deserialized as JSON in {@link jadx.cli.config.JadxConfigAdapter}.
* Annotation {@link JadxConfigExcludeExport} used to exclude environment (files, window states)
* fields from copy/export.
*/
public class JadxSettingsData extends JadxGUIArgs {
public static final int CURRENT_SETTINGS_VERSION = 23;
private static final Path USER_HOME = Paths.get(System.getProperty("user.home"));
@JadxConfigExcludeExport
private Path lastSaveProjectPath = USER_HOME;
@JadxConfigExcludeExport
private Path lastOpenFilePath = USER_HOME;
@JadxConfigExcludeExport
private Path lastSaveFilePath = USER_HOME;
@JadxConfigExcludeExport
private List<Path> recentProjects = new ArrayList<>();
@JadxConfigExcludeExport
private Map<String, WindowLocation> windowPos = new HashMap<>();
@JadxConfigExcludeExport
private int mainWindowExtendedState = JFrame.NORMAL;
private boolean flattenPackage = false;
private boolean checkForUpdates = true;
private JadxUpdateChannel jadxUpdateChannel = JadxUpdateChannel.STABLE;
private float uiZoom = 1.0f;
private String fontStr = "";
private String smaliFontStr = "";
private String editorTheme = "";
private String lafTheme = LafManager.INITIAL_THEME_NAME;
private LangLocale langLocale = NLS.defaultLocale();
private boolean autoStartJobs = false;
private String excludedPackages = "";
private SaveOptionEnum saveOption = SaveOptionEnum.ASK;
private Map<ActionModel, Shortcut> shortcuts = new HashMap<>();
private boolean showHeapUsageBar = false;
private boolean alwaysSelectOpened = false;
private boolean enablePreviewTab = false;
private boolean useAlternativeFileDialog = false;
private boolean codeAreaLineWrap = false;
private int searchResultsPerPage = 50;
private boolean useAutoSearch = true;
private boolean keepCommonDialogOpen = false;
private boolean smaliAreaShowBytecode = false;
private LineNumbersMode lineNumbersMode = LineNumbersMode.AUTO;
private int mainWindowVerticalSplitterLoc = 300;
private int debuggerStackFrameSplitterLoc = 300;
private int debuggerVarTreeSplitterLoc = 700;
private String adbDialogPath = "";
private String adbDialogHost = "localhost";
private String adbDialogPort = "5037";
private CodeCacheMode codeCacheMode = CodeCacheMode.DISK;
private UsageCacheMode usageCacheMode = UsageCacheMode.DISK;
/**
* Cache dir option values:
* null - default (system)
* "." - at project dir
* other - custom
*/
private @Nullable String cacheDir = null;
private boolean jumpOnDoubleClick = true;
private boolean disableTooltipOnHover = false;
private XposedCodegenLanguage xposedCodegenLanguage = XposedCodegenLanguage.JAVA;
private int treeWidth = 130;
private boolean dockLogViewer = true;
private boolean dockQuickTabs = false;
private TabDndGhostType tabDndGhostType = TabDndGhostType.OUTLINE;
private int settingsVersion = CURRENT_SETTINGS_VERSION;
public JadxSettingsData() {
this.logLevel = LogHelper.LogLevelEnum.INFO;
}
public String getAdbDialogHost() {
return adbDialogHost;
}
public void setAdbDialogHost(String adbDialogHost) {
this.adbDialogHost = adbDialogHost;
}
public String getAdbDialogPath() {
return adbDialogPath;
}
public void setAdbDialogPath(String adbDialogPath) {
this.adbDialogPath = adbDialogPath;
}
public String getAdbDialogPort() {
return adbDialogPort;
}
public void setAdbDialogPort(String adbDialogPort) {
this.adbDialogPort = adbDialogPort;
}
public boolean isAlwaysSelectOpened() {
return alwaysSelectOpened;
}
public void setAlwaysSelectOpened(boolean alwaysSelectOpened) {
this.alwaysSelectOpened = alwaysSelectOpened;
}
public boolean isAutoStartJobs() {
return autoStartJobs;
}
public void setAutoStartJobs(boolean autoStartJobs) {
this.autoStartJobs = autoStartJobs;
}
public @Nullable String getCacheDir() {
return cacheDir;
}
public void setCacheDir(@Nullable String cacheDir) {
this.cacheDir = cacheDir;
}
public boolean isCheckForUpdates() {
return checkForUpdates;
}
public void setCheckForUpdates(boolean checkForUpdates) {
this.checkForUpdates = checkForUpdates;
}
public boolean isCodeAreaLineWrap() {
return codeAreaLineWrap;
}
public void setCodeAreaLineWrap(boolean codeAreaLineWrap) {
this.codeAreaLineWrap = codeAreaLineWrap;
}
public CodeCacheMode getCodeCacheMode() {
return codeCacheMode;
}
public void setCodeCacheMode(CodeCacheMode codeCacheMode) {
this.codeCacheMode = codeCacheMode;
}
public int getDebuggerStackFrameSplitterLoc() {
return debuggerStackFrameSplitterLoc;
}
public void setDebuggerStackFrameSplitterLoc(int debuggerStackFrameSplitterLoc) {
this.debuggerStackFrameSplitterLoc = debuggerStackFrameSplitterLoc;
}
public int getDebuggerVarTreeSplitterLoc() {
return debuggerVarTreeSplitterLoc;
}
public void setDebuggerVarTreeSplitterLoc(int debuggerVarTreeSplitterLoc) {
this.debuggerVarTreeSplitterLoc = debuggerVarTreeSplitterLoc;
}
public boolean isDisableTooltipOnHover() {
return disableTooltipOnHover;
}
public void setDisableTooltipOnHover(boolean disableTooltipOnHover) {
this.disableTooltipOnHover = disableTooltipOnHover;
}
public boolean isDockLogViewer() {
return dockLogViewer;
}
public void setDockLogViewer(boolean dockLogViewer) {
this.dockLogViewer = dockLogViewer;
}
public boolean isDockQuickTabs() {
return dockQuickTabs;
}
public void setDockQuickTabs(boolean dockQuickTabs) {
this.dockQuickTabs = dockQuickTabs;
}
public String getEditorTheme() {
return editorTheme;
}
public void setEditorTheme(String editorTheme) {
this.editorTheme = editorTheme;
}
public boolean isEnablePreviewTab() {
return enablePreviewTab;
}
public void setEnablePreviewTab(boolean enablePreviewTab) {
this.enablePreviewTab = enablePreviewTab;
}
public String getExcludedPackages() {
return excludedPackages;
}
public void setExcludedPackages(String excludedPackages) {
this.excludedPackages = excludedPackages;
}
public boolean isFlattenPackage() {
return flattenPackage;
}
public void setFlattenPackage(boolean flattenPackage) {
this.flattenPackage = flattenPackage;
}
public JadxUpdateChannel getJadxUpdateChannel() {
return jadxUpdateChannel;
}
public void setJadxUpdateChannel(JadxUpdateChannel jadxUpdateChannel) {
this.jadxUpdateChannel = jadxUpdateChannel;
}
public boolean isJumpOnDoubleClick() {
return jumpOnDoubleClick;
}
public void setJumpOnDoubleClick(boolean jumpOnDoubleClick) {
this.jumpOnDoubleClick = jumpOnDoubleClick;
}
public boolean isKeepCommonDialogOpen() {
return keepCommonDialogOpen;
}
public void setKeepCommonDialogOpen(boolean keepCommonDialogOpen) {
this.keepCommonDialogOpen = keepCommonDialogOpen;
}
public String getLafTheme() {
return lafTheme;
}
public void setLafTheme(String lafTheme) {
this.lafTheme = lafTheme;
}
public LangLocale getLangLocale() {
return langLocale;
}
public void setLangLocale(LangLocale langLocale) {
this.langLocale = langLocale;
}
public Path getLastOpenFilePath() {
return lastOpenFilePath;
}
public void setLastOpenFilePath(Path lastOpenFilePath) {
this.lastOpenFilePath = lastOpenFilePath;
}
public Path getLastSaveFilePath() {
return lastSaveFilePath;
}
public void setLastSaveFilePath(Path lastSaveFilePath) {
this.lastSaveFilePath = lastSaveFilePath;
}
public Path getLastSaveProjectPath() {
return lastSaveProjectPath;
}
public void setLastSaveProjectPath(Path lastSaveProjectPath) {
this.lastSaveProjectPath = lastSaveProjectPath;
}
public LineNumbersMode getLineNumbersMode() {
return lineNumbersMode;
}
public void setLineNumbersMode(LineNumbersMode lineNumbersMode) {
this.lineNumbersMode = lineNumbersMode;
}
public int getMainWindowExtendedState() {
return mainWindowExtendedState;
}
public void setMainWindowExtendedState(int mainWindowExtendedState) {
this.mainWindowExtendedState = mainWindowExtendedState;
}
public int getMainWindowVerticalSplitterLoc() {
return mainWindowVerticalSplitterLoc;
}
public void setMainWindowVerticalSplitterLoc(int mainWindowVerticalSplitterLoc) {
this.mainWindowVerticalSplitterLoc = mainWindowVerticalSplitterLoc;
}
public List<Path> getRecentProjects() {
return recentProjects;
}
public void setRecentProjects(List<Path> recentProjects) {
this.recentProjects = recentProjects;
}
public SaveOptionEnum getSaveOption() {
return saveOption;
}
public void setSaveOption(SaveOptionEnum saveOption) {
this.saveOption = saveOption;
}
public int getSearchResultsPerPage() {
return searchResultsPerPage;
}
public void setSearchResultsPerPage(int searchResultsPerPage) {
this.searchResultsPerPage = searchResultsPerPage;
}
public int getSettingsVersion() {
return settingsVersion;
}
public void setSettingsVersion(int settingsVersion) {
this.settingsVersion = settingsVersion;
}
public Map<ActionModel, Shortcut> getShortcuts() {
return shortcuts;
}
public void setShortcuts(Map<ActionModel, Shortcut> shortcuts) {
this.shortcuts = shortcuts;
}
public boolean isShowHeapUsageBar() {
return showHeapUsageBar;
}
public void setShowHeapUsageBar(boolean showHeapUsageBar) {
this.showHeapUsageBar = showHeapUsageBar;
}
public boolean isSmaliAreaShowBytecode() {
return smaliAreaShowBytecode;
}
public void setSmaliAreaShowBytecode(boolean smaliAreaShowBytecode) {
this.smaliAreaShowBytecode = smaliAreaShowBytecode;
}
public String getFontStr() {
return fontStr;
}
public void setFontStr(String fontStr) {
this.fontStr = fontStr;
}
public String getSmaliFontStr() {
return smaliFontStr;
}
public void setSmaliFontStr(String smaliFontStr) {
this.smaliFontStr = smaliFontStr;
}
public TabDndGhostType getTabDndGhostType() {
return tabDndGhostType;
}
public void setTabDndGhostType(TabDndGhostType tabDndGhostType) {
this.tabDndGhostType = tabDndGhostType;
}
public int getTreeWidth() {
return treeWidth;
}
public void setTreeWidth(int treeWidth) {
this.treeWidth = treeWidth;
}
public float getUiZoom() {
return uiZoom;
}
public void setUiZoom(float uiZoom) {
this.uiZoom = uiZoom;
}
public UsageCacheMode getUsageCacheMode() {
return usageCacheMode;
}
public void setUsageCacheMode(UsageCacheMode usageCacheMode) {
this.usageCacheMode = usageCacheMode;
}
public boolean isUseAlternativeFileDialog() {
return useAlternativeFileDialog;
}
public void setUseAlternativeFileDialog(boolean useAlternativeFileDialog) {
this.useAlternativeFileDialog = useAlternativeFileDialog;
}
public boolean isUseAutoSearch() {
return useAutoSearch;
}
public void setUseAutoSearch(boolean useAutoSearch) {
this.useAutoSearch = useAutoSearch;
}
public Map<String, WindowLocation> getWindowPos() {
return windowPos;
}
public void setWindowPos(Map<String, WindowLocation> windowPos) {
this.windowPos = windowPos;
}
public XposedCodegenLanguage getXposedCodegenLanguage() {
return xposedCodegenLanguage;
}
public void setXposedCodegenLanguage(XposedCodegenLanguage xposedCodegenLanguage) {
this.xposedCodegenLanguage = xposedCodegenLanguage;
}
}
@@ -1,59 +0,0 @@
package jadx.gui.settings;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.prefs.Preferences;
import org.jetbrains.annotations.Nullable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import jadx.core.utils.StringUtils;
import jadx.core.utils.files.FileUtils;
import jadx.gui.JadxGUI;
import jadx.gui.utils.files.JadxFiles;
/**
* Jadx settings storage. Select first available option:
* 1. json file in system 'config' directory (preferred)
* 2. using java preferences api: use Windows registry or xml on Linux/Mac (obsolete, load only)
*/
public class JadxSettingsStorage {
private static final Logger LOG = LoggerFactory.getLogger(JadxSettingsStorage.class);
private final Path configFile = initConfigFile();
public synchronized @Nullable String load() throws IOException {
if (Files.exists(configFile)) {
return FileUtils.readFile(configFile);
}
return null;
}
public synchronized void save(String jsonStr) throws IOException {
FileUtils.writeFile(configFile, jsonStr);
}
private static Path initConfigFile() {
Path confPath = JadxFiles.GUI_CONF;
if (!Files.exists(confPath)) {
copyFromPreferences(confPath);
}
LOG.debug("Using config: {}", confPath);
return confPath;
}
private static void copyFromPreferences(Path confPath) {
try {
Preferences prefs = Preferences.userNodeForPackage(JadxGUI.class);
String str = prefs.get("jadx.gui.settings", "");
if (StringUtils.notEmpty(str)) {
FileUtils.writeFile(confPath, str);
LOG.warn("Settings moved from java preferences to config file: {}", confPath);
}
} catch (Exception e) {
LOG.warn("Failed to load settings from preferences", e);
}
}
}
@@ -0,0 +1,6 @@
package jadx.gui.settings;
public enum JadxUpdateChannel {
STABLE,
UNSTABLE,
}
@@ -1,6 +0,0 @@
package jadx.gui.settings
enum class JadxUpdateChannel {
STABLE,
UNSTABLE,
}
@@ -0,0 +1,6 @@
package jadx.gui.settings;
public enum XposedCodegenLanguage {
JAVA,
KOTLIN,
}
@@ -1,6 +0,0 @@
package jadx.gui.settings
enum class XposedCodegenLanguage {
JAVA,
KOTLIN,
}
@@ -0,0 +1,7 @@
package jadx.gui.settings.data;
public enum SaveOptionEnum {
ASK,
NEVER,
ALWAYS
}
@@ -42,8 +42,6 @@ import org.jetbrains.annotations.Nullable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.gson.JsonObject;
import jadx.api.CommentsLevel;
import jadx.api.DecompilationMode;
import jadx.api.JadxArgs;
@@ -56,14 +54,14 @@ import jadx.api.args.UseSourceNameAsClassNameAlias;
import jadx.api.plugins.events.JadxEvents;
import jadx.api.plugins.events.types.ReloadSettingsWindow;
import jadx.api.plugins.gui.ISettingsGroup;
import jadx.core.utils.GsonUtils;
import jadx.core.utils.StringUtils;
import jadx.core.utils.exceptions.JadxRuntimeException;
import jadx.gui.settings.JadxSettings;
import jadx.gui.settings.JadxSettingsAdapter;
import jadx.gui.settings.JadxSettingsData;
import jadx.gui.settings.JadxUpdateChannel;
import jadx.gui.settings.LineNumbersMode;
import jadx.gui.settings.XposedCodegenLanguage;
import jadx.gui.settings.data.SaveOptionEnum;
import jadx.gui.settings.ui.cache.CacheSettingsGroup;
import jadx.gui.settings.ui.font.JadxFontDialog;
import jadx.gui.settings.ui.plugins.PluginSettings;
@@ -99,7 +97,7 @@ public class JadxSettingsWindow extends JDialog {
public JadxSettingsWindow(MainWindow mainWindow, JadxSettings settings) {
this.mainWindow = mainWindow;
this.settings = settings;
this.startSettings = JadxSettingsAdapter.makeString(settings);
this.startSettings = settings.getSettingsJsonString();
this.startSettingsHash = calcSettingsHash();
this.prevLang = settings.getLangLocale();
@@ -279,7 +277,7 @@ public class JadxSettingsWindow extends JDialog {
JCheckBox useHeaders = new JCheckBox();
useHeaders.setSelected(settings.isUseHeadersForDetectResourceExtensions());
useHeaders.addItemListener(e -> {
settings.setUseHeadersForDetectResourceExtension(e.getStateChange() == ItemEvent.SELECTED);
settings.setUseHeadersForDetectResourceExtensions(e.getStateChange() == ItemEvent.SELECTED);
needReload();
});
@@ -376,10 +374,10 @@ public class JadxSettingsWindow extends JDialog {
}
private SettingsGroup makeProjectGroup() {
JComboBox<JadxSettings.SAVEOPTION> dropdown = new JComboBox<>(JadxSettings.SAVEOPTION.values());
JComboBox<SaveOptionEnum> dropdown = new JComboBox<>(SaveOptionEnum.values());
dropdown.setSelectedItem(settings.getSaveOption());
dropdown.addActionListener(e -> {
settings.setSaveOption((JadxSettings.SAVEOPTION) dropdown.getSelectedItem());
settings.setSaveOption((SaveOptionEnum) dropdown.getSelectedItem());
needReload();
});
@@ -730,16 +728,14 @@ public class JadxSettingsWindow extends JDialog {
needReload();
});
JComboBox<XposedCodegenLanguage> xposedCodegenLanguage =
new JComboBox<>(XposedCodegenLanguage.getEntries().toArray(new XposedCodegenLanguage[0]));
JComboBox<XposedCodegenLanguage> xposedCodegenLanguage = new JComboBox<>(XposedCodegenLanguage.values());
xposedCodegenLanguage.setSelectedItem(settings.getXposedCodegenLanguage());
xposedCodegenLanguage.addActionListener(e -> {
settings.setXposedCodegenLanguage((XposedCodegenLanguage) xposedCodegenLanguage.getSelectedItem());
mainWindow.loadSettings();
});
JComboBox<JadxUpdateChannel> updateChannel =
new JComboBox<>(JadxUpdateChannel.getEntries().toArray(new JadxUpdateChannel[0]));
JComboBox<JadxUpdateChannel> updateChannel = new JComboBox<>(JadxUpdateChannel.values());
updateChannel.setSelectedItem(settings.getJadxUpdateChannel());
updateChannel.addActionListener(e -> {
settings.setJadxUpdateChannel((JadxUpdateChannel) updateChannel.getSelectedItem());
@@ -788,7 +784,7 @@ public class JadxSettingsWindow extends JDialog {
private void cancel() {
closeGroups(false);
JadxSettingsAdapter.fill(settings, startSettings);
settings.loadSettingsFromJsonString(startSettings);
mainWindow.loadSettings();
dispose();
}
@@ -800,8 +796,7 @@ public class JadxSettingsWindow extends JDialog {
NLS.str("preferences.reset_title"),
JOptionPane.YES_NO_OPTION);
if (res == JOptionPane.YES_OPTION) {
String defaults = JadxSettingsAdapter.makeString(JadxSettings.makeDefault());
JadxSettingsAdapter.fill(settings, defaults);
settings.loadSettingsData(new JadxSettingsData());
mainWindow.loadSettings();
needReload();
getContentPane().removeAll();
@@ -812,15 +807,7 @@ public class JadxSettingsWindow extends JDialog {
}
private void copySettings() {
JsonObject settingsJson = JadxSettingsAdapter.makeJsonObject(this.settings);
// remove irrelevant preferences
settingsJson.remove("windowPos");
settingsJson.remove("mainWindowExtendedState");
settingsJson.remove("lastSaveProjectPath");
settingsJson.remove("lastOpenFilePath");
settingsJson.remove("lastSaveFilePath");
settingsJson.remove("recentProjects");
String settingsText = GsonUtils.buildGson().toJson(settingsJson);
String settingsText = settings.exportSettingsString();
Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard();
StringSelection selection = new StringSelection(settingsText);
clipboard.setContents(selection, selection);
@@ -118,6 +118,7 @@ import jadx.gui.plugins.mappings.RenameMappingsGui;
import jadx.gui.plugins.quark.QuarkDialog;
import jadx.gui.settings.JadxProject;
import jadx.gui.settings.JadxSettings;
import jadx.gui.settings.data.SaveOptionEnum;
import jadx.gui.settings.ui.JadxSettingsWindow;
import jadx.gui.tree.TreeExpansionService;
import jadx.gui.treemodel.ApkSignatureNode;
@@ -700,10 +701,10 @@ public class MainWindow extends JFrame {
return true;
}
// Check if we saved settings that indicate what to do
if (settings.getSaveOption() == JadxSettings.SAVEOPTION.NEVER) {
if (settings.getSaveOption() == SaveOptionEnum.NEVER) {
return true;
}
if (settings.getSaveOption() == JadxSettings.SAVEOPTION.ALWAYS) {
if (settings.getSaveOption() == SaveOptionEnum.ALWAYS) {
saveProject();
return true;
}
@@ -723,7 +724,7 @@ public class MainWindow extends JFrame {
switch (res) {
case JOptionPane.YES_OPTION:
if (remember.isSelected()) {
settings.setSaveOption(JadxSettings.SAVEOPTION.ALWAYS);
settings.setSaveOption(SaveOptionEnum.ALWAYS);
settings.sync();
}
saveProject();
@@ -731,7 +732,7 @@ public class MainWindow extends JFrame {
case JOptionPane.NO_OPTION:
if (remember.isSelected()) {
settings.setSaveOption(JadxSettings.SAVEOPTION.NEVER);
settings.setSaveOption(SaveOptionEnum.NEVER);
settings.sync();
}
return true;
@@ -1163,12 +1164,12 @@ public class MainWindow extends JFrame {
JCheckBoxMenuItem dockLog = new JCheckBoxMenuItem(NLS.str("menu.dock_log"));
dockLog.setState(settings.isDockLogViewer());
dockLog.addActionListener(event -> settings.setDockLogViewer(!settings.isDockLogViewer()));
dockLog.addActionListener(event -> settings.saveDockLogViewer(!settings.isDockLogViewer()));
ActionHandler quickTabsAction = new ActionHandler(ev -> {
boolean visible = quickTabsTree == null;
setQuickTabsVisibility(visible);
settings.setDockQuickTabs(visible);
settings.saveDockQuickTabs(visible);
});
quickTabsAction.setNameAndDesc(NLS.str("menu.dock_quick_tabs"));
quickTabsAction.setIcon(Icons.QUICK_TABS);
@@ -1586,6 +1587,7 @@ public class MainWindow extends JFrame {
shortcutsController.loadSettings();
}
@SuppressWarnings("finally")
private void closeWindow() {
saveAll();
if (!ensureProjectIsSaved()) {
@@ -1599,6 +1601,7 @@ public class MainWindow extends JFrame {
if (debuggerPanel != null) {
saveSplittersInfo();
}
settings.sync();
closeAll();
UiUtils.uiRunAndWait(() -> {
heapUsageBar.reset();
@@ -1758,7 +1761,7 @@ public class MainWindow extends JFrame {
}
Runnable undock = () -> {
hideDockedLog();
settings.setDockLogViewer(false);
settings.saveDockLogViewer(false);
LogViewerDialog.open(this, logOptions);
};
logPanel = new LogPanel(this, logOptions, undock, this::hideDockedLog);
@@ -162,6 +162,7 @@ public abstract class AbstractCodeArea extends RSyntaxTextArea {
public void actionPerformed(ActionEvent e) {
boolean wrap = !getLineWrap();
settings.setCodeAreaLineWrap(wrap);
settings.sync();
contentPanel.getTabbedPane().getTabs().forEach(v -> {
if (v instanceof AbstractCodeContentPanel) {
AbstractCodeArea codeArea = ((AbstractCodeContentPanel) v).getCodeArea();
@@ -172,7 +173,6 @@ public abstract class AbstractCodeArea extends RSyntaxTextArea {
}
}
});
settings.sync();
}
});
popupMenu.add(wrapItem);
@@ -75,7 +75,7 @@ public final class SmaliArea extends AbstractCodeArea {
@Override
public void actionPerformed(ActionEvent e) {
JadxSettings settings = getContentPanel().getMainWindow().getSettings();
settings.setSmaliAreaShowBytecode(!settings.getSmaliAreaShowBytecode());
settings.setSmaliAreaShowBytecode(!settings.isSmaliAreaShowBytecode());
contentPanel.getTabbedPane().getTabs().forEach(v -> {
if (v instanceof ClassCodeContentPanel) {
switchModel();
@@ -156,7 +156,7 @@ public final class SmaliArea extends AbstractCodeArea {
}
private boolean shouldUseSmaliPrinterV2() {
return getContentPanel().getMainWindow().getSettings().getSmaliAreaShowBytecode();
return getContentPanel().getMainWindow().getSettings().isSmaliAreaShowBytecode();
}
private abstract class SmaliModel {
@@ -46,6 +46,7 @@ import jadx.gui.device.debugger.DebugSettings;
import jadx.gui.device.protocol.ADB;
import jadx.gui.device.protocol.ADBDevice;
import jadx.gui.device.protocol.ADBDeviceInfo;
import jadx.gui.settings.JadxSettings;
import jadx.gui.ui.MainWindow;
import jadx.gui.ui.panel.IDebugController;
import jadx.gui.utils.NLS;
@@ -427,13 +428,17 @@ public class ADBDialog extends JDialog implements ADB.DeviceStateListener, ADB.J
@Override
public void dispose() {
clear();
super.dispose();
boolean save = mainWindow.getSettings().getAdbDialogPath().equals(pathTextField.getText());
boolean save1 = mainWindow.getSettings().getAdbDialogHost().equals(hostTextField.getText());
boolean save2 = mainWindow.getSettings().getAdbDialogPort().equals(portTextField.getText());
if (save || save1 || save2) {
mainWindow.getSettings().sync();
JadxSettings settings = mainWindow.getSettings();
boolean changed = !settings.getAdbDialogPath().equals(pathTextField.getText());
changed |= !settings.getAdbDialogHost().equals(hostTextField.getText());
changed |= !settings.getAdbDialogPort().equals(portTextField.getText());
if (changed) {
settings.setAdbDialogPath(pathTextField.getText());
settings.setAdbDialogHost(hostTextField.getText());
settings.setAdbDialogPort(portTextField.getText());
settings.sync();
}
super.dispose();
}
@Override
@@ -153,7 +153,7 @@ public abstract class CommonSearchDialog extends JFrame {
} else {
tabsController.codeJump(node);
}
if (!mainWindow.getSettings().getKeepCommonDialogOpen()) {
if (!mainWindow.getSettings().isKeepCommonDialogOpen()) {
dispose();
}
}
@@ -210,11 +210,8 @@ public abstract class CommonSearchDialog extends JFrame {
copyBtn.addActionListener(event -> copyAllSearchResults());
JCheckBox cbKeepOpen = new JCheckBox(NLS.str("search_dialog.keep_open"));
cbKeepOpen.setSelected(mainWindow.getSettings().getKeepCommonDialogOpen());
cbKeepOpen.addActionListener(e -> {
mainWindow.getSettings().setKeepCommonDialogOpen(cbKeepOpen.isSelected());
mainWindow.getSettings().sync();
});
cbKeepOpen.setSelected(mainWindow.getSettings().isKeepCommonDialogOpen());
cbKeepOpen.addActionListener(e -> mainWindow.getSettings().saveKeepCommonDialogOpen(cbKeepOpen.isSelected()));
cbKeepOpen.setAlignmentY(Component.CENTER_ALIGNMENT);
JPanel buttonPane = new JPanel();
@@ -32,10 +32,11 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import jadx.api.JadxDecompiler;
import jadx.cli.config.JadxConfigAdapter;
import jadx.commons.app.JadxSystemInfo;
import jadx.core.utils.exceptions.JadxRuntimeException;
import jadx.gui.settings.JadxSettings;
import jadx.gui.settings.JadxSettingsAdapter;
import jadx.gui.settings.JadxSettingsData;
import jadx.gui.utils.LafManager;
import jadx.gui.utils.Link;
@@ -130,10 +131,10 @@ public class ExceptionDialog extends JDialog {
JPanel buttonPanel = new JPanel();
JButton exitButton = new JButton("Terminate Jadx");
exitButton.addActionListener((event) -> System.exit(1));
exitButton.addActionListener(event -> System.exit(1));
buttonPanel.add(exitButton);
JButton closeButton = new JButton("Go back to Jadx");
closeButton.addActionListener((event) -> setVisible(false));
closeButton.addActionListener(event -> setVisible(false));
buttonPanel.add(closeButton);
JScrollPane messageAreaScroller = new JScrollPane(messageArea);
messageAreaScroller.setMinimumSize(new Dimension(600, 400));
@@ -152,7 +153,7 @@ public class ExceptionDialog extends JDialog {
final int y = (screenSize.height - getHeight()) / 2;
setLocation(x, y);
getRootPane().registerKeyboardAction((event) -> setVisible(false),
getRootPane().registerKeyboardAction(event -> setVisible(false),
KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0),
JComponent.WHEN_IN_FOCUSED_WINDOW);
@@ -177,8 +178,14 @@ public class ExceptionDialog extends JDialog {
}
public static void main(String[] args) {
JadxSettings settings = JadxSettingsAdapter.load();
LafManager.init(settings);
JadxConfigAdapter<JadxSettingsData> configAdapter = JadxSettings.buildConfigAdapter();
configAdapter.useConfigRef("");
JadxSettingsData settingsData = configAdapter.load();
if (settingsData != null) {
JadxSettings settings = new JadxSettings(configAdapter);
settings.loadSettingsData(settingsData);
LafManager.init(settings);
}
showTestExceptionDialog();
}
}
@@ -39,7 +39,7 @@ public class LogViewerDialog extends JFrame {
UiUtils.setWindowIcons(this);
Runnable dock = () -> {
mainWindow.getSettings().setDockLogViewer(true);
mainWindow.getSettings().saveDockLogViewer(true);
dispose();
mainWindow.showLogViewer(LogOptions.current());
};
@@ -270,7 +270,7 @@ public class SearchDialog extends CommonSearchDialog {
autoSearchCB.setSelected(autoSearch);
autoSearchCB.addActionListener(ev -> {
boolean newValue = autoSearchCB.isSelected();
mainWindow.getSettings().setUseAutoSearch(newValue);
mainWindow.getSettings().saveUseAutoSearch(newValue);
searchBtn.setVisible(!newValue);
initSearchEvents();
if (newValue) {
@@ -597,11 +597,8 @@ public class UsageDialogPlus extends CommonSearchDialog {
getRootPane().setDefaultButton(openBtn);
JCheckBox cbKeepOpen = new JCheckBox(NLS.str("search_dialog.keep_open"));
cbKeepOpen.setSelected(mainWindow.getSettings().getKeepCommonDialogOpen());
cbKeepOpen.addActionListener(e -> {
mainWindow.getSettings().setKeepCommonDialogOpen(cbKeepOpen.isSelected());
mainWindow.getSettings().sync();
});
cbKeepOpen.setSelected(mainWindow.getSettings().isKeepCommonDialogOpen());
cbKeepOpen.addActionListener(e -> mainWindow.getSettings().saveKeepCommonDialogOpen(cbKeepOpen.isSelected()));
cbKeepOpen.setAlignmentY(Component.CENTER_ALIGNMENT);
JPanel buttonPane = new JPanel();
@@ -11,7 +11,7 @@ import com.google.gson.stream.JsonWriter;
public class PathTypeAdapter {
private static final TypeAdapter<Path> SINGLETON = new TypeAdapter<Path>() {
private static final TypeAdapter<Path> SINGLETON = new TypeAdapter<>() {
@Override
public void write(JsonWriter out, Path value) throws IOException {
if (value == null) {
@@ -10,7 +10,7 @@ import com.google.gson.stream.JsonWriter;
public class RectangleTypeAdapter {
private static final TypeAdapter<Rectangle> SINGLETON = new TypeAdapter<Rectangle>() {
private static final TypeAdapter<Rectangle> SINGLETON = new TypeAdapter<>() {
@Override
public void write(JsonWriter out, Rectangle value) throws IOException {
if (value == null) {