remove old

This commit is contained in:
327135569 2021-04-07 11:55:38 +08:00
parent c7eff6fe1f
commit f4343d8e17
18 changed files with 0 additions and 2095 deletions

1
xpatch/.gitignore vendored
View File

@ -1 +0,0 @@
/build

View File

@ -1,39 +0,0 @@
apply plugin: 'java-library'
dependencies {
compile fileTree(dir: 'libs', include: ['*.jar'])
compile project(':axmlprinter')
compile project(':apksigner')
compile group: 'commons-io', name: 'commons-io', version: '2.8.0'
}
jar {
baseName = "mmpatch"
manifest {
attributes 'Main-Class': 'com.storm.wind.xpatch.MainCommand'
}
//jar的源码打入最终的jar
from {
(configurations.runtime).collect {
it.isDirectory() ? it : zipTree(it)
}
}
from fileTree(dir: 'src/main', includes: ['assets/**'])
//jar中的签名信息
exclude 'META-INF/*.SF', 'META-INF/*.DSA', 'META-INF/*.RSA', 'META-INF/*.MF'
}
tasks.build.doLast {
println("Build to " + jar.archivePath)
println("Try \'java -jar " + jar.archiveName + "\' find more help")
}
//
sourceSets.main.resources {
srcDirs = [
"src/main/java",
];
include "**/*.*"
}

Binary file not shown.

Binary file not shown.

View File

@ -1,287 +0,0 @@
package com.storm.wind.xpatch;
import com.storm.wind.xpatch.base.BaseCommand;
import com.storm.wind.xpatch.task.ApkModifyTask;
import com.storm.wind.xpatch.task.BuildAndSignApkTask;
import com.storm.wind.xpatch.task.SaveApkSignatureTask;
import com.storm.wind.xpatch.task.SaveOriginalApplicationNameTask;
import com.storm.wind.xpatch.task.SoAndDexCopyTask;
import com.storm.wind.xpatch.util.FileUtils;
import com.storm.wind.xpatch.util.ManifestParser;
import com.wind.meditor.core.FileProcesser;
import com.wind.meditor.property.AttributeItem;
import com.wind.meditor.property.ModificationProperty;
import com.wind.meditor.utils.NodeValue;
import java.io.File;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.regex.Pattern;
import static org.apache.commons.io.FileUtils.copyFile;
public class MainCommand extends BaseCommand {
private String apkPath;
private String unzipApkFilePath;
@Opt(opt = "o", longOpt = "output", description = "output .apk file, default is " +
"$source_apk_dir/[file-name]-xposed-signed.apk", argName = "out-apk-file")
private String output; // 输出的apk文件的目录以及名称
@Opt(opt = "f", longOpt = "force", hasArg = false, description = "force overwrite")
private boolean forceOverwrite = false;
@Opt(opt = "pn", longOpt = "proxyname", description = "special proxy app name with full dot path", argName = "proxy app name")
private String proxyname = "com.wind.xposed.entry.MMPApplication";
@Opt(opt = "c", longOpt = "crach", hasArg = false,
description = "disable craching the apk's signature.")
private boolean disableCrackSignature = false;
// 使用dex文件中插入代码的方式修改apk而不是默认的修改Manifest中Application name的方式
@Opt(opt = "dex", longOpt = "dex", hasArg = false, description = "insert code into the dex file, not modify manifest application name attribute")
private boolean dexModificationMode = false;
@Opt(opt = "pkg", longOpt = "packageName", description = "modify the apk package name", argName = "new package name")
private String newPackageName;
@Opt(opt = "d", longOpt = "debuggable", description = "set 1 to make the app debuggable = true, " +
"set 0 to make the app debuggable = false", argName = "0 or 1")
private int debuggable = -1; // 0: debuggable = false 1: debuggable = true
@Opt(opt = "vc", longOpt = "versionCode", description = "set the app version code",
argName = "new-version-code")
private int versionCode;
@Opt(opt = "vn", longOpt = "versionName", description = "set the app version name",
argName = "new-version-name")
private String versionName;
// 原来apk中dex文件的数量
private int dexFileCount = 0;
private static final String UNZIP_APK_FILE_NAME = "apk-unzip-files";
private List<Runnable> mXpatchTasks = new ArrayList<>();
public static void main(String... args) {
new MainCommand().doMain(args);
}
private void fuckIfFail(boolean b) {
if (!b) {
throw new IllegalStateException("wtf", new Throwable("DUMPBT"));
}
}
@Override
protected void doCommandLine() throws IOException {
if (remainingArgs.length != 1) {
if (remainingArgs.length == 0) {
System.out.println("Please choose one apk file you want to process. ");
}
if (remainingArgs.length > 1) {
System.out.println("This tool can only used with one apk file.");
}
usage();
return;
}
apkPath = remainingArgs[0];
File srcApkFile = new File(apkPath);
if (!srcApkFile.exists()) {
System.out.println("The source apk file not exsit, please choose another one. " +
"current apk file is = " + apkPath);
return;
}
String currentDir = new File(".").getAbsolutePath();
System.out.println("currentDir: " + currentDir);
System.out.println("apkPath: " + apkPath);
if (output == null || output.length() == 0) {
output = getBaseName(apkPath) + "-xposed-signed.apk";
}
File outputFile = new File(output);
if (outputFile.exists() && !forceOverwrite) {
System.err.println(output + " exists, use --force to overwrite");
usage();
return;
}
String outputApkFileParentPath = outputFile.getParent();
if (outputApkFileParentPath == null) {
String absPath = outputFile.getAbsolutePath();
int index = absPath.lastIndexOf(File.separatorChar);
outputApkFileParentPath = absPath.substring(0, index);
}
System.out.println("output apk path: " + output);
System.out.println("disableCrackSignature: " + disableCrackSignature);
String apkFileName = getBaseName(srcApkFile);
String tempFilePath = outputApkFileParentPath + File.separator +
currentTimeStr() + "-tmp" + File.separator;
unzipApkFilePath = tempFilePath + apkFileName + "-" + UNZIP_APK_FILE_NAME + File.separator;
System.out.println("outputApkFileParentPath: " + outputApkFileParentPath);
System.out.println("unzipApkFilePath = " + unzipApkFilePath);
if (!disableCrackSignature) {
// save the apk original signature info, to support crach signature.
new SaveApkSignatureTask(apkPath, unzipApkFilePath).run();
}
long currentTime = System.currentTimeMillis();
FileUtils.decompressZip(apkPath, unzipApkFilePath);
System.out.println("decompress apk cost time: " + (System.currentTimeMillis() - currentTime) + "ms");
// Get the dex count in the apk zip file
dexFileCount = findDexFileCount(unzipApkFilePath);
System.out.println("dexFileCount: " + dexFileCount);
String manifestFilePath = unzipApkFilePath + "AndroidManifest.xml";
currentTime = System.currentTimeMillis();
// parse the app main application full name from the manifest file
ManifestParser.Pair pair = ManifestParser.parseManifestFile(manifestFilePath);
String applicationName = null;
if (pair != null && pair.applicationName != null) {
applicationName = pair.applicationName;
}
System.out.println("Get application name cost time: " + (System.currentTimeMillis() - currentTime) + "ms");
System.out.println("Get the application name: " + applicationName);
// modify manifest
File manifestFile = new File(manifestFilePath);
String manifestFilePathNew = unzipApkFilePath + "AndroidManifest" + "-" + currentTimeStr() + ".xml";
File manifestFileNew = new File(manifestFilePathNew);
fuckIfFail(manifestFile.renameTo(manifestFileNew));
modifyManifestFile(manifestFilePathNew, manifestFilePath, applicationName);
// new manifest may not exist
if (manifestFile.exists() && manifestFile.length() > 0) {
fuckIfFail(manifestFileNew.delete());
} else {
fuckIfFail(manifestFileNew.renameTo(manifestFile));
}
// save original main application name to asset file
if (isNotEmpty(applicationName)) {
mXpatchTasks.add(new SaveOriginalApplicationNameTask(applicationName, unzipApkFilePath));
}
// modify the apk dex file to make xposed can run in it
if (dexModificationMode && isNotEmpty(applicationName)) {
mXpatchTasks.add(new ApkModifyTask(true, true, unzipApkFilePath, applicationName,
dexFileCount));
}
// copy xposed so and dex files into the unzipped apk
mXpatchTasks.add(new SoAndDexCopyTask(dexFileCount, unzipApkFilePath));
// compress all files into an apk and then sign it.
mXpatchTasks.add(new BuildAndSignApkTask(true, unzipApkFilePath, output));
// copy origin apk to assets
// convenient to bypass some check like CRC
copyFile(srcApkFile, new File(unzipApkFilePath, "assets/origin_apk.bin"));
// excute these tasks
for (Runnable executor : mXpatchTasks) {
currentTime = System.currentTimeMillis();
executor.run();
System.out.println(executor.getClass().getSimpleName() + " cost time: "
+ (System.currentTimeMillis() - currentTime) + "ms");
}
System.out.println("Output APK: " + output);
}
private void modifyManifestFile(String filePath, String dstFilePath, String originalApplicationName) {
ModificationProperty property = new ModificationProperty();
boolean modifyEnabled = false;
if (isNotEmpty(newPackageName)) {
modifyEnabled = true;
property.addManifestAttribute(new AttributeItem(NodeValue.Manifest.PACKAGE, newPackageName).setNamespace(null));
}
if (versionCode > 0) {
modifyEnabled = true;
property.addManifestAttribute(new AttributeItem(NodeValue.Manifest.VERSION_CODE, versionCode));
}
if (isNotEmpty(versionName)) {
modifyEnabled = true;
property.addManifestAttribute(new AttributeItem(NodeValue.Manifest.VERSION_NAME, versionName));
}
if (debuggable >= 0) {
modifyEnabled = true;
property.addApplicationAttribute(new AttributeItem(NodeValue.Application.DEBUGGABLE, debuggable != 0));
}
property.addApplicationAttribute(new AttributeItem("extractNativeLibs", true));
if (!dexModificationMode || !isNotEmpty(originalApplicationName)) {
modifyEnabled = true;
property.addApplicationAttribute(new AttributeItem(NodeValue.Application.NAME, proxyname));
}
if (modifyEnabled) {
FileProcesser.processManifestFile(filePath, dstFilePath, property);
}
}
private int findDexFileCount(String unzipApkFilePath) {
File zipfileRoot = new File(unzipApkFilePath);
if (!zipfileRoot.exists()) {
return 0;
}
File[] childFiles = zipfileRoot.listFiles();
if (childFiles == null || childFiles.length == 0) {
return 0;
}
int count = 0;
for (File file : childFiles) {
String fileName = file.getName();
if (Pattern.matches("classes.*\\.dex", fileName)) {
count++;
}
}
return count;
}
// Use the current timestamp as the name of the build file
private String currentTimeStr() {
SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd-HH-mm-ss");
return df.format(new Date());
}
private String[] getXposedModules(String modules) {
if (modules == null || modules.isEmpty()) {
return null;
}
return modules.split(File.pathSeparator);
}
private static boolean isNotEmpty(String str) {
return str != null && !str.isEmpty();
}
}

