Android resources.arsc的解析

本文涉及的产品
全局流量管理 GTM,标准版 1个月
云解析 DNS,旗舰版 1个月
云解析DNS,个人版 1个月
简介: 写在前面的话从写下这个标题开始,我就知道这篇文章需要几天的时间才能真正完成。当然不是因为一切从零开始,只是因为要整理的东西太多。说几句题外话,关于为啥要写这个resources.arsc文件的解析。

写在前面的话

从写下这个标题开始,我就知道这篇文章需要几天的时间才能真正完成。当然不是因为一切从零开始,只是因为要整理的东西太多。说几句题外话,关于为啥要写这个resources.arsc文件的解析。本来啊,想按照我之前写的一步步写下去,结果一天无聊看文章,发现了这个文件的解析过程。想起我的舍友(技术大佬)写过一篇关于.class文件的解析(很惭愧,我也照着写了一遍,还发现了点小问题),所以看到这篇文章又勾起我对二进制文件解析的兴趣(痛苦啊!!)。写这篇文章的时候,我已经基本实现了整个解析过程,但是其中我测试了七个不同的resources.arsc,只有支付宝的解析会有问题,wtf!!没办法,最后打开二进制文件,一点点找问题,最后通过我强大的填坑技术,终于完成。不过呢,这次不打算通过FileInputStream一点点去read实现,我会使用ByteArray来实现这个解析过程,整个实现过程是kotlin语言,很多不熟悉的地方,还有很多高级用法没有使用。


1. 关于resources.arsc的网传神图

网上搜索关于resources.arsc文件,一般会出现老罗的打包过程以及尼古拉斯_赵四这两位真神的讲解。其中老罗的讲解中出现了一幅图片,关于resources.arsc的格式。图片如下:


img_2aed6e14dcc3c3fffb6b42c4e4d1d6a0.png
resources.arsc格式

2. resources.arsc格式详细分析

按照上面的图片来说,整个resources.arsc文件分为了六个部分(暂时按照颜色分吧),这几个部分可能就是他们所说的chunk把,从上到下依次是:

  1. package数量
  2. String Pool,字符串池
  3. Package 信息
  4. 资源类型字符串池和资源项名称字符串池
  5. 类型规范数据块
  6. 资源类型项数据块

3. 先说说头部信息

我们可以从图中看到,每块结构都是以TYPE、头大小、文件大小开头,这一部分组成了头部的信息根据ResourceTypes.h的说明可以知道,这部分是chunk的头部,先看下定义:

ResourceTypes.h 定义
/**
 * Header that appears at the front of every data chunk in a resource.
 */
struct ResChunk_header
{
    // Type identifier for this chunk.  The meaning of this value depends
    // on the containing chunk.
    uint16_t type;

    // Size of the chunk header (in bytes).  Adding this value to
    // the address of the chunk allows you to find its associated data
    // (if any).
    uint16_t headerSize;

    // Total size of this chunk (in bytes).  This is the chunkSize plus
    // the size of any data associated with the chunk.  Adding this value
    // to the chunk allows you to completely skip its contents (including
    // any child chunks).  If this value is the same as chunkSize, there is
    // no data associated with the chunk.
    uint32_t size;
};
ResChunk_header定义
class ResChunk_header(var type: uint16_t, //当前这个chunk的类型
                      var headerSize: uint16_t, //当前这个chunk的头部大小
                      var size: uint32_t) { //当前这个chunk的大小
    //这地方可以改成companion object,懒得改了
    enum class ChunkType(var type: Int) {
        RES_NULL_TYPE(0x0000),
        RES_STRING_POOL_TYPE(0x0001),
        RES_TABLE_TYPE(0x0002),
        RES_XML_TYPE(0x0003),
        RES_XML_FIRST_CHUNK_TYPE(0x0100),
        RES_XML_START_NAMESPACE_TYPE(0x0100),
        RES_XML_END_NAMESPACE_TYPE(0x0101),
        RES_XML_START_ELEMENT_TYPE(0x0102),
        RES_XML_END_ELEMENT_TYPE(0x0103),
        RES_XML_CDATA_TYPE(0x0104),
        RES_XML_LAST_CHUNK_TYPE(0x017f),
        RES_XML_RESOURCE_MAP_TYPE(0x0180),
        RES_TABLE_PACKAGE_TYPE(0x0200),
        RES_TABLE_TYPE_TYPE(0x0201),
        RES_TABLE_TYPE_SPEC_TYPE(0x0202)

    }
    // 输出头部信息
    override fun toString(): String {
        return "type = ${getType(type)}, typeHexValue = ${type.getHexValue()}, headerSize = ${headerSize.getValue()}, headerHexValue = ${headerSize.getHexValue()}, size = ${size.getValue()}, sizeHexValue = ${size.getHexValue()}"
    }
    // 获得类型
    fun getType(type: uint16_t): String {
        when (type.getValue().toInt()) {
            ResChunk_header.ChunkType.RES_NULL_TYPE.type -> return "RES_NULL_TYPE"
            ResChunk_header.ChunkType.RES_STRING_POOL_TYPE.type -> return "RES_STRING_POOL_TYPE"
            ResChunk_header.ChunkType.RES_TABLE_TYPE.type -> return "RES_TABLE_TYPE"
            ResChunk_header.ChunkType.RES_XML_TYPE.type -> return "RES_XML_TYPE"
            ResChunk_header.ChunkType.RES_XML_FIRST_CHUNK_TYPE.type -> return "RES_XML_FIRST_CHUNK_TYPE"
            ResChunk_header.ChunkType.RES_XML_START_NAMESPACE_TYPE.type -> return "RES_XML_START_NAMESPACE_TYPE"
            ResChunk_header.ChunkType.RES_XML_END_NAMESPACE_TYPE.type -> return "RES_XML_END_NAMESPACE_TYPE"
            ResChunk_header.ChunkType.RES_XML_START_ELEMENT_TYPE.type -> return "RES_XML_START_ELEMENT_TYPE"
            ResChunk_header.ChunkType.RES_XML_END_ELEMENT_TYPE.type -> return "RES_XML_END_ELEMENT_TYPE"
            ResChunk_header.ChunkType.RES_XML_CDATA_TYPE.type -> return "RES_XML_CDATA_TYPE"
            ResChunk_header.ChunkType.RES_XML_LAST_CHUNK_TYPE.type -> return "RES_XML_LAST_CHUNK_TYPE"
            ResChunk_header.ChunkType.RES_XML_RESOURCE_MAP_TYPE.type -> return "RES_XML_RESOURCE_MAP_TYPE"
            ResChunk_header.ChunkType.RES_TABLE_PACKAGE_TYPE.type -> return "RES_TABLE_PACKAGE_TYPE"
            ResChunk_header.ChunkType.RES_TABLE_TYPE_TYPE.type -> return "RES_TABLE_TYPE_TYPE"
            ResChunk_header.ChunkType.RES_TABLE_TYPE_SPEC_TYPE.type -> return "RES_TABLE_TYPE_SPEC_TYPE"
        }

        return ""
    }
}

上面的定义就是关于chunk的头部信息的定义:

  • type:当前这个chunk的类型
  • headerSize:当前这个chunk的头部大小
  • size:当前这个chunk的大小

4. package数量格式

ResTable_header是关于这个格式的定义,其实里面只有一个uint32_t的packageCount,用于存储package的数量

ResourceTypes.h定义
/**
 * Header for a resource table.  Its data contains a series of
 * additional chunks:
 *   * A ResStringPool_header containing all table values.  This string pool
 *     contains all of the string values in the entire resource table (not
 *     the names of entries or type identifiers however).
 *   * One or more ResTable_package chunks.
 *
 * Specific entries within a resource table can be uniquely identified
 * with a single integer as defined by the ResTable_ref structure.
 */
struct ResTable_header
{
    struct ResChunk_header header;

    // The number of ResTable_package structures.
    uint32_t packageCount;
};
ResTable_header定义
class ResTable_header(var header: ResChunk_header, var packageCount: uint32_t) {
    override fun toString(): String {

        return header.toString() + ", packagerCount = ${packageCount.getValue()}, packagerCountHexValue = ${packageCount.getHexValue()}"
    }
}

Android中一个apk可能包含多个资源包,默认情况下都只有一个就是应用的包名所在的资源包

  • packageCount:被编译的资源包的个数
    下图是我使用的arsc文件的ResTable_header的信息,可以看到这一部分的TYPE是0x0002,对应了RES_TABLE_TYPE,并且其headerSize是0x000C,也就是12,整个chunk的大小是377904(0x0005C430),packag的数量是1(0x00000001)


    img_e7e4dbfcd2356791b98494d71fd67dbd.png
    ResTable_header

    img_9fbbd3d7423f693ffa0355bec3098b6a.png
    总大小.png

    这一部分总体来说解析很简单,前面的两个字节对应了类型,接着两个字节对应了headerSize,后面四个字节对应了整个大小size,最后的四个字节对应了package的大小,这里我们可以看到这个数量是1。


5. String Pool

先看下ResourceTypes.h对String Pool的定义:

ResourceTypes.h定义
struct ResStringPool_header
{
    struct ResChunk_header header;

    // Number of strings in this pool (number of uint32_t indices that follow
    // in the data).
    uint32_t stringCount;

    // Number of style span arrays in the pool (number of uint32_t indices
    // follow the string indices).
    uint32_t styleCount;

    // Flags.
    enum {
        // If set, the string index is sorted by the string values (based
        // on strcmp16()).
        SORTED_FLAG = 1<<0,

        // String pool is encoded in UTF-8
        UTF8_FLAG = 1<<8
    };
    uint32_t flags;

    // Index from header of the string data.
    uint32_t stringsStart;

    // Index from header of the style data.
    uint32_t stylesStart;
};
ResTable_header定义
class ResStringPool_header(var header: ResChunk_header,
                           var stringCount: uint32_t, //字符串的数量
                           var styleCount: uint32_t, //字符串样式的数量
                           var flags: uint32_t, //字符串的属性,可取值包括0x000(UTF-16),0x001(字符串经过排序)、0X100(UTF-8)和他们的组合值
                           var stringsStart: uint32_t, //字符串内容块相对于其头部的距离
                           var stylesStart: uint32_t, //字符串样式块相对于其头部的距离
                           var stringOffsetArray: ResString_offset_array?,//字符串偏移数组
                           var styleOffsetArray: ResStyle_offset_array?,//字符串样式偏移数组
                           var stringStringArray: ResString_string_array?,//字符串数组
                           var styleStringArray: ResStyle_string_array?//字符串样式数组

) {
    // Flags.
    enum class FLAGS(var flag: Int) {
        // If set, the string index is sorted by the string values (based
        // on strcmp16()).
        SORTED_FLAG(1 shl 0),
        // String pool is encoded in UTF-8
        UTF8_FLAG(1 shl 8)
    }

    override fun toString(): String {
        val result = """$header,
            |stringCount = ${stringCount.getValue()}, stringCountHexValue = ${stringCount.getHexValue()},
            |styleCount = ${styleCount.getValue()}, styleCountHexValue = ${styleCount.getHexValue()},
            |flags = ${flags.getValue()}, flagsHexValue = ${flags.getHexValue()},
            |stringsStart = ${stringsStart.getValue()}, stringsStartCountHexValue = ${stringsStart.getHexValue()},
            |stylesStart = ${stylesStart.getValue()}, stylesStartHexValue = ${stylesStart.getHexValue()},""".trimMargin()
        return result
    }
}

从定义可以看到,这一部分内容挺多:

  • header:头部信息,ResChunk_header格式
  • stringCount:字符串的数量,uint32_t格式
  • styleCount:字符串样式的数量,uint32_t格式
  • flags:字符串的属性,可取值包括0x000(UTF-16),0x001(字符串经过排序)、0X100(UTF-8)和他们的组合值,uint32_t格式
  • stringsStart:字符串开始位置,相对于头部,uint32_t格式
  • stylesStart:字符串样式开始位置,相对于头部,uint32_t格式
  • 四个数组:自己定义,用于存储偏移量以及string的值
    下图是我使用的arsc文件的ResStringPool_header信息(不包括数组内容):


    img_70f363805594f6555c3536bf69d70e3e.png
    StringPool头部信息.png

    从图中可以看到:

  1. headerSize是28(0x001c)
  2. size是142676(0x00022d54)
  3. stringCount是2882(0x00000b42)
  4. styleCount是1(0x00000001)
  5. flags是256(0x00000100)代表了编码是UTF-8
  6. stringsStart是11560(0x00002d28)
  7. stylesStart是142652(0x00022d3c)
    上面的信息完成后,接着便是两个偏移数组(字符串偏移数组和字符串样式偏移数组),偏移数组结束后就是字符串池。字符串资源池中的字符串前两个字节为字符串长度,长度计算方法如下。另外如果字符串编码格式为UTF-8则字符串以0X00作为结束符,UTF-16则以0X0000作为结束符,我这里默认认为是UTF-8编码,解析过程完全按照UTF-8编码来解析。在长度计算的时候,我发现网上很多人写的都不是很对,起码当长度超过128(一个字节)的时候就会有问题,这个长度计算可是费了好多脑子,写写画画好几天,终于弄出一个我认为是正确的计算方法(我不保证正确,但至少我解析的过程是正确的,姑且认为是对的吧):
var i = 0
    while (realSize and (0x80 shl i) != 0) {
        c++
        byteArray[0] = stream[index + c]
        realSize = ((realSize and 0x7f) shl 8) or (byteArray[0].toInt() and 0xff)
        i += 4
    }

6. Package信息

继String Pool信息之后,就是Package信息块。其定义如下:

ResourceTypes.h定义
struct ResTable_package
{
    struct ResChunk_header header;

    // If this is a base package, its ID.  Package IDs start
    // at 1 (corresponding to the value of the package bits in a
    // resource identifier).  0 means this is not a base package.
    uint32_t id;

    // Actual name of this package, \0-terminated.
    uint16_t name[128];

    // Offset to a ResStringPool_header defining the resource
    // type symbol table.  If zero, this package is inheriting from
    // another base package (overriding specific values in it).
    uint32_t typeStrings;

    // Last index into typeStrings that is for public use by others.
    uint32_t lastPublicType;

    // Offset to a ResStringPool_header defining the resource
    // key symbol table.  If zero, this package is inheriting from
    // another base package (overriding specific values in it).
    uint32_t keyStrings;

    // Last index into keyStrings that is for public use by others.
    uint32_t lastPublicKey;

    uint32_t typeIdOffset;
};
ResTable_package定义
class ResTable_package(
        var header: ResChunk_header,
        // If this is a base package, its ID.  Package IDs start
        // at 1 (corresponding to the value of the package bits in a
        // resource identifier).  0 means this is not a base package.
        var id: uint32_t,
        // Actual name of this package, \0-terminated.
        var name: String,
        // Offset to a ResStringPool_header defining the resource
        // type symbol table.  If zero, this package is inheriting from
        // another base package (overriding specific values in it).
        var typeStrings: uint32_t,
        // Last index into typeStrings that is for public use by others.
        var lastPublicType: uint32_t,
        // Offset to a ResStringPool_header defining the resource
        // key symbol table.  If zero, this package is inheriting from
        // another base package (overriding specific values in it).
        var keyStrings: uint32_t,
        // Last index into keyStrings that is for public use by others.
        var lastPublicKey: uint32_t) {

    override fun toString(): String {
        val result = """$header,
                    |id = ${id.getValue()}, idHexValue = ${id.getHexValue()}
                    |name = $name
                    |typeStrings = ${typeStrings.getValue()}, typeStringsHexValue = ${typeStrings.getHexValue()}
                    |lastPublicType = ${lastPublicType.getValue()}, lastPublicTypeHexValue = ${lastPublicType.getHexValue()}
                    |keyStrings = ${keyStrings.getValue()}, keyStringsHexValue = ${keyStrings.getHexValue()}
                    |lastPublicKey = ${lastPublicKey.getValue()}, lastPublicKeyHexValue = ${lastPublicKey.getHexValue()}""".trimMargin()

        return result
    }
}

每个字段对应的含义如下:

  • header:Chunk的头部信息数据结构
  • id:包的ID,等于Package Id,一般用户包的值Package Id为0X7F,系统资源包的Package Id为0X01,uint32_t格式
  • name:包名,String,256字节
  • typeString:类型字符串资源池相对头部的偏移,uint32_t格式
  • lastPublicType:最后一个导出的Public类型字符串在类型字符串资源池中的索引,uint32_t格式
  • keyStrings:资源项名称字符串相对头部的偏移,uint32_t格式
  • lastPublicKey:最后一个导出的Public资源项名称字符串在资源项名称字符串资源池中的索引,uint32_t格式
    示例如下图:


    img_12b7cae6e2b89fe60fd4521c36a49ed1.png
    Package信息.png

    从图中可以看出:

  1. headerSize = 288(0x0120)
  2. size = 235216( 0x000396d0)
  3. id = 127( 0x0000007f)
  4. name = c o m . n i c k . m y a p p l i c a t i o n
  5. typeStrings = 288(0x00000120)
  6. lastPublicType = 13(0x0000000d)
  7. keyStrings = 480(0x000001e0)
  8. lastPublicKey = 1472(0x000005c0)

7. 资源类型字符串池和资源项名称字符串池

这一部分是资源类型字符串池和资源项名称字符串池,也就是Android里面的attr、drawable、layout等文件,其格式是ResStringPool_header格式,这里不多赘述。查找这个字符串池位置比较好找,通过查找attr就可以找到大概位置。


8. 类型规范数据块

特定类型定义的资源规范,每种资源类型应该有一个这样的块。该结构之后是一个整数数组,提供了一组配置更改标志(ResTable_config :: CONFIG_ *),该标志具有该配置的多个资源。 另外,如果该资源已被公开,则高位被设置。其数据结构如下:

ResourceTypes.h定义
struct ResTable_typeSpec
{
    struct ResChunk_header header;

    // The type identifier this chunk is holding.  Type IDs start
    // at 1 (corresponding to the value of the type bits in a
    // resource identifier).  0 is invalid.
    uint8_t id;
    
    // Must be 0.
    uint8_t res0;
    // Must be 0.
    uint16_t res1;
    
    // Number of uint32_t entry configuration masks that follow.
    uint32_t entryCount;

    enum {
        // Additional flag indicating an entry is public.
        SPEC_PUBLIC = 0x40000000
    };
};
ResTable_typeSpec
class ResTable_typeSpec(var header: ResChunk_header,
                        var id: uint8_t,// id,每个资源独有的id
                        var res0: uint8_t,// 保留字段,为0
                        var res1: uint16_t, // 保留字段,为0
                        var entryCount: uint32_t) {// 资源项个数

    override fun toString(): String {
        return """header = $header ,
                |id = ${id.getValue()}, idHexValue = ${id.getHexValue()},
                |res0 =${res0.getValue()} ,res1 = ${res1.getValue()} ,
                |entryCount = ${entryCount.getValue()}, entryCountHexValue = ${entryCount.getHexValue()}""".trimMargin()
    }

    companion object {
        val SPEC_PUBLIC = 0x40000000
    }

}

每个字段对应的含义如下:

  • header:Chunk的头部信息数据结构
  • id:每个资源独有的id,uint8_t结构
  • res0和res1:保留字段,都为0,uint8_t和uint16_t结构
  • entryCount:当前类型的资源项的个数,uint32_t结构
    示例如下图:


    img_6223483c7448afc6662684d57a5c95a3.png
    ResTable_typeSpec.png

    其表示的信息为:

  1. type = RES_TABLE_TYPE_SPEC_TYPE(0x0202)
  2. headerSize = 16(0x0010)
  3. size = 1472(0x000005c0 )
  4. id = 1(0x01)
  5. res0 =0,res1 = 0
  6. entryCount = 364(0x0000016c)
  7. id对应的资源项值attr

9. 资源类型项数据块

这一部分我的理解就是attr、drawable等文件的具体项,每个资源项都对应了多个值,这些值在之前解析中已经出现,这里只通过位置来确定具体值。这边的结构有点多,这里还是只展示一部分吧,真心有点多。具体定义如下:

ResourceTypes.h定义:
struct ResTable_type
{
    struct ResChunk_header header;

    enum {
        NO_ENTRY = 0xFFFFFFFF
    };
    
    // The type identifier this chunk is holding.  Type IDs start
    // at 1 (corresponding to the value of the type bits in a
    // resource identifier).  0 is invalid.
    uint8_t id;
    
    // Must be 0.
    uint8_t res0;
    // Must be 0.
    uint16_t res1;
    
    // Number of uint32_t entry indices that follow.
    uint32_t entryCount;

    // Offset from header where ResTable_entry data starts.
    uint32_t entriesStart;
    
    // Configuration this collection of entries is designed for.
    //这个不展开了,内容有点多
    ResTable_config config; 
};
// entry
struct ResTable_entry
{
    // Number of bytes in this structure.
    uint16_t size;

    enum {
        // If set, this is a complex entry, holding a set of name/value
        // mappings.  It is followed by an array of ResTable_map structures.
        FLAG_COMPLEX = 0x0001,
        // If set, this resource has been declared public, so libraries
        // are allowed to reference it.
        FLAG_PUBLIC = 0x0002
    };
    uint16_t flags;
    
    // Reference into ResTable_package::keyStrings identifying this entry.
    struct ResStringPool_ref key;
};
// map entry
struct ResTable_map_entry : public ResTable_entry
{
    // Resource identifier of the parent mapping, or 0 if there is none.
    // This is always treated as a TYPE_DYNAMIC_REFERENCE.
    ResTable_ref parent;
    // Number of name/value pairs that follow for FLAG_COMPLEX.
    uint32_t count;
};
// map
struct ResTable_map
{
    // The resource identifier defining this mapping's name.  For attribute
    // resources, 'name' can be one of the following special resource types
    // to supply meta-data about the attribute; for all other resource types
    // it must be an attribute resource.
    ResTable_ref name;
    ......
    // This mapping's value.
    Res_value value;
};
// 真正的value
struct Res_value
{
    // Number of bytes in this structure.
    uint16_t size;
    // Always set to 0.
    uint8_t res0;
    // Type of the data value.
    enum {
        // The 'data' is either 0 or 1, specifying this resource is either
        // undefined or empty, respectively.
        TYPE_NULL = 0x00,
        // The 'data' holds a ResTable_ref, a reference to another resource
        // table entry.
        TYPE_REFERENCE = 0x01,
        // The 'data' holds an attribute resource identifier.
        TYPE_ATTRIBUTE = 0x02,
        // The 'data' holds an index into the containing resource table's
        // global value string pool.
        TYPE_STRING = 0x03,
        // The 'data' holds a single-precision floating point number.
        TYPE_FLOAT = 0x04,
        // The 'data' holds a complex number encoding a dimension value,
        // such as "100in".
        TYPE_DIMENSION = 0x05,
        // The 'data' holds a complex number encoding a fraction of a
        // container.
        TYPE_FRACTION = 0x06,
        // The 'data' holds a dynamic ResTable_ref, which needs to be
        // resolved before it can be used like a TYPE_REFERENCE.
        TYPE_DYNAMIC_REFERENCE = 0x07,

        // Beginning of integer flavors...
        TYPE_FIRST_INT = 0x10,

        // The 'data' is a raw integer value of the form n..n.
        TYPE_INT_DEC = 0x10,
        // The 'data' is a raw integer value of the form 0xn..n.
        TYPE_INT_HEX = 0x11,
        // The 'data' is either 0 or 1, for input "false" or "true" respectively.
        TYPE_INT_BOOLEAN = 0x12,

        // Beginning of color integer flavors...
        TYPE_FIRST_COLOR_INT = 0x1c,

        // The 'data' is a raw integer value of the form #aarrggbb.
        TYPE_INT_COLOR_ARGB8 = 0x1c,
        // The 'data' is a raw integer value of the form #rrggbb.
        TYPE_INT_COLOR_RGB8 = 0x1d,
        // The 'data' is a raw integer value of the form #argb.
        TYPE_INT_COLOR_ARGB4 = 0x1e,
        // The 'data' is a raw integer value of the form #rgb.
        TYPE_INT_COLOR_RGB4 = 0x1f,

        // ...end of integer flavors.
        TYPE_LAST_COLOR_INT = 0x1f,

        // ...end of integer flavors.
        TYPE_LAST_INT = 0x1f
    };
    uint8_t dataType;
    ......
    // The data for this item, as interpreted according to dataType.
    uint32_t data;
};
代码中定义:
class ResTable_type(var header: ResChunk_header,
                    var id: uint8_t,// 标识资源的type id
                    var res0: uint8_t,// 保留,0
                    var res1: uint16_t,// 保留,0
                    var entryCount: uint32_t,// 总个数
                    var entriesStart: uint32_t,// 偏移量
                    var resConfig: ResTable_config) {// 配置信息

    override fun toString(): String {
        return """header: $header ,
                |id = ${id.getValue()}, idHexValue = ${id.getHexValue()},
                |res0 = ${res0.getValue()},res1 = ${res1.getValue()},
                |entryCount = ${entryCount.getValue()}, entryCountHexValue = ${entryCount.getHexValue()},
                |entriesStart = ${entriesStart.getValue()}, entriesStartHexValue = ${entriesStart.getHexValue()}
                |resConfig = $resConfig""".trimMargin()
    }

    companion object {
        val NO_ENTRY = 0xFFFFFFFF.toInt()
    }

}
open class ResTable_entry(var size: uint16_t,// 大小
                          var flags: uint16_t,// flag,为1是ResTable_map_entry
                          var key: ResStringPool_ref?) {// 字符串池引用信息
    var dataStr: String? = ""
    override fun toString(): String {
        return "size = " + size.getValue() + ",flags = " + flags.getValue() + ",key = " + key!!.index.getValue() + ",str = " + dataStr
    }

    companion object {
        val FLAG_COMPLEX = 0x0001
        val FLAG_PUBLIC = 0x0002
    }

}
class ResTable_map_entry(size: uint16_t, flags: uint16_t, key: ResStringPool_ref?,
                         var parent: ResTable_ref, // 指向父ResTable_map_entry的资源ID,如果没有父ResTable_map_entry,则等于0
                         var count: uint32_t // ResTable_map的数量
) : ResTable_entry(size, flags, key) {

    override fun toString(): String {
        return super.toString() + "parent:" + parent.toString() + ",count:" + count.getValue()
    }

}

// bag资源:通俗的说,就是这类资源在赋值的时候,不能随便赋值,只能从事先定义好的值中选取一个赋值。
// 类型为values的资源除了是string之外,还有其它很多类型的资源,其中有一些比较特殊,如bag、style、plurals和array类的资源。
// 这些资源会给自己定义一些专用的值,这些带有专用值的资源就统称为Bag资源。
class ResTable_map(var name: ResTable_ref, // bag资源项ID,
                   var value: Res_value) { //bag资源项值
    override fun toString(): String {
        return "mapEntry: " + name.toString() + ",value = " + value.toString()
    }

}
class Res_value(var size: uint16_t, // 大小
                var res0: uint8_t, // 保留,0
                var dataType: uint8_t, // 类型
                var data: uint32_t) { // 数据,根据不同类型来展示不同数据,这部分基本上是网上拿来的

    val typeStr: String
        get() {
            when (dataType.getValue().toInt()) {
                TYPE_NULL -> return "TYPE_NULL"
                TYPE_REFERENCE -> return "TYPE_REFERENCE"
                TYPE_ATTRIBUTE -> return "TYPE_ATTRIBUTE"
                TYPE_STRING -> return "TYPE_STRING"
                TYPE_FLOAT -> return "TYPE_FLOAT"
                TYPE_DIMENSION -> return "TYPE_DIMENSION"
                TYPE_FRACTION -> return "TYPE_FRACTION"
                TYPE_FIRST_INT -> return "TYPE_FIRST_INT"
                TYPE_INT_HEX -> return "TYPE_INT_HEX"
                TYPE_INT_BOOLEAN -> return "TYPE_INT_BOOLEAN"
                TYPE_FIRST_COLOR_INT -> return "TYPE_FIRST_COLOR_INT"
                TYPE_INT_COLOR_RGB8 -> return "TYPE_INT_COLOR_RGB8"
                TYPE_INT_COLOR_ARGB4 -> return "TYPE_INT_COLOR_ARGB4"
                TYPE_INT_COLOR_RGB4 -> return "TYPE_INT_COLOR_RGB4"
            }
            return ""
        }

    val dataStr: String
        get() {
            if (dataType.getValue().toInt() == TYPE_STRING) {
                return getStringPoolStr(data.getValue())!!
            }
            if (dataType.getValue().toInt() == TYPE_ATTRIBUTE) {
                return String.format("?%s%08X", getPackage(data.getValue()), data.getValue())
            }
            if (dataType.getValue().toInt() == TYPE_REFERENCE) {
                return String.format("@%s%08X", getPackage(data.getValue()), data.getValue())
            }
            if (dataType.getValue().toInt() == TYPE_FLOAT) {
                return java.lang.Float.intBitsToFloat(data.getValue()).toString()
            }
            if (dataType.getValue().toInt() == TYPE_INT_HEX) {
                return String.format("0x%08X", data.getValue())
            }
            if (dataType.getValue().toInt() == TYPE_INT_BOOLEAN) {
                return if (data.getValue() != 0) "true" else "false"
            }
            if (dataType.getValue().toInt() == TYPE_DIMENSION) {
                return java.lang.Float.toString(complexToFloat(data.getValue())) + DIMENSION_UNITS[data.getValue() and COMPLEX_UNIT_MASK]
            }
            if (dataType.getValue().toInt() == TYPE_FRACTION) {
                return java.lang.Float.toString(complexToFloat(data.getValue())) + FRACTION_UNITS[data.getValue() and COMPLEX_UNIT_MASK]
            }
            if (dataType.getValue() in TYPE_FIRST_COLOR_INT..TYPE_LAST_COLOR_INT) {
                return String.format("#%08X", data.getValue())
            }
            if (dataType.getValue() in TYPE_FIRST_INT..TYPE_LAST_INT) {
                return data.getValue().toString()
            }
            return String.format("<0x%X, type 0x%02X>", data.getValue(), dataType.getValue())
        }

    override fun toString(): String {
        return """size = ${size.getValue()}, sizeHexValue = ${size.getHexValue()}
        |res0 = ${res0.getValue()}, res0HexValue = ${res0.getHexValue()}
        |dataType = ${dataType.getValue()}, dataTypeHexValue = ${dataType.getHexValue()}, typeStr = $typeStr
        |data = ${data.getValue()}, dataHexValue = ${data.getHexValue()}, dataStr = $dataStr""".trimMargin()
    }

    companion object {

        val TYPE_NULL = 0x00
        val TYPE_REFERENCE = 0x01
        val TYPE_ATTRIBUTE = 0x02
        val TYPE_STRING = 0x03
        val TYPE_FLOAT = 0x04
        val TYPE_DIMENSION = 0x05
        val TYPE_FRACTION = 0x06
        val TYPE_FIRST_INT = 0x10
        val TYPE_INT_DEC = 0x10
        val TYPE_INT_HEX = 0x11
        val TYPE_INT_BOOLEAN = 0x12
        val TYPE_FIRST_COLOR_INT = 0x1c
        val TYPE_INT_COLOR_ARGB8 = 0x1c
        val TYPE_INT_COLOR_RGB8 = 0x1d
        val TYPE_INT_COLOR_ARGB4 = 0x1e
        val TYPE_INT_COLOR_RGB4 = 0x1f
        val TYPE_LAST_COLOR_INT = 0x1f
        val TYPE_LAST_INT = 0x1f

        val COMPLEX_UNIT_PX = 0
        val COMPLEX_UNIT_DIP = 1
        val COMPLEX_UNIT_SP = 2
        val COMPLEX_UNIT_PT = 3
        val COMPLEX_UNIT_IN = 4
        val COMPLEX_UNIT_MM = 5
        val COMPLEX_UNIT_SHIFT = 0
        val COMPLEX_UNIT_MASK = 15
        val COMPLEX_UNIT_FRACTION = 0
        val COMPLEX_UNIT_FRACTION_PARENT = 1
        val COMPLEX_RADIX_23p0 = 0
        val COMPLEX_RADIX_16p7 = 1
        val COMPLEX_RADIX_8p15 = 2
        val COMPLEX_RADIX_0p23 = 3
        val COMPLEX_RADIX_SHIFT = 4
        val COMPLEX_RADIX_MASK = 3
        val COMPLEX_MANTISSA_SHIFT = 8
        val COMPLEX_MANTISSA_MASK = 0xFFFFFF

        private fun getPackage(id: Int): String {
            if (id.ushr(24) == 1) {
                return "android:"
            }
            return ""
        }

        fun complexToFloat(complex: Int): Float {
            return (complex and 0xFFFFFF00.toInt()).toFloat() * RADIX_MULTS[complex shr 4 and 3]
        }

        private val RADIX_MULTS = floatArrayOf(0.00390625f, 3.051758E-005f, 1.192093E-007f, 4.656613E-010f)

        private val DIMENSION_UNITS = arrayOf("px", "dip", "sp", "pt", "in", "mm", "", "")

        private val FRACTION_UNITS = arrayOf("%", "%p", "", "", "", "", "", "")
    }
}

