mirror of
https://github.com/revanced/ARSCLib.git
synced 2025-04-29 22:04:25 +02:00
pull parser style xml values decoder #18
This commit is contained in:
parent
fea0583f61
commit
c7c6863bbd
110
src/main/java/com/reandroid/apk/ApkDecoder.java
Normal file
110
src/main/java/com/reandroid/apk/ApkDecoder.java
Normal file
@ -0,0 +1,110 @@
|
||||
/*
|
||||
* Copyright (C) 2022 github.com/REAndroid
|
||||
*
|
||||
* 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 com.reandroid.apk;
|
||||
|
||||
import com.reandroid.archive.InputSource;
|
||||
import com.reandroid.archive2.block.ApkSignatureBlock;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.Collection;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
public abstract class ApkDecoder {
|
||||
private final Set<String> mDecodedPaths;
|
||||
private APKLogger apkLogger;
|
||||
private boolean mLogErrors;
|
||||
|
||||
public ApkDecoder(){
|
||||
mDecodedPaths = new HashSet<>();
|
||||
}
|
||||
public final void decodeTo(File outDir) throws IOException{
|
||||
reset();
|
||||
onDecodeTo(outDir);
|
||||
}
|
||||
abstract void onDecodeTo(File outDir) throws IOException;
|
||||
|
||||
boolean containsDecodedPath(String path){
|
||||
return mDecodedPaths.contains(path);
|
||||
}
|
||||
void addDecodedPath(String path){
|
||||
mDecodedPaths.add(path);
|
||||
}
|
||||
void writePathMap(File dir, Collection<? extends InputSource> sourceList) throws IOException {
|
||||
PathMap pathMap = new PathMap();
|
||||
pathMap.add(sourceList);
|
||||
File file = new File(dir, PathMap.JSON_FILE);
|
||||
pathMap.toJson().write(file);
|
||||
}
|
||||
void dumpSignatures(File outDir, ApkSignatureBlock signatureBlock) throws IOException {
|
||||
if(signatureBlock == null){
|
||||
return;
|
||||
}
|
||||
logMessage("Dumping signatures ...");
|
||||
File dir = new File(outDir, ApkUtil.SIGNATURE_DIR_NAME);
|
||||
signatureBlock.writeSplitRawToDirectory(dir);
|
||||
}
|
||||
void logOrThrow(String message, IOException exception) throws IOException{
|
||||
if(isLogErrors()){
|
||||
logError(message, exception);
|
||||
return;
|
||||
}
|
||||
if(message == null && exception == null){
|
||||
return;
|
||||
}
|
||||
if(exception == null){
|
||||
exception = new IOException(message);
|
||||
}
|
||||
throw exception;
|
||||
}
|
||||
private void reset(){
|
||||
mDecodedPaths.clear();
|
||||
}
|
||||
|
||||
public boolean isLogErrors() {
|
||||
return mLogErrors;
|
||||
}
|
||||
public void setLogErrors(boolean logErrors) {
|
||||
this.mLogErrors = logErrors;
|
||||
}
|
||||
|
||||
public void setApkLogger(APKLogger apkLogger) {
|
||||
this.apkLogger = apkLogger;
|
||||
}
|
||||
APKLogger getApkLogger() {
|
||||
return apkLogger;
|
||||
}
|
||||
void logMessage(String msg) {
|
||||
APKLogger apkLogger = this.apkLogger;
|
||||
if(apkLogger!=null){
|
||||
apkLogger.logMessage(msg);
|
||||
}
|
||||
}
|
||||
void logError(String msg, Throwable tr) {
|
||||
APKLogger apkLogger = this.apkLogger;
|
||||
if(apkLogger == null || (msg == null && tr == null)){
|
||||
return;
|
||||
}
|
||||
apkLogger.logError(msg, tr);
|
||||
}
|
||||
void logVerbose(String msg) {
|
||||
APKLogger apkLogger = this.apkLogger;
|
||||
if(apkLogger!=null){
|
||||
apkLogger.logVerbose(msg);
|
||||
}
|
||||
}
|
||||
}
|
@ -41,7 +41,7 @@ public class ApkJsonDecoder {
|
||||
this(apkModule, false);
|
||||
}
|
||||
public void sanitizeFilePaths(){
|
||||
PathSanitizer sanitizer = new PathSanitizer(apkModule);
|
||||
PathSanitizer sanitizer = PathSanitizer.create(apkModule);
|
||||
sanitizer.sanitize();
|
||||
}
|
||||
public File writeToDirectory(File dir) throws IOException {
|
||||
|
@ -120,14 +120,7 @@ public class ApkModule implements ApkFile {
|
||||
}
|
||||
return getAndroidManifestBlock().getSplit();
|
||||
}
|
||||
public FrameworkApk initializeAndroidFramework() throws IOException {
|
||||
if(!hasTableBlock()){
|
||||
return null;
|
||||
}
|
||||
Integer version = getAndroidFrameworkVersion();
|
||||
return initializeAndroidFramework(getTableBlock(false), version);
|
||||
}
|
||||
private FrameworkApk initializeAndroidFramework(TableBlock tableBlock, Integer version) throws IOException {
|
||||
public FrameworkApk initializeAndroidFramework(TableBlock tableBlock, Integer version) throws IOException {
|
||||
if(tableBlock == null || isAndroid(tableBlock)){
|
||||
return null;
|
||||
}
|
||||
|
@ -1,115 +1,97 @@
|
||||
/*
|
||||
* Copyright (C) 2022 github.com/REAndroid
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
* Copyright (C) 2022 github.com/REAndroid
|
||||
*
|
||||
* 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 com.reandroid.apk;
|
||||
|
||||
import com.reandroid.apk.xmldecoder.ResXmlDocumentSerializer;
|
||||
import com.reandroid.apk.xmldecoder.*;
|
||||
import com.reandroid.archive.InputSource;
|
||||
import com.reandroid.apk.xmldecoder.XMLBagDecoder;
|
||||
import com.reandroid.apk.xmldecoder.XMLNamespaceValidator;
|
||||
import com.reandroid.archive2.block.ApkSignatureBlock;
|
||||
import com.reandroid.arsc.chunk.PackageBlock;
|
||||
import com.reandroid.arsc.chunk.TableBlock;
|
||||
import com.reandroid.arsc.chunk.TypeBlock;
|
||||
import com.reandroid.arsc.chunk.xml.AndroidManifestBlock;
|
||||
import com.reandroid.arsc.chunk.xml.ResXmlDocument;
|
||||
import com.reandroid.arsc.container.SpecTypePair;
|
||||
import com.reandroid.arsc.decoder.ValueDecoder;
|
||||
import com.reandroid.arsc.value.*;
|
||||
import com.reandroid.common.EntryStore;
|
||||
import com.reandroid.json.JSONObject;
|
||||
import com.reandroid.xml.XMLAttribute;
|
||||
import com.reandroid.xml.XMLDocument;
|
||||
import com.reandroid.xml.XMLElement;
|
||||
import com.reandroid.xml.XMLException;
|
||||
import org.xmlpull.v1.XmlPullParserException;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.*;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
public class ApkModuleXmlDecoder {
|
||||
public class ApkModuleXmlDecoder extends ApkDecoder implements Predicate<Entry> {
|
||||
private final ApkModule apkModule;
|
||||
private final Map<Integer, Set<ResConfig>> decodedEntries;
|
||||
private XMLBagDecoder xmlBagDecoder;
|
||||
private final Set<String> mDecodedPaths;
|
||||
|
||||
private ResXmlDocumentSerializer documentSerializer;
|
||||
private boolean useAndroidSerializer;
|
||||
private XMLEntryDecoderSerializer entrySerializer;
|
||||
|
||||
|
||||
public ApkModuleXmlDecoder(ApkModule apkModule){
|
||||
this.apkModule=apkModule;
|
||||
super();
|
||||
this.apkModule = apkModule;
|
||||
this.decodedEntries = new HashMap<>();
|
||||
this.mDecodedPaths = new HashSet<>();
|
||||
this.useAndroidSerializer = true;
|
||||
}
|
||||
public void setUseAndroidSerializer(boolean useAndroidSerializer) {
|
||||
this.useAndroidSerializer = useAndroidSerializer;
|
||||
super.setApkLogger(apkModule.getApkLogger());
|
||||
}
|
||||
public void sanitizeFilePaths(){
|
||||
sanitizeFilePaths(false);
|
||||
}
|
||||
public void sanitizeFilePaths(boolean sanitizeResourceFiles){
|
||||
PathSanitizer sanitizer = new PathSanitizer(apkModule, sanitizeResourceFiles);
|
||||
PathSanitizer sanitizer = PathSanitizer.create(apkModule);
|
||||
sanitizer.sanitize();
|
||||
}
|
||||
public void decodeTo(File outDir)
|
||||
throws IOException, XMLException {
|
||||
@Override
|
||||
void onDecodeTo(File outDir) throws IOException{
|
||||
this.decodedEntries.clear();
|
||||
logMessage("Decoding ...");
|
||||
|
||||
if(!apkModule.hasTableBlock()){
|
||||
logOrThrow(null, new IOException("Don't have resource table"));
|
||||
return;
|
||||
}
|
||||
|
||||
decodeUncompressedFiles(outDir);
|
||||
apkModule.initializeAndroidFramework();
|
||||
TableBlock tableBlock=apkModule.getTableBlock();
|
||||
|
||||
decodePackageInfo(outDir, tableBlock);
|
||||
TableBlock tableBlock = apkModule.getTableBlock();
|
||||
|
||||
xmlBagDecoder=new XMLBagDecoder(tableBlock);
|
||||
this.entrySerializer = new XMLEntryDecoderSerializer(tableBlock);
|
||||
this.entrySerializer.setDecodedEntries(this);
|
||||
|
||||
decodePublicXml(tableBlock, outDir);
|
||||
|
||||
decodeAndroidManifest(outDir);
|
||||
|
||||
addDecodedPath(TableBlock.FILE_NAME);
|
||||
decodeAndroidManifest(outDir, apkModule.getAndroidManifestBlock());
|
||||
decodeTableBlock(outDir, tableBlock);
|
||||
|
||||
logMessage("Decoding resource files ...");
|
||||
List<ResFile> resFileList=apkModule.listResFiles();
|
||||
List<ResFile> resFileList = apkModule.listResFiles();
|
||||
for(ResFile resFile:resFileList){
|
||||
decodeResFile(outDir, resFile);
|
||||
}
|
||||
decodeValues(tableBlock, outDir, tableBlock);
|
||||
decodeValues(outDir, tableBlock);
|
||||
|
||||
extractRootFiles(outDir);
|
||||
|
||||
writePathMap(outDir);
|
||||
writePathMap(outDir, apkModule.getApkArchive().listInputSources());
|
||||
|
||||
dumpSignatures(outDir);
|
||||
dumpSignatures(outDir, apkModule.getApkSignatureBlock());
|
||||
}
|
||||
private void dumpSignatures(File outDir) throws IOException {
|
||||
ApkSignatureBlock signatureBlock = apkModule.getApkSignatureBlock();
|
||||
if(signatureBlock == null){
|
||||
return;
|
||||
private void decodeTableBlock(File outDir, TableBlock tableBlock) throws IOException {
|
||||
try{
|
||||
decodePackageInfo(outDir, tableBlock);
|
||||
decodePublicXml(tableBlock, outDir);
|
||||
addDecodedPath(TableBlock.FILE_NAME);
|
||||
}catch (IOException exception){
|
||||
logOrThrow("Error decoding resource table", exception);
|
||||
}
|
||||
logMessage("Dumping signatures ...");
|
||||
File dir = new File(outDir, ApkUtil.SIGNATURE_DIR_NAME);
|
||||
signatureBlock.writeSplitRawToDirectory(dir);
|
||||
}
|
||||
private void writePathMap(File dir) throws IOException {
|
||||
PathMap pathMap = new PathMap();
|
||||
pathMap.add(apkModule.getApkArchive());
|
||||
File file = new File(dir, PathMap.JSON_FILE);
|
||||
pathMap.toJson().write(file);
|
||||
}
|
||||
private void decodePackageInfo(File outDir, TableBlock tableBlock) throws IOException {
|
||||
for(PackageBlock packageBlock:tableBlock.listPackages()){
|
||||
@ -123,7 +105,7 @@ import java.util.*;
|
||||
jsonObject.write(packageJsonFile);
|
||||
}
|
||||
private void decodeUncompressedFiles(File outDir)
|
||||
throws IOException {
|
||||
throws IOException {
|
||||
File file=new File(outDir, UncompressedFiles.JSON_FILE);
|
||||
UncompressedFiles uncompressedFiles = apkModule.getUncompressedFiles();
|
||||
uncompressedFiles.toJson().write(file);
|
||||
@ -158,7 +140,7 @@ import java.util.*;
|
||||
addDecodedEntry(entry);
|
||||
}
|
||||
private void decodeResXml(File outDir, ResFile resFile)
|
||||
throws IOException{
|
||||
throws IOException{
|
||||
Entry entry = resFile.pickOne();
|
||||
PackageBlock packageBlock = entry.getPackageBlock();
|
||||
|
||||
@ -181,11 +163,8 @@ import java.util.*;
|
||||
}
|
||||
return documentSerializer;
|
||||
}
|
||||
private TableBlock getTableBlock(){
|
||||
return apkModule.getTableBlock();
|
||||
}
|
||||
private void decodePublicXml(TableBlock tableBlock, File outDir)
|
||||
throws IOException{
|
||||
throws IOException{
|
||||
for(PackageBlock packageBlock:tableBlock.listPackages()){
|
||||
decodePublicXml(packageBlock, outDir);
|
||||
}
|
||||
@ -207,7 +186,7 @@ import java.util.*;
|
||||
xmlDocument.save(pubXml, false);
|
||||
}
|
||||
private void decodePublicXml(PackageBlock packageBlock, File outDir)
|
||||
throws IOException {
|
||||
throws IOException {
|
||||
String packageDirName=getPackageDirName(packageBlock);
|
||||
logMessage("Decoding public.xml: "+packageDirName);
|
||||
File file=new File(outDir, packageDirName);
|
||||
@ -218,15 +197,14 @@ import java.util.*;
|
||||
resourceIds.loadPackageBlock(packageBlock);
|
||||
resourceIds.writeXml(file);
|
||||
}
|
||||
private void decodeAndroidManifest(File outDir)
|
||||
throws IOException {
|
||||
private void decodeAndroidManifest(File outDir, AndroidManifestBlock manifestBlock)
|
||||
throws IOException {
|
||||
if(!apkModule.hasAndroidManifestBlock()){
|
||||
logMessage("Don't have: "+ AndroidManifestBlock.FILE_NAME);
|
||||
return;
|
||||
}
|
||||
File file=new File(outDir, AndroidManifestBlock.FILE_NAME);
|
||||
logMessage("Decoding: "+file.getName());
|
||||
AndroidManifestBlock manifestBlock=apkModule.getAndroidManifestBlock();
|
||||
int currentPackageId = manifestBlock.guessCurrentPackageId();
|
||||
serializeXml(currentPackageId, manifestBlock, file);
|
||||
addDecodedPath(AndroidManifestBlock.FILE_NAME);
|
||||
@ -235,50 +213,28 @@ import java.util.*;
|
||||
throws IOException {
|
||||
XMLNamespaceValidator namespaceValidator = new XMLNamespaceValidator(document);
|
||||
namespaceValidator.validate();
|
||||
if(useAndroidSerializer){
|
||||
ResXmlDocumentSerializer serializer = getDocumentSerializer();
|
||||
if(currentPackageId != 0){
|
||||
serializer.getDecoder().setCurrentPackageId(currentPackageId);
|
||||
}
|
||||
try {
|
||||
serializer.write(document, outFile);
|
||||
} catch (XmlPullParserException ex) {
|
||||
throw new IOException("Error: "+outFile.getName(), ex);
|
||||
}
|
||||
}else {
|
||||
try {
|
||||
XMLDocument xmlDocument = document.decodeToXml(getTableBlock(), currentPackageId);
|
||||
xmlDocument.save(outFile, true);
|
||||
} catch (XMLException ex) {
|
||||
throw new IOException("Error: "+outFile.getName(), ex);
|
||||
}
|
||||
ResXmlDocumentSerializer serializer = getDocumentSerializer();
|
||||
if(currentPackageId != 0){
|
||||
serializer.getDecoder().setCurrentPackageId(currentPackageId);
|
||||
}
|
||||
try {
|
||||
serializer.write(document, outFile);
|
||||
} catch (XmlPullParserException ex) {
|
||||
throw new IOException("Error: "+outFile.getName(), ex);
|
||||
}
|
||||
}
|
||||
private void serializeXml(int currentPackageId, InputSource inputSource, File outFile)
|
||||
throws IOException {
|
||||
|
||||
if(useAndroidSerializer){
|
||||
ResXmlDocumentSerializer serializer = getDocumentSerializer();
|
||||
if(currentPackageId != 0){
|
||||
serializer.getDecoder().setCurrentPackageId(currentPackageId);
|
||||
}
|
||||
try {
|
||||
serializer.write(inputSource, outFile);
|
||||
} catch (XmlPullParserException ex) {
|
||||
throw new IOException("Error: "+outFile.getName(), ex);
|
||||
}
|
||||
}else {
|
||||
try {
|
||||
ResXmlDocument document = apkModule.loadResXmlDocument(inputSource);
|
||||
XMLNamespaceValidator namespaceValidator = new XMLNamespaceValidator(document);
|
||||
namespaceValidator.validate();
|
||||
XMLDocument xmlDocument = document.decodeToXml(getTableBlock(), currentPackageId);
|
||||
xmlDocument.save(outFile, true);
|
||||
} catch (XMLException ex) {
|
||||
throw new IOException("Error: "+outFile.getName(), ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
throws IOException {
|
||||
ResXmlDocumentSerializer serializer = getDocumentSerializer();
|
||||
if(currentPackageId != 0){
|
||||
serializer.getDecoder().setCurrentPackageId(currentPackageId);
|
||||
}
|
||||
try {
|
||||
serializer.write(inputSource, outFile);
|
||||
} catch (XmlPullParserException ex) {
|
||||
throw new IOException("Error: "+outFile.getName(), ex);
|
||||
}
|
||||
}
|
||||
private void addDecodedEntry(Entry entry){
|
||||
if(entry.isNull()){
|
||||
return;
|
||||
@ -298,71 +254,27 @@ import java.util.*;
|
||||
}
|
||||
return resConfigSet.contains(entry.getResConfig());
|
||||
}
|
||||
private void decodeValues(EntryStore entryStore, File outDir, TableBlock tableBlock) throws IOException {
|
||||
private void decodeValues(File outDir, TableBlock tableBlock) throws IOException {
|
||||
for(PackageBlock packageBlock:tableBlock.listPackages()){
|
||||
decodeValues(entryStore, outDir, packageBlock);
|
||||
decodeValues(outDir, packageBlock);
|
||||
}
|
||||
}
|
||||
private void decodeValues(EntryStore entryStore, File outDir, PackageBlock packageBlock) throws IOException {
|
||||
private void decodeValues(File outDir, PackageBlock packageBlock) throws IOException {
|
||||
logMessage("Decoding values: "
|
||||
+packageBlock.getIndex()
|
||||
+"-"+packageBlock.getName());
|
||||
|
||||
packageBlock.sortTypes();
|
||||
|
||||
for(SpecTypePair specTypePair: packageBlock.listSpecTypePairs()){
|
||||
for(TypeBlock typeBlock:specTypePair.listTypeBlocks()){
|
||||
decodeValues(entryStore, outDir, typeBlock);
|
||||
}
|
||||
File pkgDir = new File(outDir, getPackageDirName(packageBlock));
|
||||
File resDir = new File(pkgDir, ApkUtil.RES_DIR_NAME);
|
||||
|
||||
for(SpecTypePair specTypePair : packageBlock.listSpecTypePairs()){
|
||||
decodeValues(resDir, specTypePair);
|
||||
}
|
||||
}
|
||||
private void decodeValues(EntryStore entryStore, File outDir, TypeBlock typeBlock) throws IOException {
|
||||
XMLDocument xmlDocument = new XMLDocument("resources");
|
||||
XMLElement docElement = xmlDocument.getDocumentElement();
|
||||
for(Entry entry :typeBlock.listEntries(true)){
|
||||
if(containsDecodedEntry(entry)){
|
||||
continue;
|
||||
}
|
||||
docElement.addChild(decodeValue(entryStore, entry));
|
||||
}
|
||||
if(docElement.getChildesCount()==0){
|
||||
return;
|
||||
}
|
||||
File file=new File(outDir, getPackageDirName(typeBlock.getPackageBlock()));
|
||||
file=new File(file, ApkUtil.RES_DIR_NAME);
|
||||
file=new File(file, "values"+typeBlock.getQualifiers());
|
||||
String type=typeBlock.getTypeName();
|
||||
if(!type.endsWith("s")){
|
||||
type=type+"s";
|
||||
}
|
||||
file=new File(file, type+".xml");
|
||||
xmlDocument.save(file, false);
|
||||
}
|
||||
private XMLElement decodeValue(EntryStore entryStore, Entry entry){
|
||||
XMLElement element=new XMLElement(XmlHelper.toXMLTagName(entry.getTypeName()));
|
||||
int resourceId= entry.getResourceId();
|
||||
XMLAttribute attribute=new XMLAttribute("name", entry.getName());
|
||||
element.addAttribute(attribute);
|
||||
attribute.setNameId(resourceId);
|
||||
element.setResourceId(resourceId);
|
||||
if(!entry.isComplex()){
|
||||
ResValue resValue =(ResValue) entry.getTableEntry().getValue();
|
||||
if(resValue.getValueType()== ValueType.STRING){
|
||||
XmlHelper.setTextContent(element,
|
||||
resValue.getDataAsPoolString());
|
||||
}else {
|
||||
String value = ValueDecoder.decodeEntryValue(entryStore,
|
||||
entry.getPackageBlock(),
|
||||
resValue.getValueType(),
|
||||
resValue.getData());
|
||||
element.setTextContent(value);
|
||||
}
|
||||
}else {
|
||||
ResTableMapEntry mapEntry = (ResTableMapEntry) entry.getTableEntry();
|
||||
xmlBagDecoder.decode(mapEntry, element);
|
||||
return element;
|
||||
}
|
||||
return element;
|
||||
private void decodeValues(File outDir, SpecTypePair specTypePair) throws IOException {
|
||||
entrySerializer.decode(outDir, specTypePair);
|
||||
}
|
||||
private String getPackageDirName(PackageBlock packageBlock){
|
||||
String name = ApkUtil.sanitizeForFileName(packageBlock.getName());
|
||||
@ -394,29 +306,8 @@ import java.util.*;
|
||||
inputSource.write(outputStream);
|
||||
outputStream.close();
|
||||
}
|
||||
private boolean containsDecodedPath(String path){
|
||||
return mDecodedPaths.contains(path);
|
||||
}
|
||||
private void addDecodedPath(String path){
|
||||
mDecodedPaths.add(path);
|
||||
}
|
||||
|
||||
private void logMessage(String msg) {
|
||||
APKLogger apkLogger=apkModule.getApkLogger();
|
||||
if(apkLogger!=null){
|
||||
apkLogger.logMessage(msg);
|
||||
}
|
||||
}
|
||||
private void logError(String msg, Throwable tr) {
|
||||
APKLogger apkLogger=apkModule.getApkLogger();
|
||||
if(apkLogger!=null){
|
||||
apkLogger.logError(msg, tr);
|
||||
}
|
||||
}
|
||||
private void logVerbose(String msg) {
|
||||
APKLogger apkLogger=apkModule.getApkLogger();
|
||||
if(apkLogger!=null){
|
||||
apkLogger.logVerbose(msg);
|
||||
}
|
||||
@Override
|
||||
public boolean test(Entry entry) {
|
||||
return !containsDecodedEntry(entry);
|
||||
}
|
||||
}
|
||||
|
@ -118,7 +118,7 @@ public class PathMap implements JSONConvert<JSONArray> {
|
||||
}
|
||||
add(archive.listInputSources());
|
||||
}
|
||||
public void add(Collection<InputSource> sources){
|
||||
public void add(Collection<? extends InputSource> sources){
|
||||
if(sources==null){
|
||||
return;
|
||||
}
|
||||
|
@ -1,57 +1,62 @@
|
||||
/*
|
||||
* Copyright (C) 2022 github.com/REAndroid
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
/*
|
||||
* Copyright (C) 2022 github.com/REAndroid
|
||||
*
|
||||
* 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 com.reandroid.apk;
|
||||
|
||||
import com.reandroid.archive.InputSource;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
public class PathSanitizer {
|
||||
private final ApkModule apkModule;
|
||||
private final Collection<? extends InputSource> sourceList;
|
||||
private final boolean sanitizeResourceFiles;
|
||||
private Collection<ResFile> resFileList;
|
||||
private APKLogger apkLogger;
|
||||
private final Set<String> mSanitizedPaths;
|
||||
private final boolean sanitizeResourceFiles;
|
||||
public PathSanitizer(ApkModule apkModule, boolean sanitizeResourceFiles){
|
||||
this.apkModule = apkModule;
|
||||
this.apkLogger = apkModule.getApkLogger();
|
||||
public PathSanitizer(Collection<? extends InputSource> sourceList, boolean sanitizeResourceFiles){
|
||||
this.sourceList = sourceList;
|
||||
this.mSanitizedPaths = new HashSet<>();
|
||||
this.sanitizeResourceFiles = sanitizeResourceFiles;
|
||||
}
|
||||
public PathSanitizer(ApkModule apkModule){
|
||||
this(apkModule, false);
|
||||
public PathSanitizer(Collection<? extends InputSource> sourceList){
|
||||
this(sourceList, false);
|
||||
}
|
||||
public void sanitize(){
|
||||
mSanitizedPaths.clear();
|
||||
logMessage("Sanitizing paths ...");
|
||||
sanitizeResFiles();
|
||||
List<InputSource> sourceList = apkModule.getApkArchive().listInputSources();
|
||||
for(InputSource inputSource:sourceList){
|
||||
sanitize(inputSource, 1, false);
|
||||
}
|
||||
logMessage("DONE = "+mSanitizedPaths.size());
|
||||
}
|
||||
public void setResourceFileList(Collection<ResFile> resFileList){
|
||||
this.resFileList = resFileList;
|
||||
}
|
||||
private void sanitizeResFiles(){
|
||||
Collection<ResFile> resFileList = this.resFileList;
|
||||
if(resFileList == null){
|
||||
return;
|
||||
}
|
||||
boolean sanitizeRes = this.sanitizeResourceFiles;
|
||||
Set<String> sanitizedPaths = this.mSanitizedPaths;
|
||||
if(sanitizeRes){
|
||||
logMessage("Sanitizing resource files ...");
|
||||
}
|
||||
List<ResFile> resFileList = apkModule.listResFiles();
|
||||
for(ResFile resFile:resFileList){
|
||||
if(sanitizeRes){
|
||||
sanitize(resFile);
|
||||
@ -122,13 +127,13 @@ public class PathSanitizer {
|
||||
private String getLogTag(){
|
||||
return "[SANITIZE]: ";
|
||||
}
|
||||
private void logMessage(String msg){
|
||||
void logMessage(String msg){
|
||||
APKLogger logger = this.apkLogger;
|
||||
if(logger!=null){
|
||||
logger.logMessage(getLogTag()+msg);
|
||||
}
|
||||
}
|
||||
private void logVerbose(String msg){
|
||||
void logVerbose(String msg){
|
||||
APKLogger logger = this.apkLogger;
|
||||
if(logger!=null){
|
||||
logger.logVerbose(getLogTag()+msg);
|
||||
@ -201,6 +206,14 @@ public class PathSanitizer {
|
||||
|| (ch >= 'a' && ch <= 'z');
|
||||
}
|
||||
|
||||
public static PathSanitizer create(ApkModule apkModule){
|
||||
PathSanitizer pathSanitizer = new PathSanitizer(
|
||||
apkModule.getApkArchive().listInputSources());
|
||||
pathSanitizer.setApkLogger(apkModule.getApkLogger());
|
||||
pathSanitizer.setResourceFileList(apkModule.listResFiles());
|
||||
return pathSanitizer;
|
||||
}
|
||||
|
||||
private static final int MAX_NAME_LENGTH = 75;
|
||||
private static final int MAX_PATH_LENGTH = 100;
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
/*
|
||||
/*
|
||||
* Copyright (C) 2022 github.com/REAndroid
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
@ -16,9 +16,10 @@
|
||||
package com.reandroid.apk;
|
||||
|
||||
import com.reandroid.arsc.item.StringItem;
|
||||
import com.reandroid.xml.XMLElement;
|
||||
import com.reandroid.xml.*;
|
||||
|
||||
public class XmlHelper {
|
||||
|
||||
public static void setTextContent(XMLElement element, StringItem stringItem){
|
||||
if(stringItem==null){
|
||||
element.clearChildNodes();
|
||||
@ -37,4 +38,6 @@ public class XmlHelper {
|
||||
}
|
||||
return typeName;
|
||||
}
|
||||
|
||||
public static final String RESOURCES_TAG = "resources";
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
/*
|
||||
/*
|
||||
* Copyright (C) 2022 github.com/REAndroid
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
@ -19,14 +19,9 @@ import com.reandroid.arsc.value.ResTableMapEntry;
|
||||
import com.reandroid.common.EntryStore;
|
||||
import com.reandroid.xml.XMLElement;
|
||||
|
||||
abstract class BagDecoder {
|
||||
private final EntryStore entryStore;
|
||||
abstract class BagDecoder<OUTPUT> extends DecoderTableEntry<ResTableMapEntry, OUTPUT> {
|
||||
public BagDecoder(EntryStore entryStore){
|
||||
this.entryStore=entryStore;
|
||||
super(entryStore);
|
||||
}
|
||||
EntryStore getEntryStore(){
|
||||
return entryStore;
|
||||
}
|
||||
public abstract void decode(ResTableMapEntry mapEntry, XMLElement parentElement);
|
||||
public abstract boolean canDecode(ResTableMapEntry mapEntry);
|
||||
}
|
||||
|
@ -0,0 +1,96 @@
|
||||
/*
|
||||
* Copyright (C) 2022 github.com/REAndroid
|
||||
*
|
||||
* 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 com.reandroid.apk.xmldecoder;
|
||||
|
||||
import com.reandroid.apk.ApkUtil;
|
||||
import com.reandroid.apk.XmlHelper;
|
||||
import com.reandroid.arsc.chunk.PackageBlock;
|
||||
import com.reandroid.arsc.decoder.ValueDecoder;
|
||||
import com.reandroid.arsc.value.*;
|
||||
import com.reandroid.common.EntryStore;
|
||||
import com.reandroid.xml.XMLElement;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
class BagDecoderArray<OUTPUT> extends BagDecoder<OUTPUT>{
|
||||
public BagDecoderArray(EntryStore entryStore) {
|
||||
super(entryStore);
|
||||
}
|
||||
|
||||
@Override
|
||||
public OUTPUT decode(ResTableMapEntry mapEntry, EntryWriter<OUTPUT> writer) throws IOException {
|
||||
Entry entry = mapEntry.getParentEntry();
|
||||
String tag = getTagName(mapEntry);
|
||||
writer.startTag(tag);
|
||||
writer.attribute("name", entry.getName());
|
||||
|
||||
PackageBlock packageBlock = entry.getPackageBlock();
|
||||
ResValueMap[] resValueMaps = mapEntry.listResValueMap();
|
||||
for(int i = 0; i < resValueMaps.length; i++){
|
||||
ResValueMap valueMap = resValueMaps[i];
|
||||
String childTag = "item";
|
||||
writer.startTag(childTag);
|
||||
writeText(writer, packageBlock, valueMap);
|
||||
writer.endTag(childTag);
|
||||
}
|
||||
return writer.endTag(tag);
|
||||
}
|
||||
private String getTagName(ResTableMapEntry mapEntry){
|
||||
ResValueMap[] resValueMaps = mapEntry.listResValueMap();
|
||||
Set<ValueType> valueTypes = new HashSet<>();
|
||||
for(int i = 0; i < resValueMaps.length; i++){
|
||||
valueTypes.add(resValueMaps[i].getValueType());
|
||||
}
|
||||
if(valueTypes.contains(ValueType.STRING)){
|
||||
return ApkUtil.TAG_STRING_ARRAY;
|
||||
}
|
||||
if(valueTypes.size() == 1 && valueTypes.contains(ValueType.INT_DEC)){
|
||||
return ApkUtil.TAG_INTEGER_ARRAY;
|
||||
}
|
||||
return XmlHelper.toXMLTagName(mapEntry.getParentEntry().getTypeName());
|
||||
}
|
||||
@Override
|
||||
public boolean canDecode(ResTableMapEntry mapEntry) {
|
||||
return isArrayValue(mapEntry);
|
||||
}
|
||||
public static boolean isArrayValue(ResTableMapEntry mapEntry){
|
||||
int parentId=mapEntry.getParentId();
|
||||
if(parentId!=0){
|
||||
return false;
|
||||
}
|
||||
ResValueMap[] bagItems = mapEntry.listResValueMap();
|
||||
if(bagItems==null||bagItems.length==0){
|
||||
return false;
|
||||
}
|
||||
int len=bagItems.length;
|
||||
for(int i=0;i<len;i++){
|
||||
ResValueMap item=bagItems[i];
|
||||
int name = item.getName();
|
||||
int high = (name >> 16) & 0xffff;
|
||||
if(high!=0x0100 && high!=0x0200){
|
||||
return false;
|
||||
}
|
||||
int low = name & 0xffff;
|
||||
int id = low - 1;
|
||||
if(id!=i){
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
@ -0,0 +1,94 @@
|
||||
/*
|
||||
* Copyright (C) 2022 github.com/REAndroid
|
||||
*
|
||||
* 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 com.reandroid.apk.xmldecoder;
|
||||
|
||||
import com.reandroid.apk.XmlHelper;
|
||||
import com.reandroid.arsc.value.Entry;
|
||||
import com.reandroid.arsc.value.ResTableMapEntry;
|
||||
import com.reandroid.arsc.value.attribute.AttributeBag;
|
||||
import com.reandroid.arsc.value.attribute.AttributeBagItem;
|
||||
import com.reandroid.common.EntryStore;
|
||||
import com.reandroid.xml.XMLElement;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
class BagDecoderAttr<OUTPUT> extends BagDecoder<OUTPUT>{
|
||||
public BagDecoderAttr(EntryStore entryStore){
|
||||
super(entryStore);
|
||||
}
|
||||
|
||||
@Override
|
||||
public OUTPUT decode(ResTableMapEntry mapEntry, EntryWriter<OUTPUT> writer) throws IOException {
|
||||
Entry entry = mapEntry.getParentEntry();
|
||||
String tag = XmlHelper.toXMLTagName(entry.getTypeName());
|
||||
writer.startTag(tag);
|
||||
writer.attribute("name", entry.getName());
|
||||
AttributeBag attributeBag = AttributeBag.create(mapEntry.getValue());
|
||||
writeParentAttributes(writer, attributeBag);
|
||||
|
||||
boolean is_flag = attributeBag.isFlag();
|
||||
String childTag = is_flag ? "flag" : "enum";
|
||||
|
||||
AttributeBagItem[] bagItems = attributeBag.getBagItems();
|
||||
|
||||
EntryStore entryStore = getEntryStore();
|
||||
|
||||
for(int i=0;i< bagItems.length;i++){
|
||||
AttributeBagItem item = bagItems[i];
|
||||
if(item.isType()){
|
||||
continue;
|
||||
}
|
||||
writer.startTag(childTag);
|
||||
|
||||
String name = item.getNameOrHex(entryStore);
|
||||
writer.attribute("name", name);
|
||||
int rawVal = item.getData();
|
||||
String value;
|
||||
if(is_flag){
|
||||
value = String.format("0x%08x", rawVal);
|
||||
}else {
|
||||
value = String.valueOf(rawVal);
|
||||
}
|
||||
writer.text(value);
|
||||
|
||||
writer.endTag(childTag);
|
||||
}
|
||||
return writer.endTag(tag);
|
||||
}
|
||||
|
||||
private void writeParentAttributes(EntryWriter<OUTPUT> writer, AttributeBag attributeBag) throws IOException {
|
||||
String formats= attributeBag.decodeValueType();
|
||||
if(formats!=null){
|
||||
writer.attribute("formats", formats);
|
||||
}
|
||||
AttributeBagItem item = attributeBag.getMin();
|
||||
if(item != null){
|
||||
writer.attribute("min", item.getBound().toString());
|
||||
}
|
||||
item = attributeBag.getMax();
|
||||
if(item!=null){
|
||||
writer.attribute("max", item.getBound().toString());
|
||||
}
|
||||
item = attributeBag.getL10N();
|
||||
if(item!=null){
|
||||
writer.attribute("l10n", item.getBound().toString());
|
||||
}
|
||||
}
|
||||
@Override
|
||||
public boolean canDecode(ResTableMapEntry mapEntry) {
|
||||
return AttributeBag.isAttribute(mapEntry);
|
||||
}
|
||||
}
|
@ -0,0 +1,77 @@
|
||||
/*
|
||||
* Copyright (C) 2022 github.com/REAndroid
|
||||
*
|
||||
* 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 com.reandroid.apk.xmldecoder;
|
||||
|
||||
import com.reandroid.apk.XmlHelper;
|
||||
import com.reandroid.arsc.chunk.PackageBlock;
|
||||
import com.reandroid.arsc.decoder.ValueDecoder;
|
||||
import com.reandroid.arsc.value.Entry;
|
||||
import com.reandroid.arsc.value.ResTableMapEntry;
|
||||
import com.reandroid.arsc.value.ResValueMap;
|
||||
import com.reandroid.arsc.value.ValueType;
|
||||
import com.reandroid.common.EntryStore;
|
||||
import com.reandroid.xml.XMLElement;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
class BagDecoderCommon<OUTPUT> extends BagDecoder<OUTPUT>{
|
||||
public BagDecoderCommon(EntryStore entryStore) {
|
||||
super(entryStore);
|
||||
}
|
||||
|
||||
@Override
|
||||
public OUTPUT decode(ResTableMapEntry mapEntry, EntryWriter<OUTPUT> writer) throws IOException {
|
||||
Entry entry = mapEntry.getParentEntry();
|
||||
String tag = XmlHelper.toXMLTagName(entry.getTypeName());
|
||||
writer.startTag(tag);
|
||||
writer.attribute("name", entry.getName());
|
||||
|
||||
PackageBlock packageBlock = entry.getPackageBlock();
|
||||
|
||||
int parentId = mapEntry.getParentId();
|
||||
String parent;
|
||||
if(parentId != 0){
|
||||
parent = ValueDecoder.decodeEntryValue(getEntryStore(),
|
||||
packageBlock, ValueType.REFERENCE, parentId);
|
||||
}else {
|
||||
parent = null;
|
||||
}
|
||||
if(parent != null){
|
||||
writer.attribute("parent", parent);
|
||||
}
|
||||
|
||||
EntryStore entryStore = getEntryStore();
|
||||
ResValueMap[] resValueMaps = mapEntry.listResValueMap();
|
||||
for(int i = 0; i < resValueMaps.length; i++){
|
||||
ResValueMap valueMap = resValueMaps[i];
|
||||
String childTag = "item";
|
||||
writer.startTag(childTag);
|
||||
|
||||
String name = ValueDecoder.decodeAttributeName(
|
||||
entryStore, packageBlock, valueMap.getName());
|
||||
writer.attribute("name", name);
|
||||
|
||||
writeText(writer, packageBlock, valueMap);
|
||||
|
||||
writer.endTag(childTag);
|
||||
}
|
||||
return writer.endTag(tag);
|
||||
}
|
||||
@Override
|
||||
public boolean canDecode(ResTableMapEntry mapEntry) {
|
||||
return mapEntry !=null;
|
||||
}
|
||||
}
|
@ -0,0 +1,91 @@
|
||||
/*
|
||||
* Copyright (C) 2022 github.com/REAndroid
|
||||
*
|
||||
* 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 com.reandroid.apk.xmldecoder;
|
||||
|
||||
import com.reandroid.apk.XmlHelper;
|
||||
import com.reandroid.arsc.chunk.PackageBlock;
|
||||
import com.reandroid.arsc.decoder.ValueDecoder;
|
||||
import com.reandroid.arsc.value.*;
|
||||
import com.reandroid.arsc.value.plurals.PluralsQuantity;
|
||||
import com.reandroid.common.EntryStore;
|
||||
import com.reandroid.xml.XMLElement;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
class BagDecoderPlural<OUTPUT> extends BagDecoder<OUTPUT>{
|
||||
public BagDecoderPlural(EntryStore entryStore) {
|
||||
super(entryStore);
|
||||
}
|
||||
|
||||
@Override
|
||||
public OUTPUT decode(ResTableMapEntry mapEntry, EntryWriter<OUTPUT> writer) throws IOException {
|
||||
Entry entry = mapEntry.getParentEntry();
|
||||
String tag = XmlHelper.toXMLTagName(entry.getTypeName());
|
||||
writer.startTag(tag);
|
||||
writer.attribute("name", entry.getName());
|
||||
|
||||
ResValueMap[] resValueMaps = mapEntry.listResValueMap();
|
||||
PackageBlock packageBlock = entry.getPackageBlock();
|
||||
for(int i=0; i < resValueMaps.length; i++){
|
||||
ResValueMap valueMap = resValueMaps[i];
|
||||
String childTag = "item";
|
||||
writer.startTag(childTag);
|
||||
|
||||
PluralsQuantity quantity =
|
||||
PluralsQuantity.valueOf((short) (valueMap.getName() & 0xffff));
|
||||
if(quantity == null){
|
||||
throw new IOException("Unknown plural quantity: " + valueMap);
|
||||
}
|
||||
writer.attribute("quantity", quantity.toString());
|
||||
|
||||
writeText(writer, packageBlock, valueMap);
|
||||
|
||||
writer.endTag(childTag);
|
||||
}
|
||||
return writer.endTag(tag);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canDecode(ResTableMapEntry mapEntry) {
|
||||
return isResBagPluralsValue(mapEntry);
|
||||
}
|
||||
|
||||
public static boolean isResBagPluralsValue(ResTableMapEntry valueItem){
|
||||
int parentId=valueItem.getParentId();
|
||||
if(parentId!=0){
|
||||
return false;
|
||||
}
|
||||
ResValueMap[] bagItems = valueItem.listResValueMap();
|
||||
if(bagItems==null||bagItems.length==0){
|
||||
return false;
|
||||
}
|
||||
int len=bagItems.length;
|
||||
for(int i=0;i<len;i++){
|
||||
ResValueMap item=bagItems[i];
|
||||
int name = item.getName();
|
||||
int high = (name >> 16) & 0xffff;
|
||||
if(high!=0x0100){
|
||||
return false;
|
||||
}
|
||||
int low = name & 0xffff;
|
||||
PluralsQuantity pq=PluralsQuantity.valueOf((short) low);
|
||||
if(pq==null){
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
@ -0,0 +1,38 @@
|
||||
/*
|
||||
* Copyright (C) 2022 github.com/REAndroid
|
||||
*
|
||||
* 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 com.reandroid.apk.xmldecoder;
|
||||
|
||||
import com.reandroid.apk.XmlHelper;
|
||||
import com.reandroid.arsc.value.Entry;
|
||||
import com.reandroid.arsc.value.ResTableEntry;
|
||||
import com.reandroid.common.EntryStore;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
public class DecoderResTableEntry<OUTPUT> extends DecoderTableEntry<ResTableEntry, OUTPUT> {
|
||||
public DecoderResTableEntry(EntryStore entryStore){
|
||||
super(entryStore);
|
||||
}
|
||||
@Override
|
||||
public OUTPUT decode(ResTableEntry tableEntry, EntryWriter<OUTPUT> writer) throws IOException{
|
||||
Entry entry = tableEntry.getParentEntry();
|
||||
String tag = XmlHelper.toXMLTagName(entry.getTypeName());
|
||||
writer.startTag(tag);
|
||||
writeText(writer, entry.getPackageBlock(), tableEntry.getValue());
|
||||
return writer.endTag(tag);
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,52 @@
|
||||
/*
|
||||
* Copyright (C) 2022 github.com/REAndroid
|
||||
*
|
||||
* 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 com.reandroid.apk.xmldecoder;
|
||||
|
||||
import com.reandroid.arsc.value.ResTableMapEntry;
|
||||
import com.reandroid.common.EntryStore;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
class DecoderResTableEntryMap<OUTPUT> extends DecoderTableEntry<ResTableMapEntry, OUTPUT> {
|
||||
private final Object[] decoderList;
|
||||
private final BagDecoderCommon<OUTPUT> bagDecoderCommon;
|
||||
|
||||
public DecoderResTableEntryMap(EntryStore entryStore) {
|
||||
super(entryStore);
|
||||
this.decoderList = new Object[] {
|
||||
new BagDecoderAttr<>(entryStore),
|
||||
new BagDecoderPlural<>(entryStore),
|
||||
new BagDecoderArray<>(entryStore)
|
||||
};
|
||||
|
||||
this.bagDecoderCommon = new BagDecoderCommon<>(entryStore);
|
||||
}
|
||||
|
||||
@Override
|
||||
public OUTPUT decode(ResTableMapEntry tableEntry, EntryWriter<OUTPUT> writer) throws IOException {
|
||||
return getFor(tableEntry).decode(tableEntry, writer);
|
||||
}
|
||||
private BagDecoder<OUTPUT> getFor(ResTableMapEntry mapEntry){
|
||||
Object[] decoderList = this.decoderList;
|
||||
for(int i = 0; i < decoderList.length; i++){
|
||||
BagDecoder<OUTPUT> bagDecoder = (BagDecoder<OUTPUT>) decoderList[i];
|
||||
if(bagDecoder.canDecode(mapEntry)){
|
||||
return bagDecoder;
|
||||
}
|
||||
}
|
||||
return bagDecoderCommon;
|
||||
}
|
||||
}
|
@ -0,0 +1,57 @@
|
||||
/*
|
||||
* Copyright (C) 2022 github.com/REAndroid
|
||||
*
|
||||
* 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 com.reandroid.apk.xmldecoder;
|
||||
|
||||
import com.reandroid.arsc.chunk.PackageBlock;
|
||||
import com.reandroid.arsc.decoder.ValueDecoder;
|
||||
import com.reandroid.arsc.value.TableEntry;
|
||||
import com.reandroid.arsc.value.ValueItem;
|
||||
import com.reandroid.arsc.value.ValueType;
|
||||
import com.reandroid.common.EntryStore;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
abstract class DecoderTableEntry<INPUT extends TableEntry<?, ?>, OUTPUT> {
|
||||
private final EntryStore entryStore;
|
||||
public DecoderTableEntry(EntryStore entryStore){
|
||||
this.entryStore = entryStore;
|
||||
}
|
||||
public EntryStore getEntryStore() {
|
||||
return entryStore;
|
||||
}
|
||||
public abstract OUTPUT decode(INPUT tableEntry, EntryWriter<OUTPUT> writer) throws IOException;
|
||||
|
||||
void writeText(EntryWriter<?> writer, PackageBlock packageBlock, ValueItem valueItem)
|
||||
throws IOException {
|
||||
|
||||
if(valueItem.getValueType() == ValueType.STRING){
|
||||
XMLDecodeHelper.writeTextContent(writer, valueItem.getDataAsPoolString());
|
||||
}else {
|
||||
String value = ValueDecoder.decodeEntryValue(
|
||||
getEntryStore(),
|
||||
packageBlock,
|
||||
valueItem.getValueType(),
|
||||
valueItem.getData());
|
||||
|
||||
value = XMLDecodeHelper.escapeXmlChars(value);
|
||||
if(value == null){
|
||||
System.err.println("\nNULL: " + valueItem);
|
||||
}
|
||||
|
||||
writer.text(value);
|
||||
}
|
||||
}
|
||||
}
|
28
src/main/java/com/reandroid/apk/xmldecoder/EntryWriter.java
Normal file
28
src/main/java/com/reandroid/apk/xmldecoder/EntryWriter.java
Normal file
@ -0,0 +1,28 @@
|
||||
/*
|
||||
* Copyright (C) 2022 github.com/REAndroid
|
||||
*
|
||||
* 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 com.reandroid.apk.xmldecoder;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
public interface EntryWriter<T>{
|
||||
void setFeature(String name, Object value);
|
||||
T startTag(String name) throws IOException;
|
||||
T endTag(String name) throws IOException;
|
||||
T attribute(String name, String value) throws IOException;
|
||||
T text(String text) throws IOException;
|
||||
void comment(String comment) throws IOException;
|
||||
void flush() throws IOException;
|
||||
}
|
@ -0,0 +1,86 @@
|
||||
/*
|
||||
* Copyright (C) 2022 github.com/REAndroid
|
||||
*
|
||||
* 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 com.reandroid.apk.xmldecoder;
|
||||
|
||||
import com.reandroid.xml.XMLComment;
|
||||
import com.reandroid.xml.XMLElement;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
public class EntryWriterElement implements EntryWriter<XMLElement> {
|
||||
private XMLElement mCurrentElement;
|
||||
private XMLElement mResult;
|
||||
|
||||
public EntryWriterElement(){
|
||||
}
|
||||
|
||||
public XMLElement getElement() {
|
||||
return mResult;
|
||||
}
|
||||
@Override
|
||||
public void setFeature(String name, Object value) {
|
||||
}
|
||||
@Override
|
||||
public XMLElement startTag(String name) throws IOException {
|
||||
XMLElement xmlElement = new XMLElement(name);
|
||||
XMLElement current = mCurrentElement;
|
||||
if(current != null){
|
||||
current.addChild(xmlElement);
|
||||
}else {
|
||||
mResult = null;
|
||||
}
|
||||
mCurrentElement = xmlElement;
|
||||
return xmlElement;
|
||||
}
|
||||
@Override
|
||||
public XMLElement endTag(String name) throws IOException {
|
||||
XMLElement current = mCurrentElement;
|
||||
if(current == null){
|
||||
throw new IOException("endTag called before startTag");
|
||||
}
|
||||
if(!name.equals(current.getTagName())){
|
||||
throw new IOException("Mismatch endTag = "
|
||||
+ name + ", expect = " + current.getTagName());
|
||||
}
|
||||
XMLElement parent = current.getParent();
|
||||
if(parent == null){
|
||||
mResult = current;
|
||||
}else {
|
||||
current = parent;
|
||||
}
|
||||
mCurrentElement = parent;
|
||||
return current;
|
||||
}
|
||||
@Override
|
||||
public XMLElement attribute(String name, String value) {
|
||||
mCurrentElement.setAttribute(name, value);
|
||||
return mCurrentElement;
|
||||
}
|
||||
@Override
|
||||
public XMLElement text(String text) throws IOException {
|
||||
mCurrentElement.setTextContent(text, false);
|
||||
return mCurrentElement;
|
||||
}
|
||||
@Override
|
||||
public void comment(String comment) throws IOException {
|
||||
if(comment != null){
|
||||
mCurrentElement.addComment(new XMLComment(comment));
|
||||
}
|
||||
}
|
||||
@Override
|
||||
public void flush() throws IOException {
|
||||
}
|
||||
}
|
@ -0,0 +1,61 @@
|
||||
/*
|
||||
* Copyright (C) 2022 github.com/REAndroid
|
||||
*
|
||||
* 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 com.reandroid.apk.xmldecoder;
|
||||
|
||||
import org.xmlpull.v1.XmlSerializer;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
public class EntryWriterSerializer implements EntryWriter<XmlSerializer> {
|
||||
private final XmlSerializer xmlSerializer;
|
||||
public EntryWriterSerializer(XmlSerializer xmlSerializer){
|
||||
this.xmlSerializer = xmlSerializer;
|
||||
}
|
||||
|
||||
public XmlSerializer getXmlSerializer() {
|
||||
return xmlSerializer;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setFeature(String name, Object value) {
|
||||
xmlSerializer.setFeature(name, (Boolean)value);
|
||||
}
|
||||
@Override
|
||||
public XmlSerializer startTag(String name) throws IOException {
|
||||
xmlSerializer.setFeature("http://xmlpull.org/v1/doc/features.html#indent-output", true);
|
||||
return xmlSerializer.startTag(null, name);
|
||||
}
|
||||
@Override
|
||||
public XmlSerializer endTag(String name) throws IOException {
|
||||
return xmlSerializer.endTag(null, name);
|
||||
}
|
||||
@Override
|
||||
public XmlSerializer attribute(String name, String value) throws IOException {
|
||||
return xmlSerializer.attribute(null, name, value);
|
||||
}
|
||||
@Override
|
||||
public XmlSerializer text(String text) throws IOException {
|
||||
return xmlSerializer.text(text);
|
||||
}
|
||||
@Override
|
||||
public void comment(String comment) throws IOException {
|
||||
xmlSerializer.comment(comment);
|
||||
}
|
||||
@Override
|
||||
public void flush() throws IOException {
|
||||
xmlSerializer.flush();
|
||||
}
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
/*
|
||||
/*
|
||||
* Copyright (C) 2022 github.com/REAndroid
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
|
@ -1,88 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2022 github.com/REAndroid
|
||||
*
|
||||
* 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 com.reandroid.apk.xmldecoder;
|
||||
|
||||
import com.reandroid.apk.ApkUtil;
|
||||
import com.reandroid.apk.XmlHelper;
|
||||
import com.reandroid.arsc.decoder.ValueDecoder;
|
||||
import com.reandroid.arsc.value.ResTableMapEntry;
|
||||
import com.reandroid.arsc.value.ResValueMap;
|
||||
import com.reandroid.arsc.value.ValueType;
|
||||
import com.reandroid.common.EntryStore;
|
||||
import com.reandroid.xml.XMLElement;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
class XMLArrayDecoder extends BagDecoder{
|
||||
public XMLArrayDecoder(EntryStore entryStore) {
|
||||
super(entryStore);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void decode(ResTableMapEntry mapEntry, XMLElement parentElement) {
|
||||
ResValueMap[] bagItems = mapEntry.listResValueMap();
|
||||
EntryStore entryStore=getEntryStore();
|
||||
Set<ValueType> valueTypes = new HashSet<>();
|
||||
for(int i=0;i<bagItems.length;i++){
|
||||
ResValueMap bagItem = bagItems[i];
|
||||
ValueType valueType = bagItem.getValueType();
|
||||
XMLElement child = new XMLElement("item");
|
||||
if(valueType == ValueType.STRING){
|
||||
XmlHelper.setTextContent(child, bagItem.getDataAsPoolString());
|
||||
}else {
|
||||
String value = ValueDecoder.decodeIntEntry(entryStore, bagItem);
|
||||
child.setTextContent(value);
|
||||
}
|
||||
parentElement.addChild(child);
|
||||
valueTypes.add(valueType);
|
||||
}
|
||||
if(valueTypes.contains(ValueType.STRING)){
|
||||
parentElement.setTagName(ApkUtil.TAG_STRING_ARRAY);
|
||||
}else if(valueTypes.size()==1 && valueTypes.contains(ValueType.INT_DEC)){
|
||||
parentElement.setTagName(ApkUtil.TAG_INTEGER_ARRAY);
|
||||
}
|
||||
}
|
||||
@Override
|
||||
public boolean canDecode(ResTableMapEntry mapEntry) {
|
||||
return isArrayValue(mapEntry);
|
||||
}
|
||||
public static boolean isArrayValue(ResTableMapEntry mapEntry){
|
||||
int parentId=mapEntry.getParentId();
|
||||
if(parentId!=0){
|
||||
return false;
|
||||
}
|
||||
ResValueMap[] bagItems = mapEntry.listResValueMap();
|
||||
if(bagItems==null||bagItems.length==0){
|
||||
return false;
|
||||
}
|
||||
int len=bagItems.length;
|
||||
for(int i=0;i<len;i++){
|
||||
ResValueMap item=bagItems[i];
|
||||
int name = item.getName();
|
||||
int high = (name >> 16) & 0xffff;
|
||||
if(high!=0x0100 && high!=0x0200){
|
||||
return false;
|
||||
}
|
||||
int low = name & 0xffff;
|
||||
int id = low - 1;
|
||||
if(id!=i){
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
@ -1,80 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2022 github.com/REAndroid
|
||||
*
|
||||
* 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 com.reandroid.apk.xmldecoder;
|
||||
|
||||
import com.reandroid.arsc.value.ResTableMapEntry;
|
||||
import com.reandroid.arsc.value.attribute.AttributeBag;
|
||||
import com.reandroid.arsc.value.attribute.AttributeBagItem;
|
||||
import com.reandroid.common.EntryStore;
|
||||
import com.reandroid.xml.XMLElement;
|
||||
|
||||
class XMLAttrDecoder extends BagDecoder{
|
||||
public XMLAttrDecoder(EntryStore entryStore){
|
||||
super(entryStore);
|
||||
}
|
||||
@Override
|
||||
public void decode(ResTableMapEntry mapEntry, XMLElement parentElement){
|
||||
AttributeBag attributeBag=AttributeBag.create(mapEntry.getValue());
|
||||
decodeParentAttributes(parentElement, attributeBag);
|
||||
|
||||
boolean is_flag=attributeBag.isFlag();
|
||||
String tagName=is_flag?"flag":"enum";
|
||||
|
||||
AttributeBagItem[] bagItems = attributeBag.getBagItems();
|
||||
EntryStore entryStore=getEntryStore();
|
||||
for(int i=0;i< bagItems.length;i++){
|
||||
AttributeBagItem item=bagItems[i];
|
||||
if(item.isType()){
|
||||
continue;
|
||||
}
|
||||
XMLElement child=new XMLElement(tagName);
|
||||
String name = item.getNameOrHex(entryStore);
|
||||
child.setAttribute("name", name);
|
||||
int rawVal=item.getData();
|
||||
String value;
|
||||
if(is_flag){
|
||||
value=String.format("0x%08x", rawVal);
|
||||
}else {
|
||||
value=String.valueOf(rawVal);
|
||||
}
|
||||
child.setTextContent(value);
|
||||
parentElement.addChild(child);
|
||||
}
|
||||
}
|
||||
@Override
|
||||
public boolean canDecode(ResTableMapEntry mapEntry) {
|
||||
return AttributeBag.isAttribute(mapEntry);
|
||||
}
|
||||
|
||||
private void decodeParentAttributes(XMLElement element, AttributeBag attributeBag){
|
||||
String formats= attributeBag.decodeValueType();
|
||||
if(formats!=null){
|
||||
element.setAttribute("formats", formats);
|
||||
}
|
||||
AttributeBagItem boundItem=attributeBag.getMin();
|
||||
if(boundItem!=null){
|
||||
element.setAttribute("min", boundItem.getBound().toString());
|
||||
}
|
||||
boundItem=attributeBag.getMax();
|
||||
if(boundItem!=null){
|
||||
element.setAttribute("max", boundItem.getBound().toString());
|
||||
}
|
||||
boundItem=attributeBag.getL10N();
|
||||
if(boundItem!=null){
|
||||
element.setAttribute("l10n", boundItem.getBound().toString());
|
||||
}
|
||||
}
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
/*
|
||||
/*
|
||||
* Copyright (C) 2022 github.com/REAndroid
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
@ -15,36 +15,25 @@
|
||||
*/
|
||||
package com.reandroid.apk.xmldecoder;
|
||||
|
||||
import com.reandroid.arsc.array.ResValueMapArray;
|
||||
import com.reandroid.arsc.value.ResTableMapEntry;
|
||||
import com.reandroid.common.EntryStore;
|
||||
import com.reandroid.xml.XMLElement;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.io.IOException;
|
||||
|
||||
@Deprecated
|
||||
public class XMLBagDecoder {
|
||||
private final EntryStore entryStore;
|
||||
private final List<BagDecoder> decoderList;
|
||||
private final XMLCommonBagDecoder commonBagDecoder;
|
||||
private final DecoderResTableEntryMap<XMLElement> mDocumentDecoder;
|
||||
private final EntryWriterElement mWriter;
|
||||
public XMLBagDecoder(EntryStore entryStore){
|
||||
this.entryStore=entryStore;
|
||||
this.decoderList=new ArrayList<>();
|
||||
this.decoderList.add(new XMLAttrDecoder(entryStore));
|
||||
this.decoderList.add(new XMLPluralsDecoder(entryStore));
|
||||
this.decoderList.add(new XMLArrayDecoder(entryStore));
|
||||
this.commonBagDecoder = new XMLCommonBagDecoder(entryStore);
|
||||
mDocumentDecoder = new DecoderResTableEntryMap<>(entryStore);
|
||||
mWriter = new EntryWriterElement();
|
||||
}
|
||||
public void decode(ResTableMapEntry mapEntry, XMLElement parentElement){
|
||||
BagDecoder bagDecoder=getFor(mapEntry);
|
||||
bagDecoder.decode(mapEntry, parentElement);
|
||||
}
|
||||
private BagDecoder getFor(ResTableMapEntry mapEntry){
|
||||
for(BagDecoder bagDecoder:decoderList){
|
||||
if(bagDecoder.canDecode(mapEntry)){
|
||||
return bagDecoder;
|
||||
}
|
||||
try {
|
||||
XMLElement child = mDocumentDecoder.decode(mapEntry, mWriter);
|
||||
parentElement.addChild(child);
|
||||
} catch (IOException exception) {
|
||||
}
|
||||
return commonBagDecoder;
|
||||
}
|
||||
}
|
||||
|
@ -1,75 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2022 github.com/REAndroid
|
||||
*
|
||||
* 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 com.reandroid.apk.xmldecoder;
|
||||
|
||||
import com.reandroid.apk.XmlHelper;
|
||||
import com.reandroid.arsc.chunk.PackageBlock;
|
||||
import com.reandroid.arsc.decoder.ValueDecoder;
|
||||
import com.reandroid.arsc.value.ResTableMapEntry;
|
||||
import com.reandroid.arsc.value.ResValueMap;
|
||||
import com.reandroid.arsc.value.ValueType;
|
||||
import com.reandroid.common.EntryStore;
|
||||
import com.reandroid.xml.XMLElement;
|
||||
|
||||
class XMLCommonBagDecoder extends BagDecoder{
|
||||
public XMLCommonBagDecoder(EntryStore entryStore) {
|
||||
super(entryStore);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void decode(ResTableMapEntry mapEntry, XMLElement parentElement) {
|
||||
|
||||
PackageBlock currentPackage= mapEntry
|
||||
.getParentEntry().getPackageBlock();
|
||||
|
||||
int parentId = mapEntry.getParentId();
|
||||
String parent;
|
||||
if(parentId!=0){
|
||||
parent = ValueDecoder.decodeEntryValue(getEntryStore(),
|
||||
currentPackage, ValueType.REFERENCE, parentId);
|
||||
}else {
|
||||
parent=null;
|
||||
}
|
||||
if(parent!=null){
|
||||
parentElement.setAttribute("parent", parent);
|
||||
}
|
||||
int currentPackageId=currentPackage.getId();
|
||||
ResValueMap[] bagItems = mapEntry.listResValueMap();
|
||||
EntryStore entryStore = getEntryStore();
|
||||
for(int i=0;i< bagItems.length;i++){
|
||||
ResValueMap item=bagItems[i];
|
||||
XMLElement child=new XMLElement("item");
|
||||
String name = ValueDecoder.decodeAttributeName(
|
||||
entryStore, currentPackage, item.getName());
|
||||
|
||||
child.setAttribute("name", name);
|
||||
|
||||
ValueType valueType = item.getValueType();
|
||||
if(valueType == ValueType.STRING){
|
||||
XmlHelper.setTextContent(child, item.getDataAsPoolString());
|
||||
}else {
|
||||
String value = ValueDecoder.decode(entryStore, currentPackageId,
|
||||
item);
|
||||
child.setTextContent(value);
|
||||
}
|
||||
parentElement.addChild(child);
|
||||
}
|
||||
}
|
||||
@Override
|
||||
public boolean canDecode(ResTableMapEntry mapEntry) {
|
||||
return mapEntry !=null;
|
||||
}
|
||||
}
|
@ -0,0 +1,84 @@
|
||||
/*
|
||||
* Copyright (C) 2022 github.com/REAndroid
|
||||
*
|
||||
* 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 com.reandroid.apk.xmldecoder;
|
||||
|
||||
import com.reandroid.arsc.item.StringItem;
|
||||
import com.reandroid.xml.*;
|
||||
import com.reandroid.xml.parser.XMLSpanParser;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
public class XMLDecodeHelper {
|
||||
|
||||
public static void writeTextContent(EntryWriter<?> writer, StringItem stringItem) throws IOException {
|
||||
if(stringItem == null){
|
||||
return;
|
||||
}
|
||||
if(!stringItem.hasStyle()){
|
||||
writer.text(escapeXmlChars(stringItem.get()));
|
||||
}else {
|
||||
String xml = stringItem.getXml();
|
||||
XMLElement element = parseSpanSafe(xml);
|
||||
if(element != null){
|
||||
writeElement(writer, element);
|
||||
}else {
|
||||
// TODO: throw or investigate the reason
|
||||
writer.text(xml);
|
||||
}
|
||||
}
|
||||
}
|
||||
public static void writeElement(EntryWriter<?> writer, XMLElement element) throws IOException {
|
||||
writer.startTag(element.getTagName());
|
||||
for(XMLAttribute xmlAttribute : element.listAttributes()){
|
||||
writer.attribute(xmlAttribute.getName(), xmlAttribute.getValue());
|
||||
}
|
||||
for(XMLNode xmlNode : element.getChildNodes()){
|
||||
if(xmlNode instanceof XMLText){
|
||||
writer.text(((XMLText)xmlNode).getText(false));
|
||||
}else if(xmlNode instanceof XMLElement){
|
||||
writeElement(writer, (XMLElement) xmlNode);
|
||||
}
|
||||
}
|
||||
writer.endTag(element.getTagName());
|
||||
}
|
||||
private static XMLElement parseSpanSafe(String spanText){
|
||||
if(spanText==null){
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
XMLSpanParser spanParser = new XMLSpanParser();
|
||||
return spanParser.parse(spanText);
|
||||
} catch (XMLException ignored) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
public static String escapeXmlChars(String str){
|
||||
if(str == null){
|
||||
return null;
|
||||
}
|
||||
if(str.indexOf('&') < 0 && str.indexOf('<') < 0 && str.indexOf('>') < 0){
|
||||
return str;
|
||||
}
|
||||
str=str.replaceAll("&", "&");
|
||||
str=str.replaceAll("<", "<");
|
||||
str=str.replaceAll(">", ">");
|
||||
str=str.replaceAll("&", "&");
|
||||
str=str.replaceAll("<", "<");
|
||||
str=str.replaceAll(">", ">");
|
||||
return str;
|
||||
}
|
||||
|
||||
}
|
135
src/main/java/com/reandroid/apk/xmldecoder/XMLEntryDecoder.java
Normal file
135
src/main/java/com/reandroid/apk/xmldecoder/XMLEntryDecoder.java
Normal file
@ -0,0 +1,135 @@
|
||||
/*
|
||||
* Copyright (C) 2022 github.com/REAndroid
|
||||
*
|
||||
* 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 com.reandroid.apk.xmldecoder;
|
||||
|
||||
import com.reandroid.arsc.chunk.TypeBlock;
|
||||
import com.reandroid.arsc.group.EntryGroup;
|
||||
import com.reandroid.arsc.value.*;
|
||||
import com.reandroid.common.EntryStore;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.Collection;
|
||||
import java.util.Iterator;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
public class XMLEntryDecoder<OUTPUT>{
|
||||
private final Object mLock = new Object();
|
||||
private final DecoderResTableEntry<OUTPUT> decoderEntry;
|
||||
private final DecoderResTableEntryMap<OUTPUT> decoderEntryMap;
|
||||
private Predicate<Entry> mDecodedEntries;
|
||||
|
||||
public XMLEntryDecoder(EntryStore entryStore){
|
||||
this.decoderEntry = new DecoderResTableEntry<>(entryStore);
|
||||
this.decoderEntryMap = new DecoderResTableEntryMap<>(entryStore);
|
||||
}
|
||||
|
||||
public void setDecodedEntries(Predicate<Entry> decodedEntries) {
|
||||
this.mDecodedEntries = decodedEntries;
|
||||
}
|
||||
|
||||
private boolean shouldDecode(Entry entry){
|
||||
if(entry == null || entry.isNull()){
|
||||
return false;
|
||||
}
|
||||
if(this.mDecodedEntries != null){
|
||||
return mDecodedEntries.test(entry);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public OUTPUT decode(EntryWriter<OUTPUT> writer, Entry entry) throws IOException{
|
||||
if(!shouldDecode(entry)){
|
||||
return null;
|
||||
}
|
||||
synchronized (mLock){
|
||||
TableEntry<?, ?> tableEntry = entry.getTableEntry();
|
||||
if(tableEntry instanceof ResTableMapEntry){
|
||||
return decoderEntryMap.decode((ResTableMapEntry) tableEntry, writer);
|
||||
}
|
||||
return decoderEntry.decode((ResTableEntry) tableEntry, writer);
|
||||
}
|
||||
}
|
||||
public int decode(EntryWriter<OUTPUT> writer, Collection<Entry> entryList) throws IOException {
|
||||
int count = 0;
|
||||
for(Entry entry : entryList){
|
||||
OUTPUT output = decode(writer, entry);
|
||||
if(output != null){
|
||||
count ++;
|
||||
}
|
||||
}
|
||||
return count;
|
||||
}
|
||||
public int decode(EntryWriter<OUTPUT> writer, ResConfig resConfig, Collection<EntryGroup> entryGroupList) throws IOException {
|
||||
int count = 0;
|
||||
for(EntryGroup entryGroup : entryGroupList){
|
||||
OUTPUT output = decode(writer, entryGroup.getEntry(resConfig));
|
||||
if(output != null){
|
||||
count ++;
|
||||
}
|
||||
}
|
||||
return count;
|
||||
}
|
||||
public int decode(EntryWriter<OUTPUT> writer, TypeBlock typeBlock) throws IOException {
|
||||
Iterator<Entry> iterator = typeBlock.getEntryArray()
|
||||
.iterator(true);
|
||||
int count = 0;
|
||||
while (iterator.hasNext()){
|
||||
Entry entry = iterator.next();
|
||||
OUTPUT output = decode(writer, entry);
|
||||
if(output != null){
|
||||
count++;
|
||||
}
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
void deleteIfZero(int decodeCount, File file){
|
||||
if(decodeCount > 0){
|
||||
return;
|
||||
}
|
||||
file.delete();
|
||||
File dir = file.getParentFile();
|
||||
if(isEmptyDirectory(dir)){
|
||||
dir.delete();
|
||||
}
|
||||
}
|
||||
private boolean isEmptyDirectory(File dir){
|
||||
if(dir == null || !dir.isDirectory()){
|
||||
return false;
|
||||
}
|
||||
File[] files = dir.listFiles();
|
||||
return files == null || files.length == 0;
|
||||
}
|
||||
File toOutXmlFile(File resDirectory, TypeBlock typeBlock){
|
||||
String path = toValuesXml(typeBlock);
|
||||
return new File(resDirectory, path);
|
||||
}
|
||||
String toValuesXml(TypeBlock typeBlock){
|
||||
StringBuilder builder = new StringBuilder();
|
||||
char sepChar = File.separatorChar;
|
||||
builder.append("values");
|
||||
builder.append(typeBlock.getQualifiers());
|
||||
builder.append(sepChar);
|
||||
String type = typeBlock.getTypeName();
|
||||
builder.append(type);
|
||||
if(!type.endsWith("s")){
|
||||
builder.append('s');
|
||||
}
|
||||
builder.append(".xml");
|
||||
return builder.toString();
|
||||
}
|
||||
}
|
@ -0,0 +1,55 @@
|
||||
/*
|
||||
* Copyright (C) 2022 github.com/REAndroid
|
||||
*
|
||||
* 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 com.reandroid.apk.xmldecoder;
|
||||
|
||||
import com.reandroid.apk.XmlHelper;
|
||||
import com.reandroid.arsc.value.Entry;
|
||||
import com.reandroid.common.EntryStore;
|
||||
import com.reandroid.xml.XMLDocument;
|
||||
import com.reandroid.xml.XMLElement;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Collection;
|
||||
|
||||
public class XMLEntryDecoderDocument extends XMLEntryDecoder<XMLElement>{
|
||||
private final EntryWriterElement entryWriterElement;
|
||||
public XMLEntryDecoderDocument(EntryStore entryStore) {
|
||||
super(entryStore);
|
||||
this.entryWriterElement = new EntryWriterElement();
|
||||
}
|
||||
|
||||
public XMLElement decode(Entry entry) throws IOException {
|
||||
return super.decode(this.entryWriterElement, entry);
|
||||
}
|
||||
|
||||
public XMLDocument decode(XMLDocument xmlDocument, Collection<Entry> entryList)
|
||||
throws IOException {
|
||||
|
||||
if(xmlDocument == null){
|
||||
xmlDocument = new XMLDocument(XmlHelper.RESOURCES_TAG);
|
||||
}
|
||||
XMLElement docElement = xmlDocument.getDocumentElement();
|
||||
|
||||
if(docElement == null){
|
||||
docElement = new XMLElement(XmlHelper.RESOURCES_TAG);
|
||||
xmlDocument.setDocumentElement(docElement);
|
||||
}
|
||||
for(Entry entry : entryList){
|
||||
docElement.addChild(decode(entry));
|
||||
}
|
||||
return xmlDocument;
|
||||
}
|
||||
}
|
@ -0,0 +1,145 @@
|
||||
/*
|
||||
* Copyright (C) 2022 github.com/REAndroid
|
||||
*
|
||||
* 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 com.reandroid.apk.xmldecoder;
|
||||
|
||||
import com.android.org.kxml2.io.KXmlSerializer;
|
||||
import com.reandroid.apk.XmlHelper;
|
||||
import com.reandroid.arsc.chunk.TypeBlock;
|
||||
import com.reandroid.arsc.container.SpecTypePair;
|
||||
import com.reandroid.arsc.group.EntryGroup;
|
||||
import com.reandroid.arsc.value.ResConfig;
|
||||
import com.reandroid.common.EntryStore;
|
||||
import org.xmlpull.v1.XmlSerializer;
|
||||
|
||||
import java.io.*;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Collection;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
|
||||
public class XMLEntryDecoderSerializer extends XMLEntryDecoder<XmlSerializer> implements Closeable {
|
||||
private final EntryWriterSerializer entryWriterSerializer;
|
||||
private Closeable mClosable;
|
||||
private boolean mStart;
|
||||
|
||||
public XMLEntryDecoderSerializer(EntryStore entryStore, XmlSerializer serializer) {
|
||||
super(entryStore);
|
||||
this.entryWriterSerializer = new EntryWriterSerializer(serializer);
|
||||
}
|
||||
public XMLEntryDecoderSerializer(EntryStore entryStore) {
|
||||
this(entryStore, new KXmlSerializer());
|
||||
}
|
||||
|
||||
public int decode(File resDirectory, SpecTypePair specTypePair) throws IOException {
|
||||
int count;
|
||||
if(specTypePair.hasDuplicateResConfig(true)){
|
||||
count = decodeDuplicateConfigs(resDirectory, specTypePair);
|
||||
}else {
|
||||
count = decodeUniqueConfigs(resDirectory, specTypePair);
|
||||
}
|
||||
return count;
|
||||
}
|
||||
private int decodeDuplicateConfigs(File resDirectory, SpecTypePair specTypePair) throws IOException {
|
||||
List<ResConfig> resConfigList = specTypePair.listResConfig();
|
||||
Collection<EntryGroup> entryGroupList = specTypePair
|
||||
.createEntryGroups(true).values();
|
||||
int total = 0;
|
||||
for(ResConfig resConfig : resConfigList){
|
||||
TypeBlock typeBlock = resConfig.getParentInstance(TypeBlock.class);
|
||||
File outXml = toOutXmlFile(resDirectory, typeBlock);
|
||||
total += decode(outXml, resConfig, entryGroupList);
|
||||
}
|
||||
return total;
|
||||
}
|
||||
private int decodeUniqueConfigs(File resDirectory, SpecTypePair specTypePair) throws IOException {
|
||||
int total = 0;
|
||||
Iterator<TypeBlock> itr = specTypePair.iteratorNonEmpty();
|
||||
while (itr.hasNext()){
|
||||
TypeBlock typeBlock = itr.next();
|
||||
File outXml = toOutXmlFile(resDirectory, typeBlock);
|
||||
total += decode(outXml, typeBlock);
|
||||
}
|
||||
return total;
|
||||
}
|
||||
public int decode(File outXmlFile, ResConfig resConfig, Collection<EntryGroup> entryGroupList) throws IOException {
|
||||
setOutput(outXmlFile);
|
||||
int count = decode(resConfig, entryGroupList);
|
||||
close();
|
||||
deleteIfZero(count, outXmlFile);
|
||||
return count;
|
||||
}
|
||||
public int decode(File outXmlFile, TypeBlock typeBlock) throws IOException {
|
||||
setOutput(outXmlFile);
|
||||
int count = super.decode(entryWriterSerializer, typeBlock);
|
||||
close();
|
||||
deleteIfZero(count, outXmlFile);
|
||||
return count;
|
||||
}
|
||||
public int decode(ResConfig resConfig, Collection<EntryGroup> entryGroupList) throws IOException {
|
||||
return super.decode(entryWriterSerializer, resConfig, entryGroupList);
|
||||
}
|
||||
public void setOutput(File file) throws IOException {
|
||||
File dir = file.getParentFile();
|
||||
if(dir != null && !dir.exists()){
|
||||
dir.mkdirs();
|
||||
}
|
||||
setOutput(new FileOutputStream(file));
|
||||
}
|
||||
public void setOutput(OutputStream outputStream) throws IOException {
|
||||
close();
|
||||
getXmlSerializer().setOutput(outputStream, StandardCharsets.UTF_8.name());
|
||||
this.mClosable = outputStream;
|
||||
start();
|
||||
}
|
||||
public void setOutput(Writer writer) throws IOException {
|
||||
close();
|
||||
getXmlSerializer().setOutput(writer);
|
||||
this.mClosable = writer;
|
||||
start();
|
||||
}
|
||||
|
||||
private void start() throws IOException {
|
||||
if(!mStart){
|
||||
XmlSerializer xmlSerializer = getXmlSerializer();
|
||||
xmlSerializer.startDocument("utf-8", null);
|
||||
xmlSerializer.startTag(null, XmlHelper.RESOURCES_TAG);
|
||||
mStart = true;
|
||||
}
|
||||
}
|
||||
private void end() throws IOException {
|
||||
if(mStart){
|
||||
XmlSerializer xmlSerializer = getXmlSerializer();
|
||||
xmlSerializer.endTag(null, XmlHelper.RESOURCES_TAG);
|
||||
xmlSerializer.endDocument();
|
||||
xmlSerializer.flush();
|
||||
mStart = false;
|
||||
}
|
||||
}
|
||||
private XmlSerializer getXmlSerializer(){
|
||||
return entryWriterSerializer.getXmlSerializer();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() throws IOException {
|
||||
Closeable closeable = this.mClosable;
|
||||
end();
|
||||
if(closeable != null){
|
||||
closeable.close();
|
||||
}
|
||||
this.mClosable = null;
|
||||
}
|
||||
|
||||
}
|
@ -1,82 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2022 github.com/REAndroid
|
||||
*
|
||||
* 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 com.reandroid.apk.xmldecoder;
|
||||
|
||||
import com.reandroid.apk.XmlHelper;
|
||||
import com.reandroid.arsc.decoder.ValueDecoder;
|
||||
import com.reandroid.arsc.value.*;
|
||||
import com.reandroid.arsc.value.plurals.PluralsQuantity;
|
||||
import com.reandroid.common.EntryStore;
|
||||
import com.reandroid.xml.XMLElement;
|
||||
|
||||
class XMLPluralsDecoder extends BagDecoder{
|
||||
public XMLPluralsDecoder(EntryStore entryStore) {
|
||||
super(entryStore);
|
||||
}
|
||||
@Override
|
||||
public void decode(ResTableMapEntry mapEntry, XMLElement parentElement) {
|
||||
ResValueMap[] bagItems = mapEntry.listResValueMap();
|
||||
int len=bagItems.length;
|
||||
EntryStore entryStore=getEntryStore();
|
||||
for(int i=0;i<len;i++){
|
||||
ResValueMap item = bagItems[i];
|
||||
int low = item.getName() & 0xffff;
|
||||
PluralsQuantity quantity = PluralsQuantity.valueOf((short) low);
|
||||
XMLElement child=new XMLElement("item");
|
||||
child.setAttribute("quantity", quantity.toString());
|
||||
|
||||
if(item.getValueType() == ValueType.STRING){
|
||||
XmlHelper.setTextContent(child, item.getDataAsPoolString());
|
||||
}else {
|
||||
String value = ValueDecoder.decodeIntEntry(entryStore, item);
|
||||
child.setTextContent(value);
|
||||
}
|
||||
|
||||
parentElement.addChild(child);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canDecode(ResTableMapEntry mapEntry) {
|
||||
return isResBagPluralsValue(mapEntry);
|
||||
}
|
||||
|
||||
public static boolean isResBagPluralsValue(ResTableMapEntry valueItem){
|
||||
int parentId=valueItem.getParentId();
|
||||
if(parentId!=0){
|
||||
return false;
|
||||
}
|
||||
ResValueMap[] bagItems = valueItem.listResValueMap();
|
||||
if(bagItems==null||bagItems.length==0){
|
||||
return false;
|
||||
}
|
||||
int len=bagItems.length;
|
||||
for(int i=0;i<len;i++){
|
||||
ResValueMap item=bagItems[i];
|
||||
int name = item.getName();
|
||||
int high = (name >> 16) & 0xffff;
|
||||
if(high!=0x0100){
|
||||
return false;
|
||||
}
|
||||
int low = name & 0xffff;
|
||||
PluralsQuantity pq=PluralsQuantity.valueOf((short) low);
|
||||
if(pq==null){
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
@ -32,6 +32,7 @@ import com.reandroid.json.JSONObject;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.*;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
public class TypeBlockArray extends BlockArray<TypeBlock>
|
||||
implements JSONConvert<JSONArray>, Comparator<TypeBlock> {
|
||||
@ -236,6 +237,27 @@ public class TypeBlockArray extends BlockArray<TypeBlock>
|
||||
}
|
||||
};
|
||||
}
|
||||
public Iterator<TypeBlock> iteratorNonEmpty(){
|
||||
return super.iterator(NON_EMPTY_TESTER);
|
||||
}
|
||||
public boolean hasDuplicateResConfig(boolean ignoreEmpty){
|
||||
Set<Integer> uniqueHashSet = new HashSet<>();
|
||||
Iterator<TypeBlock> itr;
|
||||
if(ignoreEmpty){
|
||||
itr = iteratorNonEmpty();
|
||||
}else {
|
||||
itr = iterator(true);
|
||||
}
|
||||
while (itr.hasNext()){
|
||||
Integer hash = itr.next()
|
||||
.getResConfig().hashCode();
|
||||
if(uniqueHashSet.contains(hash)){
|
||||
return true;
|
||||
}
|
||||
uniqueHashSet.add(hash);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
private SpecBlock getSpecBlock(){
|
||||
SpecTypePair parent = getParent(SpecTypePair.class);
|
||||
if(parent != null){
|
||||
@ -383,4 +405,14 @@ public class TypeBlockArray extends BlockArray<TypeBlock>
|
||||
public int compare(TypeBlock typeBlock1, TypeBlock typeBlock2) {
|
||||
return typeBlock1.compareTo(typeBlock2);
|
||||
}
|
||||
|
||||
private static final Predicate<TypeBlock> NON_EMPTY_TESTER = new Predicate<TypeBlock>() {
|
||||
@Override
|
||||
public boolean test(TypeBlock typeBlock) {
|
||||
if(typeBlock == null || typeBlock.isNull()){
|
||||
return false;
|
||||
}
|
||||
return !typeBlock.isEmpty();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
@ -1,21 +1,22 @@
|
||||
/*
|
||||
* Copyright (C) 2022 github.com/REAndroid
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
/*
|
||||
* Copyright (C) 2022 github.com/REAndroid
|
||||
*
|
||||
* 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 com.reandroid.arsc.base;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
|
||||
public abstract class BlockArray<T extends Block> extends BlockContainer<T> implements BlockArrayCreator<T> {
|
||||
@ -273,6 +274,9 @@ public abstract class BlockArray<T extends Block> extends BlockContainer<T> impl
|
||||
public Iterator<T> iterator(boolean skipNullBlock) {
|
||||
return new BlockIterator(skipNullBlock);
|
||||
}
|
||||
public Iterator<T> iterator(Predicate<T> tester) {
|
||||
return new PredicateIterator(tester);
|
||||
}
|
||||
public boolean contains(Object block){
|
||||
T[] items=elementData;
|
||||
if(block==null || items==null){
|
||||
@ -437,14 +441,62 @@ public abstract class BlockArray<T extends Block> extends BlockContainer<T> impl
|
||||
if(!mSkipNullBlock || isFinished()){
|
||||
return;
|
||||
}
|
||||
T item=BlockArray.this.get(mCursor);
|
||||
while (item==null||item.isNull()){
|
||||
T item = BlockArray.this.get(mCursor);
|
||||
while (item == null || item.isNull()){
|
||||
mCursor++;
|
||||
item=BlockArray.this.get(mCursor);
|
||||
item = BlockArray.this.get(mCursor);
|
||||
if(mCursor>=mMaxSize){
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private class PredicateIterator implements Iterator<T> {
|
||||
private int mCursor;
|
||||
private final int mMaxSize;
|
||||
private final Predicate<T> mTester;
|
||||
PredicateIterator(Predicate<T> tester){
|
||||
this.mTester = tester;
|
||||
mCursor = 0;
|
||||
mMaxSize = BlockArray.this.childesCount();
|
||||
}
|
||||
@Override
|
||||
public boolean hasNext() {
|
||||
checkCursor();
|
||||
return hasItems();
|
||||
}
|
||||
@Override
|
||||
public T next() {
|
||||
if(hasItems()){
|
||||
T item=BlockArray.this.get(mCursor);
|
||||
mCursor++;
|
||||
checkCursor();
|
||||
return item;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
private boolean hasItems(){
|
||||
return mCursor < mMaxSize;
|
||||
}
|
||||
private void checkCursor(){
|
||||
if(mTester == null){
|
||||
return;
|
||||
}
|
||||
while (hasItems() && !test(BlockArray.this.get(getCursor()))){
|
||||
mCursor++;
|
||||
}
|
||||
}
|
||||
private int getCursor(){
|
||||
return mCursor;
|
||||
}
|
||||
private boolean test(T item){
|
||||
Predicate<T> tester = mTester;
|
||||
if(tester != null){
|
||||
return tester.test(item);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -74,11 +74,11 @@ public class SpecTypePair extends BlockContainer<Block>
|
||||
return createEntryGroups(false);
|
||||
}
|
||||
public Map<Integer, EntryGroup> createEntryGroups(boolean skipNullEntries){
|
||||
Map<Integer, EntryGroup> map = new HashMap<>();
|
||||
for(TypeBlock typeBlock:listTypeBlocks()){
|
||||
Map<Integer, EntryGroup> map = new LinkedHashMap<>();
|
||||
for(TypeBlock typeBlock : listTypeBlocks()){
|
||||
EntryArray entryArray = typeBlock.getEntryArray();
|
||||
for(Entry entry:entryArray.listItems(skipNullEntries)){
|
||||
if(entry==null){
|
||||
for(Entry entry : entryArray.listItems(skipNullEntries)){
|
||||
if(entry == null){
|
||||
continue;
|
||||
}
|
||||
int id = entry.getResourceId();
|
||||
@ -177,6 +177,13 @@ public class SpecTypePair extends BlockContainer<Block>
|
||||
return mTypeBlockArray.listResConfig();
|
||||
}
|
||||
|
||||
public Iterator<TypeBlock> iteratorNonEmpty(){
|
||||
return mTypeBlockArray.iteratorNonEmpty();
|
||||
}
|
||||
public boolean hasDuplicateResConfig(boolean ignoreEmpty){
|
||||
return mTypeBlockArray.hasDuplicateResConfig(ignoreEmpty);
|
||||
}
|
||||
|
||||
public byte getTypeId(){
|
||||
return mSpecBlock.getTypeId();
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
/*
|
||||
/*
|
||||
* Copyright (C) 2022 github.com/REAndroid
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
@ -32,6 +32,23 @@ public class EntryGroup extends ItemGroup<Entry> {
|
||||
super(ARRAY_CREATOR, String.valueOf(resId));
|
||||
this.resourceId=resId;
|
||||
}
|
||||
public Entry getEntry(ResConfig resConfig){
|
||||
Entry[] items = getItems();
|
||||
if(items == null || resConfig == null){
|
||||
return null;
|
||||
}
|
||||
int length = items.length;
|
||||
for(int i=0; i<length; i++){
|
||||
Entry entry = items[i];
|
||||
if(entry == null || entry.isNull()){
|
||||
continue;
|
||||
}
|
||||
if(resConfig.equals(entry.getResConfig())){
|
||||
return entry;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
public int getResourceId(){
|
||||
return resourceId;
|
||||
}
|
||||
|
@ -160,7 +160,7 @@ public class XMLElement extends XMLNode{
|
||||
}
|
||||
this.mEndPrefix = pfx;
|
||||
}
|
||||
void setIndentScale(float scale){
|
||||
public void setIndentScale(float scale){
|
||||
mIndentScale=scale;
|
||||
}
|
||||
private float getIndentScale(){
|
||||
|
Loading…
x
Reference in New Issue
Block a user