View File

@ -1,478 +0,0 @@
package com.storm.wind.xpatch.base;
import java.io.File;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.lang.reflect.Field;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.nio.charset.StandardCharsets;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeSet;
/**
* Created by Wind
*/
public abstract class BaseCommand {
private String onlineHelp;
protected Map<String, Option> optMap = new HashMap<String, Option>();
@Opt(opt = "h", longOpt = "help", hasArg = false, description = "Print this help message")
private boolean printHelp = false;
protected String[] remainingArgs;
protected String[] orginalArgs;
@Retention(value = RetentionPolicy.RUNTIME)
@Target(value = { ElementType.FIELD })
static public @interface Opt {
String argName() default "";
String description() default "";
boolean hasArg() default true;
String longOpt() default "";
String opt() default "";
boolean required() default false;
}
static protected class Option implements Comparable<Option> {
public String argName = "arg";
public String description;
public Field field;
public boolean hasArg = true;
public String longOpt;
public String opt;
public boolean required = false;
@Override
public int compareTo(Option o) {
int result = s(this.opt, o.opt);
if (result == 0) {
result = s(this.longOpt, o.longOpt);
if (result == 0) {
result = s(this.argName, o.argName);
if (result == 0) {
result = s(this.description, o.description);
}
}
}
return result;
}
private static int s(String a, String b) {
if (a != null && b != null) {
return a.compareTo(b);
} else if (a != null) {
return 1;
} else if (b != null) {
return -1;
} else {
return 0;
}
}
public String getOptAndLongOpt() {
StringBuilder sb = new StringBuilder();
boolean havePrev = false;
if (opt != null && opt.length() > 0) {
sb.append("-").append(opt);
havePrev = true;
}
if (longOpt != null && longOpt.length() > 0) {
if (havePrev) {
sb.append(",");
}
sb.append("--").append(longOpt);
}
return sb.toString();
}
}
@Retention(value = RetentionPolicy.RUNTIME)
@Target(value = { ElementType.TYPE })
static public @interface Syntax {
String cmd();
String desc() default "";
String onlineHelp() default "";
String syntax() default "";
}
public void doMain(String... args) {
try {
initOptions();
parseSetArgs(args);
doCommandLine();
} catch (HelpException e) {
String msg = e.getMessage();
if (msg != null && msg.length() > 0) {
System.err.println("ERROR: " + msg);
}
usage();
} catch (Exception e) {
e.printStackTrace(System.err);
}
}
protected abstract void doCommandLine() throws Exception;
protected String getVersionString() {
return getClass().getPackage().getImplementationVersion();
}
protected void initOptions() {
initOptionFromClass(this.getClass());
}
protected void initOptionFromClass(Class<?> clz) {
if (clz == null) {
return;
} else {
initOptionFromClass(clz.getSuperclass());
}
Syntax syntax = clz.getAnnotation(Syntax.class);
if (syntax != null) {
this.onlineHelp = syntax.onlineHelp();
}
Field[] fs = clz.getDeclaredFields();
for (Field f : fs) {
Opt opt = f.getAnnotation(Opt.class);
if (opt != null) {
f.setAccessible(true);
Option option = new Option();
option.field = f;
option.description = opt.description();
option.hasArg = opt.hasArg();
option.required = opt.required();
if ("".equals(opt.longOpt()) && "".equals(opt.opt())) { // into automode
option.longOpt = fromCamel(f.getName());
if (f.getType().equals(boolean.class)) {
option.hasArg=false;
try {
if (f.getBoolean(this)) {
throw new RuntimeException("the value of " + f +
" must be false, as it is declared as no args");
}
} catch (IllegalAccessException e) {
throw new RuntimeException(e);
}
}
checkConflict(option, "--" + option.longOpt);
continue;
}
if (!opt.hasArg()) {
if (!f.getType().equals(boolean.class)) {
throw new RuntimeException("the type of " + f
+ " must be boolean, as it is declared as no args");
}
try {
if (f.getBoolean(this)) {
throw new RuntimeException("the value of " + f +
" must be false, as it is declared as no args");
}
} catch (IllegalAccessException e) {
throw new RuntimeException(e);
}
}
boolean haveLongOpt = false;
if (!"".equals(opt.longOpt())) {
option.longOpt = opt.longOpt();
checkConflict(option, "--" + option.longOpt);
haveLongOpt = true;
}
if (!"".equals(opt.argName())) {
option.argName = opt.argName();
}
if (!"".equals(opt.opt())) {
option.opt = opt.opt();
checkConflict(option, "-" + option.opt);
} else {
if (!haveLongOpt) {
throw new RuntimeException("opt or longOpt is not set in @Opt(...) " + f);
}
}
}
}
}
private void checkConflict(Option option, String key) {
if (optMap.containsKey(key)) {
Option preOption = optMap.get(key);
throw new RuntimeException(String.format("[@Opt(...) %s] conflict with [@Opt(...) %s]",
preOption.field.toString(), option.field
));
}
optMap.put(key, option);
}
private static String fromCamel(String name) {
if (name.length() == 0) {
return "";
}
StringBuilder sb = new StringBuilder();
char[] charArray = name.toCharArray();
sb.append(Character.toLowerCase(charArray[0]));
for (int i = 1; i < charArray.length; i++) {
char c = charArray[i];
if (Character.isUpperCase(c)) {
sb.append("-").append(Character.toLowerCase(c));
} else {
sb.append(c);
}
}
return sb.toString();
}
protected void parseSetArgs(String... args) throws IllegalArgumentException, IllegalAccessException {
this.orginalArgs = args;
List<String> remainsOptions = new ArrayList<String>();
Set<Option> requiredOpts = collectRequriedOptions(optMap);
Option needArgOpt = null;
for (String s : args) {
if (needArgOpt != null) {
Field field = needArgOpt.field;
Class clazz = field.getType();
if (clazz.equals(List.class)) {
try {
List<Object> object = ((List<Object>) field.get(this));
// 获取List对象的泛型类型
ParameterizedType listGenericType = (ParameterizedType) field.getGenericType();
Type[] listActualTypeArguments = listGenericType.getActualTypeArguments();
Class typeClazz = (Class) listActualTypeArguments[0];
object.add(convert(s, typeClazz));
} catch (Exception e) {
e.printStackTrace();
}
} else {
field.set(this, convert(s, clazz));
}
needArgOpt = null;
} else if (s.startsWith("-")) {// its a short or long option
Option opt = optMap.get(s);
requiredOpts.remove(opt);
if (opt == null) {
System.err.println("ERROR: Unrecognized option: " + s);
throw new HelpException();
} else {
if (opt.hasArg) {
needArgOpt = opt;
} else {
opt.field.set(this, true);
}
}
} else {
remainsOptions.add(s);
}
}
if (needArgOpt != null) {
System.err.println("ERROR: Option " + needArgOpt.getOptAndLongOpt() + " need an argument value");
throw new HelpException();
}
this.remainingArgs = remainsOptions.toArray(new String[remainsOptions.size()]);
if (this.printHelp) {
throw new HelpException();
}
if (!requiredOpts.isEmpty()) {
StringBuilder sb = new StringBuilder();
sb.append("ERROR: Options: ");
boolean first = true;
for (Option option : requiredOpts) {
if (first) {
first = false;
} else {
sb.append(" and ");
}
sb.append(option.getOptAndLongOpt());
}
sb.append(" is required");
System.err.println(sb.toString());
throw new HelpException();
}
}
@SuppressWarnings({ "rawtypes", "unchecked" })
protected Object convert(String value, Class type) {
if (type.equals(String.class)) {
return value;
}
if (type.equals(int.class) || type.equals(Integer.class)) {
return Integer.parseInt(value);
}
if (type.equals(long.class) || type.equals(Long.class)) {
return Long.parseLong(value);
}
if (type.equals(float.class) || type.equals(Float.class)) {
return Float.parseFloat(value);
}
if (type.equals(double.class) || type.equals(Double.class)) {
return Double.parseDouble(value);
}
if (type.equals(boolean.class) || type.equals(Boolean.class)) {
return Boolean.parseBoolean(value);
}
if (type.equals(File.class)) {
return new File(value);
}
if (type.equals(Path.class)) {
return new File(value).toPath();
}
try {
type.asSubclass(Enum.class);
return Enum.valueOf(type, value);
} catch (Exception e) {
}
throw new RuntimeException("can't convert [" + value + "] to type " + type);
}
private Set<Option> collectRequriedOptions(Map<String, Option> optMap) {
Set<Option> options = new HashSet<Option>();
for (Map.Entry<String, Option> e : optMap.entrySet()) {
Option option = e.getValue();
if (option.required) {
options.add(option);
}
}
return options;
}
@SuppressWarnings("serial")
protected static class HelpException extends RuntimeException {
public HelpException() {
super();
}
public HelpException(String message) {
super(message);
}
}
protected void usage() {
PrintWriter out = new PrintWriter(new OutputStreamWriter(System.err, StandardCharsets.UTF_8), true);
final int maxLength = 80;
final int maxPaLength = 40;
// out.println(this.cmdName + " -- " + desc);
// out.println("usage: " + this.cmdName + " " + cmdLineSyntax);
if (this.optMap.size() > 0) {
out.println("options:");
}
// [PART.A.........][Part.B
// .-a,--aa.<arg>...desc1
// .................desc2
// .-b,--bb
TreeSet<Option> options = new TreeSet<Option>(this.optMap.values());
int palength = -1;
for (Option option : options) {
int pa = 4 + option.getOptAndLongOpt().length();
if (option.hasArg) {
pa += 3 + option.argName.length();
}
if (pa < maxPaLength) {
if (pa > palength) {
palength = pa;
}
}
}
int pblength = maxLength - palength;
StringBuilder sb = new StringBuilder();
for (Option option : options) {
sb.setLength(0);
sb.append(" ").append(option.getOptAndLongOpt());
if (option.hasArg) {
sb.append(" <").append(option.argName).append(">");
}
String desc = option.description;
if (desc == null || desc.length() == 0) {// no description
out.println(sb);
} else {
for (int i = palength - sb.length(); i > 0; i--) {
sb.append(' ');
}
if (sb.length() > maxPaLength) {// to huge part A
out.println(sb);
sb.setLength(0);
for (int i = 0; i < palength; i++) {
sb.append(' ');
}
}
int nextStart = 0;
while (nextStart < desc.length()) {
if (desc.length() - nextStart < pblength) {// can put in one line
sb.append(desc.substring(nextStart));
out.println(sb);
nextStart = desc.length();
sb.setLength(0);
} else {
sb.append(desc.substring(nextStart, nextStart + pblength));
out.println(sb);
nextStart += pblength;
sb.setLength(0);
if (nextStart < desc.length()) {
for (int i = 0; i < palength; i++) {
sb.append(' ');
}
}
}
}
if (sb.length() > 0) {
out.println(sb);
sb.setLength(0);
}
}
}
String ver = getVersionString();
if (ver != null && !"".equals(ver)) {
out.println("version: " + ver);
}
if (onlineHelp != null && !"".equals(onlineHelp)) {
if (onlineHelp.length() + "online help: ".length() > maxLength) {
out.println("online help: ");
out.println(onlineHelp);
} else {
out.println("online help: " + onlineHelp);
}
}
out.flush();
}
public static String getBaseName(String fn) {
int x = fn.lastIndexOf('.');
return x >= 0 ? fn.substring(0, x) : fn;
}
// 获取文件不包含后缀的名称
public static String getBaseName(File fn) {
return getBaseName(fn.getName());
}
}

