refine
This commit is contained in:
parent
7fd0ad99ac
commit
aa5de4e166
256
README.md
256
README.md
|
|
@ -1,247 +1,69 @@
|
||||||
### [**English Version**](https://github.com/WindySha/Xpatch/blob/6ec0f3c16128dda46ab05bdd915d66ebbdaaf9fc/README_en.md)
|
# What is MMPatch
|
||||||
|
|
||||||
# Android App破解工具Xpatch的使用方法
|
fork from [Xpatch][11]
|
||||||
|
|
||||||
## Xpatch概述
|
MMPatch is a jar tool which is used to repackage the apk file. Then, the new apk can load any Xposed modules installed in the android system.
|
||||||
Xpatch用来重新签名打包Apk文件,使重打包后的Apk能加载安装在系统里的Xposed插件,从而实现免Root Hook任意App。
|
|
||||||
|
|
||||||
## Xpatch基本原理
|
This is a way to use Xposed modules without root your device.
|
||||||
Xpatch的原理是对Apk文件进行二次打包,重新签名,并生成一个新的apk文件。
|
|
||||||
在Apk二次打包过程中,插入加载Xposed插件的逻辑,这样,新的Apk文件就可以加载任意Xposed插件,从而实现免Root Hook任意App的Java代码。
|
|
||||||
|
|
||||||
1.0~1.4版本,Hook框架使用的是Lody的[whale](https://github.com/asLody/whale)
|
It is easy way to modify one app using xposed module. And any apps changed by MMPatch can load every modules downloaded in the [Xposed Module Repository](https://repo.xposed.info/).
|
||||||
2.0版本开始,Hook框架底层使用的是ganyao114的[SandHook](https://github.com/ganyao114/SandHook)。
|
|
||||||
3.0版本开始,默认使用SandHook,同时,兼容切换为whale
|
|
||||||
|
|
||||||
## Xpatch工具包下载
|
# Benefits
|
||||||
[下载最新的Xpatch Jar包][1]
|
|
||||||
或者进入Releases页面下载指定版本:[releases][2]
|
|
||||||
|
|
||||||
## Xpatch App版本(Xposed Tool)下载
|
1. Use xposed modules without your device;
|
||||||
[XposedTool][16] [下载XposedTool Apk][15]
|
2. Modify any apps without root your device.
|
||||||
|
|
||||||
## Xpatch使用方法
|
# How to use
|
||||||
Xpatch项目最终生成物是一个Jar包,此Jar使用起来非常简单,只需要一行命令,一个接入xposed hook功能的apk就生成:
|
|
||||||
|
1. Download the latest jar file from the [release page](https://github.com/327135569/MMPatch/releases);
|
||||||
|
2. Run this command in the Windows/Mac console:
|
||||||
```
|
```
|
||||||
$ java -jar XpatchJar包路径 apk文件路径
|
$ java -jar mmpatch.jar source.apk
|
||||||
|
```
|
||||||
|
Then, a new apk named `source-xposed-signed.apk` in the same folder as `source.apk`.
|
||||||
|
|
||||||
For example:
|
More command details can be found when no parameter is added, eg:
|
||||||
$ java -jar ../xpatch.jar ../Test.apk
|
```
|
||||||
|
$ java -jar mmpatch.jar
|
||||||
```
|
```
|
||||||
|
|
||||||
这条命令之后,在原apk文件(Test.apk)相同的文件夹中,会生成一个名称为`Test-xposed-signed.apk`的新apk,这就是重新签名之后的支持xposed插件的apk。
|
# How to manage Xposed modules
|
||||||
|
|
||||||
**Note:** 由于签名与原签名不一致,因此需要先卸载掉系统上已经安装的原apk,才能安装这个Xpatch后的apk
|
When the new apk is installed in the device, It will load all the Xposed modules installed in the device when it's process started.
|
||||||
|
|
||||||
当然,也可以增加`-o`参数,指定新apk生成的路径:
|
But you can manage the installed Xposed modules on/off state by a file in the storage.
|
||||||
```
|
The file path is `/sdcard/xpmodules.list`.
|
||||||
$ java -jar ../xpatch.jar ../Test.apk -o ../new-Test.apk
|
|
||||||
```
|
|
||||||
|
|
||||||
更多参数类型可以使用--help查看,或者不输入任何参数运行jar包:
|
When the new app started, it will search all the installed Xposed modules and write the the module app name and the module application name into this file. (`/sdcard/xpmodules.list`)
|
||||||
```
|
eg:
|
||||||
$ java -jar ../xpatch.jar --h(可省略)
|
|
||||||
```
|
|
||||||
这行命令之后得到结果(v1.0-v2.0):
|
|
||||||
```
|
|
||||||
Please choose one apk file you want to process.
|
|
||||||
options:
|
|
||||||
-f,--force force overwrite
|
|
||||||
-h,--help Print this help message
|
|
||||||
-k,--keep not delete the jar file that is changed by dex2jar
|
|
||||||
and the apk zip files
|
|
||||||
-l,--log show all the debug logs
|
|
||||||
-o,--output <out-apk-file> output .apk file, default is $source_apk_dir/[file
|
|
||||||
-name]-xposed-signed.apk
|
|
||||||
```
|
|
||||||
|
|
||||||
## Xposed模块开关控制的两种方法
|
|
||||||
### 1. 手动修改sdcard文件控制模块开关
|
|
||||||
当新apk安装到系统之后,应用启动时,默认会加载所有已安装的Xposed插件(Xposed Module)。
|
|
||||||
|
|
||||||
一般情况下,Xposed插件中都会对包名过滤,有些Xposed插件有界面,并且在界面上可以设置开关,所以默认启用所有的Xposed插件的方式,大多数情形下都可行。
|
|
||||||
|
|
||||||
但在少数情况可能会出现问题,比如,同一个应用安装有多个Xposed插件(wechat插件就非常多),并且都没有独立的开关界面,同时启用这些插件可能会产生冲突。
|
|
||||||
|
|
||||||
为了解决此问题,当应用启动时,会查找系统中所有已安装的Xposed插件,并在文件目录下生成一个文件
|
|
||||||
`mnt/sdcard/xposed_config/modules.list`,记录这些Xposed插件App。
|
|
||||||
比如:
|
|
||||||
```
|
```
|
||||||
com.blanke.mdwechat#MDWechat
|
com.blanke.mdwechat#MDWechat
|
||||||
com.example.wx_plug_in3#畅玩微信
|
|
||||||
liubaoyua.customtext#文本自定义
|
liubaoyua.customtext#文本自定义
|
||||||
```
|
```
|
||||||
记录的方式是:`插件app包名#插件app名称`
|
Each line of this file is Application Name#App Name.
|
||||||
|
You can disable a Xposed module by add `#` before the Application Name, eg:
|
||||||
需要禁用某个插件,只需要修改此文件,在该插件包名前面增加一个`#`号即可。
|
|
||||||
|
|
||||||
比如,需要禁用`畅玩微信`和`文本自定义`两个插件,只需要修改该文本文件,增加一个`#`号即可:
|
|
||||||
```
|
```
|
||||||
com.blanke.mdwechat#MDWechat
|
#com.blanke.mdwechat#MDWechat
|
||||||
#com.example.wx_plug_in3#畅玩微信
|
liubaoyua.customtext#文本自定义
|
||||||
|
```
|
||||||
|
This means the MDWechat Xposed module is disabled.
|
||||||
|
|
||||||
|
```
|
||||||
|
#com.blanke.mdwechat#MDWechat
|
||||||
#liubaoyua.customtext#文本自定义
|
#liubaoyua.customtext#文本自定义
|
||||||
```
|
```
|
||||||
如果需要禁用所有插件,只需在所有的包名前面增加`#`。
|
This means all Xposed modules are disabled.
|
||||||
|
|
||||||
**注意:**
|
Note: The target app must have file system access permission. Otherwise this file will not be created, and all xposed modules are enabled.
|
||||||
有些App没有获取到sd卡文件读写权限,这会导致无法读取modules.list配置文件,此时会默认启用所有插件。这种情况下,需要手动打开app的文件读写权限。
|
|
||||||
### 2. 通过Xposed Tool App控制模块开关
|
|
||||||
下载并安装Xpatch App(Xposed Tool)
|
|
||||||
[点我下载XposedTool Apk][15]
|
|
||||||
通过`Xposed模块管理`页面来控制模块开关。(原理跟方法1一致)
|
|
||||||

|
|
||||||
|
|
||||||
## 可用的Xposed模块示例
|
|
||||||
|
|
||||||
- [MDWechat][8]
|
|
||||||
- [文本自定义][9]
|
|
||||||
- [RemoveVideoAdsPlugin](https://github.com/WindySha/RemoveVideoAdsPlugin)
|
|
||||||
- **你自己编写的Xposed模块**
|
|
||||||
|
|
||||||
|
|
||||||
## 源码解析
|
# Thanks to
|
||||||
Xpatch源码解析博文已发布到个人技术公众号**Android葵花宝典**上。
|
|
||||||
扫一扫关注公众号,即可查看:
|
|
||||||

|
|
||||||
|
|
||||||
## 其他
|
|
||||||
assets目录下的classes.dex是来加载设备上已安装的Xposed插件,其源代码也已经开源:
|
|
||||||
[xposed_module_loader](https://github.com/WindySha/xposed_module_loader)
|
|
||||||
欢迎star and fork.
|
|
||||||
|
|
||||||
## 局限性
|
|
||||||
Xpatch是基于apk二次打包实现的,而且使用到了dex2Jar工具,因此,也存在不少的局限性。大概有以下几点:
|
|
||||||
|
|
||||||
1. Hook框架默认使用的是SandHook,此框架存在一些不稳定性,在少数机型上hook可能会崩溃。
|
|
||||||
2. 对于校验了文件完整性的app,重打包后可能无法启动;
|
|
||||||
3. Xposed Hook框架暂时不支持Dalvik虚拟机。
|
|
||||||
4. 暂时不支持Xposed插件中的资源Hook。
|
|
||||||
|
|
||||||
## Technology Discussion
|
|
||||||
**QQ Group: 977513757**
|
|
||||||
or
|
|
||||||
**Post comments under this article: [Xpatch: 免Root实现App加载Xposed插件的一种方案](https://windysha.github.io/2019/04/18/Xpatch-%E5%85%8DRoot%E5%AE%9E%E7%8E%B0App%E5%8A%A0%E8%BD%BDXposed%E6%8F%92%E4%BB%B6%E7%9A%84%E4%B8%80%E7%A7%8D%E6%96%B9%E6%A1%88/)**
|
|
||||||
|
|
||||||
## 功能更新
|
|
||||||
|
|
||||||
----
|
|
||||||
### 1. 2019/4/15 updated
|
|
||||||
增加自动破解签名检验的功能,此功能默认开启,如果需要关闭可以增加`-c`即可,比如:
|
|
||||||
```
|
|
||||||
$ java -jar ../xpatch.jar ../Test.apk -c
|
|
||||||
```
|
|
||||||
通过help(-h)可以查看到:
|
|
||||||
>options:
|
|
||||||
> -c,--crach disable craching the apk's signature.
|
|
||||||
|
|
||||||
### 2. 2019/4/25 updated
|
|
||||||
增加将Xposed modules打包到apk中的功能
|
|
||||||
通过help(-h)可以查看到:
|
|
||||||
>-xm,--xposed-modules <arg> the xposed mpdule files to be packaged into the ap
|
|
||||||
> k, multi files should be seperated by :(mac) or ;(
|
|
||||||
> win)
|
|
||||||
|
|
||||||
使用方式为在命令后面增加`-xm path`即可,比如:
|
|
||||||
```
|
|
||||||
$ java -jar ../xpatch.jar ../source.apk -xm ../module1.apk
|
|
||||||
```
|
|
||||||
假如需要将多个Xposed插件打包进去,在Mac中使用":",在Windows下使用";",隔开多个文件路径即可,比如:
|
|
||||||
```
|
|
||||||
mac
|
|
||||||
$ java -jar ../xpatch.jar ../source.apk -xm ../module1.apk:../module2.apk
|
|
||||||
|
|
||||||
windows
|
|
||||||
$ java -jar ../xpatch.jar ../source.apk -xm ../module1.apk;../module2.apk
|
|
||||||
```
|
|
||||||
|
|
||||||
**注意:**
|
|
||||||
1. 多个Xposed modules使用`:`(mac)/`;`(win)分割;
|
|
||||||
2. 假如此module既被打包到apk中,又安装在设备上,则只会加载打包到apk中的module,不会加载安装的。
|
|
||||||
这里是通过包名区分是不是同一个module。
|
|
||||||
|
|
||||||
----
|
|
||||||
|
|
||||||
### 3. 2020/02/09 updated (Version 3.0)
|
|
||||||
3.0版本增加了不少新功能,同时修复了一些用户反馈的Bug。
|
|
||||||
|
|
||||||
新增功能:
|
|
||||||
1. 支持android 10;
|
|
||||||
2. 支持更改植入的hook框架,默认使用Sandhook(支持android10),可更改为whale(暂不支持android10)(-w);
|
|
||||||
3. 默认使用修改Maniest文件方式,植入初始化代码,同时,支持更改为老版本中的修改dex文件的方式植入代码(-dex);
|
|
||||||
4. 支持修改apk包名(一般不建议使用,很多应用会校验包名,会导致无法使用)
|
|
||||||
5. 支持修改apk的version code;
|
|
||||||
6. 支持修改apk的version name;
|
|
||||||
7. 支持修改apk的debuggable为true或者false;
|
|
||||||
|
|
||||||
Bug修复:
|
|
||||||
1. 修复Manifest文件中未定义ApplicationName类,导致无法实现Hook的问题;
|
|
||||||
2. 修复破解无so文件的apk时,出现无法找到so的问题;
|
|
||||||
3. 修复签名可能会失败的问题;
|
|
||||||
4. 修复dex文件数超过65536的问题;
|
|
||||||
|
|
||||||
#### 新功能用法
|
|
||||||
在命令行中输入-h,可以看到3.0版本完整的帮助文档:
|
|
||||||
`$ java -jar ../xpatch-3.0.jar -h`
|
|
||||||
```
|
|
||||||
options:
|
|
||||||
-c,--crach disable craching the apk's signature.
|
|
||||||
-d,--debuggable <0 or 1> set 1 to make the app debuggable = true, set 0 to
|
|
||||||
make the app debuggable = false
|
|
||||||
-dex,--dex insert code into the dex file, not modify manifest
|
|
||||||
application name attribute
|
|
||||||
-f,--force force overwrite
|
|
||||||
-h,--help Print this help message
|
|
||||||
-k,--keep not delete the jar file that is changed by dex2jar
|
|
||||||
and the apk zip files
|
|
||||||
-l,--log show all the debug logs
|
|
||||||
-o,--output <out-apk-file> output .apk file, default is $source_apk_dir/[file
|
|
||||||
-name]-xposed-signed.apk
|
|
||||||
-pkg,--packageName <new package name>modify the apk package name
|
|
||||||
-vc,--versionCode <new-version-code>set the app version code
|
|
||||||
-vn,--versionName <new-version-name>set the app version name
|
|
||||||
-w,--whale Change hook framework to Lody's whale
|
|
||||||
-xm,--xposed-modules <xposed module file path>
|
|
||||||
the xposed module files to be packaged into the ap
|
|
||||||
k, multi files should be seperated by :(mac) or ;(
|
|
||||||
win)
|
|
||||||
version: 3.0
|
|
||||||
```
|
|
||||||
具体用法:
|
|
||||||
1. 修改Apk的debuggable = true:
|
|
||||||
`$ java -jar ../xpatch-3.0.jar ../Test.apk -d 1` (false改为0)
|
|
||||||
2. 使用老版本的破解dex方法破解apk:
|
|
||||||
`$ java -jar ../xpatch-3.0.jar ../Test.apk -dex`
|
|
||||||
3. 修改包名,版本号:
|
|
||||||
`$ java -jar ../xpatch-3.0.jar ../Test.apk -pkg com.test.test -vc 1000 -vn 1.1.1`
|
|
||||||
2. 更改Hook框架为whale:
|
|
||||||
`$ java -jar ../xpatch-3.0.jar ../Test.apk -w`
|
|
||||||
|
|
||||||
### 4. 2021/01/13 updated (Version 4.0)
|
|
||||||
Update SandHook to the newest version and SandHook support the Android 11.
|
|
||||||
|
|
||||||
## Thanks
|
|
||||||
|
|
||||||
|
- [Xpatch][11]
|
||||||
- [Xposed][10]
|
- [Xposed][10]
|
||||||
- [whale][11]
|
|
||||||
- [dex2jar][12]
|
|
||||||
- [AXMLPrinter2][13]
|
- [AXMLPrinter2][13]
|
||||||
- [SandHook](https://github.com/ganyao114/SandHook)
|
|
||||||
- [xposed_module_loader](https://github.com/WindySha/xposed_module_loader)
|
|
||||||
- [axml](https://github.com/Sable/axml)
|
|
||||||
|
|
||||||
[1]: https://github.com/WindySha/Xpatch/releases/download/v3.0/xpatch-3.0.jar
|
|
||||||
[2]: https://github.com/WindySha/Xpatch/releases
|
[11]: https://github.com/WindySha/Xpatch.git
|
||||||
[3]: https://ibotpeaches.github.io/Apktool/install/
|
|
||||||
[5]: https://github.com/asLody/whale
|
|
||||||
[6]: https://repo.xposed.info/module/com.example.wx_plug_in3
|
|
||||||
[7]: https://github.com/Gh0u1L5/WechatMagician/releases
|
|
||||||
[8]: https://github.com/Blankeer/MDWechat
|
|
||||||
[9]: https://repo.xposed.info/module/liubaoyua.customtext
|
|
||||||
[10]: https://github.com/rovo89/Xposed
|
[10]: https://github.com/rovo89/Xposed
|
||||||
[11]: https://github.com/asLody/whale
|
|
||||||
[12]: https://github.com/pxb1988/dex2jar
|
|
||||||
[13]: https://code.google.com/archive/p/android4me/downloads
|
[13]: https://code.google.com/archive/p/android4me/downloads
|
||||||
[14]: http://www.apache.org/licenses/LICENSE-2.0.html
|
|
||||||
[15]: https://xposed-tool-app.oss-cn-beijing.aliyuncs.com/data/Xposed_Tool_2.0.3.apk
|
|
||||||
[16]: https://github.com/WindySha/xposed-tool-app
|
|
||||||
|
|
||||||
|
|
|
||||||
98
README_en.md
98
README_en.md
|
|
@ -1,98 +0,0 @@
|
||||||
# What is Xpatch
|
|
||||||
|
|
||||||
Xpatch is a jar tool which is used to repackage the apk file. Then, the new apk can load any Xposed modules installed in the android system.
|
|
||||||
|
|
||||||
This is a way to use Xposed modules without root your device.
|
|
||||||
|
|
||||||
It is easy way to modify one app using xposed module. And any apps changed by Xpatch can load every modules downloaded in the [Xposed Module Repository](https://repo.xposed.info/).
|
|
||||||
# Benefits
|
|
||||||
1. Use xposed modules without your device;
|
|
||||||
2. Modify any apps without root your device.
|
|
||||||
|
|
||||||
# How to use
|
|
||||||
1. Download the latest jar file from the [release page](https://github.com/WindySha/Xpatch/releases);
|
|
||||||
2. Run this command in the Windows/Mac console:
|
|
||||||
```
|
|
||||||
$ java -jar ../../xpatch.jar ../../source.apk
|
|
||||||
```
|
|
||||||
Then, a new apk named `source-xposed-signed.apk` in the same folder as `source.apk`.
|
|
||||||
|
|
||||||
# More commands
|
|
||||||
1. You can specify the output apk path by add `-o` parameter, eg:
|
|
||||||
```
|
|
||||||
$ java -jar ../../xpatch.jar ../../source.apk -o ../../dst.apk
|
|
||||||
```
|
|
||||||
2. Show all the building new apk logs, just add `-l`, eg:
|
|
||||||
```
|
|
||||||
$ java -jar ../../xpatch.jar ../../source.apk -l
|
|
||||||
```
|
|
||||||
3. Not delete the build files, just add `-k`, eg:
|
|
||||||
```
|
|
||||||
$ java -jar ../../xpatch.jar ../../source.apk -k
|
|
||||||
```
|
|
||||||
4. After the version 1.2, craching app signature verifying is added, if you won't need the function, just add '-c', eg:
|
|
||||||
```
|
|
||||||
$ java -jar ../../xpatch.jar ../../source.apk -c
|
|
||||||
```
|
|
||||||
5. More command details can be found when no parameter is added, eg:
|
|
||||||
```
|
|
||||||
$ java -jar ../../xpatch.jar
|
|
||||||
```
|
|
||||||
# How to manage Xposed modules
|
|
||||||
When the new apk is installed in the device, It will load all the Xposed modules installed in the device when it's process started.
|
|
||||||
|
|
||||||
But you can manage the installed Xposed modules on/off state by a file in the storage.
|
|
||||||
The file path is `mnt/sdcard/xposed_config/modules.list`.
|
|
||||||
|
|
||||||
When the new app started, it will search all the installed Xposed modules and write the the module app name and the module application name into this file. (`mnt/sdcard/xposed_config/modules.list`)
|
|
||||||
eg:
|
|
||||||
```
|
|
||||||
com.blanke.mdwechat#MDWechat
|
|
||||||
|
|
||||||
liubaoyua.customtext#文本自定义
|
|
||||||
```
|
|
||||||
Each line of this file is Application Name#App Name.
|
|
||||||
You can disable a Xposed module by add `#` before the Application Name, eg:
|
|
||||||
```
|
|
||||||
#com.blanke.mdwechat#MDWechat
|
|
||||||
|
|
||||||
liubaoyua.customtext#文本自定义
|
|
||||||
```
|
|
||||||
This means the MDWechat Xposed module is disabled.
|
|
||||||
|
|
||||||
```
|
|
||||||
#com.blanke.mdwechat#MDWechat
|
|
||||||
|
|
||||||
#liubaoyua.customtext#文本自定义
|
|
||||||
```
|
|
||||||
This means all Xposed modules are disabled.
|
|
||||||
|
|
||||||
Note: The target app must have file system access permission. Otherwise this file will not be created, and all xposed modules are enabled.
|
|
||||||
|
|
||||||
|
|
||||||
# Todo list
|
|
||||||
1. Support packaging the xposed modules into the source apk;
|
|
||||||
2. Support loading so library in the xposed modules;
|
|
||||||
3. Crach apk protections.
|
|
||||||
|
|
||||||
# Issues
|
|
||||||
1. If the apk dex files are protected, dex2jar can not effect on the dexs, then this tool will not work;
|
|
||||||
2. The hook framework is using [whale](https://github.com/asLody/whale); this framework is not very stable, some hooks may fail;
|
|
||||||
3. Do not support Davlik VM;
|
|
||||||
4. Do not support resource hook;
|
|
||||||
|
|
||||||
# Discuss
|
|
||||||
You can discuss with me under this page.
|
|
||||||
[Xpatch Comments](https://windysha.github.io/2019/04/18/Xpatch-%E5%85%8DRoot%E5%AE%9E%E7%8E%B0App%E5%8A%A0%E8%BD%BDXposed%E6%8F%92%E4%BB%B6%E7%9A%84%E4%B8%80%E7%A7%8D%E6%96%B9%E6%A1%88/)
|
|
||||||
|
|
||||||
|
|
||||||
# Thanks to
|
|
||||||
- [Xposed][10]
|
|
||||||
- [whale][11]
|
|
||||||
- [dex2jar][12]
|
|
||||||
- [AXMLPrinter2][13]
|
|
||||||
|
|
||||||
[10]: https://github.com/rovo89/Xposed
|
|
||||||
[11]: https://github.com/asLody/whale
|
|
||||||
[12]: https://github.com/pxb1988/dex2jar
|
|
||||||
[13]: https://code.google.com/archive/p/android4me/downloads
|
|
||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
|
@ -33,21 +33,10 @@ public class MainCommand extends BaseCommand {
|
||||||
@Opt(opt = "f", longOpt = "force", hasArg = false, description = "force overwrite")
|
@Opt(opt = "f", longOpt = "force", hasArg = false, description = "force overwrite")
|
||||||
private boolean forceOverwrite = false;
|
private boolean forceOverwrite = false;
|
||||||
|
|
||||||
@Opt(opt = "k", longOpt = "keep", hasArg = false, description = "not delete the jar file " +
|
|
||||||
"that is changed by dex2jar and the apk zip files")
|
|
||||||
private boolean keepBuildFiles = false;
|
|
||||||
|
|
||||||
@Opt(opt = "l", longOpt = "log", hasArg = false, description = "show all the debug logs")
|
|
||||||
private boolean showAllLogs = false;
|
|
||||||
|
|
||||||
@Opt(opt = "c", longOpt = "crach", hasArg = false,
|
@Opt(opt = "c", longOpt = "crach", hasArg = false,
|
||||||
description = "disable craching the apk's signature.")
|
description = "disable craching the apk's signature.")
|
||||||
private boolean disableCrackSignature = false;
|
private boolean disableCrackSignature = false;
|
||||||
|
|
||||||
@Opt(opt = "xm", longOpt = "xposed-modules", description = "the xposed module files to be packaged into the apk, " +
|
|
||||||
"multi files should be seperated by :(mac) or ;(win) ", argName = "xposed module file path")
|
|
||||||
private String xposedModules;
|
|
||||||
|
|
||||||
// 使用dex文件中插入代码的方式修改apk,而不是默认的修改Manifest中Application name的方式
|
// 使用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")
|
@Opt(opt = "dex", longOpt = "dex", hasArg = false, description = "insert code into the dex file, not modify manifest application name attribute")
|
||||||
private boolean dexModificationMode = false;
|
private boolean dexModificationMode = false;
|
||||||
|
|
@ -101,15 +90,14 @@ public class MainCommand extends BaseCommand {
|
||||||
File srcApkFile = new File(apkPath);
|
File srcApkFile = new File(apkPath);
|
||||||
|
|
||||||
if (!srcApkFile.exists()) {
|
if (!srcApkFile.exists()) {
|
||||||
System.out.println(" The source apk file not exsit, please choose another one. " +
|
System.out.println("The source apk file not exsit, please choose another one. " +
|
||||||
"current apk file is = " + apkPath);
|
"current apk file is = " + apkPath);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
String currentDir = new File(".").getAbsolutePath(); // 当前命令行所在的目录
|
String currentDir = new File(".").getAbsolutePath(); // 当前命令行所在的目录
|
||||||
if (showAllLogs) {
|
System.out.println("currentDir: " + currentDir);
|
||||||
System.out.println(" currentDir = " + currentDir + " \n apkPath = " + apkPath);
|
System.out.println("apkPath: " + apkPath);
|
||||||
}
|
|
||||||
|
|
||||||
if (output == null || output.length() == 0) {
|
if (output == null || output.length() == 0) {
|
||||||
output = getBaseName(apkPath) + "-xposed-signed.apk";
|
output = getBaseName(apkPath) + "-xposed-signed.apk";
|
||||||
|
|
@ -129,8 +117,8 @@ public class MainCommand extends BaseCommand {
|
||||||
outputApkFileParentPath = absPath.substring(0, index);
|
outputApkFileParentPath = absPath.substring(0, index);
|
||||||
}
|
}
|
||||||
|
|
||||||
System.out.println(" !!!!! output apk path --> " + output +
|
System.out.println("output apk path: " + output);
|
||||||
" disableCrackSignature --> " + disableCrackSignature);
|
System.out.println("disableCrackSignature: " + disableCrackSignature);
|
||||||
|
|
||||||
String apkFileName = getBaseName(srcApkFile);
|
String apkFileName = getBaseName(srcApkFile);
|
||||||
|
|
||||||
|
|
@ -141,10 +129,8 @@ public class MainCommand extends BaseCommand {
|
||||||
// apk文件解压的目录
|
// apk文件解压的目录
|
||||||
unzipApkFilePath = tempFilePath + apkFileName + "-" + UNZIP_APK_FILE_NAME + File.separator;
|
unzipApkFilePath = tempFilePath + apkFileName + "-" + UNZIP_APK_FILE_NAME + File.separator;
|
||||||
|
|
||||||
if (showAllLogs) {
|
System.out.println("outputApkFileParentPath: " + outputApkFileParentPath);
|
||||||
System.out.println(" !!!!! outputApkFileParentPath = " + outputApkFileParentPath +
|
System.out.println("unzipApkFilePath = " + unzipApkFilePath);
|
||||||
"\n unzipApkFilePath = " + unzipApkFilePath);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!disableCrackSignature) {
|
if (!disableCrackSignature) {
|
||||||
// save the apk original signature info, to support crach signature.
|
// save the apk original signature info, to support crach signature.
|
||||||
|
|
@ -155,16 +141,12 @@ public class MainCommand extends BaseCommand {
|
||||||
long currentTime = System.currentTimeMillis();
|
long currentTime = System.currentTimeMillis();
|
||||||
FileUtils.decompressZip(apkPath, unzipApkFilePath);
|
FileUtils.decompressZip(apkPath, unzipApkFilePath);
|
||||||
|
|
||||||
if (showAllLogs) {
|
System.out.println("decompress apk cost time: " + (System.currentTimeMillis() - currentTime) + "ms");
|
||||||
System.out.println(" decompress apk cost time: " + (System.currentTimeMillis() - currentTime));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get the dex count in the apk zip file
|
// Get the dex count in the apk zip file
|
||||||
dexFileCount = findDexFileCount(unzipApkFilePath);
|
dexFileCount = findDexFileCount(unzipApkFilePath);
|
||||||
|
|
||||||
if (showAllLogs) {
|
System.out.println("dexFileCount: " + dexFileCount);
|
||||||
System.out.println(" --- dexFileCount = " + dexFileCount);
|
|
||||||
}
|
|
||||||
|
|
||||||
String manifestFilePath = unzipApkFilePath + "AndroidManifest.xml";
|
String manifestFilePath = unzipApkFilePath + "AndroidManifest.xml";
|
||||||
|
|
||||||
|
|
@ -177,10 +159,8 @@ public class MainCommand extends BaseCommand {
|
||||||
applicationName = pair.applicationName;
|
applicationName = pair.applicationName;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (showAllLogs) {
|
System.out.println("Get application name cost time: " + (System.currentTimeMillis() - currentTime) + "ms");
|
||||||
System.out.println(" Get application name cost time: " + (System.currentTimeMillis() - currentTime));
|
System.out.println("Get the application name: " + applicationName);
|
||||||
System.out.println(" Get the application name --> " + applicationName);
|
|
||||||
}
|
|
||||||
|
|
||||||
// modify manifest
|
// modify manifest
|
||||||
File manifestFile = new File(manifestFilePath);
|
File manifestFile = new File(manifestFilePath);
|
||||||
|
|
@ -204,20 +184,19 @@ public class MainCommand extends BaseCommand {
|
||||||
|
|
||||||
// modify the apk dex file to make xposed can run in it
|
// modify the apk dex file to make xposed can run in it
|
||||||
if (dexModificationMode && isNotEmpty(applicationName)) {
|
if (dexModificationMode && isNotEmpty(applicationName)) {
|
||||||
mXpatchTasks.add(new ApkModifyTask(showAllLogs, keepBuildFiles, unzipApkFilePath, applicationName,
|
mXpatchTasks.add(new ApkModifyTask(true, true, unzipApkFilePath, applicationName,
|
||||||
dexFileCount));
|
dexFileCount));
|
||||||
}
|
}
|
||||||
|
|
||||||
// copy xposed so and dex files into the unzipped apk
|
// copy xposed so and dex files into the unzipped apk
|
||||||
mXpatchTasks.add(new SoAndDexCopyTask(dexFileCount, unzipApkFilePath,
|
mXpatchTasks.add(new SoAndDexCopyTask(dexFileCount, unzipApkFilePath));
|
||||||
getXposedModules(xposedModules), useWhaleHookFramework));
|
|
||||||
|
|
||||||
// compress all files into an apk and then sign it.
|
// compress all files into an apk and then sign it.
|
||||||
mXpatchTasks.add(new BuildAndSignApkTask(keepBuildFiles, unzipApkFilePath, output));
|
mXpatchTasks.add(new BuildAndSignApkTask(true, unzipApkFilePath, output));
|
||||||
|
|
||||||
// copy origin apk to assets
|
// copy origin apk to assets
|
||||||
// convenient to bypass some check like CRC
|
// convenient to bypass some check like CRC
|
||||||
if(!FileUtils.copyFile(srcApkFile, new File(unzipApkFilePath, "assets/origin_apk.bin"))){
|
if (!FileUtils.copyFile(srcApkFile, new File(unzipApkFilePath, "assets/origin_apk.bin"))) {
|
||||||
throw new IllegalStateException("orignal apk copy fail");
|
throw new IllegalStateException("orignal apk copy fail");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -226,21 +205,8 @@ public class MainCommand extends BaseCommand {
|
||||||
currentTime = System.currentTimeMillis();
|
currentTime = System.currentTimeMillis();
|
||||||
executor.run();
|
executor.run();
|
||||||
|
|
||||||
if (showAllLogs) {
|
|
||||||
System.out.println(executor.getClass().getSimpleName() + " cost time: "
|
System.out.println(executor.getClass().getSimpleName() + " cost time: "
|
||||||
+ (System.currentTimeMillis() - currentTime));
|
+ (System.currentTimeMillis() - currentTime) + "ms");
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 5. delete all the build files that is useless now
|
|
||||||
File unzipApkFile = new File(unzipApkFilePath);
|
|
||||||
if (!keepBuildFiles && unzipApkFile.exists()) {
|
|
||||||
FileUtils.deleteDir(unzipApkFile);
|
|
||||||
}
|
|
||||||
|
|
||||||
File tempFile = new File(tempFilePath);
|
|
||||||
if (!keepBuildFiles && tempFile.exists()) {
|
|
||||||
tempFile.delete();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,7 @@ public class SaveApkSignatureTask implements Runnable {
|
||||||
private String apkPath;
|
private String apkPath;
|
||||||
private String dstFilePath;
|
private String dstFilePath;
|
||||||
|
|
||||||
private final static String SIGNATURE_INFO_ASSET_PATH = "assets/xpatch_asset/original_signature_info.ini";
|
private final static String SIGNATURE_INFO_ASSET_PATH = "assets/original_signature_info.ini";
|
||||||
|
|
||||||
public SaveApkSignatureTask(String apkPath, String unzipApkFilePath) {
|
public SaveApkSignatureTask(String apkPath, String unzipApkFilePath) {
|
||||||
this.apkPath = apkPath;
|
this.apkPath = apkPath;
|
||||||
|
|
@ -33,7 +33,9 @@ public class SaveApkSignatureTask implements Runnable {
|
||||||
File file = new File(dstFilePath);
|
File file = new File(dstFilePath);
|
||||||
File fileParent = file.getParentFile();
|
File fileParent = file.getParentFile();
|
||||||
if (!fileParent.exists()) {
|
if (!fileParent.exists()) {
|
||||||
fileParent.mkdirs();
|
if(!fileParent.mkdirs()){
|
||||||
|
System.out.println("mkdir fails " + fileParent.getAbsolutePath());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
FileUtils.writeFile(dstFilePath, originalSignature);
|
FileUtils.writeFile(dstFilePath, originalSignature);
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,7 @@ public class SaveOriginalApplicationNameTask implements Runnable {
|
||||||
private final String unzipApkFilePath;
|
private final String unzipApkFilePath;
|
||||||
private String dstFilePath;
|
private String dstFilePath;
|
||||||
|
|
||||||
private final String APPLICATION_NAME_ASSET_PATH = "assets/xpatch_asset/original_application_name.ini";
|
private final String APPLICATION_NAME_ASSET_PATH = "assets/original_application_name.ini";
|
||||||
|
|
||||||
public SaveOriginalApplicationNameTask(String applicationName, String unzipApkFilePath) {
|
public SaveOriginalApplicationNameTask(String applicationName, String unzipApkFilePath) {
|
||||||
this.applcationName = applicationName;
|
this.applcationName = applicationName;
|
||||||
|
|
|
||||||
|
|
@ -3,52 +3,27 @@ package com.storm.wind.xpatch.task;
|
||||||
import com.storm.wind.xpatch.util.FileUtils;
|
import com.storm.wind.xpatch.util.FileUtils;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
|
import java.util.ArrayList;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Created by Wind
|
* Created by Wind
|
||||||
*/
|
*/
|
||||||
public class SoAndDexCopyTask implements Runnable {
|
public class SoAndDexCopyTask implements Runnable {
|
||||||
|
|
||||||
private static final String SANDHOOK_SO_FILE_NAME = "libsandhook";
|
|
||||||
private static final String WHALE_SO_FILE_NAME = "libwhale";
|
|
||||||
|
|
||||||
private static final String SANDHOOK_SO_FILE_NAME_WITH_SUFFIX = "libsandhook.so";
|
|
||||||
private static final String WHALE_SO_FILE_NAME_WITH_SUFFIX = "libwhale.so";
|
|
||||||
|
|
||||||
private static final String XPOSED_MODULE_FILE_NAME_PREFIX = "libxpatch_xp_module_";
|
|
||||||
private static final String SO_FILE_SUFFIX = ".so";
|
|
||||||
|
|
||||||
private final String[] APK_LIB_PATH_ARRAY = {
|
private final String[] APK_LIB_PATH_ARRAY = {
|
||||||
"lib/armeabi-v7a/",
|
"lib/armeabi-v7a/",
|
||||||
"lib/armeabi/",
|
"lib/armeabi/",
|
||||||
"lib/arm64-v8a/"
|
"lib/arm64-v8a/"
|
||||||
};
|
};
|
||||||
|
|
||||||
private final HashMap<String, String> mSoFilePathMap = new HashMap<>();
|
|
||||||
private int dexFileCount;
|
private int dexFileCount;
|
||||||
private String unzipApkFilePath;
|
private String unzipApkFilePath;
|
||||||
private String[] xposedModuleArray;
|
|
||||||
|
|
||||||
private boolean useWhaleHookFramework;
|
public SoAndDexCopyTask(int dexFileCount, String unzipApkFilePath) {
|
||||||
|
|
||||||
public SoAndDexCopyTask(int dexFileCount, String unzipApkFilePath,
|
|
||||||
String[] xposedModuleArray, boolean useWhaleHookFramework) {
|
|
||||||
this.dexFileCount = dexFileCount;
|
this.dexFileCount = dexFileCount;
|
||||||
this.unzipApkFilePath = unzipApkFilePath;
|
this.unzipApkFilePath = unzipApkFilePath;
|
||||||
this.xposedModuleArray = xposedModuleArray;
|
|
||||||
this.useWhaleHookFramework = useWhaleHookFramework;
|
|
||||||
|
|
||||||
String soFileName;
|
|
||||||
if (useWhaleHookFramework) {
|
|
||||||
soFileName = WHALE_SO_FILE_NAME;
|
|
||||||
} else {
|
|
||||||
soFileName = SANDHOOK_SO_FILE_NAME;
|
|
||||||
}
|
|
||||||
|
|
||||||
mSoFilePathMap.put(APK_LIB_PATH_ARRAY[0], "assets/lib/armeabi-v7a/" + soFileName);
|
|
||||||
mSoFilePathMap.put(APK_LIB_PATH_ARRAY[1], "assets/lib/armeabi-v7a/" + soFileName);
|
|
||||||
mSoFilePathMap.put(APK_LIB_PATH_ARRAY[2], "assets/lib/arm64-v8a/" + soFileName);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
@ -62,96 +37,74 @@ public class SoAndDexCopyTask implements Runnable {
|
||||||
}
|
}
|
||||||
|
|
||||||
private void copySoFile() {
|
private void copySoFile() {
|
||||||
String[] existLibPathArray = new String[3];
|
List<String> existLibPathArray = new ArrayList<>();
|
||||||
int arrayIndex = 0;
|
|
||||||
for (String libPath : APK_LIB_PATH_ARRAY) {
|
for (String libPath : APK_LIB_PATH_ARRAY) {
|
||||||
String apkSoFullPath = fullLibPath(libPath);
|
String apkSoFullPath = fullLibPath(libPath);
|
||||||
File apkSoFullPathFile = new File(apkSoFullPath);
|
File apkSoFullPathFile = new File(apkSoFullPath);
|
||||||
if (apkSoFullPathFile.exists()) {
|
if (apkSoFullPathFile.exists()) {
|
||||||
existLibPathArray[arrayIndex] = libPath;
|
existLibPathArray.add(libPath);
|
||||||
arrayIndex++;
|
} else {
|
||||||
|
System.out.println("target app dont have " + libPath + ", skip");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 不存在lib目录,则创建lib/armeabi-v7 文件夹
|
if (existLibPathArray.isEmpty()) {
|
||||||
if (arrayIndex == 0) {
|
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 libPath = APK_LIB_PATH_ARRAY[0];
|
||||||
String apkSoFullPath = fullLibPath(libPath);
|
String apkSoFullPath = fullLibPath(libPath);
|
||||||
File apkSoFullPathFile = new File(apkSoFullPath);
|
File apkSoFullPathFile = new File(apkSoFullPath);
|
||||||
apkSoFullPathFile.mkdirs();
|
if (apkSoFullPathFile.mkdirs()) {
|
||||||
existLibPathArray[arrayIndex] = libPath;
|
throw new IllegalStateException("mkdir fail " + apkSoFullPathFile.getAbsolutePath());
|
||||||
|
}
|
||||||
|
existLibPathArray.add(libPath);
|
||||||
}
|
}
|
||||||
|
|
||||||
for (String libPath : existLibPathArray) {
|
for (String libPath : existLibPathArray) {
|
||||||
if (libPath != null && !libPath.isEmpty()) {
|
if (libPath == null || libPath.isEmpty()) {
|
||||||
String apkSoFullPath = fullLibPath(libPath);
|
throw new IllegalStateException("fail eabi path");
|
||||||
copyLibFile(apkSoFullPath, mSoFilePathMap.get(libPath));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// copy xposed modules into the lib path
|
|
||||||
if (xposedModuleArray != null && xposedModuleArray.length > 0) {
|
|
||||||
int index = 0;
|
|
||||||
for (String modulePath : xposedModuleArray) {
|
|
||||||
modulePath = modulePath.trim();
|
|
||||||
if (modulePath == null || modulePath.length() == 0) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
File moduleFile = new File(modulePath);
|
|
||||||
if (!moduleFile.exists()) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
for (String libPath : existLibPathArray) {
|
|
||||||
if (libPath != null && !libPath.isEmpty()) {
|
|
||||||
String apkSoFullPath = fullLibPath(libPath);
|
String apkSoFullPath = fullLibPath(libPath);
|
||||||
String outputModuleName = XPOSED_MODULE_FILE_NAME_PREFIX + index + SO_FILE_SUFFIX;
|
String eabi = libPath.substring(libPath.indexOf("/"));
|
||||||
File outputModuleSoFile = new File(apkSoFullPath, outputModuleName);
|
if (eabi.isEmpty()) {
|
||||||
FileUtils.copyFile(moduleFile, outputModuleSoFile);
|
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;
|
||||||
}
|
}
|
||||||
index++;
|
for (File mySoFile : files) {
|
||||||
|
File target = new File(apkSoFullPath, mySoFile.getName());
|
||||||
|
FileUtils.copyFile(mySoFile, target);
|
||||||
|
System.out.println("Copy " + mySoFile.getAbsolutePath() + " to " + target.getAbsolutePath());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void copyDexFile(int dexFileCount) {
|
private void copyDexFile(int dexFileCount) {
|
||||||
// copy dex file to root dir, rename it first
|
boolean copyed = false;
|
||||||
String copiedDexFileName = "classes" + (dexFileCount + 1) + ".dex";
|
// copy all dex files in list-dex
|
||||||
// assets/classes.dex分隔符不能使用File.seperater,否则在windows上无法读取到文件,IOException
|
File[] files = new File("list-dex").listFiles();
|
||||||
String dexAssetPath;
|
if (files == null || files.length == 0) {
|
||||||
if (useWhaleHookFramework) {
|
System.out.println("Warning: Nothing dex file has been copied");
|
||||||
dexAssetPath = "assets/dex/whale/classes-1.0.dex";
|
return;
|
||||||
} else {
|
}
|
||||||
dexAssetPath = "assets/dex/sandhook/classes-2.0.dex";
|
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++;
|
||||||
|
copyed = true;
|
||||||
}
|
}
|
||||||
FileUtils.copyFileFromJar(dexAssetPath, unzipApkFilePath + copiedDexFileName);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private String fullLibPath(String libPath) {
|
private String fullLibPath(String libPath) {
|
||||||
return unzipApkFilePath + libPath.replace("/", File.separator);
|
return unzipApkFilePath + libPath.replace("/", File.separator);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void copyLibFile(String libFilePath, String srcSoPath) {
|
|
||||||
File apkSoParentFile = new File(libFilePath);
|
|
||||||
if (!apkSoParentFile.exists()) {
|
|
||||||
apkSoParentFile.mkdirs();
|
|
||||||
}
|
|
||||||
|
|
||||||
// get the file name first
|
|
||||||
// int lastIndex = srcSoPath.lastIndexOf('/');
|
|
||||||
// int length = srcSoPath.length();
|
|
||||||
String soFileName;
|
|
||||||
if (useWhaleHookFramework) {
|
|
||||||
soFileName = WHALE_SO_FILE_NAME_WITH_SUFFIX;
|
|
||||||
} else {
|
|
||||||
soFileName = SANDHOOK_SO_FILE_NAME_WITH_SUFFIX;
|
|
||||||
}
|
|
||||||
|
|
||||||
// do copy
|
|
||||||
FileUtils.copyFileFromJar(srcSoPath, new File(apkSoParentFile, soFileName).getAbsolutePath());
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private void deleteMetaInfo() {
|
private void deleteMetaInfo() {
|
||||||
String metaInfoFilePath = "META-INF";
|
String metaInfoFilePath = "META-INF";
|
||||||
File metaInfoFileRoot = new File(unzipApkFilePath + metaInfoFilePath);
|
File metaInfoFileRoot = new File(unzipApkFilePath + metaInfoFilePath);
|
||||||
|
|
|
||||||
|
|
@ -72,7 +72,7 @@ public class ApkSignatureHelper {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
jarFile.close();
|
jarFile.close();
|
||||||
System.out.println(" getApkSignInfo result --> " + certs[0]);
|
System.out.println("getApkSignInfo result: " + certs[0]);
|
||||||
return new String(toChars(certs[0].getEncoded()));
|
return new String(toChars(certs[0].getEncoded()));
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
|
|
|
||||||
|
|
@ -44,7 +44,9 @@ public class FileUtils {
|
||||||
}
|
}
|
||||||
File pathFile = new File(descDir);
|
File pathFile = new File(descDir);
|
||||||
if (!pathFile.exists()) {
|
if (!pathFile.exists()) {
|
||||||
pathFile.mkdirs();
|
if(!pathFile.mkdirs()){
|
||||||
|
throw new IllegalStateException("mkdir fail " + pathFile.getAbsolutePath());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ZipFile zip = null;
|
ZipFile zip = null;
|
||||||
|
|
@ -67,7 +69,9 @@ public class FileUtils {
|
||||||
File file = new File(outPath.substring(0, outPath.lastIndexOf(File.separator)));
|
File file = new File(outPath.substring(0, outPath.lastIndexOf(File.separator)));
|
||||||
|
|
||||||
if (!file.exists()) {
|
if (!file.exists()) {
|
||||||
file.mkdirs();
|
if(!file.mkdirs()){
|
||||||
|
throw new IllegalStateException("mkdir fail " + file.getAbsolutePath());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
//判断文件全路径是否为文件夹,如果是上面已经上传,不需要解压
|
//判断文件全路径是否为文件夹,如果是上面已经上传,不需要解压
|
||||||
if (new File(outPath).isDirectory()) {
|
if (new File(outPath).isDirectory()) {
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue