Android resources.arsc的解析

寫在前面的話

從寫下這個標題開始,我就知道這篇文章需要幾天的時間才能真正完成。當然不是因為一切從零開始,只是因為要整理的東西太多。說幾句題外話,關(guān)于為啥要寫這個resources.arsc文件的解析。本來啊,想按照我之前寫的一步步寫下去,結(jié)果一天無聊看文章,發(fā)現(xiàn)了這個文件的解析過程。想起我的舍友(技術(shù)大佬)寫過一篇關(guān)于.class文件的解析(很慚愧,我也照著寫了一遍,還發(fā)現(xiàn)了點小問題),所以看到這篇文章又勾起我對二進制文件解析的興趣(痛苦啊?。。戇@篇文章的時候,我已經(jīng)基本實現(xiàn)了整個解析過程,但是其中我測試了七個不同的resources.arsc,只有支付寶的解析會有問題,wtf!!沒辦法,最后打開二進制文件,一點點找問題,最后通過我強大的填坑技術(shù),終于完成。不過呢,這次不打算通過FileInputStream一點點去read實現(xiàn),我會使用ByteArray來實現(xiàn)這個解析過程,整個實現(xiàn)過程是kotlin語言,很多不熟悉的地方,還有很多高級用法沒有使用。


1. 關(guān)于resources.arsc的網(wǎng)傳神圖

網(wǎng)上搜索關(guān)于resources.arsc文件,一般會出現(xiàn)老羅的打包過程以及尼古拉斯_趙四這兩位真神的講解。其中老羅的講解中出現(xiàn)了一幅圖片,關(guān)于resources.arsc的格式。圖片如下:


resources.arsc格式

2. resources.arsc格式詳細分析

按照上面的圖片來說,整個resources.arsc文件分為了六個部分(暫時按照顏色分吧),這幾個部分可能就是他們所說的chunk把,從上到下依次是:

  1. package數(shù)量
  2. String Pool,字符串池
  3. Package 信息
  4. 資源類型字符串池和資源項名稱字符串池
  5. 類型規(guī)范數(shù)據(jù)塊
  6. 資源類型項數(shù)據(jù)塊

3. 先說說頭部信息

我們可以從圖中看到,每塊結(jié)構(gòu)都是以TYPE、頭大小、文件大小開頭,這一部分組成了頭部的信息根據(jù)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 ""
    }
}

上面的定義就是關(guān)于chunk的頭部信息的定義:

  • type:當前這個chunk的類型
  • headerSize:當前這個chunk的頭部大小
  • size:當前這個chunk的大小

4. package數(shù)量格式

ResTable_header是關(guān)于這個格式的定義,其實里面只有一個uint32_t的packageCount,用于存儲package的數(shù)量

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可能包含多個資源包,默認情況下都只有一個就是應(yīng)用的包名所在的資源包

  • packageCount:被編譯的資源包的個數(shù)
    下圖是我使用的arsc文件的ResTable_header的信息,可以看到這一部分的TYPE是0x0002,對應(yīng)了RES_TABLE_TYPE,并且其headerSize是0x000C,也就是12,整個chunk的大小是377904(0x0005C430),packag的數(shù)量是1(0x00000001)


    ResTable_header

    總大小.png

    這一部分總體來說解析很簡單,前面的兩個字節(jié)對應(yīng)了類型,接著兩個字節(jié)對應(yīng)了headerSize,后面四個字節(jié)對應(yīng)了整個大小size,最后的四個字節(jié)對應(yīng)了package的大小,這里我們可以看到這個數(shù)量是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, //字符串的數(shù)量
                           var styleCount: uint32_t, //字符串樣式的數(shù)量
                           var flags: uint32_t, //字符串的屬性,可取值包括0x000(UTF-16),0x001(字符串經(jīng)過排序)、0X100(UTF-8)和他們的組合值
                           var stringsStart: uint32_t, //字符串內(nèi)容塊相對于其頭部的距離
                           var stylesStart: uint32_t, //字符串樣式塊相對于其頭部的距離
                           var stringOffsetArray: ResString_offset_array?,//字符串偏移數(shù)組
                           var styleOffsetArray: ResStyle_offset_array?,//字符串樣式偏移數(shù)組
                           var stringStringArray: ResString_string_array?,//字符串數(shù)組
                           var styleStringArray: ResStyle_string_array?//字符串樣式數(shù)組

) {
    // 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
    }
}

從定義可以看到,這一部分內(nèi)容挺多:

  • header:頭部信息,ResChunk_header格式
  • stringCount:字符串的數(shù)量,uint32_t格式
  • styleCount:字符串樣式的數(shù)量,uint32_t格式
  • flags:字符串的屬性,可取值包括0x000(UTF-16),0x001(字符串經(jīng)過排序)、0X100(UTF-8)和他們的組合值,uint32_t格式
  • stringsStart:字符串開始位置,相對于頭部,uint32_t格式
  • stylesStart:字符串樣式開始位置,相對于頭部,uint32_t格式
  • 四個數(shù)組:自己定義,用于存儲偏移量以及string的值
    下圖是我使用的arsc文件的ResStringPool_header信息(不包括數(shù)組內(nèi)容):


    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)
    上面的信息完成后,接著便是兩個偏移數(shù)組(字符串偏移數(shù)組和字符串樣式偏移數(shù)組),偏移數(shù)組結(jié)束后就是字符串池。字符串資源池中的字符串前兩個字節(jié)為字符串長度,長度計算方法如下。另外如果字符串編碼格式為UTF-8則字符串以0X00作為結(jié)束符,UTF-16則以0X0000作為結(jié)束符,我這里默認認為是UTF-8編碼,解析過程完全按照UTF-8編碼來解析。在長度計算的時候,我發(fā)現(xiàn)網(wǎng)上很多人寫的都不是很對,起碼當長度超過128(一個字節(jié))的時候就會有問題,這個長度計算可是費了好多腦子,寫寫畫畫好幾天,終于弄出一個我認為是正確的計算方法(我不保證正確,但至少我解析的過程是正確的,姑且認為是對的吧):
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
    }
}

每個字段對應(yīng)的含義如下:

  • header:Chunk的頭部信息數(shù)據(jù)結(jié)構(gòu)
  • id:包的ID,等于Package Id,一般用戶包的值Package Id為0X7F,系統(tǒng)資源包的Package Id為0X01,uint32_t格式
  • name:包名,String,256字節(jié)
  • typeString:類型字符串資源池相對頭部的偏移,uint32_t格式
  • lastPublicType:最后一個導(dǎo)出的Public類型字符串在類型字符串資源池中的索引,uint32_t格式
  • keyStrings:資源項名稱字符串相對頭部的偏移,uint32_t格式
  • lastPublicKey:最后一個導(dǎo)出的Public資源項名稱字符串在資源項名稱字符串資源池中的索引,uint32_t格式
    示例如下圖:


    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. 類型規(guī)范數(shù)據(jù)塊

特定類型定義的資源規(guī)范,每種資源類型應(yīng)該有一個這樣的塊。該結(jié)構(gòu)之后是一個整數(shù)數(shù)組,提供了一組配置更改標志(ResTable_config :: CONFIG_ *),該標志具有該配置的多個資源。 另外,如果該資源已被公開,則高位被設(shè)置。其數(shù)據(jù)結(jié)構(gòu)如下:

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) {// 資源項個數(shù)

    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
    }

}

每個字段對應(yīng)的含義如下:

  • header:Chunk的頭部信息數(shù)據(jù)結(jié)構(gòu)
  • id:每個資源獨有的id,uint8_t結(jié)構(gòu)
  • res0和res1:保留字段,都為0,uint8_t和uint16_t結(jié)構(gòu)
  • entryCount:當前類型的資源項的個數(shù),uint32_t結(jié)構(gòu)
    示例如下圖:


    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對應(yīng)的資源項值attr

9. 資源類型項數(shù)據(jù)塊

這一部分我的理解就是attr、drawable等文件的具體項,每個資源項都對應(yīng)了多個值,這些值在之前解析中已經(jīng)出現(xiàn),這里只通過位置來確定具體值。這邊的結(jié)構(gòu)有點多,這里還是只展示一部分吧,真心有點多。具體定義如下:

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.
    //這個不展開了,內(nèi)容有點多
    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,// 總個數(shù)
                    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的數(shù)量
) : 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類的資源。
// 這些資源會給自己定義一些專用的值,這些帶有專用值的資源就統(tǒng)稱為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) { // 數(shù)據(jù),根據(jù)不同類型來展示不同數(shù)據(jù),這部分基本上是網(wǎng)上拿來的

    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", "", "", "", "", "", "")
    }
}

上面的結(jié)構(gòu)定義確實很多,有一部分是從先人的博客中摘抄的,但我并不認為他們的全是正確的,因為在我自己解析的過程中一次次肯定又一次次否定,慢慢自己發(fā)現(xiàn)規(guī)律并完成整個過程。
上面結(jié)構(gòu)體ResTable_type定義了該資源項的資源id、總個數(shù)、配置信息以及偏移量,接著根據(jù)ResTable_entry的flags來判斷具體是ResTable_map_entry還是ResTable_entry,這里面定義了size,即大小。如果類型是ResTable_map_entry,則根據(jù)ResTable_map_entry的count設(shè)置具體的ResTable_map,ResTable_map中包括了資源項的id和值(Res_value)。如果類型是ResTable_entry,那么這里會有一個資源項的值Res_value。Res_value中根據(jù)類型(dataType)來判斷具體屬于什么類型,并根據(jù)類型和數(shù)據(jù)(data)來獲得具體的值。


10. 解析過程

了解了resources.arsc的文件結(jié)構(gòu),可以通過結(jié)構(gòu)和二進制文件查看器來進行文件的解析。

10.1 工具類和類型定義

ibasic_unit:基礎(chǔ)的接口
interface ibasic_unit<T> {
    fun getValue(): T //value
    fun getHexValue(): String // 返回16進制格式化字符串
}
basic_unit:基礎(chǔ)的抽象實現(xiàn)類
abstract class basic_unit<T>(protected var relValue: T) : ibasic_unit<T> {
    override fun getValue(): T {
        return relValue
    }
}
uint8_t:讀取一個字節(jié),對應(yīng)類型是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:讀取兩個字節(jié),對應(yīng)的值的類型是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:讀取四個字節(jié)的值,對應(yīng)類型是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 //字符串池數(shù)組

//讀取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
}

//根據(jù)字符串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頭部信息的讀取,字符串的讀取以及真正值的轉(zhuǎn)換。


10.2 文件讀取和字節(jié)數(shù)組轉(zhuǎn)換

    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信息完畢---------------------")

有了類的定義,我們可以很簡單的去解析頭部的信息。輸出如下:


table header輸出.png

10.4 解析字符串資源池

    // 偏移量即headerSize
    offset = resTable_header.header.headerSize.getValue().toInt()
    //讀取字符串池
    val resStringPool_header = resStringPool_header(streamByte, offset)
    //全局的字符串池數(shù)組保存
    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信息完畢---------------------")
    //解析完成后會有兩個偏移數(shù)組,一個是string偏移數(shù)組,另一個是style偏移數(shù)組

    // string偏移數(shù)組
    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偏移數(shù)組
    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
}

這一部分其實解析并不難,難的是關(guān)于字符串長度的計算。正如我之前說的,長度計算我在網(wǎng)上查了不少資料,但是實際操作的過程中總會出現(xiàn)亂碼(長度計算錯誤)的現(xiàn)象。說實話,這個問題我可以通過其他方法來跳過,就如代碼里寫的,通過偏移數(shù)組來判斷具體長度,解析的時候根據(jù)長度來解析。但是這么做明顯是個錯誤的做法。經(jīng)過幾天的探索,找出了自認為是正確的方法,最后完成了這部分的解析。解析結(jié)果(部分):


字符串池.png

10.5 Package信息解析

    println("讀取Package信息")
    // package的偏移量
    val packageOffset = resStringPool_header.header.size.getValue() + offset
    // 解析Package數(shù)據(jù)塊
    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信息完畢")

    // 解析資源類型
    // 偏移量設(shè)置,
    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數(shù)據(jù),接著解析了資源項以及資源項的值。結(jié)果如下:


Package和資源項部分結(jié)果

10.6 類型規(guī)范數(shù)據(jù)塊和資源類型項數(shù)據(jù)塊解析

    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是成對出現(xiàn)(通過現(xiàn)象得出結(jié)論。。),ResTable_typeSpec里面包括了該資源項對應(yīng)的id(這個id對應(yīng)的是解析資源項結(jié)果的索引+1)以及資源項個數(shù),接著便是一個數(shù)組(具體用處不詳),接著便是ResTable_type,這里面定義了資源的type id,資源項值的總個數(shù),開始位置的偏移量以及配置信息。順便說一句,我這個解析的時候,通過的是整體的size來判斷是否解析完成,并沒有根據(jù)entryCount來判斷,這里和其他解析可能會有些出入。解析完ResTable_type,解析完成ResTable_type還需要根據(jù)entryCount來解析一個便宜數(shù)組,接著可以根據(jù)后面解析的flags來判斷資源項的值是ResTable_entry還是ResTable_map_entry,如果是ResTable_entry的話,則添加對Res_value的解析,如果是ResTable_map_entry,則添加對ResTable_map的解析。
部分解析結(jié)果:


ResTable_typeSpec結(jié)果

ResTable_type結(jié)果

寫在后面的話

整個解析過程已經(jīng)完成了,代碼這里可以看到
總結(jié)下,還是不要瞎搞事情啊。本來自己有打算一步步來的,結(jié)果半路看到這個斷斷續(xù)續(xù)搞了兩周時間,其中心酸的歷程不多說了,不斷的肯定自己又不斷的否定自己。不過呢,最后終于算是將東西完成了。以前覺得吧,站在巨人的肩膀上看世界,很輕松。后來發(fā)現(xiàn),巨人的肩膀可能也有漏洞。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

推薦閱讀更多精彩內(nèi)容

  • Apk中的resources.arsc是aapt工具編譯資源時生成的一個重要文件。App資源能根據(jù)配置的變化,索引...
    小爨閱讀 21,202評論 4 44
  • 文章中所使用軟件和代碼資源 示例apk示例代碼binary view二進制文件查看工具:android 6.0系統(tǒng)...
    第八區(qū)閱讀 3,514評論 0 3
  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理,服務(wù)發(fā)現(xiàn),斷路器,智...
    卡卡羅2017閱讀 134,973評論 19 139
  • 第5章 引用類型(返回首頁) 本章內(nèi)容 使用對象 創(chuàng)建并操作數(shù)組 理解基本的JavaScript類型 使用基本類型...
    大學(xué)一百閱讀 3,270評論 0 4
  • 插件化-資源處理 寫的比較長,可以選擇跳過前面2節(jié),直接從0x03實例分析開始。如有錯誤,請不吝指正。 0x00 ...
    唐一川閱讀 5,443評論 2 22