This commit is contained in:
peaches 2012-03-22 12:22:12 -05:00
parent 5174001e5b
commit 321dcfa91b
16 changed files with 1254 additions and 20 deletions

View File

@ -16,7 +16,9 @@
package brut.apktool;
import brut.androlib.*;
import brut.androlib.Androlib;
import brut.androlib.AndrolibException;
import brut.androlib.ApkDecoder;
import brut.androlib.err.CantFindFrameworkResException;
import brut.androlib.err.InFileNotFoundException;
import brut.androlib.err.OutDirExistsException;
@ -209,6 +211,7 @@ public class Main {
private static void usage() {
System.out.println(
"Apktool v" + Androlib.getVersion() + " - a tool for reengineering Android apk files\n" +
"Edited by iBotPeaches (@iBotPeaches) \n" +
"Copyright 2010 Ryszard Wiśniewski <brut.alll@gmail.com>\n" +
"Apache License 2.0 (http://www.apache.org/licenses/LICENSE-2.0)\n" +
"\n" +
@ -236,7 +239,7 @@ public class Main {
" \"Invalid config flags detected. Dropping resources\", but you\n" +
" want to decode them anyway, even with errors. You will have to\n" +
" fix them manually before building." +
"\n" +
"\n\n" +
" b[uild] [OPTS] [<app_path>] [<out_file>]\n" +
" Build an apk from already decoded application located in <app_path>.\n" +
"\n" +

View File

@ -0,0 +1,299 @@
/**
* Copyright 2011 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.
*/
package brut.apktool;
import brut.androlib.*;
import brut.androlib.err.CantFindFrameworkResException;
import brut.androlib.err.InFileNotFoundException;
import brut.androlib.err.OutDirExistsException;
import java.io.File;
import java.io.IOException;
import java.util.Arrays;
import java.util.logging.*;
/**
* @author Ryszard Wiśniewski <brut.alll@gmail.com>
*/
public class Main {
public static void main(String[] args)
throws IOException, AndrolibException, InterruptedException {
try {
Verbosity verbosity = Verbosity.NORMAL;
int i;
for (i = 0; i < args.length; i++) {
String opt = args[i];
if (! opt.startsWith("-")) {
break;
}
if ("-v".equals(opt) || "--verbose".equals(opt)) {
if (verbosity != Verbosity.NORMAL) {
throw new InvalidArgsError();
}
verbosity = Verbosity.VERBOSE;
} else if ("-q".equals(opt) || "--quiet".equals(opt)) {
if (verbosity != Verbosity.NORMAL) {
throw new InvalidArgsError();
}
verbosity = Verbosity.QUIET;
} else {
throw new InvalidArgsError();
}
}
setupLogging(verbosity);
if (args.length <= i) {
throw new InvalidArgsError();
}
String cmd = args[i];
args = Arrays.copyOfRange(args, i + 1, args.length);
if ("d".equals(cmd) || "decode".equals(cmd)) {
cmdDecode(args);
} else if ("b".equals(cmd) || "build".equals(cmd)) {
cmdBuild(args);
} else if ("if".equals(cmd) || "install-framework".equals(cmd)) {
cmdInstallFramework(args);
} else if ("publicize-resources".equals(cmd)) {
cmdPublicizeResources(args);
} else {
throw new InvalidArgsError();
}
} catch (InvalidArgsError ex) {
usage();
System.exit(1);
}
}
private static void cmdDecode(String[] args) throws InvalidArgsError,
AndrolibException {
ApkDecoder decoder = new ApkDecoder();
int i;
for (i = 0; i < args.length; i++) {
String opt = args[i];
if (! opt.startsWith("-")) {
break;
}
if ("-s".equals(opt) || "--no-src".equals(opt)) {
decoder.setDecodeSources(ApkDecoder.DECODE_SOURCES_NONE);
} else if ("-d".equals(opt) || "--debug".equals(opt)) {
decoder.setDebugMode(true);
} else if ("-t".equals(opt) || "--frame-tag".equals(opt)) {
i++;
if (i >= args.length) {
throw new InvalidArgsError();
}
decoder.setFrameworkTag(args[i]);
} else if ("-f".equals(opt) || "--force".equals(opt)) {
decoder.setForceDelete(true);
} else if ("-r".equals(opt) || "--no-res".equals(opt)) {
decoder.setDecodeResources(ApkDecoder.DECODE_RESOURCES_NONE);
} else if ("--keep-broken-res".equals(opt)) {
decoder.setKeepBrokenResources(true);
} else {
throw new InvalidArgsError();
}
}
String outName = null;
if (args.length == i + 2) {
outName = args[i + 1];
} else if (args.length == i + 1) {
outName = args[i];
outName = outName.endsWith(".apk") ?
outName.substring(0, outName.length() - 4) : outName + ".out";
outName = new File(outName).getName();
} else {
throw new InvalidArgsError();
}
File outDir = new File(outName);
decoder.setOutDir(outDir);
decoder.setApkFile(new File(args[i]));
try {
decoder.decode();
} catch (OutDirExistsException ex) {
System.out.println(
"Destination directory (" + outDir.getAbsolutePath() + ") " +
"already exists. Use -f switch if you want to overwrite it.");
System.exit(1);
} catch (InFileNotFoundException ex) {
System.out.println(
"Input file (" + args[i] + ") " +
"was not found or was not readable.");
System.exit(1);
} catch (CantFindFrameworkResException ex) {
System.out.println(
"Can't find framework resources for package of id: " +
String.valueOf(ex.getPkgId()) + ". You must install proper " +
"framework files, see project website for more info.");
System.exit(1);
}
}
private static void cmdBuild(String[] args) throws InvalidArgsError,
AndrolibException {
boolean forceBuildAll = false;
boolean debug = false;
int i;
for (i = 0; i < args.length; i++) {
String opt = args[i];
if (! opt.startsWith("-")) {
break;
}
if ("-f".equals(opt) || "--force-all".equals(opt)) {
forceBuildAll = true;
} else if ("-d".equals(opt) || "--debug".equals(opt)) {
debug = true;
} else {
throw new InvalidArgsError();
}
}
String appDirName;
File outFile = null;
switch (args.length - i) {
case 0:
appDirName = ".";
break;
case 2:
outFile = new File(args[i + 1]);
case 1:
appDirName = args[i];
break;
default:
throw new InvalidArgsError();
}
new Androlib().build(new File(appDirName), outFile, forceBuildAll,
debug);
}
private static void cmdInstallFramework(String[] args)
throws AndrolibException {
String tag = null;
switch (args.length) {
case 2:
tag = args[1];
case 1:
new Androlib().installFramework(new File(args[0]), tag);
return;
}
throw new InvalidArgsError();
}
private static void cmdPublicizeResources(String[] args)
throws InvalidArgsError, AndrolibException {
if (args.length != 1) {
throw new InvalidArgsError();
}
new Androlib().publicizeResources(new File(args[0]));
}
private static void usage() {
System.out.println(
"Apktool v" + Androlib.getVersion() + " - a tool for reengineering Android apk files\n" +
"Edited by iBotPeachs of http://miuiandroid.com\n" +
"Copyright 2010 Ryszard Wiśniewski <brut.alll@gmail.com>\n" +
"Apache License 2.0 (http://www.apache.org/licenses/LICENSE-2.0)\n" +
"\n" +
"Usage: apktool [-q|--quiet OR -v|--verbose] COMMAND [...]\n" +
"\n" +
"COMMANDs are:\n" +
"\n" +
" d[ecode] [OPTS] <file.apk> [<dir>]\n" +
" Decode <file.apk> to <dir>.\n" +
"\n" +
" OPTS:\n" +
"\n" +
" -s, --no-src\n" +
" Do not decode sources.\n" +
" -r, --no-res\n" +
" Do not decode resources.\n" +
" -d, --debug\n" +
" Decode in debug mode. Check project page for more info.\n" +
" -f, --force\n" +
" Force delete destination directory.\n" +
" -t <tag>, --frame-tag <tag>\n" +
" Try to use framework files tagged by <tag>.\n" +
" --keep-broken-res\n" +
" Use if there was an error and some resources were dropped, e.g.:\n" +
" \"Invalid config flags detected. Dropping resources\", but you\n" +
" want to decode them anyway, even with errors. You will have to\n" +
" fix them manually before building." +
"\n" +
" b[uild] [OPTS] [<app_path>] [<out_file>]\n" +
" Build an apk from already decoded application located in <app_path>.\n" +
"\n" +
" It will automatically detect, whether files was changed and perform\n" +
" needed steps only.\n" +
"\n" +
" If you omit <app_path> then current directory will be used.\n" +
" If you omit <out_file> then <app_path>/dist/<name_of_original.apk>\n" +
" will be used.\n" +
"\n" +
" OPTS:\n" +
"\n" +
" -f, --force-all\n" +
" Skip changes detection and build all files.\n" +
" -d, --debug\n" +
" Build in debug mode. Check project page for more info.\n" +
"\n" +
" if|install-framework <framework.apk> [<tag>]\n" +
" Install framework file to your system.\n" +
"\n" +
"For additional info, see: http://code.google.com/p/android-apktool/"
);
}
private static void setupLogging(Verbosity verbosity) {
Logger logger = Logger.getLogger("");
for (Handler handler : logger.getHandlers()) {
logger.removeHandler(handler);
}
if (verbosity == Verbosity.QUIET) {
return;
}
Handler handler = new ConsoleHandler();
logger.addHandler(handler);
if (verbosity == Verbosity.VERBOSE) {
handler.setLevel(Level.ALL);
logger.setLevel(Level.ALL);
} else {
handler.setFormatter(new Formatter() {
@Override
public String format(LogRecord record) {
return record.getLevel().toString().charAt(0) + ": "
+ record.getMessage()
+ System.getProperty("line.separator");
}
});
}
}
private static enum Verbosity {
NORMAL, VERBOSE, QUIET;
}
static class InvalidArgsError extends AndrolibException {
}
}

View File

@ -4,7 +4,7 @@
<groupId>brut.apktool</groupId>
<artifactId>apktool-lib</artifactId>
<version>1.4.4-SNAPSHOT</version>
<version>1.4.5-SNAPSHOT</version>
<packaging>jar</packaging>
<parent>

View File

@ -24,7 +24,8 @@ 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.directory.Directory;
import brut.directory.DirectoryException;
import brut.util.BrutIO;
import brut.util.OS;
import java.io.*;

View File

@ -0,0 +1,443 @@
/**
* Copyright 2011 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.
*/
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;
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.util.List;
import java.util.Map;
import java.util.logging.Logger;
import org.yaml.snakeyaml.DumperOptions;
import org.yaml.snakeyaml.Yaml;
/**
* @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 {
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 writeMetaFile(File mOutDir, Map<String, Object> meta)
throws AndrolibException {
DumperOptions options = new DumperOptions();
options.setDefaultFlowStyle(DumperOptions.FlowStyle.BLOCK);
// options.setIndent(4);
Yaml yaml = new Yaml(options);
FileWriter writer = null;
try {
writer = new FileWriter(new File(mOutDir, "apktool.yml"));
yaml.dump(meta, writer);
} catch (IOException ex) {
throw new AndrolibException(ex);
} finally {
if (writer != null) {
try {
writer.close();
} catch (IOException ex) {}
}
}
}
public Map<String, Object> readMetaFile(ExtFile appDir)
throws AndrolibException {
InputStream in = null;
try {
in = appDir.getDirectory().getFileInput("apktool.yml");
Yaml yaml = new Yaml();
return (Map<String, Object>) yaml.load(in);
} catch (DirectoryException ex) {
throw new AndrolibException(ex);
} finally {
if (in != null) {
try {
in.close();
} catch (IOException ex) {}
}
}
}
public void build(File appDir, File outFile, boolean forceBuildAll,
boolean debug) throws AndrolibException {
build(new ExtFile(appDir), outFile, forceBuildAll, debug);
}
public void build(ExtFile appDir, File outFile, boolean forceBuildAll,
boolean debug) throws AndrolibException {
Map<String, Object> meta = readMetaFile(appDir);
Object t1 = meta.get("isFrameworkApk");
boolean framework = t1 == null ? false : (Boolean) t1;
if (outFile == null) {
String outFileName = (String) meta.get("apkFileName");
outFile = new File(appDir, "dist" + File.separator +
(outFileName == null ? "out.apk" : outFileName));
}
new File(appDir, APK_DIRNAME).mkdirs();
buildSources(appDir, forceBuildAll, debug);
buildResources(appDir, forceBuildAll, framework,
(Map<String, Object>) meta.get("usesFramework"));
buildLib(appDir, forceBuildAll);
buildApk(appDir, outFile, 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, Map<String, Object> usesFramework)
throws AndrolibException {
if (! buildResourcesRaw(appDir, forceBuildAll)
&& ! buildResourcesFull(appDir, forceBuildAll, framework,
usesFramework)) {
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, Map<String, Object> usesFramework)
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, parseUsesFramework(usesFramework),
false, framework
);
Directory tmpDir = new ExtFile(apkFile).getDirectory();
tmpDir.copyToDir(apkDir,
tmpDir.containsDir("res") ? APK_RESOURCES_FILENAMES :
APK_RESOURCES_WITHOUT_RES_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, File outApk, boolean framework)
throws AndrolibException {
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;
}
mAndRes.aaptPackage(outApk, null, null,
new File(appDir, APK_DIRNAME), assetDir, null, false, framework);
}
public void publicizeResources(File arscFile) throws AndrolibException {
mAndRes.publicizeResources(arscFile);
}
public void installFramework(File frameFile, String tag)
throws AndrolibException {
mAndRes.installFramework(frameFile, tag);
}
public boolean isFrameworkApk(ResTable resTable) {
for (ResPackage pkg : resTable.listMainPackages()) {
if (pkg.getId() < 64) {
return true;
}
}
return false;
}
public static String getVersion() {
String version = ApktoolProperties.get("version");
return version.endsWith("-SNAPSHOT") ?
version.substring(0, version.length() - 9) + '.' +
ApktoolProperties.get("git.commit.id.abbrev")
: 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) {
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[] 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"};
}

View File

@ -26,7 +26,10 @@ import brut.common.BrutException;
import brut.directory.DirectoryException;
import brut.util.OS;
import java.io.File;
import java.util.*;
import java.util.Arrays;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Set;
/**
* @author Ryszard Wiśniewski <brut.alll@gmail.com>

View File

@ -0,0 +1,234 @@
/**
* Copyright 2011 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.
*/
package brut.androlib;
import brut.androlib.err.InFileNotFoundException;
import brut.androlib.err.OutDirExistsException;
import brut.androlib.res.AndrolibResources;
import brut.androlib.res.data.ResPackage;
import brut.androlib.res.data.ResTable;
import brut.androlib.res.util.ExtFile;
import brut.common.BrutException;
import brut.directory.DirectoryException;
import brut.util.OS;
import java.io.File;
import java.util.*;
/**
* @author Ryszard Wiśniewski <brut.alll@gmail.com>
*/
public class ApkDecoder {
public ApkDecoder() {
this(new Androlib());
}
public ApkDecoder(Androlib androlib) {
mAndrolib = androlib;
}
public ApkDecoder(File apkFile) {
this(apkFile, new Androlib());
}
public ApkDecoder(File apkFile, Androlib androlib) {
mAndrolib = androlib;
setApkFile(apkFile);
}
public void setApkFile(File apkFile) {
mApkFile = new ExtFile(apkFile);
mResTable = null;
}
public void setOutDir(File outDir) throws AndrolibException {
mOutDir = outDir;
}
public void decode() throws AndrolibException {
File outDir = getOutDir();
if (! mForceDelete && outDir.exists()) {
throw new OutDirExistsException();
}
if (! mApkFile.isFile() || ! mApkFile.canRead() ) {
throw new InFileNotFoundException();
}
try {
OS.rmdir(outDir);
} catch (BrutException ex) {
throw new AndrolibException(ex);
}
outDir.mkdirs();
if (hasSources()) {
switch (mDecodeSources) {
case DECODE_SOURCES_NONE:
mAndrolib.decodeSourcesRaw(mApkFile, outDir, mDebug);
break;
case DECODE_SOURCES_SMALI:
mAndrolib.decodeSourcesSmali(mApkFile, outDir, mDebug);
break;
case DECODE_SOURCES_JAVA:
mAndrolib.decodeSourcesJava(mApkFile, outDir, mDebug);
break;
}
}
if (hasResources()) {
switch (mDecodeResources) {
case DECODE_RESOURCES_NONE:
mAndrolib.decodeResourcesRaw(mApkFile, outDir);
break;
case DECODE_RESOURCES_FULL:
mAndrolib.decodeResourcesFull(mApkFile, outDir,
getResTable());
break;
}
}
mAndrolib.decodeRawFiles(mApkFile, outDir);
writeMetaFile();
}
public void setDecodeSources(short mode) throws AndrolibException {
if (mode != DECODE_SOURCES_NONE && mode != DECODE_SOURCES_SMALI
&& mode != DECODE_SOURCES_JAVA) {
throw new AndrolibException("Invalid decode sources mode: " + mode);
}
mDecodeSources = mode;
}
public void setDecodeResources(short mode) throws AndrolibException {
if (mode != DECODE_RESOURCES_NONE && mode != DECODE_RESOURCES_FULL) {
throw new AndrolibException("Invalid decode resources mode");
}
mDecodeResources = mode;
}
public void setDebugMode(boolean debug) {
mDebug = debug;
}
public void setForceDelete(boolean forceDelete) {
mForceDelete = forceDelete;
}
public void setFrameworkTag(String tag) throws AndrolibException {
mFrameTag = tag;
if (mResTable != null) {
getResTable().setFrameTag(tag);
}
}
public void setKeepBrokenResources(boolean keepBrokenResources) {
mKeepBrokenResources = keepBrokenResources;
}
public ResTable getResTable() throws AndrolibException {
if (mResTable == null) {
if (! hasResources()) {
throw new AndrolibException(
"Apk doesn't containt resources.arsc file");
}
AndrolibResources.sKeepBroken = mKeepBrokenResources;
mResTable = mAndrolib.getResTable(mApkFile);
mResTable.setFrameTag(mFrameTag);
}
return mResTable;
}
public boolean hasSources() throws AndrolibException {
try {
return mApkFile.getDirectory().containsFile("classes.dex");
} catch (DirectoryException ex) {
throw new AndrolibException(ex);
}
}
public boolean hasResources() throws AndrolibException {
try {
return mApkFile.getDirectory().containsFile("resources.arsc");
} catch (DirectoryException ex) {
throw new AndrolibException(ex);
}
}
public final static short DECODE_SOURCES_NONE = 0x0000;
public final static short DECODE_SOURCES_SMALI = 0x0001;
public final static short DECODE_SOURCES_JAVA = 0x0002;
public final static short DECODE_RESOURCES_NONE = 0x0100;
public final static short DECODE_RESOURCES_FULL = 0x0101;
private File getOutDir() throws AndrolibException {
if (mOutDir == null) {
throw new AndrolibException("Out dir not set");
}
return mOutDir;
}
private void writeMetaFile() throws AndrolibException {
Map<String, Object> meta = new LinkedHashMap<String, Object>();
meta.put("version", Androlib.getVersion());
meta.put("apkFileName", mApkFile.getName());
if (mDecodeResources != DECODE_RESOURCES_NONE && hasResources()) {
meta.put("isFrameworkApk",
Boolean.valueOf(mAndrolib.isFrameworkApk(getResTable())));
putUsesFramework(meta);
}
mAndrolib.writeMetaFile(mOutDir, meta);
}
private void putUsesFramework(Map<String, Object> meta)
throws AndrolibException {
Set<ResPackage> pkgs = getResTable().listFramePackages();
if (pkgs.isEmpty()) {
return;
}
Integer[] ids = new Integer[pkgs.size()];
int i = 0;
for (ResPackage pkg : pkgs) {
ids[i++] = pkg.getId();
}
Arrays.sort(ids);
Map<String, Object> uses = new LinkedHashMap<String, Object>();
uses.put("ids", ids);
if (mFrameTag != null) {
uses.put("tag", mFrameTag);
}
meta.put("usesFramework", uses);
}
private final Androlib mAndrolib;
private ExtFile mApkFile;
private File mOutDir;
private ResTable mResTable;
private short mDecodeSources = DECODE_SOURCES_SMALI;
private short mDecodeResources = DECODE_RESOURCES_FULL;
private boolean mDebug = false;
private boolean mForceDelete = false;
private String mFrameTag;
private boolean mKeepBrokenResources = false;
}

View File

@ -16,7 +16,9 @@
package brut.androlib.mod;
import java.io.*;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import org.antlr.runtime.*;
import org.antlr.runtime.tree.CommonTree;
import org.antlr.runtime.tree.CommonTreeNodeStream;

View File

@ -0,0 +1,167 @@
/**
* Copyright 2011 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.
*/
package brut.androlib.res;
import brut.androlib.AndrolibException;
import brut.androlib.err.UndefinedResObject;
import brut.androlib.res.data.ResResSpec;
import brut.androlib.res.data.ResTable;
import brut.directory.Directory;
import brut.directory.DirectoryException;
import brut.directory.FileDirectory;
import java.io.File;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.Iterator;
import java.util.logging.Logger;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.apache.commons.io.IOUtils;
/**
* @author Ryszard Wiśniewski <brut.alll@gmail.com>
*/
public class ResSmaliUpdater {
public void tagResIDs(ResTable resTable, File smaliDir)
throws AndrolibException {
Directory dir = null;
try {
dir = new FileDirectory(smaliDir);
} catch (DirectoryException ex) {
throw new AndrolibException(
"Could not tag res IDs", ex);
}
for (String fileName : dir.getFiles(true)) {
try {
tagResIdsForFile(resTable, dir, fileName);
} catch (IOException ex) {
throw new AndrolibException(
"Could not tag resIDs for file: " + fileName, ex);
} catch (DirectoryException ex) {
throw new AndrolibException(
"Could not tag resIDs for file: " + fileName, ex);
} catch (AndrolibException ex) {
throw new AndrolibException(
"Could not tag resIDs for file: " + fileName, ex);
}
}
}
public void updateResIDs(ResTable resTable, File smaliDir)
throws AndrolibException {
try {
Directory dir = new FileDirectory(smaliDir);
for (String fileName : dir.getFiles(true)) {
Iterator<String> it =
IOUtils.readLines(dir.getFileInput(fileName)).iterator();
PrintWriter out = new PrintWriter(dir.getFileOutput(fileName));
while (it.hasNext()) {
String line = it.next();
out.println(line);
Matcher m1 = RES_NAME_PATTERN.matcher(line);
if (! m1.matches()) {
continue;
}
Matcher m2 = RES_ID_PATTERN.matcher(it.next());
if (! m2.matches()) {
throw new AndrolibException();
}
int resID = resTable.getPackage(m1.group(1))
.getType(m1.group(2)).getResSpec(m1.group(3))
.getId().id;
if (m2.group(1) != null) {
out.println(String.format(
RES_ID_FORMAT_FIELD, m2.group(1), resID));
} else {
out.println(String.format(
RES_ID_FORMAT_CONST, m2.group(2), resID));
}
}
out.close();
}
} catch (IOException ex) {
throw new AndrolibException(
"Could not tag res IDs for: " + smaliDir.getAbsolutePath(), ex);
} catch (DirectoryException ex) {
throw new AndrolibException(
"Could not tag res IDs for: " + smaliDir.getAbsolutePath(), ex);
}
}
private void tagResIdsForFile(ResTable resTable, Directory dir,
String fileName) throws IOException, DirectoryException,
AndrolibException {
Iterator<String> it =
IOUtils.readLines(dir.getFileInput(fileName)).iterator();
PrintWriter out = new PrintWriter(dir.getFileOutput(fileName));
while (it.hasNext()) {
String line = it.next();
if (RES_NAME_PATTERN.matcher(line).matches()) {
out.println(line);
out.println(it.next());
continue;
}
Matcher m = RES_ID_PATTERN.matcher(line);
if (m.matches()) {
int resID = parseResID(m.group(3));
if (resID != -1) {
try {
ResResSpec spec = resTable.getResSpec(resID);
out.println(String.format(
RES_NAME_FORMAT, spec.getFullName()));
} catch (UndefinedResObject ex) {
if (! R_FILE_PATTERN.matcher(fileName).matches()) {
LOGGER.warning(String.format(
"Undefined resource spec in %s: 0x%08x"
, fileName, resID));
}
}
}
}
out.println(line);
}
out.close();
}
private int parseResID(String resIDHex) {
if (resIDHex.endsWith("ff")) {
return -1;
}
int resID = Integer.valueOf(resIDHex, 16);
if (resIDHex.length() == 4) {
resID = resID << 16;
}
return resID;
}
private final static String RES_ID_FORMAT_FIELD =
".field %s:I = 0x%08x";
private final static String RES_ID_FORMAT_CONST =
" const %s, 0x%08x";
private final static Pattern RES_ID_PATTERN = Pattern.compile(
"^(?:\\.field (.+?):I =| const(?:|/(?:|high)16) ([pv]\\d+?),) 0x(7[a-f]0[1-9a-f](?:|[0-9a-f]{4}))$");
private final static String RES_NAME_FORMAT =
"# APKTOOL/RES_NAME: %s";
private final static Pattern RES_NAME_PATTERN = Pattern.compile(
"^# APKTOOL/RES_NAME: (+)$");
private final static Pattern R_FILE_PATTERN = Pattern.compile(
".*R\\$[a-z]+\\.smali$");
private final static Logger LOGGER =
Logger.getLogger(ResSmaliUpdater.class.getName());
}

View File

@ -16,8 +16,8 @@
package brut.androlib.res.data;
import brut.androlib.err.UndefinedResObject;
import brut.androlib.AndrolibException;
import brut.androlib.err.UndefinedResObject;
import brut.androlib.res.data.value.ResFileValue;
import brut.androlib.res.data.value.ResValueFactory;
import brut.androlib.res.xml.ResValuesXmlSerializable;

View File

@ -23,7 +23,9 @@ import brut.androlib.res.data.value.ResBoolValue;
import brut.androlib.res.data.value.ResFileValue;
import brut.directory.Directory;
import brut.directory.DirectoryException;
import java.io.*;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.logging.Level;
import java.util.logging.Logger;

View File

@ -19,8 +19,7 @@ package brut.androlib.src;
import brut.androlib.AndrolibException;
import java.io.File;
import java.io.IOException;
import org.jf.baksmali.baksmali;
import org.jf.baksmali.main;
import org.jf.baksmali.*;
import org.jf.dexlib.DexFile;
/**

View File

@ -19,11 +19,19 @@ package brut.androlib;
import brut.androlib.res.util.ExtFile;
import brut.common.BrutException;
import brut.util.OS;
import java.io.*;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.io.Reader;
import java.util.logging.Logger;
import org.custommonkey.xmlunit.*;
import org.junit.*;
import static org.junit.Assert.*;
import org.custommonkey.xmlunit.DetailedDiff;
import org.custommonkey.xmlunit.Diff;
import org.custommonkey.xmlunit.ElementNameAndAttributeQualifier;
import org.custommonkey.xmlunit.ElementQualifier;
import org.junit.AfterClass;
import static org.junit.Assert.assertTrue;
import org.junit.BeforeClass;
import org.junit.Test;
import org.xml.sax.SAXException;
@ -116,9 +124,7 @@ public class BuildAndDecodeTest {
@Test
public void qualifiersTest() throws BrutException {
compareValuesFiles("values-mcc004-mnc4-en-rUS-sw100dp-w200dp-h300dp" +
"-xlarge-long-land-television-night-xhdpi-finger-keyssoft-12key" +
"-navhidden-dpad/strings.xml");
compareValuesFiles("values-mcc004/strings.xml");
}
private void compareValuesFiles(String path) throws BrutException {

View File

@ -76,18 +76,18 @@
<repositories>
<repository>
<id>android-apktool.googlecode.com</id>
<url>http://android-apktool.googlecode.com/svn/m2-releases</url>
<url>http://apktool2.googlecode.com/svn/m2-releases</url>
</repository>
</repositories>
<distributionManagement>
<repository>
<id>android-apktool.googlecode.com</id>
<url>dav:https://android-apktool.googlecode.com/svn/m2-releases</url>
<url>dav:https://apktool2.googlecode.com/svn/m2-releases</url>
</repository>
<snapshotRepository>
<id>android-apktool.googlecode.com</id>
<url>dav:https://android-apktool.googlecode.com/svn/m2-snapshots</url>
<url>dav:https://apktool2.googlecode.com/svn/m2-snapshots</url>
</snapshotRepository>
</distributionManagement>
</project>

75
pom.xml~ Normal file
View File

@ -0,0 +1,75 @@
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>brut.apktool</groupId>
<artifactId>apktool-project</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>pom</packaging>
<name>apktool</name>
<url>http://github.com/brutall/brut.apktool</url>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<root.basedir>${basedir}</root.basedir>
</properties>
<modules>
<module>apktool-lib</module>
<module>apktool-cli</module>
</modules>
<build>
<resources>
<resource>
<directory>..</directory>
<includes>
<include>LICENSE</include>
<include>NOTICE</include>
<include>NOTICE-smali</include>
</includes>
</resource>
</resources>
<plugins>
<plugin>
<groupId>com.mycila.maven-license-plugin</groupId>
<artifactId>maven-license-plugin</artifactId>
<configuration>
<header>${root.basedir}/src/templates/apache2.0-header.txt</header>
<strictCheck>true</strictCheck>
<excludes>
<exclude>.gitignore</exclude>
<exclude>LICENSE</exclude>
<exclude>NOTICE</exclude>
<exclude>NOTICE-smali</exclude>
</excludes>
</configuration>
<executions>
<execution>
<goals>
<goal>check</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>6</source>
<target>6</target>
</configuration>
</plugin>
</plugins>
<extensions>
<extension>
<groupId>org.apache.maven.wagon</groupId>
<artifactId>wagon-webdav</artifactId>
<version>1.0-beta-2</version>
</extension>
</extensions>
</build>
</project>