上面的结构定义确实很多,有一部分是从先人的博客中摘抄的,但我并不认为他们的全是正确的,因为在我自己解析的过程中一次次肯定又一次次否定,慢慢自己发现规律并完成整个过程。
上面结构体ResTable_type定义了该资源项的资源id、总个数、配置信息以及偏移量,接着根据ResTable_entry的flags来判断具体是ResTable_map_entry还是ResTable_entry,这里面定义了size,即大小。如果类型是ResTable_map_entry,则根据ResTable_map_entry的count设置具体的ResTable_map,ResTable_map中包括了资源项的id和值(Res_value)。如果类型是ResTable_entry,那么这里会有一个资源项的值Res_value。Res_value中根据类型(dataType)来判断具体属于什么类型,并根据类型和数据(data)来获得具体的值。


10. 解析过程

了解了resources.arsc的文件结构,可以通过结构和二进制文件查看器来进行文件的解析。

10.1 工具类和类型定义

ibasic_unit:基础的接口
interface ibasic_unit<T> {
    fun getValue(): T //value
    fun getHexValue(): String // 返回16进制格式化字符串
}
basic_unit:基础的抽象实现类
abstract class basic_unit<T>(protected var relValue: T) : ibasic_unit<T> {
    override fun getValue(): T {
        return relValue
    }
}
uint8_t:读取一个字节,对应类型是Byte
class uint8_t(value: Byte) : basic_unit<Byte>(value) {
    override fun getHexValue(): String {
        var toHexString = Integer.toHexString(getValue().toInt())
        if (toHexString.length < 2) {
            for (i in 1..2 - toHexString.length) {
                toHexString = "0" + toHexString
            }
        }
        return "0x" + toHexString
    }
}
uint16_t:读取两个字节,对应的值的类型是Short
class uint16_t(value: Short) : basic_unit<Short>(value) {
    override fun getValue(): Short {
        return getShortValue(relValue)
    }

    override fun getHexValue(): String {
        var toHexString = Integer.toHexString(com.nick.model.type.getShortValue(relValue).toInt())
        if (toHexString.length < 4) {
            for (i in 1..4 - toHexString.length) {
                toHexString = "0" + toHexString
            }
        }
        return "0x" + toHexString
    }
}
uint32_t:读取四个字节的值,对应类型是Int
class uint32_t(var value: Int) : basic_unit<Int>(value) {
    override fun getValue(): Int {
        return getIntValue(relValue)
    }

    override fun getHexValue(): String {
        var toHexString = Integer.toHexString(getValue())
        if (toHexString.length < 8) {
            for (i in 1..(8 - toHexString.length)) {
                toHexString = "0" + toHexString
            }
        }
        return "0x" + toHexString
    }
}
Config:配置信息
class Config {
    companion object {
        val RESCHUNK_HEADER_SIZE = 8 //chunk头部大小
        val RESTABLE_MAP_SIZE = 12 //res table map大小
        val RES_VALUE_SIZE = 8 //res value大小
    }
}
ReadUtils.kt:
var stringList: Array<String>? = null //字符串池数组

//读取uint8_t
fun read_uint8_t(byteArray: ByteArray, offset: Int): uint8_t {
    return uint8_t(byteArray[offset])
}

fun byte22Uint16_t(byteArray: ByteArray): uint16_t {
    var value: Short = 0
    for (i in 0..1) {
        value = (value.toInt() shl 8).toShort()
        value = value or (byteArray[i].toInt() and 0xFF).toShort()
    }
    return uint16_t(value)
}

//读取uint16_t
fun read_uint16_t(stream: ByteArray, index: Int): uint16_t {
    val byte = ByteArray(2)
    byte[0] = stream[index]
    byte[1] = stream[index + 1]

    var value: Short = 0
    for (i in 0..1) {
        value = (value.toInt() shl 8).toShort()
        value = value or (byte[i].toInt() and 0xFF).toShort()
    }
    return uint16_t(value)
}

//读取uint32_t
fun read_uint32_t(stream: ByteArray, index: Int): uint32_t {
    val byte = ByteArray(4)
    for (i in 0..byte.size - 1) {
        byte[i] = stream[index + i]
    }

    var value: Int = 0
    for (i in 0..3) {
        value = value shl 8
        value = value or (byte[i].toInt() and 0xFF)
    }
    return uint32_t(value)
}

//读取uint64_t
fun read_uint64_t(stream: ByteArray, index: Int): uint64_t {
    val byte = ByteArray(8)
    for (i in 0..byte.size - 1) {
        byte[i] = stream[index + i]
    }

    var value: Long = 0
    for (i in 0..7) {
        value = value shl 8
        value = value or (byte[i].toLong() and 0xFF)
    }
    return uint64_t(value)
}

//读取字符串
fun readString2(stream: ByteArray, size: Int, index: Int): Pair<String, Int> {
    val bytes = ByteArray(1)
    val byteArray = ByteArray(1)
    var realSize = 0
    bytes[0] = stream[index]
    byteArray[0] = stream[index + 1]
    var c = 1
    if (bytes[0].toInt() and 0x80 != 0) {
        c++
        byteArray[0] = stream[index + c]
    }
    var result = ""
    realSize = byteArray[0].toInt()
    var i = 0
    while (realSize and (0x80 shl i) != 0) {
        c++
        byteArray[0] = stream[index + c]
        realSize = ((realSize and 0x7f) shl 8) or (byteArray[0].toInt() and 0xff)
        i += 4
    }

    if (realSize > 0) {
        val tempBytes = ByteArray(realSize)
        for (j in 0..tempBytes.size - 1) {
            tempBytes[j] = stream[index + c + 1 + j]
        }
        result += "${String(tempBytes)}  realSize = $realSize size = $size c = $c"
    }

    val resultPair = Pair(result, realSize + c + i + 1)

    return resultPair
}

//根据字符串size读取字符串
fun readStringWithSize(stream: ByteArray, offset: Int, size: Int): String {
    var result = ""
    if (size > 0) {
        val tempBytes = ByteArray(size)

        for (i in 0..tempBytes.size - 1) {
            tempBytes[i] = stream[offset + i]
        }

        result = String(tempBytes)
    }

    return result
}

//读取chunk的头部
fun getResChunk_header(stream: ByteArray, index: Int): ResChunk_header {
    return ResChunk_header(read_uint16_t(stream, index), read_uint16_t(stream, index + 2), read_uint32_t(stream, index + 4))
}

