寫在前面的話
從寫下這個標題開始,我就知道這篇文章需要幾天的時間才能真正完成。當然不是因為一切從零開始,只是因為要整理的東西太多。說幾句題外話,關(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的格式。圖片如下:
2. resources.arsc格式詳細分析
按照上面的圖片來說,整個resources.arsc文件分為了六個部分(暫時按照顏色分吧),這幾個部分可能就是他們所說的chunk把,從上到下依次是:
- package數(shù)量
- String Pool,字符串池
- Package 信息
- 資源類型字符串池和資源項名稱字符串池
- 類型規(guī)范數(shù)據(jù)塊
- 資源類型項數(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
從圖中可以看到:
- headerSize是28(0x001c)
- size是142676(0x00022d54)
- stringCount是2882(0x00000b42)
- styleCount是1(0x00000001)
- flags是256(0x00000100)代表了編碼是UTF-8
- stringsStart是11560(0x00002d28)
- 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
從圖中可以看出:
- headerSize = 288(0x0120)
- size = 235216( 0x000396d0)
- id = 127( 0x0000007f)
- name = c o m . n i c k . m y a p p l i c a t i o n
- typeStrings = 288(0x00000120)
- lastPublicType = 13(0x0000000d)
- keyStrings = 480(0x000001e0)
- 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
其表示的信息為:
- type = RES_TABLE_TYPE_SPEC_TYPE(0x0202)
- headerSize = 16(0x0010)
- size = 1472(0x000005c0 )
- id = 1(0x01)
- res0 =0,res1 = 0
- entryCount = 364(0x0000016c)
- 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信息完畢---------------------")
有了類的定義,我們可以很簡單的去解析頭部的信息。輸出如下:
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é)果(部分):
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é)果如下:
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é)果:
寫在后面的話
整個解析過程已經(jīng)完成了,代碼這里可以看到。
總結(jié)下,還是不要瞎搞事情啊。本來自己有打算一步步來的,結(jié)果半路看到這個斷斷續(xù)續(xù)搞了兩周時間,其中心酸的歷程不多說了,不斷的肯定自己又不斷的否定自己。不過呢,最后終于算是將東西完成了。以前覺得吧,站在巨人的肩膀上看世界,很輕松。后來發(fā)現(xiàn),巨人的肩膀可能也有漏洞。