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

net.dongliu.apk.parser.utils.ParseUtils Maven / Gradle / Ivy

The newest version!
package net.dongliu.apk.parser.utils;

import net.dongliu.apk.parser.bean.Locales;
import net.dongliu.apk.parser.exception.ParserException;
import net.dongliu.apk.parser.io.TellableInputStream;
import net.dongliu.apk.parser.parser.StringPoolEntry;
import net.dongliu.apk.parser.struct.*;
import net.dongliu.apk.parser.struct.resource.*;

import java.io.IOException;
import java.util.Arrays;
import java.util.List;
import java.util.Locale;

/**
 * @author dongliu
 */
public class ParseUtils {


    /**
     * read string from input stream. if get EOF before read enough data, throw IOException.
     */
    public static String readString(TellableInputStream in, StringEncoding encoding)
            throws IOException {
        if (encoding == StringEncoding.UTF8) {
            //  The lengths are encoded in the same way as for the 16-bit format
            // but using 8-bit rather than 16-bit integers.
            int strLen = readLen(in);
            int bytesLen = readLen(in);
            byte[] bytes = in.readBytes(bytesLen);
            String str = new String(bytes, "UTF-8");
            // zero
            int trailling = in.readUByte();
            return str;

        } else {
            // The length is encoded as either one or two 16-bit integers as per the commentRef...
            int strLen = readLen16(in);
            String str = in.readStringUTF16(strLen);
            // zero
            int trailling = in.readUShort();
            return str;
        }
    }

    /**
     * read utf-16 encoding str, use zero char to end str.
     *
     * @param in
     * @param strLen
     * @return
     * @throws IOException
     */
    public static String readStringUTF16(TellableInputStream in, int strLen) throws IOException {
        String str = in.readStringUTF16(strLen);
        for (int i = 0; i < str.length(); i++) {
            char c = str.charAt(i);
            if (c == 0) {
                return str.substring(0, i);
            }
        }
        return str;
    }

    /**
     * read encoding len.
     * see Stringpool.cpp ENCODE_LENGTH
     *
     * @param in
     * @return
     * @throws IOException
     */
    private static int readLen(TellableInputStream in) throws IOException {
        int len = 0;
        int i = in.read();
        if ((i & 0x80) != 0) {
            //read one more byte.
            len |= (i & 0x7f) << 7;
            len += in.read();
        } else {
            len = i;
        }
        return len;
    }

    /**
     * read encoding len.
     * see Stringpool.cpp ENCODE_LENGTH
     *
     * @param in
     * @return
     * @throws IOException
     */
    private static int readLen16(TellableInputStream in) throws IOException {
        int len = 0;
        int i = in.readUShort();
        if ((i & 0x8000) != 0) {
            len |= (i & 0x7fff) << 15;
            len += in.readUShort();
        } else {
            len = i;
        }
        return len;
    }