//得到int值
fun getIntValue(value: Int): Int {
    val bytes = ByteArray(4)
    bytes[0] = (value and 0x000000ff).toByte()
    bytes[1] = ((value ushr 8) and 0x000000ff).toByte()
    bytes[2] = ((value ushr 16) and 0x000000ff).toByte()
    bytes[3] = ((value ushr 24) and 0x000000ff).toByte()
    var result: Int = 0
    for (i in 0..3) {
        result = result shl 8
        result = result or (bytes[i].toInt() and 0xFF)
    }
    return result
}
//得到long值
fun getLongValue(value: Long): Long {
    val bytes = ByteArray(8)
    bytes[0] = (value and 0x000000ff).toByte()
    bytes[1] = ((value ushr 8) and 0x000000ff).toByte()
    bytes[2] = ((value ushr 16) and 0x000000ff).toByte()
    bytes[3] = ((value ushr 24) and 0x000000ff).toByte()
    bytes[0] = ((value ushr 32) and 0x000000ff).toByte()
    bytes[1] = ((value ushr 40) and 0x000000ff).toByte()
    bytes[2] = ((value ushr 48) and 0x000000ff).toByte()
    bytes[3] = ((value ushr 56) and 0x000000ff).toByte()
    var result: Long = 0
    for (i in 0..bytes.size - 1) {
        result = result shl 8
        result = result or (bytes[i].toLong() and 0xFF)
    }
    return result
}
//得到short值
fun getShortValue(value: Short): Short {
    val bytes = ByteArray(2)
    bytes[0] = (value and 0x00ff).toByte()
    bytes[1] = ((value.toInt() shr 8) and 0x00ff).toByte()
    var result: Short = 0
    for (i in 0..1) {
        result = (result.toInt() shl 8).toShort()
        result = result or (bytes[i].toInt() and 0xFF).toShort()
    }
    return result
}
//得到字符串池中具体的值
fun getStringPoolStr(index: Int): String? {
    if (stringList == null || stringList?.get(index).isNullOrEmpty()) {
        return ""
    }
    return stringList?.get(index)
}

工具类里面主要包括了所有类型的读取,chunk头部信息的读取,字符串的读取以及真正值的转换。


10.2 文件读取和字节数组转换

    val stream = File("H:\\javaworkpace\\idea\\resourcesAnalyzer\\src\\com\\nick\\resources.arsc").inputStream()
    val os = ByteArrayOutputStream()
    val bytes = ByteArray(1024)
    var len = stream.read(bytes)
    while (len != -1) {
        os.write(bytes, 0, len)
        len = stream.read(bytes)
    }

10.3 解析ResTable_header信息

    println("---------------------解析ResTable_header信息开始---------------------")
    val resTable_header = ResTable_header(getResChunk_header(streamByte, 0), read_uint32_t(streamByte, Config.RESCHUNK_HEADER_SIZE))
    println(resTable_header)
    println("---------------------解析ResTable_header信息完毕---------------------")

有了类的定义,我们可以很简单的去解析头部的信息。输出如下:


img_277584b716f8860e3fa87c924fdba772.png
table header输出.png

10.4 解析字符串资源池

    // 偏移量即headerSize
    offset = resTable_header.header.headerSize.getValue().toInt()
    //读取字符串池
    val resStringPool_header = resStringPool_header(streamByte, offset)
    //全局的字符串池数组保存
    stringList = resStringPool_header.stringStringArray!!.stringArray

private fun resStringPool_header(streamByte: ByteArray, offset: Int): ResStringPool_header {
    println("---------------------解析ResStringPool_header信息开始---------------------")
    val resStringPool_header = ResStringPool_header(getResChunk_header(streamByte, offset),
            read_uint32_t(streamByte, offset + Config.RESCHUNK_HEADER_SIZE),
            read_uint32_t(streamByte, offset + Config.RESCHUNK_HEADER_SIZE + 4),
            read_uint32_t(streamByte, offset + Config.RESCHUNK_HEADER_SIZE + 8),
            read_uint32_t(streamByte, offset + Config.RESCHUNK_HEADER_SIZE + 12),
            read_uint32_t(streamByte, offset + Config.RESCHUNK_HEADER_SIZE + 16),
            null,
            null,
            null,
            null)
    println(resStringPool_header)
    println("---------------------解析ResStringPool_header信息完毕---------------------")
    //解析完成后会有两个偏移数组,一个是string偏移数组,另一个是style偏移数组

    // string偏移数组
    val stringCount = resStringPool_header.stringCount.getValue()
    val resString_offset_array = ResString_offset_array(Array(stringCount, {
        uint32_t(0)
    }))
    for (i in 0..stringCount - 1) {
        resString_offset_array.offset_array[i] = read_uint32_t(streamByte, resStringPool_header.header.headerSize.getValue() + offset + i * 4)
    }

    // style偏移数组
    val styleCount = resStringPool_header.styleCount.getValue()
    val resStyle_offset_array = ResStyle_offset_array(Array(styleCount, {
        uint32_t(0)
    }))
    for (i in 0..styleCount - 1) {
        resStyle_offset_array.offset_array[i] = read_uint32_t(streamByte, resStringPool_header.header.headerSize.getValue() + stringCount * 4 + offset + i * 4)
    }

    //解析字符串池
    val stringStartOffset = offset + resStringPool_header.stringsStart.getValue()
    val stringArray: Array<String> = Array(resStringPool_header.stringCount.getValue(), {
        ""
    })

    for (i in 0..resStringPool_header.stringCount.getValue() - 1) {
        var size: Int
        if (i + 1 <= resStringPool_header.stringCount.getValue() - 1) {
            size = resString_offset_array.offset_array[i + 1].getValue() - resString_offset_array.offset_array[i].getValue()
        } else {
            size = resStringPool_header.header.size.getValue() - resString_offset_array.offset_array[i].getValue() - resStringPool_header.stringCount.getValue() * 4 - 28
        }
        val resultPair = readString2(streamByte, size, stringStartOffset + resString_offset_array.offset_array[i].getValue())
        stringArray[i] = resultPair.first
    }

    //资源字符串值
    val resString_string_array = ResString_string_array(stringArray)
    resString_string_array.stringArray.forEachIndexed { index, s ->
        println("$index : $s")
    }

    println("----------------------读取style----------------------")
    val styleStartOffset = offset + resStringPool_header.stylesStart.getValue()
    val styleStringArray: Array<ResStringPool_span> = Array(resStringPool_header.styleCount.getValue(), {
        ResStringPool_span(
                ResStringPool_ref(uint32_t(0)),
                uint32_t(0),
                uint32_t(0)
        )
    })

    for (i in 0..resStringPool_header.styleCount.getValue() - 1) {
        styleStringArray[i] = ResStringPool_span(
                ResStringPool_ref(read_uint32_t(streamByte, styleStartOffset + resStyle_offset_array.offset_array[i].getValue())),
                read_uint32_t(streamByte, styleStartOffset + resStyle_offset_array.offset_array[i].getValue() + 4),
                read_uint32_t(streamByte, styleStartOffset + resStyle_offset_array.offset_array[i].getValue() + 8)
        )
    }

    for (i in 0..resStringPool_header.styleCount.getValue() - 1) {
        println(styleStringArray[i])
    }

    resStringPool_header.stringOffsetArray = resString_offset_array
    resStringPool_header.styleOffsetArray = resStyle_offset_array
    resStringPool_header.stringStringArray = resString_string_array
    resStringPool_header.styleStringArray = ResStyle_string_array(styleStringArray)
    return resStringPool_header
}

这一部分其实解析并不难,难的是关于字符串长度的计算。正如我之前说的,长度计算我在网上查了不少资料,但是实际操作的过程中总会出现乱码(长度计算错误)的现象。说实话,这个问题我可以通过其他方法来跳过,就如代码里写的,通过偏移数组来判断具体长度,解析的时候根据长度来解析。但是这么做明显是个错误的做法。经过几天的探索,找出了自认为是正确的方法,最后完成了这部分的解析。解析结果(部分):


img_281b7c882324557506e465c7462cedc2.png
字符串池.png

