fix multi-package xml compiling #40

This commit is contained in:
REAndroid 2023-05-09 18:03:02 +02:00
parent e136a8d570
commit ada5aaf17d
6 changed files with 497 additions and 341 deletions

View File

@ -18,8 +18,11 @@ package com.reandroid.apk;
import com.reandroid.archive.APKArchive; import com.reandroid.archive.APKArchive;
import com.reandroid.archive.FileInputSource; import com.reandroid.archive.FileInputSource;
import com.reandroid.apk.xmlencoder.RESEncoder; import com.reandroid.apk.xmlencoder.RESEncoder;
import com.reandroid.archive.InputSource;
import com.reandroid.archive.InputSourceUtil;
import com.reandroid.archive2.block.ApkSignatureBlock; import com.reandroid.archive2.block.ApkSignatureBlock;
import com.reandroid.arsc.chunk.TableBlock; import com.reandroid.arsc.chunk.TableBlock;
import com.reandroid.arsc.chunk.xml.AndroidManifestBlock;
import com.reandroid.json.JSONArray; import com.reandroid.json.JSONArray;
import com.reandroid.xml.XMLException; import com.reandroid.xml.XMLException;
@ -43,6 +46,7 @@ public class ApkModuleXmlEncoder {
scanRootDir(rootDir); scanRootDir(rootDir);
restorePathMap(mainDirectory); restorePathMap(mainDirectory);
restoreSignatures(mainDirectory); restoreSignatures(mainDirectory);
sortFiles();
} }
private void restoreSignatures(File dir) throws IOException { private void restoreSignatures(File dir) throws IOException {
File sigDir = new File(dir, ApkUtil.SIGNATURE_DIR_NAME); File sigDir = new File(dir, ApkUtil.SIGNATURE_DIR_NAME);
@ -79,6 +83,24 @@ public class ApkModuleXmlEncoder {
archive.add(inputSource); archive.add(inputSource);
} }
} }
private void sortFiles(){
APKArchive archive=getApkModule().getApkArchive();
int i = 1;
for(InputSource inputSource:archive.listInputSources()){
if(inputSource.getSort() == 0){
inputSource.setSort(i);
i++;
}
}
InputSource manifest = archive.getInputSource(AndroidManifestBlock.FILE_NAME);
if(manifest != null){
manifest.setSort(0);
}
List<InputSource> sourceList = archive.listInputSources();
InputSourceUtil.sort(sourceList);
archive.clear();
archive.addAll(sourceList);
}
private void loadUncompressedFiles(File mainDirectory) throws IOException, XMLException { private void loadUncompressedFiles(File mainDirectory) throws IOException, XMLException {
File file=new File(mainDirectory, UncompressedFiles.JSON_FILE); File file=new File(mainDirectory, UncompressedFiles.JSON_FILE);
UncompressedFiles uncompressedFiles = getApkModule().getUncompressedFiles(); UncompressedFiles uncompressedFiles = getApkModule().getUncompressedFiles();

View File

@ -29,7 +29,6 @@ import com.reandroid.arsc.util.FrameworkTable;
import com.reandroid.arsc.util.HexUtil; import com.reandroid.arsc.util.HexUtil;
import com.reandroid.arsc.util.ResNameMap; import com.reandroid.arsc.util.ResNameMap;
import com.reandroid.arsc.value.Entry; import com.reandroid.arsc.value.Entry;
import com.reandroid.common.Frameworks;
import java.util.HashSet; import java.util.HashSet;
import java.util.Set; import java.util.Set;
@ -38,6 +37,7 @@ import java.util.regex.Matcher;
public class EncodeMaterials { public class EncodeMaterials {
private final Set<ResourceIds.Table.Package> packageIdSet = new HashSet<>(); private final Set<ResourceIds.Table.Package> packageIdSet = new HashSet<>();
private PackageBlock currentPackage; private PackageBlock currentPackage;
private ResourceIds.Table.Package currentLocalPackage;
private final Set<FrameworkTable> frameworkTables = new HashSet<>(); private final Set<FrameworkTable> frameworkTables = new HashSet<>();
private APKLogger apkLogger; private APKLogger apkLogger;
private boolean mForceCreateNamespaces = true; private boolean mForceCreateNamespaces = true;
@ -99,6 +99,10 @@ public class EncodeMaterials {
} }
String type = matcher.group(4); String type = matcher.group(4);
String name = matcher.group(5); String name = matcher.group(5);
if(isLocalPackageName(packageName)){
return resolveLocalResourceId(packageName, type, name);
}
if(EncodeUtil.isEmpty(packageName) if(EncodeUtil.isEmpty(packageName)
|| packageName.equals(getCurrentPackageName()) || packageName.equals(getCurrentPackageName())
|| !isFrameworkPackageName(packageName)){ || !isFrameworkPackageName(packageName)){
@ -106,12 +110,35 @@ public class EncodeMaterials {
} }
return resolveFrameworkResourceId(packageName, type, name); return resolveFrameworkResourceId(packageName, type, name);
} }
private int resolveLocalResourceId(String packageName, String type, String name){
ResourceIds.Table.Package pkg = getLocalPackage(packageName);
Integer resourceId = pkg.getResourceId(type, name);
if(resourceId != null){
return resourceId;
}
EntryGroup entryGroup=getLocalEntryGroup(type, name);
if(entryGroup!=null){
return entryGroup.getResourceId();
}
throw new EncodeException("Local entry not found: " +
"package=" + packageName +
", type=" + type +
", name=" + name);
}
public int resolveLocalResourceId(String type, String name){ public int resolveLocalResourceId(String type, String name){
for(ResourceIds.Table.Package pkg:packageIdSet){ ResourceIds.Table.Package current = this.currentLocalPackage;
Integer resId = pkg.getResourceId(type, name); if(current != null){
if(resId!=null){ Integer resId = current.getResourceId(type, name);
if(resId != null){
return resId; return resId;
} }
}else {
for(ResourceIds.Table.Package pkg:packageIdSet){
Integer resId = pkg.getResourceId(type, name);
if(resId!=null){
return resId;
}
}
} }
EntryGroup entryGroup=getLocalEntryGroup(type, name); EntryGroup entryGroup=getLocalEntryGroup(type, name);
if(entryGroup!=null){ if(entryGroup!=null){
@ -286,8 +313,73 @@ public class EncodeMaterials {
} }
public EncodeMaterials setCurrentPackage(PackageBlock currentPackage) { public EncodeMaterials setCurrentPackage(PackageBlock currentPackage) {
this.currentPackage = currentPackage; this.currentPackage = currentPackage;
onCurrentPackageChanged(currentPackage);
return this; return this;
} }
public EncodeMaterials setCurrentLocalPackage(ResourceIds.Table.Package currentLocalPackage) {
this.currentLocalPackage = currentLocalPackage;
return this;
}
private void onCurrentPackageChanged(PackageBlock currentPackage){
if(currentPackage == null){
return;
}
ResourceIds.Table.Package current = null;
if(isUniquePackageIds()){
current = getLocalPackage(currentPackage.getId());
}
if(current == null && isUniquePackageNames()){
current = getLocalPackage(currentPackage.getName());
}
if(current != null){
this.currentLocalPackage = current;
}
}
private ResourceIds.Table.Package getLocalPackage(int packageId){
byte id = (byte) packageId;
for(ResourceIds.Table.Package pkg : packageIdSet){
if(id == pkg.id){
return pkg;
}
}
return null;
}
private ResourceIds.Table.Package getLocalPackage(String name){
if(name == null){
return null;
}
for(ResourceIds.Table.Package pkg : packageIdSet){
if(name.equals(pkg.name)){
return pkg;
}
}
return null;
}
private boolean isLocalPackageName(String packageName){
if(packageName == null){
return false;
}
for(ResourceIds.Table.Package pkg : packageIdSet){
if(packageName.equals(pkg.name)){
return true;
}
}
return false;
}
private boolean isUniquePackageNames(){
Set<String> names = new HashSet<>();
for(ResourceIds.Table.Package pkg : packageIdSet){
names.add(pkg.name);
}
return names.size() == packageIdSet.size();
}
private boolean isUniquePackageIds(){
Set<Byte> ids = new HashSet<>();
for(ResourceIds.Table.Package pkg : packageIdSet){
ids.add(pkg.id);
}
return ids.size() == packageIdSet.size();
}
public EncodeMaterials addFramework(FrameworkApk frameworkApk) { public EncodeMaterials addFramework(FrameworkApk frameworkApk) {
if(frameworkApk!=null){ if(frameworkApk!=null){
addFramework(frameworkApk.getTableBlock()); addFramework(frameworkApk.getTableBlock());

View File

@ -1,35 +1,35 @@
/* /*
* Copyright (C) 2022 github.com/REAndroid * Copyright (C) 2022 github.com/REAndroid
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
* You may obtain a copy of the License at * You may obtain a copy of the License at
* *
* http://www.apache.org/licenses/LICENSE-2.0 * http://www.apache.org/licenses/LICENSE-2.0
* *
* Unless required by applicable law or agreed to in writing, software * Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, * distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package com.reandroid.apk.xmlencoder; package com.reandroid.apk.xmlencoder;
import com.reandroid.archive.APKArchive; import com.reandroid.archive.APKArchive;
import com.reandroid.archive.FileInputSource; import com.reandroid.archive.FileInputSource;
import com.reandroid.archive.InputSource; import com.reandroid.archive.InputSource;
import com.reandroid.apk.ApkUtil; import com.reandroid.apk.ApkUtil;
import com.reandroid.apk.UncompressedFiles; import com.reandroid.apk.UncompressedFiles;
import com.reandroid.arsc.chunk.PackageBlock; import com.reandroid.arsc.chunk.PackageBlock;
import com.reandroid.arsc.chunk.TypeBlock; import com.reandroid.arsc.chunk.TypeBlock;
import com.reandroid.arsc.value.Entry; import com.reandroid.arsc.value.Entry;
import com.reandroid.xml.source.XMLFileSource; import com.reandroid.xml.source.XMLFileSource;
import com.reandroid.xml.source.XMLSource; import com.reandroid.xml.source.XMLSource;
import java.io.File; import java.io.File;
import java.util.List; import java.util.List;
public class FilePathEncoder { public class FilePathEncoder {
private final EncodeMaterials materials; private final EncodeMaterials materials;
private APKArchive apkArchive; private APKArchive apkArchive;
private UncompressedFiles uncompressedFiles; private UncompressedFiles uncompressedFiles;
@ -78,6 +78,9 @@ package com.reandroid.apk.xmlencoder;
entry.setValueAsString(path); entry.setValueAsString(path);
entry.setSpecReference(materials.getSpecString(name)); entry.setSpecReference(materials.getSpecString(name));
InputSource inputSource=createInputSource(path, resFile); InputSource inputSource=createInputSource(path, resFile);
if(inputSource instanceof XMLEncodeSource){
((XMLEncodeSource)inputSource).setEntry(entry);
}
addInputSource(inputSource); addInputSource(inputSource);
return inputSource; return inputSource;
} }

View File

@ -1,18 +1,18 @@
/* /*
* Copyright (C) 2022 github.com/REAndroid * Copyright (C) 2022 github.com/REAndroid
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
* You may obtain a copy of the License at * You may obtain a copy of the License at
* *
* http://www.apache.org/licenses/LICENSE-2.0 * http://www.apache.org/licenses/LICENSE-2.0
* *
* Unless required by applicable law or agreed to in writing, software * Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, * distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package com.reandroid.apk.xmlencoder; package com.reandroid.apk.xmlencoder;
import com.reandroid.apk.APKLogger; import com.reandroid.apk.APKLogger;
@ -45,14 +45,13 @@ class PackageCreator {
} }
public PackageBlock createNew(TableBlock tableBlock, ResourceIds.Table.Package pkgResourceIds){ public PackageBlock createNew(TableBlock tableBlock, ResourceIds.Table.Package pkgResourceIds){
loadNames(pkgResourceIds); loadNames(pkgResourceIds);
PackageBlock packageBlock=new PackageBlock(); PackageBlock packageBlock = tableBlock.getPackageArray().createNext();
packageBlock.setName(mPackageName); packageBlock.setName(mPackageName);
packageBlock.setId(mPackageId); packageBlock.setId(mPackageId);
loadPackageInfoJson(packageBlock); loadPackageInfoJson(packageBlock);
this.mPackageName = packageBlock.getName();
tableBlock.getPackageArray()
.add(packageBlock);
packageBlock.getSpecStringPool() packageBlock.getSpecStringPool()
.addStrings(mSpecNames); .addStrings(mSpecNames);

View File

@ -1,306 +1,315 @@
/* /*
* Copyright (C) 2022 github.com/REAndroid * Copyright (C) 2022 github.com/REAndroid
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
* You may obtain a copy of the License at * You may obtain a copy of the License at
* *
* http://www.apache.org/licenses/LICENSE-2.0 * http://www.apache.org/licenses/LICENSE-2.0
* *
* Unless required by applicable law or agreed to in writing, software * Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, * distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package com.reandroid.apk.xmlencoder; package com.reandroid.apk.xmlencoder;
import com.reandroid.apk.*; import com.reandroid.apk.*;
import com.reandroid.archive.APKArchive; import com.reandroid.archive.APKArchive;
import com.reandroid.arsc.chunk.PackageBlock; import com.reandroid.arsc.chunk.PackageBlock;
import com.reandroid.arsc.chunk.TableBlock; import com.reandroid.arsc.chunk.TableBlock;
import com.reandroid.arsc.chunk.xml.AndroidManifestBlock; import com.reandroid.arsc.chunk.xml.AndroidManifestBlock;
import com.reandroid.xml.XMLDocument; import com.reandroid.xml.XMLDocument;
import com.reandroid.xml.XMLException; import com.reandroid.xml.XMLException;
import com.reandroid.xml.source.XMLFileSource; import com.reandroid.xml.source.XMLFileSource;
import com.reandroid.xml.source.XMLSource; import com.reandroid.xml.source.XMLSource;
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.util.*; import java.util.*;
import java.util.zip.ZipEntry; import java.util.zip.ZipEntry;
public class RESEncoder { public class RESEncoder {
private APKLogger apkLogger; private APKLogger apkLogger;
private final TableBlock tableBlock; private final TableBlock tableBlock;
private final Set<File> parsedFiles = new HashSet<>(); private final Set<File> parsedFiles = new HashSet<>();
private final ApkModule apkModule; private final ApkModule apkModule;
public RESEncoder(){ public RESEncoder(){
this(new ApkModule("encoded", this(new ApkModule("encoded",
new APKArchive()), new TableBlock()); new APKArchive()), new TableBlock());
} }
public RESEncoder(ApkModule module, TableBlock block){ public RESEncoder(ApkModule module, TableBlock block){
this.apkModule = module; this.apkModule = module;
this.tableBlock = block; this.tableBlock = block;
if(!module.hasTableBlock()){ if(!module.hasTableBlock()){
BlockInputSource<TableBlock> inputSource= BlockInputSource<TableBlock> inputSource=
new BlockInputSource<>(TableBlock.FILE_NAME, block); new BlockInputSource<>(TableBlock.FILE_NAME, block);
inputSource.setMethod(ZipEntry.STORED); inputSource.setMethod(ZipEntry.STORED);
this.apkModule.getUncompressedFiles().addPath(inputSource); this.apkModule.getUncompressedFiles().addPath(inputSource);
this.apkModule.getApkArchive().add(inputSource); this.apkModule.getApkArchive().add(inputSource);
} }
} }
public TableBlock getTableBlock(){ public TableBlock getTableBlock(){
return tableBlock; return tableBlock;
} }
public ApkModule getApkModule(){ public ApkModule getApkModule(){
return apkModule; return apkModule;
} }
public void scanDirectory(File mainDir) throws IOException, XMLException { public void scanDirectory(File mainDir) throws IOException, XMLException {
scanResourceFiles(mainDir); scanResourceFiles(mainDir);
} }
private void scanResourceFiles(File mainDir) throws IOException, XMLException { private void scanResourceFiles(File mainDir) throws IOException, XMLException {
List<File> pubXmlFileList = searchPublicXmlFiles(mainDir); List<File> pubXmlFileList = searchPublicXmlFiles(mainDir);
if(pubXmlFileList.size()==0){ if(pubXmlFileList.size()==0){
throw new IOException("No .*/values/" throw new IOException("No .*/values/"
+ ApkUtil.FILE_NAME_PUBLIC_XML+" file found in '"+mainDir); + ApkUtil.FILE_NAME_PUBLIC_XML+" file found in '"+mainDir);
} }
preloadStringPool(pubXmlFileList); preloadStringPool(pubXmlFileList);
EncodeMaterials encodeMaterials=new EncodeMaterials(); EncodeMaterials encodeMaterials=new EncodeMaterials();
Map<File, ResourceIds.Table.Package> map = Map<File, ResourceIds.Table.Package> map =
initializeEncodeMaterials(pubXmlFileList, encodeMaterials); initializeEncodeMaterials(pubXmlFileList, encodeMaterials);
Map<File, PackageBlock> packageBlockMap=new HashMap<>(); Map<File, PackageBlock> packageBlockMap=new HashMap<>();
for(File pubXmlFile:pubXmlFileList){ for(File pubXmlFile:pubXmlFileList){
ResourceIds.Table.Package pkgResourceIds=map.get(pubXmlFile); ResourceIds.Table.Package pkgResourceIds = map.get(pubXmlFile);
if(pkgResourceIds==null){ if(pkgResourceIds==null){
continue; continue;
} }
addParsedFiles(pubXmlFile); addParsedFiles(pubXmlFile);
PackageBlock packageBlock = createPackage(pkgResourceIds, pubXmlFile); PackageBlock packageBlock = createPackage(pkgResourceIds, pubXmlFile);
encodeMaterials.setCurrentPackage(packageBlock); encodeMaterials.setCurrentPackage(packageBlock);
packageBlockMap.put(pubXmlFile, packageBlock); encodeMaterials.setCurrentLocalPackage(pkgResourceIds);
packageBlockMap.put(pubXmlFile, packageBlock);
ValuesEncoder valuesEncoder = new ValuesEncoder(encodeMaterials); ValuesEncoder valuesEncoder = new ValuesEncoder(encodeMaterials);
File fileIds = toId(pubXmlFile); File fileIds = toId(pubXmlFile);
if(fileIds.isFile()){ if(fileIds.isFile()){
valuesEncoder.encodeValuesXml(fileIds); valuesEncoder.encodeValuesXml(fileIds);
packageBlock.sortTypes(); packageBlock.sortTypes();
packageBlock.refresh(); packageBlock.refresh();
addParsedFiles(fileIds); addParsedFiles(fileIds);
} }
File fileAttrs = toAttr(pubXmlFile); File fileAttrs = toAttr(pubXmlFile);
if(fileAttrs.isFile()){ if(fileAttrs.isFile()){
valuesEncoder.encodeValuesXml(fileAttrs); valuesEncoder.encodeValuesXml(fileAttrs);
packageBlock.sortTypes(); packageBlock.sortTypes();
packageBlock.refresh(); packageBlock.refresh();
addParsedFiles(fileAttrs); addParsedFiles(fileAttrs);
} }
} }
for(File pubXmlFile:pubXmlFileList){ File manifestFile = null;
ResourceIds.Table.Package pkgResourceIds=map.get(pubXmlFile); for(File pubXmlFile:pubXmlFileList){
if(pkgResourceIds==null){ ResourceIds.Table.Package pkgResourceIds = map.get(pubXmlFile);
continue; if(pkgResourceIds==null){
} continue;
addParsedFiles(pubXmlFile); }
addParsedFiles(pubXmlFile);
if(manifestFile == null){
manifestFile = toAndroidManifest(pubXmlFile);
}
PackageBlock packageBlock=packageBlockMap.get(pubXmlFile); PackageBlock packageBlock=packageBlockMap.get(pubXmlFile);
if(packageBlock==null){ if(packageBlock==null){
packageBlock = createPackage(pkgResourceIds, pubXmlFile); packageBlock = createPackage(pkgResourceIds, pubXmlFile);
} }
encodeMaterials.setCurrentPackage(packageBlock); encodeMaterials.setCurrentPackage(packageBlock);
encodeMaterials.setCurrentLocalPackage(pkgResourceIds);
File resDir=toResDirectory(pubXmlFile); File resDir=toResDirectory(pubXmlFile);
encodeResDir(encodeMaterials, resDir); encodeResDir(encodeMaterials, resDir);
FilePathEncoder filePathEncoder = new FilePathEncoder(encodeMaterials); FilePathEncoder filePathEncoder = new FilePathEncoder(encodeMaterials);
filePathEncoder.setApkArchive(getApkModule().getApkArchive()); filePathEncoder.setApkArchive(getApkModule().getApkArchive());
filePathEncoder.setUncompressedFiles(getApkModule().getUncompressedFiles()); filePathEncoder.setUncompressedFiles(getApkModule().getUncompressedFiles());
filePathEncoder.encodeResDir(resDir); filePathEncoder.encodeResDir(resDir);
packageBlock.sortTypes(); packageBlock.sortTypes();
packageBlock.refresh(); packageBlock.refresh();
}
tableBlock.refresh();
PackageBlock packageBlock = tableBlock.pickOne();
if(manifestFile != null){
if(packageBlock != null){
encodeMaterials.setCurrentPackage(packageBlock);
}
XMLSource xmlSource =
new XMLFileSource(AndroidManifestBlock.FILE_NAME, manifestFile);
XMLEncodeSource xmlEncodeSource =
new XMLEncodeSource(encodeMaterials, xmlSource);
getApkModule().getApkArchive().add(xmlEncodeSource);
}
}
private PackageBlock createPackage(ResourceIds.Table.Package pkgResourceIds
, File pubXmlFile){
PackageCreator packageCreator = new PackageCreator();
packageCreator.setPackageName(pkgResourceIds.name);
packageCreator.setAPKLogger(apkLogger);
packageCreator.setPackageDirectory(toPackageDirectory(pubXmlFile));
return packageCreator.createNew(this.tableBlock, pkgResourceIds);
}
private void preloadStringPool(List<File> pubXmlFileList){
logMessage("Loading string pool ...");
ValuesStringPoolBuilder poolBuilder=new ValuesStringPoolBuilder();
for(File pubXml:pubXmlFileList){
File resDir=toResDirectory(pubXml);
List<File> valuesDirList = listValuesDir(resDir);
for(File dir:valuesDirList){
logVerbose(poolBuilder.size()+" building pool: "+dir.getName());
poolBuilder.scanValuesDirectory(dir);
}
}
poolBuilder.addTo(tableBlock.getTableStringPool());
}
File manifestFile=toAndroidManifest(pubXmlFile); private Map<File, ResourceIds.Table.Package> initializeEncodeMaterials(
XMLSource xmlSource = List<File> pubXmlFileList, EncodeMaterials encodeMaterials)
new XMLFileSource(AndroidManifestBlock.FILE_NAME, manifestFile); throws IOException, XMLException {
XMLEncodeSource xmlEncodeSource =
new XMLEncodeSource(encodeMaterials, xmlSource);
getApkModule().getApkArchive().add(xmlEncodeSource); encodeMaterials.setAPKLogger(apkLogger);
}
tableBlock.refresh();
}
private PackageBlock createPackage(ResourceIds.Table.Package pkgResourceIds
, File pubXmlFile){
PackageCreator packageCreator = new PackageCreator();
packageCreator.setPackageName(pkgResourceIds.name);
packageCreator.setAPKLogger(apkLogger);
packageCreator.setPackageDirectory(toPackageDirectory(pubXmlFile));
return packageCreator.createNew(this.tableBlock, pkgResourceIds);
}
private void preloadStringPool(List<File> pubXmlFileList){
logMessage("Loading string pool ...");
ValuesStringPoolBuilder poolBuilder=new ValuesStringPoolBuilder();
for(File pubXml:pubXmlFileList){
File resDir=toResDirectory(pubXml);
List<File> valuesDirList = listValuesDir(resDir);
for(File dir:valuesDirList){
logVerbose(poolBuilder.size()+" building pool: "+dir.getName());
poolBuilder.scanValuesDirectory(dir);
}
}
poolBuilder.addTo(tableBlock.getTableStringPool());
}
private Map<File, ResourceIds.Table.Package> initializeEncodeMaterials( Map<File, ResourceIds.Table.Package> results = new HashMap<>();
List<File> pubXmlFileList, EncodeMaterials encodeMaterials)
throws IOException, XMLException {
encodeMaterials.setAPKLogger(apkLogger); String packageName=null;
for(File pubXmlFile:pubXmlFileList){
if(packageName==null){
File manifestFile = toAndroidManifest(pubXmlFile);
packageName=readManifestPackageName(manifestFile);
FrameworkApk frameworkApk = getApkModule()
.initializeAndroidFramework(XMLDocument.load(manifestFile));
encodeMaterials.addFramework(frameworkApk);
}
ResourceIds resourceIds=new ResourceIds();
resourceIds.fromXml(pubXmlFile);
List<ResourceIds.Table.Package> pkgList = resourceIds.getTable()
.listPackages();
if(pkgList.size()==0){
continue;
}
ResourceIds.Table.Package pkg = pkgList.get(0);
if(pkg.name==null){
pkg.name=packageName;
}
encodeMaterials.addPackageIds(pkg);
results.put(pubXmlFile, pkg);
}
Map<File, ResourceIds.Table.Package> results = new HashMap<>(); encodeMaterials.setAPKLogger(apkLogger);
return results;
}
private String readManifestPackageName(File manifestFile) throws XMLException {
XMLDocument manifestDocument = XMLDocument.load(manifestFile);
return manifestDocument
.getDocumentElement().getAttributeValue("package");
}
private void encodeResDir(EncodeMaterials materials, File resDir) throws XMLException {
String packageName=null; List<File> valuesDirList = listValuesDir(resDir);
for(File pubXmlFile:pubXmlFileList){ for(File valuesDir:valuesDirList){
if(packageName==null){ encodeValuesDir(materials, valuesDir);
File manifestFile = toAndroidManifest(pubXmlFile); }
packageName=readManifestPackageName(manifestFile); }
FrameworkApk frameworkApk = getApkModule() private void encodeValuesDir(EncodeMaterials materials, File valuesDir) throws XMLException {
.initializeAndroidFramework(XMLDocument.load(manifestFile)); ValuesEncoder valuesEncoder = new ValuesEncoder(materials);
encodeMaterials.addFramework(frameworkApk); List<File> xmlFiles = ApkUtil.listFiles(valuesDir, ".xml");
} EncodeUtil.sortValuesXml(xmlFiles);
ResourceIds resourceIds=new ResourceIds(); for(File file:xmlFiles){
resourceIds.fromXml(pubXmlFile); if(isAlreadyParsed(file)){
List<ResourceIds.Table.Package> pkgList = resourceIds.getTable() continue;
.listPackages(); }
if(pkgList.size()==0){ addParsedFiles(file);
continue; valuesEncoder.encodeValuesXml(file);
} }
ResourceIds.Table.Package pkg = pkgList.get(0); }
if(pkg.name==null){ private File toAndroidManifest(File pubXmlFile){
pkg.name=packageName; File resDirectory = toResDirectory(pubXmlFile);
} File packageDirectory = resDirectory.getParentFile();
encodeMaterials.addPackageIds(pkg); File root = packageDirectory.getParentFile();
results.put(pubXmlFile, pkg); return new File(root, AndroidManifestBlock.FILE_NAME);
} }
private File toPackageDirectory(File pubXmlFile){
return toResDirectory(pubXmlFile)
.getParentFile();
}
private File toResDirectory(File pubXmlFile){
return pubXmlFile
.getParentFile()
.getParentFile();
}
private File toId(File pubXmlFile){
return new File(pubXmlFile.getParentFile(), "ids.xml");
}
private File toAttr(File pubXmlFile){
return new File(pubXmlFile.getParentFile(), "attrs.xml");
}
private List<File> listValuesDir(File resDir){
List<File> results=new ArrayList<>();
File def=new File(resDir, "values");
results.add(def);
File[] dirList=resDir.listFiles();
if(dirList!=null){
for(File dir:dirList){
if(def.equals(dir) || !dir.isDirectory()){
continue;
}
if(dir.getName().startsWith("values-")){
results.add(dir);
}
}
}
return results;
}
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(dir, ApkUtil.FILE_NAME_PUBLIC_XML));
}
List<File> results = new ArrayList<>();
for(File file:xmlFiles){
if(!EncodeUtil.isPublicXml(file)){
continue;
}
if(toAndroidManifest(file).isFile()){
results.add(file);
}
}
EncodeUtil.sortPublicXml(results);
return results;
}
encodeMaterials.setAPKLogger(apkLogger); private boolean isAlreadyParsed(File file){
return results; return parsedFiles.contains(file);
} }
private String readManifestPackageName(File manifestFile) throws XMLException { private void addParsedFiles(File file){
XMLDocument manifestDocument = XMLDocument.load(manifestFile); parsedFiles.add(file);
return manifestDocument }
.getDocumentElement().getAttributeValue("package"); public void setAPKLogger(APKLogger logger) {
} this.apkLogger = logger;
private void encodeResDir(EncodeMaterials materials, File resDir) throws XMLException { this.apkModule.setAPKLogger(logger);
}
List<File> valuesDirList = listValuesDir(resDir); private void logMessage(String msg) {
for(File valuesDir:valuesDirList){ if(apkLogger!=null){
encodeValuesDir(materials, valuesDir); apkLogger.logMessage(msg);
} }
} }
private void encodeValuesDir(EncodeMaterials materials, File valuesDir) throws XMLException { private void logError(String msg, Throwable tr) {
ValuesEncoder valuesEncoder = new ValuesEncoder(materials); if(apkLogger!=null){
List<File> xmlFiles = ApkUtil.listFiles(valuesDir, ".xml"); apkLogger.logError(msg, tr);
EncodeUtil.sortValuesXml(xmlFiles); }
for(File file:xmlFiles){ }
if(isAlreadyParsed(file)){ private void logVerbose(String msg) {
continue; if(apkLogger!=null){
} apkLogger.logVerbose(msg);
addParsedFiles(file); }
valuesEncoder.encodeValuesXml(file); }
} }
}
private File toAndroidManifest(File pubXmlFile){
File resDirectory = toResDirectory(pubXmlFile);
File packageDirectory = resDirectory.getParentFile();
File root = packageDirectory.getParentFile();
return new File(root, AndroidManifestBlock.FILE_NAME);
}
private File toPackageDirectory(File pubXmlFile){
return toResDirectory(pubXmlFile)
.getParentFile();
}
private File toResDirectory(File pubXmlFile){
return pubXmlFile
.getParentFile()
.getParentFile();
}
private File toId(File pubXmlFile){
return new File(pubXmlFile.getParentFile(), "ids.xml");
}
private File toAttr(File pubXmlFile){
return new File(pubXmlFile.getParentFile(), "attrs.xml");
}
private List<File> listValuesDir(File resDir){
List<File> results=new ArrayList<>();
File def=new File(resDir, "values");
results.add(def);
File[] dirList=resDir.listFiles();
if(dirList!=null){
for(File dir:dirList){
if(def.equals(dir) || !dir.isDirectory()){
continue;
}
if(dir.getName().startsWith("values-")){
results.add(dir);
}
}
}
return results;
}
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(dir, ApkUtil.FILE_NAME_PUBLIC_XML));
}
List<File> results = new ArrayList<>();
for(File file:xmlFiles){
if(!EncodeUtil.isPublicXml(file)){
continue;
}
if(toAndroidManifest(file).isFile()){
results.add(file);
}
}
EncodeUtil.sortPublicXml(results);
return results;
}
private boolean isAlreadyParsed(File file){
return parsedFiles.contains(file);
}
private void addParsedFiles(File file){
parsedFiles.add(file);
}
public void setAPKLogger(APKLogger logger) {
this.apkLogger = logger;
this.apkModule.setAPKLogger(logger);
}
private void logMessage(String msg) {
if(apkLogger!=null){
apkLogger.logMessage(msg);
}
}
private void logError(String msg, Throwable tr) {
if(apkLogger!=null){
apkLogger.logError(msg, tr);
}
}
private void logVerbose(String msg) {
if(apkLogger!=null){
apkLogger.logVerbose(msg);
}
}
}