View File

@ -1,119 +0,0 @@
package com.storm.wind.xpatch.task;
import com.googlecode.dex2jar.tools.Dex2jarCmd;
import com.googlecode.dex2jar.tools.Jar2Dex;
import java.io.File;
import java.util.ArrayList;
/**
* Created by Wind
*/
public class ApkModifyTask implements Runnable {
private static final String JAR_FILE_NAME = "output-jar.jar";
private String unzipApkFilePath;
private boolean keepJarFile;
private boolean showAllLogs;
private String applicationName;
private int dexFileCount;
public ApkModifyTask(boolean showAllLogs, boolean keepJarFile, String unzipApkFilePath, String applicationName, int
dexFileCount) {
this.showAllLogs = showAllLogs;
this.unzipApkFilePath = unzipApkFilePath;
this.keepJarFile = keepJarFile;
this.applicationName = applicationName;
this.dexFileCount = dexFileCount;
}
@Override
public void run() {
File unzipApkFile = new File(unzipApkFilePath);
String jarOutputPath = unzipApkFile.getParent() + File.separator + JAR_FILE_NAME;
// classes.dex
String targetDexFileName = dumpJarFile(dexFileCount, unzipApkFilePath, jarOutputPath, applicationName);
if (showAllLogs) {
System.out.println(" the application class is in this dex file = " + targetDexFileName);
}
String dexOutputPath = unzipApkFilePath + targetDexFileName;
File dexFile = new File(dexOutputPath);
if (dexFile.exists()) {
dexFile.delete();
}
// 将jar转换为dex文件
jar2DexCmd(jarOutputPath, dexOutputPath);
// 删除掉jar文件
File jarFile = new File(jarOutputPath);
if (!keepJarFile && jarFile.exists()) {
jarFile.delete();
}
}
private String dumpJarFile(int dexFileCount, String dexFilePath, String jarOutputPath, String applicationName) {
ArrayList<String> dexFileList = createClassesDotDexFileList(dexFileCount);
// String jarOutputPath = dexFilePath + JAR_FILE_NAME;
for (String dexFileName : dexFileList) {
String filePath = dexFilePath + dexFileName;
// 执行dex2jar命令修改源代码
boolean isApplicationClassFound = dex2JarCmd(filePath, jarOutputPath, applicationName);
// 找到了目标应用主application的包名说明代码注入成功则返回当前dex文件
if (isApplicationClassFound) {
return dexFileName;
}
}
return "";
}
private boolean dex2JarCmd(String dexPath, String jarOutputPath, String applicationName) {
Dex2jarCmd cmd = new Dex2jarCmd();
String[] args = new String[]{
dexPath,
"-o",
jarOutputPath,
"-app",
applicationName,
"--force"
};
cmd.doMain(args);
boolean isApplicationClassFounded = cmd.isApplicationClassFounded();
if (showAllLogs) {
System.out.println("isApplicationClassFounded -> " + isApplicationClassFounded + "the dexPath is " +
dexPath);
}
return isApplicationClassFounded;
}
private void jar2DexCmd(String jarFilePath, String dexOutPath) {
Jar2Dex cmd = new Jar2Dex();
String[] args = new String[]{
jarFilePath,
"-o",
dexOutPath
};
cmd.doMain(args);
}
// 列出目录下所有dex文件classes.dexclasses2.dexclasses3.dex .....
private ArrayList<String> createClassesDotDexFileList(int dexFileCount) {
ArrayList<String> list = new ArrayList<>();
for (int i = 0; i < dexFileCount; i++) {
if (i == 0) {
list.add("classes.dex");
} else {
list.add("classes" + (i + 1) + ".dex");
}
}
return list;
}
}

