2012-09-20 03:27:35 +02:00
|
|
|
/**
|
2014-10-24 01:14:48 +02:00
|
|
|
* Copyright 2014 Ryszard Wiśniewski <brut.alll@gmail.com>
|
2012-09-20 03:27:35 +02:00
|
|
|
*
|
|
|
|
* 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
|
|
|
|
*
|
|
|
|
* http://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.
|
|
|
|
*/
|
|
|
|
|
|
|
|
package brut.androlib;
|
|
|
|
|
|
|
|
import brut.androlib.java.AndrolibJava;
|
|
|
|
import brut.androlib.res.AndrolibResources;
|
|
|
|
import brut.androlib.res.data.ResPackage;
|
|
|
|
import brut.androlib.res.data.ResTable;
|
2014-03-02 15:46:35 +01:00
|
|
|
import brut.androlib.res.data.ResUnknownFiles;
|
2012-09-20 03:27:35 +02:00
|
|
|
import brut.androlib.res.util.ExtFile;
|
2015-04-16 15:02:24 +02:00
|
|
|
import brut.androlib.res.xml.ResXmlPatcher;
|
2012-09-20 03:27:35 +02:00
|
|
|
import brut.androlib.src.SmaliBuilder;
|
|
|
|
import brut.androlib.src.SmaliDecoder;
|
|
|
|
import brut.common.BrutException;
|
|
|
|
import brut.directory.*;
|
|
|
|
import brut.util.BrutIO;
|
|
|
|
import brut.util.OS;
|
|
|
|
import java.io.*;
|
2013-03-31 16:04:12 +02:00
|
|
|
import java.util.*;
|
2012-09-20 03:27:35 +02:00
|
|
|
import java.util.logging.Logger;
|
2015-03-25 04:14:05 +01:00
|
|
|
import java.util.zip.CRC32;
|
2013-03-31 16:04:12 +02:00
|
|
|
import java.util.zip.ZipEntry;
|
2015-01-21 14:12:26 +01:00
|
|
|
import java.util.zip.ZipFile;
|
2015-03-25 04:14:05 +01:00
|
|
|
import java.util.zip.ZipOutputStream;
|
2015-01-21 14:12:26 +01:00
|
|
|
|
2015-03-30 07:30:52 +02:00
|
|
|
import org.apache.commons.io.IOUtils;
|
2012-09-20 03:27:35 +02:00
|
|
|
import org.yaml.snakeyaml.DumperOptions;
|
|
|
|
import org.yaml.snakeyaml.Yaml;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @author Ryszard Wiśniewski <brut.alll@gmail.com>
|
|
|
|
*/
|
|
|
|
public class Androlib {
|
2014-02-10 02:01:57 +01:00
|
|
|
private final AndrolibResources mAndRes = new AndrolibResources();
|
2014-03-02 15:46:35 +01:00
|
|
|
protected final ResUnknownFiles mResUnknownFiles = new ResUnknownFiles();
|
2014-11-02 03:07:02 +01:00
|
|
|
public ApkOptions apkOptions;
|
|
|
|
|
|
|
|
public Androlib(ApkOptions apkOptions) {
|
|
|
|
this.apkOptions = apkOptions;
|
|
|
|
mAndRes.apkOptions = apkOptions;
|
|
|
|
}
|
|
|
|
|
|
|
|
public Androlib() {
|
|
|
|
this.apkOptions = new ApkOptions();
|
|
|
|
mAndRes.apkOptions = this.apkOptions;
|
|
|
|
}
|
2014-02-10 02:01:57 +01:00
|
|
|
|
2014-08-16 03:24:12 +02:00
|
|
|
public ResTable getResTable(ExtFile apkFile)
|
|
|
|
throws AndrolibException {
|
2014-02-10 02:01:57 +01:00
|
|
|
return mAndRes.getResTable(apkFile, true);
|
|
|
|
}
|
|
|
|
|
|
|
|
public ResTable getResTable(ExtFile apkFile, boolean loadMainPkg)
|
|
|
|
throws AndrolibException {
|
|
|
|
return mAndRes.getResTable(apkFile, loadMainPkg);
|
|
|
|
}
|
|
|
|
|
2014-08-16 15:45:43 +02:00
|
|
|
public void decodeSourcesRaw(ExtFile apkFile, File outDir, String filename)
|
2014-02-10 02:01:57 +01:00
|
|
|
throws AndrolibException {
|
|
|
|
try {
|
2015-04-04 15:09:07 +02:00
|
|
|
LOGGER.info("Copying raw " + filename + " file...");
|
2014-08-16 03:24:12 +02:00
|
|
|
apkFile.getDirectory().copyToDir(outDir, filename);
|
2014-02-10 02:01:57 +01:00
|
|
|
} catch (DirectoryException ex) {
|
|
|
|
throw new AndrolibException(ex);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-08-16 15:45:43 +02:00
|
|
|
public void decodeSourcesSmali(File apkFile, File outDir, String filename, boolean debug, String debugLinePrefix,
|
2014-02-10 02:01:57 +01:00
|
|
|
boolean bakdeb, int api) throws AndrolibException {
|
2014-08-16 03:24:12 +02:00
|
|
|
try {
|
2014-08-16 16:54:34 +02:00
|
|
|
File smaliDir;
|
|
|
|
if (filename.equalsIgnoreCase("classes.dex")) {
|
|
|
|
smaliDir = new File(outDir, SMALI_DIRNAME);
|
|
|
|
} else {
|
|
|
|
smaliDir = new File(outDir, SMALI_DIRNAME + "_" + filename.substring(0, filename.indexOf(".")));
|
|
|
|
}
|
2014-08-16 03:24:12 +02:00
|
|
|
OS.rmdir(smaliDir);
|
2014-08-16 15:45:43 +02:00
|
|
|
smaliDir.mkdirs();
|
2014-08-16 03:24:12 +02:00
|
|
|
LOGGER.info("Baksmaling " + filename + "...");
|
2014-08-16 15:45:43 +02:00
|
|
|
SmaliDecoder.decode(apkFile, smaliDir, filename, debug, debugLinePrefix, bakdeb, api);
|
2014-02-10 02:01:57 +01:00
|
|
|
} catch (BrutException ex) {
|
|
|
|
throw new AndrolibException(ex);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
public void decodeSourcesJava(ExtFile apkFile, File outDir, boolean debug)
|
|
|
|
throws AndrolibException {
|
|
|
|
LOGGER.info("Decoding Java sources...");
|
|
|
|
new AndrolibJava().decode(apkFile, outDir);
|
|
|
|
}
|
|
|
|
|
|
|
|
public void decodeManifestRaw(ExtFile apkFile, File outDir)
|
|
|
|
throws AndrolibException {
|
|
|
|
try {
|
|
|
|
Directory apk = apkFile.getDirectory();
|
|
|
|
LOGGER.info("Copying raw manifest...");
|
|
|
|
apkFile.getDirectory().copyToDir(outDir, APK_MANIFEST_FILENAMES);
|
|
|
|
} catch (DirectoryException ex) {
|
|
|
|
throw new AndrolibException(ex);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-08-16 03:24:12 +02:00
|
|
|
public void decodeManifestFull(ExtFile apkFile, File outDir, ResTable resTable)
|
|
|
|
throws AndrolibException {
|
2014-02-10 02:01:57 +01:00
|
|
|
mAndRes.decodeManifest(resTable, apkFile, outDir);
|
|
|
|
}
|
|
|
|
|
|
|
|
public void decodeResourcesRaw(ExtFile apkFile, File outDir)
|
|
|
|
throws AndrolibException {
|
|
|
|
try {
|
|
|
|
LOGGER.info("Copying raw resources...");
|
|
|
|
apkFile.getDirectory().copyToDir(outDir, APK_RESOURCES_FILENAMES);
|
|
|
|
} catch (DirectoryException ex) {
|
|
|
|
throw new AndrolibException(ex);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-08-16 03:24:12 +02:00
|
|
|
public void decodeResourcesFull(ExtFile apkFile, File outDir, ResTable resTable)
|
|
|
|
throws AndrolibException {
|
2014-02-10 02:01:57 +01:00
|
|
|
mAndRes.decode(resTable, apkFile, outDir);
|
|
|
|
}
|
|
|
|
|
2014-12-26 15:08:50 +01:00
|
|
|
public void decodeManifestWithResources(ExtFile apkFile, File outDir, ResTable resTable)
|
|
|
|
throws AndrolibException {
|
|
|
|
mAndRes.decodeManifestWithResources(resTable, apkFile, outDir);
|
|
|
|
}
|
|
|
|
|
2014-02-10 02:01:57 +01:00
|
|
|
public void decodeRawFiles(ExtFile apkFile, File outDir)
|
|
|
|
throws AndrolibException {
|
|
|
|
LOGGER.info("Copying assets and libs...");
|
|
|
|
try {
|
|
|
|
Directory in = apkFile.getDirectory();
|
|
|
|
if (in.containsDir("assets")) {
|
|
|
|
in.copyToDir(outDir, "assets");
|
|
|
|
}
|
|
|
|
if (in.containsDir("lib")) {
|
|
|
|
in.copyToDir(outDir, "lib");
|
|
|
|
}
|
2013-05-10 15:24:24 +02:00
|
|
|
if (in.containsDir("libs")) {
|
|
|
|
in.copyToDir(outDir, "libs");
|
|
|
|
}
|
2014-02-10 02:01:57 +01:00
|
|
|
} catch (DirectoryException ex) {
|
|
|
|
throw new AndrolibException(ex);
|
|
|
|
}
|
|
|
|
}
|
2013-02-13 04:12:17 +01:00
|
|
|
|
2013-03-31 16:04:12 +02:00
|
|
|
private boolean isAPKFileNames(String file) {
|
|
|
|
for (String apkFile : APK_STANDARD_ALL_FILENAMES) {
|
2014-02-16 18:20:06 +01:00
|
|
|
if (apkFile.equals(file) || file.startsWith(apkFile + "/")) {
|
2013-03-31 16:04:12 +02:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2013-04-01 00:13:10 +02:00
|
|
|
public void decodeUnknownFiles(ExtFile apkFile, File outDir, ResTable resTable)
|
2013-03-31 16:04:12 +02:00
|
|
|
throws AndrolibException {
|
2014-02-16 18:20:06 +01:00
|
|
|
LOGGER.info("Copying unknown files...");
|
2013-03-31 16:04:12 +02:00
|
|
|
File unknownOut = new File(outDir, UNK_DIRNAME);
|
2013-04-01 00:13:10 +02:00
|
|
|
ZipEntry invZipFile;
|
2013-03-31 16:04:12 +02:00
|
|
|
|
|
|
|
// have to use container of ZipFile to help identify compression type
|
|
|
|
// with regular looping of apkFile for easy copy
|
|
|
|
try {
|
|
|
|
Directory unk = apkFile.getDirectory();
|
2015-01-21 14:12:26 +01:00
|
|
|
ZipFile apkZipFile = new ZipFile(apkFile.getAbsolutePath());
|
2013-03-31 16:04:12 +02:00
|
|
|
|
2013-04-01 00:13:10 +02:00
|
|
|
// loop all items in container recursively, ignoring any that are pre-defined by aapt
|
|
|
|
Set<String> files = unk.getFiles(true);
|
2013-03-31 16:04:12 +02:00
|
|
|
for (String file : files) {
|
2014-08-16 15:29:57 +02:00
|
|
|
if (!isAPKFileNames(file) && !file.endsWith(".dex")) {
|
2013-04-01 00:13:10 +02:00
|
|
|
|
|
|
|
// copy file out of archive into special "unknown" folder
|
2014-08-16 15:29:57 +02:00
|
|
|
unk.copyToDir(unknownOut, file);
|
2013-04-01 00:13:10 +02:00
|
|
|
try {
|
2014-08-16 03:24:12 +02:00
|
|
|
invZipFile = apkZipFile.getEntry(file);
|
2013-04-01 00:13:10 +02:00
|
|
|
|
|
|
|
// lets record the name of the file, and its compression type
|
|
|
|
// so that we may re-include it the same way
|
|
|
|
if (invZipFile != null) {
|
2014-03-02 15:46:35 +01:00
|
|
|
mResUnknownFiles.addUnknownFileInfo(invZipFile.getName(), String.valueOf(invZipFile.getMethod()));
|
2013-04-01 00:13:10 +02:00
|
|
|
}
|
2014-08-16 16:48:02 +02:00
|
|
|
} catch (NullPointerException ignored) { }
|
2013-03-31 16:04:12 +02:00
|
|
|
}
|
|
|
|
}
|
2013-05-12 14:20:07 +02:00
|
|
|
apkZipFile.close();
|
2014-10-29 22:18:25 +01:00
|
|
|
} catch (DirectoryException | IOException ex) {
|
2013-03-31 16:04:12 +02:00
|
|
|
throw new AndrolibException(ex);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-02-10 02:01:57 +01:00
|
|
|
public void writeOriginalFiles(ExtFile apkFile, File outDir)
|
|
|
|
throws AndrolibException {
|
|
|
|
LOGGER.info("Copying original files...");
|
|
|
|
File originalDir = new File(outDir, "original");
|
|
|
|
if (!originalDir.exists()) {
|
|
|
|
originalDir.mkdirs();
|
|
|
|
}
|
|
|
|
|
|
|
|
try {
|
|
|
|
Directory in = apkFile.getDirectory();
|
|
|
|
if(in.containsFile("AndroidManifest.xml")) {
|
|
|
|
in.copyToDir(originalDir, "AndroidManifest.xml");
|
|
|
|
}
|
|
|
|
if (in.containsDir("META-INF")) {
|
|
|
|
in.copyToDir(originalDir, "META-INF");
|
|
|
|
}
|
|
|
|
} catch (DirectoryException ex) {
|
|
|
|
throw new AndrolibException(ex);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
public void writeMetaFile(File mOutDir, Map<String, Object> meta)
|
|
|
|
throws AndrolibException {
|
|
|
|
DumperOptions options = new DumperOptions();
|
|
|
|
options.setDefaultFlowStyle(DumperOptions.FlowStyle.BLOCK);
|
|
|
|
Yaml yaml = new Yaml(options);
|
|
|
|
|
2014-10-29 22:18:25 +01:00
|
|
|
try (
|
|
|
|
Writer writer = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(
|
2015-03-30 07:30:52 +02:00
|
|
|
new File(mOutDir, "apktool.yml")), "UTF-8"))
|
2014-10-29 22:18:25 +01:00
|
|
|
) {
|
2014-02-10 02:01:57 +01:00
|
|
|
yaml.dump(meta, writer);
|
|
|
|
} catch (IOException ex) {
|
|
|
|
throw new AndrolibException(ex);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
public Map<String, Object> readMetaFile(ExtFile appDir)
|
|
|
|
throws AndrolibException {
|
2014-10-29 22:18:25 +01:00
|
|
|
try(
|
2015-03-30 07:30:52 +02:00
|
|
|
InputStream in = appDir.getDirectory().getFileInput("apktool.yml")
|
2014-10-29 22:18:25 +01:00
|
|
|
) {
|
2014-02-10 02:01:57 +01:00
|
|
|
Yaml yaml = new Yaml();
|
|
|
|
return (Map<String, Object>) yaml.load(in);
|
2014-10-29 22:18:25 +01:00
|
|
|
} catch (DirectoryException | IOException ex) {
|
2014-02-10 02:01:57 +01:00
|
|
|
throw new AndrolibException(ex);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-11-02 03:07:02 +01:00
|
|
|
public void build(File appDir, File outFile) throws BrutException {
|
|
|
|
build(new ExtFile(appDir), outFile);
|
2014-02-10 02:01:57 +01:00
|
|
|
}
|
|
|
|
|
2014-11-02 03:07:02 +01:00
|
|
|
public void build(ExtFile appDir, File outFile)
|
2014-02-10 02:01:57 +01:00
|
|
|
throws BrutException {
|
2015-01-19 14:25:21 +01:00
|
|
|
LOGGER.info("Using Apktool " + Androlib.getVersion());
|
2013-09-18 04:52:09 +02:00
|
|
|
|
2014-02-10 02:01:57 +01:00
|
|
|
Map<String, Object> meta = readMetaFile(appDir);
|
|
|
|
Object t1 = meta.get("isFrameworkApk");
|
2014-11-02 03:07:02 +01:00
|
|
|
apkOptions.isFramework = (t1 == null ? false : (Boolean) t1);
|
|
|
|
apkOptions.resourcesAreCompressed = meta.get("compressionType") == null
|
2014-08-16 03:24:12 +02:00
|
|
|
? false
|
2014-11-02 03:07:02 +01:00
|
|
|
: Boolean.valueOf(meta.get("compressionType").toString());
|
2014-08-16 03:24:12 +02:00
|
|
|
|
2014-02-10 02:01:57 +01:00
|
|
|
mAndRes.setSdkInfo((Map<String, String>) meta.get("sdkInfo"));
|
|
|
|
mAndRes.setPackageId((Map<String, String>) meta.get("packageInfo"));
|
2014-01-07 17:59:37 +01:00
|
|
|
mAndRes.setPackageInfo((Map<String, String>) meta.get("packageInfo"));
|
2014-02-10 02:01:57 +01:00
|
|
|
mAndRes.setVersionInfo((Map<String, String>) meta.get("versionInfo"));
|
2015-03-14 14:20:00 +01:00
|
|
|
mAndRes.setSharedLibrary((boolean) (meta.get("sharedLibrary") == null ? false : meta.get("sharedLibrary")));
|
2014-02-10 02:01:57 +01:00
|
|
|
|
|
|
|
if (outFile == null) {
|
|
|
|
String outFileName = (String) meta.get("apkFileName");
|
2014-08-16 16:48:02 +02:00
|
|
|
outFile = new File(appDir, "dist" + File.separator + (outFileName == null ? "out.apk" : outFileName));
|
2014-02-10 02:01:57 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
new File(appDir, APK_DIRNAME).mkdirs();
|
2014-11-02 03:07:02 +01:00
|
|
|
buildSources(appDir);
|
|
|
|
buildNonDefaultSources(appDir);
|
2015-04-16 15:02:24 +02:00
|
|
|
ResXmlPatcher.fixingPublicAttrsInProviderAttributes(new File(appDir, "AndroidManifest.xml"));
|
2014-11-02 03:07:02 +01:00
|
|
|
buildResources(appDir, (Map<String, Object>) meta.get("usesFramework"));
|
2015-04-16 14:24:27 +02:00
|
|
|
|
2014-11-02 03:07:02 +01:00
|
|
|
buildLib(appDir);
|
|
|
|
buildCopyOriginalFiles(appDir);
|
|
|
|
buildApk(appDir, outFile);
|
2013-04-01 00:13:10 +02:00
|
|
|
|
|
|
|
// we must go after the Apk is built, and copy the files in via Zip
|
|
|
|
// this is because Aapt won't add files it doesn't know (ex unknown files)
|
2014-08-16 03:24:12 +02:00
|
|
|
buildUnknownFiles(appDir, outFile, meta);
|
2014-02-10 02:01:57 +01:00
|
|
|
}
|
|
|
|
|
2014-11-02 03:07:02 +01:00
|
|
|
public void buildSources(File appDir)
|
2014-02-10 02:01:57 +01:00
|
|
|
throws AndrolibException {
|
2014-11-02 03:07:02 +01:00
|
|
|
if (!buildSourcesRaw(appDir, "classes.dex")
|
|
|
|
&& !buildSourcesSmali(appDir, "smali", "classes.dex")
|
|
|
|
&& !buildSourcesJava(appDir)) {
|
2014-02-10 02:01:57 +01:00
|
|
|
LOGGER.warning("Could not find sources");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-11-02 03:07:02 +01:00
|
|
|
public void buildNonDefaultSources(ExtFile appDir)
|
2014-02-10 02:01:57 +01:00
|
|
|
throws AndrolibException {
|
|
|
|
try {
|
2015-04-04 15:09:07 +02:00
|
|
|
// loop through any smali_ directories for multi-dex apks
|
2014-08-16 15:29:57 +02:00
|
|
|
Map<String, Directory> dirs = appDir.getDirectory().getDirs();
|
|
|
|
for (Map.Entry<String, Directory> directory : dirs.entrySet()) {
|
|
|
|
String name = directory.getKey();
|
|
|
|
if (name.startsWith("smali_")) {
|
|
|
|
String filename = name.substring(name.indexOf("_") + 1) + ".dex";
|
|
|
|
|
2014-11-02 03:07:02 +01:00
|
|
|
if (!buildSourcesRaw(appDir, filename)
|
|
|
|
&& !buildSourcesSmali(appDir, name, filename)
|
|
|
|
&& !buildSourcesJava(appDir)) {
|
2014-08-16 15:29:57 +02:00
|
|
|
LOGGER.warning("Could not find sources");
|
|
|
|
}
|
|
|
|
}
|
2014-02-10 02:01:57 +01:00
|
|
|
}
|
2015-04-04 15:09:07 +02:00
|
|
|
|
|
|
|
// loop through any classes#.dex files for multi-dex apks
|
|
|
|
File[] dexFiles = appDir.listFiles();
|
|
|
|
if (dexFiles != null) {
|
|
|
|
for (File dex : dexFiles) {
|
|
|
|
|
|
|
|
// skip classes.dex because we have handled it in buildSources()
|
|
|
|
if (dex.getName().endsWith(".dex") && ! dex.getName().equalsIgnoreCase("classes.dex")) {
|
|
|
|
buildSourcesRaw(appDir, dex.getName());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2014-08-16 15:29:57 +02:00
|
|
|
} catch(DirectoryException ex) {
|
2014-02-10 02:01:57 +01:00
|
|
|
throw new AndrolibException(ex);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-11-02 03:07:02 +01:00
|
|
|
public boolean buildSourcesRaw(File appDir, String filename)
|
2014-02-10 02:01:57 +01:00
|
|
|
throws AndrolibException {
|
2014-08-16 15:29:57 +02:00
|
|
|
File working = new File(appDir, filename);
|
|
|
|
if (!working.exists()) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
File stored = new File(appDir, APK_DIRNAME + "/" + filename);
|
2014-11-02 03:07:02 +01:00
|
|
|
if (apkOptions.forceBuildAll || isModified(working, stored)) {
|
2014-08-16 15:29:57 +02:00
|
|
|
LOGGER.info("Copying " + appDir.toString() + " " + filename + " file...");
|
|
|
|
try {
|
|
|
|
BrutIO.copyAndClose(new FileInputStream(working), new FileOutputStream(stored));
|
|
|
|
return true;
|
|
|
|
} catch (IOException ex) {
|
|
|
|
throw new AndrolibException(ex);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2014-11-02 03:07:02 +01:00
|
|
|
public boolean buildSourcesSmali(File appDir, String folder, String filename)
|
2014-08-16 15:29:57 +02:00
|
|
|
throws AndrolibException {
|
|
|
|
ExtFile smaliDir = new ExtFile(appDir, folder);
|
2014-02-10 02:01:57 +01:00
|
|
|
if (!smaliDir.exists()) {
|
|
|
|
return false;
|
|
|
|
}
|
2014-08-16 15:29:57 +02:00
|
|
|
File dex = new File(appDir, APK_DIRNAME + "/" + filename);
|
2014-11-02 03:07:02 +01:00
|
|
|
if (! apkOptions.forceBuildAll) {
|
2014-02-10 02:01:57 +01:00
|
|
|
LOGGER.info("Checking whether sources has changed...");
|
|
|
|
}
|
2014-11-02 03:07:02 +01:00
|
|
|
if (apkOptions.forceBuildAll || isModified(smaliDir, dex)) {
|
2014-08-16 15:29:57 +02:00
|
|
|
LOGGER.info("Smaling " + folder + " folder into " + filename +"...");
|
2014-02-10 02:01:57 +01:00
|
|
|
dex.delete();
|
2014-11-02 03:07:02 +01:00
|
|
|
SmaliBuilder.build(smaliDir, dex, apkOptions.debugMode);
|
2014-02-10 02:01:57 +01:00
|
|
|
}
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2014-11-02 03:07:02 +01:00
|
|
|
public boolean buildSourcesJava(File appDir)
|
2014-02-10 02:01:57 +01:00
|
|
|
throws AndrolibException {
|
|
|
|
File javaDir = new File(appDir, "src");
|
|
|
|
if (!javaDir.exists()) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
File dex = new File(appDir, APK_DIRNAME + "/classes.dex");
|
2014-11-02 03:07:02 +01:00
|
|
|
if (! apkOptions.forceBuildAll) {
|
2014-02-10 02:01:57 +01:00
|
|
|
LOGGER.info("Checking whether sources has changed...");
|
|
|
|
}
|
2014-11-02 03:07:02 +01:00
|
|
|
if (apkOptions.forceBuildAll || isModified(javaDir, dex)) {
|
2014-02-10 02:01:57 +01:00
|
|
|
LOGGER.info("Building java sources...");
|
|
|
|
dex.delete();
|
|
|
|
new AndrolibJava().build(javaDir, dex);
|
|
|
|
}
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2014-11-02 03:07:02 +01:00
|
|
|
public void buildResources(ExtFile appDir, Map<String, Object> usesFramework)
|
2014-08-16 03:24:12 +02:00
|
|
|
throws BrutException {
|
2014-11-02 03:07:02 +01:00
|
|
|
if (!buildResourcesRaw(appDir) && !buildResourcesFull(appDir, usesFramework)
|
|
|
|
&& !buildManifest(appDir, usesFramework)) {
|
2014-02-10 02:01:57 +01:00
|
|
|
LOGGER.warning("Could not find resources");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-11-02 03:07:02 +01:00
|
|
|
public boolean buildResourcesRaw(ExtFile appDir)
|
2014-08-16 03:24:12 +02:00
|
|
|
throws AndrolibException {
|
2014-02-10 02:01:57 +01:00
|
|
|
try {
|
|
|
|
if (!new File(appDir, "resources.arsc").exists()) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
File apkDir = new File(appDir, APK_DIRNAME);
|
2014-11-02 03:07:02 +01:00
|
|
|
if (! apkOptions.forceBuildAll) {
|
2014-02-10 02:01:57 +01:00
|
|
|
LOGGER.info("Checking whether resources has changed...");
|
|
|
|
}
|
2014-11-02 03:07:02 +01:00
|
|
|
if (apkOptions.forceBuildAll || isModified(newFiles(APK_RESOURCES_FILENAMES, appDir),
|
2014-02-10 02:01:57 +01:00
|
|
|
newFiles(APK_RESOURCES_FILENAMES, apkDir))) {
|
|
|
|
LOGGER.info("Copying raw resources...");
|
2015-04-16 15:12:20 +02:00
|
|
|
appDir.getDirectory().copyToDir(apkDir, APK_RESOURCES_FILENAMES);
|
2014-02-10 02:01:57 +01:00
|
|
|
}
|
|
|
|
return true;
|
|
|
|
} catch (DirectoryException ex) {
|
|
|
|
throw new AndrolibException(ex);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-11-02 03:07:02 +01:00
|
|
|
public boolean buildResourcesFull(File appDir, Map<String, Object> usesFramework)
|
2014-02-10 02:01:57 +01:00
|
|
|
throws AndrolibException {
|
|
|
|
try {
|
|
|
|
if (!new File(appDir, "res").exists()) {
|
|
|
|
return false;
|
|
|
|
}
|
2014-11-02 03:07:02 +01:00
|
|
|
if (! apkOptions.forceBuildAll) {
|
2014-02-10 02:01:57 +01:00
|
|
|
LOGGER.info("Checking whether resources has changed...");
|
|
|
|
}
|
|
|
|
File apkDir = new File(appDir, APK_DIRNAME);
|
2014-11-02 03:07:02 +01:00
|
|
|
if (apkOptions.forceBuildAll || isModified(newFiles(APP_RESOURCES_FILENAMES, appDir),
|
2014-02-10 02:01:57 +01:00
|
|
|
newFiles(APK_RESOURCES_FILENAMES, apkDir))) {
|
|
|
|
LOGGER.info("Building resources...");
|
|
|
|
|
|
|
|
File apkFile = File.createTempFile("APKTOOL", null);
|
|
|
|
apkFile.delete();
|
|
|
|
|
|
|
|
File ninePatch = new File(appDir, "9patch");
|
|
|
|
if (!ninePatch.exists()) {
|
|
|
|
ninePatch = null;
|
|
|
|
}
|
|
|
|
mAndRes.aaptPackage(apkFile, new File(appDir,
|
|
|
|
"AndroidManifest.xml"), new File(appDir, "res"),
|
2014-11-02 03:07:02 +01:00
|
|
|
ninePatch, null, parseUsesFramework(usesFramework));
|
2014-02-10 02:01:57 +01:00
|
|
|
|
|
|
|
Directory tmpDir = new ExtFile(apkFile).getDirectory();
|
|
|
|
tmpDir.copyToDir(apkDir,
|
|
|
|
tmpDir.containsDir("res") ? APK_RESOURCES_FILENAMES
|
|
|
|
: APK_RESOURCES_WITHOUT_RES_FILENAMES);
|
|
|
|
|
|
|
|
// delete tmpDir
|
|
|
|
apkFile.delete();
|
|
|
|
}
|
|
|
|
return true;
|
2014-09-23 15:24:45 +02:00
|
|
|
} catch (IOException | BrutException ex) {
|
2014-02-10 02:01:57 +01:00
|
|
|
throw new AndrolibException(ex);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-11-02 03:07:02 +01:00
|
|
|
public boolean buildManifestRaw(ExtFile appDir)
|
2014-08-16 03:24:12 +02:00
|
|
|
throws AndrolibException {
|
2014-02-10 02:01:57 +01:00
|
|
|
try {
|
|
|
|
File apkDir = new File(appDir, APK_DIRNAME);
|
|
|
|
LOGGER.info("Copying raw AndroidManifest.xml...");
|
|
|
|
appDir.getDirectory().copyToDir(apkDir, APK_MANIFEST_FILENAMES);
|
|
|
|
return true;
|
|
|
|
} catch (DirectoryException ex) {
|
|
|
|
throw new AndrolibException(ex);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-11-02 03:07:02 +01:00
|
|
|
public boolean buildManifest(ExtFile appDir, Map<String, Object> usesFramework)
|
2014-02-10 02:01:57 +01:00
|
|
|
throws BrutException {
|
|
|
|
try {
|
|
|
|
if (!new File(appDir, "AndroidManifest.xml").exists()) {
|
|
|
|
return false;
|
|
|
|
}
|
2014-11-02 03:07:02 +01:00
|
|
|
if (! apkOptions.forceBuildAll) {
|
2014-02-10 02:01:57 +01:00
|
|
|
LOGGER.info("Checking whether resources has changed...");
|
|
|
|
}
|
|
|
|
|
|
|
|
File apkDir = new File(appDir, APK_DIRNAME);
|
|
|
|
|
2014-11-02 03:07:02 +01:00
|
|
|
if (apkOptions.debugMode) {
|
2015-04-16 15:02:24 +02:00
|
|
|
ResXmlPatcher.removeApplicationDebugTag(new File(apkDir,"AndroidManifest.xml"));
|
2014-02-10 02:01:57 +01:00
|
|
|
}
|
|
|
|
|
2014-11-02 03:07:02 +01:00
|
|
|
if (apkOptions.forceBuildAll || isModified(newFiles(APK_MANIFEST_FILENAMES, appDir),
|
2014-02-10 02:01:57 +01:00
|
|
|
newFiles(APK_MANIFEST_FILENAMES, apkDir))) {
|
|
|
|
LOGGER.info("Building AndroidManifest.xml...");
|
|
|
|
|
|
|
|
File apkFile = File.createTempFile("APKTOOL", null);
|
|
|
|
apkFile.delete();
|
|
|
|
|
|
|
|
File ninePatch = new File(appDir, "9patch");
|
|
|
|
if (!ninePatch.exists()) {
|
|
|
|
ninePatch = null;
|
|
|
|
}
|
|
|
|
|
|
|
|
mAndRes.aaptPackage(apkFile, new File(appDir,
|
|
|
|
"AndroidManifest.xml"), null, ninePatch, null,
|
2014-11-02 03:07:02 +01:00
|
|
|
parseUsesFramework(usesFramework));
|
2014-02-10 02:01:57 +01:00
|
|
|
|
|
|
|
Directory tmpDir = new ExtFile(apkFile).getDirectory();
|
|
|
|
tmpDir.copyToDir(apkDir, APK_MANIFEST_FILENAMES);
|
|
|
|
|
|
|
|
}
|
|
|
|
return true;
|
2014-09-23 15:24:45 +02:00
|
|
|
} catch (IOException | DirectoryException ex) {
|
2014-02-10 02:01:57 +01:00
|
|
|
throw new AndrolibException(ex);
|
|
|
|
} catch (AndrolibException ex) {
|
|
|
|
LOGGER.warning("Parse AndroidManifest.xml failed, treat it as raw file.");
|
2014-11-02 03:07:02 +01:00
|
|
|
return buildManifestRaw(appDir);
|
2014-02-10 02:01:57 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-11-02 03:07:02 +01:00
|
|
|
public void buildLib(File appDir)
|
2014-02-10 02:01:57 +01:00
|
|
|
throws AndrolibException {
|
|
|
|
File working = new File(appDir, "lib");
|
|
|
|
if (!working.exists()) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
File stored = new File(appDir, APK_DIRNAME + "/lib");
|
2014-11-02 03:07:02 +01:00
|
|
|
if (apkOptions.forceBuildAll || isModified(working, stored)) {
|
2014-02-10 02:01:57 +01:00
|
|
|
LOGGER.info("Copying libs...");
|
|
|
|
try {
|
|
|
|
OS.rmdir(stored);
|
|
|
|
OS.cpdir(working, stored);
|
|
|
|
} catch (BrutException ex) {
|
|
|
|
throw new AndrolibException(ex);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-11-02 03:07:02 +01:00
|
|
|
public void buildCopyOriginalFiles(File appDir)
|
2014-08-16 03:24:12 +02:00
|
|
|
throws AndrolibException {
|
2014-11-02 03:07:02 +01:00
|
|
|
if (apkOptions.copyOriginalFiles) {
|
2014-02-10 02:01:57 +01:00
|
|
|
File originalDir = new File(appDir, "original");
|
|
|
|
if(originalDir.exists()) {
|
|
|
|
try {
|
|
|
|
LOGGER.info("Copy original files...");
|
|
|
|
Directory in = (new ExtFile(originalDir)).getDirectory();
|
|
|
|
if(in.containsFile("AndroidManifest.xml")) {
|
|
|
|
LOGGER.info("Copy AndroidManifest.xml...");
|
|
|
|
in.copyToDir(new File(appDir, APK_DIRNAME), "AndroidManifest.xml");
|
|
|
|
}
|
|
|
|
if (in.containsDir("META-INF")) {
|
|
|
|
LOGGER.info("Copy META-INF...");
|
|
|
|
in.copyToDir(new File(appDir, APK_DIRNAME), "META-INF");
|
|
|
|
}
|
|
|
|
} catch (DirectoryException ex) {
|
|
|
|
throw new AndrolibException(ex);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2013-03-17 01:50:00 +01:00
|
|
|
|
2013-04-01 00:13:10 +02:00
|
|
|
public void buildUnknownFiles(File appDir, File outFile, Map<String, Object> meta)
|
2013-09-07 15:53:07 +02:00
|
|
|
throws AndrolibException {
|
2013-04-01 00:13:10 +02:00
|
|
|
if (meta.containsKey("unknownFiles")) {
|
|
|
|
LOGGER.info("Copying unknown files/dir...");
|
2013-03-21 14:58:14 +01:00
|
|
|
|
2013-04-01 00:13:10 +02:00
|
|
|
Map<String, String> files = (Map<String, String>)meta.get("unknownFiles");
|
2015-03-25 04:14:05 +01:00
|
|
|
File tempFile = new File(outFile.getParent(), outFile.getName() + ".apktool_temp");
|
|
|
|
boolean renamed = outFile.renameTo(tempFile);
|
|
|
|
if(!renamed) {
|
|
|
|
throw new AndrolibException("Unable to rename temporary file");
|
|
|
|
}
|
2013-03-21 14:58:14 +01:00
|
|
|
|
2015-03-25 04:14:05 +01:00
|
|
|
try (
|
|
|
|
ZipFile inputFile = new ZipFile(tempFile);
|
2015-04-16 15:12:20 +02:00
|
|
|
ZipOutputStream actualOutput = new ZipOutputStream(new FileOutputStream(outFile))
|
2015-03-25 04:14:05 +01:00
|
|
|
) {
|
2015-03-30 07:30:52 +02:00
|
|
|
copyExistingFiles(inputFile, actualOutput);
|
|
|
|
copyUnknownFiles(appDir, actualOutput, files);
|
2013-04-01 00:13:10 +02:00
|
|
|
} catch (IOException ex) {
|
|
|
|
throw new AndrolibException(ex);
|
|
|
|
}
|
2015-03-25 04:14:05 +01:00
|
|
|
|
|
|
|
// Remove our temporary file.
|
|
|
|
tempFile.delete();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-03-30 07:30:52 +02:00
|
|
|
private void copyExistingFiles(ZipFile inputFile, ZipOutputStream outputFile) throws IOException {
|
2015-03-25 04:14:05 +01:00
|
|
|
// First, copy the contents from the existing outFile:
|
|
|
|
Enumeration<? extends ZipEntry> entries = inputFile.entries();
|
|
|
|
while (entries.hasMoreElements()) {
|
|
|
|
ZipEntry entry = new ZipEntry(entries.nextElement());
|
2015-04-16 15:12:20 +02:00
|
|
|
|
2015-03-25 04:14:05 +01:00
|
|
|
// We can't reuse the compressed size because it depends on compression sizes.
|
|
|
|
entry.setCompressedSize(-1);
|
|
|
|
outputFile.putNextEntry(entry);
|
|
|
|
|
|
|
|
// No need to create directory entries in the final apk
|
2015-04-16 15:12:20 +02:00
|
|
|
if (! entry.isDirectory()) {
|
|
|
|
BrutIO.copy(inputFile, outputFile, entry);
|
2015-03-25 04:14:05 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
outputFile.closeEntry();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-03-30 07:30:52 +02:00
|
|
|
private void copyUnknownFiles(File appDir, ZipOutputStream outputFile, Map<String, String> files)
|
2015-03-25 04:22:05 +01:00
|
|
|
throws IOException {
|
2015-03-25 04:14:05 +01:00
|
|
|
File unknownFileDir = new File(appDir, UNK_DIRNAME);
|
|
|
|
|
|
|
|
// loop through unknown files
|
|
|
|
for (Map.Entry<String,String> unknownFileInfo : files.entrySet()) {
|
|
|
|
File inputFile = new File(unknownFileDir, unknownFileInfo.getKey());
|
2015-04-16 15:12:20 +02:00
|
|
|
if (inputFile.isDirectory()) {
|
2015-03-25 04:14:05 +01:00
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
ZipEntry newEntry = new ZipEntry(unknownFileInfo.getKey());
|
|
|
|
int method = Integer.valueOf(unknownFileInfo.getValue());
|
2015-03-25 04:22:05 +01:00
|
|
|
LOGGER.fine(String.format("Copying unknown file %s with method %d", unknownFileInfo.getKey(), method));
|
2015-04-16 15:12:20 +02:00
|
|
|
if (method == ZipEntry.STORED) {
|
2015-03-25 04:14:05 +01:00
|
|
|
newEntry.setMethod(ZipEntry.STORED);
|
|
|
|
newEntry.setSize(inputFile.length());
|
|
|
|
newEntry.setCompressedSize(-1);
|
|
|
|
BufferedInputStream unknownFile = new BufferedInputStream(new FileInputStream(inputFile));
|
2015-03-30 07:30:52 +02:00
|
|
|
CRC32 crc = BrutIO.calculateCrc(unknownFile);
|
2015-03-25 04:14:05 +01:00
|
|
|
newEntry.setCrc(crc.getValue());
|
2015-03-25 17:56:55 +01:00
|
|
|
} else {
|
2015-03-25 04:14:05 +01:00
|
|
|
newEntry.setMethod(ZipEntry.DEFLATED);
|
|
|
|
}
|
|
|
|
outputFile.putNextEntry(newEntry);
|
|
|
|
|
2015-04-16 15:12:20 +02:00
|
|
|
BrutIO.copy(inputFile, outputFile);
|
2015-03-25 04:14:05 +01:00
|
|
|
outputFile.closeEntry();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-11-02 03:07:02 +01:00
|
|
|
public void buildApk(File appDir, File outApk) throws AndrolibException {
|
2014-02-10 02:01:57 +01:00
|
|
|
LOGGER.info("Building apk file...");
|
|
|
|
if (outApk.exists()) {
|
|
|
|
outApk.delete();
|
|
|
|
} else {
|
|
|
|
File outDir = outApk.getParentFile();
|
|
|
|
if (outDir != null && !outDir.exists()) {
|
|
|
|
outDir.mkdirs();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
File assetDir = new File(appDir, "assets");
|
|
|
|
if (!assetDir.exists()) {
|
|
|
|
assetDir = null;
|
|
|
|
}
|
2014-11-02 03:07:02 +01:00
|
|
|
mAndRes.aaptPackage(outApk, null, null, new File(appDir, APK_DIRNAME), assetDir, null);
|
2014-02-10 02:01:57 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
public void publicizeResources(File arscFile) throws AndrolibException {
|
|
|
|
mAndRes.publicizeResources(arscFile);
|
|
|
|
}
|
|
|
|
|
2014-11-02 03:07:02 +01:00
|
|
|
public void installFramework(File frameFile)
|
2014-02-10 02:01:57 +01:00
|
|
|
throws AndrolibException {
|
2014-11-02 03:07:02 +01:00
|
|
|
mAndRes.installFramework(frameFile);
|
2014-02-10 02:01:57 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
public boolean isFrameworkApk(ResTable resTable) {
|
|
|
|
for (ResPackage pkg : resTable.listMainPackages()) {
|
|
|
|
if (pkg.getId() < 64) {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
public static String getVersion() {
|
|
|
|
return ApktoolProperties.get("application.version");
|
|
|
|
}
|
|
|
|
|
|
|
|
private File[] parseUsesFramework(Map<String, Object> usesFramework)
|
|
|
|
throws AndrolibException {
|
|
|
|
if (usesFramework == null) {
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
|
|
|
List<Integer> ids = (List<Integer>) usesFramework.get("ids");
|
|
|
|
if (ids == null || ids.isEmpty()) {
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
|
|
|
String tag = (String) usesFramework.get("tag");
|
|
|
|
File[] files = new File[ids.size()];
|
|
|
|
int i = 0;
|
|
|
|
for (int id : ids) {
|
|
|
|
files[i++] = mAndRes.getFrameworkApk(id, tag);
|
|
|
|
}
|
|
|
|
return files;
|
|
|
|
}
|
|
|
|
|
|
|
|
private boolean isModified(File working, File stored) {
|
2014-10-29 22:18:25 +01:00
|
|
|
return ! stored.exists() || BrutIO.recursiveModifiedTime(working) > BrutIO .recursiveModifiedTime(stored);
|
2014-02-10 02:01:57 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
private boolean isModified(File[] working, File[] stored) {
|
|
|
|
for (int i = 0; i < stored.length; i++) {
|
|
|
|
if (!stored[i].exists()) {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}
|
2014-10-29 22:18:25 +01:00
|
|
|
return BrutIO.recursiveModifiedTime(working) > BrutIO.recursiveModifiedTime(stored);
|
2014-02-10 02:01:57 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
private File[] newFiles(String[] names, File dir) {
|
|
|
|
File[] files = new File[names.length];
|
|
|
|
for (int i = 0; i < names.length; i++) {
|
|
|
|
files[i] = new File(dir, names[i]);
|
|
|
|
}
|
|
|
|
return files;
|
|
|
|
}
|
|
|
|
|
2014-08-16 15:29:57 +02:00
|
|
|
private final static Logger LOGGER = Logger.getLogger(Androlib.class.getName());
|
2013-02-13 04:12:17 +01:00
|
|
|
|
2014-02-10 02:01:57 +01:00
|
|
|
private final static String SMALI_DIRNAME = "smali";
|
|
|
|
private final static String APK_DIRNAME = "build/apk";
|
2013-03-31 16:04:12 +02:00
|
|
|
private final static String UNK_DIRNAME = "unknown";
|
2014-02-10 02:01:57 +01:00
|
|
|
private final static String[] APK_RESOURCES_FILENAMES = new String[] {
|
|
|
|
"resources.arsc", "AndroidManifest.xml", "res" };
|
|
|
|
private final static String[] APK_RESOURCES_WITHOUT_RES_FILENAMES = new String[] {
|
|
|
|
"resources.arsc", "AndroidManifest.xml" };
|
|
|
|
private final static String[] APP_RESOURCES_FILENAMES = new String[] {
|
|
|
|
"AndroidManifest.xml", "res" };
|
|
|
|
private final static String[] APK_MANIFEST_FILENAMES = new String[] {
|
2013-03-31 16:04:12 +02:00
|
|
|
"AndroidManifest.xml" };
|
|
|
|
private final static String[] APK_STANDARD_ALL_FILENAMES = new String[] {
|
2014-08-16 15:29:57 +02:00
|
|
|
"classes.dex", "AndroidManifest.xml", "resources.arsc", "res", "lib", "libs", "assets", "META-INF" };
|
2012-09-20 03:27:35 +02:00
|
|
|
}
|