feat(cli): install and manage plugins from command line
This commit is contained in:
@@ -25,13 +25,17 @@ import jadx.api.plugins.options.OptionDescription;
|
||||
import jadx.core.plugins.JadxPluginManager;
|
||||
import jadx.core.plugins.PluginContext;
|
||||
import jadx.core.utils.Utils;
|
||||
import jadx.plugins.tools.JadxExternalPluginsLoader;
|
||||
|
||||
public class JCommanderWrapper<T> {
|
||||
private final JCommander jc;
|
||||
private final JadxCLIArgs argsObj;
|
||||
|
||||
public JCommanderWrapper(JadxCLIArgs argsObj) {
|
||||
this.jc = JCommander.newBuilder().addObject(argsObj).build();
|
||||
JCommander.Builder builder = JCommander.newBuilder().addObject(argsObj);
|
||||
builder.acceptUnknownOptions(true); // workaround for "default" command
|
||||
JadxCLICommands.append(builder);
|
||||
this.jc = builder.build();
|
||||
this.argsObj = argsObj;
|
||||
}
|
||||
|
||||
@@ -46,6 +50,14 @@ public class JCommanderWrapper<T> {
|
||||
}
|
||||
}
|
||||
|
||||
public boolean processCommands() {
|
||||
String parsedCommand = jc.getParsedCommand();
|
||||
if (parsedCommand == null) {
|
||||
return false;
|
||||
}
|
||||
return JadxCLICommands.process(this, jc, parsedCommand);
|
||||
}
|
||||
|
||||
public void overrideProvided(JadxCLIArgs obj) {
|
||||
List<ParameterDescription> fieldsParams = jc.getParameters();
|
||||
List<ParameterDescription> parameters = new ArrayList<>(1 + fieldsParams.size());
|
||||
@@ -73,6 +85,10 @@ public class JCommanderWrapper<T> {
|
||||
return value;
|
||||
}
|
||||
|
||||
public List<String> getUnknownOptions() {
|
||||
return jc.getUnknownOptions();
|
||||
}
|
||||
|
||||
public void printUsage() {
|
||||
LogHelper.setLogLevel(LogHelper.LogLevelEnum.ERROR); // mute logger while printing help
|
||||
|
||||
@@ -81,7 +97,32 @@ public class JCommanderWrapper<T> {
|
||||
out.println();
|
||||
out.println("jadx - dex to java decompiler, version: " + JadxDecompiler.getVersion());
|
||||
out.println();
|
||||
out.println("usage: jadx [options] " + jc.getMainParameterDescription());
|
||||
out.println("usage: jadx [command] [options] " + jc.getMainParameterDescription());
|
||||
|
||||
out.println("commands (use '<command> --help' for command options):");
|
||||
for (String command : jc.getCommands().keySet()) {
|
||||
out.println(" " + command + "\t - " + jc.getUsageFormatter().getCommandDescription(command));
|
||||
}
|
||||
out.println();
|
||||
|
||||
int maxNamesLen = printOptions(jc, out, true);
|
||||
out.println(appendPluginOptions(maxNamesLen));
|
||||
out.println();
|
||||
out.println("Examples:");
|
||||
out.println(" jadx -d out classes.dex");
|
||||
out.println(" jadx --rename-flags \"none\" classes.dex");
|
||||
out.println(" jadx --rename-flags \"valid, printable\" classes.dex");
|
||||
out.println(" jadx --log-level ERROR app.apk");
|
||||
out.println(" jadx -Pdex-input.verify-checksum=no app.apk");
|
||||
}
|
||||
|
||||
public void printUsage(JCommander subCommander) {
|
||||
PrintStream out = System.out;
|
||||
out.println("usage: " + subCommander.getProgramName() + " [options]");
|
||||
printOptions(subCommander, out, false);
|
||||
}
|
||||
|
||||
private static int printOptions(JCommander jc, PrintStream out, boolean addDefaults) {
|
||||
out.println("options:");
|
||||
|
||||
List<ParameterDescription> params = jc.getParameters();
|
||||
@@ -96,7 +137,7 @@ public class JCommanderWrapper<T> {
|
||||
}
|
||||
maxNamesLen += 3;
|
||||
|
||||
JadxCLIArgs args = (JadxCLIArgs) jc.getObjects().get(0);
|
||||
Object args = jc.getObjects().get(0);
|
||||
for (Field f : getFields(args.getClass())) {
|
||||
String name = f.getName();
|
||||
ParameterDescription p = paramsMap.get(name);
|
||||
@@ -118,26 +159,21 @@ public class JCommanderWrapper<T> {
|
||||
} else {
|
||||
opt.append("- ").append(description);
|
||||
}
|
||||
String defaultValue = getDefaultValue(args, f, opt);
|
||||
if (defaultValue != null && !description.contains("(default)")) {
|
||||
opt.append(", default: ").append(defaultValue);
|
||||
if (addDefaults) {
|
||||
String defaultValue = getDefaultValue(args, f, opt);
|
||||
if (defaultValue != null && !description.contains("(default)")) {
|
||||
opt.append(", default: ").append(defaultValue);
|
||||
}
|
||||
}
|
||||
out.println(opt);
|
||||
}
|
||||
out.println(appendPluginOptions(maxNamesLen));
|
||||
out.println();
|
||||
out.println("Examples:");
|
||||
out.println(" jadx -d out classes.dex");
|
||||
out.println(" jadx --rename-flags \"none\" classes.dex");
|
||||
out.println(" jadx --rename-flags \"valid, printable\" classes.dex");
|
||||
out.println(" jadx --log-level ERROR app.apk");
|
||||
out.println(" jadx -Pdex-input.verify-checksum=no app.apk");
|
||||
return maxNamesLen;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all declared fields of the specified class and all super classes
|
||||
*/
|
||||
private List<Field> getFields(Class<?> clazz) {
|
||||
private static List<Field> getFields(Class<?> clazz) {
|
||||
List<Field> fieldList = new ArrayList<>();
|
||||
while (clazz != null) {
|
||||
fieldList.addAll(Arrays.asList(clazz.getDeclaredFields()));
|
||||
@@ -147,7 +183,7 @@ public class JCommanderWrapper<T> {
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private String getDefaultValue(JadxCLIArgs args, Field f, StringBuilder opt) {
|
||||
private static String getDefaultValue(Object args, Field f, StringBuilder opt) {
|
||||
try {
|
||||
Class<?> fieldType = f.getType();
|
||||
if (fieldType == int.class) {
|
||||
@@ -180,7 +216,7 @@ public class JCommanderWrapper<T> {
|
||||
// load and init all options plugins to print all options
|
||||
try (JadxDecompiler decompiler = new JadxDecompiler(new JadxArgs())) {
|
||||
JadxPluginManager pluginManager = decompiler.getPluginManager();
|
||||
pluginManager.load();
|
||||
pluginManager.load(new JadxExternalPluginsLoader());
|
||||
pluginManager.initAll();
|
||||
for (PluginContext context : pluginManager.getAllPluginContexts()) {
|
||||
JadxPluginOptions options = context.getOptions();
|
||||
|
||||
@@ -10,6 +10,7 @@ import jadx.api.impl.SimpleCodeWriter;
|
||||
import jadx.cli.LogHelper.LogLevelEnum;
|
||||
import jadx.core.utils.exceptions.JadxArgsValidateException;
|
||||
import jadx.core.utils.files.FileUtils;
|
||||
import jadx.plugins.tools.JadxExternalPluginsLoader;
|
||||
|
||||
public class JadxCLI {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(JadxCLI.class);
|
||||
@@ -44,6 +45,7 @@ public class JadxCLI {
|
||||
JadxArgs jadxArgs = cliArgs.toJadxArgs();
|
||||
jadxArgs.setCodeCache(new NoOpCodeCache());
|
||||
jadxArgs.setCodeWriterProvider(SimpleCodeWriter::new);
|
||||
jadxArgs.setPluginLoader(new JadxExternalPluginsLoader());
|
||||
try (JadxDecompiler jadx = new JadxDecompiler(jadxArgs)) {
|
||||
jadx.load();
|
||||
if (checkForErrors(jadx)) {
|
||||
|
||||
@@ -264,6 +264,10 @@ public class JadxCLIArgs {
|
||||
}
|
||||
|
||||
private boolean process(JCommanderWrapper<JadxCLIArgs> jcw) {
|
||||
files.addAll(jcw.getUnknownOptions());
|
||||
if (jcw.processCommands()) {
|
||||
return false;
|
||||
}
|
||||
if (printHelp) {
|
||||
jcw.printUsage();
|
||||
return false;
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
package jadx.cli;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.TreeMap;
|
||||
|
||||
import com.beust.jcommander.JCommander;
|
||||
|
||||
import jadx.cli.commands.CommandPlugins;
|
||||
import jadx.cli.commands.ICommand;
|
||||
|
||||
public class JadxCLICommands {
|
||||
private static final Map<String, ICommand> COMMANDS_MAP = new TreeMap<>();
|
||||
|
||||
static {
|
||||
JadxCLICommands.register(new CommandPlugins());
|
||||
}
|
||||
|
||||
public static void register(ICommand command) {
|
||||
COMMANDS_MAP.put(command.name(), command);
|
||||
}
|
||||
|
||||
public static void append(JCommander.Builder builder) {
|
||||
COMMANDS_MAP.forEach(builder::addCommand);
|
||||
}
|
||||
|
||||
public static boolean process(JCommanderWrapper<?> jcw, JCommander jc, String parsedCommand) {
|
||||
ICommand command = COMMANDS_MAP.get(parsedCommand);
|
||||
if (command == null) {
|
||||
throw new IllegalArgumentException("Unknown command: " + parsedCommand);
|
||||
}
|
||||
JCommander subCommander = jc.getCommands().get(parsedCommand);
|
||||
command.process(jcw, subCommander);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -15,6 +15,7 @@ import jadx.api.JadxArgs;
|
||||
import jadx.api.JadxDecompiler;
|
||||
import jadx.api.plugins.input.ICodeLoader;
|
||||
import jadx.api.plugins.input.JadxCodeInput;
|
||||
import jadx.api.plugins.loader.JadxBasePluginLoader;
|
||||
import jadx.core.clsp.ClsSet;
|
||||
import jadx.core.dex.nodes.ClassNode;
|
||||
import jadx.core.dex.nodes.RootNode;
|
||||
@@ -43,7 +44,7 @@ public class ConvertToClsSet {
|
||||
jadxArgs.setRenameFlags(EnumSet.noneOf(JadxArgs.RenameEnum.class));
|
||||
try (JadxDecompiler decompiler = new JadxDecompiler(jadxArgs)) {
|
||||
JadxPluginManager pluginManager = decompiler.getPluginManager();
|
||||
pluginManager.load();
|
||||
pluginManager.load(new JadxBasePluginLoader());
|
||||
pluginManager.initResolved();
|
||||
List<ICodeLoader> loadedInputs = new ArrayList<>();
|
||||
for (JadxCodeInput inputPlugin : pluginManager.getCodeInputs()) {
|
||||
|
||||
@@ -0,0 +1,81 @@
|
||||
package jadx.cli.commands;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import com.beust.jcommander.JCommander;
|
||||
import com.beust.jcommander.Parameter;
|
||||
import com.beust.jcommander.Parameters;
|
||||
|
||||
import jadx.cli.JCommanderWrapper;
|
||||
import jadx.plugins.tools.JadxPluginsTools;
|
||||
import jadx.plugins.tools.data.JadxPluginMetadata;
|
||||
import jadx.plugins.tools.data.JadxPluginUpdate;
|
||||
|
||||
@Parameters(commandDescription = "manage jadx plugins")
|
||||
public class CommandPlugins implements ICommand {
|
||||
|
||||
@Parameter(names = { "-i", "--install" }, description = "install plugin with locationId")
|
||||
protected String install;
|
||||
|
||||
@Parameter(names = { "-j", "--install-jar" }, description = "install plugin from jar file")
|
||||
protected String installJar;
|
||||
|
||||
@Parameter(names = { "-l", "--list" }, description = "list installed plugins")
|
||||
protected boolean list;
|
||||
|
||||
@Parameter(names = { "-u", "--update" }, description = "update installed plugins")
|
||||
protected boolean update;
|
||||
|
||||
@Parameter(names = { "--uninstall" }, description = "uninstall plugin with pluginId")
|
||||
protected String uninstall;
|
||||
|
||||
@Parameter(names = { "-h", "--help" }, description = "print this help", help = true)
|
||||
protected boolean printHelp = false;
|
||||
|
||||
@Override
|
||||
public String name() {
|
||||
return "plugins";
|
||||
}
|
||||
|
||||
@Override
|
||||
public void process(JCommanderWrapper<?> jcw, JCommander subCommander) {
|
||||
if (printHelp) {
|
||||
jcw.printUsage(subCommander);
|
||||
return;
|
||||
}
|
||||
if (install != null) {
|
||||
installPlugin(install);
|
||||
}
|
||||
if (installJar != null) {
|
||||
installPlugin("file:" + installJar);
|
||||
}
|
||||
if (uninstall != null) {
|
||||
boolean uninstalled = JadxPluginsTools.getInstance().uninstall(uninstall);
|
||||
System.out.println(uninstalled ? "Uninstalled" : "Plugin not found");
|
||||
}
|
||||
if (update) {
|
||||
List<JadxPluginUpdate> updates = JadxPluginsTools.getInstance().updateAll();
|
||||
if (updates.isEmpty()) {
|
||||
System.out.println("No updates");
|
||||
} else {
|
||||
System.out.println("Installed updates: " + updates.size());
|
||||
for (JadxPluginUpdate update : updates) {
|
||||
System.out.println(" " + update.getPluginId() + ": " + update.getOldVersion() + " -> " + update.getNewVersion());
|
||||
}
|
||||
}
|
||||
}
|
||||
if (list) {
|
||||
List<JadxPluginMetadata> installed = JadxPluginsTools.getInstance().getInstalled();
|
||||
System.out.println("Installed plugins: " + installed.size());
|
||||
for (JadxPluginMetadata plugin : installed) {
|
||||
System.out.println(" " + plugin.getPluginId() + ":" + plugin.getVersion()
|
||||
+ " - " + plugin.getName() + ": " + plugin.getDescription());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void installPlugin(String locationId) {
|
||||
JadxPluginMetadata plugin = JadxPluginsTools.getInstance().install(locationId);
|
||||
System.out.println("Plugin installed: " + plugin.getPluginId() + ":" + plugin.getVersion());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
package jadx.cli.commands;
|
||||
|
||||
import com.beust.jcommander.JCommander;
|
||||
|
||||
import jadx.cli.JCommanderWrapper;
|
||||
|
||||
public interface ICommand {
|
||||
String name();
|
||||
|
||||
void process(JCommanderWrapper<?> jcw, JCommander subCommander);
|
||||
}
|
||||
Reference in New Issue
Block a user