All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.reandroid.apk.ApkModuleXmlDecoder Maven / Gradle / Ivy

/*
 *  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.arsc.chunk.PackageBlock;
import com.reandroid.arsc.chunk.TableBlock;
import com.reandroid.arsc.chunk.xml.AndroidManifestBlock;
import com.reandroid.arsc.chunk.xml.ResXmlDocument;
import com.reandroid.arsc.coder.xml.XmlCoder;
import com.reandroid.utils.io.IOUtil;
import com.reandroid.arsc.value.*;
import com.reandroid.json.JSONObject;
import com.reandroid.xml.XMLFactory;
import org.xmlpull.v1.XmlSerializer;

import java.io.File;
import java.io.IOException;
import java.util.*;
import java.util.function.Predicate;

public class ApkModuleXmlDecoder extends ApkModuleDecoder implements Predicate {
    private final Map> decodedEntries;
    private boolean keepResPath;

    public ApkModuleXmlDecoder(ApkModule apkModule){
        super(apkModule);
        this.decodedEntries = new HashMap<>();
    }
    public void setKeepResPath(boolean keepResPath){
        this.keepResPath = keepResPath;
    }
    public boolean keepResPath() {
        return keepResPath;
    }

    @Override
    void initialize(){
        super.initialize();
        validateResourceNames();
    }
    @Override
    public void decodeResourceTable(File mainDirectory) throws IOException{
        TableBlock tableBlock = getApkModule().getTableBlock();
        decodeTableBlock(mainDirectory, tableBlock);
        decodeResFiles(mainDirectory);
        decodeValues(mainDirectory, tableBlock);
    }
    private void decodeTableBlock(File mainDirectory, TableBlock tableBlock) throws IOException {
        try{
            decodePackageInfo(mainDirectory, tableBlock);
            decodePublicXml(mainDirectory, tableBlock);
            addDecodedPath(TableBlock.FILE_NAME);
        }catch (IOException exception){
            logOrThrow("Error decoding resource table", exception);
        }
    }
    private void decodePackageInfo(File mainDirectory, TableBlock tableBlock) throws IOException {
        for(PackageBlock packageBlock:tableBlock.listPackages()){
            decodePackageInfo(mainDirectory, packageBlock);
        }
    }
    private void decodePackageInfo(File mainDirectory, PackageBlock packageBlock) throws IOException {
        File packageDirectory = toPackageDirectory(mainDirectory, packageBlock);
        File packageJsonFile = new File(packageDirectory, PackageBlock.JSON_FILE_NAME);
        JSONObject jsonObject = packageBlock.toJson(false);
        jsonObject.write(packageJsonFile);
    }
    private void decodeResFiles(File mainDirectory) throws IOException{
        if(keepResPath()){
            logMessage("Res files: " + TableBlock.RES_FILES_DIRECTORY_NAME);
        }else {
            logMessage("Res files: " + TableBlock.DIRECTORY_NAME);
        }
        List resFileList = getApkModule().listResFiles();
        for(ResFile resFile:resFileList){
            decodeResFile(mainDirectory, resFile);
        }
    }
    private void decodeResFile(File mainDirectory, ResFile resFile)
            throws IOException{
        if(resFile.isBinaryXml()){
            try{
                decodeResXml(mainDirectory, resFile);
            }catch (Exception ex){
                logOrThrow("Failed to decode: "
                        + resFile.getFilePath(), ex);
            }
            return;
        }
        String path = resFile.getFilePath();
        if(path.endsWith(".xml")){
            logMessage("Ignore non bin xml: " + path);
            return;
        }
        decodeResRaw(mainDirectory, resFile);
    }
    private void decodeResRaw(File mainDirectory, ResFile resFile)
            throws IOException {
        Entry entry = resFile.pickOne();
        PackageBlock packageBlock = entry.getPackageBlock();

        File file = toDecodeResFile(mainDirectory, resFile, packageBlock);
        InputSource inputSource = resFile.getInputSource();
        logVerbose(inputSource.getAlias());
        inputSource.write(file);
        if(!keepResPath()){
            addDecodedEntry(entry);
        }
        addDecodedPath(inputSource.getAlias());
    }
    private void decodeResXml(File mainDirectory, ResFile resFile)
            throws IOException{
        Entry entry = resFile.pickOne();
        PackageBlock packageBlock = entry.getPackageBlock();

        File file = toDecodeResFile(mainDirectory, resFile, packageBlock);
        InputSource inputSource = resFile.getInputSource();

        logVerbose(inputSource.getAlias());
        serializeXml(packageBlock, resFile.getInputSource(), file);

        if(!keepResPath()){
            addDecodedEntry(entry);
        }
        addDecodedPath(inputSource.getAlias());
    }
    private File toDecodeResFile(File mainDirectory, ResFile resFile, PackageBlock packageBlock){
        String path;
        File dir;
        if(keepResPath()){
            path = resFile.getInputSource().getAlias();
            dir = new File(mainDirectory, TableBlock.RES_FILES_DIRECTORY_NAME);
        }else {
            path = resFile.buildPath(PackageBlock.RES_DIRECTORY_NAME);
            dir = toPackageDirectory(mainDirectory, packageBlock);
            resFile.setFilePath(path);
        }
        path = path.replace('/', File.separatorChar);
        return new File(dir, path);
    }
    private void decodePublicXml(File mainDirectory, TableBlock tableBlock)
            throws IOException{
        for(PackageBlock packageBlock:tableBlock.listPackages()){
            decodePublicXml(mainDirectory, packageBlock);
        }
        if(tableBlock.countPackages() == 0){
            decodeEmptyTable(mainDirectory);
        }
    }
    private void decodePublicXml(File mainDirectory, PackageBlock packageBlock)
            throws IOException {
        File packageDirectory = toPackageDirectory(mainDirectory, packageBlock);
        logMessage("public.xml: "
                + packageBlock.getName() + " -> " + packageDirectory.getName());
        File file = new File(packageDirectory, PackageBlock.RES_DIRECTORY_NAME);
        file = new File(file, PackageBlock.VALUES_DIRECTORY_NAME);
        file = new File(file, PackageBlock.PUBLIC_XML);
        packageBlock.serializePublicXml(file);
    }
    private void decodeEmptyTable(File mainDirectory) throws IOException {
        logMessage("Decoding empty table ...");
        String pkgName = getApkModule().getPackageName();
        if(pkgName == null){
            return;
        }
        File packageDirectory = new File(mainDirectory, TableBlock.DIRECTORY_NAME);
        packageDirectory = new File(packageDirectory, PackageBlock.DIRECTORY_NAME_PREFIX + "1");
        logMessage("Empty public.xml: "
                + packageDirectory.getName());
        File file = new File(packageDirectory, PackageBlock.RES_DIRECTORY_NAME);
        file = new File(file, PackageBlock.VALUES_DIRECTORY_NAME);
        file = new File(file, PackageBlock.PUBLIC_XML);
        PackageBlock packageBlock = new PackageBlock();
        packageBlock.serializePublicXml(file);
    }
    @Override
    public void decodeAndroidManifest(File mainDirectory)
            throws IOException {
        if(containsDecodedPath(AndroidManifestBlock.FILE_NAME)){
            return;
        }
        if(!getApkModule().hasAndroidManifestBlock()){
            logMessage("Don't have: "+ AndroidManifestBlock.FILE_NAME);
            return;
        }
        if(isExcluded(AndroidManifestBlock.FILE_NAME)){
            decodeAndroidManifestBin(mainDirectory);
        }else {
            decodeAndroidManifestXml(mainDirectory);
        }
    }
    private void decodeAndroidManifestXml(File mainDirectory)
            throws IOException {
        AndroidManifestBlock manifestBlock = getApkModule().getAndroidManifestBlock();
        File file = new File(mainDirectory, AndroidManifestBlock.FILE_NAME);
        logMessage("Decoding: " + file.getName());
        PackageBlock packageBlock = manifestBlock.getPackageBlock();
        if(packageBlock == null){
            int packageId = manifestBlock.guessCurrentPackageId();
            TableBlock tableBlock = getApkModule().getTableBlock();
            packageBlock = tableBlock.pickOne(packageId);
            if(packageBlock == null){
                packageBlock = tableBlock.pickOne();
            }
        }
        serializeXml(packageBlock, manifestBlock, file);
        addDecodedPath(AndroidManifestBlock.FILE_NAME);
    }
    private void decodeAndroidManifestBin(File mainDirectory)
            throws IOException {
        File file = new File(mainDirectory, AndroidManifestBlock.FILE_NAME_BIN);
        logMessage("Decode manifest binary: " + file.getName());
        ApkModule apkModule = getApkModule();
        InputSource inputSource = apkModule.getManifestOriginalSource();
        if(inputSource == null){
            inputSource = apkModule.getInputSource(AndroidManifestBlock.FILE_NAME);
        }
        inputSource.write(file);
        addDecodedPath(AndroidManifestBlock.FILE_NAME);
    }
    private void serializeXml(PackageBlock packageBlock, ResXmlDocument document, File outFile)
            throws IOException {
        if(packageBlock != null && document.getPackageBlock() == null){
            document.setPackageBlock(packageBlock);
        }
        XmlSerializer serializer = XMLFactory.newSerializer(outFile);
        document.serialize(serializer);
        IOUtil.close(serializer);
    }
    private void serializeXml(PackageBlock packageBlock, InputSource inputSource, File outFile)
            throws IOException {
        ResXmlDocument document = new ResXmlDocument();
        document.readBytes(inputSource.openStream());
        document.setPackageBlock(packageBlock);
        serializeXml(packageBlock, document, outFile);
    }
    private void addDecodedEntry(Entry entry){
        if(entry.isNull()){
            return;
        }
        int resourceId= entry.getResourceId();
        Set resConfigSet = decodedEntries.get(resourceId);
        if(resConfigSet==null){
            resConfigSet=new HashSet<>();
            decodedEntries.put(resourceId, resConfigSet);
        }
        resConfigSet.add(entry.getResConfig());
    }
    private boolean containsDecodedEntry(Entry entry){
        Set resConfigSet = decodedEntries.get(entry.getResourceId());
        if(resConfigSet == null){
            return false;
        }
        return resConfigSet.contains(entry.getResConfig());
    }
    private void decodeValues(File mainDirectory, TableBlock tableBlock) throws IOException {
        File resourcesDir = new File(mainDirectory, TableBlock.DIRECTORY_NAME);
        XmlCoder xmlCoder = XmlCoder.getInstance();
        xmlCoder.VALUES_XML.decodeTable(resourcesDir, tableBlock, this);
    }
    @Override
    public boolean test(Entry entry) {
        return containsDecodedEntry(entry);
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy