feat(cli): install and manage plugins from command line

This commit is contained in:
Skylot
2023-05-23 17:53:48 +01:00
parent 67054bda3d
commit 8a67c39279
32 changed files with 1162 additions and 25 deletions
@@ -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);
}