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

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

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

import tinker.net.dongliu.apk.parser.bean.Locales;
import tinker.net.dongliu.apk.parser.exception.ParserException;
import tinker.net.dongliu.apk.parser.parser.StringPoolEntry;

import javax.annotation.Nullable;
import java.nio.ByteBuffer;
import java.nio.charset.Charset;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;

import tinker.net.dongliu.apk.parser.struct.AndroidConstants;
import tinker.net.dongliu.apk.parser.struct.ResValue;
import tinker.net.dongliu.apk.parser.struct.ResourceValue;
import tinker.net.dongliu.apk.parser.struct.StringPool;
import tinker.net.dongliu.apk.parser.struct.StringPoolHeader;
import tinker.net.dongliu.apk.parser.struct.resource.ResourceEntry;
import tinker.net.dongliu.apk.parser.struct.resource.ResourcePackage;
import tinker.net.dongliu.apk.parser.struct.resource.ResourceTable;
import tinker.net.dongliu.apk.parser.struct.resource.Type;
import tinker.net.dongliu.apk.parser.struct.resource.TypeSpec;

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

    public static Charset charsetUTF8  = Charset.forName("UTF-8");
    public static Charset charsetUTF16 = Charset.forName("UTF-16LE");

    /**
     * read string from input buffer. if get EOF before read enough data, throw IOException.
     */
    public static String readString(ByteBuffer buffer, boolean utf8) {
        if (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(buffer);
            int bytesLen = readLen(buffer);
            byte[] bytes = Buffers.readBytes(buffer, bytesLen);
            String str = new String(bytes, charsetUTF8);
            // zero
            int trailling = Buffers.readUByte(buffer);
            return str;
        } else {
            // The length is encoded as either one or two 16-bit integers as per the commentRef...
            int strLen = readLen16(buffer);
            String str = Buffers.readString(buffer, strLen);
            // zero
            int trailling = Buffers.readUShort(buffer);
            return str;
        }
    }

    /**
     * read utf-16 encoding str, use zero char to end str.
     */
    public static String readStringUTF16(ByteBuffer buffer, int strLen) {
        String str = Buffers.readString(buffer, 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
     */
    private static int readLen(ByteBuffer buffer) {
        int len = 0;
        int i = Buffers.readUByte(buffer);
        if ((i & 0x80) != 0) {
            //read one more byte.
            len |= (i & 0x7f) << 7;
            len += Buffers.readUByte(buffer);
        } else {
            len = i;
        }
        return len;
    }

    /**
     * read encoding len.
     * see Stringpool.cpp ENCODE_LENGTH
     */
    private static int readLen16(ByteBuffer buffer) {
        int len = 0;
        int i = Buffers.readUShort(buffer);
        if ((i & 0x8000) != 0) {
            len |= (i & 0x7fff) << 15;
            len += Buffers.readUShort(buffer);
        } else {
            len = i;
        }
        return len;
    }


    /**
     * read String pool, for apk binary xml file and resource table.
     */
    public static StringPool readStringPool(ByteBuffer buffer, StringPoolHeader stringPoolHeader) {

        long beginPos = buffer.position();
        long[] offsets = new long[(int) stringPoolHeader.getStringCount()];
        // read strings offset
        if (stringPoolHeader.getStringCount() > 0) {
            for (int idx = 0; idx < stringPoolHeader.getStringCount(); idx++) {
                offsets[idx] = Buffers.readUInt(buffer);
            }
        }
        // read flag
        // the string index is sorted by the string values if true
        boolean sorted = (stringPoolHeader.getFlags() & StringPoolHeader.SORTED_FLAG) != 0;
        // string use utf-8 format if true, otherwise utf-16
        boolean utf8 = (stringPoolHeader.getFlags() & StringPoolHeader.UTF8_FLAG) != 0;

        // read strings. the head and metas have 28 bytes
        long stringPos = beginPos + stringPoolHeader.getStringsStart() - stringPoolHeader.getHeaderSize();
        buffer.position((int) stringPos);

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

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

            buffer.position((int) entry.getOffset());
            lastOffset = entry.getOffset();
            String str = ParseUtils.readString(buffer, utf8);
            lastStr = str;
            stringPool.set(entry.getIdx(), str);
        }

        // read styles
        if (stringPoolHeader.getStyleCount() > 0) {
            // now we just skip it
        }
        stringPool.setUtf8(utf8);
        stringPool.setPoolOffsets(stringOffsets);
        buffer.position((int) (beginPos + stringPoolHeader.getBodySize()));

        return stringPool;
    }

    /**
     * read res value, convert from different types to string.
     */
    @Nullable
    public static ResourceValue readResValue(ByteBuffer buffer, StringPool stringPool, ResourceTable resourceTable, Locale locale) {
        ResourceValue resValue;
        int size = Buffers.readUShort(buffer);
        short res0 = Buffers.readUByte(buffer);
        short dataType = Buffers.readUByte(buffer);

        switch (dataType) {
            case ResValue.ResType.INT_DEC:
                resValue = ResourceValue.decimal(buffer.getInt());
                break;
            case ResValue.ResType.FLOAT:
                int rawValue = buffer.getInt();
                resValue = ResourceValue.floatValue(rawValue, Float.intBitsToFloat(rawValue));
                break;
            case ResValue.ResType.ATTRIBUTE:
                resValue = ResourceValue.reference(buffer.getInt(), resourceTable, locale);
                break;
            case ResValue.ResType.INT_HEX:
                resValue = ResourceValue.hexadecimal(buffer.getInt());
                break;
            case ResValue.ResType.STRING:
                int strRef = buffer.getInt();
                if (strRef >= 0) {
                    resValue = ResourceValue.string(strRef, stringPool);
                } else {
                    resValue = null;
                }
                break;
            case ResValue.ResType.REFERENCE:
                resValue = ResourceValue.reference(buffer.getInt(), resourceTable, locale);
                break;
            case ResValue.ResType.INT_BOOLEAN:
                resValue = ResourceValue.bool(buffer.getInt());
                break;
            case ResValue.ResType.NULL:
                resValue = ResourceValue.nullValue();
                break;
            case ResValue.ResType.INT_COLOR_RGB8:
            case ResValue.ResType.INT_COLOR_RGB4:
                resValue = ResourceValue.rgb(buffer.getInt(), 6);
                break;
            case ResValue.ResType.INT_COLOR_ARGB8:
            case ResValue.ResType.INT_COLOR_ARGB4:
                resValue = ResourceValue.rgb(buffer.getInt(), 8);
                break;
            case ResValue.ResType.DIMENSION:
                resValue = ResourceValue.dimension(buffer.getInt());
                break;
            case ResValue.ResType.FRACTION:
                resValue = ResourceValue.fraction(buffer.getInt());
                break;
            default:
                resValue = ResourceValue.raw(buffer.getInt(), dataType);
                break;
        }
        if (resValue != null) {
            resValue.setDataType(dataType);
            resValue.setSize(size);
        }
        return resValue;
    }

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

    /**
     * get resource value by string-format via resourceId.
     */
    public static String getResourceById(long resourceId, ResourceTable resourceTable, Locale locale, boolean fetchRealValue) {
//        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 (resourceId > AndroidConstants.SYS_STYLE_ID_START && resourceId < AndroidConstants.SYS_STYLE_ID_END) {
            return "@android:style/" + ResourceTable.sysStyle.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
        ResourceEntry resource = null;
        String ref = null;
        int currentLevel = -1;
        for (Type type : types) {
            ResourceEntry curResourceEntry = type.getResourceEntry(entryIndex);

            if (curResourceEntry == null) {
                continue;
            }
            ref = curResourceEntry.getKey();

            ResourceValue currentResourceValue = curResourceEntry.getValue();
            if (currentResourceValue == null) {
                continue;
            }

            // cyclic reference detect
            if (currentResourceValue instanceof ResourceValue.ReferenceResourceValue) {
                if (resourceId == ((ResourceValue.ReferenceResourceValue) currentResourceValue)
                        .getReferenceResourceId()) {
                    continue;
                }
            }

            int level = Locales.match(locale, type.getLocale());
            if (level == 2) {
                resource = curResourceEntry;
                break;
            } else if (level > currentLevel) {
                resource = curResourceEntry;
                currentLevel = level;
            }
        }
        String result;

        if (!fetchRealValue || locale == null || resource == null) {
            result = "@" + typeSpec.getName() + "/" + ref;
        } else {
            result = resource.toStringValue();
        }
        return result;
    }

    public static String getResourceNameById(long resourceId, ResourceTable resourceTable) {
//        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 (resourceId > AndroidConstants.SYS_STYLE_ID_START && resourceId < AndroidConstants.SYS_STYLE_ID_END) {
            return "@android:style/" + ResourceTable.sysStyle.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
        ResourceEntry resource = null;
        String ref = null;
        for (Type type : types) {
            ResourceEntry curResourceEntry = type.getResourceEntry(entryIndex);

            if (curResourceEntry == null) {
                continue;
            }
            ref = curResourceEntry.getKey();
            return ref;

        }
        String result;

        if (resource == null) {
            result = "@" + typeSpec.getName() + "/" + ref;
        } else {
            result = resource.toStringValue();
        }
        return result;
    }

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy