/* * Copyright (C) 2010 Ryszard Wiśniewski * Copyright (C) 2010 Connor Tumbleson * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package brut.androlib; import brut.androlib.meta.MetaInfo; import brut.androlib.meta.UsesFramework; import brut.androlib.options.BuildOptions; import brut.androlib.res.AndrolibResources; import brut.androlib.res.data.ResConfigFlags; import brut.androlib.res.data.ResPackage; import brut.androlib.res.data.ResTable; import brut.androlib.res.data.ResUnknownFiles; import brut.common.InvalidUnknownFileException; import brut.common.RootUnknownFileException; import brut.common.TraversalUnknownFileException; import brut.androlib.res.xml.ResXmlPatcher; import brut.androlib.src.SmaliBuilder; import brut.androlib.src.SmaliDecoder; import brut.common.BrutException; import brut.directory.*; import brut.util.*; import org.apache.commons.io.FileUtils; import org.apache.commons.io.FilenameUtils; import org.jf.dexlib2.iface.DexFile; import org.xml.sax.SAXException; import javax.xml.parsers.ParserConfigurationException; import javax.xml.transform.TransformerException; import java.io.*; import java.util.*; import java.util.logging.Logger; import java.util.regex.Pattern; import java.util.zip.CRC32; import java.util.zip.ZipEntry; import java.util.zip.ZipFile; import java.util.zip.ZipOutputStream; public class Androlib { private final AndrolibResources mAndRes = new AndrolibResources(); protected final ResUnknownFiles mResUnknownFiles = new ResUnknownFiles(); public final BuildOptions buildOptions; private int mMinSdkVersion = 0; public Androlib() { this(new BuildOptions()); } public Androlib(BuildOptions buildOptions) { this.buildOptions = buildOptions; mAndRes.buildOptions = buildOptions; } public ResTable getResTable(ExtFile apkFile) throws AndrolibException { return mAndRes.getResTable(apkFile, true); } public ResTable getResTable(ExtFile apkFile, boolean loadMainPkg) throws AndrolibException { return mAndRes.getResTable(apkFile, loadMainPkg); } public int getMinSdkVersion() { return mMinSdkVersion; } public void decodeSourcesRaw(ExtFile apkFile, File outDir, String filename) throws AndrolibException { try { LOGGER.fine("Copying raw " + filename + " file..."); apkFile.getDirectory().copyToDir(outDir, filename); } catch (DirectoryException ex) { throw new AndrolibException(ex); } } public void decodeSourcesSmali(File apkFile, File outDir, String filename, boolean bakDeb, int apiLevel) throws AndrolibException { try { 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("."))); } OS.rmdir(smaliDir); smaliDir.mkdirs(); LOGGER.fine("Baksmaling " + filename + "..."); DexFile dexFile = SmaliDecoder.decode(apkFile, smaliDir, filename, bakDeb, apiLevel); int minSdkVersion = dexFile.getOpcodes().api; if (mMinSdkVersion == 0 || mMinSdkVersion > minSdkVersion) { mMinSdkVersion = minSdkVersion; } } catch (BrutException ex) { throw new AndrolibException(ex); } } public void decodeManifestRaw(ExtFile apkFile, File outDir) throws AndrolibException { try { LOGGER.fine("Copying raw manifest..."); apkFile.getDirectory().copyToDir(outDir, APK_MANIFEST_FILENAMES); } catch (DirectoryException ex) { throw new AndrolibException(ex); } } public void decodeManifestFull(ExtFile apkFile, File outDir, ResTable resTable) throws AndrolibException { mAndRes.decodeManifest(resTable, apkFile, outDir); } public void decodeResourcesRaw(ExtFile apkFile, File outDir) throws AndrolibException { try { LOGGER.fine("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 decodeManifestWithResources(ExtFile apkFile, File outDir, ResTable resTable) throws AndrolibException { mAndRes.decodeManifestWithResources(resTable, apkFile, outDir); } public void decodeRawFiles(ExtFile apkFile, File outDir, short decodeAssetMode) throws AndrolibException { LOGGER.fine("Copying assets and libs..."); try { Directory in = apkFile.getDirectory(); if (decodeAssetMode == ApkDecoder.DECODE_ASSETS_FULL) { if (in.containsDir("assets")) { in.copyToDir(outDir, "assets"); } } if (in.containsDir("lib")) { in.copyToDir(outDir, "lib"); } if (in.containsDir("libs")) { in.copyToDir(outDir, "libs"); } if (in.containsDir("kotlin")) { in.copyToDir(outDir, "kotlin"); } } catch (DirectoryException ex) { throw new AndrolibException(ex); } } public void recordUncompressedFiles(ExtFile apkFile, Collection uncompressedFilesOrExts) throws AndrolibException { try { Directory unk = apkFile.getDirectory(); Set files = unk.getFiles(true); for (String file : files) { if (isAPKFileNames(file) && unk.getCompressionLevel(file) == 0) { String extOrFile = ""; if (unk.getSize(file) != 0) { extOrFile = FilenameUtils.getExtension(file); } if (extOrFile.isEmpty() || !NO_COMPRESS_PATTERN.matcher(extOrFile).find()) { extOrFile = file; if (mAndRes.mResFileMapping.containsKey(extOrFile)) { extOrFile = mAndRes.mResFileMapping.get(extOrFile); } } if (!uncompressedFilesOrExts.contains(extOrFile)) { uncompressedFilesOrExts.add(extOrFile); } } } } catch (DirectoryException ex) { throw new AndrolibException(ex); } } private boolean isAPKFileNames(String file) { for (String apkFile : APK_STANDARD_ALL_FILENAMES) { if (apkFile.equals(file) || file.startsWith(apkFile + "/")) { return true; } } return false; } public void decodeUnknownFiles(ExtFile apkFile, File outDir) throws AndrolibException { LOGGER.fine("Copying unknown files..."); File unknownOut = new File(outDir, UNK_DIRNAME); try { Directory unk = apkFile.getDirectory(); // loop all items in container recursively, ignoring any that are pre-defined by aapt Set files = unk.getFiles(true); for (String file : files) { if (!isAPKFileNames(file) && !file.endsWith(".dex")) { // copy file out of archive into special "unknown" folder unk.copyToDir(unknownOut, file); // lets record the name of the file, and its compression type // so that we may re-include it the same way mResUnknownFiles.addUnknownFileInfo(file, String.valueOf(unk.getCompressionLevel(file))); } } } catch (DirectoryException ex) { throw new AndrolibException(ex); } } public void writeOriginalFiles(ExtFile apkFile, File outDir) throws AndrolibException { LOGGER.fine("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.containsFile("stamp-cert-sha256")) { in.copyToDir(originalDir, "stamp-cert-sha256"); } if (in.containsDir("META-INF")) { in.copyToDir(originalDir, "META-INF"); if (in.containsDir("META-INF/services")) { // If the original APK contains the folder META-INF/services folder // that is used for service locators (like coroutines on android), // copy it to the destination folder so it does not get dropped. LOGGER.fine("Copying META-INF/services directory"); in.copyToDir(outDir, "META-INF/services"); } } } catch (DirectoryException ex) { throw new AndrolibException(ex); } } public void writeMetaFile(File mOutDir, MetaInfo meta) throws AndrolibException { try { meta.save(new File(mOutDir, "apktool.yml")); } catch (IOException ex) { throw new AndrolibException(ex); } } public MetaInfo readMetaFile(ExtFile appDir) throws AndrolibException { try( InputStream in = appDir.getDirectory().getFileInput("apktool.yml") ) { return MetaInfo.load(in); } catch (DirectoryException | IOException ex) { throw new AndrolibException(ex); } } public void build(File appDir, File outFile) throws BrutException { build(new ExtFile(appDir), outFile); } public void build(ExtFile appDir, File outFile) throws BrutException { LOGGER.fine("Using Apktool " + Androlib.getVersion()); MetaInfo meta = readMetaFile(appDir); buildOptions.isFramework = meta.isFrameworkApk; buildOptions.resourcesAreCompressed = meta.compressionType; buildOptions.doNotCompress = meta.doNotCompress; mAndRes.setSdkInfo(meta.sdkInfo); mAndRes.setPackageId(meta.packageInfo); mAndRes.setPackageRenamed(meta.packageInfo); mAndRes.setVersionInfo(meta.versionInfo); mAndRes.setSharedLibrary(meta.sharedLibrary); mAndRes.setSparseResources(meta.sparseResources); if (meta.sdkInfo != null && meta.sdkInfo.get("minSdkVersion") != null) { String minSdkVersion = meta.sdkInfo.get("minSdkVersion"); mMinSdkVersion = mAndRes.getMinSdkVersionFromAndroidCodename(meta, minSdkVersion); } if (outFile == null) { String outFileName = meta.apkFileName; outFile = new File(appDir, "dist" + File.separator + (outFileName == null ? "out.apk" : outFileName)); } new File(appDir, APK_DIRNAME).mkdirs(); File manifest = new File(appDir, "AndroidManifest.xml"); File manifestOriginal = new File(appDir, "AndroidManifest.xml.orig"); buildSources(appDir); buildNonDefaultSources(appDir); buildManifestFile(appDir, manifest, manifestOriginal); buildResources(appDir, meta.usesFramework); buildLibs(appDir); buildCopyOriginalFiles(appDir); buildApk(appDir, outFile); // 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) buildUnknownFiles(appDir, outFile, meta); // we copied the AndroidManifest.xml to AndroidManifest.xml.orig so we can edit it // lets restore the unedited one, to not change the original if (manifest.isFile() && manifest.exists() && manifestOriginal.isFile()) { try { if (new File(appDir, "AndroidManifest.xml").delete()) { FileUtils.moveFile(manifestOriginal, manifest); } } catch (IOException ex) { throw new AndrolibException(ex.getMessage()); } } LOGGER.fine("Built apk into: " + outFile.getPath()); } private void buildManifestFile(File appDir, File manifest, File manifestOriginal) throws AndrolibException { // If we decoded in "raw", we cannot patch AndroidManifest if (new File(appDir, "resources.arsc").exists()) { return; } if (manifest.isFile() && manifest.exists()) { try { if (manifestOriginal.exists()) { manifestOriginal.delete(); } FileUtils.copyFile(manifest, manifestOriginal); ResXmlPatcher.fixingPublicAttrsInProviderAttributes(manifest); } catch (IOException ex) { throw new AndrolibException(ex.getMessage()); } } } public void buildSources(File appDir) throws AndrolibException { if (!buildSourcesRaw(appDir, "classes.dex") && !buildSourcesSmali(appDir, "smali", "classes.dex")) { LOGGER.warning("Could not find sources"); } } public void buildNonDefaultSources(ExtFile appDir) throws AndrolibException { try { // loop through any smali_ directories for multi-dex apks Map dirs = appDir.getDirectory().getDirs(); for (Map.Entry directory : dirs.entrySet()) { String name = directory.getKey(); if (name.startsWith("smali_")) { String filename = name.substring(name.indexOf("_") + 1) + ".dex"; if (!buildSourcesRaw(appDir, filename) && !buildSourcesSmali(appDir, name, filename)) { LOGGER.warning("Could not find sources"); } } } // 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()); } } } } catch(DirectoryException ex) { throw new AndrolibException(ex); } } public boolean buildSourcesRaw(File appDir, String filename) throws AndrolibException { File working = new File(appDir, filename); if (!working.exists()) { return false; } File stored = new File(appDir, APK_DIRNAME + "/" + filename); if (buildOptions.forceBuildAll || isModified(working, stored)) { LOGGER.fine("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; } public boolean buildSourcesSmali(File appDir, String folder, String filename) throws AndrolibException { ExtFile smaliDir = new ExtFile(appDir, folder); if (!smaliDir.exists()) { return false; } File dex = new File(appDir, APK_DIRNAME + "/" + filename); if (! buildOptions.forceBuildAll) { LOGGER.fine("Checking whether sources has changed..."); } if (buildOptions.forceBuildAll || isModified(smaliDir, dex)) { LOGGER.fine("Smaling " + folder + " folder into " + filename + "..."); dex.delete(); SmaliBuilder.build(smaliDir, dex, buildOptions.forceApi > 0 ? buildOptions.forceApi : mMinSdkVersion); } return true; } public void buildResources(ExtFile appDir, UsesFramework usesFramework) throws BrutException { if (!buildResourcesRaw(appDir) && !buildResourcesFull(appDir, usesFramework) && !buildManifest(appDir, usesFramework)) { LOGGER.warning("Could not find resources"); } } public boolean buildResourcesRaw(ExtFile appDir) throws AndrolibException { try { if (!new File(appDir, "resources.arsc").exists()) { return false; } File apkDir = new File(appDir, APK_DIRNAME); if (! buildOptions.forceBuildAll) { LOGGER.fine("Checking whether resources has changed..."); } if (buildOptions.forceBuildAll || isModified(newFiles(APK_RESOURCES_FILENAMES, appDir), newFiles(APK_RESOURCES_FILENAMES, apkDir))) { LOGGER.fine("Copying raw resources..."); appDir.getDirectory().copyToDir(apkDir, APK_RESOURCES_FILENAMES); } return true; } catch (DirectoryException ex) { throw new AndrolibException(ex); } } public boolean buildResourcesFull(File appDir, UsesFramework usesFramework) throws AndrolibException { try { if (!new File(appDir, "res").exists()) { return false; } if (! buildOptions.forceBuildAll) { LOGGER.fine("Checking whether resources has changed..."); } File apkDir = new File(appDir, APK_DIRNAME); File resourceFile = new File(apkDir.getParent(), "resources.zip"); if (buildOptions.forceBuildAll || isModified(newFiles(APP_RESOURCES_FILENAMES, appDir), newFiles(APK_RESOURCES_FILENAMES, apkDir)) || (buildOptions.isAapt2() && !isFile(resourceFile))) { LOGGER.fine("Building resources..."); if (buildOptions.debugMode) { if (buildOptions.isAapt2()) { LOGGER.fine("Using aapt2 - setting 'debuggable' attribute to 'true' in AndroidManifest.xml"); ResXmlPatcher.setApplicationDebugTagTrue(new File(appDir, "AndroidManifest.xml")); } else { ResXmlPatcher.removeApplicationDebugTag(new File(appDir, "AndroidManifest.xml")); } } if (buildOptions.netSecConf) { MetaInfo meta = readMetaFile(new ExtFile(appDir)); if (meta.sdkInfo != null && meta.sdkInfo.get("targetSdkVersion") != null) { if (Integer.parseInt(meta.sdkInfo.get("targetSdkVersion")) < ResConfigFlags.SDK_NOUGAT) { LOGGER.warning("Target SDK version is lower than 24! Network Security Configuration might be ignored!"); } } File netSecConfOrig = new File(appDir, "res/xml/network_security_config.xml"); if (netSecConfOrig.exists()) { LOGGER.fine("Replacing existing network_security_config.xml!"); netSecConfOrig.delete(); } ResXmlPatcher.modNetworkSecurityConfig(netSecConfOrig); ResXmlPatcher.setNetworkSecurityConfig(new File(appDir, "AndroidManifest.xml")); LOGGER.fine("Added permissive network security config in manifest"); } File apkFile = File.createTempFile("APKTOOL", null); apkFile.delete(); resourceFile.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)); ExtFile tmpExtFile = new ExtFile(apkFile); Directory tmpDir = tmpExtFile.getDirectory(); // Sometimes an application is built with a resources.arsc file with no resources, // Apktool assumes it will have a rebuilt arsc file, when it doesn't. So if we // encounter a copy error, move to a warning and continue on. (#1730) try { tmpDir.copyToDir(apkDir, tmpDir.containsDir("res") ? APK_RESOURCES_FILENAMES : APK_RESOURCES_WITHOUT_RES_FILENAMES); } catch (DirectoryException ex) { LOGGER.warning(ex.getMessage()); } finally { tmpExtFile.close(); } // delete tmpDir apkFile.delete(); } return true; } catch (IOException | BrutException | ParserConfigurationException | TransformerException | SAXException ex) { throw new AndrolibException(ex); } } public boolean buildManifestRaw(ExtFile appDir) throws AndrolibException { try { File apkDir = new File(appDir, APK_DIRNAME); LOGGER.fine("Copying raw AndroidManifest.xml..."); appDir.getDirectory().copyToDir(apkDir, APK_MANIFEST_FILENAMES); return true; } catch (DirectoryException ex) { throw new AndrolibException(ex); } } public boolean buildManifest(ExtFile appDir, UsesFramework usesFramework) throws BrutException { try { if (!new File(appDir, "AndroidManifest.xml").exists()) { return false; } if (! buildOptions.forceBuildAll) { LOGGER.fine("Checking whether resources has changed..."); } File apkDir = new File(appDir, APK_DIRNAME); if (buildOptions.forceBuildAll || isModified(newFiles(APK_MANIFEST_FILENAMES, appDir), newFiles(APK_MANIFEST_FILENAMES, apkDir))) { LOGGER.fine("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, parseUsesFramework(usesFramework)); Directory tmpDir = new ExtFile(apkFile).getDirectory(); tmpDir.copyToDir(apkDir, APK_MANIFEST_FILENAMES); apkFile.delete(); } return true; } catch (IOException | DirectoryException ex) { throw new AndrolibException(ex); } catch (AndrolibException ex) { LOGGER.warning("Parse AndroidManifest.xml failed, treat it as raw file."); return buildManifestRaw(appDir); } } public void buildLibs(File appDir) throws AndrolibException { buildLibrary(appDir, "lib"); buildLibrary(appDir, "libs"); buildLibrary(appDir, "kotlin"); buildLibrary(appDir, "META-INF/services"); } public void buildLibrary(File appDir, String folder) throws AndrolibException { File working = new File(appDir, folder); if (! working.exists()) { return; } File stored = new File(appDir, APK_DIRNAME + "/" + folder); if (buildOptions.forceBuildAll || isModified(working, stored)) { LOGGER.fine("Copying libs... (/" + folder + ")"); try { OS.rmdir(stored); OS.cpdir(working, stored); } catch (BrutException ex) { throw new AndrolibException(ex); } } } public void buildCopyOriginalFiles(File appDir) throws AndrolibException { if (buildOptions.copyOriginalFiles) { File originalDir = new File(appDir, "original"); if (originalDir.exists()) { try { LOGGER.fine("Copy original files..."); Directory in = (new ExtFile(originalDir)).getDirectory(); if (in.containsFile("AndroidManifest.xml")) { LOGGER.fine("Copy AndroidManifest.xml..."); in.copyToDir(new File(appDir, APK_DIRNAME), "AndroidManifest.xml"); } if (in.containsFile("stamp-cert-sha256")) { LOGGER.fine("Copy stamp-cert-sha256..."); in.copyToDir(new File(appDir, APK_DIRNAME), "stamp-cert-sha256"); } if (in.containsDir("META-INF")) { LOGGER.fine("Copy META-INF..."); in.copyToDir(new File(appDir, APK_DIRNAME), "META-INF"); } } catch (DirectoryException ex) { throw new AndrolibException(ex); } } } } public void buildUnknownFiles(File appDir, File outFile, MetaInfo meta) throws AndrolibException { if (meta.unknownFiles != null) { LOGGER.fine("Copying unknown files/dir..."); Map files = meta.unknownFiles; 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"); } try ( ZipFile inputFile = new ZipFile(tempFile); ZipOutputStream actualOutput = new ZipOutputStream(new FileOutputStream(outFile)) ) { copyExistingFiles(inputFile, actualOutput); copyUnknownFiles(appDir, actualOutput, files); } catch (IOException | BrutException ex) { throw new AndrolibException(ex); } // Remove our temporary file. tempFile.delete(); } } private void copyExistingFiles(ZipFile inputFile, ZipOutputStream outputFile) throws IOException { // First, copy the contents from the existing outFile: Enumeration entries = inputFile.entries(); while (entries.hasMoreElements()) { ZipEntry entry = new ZipEntry(entries.nextElement()); // 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 if (! entry.isDirectory()) { BrutIO.copy(inputFile, outputFile, entry); } outputFile.closeEntry(); } } private void copyUnknownFiles(File appDir, ZipOutputStream outputFile, Map files) throws BrutException, IOException { File unknownFileDir = new File(appDir, UNK_DIRNAME); // loop through unknown files for (Map.Entry unknownFileInfo : files.entrySet()) { File inputFile; try { inputFile = new File(unknownFileDir, BrutIO.sanitizeUnknownFile(unknownFileDir, unknownFileInfo.getKey())); } catch (RootUnknownFileException | InvalidUnknownFileException | TraversalUnknownFileException exception) { LOGGER.warning(String.format("Skipping file %s (%s)", unknownFileInfo.getKey(), exception.getMessage())); continue; } if (inputFile.isDirectory()) { continue; } ZipEntry newEntry = new ZipEntry(unknownFileInfo.getKey()); int method = Integer.parseInt(unknownFileInfo.getValue()); LOGGER.fine(String.format("Copying unknown file %s with method %d", unknownFileInfo.getKey(), method)); if (method == ZipEntry.STORED) { newEntry.setMethod(ZipEntry.STORED); newEntry.setSize(inputFile.length()); newEntry.setCompressedSize(-1); BufferedInputStream unknownFile = new BufferedInputStream(new FileInputStream(inputFile)); CRC32 crc = BrutIO.calculateCrc(unknownFile); newEntry.setCrc(crc.getValue()); unknownFile.close(); } else { newEntry.setMethod(ZipEntry.DEFLATED); } outputFile.putNextEntry(newEntry); BrutIO.copy(inputFile, outputFile); outputFile.closeEntry(); } } public void buildApk(File appDir, File outApk) throws AndrolibException { LOGGER.fine("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.zipPackage(outApk, new File(appDir, APK_DIRNAME), assetDir); } public void publicizeResources(File arscFile) throws AndrolibException { mAndRes.publicizeResources(arscFile); } public void installFramework(File frameFile) throws AndrolibException { mAndRes.installFramework(frameFile); } public void listFrameworks() throws AndrolibException { mAndRes.listFrameworkDirectory(); } public void emptyFrameworkDirectory() throws AndrolibException { mAndRes.emptyFrameworkDirectory(); } 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(UsesFramework usesFramework) throws AndrolibException { if (usesFramework == null) { return null; } List ids = usesFramework.ids; if (ids == null || ids.isEmpty()) { return null; } String tag = usesFramework.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) { return ! stored.exists() || BrutIO.recursiveModifiedTime(working) > BrutIO .recursiveModifiedTime(stored); } private boolean isFile(File working) { return working.exists(); } 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(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; } public void close() throws IOException { mAndRes.close(); } 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 UNK_DIRNAME = "unknown"; private final static String[] APK_RESOURCES_FILENAMES = new String[] { "resources.arsc", "AndroidManifest.xml", "res", "r", "R" }; 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[] { "AndroidManifest.xml" }; private final static String[] APK_STANDARD_ALL_FILENAMES = new String[] { "classes.dex", "AndroidManifest.xml", "resources.arsc", "res", "r", "R", "lib", "libs", "assets", "META-INF", "kotlin" }; private final static Pattern NO_COMPRESS_PATTERN = Pattern.compile("(" + "jpg|jpeg|png|gif|wav|mp2|mp3|ogg|aac|mpg|mpeg|mid|midi|smf|jet|rtttl|imy|xmf|mp4|" + "m4a|m4v|3gp|3gpp|3g2|3gpp2|amr|awb|wma|wmv|webm|webp|mkv)$"); }