From 948f9456f502b48d5a10476544abb5ab6ef63ec6 Mon Sep 17 00:00:00 2001 From: Skylot Date: Sat, 10 Feb 2018 21:32:51 +0300 Subject: [PATCH] core: change jadx args api for easier processing and validation --- jadx-cli/src/main/java/jadx/cli/JadxCLI.java | 42 +---- .../src/main/java/jadx/cli/JadxCLIArgs.java | 122 +++++--------- .../test/java/jadx/cli/JadxCLIArgsTest.java | 2 +- .../src/main/java/jadx/api/IJadxArgs.java | 51 ------ .../src/main/java/jadx/api/JadxArgs.java | 78 ++++----- .../main/java/jadx/api/JadxArgsValidator.java | 89 ++++++++++ .../main/java/jadx/api/JadxDecompiler.java | 154 +++++++----------- jadx-core/src/main/java/jadx/core/Jadx.java | 33 ++-- .../main/java/jadx/core/codegen/ClassGen.java | 6 +- .../main/java/jadx/core/codegen/CodeGen.java | 10 +- .../java/jadx/core/deobf/Deobfuscator.java | 8 +- .../java/jadx/core/dex/info/ConstStorage.java | 4 +- .../java/jadx/core/dex/nodes/ClassNode.java | 4 +- .../java/jadx/core/dex/nodes/DexNode.java | 2 +- .../java/jadx/core/dex/nodes/RootNode.java | 67 ++++---- .../core/dex/visitors/DotGraphVisitor.java | 42 ++--- .../jadx/core/dex/visitors/RenameVisitor.java | 5 +- .../java/jadx/core/dex/visitors/SaveCode.java | 4 +- .../main/java/jadx/core/utils/DebugUtils.java | 6 +- .../java/jadx/core/utils/StringUtils.java | 6 +- .../java/jadx/core/utils/files/FileUtils.java | 8 + .../src/test/groovy/jadx/tests/TestAPI.groovy | 68 -------- .../api/JadxArgsValidatorOutDirsTest.java | 72 ++++++++ .../java/jadx/api/JadxDecompilerTest.java | 23 +++ .../java/jadx/tests/api/IntegrationTest.java | 16 +- .../functional/JadxVisitorsOrderTest.java | 3 +- jadx-gui/src/main/java/jadx/gui/JadxGUI.java | 20 +-- .../src/main/java/jadx/gui/JadxWrapper.java | 26 +-- .../java/jadx/gui/settings/JadxSettings.java | 7 +- .../jadx/gui/settings/JadxSettingsWindow.java | 5 +- .../src/main/java/jadx/gui/ui/MainWindow.java | 10 +- .../java/jadx/gui/treemodel/JSourcesTest.java | 4 +- 32 files changed, 473 insertions(+), 524 deletions(-) delete mode 100644 jadx-core/src/main/java/jadx/api/IJadxArgs.java create mode 100644 jadx-core/src/main/java/jadx/api/JadxArgsValidator.java delete mode 100644 jadx-core/src/test/groovy/jadx/tests/TestAPI.groovy create mode 100644 jadx-core/src/test/java/jadx/api/JadxArgsValidatorOutDirsTest.java create mode 100644 jadx-core/src/test/java/jadx/api/JadxDecompilerTest.java diff --git a/jadx-cli/src/main/java/jadx/cli/JadxCLI.java b/jadx-cli/src/main/java/jadx/cli/JadxCLI.java index d67d7183e..719390636 100644 --- a/jadx-cli/src/main/java/jadx/cli/JadxCLI.java +++ b/jadx-cli/src/main/java/jadx/cli/JadxCLI.java @@ -1,12 +1,9 @@ package jadx.cli; -import java.io.File; - import org.slf4j.Logger; import org.slf4j.LoggerFactory; import jadx.api.JadxDecompiler; -import jadx.core.utils.exceptions.JadxException; public class JadxCLI { private static final Logger LOG = LoggerFactory.getLogger(JadxCLI.class); @@ -14,7 +11,7 @@ public class JadxCLI { public static void main(String[] args) { try { JadxCLIArgs jadxArgs = new JadxCLIArgs(); - if (processArgs(jadxArgs, args)) { + if (jadxArgs.processArgs(args)) { processAndSave(jadxArgs); } } catch (Exception e) { @@ -23,10 +20,9 @@ public class JadxCLI { } } - static void processAndSave(JadxCLIArgs jadxArgs) throws JadxException { - JadxDecompiler jadx = new JadxDecompiler(jadxArgs); - jadx.setOutputDir(jadxArgs.getOutDir()); - jadx.loadFiles(jadxArgs.getInput()); + static void processAndSave(JadxCLIArgs inputArgs) { + JadxDecompiler jadx = new JadxDecompiler(inputArgs.toJadxArgs()); + jadx.load(); jadx.save(); if (jadx.getErrorsCount() != 0) { jadx.printErrorsReport(); @@ -35,34 +31,4 @@ public class JadxCLI { LOG.info("done"); } } - - static boolean processArgs(JadxCLIArgs jadxArgs, String[] args) throws JadxException { - if (!jadxArgs.processArgs(args)) { - return false; - } - if (jadxArgs.getInput().isEmpty()) { - LOG.error("Please specify input file"); - jadxArgs.printUsage(); - return false; - } - File outputDir = jadxArgs.getOutDir(); - if (outputDir == null) { - String outDirName; - File file = jadxArgs.getInput().get(0); - String name = file.getName(); - int pos = name.lastIndexOf('.'); - if (pos != -1) { - outDirName = name.substring(0, pos); - } else { - outDirName = name + "-jadx-out"; - } - LOG.info("output directory: {}", outDirName); - outputDir = new File(outDirName); - jadxArgs.setOutputDir(outputDir); - } - if (outputDir.exists() && !outputDir.isDirectory()) { - throw new JadxException("Output directory exists as file " + outputDir); - } - return true; - } } diff --git a/jadx-cli/src/main/java/jadx/cli/JadxCLIArgs.java b/jadx-cli/src/main/java/jadx/cli/JadxCLIArgs.java index 6ac04faa5..38278afba 100644 --- a/jadx-cli/src/main/java/jadx/cli/JadxCLIArgs.java +++ b/jadx-cli/src/main/java/jadx/cli/JadxCLIArgs.java @@ -1,12 +1,12 @@ package jadx.cli; -import java.io.File; import java.io.PrintStream; import java.lang.reflect.Field; import java.util.ArrayList; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; +import java.util.stream.Collectors; import ch.qos.logback.classic.spi.ILoggingEvent; import ch.qos.logback.core.Appender; @@ -18,28 +18,27 @@ import com.beust.jcommander.ParameterException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import jadx.api.IJadxArgs; +import jadx.api.JadxArgs; import jadx.api.JadxDecompiler; import jadx.core.utils.exceptions.JadxException; +import jadx.core.utils.files.FileUtils; -public class JadxCLIArgs implements IJadxArgs { - - protected static final int DEFAULT_THREADS_COUNT = Math.max(1, Runtime.getRuntime().availableProcessors() / 2); +public class JadxCLIArgs { @Parameter(description = " (.dex, .apk, .jar or .class)") - protected List files; + protected List files = new ArrayList<>(1); @Parameter(names = {"-d", "--output-dir"}, description = "output directory") - protected String outDirName; + protected String outDir; @Parameter(names = {"-ds", "--output-dir-src"}, description = "output directory for sources") - protected String outDirNameSrc; + protected String outDirSrc; @Parameter(names = {"-dr", "--output-dir-res"}, description = "output directory for resources") - protected String outDirNameRes; + protected String outDirRes; @Parameter(names = {"-j", "--threads-count"}, description = "processing threads count") - protected int threadsCount = DEFAULT_THREADS_COUNT; + protected int threadsCount = JadxArgs.DEFAULT_THREADS_COUNT; @Parameter(names = {"-r", "--no-res"}, description = "do not decode resources") protected boolean skipResources = false; @@ -94,11 +93,6 @@ public class JadxCLIArgs implements IJadxArgs { @Parameter(names = {"-h", "--help"}, description = "print this help", help = true) protected boolean printHelp = false; - private final List input = new ArrayList<>(1); - private File outputDir; - private File outputDirSrc; - private File outputDirRes; - public boolean processArgs(String[] args) { return parse(args) && process(); } @@ -123,35 +117,6 @@ public class JadxCLIArgs implements IJadxArgs { if (threadsCount <= 0) { throw new JadxException("Threads count must be positive, got: " + threadsCount); } - if (files != null) { - for (String fileName : files) { - File file = new File(fileName); - if (file.exists()) { - input.add(file); - } else { - throw new JadxException("File not found: " + file); - } - } - } - if (input.size() > 1) { - throw new JadxException("Only one input file is supported"); - } - if(outDirNameSrc != null) { - outputDirSrc = new File(outDirNameSrc); - } - if(outDirNameRes != null) { - outputDirRes = new File(outDirNameRes); - } - if (outDirName != null) { - outputDir = new File(outDirName); - if(outputDirSrc == null) { - outputDirSrc = new File(outputDir, "source"); - } - if(outputDirRes == null) { - outputDirRes = new File(outputDir, "res"); - } - } - if (isVerbose()) { ch.qos.logback.classic.Logger rootLogger = (ch.qos.logback.classic.Logger) LoggerFactory.getLogger(Logger.ROOT_LOGGER_NAME); @@ -212,6 +177,31 @@ public class JadxCLIArgs implements IJadxArgs { } } + public JadxArgs toJadxArgs() { + JadxArgs args = new JadxArgs(); + args.setInputFiles(files.stream().map(FileUtils::toFile).collect(Collectors.toList())); + args.setOutDir(FileUtils.toFile(outDir)); + args.setOutDirSrc(FileUtils.toFile(outDirSrc)); + args.setOutDirRes(FileUtils.toFile(outDirRes)); + args.setThreadsCount(threadsCount); + args.setSkipSources(skipSources); + args.setSkipResources(skipResources); + args.setFallbackMode(fallbackMode); + args.setShowInconsistentCode(showInconsistentCode); + args.setCfgOutput(cfgOutput); + args.setRawCFGOutput(rawCfgOutput); + args.setReplaceConsts(replaceConsts); + args.setDeobfuscationOn(deobfuscationOn); + args.setDeobfuscationForceSave(deobfuscationForceSave); + args.setDeobfuscationMinLength(deobfuscationMinLength); + args.setDeobfuscationMaxLength(deobfuscationMaxLength); + args.setUseSourceNameAsClassAlias(deobfuscationUseSourceNameAsAlias); + args.setEscapeUnicode(escapeUnicode); + args.setExportAsGradleProject(exportAsGradleProject); + args.setUseImports(useImports); + return args; + } + public static class InvertedBooleanConverter implements IStringConverter { @Override public Boolean convert(String value) { @@ -219,114 +209,90 @@ public class JadxCLIArgs implements IJadxArgs { } } - public List getInput() { - return input; + public List getFiles() { + return files; } - @Override - public File getOutDir() { - return outputDir; + public String getOutDir() { + return outDir; } - @Override - public File getOutDirSrc() { - return outputDirSrc; + public String getOutDirSrc() { + return outDirSrc; } - @Override - public File getOutDirRes() { - return outputDirRes; - } - - public void setOutputDir(File outputDir) { - this.outputDir = outputDir; + public String getOutDirRes() { + return outDirRes; } public boolean isPrintHelp() { return printHelp; } - @Override public boolean isSkipResources() { return skipResources; } - @Override public boolean isSkipSources() { return skipSources; } - @Override public int getThreadsCount() { return threadsCount; } - @Override public boolean isCFGOutput() { return cfgOutput; } - @Override public boolean isRawCFGOutput() { return rawCfgOutput; } - @Override public boolean isFallbackMode() { return fallbackMode; } - @Override public boolean isShowInconsistentCode() { return showInconsistentCode; } - @Override - public boolean isUsingImports() { + public boolean isUseImports() { return useImports; } - @Override public boolean isVerbose() { return verbose; } - @Override public boolean isDeobfuscationOn() { return deobfuscationOn; } - @Override public int getDeobfuscationMinLength() { return deobfuscationMinLength; } - @Override public int getDeobfuscationMaxLength() { return deobfuscationMaxLength; } - @Override public boolean isDeobfuscationForceSave() { return deobfuscationForceSave; } - @Override - public boolean useSourceNameAsClassAlias() { + public boolean isDeobfuscationUseSourceNameAsAlias() { return deobfuscationUseSourceNameAsAlias; } - @Override public boolean escapeUnicode() { return escapeUnicode; } - @Override public boolean isReplaceConsts() { return replaceConsts; } - @Override public boolean isExportAsGradleProject() { return exportAsGradleProject; } diff --git a/jadx-cli/src/test/java/jadx/cli/JadxCLIArgsTest.java b/jadx-cli/src/test/java/jadx/cli/JadxCLIArgsTest.java index 428efccfa..6704d0598 100644 --- a/jadx-cli/src/test/java/jadx/cli/JadxCLIArgsTest.java +++ b/jadx-cli/src/test/java/jadx/cli/JadxCLIArgsTest.java @@ -8,7 +8,7 @@ import static org.junit.Assert.assertThat; public class JadxCLIArgsTest { @Test - public void testInvertedBooleanOption() throws Exception { + public void testInvertedBooleanOption() { assertThat(parse("--no-replace-consts").isReplaceConsts(), is(false)); assertThat(parse("").isReplaceConsts(), is(true)); } diff --git a/jadx-core/src/main/java/jadx/api/IJadxArgs.java b/jadx-core/src/main/java/jadx/api/IJadxArgs.java deleted file mode 100644 index 8a0d30370..000000000 --- a/jadx-core/src/main/java/jadx/api/IJadxArgs.java +++ /dev/null @@ -1,51 +0,0 @@ -package jadx.api; - -import java.io.File; - -public interface IJadxArgs { - File getOutDir(); - - File getOutDirSrc(); - - File getOutDirRes(); - - int getThreadsCount(); - - boolean isCFGOutput(); - - boolean isRawCFGOutput(); - - boolean isFallbackMode(); - - boolean isShowInconsistentCode(); - - boolean isUsingImports(); - - boolean isVerbose(); - - boolean isSkipResources(); - - boolean isSkipSources(); - - boolean isDeobfuscationOn(); - - int getDeobfuscationMinLength(); - - int getDeobfuscationMaxLength(); - - boolean isDeobfuscationForceSave(); - - boolean useSourceNameAsClassAlias(); - - boolean escapeUnicode(); - - /** - * Replace constant values with static final fields with same value - */ - boolean isReplaceConsts(); - - /** - * Save as gradle project - */ - boolean isExportAsGradleProject(); -} diff --git a/jadx-core/src/main/java/jadx/api/JadxArgs.java b/jadx-core/src/main/java/jadx/api/JadxArgs.java index e3dfa8e9a..cda877809 100644 --- a/jadx-core/src/main/java/jadx/api/JadxArgs.java +++ b/jadx-core/src/main/java/jadx/api/JadxArgs.java @@ -1,21 +1,31 @@ package jadx.api; import java.io.File; +import java.util.ArrayList; +import java.util.List; -public class JadxArgs implements IJadxArgs { +public class JadxArgs { - private File outDir = new File("jadx-output"); - private File outDirSrc = new File(outDir, "source"); - private File outDirRes = new File(outDir, "res"); - private int threadsCount = Math.max(1, Runtime.getRuntime().availableProcessors() - 1); + public static final int DEFAULT_THREADS_COUNT = Math.max(1, Runtime.getRuntime().availableProcessors() / 2); + + public static final String DEFAULT_OUT_DIR = "jadx-output"; + public static final String DEFAULT_SRC_DIR = "sources"; + public static final String DEFAULT_RES_DIR = "resources"; + + private List inputFiles = new ArrayList<>(1); + + private File outDir; + private File outDirSrc; + private File outDirRes; + + private int threadsCount = DEFAULT_THREADS_COUNT; private boolean cfgOutput = false; private boolean rawCFGOutput = false; - private boolean isVerbose = false; private boolean fallbackMode = false; private boolean showInconsistentCode = false; - + private boolean useImports = true; private boolean isSkipResources = false; @@ -32,7 +42,24 @@ public class JadxArgs implements IJadxArgs { private boolean replaceConsts = true; private boolean exportAsGradleProject = false; - @Override + public JadxArgs() { + // use default options + } + + public void setRootDir(File rootDir) { + setOutDir(rootDir); + setOutDirSrc(new File(rootDir, DEFAULT_SRC_DIR)); + setOutDirRes(new File(rootDir, DEFAULT_RES_DIR)); + } + + public List getInputFiles() { + return inputFiles; + } + + public void setInputFiles(List inputFiles) { + this.inputFiles = inputFiles; + } + public File getOutDir() { return outDir; } @@ -41,7 +68,6 @@ public class JadxArgs implements IJadxArgs { this.outDir = outDir; } - @Override public File getOutDirSrc() { return outDirSrc; } @@ -50,7 +76,6 @@ public class JadxArgs implements IJadxArgs { this.outDirSrc = outDirSrc; } - @Override public File getOutDirRes() { return outDirRes; } @@ -59,7 +84,6 @@ public class JadxArgs implements IJadxArgs { this.outDirRes = outDirRes; } - @Override public int getThreadsCount() { return threadsCount; } @@ -68,8 +92,7 @@ public class JadxArgs implements IJadxArgs { this.threadsCount = threadsCount; } - @Override - public boolean isCFGOutput() { + public boolean isCfgOutput() { return cfgOutput; } @@ -77,7 +100,6 @@ public class JadxArgs implements IJadxArgs { this.cfgOutput = cfgOutput; } - @Override public boolean isRawCFGOutput() { return rawCFGOutput; } @@ -86,7 +108,6 @@ public class JadxArgs implements IJadxArgs { this.rawCFGOutput = rawCFGOutput; } - @Override public boolean isFallbackMode() { return fallbackMode; } @@ -95,7 +116,6 @@ public class JadxArgs implements IJadxArgs { this.fallbackMode = fallbackMode; } - @Override public boolean isShowInconsistentCode() { return showInconsistentCode; } @@ -104,8 +124,7 @@ public class JadxArgs implements IJadxArgs { this.showInconsistentCode = showInconsistentCode; } - @Override - public boolean isUsingImports() { + public boolean isUseImports() { return useImports; } @@ -113,16 +132,6 @@ public class JadxArgs implements IJadxArgs { this.useImports = useImports; } - @Override - public boolean isVerbose() { - return isVerbose; - } - - public void setVerbose(boolean verbose) { - isVerbose = verbose; - } - - @Override public boolean isSkipResources() { return isSkipResources; } @@ -131,7 +140,6 @@ public class JadxArgs implements IJadxArgs { isSkipResources = skipResources; } - @Override public boolean isSkipSources() { return isSkipSources; } @@ -140,7 +148,6 @@ public class JadxArgs implements IJadxArgs { isSkipSources = skipSources; } - @Override public boolean isDeobfuscationOn() { return isDeobfuscationOn; } @@ -149,7 +156,6 @@ public class JadxArgs implements IJadxArgs { isDeobfuscationOn = deobfuscationOn; } - @Override public boolean isDeobfuscationForceSave() { return isDeobfuscationForceSave; } @@ -158,8 +164,7 @@ public class JadxArgs implements IJadxArgs { isDeobfuscationForceSave = deobfuscationForceSave; } - @Override - public boolean useSourceNameAsClassAlias() { + public boolean isUseSourceNameAsClassAlias() { return useSourceNameAsClassAlias; } @@ -167,7 +172,6 @@ public class JadxArgs implements IJadxArgs { this.useSourceNameAsClassAlias = useSourceNameAsClassAlias; } - @Override public int getDeobfuscationMinLength() { return deobfuscationMinLength; } @@ -176,7 +180,6 @@ public class JadxArgs implements IJadxArgs { this.deobfuscationMinLength = deobfuscationMinLength; } - @Override public int getDeobfuscationMaxLength() { return deobfuscationMaxLength; } @@ -185,8 +188,7 @@ public class JadxArgs implements IJadxArgs { this.deobfuscationMaxLength = deobfuscationMaxLength; } - @Override - public boolean escapeUnicode() { + public boolean isEscapeUnicode() { return escapeUnicode; } @@ -194,7 +196,6 @@ public class JadxArgs implements IJadxArgs { this.escapeUnicode = escapeUnicode; } - @Override public boolean isReplaceConsts() { return replaceConsts; } @@ -203,7 +204,6 @@ public class JadxArgs implements IJadxArgs { this.replaceConsts = replaceConsts; } - @Override public boolean isExportAsGradleProject() { return exportAsGradleProject; } diff --git a/jadx-core/src/main/java/jadx/api/JadxArgsValidator.java b/jadx-core/src/main/java/jadx/api/JadxArgsValidator.java new file mode 100644 index 000000000..761aa09d1 --- /dev/null +++ b/jadx-core/src/main/java/jadx/api/JadxArgsValidator.java @@ -0,0 +1,89 @@ +package jadx.api; + +import java.io.File; + +import org.jetbrains.annotations.NotNull; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import jadx.core.utils.exceptions.JadxRuntimeException; + +public class JadxArgsValidator { + + private static final Logger LOG = LoggerFactory.getLogger(JadxArgsValidator.class); + + public static void validate(JadxArgs args) { + if (args.getInputFiles().isEmpty()) { + throw new JadxRuntimeException("Please specify input file"); + } + for (File file : args.getInputFiles()) { + checkFile(file); + } + validateOutDirs(args); + } + + private static void validateOutDirs(JadxArgs args) { + File outDir = args.getOutDir(); + File srcDir = args.getOutDirSrc(); + File resDir = args.getOutDirRes(); + if (outDir == null) { + if (srcDir != null) { + outDir = srcDir; + } else if (resDir != null) { + outDir = resDir; + } else { + outDir = makeDirFromInput(args); + } + } + args.setOutDir(outDir); + setFromOut(args); + + checkDir(args.getOutDir()); + checkDir(args.getOutDirSrc()); + checkDir(args.getOutDirRes()); + } + + @NotNull + private static File makeDirFromInput(JadxArgs args) { + File outDir; + String outDirName; + File file = args.getInputFiles().get(0); + String name = file.getName(); + int pos = name.lastIndexOf('.'); + if (pos != -1) { + outDirName = name.substring(0, pos); + } else { + outDirName = name + "-" + JadxArgs.DEFAULT_OUT_DIR; + } + LOG.info("output directory: {}", outDirName); + outDir = new File(outDirName); + return outDir; + } + + private static void setFromOut(JadxArgs args) { + if (args.getOutDirSrc() == null) { + args.setOutDirSrc(new File(args.getOutDir(), JadxArgs.DEFAULT_SRC_DIR)); + } + if (args.getOutDirRes() == null) { + args.setOutDirRes(new File(args.getOutDir(), JadxArgs.DEFAULT_RES_DIR)); + } + } + + private static void checkFile(File file) { + if (!file.exists()) { + throw new JadxRuntimeException("File not found " + file.getAbsolutePath()); + } + if (file.isDirectory()) { + throw new JadxRuntimeException("Expected file but found directory instead: " + file.getAbsolutePath()); + } + } + + private static void checkDir(File dir) { + if (dir != null && dir.exists() && !dir.isDirectory()) { + throw new JadxRuntimeException("Output directory exists as file " + dir); + } + } + + private JadxArgsValidator() { + } +} diff --git a/jadx-core/src/main/java/jadx/api/JadxDecompiler.java b/jadx-core/src/main/java/jadx/api/JadxDecompiler.java index 4c498956e..315a2afbd 100644 --- a/jadx-core/src/main/java/jadx/api/JadxDecompiler.java +++ b/jadx-core/src/main/java/jadx/api/JadxDecompiler.java @@ -1,24 +1,6 @@ package jadx.api; -import jadx.core.Jadx; -import jadx.core.ProcessClass; -import jadx.core.codegen.CodeGen; -import jadx.core.dex.attributes.AFlag; -import jadx.core.dex.nodes.ClassNode; -import jadx.core.dex.nodes.FieldNode; -import jadx.core.dex.nodes.MethodNode; -import jadx.core.dex.nodes.RootNode; -import jadx.core.dex.visitors.IDexTreeVisitor; -import jadx.core.dex.visitors.SaveCode; -import jadx.core.export.ExportGradleProject; -import jadx.core.utils.exceptions.JadxException; -import jadx.core.utils.exceptions.JadxRuntimeException; -import jadx.core.utils.files.InputFile; -import jadx.core.xmlgen.BinaryXMLParser; -import jadx.core.xmlgen.ResourcesSaver; - import java.io.File; -import java.io.IOException; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; @@ -33,16 +15,35 @@ import java.util.concurrent.TimeUnit; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import jadx.core.Jadx; +import jadx.core.ProcessClass; +import jadx.core.codegen.CodeGen; +import jadx.core.dex.attributes.AFlag; +import jadx.core.dex.nodes.ClassNode; +import jadx.core.dex.nodes.FieldNode; +import jadx.core.dex.nodes.MethodNode; +import jadx.core.dex.nodes.RootNode; +import jadx.core.dex.visitors.IDexTreeVisitor; +import jadx.core.dex.visitors.SaveCode; +import jadx.core.export.ExportGradleProject; +import jadx.core.utils.exceptions.JadxRuntimeException; +import jadx.core.utils.files.InputFile; +import jadx.core.xmlgen.BinaryXMLParser; +import jadx.core.xmlgen.ResourcesSaver; + /** * Jadx API usage example: *

- *  JadxDecompiler jadx = new JadxDecompiler();
- *  jadx.loadFile(new File("classes.dex"));
- *  jadx.setOutputDir(new File("out"));
- *  jadx.save();
+ * JadxArgs args = new JadxArgs();
+ * args.getInputFiles().add(new File("test.apk"));
+ * args.setOutDir(new File("jadx-test-output"));
+ *
+ * JadxDecompiler jadx = new JadxDecompiler(args);
+ * jadx.load();
+ * jadx.save();
  * 
*

- * Instead of 'save()' you can get list of decompiled classes: + * Instead of 'save()' you can iterate over decompiled classes: *


  *  for(JavaClass cls : jadx.getClasses()) {
  *      System.out.println(cls.getCode());
@@ -52,12 +53,9 @@ import org.slf4j.LoggerFactory;
 public final class JadxDecompiler {
 	private static final Logger LOG = LoggerFactory.getLogger(JadxDecompiler.class);
 
-	private final IJadxArgs args;
-	private final List inputFiles = new ArrayList<>();
+	private JadxArgs args;
 
-	private File outDir;
-	private File outDirRes;
-	private File outDirSrc;
+	private final List inputFiles = new ArrayList<>();
 
 	private RootNode root;
 	private List passes;
@@ -72,54 +70,35 @@ public final class JadxDecompiler {
 	private Map methodsMap = new ConcurrentHashMap<>();
 	private Map fieldsMap = new ConcurrentHashMap<>();
 
-	public JadxDecompiler() throws JadxException {
+	public JadxDecompiler() {
 		this(new JadxArgs());
 	}
 
-	public JadxDecompiler(IJadxArgs jadxArgs) throws JadxException {
-		this.args = jadxArgs;
-		this.outDir = jadxArgs.getOutDir();
-		this.outDirSrc = jadxArgs.getOutDirSrc();
-		this.outDirRes = jadxArgs.getOutDirRes();
+	public JadxDecompiler(JadxArgs args) {
+		this.args = args;
+	}
+
+	public void load() {
 		reset();
+		JadxArgsValidator.validate(args);
 		init();
+		LOG.info("loading ...");
+
+		loadFiles(args.getInputFiles());
+
+		root = new RootNode(args);
+		root.load(inputFiles);
+
+		root.initClassPath();
+		root.loadResources(getResources());
+		root.initAppResClass();
+
+		initVisitors();
 	}
 
-	public void setOutputDir(File outDir) throws JadxException {
-		this.outDir = outDir;
-		init();
-	}
-
-	public void setOutputDirSrc(File outDirSrc) throws JadxException {
-		this.outDirSrc = outDirSrc;
-		init();
-	}
-
-	public void setOutputDirRes(File outDirRes) throws JadxException {
-		this.outDirRes = outDirRes;
-		init();
-	}
-
-	void init() throws JadxException {
-		if(outDir == null && outDirSrc == null) {
-			outDirSrc = new JadxArgs().getOutDirSrc();
-		}
-		if(outDir == null && outDirRes == null) {
-			outDirRes = new JadxArgs().getOutDirRes();
-		}
-		if (outDir == null) {
-			outDir = new JadxArgs().getOutDir();
-		}
-		else {
-			if(outDirSrc == null && outDirRes != null && !args.isSkipSources()) {
-				throw new JadxException("--output-dir-src must be specified");
-			}
-			if(outDirSrc != null && outDirRes == null && !args.isSkipResources()) {
-				throw new JadxException("--output-dir-res must be specified");
-			}
-		}
-		this.passes = Jadx.getPassesList(args, outDir);
-		this.codeGen = new CodeGen(args);
+	void init() {
+		this.passes = Jadx.getPassesList(args);
+		this.codeGen = new CodeGen();
 	}
 
 	void reset() {
@@ -135,23 +114,18 @@ public final class JadxDecompiler {
 		return Jadx.getVersion();
 	}
 
-	public void loadFile(File file) throws JadxException {
-		loadFiles(Collections.singletonList(file));
-	}
-
-	public void loadFiles(List files) throws JadxException {
+	private void loadFiles(List files) {
 		if (files.isEmpty()) {
-			throw new JadxException("Empty file list");
+			throw new JadxRuntimeException("Empty file list");
 		}
 		inputFiles.clear();
 		for (File file : files) {
 			try {
 				InputFile.addFilesFrom(file, inputFiles, args.isSkipSources());
-			} catch (IOException e) {
-				throw new JadxException("Error load file: " + file, e);
+			} catch (Exception e) {
+				throw new JadxRuntimeException("Error load file: " + file, e);
 			}
 		}
-		parse();
 	}
 
 	public void save() {
@@ -194,13 +168,13 @@ public final class JadxDecompiler {
 		File sourcesOutDir;
 		File resOutDir;
 		if (args.isExportAsGradleProject()) {
-			ExportGradleProject export = new ExportGradleProject(root, outDir);
+			ExportGradleProject export = new ExportGradleProject(root, args.getOutDir());
 			export.init();
 			sourcesOutDir = export.getSrcOutDir();
 			resOutDir = export.getResOutDir();
 		} else {
-			sourcesOutDir = outDirSrc;
-			resOutDir = outDirRes;
+			sourcesOutDir = args.getOutDirSrc();
+			resOutDir = args.getOutDirRes();
 		}
 		if (saveSources) {
 			appendSourcesSave(executor, sourcesOutDir);
@@ -294,21 +268,6 @@ public final class JadxDecompiler {
 		root.getErrorsCounter().printReport();
 	}
 
-	void parse() throws JadxException {
-		reset();
-		init();
-
-		root = new RootNode(args);
-		LOG.info("loading ...");
-		root.load(inputFiles);
-
-		root.initClassPath();
-		root.loadResources(getResources());
-		root.initAppResClass();
-
-		initVisitors();
-	}
-
 	private void initVisitors() {
 		for (IDexTreeVisitor pass : passes) {
 			try {
@@ -346,7 +305,7 @@ public final class JadxDecompiler {
 		return fieldsMap;
 	}
 
-	public IJadxArgs getArgs() {
+	public JadxArgs getArgs() {
 		return args;
 	}
 
@@ -354,5 +313,4 @@ public final class JadxDecompiler {
 	public String toString() {
 		return "jadx decompiler " + getVersion();
 	}
-
 }
diff --git a/jadx-core/src/main/java/jadx/core/Jadx.java b/jadx-core/src/main/java/jadx/core/Jadx.java
index aba779605..eb0ffea86 100644
--- a/jadx-core/src/main/java/jadx/core/Jadx.java
+++ b/jadx-core/src/main/java/jadx/core/Jadx.java
@@ -1,6 +1,15 @@
 package jadx.core;
 
-import jadx.api.IJadxArgs;
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.Enumeration;
+import java.util.List;
+import java.util.jar.Manifest;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import jadx.api.JadxArgs;
 import jadx.core.dex.visitors.ClassModifier;
 import jadx.core.dex.visitors.CodeShrinker;
 import jadx.core.dex.visitors.ConstInlineVisitor;
@@ -33,16 +42,6 @@ import jadx.core.dex.visitors.ssa.SSATransform;
 import jadx.core.dex.visitors.typeinference.FinishTypeInference;
 import jadx.core.dex.visitors.typeinference.TypeInference;
 
-import java.io.File;
-import java.net.URL;
-import java.util.ArrayList;
-import java.util.Enumeration;
-import java.util.List;
-import java.util.jar.Manifest;
-
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
 public class Jadx {
 	private static final Logger LOG = LoggerFactory.getLogger(Jadx.class);
 
@@ -55,7 +54,7 @@ public class Jadx {
 		}
 	}
 
-	public static List getPassesList(IJadxArgs args, File outDir) {
+	public static List getPassesList(JadxArgs args) {
 		List passes = new ArrayList<>();
 		if (args.isFallbackMode()) {
 			passes.add(new FallbackModeVisitor());
@@ -71,7 +70,7 @@ public class Jadx {
 			passes.add(new TypeInference());
 
 			if (args.isRawCFGOutput()) {
-				passes.add(DotGraphVisitor.dumpRaw(outDir));
+				passes.add(DotGraphVisitor.dumpRaw());
 			}
 
 			passes.add(new ConstInlineVisitor());
@@ -83,8 +82,8 @@ public class Jadx {
 			passes.add(new CodeShrinker());
 			passes.add(new ReSugarCode());
 
-			if (args.isCFGOutput()) {
-				passes.add(DotGraphVisitor.dump(outDir));
+			if (args.isCfgOutput()) {
+				passes.add(DotGraphVisitor.dump());
 			}
 
 			passes.add(new RegionMakerVisitor());
@@ -95,8 +94,8 @@ public class Jadx {
 			passes.add(new SimplifyVisitor());
 			passes.add(new CheckRegions());
 
-			if (args.isCFGOutput()) {
-				passes.add(DotGraphVisitor.dumpRegions(outDir));
+			if (args.isCfgOutput()) {
+				passes.add(DotGraphVisitor.dumpRegions());
 			}
 
 			passes.add(new MethodInlineVisitor());
diff --git a/jadx-core/src/main/java/jadx/core/codegen/ClassGen.java b/jadx-core/src/main/java/jadx/core/codegen/ClassGen.java
index 1b4a89bd6..c0a6b92e6 100644
--- a/jadx-core/src/main/java/jadx/core/codegen/ClassGen.java
+++ b/jadx-core/src/main/java/jadx/core/codegen/ClassGen.java
@@ -12,7 +12,7 @@ import java.util.Set;
 
 import com.android.dx.rop.code.AccessFlags;
 
-import jadx.api.IJadxArgs;
+import jadx.api.JadxArgs;
 import jadx.core.dex.attributes.AFlag;
 import jadx.core.dex.attributes.AType;
 import jadx.core.dex.attributes.AttrNode;
@@ -48,8 +48,8 @@ public class ClassGen {
 	private final Set imports = new HashSet<>();
 	private int clsDeclLine;
 
-	public ClassGen(ClassNode cls, IJadxArgs jadxArgs) {
-		this(cls, null, jadxArgs.isUsingImports(), jadxArgs.isFallbackMode(), jadxArgs.isShowInconsistentCode());
+	public ClassGen(ClassNode cls, JadxArgs jadxArgs) {
+		this(cls, null, jadxArgs.isUseImports(), jadxArgs.isFallbackMode(), jadxArgs.isShowInconsistentCode());
 	}
 
 	public ClassGen(ClassNode cls, ClassGen parentClsGen) {
diff --git a/jadx-core/src/main/java/jadx/core/codegen/CodeGen.java b/jadx-core/src/main/java/jadx/core/codegen/CodeGen.java
index 90c89e1cd..8c33d8862 100644
--- a/jadx-core/src/main/java/jadx/core/codegen/CodeGen.java
+++ b/jadx-core/src/main/java/jadx/core/codegen/CodeGen.java
@@ -1,25 +1,17 @@
 package jadx.core.codegen;
 
-import jadx.api.IJadxArgs;
 import jadx.core.dex.nodes.ClassNode;
 import jadx.core.dex.visitors.AbstractVisitor;
 import jadx.core.utils.exceptions.CodegenException;
 
 public class CodeGen extends AbstractVisitor {
 
-	private final IJadxArgs args;
-
-	public CodeGen(IJadxArgs args) {
-		this.args = args;
-	}
-
 	@Override
 	public boolean visit(ClassNode cls) throws CodegenException {
-		ClassGen clsGen = new ClassGen(cls, args);
+		ClassGen clsGen = new ClassGen(cls, cls.root().getArgs());
 		CodeWriter clsCode = clsGen.makeClass();
 		clsCode.finish();
 		cls.setCode(clsCode);
 		return false;
 	}
-
 }
diff --git a/jadx-core/src/main/java/jadx/core/deobf/Deobfuscator.java b/jadx-core/src/main/java/jadx/core/deobf/Deobfuscator.java
index a01f7be4f..f7764f655 100644
--- a/jadx-core/src/main/java/jadx/core/deobf/Deobfuscator.java
+++ b/jadx-core/src/main/java/jadx/core/deobf/Deobfuscator.java
@@ -1,6 +1,6 @@
 package jadx.core.deobf;
 
-import jadx.api.IJadxArgs;
+import jadx.api.JadxArgs;
 import jadx.core.dex.attributes.AType;
 import jadx.core.dex.attributes.nodes.SourceFileAttr;
 import jadx.core.dex.info.ClassInfo;
@@ -34,7 +34,7 @@ public class Deobfuscator {
 	public static final String CLASS_NAME_SEPARATOR = ".";
 	public static final String INNER_CLASS_SEPARATOR = "$";
 
-	private final IJadxArgs args;
+	private final JadxArgs args;
 	@NotNull
 	private final List dexNodes;
 	private final DeobfPresets deobfPresets;
@@ -58,13 +58,13 @@ public class Deobfuscator {
 	private int fldIndex = 0;
 	private int mthIndex = 0;
 
-	public Deobfuscator(IJadxArgs args, @NotNull List dexNodes, File deobfMapFile) {
+	public Deobfuscator(JadxArgs args, @NotNull List dexNodes, File deobfMapFile) {
 		this.args = args;
 		this.dexNodes = dexNodes;
 
 		this.minLength = args.getDeobfuscationMinLength();
 		this.maxLength = args.getDeobfuscationMaxLength();
-		this.useSourceNameAsAlias = args.useSourceNameAsClassAlias();
+		this.useSourceNameAsAlias = args.isUseSourceNameAsClassAlias();
 
 		this.deobfPresets = new DeobfPresets(this, deobfMapFile);
 	}
diff --git a/jadx-core/src/main/java/jadx/core/dex/info/ConstStorage.java b/jadx-core/src/main/java/jadx/core/dex/info/ConstStorage.java
index 5468be17f..7be69192f 100644
--- a/jadx-core/src/main/java/jadx/core/dex/info/ConstStorage.java
+++ b/jadx-core/src/main/java/jadx/core/dex/info/ConstStorage.java
@@ -8,7 +8,7 @@ import java.util.Set;
 
 import org.jetbrains.annotations.Nullable;
 
-import jadx.api.IJadxArgs;
+import jadx.api.JadxArgs;
 import jadx.core.dex.attributes.AType;
 import jadx.core.dex.instructions.args.LiteralArg;
 import jadx.core.dex.instructions.args.PrimitiveType;
@@ -60,7 +60,7 @@ public class ConstStorage {
 
 	private Map resourcesNames = new HashMap<>();
 
-	public ConstStorage(IJadxArgs args) {
+	public ConstStorage(JadxArgs args) {
 		this.replaceEnabled = args.isReplaceConsts();
 	}
 
diff --git a/jadx-core/src/main/java/jadx/core/dex/nodes/ClassNode.java b/jadx-core/src/main/java/jadx/core/dex/nodes/ClassNode.java
index d246011b3..21c18cd29 100644
--- a/jadx-core/src/main/java/jadx/core/dex/nodes/ClassNode.java
+++ b/jadx-core/src/main/java/jadx/core/dex/nodes/ClassNode.java
@@ -65,7 +65,7 @@ public class ClassNode extends LineAttrNode implements ILoadable, IDexNode {
 	// cache maps
 	private Map mthInfoMap = Collections.emptyMap();
 
-	public ClassNode(DexNode dex, ClassDef cls) throws DecodeException {
+	public ClassNode(DexNode dex, ClassDef cls) {
 		this.dex = dex;
 		this.clsInfo = ClassInfo.fromDex(dex, cls.getTypeIndex());
 		try {
@@ -128,7 +128,7 @@ public class ClassNode extends LineAttrNode implements ILoadable, IDexNode {
 
 			buildCache();
 		} catch (Exception e) {
-			throw new DecodeException("Error decode class: " + clsInfo, e);
+			throw new JadxRuntimeException("Error decode class: " + clsInfo, e);
 		}
 	}
 
diff --git a/jadx-core/src/main/java/jadx/core/dex/nodes/DexNode.java b/jadx-core/src/main/java/jadx/core/dex/nodes/DexNode.java
index b58d80281..a63434498 100644
--- a/jadx-core/src/main/java/jadx/core/dex/nodes/DexNode.java
+++ b/jadx-core/src/main/java/jadx/core/dex/nodes/DexNode.java
@@ -46,7 +46,7 @@ public class DexNode implements IDexNode {
 		this.dexId = dexId;
 	}
 
-	public void loadClasses() throws DecodeException {
+	public void loadClasses() {
 		for (ClassDef cls : dexBuf.classDefs()) {
 			ClassNode clsNode = new ClassNode(this, cls);
 			classes.add(clsNode);
diff --git a/jadx-core/src/main/java/jadx/core/dex/nodes/RootNode.java b/jadx-core/src/main/java/jadx/core/dex/nodes/RootNode.java
index 4c5ff3640..bea8bd066 100644
--- a/jadx-core/src/main/java/jadx/core/dex/nodes/RootNode.java
+++ b/jadx-core/src/main/java/jadx/core/dex/nodes/RootNode.java
@@ -1,24 +1,5 @@
 package jadx.core.dex.nodes;
 
-import jadx.api.IJadxArgs;
-import jadx.api.ResourceFile;
-import jadx.api.ResourceType;
-import jadx.api.ResourcesLoader;
-import jadx.core.clsp.ClspGraph;
-import jadx.core.dex.info.ClassInfo;
-import jadx.core.dex.info.ConstStorage;
-import jadx.core.dex.info.InfoStorage;
-import jadx.core.utils.ErrorsCounter;
-import jadx.core.utils.StringUtils;
-import jadx.core.utils.android.AndroidResourcesUtils;
-import jadx.core.utils.exceptions.DecodeException;
-import jadx.core.utils.exceptions.JadxException;
-import jadx.core.utils.files.DexFile;
-import jadx.core.utils.files.InputFile;
-import jadx.core.xmlgen.ResContainer;
-import jadx.core.xmlgen.ResTableParser;
-import jadx.core.xmlgen.ResourceStorage;
-
 import java.io.IOException;
 import java.io.InputStream;
 import java.util.ArrayList;
@@ -28,11 +9,30 @@ import org.jetbrains.annotations.Nullable;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import jadx.api.JadxArgs;
+import jadx.api.ResourceFile;
+import jadx.api.ResourceType;
+import jadx.api.ResourcesLoader;
+import jadx.core.clsp.ClspGraph;
+import jadx.core.dex.info.ClassInfo;
+import jadx.core.dex.info.ConstStorage;
+import jadx.core.dex.info.InfoStorage;
+import jadx.core.utils.ErrorsCounter;
+import jadx.core.utils.StringUtils;
+import jadx.core.utils.android.AndroidResourcesUtils;
+import jadx.core.utils.exceptions.JadxException;
+import jadx.core.utils.exceptions.JadxRuntimeException;
+import jadx.core.utils.files.DexFile;
+import jadx.core.utils.files.InputFile;
+import jadx.core.xmlgen.ResContainer;
+import jadx.core.xmlgen.ResTableParser;
+import jadx.core.xmlgen.ResourceStorage;
+
 public class RootNode {
 	private static final Logger LOG = LoggerFactory.getLogger(RootNode.class);
 
 	private final ErrorsCounter errorsCounter = new ErrorsCounter();
-	private final IJadxArgs args;
+	private final JadxArgs args;
 	private final StringUtils stringUtils;
 	private final ConstStorage constValues;
 	private final InfoStorage infoStorage = new InfoStorage();
@@ -43,13 +43,13 @@ public class RootNode {
 	private ClassNode appResClass;
 	private ClspGraph clsp;
 
-	public RootNode(IJadxArgs args) {
+	public RootNode(JadxArgs args) {
 		this.args = args;
 		this.stringUtils = new StringUtils(args);
 		this.constValues = new ConstStorage(args);
 	}
 
-	public void load(List inputFiles) throws DecodeException {
+	public void load(List inputFiles) {
 		dexNodes = new ArrayList<>();
 		for (InputFile input : inputFiles) {
 			for (DexFile dexFile : input.getDexFiles()) {
@@ -58,7 +58,7 @@ public class RootNode {
 					DexNode dexNode = new DexNode(this, dexFile, dexNodes.size());
 					dexNodes.add(dexNode);
 				} catch (Exception e) {
-					throw new DecodeException("Error decode file: " + dexFile, e);
+					throw new JadxRuntimeException("Error decode file: " + dexFile, e);
 				}
 			}
 		}
@@ -103,22 +103,22 @@ public class RootNode {
 		appResClass = AndroidResourcesUtils.searchAppResClass(this);
 	}
 
-	public void initClassPath() throws DecodeException {
+	public void initClassPath() {
 		try {
 			if (this.clsp == null) {
-				ClspGraph clsp = new ClspGraph();
-				clsp.load();
+				ClspGraph newClsp = new ClspGraph();
+				newClsp.load();
 
 				List classes = new ArrayList<>();
 				for (DexNode dexNode : dexNodes) {
 					classes.addAll(dexNode.getClasses());
 				}
-				clsp.addApp(classes);
+				newClsp.addApp(classes);
 
-				this.clsp = clsp;
+				this.clsp = newClsp;
 			}
-		} catch (IOException e) {
-			throw new DecodeException("Error loading classpath", e);
+		} catch (Exception e) {
+			throw new JadxRuntimeException("Error loading classpath", e);
 		}
 	}
 
@@ -188,10 +188,6 @@ public class RootNode {
 		return appResClass;
 	}
 
-	public IJadxArgs getArgs() {
-		return args;
-	}
-
 	public StringUtils getStringUtils() {
 		return stringUtils;
 	}
@@ -204,4 +200,7 @@ public class RootNode {
 		return infoStorage;
 	}
 
+	public JadxArgs getArgs() {
+		return args;
+	}
 }
diff --git a/jadx-core/src/main/java/jadx/core/dex/visitors/DotGraphVisitor.java b/jadx-core/src/main/java/jadx/core/dex/visitors/DotGraphVisitor.java
index 54eaa4823..b78ac7c93 100644
--- a/jadx-core/src/main/java/jadx/core/dex/visitors/DotGraphVisitor.java
+++ b/jadx-core/src/main/java/jadx/core/dex/visitors/DotGraphVisitor.java
@@ -23,42 +23,33 @@ import java.util.HashSet;
 import java.util.List;
 import java.util.Set;
 
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
 public class DotGraphVisitor extends AbstractVisitor {
 
-	private static final Logger LOG = LoggerFactory.getLogger(DotGraphVisitor.class);
-
 	private static final String NL = "\\l";
 	private static final boolean PRINT_DOMINATORS = false;
 
-	private final File dir;
 	private final boolean useRegions;
 	private final boolean rawInsn;
 
-	public static DotGraphVisitor dump(File outDir) {
-		return new DotGraphVisitor(outDir, false, false);
+	public static DotGraphVisitor dump() {
+		return new DotGraphVisitor(false, false);
 	}
 
-	public static DotGraphVisitor dumpRaw(File outDir) {
-		return new DotGraphVisitor(outDir, false, true);
+	public static DotGraphVisitor dumpRaw() {
+		return new DotGraphVisitor(false, true);
 	}
 
-	public static DotGraphVisitor dumpRegions(File outDir) {
-		return new DotGraphVisitor(outDir, true, false);
+	public static DotGraphVisitor dumpRegions() {
+		return new DotGraphVisitor(true, false);
 	}
 
-	public static DotGraphVisitor dumpRawRegions(File outDir) {
-		return new DotGraphVisitor(outDir, true, true);
+	public static DotGraphVisitor dumpRawRegions() {
+		return new DotGraphVisitor(true, true);
 	}
 
-	private DotGraphVisitor(File outDir, boolean useRegions, boolean rawInsn) {
-		this.dir = outDir;
+	private DotGraphVisitor(boolean useRegions, boolean rawInsn) {
 		this.useRegions = useRegions;
 		this.rawInsn = rawInsn;
-		LOG.debug("DOT {}{}graph dump dir: {}",
-				useRegions ? "regions " : "", rawInsn ? "raw " : "", outDir.getAbsolutePath());
 	}
 
 	@Override
@@ -66,12 +57,25 @@ public class DotGraphVisitor extends AbstractVisitor {
 		if (mth.isNoCode()) {
 			return;
 		}
-		new DumpDotGraph().process(mth);
+		File outRootDir = mth.root().getArgs().getOutDir();
+		new DumpDotGraph(outRootDir).process(mth);
+	}
+
+	public void save(File dir, MethodNode mth) {
+		if (mth.isNoCode()) {
+			return;
+		}
+		new DumpDotGraph(dir).process(mth);
 	}
 
 	private class DumpDotGraph {
 		private final CodeWriter dot = new CodeWriter();
 		private final CodeWriter conn = new CodeWriter();
+		private final File dir;
+
+		public DumpDotGraph(File dir) {
+			this.dir = dir;
+		}
 
 		public void process(MethodNode mth) {
 			dot.startLine("digraph \"CFG for");
diff --git a/jadx-core/src/main/java/jadx/core/dex/visitors/RenameVisitor.java b/jadx-core/src/main/java/jadx/core/dex/visitors/RenameVisitor.java
index df06281f8..3853aed7f 100644
--- a/jadx-core/src/main/java/jadx/core/dex/visitors/RenameVisitor.java
+++ b/jadx-core/src/main/java/jadx/core/dex/visitors/RenameVisitor.java
@@ -7,7 +7,7 @@ import java.util.Set;
 
 import org.apache.commons.io.FilenameUtils;
 
-import jadx.api.IJadxArgs;
+import jadx.api.JadxArgs;
 import jadx.core.Consts;
 import jadx.core.codegen.TypeGen;
 import jadx.core.deobf.Deobfuscator;
@@ -31,8 +31,6 @@ public class RenameVisitor extends AbstractVisitor {
 
 	@Override
 	public void init(RootNode root) {
-		IJadxArgs args = root.getArgs();
-
 		List dexNodes = root.getDexNodes();
 		if (dexNodes.isEmpty()) {
 			return;
@@ -43,6 +41,7 @@ public class RenameVisitor extends AbstractVisitor {
 		String inputName = FilenameUtils.getBaseName(firstInputFileName);
 
 		File deobfMapFile = new File(inputPath, inputName + ".jobf");
+		JadxArgs args = root.getArgs();
 		deobfuscator = new Deobfuscator(args, dexNodes, deobfMapFile);
 		boolean deobfuscationOn = args.isDeobfuscationOn();
 		if (deobfuscationOn) {
diff --git a/jadx-core/src/main/java/jadx/core/dex/visitors/SaveCode.java b/jadx-core/src/main/java/jadx/core/dex/visitors/SaveCode.java
index 9c2a08907..abbab0d15 100644
--- a/jadx-core/src/main/java/jadx/core/dex/visitors/SaveCode.java
+++ b/jadx-core/src/main/java/jadx/core/dex/visitors/SaveCode.java
@@ -2,7 +2,7 @@ package jadx.core.dex.visitors;
 
 import java.io.File;
 
-import jadx.api.IJadxArgs;
+import jadx.api.JadxArgs;
 import jadx.core.codegen.CodeWriter;
 import jadx.core.dex.nodes.ClassNode;
 
@@ -10,7 +10,7 @@ public class SaveCode {
 
 	private SaveCode() {}
 
-	public static void save(File dir, IJadxArgs args, ClassNode cls) {
+	public static void save(File dir, JadxArgs args, ClassNode cls) {
 		CodeWriter clsCode = cls.getCode();
 		String fileName = cls.getClassInfo().getFullPath() + ".java";
 		if (args.isFallbackMode()) {
diff --git a/jadx-core/src/main/java/jadx/core/utils/DebugUtils.java b/jadx-core/src/main/java/jadx/core/utils/DebugUtils.java
index 4f537b004..819957053 100644
--- a/jadx-core/src/main/java/jadx/core/utils/DebugUtils.java
+++ b/jadx-core/src/main/java/jadx/core/utils/DebugUtils.java
@@ -44,9 +44,9 @@ public class DebugUtils {
 
 	public static void dump(MethodNode mth, String desc) {
 		File out = new File("test-graph" + desc + "-tmp");
-		DotGraphVisitor.dump(out).visit(mth);
-		DotGraphVisitor.dumpRaw(out).visit(mth);
-		DotGraphVisitor.dumpRegions(out).visit(mth);
+		DotGraphVisitor.dump().save(out, mth);
+		DotGraphVisitor.dumpRaw().save(out, mth);
+		DotGraphVisitor.dumpRegions().save(out, mth);
 	}
 
 	public static void printRegionsWithBlock(MethodNode mth, BlockNode block) {
diff --git a/jadx-core/src/main/java/jadx/core/utils/StringUtils.java b/jadx-core/src/main/java/jadx/core/utils/StringUtils.java
index acdd5d0a3..186b8b23a 100644
--- a/jadx-core/src/main/java/jadx/core/utils/StringUtils.java
+++ b/jadx-core/src/main/java/jadx/core/utils/StringUtils.java
@@ -1,13 +1,13 @@
 package jadx.core.utils;
 
-import jadx.api.IJadxArgs;
+import jadx.api.JadxArgs;
 
 public class StringUtils {
 
 	private final boolean escapeUnicode;
 
-	public StringUtils(IJadxArgs args) {
-		this.escapeUnicode = args.escapeUnicode();
+	public StringUtils(JadxArgs args) {
+		this.escapeUnicode = args.isEscapeUnicode();
 	}
 
 	public String unescapeString(String str) {
diff --git a/jadx-core/src/main/java/jadx/core/utils/files/FileUtils.java b/jadx-core/src/main/java/jadx/core/utils/files/FileUtils.java
index 360554a3d..91633239d 100644
--- a/jadx-core/src/main/java/jadx/core/utils/files/FileUtils.java
+++ b/jadx-core/src/main/java/jadx/core/utils/files/FileUtils.java
@@ -214,4 +214,12 @@ public class FileUtils {
 		}
 		return IOCase.SYSTEM.isCaseSensitive();
 	}
+
+	public static File toFile(String path) {
+		if (path == null) {
+			return null;
+		}
+		return new File(path);
+	}
+
 }
diff --git a/jadx-core/src/test/groovy/jadx/tests/TestAPI.groovy b/jadx-core/src/test/groovy/jadx/tests/TestAPI.groovy
deleted file mode 100644
index 8e4208355..000000000
--- a/jadx-core/src/test/groovy/jadx/tests/TestAPI.groovy
+++ /dev/null
@@ -1,68 +0,0 @@
-package jadx.tests
-
-import jadx.api.IJadxArgs
-import jadx.api.JadxDecompiler
-import jadx.core.utils.exceptions.JadxException
-import jadx.core.utils.exceptions.JadxRuntimeException
-import spock.lang.Specification
-
-class TestAPI extends Specification {
-
-    def "no loaded files"() {
-        setup:
-        def d = new JadxDecompiler()
-        when:
-        def classes = d.getClasses()
-        def packages = d.getPackages()
-        then:
-        notThrown(NullPointerException)
-        classes?.isEmpty()
-        packages?.isEmpty()
-    }
-
-    def "save with no loaded files"() {
-        when:
-        new JadxDecompiler().save()
-        then:
-        def e = thrown(JadxRuntimeException)
-        e.message == "No loaded files"
-    }
-
-    def "load empty files list"() {
-        when:
-        new JadxDecompiler().loadFiles(Collections.emptyList())
-        then:
-        def e = thrown(JadxException)
-        e.message == "Empty file list"
-    }
-
-    def "load null"() {
-        when:
-        new JadxDecompiler().loadFile(null)
-        then:
-        thrown(NullPointerException)
-    }
-
-    def "load missing file"() {
-        when:
-        new JadxDecompiler().loadFile(new File("_.dex"))
-        then:
-        def e = thrown(JadxException)
-        e.message == "Error load file: _.dex"
-        e.cause.class == IOException
-    }
-
-    def "pass decompiler args"() {
-        setup:
-        def args = Mock(IJadxArgs)
-        when:
-        new JadxDecompiler(args)
-        then:
-        noExceptionThrown()
-    }
-
-    def "get errors count for new decompiler"() {
-        expect:
-        new JadxDecompiler().getErrorsCount() == 0
-    }
-}
diff --git a/jadx-core/src/test/java/jadx/api/JadxArgsValidatorOutDirsTest.java b/jadx-core/src/test/java/jadx/api/JadxArgsValidatorOutDirsTest.java
new file mode 100644
index 000000000..e03775b27
--- /dev/null
+++ b/jadx-core/src/test/java/jadx/api/JadxArgsValidatorOutDirsTest.java
@@ -0,0 +1,72 @@
+package jadx.api;
+
+import org.junit.Test;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import jadx.core.utils.files.FileUtils;
+
+import static jadx.core.utils.files.FileUtils.toFile;
+import static org.hamcrest.Matchers.is;
+import static org.junit.Assert.assertThat;
+
+public class JadxArgsValidatorOutDirsTest {
+
+	private static final Logger LOG = LoggerFactory.getLogger(JadxArgsValidatorOutDirsTest.class);
+	public JadxArgs args;
+
+	@Test
+	public void checkAllSet() {
+		setOutDirs("r", "s", "r");
+		checkOutDirs("r", "s", "r");
+	}
+
+	@Test
+	public void checkRootOnly() {
+		setOutDirs("out", null, null);
+		checkOutDirs("out", "out/" + JadxArgs.DEFAULT_SRC_DIR, "out/" + JadxArgs.DEFAULT_RES_DIR);
+	}
+
+	@Test
+	public void checkSrcOnly() {
+		setOutDirs(null, "src", null);
+		checkOutDirs("src", "src", "src/" + JadxArgs.DEFAULT_RES_DIR);
+	}
+
+	@Test
+	public void checkResOnly() {
+		setOutDirs(null, null, "res");
+		checkOutDirs("res", "res/" + JadxArgs.DEFAULT_SRC_DIR, "res");
+	}
+
+	@Test
+	public void checkNone() {
+		setOutDirs(null, null, null);
+		String inputFileBase = args.getInputFiles().get(0).getName().replace(".apk", "");
+		checkOutDirs(inputFileBase,
+				inputFileBase + "/" + JadxArgs.DEFAULT_SRC_DIR,
+				inputFileBase + "/" + JadxArgs.DEFAULT_RES_DIR);
+	}
+
+	private void setOutDirs(String outDir, String srcDir, String resDir) {
+		args = makeArgs();
+		args.setOutDir(toFile(outDir));
+		args.setOutDirSrc(toFile(srcDir));
+		args.setOutDirRes(toFile(resDir));
+		LOG.debug("Set dirs: out={}, src={}, res={}", outDir, srcDir, resDir);
+	}
+
+	private void checkOutDirs(String outDir, String srcDir, String resDir) {
+		JadxArgsValidator.validate(args);
+		LOG.debug("Got dirs: out={}, src={}, res={}", args.getOutDir(), args.getOutDirSrc(), args.getOutDirRes());
+		assertThat(args.getOutDir(), is(toFile(outDir)));
+		assertThat(args.getOutDirSrc(), is(toFile(srcDir)));
+		assertThat(args.getOutDirRes(), is(toFile(resDir)));
+	}
+
+	private JadxArgs makeArgs() {
+		JadxArgs args = new JadxArgs();
+		args.getInputFiles().add(FileUtils.createTempFile("some.apk"));
+		return args;
+	}
+}
diff --git a/jadx-core/src/test/java/jadx/api/JadxDecompilerTest.java b/jadx-core/src/test/java/jadx/api/JadxDecompilerTest.java
new file mode 100644
index 000000000..5c3814d8d
--- /dev/null
+++ b/jadx-core/src/test/java/jadx/api/JadxDecompilerTest.java
@@ -0,0 +1,23 @@
+package jadx.api;
+
+import java.io.File;
+
+import org.junit.Ignore;
+import org.junit.Test;
+
+public class JadxDecompilerTest {
+
+	@Test
+	@Ignore
+	public void testExampleUsage() {
+		JadxArgs args = new JadxArgs();
+		args.getInputFiles().add(new File("test.apk"));
+		args.setOutDir(new File("jadx-test-output"));
+
+		JadxDecompiler jadx = new JadxDecompiler(args);
+		jadx.load();
+		jadx.save();
+	}
+
+	// TODO make more tests
+}
diff --git a/jadx-core/src/test/java/jadx/tests/api/IntegrationTest.java b/jadx-core/src/test/java/jadx/tests/api/IntegrationTest.java
index 04c5ac700..a060fc7a9 100644
--- a/jadx-core/src/test/java/jadx/tests/api/IntegrationTest.java
+++ b/jadx-core/src/test/java/jadx/tests/api/IntegrationTest.java
@@ -10,7 +10,6 @@ import java.net.URISyntaxException;
 import java.net.URL;
 import java.util.ArrayList;
 import java.util.Collections;
-import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
 import java.util.jar.JarOutputStream;
@@ -29,7 +28,6 @@ import jadx.core.dex.nodes.RootNode;
 import jadx.core.dex.visitors.DepthTraversal;
 import jadx.core.dex.visitors.IDexTreeVisitor;
 import jadx.core.utils.exceptions.CodegenException;
-import jadx.core.utils.exceptions.JadxException;
 import jadx.tests.api.compiler.DynamicCompiler;
 import jadx.tests.api.compiler.StaticCompiler;
 import jadx.tests.api.utils.TestUtils;
@@ -65,6 +63,7 @@ public abstract class IntegrationTest extends TestUtils {
 
 	public IntegrationTest() {
 		args = new JadxArgs();
+		args.setOutDir(new File(outDir));
 		args.setShowInconsistentCode(true);
 		args.setThreadsCount(1);
 		args.setSkipResources(true);
@@ -84,9 +83,10 @@ public abstract class IntegrationTest extends TestUtils {
 	public ClassNode getClassNodeFromFile(File file, String clsName) {
 		JadxDecompiler d = null;
 		try {
+			args.setInputFiles(Collections.singletonList(file));
 			d = new JadxDecompiler(args);
-			d.loadFile(file);
-		} catch (JadxException e) {
+			d.load();
+		} catch (Exception e) {
 			e.printStackTrace();
 			fail(e.getMessage());
 		}
@@ -114,18 +114,18 @@ public abstract class IntegrationTest extends TestUtils {
 	}
 
 	private void decompile(JadxDecompiler jadx, ClassNode cls) {
-		List passes = Jadx.getPassesList(jadx.getArgs(), new File(outDir));
-		ProcessClass.process(cls, passes, new CodeGen(jadx.getArgs()));
+		List passes = Jadx.getPassesList(jadx.getArgs());
+		ProcessClass.process(cls, passes, new CodeGen());
 	}
 
 	private void decompileWithoutUnload(JadxDecompiler d, ClassNode cls) {
 		cls.load();
-		List passes = Jadx.getPassesList(d.getArgs(), new File(outDir));
+		List passes = Jadx.getPassesList(d.getArgs());
 		for (IDexTreeVisitor visitor : passes) {
 			DepthTraversal.visit(visitor, cls);
 		}
 		try {
-			new CodeGen(d.getArgs()).visit(cls);
+			new CodeGen().visit(cls);
 		} catch (CodegenException e) {
 			e.printStackTrace();
 			fail(e.getMessage());
diff --git a/jadx-core/src/test/java/jadx/tests/functional/JadxVisitorsOrderTest.java b/jadx-core/src/test/java/jadx/tests/functional/JadxVisitorsOrderTest.java
index 1a4774535..23d9309dc 100644
--- a/jadx-core/src/test/java/jadx/tests/functional/JadxVisitorsOrderTest.java
+++ b/jadx-core/src/test/java/jadx/tests/functional/JadxVisitorsOrderTest.java
@@ -1,6 +1,5 @@
 package jadx.tests.functional;
 
-import java.io.File;
 import java.util.ArrayList;
 import java.util.HashSet;
 import java.util.List;
@@ -24,7 +23,7 @@ public class JadxVisitorsOrderTest {
 
 	@Test
 	public void testOrder() {
-		List passes = Jadx.getPassesList(new JadxArgs(), new File("out"));
+		List passes = Jadx.getPassesList(new JadxArgs());
 
 		List errors = check(passes);
 		for (String str : errors) {
diff --git a/jadx-gui/src/main/java/jadx/gui/JadxGUI.java b/jadx-gui/src/main/java/jadx/gui/JadxGUI.java
index 5856b924b..5ccc9c2a9 100644
--- a/jadx-gui/src/main/java/jadx/gui/JadxGUI.java
+++ b/jadx-gui/src/main/java/jadx/gui/JadxGUI.java
@@ -5,7 +5,6 @@ import javax.swing.*;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
-import jadx.core.utils.exceptions.JadxException;
 import jadx.gui.settings.JadxSettings;
 import jadx.gui.settings.JadxSettingsAdapter;
 import jadx.gui.ui.MainWindow;
@@ -17,24 +16,17 @@ public class JadxGUI {
 	public static void main(String[] args) {
 		try {
 			LogCollector.register();
-			final JadxSettings jadxArgs = JadxSettingsAdapter.load();
+			final JadxSettings settings = JadxSettingsAdapter.load();
 			// overwrite loaded settings by command line arguments
-			if (!jadxArgs.processArgs(args)) {
+			if (!settings.processArgs(args)) {
 				return;
 			}
 			UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
-			SwingUtilities.invokeLater(new Runnable() {
-				public void run() {
-					try {
-						MainWindow window = new MainWindow(jadxArgs);
-						window.open();
-					}
-					catch(JadxException e) {
-						throw new RuntimeException(e);
-					}
-				}
+			SwingUtilities.invokeLater(() -> {
+				MainWindow window = new MainWindow(settings);
+				window.open();
 			});
-		} catch (Throwable e) {
+		} catch (Exception e) {
 			LOG.error("Error: {}", e.getMessage(), e);
 			System.exit(1);
 		}
diff --git a/jadx-gui/src/main/java/jadx/gui/JadxWrapper.java b/jadx-gui/src/main/java/jadx/gui/JadxWrapper.java
index 406c76fe1..a17221dae 100644
--- a/jadx-gui/src/main/java/jadx/gui/JadxWrapper.java
+++ b/jadx-gui/src/main/java/jadx/gui/JadxWrapper.java
@@ -2,38 +2,38 @@ package jadx.gui;
 
 import javax.swing.*;
 import java.io.File;
+import java.util.Collections;
 import java.util.List;
 import java.util.concurrent.ThreadPoolExecutor;
 
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
-import jadx.api.IJadxArgs;
 import jadx.api.JadxDecompiler;
 import jadx.api.JavaClass;
 import jadx.api.JavaPackage;
 import jadx.api.ResourceFile;
-import jadx.core.utils.exceptions.DecodeException;
-import jadx.core.utils.exceptions.JadxException;
+import jadx.gui.settings.JadxSettings;
 
 public class JadxWrapper {
 	private static final Logger LOG = LoggerFactory.getLogger(JadxWrapper.class);
 
-	private final JadxDecompiler decompiler;
+	private final JadxSettings settings;
+	private JadxDecompiler decompiler;
 	private File openFile;
 
-	public JadxWrapper(IJadxArgs jadxArgs) throws JadxException {
-		this.decompiler = new JadxDecompiler(jadxArgs);
+	public JadxWrapper(JadxSettings settings) {
+		this.settings = settings;
 	}
 
 	public void openFile(File file) {
 		this.openFile = file;
 		try {
-			this.decompiler.loadFile(file);
-		} catch (DecodeException e) {
-			LOG.error("Error decode file: {}", file, e);
-		} catch (JadxException e) {
-			LOG.error("Error open file: {}", file, e);
+			this.decompiler = new JadxDecompiler(settings.toJadxArgs());
+			this.decompiler.getArgs().setInputFiles(Collections.singletonList(file));
+			this.decompiler.load();
+		} catch (Exception e) {
+			LOG.error("Error load file: {}", file, e);
 		}
 	}
 
@@ -42,7 +42,7 @@ public class JadxWrapper {
 			@Override
 			public void run() {
 				try {
-					decompiler.setOutputDir(dir);
+					decompiler.getArgs().setRootDir(dir);
 					ThreadPoolExecutor ex = (ThreadPoolExecutor) decompiler.getSaveExecutor();
 					ex.shutdown();
 					while (ex.isTerminating()) {
@@ -53,7 +53,7 @@ public class JadxWrapper {
 					}
 					progressMonitor.close();
 					LOG.info("done");
-				} catch (InterruptedException|JadxException e) {
+				} catch (InterruptedException e) {
 					LOG.error("Save interrupted", e);
 					Thread.currentThread().interrupt();
 				}
diff --git a/jadx-gui/src/main/java/jadx/gui/settings/JadxSettings.java b/jadx-gui/src/main/java/jadx/gui/settings/JadxSettings.java
index e89805cb1..716d09347 100644
--- a/jadx-gui/src/main/java/jadx/gui/settings/JadxSettings.java
+++ b/jadx-gui/src/main/java/jadx/gui/settings/JadxSettings.java
@@ -11,6 +11,7 @@ import java.util.Set;
 
 import org.fife.ui.rsyntaxtextarea.RSyntaxTextArea;
 
+import jadx.api.JadxArgs;
 import jadx.cli.JadxCLIArgs;
 
 public class JadxSettings extends JadxCLIArgs {
@@ -44,7 +45,7 @@ public class JadxSettings extends JadxCLIArgs {
 
 	public void fixOnLoad() {
 		if (threadsCount <= 0) {
-			threadsCount = DEFAULT_THREADS_COUNT;
+			threadsCount = JadxArgs.DEFAULT_THREADS_COUNT;
 		}
 	}
 
@@ -165,8 +166,8 @@ public class JadxSettings extends JadxCLIArgs {
 		this.deobfuscationForceSave = deobfuscationForceSave;
 	}
 
-	public void setUseSourceNameAsClassAlias(boolean useSourceNameAsAlias) {
-		this.deobfuscationUseSourceNameAsAlias = useSourceNameAsAlias;
+	public void setDeobfuscationUseSourceNameAsAlias(boolean deobfuscationUseSourceNameAsAlias) {
+		this.deobfuscationUseSourceNameAsAlias = deobfuscationUseSourceNameAsAlias;
 	}
 
 	public void setEscapeUnicode(boolean escapeUnicode) {
diff --git a/jadx-gui/src/main/java/jadx/gui/settings/JadxSettingsWindow.java b/jadx-gui/src/main/java/jadx/gui/settings/JadxSettingsWindow.java
index 6fb79f09c..5c28e56f2 100644
--- a/jadx-gui/src/main/java/jadx/gui/settings/JadxSettingsWindow.java
+++ b/jadx-gui/src/main/java/jadx/gui/settings/JadxSettingsWindow.java
@@ -145,10 +145,10 @@ public class JadxSettingsWindow extends JDialog {
 		});
 
 		JCheckBox deobfSourceAlias = new JCheckBox();
-		deobfSourceAlias.setSelected(settings.useSourceNameAsClassAlias());
+		deobfSourceAlias.setSelected(settings.isDeobfuscationUseSourceNameAsAlias());
 		deobfSourceAlias.addItemListener(new ItemListener() {
 			public void itemStateChanged(ItemEvent e) {
-				settings.setUseSourceNameAsClassAlias(e.getStateChange() == ItemEvent.SELECTED);
+				settings.setDeobfuscationUseSourceNameAsAlias(e.getStateChange() == ItemEvent.SELECTED);
 				needReload();
 			}
 		});
@@ -219,6 +219,7 @@ public class JadxSettingsWindow extends JDialog {
 			@Override
 			public void stateChanged(ChangeEvent e) {
 				settings.setThreadsCount((Integer) threadsCount.getValue());
+				needReload();
 			}
 		});
 
diff --git a/jadx-gui/src/main/java/jadx/gui/ui/MainWindow.java b/jadx-gui/src/main/java/jadx/gui/ui/MainWindow.java
index 42cd07034..e2cec158e 100644
--- a/jadx-gui/src/main/java/jadx/gui/ui/MainWindow.java
+++ b/jadx-gui/src/main/java/jadx/gui/ui/MainWindow.java
@@ -27,12 +27,13 @@ import java.util.Arrays;
 import java.util.EnumSet;
 import java.util.Timer;
 import java.util.TimerTask;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
 
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
 import jadx.api.ResourceFile;
-import jadx.core.utils.exceptions.JadxException;
 import jadx.gui.JadxWrapper;
 import jadx.gui.jobs.BackgroundWorker;
 import jadx.gui.jobs.DecompileJob;
@@ -101,7 +102,7 @@ public class MainWindow extends JFrame {
 	private transient ProgressPanel progressPane;
 	private transient BackgroundWorker backgroundWorker;
 
-	public MainWindow(JadxSettings settings) throws JadxException {
+	public MainWindow(JadxSettings settings) {
 		this.wrapper = new JadxWrapper(settings);
 		this.settings = settings;
 		this.cacheObject = new CacheObject();
@@ -119,10 +120,10 @@ public class MainWindow extends JFrame {
 		setLocationRelativeTo(null);
 		setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
 
-		if (settings.getInput().isEmpty()) {
+		if (settings.getFiles().isEmpty()) {
 			openFile();
 		} else {
-			openFile(settings.getInput().get(0));
+			openFile(new File(settings.getFiles().get(0)));
 		}
 	}
 
@@ -689,5 +690,4 @@ public class MainWindow extends JFrame {
 		public void menuCanceled(MenuEvent e) {
 		}
 	}
-
 }
diff --git a/jadx-gui/src/test/java/jadx/gui/treemodel/JSourcesTest.java b/jadx-gui/src/test/java/jadx/gui/treemodel/JSourcesTest.java
index 8a248b50e..fe836984b 100644
--- a/jadx-gui/src/test/java/jadx/gui/treemodel/JSourcesTest.java
+++ b/jadx-gui/src/test/java/jadx/gui/treemodel/JSourcesTest.java
@@ -7,7 +7,7 @@ import org.junit.Before;
 import org.junit.Test;
 
 import jadx.api.Factory;
-import jadx.api.IJadxArgs;
+import jadx.api.JadxArgs;
 import jadx.api.JadxDecompiler;
 import jadx.api.JavaClass;
 import jadx.api.JavaPackage;
@@ -30,7 +30,7 @@ public class JSourcesTest {
 		when(root.isFlatPackages()).thenReturn(false);
 		JadxWrapper wrapper = mock(JadxWrapper.class);
 		sources = new JSources(root, wrapper);
-		decompiler = new JadxDecompiler(mock(IJadxArgs.class));
+		decompiler = new JadxDecompiler(new JadxArgs());
 	}
 
 	@Test