View File

@ -1,4 +1,4 @@
/* /*
* Copyright (C) 2022 github.com/REAndroid * Copyright (C) 2022 github.com/REAndroid
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
@ -17,7 +17,9 @@ package com.reandroid.apk.xmlencoder;
import com.reandroid.archive.ByteInputSource; import com.reandroid.archive.ByteInputSource;
import com.reandroid.apk.CrcOutputStream; import com.reandroid.apk.CrcOutputStream;
import com.reandroid.arsc.chunk.PackageBlock;
import com.reandroid.arsc.chunk.xml.ResXmlDocument; import com.reandroid.arsc.chunk.xml.ResXmlDocument;
import com.reandroid.arsc.value.Entry;
import com.reandroid.xml.XMLException; import com.reandroid.xml.XMLException;
import com.reandroid.xml.source.XMLSource; import com.reandroid.xml.source.XMLSource;
@ -28,11 +30,27 @@ public class XMLEncodeSource extends ByteInputSource {
private final EncodeMaterials encodeMaterials; private final EncodeMaterials encodeMaterials;
private final XMLSource xmlSource; private final XMLSource xmlSource;
private ResXmlDocument resXmlDocument; private ResXmlDocument resXmlDocument;
public XMLEncodeSource(EncodeMaterials encodeMaterials, XMLSource xmlSource){ private Entry mEntry;
public XMLEncodeSource(EncodeMaterials encodeMaterials, XMLSource xmlSource, Entry entry){
super(new byte[0], xmlSource.getPath()); super(new byte[0], xmlSource.getPath());
this.encodeMaterials=encodeMaterials; this.encodeMaterials = encodeMaterials;
this.xmlSource=xmlSource; this.xmlSource = xmlSource;
this.mEntry = entry;
} }
public XMLEncodeSource(EncodeMaterials encodeMaterials, XMLSource xmlSource){
this(encodeMaterials, xmlSource, null);
}
public XMLSource getXmlSource() {
return xmlSource;
}
public Entry getEntry(){
return mEntry;
}
public void setEntry(Entry entry) {
this.mEntry = entry;
}
@Override @Override
public long getLength() throws IOException{ public long getLength() throws IOException{
return getResXmlBlock().countBytes(); return getResXmlBlock().countBytes();
@ -64,7 +82,13 @@ public class XMLEncodeSource extends ByteInputSource {
try { try {
XMLFileEncoder xmlFileEncoder=new XMLFileEncoder(encodeMaterials); XMLFileEncoder xmlFileEncoder=new XMLFileEncoder(encodeMaterials);
xmlFileEncoder.setCurrentPath(xmlSource.getPath()); xmlFileEncoder.setCurrentPath(xmlSource.getPath());
encodeMaterials.logVerbose("Encoding xml: "+xmlSource.getPath()); EncodeMaterials encodeMaterials = this.encodeMaterials;
encodeMaterials.logVerbose("Encoding xml: " + xmlSource.getPath());
PackageBlock currentPackage = encodeMaterials.getCurrentPackage();
PackageBlock packageBlock = getEntryPackageBlock();
if(packageBlock != null && packageBlock != currentPackage){
encodeMaterials.setCurrentPackage(packageBlock);
}
resXmlDocument = xmlFileEncoder.encode(xmlSource.getXMLDocument()); resXmlDocument = xmlFileEncoder.encode(xmlSource.getXMLDocument());
} catch (XMLException ex) { } catch (XMLException ex) {
throw new EncodeException("XMLException on: '"+xmlSource.getPath() throw new EncodeException("XMLException on: '"+xmlSource.getPath()
@ -72,6 +96,13 @@ public class XMLEncodeSource extends ByteInputSource {
} }
return resXmlDocument; return resXmlDocument;
} }
private PackageBlock getEntryPackageBlock(){
Entry entry = getEntry();
if(entry != null){
return entry.getPackageBlock();
}
return null;
}
@Override @Override
public void disposeInputSource(){ public void disposeInputSource(){
this.xmlSource.disposeXml(); this.xmlSource.disposeXml();