mirror of
https://github.com/skylot/jadx.git
synced 2024-11-23 12:50:02 +00:00
feat(cli): install and manage plugins from command line
This commit is contained in:
parent
67054bda3d
commit
8a67c39279
@ -79,7 +79,12 @@ and also packed to `build/jadx-<version>.zip`
|
|||||||
|
|
||||||
### Usage
|
### Usage
|
||||||
```
|
```
|
||||||
jadx[-gui] [options] <input files> (.apk, .dex, .jar, .class, .smali, .zip, .aar, .arsc, .aab)
|
jadx[-gui] [command] [options] <input files> (.apk, .dex, .jar, .class, .smali, .zip, .aar, .arsc, .aab)
|
||||||
|
commands (use '<command> --help' for command options):
|
||||||
|
plugins - manage jadx plugins
|
||||||
|
|
||||||
|
options:
|
||||||
|
-d, --output-dir - output directory
|
||||||
options:
|
options:
|
||||||
-d, --output-dir - output directory
|
-d, --output-dir - output directory
|
||||||
-ds, --output-dir-src - output directory for sources
|
-ds, --output-dir-src - output directory for sources
|
||||||
|
@ -7,6 +7,7 @@ plugins {
|
|||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
implementation(project(':jadx-core'))
|
implementation(project(':jadx-core'))
|
||||||
|
implementation(project(':jadx-plugins-tools'))
|
||||||
|
|
||||||
runtimeOnly(project(':jadx-plugins:jadx-dex-input'))
|
runtimeOnly(project(':jadx-plugins:jadx-dex-input'))
|
||||||
runtimeOnly(project(':jadx-plugins:jadx-java-input'))
|
runtimeOnly(project(':jadx-plugins:jadx-java-input'))
|
||||||
|
@ -25,13 +25,17 @@ import jadx.api.plugins.options.OptionDescription;
|
|||||||
import jadx.core.plugins.JadxPluginManager;
|
import jadx.core.plugins.JadxPluginManager;
|
||||||
import jadx.core.plugins.PluginContext;
|
import jadx.core.plugins.PluginContext;
|
||||||
import jadx.core.utils.Utils;
|
import jadx.core.utils.Utils;
|
||||||
|
import jadx.plugins.tools.JadxExternalPluginsLoader;
|
||||||
|
|
||||||
public class JCommanderWrapper<T> {
|
public class JCommanderWrapper<T> {
|
||||||
private final JCommander jc;
|
private final JCommander jc;
|
||||||
private final JadxCLIArgs argsObj;
|
private final JadxCLIArgs argsObj;
|
||||||
|
|
||||||
public JCommanderWrapper(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;
|
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) {
|
public void overrideProvided(JadxCLIArgs obj) {
|
||||||
List<ParameterDescription> fieldsParams = jc.getParameters();
|
List<ParameterDescription> fieldsParams = jc.getParameters();
|
||||||
List<ParameterDescription> parameters = new ArrayList<>(1 + fieldsParams.size());
|
List<ParameterDescription> parameters = new ArrayList<>(1 + fieldsParams.size());
|
||||||
@ -73,6 +85,10 @@ public class JCommanderWrapper<T> {
|
|||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public List<String> getUnknownOptions() {
|
||||||
|
return jc.getUnknownOptions();
|
||||||
|
}
|
||||||
|
|
||||||
public void printUsage() {
|
public void printUsage() {
|
||||||
LogHelper.setLogLevel(LogHelper.LogLevelEnum.ERROR); // mute logger while printing help
|
LogHelper.setLogLevel(LogHelper.LogLevelEnum.ERROR); // mute logger while printing help
|
||||||
|
|
||||||
@ -81,7 +97,32 @@ public class JCommanderWrapper<T> {
|
|||||||
out.println();
|
out.println();
|
||||||
out.println("jadx - dex to java decompiler, version: " + JadxDecompiler.getVersion());
|
out.println("jadx - dex to java decompiler, version: " + JadxDecompiler.getVersion());
|
||||||
out.println();
|
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:");
|
out.println("options:");
|
||||||
|
|
||||||
List<ParameterDescription> params = jc.getParameters();
|
List<ParameterDescription> params = jc.getParameters();
|
||||||
@ -96,7 +137,7 @@ public class JCommanderWrapper<T> {
|
|||||||
}
|
}
|
||||||
maxNamesLen += 3;
|
maxNamesLen += 3;
|
||||||
|
|
||||||
JadxCLIArgs args = (JadxCLIArgs) jc.getObjects().get(0);
|
Object args = jc.getObjects().get(0);
|
||||||
for (Field f : getFields(args.getClass())) {
|
for (Field f : getFields(args.getClass())) {
|
||||||
String name = f.getName();
|
String name = f.getName();
|
||||||
ParameterDescription p = paramsMap.get(name);
|
ParameterDescription p = paramsMap.get(name);
|
||||||
@ -118,26 +159,21 @@ public class JCommanderWrapper<T> {
|
|||||||
} else {
|
} else {
|
||||||
opt.append("- ").append(description);
|
opt.append("- ").append(description);
|
||||||
}
|
}
|
||||||
String defaultValue = getDefaultValue(args, f, opt);
|
if (addDefaults) {
|
||||||
if (defaultValue != null && !description.contains("(default)")) {
|
String defaultValue = getDefaultValue(args, f, opt);
|
||||||
opt.append(", default: ").append(defaultValue);
|
if (defaultValue != null && !description.contains("(default)")) {
|
||||||
|
opt.append(", default: ").append(defaultValue);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
out.println(opt);
|
out.println(opt);
|
||||||
}
|
}
|
||||||
out.println(appendPluginOptions(maxNamesLen));
|
return 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");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get all declared fields of the specified class and all super classes
|
* 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<>();
|
List<Field> fieldList = new ArrayList<>();
|
||||||
while (clazz != null) {
|
while (clazz != null) {
|
||||||
fieldList.addAll(Arrays.asList(clazz.getDeclaredFields()));
|
fieldList.addAll(Arrays.asList(clazz.getDeclaredFields()));
|
||||||
@ -147,7 +183,7 @@ public class JCommanderWrapper<T> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
private String getDefaultValue(JadxCLIArgs args, Field f, StringBuilder opt) {
|
private static String getDefaultValue(Object args, Field f, StringBuilder opt) {
|
||||||
try {
|
try {
|
||||||
Class<?> fieldType = f.getType();
|
Class<?> fieldType = f.getType();
|
||||||
if (fieldType == int.class) {
|
if (fieldType == int.class) {
|
||||||
@ -180,7 +216,7 @@ public class JCommanderWrapper<T> {
|
|||||||
// load and init all options plugins to print all options
|
// load and init all options plugins to print all options
|
||||||
try (JadxDecompiler decompiler = new JadxDecompiler(new JadxArgs())) {
|
try (JadxDecompiler decompiler = new JadxDecompiler(new JadxArgs())) {
|
||||||
JadxPluginManager pluginManager = decompiler.getPluginManager();
|
JadxPluginManager pluginManager = decompiler.getPluginManager();
|
||||||
pluginManager.load();
|
pluginManager.load(new JadxExternalPluginsLoader());
|
||||||
pluginManager.initAll();
|
pluginManager.initAll();
|
||||||
for (PluginContext context : pluginManager.getAllPluginContexts()) {
|
for (PluginContext context : pluginManager.getAllPluginContexts()) {
|
||||||
JadxPluginOptions options = context.getOptions();
|
JadxPluginOptions options = context.getOptions();
|
||||||
|
@ -10,6 +10,7 @@ import jadx.api.impl.SimpleCodeWriter;
|
|||||||
import jadx.cli.LogHelper.LogLevelEnum;
|
import jadx.cli.LogHelper.LogLevelEnum;
|
||||||
import jadx.core.utils.exceptions.JadxArgsValidateException;
|
import jadx.core.utils.exceptions.JadxArgsValidateException;
|
||||||
import jadx.core.utils.files.FileUtils;
|
import jadx.core.utils.files.FileUtils;
|
||||||
|
import jadx.plugins.tools.JadxExternalPluginsLoader;
|
||||||
|
|
||||||
public class JadxCLI {
|
public class JadxCLI {
|
||||||
private static final Logger LOG = LoggerFactory.getLogger(JadxCLI.class);
|
private static final Logger LOG = LoggerFactory.getLogger(JadxCLI.class);
|
||||||
@ -44,6 +45,7 @@ public class JadxCLI {
|
|||||||
JadxArgs jadxArgs = cliArgs.toJadxArgs();
|
JadxArgs jadxArgs = cliArgs.toJadxArgs();
|
||||||
jadxArgs.setCodeCache(new NoOpCodeCache());
|
jadxArgs.setCodeCache(new NoOpCodeCache());
|
||||||
jadxArgs.setCodeWriterProvider(SimpleCodeWriter::new);
|
jadxArgs.setCodeWriterProvider(SimpleCodeWriter::new);
|
||||||
|
jadxArgs.setPluginLoader(new JadxExternalPluginsLoader());
|
||||||
try (JadxDecompiler jadx = new JadxDecompiler(jadxArgs)) {
|
try (JadxDecompiler jadx = new JadxDecompiler(jadxArgs)) {
|
||||||
jadx.load();
|
jadx.load();
|
||||||
if (checkForErrors(jadx)) {
|
if (checkForErrors(jadx)) {
|
||||||
|
@ -264,6 +264,10 @@ public class JadxCLIArgs {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private boolean process(JCommanderWrapper<JadxCLIArgs> jcw) {
|
private boolean process(JCommanderWrapper<JadxCLIArgs> jcw) {
|
||||||
|
files.addAll(jcw.getUnknownOptions());
|
||||||
|
if (jcw.processCommands()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
if (printHelp) {
|
if (printHelp) {
|
||||||
jcw.printUsage();
|
jcw.printUsage();
|
||||||
return false;
|
return false;
|
||||||
|
35
jadx-cli/src/main/java/jadx/cli/JadxCLICommands.java
Normal file
35
jadx-cli/src/main/java/jadx/cli/JadxCLICommands.java
Normal file
@ -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.JadxDecompiler;
|
||||||
import jadx.api.plugins.input.ICodeLoader;
|
import jadx.api.plugins.input.ICodeLoader;
|
||||||
import jadx.api.plugins.input.JadxCodeInput;
|
import jadx.api.plugins.input.JadxCodeInput;
|
||||||
|
import jadx.api.plugins.loader.JadxBasePluginLoader;
|
||||||
import jadx.core.clsp.ClsSet;
|
import jadx.core.clsp.ClsSet;
|
||||||
import jadx.core.dex.nodes.ClassNode;
|
import jadx.core.dex.nodes.ClassNode;
|
||||||
import jadx.core.dex.nodes.RootNode;
|
import jadx.core.dex.nodes.RootNode;
|
||||||
@ -43,7 +44,7 @@ public class ConvertToClsSet {
|
|||||||
jadxArgs.setRenameFlags(EnumSet.noneOf(JadxArgs.RenameEnum.class));
|
jadxArgs.setRenameFlags(EnumSet.noneOf(JadxArgs.RenameEnum.class));
|
||||||
try (JadxDecompiler decompiler = new JadxDecompiler(jadxArgs)) {
|
try (JadxDecompiler decompiler = new JadxDecompiler(jadxArgs)) {
|
||||||
JadxPluginManager pluginManager = decompiler.getPluginManager();
|
JadxPluginManager pluginManager = decompiler.getPluginManager();
|
||||||
pluginManager.load();
|
pluginManager.load(new JadxBasePluginLoader());
|
||||||
pluginManager.initResolved();
|
pluginManager.initResolved();
|
||||||
List<ICodeLoader> loadedInputs = new ArrayList<>();
|
List<ICodeLoader> loadedInputs = new ArrayList<>();
|
||||||
for (JadxCodeInput inputPlugin : pluginManager.getCodeInputs()) {
|
for (JadxCodeInput inputPlugin : pluginManager.getCodeInputs()) {
|
||||||
|
81
jadx-cli/src/main/java/jadx/cli/commands/CommandPlugins.java
Normal file
81
jadx-cli/src/main/java/jadx/cli/commands/CommandPlugins.java
Normal file
@ -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());
|
||||||
|
}
|
||||||
|
}
|
11
jadx-cli/src/main/java/jadx/cli/commands/ICommand.java
Normal file
11
jadx-cli/src/main/java/jadx/cli/commands/ICommand.java
Normal file
@ -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);
|
||||||
|
}
|
@ -25,6 +25,8 @@ import jadx.api.deobf.IAliasProvider;
|
|||||||
import jadx.api.deobf.IRenameCondition;
|
import jadx.api.deobf.IRenameCondition;
|
||||||
import jadx.api.impl.AnnotatedCodeWriter;
|
import jadx.api.impl.AnnotatedCodeWriter;
|
||||||
import jadx.api.impl.InMemoryCodeCache;
|
import jadx.api.impl.InMemoryCodeCache;
|
||||||
|
import jadx.api.plugins.loader.JadxBasePluginLoader;
|
||||||
|
import jadx.api.plugins.loader.JadxPluginLoader;
|
||||||
import jadx.api.usage.IUsageInfoCache;
|
import jadx.api.usage.IUsageInfoCache;
|
||||||
import jadx.api.usage.impl.InMemoryUsageInfoCache;
|
import jadx.api.usage.impl.InMemoryUsageInfoCache;
|
||||||
import jadx.core.deobf.DeobfAliasProvider;
|
import jadx.core.deobf.DeobfAliasProvider;
|
||||||
@ -150,6 +152,8 @@ public class JadxArgs implements Closeable {
|
|||||||
|
|
||||||
private Map<String, String> pluginOptions = new HashMap<>();
|
private Map<String, String> pluginOptions = new HashMap<>();
|
||||||
|
|
||||||
|
private JadxPluginLoader pluginLoader = new JadxBasePluginLoader();
|
||||||
|
|
||||||
public JadxArgs() {
|
public JadxArgs() {
|
||||||
// use default options
|
// use default options
|
||||||
}
|
}
|
||||||
@ -170,6 +174,9 @@ public class JadxArgs implements Closeable {
|
|||||||
if (usageInfoCache != null) {
|
if (usageInfoCache != null) {
|
||||||
usageInfoCache.close();
|
usageInfoCache.close();
|
||||||
}
|
}
|
||||||
|
if (pluginLoader != null) {
|
||||||
|
pluginLoader.close();
|
||||||
|
}
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
LOG.error("Failed to close JadxArgs", e);
|
LOG.error("Failed to close JadxArgs", e);
|
||||||
} finally {
|
} finally {
|
||||||
@ -634,6 +641,14 @@ public class JadxArgs implements Closeable {
|
|||||||
this.pluginOptions = pluginOptions;
|
this.pluginOptions = pluginOptions;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public JadxPluginLoader getPluginLoader() {
|
||||||
|
return pluginLoader;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setPluginLoader(JadxPluginLoader pluginLoader) {
|
||||||
|
this.pluginLoader = pluginLoader;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Hash of all options that can change result code
|
* Hash of all options that can change result code
|
||||||
*/
|
*/
|
||||||
|
@ -182,7 +182,7 @@ public final class JadxDecompiler implements Closeable {
|
|||||||
|
|
||||||
private void loadPlugins() {
|
private void loadPlugins() {
|
||||||
pluginManager.providesSuggestion("java-input", args.isUseDxInput() ? "java-convert" : "java-input");
|
pluginManager.providesSuggestion("java-input", args.isUseDxInput() ? "java-convert" : "java-input");
|
||||||
pluginManager.load();
|
pluginManager.load(args.getPluginLoader());
|
||||||
if (LOG.isDebugEnabled()) {
|
if (LOG.isDebugEnabled()) {
|
||||||
LOG.debug("Resolved plugins: {}", pluginManager.getResolvedPluginContexts());
|
LOG.debug("Resolved plugins: {}", pluginManager.getResolvedPluginContexts());
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,29 @@
|
|||||||
|
package jadx.api.plugins.loader;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.ServiceLoader;
|
||||||
|
|
||||||
|
import jadx.api.plugins.JadxPlugin;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Loading plugins from current classpath
|
||||||
|
*/
|
||||||
|
public class JadxBasePluginLoader implements JadxPluginLoader {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<JadxPlugin> load() {
|
||||||
|
List<JadxPlugin> list = new ArrayList<>();
|
||||||
|
ServiceLoader<JadxPlugin> plugins = ServiceLoader.load(JadxPlugin.class);
|
||||||
|
for (JadxPlugin plugin : plugins) {
|
||||||
|
list.add(plugin);
|
||||||
|
}
|
||||||
|
return list;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void close() throws IOException {
|
||||||
|
// nothing to close
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,11 @@
|
|||||||
|
package jadx.api.plugins.loader;
|
||||||
|
|
||||||
|
import java.io.Closeable;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import jadx.api.plugins.JadxPlugin;
|
||||||
|
|
||||||
|
public interface JadxPluginLoader extends Closeable {
|
||||||
|
|
||||||
|
List<JadxPlugin> load();
|
||||||
|
}
|
@ -4,7 +4,6 @@ import java.util.ArrayList;
|
|||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
import java.util.ServiceLoader;
|
|
||||||
import java.util.SortedSet;
|
import java.util.SortedSet;
|
||||||
import java.util.TreeMap;
|
import java.util.TreeMap;
|
||||||
import java.util.TreeSet;
|
import java.util.TreeSet;
|
||||||
@ -18,6 +17,7 @@ import jadx.api.JadxDecompiler;
|
|||||||
import jadx.api.plugins.JadxPlugin;
|
import jadx.api.plugins.JadxPlugin;
|
||||||
import jadx.api.plugins.gui.JadxGuiContext;
|
import jadx.api.plugins.gui.JadxGuiContext;
|
||||||
import jadx.api.plugins.input.JadxCodeInput;
|
import jadx.api.plugins.input.JadxCodeInput;
|
||||||
|
import jadx.api.plugins.loader.JadxPluginLoader;
|
||||||
import jadx.api.plugins.options.JadxPluginOptions;
|
import jadx.api.plugins.options.JadxPluginOptions;
|
||||||
import jadx.api.plugins.options.OptionDescription;
|
import jadx.api.plugins.options.OptionDescription;
|
||||||
import jadx.core.utils.exceptions.JadxRuntimeException;
|
import jadx.core.utils.exceptions.JadxRuntimeException;
|
||||||
@ -43,10 +43,9 @@ public class JadxPluginManager {
|
|||||||
provideSuggestions.put(provides, pluginId);
|
provideSuggestions.put(provides, pluginId);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void load() {
|
public void load(JadxPluginLoader pluginLoader) {
|
||||||
allPlugins.clear();
|
allPlugins.clear();
|
||||||
ServiceLoader<JadxPlugin> jadxPlugins = ServiceLoader.load(JadxPlugin.class);
|
for (JadxPlugin plugin : pluginLoader.load()) {
|
||||||
for (JadxPlugin plugin : jadxPlugins) {
|
|
||||||
addPlugin(plugin);
|
addPlugin(plugin);
|
||||||
}
|
}
|
||||||
resolve();
|
resolve();
|
||||||
|
@ -9,6 +9,7 @@ plugins {
|
|||||||
dependencies {
|
dependencies {
|
||||||
implementation(project(':jadx-core'))
|
implementation(project(':jadx-core'))
|
||||||
implementation(project(':jadx-cli'))
|
implementation(project(':jadx-cli'))
|
||||||
|
implementation(project(':jadx-plugins-tools'))
|
||||||
|
|
||||||
// import mappings
|
// import mappings
|
||||||
implementation project(':jadx-plugins:jadx-rename-mappings')
|
implementation project(':jadx-plugins:jadx-rename-mappings')
|
||||||
|
@ -36,6 +36,7 @@ import jadx.gui.settings.JadxProject;
|
|||||||
import jadx.gui.settings.JadxSettings;
|
import jadx.gui.settings.JadxSettings;
|
||||||
import jadx.gui.ui.MainWindow;
|
import jadx.gui.ui.MainWindow;
|
||||||
import jadx.gui.utils.CacheObject;
|
import jadx.gui.utils.CacheObject;
|
||||||
|
import jadx.plugins.tools.JadxExternalPluginsLoader;
|
||||||
|
|
||||||
import static jadx.core.dex.nodes.ProcessState.GENERATED_AND_UNLOADED;
|
import static jadx.core.dex.nodes.ProcessState.GENERATED_AND_UNLOADED;
|
||||||
import static jadx.core.dex.nodes.ProcessState.NOT_LOADED;
|
import static jadx.core.dex.nodes.ProcessState.NOT_LOADED;
|
||||||
@ -61,6 +62,7 @@ public class JadxWrapper {
|
|||||||
synchronized (DECOMPILER_UPDATE_SYNC) {
|
synchronized (DECOMPILER_UPDATE_SYNC) {
|
||||||
JadxProject project = getProject();
|
JadxProject project = getProject();
|
||||||
JadxArgs jadxArgs = getSettings().toJadxArgs();
|
JadxArgs jadxArgs = getSettings().toJadxArgs();
|
||||||
|
jadxArgs.setPluginLoader(new JadxExternalPluginsLoader());
|
||||||
project.fillJadxArgs(jadxArgs);
|
project.fillJadxArgs(jadxArgs);
|
||||||
|
|
||||||
decompiler = new JadxDecompiler(jadxArgs);
|
decompiler = new JadxDecompiler(jadxArgs);
|
||||||
|
@ -10,6 +10,7 @@ import jadx.api.JadxDecompiler;
|
|||||||
import jadx.core.plugins.JadxPluginManager;
|
import jadx.core.plugins.JadxPluginManager;
|
||||||
import jadx.core.plugins.PluginContext;
|
import jadx.core.plugins.PluginContext;
|
||||||
import jadx.gui.JadxWrapper;
|
import jadx.gui.JadxWrapper;
|
||||||
|
import jadx.plugins.tools.JadxExternalPluginsLoader;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Collect options from all plugins.
|
* Collect options from all plugins.
|
||||||
@ -32,7 +33,7 @@ public class CollectPluginOptions {
|
|||||||
// collect and init not loaded plugins in new context
|
// collect and init not loaded plugins in new context
|
||||||
try (JadxDecompiler decompiler = new JadxDecompiler(new JadxArgs())) {
|
try (JadxDecompiler decompiler = new JadxDecompiler(new JadxArgs())) {
|
||||||
JadxPluginManager pluginManager = decompiler.getPluginManager();
|
JadxPluginManager pluginManager = decompiler.getPluginManager();
|
||||||
pluginManager.load();
|
pluginManager.load(new JadxExternalPluginsLoader());
|
||||||
SortedSet<PluginContext> missingPlugins = new TreeSet<>();
|
SortedSet<PluginContext> missingPlugins = new TreeSet<>();
|
||||||
for (PluginContext context : pluginManager.getAllPluginContexts()) {
|
for (PluginContext context : pluginManager.getAllPluginContexts()) {
|
||||||
if (!allPlugins.contains(context)) {
|
if (!allPlugins.contains(context)) {
|
||||||
|
10
jadx-plugins-tools/build.gradle.kts
Normal file
10
jadx-plugins-tools/build.gradle.kts
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
plugins {
|
||||||
|
id("jadx-library")
|
||||||
|
}
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
api(project(":jadx-core"))
|
||||||
|
|
||||||
|
implementation("dev.dirs:directories:26")
|
||||||
|
implementation("com.google.code.gson:gson:2.10.1")
|
||||||
|
}
|
@ -0,0 +1,107 @@
|
|||||||
|
package jadx.plugins.tools;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.net.URL;
|
||||||
|
import java.net.URLClassLoader;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Comparator;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.ServiceLoader;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import jadx.api.plugins.JadxPlugin;
|
||||||
|
import jadx.api.plugins.loader.JadxPluginLoader;
|
||||||
|
import jadx.core.utils.exceptions.JadxRuntimeException;
|
||||||
|
|
||||||
|
public class JadxExternalPluginsLoader implements JadxPluginLoader {
|
||||||
|
private static final Logger LOG = LoggerFactory.getLogger(JadxExternalPluginsLoader.class);
|
||||||
|
|
||||||
|
private final List<URLClassLoader> classLoaders = new ArrayList<>();
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<JadxPlugin> load() {
|
||||||
|
close();
|
||||||
|
long start = System.currentTimeMillis();
|
||||||
|
Map<Class<? extends JadxPlugin>, JadxPlugin> map = new HashMap<>();
|
||||||
|
ClassLoader classLoader = JadxPluginsTools.class.getClassLoader();
|
||||||
|
loadFromClsLoader(map, classLoader);
|
||||||
|
loadInstalledPlugins(map, classLoader);
|
||||||
|
|
||||||
|
List<JadxPlugin> list = new ArrayList<>(map.size());
|
||||||
|
list.addAll(map.values());
|
||||||
|
list.sort(Comparator.comparing(p -> p.getClass().getSimpleName()));
|
||||||
|
if (LOG.isDebugEnabled()) {
|
||||||
|
LOG.debug("Collected {} plugins in {}ms", list.size(), System.currentTimeMillis() - start);
|
||||||
|
}
|
||||||
|
return list;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* TODO: find a better way to load only plugin from single jar without plugins from parent
|
||||||
|
* classloader
|
||||||
|
*/
|
||||||
|
public JadxPlugin loadFromJar(Path jar) {
|
||||||
|
Map<Class<? extends JadxPlugin>, JadxPlugin> map = new HashMap<>();
|
||||||
|
ClassLoader classLoader = JadxPluginsTools.class.getClassLoader();
|
||||||
|
loadFromClsLoader(map, classLoader);
|
||||||
|
Set<Class<? extends JadxPlugin>> clspPlugins = new HashSet<>(map.keySet());
|
||||||
|
try (URLClassLoader pluginClassLoader = loadFromJar(map, classLoader, jar)) {
|
||||||
|
return map.entrySet().stream()
|
||||||
|
.filter(entry -> !clspPlugins.contains(entry.getKey()))
|
||||||
|
.findFirst()
|
||||||
|
.map(Map.Entry::getValue)
|
||||||
|
.orElseThrow(() -> new RuntimeException("No plugin found in jar: " + jar));
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new RuntimeException("Failed to load plugin jar: " + jar, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void loadFromClsLoader(Map<Class<? extends JadxPlugin>, JadxPlugin> map, ClassLoader classLoader) {
|
||||||
|
ServiceLoader.load(JadxPlugin.class, classLoader)
|
||||||
|
.stream()
|
||||||
|
.filter(p -> !map.containsKey(p.type()))
|
||||||
|
.forEach(p -> map.put(p.type(), p.get()));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void loadInstalledPlugins(Map<Class<? extends JadxPlugin>, JadxPlugin> map, ClassLoader classLoader) {
|
||||||
|
List<Path> jars = JadxPluginsTools.getInstance().getAllPluginJars();
|
||||||
|
for (Path jar : jars) {
|
||||||
|
classLoaders.add(loadFromJar(map, classLoader, jar));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private URLClassLoader loadFromJar(Map<Class<? extends JadxPlugin>, JadxPlugin> map, ClassLoader classLoader, Path jar) {
|
||||||
|
try {
|
||||||
|
File jarFile = jar.toFile();
|
||||||
|
URL[] urls = new URL[] { jarFile.toURI().toURL() };
|
||||||
|
URLClassLoader pluginClsLoader = new URLClassLoader("jadx-plugin:" + jarFile.getName(), urls, classLoader);
|
||||||
|
loadFromClsLoader(map, pluginClsLoader);
|
||||||
|
return pluginClsLoader;
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new JadxRuntimeException("Failed to load plugins, jar: " + jar, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void close() {
|
||||||
|
try {
|
||||||
|
for (URLClassLoader classLoader : classLoaders) {
|
||||||
|
try {
|
||||||
|
classLoader.close();
|
||||||
|
} catch (Exception e) {
|
||||||
|
// ignore
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
classLoaders.clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,252 @@
|
|||||||
|
package jadx.plugins.tools;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.io.Reader;
|
||||||
|
import java.io.Writer;
|
||||||
|
import java.net.URL;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.nio.file.Paths;
|
||||||
|
import java.nio.file.StandardCopyOption;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Optional;
|
||||||
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
|
import com.google.gson.Gson;
|
||||||
|
import com.google.gson.GsonBuilder;
|
||||||
|
|
||||||
|
import dev.dirs.ProjectDirectories;
|
||||||
|
|
||||||
|
import jadx.api.plugins.JadxPlugin;
|
||||||
|
import jadx.api.plugins.JadxPluginInfo;
|
||||||
|
import jadx.core.utils.files.FileUtils;
|
||||||
|
import jadx.plugins.tools.data.JadxInstalledPlugins;
|
||||||
|
import jadx.plugins.tools.data.JadxPluginMetadata;
|
||||||
|
import jadx.plugins.tools.data.JadxPluginUpdate;
|
||||||
|
import jadx.plugins.tools.resolvers.IJadxPluginResolver;
|
||||||
|
import jadx.plugins.tools.resolvers.ResolversRegistry;
|
||||||
|
|
||||||
|
import static jadx.core.utils.files.FileUtils.makeDirs;
|
||||||
|
|
||||||
|
public class JadxPluginsTools {
|
||||||
|
private static final JadxPluginsTools INSTANCE = new JadxPluginsTools();
|
||||||
|
|
||||||
|
public static JadxPluginsTools getInstance() {
|
||||||
|
return INSTANCE;
|
||||||
|
}
|
||||||
|
|
||||||
|
private final Path pluginsJson;
|
||||||
|
private final Path dropins;
|
||||||
|
private final Path installed;
|
||||||
|
|
||||||
|
private JadxPluginsTools() {
|
||||||
|
ProjectDirectories jadxDirs = ProjectDirectories.from("io.github", "skylot", "jadx");
|
||||||
|
Path plugins = Paths.get(jadxDirs.configDir, "plugins"); // TODO: use dataDir?
|
||||||
|
makeDirs(plugins);
|
||||||
|
pluginsJson = plugins.resolve("plugins.json");
|
||||||
|
dropins = plugins.resolve("dropins");
|
||||||
|
makeDirs(dropins);
|
||||||
|
installed = plugins.resolve("installed");
|
||||||
|
makeDirs(installed);
|
||||||
|
}
|
||||||
|
|
||||||
|
public JadxPluginMetadata install(String locationId) {
|
||||||
|
JadxPluginMetadata pluginMetadata = ResolversRegistry.resolve(locationId)
|
||||||
|
.orElseThrow(() -> new RuntimeException("Failed to resolve locationId: " + locationId));
|
||||||
|
install(pluginMetadata);
|
||||||
|
return pluginMetadata;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<JadxPluginUpdate> updateAll() {
|
||||||
|
JadxInstalledPlugins plugins = loadPluginsJson();
|
||||||
|
int size = plugins.getInstalled().size();
|
||||||
|
List<JadxPluginUpdate> updates = new ArrayList<>(size);
|
||||||
|
List<JadxPluginMetadata> newList = new ArrayList<>(size);
|
||||||
|
for (JadxPluginMetadata plugin : plugins.getInstalled()) {
|
||||||
|
JadxPluginMetadata newVersion = update(plugin);
|
||||||
|
if (newVersion != null) {
|
||||||
|
updates.add(new JadxPluginUpdate(plugin, newVersion));
|
||||||
|
newList.add(newVersion);
|
||||||
|
} else {
|
||||||
|
newList.add(plugin);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!updates.isEmpty()) {
|
||||||
|
plugins.setUpdated(System.currentTimeMillis());
|
||||||
|
plugins.setInstalled(newList);
|
||||||
|
savePluginsJson(plugins);
|
||||||
|
}
|
||||||
|
return updates;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Optional<JadxPluginUpdate> update(String pluginId) {
|
||||||
|
JadxInstalledPlugins plugins = loadPluginsJson();
|
||||||
|
JadxPluginMetadata plugin = plugins.getInstalled().stream()
|
||||||
|
.filter(p -> p.getPluginId().equals(pluginId))
|
||||||
|
.findFirst()
|
||||||
|
.orElseThrow(() -> new RuntimeException("Plugin not found: " + pluginId));
|
||||||
|
|
||||||
|
JadxPluginMetadata newVersion = update(plugin);
|
||||||
|
if (newVersion == null) {
|
||||||
|
return Optional.empty();
|
||||||
|
}
|
||||||
|
plugins.setUpdated(System.currentTimeMillis());
|
||||||
|
plugins.getInstalled().remove(plugin);
|
||||||
|
plugins.getInstalled().add(newVersion);
|
||||||
|
savePluginsJson(plugins);
|
||||||
|
return Optional.of(new JadxPluginUpdate(plugin, newVersion));
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean uninstall(String pluginId) {
|
||||||
|
JadxInstalledPlugins plugins = loadPluginsJson();
|
||||||
|
Optional<JadxPluginMetadata> found = plugins.getInstalled().stream()
|
||||||
|
.filter(p -> p.getPluginId().equals(pluginId))
|
||||||
|
.findFirst();
|
||||||
|
if (found.isEmpty()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
JadxPluginMetadata plugin = found.get();
|
||||||
|
deletePluginJar(plugin);
|
||||||
|
plugins.getInstalled().remove(plugin);
|
||||||
|
savePluginsJson(plugins);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void deletePluginJar(JadxPluginMetadata plugin) {
|
||||||
|
try {
|
||||||
|
Files.deleteIfExists(installed.resolve(plugin.getJar()));
|
||||||
|
} catch (IOException e) {
|
||||||
|
// ignore
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<JadxPluginMetadata> getInstalled() {
|
||||||
|
return loadPluginsJson().getInstalled();
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<Path> getAllPluginJars() {
|
||||||
|
List<Path> list = new ArrayList<>();
|
||||||
|
for (JadxPluginMetadata pluginMetadata : loadPluginsJson().getInstalled()) {
|
||||||
|
list.add(installed.resolve(pluginMetadata.getJar()));
|
||||||
|
}
|
||||||
|
collectFromDir(list, dropins);
|
||||||
|
return list;
|
||||||
|
}
|
||||||
|
|
||||||
|
private @Nullable JadxPluginMetadata update(JadxPluginMetadata plugin) {
|
||||||
|
IJadxPluginResolver resolver = ResolversRegistry.getById(plugin.getResolverId());
|
||||||
|
if (!resolver.isUpdateSupported()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
Optional<JadxPluginMetadata> updateOpt = resolver.resolve(plugin.getLocationId());
|
||||||
|
if (updateOpt.isEmpty()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
JadxPluginMetadata update = updateOpt.get();
|
||||||
|
if (update.getVersion().equals(plugin.getVersion())) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
install(update);
|
||||||
|
return update;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void install(JadxPluginMetadata metadata) {
|
||||||
|
Path tmpJar;
|
||||||
|
if (needDownload(metadata.getJar())) {
|
||||||
|
tmpJar = FileUtils.createTempFile("plugin.jar");
|
||||||
|
downloadJar(metadata.getJar(), tmpJar);
|
||||||
|
} else {
|
||||||
|
tmpJar = Paths.get(metadata.getJar());
|
||||||
|
}
|
||||||
|
fillPluginInfoFromJar(metadata, tmpJar);
|
||||||
|
|
||||||
|
Path pluginJar = installed.resolve(metadata.getPluginId() + '-' + metadata.getVersion() + ".jar");
|
||||||
|
copyJar(tmpJar, pluginJar);
|
||||||
|
metadata.setJar(installed.relativize(pluginJar).toString());
|
||||||
|
|
||||||
|
JadxInstalledPlugins plugins = loadPluginsJson();
|
||||||
|
// remove previous version jar
|
||||||
|
plugins.getInstalled().stream()
|
||||||
|
.filter(p -> p.getPluginId().equals(metadata.getPluginId()))
|
||||||
|
.forEach(this::deletePluginJar);
|
||||||
|
plugins.getInstalled().remove(metadata);
|
||||||
|
plugins.getInstalled().add(metadata);
|
||||||
|
plugins.setUpdated(System.currentTimeMillis());
|
||||||
|
savePluginsJson(plugins);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void fillPluginInfoFromJar(JadxPluginMetadata metadata, Path jar) {
|
||||||
|
try (JadxExternalPluginsLoader loader = new JadxExternalPluginsLoader()) {
|
||||||
|
JadxPlugin jadxPlugin = loader.loadFromJar(jar);
|
||||||
|
JadxPluginInfo pluginInfo = jadxPlugin.getPluginInfo();
|
||||||
|
metadata.setPluginId(pluginInfo.getPluginId());
|
||||||
|
metadata.setName(pluginInfo.getName());
|
||||||
|
metadata.setDescription(pluginInfo.getDescription());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean needDownload(String jar) {
|
||||||
|
return jar.startsWith("https://") || jar.startsWith("http://");
|
||||||
|
}
|
||||||
|
|
||||||
|
private void downloadJar(String sourceJar, Path destPath) {
|
||||||
|
try (InputStream in = new URL(sourceJar).openStream()) {
|
||||||
|
Files.copy(in, destPath, StandardCopyOption.REPLACE_EXISTING);
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new RuntimeException("Failed to download jar: " + sourceJar, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void copyJar(Path sourceJar, Path destJar) {
|
||||||
|
try {
|
||||||
|
Files.copy(sourceJar, destJar, StandardCopyOption.REPLACE_EXISTING);
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new RuntimeException("Failed to copy plugin jar: " + sourceJar + " to: " + destJar, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Gson buildGson() {
|
||||||
|
return new GsonBuilder().setPrettyPrinting().create();
|
||||||
|
}
|
||||||
|
|
||||||
|
private JadxInstalledPlugins loadPluginsJson() {
|
||||||
|
if (!Files.isRegularFile(pluginsJson)) {
|
||||||
|
return new JadxInstalledPlugins();
|
||||||
|
}
|
||||||
|
try (Reader reader = Files.newBufferedReader(pluginsJson, StandardCharsets.UTF_8)) {
|
||||||
|
return buildGson().fromJson(reader, JadxInstalledPlugins.class);
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new RuntimeException("Failed to read file: " + pluginsJson);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void savePluginsJson(JadxInstalledPlugins data) {
|
||||||
|
if (data.getInstalled().isEmpty()) {
|
||||||
|
try {
|
||||||
|
Files.deleteIfExists(pluginsJson);
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new RuntimeException("Failed to remove file: " + pluginsJson, e);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
data.getInstalled().sort(null);
|
||||||
|
try (Writer writer = Files.newBufferedWriter(pluginsJson, StandardCharsets.UTF_8)) {
|
||||||
|
buildGson().toJson(data, writer);
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new RuntimeException("Error saving file: " + pluginsJson, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void collectFromDir(List<Path> list, Path dir) {
|
||||||
|
try (Stream<Path> files = Files.list(dir)) {
|
||||||
|
files.filter(p -> p.getFileName().toString().endsWith(".jar")).forEach(list::add);
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,27 @@
|
|||||||
|
package jadx.plugins.tools.data;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public class JadxInstalledPlugins {
|
||||||
|
|
||||||
|
private long updated;
|
||||||
|
|
||||||
|
private List<JadxPluginMetadata> installed = new ArrayList<>();
|
||||||
|
|
||||||
|
public long getUpdated() {
|
||||||
|
return updated;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setUpdated(long updated) {
|
||||||
|
this.updated = updated;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<JadxPluginMetadata> getInstalled() {
|
||||||
|
return installed;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setInstalled(List<JadxPluginMetadata> installed) {
|
||||||
|
this.installed = installed;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,101 @@
|
|||||||
|
package jadx.plugins.tools.data;
|
||||||
|
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
|
public class JadxPluginMetadata implements Comparable<JadxPluginMetadata> {
|
||||||
|
private String pluginId;
|
||||||
|
private String name;
|
||||||
|
private String description;
|
||||||
|
private String version;
|
||||||
|
private String locationId;
|
||||||
|
private String resolverId;
|
||||||
|
private String jar;
|
||||||
|
|
||||||
|
public String getPluginId() {
|
||||||
|
return pluginId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setPluginId(String pluginId) {
|
||||||
|
this.pluginId = pluginId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getName() {
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setName(String name) {
|
||||||
|
this.name = name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getVersion() {
|
||||||
|
return version;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setVersion(String version) {
|
||||||
|
this.version = version;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getDescription() {
|
||||||
|
return description;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setDescription(String description) {
|
||||||
|
this.description = description;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getLocationId() {
|
||||||
|
return locationId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setLocationId(String locationId) {
|
||||||
|
this.locationId = locationId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getResolverId() {
|
||||||
|
return resolverId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setResolverId(String resolverId) {
|
||||||
|
this.resolverId = resolverId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getJar() {
|
||||||
|
return jar;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setJar(String jar) {
|
||||||
|
this.jar = jar;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object other) {
|
||||||
|
if (this == other) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (!(other instanceof JadxPluginMetadata)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return pluginId.equals(((JadxPluginMetadata) other).pluginId);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
return pluginId.hashCode();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int compareTo(@NotNull JadxPluginMetadata o) {
|
||||||
|
return pluginId.compareTo(o.pluginId);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "JadxPluginMetadata{"
|
||||||
|
+ "id=" + pluginId
|
||||||
|
+ ", name=" + name
|
||||||
|
+ ", version=" + version
|
||||||
|
+ ", locationId=" + locationId
|
||||||
|
+ ", jar=" + jar
|
||||||
|
+ '}';
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,37 @@
|
|||||||
|
package jadx.plugins.tools.data;
|
||||||
|
|
||||||
|
public class JadxPluginUpdate {
|
||||||
|
private final JadxPluginMetadata oldVersion;
|
||||||
|
private final JadxPluginMetadata newVersion;
|
||||||
|
|
||||||
|
public JadxPluginUpdate(JadxPluginMetadata oldVersion, JadxPluginMetadata newVersion) {
|
||||||
|
this.oldVersion = oldVersion;
|
||||||
|
this.newVersion = newVersion;
|
||||||
|
}
|
||||||
|
|
||||||
|
public JadxPluginMetadata getOld() {
|
||||||
|
return oldVersion;
|
||||||
|
}
|
||||||
|
|
||||||
|
public JadxPluginMetadata getNew() {
|
||||||
|
return newVersion;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getPluginId() {
|
||||||
|
return newVersion.getPluginId();
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getOldVersion() {
|
||||||
|
return oldVersion.getVersion();
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getNewVersion() {
|
||||||
|
return newVersion.getVersion();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "PluginUpdate{" + oldVersion.getPluginId()
|
||||||
|
+ ": " + oldVersion.getVersion() + " -> " + newVersion.getVersion() + "}";
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,14 @@
|
|||||||
|
package jadx.plugins.tools.resolvers;
|
||||||
|
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
|
import jadx.plugins.tools.data.JadxPluginMetadata;
|
||||||
|
|
||||||
|
public interface IJadxPluginResolver {
|
||||||
|
|
||||||
|
String id();
|
||||||
|
|
||||||
|
boolean isUpdateSupported();
|
||||||
|
|
||||||
|
Optional<JadxPluginMetadata> resolve(String locationId);
|
||||||
|
}
|
@ -0,0 +1,28 @@
|
|||||||
|
### Supported publish locations for Jadx plugins
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
#### GitHub release artifact
|
||||||
|
|
||||||
|
Pattern: `github:<owner>:<repo>[:<version>][:<artifact name prefix>]`
|
||||||
|
|
||||||
|
Examples: `github:skylot:jadx`, `github:skylot:jadx:sample-plugin` or `github:skylot:jadx:0.1.0`
|
||||||
|
|
||||||
|
`<version>` - exact version to install (optional), should be equal to release name
|
||||||
|
|
||||||
|
Artifact should have a name: `<artifact name prefix>-<release-version-name>.jar`.
|
||||||
|
|
||||||
|
Default value for `<artifact name prefix>` is a repo name,
|
||||||
|
`release-version-name` should have a `x.x.x` format.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
#### Local file
|
||||||
|
|
||||||
|
Install local jar file.
|
||||||
|
|
||||||
|
Pattern: `file:<path to file>.jar`
|
||||||
|
|
||||||
|
Example: `file:/home/user/plugin.jar`
|
||||||
|
|
||||||
|
As alternative to install, plugin jars can be copied to `plugins/dropins` folder.
|
@ -0,0 +1,41 @@
|
|||||||
|
package jadx.plugins.tools.resolvers;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Optional;
|
||||||
|
import java.util.TreeMap;
|
||||||
|
|
||||||
|
import jadx.plugins.tools.data.JadxPluginMetadata;
|
||||||
|
import jadx.plugins.tools.resolvers.file.LocalFileResolver;
|
||||||
|
import jadx.plugins.tools.resolvers.github.GithubReleaseResolver;
|
||||||
|
|
||||||
|
public class ResolversRegistry {
|
||||||
|
|
||||||
|
private static final Map<String, IJadxPluginResolver> RESOLVERS_MAP = new TreeMap<>();
|
||||||
|
|
||||||
|
static {
|
||||||
|
register(new LocalFileResolver());
|
||||||
|
register(new GithubReleaseResolver());
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void register(IJadxPluginResolver resolver) {
|
||||||
|
RESOLVERS_MAP.put(resolver.id(), resolver);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Optional<JadxPluginMetadata> resolve(String locationId) {
|
||||||
|
for (IJadxPluginResolver resolver : RESOLVERS_MAP.values()) {
|
||||||
|
Optional<JadxPluginMetadata> result = resolver.resolve(locationId);
|
||||||
|
if (result.isPresent()) {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return Optional.empty();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static IJadxPluginResolver getById(String resolverId) {
|
||||||
|
IJadxPluginResolver resolver = RESOLVERS_MAP.get(resolverId);
|
||||||
|
if (resolver == null) {
|
||||||
|
throw new IllegalArgumentException("Unknown resolverId: " + resolverId);
|
||||||
|
}
|
||||||
|
return resolver;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,37 @@
|
|||||||
|
package jadx.plugins.tools.resolvers.file;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
|
import jadx.plugins.tools.data.JadxPluginMetadata;
|
||||||
|
import jadx.plugins.tools.resolvers.IJadxPluginResolver;
|
||||||
|
|
||||||
|
import static jadx.plugins.tools.utils.PluginsUtils.removePrefix;
|
||||||
|
|
||||||
|
public class LocalFileResolver implements IJadxPluginResolver {
|
||||||
|
@Override
|
||||||
|
public String id() {
|
||||||
|
return "file";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isUpdateSupported() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Optional<JadxPluginMetadata> resolve(String locationId) {
|
||||||
|
if (!locationId.startsWith("file:") || !locationId.endsWith(".jar")) {
|
||||||
|
return Optional.empty();
|
||||||
|
}
|
||||||
|
File jarFile = new File(removePrefix(locationId, "file:"));
|
||||||
|
if (!jarFile.isFile()) {
|
||||||
|
throw new RuntimeException("File not found: " + jarFile.getAbsolutePath());
|
||||||
|
}
|
||||||
|
JadxPluginMetadata metadata = new JadxPluginMetadata();
|
||||||
|
metadata.setLocationId(locationId);
|
||||||
|
metadata.setResolverId(id());
|
||||||
|
metadata.setJar(jarFile.getAbsolutePath());
|
||||||
|
return Optional.of(metadata);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,129 @@
|
|||||||
|
package jadx.plugins.tools.resolvers.github;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStreamReader;
|
||||||
|
import java.io.Reader;
|
||||||
|
import java.lang.reflect.Type;
|
||||||
|
import java.net.HttpURLConnection;
|
||||||
|
import java.net.URL;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Optional;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
|
import com.google.gson.Gson;
|
||||||
|
import com.google.gson.reflect.TypeToken;
|
||||||
|
|
||||||
|
import jadx.plugins.tools.data.JadxPluginMetadata;
|
||||||
|
import jadx.plugins.tools.resolvers.IJadxPluginResolver;
|
||||||
|
import jadx.plugins.tools.resolvers.github.data.Asset;
|
||||||
|
import jadx.plugins.tools.resolvers.github.data.Release;
|
||||||
|
|
||||||
|
import static jadx.plugins.tools.utils.PluginsUtils.removePrefix;
|
||||||
|
|
||||||
|
public class GithubReleaseResolver implements IJadxPluginResolver {
|
||||||
|
private static final String GITHUB_API_URL = "https://api.github.com/";
|
||||||
|
private static final Pattern VERSION_PATTERN = Pattern.compile("v?\\d+\\.\\d+(\\.\\d+)?");
|
||||||
|
|
||||||
|
private static final Type RELEASE_TYPE = new TypeToken<Release>() {
|
||||||
|
}.getType();
|
||||||
|
private static final Type RELEASE_LIST_TYPE = new TypeToken<List<Release>>() {
|
||||||
|
}.getType();
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Optional<JadxPluginMetadata> resolve(String locationId) {
|
||||||
|
if (!locationId.startsWith("github:")) {
|
||||||
|
return Optional.empty();
|
||||||
|
}
|
||||||
|
String[] parts = locationId.split(":");
|
||||||
|
if (parts.length < 3) {
|
||||||
|
return Optional.empty();
|
||||||
|
}
|
||||||
|
String owner = parts[1];
|
||||||
|
String project = parts[2];
|
||||||
|
String version = null;
|
||||||
|
String artifactPrefix = project;
|
||||||
|
if (parts.length >= 4) {
|
||||||
|
String part = parts[3];
|
||||||
|
if (VERSION_PATTERN.matcher(part).matches()) {
|
||||||
|
version = part;
|
||||||
|
if (parts.length >= 5) {
|
||||||
|
artifactPrefix = parts[4];
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
artifactPrefix = part;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Release release = getRelease(owner, project, version);
|
||||||
|
String releaseVersion = removePrefix(release.getName(), "v");
|
||||||
|
String artifactName = artifactPrefix + '-' + releaseVersion + ".jar";
|
||||||
|
Asset asset = release.getAssets().stream()
|
||||||
|
.filter(a -> a.getName().equals(artifactName))
|
||||||
|
.findFirst()
|
||||||
|
.orElseThrow(() -> new RuntimeException("Release artifact with name '" + artifactName + "' not found"));
|
||||||
|
|
||||||
|
JadxPluginMetadata metadata = new JadxPluginMetadata();
|
||||||
|
metadata.setResolverId(id());
|
||||||
|
metadata.setVersion(releaseVersion);
|
||||||
|
metadata.setLocationId(buildLocationId(owner, project, artifactPrefix)); // exclude version for later updates
|
||||||
|
metadata.setJar(asset.getDownloadUrl());
|
||||||
|
return Optional.of(metadata);
|
||||||
|
}
|
||||||
|
|
||||||
|
@NotNull
|
||||||
|
private static String buildLocationId(String owner, String project, String artifactPrefix) {
|
||||||
|
if (project.equals(artifactPrefix)) {
|
||||||
|
return "github:" + owner + ':' + project;
|
||||||
|
}
|
||||||
|
return "github:" + owner + ':' + project + ':' + artifactPrefix;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Release getRelease(String owner, String project, @Nullable String version) {
|
||||||
|
String projectUrl = GITHUB_API_URL + "repos/" + owner + "/" + project;
|
||||||
|
if (version == null) {
|
||||||
|
// get latest version
|
||||||
|
return get(projectUrl + "/releases/latest", RELEASE_TYPE);
|
||||||
|
}
|
||||||
|
// search version among all releases (by name)
|
||||||
|
List<Release> releases = get(projectUrl + "/releases", RELEASE_LIST_TYPE);
|
||||||
|
return releases.stream()
|
||||||
|
.filter(r -> r.getName().equals(version))
|
||||||
|
.findFirst()
|
||||||
|
.orElseThrow(() -> new RuntimeException("Release with version: " + version + " not found."
|
||||||
|
+ " Available versions: " + releases.stream().map(Release::getName).collect(Collectors.joining(", "))));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static <T> T get(String url, Type type) {
|
||||||
|
HttpURLConnection con;
|
||||||
|
try {
|
||||||
|
con = (HttpURLConnection) new URL(url).openConnection();
|
||||||
|
con.setRequestMethod("GET");
|
||||||
|
int code = con.getResponseCode();
|
||||||
|
if (code != 200) {
|
||||||
|
// TODO: support redirects?
|
||||||
|
throw new RuntimeException("Request failed, response: " + code + ", url: " + url);
|
||||||
|
}
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new RuntimeException("Request failed, url: " + url, e);
|
||||||
|
}
|
||||||
|
try (Reader reader = new InputStreamReader(con.getInputStream(), StandardCharsets.UTF_8)) {
|
||||||
|
return new Gson().fromJson(reader, type);
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new RuntimeException("Failed to parse response, url: " + url, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String id() {
|
||||||
|
return "github-release";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isUpdateSupported() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,63 @@
|
|||||||
|
package jadx.plugins.tools.resolvers.github.data;
|
||||||
|
|
||||||
|
import com.google.gson.annotations.SerializedName;
|
||||||
|
|
||||||
|
public class Asset {
|
||||||
|
private int id;
|
||||||
|
private String name;
|
||||||
|
private long size;
|
||||||
|
|
||||||
|
@SerializedName("browser_download_url")
|
||||||
|
private String downloadUrl;
|
||||||
|
|
||||||
|
@SerializedName("created_at")
|
||||||
|
private String createdAt;
|
||||||
|
|
||||||
|
public int getId() {
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setId(int id) {
|
||||||
|
this.id = id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getName() {
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setName(String name) {
|
||||||
|
this.name = name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public long getSize() {
|
||||||
|
return size;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setSize(long size) {
|
||||||
|
this.size = size;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getDownloadUrl() {
|
||||||
|
return downloadUrl;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setDownloadUrl(String downloadUrl) {
|
||||||
|
this.downloadUrl = downloadUrl;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getCreatedAt() {
|
||||||
|
return createdAt;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setCreatedAt(String createdAt) {
|
||||||
|
this.createdAt = createdAt;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return name
|
||||||
|
+ ", size: " + String.format("%.2fMB", size / 1024. / 1024.)
|
||||||
|
+ ", url: " + downloadUrl
|
||||||
|
+ ", date: " + createdAt;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,44 @@
|
|||||||
|
package jadx.plugins.tools.resolvers.github.data;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public class Release {
|
||||||
|
private int id;
|
||||||
|
private String name;
|
||||||
|
private List<Asset> assets;
|
||||||
|
|
||||||
|
public String getName() {
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setName(String name) {
|
||||||
|
this.name = name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getId() {
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setId(int id) {
|
||||||
|
this.id = id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<Asset> getAssets() {
|
||||||
|
return assets;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setAssets(List<Asset> assets) {
|
||||||
|
this.assets = assets;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
sb.append(name);
|
||||||
|
for (Asset asset : getAssets()) {
|
||||||
|
sb.append("\n ");
|
||||||
|
sb.append(asset);
|
||||||
|
}
|
||||||
|
return sb.toString();
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,11 @@
|
|||||||
|
package jadx.plugins.tools.utils;
|
||||||
|
|
||||||
|
public class PluginsUtils {
|
||||||
|
|
||||||
|
public static String removePrefix(String str, String prefix) {
|
||||||
|
if (str.startsWith(prefix)) {
|
||||||
|
return str.substring(prefix.length());
|
||||||
|
}
|
||||||
|
return str;
|
||||||
|
}
|
||||||
|
}
|
@ -8,6 +8,8 @@ include("jadx-core")
|
|||||||
include("jadx-cli")
|
include("jadx-cli")
|
||||||
include("jadx-gui")
|
include("jadx-gui")
|
||||||
|
|
||||||
|
include("jadx-plugins-tools")
|
||||||
|
|
||||||
include("jadx-plugins:jadx-input-api")
|
include("jadx-plugins:jadx-input-api")
|
||||||
include("jadx-plugins:jadx-dex-input")
|
include("jadx-plugins:jadx-dex-input")
|
||||||
include("jadx-plugins:jadx-java-input")
|
include("jadx-plugins:jadx-java-input")
|
||||||
|
Loading…
Reference in New Issue
Block a user