10.5 Package信息解析

    println("读取Package信息")
    // package的偏移量
    val packageOffset = resStringPool_header.header.size.getValue() + offset
    // 解析Package数据块
    val resTable_package = ResTable_package(
            getResChunk_header(streamByte, packageOffset),
            read_uint32_t(streamByte, packageOffset + Config.RESCHUNK_HEADER_SIZE),
            readStringWithSize(streamByte, packageOffset + Config.RESCHUNK_HEADER_SIZE + 4, 256),
            read_uint32_t(streamByte, packageOffset + Config.RESCHUNK_HEADER_SIZE + 4 + 256),
            read_uint32_t(streamByte, packageOffset + Config.RESCHUNK_HEADER_SIZE + 4 + 256 + 4),
            read_uint32_t(streamByte, packageOffset + Config.RESCHUNK_HEADER_SIZE + 4 + 256 + 4 + 4),
            read_uint32_t(streamByte, packageOffset + Config.RESCHUNK_HEADER_SIZE + 4 + 256 + 4 + 4 + 4))
    println(resTable_package)
    println("读取Package信息完毕")

    // 解析资源类型
    // 偏移量设置,
    val resTypePoolOffset = resTable_package.header.headerSize.getValue().toInt() + packageOffset
    val resTypeStringPool_header = resStringPool_header(streamByte, resTypePoolOffset)
    // 解析资源类型值
    val resInTypeStringPoolOffset = resTypePoolOffset + resTypeStringPool_header.header.size.getValue()
    val resInTypeStringPool_header = resStringPool_header(streamByte, resInTypeStringPoolOffset)

    var resTableTypeSpecOffset = resInTypeStringPoolOffset + resInTypeStringPool_header.header.size.getValue()

    var resTypeSpecOffset = resTableTypeSpecOffset

这里面解析比较简单,先解析Package数据,接着解析了资源项以及资源项的值。结果如下:


img_1366cf7b3dad25bfcddc1de301639895.png
Package和资源项部分结果

10.6 类型规范数据块和资源类型项数据块解析

    while (resTypeSpecOffset < streamByte.size) {

        if (read_uint16_t(streamByte, resTypeSpecOffset).getValue().toInt() == ResChunk_header.ChunkType.RES_TABLE_TYPE_SPEC_TYPE.type) {
            val resTable_typeSpec = ResTable_typeSpec(getResChunk_header(streamByte, resTypeSpecOffset),
                    read_uint8_t(streamByte, resTypeSpecOffset + Config.RESCHUNK_HEADER_SIZE),
                    read_uint8_t(streamByte, resTypeSpecOffset + Config.RESCHUNK_HEADER_SIZE + 1),
                    read_uint16_t(streamByte, resTypeSpecOffset + Config.RESCHUNK_HEADER_SIZE + 1 + 1),
                    read_uint32_t(streamByte, resTypeSpecOffset + Config.RESCHUNK_HEADER_SIZE + 1 + 1 + 2))
            println("$resTable_typeSpec, idValue = ${resTypeStringPool_header.stringStringArray!!.stringArray[resTable_typeSpec.id.getValue().toInt() - 1]}")

            val arrayOfUint32_ts = Array(resTable_typeSpec.entryCount.getValue(), {
                uint32_t(0)
            })

            (0..resTable_typeSpec.entryCount.getValue() - 1).forEachIndexed { index, i ->
                arrayOfUint32_ts[index] = read_uint32_t(streamByte, resTypeSpecOffset + Config.RESCHUNK_HEADER_SIZE + 1 + 1 + 2 + 4 + 4 * index)
            }
            val res_entry_array = Res_entry_array(arrayOfUint32_ts)
            println(res_entry_array)
            resTypeSpecOffset += resTable_typeSpec.header.size.getValue()
            println("===========================================")
        } else {
            val resTable_type = ResTable_type(
                    getResChunk_header(streamByte, resTypeSpecOffset),
                    read_uint8_t(streamByte, resTypeSpecOffset + Config.RESCHUNK_HEADER_SIZE),
                    read_uint8_t(streamByte, resTypeSpecOffset + Config.RESCHUNK_HEADER_SIZE + 1),
                    read_uint16_t(streamByte, resTypeSpecOffset + Config.RESCHUNK_HEADER_SIZE + 1 + 1),
                    read_uint32_t(streamByte, resTypeSpecOffset + Config.RESCHUNK_HEADER_SIZE + 1 + 1 + 2),
                    read_uint32_t(streamByte, resTypeSpecOffset + Config.RESCHUNK_HEADER_SIZE + 1 + 1 + 2 + 4),
                    ResTable_config(
                            read_uint32_t(streamByte, resTypeSpecOffset + Config.RESCHUNK_HEADER_SIZE + 1 + 1 + 2 + 4 * 2),
                            read_uint32_t(streamByte, resTypeSpecOffset + Config.RESCHUNK_HEADER_SIZE + 1 + 1 + 2 + 4 * 3),
                            read_uint32_t(streamByte, resTypeSpecOffset + Config.RESCHUNK_HEADER_SIZE + 1 + 1 + 2 + 4 * 4),
                            read_uint32_t(streamByte, resTypeSpecOffset + Config.RESCHUNK_HEADER_SIZE + 1 + 1 + 2 + 4 * 5),
                            read_uint32_t(streamByte, resTypeSpecOffset + Config.RESCHUNK_HEADER_SIZE + 1 + 1 + 2 + 4 * 6),
                            read_uint32_t(streamByte, resTypeSpecOffset + Config.RESCHUNK_HEADER_SIZE + 1 + 1 + 2 + 4 * 7),
                            read_uint32_t(streamByte, resTypeSpecOffset + Config.RESCHUNK_HEADER_SIZE + 1 + 1 + 2 + 4 * 8),
                            read_uint32_t(streamByte, resTypeSpecOffset + Config.RESCHUNK_HEADER_SIZE + 1 + 1 + 2 + 4 * 9),
                            read_uint32_t(streamByte, resTypeSpecOffset + Config.RESCHUNK_HEADER_SIZE + 1 + 1 + 2 + 4 * 10),
                            read_uint32_t(streamByte, resTypeSpecOffset + Config.RESCHUNK_HEADER_SIZE + 1 + 1 + 2 + 4 * 11),
                            read_uint64_t(streamByte, resTypeSpecOffset + Config.RESCHUNK_HEADER_SIZE + 1 + 1 + 2 + 4 * 12)
                    ))
            println(resTable_type)
            var tempOffset = resTypeSpecOffset + resTable_type.header.headerSize.getValue().toInt()

            val arrayOfUint32_ts = Array(resTable_type.entryCount.getValue(), {
                uint32_t(0)
            })

            (0..resTable_type.entryCount.getValue() - 1).forEachIndexed { index, i ->
                arrayOfUint32_ts[index] = read_uint32_t(streamByte, tempOffset + 4 * index)
            }

            val res_entry_array2 = Res_entry_array(arrayOfUint32_ts)
            println(res_entry_array2)

            tempOffset += resTable_type.entriesStart.getValue() - resTable_type.header.headerSize.getValue().toInt()

            val resString_string_array = resInTypeStringPool_header.stringStringArray
            var count = 0
            var resId = 0
            while (count < resTable_type.header.size.getValue() - resTable_type.entriesStart.getValue()) {
                println("resId = ${Integer.toHexString(resId)}")
                val size = read_uint16_t(streamByte, tempOffset + count)
                val flags = read_uint16_t(streamByte, tempOffset + count + 2)
                if (flags.getValue().toInt() == 1) {
                    val resTable_map_entry = ResTable_map_entry(size,
                            flags,
                            ResStringPool_ref(read_uint32_t(streamByte, tempOffset + count + 2 + 2)),
                            ResTable_ref(read_uint32_t(streamByte, tempOffset + count + 2 + 2 + 4)),
                            read_uint32_t(streamByte, tempOffset + count + 2 + 2 + 4 + 4))

                    val mapStr = resTable_map_entry.key!!.index.getValue()
                    resTable_map_entry.dataStr = resInTypeStringPool_header.stringStringArray!!.stringArray[mapStr]
                    println(resTable_map_entry)
                    count += resTable_map_entry.size.getValue().toInt()
                    println(resString_string_array!!.stringArray[mapStr])
                    println("--------------------------resTable_map start------------------------------")
                    for (index in 0..resTable_map_entry.count.getValue() - 1) {
                        val resTable_map = ResTable_map(
                                ResTable_ref(
                                        read_uint32_t(streamByte, tempOffset)),
                                Res_value(
                                        read_uint16_t(streamByte, tempOffset + 4),
                                        read_uint8_t(streamByte, tempOffset + 4 + 2),
                                        read_uint8_t(streamByte, tempOffset + 4 + 2 + 1),
                                        read_uint32_t(streamByte, tempOffset + 4 + 2 + 1 + 1)))
                        println(resTable_map)
                        count += Config.RESTABLE_MAP_SIZE
                    }
                    println("--------------------------resTable_map end------------------------------")
                } else {
                    val resTable_entry = ResTable_entry(size, flags, ResStringPool_ref(read_uint32_t(streamByte, tempOffset + count + 2 + 2)))
                    val index = resTable_entry.key!!.index.getValue()
                    if (index > 0 && index <= resInTypeStringPool_header.stringStringArray!!.stringArray.size - 1) {
                        resTable_entry.dataStr = resInTypeStringPool_header.stringStringArray!!.stringArray[index]
                    }
                    count += 8
                    val res_value = Res_value(read_uint16_t(streamByte, tempOffset + count),
                            read_uint8_t(streamByte, tempOffset + count + 2)
                            , read_uint8_t(streamByte, tempOffset + count + 2 + 1),
                            read_uint32_t(streamByte, tempOffset + count + 2 + 1 + 1))
                    println("--------------------------resTable_entry start------------------------------")
                    println(resTable_entry)
                    println("--------------------------resTable_entry end------------------------------")
                    println("--------------------------res_value start------------------------------")
                    println(res_value)
                    println("--------------------------res_value end------------------------------")
                    count += Config.RES_VALUE_SIZE
                }

                resId++
            }
            resTypeSpecOffset += resTable_type.header.size.getValue()
            println("===========================================")
        }
    }