    /**
     * read String pool, for apk binary xml file and resource table.
     *
     * @param in
     * @param stringPoolHeader
     * @return
     * @throws IOException
     */
    public static StringPool readStringPool(TellableInputStream in,
                                            StringPoolHeader stringPoolHeader) throws IOException {

        long beginPos = in.tell();
        long[] offsets = new long[(int) stringPoolHeader.stringCount];
        // read strings offset
        if (stringPoolHeader.stringCount > 0) {
            for (int idx = 0; idx < stringPoolHeader.stringCount; idx++) {
                offsets[idx] = in.readUInt();
            }
        }
        // read flag
        boolean sorted = (stringPoolHeader.flags & StringPoolHeader.SORTED_FLAG) != 0;
        StringEncoding stringEncoding = (stringPoolHeader.flags & StringPoolHeader.UTF8_FLAG) != 0 ?
                StringEncoding.UTF8 : StringEncoding.UTF16;

        // read strings. the head and metas have 28 bytes
        long stringPos = beginPos + stringPoolHeader.stringsStart - stringPoolHeader.headerSize;
        in.advanceIfNotRearch(stringPos);

        StringPoolEntry[] entries = new StringPoolEntry[offsets.length];
        for (int i = 0; i < offsets.length; i++) {
            entries[i] = new StringPoolEntry(i, stringPos + offsets[i]);
        }
        Arrays.sort(entries);

        String lastStr = null;
        long lastOffset = -1;
        StringPool stringPool = new StringPool((int) stringPoolHeader.stringCount);
        for (StringPoolEntry entry : entries) {
            if (entry.getOffset() == lastOffset) {
                stringPool.set(entry.getIdx(), lastStr);
                continue;
            }

            in.advanceIfNotRearch(entry.getOffset());
            lastOffset = entry.getOffset();
            String str = ParseUtils.readString(in, stringEncoding);
            lastStr = str;
            stringPool.set(entry.getIdx(), str);
        }

        // read styles
        if (stringPoolHeader.styleCount > 0) {
            // now we just skip it
        }

        in.advanceIfNotRearch(beginPos + stringPoolHeader.chunkSize - stringPoolHeader.headerSize);

        return stringPool;
    }

    /**
     * method to read resource value RGB/ARGB type.
     *
     * @return
     */
    public static String readRGBs(TellableInputStream in, int strLen)
            throws IOException {
        long l = in.readUInt();
        StringBuilder sb = new StringBuilder();
        for (int i = strLen / 2 - 1; i >= 0; i--) {
            sb.append(Integer.toHexString((int) ((l >> i * 8) & 0xff)));
        }
        return sb.toString();
    }

    /**
     * read res value, convert from different types to string.
     *
     * @param in
     * @param stringPool
     * @return
     * @throws IOException
     */
    public static ResourceEntity readResValue(TellableInputStream in, StringPool stringPool,
                                              boolean isStyle) throws IOException {
        ResValue resValue = new ResValue();
        resValue.size = in.readUShort();
        resValue.res0 = in.readUByte();
        resValue.dataType = in.readUByte();

        switch (resValue.dataType) {
            case ResValue.ResType.INT_DEC:
            case ResValue.ResType.INT_HEX:
                resValue.data = new ResourceEntity(in.readInt());
                break;
            case ResValue.ResType.STRING:
                int strRef = in.readInt();
                if (strRef > 0) {
                    resValue.data = new ResourceEntity(stringPool.get(strRef));
                }
                break;
            case ResValue.ResType.REFERENCE:
                long resourceId = in.readUInt();
                resValue.data = new ResourceEntity(resourceId, isStyle);
                break;
            case ResValue.ResType.INT_BOOLEAN:
                resValue.data = new ResourceEntity(in.readInt() != 0);
                break;
            case ResValue.ResType.NULL:
                resValue.data = new ResourceEntity("");
                break;
            case ResValue.ResType.INT_COLOR_RGB8:
            case ResValue.ResType.INT_COLOR_RGB4:
                resValue.data = new ResourceEntity(readRGBs(in, 6));
                break;
            case ResValue.ResType.INT_COLOR_ARGB8:
            case ResValue.ResType.INT_COLOR_ARGB4:
                resValue.data = new ResourceEntity(readRGBs(in, 8));
                break;
            case ResValue.ResType.DIMENSION:
                resValue.data = new ResourceEntity(getDemension(in));
                break;
            case ResValue.ResType.FRACTION:
                resValue.data = new ResourceEntity(getFraction(in));
                break;
            default:
                resValue.data = new ResourceEntity("{" + resValue.dataType + ":" + in.readUInt()
                        + "}");
        }
        return resValue.data;
    }