View File

@ -1,151 +0,0 @@
package com.storm.wind.xpatch.task;
import com.android.apksigner.ApkSignerTool;
import com.storm.wind.xpatch.util.FileUtils;
import com.storm.wind.xpatch.util.ShellCmdUtil;
import java.io.File;
import java.util.ArrayList;
/**
* Created by Wind
*/
public class BuildAndSignApkTask implements Runnable {
private boolean keepUnsignedApkFile;
private String signedApkPath;
private String unzipApkFilePath;
public BuildAndSignApkTask(boolean keepUnsignedApkFile, String unzipApkFilePath, String signedApkPath) {
this.keepUnsignedApkFile = keepUnsignedApkFile;
this.unzipApkFilePath = unzipApkFilePath;
this.signedApkPath = signedApkPath;
}
@Override
public void run() {
File unzipApkFile = new File(unzipApkFilePath);
// 将文件压缩到当前apk文件的上一级目录上
String unsignedApkPath = unzipApkFile.getParent() + File.separator + "unsigned.apk";
FileUtils.compressToZip(unzipApkFilePath, unsignedApkPath);
// 将签名文件复制从assets目录下复制出来
String keyStoreFilePath = unzipApkFile.getParent() + File.separator + "keystore";
File keyStoreFile = new File(keyStoreFilePath);
// assets/keystore分隔符不能使用File.separator否则在windows上抛出IOException !!!
String keyStoreAssetPath;
if (isAndroid()) {
// BKS-V1 类型
keyStoreAssetPath = "assets/android.keystore";
} else {
// BKS 类型
keyStoreAssetPath = "assets/keystore";
}
FileUtils.copyFileFromJar(keyStoreAssetPath, keyStoreFilePath);
boolean signResult = signApk(unsignedApkPath, keyStoreFilePath, signedApkPath);
File unsignedApkFile = new File(unsignedApkPath);
File signedApkFile = new File(signedApkPath);
// delete unsigned apk file
if (!keepUnsignedApkFile && unsignedApkFile.exists() && signedApkFile.exists() && signResult) {
unsignedApkFile.delete();
}
// delete the keystore file
if (keyStoreFile.exists()) {
keyStoreFile.delete();
}
}
private boolean signApk(String apkPath, String keyStorePath, String signedApkPath) {
if (signApkUsingAndroidApksigner(apkPath, keyStorePath, signedApkPath, "123456")) {
return true;
}
if (isAndroid()) {
System.out.println(" Sign apk failed, please sign it yourself.");
return false;
}
try {
long time = System.currentTimeMillis();
File keystoreFile = new File(keyStorePath);
if (keystoreFile.exists()) {
StringBuilder signCmd;
signCmd = new StringBuilder("jarsigner ");
signCmd.append(" -keystore ")
.append(keyStorePath)
.append(" -storepass ")
.append("123456")
.append(" -signedjar ")
.append(" " + signedApkPath + " ")
.append(" " + apkPath + " ")
.append(" -digestalg SHA1 -sigalg SHA1withRSA ")
.append(" key0 ");
// System.out.println("\n" + signCmd + "\n");
String result = ShellCmdUtil.execCmd(signCmd.toString(), null);
System.out.println(" sign apk time is :" + ((System.currentTimeMillis() - time) / 1000) +
"s\n\n" + " result=" + result);
return true;
}
System.out.println(" keystore not exist :" + keystoreFile.getAbsolutePath() +
" please sign the apk by hand. \n");
return false;
} catch (Throwable e) {
System.out.println("use default jarsigner to sign apk failed, fail msg is :" +
e.toString());
return false;
}
}
private boolean isAndroid() {
boolean isAndroid = true;
try {
Class.forName("android.content.Context");
} catch (ClassNotFoundException e) {
isAndroid = false;
}
return isAndroid;
}
// 使用Android build-tools里自带的apksigner工具进行签名
private boolean signApkUsingAndroidApksigner(String apkPath, String keyStorePath, String signedApkPath, String keyStorePassword) {
ArrayList<String> commandList = new ArrayList<>();
commandList.add("sign");
commandList.add("--ks");
commandList.add(keyStorePath);
commandList.add("--ks-key-alias");
commandList.add("key0");
commandList.add("--ks-pass");
commandList.add("pass:" + keyStorePassword);
commandList.add("--key-pass");
commandList.add("pass:" + keyStorePassword);
commandList.add("--out");
commandList.add(signedApkPath);
commandList.add("--v1-signing-enabled");
commandList.add("true");
commandList.add("--v2-signing-enabled"); // v2签名不兼容android 6
commandList.add("false");
commandList.add("--v3-signing-enabled"); // v3签名不兼容android 6
commandList.add("false");
commandList.add(apkPath);
int size = commandList.size();
String[] commandArray = new String[size];
commandArray = commandList.toArray(commandArray);
try {
ApkSignerTool.main(commandArray);
} catch (Exception e) {
e.printStackTrace();
return false;
}
return true;
}
}

