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);
|
this(apkModule, false);
|
||||||
}
|
}
|
||||||
public void sanitizeFilePaths(){
|
public void sanitizeFilePaths(){
|
||||||
PathSanitizer sanitizer = new PathSanitizer(apkModule);
|
PathSanitizer sanitizer = PathSanitizer.create(apkModule);
|
||||||
sanitizer.sanitize();
|
sanitizer.sanitize();
|
||||||
}
|
}
|
||||||
public File writeToDirectory(File dir) throws IOException {
|
public File writeToDirectory(File dir) throws IOException {
|
||||||
|
@ -120,14 +120,7 @@ public class ApkModule implements ApkFile {
|
|||||||
}
|
}
|
||||||
return getAndroidManifestBlock().getSplit();
|
return getAndroidManifestBlock().getSplit();
|
||||||
}
|
}
|
||||||
public FrameworkApk initializeAndroidFramework() throws IOException {
|
public FrameworkApk initializeAndroidFramework(TableBlock tableBlock, Integer version) throws IOException {
|
||||||
if(!hasTableBlock()){
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
Integer version = getAndroidFrameworkVersion();
|
|
||||||
return initializeAndroidFramework(getTableBlock(false), version);
|
|
||||||
}
|
|
||||||
private FrameworkApk initializeAndroidFramework(TableBlock tableBlock, Integer version) throws IOException {
|
|
||||||
if(tableBlock == null || isAndroid(tableBlock)){
|
if(tableBlock == null || isAndroid(tableBlock)){
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
@ -1,115 +1,97 @@
|
|||||||
/*
|
/*
|
||||||
* 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;
|
package com.reandroid.apk;
|
||||||
|
|
||||||
import com.reandroid.apk.xmldecoder.ResXmlDocumentSerializer;
|
import com.reandroid.apk.xmldecoder.*;
|
||||||
import com.reandroid.archive.InputSource;
|
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.PackageBlock;
|
||||||
import com.reandroid.arsc.chunk.TableBlock;
|
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.AndroidManifestBlock;
|
||||||
import com.reandroid.arsc.chunk.xml.ResXmlDocument;
|
import com.reandroid.arsc.chunk.xml.ResXmlDocument;
|
||||||
import com.reandroid.arsc.container.SpecTypePair;
|
import com.reandroid.arsc.container.SpecTypePair;
|
||||||
import com.reandroid.arsc.decoder.ValueDecoder;
|
|
||||||
import com.reandroid.arsc.value.*;
|
import com.reandroid.arsc.value.*;
|
||||||
import com.reandroid.common.EntryStore;
|
|
||||||
import com.reandroid.json.JSONObject;
|
import com.reandroid.json.JSONObject;
|
||||||
import com.reandroid.xml.XMLAttribute;
|
|
||||||
import com.reandroid.xml.XMLDocument;
|
import com.reandroid.xml.XMLDocument;
|
||||||
import com.reandroid.xml.XMLElement;
|
|
||||||
import com.reandroid.xml.XMLException;
|
|
||||||
import org.xmlpull.v1.XmlPullParserException;
|
import org.xmlpull.v1.XmlPullParserException;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.FileOutputStream;
|
import java.io.FileOutputStream;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
|
||||||
import java.util.*;
|
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 ApkModule apkModule;
|
||||||
private final Map<Integer, Set<ResConfig>> decodedEntries;
|
private final Map<Integer, Set<ResConfig>> decodedEntries;
|
||||||
private XMLBagDecoder xmlBagDecoder;
|
|
||||||
private final Set<String> mDecodedPaths;
|
|
||||||
private ResXmlDocumentSerializer documentSerializer;
|
private ResXmlDocumentSerializer documentSerializer;
|
||||||
private boolean useAndroidSerializer;
|
private XMLEntryDecoderSerializer entrySerializer;
|
||||||
|
|
||||||
|
|
||||||
public ApkModuleXmlDecoder(ApkModule apkModule){
|
public ApkModuleXmlDecoder(ApkModule apkModule){
|
||||||
this.apkModule=apkModule;
|
super();
|
||||||
|
this.apkModule = apkModule;
|
||||||
this.decodedEntries = new HashMap<>();
|
this.decodedEntries = new HashMap<>();
|
||||||
this.mDecodedPaths = new HashSet<>();
|
super.setApkLogger(apkModule.getApkLogger());
|
||||||
this.useAndroidSerializer = true;
|
|
||||||
}
|
|
||||||
public void setUseAndroidSerializer(boolean useAndroidSerializer) {
|
|
||||||
this.useAndroidSerializer = useAndroidSerializer;
|
|
||||||
}
|
}
|
||||||
public void sanitizeFilePaths(){
|
public void sanitizeFilePaths(){
|
||||||
sanitizeFilePaths(false);
|
PathSanitizer sanitizer = PathSanitizer.create(apkModule);
|
||||||
}
|
|
||||||
public void sanitizeFilePaths(boolean sanitizeResourceFiles){
|
|
||||||
PathSanitizer sanitizer = new PathSanitizer(apkModule, sanitizeResourceFiles);
|
|
||||||
sanitizer.sanitize();
|
sanitizer.sanitize();
|
||||||
}
|
}
|
||||||
public void decodeTo(File outDir)
|
@Override
|
||||||
throws IOException, XMLException {
|
void onDecodeTo(File outDir) throws IOException{
|
||||||
this.decodedEntries.clear();
|
this.decodedEntries.clear();
|
||||||
logMessage("Decoding ...");
|
logMessage("Decoding ...");
|
||||||
|
|
||||||
|
if(!apkModule.hasTableBlock()){
|
||||||
|
logOrThrow(null, new IOException("Don't have resource table"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
decodeUncompressedFiles(outDir);
|
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, apkModule.getAndroidManifestBlock());
|
||||||
|
decodeTableBlock(outDir, tableBlock);
|
||||||
decodeAndroidManifest(outDir);
|
|
||||||
|
|
||||||
addDecodedPath(TableBlock.FILE_NAME);
|
|
||||||
|
|
||||||
logMessage("Decoding resource files ...");
|
logMessage("Decoding resource files ...");
|
||||||
List<ResFile> resFileList=apkModule.listResFiles();
|
List<ResFile> resFileList = apkModule.listResFiles();
|
||||||
for(ResFile resFile:resFileList){
|
for(ResFile resFile:resFileList){
|
||||||
decodeResFile(outDir, resFile);
|
decodeResFile(outDir, resFile);
|
||||||
}
|
}
|
||||||
decodeValues(tableBlock, outDir, tableBlock);
|
decodeValues(outDir, tableBlock);
|
||||||
|
|
||||||
extractRootFiles(outDir);
|
extractRootFiles(outDir);
|
||||||
|
|
||||||
writePathMap(outDir);
|
writePathMap(outDir, apkModule.getApkArchive().listInputSources());
|
||||||
|
|
||||||
dumpSignatures(outDir);
|
dumpSignatures(outDir, apkModule.getApkSignatureBlock());
|
||||||
}
|
}
|
||||||
private void dumpSignatures(File outDir) throws IOException {
|
private void decodeTableBlock(File outDir, TableBlock tableBlock) throws IOException {
|
||||||
ApkSignatureBlock signatureBlock = apkModule.getApkSignatureBlock();
|
try{
|
||||||
if(signatureBlock == null){
|
decodePackageInfo(outDir, tableBlock);
|
||||||
return;
|
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 {
|
private void decodePackageInfo(File outDir, TableBlock tableBlock) throws IOException {
|
||||||
for(PackageBlock packageBlock:tableBlock.listPackages()){
|
for(PackageBlock packageBlock:tableBlock.listPackages()){
|
||||||
@ -123,7 +105,7 @@ import java.util.*;
|
|||||||
jsonObject.write(packageJsonFile);
|
jsonObject.write(packageJsonFile);
|
||||||
}
|
}
|
||||||
private void decodeUncompressedFiles(File outDir)
|
private void decodeUncompressedFiles(File outDir)
|
||||||
throws IOException {
|
throws IOException {
|
||||||
File file=new File(outDir, UncompressedFiles.JSON_FILE);
|
File file=new File(outDir, UncompressedFiles.JSON_FILE);
|
||||||
UncompressedFiles uncompressedFiles = apkModule.getUncompressedFiles();
|
UncompressedFiles uncompressedFiles = apkModule.getUncompressedFiles();
|
||||||
uncompressedFiles.toJson().write(file);
|
uncompressedFiles.toJson().write(file);
|
||||||
@ -158,7 +140,7 @@ import java.util.*;
|
|||||||
addDecodedEntry(entry);
|
addDecodedEntry(entry);
|
||||||
}
|
}
|
||||||
private void decodeResXml(File outDir, ResFile resFile)
|
private void decodeResXml(File outDir, ResFile resFile)
|
||||||
throws IOException{
|
throws IOException{
|
||||||
Entry entry = resFile.pickOne();
|
Entry entry = resFile.pickOne();
|
||||||
PackageBlock packageBlock = entry.getPackageBlock();
|
PackageBlock packageBlock = entry.getPackageBlock();
|
||||||
|
|
||||||
@ -181,11 +163,8 @@ import java.util.*;
|
|||||||
}
|
}
|
||||||
return documentSerializer;
|
return documentSerializer;
|
||||||
}
|
}
|
||||||
private TableBlock getTableBlock(){
|
|
||||||
return apkModule.getTableBlock();
|
|
||||||
}
|
|
||||||
private void decodePublicXml(TableBlock tableBlock, File outDir)
|
private void decodePublicXml(TableBlock tableBlock, File outDir)
|
||||||
throws IOException{
|
throws IOException{
|
||||||
for(PackageBlock packageBlock:tableBlock.listPackages()){
|
for(PackageBlock packageBlock:tableBlock.listPackages()){
|
||||||
decodePublicXml(packageBlock, outDir);
|
decodePublicXml(packageBlock, outDir);
|
||||||
}
|
}
|
||||||
@ -207,7 +186,7 @@ import java.util.*;
|
|||||||
xmlDocument.save(pubXml, false);
|
xmlDocument.save(pubXml, false);
|
||||||
}
|
}
|
||||||
private void decodePublicXml(PackageBlock packageBlock, File outDir)
|
private void decodePublicXml(PackageBlock packageBlock, File outDir)
|
||||||
throws IOException {
|
throws IOException {
|
||||||
String packageDirName=getPackageDirName(packageBlock);
|
String packageDirName=getPackageDirName(packageBlock);
|
||||||
logMessage("Decoding public.xml: "+packageDirName);
|
logMessage("Decoding public.xml: "+packageDirName);
|
||||||
File file=new File(outDir, packageDirName);
|
File file=new File(outDir, packageDirName);
|
||||||
@ -218,15 +197,14 @@ import java.util.*;
|
|||||||
resourceIds.loadPackageBlock(packageBlock);
|
resourceIds.loadPackageBlock(packageBlock);
|
||||||
resourceIds.writeXml(file);
|
resourceIds.writeXml(file);
|
||||||
}
|
}
|
||||||
private void decodeAndroidManifest(File outDir)
|
private void decodeAndroidManifest(File outDir, AndroidManifestBlock manifestBlock)
|
||||||
throws IOException {
|
throws IOException {
|
||||||
if(!apkModule.hasAndroidManifestBlock()){
|
if(!apkModule.hasAndroidManifestBlock()){
|
||||||
logMessage("Don't have: "+ AndroidManifestBlock.FILE_NAME);
|
logMessage("Don't have: "+ AndroidManifestBlock.FILE_NAME);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
File file=new File(outDir, AndroidManifestBlock.FILE_NAME);
|
File file=new File(outDir, AndroidManifestBlock.FILE_NAME);
|
||||||
logMessage("Decoding: "+file.getName());
|
logMessage("Decoding: "+file.getName());
|
||||||
AndroidManifestBlock manifestBlock=apkModule.getAndroidManifestBlock();
|
|
||||||
int currentPackageId = manifestBlock.guessCurrentPackageId();
|
int currentPackageId = manifestBlock.guessCurrentPackageId();
|
||||||
serializeXml(currentPackageId, manifestBlock, file);
|
serializeXml(currentPackageId, manifestBlock, file);
|
||||||
addDecodedPath(AndroidManifestBlock.FILE_NAME);
|
addDecodedPath(AndroidManifestBlock.FILE_NAME);
|
||||||
@ -235,50 +213,28 @@ import java.util.*;
|
|||||||
throws IOException {
|
throws IOException {
|
||||||
XMLNamespaceValidator namespaceValidator = new XMLNamespaceValidator(document);
|
XMLNamespaceValidator namespaceValidator = new XMLNamespaceValidator(document);
|
||||||
namespaceValidator.validate();
|
namespaceValidator.validate();
|
||||||
if(useAndroidSerializer){
|
ResXmlDocumentSerializer serializer = getDocumentSerializer();
|
||||||
ResXmlDocumentSerializer serializer = getDocumentSerializer();
|
if(currentPackageId != 0){
|
||||||
if(currentPackageId != 0){
|
serializer.getDecoder().setCurrentPackageId(currentPackageId);
|
||||||
serializer.getDecoder().setCurrentPackageId(currentPackageId);
|
}
|
||||||
}
|
try {
|
||||||
try {
|
serializer.write(document, outFile);
|
||||||
serializer.write(document, outFile);
|
} catch (XmlPullParserException ex) {
|
||||||
} catch (XmlPullParserException ex) {
|
throw new IOException("Error: "+outFile.getName(), 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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
private void serializeXml(int currentPackageId, InputSource inputSource, File outFile)
|
private void serializeXml(int currentPackageId, InputSource inputSource, File outFile)
|
||||||
throws IOException {
|
throws IOException {
|
||||||
|
ResXmlDocumentSerializer serializer = getDocumentSerializer();
|
||||||
if(useAndroidSerializer){
|
if(currentPackageId != 0){
|
||||||
ResXmlDocumentSerializer serializer = getDocumentSerializer();
|
serializer.getDecoder().setCurrentPackageId(currentPackageId);
|
||||||
if(currentPackageId != 0){
|
}
|
||||||
serializer.getDecoder().setCurrentPackageId(currentPackageId);
|
try {
|
||||||
}
|
serializer.write(inputSource, outFile);
|
||||||
try {
|
} catch (XmlPullParserException ex) {
|
||||||
serializer.write(inputSource, outFile);
|
throw new IOException("Error: "+outFile.getName(), ex);
|
||||||
} 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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
private void addDecodedEntry(Entry entry){
|
private void addDecodedEntry(Entry entry){
|
||||||
if(entry.isNull()){
|
if(entry.isNull()){
|
||||||
return;
|
return;
|
||||||
@ -298,71 +254,27 @@ import java.util.*;
|
|||||||
}
|
}
|
||||||
return resConfigSet.contains(entry.getResConfig());
|
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()){
|
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: "
|
logMessage("Decoding values: "
|
||||||
+packageBlock.getIndex()
|
+packageBlock.getIndex()
|
||||||
+"-"+packageBlock.getName());
|
+"-"+packageBlock.getName());
|
||||||
|
|
||||||
packageBlock.sortTypes();
|
packageBlock.sortTypes();
|
||||||
|
|
||||||
for(SpecTypePair specTypePair: packageBlock.listSpecTypePairs()){
|
File pkgDir = new File(outDir, getPackageDirName(packageBlock));
|
||||||
for(TypeBlock typeBlock:specTypePair.listTypeBlocks()){
|
File resDir = new File(pkgDir, ApkUtil.RES_DIR_NAME);
|
||||||
decodeValues(entryStore, outDir, typeBlock);
|
|
||||||
}
|
for(SpecTypePair specTypePair : packageBlock.listSpecTypePairs()){
|
||||||
|
decodeValues(resDir, specTypePair);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
private void decodeValues(EntryStore entryStore, File outDir, TypeBlock typeBlock) throws IOException {
|
private void decodeValues(File outDir, SpecTypePair specTypePair) throws IOException {
|
||||||
XMLDocument xmlDocument = new XMLDocument("resources");
|
entrySerializer.decode(outDir, specTypePair);
|
||||||
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 String getPackageDirName(PackageBlock packageBlock){
|
private String getPackageDirName(PackageBlock packageBlock){
|
||||||
String name = ApkUtil.sanitizeForFileName(packageBlock.getName());
|
String name = ApkUtil.sanitizeForFileName(packageBlock.getName());
|
||||||
@ -394,29 +306,8 @@ import java.util.*;
|
|||||||
inputSource.write(outputStream);
|
inputSource.write(outputStream);
|
||||||
outputStream.close();
|
outputStream.close();
|
||||||
}
|
}
|
||||||
private boolean containsDecodedPath(String path){
|
@Override
|
||||||
return mDecodedPaths.contains(path);
|
public boolean test(Entry entry) {
|
||||||
}
|
return !containsDecodedEntry(entry);
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -118,7 +118,7 @@ public class PathMap implements JSONConvert<JSONArray> {
|
|||||||
}
|
}
|
||||||
add(archive.listInputSources());
|
add(archive.listInputSources());
|
||||||
}
|
}
|
||||||
public void add(Collection<InputSource> sources){
|
public void add(Collection<? extends InputSource> sources){
|
||||||
if(sources==null){
|
if(sources==null){
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -1,57 +1,62 @@
|
|||||||
/*
|
/*
|
||||||
* 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;
|
package com.reandroid.apk;
|
||||||
|
|
||||||
import com.reandroid.archive.InputSource;
|
import com.reandroid.archive.InputSource;
|
||||||
|
|
||||||
|
import java.util.Collection;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.List;
|
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
public class PathSanitizer {
|
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 APKLogger apkLogger;
|
||||||
private final Set<String> mSanitizedPaths;
|
private final Set<String> mSanitizedPaths;
|
||||||
private final boolean sanitizeResourceFiles;
|
public PathSanitizer(Collection<? extends InputSource> sourceList, boolean sanitizeResourceFiles){
|
||||||
public PathSanitizer(ApkModule apkModule, boolean sanitizeResourceFiles){
|
this.sourceList = sourceList;
|
||||||
this.apkModule = apkModule;
|
|
||||||
this.apkLogger = apkModule.getApkLogger();
|
|
||||||
this.mSanitizedPaths = new HashSet<>();
|
this.mSanitizedPaths = new HashSet<>();
|
||||||
this.sanitizeResourceFiles = sanitizeResourceFiles;
|
this.sanitizeResourceFiles = sanitizeResourceFiles;
|
||||||
}
|
}
|
||||||
public PathSanitizer(ApkModule apkModule){
|
public PathSanitizer(Collection<? extends InputSource> sourceList){
|
||||||
this(apkModule, false);
|
this(sourceList, false);
|
||||||
}
|
}
|
||||||
public void sanitize(){
|
public void sanitize(){
|
||||||
mSanitizedPaths.clear();
|
mSanitizedPaths.clear();
|
||||||
logMessage("Sanitizing paths ...");
|
logMessage("Sanitizing paths ...");
|
||||||
sanitizeResFiles();
|
sanitizeResFiles();
|
||||||
List<InputSource> sourceList = apkModule.getApkArchive().listInputSources();
|
|
||||||
for(InputSource inputSource:sourceList){
|
for(InputSource inputSource:sourceList){
|
||||||
sanitize(inputSource, 1, false);
|
sanitize(inputSource, 1, false);
|
||||||
}
|
}
|
||||||
logMessage("DONE = "+mSanitizedPaths.size());
|
logMessage("DONE = "+mSanitizedPaths.size());
|
||||||
}
|
}
|
||||||
|
public void setResourceFileList(Collection<ResFile> resFileList){
|
||||||
|
this.resFileList = resFileList;
|
||||||
|
}
|
||||||
private void sanitizeResFiles(){
|
private void sanitizeResFiles(){
|
||||||
|
Collection<ResFile> resFileList = this.resFileList;
|
||||||
|
if(resFileList == null){
|
||||||
|
return;
|
||||||
|
}
|
||||||
boolean sanitizeRes = this.sanitizeResourceFiles;
|
boolean sanitizeRes = this.sanitizeResourceFiles;
|
||||||
Set<String> sanitizedPaths = this.mSanitizedPaths;
|
Set<String> sanitizedPaths = this.mSanitizedPaths;
|
||||||
if(sanitizeRes){
|
if(sanitizeRes){
|
||||||
logMessage("Sanitizing resource files ...");
|
logMessage("Sanitizing resource files ...");
|
||||||
}
|
}
|
||||||
List<ResFile> resFileList = apkModule.listResFiles();
|
|
||||||
for(ResFile resFile:resFileList){
|
for(ResFile resFile:resFileList){
|
||||||
if(sanitizeRes){
|
if(sanitizeRes){
|
||||||
sanitize(resFile);
|
sanitize(resFile);
|
||||||
@ -122,13 +127,13 @@ public class PathSanitizer {
|
|||||||
private String getLogTag(){
|
private String getLogTag(){
|
||||||
return "[SANITIZE]: ";
|
return "[SANITIZE]: ";
|
||||||
}
|
}
|
||||||
private void logMessage(String msg){
|
void logMessage(String msg){
|
||||||
APKLogger logger = this.apkLogger;
|
APKLogger logger = this.apkLogger;
|
||||||
if(logger!=null){
|
if(logger!=null){
|
||||||
logger.logMessage(getLogTag()+msg);
|
logger.logMessage(getLogTag()+msg);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
private void logVerbose(String msg){
|
void logVerbose(String msg){
|
||||||
APKLogger logger = this.apkLogger;
|
APKLogger logger = this.apkLogger;
|
||||||
if(logger!=null){
|
if(logger!=null){
|
||||||
logger.logVerbose(getLogTag()+msg);
|
logger.logVerbose(getLogTag()+msg);
|
||||||
@ -201,6 +206,14 @@ public class PathSanitizer {
|
|||||||
|| (ch >= 'a' && ch <= 'z');
|
|| (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_NAME_LENGTH = 75;
|
||||||
private static final int MAX_PATH_LENGTH = 100;
|
private static final int MAX_PATH_LENGTH = 100;
|
||||||
}
|
}
|
||||||
|
@ -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");
|
||||||
@ -16,9 +16,10 @@
|
|||||||
package com.reandroid.apk;
|
package com.reandroid.apk;
|
||||||
|
|
||||||
import com.reandroid.arsc.item.StringItem;
|
import com.reandroid.arsc.item.StringItem;
|
||||||
import com.reandroid.xml.XMLElement;
|
import com.reandroid.xml.*;
|
||||||
|
|
||||||
public class XmlHelper {
|
public class XmlHelper {
|
||||||
|
|
||||||
public static void setTextContent(XMLElement element, StringItem stringItem){
|
public static void setTextContent(XMLElement element, StringItem stringItem){
|
||||||
if(stringItem==null){
|
if(stringItem==null){
|
||||||
element.clearChildNodes();
|
element.clearChildNodes();
|
||||||
@ -37,4 +38,6 @@ public class XmlHelper {
|
|||||||
}
|
}
|
||||||
return typeName;
|
return typeName;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static final String RESOURCES_TAG = "resources";
|
||||||
}
|
}
|
||||||
|
@ -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");
|
||||||
@ -19,14 +19,9 @@ import com.reandroid.arsc.value.ResTableMapEntry;
|
|||||||
import com.reandroid.common.EntryStore;
|
import com.reandroid.common.EntryStore;
|
||||||
import com.reandroid.xml.XMLElement;
|
import com.reandroid.xml.XMLElement;
|
||||||
|
|
||||||
abstract class BagDecoder {
|
abstract class BagDecoder<OUTPUT> extends DecoderTableEntry<ResTableMapEntry, OUTPUT> {
|
||||||
private final EntryStore entryStore;
|
|
||||||
public BagDecoder(EntryStore entryStore){
|
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);
|
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
|
* 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");
|
||||||
|
@ -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
|
* 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");
|
||||||
@ -15,36 +15,25 @@
|
|||||||
*/
|
*/
|
||||||
package com.reandroid.apk.xmldecoder;
|
package com.reandroid.apk.xmldecoder;
|
||||||
|
|
||||||
import com.reandroid.arsc.array.ResValueMapArray;
|
|
||||||
import com.reandroid.arsc.value.ResTableMapEntry;
|
import com.reandroid.arsc.value.ResTableMapEntry;
|
||||||
import com.reandroid.common.EntryStore;
|
import com.reandroid.common.EntryStore;
|
||||||
import com.reandroid.xml.XMLElement;
|
import com.reandroid.xml.XMLElement;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.io.IOException;
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
|
@Deprecated
|
||||||
public class XMLBagDecoder {
|
public class XMLBagDecoder {
|
||||||
private final EntryStore entryStore;
|
private final DecoderResTableEntryMap<XMLElement> mDocumentDecoder;
|
||||||
private final List<BagDecoder> decoderList;
|
private final EntryWriterElement mWriter;
|
||||||
private final XMLCommonBagDecoder commonBagDecoder;
|
|
||||||
public XMLBagDecoder(EntryStore entryStore){
|
public XMLBagDecoder(EntryStore entryStore){
|
||||||
this.entryStore=entryStore;
|
mDocumentDecoder = new DecoderResTableEntryMap<>(entryStore);
|
||||||
this.decoderList=new ArrayList<>();
|
mWriter = new EntryWriterElement();
|
||||||
this.decoderList.add(new XMLAttrDecoder(entryStore));
|
|
||||||
this.decoderList.add(new XMLPluralsDecoder(entryStore));
|
|
||||||
this.decoderList.add(new XMLArrayDecoder(entryStore));
|
|
||||||
this.commonBagDecoder = new XMLCommonBagDecoder(entryStore);
|
|
||||||
}
|
}
|
||||||
public void decode(ResTableMapEntry mapEntry, XMLElement parentElement){
|
public void decode(ResTableMapEntry mapEntry, XMLElement parentElement){
|
||||||
BagDecoder bagDecoder=getFor(mapEntry);
|
try {
|
||||||
bagDecoder.decode(mapEntry, parentElement);
|
XMLElement child = mDocumentDecoder.decode(mapEntry, mWriter);
|
||||||
}
|
parentElement.addChild(child);
|
||||||
private BagDecoder getFor(ResTableMapEntry mapEntry){
|
} catch (IOException exception) {
|
||||||
for(BagDecoder bagDecoder:decoderList){
|
|
||||||
if(bagDecoder.canDecode(mapEntry)){
|
|
||||||
return bagDecoder;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
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.io.IOException;
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
|
import java.util.function.Predicate;
|
||||||
|
|
||||||
public class TypeBlockArray extends BlockArray<TypeBlock>
|
public class TypeBlockArray extends BlockArray<TypeBlock>
|
||||||
implements JSONConvert<JSONArray>, Comparator<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(){
|
private SpecBlock getSpecBlock(){
|
||||||
SpecTypePair parent = getParent(SpecTypePair.class);
|
SpecTypePair parent = getParent(SpecTypePair.class);
|
||||||
if(parent != null){
|
if(parent != null){
|
||||||
@ -383,4 +405,14 @@ public class TypeBlockArray extends BlockArray<TypeBlock>
|
|||||||
public int compare(TypeBlock typeBlock1, TypeBlock typeBlock2) {
|
public int compare(TypeBlock typeBlock1, TypeBlock typeBlock2) {
|
||||||
return typeBlock1.compareTo(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
|
* 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.arsc.base;
|
package com.reandroid.arsc.base;
|
||||||
|
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
|
import java.util.function.Predicate;
|
||||||
|
|
||||||
|
|
||||||
public abstract class BlockArray<T extends Block> extends BlockContainer<T> implements BlockArrayCreator<T> {
|
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) {
|
public Iterator<T> iterator(boolean skipNullBlock) {
|
||||||
return new BlockIterator(skipNullBlock);
|
return new BlockIterator(skipNullBlock);
|
||||||
}
|
}
|
||||||
|
public Iterator<T> iterator(Predicate<T> tester) {
|
||||||
|
return new PredicateIterator(tester);
|
||||||
|
}
|
||||||
public boolean contains(Object block){
|
public boolean contains(Object block){
|
||||||
T[] items=elementData;
|
T[] items=elementData;
|
||||||
if(block==null || items==null){
|
if(block==null || items==null){
|
||||||
@ -437,14 +441,62 @@ public abstract class BlockArray<T extends Block> extends BlockContainer<T> impl
|
|||||||
if(!mSkipNullBlock || isFinished()){
|
if(!mSkipNullBlock || isFinished()){
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
T item=BlockArray.this.get(mCursor);
|
T item = BlockArray.this.get(mCursor);
|
||||||
while (item==null||item.isNull()){
|
while (item == null || item.isNull()){
|
||||||
mCursor++;
|
mCursor++;
|
||||||
item=BlockArray.this.get(mCursor);
|
item = BlockArray.this.get(mCursor);
|
||||||
if(mCursor>=mMaxSize){
|
if(mCursor>=mMaxSize){
|
||||||
break;
|
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);
|
return createEntryGroups(false);
|
||||||
}
|
}
|
||||||
public Map<Integer, EntryGroup> createEntryGroups(boolean skipNullEntries){
|
public Map<Integer, EntryGroup> createEntryGroups(boolean skipNullEntries){
|
||||||
Map<Integer, EntryGroup> map = new HashMap<>();
|
Map<Integer, EntryGroup> map = new LinkedHashMap<>();
|
||||||
for(TypeBlock typeBlock:listTypeBlocks()){
|
for(TypeBlock typeBlock : listTypeBlocks()){
|
||||||
EntryArray entryArray = typeBlock.getEntryArray();
|
EntryArray entryArray = typeBlock.getEntryArray();
|
||||||
for(Entry entry:entryArray.listItems(skipNullEntries)){
|
for(Entry entry : entryArray.listItems(skipNullEntries)){
|
||||||
if(entry==null){
|
if(entry == null){
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
int id = entry.getResourceId();
|
int id = entry.getResourceId();
|
||||||
@ -177,6 +177,13 @@ public class SpecTypePair extends BlockContainer<Block>
|
|||||||
return mTypeBlockArray.listResConfig();
|
return mTypeBlockArray.listResConfig();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Iterator<TypeBlock> iteratorNonEmpty(){
|
||||||
|
return mTypeBlockArray.iteratorNonEmpty();
|
||||||
|
}
|
||||||
|
public boolean hasDuplicateResConfig(boolean ignoreEmpty){
|
||||||
|
return mTypeBlockArray.hasDuplicateResConfig(ignoreEmpty);
|
||||||
|
}
|
||||||
|
|
||||||
public byte getTypeId(){
|
public byte getTypeId(){
|
||||||
return mSpecBlock.getTypeId();
|
return mSpecBlock.getTypeId();
|
||||||
}
|
}
|
||||||
|
@ -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");
|
||||||
@ -32,6 +32,23 @@ public class EntryGroup extends ItemGroup<Entry> {
|
|||||||
super(ARRAY_CREATOR, String.valueOf(resId));
|
super(ARRAY_CREATOR, String.valueOf(resId));
|
||||||
this.resourceId=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(){
|
public int getResourceId(){
|
||||||
return resourceId;
|
return resourceId;
|
||||||
}
|
}
|
||||||
|
@ -160,7 +160,7 @@ public class XMLElement extends XMLNode{
|
|||||||
}
|
}
|
||||||
this.mEndPrefix = pfx;
|
this.mEndPrefix = pfx;
|
||||||
}
|
}
|
||||||
void setIndentScale(float scale){
|
public void setIndentScale(float scale){
|
||||||
mIndentScale=scale;
|
mIndentScale=scale;
|
||||||
}
|
}
|
||||||
private float getIndentScale(){
|
private float getIndentScale(){
|
||||||
|
Loading…
x
Reference in New Issue
Block a user