diff --git a/LICENSE.md b/LICENSE.md new file mode 100644 index 0000000..1a20d82 --- /dev/null +++ b/LICENSE.md @@ -0,0 +1,190 @@ + Apache License + Version 2.0, January 2004 + https://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + Copyright 2010 Ryszard Wiśniewski + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + https://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/README.md b/README.md index d529a84..fa11e59 100644 --- a/README.md +++ b/README.md @@ -7,8 +7,10 @@ Apk to gradle project [dex2jar](https://github.com/pxb1988/dex2jar) +[apktool修改版本](https://github.com/mosect/Apktool/) + ## 注意 -1. 不支持加固(加壳)apk,以后也不提供相关脱壳工具 +1. 不支持加固(加壳)apk 2. 资源res可以添加和修改,但是不能删除 3. java类可以添加和修改,不能删除;修改的最小单位是方法和字段 @@ -21,8 +23,16 @@ gradlew outputProject 不想构建工具?直接点击下载: -[V1.0.1](http://mosect.com/assets/apk2gradle/apk2gradle-1.0.1.zip) +[V1.1.0](http://mosect.com/assets/apk2gradle/apk2gradle-1.1.0.zip) +## 更新记录 +**V1.1.0** +``` +1. 改用aar方式导入原apk资源文件,相关文件:res.aar +2. 修复smali合并错误问题 +3. 弃用unknown.jar方式,改用resources目录存放 +5. 更新apktool工具,此工具为修改后版本,增加参数 -r-txt 来输出aar的R.txt +``` ## 使用工具: 进入工具目录,执行: diff --git a/build.gradle b/build.gradle index e587010..eeb5014 100644 --- a/build.gradle +++ b/build.gradle @@ -25,7 +25,7 @@ allprojects { apply plugin: 'java-library' apply plugin: 'com.github.johnrengelman.shadow' -def appVersion = '1.0.1' +def appVersion = '1.1.0' def appName = 'apk2gradle' dependencies { diff --git a/data/.gitignore b/data/.gitignore new file mode 100644 index 0000000..27dbc2a --- /dev/null +++ b/data/.gitignore @@ -0,0 +1,2 @@ +/cache +/output diff --git a/data/apktool.jar b/data/apktool.jar index 53d723d..1c2db4d 100644 Binary files a/data/apktool.jar and b/data/apktool.jar differ diff --git a/data/template/app/build2.gradle b/data/template/app/build2.gradle index b097980..8dec256 100644 --- a/data/template/app/build2.gradle +++ b/data/template/app/build2.gradle @@ -7,8 +7,8 @@ dependencies { if (file('original/classes.jar').exists()) { compileOnly files('original/classes.jar') } - if (file('original/unknown.jar').exists()) { - implementation files('original/unknown.jar') + if (file('original/res.aar').exists()) { + implementation files('original/res.aar') } } @@ -65,6 +65,7 @@ def dumpDexDir = { File dir, File outDir -> files.each { if (it.name ==~ '^classes[0-9]?.dex$') { def smaliDir = new File(outDir, it.name.replace('.', '_')) + smaliDir.delete() dumpDex(it, smaliDir) result.add(smaliDir) } @@ -73,7 +74,7 @@ def dumpDexDir = { File dir, File outDir -> } def readSmali = { File file -> - println "readSmali: ${file}" +// println "readSmali: ${file}" def result = [ members: [], methods: [:], @@ -106,6 +107,7 @@ def readSmali = { File file -> name : name, lines: [], ] + method.lines.add(line) result.methods[method.name] = method result.members.add(method) handled = true @@ -191,21 +193,35 @@ def margeSmali = { File from, File to -> } } -def putSmaliFile = { File file, def classPath, def outSmaliList -> +def putSmaliFile = { File file, String classPath, def outSmaliList, String packageName -> println "putSmaliFile: ${classPath}" + if (classPath.startsWith("com/mosect/a2g/app/res/R")) { + println "ignoreSmali: ${file}" + return + } File mainDir = null + def rClassPath = "${packageName.replace(".", "/")}/R" + def isRClass = classPath.startsWith(rClassPath) for (File smaliDir : outSmaliList) { if (smaliDir.name == 'classes_dex') { mainDir = smaliDir } File outFile = new File(smaliDir, classPath) if (outFile.exists()) { - margeSmali(file, outFile) + if (isRClass) { + // R class, replace + println "replaceSmali: ${file} >>> ${outFile}" + outFile.text = file.text + } else { + margeSmali(file, outFile) + } + return } } // 新增的smali,复制smali if (mainDir) { File outFile = new File(mainDir, classPath) + println "addSmali: ${outFile}" if (!outFile.parentFile.exists()) outFile.parentFile.mkdirs() outFile.text = file.text } @@ -231,6 +247,8 @@ tasks.whenTaskAdded { if (cur.name ==~ '^mergeDex\\S+') { cur.doLast { def variantName = cur.variantName + Document manifest = readXml(file('src/main/AndroidManifest.xml')) + String packageName = manifest.getDocumentElement().getAttribute("package") cur.outputs.files.each { // 生成新的smali def newSmaliList = dumpDexDir(it, new File(buildDir, "smali/${variantName}/_new")) @@ -244,7 +262,7 @@ tasks.whenTaskAdded { files.each { File file -> if (file.name ==~ '^\\S+.smali$') { String path = dir.relativePath(file) - putSmaliFile(file, path, oldSmaliList) + putSmaliFile(file, path, oldSmaliList, packageName) } } } diff --git a/src/main/java/com/mosect/a2g/Apk2Gradle.java b/src/main/java/com/mosect/a2g/Apk2Gradle.java index 2ed9d43..3e26baa 100644 --- a/src/main/java/com/mosect/a2g/Apk2Gradle.java +++ b/src/main/java/com/mosect/a2g/Apk2Gradle.java @@ -101,16 +101,17 @@ public static void main(String[] args) { File apkFile = new File(args[1]); String outName = apkFile.getName().replace(".", "_") + "_a2g"; - File outDir = new File(args.length > 2 ? args[2] : outName); + File outDir = new File(args.length > 2 ? args[2] : "output/" + outName); + File tempDir = new File(cacheDir, String.valueOf(System.currentTimeMillis())); - // dump apk - LogUtils.i(TAG, "dump apk"); - File apkDir = new File(cacheDir, "apk-" + System.currentTimeMillis()); - boolean dumpOk = exec(new ProcessBuilder( - config.java, "-jar", apktoolFile.getAbsolutePath(), - "d", "-s", "-o", apkDir.getAbsolutePath(), apkFile.getAbsolutePath() - )); - if (dumpOk) { + try { + // dump apk + LogUtils.i(TAG, "dump apk"); + File apkDir = new File(tempDir, "apk"); + execWithException(new ProcessBuilder( + config.java, "-jar", apktoolFile.getAbsolutePath(), + "d", "-s", "--use-aapt2", "-o", apkDir.getAbsolutePath(), apkFile.getAbsolutePath() + )); if (outDir.exists()) IOUtils.delete(outDir); outDir.mkdirs(); // copy template files @@ -131,6 +132,9 @@ public static void main(String[] args) { String packageName = manifest.getDocumentElement().getAttribute("package"); String compileSdk = manifest.getDocumentElement().getAttribute("platformBuildVersionCode"); if (TextUtils.empty(compileSdk)) compileSdk = targetSdk; + if (Integer.parseInt(compileSdk) < 30) { + LogUtils.e(TAG, "compileSdk: " + compileSdk + ", less than 30!"); + } manifest.getDocumentElement().removeAttribute("platformBuildVersionCode"); manifest.getDocumentElement().removeAttribute("platformBuildVersionName"); @@ -151,32 +155,69 @@ public static void main(String[] args) { LogUtils.i(TAG, "create AndroidManifest.xml"); IOUtils.saveDocument(new File(mainDir, "AndroidManifest.xml"), manifest); // copy res - LogUtils.i(TAG, "copy res"); - IOUtils.copy(new File(apkDir, "res"), new File(mainDir, "res")); + // changed: create res.aar +// LogUtils.i(TAG, "copy res"); +// IOUtils.copy(new File(apkDir, "res"), new File(mainDir, "res")); + // copy public.xml + IOUtils.copy(new File(apkDir, "res/values/public.xml"), new File(mainDir, "res/values/public.xml")); // copy assets LogUtils.i(TAG, "copy assets"); IOUtils.copy(new File(apkDir, "assets"), new File(mainDir, "assets")); + // copy jniLibs + LogUtils.i(TAG, "copy jniLibs"); + IOUtils.copy(new File(apkDir, "lib"), new File(mainDir, "jniLibs")); + // copy resources + LogUtils.i(TAG, "copy resources"); + IOUtils.copy(new File(apkDir, "kotlin"), new File(mainDir, "resources")); + IOUtils.copy(new File(apkDir, "unknown"), new File(mainDir, "resources")); + IOUtils.copy(new File(apkDir, "META-INF"), new File(mainDir, "resources")); + + new File(mainDir, "java").mkdirs(); File originalDir = new File(outDir, "app/original"); originalDir.mkdirs(); + // create res.aar + File resAarDir = new File(tempDir, "res_aar"); + resAarDir.mkdirs(); + LogUtils.i(TAG, "create res.aar"); + File resAarFile = new File(originalDir, "res.aar"); + execWithException(new ProcessBuilder( + config.java, "-jar", apktoolFile.getAbsolutePath(), + "b", "--use-aapt2", "--r-txt", "-f", + apkDir.getAbsolutePath() + )); + File rTxtFile = new File(apkDir, "build/R.txt"); + IOUtils.zip(new ZipItem[]{ + new ZipItem("proguard.txt"), + new ZipItem(rTxtFile, "R.txt"), + new ZipItem(new File(apkDir, "res"), "res"), + new ZipItem(Apk2Gradle.class.getResourceAsStream("/aar-manifest.xml"), "AndroidManifest.xml") + }, resAarFile); + // create classes.jar LogUtils.i(TAG, "create classes.jar"); File classesJar = new File(originalDir, "classes.jar"); - exec(new ProcessBuilder(config.dex2jar, + execWithException(new ProcessBuilder(config.dex2jar, "-o", classesJar.getAbsolutePath(), apkFile.getAbsolutePath())); // create unknown.jar - LogUtils.i(TAG, "create unknown.jar"); - IOUtils.zipDir(new File(apkDir, "unknown"), new File(originalDir, "unknown.jar")); + // changed: copy resources +// LogUtils.i(TAG, "create unknown.jar"); +// IOUtils.zipDir( +// new ZipItem[]{ +// new ZipItem(new File(apkDir, "unknown"), ""), +// new ZipItem(new File(apkDir, "kotlin"), "kotlin"), +// new ZipItem(new File(apkDir, "META-INF"), "META-INF") +// }, +// new File(originalDir, "unknown.jar") +// ); // copy dex files LogUtils.i(TAG, "copy dex files"); copyDexFiles(apkDir, originalDir); - } - LogUtils.i(TAG, "result: " + dumpOk); - // 清理缓存 - IOUtils.delete(apkDir); - if (!dumpOk) { - System.exit(1); + LogUtils.i(TAG, "apk to gradle ok"); + } finally { + // 清理缓存 + IOUtils.delete(tempDir); } } @@ -243,7 +284,7 @@ private static Config readConfig() { return config; } - private static boolean exec(ProcessBuilder builder) { + private static void execWithException(ProcessBuilder builder) { StringBuilder stringBuilder = new StringBuilder(); for (String str : builder.command()) { stringBuilder.append(str).append(' '); @@ -269,10 +310,9 @@ private static boolean exec(ProcessBuilder builder) { }); thread.start(); int code = process.waitFor(); - if (code == 0) return true; + if (code != 0) throw new RuntimeException("Exec failed: " + code); } catch (Exception e) { - e.printStackTrace(); + throw new RuntimeException(e); } - return false; } } diff --git a/src/main/java/com/mosect/a2g/IOUtils.java b/src/main/java/com/mosect/a2g/IOUtils.java index e9d14f6..da2a879 100644 --- a/src/main/java/com/mosect/a2g/IOUtils.java +++ b/src/main/java/com/mosect/a2g/IOUtils.java @@ -13,6 +13,7 @@ import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; +import java.io.OutputStream; import java.io.OutputStreamWriter; import java.nio.charset.StandardCharsets; import java.util.ArrayList; @@ -162,9 +163,6 @@ public static void copy(File src, File dst) { private static void copy(byte[] buffer, File src, File dst) { if (src.exists()) { - if (dst.exists()) { - delete(dst); - } File parentFile = dst.getParentFile(); if (null != parentFile && !parentFile.exists()) { parentFile.mkdirs(); @@ -216,16 +214,29 @@ public static void saveDocument(File file, Document document) { } } - public static void zipDir(File dir, File out) { - if (dir.isDirectory()) { - try (FileOutputStream fos = new FileOutputStream(out)) { - ZipOutputStream zos = new ZipOutputStream(fos, StandardCharsets.UTF_8); - putChildrenToZip(dir, "", zos, new byte[1024]); - zos.flush(); - zos.close(); - } catch (Exception e) { - e.printStackTrace(); + public static void zip(ZipItem[] items, File out) { + try (FileOutputStream fos = new FileOutputStream(out); + ZipOutputStream zos = new ZipOutputStream(fos, StandardCharsets.UTF_8)) { + byte[] buffer = new byte[1024]; + for (ZipItem item : items) { + if (null != item.getFile()) { + if (item.getFile().isDirectory()) { + putChildrenToZip(item.getFile(), item.getPath(), zos, buffer); + } else if (item.getFile().isFile()) { + zos.putNextEntry(new ZipEntry(item.getPath())); + copyData(item.getFile(), zos, buffer); + } + } else if (null != item.getData()) { + zos.putNextEntry(new ZipEntry(item.getPath())); + copyData(item.getData(), zos, buffer); + item.getData().close(); + } else { + zos.putNextEntry(new ZipEntry(item.getPath())); + } } + zos.flush(); + } catch (Exception e) { + e.printStackTrace(); } } @@ -236,18 +247,33 @@ private static void putChildrenToZip(File dir, String path, ZipOutputStream zos, String name = f.getName(); if (".".equals(name) || "..".equals(name)) continue; String curPath = path + "/" + name; - zos.putNextEntry(new ZipEntry(curPath)); if (f.isDirectory()) { putChildrenToZip(f, curPath, zos, buffer); - } else { - int len; - try (FileInputStream fis = new FileInputStream(f)) { - while ((len = fis.read(buffer)) > 0) { - zos.write(buffer, 0, len); - } - } + } else if (f.isFile()) { + zos.putNextEntry(new ZipEntry(curPath)); + copyData(f, zos, buffer); } } } } + + private static void copyData(File file, OutputStream dst, byte[] buffer) throws IOException { + try (FileInputStream fis = new FileInputStream(file)) { + copyData(fis, dst, buffer); + } + } + + public static void copyData(InputStream src, OutputStream dst, byte[] buffer) throws IOException { + int len; + while ((len = src.read(buffer)) > 0) { + dst.write(buffer, 0, len); + } + } + + public static void copyResToFile(String resName, File file) throws IOException { + try (InputStream is = Apk2Gradle.class.getResourceAsStream(resName); + FileOutputStream dst = new FileOutputStream(file)) { + IOUtils.copyData(is, dst, new byte[1024]); + } + } } diff --git a/src/main/java/com/mosect/a2g/ZipItem.java b/src/main/java/com/mosect/a2g/ZipItem.java new file mode 100644 index 0000000..9d6f3b8 --- /dev/null +++ b/src/main/java/com/mosect/a2g/ZipItem.java @@ -0,0 +1,41 @@ +package com.mosect.a2g; + +import java.io.File; +import java.io.InputStream; + +public class ZipItem { + + private final File file; + private final String path; + private final InputStream data; + + public ZipItem(String path) { + this.path = path; + this.file = null; + this.data = null; + } + + public ZipItem(File file, String path) { + this.file = file; + this.path = path; + this.data = null; + } + + public ZipItem(InputStream data, String path) { + this.file = null; + this.path = path; + this.data = data; + } + + public InputStream getData() { + return data; + } + + public File getFile() { + return file; + } + + public String getPath() { + return path; + } +} diff --git a/src/main/resources/aar-manifest.xml b/src/main/resources/aar-manifest.xml new file mode 100644 index 0000000..16929f4 --- /dev/null +++ b/src/main/resources/aar-manifest.xml @@ -0,0 +1,6 @@ + + +