View File

@ -1,43 +0,0 @@
package com.storm.wind.xpatch.task;
import com.storm.wind.xpatch.util.ApkSignatureHelper;
import com.storm.wind.xpatch.util.FileUtils;
import java.io.File;
/**
* Created by Wind
*/
public class SaveApkSignatureTask implements Runnable {
private String apkPath;
private String dstFilePath;
private final static String SIGNATURE_INFO_ASSET_PATH = "assets/original_signature_info.ini";
public SaveApkSignatureTask(String apkPath, String unzipApkFilePath) {
this.apkPath = apkPath;
this.dstFilePath = (unzipApkFilePath + SIGNATURE_INFO_ASSET_PATH).replace("/", File.separator);
}
@Override
public void run() {
// First, get the original signature
String originalSignature = ApkSignatureHelper.getApkSignInfo(apkPath);
if (originalSignature == null || originalSignature.isEmpty()) {
System.out.println(" Get original signature failed !!!!");
return;
}
// Then, save the signature chars to the asset file
File file = new File(dstFilePath);
File fileParent = file.getParentFile();
if (!fileParent.exists()) {
if(!fileParent.mkdirs()){
System.out.println("mkdir fails " + fileParent.getAbsolutePath());
}
}
FileUtils.writeFile(dstFilePath, originalSignature);
}
}

View File

@ -1,44 +0,0 @@
package com.storm.wind.xpatch.task;
import com.storm.wind.xpatch.util.FileUtils;
import java.io.File;
/**
* Created by xiawanli on 2019/4/6
*/
public class SaveOriginalApplicationNameTask implements Runnable {
private final String applcationName;
private final String unzipApkFilePath;
private String dstFilePath;
private final String APPLICATION_NAME_ASSET_PATH = "assets/original_application_name.ini";
public SaveOriginalApplicationNameTask(String applicationName, String unzipApkFilePath) {
this.applcationName = applicationName;
this.unzipApkFilePath = unzipApkFilePath;
this.dstFilePath = (unzipApkFilePath + APPLICATION_NAME_ASSET_PATH).replace("/", File.separator);
}
@Override
public void run() {
ensureDstFileCreated();
FileUtils.writeFile(dstFilePath, applcationName);
}
private void ensureDstFileCreated() {
File dstParentFile = new File(dstFilePath);
if (!dstParentFile.getParentFile().getParentFile().exists()) {
if(!dstParentFile.getParentFile().getParentFile().mkdirs()){
throw new IllegalStateException("mkdir fail");
}
}
if (!dstParentFile.getParentFile().exists()) {
if(!dstParentFile.getParentFile().mkdirs()){
throw new IllegalStateException("mkdir fail");
}
}
}
}

View File

