Apktool/src/brut/androlib/Androlib.java
2010-06-02 09:08:54 +02:00

371 lines
13 KiB
Java

/*
* Copyright 2010 Ryszard Wiśniewski <brut.alll@gmail.com>.
*
* 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.
* under the License.
*/
package brut.androlib;
import brut.androlib.java.AndrolibJava;
import brut.androlib.res.AndrolibResources;
import brut.androlib.res.data.ResTable;
import brut.androlib.res.decoder.ARSCDecoder;
import brut.androlib.res.decoder.ARSCDecoder.FlagsOffset;
import brut.androlib.res.util.ExtFile;
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.*;
import java.nio.MappedByteBuffer;
import java.nio.channels.FileChannel.MapMode;
import java.util.List;
import java.util.logging.Logger;
/**
* @author Ryszard Wiśniewski <brut.alll@gmail.com>
*/
public class Androlib {
private final AndrolibResources mAndRes = new AndrolibResources();
public ResTable getResTable(ExtFile apkFile) throws AndrolibException {
return mAndRes.getResTable(apkFile);
}
public void decodeSourcesRaw(ExtFile apkFile, File outDir, boolean debug)
throws AndrolibException {
try {
if (debug) {
LOGGER.warning("Debug mode not available.");
}
Directory apk = apkFile.getDirectory();
LOGGER.info("Copying raw classes.dex file...");
apkFile.getDirectory().copyToDir(outDir, "classes.dex");
} catch (DirectoryException ex) {
throw new AndrolibException(ex);
}
}
public void decodeSourcesSmali(File apkFile, File outDir, boolean debug)
throws AndrolibException {
try {
File smaliDir = new File(outDir, SMALI_DIRNAME);
OS.rmdir(smaliDir);
smaliDir.mkdirs();
LOGGER.info("Baksmaling...");
SmaliDecoder.decode(apkFile, smaliDir, debug);
} 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 decodeResourcesRaw(ExtFile apkFile, File outDir)
throws AndrolibException {
try {
Directory apk = apkFile.getDirectory();
LOGGER.info("Copying raw resources...");
apkFile.getDirectory().copyToDir(outDir, APK_RESOURCES_FILENAMES);
} catch (DirectoryException ex) {
throw new AndrolibException(ex);
}
}
public void decodeResourcesFull(ExtFile apkFile, File outDir,
ResTable resTable) throws AndrolibException {
LOGGER.info("Decoding resources...");
mAndRes.decode(resTable, apkFile, outDir);
}
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");
}
} catch (DirectoryException ex) {
throw new AndrolibException(ex);
}
}
public void build(File appDir, boolean forceBuildAll, boolean debug)
throws AndrolibException {
build(new ExtFile(appDir), forceBuildAll, debug);
}
public void build(ExtFile appDir, boolean forceBuildAll, boolean debug)
throws AndrolibException {
boolean framework = mAndRes.detectWhetherAppIsFramework(appDir);
new File(appDir, APK_DIRNAME).mkdirs();
buildSources(appDir, forceBuildAll, debug);
buildResources(appDir, forceBuildAll, framework);
buildLib(appDir, forceBuildAll);
buildApk(appDir, framework);
}
public void buildSources(File appDir, boolean forceBuildAll, boolean debug)
throws AndrolibException {
if (! buildSourcesRaw(appDir, forceBuildAll, debug)
&& ! buildSourcesSmali(appDir, forceBuildAll, debug)
&& ! buildSourcesJava(appDir, forceBuildAll, debug)
) {
LOGGER.warning("Could not find sources");
}
}
public boolean buildSourcesRaw(File appDir, boolean forceBuildAll,
boolean debug) throws AndrolibException {
try {
File working = new File(appDir, "classes.dex");
if (! working.exists()) {
return false;
}
if (debug) {
LOGGER.warning("Debug mode not available.");
}
File stored = new File(appDir, APK_DIRNAME + "/classes.dex");
if (forceBuildAll || isModified(working, stored)) {
LOGGER.info("Copying classes.dex file...");
BrutIO.copyAndClose(new FileInputStream(working),
new FileOutputStream(stored));
}
return true;
} catch (IOException ex) {
throw new AndrolibException(ex);
}
}
public boolean buildSourcesSmali(File appDir, boolean forceBuildAll,
boolean debug) throws AndrolibException {
ExtFile smaliDir = new ExtFile(appDir, "smali");
if (! smaliDir.exists()) {
return false;
}
File dex = new File(appDir, APK_DIRNAME + "/classes.dex");
if (! forceBuildAll) {
LOGGER.info("Checking whether sources has changed...");
}
if (forceBuildAll || isModified(smaliDir, dex)) {
LOGGER.info("Smaling...");
dex.delete();
SmaliBuilder.build(smaliDir, dex, debug);
}
return true;
}
public boolean buildSourcesJava(File appDir, boolean forceBuildAll,
boolean debug) throws AndrolibException {
File javaDir = new File(appDir, "src");
if (! javaDir.exists()) {
return false;
}
File dex = new File(appDir, APK_DIRNAME + "/classes.dex");
if (! forceBuildAll) {
LOGGER.info("Checking whether sources has changed...");
}
if (forceBuildAll || isModified(javaDir, dex)) {
LOGGER.info("Building java sources...");
dex.delete();
new AndrolibJava().build(javaDir, dex);
}
return true;
}
public void buildResources(ExtFile appDir, boolean forceBuildAll,
boolean framework) throws AndrolibException {
if (! buildResourcesRaw(appDir, forceBuildAll)
&& ! buildResourcesFull(appDir, forceBuildAll, framework)) {
LOGGER.warning("Could not find resources");
}
}
public boolean buildResourcesRaw(ExtFile appDir, boolean forceBuildAll)
throws AndrolibException {
try {
if (! new File(appDir, "resources.arsc").exists()) {
return false;
}
File apkDir = new File(appDir, APK_DIRNAME);
if (! forceBuildAll) {
LOGGER.info("Checking whether resources has changed...");
}
if (forceBuildAll || isModified(
newFiles(APK_RESOURCES_FILENAMES, appDir),
newFiles(APK_RESOURCES_FILENAMES, apkDir))) {
LOGGER.info("Copying raw resources...");
appDir.getDirectory()
.copyToDir(apkDir, APK_RESOURCES_FILENAMES);
}
return true;
} catch (DirectoryException ex) {
throw new AndrolibException(ex);
}
}
public boolean buildResourcesFull(File appDir, boolean forceBuildAll,
boolean framework) throws AndrolibException {
try {
if (! new File(appDir, "res").exists()) {
return false;
}
if (! forceBuildAll) {
LOGGER.info("Checking whether resources has changed...");
}
File apkDir = new File(appDir, APK_DIRNAME);
if (forceBuildAll || isModified(
newFiles(APP_RESOURCES_FILENAMES, appDir),
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"),
ninePatch, null, false, framework
);
new ExtFile(apkFile).getDirectory()
.copyToDir(apkDir, APK_RESOURCES_FILENAMES);
}
return true;
} catch (IOException ex) {
throw new AndrolibException(ex);
} catch (DirectoryException ex) {
throw new AndrolibException(ex);
}
}
public void buildLib(File appDir, boolean forceBuildAll)
throws AndrolibException {
File working = new File(appDir, "lib");
if (! working.exists()) {
return;
}
File stored = new File(appDir, APK_DIRNAME + "/lib");
if (forceBuildAll || isModified(working, stored)) {
LOGGER.info("Copying libs...");
try {
OS.rmdir(stored);
OS.cpdir(working, stored);
} catch (BrutException ex) {
throw new AndrolibException(ex);
}
}
}
public void buildApk(File appDir, boolean framework)
throws AndrolibException {
LOGGER.info("Building apk file...");
File outApk = new File(appDir, OUT_APK_FILENAME);
if (outApk.exists()) {
outApk.delete();
} else {
File outDir = outApk.getParentFile();
if (! outDir.exists()) {
outDir.mkdirs();
}
}
File assetDir = new File(appDir, "assets");
if (! assetDir.exists()) {
assetDir = null;
}
mAndRes.aaptPackage(outApk, null, null,
new File(appDir, APK_DIRNAME), assetDir, false, true);
}
public void publicizeResources(File arscFile) throws AndrolibException {
try {
FileInputStream in = new FileInputStream(arscFile);
List<FlagsOffset> offsets = ARSCDecoder.findFlagsOffsets(in);
in.close();
RandomAccessFile raf = new RandomAccessFile(arscFile, "rw");
MappedByteBuffer buf = raf.getChannel().map(MapMode.READ_WRITE, 0, arscFile.length());
for (FlagsOffset flags : offsets) {
int offset = flags.offset + 3;
int end = offset + 4 * flags.count;
while(offset < end) {
buf.put(offset, (byte) (buf.get(offset) | (byte) 0x40));
offset += 4;
}
}
raf.close();
} catch (IOException ex) {
throw new AndrolibException(ex);
}
}
public static String getVersion() {
return VERSION;
}
private boolean isModified(File working, File stored) {
if (! stored.exists()) {
return true;
}
return BrutIO.recursiveModifiedTime(working) >
BrutIO.recursiveModifiedTime(stored);
}
private boolean isModified(File[] working, File[] stored) {
for (int i = 0; i < stored.length; i++) {
if (! stored[i].exists()) {
return true;
}
}
return BrutIO.recursiveModifiedTime(working) >
BrutIO.recursiveModifiedTime(stored);
}
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;
}
private final static Logger LOGGER =
Logger.getLogger(Androlib.class.getName());
private final static String SMALI_DIRNAME = "smali";
private final static String APK_DIRNAME = "build/apk";
private final static String OUT_APK_FILENAME = "dist/out.apk";
private final static String[] APK_RESOURCES_FILENAMES =
new String[]{"resources.arsc", "AndroidManifest.xml", "res"};
private final static String[] APP_RESOURCES_FILENAMES =
new String[]{"AndroidManifest.xml", "res"};
private final static String VERSION = "1.1.1";
}