mirror of
https://github.com/revanced/Apktool.git
synced 2024-11-14 00:19:25 +01:00
Moves all XML parsing related functions to new class: ResXmlPatcher
This commit is contained in:
parent
addbf8336d
commit
3208624bf6
@ -22,6 +22,7 @@ import brut.androlib.res.data.ResPackage;
|
||||
import brut.androlib.res.data.ResTable;
|
||||
import brut.androlib.res.data.ResUnknownFiles;
|
||||
import brut.androlib.res.util.ExtFile;
|
||||
import brut.androlib.res.xml.ResXmlPatcher;
|
||||
import brut.androlib.src.SmaliBuilder;
|
||||
import brut.androlib.src.SmaliDecoder;
|
||||
import brut.common.BrutException;
|
||||
@ -280,7 +281,7 @@ public class Androlib {
|
||||
new File(appDir, APK_DIRNAME).mkdirs();
|
||||
buildSources(appDir);
|
||||
buildNonDefaultSources(appDir);
|
||||
mAndRes.fixing_public_attrs_in_providers(new File(appDir, "AndroidManifest.xml"));
|
||||
ResXmlPatcher.fixingPublicAttrsInProviderAttributes(new File(appDir, "AndroidManifest.xml"));
|
||||
buildResources(appDir, (Map<String, Object>) meta.get("usesFramework"));
|
||||
|
||||
buildLib(appDir);
|
||||
@ -483,7 +484,7 @@ public class Androlib {
|
||||
File apkDir = new File(appDir, APK_DIRNAME);
|
||||
|
||||
if (apkOptions.debugMode) {
|
||||
mAndRes.remove_application_debug(new File(apkDir, "AndroidManifest.xml").getAbsolutePath());
|
||||
ResXmlPatcher.removeApplicationDebugTag(new File(apkDir,"AndroidManifest.xml"));
|
||||
}
|
||||
|
||||
if (apkOptions.forceBuildAll || isModified(newFiles(APK_MANIFEST_FILENAMES, appDir),
|
||||
|
@ -25,6 +25,7 @@ import brut.androlib.res.decoder.ARSCDecoder.ARSCData;
|
||||
import brut.androlib.res.decoder.ARSCDecoder.FlagsOffset;
|
||||
import brut.androlib.res.util.*;
|
||||
import brut.androlib.res.xml.ResValuesXmlSerializable;
|
||||
import brut.androlib.res.xml.ResXmlPatcher;
|
||||
import brut.common.BrutException;
|
||||
import brut.directory.*;
|
||||
import brut.util.*;
|
||||
@ -35,21 +36,8 @@ import java.util.zip.*;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import javax.xml.parsers.DocumentBuilder;
|
||||
import javax.xml.parsers.DocumentBuilderFactory;
|
||||
import javax.xml.parsers.ParserConfigurationException;
|
||||
import javax.xml.transform.*;
|
||||
import javax.xml.transform.dom.DOMSource;
|
||||
import javax.xml.transform.stream.StreamResult;
|
||||
import javax.xml.xpath.*;
|
||||
|
||||
import org.w3c.dom.Document;
|
||||
import org.w3c.dom.NamedNodeMap;
|
||||
import org.w3c.dom.Node;
|
||||
|
||||
import org.apache.commons.io.IOUtils;
|
||||
import org.w3c.dom.NodeList;
|
||||
import org.xml.sax.SAXException;
|
||||
import org.xmlpull.v1.XmlSerializer;
|
||||
|
||||
/**
|
||||
@ -169,173 +157,30 @@ final public class AndrolibResources {
|
||||
}
|
||||
}
|
||||
|
||||
public void remove_application_debug(String filePath)
|
||||
throws AndrolibException {
|
||||
|
||||
// change application:debug to true
|
||||
try {
|
||||
Document doc = loadDocument(filePath);
|
||||
Node application = doc.getElementById("application");
|
||||
|
||||
// load attr
|
||||
NamedNodeMap attr = application.getAttributes();
|
||||
Node debugAttr = attr.getNamedItem("debug");
|
||||
|
||||
// remove application:debug
|
||||
if (debugAttr != null) {
|
||||
attr.removeNamedItem("debug");
|
||||
}
|
||||
|
||||
saveDocument(filePath, doc);
|
||||
|
||||
} catch (SAXException | ParserConfigurationException | IOException | TransformerException ignored) {
|
||||
}
|
||||
}
|
||||
|
||||
public void adjust_package_manifest(ResTable resTable, String filePath)
|
||||
public void adjustPackageManifest(ResTable resTable, String filePath)
|
||||
throws AndrolibException {
|
||||
|
||||
// compare resources.arsc package name to the one present in AndroidManifest
|
||||
ResPackage resPackage = resTable.getCurrentResPackage();
|
||||
mPackageOriginal = resPackage.getName();
|
||||
String packageOriginal = resPackage.getName();
|
||||
mPackageRenamed = resTable.getPackageRenamed();
|
||||
|
||||
resTable.setPackageId(resPackage.getId());
|
||||
resTable.setPackageOriginal(mPackageOriginal);
|
||||
resTable.setPackageOriginal(packageOriginal);
|
||||
|
||||
// 1) Check if mPackageOriginal === mPackageRenamed
|
||||
// 2) Check if mPackageOriginal is ignored via IGNORED_PACKAGES
|
||||
// 1) Check if packageOriginal === mPackageRenamed
|
||||
// 2) Check if packageOriginal is ignored via IGNORED_PACKAGES
|
||||
// 2a) If its ignored, make sure the mPackageRenamed isn't explicitly allowed
|
||||
if (mPackageOriginal.equalsIgnoreCase(mPackageRenamed) ||
|
||||
(Arrays.asList(IGNORED_PACKAGES).contains(mPackageOriginal) &&
|
||||
if (packageOriginal.equalsIgnoreCase(mPackageRenamed) ||
|
||||
(Arrays.asList(IGNORED_PACKAGES).contains(packageOriginal) &&
|
||||
! Arrays.asList(ALLOWED_PACKAGES).contains(mPackageRenamed))) {
|
||||
LOGGER.info("Regular manifest package...");
|
||||
} else {
|
||||
try {
|
||||
LOGGER.info("Renamed manifest package found! Replacing " + mPackageRenamed + " with " + mPackageOriginal);
|
||||
Document doc = loadDocument(filePath);
|
||||
|
||||
// Get the manifest line
|
||||
Node manifest = doc.getFirstChild();
|
||||
|
||||
// update package attribute
|
||||
NamedNodeMap attr = manifest.getAttributes();
|
||||
Node nodeAttr = attr.getNamedItem("package");
|
||||
nodeAttr.setNodeValue(mPackageOriginal);
|
||||
saveDocument(filePath, doc);
|
||||
|
||||
} catch (SAXException | ParserConfigurationException | IOException | TransformerException ignored) {
|
||||
}
|
||||
LOGGER.info("Renamed manifest package found! Replacing " + mPackageRenamed + " with " + packageOriginal);
|
||||
ResXmlPatcher.renameManifestPackage(new File(filePath), packageOriginal);
|
||||
}
|
||||
}
|
||||
|
||||
public void fixing_public_attrs_in_providers(File file) throws AndrolibException {
|
||||
if (file.exists()) {
|
||||
try {
|
||||
Document doc = loadDocument(file.getAbsolutePath());
|
||||
XPath xPath = XPathFactory.newInstance().newXPath();
|
||||
XPathExpression expression = xPath.compile("/manifest/application/provider");
|
||||
|
||||
Object result = expression.evaluate(doc, XPathConstants.NODESET);
|
||||
NodeList nodes = (NodeList) result;
|
||||
|
||||
for (int i = 0; i < nodes.getLength(); i++) {
|
||||
Node node = nodes.item(i);
|
||||
NamedNodeMap attrs = node.getAttributes();
|
||||
|
||||
if (attrs != null) {
|
||||
Node provider = attrs.getNamedItem("android:authorities");
|
||||
|
||||
if (provider != null) {
|
||||
String reference = provider.getNodeValue();
|
||||
String replacement = pull_value_from_strings(file.getParentFile(), reference);
|
||||
|
||||
if (replacement != null) {
|
||||
provider.setNodeValue(replacement);
|
||||
saveDocument(file.getAbsolutePath(), doc);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} catch (SAXException | ParserConfigurationException | IOException |
|
||||
XPathExpressionException | TransformerException ignored) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public String pull_value_from_strings(File directory, String key) throws AndrolibException {
|
||||
File file = new File(directory, "/res/values/strings.xml");
|
||||
key = key.replace("@string/", "");
|
||||
|
||||
if (file.exists()) {
|
||||
try {
|
||||
Document doc = loadDocument(file.getAbsolutePath());
|
||||
XPath xPath = XPathFactory.newInstance().newXPath();
|
||||
XPathExpression expression = xPath.compile("/resources/string[@name=" + '"' + key + "\"]/text()");
|
||||
|
||||
Object result = expression.evaluate(doc, XPathConstants.STRING);
|
||||
|
||||
if (result != null) {
|
||||
return (String) result;
|
||||
}
|
||||
|
||||
} catch (SAXException | ParserConfigurationException | IOException | XPathExpressionException ignored) {
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public void remove_manifest_versions(String filePath)
|
||||
throws AndrolibException {
|
||||
|
||||
File f = new File(filePath);
|
||||
|
||||
if (f.exists()) {
|
||||
try {
|
||||
Document doc = loadDocument(filePath);
|
||||
Node manifest = doc.getFirstChild();
|
||||
|
||||
// load attr
|
||||
NamedNodeMap attr = manifest.getAttributes();
|
||||
Node vCode = attr.getNamedItem("android:versionCode");
|
||||
Node vName = attr.getNamedItem("android:versionName");
|
||||
|
||||
// remove versionCode
|
||||
if (vCode != null) {
|
||||
attr.removeNamedItem("android:versionCode");
|
||||
}
|
||||
if (vName != null) {
|
||||
attr.removeNamedItem("android:versionName");
|
||||
}
|
||||
saveDocument(filePath, doc);
|
||||
|
||||
} catch (SAXException | ParserConfigurationException | IOException | TransformerException ignored) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private Document loadDocument(String filename)
|
||||
throws IOException, SAXException, ParserConfigurationException {
|
||||
|
||||
DocumentBuilderFactory docFactory = DocumentBuilderFactory.newInstance();
|
||||
DocumentBuilder docBuilder = docFactory.newDocumentBuilder();
|
||||
return docBuilder.parse(filename);
|
||||
}
|
||||
|
||||
private void saveDocument(String filename, Document doc)
|
||||
throws IOException, SAXException, ParserConfigurationException, TransformerException {
|
||||
|
||||
TransformerFactory transformerFactory = TransformerFactory.newInstance();
|
||||
Transformer transformer = transformerFactory.newTransformer();
|
||||
transformer.setOutputProperty(OutputKeys.INDENT, "yes");
|
||||
transformer.setOutputProperty(OutputKeys.STANDALONE,"yes");
|
||||
DOMSource source = new DOMSource(doc);
|
||||
StreamResult result = new StreamResult(new File(filename));
|
||||
transformer.transform(source, result);
|
||||
}
|
||||
|
||||
public void decodeManifestWithResources(ResTable resTable, ExtFile apkFile, File outDir)
|
||||
throws AndrolibException {
|
||||
|
||||
@ -360,8 +205,11 @@ final public class AndrolibResources {
|
||||
// also remove the android::versionCode / versionName from manifest for rebuild
|
||||
// this is a required change to prevent aapt warning about conflicting versions
|
||||
// it will be passed as a parameter to aapt like "--min-sdk-version" via apktool.yml
|
||||
adjust_package_manifest(resTable, outDir.getAbsolutePath() + File.separator + "AndroidManifest.xml");
|
||||
remove_manifest_versions(outDir.getAbsolutePath() + File.separator + "AndroidManifest.xml");
|
||||
adjustPackageManifest(resTable, outDir.getAbsolutePath() + File.separator + "AndroidManifest.xml");
|
||||
|
||||
ResXmlPatcher.removeManifestVersions(new File(
|
||||
outDir.getAbsolutePath() + File.separator + "AndroidManifest.xml"));
|
||||
|
||||
mPackageId = String.valueOf(resTable.getPackageId());
|
||||
}
|
||||
} catch (DirectoryException ex) {
|
||||
@ -856,13 +704,15 @@ final public class AndrolibResources {
|
||||
* @throws AndrolibException
|
||||
*/
|
||||
public File getAaptBinaryFile() throws AndrolibException {
|
||||
File aaptBinary;
|
||||
|
||||
try {
|
||||
if (OSDetection.isMacOSX()) {
|
||||
mAaptBinary = Jar.getResourceAsFile("/prebuilt/aapt/macosx/aapt");
|
||||
aaptBinary = Jar.getResourceAsFile("/prebuilt/aapt/macosx/aapt");
|
||||
} else if (OSDetection.isUnix()) {
|
||||
mAaptBinary = Jar.getResourceAsFile("/prebuilt/aapt/linux/aapt");
|
||||
aaptBinary = Jar.getResourceAsFile("/prebuilt/aapt/linux/aapt");
|
||||
} else if (OSDetection.isWindows()) {
|
||||
mAaptBinary = Jar.getResourceAsFile("/prebuilt/aapt/windows/aapt.exe");
|
||||
aaptBinary = Jar.getResourceAsFile("/prebuilt/aapt/windows/aapt.exe");
|
||||
} else {
|
||||
LOGGER.warning("Unknown Operating System: " + OSDetection.returnOS());
|
||||
return null;
|
||||
@ -870,8 +720,8 @@ final public class AndrolibResources {
|
||||
} catch (BrutException ex) {
|
||||
throw new AndrolibException(ex);
|
||||
}
|
||||
if (mAaptBinary.setExecutable(true)) {
|
||||
return mAaptBinary;
|
||||
if (aaptBinary.setExecutable(true)) {
|
||||
return aaptBinary;
|
||||
}
|
||||
|
||||
System.err.println("Can't set aapt binary as executable");
|
||||
@ -901,13 +751,10 @@ final public class AndrolibResources {
|
||||
private String mVersionCode = null;
|
||||
private String mVersionName = null;
|
||||
private String mPackageRenamed = null;
|
||||
private String mPackageOriginal = null;
|
||||
private String mPackageId = null;
|
||||
|
||||
private boolean mSharedLibrary = false;
|
||||
|
||||
private File mAaptBinary = null;
|
||||
|
||||
private final static String[] IGNORED_PACKAGES = new String[] {
|
||||
"android", "com.htc", "miui", "com.lge", "com.lge.internal", "yi" };
|
||||
|
||||
|
@ -0,0 +1,238 @@
|
||||
/**
|
||||
* Copyright 2014 Ryszard Wiśniewski <brut.alll@gmail.com>
|
||||
* Copyright 2015 Connor Tumbleson <connor.tumbleson@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.xml;
|
||||
|
||||
import brut.androlib.AndrolibException;
|
||||
import org.w3c.dom.Document;
|
||||
import org.w3c.dom.NamedNodeMap;
|
||||
import org.w3c.dom.Node;
|
||||
import org.w3c.dom.NodeList;
|
||||
import org.xml.sax.SAXException;
|
||||
|
||||
import javax.xml.parsers.DocumentBuilder;
|
||||
import javax.xml.parsers.DocumentBuilderFactory;
|
||||
import javax.xml.parsers.ParserConfigurationException;
|
||||
import javax.xml.transform.OutputKeys;
|
||||
import javax.xml.transform.Transformer;
|
||||
import javax.xml.transform.TransformerException;
|
||||
import javax.xml.transform.TransformerFactory;
|
||||
import javax.xml.transform.dom.DOMSource;
|
||||
import javax.xml.transform.stream.StreamResult;
|
||||
import javax.xml.xpath.*;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* @author Connor Tumbleson <connor.tumbleson@gmail.com>
|
||||
*/
|
||||
public final class ResXmlPatcher {
|
||||
|
||||
/**
|
||||
* Removes "debug" tag from file
|
||||
*
|
||||
* @param file AndroidManifest file
|
||||
* @throws AndrolibException
|
||||
*/
|
||||
public static void removeApplicationDebugTag(File file) throws AndrolibException {
|
||||
if (file.exists()) {
|
||||
try {
|
||||
Document doc = loadDocument(file);
|
||||
Node application = doc.getElementById("application");
|
||||
|
||||
// load attr
|
||||
NamedNodeMap attr = application.getAttributes();
|
||||
Node debugAttr = attr.getNamedItem("debug");
|
||||
|
||||
// remove application:debug
|
||||
if (debugAttr != null) {
|
||||
attr.removeNamedItem("debug");
|
||||
}
|
||||
|
||||
saveDocument(file, doc);
|
||||
|
||||
} catch (SAXException | ParserConfigurationException | IOException | TransformerException ignored) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Any @string reference in a <provider> value in AndroidManifest.xml will break on
|
||||
* build, thus preventing the application from installing. This is from a bug/error
|
||||
* in AOSP where public resources cannot be part of an authorities attribute within
|
||||
* a <provider> tag.
|
||||
*
|
||||
* This finds any reference and replaces it with the literal value found in the
|
||||
* res/values/strings.xml file.
|
||||
*
|
||||
* @param file File for AndroidManifest.xml
|
||||
* @throws AndrolibException
|
||||
*/
|
||||
public static void fixingPublicAttrsInProviderAttributes(File file) throws AndrolibException {
|
||||
if (file.exists()) {
|
||||
try {
|
||||
Document doc = loadDocument(file);
|
||||
XPath xPath = XPathFactory.newInstance().newXPath();
|
||||
XPathExpression expression = xPath.compile("/manifest/application/provider");
|
||||
|
||||
Object result = expression.evaluate(doc, XPathConstants.NODESET);
|
||||
NodeList nodes = (NodeList) result;
|
||||
|
||||
for (int i = 0; i < nodes.getLength(); i++) {
|
||||
Node node = nodes.item(i);
|
||||
NamedNodeMap attrs = node.getAttributes();
|
||||
|
||||
if (attrs != null) {
|
||||
Node provider = attrs.getNamedItem("android:authorities");
|
||||
|
||||
if (provider != null) {
|
||||
String reference = provider.getNodeValue();
|
||||
String replacement = pullValueFromStrings(file.getParentFile(), reference);
|
||||
|
||||
if (replacement != null) {
|
||||
provider.setNodeValue(replacement);
|
||||
saveDocument(file, doc);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} catch (SAXException | ParserConfigurationException | IOException |
|
||||
XPathExpressionException | TransformerException ignored) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds key in strings.xml file and returns text value
|
||||
*
|
||||
* @param directory Root directory of apk
|
||||
* @param key String reference (ie @string/foo)
|
||||
* @return String|null
|
||||
* @throws AndrolibException
|
||||
*/
|
||||
public static String pullValueFromStrings(File directory, String key) throws AndrolibException {
|
||||
File file = new File(directory, "/res/values/strings.xml");
|
||||
key = key.replace("@string/", "");
|
||||
|
||||
if (file.exists()) {
|
||||
try {
|
||||
Document doc = loadDocument(file);
|
||||
XPath xPath = XPathFactory.newInstance().newXPath();
|
||||
XPathExpression expression = xPath.compile("/resources/string[@name=" + '"' + key + "\"]/text()");
|
||||
|
||||
Object result = expression.evaluate(doc, XPathConstants.STRING);
|
||||
|
||||
if (result != null) {
|
||||
return (String) result;
|
||||
}
|
||||
|
||||
} catch (SAXException | ParserConfigurationException | IOException | XPathExpressionException ignored) {
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes attributes like "versionCode" and "versionName" from file.
|
||||
*
|
||||
* @param file File representing AndroidManifest.xml
|
||||
* @throws AndrolibException
|
||||
*/
|
||||
public static void removeManifestVersions(File file) throws AndrolibException {
|
||||
if (file.exists()) {
|
||||
try {
|
||||
Document doc = loadDocument(file);
|
||||
Node manifest = doc.getFirstChild();
|
||||
NamedNodeMap attr = manifest.getAttributes();
|
||||
Node vCode = attr.getNamedItem("android:versionCode");
|
||||
Node vName = attr.getNamedItem("android:versionName");
|
||||
|
||||
if (vCode != null) {
|
||||
attr.removeNamedItem("android:versionCode");
|
||||
}
|
||||
if (vName != null) {
|
||||
attr.removeNamedItem("android:versionName");
|
||||
}
|
||||
saveDocument(file, doc);
|
||||
|
||||
} catch (SAXException | ParserConfigurationException | IOException | TransformerException ignored) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Replaces package value with passed packageOriginal string
|
||||
*
|
||||
* @param file File for AndroidManifest.xml
|
||||
* @param packageOriginal Package name to replace
|
||||
* @throws AndrolibException
|
||||
*/
|
||||
public static void renameManifestPackage(File file, String packageOriginal) throws AndrolibException {
|
||||
try {
|
||||
Document doc = loadDocument(file);
|
||||
|
||||
// Get the manifest line
|
||||
Node manifest = doc.getFirstChild();
|
||||
|
||||
// update package attribute
|
||||
NamedNodeMap attr = manifest.getAttributes();
|
||||
Node nodeAttr = attr.getNamedItem("package");
|
||||
nodeAttr.setNodeValue(packageOriginal);
|
||||
saveDocument(file, doc);
|
||||
|
||||
} catch (SAXException | ParserConfigurationException | IOException | TransformerException ignored) {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param file File to load into Document
|
||||
* @return Document
|
||||
* @throws IOException
|
||||
* @throws SAXException
|
||||
* @throws ParserConfigurationException
|
||||
*/
|
||||
private static Document loadDocument(File file)
|
||||
throws IOException, SAXException, ParserConfigurationException {
|
||||
|
||||
DocumentBuilderFactory docFactory = DocumentBuilderFactory.newInstance();
|
||||
DocumentBuilder docBuilder = docFactory.newDocumentBuilder();
|
||||
return docBuilder.parse(file);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param file File to save Document to (ie AndroidManifest.xml)
|
||||
* @param doc Document being saved
|
||||
* @throws IOException
|
||||
* @throws SAXException
|
||||
* @throws ParserConfigurationException
|
||||
* @throws TransformerException
|
||||
*/
|
||||
private static void saveDocument(File file, Document doc)
|
||||
throws IOException, SAXException, ParserConfigurationException, TransformerException {
|
||||
|
||||
TransformerFactory transformerFactory = TransformerFactory.newInstance();
|
||||
Transformer transformer = transformerFactory.newTransformer();
|
||||
transformer.setOutputProperty(OutputKeys.INDENT, "yes");
|
||||
transformer.setOutputProperty(OutputKeys.STANDALONE,"yes");
|
||||
DOMSource source = new DOMSource(doc);
|
||||
StreamResult result = new StreamResult(file);
|
||||
transformer.transform(source, result);
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user