@ -1,134 +0,0 @@
package com.storm.wind.xpatch.task;
import org.apache.commons.io.FileUtils;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
/**
* Created by Wind
*/
public class SoAndDexCopyTask implements Runnable {
private final String[] APK_LIB_PATH_ARRAY = {
"lib/armeabi-v7a/",
"lib/armeabi/",
"lib/arm64-v8a/",
"lib/x86",
"lib/x86_64"
};
private int dexFileCount;
private String unzipApkFilePath;
public SoAndDexCopyTask(int dexFileCount, String unzipApkFilePath) {
this.dexFileCount = dexFileCount;
this.unzipApkFilePath = unzipApkFilePath;
}
@Override
public void run() {
// 复制xposed兼容层的dex文件以及so文件到当前目录下
copySoFile();
copyDexFile(dexFileCount);
// 删除签名信息
deleteMetaInfo();
}
private void copySoFile() {
List<String> existLibPathArray = new ArrayList<>();
for (String libPath : APK_LIB_PATH_ARRAY) {
String apkSoFullPath = fullLibPath(libPath);
File apkSoFullPathFile = new File(apkSoFullPath);
if (apkSoFullPathFile.exists()) {
existLibPathArray.add(libPath);
} else {
System.out.println("target app dont have " + libPath + ", skip");
}
}
if (existLibPathArray.isEmpty()) {
System.out.println("target app dont have any so in \"lib/{eabi}\" dir, so create default \"armeabi-v7a\"");
String libPath = APK_LIB_PATH_ARRAY[0];
String apkSoFullPath = fullLibPath(libPath);
File apkSoFullPathFile = new File(apkSoFullPath);
if (apkSoFullPathFile.mkdirs()) {
throw new IllegalStateException("mkdir fail " + apkSoFullPathFile.getAbsolutePath());
}
existLibPathArray.add(libPath);
}
for (String libPath : existLibPathArray) {
if (libPath == null || libPath.isEmpty()) {
throw new IllegalStateException("fail eabi path");
}
String apkSoFullPath = fullLibPath(libPath);
String eabi = libPath.substring(libPath.indexOf("/"));
if (eabi.isEmpty()) {
throw new IllegalStateException("fail find eabi in " + libPath);
}
File[] files = new File("list-so", eabi).listFiles();
if (files == null) {
System.out.println("Warning: Nothing so file has been copied in " + libPath);
continue;
}
for (File mySoFile : files) {
File target = new File(apkSoFullPath, mySoFile.getName());
try {
FileUtils.copyFile(mySoFile, target);
} catch (Exception err) {
throw new IllegalStateException("wtf", err);
}
System.out.println("Copy " + mySoFile.getAbsolutePath() + " to " + target.getAbsolutePath());
}
}
}
private void copyDexFile(int dexFileCount) {
try {
// copy all dex files in list-dex
File[] files = new File("list-dex").listFiles();
if (files == null || files.length == 0) {
System.out.println("Warning: Nothing dex file has been copied");
return;
}
for (File file : files) {
String copiedDexFileName = "classes" + (dexFileCount + 1) + ".dex";
File target = new File(unzipApkFilePath, copiedDexFileName);
FileUtils.copyFile(file, target);
System.out.println("Copy " + file.getAbsolutePath() + " to " + target.getAbsolutePath());
dexFileCount++;
}
} catch (Exception err) {
throw new IllegalStateException("wtf", err);
}
}
private String fullLibPath(String libPath) {
return unzipApkFilePath + libPath.replace("/", File.separator);
}
private void deleteMetaInfo() {
String metaInfoFilePath = "META-INF";
File metaInfoFileRoot = new File(unzipApkFilePath + metaInfoFilePath);
if (!metaInfoFileRoot.exists()) {
return;
}
File[] childFileList = metaInfoFileRoot.listFiles();
if (childFileList == null || childFileList.length == 0) {
return;
}
for (File file : childFileList) {
String fileName = file.getName().toUpperCase();
if (fileName.endsWith(".MF") || fileName.endsWith(".RAS") || fileName.endsWith(".SF")) {
file.delete();
}
}
}
}

View File

