allow xml encoder/decoder to process root apk files

This commit is contained in:
REAndroid
2023-01-03 11:42:55 -05:00
parent 986c5c1ccd
commit d75a8b66bc
11 changed files with 247 additions and 86 deletions

View File

@ -40,7 +40,7 @@ public class ApkModule {
private AndroidManifestBlock mManifestBlock;
private final UncompressedFiles mUncompressedFiles;
private APKLogger apkLogger;
ApkModule(String moduleName, APKArchive apkArchive){
public ApkModule(String moduleName, APKArchive apkArchive){
this.moduleName=moduleName;
this.apkArchive=apkArchive;
this.mUncompressedFiles=new UncompressedFiles();

View File

@ -16,6 +16,7 @@
package com.reandroid.lib.apk;
import com.reandroid.archive.InputSource;
import com.reandroid.lib.apk.xmlencoder.XMLEncodeSource;
import com.reandroid.lib.arsc.chunk.TypeBlock;
import com.reandroid.lib.arsc.chunk.xml.ResXmlBlock;
import com.reandroid.lib.arsc.value.EntryBlock;
@ -101,9 +102,14 @@ public class ResFile {
return mBinXml;
}
mBinXmlChecked=true;
try {
mBinXml=ResXmlBlock.isResXmlBlock(getInputSource().openStream());
} catch (IOException exception) {
InputSource inputSource=getInputSource();
if(inputSource instanceof XMLEncodeSource){
mBinXml=true;
}else{
try {
mBinXml=ResXmlBlock.isResXmlBlock(getInputSource().openStream());
} catch (IOException exception) {
}
}
return mBinXml;
}

View File

@ -18,6 +18,7 @@
import com.reandroid.lib.apk.APKLogger;
import com.reandroid.lib.apk.ResourceIds;
import com.reandroid.lib.arsc.chunk.PackageBlock;
import com.reandroid.lib.arsc.chunk.TableBlock;
import com.reandroid.lib.arsc.chunk.TypeBlock;
import com.reandroid.lib.arsc.container.SpecTypePair;
import com.reandroid.lib.arsc.decoder.ValueDecoder;
@ -25,6 +26,7 @@
import com.reandroid.lib.arsc.item.SpecString;
import com.reandroid.lib.arsc.util.FrameworkTable;
import com.reandroid.lib.arsc.value.EntryBlock;
import com.reandroid.lib.common.Frameworks;
import com.reandroid.lib.common.ResourceResolver;
import java.util.Collection;
@ -336,5 +338,21 @@
apkLogger.logVerbose(msg);
}
}
public static EncodeMaterials create(TableBlock tableBlock){
PackageBlock packageBlock = tableBlock.pickOne();
if(packageBlock==null){
throw new EncodeException("No packages found on table block");
}
return create(packageBlock);
}
public static EncodeMaterials create(PackageBlock packageBlock){
ResourceIds resourceIds = new ResourceIds();
resourceIds.loadPackageBlock(packageBlock);
ResourceIds.Table.Package packageId = resourceIds.getTable().listPackages().get(0);
return new EncodeMaterials()
.setPackageIds(packageId)
.setCurrentPackage(packageBlock)
.addFramework(Frameworks.getAndroid());
}
}

View File

