Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
164 changes: 71 additions & 93 deletions brut.apktool/apktool-lib/src/main/java/brut/androlib/ApkBuilder.java
Original file line number Diff line number Diff line change
Expand Up @@ -123,53 +123,56 @@ public void build(File outApk) throws AndrolibException {
}

private void buildSources(File outDir) throws AndrolibException {
if (!copySourcesRaw(outDir, "classes.dex")) {
buildSourcesSmali(outDir, "smali", "classes.dex");
}

try {
Directory in = mApkDir.getDirectory();

// Loop through any smali_ directories for multi-dex APKs.
for (String dirName : in.getDirs().keySet()) {
if (dirName.startsWith("smali_")) {
String fileName = dirName.substring(dirName.indexOf("_") + 1) + ".dex";
if (!copySourcesRaw(outDir, fileName)) {
buildSourcesSmali(outDir, dirName, fileName);
}
// Copy raw dex files.
Set<String> dexFiles = new HashSet<>();
for (String fileName : in.getFiles()) {
if (fileName.endsWith(".dex")) {
copySourcesRaw(outDir, fileName);
dexFiles.add(fileName);
}
}

// Loop through any classes#.dex files for multi-dex APKs.
for (String fileName : in.getFiles()) {
// Skip classes.dex because we have handled it.
if (fileName.endsWith(".dex") && !fileName.equals("classes.dex")) {
copySourcesRaw(outDir, fileName);
// Build smali dirs.
for (String dirName : in.getDirs().keySet()) {
String fileName;
if (dirName.equals("smali")) {
fileName = "classes.dex";
} else if (dirName.startsWith("smali_")) {
fileName = dirName.substring(dirName.indexOf('_') + 1) + ".dex";
} else {
continue;
}

if (!dexFiles.contains(fileName)) {
buildSourcesSmali(outDir, dirName, fileName);
}
}
} catch (DirectoryException ex) {
throw new AndrolibException(ex);
}
}

private boolean copySourcesRaw(File outDir, String fileName) throws AndrolibException {
File working = new File(mApkDir, fileName);
if (!working.isFile()) {
return false;
private void copySourcesRaw(File outDir, String fileName) throws AndrolibException {
File inFile = new File(mApkDir, fileName);
if (!inFile.isFile()) {
return;
}

File stored = new File(outDir, fileName);
if (!mConfig.isForced() && !isModified(working, stored)) {
return true;
File outFile = new File(outDir, fileName);
if (!mConfig.isForced() && !isFileNewer(inFile, outFile)) {
LOGGER.info("File " + fileName + " has not changed.");
return;
}

LOGGER.info("Copying raw " + fileName + " file...");
try {
BrutIO.copyAndClose(Files.newInputStream(working.toPath()), Files.newOutputStream(stored.toPath()));
BrutIO.copyAndClose(Files.newInputStream(inFile.toPath()), Files.newOutputStream(outFile.toPath()));
} catch (IOException ex) {
throw new AndrolibException(ex);
}
return true;
}

private void buildSourcesSmali(File outDir, String dirName, String fileName) throws AndrolibException {
Expand All @@ -195,11 +198,9 @@ private void buildSourcesSmaliJob(File outDir, String dirName, String fileName)
}

File dexFile = new File(outDir, fileName);
if (!mConfig.isForced()) {
LOGGER.info("Checking whether sources have changed...");
if (!isModified(smaliDir, dexFile)) {
return;
}
if (!mConfig.isForced() && !isFileNewer(smaliDir, dexFile)) {
LOGGER.info("Sources in " + dirName + " have not changed.");
return;
}
OS.rmfile(dexFile);

Expand Down Expand Up @@ -230,55 +231,55 @@ private void backupManifestFile(File manifest, File manifestOrig) throws Androli

private void buildResources(File outDir, File manifest) throws AndrolibException {
if (!manifest.isFile()) {
LOGGER.fine("Could not find AndroidManifest.xml");
return;
}

if (new File(mApkDir, "resources.arsc").isFile()) {
copyResourcesRaw(outDir, manifest);
} else if (new File(mApkDir, "res").isDirectory()) {
buildResourcesFull(outDir, manifest);
} else {
LOGGER.fine("Could not find resources");
buildManifest(outDir, manifest);
// Copy raw resources.
File arscFile = new File(mApkDir, "resources.arsc");
if (arscFile.isFile()) {
copyResourcesRaw(outDir, manifest, arscFile);
return;
}

// Build resources.
File resDir = new File(mApkDir, "res");
if (resDir.isDirectory()) {
buildResourcesFull(outDir, manifest, resDir);
return;
}

// Build manifest only.
LOGGER.fine("Could not find resources.");
buildManifest(outDir, manifest);
}

private void copyResourcesRaw(File outDir, File manifest) throws AndrolibException {
if (!mConfig.isForced()) {
LOGGER.info("Checking whether resources have changed...");
if (!isModified(manifest, new File(outDir, "AndroidManifest.xml"))
&& !isModified(new File(mApkDir, "resources.arsc"), new File(outDir, "resources.arsc"))
&& !isModified(newFiles(mApkDir, ApkInfo.RESOURCES_DIRNAMES),
newFiles(outDir, ApkInfo.RESOURCES_DIRNAMES))) {
return;
}
private void copyResourcesRaw(File outDir, File manifest, File arscFile) throws AndrolibException {
if (!mConfig.isForced()
&& !isFileNewer(manifest, new File(outDir, "AndroidManifest.xml"))
&& !isFileNewer(arscFile, new File(outDir, "resources.arsc"))) {
LOGGER.info("Resources have not changed.");
return;
}

LOGGER.info("Copying raw resources...");
try {
Directory in = mApkDir.getDirectory();

in.copyToDir(outDir, "AndroidManifest.xml");
in.copyToDir(outDir, "resources.arsc");
in.copyToDir(outDir, ApkInfo.RESOURCES_DIRNAMES);
in.copyToDir(outDir, "AndroidManifest.xml", "resources.arsc");
} catch (DirectoryException ex) {
throw new AndrolibException(ex);
}
}

private void buildResourcesFull(File outDir, File manifest) throws AndrolibException {
File resourcesFile = new File(outDir.getParentFile(), "resources.zip");
if (!mConfig.isForced()) {
LOGGER.info("Checking whether resources have changed...");
if (!isModified(manifest, new File(outDir, "AndroidManifest.xml"))
&& !isModified(newFiles(mApkDir, ApkInfo.RESOURCES_DIRNAMES),
newFiles(outDir, ApkInfo.RESOURCES_DIRNAMES))
&& resourcesFile.isFile()) {
return;
}
private void buildResourcesFull(File outDir, File manifest, File resDir) throws AndrolibException {
File resZip = new File(outDir.getParentFile(), "resources.zip");
if (!mConfig.isForced() && resZip.isFile()
&& !isFileNewer(manifest, new File(outDir, "AndroidManifest.xml"))
&& !isFileNewer(resDir, new File(outDir, "res"))) {
LOGGER.info("Resources have not changed.");
return;
}
OS.rmfile(resourcesFile);
OS.rmfile(resZip);

if (mConfig.isDebuggable()) {
LOGGER.info("Setting 'debuggable' attribute to 'true' in AndroidManifest.xml");
Expand All @@ -287,10 +288,8 @@ private void buildResourcesFull(File outDir, File manifest) throws AndrolibExcep

if (mConfig.isNetSecConf()) {
String targetSdkVersion = mApkInfo.getSdkInfo().getTargetSdkVersion();
if (targetSdkVersion != null) {
if (SdkInfo.parseSdkInt(targetSdkVersion) < ResConfig.SDK_NOUGAT) {
LOGGER.warning("Target SDK version is lower than 24! Network Security Configuration might be ignored!");
}
if (targetSdkVersion != null && SdkInfo.parseSdkInt(targetSdkVersion) < ResConfig.SDK_NOUGAT) {
LOGGER.warning("Target SDK version is lower than 24! Network Security Configuration might be ignored!");
}

File netSecConfOrig = new File(mApkDir, "res/xml/network_security_config.xml");
Expand All @@ -308,7 +307,6 @@ private void buildResourcesFull(File outDir, File manifest) throws AndrolibExcep
}
OS.rmfile(tmpFile);

File resDir = new File(mApkDir, "res");
File npDir = new File(mApkDir, "9patch");
if (!npDir.isDirectory()) {
npDir = null;
Expand All @@ -320,9 +318,7 @@ private void buildResourcesFull(File outDir, File manifest) throws AndrolibExcep
invoker.invoke(tmpFile, manifest, resDir, npDir, null, getIncludeFiles());

Directory tmpDir = tmpFile.getDirectory();
tmpDir.copyToDir(outDir, "AndroidManifest.xml");
tmpDir.copyToDir(outDir, "resources.arsc");
tmpDir.copyToDir(outDir, ApkInfo.RESOURCES_DIRNAMES);
tmpDir.copyToDir(outDir, "AndroidManifest.xml", "resources.arsc", "res");
} catch (DirectoryException ex) {
throw new AndrolibException(ex);
} finally {
Expand All @@ -331,11 +327,10 @@ private void buildResourcesFull(File outDir, File manifest) throws AndrolibExcep
}

private void buildManifest(File outDir, File manifest) throws AndrolibException {
if (!mConfig.isForced()) {
LOGGER.info("Checking whether AndroidManifest.xml has changed...");
if (!isModified(manifest, new File(outDir, "AndroidManifest.xml"))) {
return;
}
if (!mConfig.isForced()
&& !isFileNewer(manifest, new File(outDir, "AndroidManifest.xml"))) {
LOGGER.info("AndroidManifest.xml has not changed.");
return;
}

ExtFile tmpFile;
Expand Down Expand Up @@ -480,24 +475,7 @@ private File[] getIncludeFiles() throws AndrolibException {
return files.toArray(new File[0]);
}

private boolean isModified(File working, File stored) {
return !stored.exists() || BrutIO.recursiveModifiedTime(working) > BrutIO.recursiveModifiedTime(stored);
}

private boolean isModified(File[] working, File[] stored) {
for (File file : stored) {
if (!file.exists()) {
return true;
}
}
return BrutIO.recursiveModifiedTime(working) > BrutIO.recursiveModifiedTime(stored);
}

private File[] newFiles(File dir, String[] names) {
File[] files = new File[names.length];
for (int i = 0; i < names.length; i++) {
files[i] = new File(dir, names[i]);
}
return files;
private boolean isFileNewer(File file, File reference) {
return !reference.exists() || BrutIO.recursiveModifiedTime(file) > BrutIO.recursiveModifiedTime(reference);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -111,21 +111,11 @@ private void decodeSources(File outDir) throws AndrolibException {
return;
}

switch (mConfig.getDecodeSources()) {
case NONE:
copySourcesRaw(outDir, "classes.dex");
break;
case FULL:
case ONLY_MAIN_CLASSES:
decodeSourcesSmali(outDir, "classes.dex");
break;
}

try {
Directory in = mApkFile.getDirectory();

for (String fileName : in.getFiles(true)) {
if (!fileName.endsWith(".dex") || fileName.equals("classes.dex")) {
if (!fileName.endsWith(".dex")) {
continue;
}

Expand Down Expand Up @@ -178,12 +168,8 @@ private void decodeSourcesSmali(File outDir, String fileName) throws AndrolibExc
}

private void decodeSourcesSmaliJob(File outDir, String fileName) throws AndrolibException {
File smaliDir;
if (fileName.equals("classes.dex")) {
smaliDir = new File(outDir, "smali");
} else {
smaliDir = new File(outDir, "smali_" + fileName.substring(0, fileName.indexOf('.')));
}
File smaliDir = new File(outDir, "smali" + (!fileName.equals("classes.dex")
? "_" + fileName.substring(0, fileName.indexOf('.')) : ""));

OS.mkdir(smaliDir);

Expand Down Expand Up @@ -220,7 +206,6 @@ private void copyResourcesRaw(File outDir) throws AndrolibException {
Directory in = mApkFile.getDirectory();

in.copyToDir(outDir, "resources.arsc");
in.copyToDir(outDir, ApkInfo.RESOURCES_DIRNAMES);
} catch (DirectoryException ex) {
throw new AndrolibException(ex);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,15 +33,14 @@
import java.util.regex.Pattern;

public class ApkInfo implements YamlSerializable {
public static final String[] RESOURCES_DIRNAMES = { "res", "r", "R" };
public static final String[] RAW_DIRNAMES = { "assets", "lib" };

public static final Pattern ORIGINAL_FILENAMES_PATTERN = Pattern.compile(
"AndroidManifest\\.xml|META-INF/[^/]+\\.(RSA|SF|MF)|stamp-cert-sha256");

public static final Pattern STANDARD_FILENAMES_PATTERN = Pattern.compile(
"[^/]+\\.dex|resources\\.arsc|(" + String.join("|", RESOURCES_DIRNAMES) + "|"
+ String.join("|", RAW_DIRNAMES) + ")/.*|" + ORIGINAL_FILENAMES_PATTERN.pattern());
"[^/]+\\.dex|resources\\.arsc|(" + String.join("|", RAW_DIRNAMES) + ")/.*|"
+ ORIGINAL_FILENAMES_PATTERN.pattern());

private String mVersion;
private String mApkFileName;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,10 @@
import brut.androlib.exceptions.AndrolibException;
import brut.androlib.exceptions.NinePatchNotFoundException;
import brut.androlib.exceptions.RawXmlEncounteredException;
import brut.androlib.meta.ApkInfo;
import brut.androlib.res.table.ResEntry;
import brut.androlib.res.table.value.*;
import brut.directory.Directory;
import brut.directory.DirectoryException;
import brut.util.BrutIO;
import org.apache.commons.io.FilenameUtils;

import java.io.InputStream;
Expand Down Expand Up @@ -56,32 +54,12 @@ public void decode(ResEntry entry, Directory inDir, Directory outDir, Map<String
return;
}

// Strip resources dir to get inner path.
String inResPath = inFileName;
for (String dirName : ApkInfo.RESOURCES_DIRNAMES) {
String prefix = dirName + "/";
if (inResPath.startsWith(prefix)) {
inResPath = inResPath.substring(prefix.length());
break;
}
}

// Get resource file name.
String inResFileName = FilenameUtils.getName(inResPath);

// Some apps were somehow built with Thumbs.db in drawables.
// Replace with a null reference so we can rebuild the app.
if (inResFileName.equals("Thumbs.db")) {
entry.setValue(ResReference.NULL);
return;
}

// Get the file extension.
// Get input file extension.
String ext;
if (inResFileName.endsWith(".9.png")) {
if (inFileName.endsWith(".9.png")) {
ext = "9.png";
} else {
ext = FilenameUtils.getExtension(inResFileName).toLowerCase();
ext = FilenameUtils.getExtension(inFileName).toLowerCase();
}

// Use aapt2-like logic to determine which decoder to use.
Expand All @@ -99,15 +77,12 @@ public void decode(ResEntry entry, Directory inDir, Directory outDir, Map<String
}
}

// Generate output file path from entry.
String outResPath = entry.getTypeName() + entry.getConfig().getQualifiers() + "/" + entry.getName()
+ (ext.isEmpty() ? "" : "." + ext);
// Generate output file name from entry.
String outFileName = "res/" + entry.getTypeName() + entry.getConfig().getQualifiers()
+ "/" + entry.getName() + (ext.isEmpty() ? "" : "." + ext);

// Map output path to original path if it's different.
String outFileName = "res/" + outResPath;
if (!inFileName.equals(outFileName)) {
resFileMapping.put(inFileName, outFileName);
}
// Map input file name to output file name.
resFileMapping.put(inFileName, outFileName);

LOGGER.fine("Decoding file " + inFileName + " to " + outFileName);

Expand Down
Loading