@ -1,82 +0,0 @@
package com.storm.wind.xpatch.util;
import java.io.InputStream;
import java.security.cert.Certificate;
import java.util.Enumeration;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
/**
* Created by Wind
*/
public class ApkSignatureHelper {
private static char[] toChars(byte[] mSignature) {
byte[] sig = mSignature;
final int N = sig.length;
final int N2 = N * 2;
char[] text = new char[N2];
for (int j = 0; j < N; j++) {
byte v = sig[j];
int d = (v >> 4) & 0xf;
text[j * 2] = (char) (d >= 10 ? ('a' + d - 10) : ('0' + d));
d = v & 0xf;
text[j * 2 + 1] = (char) (d >= 10 ? ('a' + d - 10) : ('0' + d));
}
return text;
}
private static Certificate[] loadCertificates(JarFile jarFile, JarEntry je, byte[] readBuffer) {
try {
InputStream is = jarFile.getInputStream(je);
while (is.read(readBuffer, 0, readBuffer.length) != -1) {
}
is.close();
return (Certificate[]) (je != null ? je.getCertificates() : null);
} catch (Exception e) {
}
return null;
}
public static String getApkSignInfo(String apkFilePath) {
byte[] readBuffer = new byte[8192];
Certificate[] certs = null;
try {
JarFile jarFile = new JarFile(apkFilePath);
Enumeration<?> entries = jarFile.entries();
while (entries.hasMoreElements()) {
JarEntry je = (JarEntry) entries.nextElement();
if (je.isDirectory()) {
continue;
}
if (je.getName().startsWith("META-INF/")) {
continue;
}
Certificate[] localCerts = loadCertificates(jarFile, je, readBuffer);
if (certs == null) {
certs = localCerts;
} else {
for (int i = 0; i < certs.length; i++) {
boolean found = false;
for (int j = 0; j < localCerts.length; j++) {
if (certs[i] != null && certs[i].equals(localCerts[j])) {
found = true;
break;
}
}
if (!found || certs.length != localCerts.length) {
jarFile.close();
return null;
}
}
}
}
jarFile.close();
System.out.println("getApkSignInfo result: " + certs[0]);
return new String(toChars(certs[0].getEncoded()));
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
}

View File

@ -1,239 +0,0 @@
package com.storm.wind.xpatch.util;
import org.apache.commons.io.FileSystemUtils;
import org.apache.commons.io.IOUtils;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.BufferedWriter;
import java.io.Closeable;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.charset.Charset;
import java.util.Enumeration;
import java.util.zip.CRC32;
import java.util.zip.CheckedOutputStream;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
import java.util.zip.ZipOutputStream;
/**
* Created by Wind
*/
public class FileUtils {
static final int BUFFER = 8192;
/**
* 解压文件
*
* @param zipPath 要解压的目标文件
* @param descDir 指定解压目录
* @return 解压结果成功失败
*/
@SuppressWarnings("rawtypes")
public static void decompressZip(String zipPath, String descDir) throws IOException {
File zipFile = new File(zipPath);
if (!descDir.endsWith(File.separator)) {
descDir = descDir + File.separator;
}
File pathFile = new File(descDir);
if (!pathFile.exists()) {
if (!pathFile.mkdirs()) {
throw new IllegalStateException("mkdir fail " + pathFile.getAbsolutePath());
}
}
try (ZipFile zip = new ZipFile(zipFile, Charset.forName("gbk"))) {
for (Enumeration entries = zip.entries(); entries.hasMoreElements(); ) {
ZipEntry entry = (ZipEntry) entries.nextElement();
String zipEntryName = entry.getName();
String outPath = (descDir + zipEntryName).replace("/", File.separator);
File file = new File(outPath);
if (entry.isDirectory()) {
if (!file.mkdirs()) {
throw new IllegalStateException("mkdir fail " + file.getAbsolutePath());
}
continue;
}
try (InputStream in = zip.getInputStream(entry)) {
if (file.getParentFile() != null && !file.getParentFile().exists()) {
if (!file.getParentFile().mkdirs()) {
throw new IllegalStateException("mkdir fail " + file.getAbsolutePath());
}
if (System.getProperty("os.name", "").toLowerCase().contains("win")) {
Runtime.getRuntime().exec("fsutil file setCaseSensitiveInfo " + file.getParentFile().getAbsolutePath());
System.out.println("Enable setCaseSensitiveInfo for " + file.getParentFile().getAbsolutePath());
}
}
OutputStream out = new FileOutputStream(outPath);
IOUtils.copy(in, out);
out.close();
} catch (Exception err) {
throw new IllegalStateException("wtf", err);
}
}
}
}
private static InputStream getInputStreamFromFile(String filePath) {
return FileUtils.class.getClassLoader().getResourceAsStream(filePath);
}
// copy an asset file into a path
public static void copyFileFromJar(String inJarPath, String distPath) {
// System.out.println("start copyFile inJarPath =" + inJarPath + " distPath = " + distPath);
InputStream inputStream = getInputStreamFromFile(inJarPath);
BufferedInputStream in = null;
BufferedOutputStream out = null;
try {
in = new BufferedInputStream(inputStream);
out = new BufferedOutputStream(new FileOutputStream(distPath));
int len = -1;
byte[] b = new byte[1024];
while ((len = in.read(b)) != -1) {
out.write(b, 0, len);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
close(out);
close(in);
}
}
public static void compressToZip(String srcPath, String dstPath) {
File srcFile = new File(srcPath);
File dstFile = new File(dstPath);
if (!srcFile.exists()) {
System.out.println(srcPath + " does not exist ");
return;
}
FileOutputStream out = null;
ZipOutputStream zipOut = null;
try {
out = new FileOutputStream(dstFile);
CheckedOutputStream cos = new CheckedOutputStream(out, new CRC32());
zipOut = new ZipOutputStream(cos);
String baseDir = "";
compress(srcFile, zipOut, baseDir, true);
} catch (IOException e) {
System.out.println(" compress exception = " + e.getMessage());
} finally {
try {
if (zipOut != null) {
zipOut.closeEntry();
}
} catch (IOException e) {
e.printStackTrace();
}
close(zipOut);
close(out);
}
}
private static void compress(File file, ZipOutputStream zipOut, String baseDir, boolean isRootDir) throws IOException {
if (file.isDirectory()) {
compressDirectory(file, zipOut, baseDir, isRootDir);
} else {
compressFile(file, zipOut, baseDir);
}
}
/**
* 压缩一个目录
*/
private static void compressDirectory(File dir, ZipOutputStream zipOut, String baseDir, boolean isRootDir) throws IOException {
File[] files = dir.listFiles();
if (files == null) {
return;
}
for (int i = 0; i < files.length; i++) {
String compressBaseDir = "";
if (!isRootDir) {
compressBaseDir = baseDir + dir.getName() + "/";
}
compress(files[i], zipOut, compressBaseDir, false);
}
}
/**
* 压缩一个文件
*/
private static void compressFile(File file, ZipOutputStream zipOut, String baseDir) throws IOException {
if (!file.exists()) {
return;
}
BufferedInputStream bis = null;
try {
bis = new BufferedInputStream(new FileInputStream(file));
ZipEntry entry = new ZipEntry(baseDir + file.getName());
zipOut.putNextEntry(entry);
int count;
byte data[] = new byte[BUFFER];
while ((count = bis.read(data, 0, BUFFER)) != -1) {
zipOut.write(data, 0, count);
}
} finally {
if (null != bis) {
bis.close();
}
}
}
public static void writeFile(String filePath, String content) {
if (filePath == null || filePath.isEmpty()) {
return;
}
if (content == null || content.isEmpty()) {
return;
}
File dstFile = new File(filePath);
if (!dstFile.getParentFile().exists()) {
dstFile.getParentFile().mkdirs();
}
FileOutputStream outputStream = null;
BufferedWriter writer = null;
try {
outputStream = new FileOutputStream(dstFile);
writer = new BufferedWriter(new OutputStreamWriter(outputStream));
writer.write(content);
writer.flush();
} catch (Exception e) {
e.printStackTrace();
} finally {
close(outputStream);
close(writer);
}
}
private static void close(Closeable closeable) {
try {
if (closeable != null) {
closeable.close();
}
} catch (IOException io) {
io.printStackTrace();
}
}
}

View File

@ -1,91 +0,0 @@
package com.storm.wind.xpatch.util;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import wind.android.content.res.AXmlResourceParser;
import wind.v1.XmlPullParser;
import wind.v1.XmlPullParserException;
/**
* Created by Wind
*/
public class ManifestParser {
/**
* Get the package name and the main application name from the manifest file
* */
public static Pair parseManifestFile(String filePath) {
AXmlResourceParser parser = new AXmlResourceParser();
File file = new File(filePath);
String packageName = null;
String applicationName = null;
if (!file.exists()) {
System.out.println(" manifest file not exist!!! filePath -> " + filePath);
return null;
}
FileInputStream inputStream = null;
try {
inputStream = new FileInputStream(file);
parser.open(inputStream);
while (true) {
int type = parser.next();
if (type == XmlPullParser.END_DOCUMENT) {
break;
}
if (type == XmlPullParser.START_TAG) {
int attrCount = parser.getAttributeCount();
for (int i = 0; i < attrCount; i++) {
String attrName = parser.getAttributeName(i);
String name = parser.getName();
if ("manifest".equals(name)) {
if ("package".equals(attrName)) {
packageName = parser.getAttributeValue(i);
}
}
if ("application".equals(name)) {
if ("name".equals(attrName)) {
applicationName = parser.getAttributeValue(i);
}
}
if (packageName != null && packageName.length() > 0 && applicationName != null && applicationName.length() > 0) {
return new Pair(packageName, applicationName);
}
}
} else if (type == XmlPullParser.END_TAG) {
// ignored
}
}
} catch (XmlPullParserException | IOException e) {
e.printStackTrace();
System.out.println("parseManifestFile failed, reason --> " + e.getMessage());
} finally {
if (inputStream != null) {
try {
inputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
return new Pair(packageName, applicationName);
}
public static class Pair {
public String packageName;
public String applicationName;
public Pair(String packageName, String applicationName) {
this.packageName = packageName;
this.applicationName = applicationName;
}
}
}

View File

@ -1,267 +0,0 @@
package com.storm.wind.xpatch.util;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
public class ReflectUtils {
//获取类的实例的变量的值
public static Object getField(Object receiver, String fieldName) {
return getField(null, receiver, fieldName);
}
//获取类的静态变量的值
public static Object getField(String className, String fieldName) {
return getField(className, null, fieldName);
}
public static Object getField(Class<?> clazz, String className, String fieldName, Object receiver) {
try {
if (clazz == null) {
clazz = Class.forName(className);
}
Field field = clazz.getDeclaredField(fieldName);
if (field == null) {
return null;
}
field.setAccessible(true);
return field.get(receiver);
} catch (Throwable e) {
e.printStackTrace();
}
return null;
}
private static Object getField(String className, Object receiver, String fieldName) {
Class<?> clazz = null;
Field field;
if (className != null && className.length() > 0) {
try {
clazz = Class.forName(className);
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
} else {
if (receiver != null) {
clazz = receiver.getClass();
}
}
if (clazz == null) {
return null;
}
try {
field = findField(clazz, fieldName);
if (field == null) {
return null;
}
field.setAccessible(true);
return field.get(receiver);
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (IllegalArgumentException e) {
e.printStackTrace();
} catch (NullPointerException e) {
e.printStackTrace();
}
return null;
}
public static Object setField(Object receiver, String fieldName, Object value) {
try {
Field field;
field = findField(receiver.getClass(), fieldName);
if (field == null) {
return null;
}
field.setAccessible(true);
Object old = field.get(receiver);
field.set(receiver, value);
return old;
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (IllegalArgumentException e) {
e.printStackTrace();
}
return null;
}
public static Object setField(Class<?> clazz, Object receiver, String fieldName, Object value) {
try {
Field field;
field = findField(clazz, fieldName);
if (field == null) {
return null;
}
field.setAccessible(true);
Object old = field.get(receiver);
field.set(receiver, value);
return old;
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (IllegalArgumentException e) {
e.printStackTrace();
}
return null;
}
public static Object callMethod(Object receiver, String methodName, Object... params) {
return callMethod(null, receiver, methodName, params);
}
public static Object setField(String clazzName, Object receiver, String fieldName, Object value) {
try {
Class<?> clazz = Class.forName(clazzName);
Field field;
field = findField(clazz, fieldName);
if (field == null) {
return null;
}
field.setAccessible(true);
Object old = field.get(receiver);
field.set(receiver, value);
return old;
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (IllegalArgumentException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
return null;
}
public static Object callMethod(String className, String methodName, Object... params) {
return callMethod(className, null, methodName, params);
}
public static Object callMethod(Class<?> clazz, String className, String methodName, Object receiver,
Class[] types, Object... params) {
try {
if (clazz == null) {
clazz = Class.forName(className);
}
Method method = clazz.getDeclaredMethod(methodName, types);
method.setAccessible(true);
return method.invoke(receiver, params);
} catch (Throwable throwable) {
throwable.printStackTrace();
}
return null;
}
private static Object callMethod(String className, Object receiver, String methodName, Object... params) {
Class<?> clazz = null;
if (className != null && className.length() > 0) {
try {
clazz = Class.forName(className);
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
} else {
if (receiver != null) {
clazz = receiver.getClass();
}
}
if (clazz == null) {
return null;
}
try {
Method method = findMethod(clazz, methodName, params);
if (method == null) {
return null;
}
method.setAccessible(true);
return method.invoke(receiver, params);
} catch (IllegalArgumentException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
return null;
}
private static Method findMethod(Class<?> clazz, String name, Object... arg) {
Method[] methods = clazz.getMethods();
Method method = null;
for (Method m : methods) {
if (methodFitParam(m, name, arg)) {
method = m;
break;
}
}
if (method == null) {
method = findDeclaredMethod(clazz, name, arg);
}
return method;
}
private static Method findDeclaredMethod(Class<?> clazz, String name, Object... arg) {
Method[] methods = clazz.getDeclaredMethods();
Method method = null;
for (Method m : methods) {
if (methodFitParam(m, name, arg)) {
method = m;
break;
}
}
if (method == null) {
if (clazz.equals(Object.class)) {
return null;
}
return findDeclaredMethod(clazz.getSuperclass(), name, arg);
}
return method;
}
private static boolean methodFitParam(Method method, String methodName, Object... arg) {
if (!methodName.equals(method.getName())) {
return false;
}
Class<?>[] paramTypes = method.getParameterTypes();
if (arg == null || arg.length == 0) {
return paramTypes == null || paramTypes.length == 0;
}
if (paramTypes.length != arg.length) {
return false;
}
for (int i = 0; i < arg.length; ++i) {
Object ar = arg[i];
Class<?> paramT = paramTypes[i];
if (ar == null) {
continue;
}
//TODO for primitive type
if (paramT.isPrimitive()) {
continue;
}
if (!paramT.isInstance(ar)) {
return false;
}
}
return true;
}
private static Field findField(Class<?> clazz, String name) {
try {
return clazz.getDeclaredField(name);
} catch (NoSuchFieldException e) {
if (clazz.equals(Object.class)) {
e.printStackTrace();
return null;
}
Class<?> base = clazz.getSuperclass();
return findField(base, name);
}
}
}

View File

@ -1,120 +0,0 @@
package com.storm.wind.xpatch.util;
import java.io.BufferedReader;
import java.io.Closeable;
import java.io.File;
import java.io.InputStreamReader;
/**
* Created by Wind
*/
public class ShellCmdUtil {
/**
* 执行系统命令, 返回执行结果
*
* @param cmd 需要执行的命令
* @param dir 执行命令的子进程的工作目录, null 表示和当前主进程工作目录相同
*/
public static String execCmd(String cmd, File dir) throws Exception {
StringBuilder result = new StringBuilder();
Process process = null;
BufferedReader bufrIn = null;
BufferedReader bufrError = null;
try {
// 执行命令, 返回一个子进程对象命令在子进程中执行
process = Runtime.getRuntime().exec(cmd, null, dir);
// 方法阻塞, 等待命令执行完成成功会返回0
process.waitFor();
// 获取命令执行结果, 有两个结果: 正常的输出 错误的输出PS: 子进程的输出就是主进程的输入
bufrIn = new BufferedReader(new InputStreamReader(process.getInputStream(), "UTF-8"));
bufrError = new BufferedReader(new InputStreamReader(process.getErrorStream(), "UTF-8"));
// 读取输出
String line = null;
while ((line = bufrIn.readLine()) != null) {
result.append(line).append('\n');
}
while ((line = bufrError.readLine()) != null) {
result.append(line).append('\n');
}
} finally {
close(bufrIn);
close(bufrError);
// 销毁子进程
if (process != null) {
process.destroy();
}
}
// 返回执行结果
return result.toString();
}
public static void chmodNoException(String path, int mode) {
try {
chmod(path, mode);
} catch (Exception e) {
e.printStackTrace();
System.err.println("chmod exception path --> " + path + " exception -->" + e.getMessage());
}
}
public static void chmod(String path, int mode) throws Exception {
chmodOnAndroid(path, mode);
File file = new File(path);
String cmd = "chmod ";
if (file.isDirectory()) {
cmd += " -R ";
}
String cmode = String.format("%o", mode);
Runtime.getRuntime().exec(cmd + cmode + " " + path).waitFor();
}
private static void chmodOnAndroid(String path, int mode) {
Object sdk_int = ReflectUtils.getField("android.os.Build$VERSION", "SDK_INT");
if (!(sdk_int instanceof Integer)) {
return;
}
if ((int)sdk_int >= 21) {
System.out.println("chmod on android is called, path = " + path);
ReflectUtils.callMethod("android.system.Os", "chmod", path, mode);
}
}
private static void close(Closeable stream) {
if (stream != null) {
try {
stream.close();
} catch (Exception e) {
// nothing
}
}
}
public interface FileMode {
int MODE_ISUID = 04000;
int MODE_ISGID = 02000;
int MODE_ISVTX = 01000;
int MODE_IRUSR = 00400;
int MODE_IWUSR = 00200;
int MODE_IXUSR = 00100;
int MODE_IRGRP = 00040;
int MODE_IWGRP = 00020;
int MODE_IXGRP = 00010;
int MODE_IROTH = 00004;
int MODE_IWOTH = 00002;
int MODE_IXOTH = 00001;
int MODE_755 = MODE_IRUSR | MODE_IWUSR | MODE_IXUSR
| MODE_IRGRP | MODE_IXGRP
| MODE_IROTH | MODE_IXOTH;
}
}