feat(cli): implement config file load and save (#1731)
This commit is contained in:
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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());
|
||||
|
||||
@@ -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 {
|
||||
}
|
||||
Reference in New Issue
Block a user