@ -15,19 +15,34 @@
*/
package com.reandroid.lib.apk.xmlencoder;
import com.reandroid.lib.apk.ApkUtil;
import com.reandroid.lib.arsc.chunk.PackageBlock;
import com.reandroid.lib.arsc.chunk.TypeBlock;
import com.reandroid.lib.arsc.value.EntryBlock;
import com.reandroid.archive.APKArchive;
import com.reandroid.archive.FileInputSource;
import com.reandroid.archive.InputSource;
import com.reandroid.lib.apk.ApkUtil;
import com.reandroid.lib.apk.UncompressedFiles;
import com.reandroid.lib.arsc.chunk.PackageBlock;
import com.reandroid.lib.arsc.chunk.TypeBlock;
import com.reandroid.lib.arsc.value.EntryBlock;
import com.reandroid.xml.source.XMLFileSource;
import com.reandroid.xml.source.XMLSource;
import java.io.File;
import java.util.List;
import java.io.File;
import java.util.List;
public class FilePathEncoder {
public class FilePathEncoder {
private final EncodeMaterials materials;
private APKArchive apkArchive;
private UncompressedFiles uncompressedFiles;
public FilePathEncoder(EncodeMaterials encodeMaterials){
this.materials =encodeMaterials;
}
public void setApkArchive(APKArchive apkArchive) {
this.apkArchive = apkArchive;
}
public void setUncompressedFiles(UncompressedFiles uncompressedFiles){
this.uncompressedFiles=uncompressedFiles;
}
public void encodeResDir(File resDir){
materials.logVerbose("Scanning file list: "
+resDir.getParentFile().getName()
@ -46,7 +61,7 @@ public class FilePathEncoder {
encodeFileEntry(file);
}
}
public void encodeFileEntry(File resFile){
public InputSource encodeFileEntry(File resFile){
String type = EncodeUtil.getTypeNameFromResFile(resFile);
PackageBlock packageBlock = materials.getCurrentPackage();
byte typeId=packageBlock
@ -59,11 +74,43 @@ public class FilePathEncoder {
EntryBlock entryBlock=typeBlock
.getOrCreateEntry((short) (0xffff & resourceId));
entryBlock.setValueAsString(EncodeUtil.getEntryPathFromResFile(resFile));
String path=EncodeUtil.getEntryPathFromResFile(resFile);
entryBlock.setValueAsString(path);
entryBlock.setSpecReference(materials.getSpecString(name));
if(resFile.getName().endsWith(".xml")){
XMLFileEncoder fileEncoder=new XMLFileEncoder(materials);
fileEncoder.encode(resFile);
InputSource inputSource=createInputSource(path, resFile);
addInputSource(inputSource);
return inputSource;
}
private InputSource createInputSource(String path, File resFile){
if(isXmlFile(resFile)){
return createXMLEncodeInputSource(path, resFile);
}
addUncompressedFiles(path);
return createRawFileInputSource(path, resFile);
}
private InputSource createRawFileInputSource(String path, File resFile){
return new FileInputSource(resFile, path);
}
private InputSource createXMLEncodeInputSource(String path, File resFile){
XMLSource xmlSource = new XMLFileSource(path, resFile);
return new XMLEncodeSource(materials, xmlSource);
}
private boolean isXmlFile(File resFile){
String name=resFile.getName();
if(!name.endsWith(".xml")){
return false;
}
String type=EncodeUtil.getTypeNameFromResFile(resFile);
return !type.equals("raw");
}
private void addInputSource(InputSource inputSource){
if(inputSource!=null && this.apkArchive!=null){
apkArchive.add(inputSource);
}
}
private void addUncompressedFiles(String path){
if(uncompressedFiles!=null){
uncompressedFiles.addPath(path);
}
}
}

View File

@ -15,15 +15,17 @@
*/
package com.reandroid.lib.apk.xmlencoder;
import com.reandroid.lib.apk.APKLogger;
import com.reandroid.lib.apk.ApkUtil;
import com.reandroid.lib.apk.ResourceIds;
import com.reandroid.archive.APKArchive;
import com.reandroid.archive.FileInputSource;
import com.reandroid.lib.apk.*;
import com.reandroid.lib.arsc.chunk.PackageBlock;
import com.reandroid.lib.arsc.chunk.TableBlock;
import com.reandroid.lib.arsc.chunk.xml.AndroidManifestBlock;
import com.reandroid.lib.common.Frameworks;
import com.reandroid.xml.XMLDocument;
import com.reandroid.xml.XMLException;
import com.reandroid.xml.source.XMLFileSource;
import com.reandroid.xml.source.XMLSource;
import java.io.File;
import java.io.IOException;
@ -35,19 +37,37 @@
public class RESEncoder {
private APKLogger apkLogger;
private final TableBlock tableBlock;
private final Set<File> parsedValueFiles;
private final Set<File> parsedFiles = new HashSet<>();
private final ApkModule apkModule;
public RESEncoder(){
this.tableBlock = new TableBlock();
this.parsedValueFiles = new HashSet<>();
this(new ApkModule("encoded",
new APKArchive()), new TableBlock());
}
public RESEncoder(ApkModule module, TableBlock block){
this.apkModule = module;
this.tableBlock = block;
if(!module.hasTableBlock()){
BlockInputSource<TableBlock> inputSource=
new BlockInputSource<>(TableBlock.FILE_NAME, block);
this.apkModule.getApkArchive().add(inputSource);
}
}
public TableBlock getTableBlock(){
return tableBlock;
}
public void scanDirectory(File rootDir) throws IOException, XMLException {
List<File> pubXmlFileList = searchPublicXmlFiles(rootDir);
public ApkModule getApkModule(){
return apkModule;
}
public void scanDirectory(File mainDir) throws IOException, XMLException {
scanResourceFiles(mainDir);
File rootDir=new File(mainDir, "root");
scanRootDir(rootDir);
}
private void scanResourceFiles(File mainDir) throws IOException, XMLException {
List<File> pubXmlFileList = searchPublicXmlFiles(mainDir);
if(pubXmlFileList.size()==0){
throw new IOException("No .*/values/"
+ApkUtil.FILE_NAME_PUBLIC_XML+" file found in '"+rootDir);
+ApkUtil.FILE_NAME_PUBLIC_XML+" file found in '"+mainDir);
}
for(File pubXmlFile:pubXmlFileList){
EncodeMaterials encodeMaterials = loadPublicXml(pubXmlFile);
@ -55,10 +75,21 @@
File resDir=toResDirectory(pubXmlFile);
encodeResDir(encodeMaterials, resDir);
FilePathEncoder filePathEncoder = new FilePathEncoder(encodeMaterials);
filePathEncoder.setApkArchive(getApkModule().getApkArchive());
filePathEncoder.setUncompressedFiles(getApkModule().getUncompressedFiles());
filePathEncoder.encodeResDir(resDir);
PackageBlock packageBlock = encodeMaterials.getCurrentPackage();
packageBlock.sortTypes();
packageBlock.refresh();
File manifestFile=toAndroidManifest(pubXmlFile);
XMLSource xmlSource =
new XMLFileSource(AndroidManifestBlock.FILE_NAME, manifestFile);
XMLEncodeSource xmlEncodeSource =
new XMLEncodeSource(encodeMaterials, xmlSource);
getApkModule().getApkArchive().add(xmlEncodeSource);
}
tableBlock.refresh();
}
@ -137,9 +168,17 @@
}
return results;
}
private List<File> searchPublicXmlFiles(File rootDir){
logVerbose("Searching public.xml: "+rootDir);
List<File> xmlFiles = ApkUtil.recursiveFiles(rootDir, ApkUtil.FILE_NAME_PUBLIC_XML);
private List<File> searchPublicXmlFiles(File mainDir){
logVerbose("Searching public.xml: "+mainDir);
List<File> dirList=ApkUtil.listDirectories(mainDir);
List<File> xmlFiles = new ArrayList<>();
for(File dir:dirList){
if(dir.getName().equals("root")){
continue;
}
xmlFiles.addAll(
ApkUtil.recursiveFiles(mainDir, ApkUtil.FILE_NAME_PUBLIC_XML));
}
List<File> results = new ArrayList<>();
for(File file:xmlFiles){
if(toAndroidManifest(file).isFile()){
@ -148,11 +187,23 @@
}
return results;
}
//TODO: do this in separate class
private void scanRootDir(File rootDir){
APKArchive archive=getApkModule().getApkArchive();
List<File> rootFileList=ApkUtil.recursiveFiles(rootDir);
for(File file:rootFileList){
String path=ApkUtil.toArchivePath(rootDir, file);
FileInputSource inputSource=new FileInputSource(file, path);
archive.add(inputSource);
}
}
private boolean isAlreadyParsed(File file){
return parsedValueFiles.contains(file);
return parsedFiles.contains(file);
}
private void addParsedFiles(File file){
parsedValueFiles.add(file);
parsedFiles.add(file);
}
public void setAPKLogger(APKLogger logger) {
this.apkLogger = logger;

View File

@ -15,15 +15,13 @@
*/
package com.reandroid.lib.apk.xmlencoder;
import com.reandroid.lib.arsc.chunk.xml.ResIdBuilder;
import com.reandroid.lib.arsc.chunk.xml.ResXmlAttribute;
import com.reandroid.lib.arsc.chunk.xml.ResXmlBlock;
import com.reandroid.lib.arsc.chunk.xml.ResXmlElement;
import com.reandroid.lib.arsc.chunk.xml.*;
import com.reandroid.lib.arsc.decoder.ValueDecoder;
import com.reandroid.lib.arsc.value.EntryBlock;
import com.reandroid.lib.arsc.value.ResValueBag;
import com.reandroid.lib.arsc.value.ValueType;
import com.reandroid.lib.arsc.value.attribute.AttributeBag;
import com.reandroid.lib.arsc.value.attribute.AttributeValueType;
import com.reandroid.xml.*;
import java.io.File;
@ -35,32 +33,36 @@ public class XMLFileEncoder {
public XMLFileEncoder(EncodeMaterials materials){
this.materials=materials;
}
public void encode(String xmlString){
public ResXmlBlock encode(String xmlString){
try {
encode(XMLDocument.load(xmlString));
return encode(XMLDocument.load(xmlString));
} catch (XMLException ex) {
materials.logMessage(ex.getMessage());
}
return null;
}
public void encode(InputStream inputStream){
public ResXmlBlock encode(InputStream inputStream){
try {
encode(XMLDocument.load(inputStream));
return encode(XMLDocument.load(inputStream));
} catch (XMLException ex) {
materials.logMessage(ex.getMessage());
}
return null;
}
public void encode(File xmlFile){
public ResXmlBlock encode(File xmlFile){
try {
encode(XMLDocument.load(xmlFile));
return encode(XMLDocument.load(xmlFile));
} catch (XMLException ex) {
materials.logMessage(ex.getMessage());
}
return null;
}
public void encode(XMLDocument xmlDocument){
public ResXmlBlock encode(XMLDocument xmlDocument){
resXmlBlock=new ResXmlBlock();
buildIdMap(xmlDocument);
buildElement(xmlDocument);
resXmlBlock.refresh();
return resXmlBlock;
}
public ResXmlBlock getResXmlBlock(){
return resXmlBlock;
@ -93,27 +95,39 @@ public class XMLFileEncoder {
if(entryBlock!=null){
resourceId=entryBlock.getResourceId();
}
ResXmlAttribute xmlAttribute =
resXmlElement.createAttribute(attribute.getNameWoPrefix(), resourceId);
String prefix=attribute.getNamePrefix();
if(prefix!=null){
ResXmlStartNamespace ns = resXmlElement.getStartNamespaceByPrefix(prefix);
xmlAttribute.setNamespaceReference(ns.getUriReference());
}
String valueText=attribute.getValue();
ResXmlAttribute xmlAttribute =
resXmlElement.createAttribute(attribute.getName(), resourceId);
if(ValueDecoder.isReference(valueText)){
xmlAttribute.setValueType(ValueType.REFERENCE);
xmlAttribute.setRawValue(materials.resolveReference(valueText));
continue;
}
if(entryBlock!=null){
AttributeBag attributeBag=AttributeBag
.create((ResValueBag) entryBlock.getResValue());
ValueDecoder.EncodeResult encodeResult =
attributeBag.encodeName(valueText);
attributeBag.encodeEnumOrFlagValue(valueText);
if(encodeResult!=null){
xmlAttribute.setValueType(encodeResult.valueType);
xmlAttribute.setRawValue(encodeResult.value);
continue;
}
if(attributeBag.contains(AttributeValueType.STRING)) {
xmlAttribute.setValueAsString(valueText);
continue;
}
}
if(ValueDecoder.isReference(valueText)){
xmlAttribute.setValueType(ValueType.REFERENCE);
xmlAttribute.setRawValue(materials.resolveReference(valueText));
}else if(EncodeUtil.isEmpty(valueText)) {
if(EncodeUtil.isEmpty(valueText)) {
xmlAttribute.setValueType(ValueType.NULL);
xmlAttribute.setRawValue(0);
}else{

View File

@ -40,17 +40,16 @@ class XMLValuesEncoderArray extends XMLValuesEncoderBag{
String valueText=child.getTextContent();
if(ValueDecoder.isReference(valueText)){
bagItem.setType(ValueType.REFERENCE);
bagItem.setData(getMaterials().resolveReference(valueText));
bagItem.setTypeAndData(ValueType.REFERENCE,
getMaterials().resolveReference(valueText));
}else if(EncodeUtil.isEmpty(valueText)) {
bagItem.setType(ValueType.NULL);
bagItem.setData(0);
bagItem.setTypeAndData(ValueType.NULL, 0);
}else if(!tag_string){
ValueDecoder.EncodeResult encodeResult =
ValueDecoder.encodeGuessAny(valueText);
if(encodeResult!=null){
bagItem.setType(encodeResult.valueType);
bagItem.setData(encodeResult.value);
bagItem.setTypeAndData(encodeResult.valueType,
encodeResult.value);
}else {
bagItem.setValueAsString(valueText);
}

View File

@ -36,6 +36,8 @@ class XMLValuesEncoderBag extends XMLValuesEncoder{
encodeChildes(element, resValueBag);
}
void encodeChildes(XMLElement element, ResValueBag resValueBag){
throw new EncodeException("Unimplemented bag type encoder: "
+element.getTagName());
}
int getChildesCount(XMLElement element){

View File

@ -22,6 +22,7 @@ import com.reandroid.lib.arsc.value.ResValueBag;
import com.reandroid.lib.arsc.value.ResValueBagItem;
import com.reandroid.lib.arsc.value.ValueType;
import com.reandroid.lib.arsc.value.attribute.AttributeBag;
import com.reandroid.lib.arsc.value.attribute.AttributeValueType;
import com.reandroid.xml.XMLElement;
@ -33,41 +34,48 @@ class XMLValuesEncoderStyle extends XMLValuesEncoderBag{
void encodeChildes(XMLElement parentElement, ResValueBag resValueBag){
int count = parentElement.getChildesCount();
ResValueBagItemArray itemArray = resValueBag.getResValueBagItemArray();
EncodeMaterials materials=getMaterials();
for(int i=0;i<count;i++){
XMLElement child=parentElement.getChildAt(i);
ResValueBagItem bagItem = itemArray.get(i);
EntryBlock entryBlock=materials
EntryBlock attributeEntry=getMaterials()
.getAttributeBlock(child.getAttributeValue("name"));
if(entryBlock==null){
throw new EncodeException("Unknown attribute name: '"+child.toText()+"'");
if(attributeEntry==null){
throw new EncodeException("Unknown attribute name: '"+child.toText()
+"', for style: "+parentElement.getAttributeValue("name"));
}
bagItem.setId(entryBlock.getResourceId());
AttributeBag attributeBag=AttributeBag
.create((ResValueBag) entryBlock.getResValue());
encodeChild(parentElement.getChildAt(i), attributeEntry, itemArray.get(i));
}
}
private void encodeChild(XMLElement child, EntryBlock attributeEntry, ResValueBagItem bagItem){
String valueText=child.getTextContent();
ValueDecoder.EncodeResult encodeResult =
attributeBag.encodeName(valueText);
if(encodeResult!=null){
bagItem.setType(encodeResult.valueType);
bagItem.setData(encodeResult.value);
continue;
}
if(ValueDecoder.isReference(valueText)){
bagItem.setId(attributeEntry.getResourceId());
AttributeBag attributeBag=AttributeBag
.create((ResValueBag) attributeEntry.getResValue());
String valueText=child.getTextContent();
ValueDecoder.EncodeResult encodeEnumFlag =
attributeBag.encodeEnumOrFlagValue(valueText);
if(encodeEnumFlag!=null){
bagItem.setTypeAndData(encodeEnumFlag.valueType, encodeEnumFlag.value);
return;
}
if(ValueDecoder.isReference(valueText)){
if(valueText.startsWith("?")){
bagItem.setType(ValueType.ATTRIBUTE);
}else {
bagItem.setType(ValueType.REFERENCE);
bagItem.setData(getMaterials().resolveReference(valueText));
}else if(EncodeUtil.isEmpty(valueText)) {
bagItem.setType(ValueType.NULL);
bagItem.setData(0);
}else{
encodeResult=ValueDecoder.encodeGuessAny(valueText);
if(encodeResult!=null){
bagItem.setType(encodeResult.valueType);
bagItem.setData(encodeResult.value);
}else {
bagItem.setValueAsString(valueText);
}
}
bagItem.setData(getMaterials().resolveReference(valueText));
}else if(attributeBag.contains(AttributeValueType.STRING)) {
bagItem.setValueAsString(valueText);
}else if(EncodeUtil.isEmpty(valueText)) {
bagItem.setTypeAndData(ValueType.NULL, 0);
}else{
ValueDecoder.EncodeResult encodeResult = ValueDecoder.encodeGuessAny(valueText);
if(encodeResult!=null){
bagItem.setTypeAndData(encodeResult.valueType,
encodeResult.value);
}else {
bagItem.setValueAsString(valueText);
}
}
}

View File

@ -27,8 +27,12 @@ public class AttributeBag {
public AttributeBag(AttributeBagItem[] bagItems){
this.mBagItems=bagItems;
}
public ValueDecoder.EncodeResult encodeName(String valueString){
if(valueString==null){
public boolean contains(AttributeValueType valueType){
return getFormat().contains(valueType);
}
public ValueDecoder.EncodeResult encodeEnumOrFlagValue(String valueString){
if(valueString==null || !isEnumOrFlag()){
return null;
}
int value=0;
@ -45,7 +49,8 @@ public class AttributeBag {
if(!foundOnce){
return null;
}
return new ValueDecoder.EncodeResult(ValueType.INT_HEX, value);
ValueType valueType = isFlag()?ValueType.INT_HEX:ValueType.INT_DEC;
return new ValueDecoder.EncodeResult(valueType, value);
}
public String decodeAttributeValue(EntryStore entryStore, int attrValue){
AttributeBagItem[] bagItems=searchValue(attrValue);
@ -197,6 +202,9 @@ public class AttributeBag {
}
return null;
}
private boolean isEnumOrFlag(){
return isFlag() || isEnum();
}
public boolean isFlag(){
return getFormat().isFlag();
}

View File

@ -84,6 +84,14 @@ public class AttributeBagItem {
ResValueBagItem item=getBagItem();
return item.getIdHigh()==0x0100;
}
public boolean contains(AttributeValueType valueType){
if(valueType == null || getItemType()!=AttributeItemType.FORMAT){
return false;
}
int value = 0xff & valueType.getByte();
int dataLow = 0xffff & getBagItem().getDataLow();
return (dataLow & value) == value;
}
public AttributeValueType[] getValueTypes(){
AttributeItemType type=getItemType();
if(type!=AttributeItemType.FORMAT){