这部分解析其实很繁琐,首先ResTable_typeSpec和ResTable_type是成对出现(通过现象得出结论。。),ResTable_typeSpec里面包括了该资源项对应的id(这个id对应的是解析资源项结果的索引+1)以及资源项个数,接着便是一个数组(具体用处不详),接着便是ResTable_type,这里面定义了资源的type id,资源项值的总个数,开始位置的偏移量以及配置信息。顺便说一句,我这个解析的时候,通过的是整体的size来判断是否解析完成,并没有根据entryCount来判断,这里和其他解析可能会有些出入。解析完ResTable_type,解析完成ResTable_type还需要根据entryCount来解析一个便宜数组,接着可以根据后面解析的flags来判断资源项的值是ResTable_entry还是ResTable_map_entry,如果是ResTable_entry的话,则添加对Res_value的解析,如果是ResTable_map_entry,则添加对ResTable_map的解析。
部分解析结果:


img_85b665940e74e117b500b1490b04c0c6.png
ResTable_typeSpec结果

img_903c9096a2d7623670aa6137c654f310.png
ResTable_type结果

写在后面的话

整个解析过程已经完成了,代码这里可以看到
总结下,还是不要瞎搞事情啊。本来自己有打算一步步来的,结果半路看到这个断断续续搞了两周时间,其中心酸的历程不多说了,不断的肯定自己又不断的否定自己。不过呢,最后终于算是将东西完成了。以前觉得吧,站在巨人的肩膀上看世界,很轻松。后来发现,巨人的肩膀可能也有漏洞。

img_b0d1d676a9658f5c9669ccb942d473ea.png

目录
相关文章
|
20天前
|
开发框架 前端开发 Android开发
安卓与iOS开发中的跨平台框架解析
在移动应用开发的广阔舞台上,安卓和iOS一直是两大主角。随着技术的进步,开发者们渴望能有一种方式,让他们的应用能同时在这两大平台上运行,而不必为每一个平台单独编写代码。这就是跨平台框架诞生的背景。本文将探讨几种流行的跨平台框架,包括它们的优势、局限性,以及如何根据项目需求选择合适的框架。我们将从技术的深度和广度两个维度,对这些框架进行比较分析,旨在为开发者提供一个清晰的指南,帮助他们在安卓和iOS的开发旅程中,做出明智的选择。
|
1月前
|
Java 调度 Android开发
深入解析Android应用开发中的响应式编程与RxJava应用
在现代Android应用开发中,响应式编程及其核心框架RxJava正逐渐成为开发者的首选。本文将深入探讨响应式编程的基本概念、RxJava的核心特性以及如何在Android应用中利用RxJava提升代码的可读性和性能。 【7月更文挑战第7天】
|
2月前
|
XML 存储 JSON
51. 【Android教程】JSON 数据解析
51. 【Android教程】JSON 数据解析
58 2
|
2天前
|
JSON Java Android开发
Android 开发者必备秘籍:轻松攻克 JSON 格式数据解析难题,让你的应用更出色!
【8月更文挑战第18天】在Android开发中,解析JSON数据至关重要。JSON以其简洁和易读成为首选的数据交换格式。开发者可通过多种途径解析JSON,如使用内置的`JSONObject`和`JSONArray`类直接操作数据,或借助Google提供的Gson库将JSON自动映射为Java对象。无论哪种方法,正确解析JSON都是实现高效应用的关键,能帮助开发者处理网络请求返回的数据,并将其展示给用户,从而提升应用的功能性和用户体验。
|
18天前
|
XML Android开发 UED
"掌握安卓开发新境界:深度解析AndroidManifest.xml中的Intent-filter配置,让你的App轻松响应scheme_url,开启无限交互可能!"
【8月更文挑战第2天】在安卓开发中,scheme_url 通过在`AndroidManifest.xml`中配置`Intent-filter`,使应用能响应特定URL启动或执行操作。基本配置下,应用可通过定义特定URL模式的`Intent-filter`响应相应链接。
48 12
|
23天前
|
存储 数据库 Android开发
🔥Android Jetpack全解析!拥抱Google官方库,让你的开发之旅更加顺畅无阻!🚀
【7月更文挑战第28天】在Android开发中追求高效稳定的路径?Android Jetpack作为Google官方库集合,是你的理想选择。它包含多个独立又协同工作的库,覆盖UI到安全性等多个领域,旨在减少样板代码,提高开发效率与应用质量。Jetpack核心组件如LiveData、ViewModel、Room等简化了数据绑定、状态保存及数据库操作。引入Jetpack只需在`build.gradle`中添加依赖。例如,使用Room进行数据库操作变得异常简单,从定义实体到实现CRUD操作,一切尽在掌握之中。拥抱Jetpack,提升开发效率,构建高质量应用!
42 4
|
3天前
|
编译器 Android开发 开发者
Android经典实战之Kotlin 2.0 迁移指南:全方位优化与新特性解析
本文首发于公众号“AntDream”。Kotlin 2.0 已经到来,带来了 K2 编译器、多平台项目支持、智能转换等重大改进。本文提供全面迁移指南,涵盖编译器升级、多平台配置、Jetpack Compose 整合、性能优化等多个方面,帮助开发者顺利过渡到 Kotlin 2.0,开启高效开发新时代。
6 0
|
1月前
|
开发工具 Android开发 Swift
安卓与iOS开发环境的差异性解析
【7月更文挑战第11天】在移动应用开发的广阔天地中,安卓与iOS两大阵营各据一方,它们的开发环境差异如同东西方文化的差异一样鲜明。本文将深入探讨这两个平台在开发工具、编程语言、用户界面设计以及系统架构等方面的不同,旨在为开发者提供一个清晰的对比视角,帮助他们根据项目需求和个人偏好选择最合适的开发路径。
|
2月前
|
前端开发 JavaScript 测试技术
安卓应用开发中的架构模式解析
【6月更文挑战第21天】在软件开发领域,架构模式是设计优雅、高效、可维护应用程序的基石。本文深入探讨了安卓应用开发中常见的架构模式,包括MVC、MVP、MVVM和Clean Architecture,旨在为开发者提供一个清晰的指导,帮助他们选择最适合自己项目的架构风格。通过对比分析这些架构模式的特点、优势以及适用场景,文章揭示了如何根据项目需求和团队能力来采用恰当的架构模式,以实现代码的可维护性、可扩展性和可测试性。
40 7
|
2月前
|
Java 开发工具 Android开发
安卓与iOS开发差异解析
【6月更文挑战第21天】本文旨在深入探讨安卓和iOS两大移动操作系统在应用开发过程中的主要差异。通过对比分析,揭示各自的设计哲学、编程语言选择、用户界面构建、性能优化策略以及发布流程的异同。文章将提供开发者视角下的实用信息,帮助他们更好地理解各自平台的特点和挑战,从而做出更明智的开发决策。

推荐镜像

更多