    private static String getDemension(TellableInputStream in) throws IOException {
        long l = in.readUInt();
        short unit = (short) (l & 0xff);
        String unitStr;
        switch (unit) {
            case ResValue.ResDataCOMPLEX.UNIT_MM:
                unitStr = "mm";
                break;
            case ResValue.ResDataCOMPLEX.UNIT_PX:
                unitStr = "px";
                break;
            case ResValue.ResDataCOMPLEX.UNIT_DIP:
                unitStr = "dp";
                break;
            case ResValue.ResDataCOMPLEX.UNIT_SP:
                unitStr = "sp";
                break;
            case ResValue.ResDataCOMPLEX.UNIT_PT:
                unitStr = "pt";
                break;
            case ResValue.ResDataCOMPLEX.UNIT_IN:
                unitStr = "in";
                break;
            default:
                unitStr = "unknow unit:0x" + Integer.toHexString(unit);
        }
        return (l >> 8) + unitStr;
    }

    private static String getFraction(TellableInputStream in) throws IOException {
        long l = in.readUInt();
        // The low-order 4 bits of the data value specify the type of the fraction
        short type = (short) (l & 0xf);
        String pstr;
        switch (type) {
            case ResValue.ResDataCOMPLEX.UNIT_FRACTION:
                pstr = "%";
                break;
            case ResValue.ResDataCOMPLEX.UNIT_FRACTION_PARENT:
                pstr = "%p";
                break;
            default:
                pstr = "unknow type:0x" + Integer.toHexString(type);
        }
        float value = Float.intBitsToFloat((int) (l >> 4));
        return value + pstr;
    }

    public static void checkChunkType(int expected, int real) {
        if (expected != real) {
            throw new ParserException("Excepct chunk type:" + Integer.toHexString(expected)
                    + ", but got:" + Integer.toHexString(real));
        }
    }

    /**
     * get resource value by string-format via resourceId.
     *
     * @param resourceId
     * @param resourceTable
     * @param locale
     * @return
     */
    public static String getResourceByid(long resourceId, boolean isStyle,
                                         ResourceTable resourceTable, Locale locale) {
//        An Android Resource id is a 32-bit integer. It comprises
//        an 8-bit Package id [bits 24-31]
//        an 8-bit Type id [bits 16-23]
//        a 16-bit Entry index [bits 0-15]

        // android system styles.
        if (isStyle && (resourceId & AndroidConstants.STYLE_ID_START) == AndroidConstants.STYLE_ID_START) {
            return "@android:style/" + ResourceTable.styleMap.get((int) resourceId);
        }

        String str = "resourceId:0x" + Long.toHexString(resourceId);
        if (resourceTable == null) {
            return str;
        }

        short packageId = (short) (resourceId >> 24 & 0xff);
        short typeId = (short) ((resourceId >> 16) & 0xff);
        int entryIndex = (int) (resourceId & 0xffff);
        ResourcePackage resourcePackage = resourceTable.getPackage(packageId);
        if (resourcePackage == null) {
            return str;
        }
        TypeSpec typeSpec = resourcePackage.getTypeSpec(typeId);
        List types = resourcePackage.getTypes(typeId);
        if (typeSpec == null || types == null) {
            return str;
        }
        if (!typeSpec.exists(entryIndex)) {
            return str;
        }

        // read from type resource
        String result = null;
        String ref = null;
        int currentLevel = -1;
        for (Type type : types) {
            ResourceEntry resource = type.getResourceEntry(entryIndex);
            if (resource == null) {
                continue;
            }
            ref = resource.key;
            int level = Locales.match(locale, type.locale);
            if (level == 2) {
                result = resource.toStringValue(resourceTable, locale);
                break;
            } else if (level > currentLevel) {
                result = resource.toStringValue(resourceTable, locale);
            }
        }
        if (locale == null || result == null) {
            result = "@" + typeSpec.name + "/" + ref;
        }
        return result;
    }

    /**
     * read res value. for resource table parser
     *
     * @param in
     * @param stringPool
     * @return
     */
    public static ResourceEntity readResValue(TellableInputStream in, StringPool stringPool)
            throws IOException {
        return readResValue(in, stringPool, false);
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy