写在前面的话
从写下这个标题开始,我就知道这篇文章需要几天的时间才能真正完成。当然不是因为一切从零开始,只是因为要整理的东西太多。说几句题外话,关于为啥要写这个resources.arsc文件的解析。本来啊,想按照我之前写的一步步写下去,结果一天无聊看文章,发现了这个文件的解析过程。想起我的舍友(技术大佬)写过一篇关于.class文件的解析(很惭愧,我也照着写了一遍,还发现了点小问题),所以看到这篇文章又勾起我对二进制文件解析的兴趣(痛苦啊!!)。写这篇文章的时候,我已经基本实现了整个解析过程,但是其中我测试了七个不同的resources.arsc,只有支付宝的解析会有问题,wtf!!没办法,最后打开二进制文件,一点点找问题,最后通过我强大的填坑技术,终于完成。不过呢,这次不打算通过FileInputStream
一点点去read
实现,我会使用ByteArray
来实现这个解析过程,整个实现过程是kotlin语言,很多不熟悉的地方,还有很多高级用法没有使用。
1. 关于resources.arsc的网传神图
网上搜索关于resources.arsc文件,一般会出现老罗的打包过程以及尼古拉斯_赵四这两位真神的讲解。其中老罗的讲解中出现了一幅图片,关于resources.arsc的格式。图片如下:
2. resources.arsc格式详细分析
按照上面的图片来说,整个resources.arsc文件分为了六个部分(暂时按照颜色分吧),这几个部分可能就是他们所说的chunk把,从上到下依次是:
- package数量
- String Pool,字符串池
- Package 信息
- 资源类型字符串池和资源项名称字符串池
- 类型规范数据块
- 资源类型项数据块
3. 先说说头部信息
我们可以从图中看到,每块结构都是以TYPE、头大小、文件大小开头,这一部分组成了头部的信息根据ResourceTypes.h的说明可以知道,这部分是chunk的头部,先看下定义:
ResourceTypes.h 定义
/**
* Header that appears at the front of every data chunk in a resource.
*/
struct ResChunk_header
{
// Type identifier for this chunk. The meaning of this value depends
// on the containing chunk.
uint16_t type;
// Size of the chunk header (in bytes). Adding this value to
// the address of the chunk allows you to find its associated data
// (if any).
uint16_t headerSize;
// Total size of this chunk (in bytes). This is the chunkSize plus
// the size of any data associated with the chunk. Adding this value
// to the chunk allows you to completely skip its contents (including
// any child chunks). If this value is the same as chunkSize, there is
// no data associated with the chunk.
uint32_t size;
};
ResChunk_header定义
class ResChunk_header(var type: uint16_t, //当前这个chunk的类型
var headerSize: uint16_t, //当前这个chunk的头部大小
var size: uint32_t) { //当前这个chunk的大小
//这地方可以改成companion object,懒得改了
enum class ChunkType(var type: Int) {
RES_NULL_TYPE(0x0000),
RES_STRING_POOL_TYPE(0x0001),
RES_TABLE_TYPE(0x0002),
RES_XML_TYPE(0x0003),
RES_XML_FIRST_CHUNK_TYPE(0x0100),
RES_XML_START_NAMESPACE_TYPE(0x0100),
RES_XML_END_NAMESPACE_TYPE(0x0101),
RES_XML_START_ELEMENT_TYPE(0x0102),
RES_XML_END_ELEMENT_TYPE(0x0103),
RES_XML_CDATA_TYPE(0x0104),
RES_XML_LAST_CHUNK_TYPE(0x017f),
RES_XML_RESOURCE_MAP_TYPE(0x0180),
RES_TABLE_PACKAGE_TYPE(0x0200),
RES_TABLE_TYPE_TYPE(0x0201),
RES_TABLE_TYPE_SPEC_TYPE(0x0202)
}
// 输出头部信息
override fun toString(): String {
return "type = ${getType(type)}, typeHexValue = ${type.getHexValue()}, headerSize = ${headerSize.getValue()}, headerHexValue = ${headerSize.getHexValue()}, size = ${size.getValue()}, sizeHexValue = ${size.getHexValue()}"
}
// 获得类型
fun getType(type: uint16_t): String {
when (type.getValue().toInt()) {
ResChunk_header.ChunkType.RES_NULL_TYPE.type -> return "RES_NULL_TYPE"
ResChunk_header.ChunkType.RES_STRING_POOL_TYPE.type -> return "RES_STRING_POOL_TYPE"
ResChunk_header.ChunkType.RES_TABLE_TYPE.type -> return "RES_TABLE_TYPE"
ResChunk_header.ChunkType.RES_XML_TYPE.type -> return "RES_XML_TYPE"
ResChunk_header.ChunkType.RES_XML_FIRST_CHUNK_TYPE.type -> return "RES_XML_FIRST_CHUNK_TYPE"
ResChunk_header.ChunkType.RES_XML_START_NAMESPACE_TYPE.type -> return "RES_XML_START_NAMESPACE_TYPE"
ResChunk_header.ChunkType.RES_XML_END_NAMESPACE_TYPE.type -> return "RES_XML_END_NAMESPACE_TYPE"
ResChunk_header.ChunkType.RES_XML_START_ELEMENT_TYPE.type -> return "RES_XML_START_ELEMENT_TYPE"
ResChunk_header.ChunkType.RES_XML_END_ELEMENT_TYPE.type -> return "RES_XML_END_ELEMENT_TYPE"
ResChunk_header.ChunkType.RES_XML_CDATA_TYPE.type -> return "RES_XML_CDATA_TYPE"
ResChunk_header.ChunkType.RES_XML_LAST_CHUNK_TYPE.type -> return "RES_XML_LAST_CHUNK_TYPE"
ResChunk_header.ChunkType.RES_XML_RESOURCE_MAP_TYPE.type -> return "RES_XML_RESOURCE_MAP_TYPE"
ResChunk_header.ChunkType.RES_TABLE_PACKAGE_TYPE.type -> return "RES_TABLE_PACKAGE_TYPE"
ResChunk_header.ChunkType.RES_TABLE_TYPE_TYPE.type -> return "RES_TABLE_TYPE_TYPE"
ResChunk_header.ChunkType.RES_TABLE_TYPE_SPEC_TYPE.type -> return "RES_TABLE_TYPE_SPEC_TYPE"
}
return ""
}
}
上面的定义就是关于chunk的头部信息的定义:
- type:当前这个chunk的类型
- headerSize:当前这个chunk的头部大小
- size:当前这个chunk的大小
4. package数量格式
ResTable_header是关于这个格式的定义,其实里面只有一个uint32_t的packageCount,用于存储package的数量
ResourceTypes.h定义
/**
* Header for a resource table. Its data contains a series of
* additional chunks:
* * A ResStringPool_header containing all table values. This string pool
* contains all of the string values in the entire resource table (not
* the names of entries or type identifiers however).
* * One or more ResTable_package chunks.
*
* Specific entries within a resource table can be uniquely identified
* with a single integer as defined by the ResTable_ref structure.
*/
struct ResTable_header
{
struct ResChunk_header header;
// The number of ResTable_package structures.
uint32_t packageCount;
};
ResTable_header定义
class ResTable_header(var header: ResChunk_header, var packageCount: uint32_t) {
override fun toString(): String {
return header.toString() + ", packagerCount = ${packageCount.getValue()}, packagerCountHexValue = ${packageCount.getHexValue()}"
}
}
Android中一个apk可能包含多个资源包,默认情况下都只有一个就是应用的包名所在的资源包
-
packageCount:被编译的资源包的个数
下图是我使用的arsc文件的ResTable_header的信息,可以看到这一部分的TYPE是0x0002,对应了RES_TABLE_TYPE,并且其headerSize是0x000C,也就是12,整个chunk的大小是377904(0x0005C430),packag的数量是1(0x00000001)
这一部分总体来说解析很简单,前面的两个字节对应了类型,接着两个字节对应了headerSize,后面四个字节对应了整个大小size,最后的四个字节对应了package的大小,这里我们可以看到这个数量是1。
5. String Pool
先看下ResourceTypes.h对String Pool的定义:
ResourceTypes.h定义
struct ResStringPool_header
{
struct ResChunk_header header;
// Number of strings in this pool (number of uint32_t indices that follow
// in the data).
uint32_t stringCount;
// Number of style span arrays in the pool (number of uint32_t indices
// follow the string indices).
uint32_t styleCount;
// Flags.
enum {
// If set, the string index is sorted by the string values (based
// on strcmp16()).
SORTED_FLAG = 1<<0,
// String pool is encoded in UTF-8
UTF8_FLAG = 1<<8
};
uint32_t flags;
// Index from header of the string data.
uint32_t stringsStart;
// Index from header of the style data.
uint32_t stylesStart;
};
ResTable_header定义
class ResStringPool_header(var header: ResChunk_header,
var stringCount: uint32_t, //字符串的数量
var styleCount: uint32_t, //字符串样式的数量
var flags: uint32_t, //字符串的属性,可取值包括0x000(UTF-16),0x001(字符串经过排序)、0X100(UTF-8)和他们的组合值
var stringsStart: uint32_t, //字符串内容块相对于其头部的距离
var stylesStart: uint32_t, //字符串样式块相对于其头部的距离
var stringOffsetArray: ResString_offset_array?,//字符串偏移数组
var styleOffsetArray: ResStyle_offset_array?,//字符串样式偏移数组
var stringStringArray: ResString_string_array?,//字符串数组
var styleStringArray: ResStyle_string_array?//字符串样式数组
) {
// Flags.
enum class FLAGS(var flag: Int) {
// If set, the string index is sorted by the string values (based
// on strcmp16()).
SORTED_FLAG(1 shl 0),
// String pool is encoded in UTF-8
UTF8_FLAG(1 shl 8)
}
override fun toString(): String {
val result = """$header,
|stringCount = ${stringCount.getValue()}, stringCountHexValue = ${stringCount.getHexValue()},
|styleCount = ${styleCount.getValue()}, styleCountHexValue = ${styleCount.getHexValue()},
|flags = ${flags.getValue()}, flagsHexValue = ${flags.getHexValue()},
|stringsStart = ${stringsStart.getValue()}, stringsStartCountHexValue = ${stringsStart.getHexValue()},
|stylesStart = ${stylesStart.getValue()}, stylesStartHexValue = ${stylesStart.getHexValue()},""".trimMargin()
return result
}
}
从定义可以看到,这一部分内容挺多:
- header:头部信息,ResChunk_header格式
- stringCount:字符串的数量,uint32_t格式
- styleCount:字符串样式的数量,uint32_t格式
- flags:字符串的属性,可取值包括0x000(UTF-16),0x001(字符串经过排序)、0X100(UTF-8)和他们的组合值,uint32_t格式
- stringsStart:字符串开始位置,相对于头部,uint32_t格式
- stylesStart:字符串样式开始位置,相对于头部,uint32_t格式
-
四个数组:自己定义,用于存储偏移量以及string的值
下图是我使用的arsc文件的ResStringPool_header信息(不包括数组内容):
从图中可以看到:
- headerSize是28(0x001c)
- size是142676(0x00022d54)
- stringCount是2882(0x00000b42)
- styleCount是1(0x00000001)
- flags是256(0x00000100)代表了编码是UTF-8
- stringsStart是11560(0x00002d28)
- stylesStart是142652(0x00022d3c)
上面的信息完成后,接着便是两个偏移数组(字符串偏移数组和字符串样式偏移数组),偏移数组结束后就是字符串池。字符串资源池中的字符串前两个字节为字符串长度,长度计算方法如下。另外如果字符串编码格式为UTF-8则字符串以0X00作为结束符,UTF-16则以0X0000作为结束符,我这里默认认为是UTF-8编码,解析过程完全按照UTF-8编码来解析。在长度计算的时候,我发现网上很多人写的都不是很对,起码当长度超过128(一个字节)的时候就会有问题,这个长度计算可是费了好多脑子,写写画画好几天,终于弄出一个我认为是正确的计算方法(我不保证正确,但至少我解析的过程是正确的,姑且认为是对的吧):
var i = 0
while (realSize and (0x80 shl i) != 0) {
c++
byteArray[0] = stream[index + c]
realSize = ((realSize and 0x7f) shl 8) or (byteArray[0].toInt() and 0xff)
i += 4
}
6. Package信息
继String Pool信息之后,就是Package信息块。其定义如下:
ResourceTypes.h定义
struct ResTable_package
{
struct ResChunk_header header;
// If this is a base package, its ID. Package IDs start
// at 1 (corresponding to the value of the package bits in a
// resource identifier). 0 means this is not a base package.
uint32_t id;
// Actual name of this package, \0-terminated.
uint16_t name[128];
// Offset to a ResStringPool_header defining the resource
// type symbol table. If zero, this package is inheriting from
// another base package (overriding specific values in it).
uint32_t typeStrings;
// Last index into typeStrings that is for public use by others.
uint32_t lastPublicType;
// Offset to a ResStringPool_header defining the resource
// key symbol table. If zero, this package is inheriting from
// another base package (overriding specific values in it).
uint32_t keyStrings;
// Last index into keyStrings that is for public use by others.
uint32_t lastPublicKey;
uint32_t typeIdOffset;
};
ResTable_package定义
class ResTable_package(
var header: ResChunk_header,
// If this is a base package, its ID. Package IDs start
// at 1 (corresponding to the value of the package bits in a
// resource identifier). 0 means this is not a base package.
var id: uint32_t,
// Actual name of this package, \0-terminated.
var name: String,
// Offset to a ResStringPool_header defining the resource
// type symbol table. If zero, this package is inheriting from
// another base package (overriding specific values in it).
var typeStrings: uint32_t,
// Last index into typeStrings that is for public use by others.
var lastPublicType: uint32_t,
// Offset to a ResStringPool_header defining the resource
// key symbol table. If zero, this package is inheriting from
// another base package (overriding specific values in it).
var keyStrings: uint32_t,
// Last index into keyStrings that is for public use by others.
var lastPublicKey: uint32_t) {
override fun toString(): String {
val result = """$header,
|id = ${id.getValue()}, idHexValue = ${id.getHexValue()}
|name = $name
|typeStrings = ${typeStrings.getValue()}, typeStringsHexValue = ${typeStrings.getHexValue()}
|lastPublicType = ${lastPublicType.getValue()}, lastPublicTypeHexValue = ${lastPublicType.getHexValue()}
|keyStrings = ${keyStrings.getValue()}, keyStringsHexValue = ${keyStrings.getHexValue()}
|lastPublicKey = ${lastPublicKey.getValue()}, lastPublicKeyHexValue = ${lastPublicKey.getHexValue()}""".trimMargin()
return result
}
}
每个字段对应的含义如下:
- header:Chunk的头部信息数据结构
- id:包的ID,等于Package Id,一般用户包的值Package Id为0X7F,系统资源包的Package Id为0X01,uint32_t格式
- name:包名,String,256字节
- typeString:类型字符串资源池相对头部的偏移,uint32_t格式
- lastPublicType:最后一个导出的Public类型字符串在类型字符串资源池中的索引,uint32_t格式
- keyStrings:资源项名称字符串相对头部的偏移,uint32_t格式
-
lastPublicKey:最后一个导出的Public资源项名称字符串在资源项名称字符串资源池中的索引,uint32_t格式
示例如下图:
从图中可以看出:
- 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. 类型规范数据块
特定类型定义的资源规范,每种资源类型应该有一个这样的块。该结构之后是一个整数数组,提供了一组配置更改标志(ResTable_config :: CONFIG_ *),该标志具有该配置的多个资源。 另外,如果该资源已被公开,则高位被设置。其数据结构如下:
ResourceTypes.h定义
struct ResTable_typeSpec
{
struct ResChunk_header header;
// The type identifier this chunk is holding. Type IDs start
// at 1 (corresponding to the value of the type bits in a
// resource identifier). 0 is invalid.
uint8_t id;
// Must be 0.
uint8_t res0;
// Must be 0.
uint16_t res1;
// Number of uint32_t entry configuration masks that follow.
uint32_t entryCount;
enum {
// Additional flag indicating an entry is public.
SPEC_PUBLIC = 0x40000000
};
};
ResTable_typeSpec
class ResTable_typeSpec(var header: ResChunk_header,
var id: uint8_t,// id,每个资源独有的id
var res0: uint8_t,// 保留字段,为0
var res1: uint16_t, // 保留字段,为0
var entryCount: uint32_t) {// 资源项个数
override fun toString(): String {
return """header = $header ,
|id = ${id.getValue()}, idHexValue = ${id.getHexValue()},
|res0 =${res0.getValue()} ,res1 = ${res1.getValue()} ,
|entryCount = ${entryCount.getValue()}, entryCountHexValue = ${entryCount.getHexValue()}""".trimMargin()
}
companion object {
val SPEC_PUBLIC = 0x40000000
}
}
每个字段对应的含义如下:
- header:Chunk的头部信息数据结构
- id:每个资源独有的id,uint8_t结构
- res0和res1:保留字段,都为0,uint8_t和uint16_t结构
-
entryCount:当前类型的资源项的个数,uint32_t结构
示例如下图:
其表示的信息为:
- type = RES_TABLE_TYPE_SPEC_TYPE(0x0202)
- headerSize = 16(0x0010)
- size = 1472(0x000005c0 )
- id = 1(0x01)
- res0 =0,res1 = 0
- entryCount = 364(0x0000016c)
- id对应的资源项值attr
9. 资源类型项数据块
这一部分我的理解就是attr、drawable等文件的具体项,每个资源项都对应了多个值,这些值在之前解析中已经出现,这里只通过位置来确定具体值。这边的结构有点多,这里还是只展示一部分吧,真心有点多。具体定义如下:
ResourceTypes.h定义:
struct ResTable_type
{
struct ResChunk_header header;
enum {
NO_ENTRY = 0xFFFFFFFF
};
// The type identifier this chunk is holding. Type IDs start
// at 1 (corresponding to the value of the type bits in a
// resource identifier). 0 is invalid.
uint8_t id;
// Must be 0.
uint8_t res0;
// Must be 0.
uint16_t res1;
// Number of uint32_t entry indices that follow.
uint32_t entryCount;
// Offset from header where ResTable_entry data starts.
uint32_t entriesStart;
// Configuration this collection of entries is designed for.
//这个不展开了,内容有点多
ResTable_config config;
};
// entry
struct ResTable_entry
{
// Number of bytes in this structure.
uint16_t size;
enum {
// If set, this is a complex entry, holding a set of name/value
// mappings. It is followed by an array of ResTable_map structures.
FLAG_COMPLEX = 0x0001,
// If set, this resource has been declared public, so libraries
// are allowed to reference it.
FLAG_PUBLIC = 0x0002
};
uint16_t flags;
// Reference into ResTable_package::keyStrings identifying this entry.
struct ResStringPool_ref key;
};
// map entry
struct ResTable_map_entry : public ResTable_entry
{
// Resource identifier of the parent mapping, or 0 if there is none.
// This is always treated as a TYPE_DYNAMIC_REFERENCE.
ResTable_ref parent;
// Number of name/value pairs that follow for FLAG_COMPLEX.
uint32_t count;
};
// map
struct ResTable_map
{
// The resource identifier defining this mapping's name. For attribute
// resources, 'name' can be one of the following special resource types
// to supply meta-data about the attribute; for all other resource types
// it must be an attribute resource.
ResTable_ref name;
......
// This mapping's value.
Res_value value;
};
// 真正的value
struct Res_value
{
// Number of bytes in this structure.
uint16_t size;
// Always set to 0.
uint8_t res0;
// Type of the data value.
enum {
// The 'data' is either 0 or 1, specifying this resource is either
// undefined or empty, respectively.
TYPE_NULL = 0x00,
// The 'data' holds a ResTable_ref, a reference to another resource
// table entry.
TYPE_REFERENCE = 0x01,
// The 'data' holds an attribute resource identifier.
TYPE_ATTRIBUTE = 0x02,
// The 'data' holds an index into the containing resource table's
// global value string pool.
TYPE_STRING = 0x03,
// The 'data' holds a single-precision floating point number.
TYPE_FLOAT = 0x04,
// The 'data' holds a complex number encoding a dimension value,
// such as "100in".
TYPE_DIMENSION = 0x05,
// The 'data' holds a complex number encoding a fraction of a
// container.
TYPE_FRACTION = 0x06,
// The 'data' holds a dynamic ResTable_ref, which needs to be
// resolved before it can be used like a TYPE_REFERENCE.
TYPE_DYNAMIC_REFERENCE = 0x07,
// Beginning of integer flavors...
TYPE_FIRST_INT = 0x10,
// The 'data' is a raw integer value of the form n..n.
TYPE_INT_DEC = 0x10,
// The 'data' is a raw integer value of the form 0xn..n.
TYPE_INT_HEX = 0x11,
// The 'data' is either 0 or 1, for input "false" or "true" respectively.
TYPE_INT_BOOLEAN = 0x12,
// Beginning of color integer flavors...
TYPE_FIRST_COLOR_INT = 0x1c,
// The 'data' is a raw integer value of the form #aarrggbb.
TYPE_INT_COLOR_ARGB8 = 0x1c,
// The 'data' is a raw integer value of the form #rrggbb.
TYPE_INT_COLOR_RGB8 = 0x1d,
// The 'data' is a raw integer value of the form #argb.
TYPE_INT_COLOR_ARGB4 = 0x1e,
// The 'data' is a raw integer value of the form #rgb.
TYPE_INT_COLOR_RGB4 = 0x1f,
// ...end of integer flavors.
TYPE_LAST_COLOR_INT = 0x1f,
// ...end of integer flavors.
TYPE_LAST_INT = 0x1f
};
uint8_t dataType;
......
// The data for this item, as interpreted according to dataType.
uint32_t data;
};
代码中定义:
class ResTable_type(var header: ResChunk_header,
var id: uint8_t,// 标识资源的type id
var res0: uint8_t,// 保留,0
var res1: uint16_t,// 保留,0
var entryCount: uint32_t,// 总个数
var entriesStart: uint32_t,// 偏移量
var resConfig: ResTable_config) {// 配置信息
override fun toString(): String {
return """header: $header ,
|id = ${id.getValue()}, idHexValue = ${id.getHexValue()},
|res0 = ${res0.getValue()},res1 = ${res1.getValue()},
|entryCount = ${entryCount.getValue()}, entryCountHexValue = ${entryCount.getHexValue()},
|entriesStart = ${entriesStart.getValue()}, entriesStartHexValue = ${entriesStart.getHexValue()}
|resConfig = $resConfig""".trimMargin()
}
companion object {
val NO_ENTRY = 0xFFFFFFFF.toInt()
}
}
open class ResTable_entry(var size: uint16_t,// 大小
var flags: uint16_t,// flag,为1是ResTable_map_entry
var key: ResStringPool_ref?) {// 字符串池引用信息
var dataStr: String? = ""
override fun toString(): String {
return "size = " + size.getValue() + ",flags = " + flags.getValue() + ",key = " + key!!.index.getValue() + ",str = " + dataStr
}
companion object {
val FLAG_COMPLEX = 0x0001
val FLAG_PUBLIC = 0x0002
}
}
class ResTable_map_entry(size: uint16_t, flags: uint16_t, key: ResStringPool_ref?,
var parent: ResTable_ref, // 指向父ResTable_map_entry的资源ID,如果没有父ResTable_map_entry,则等于0
var count: uint32_t // ResTable_map的数量
) : ResTable_entry(size, flags, key) {
override fun toString(): String {
return super.toString() + "parent:" + parent.toString() + ",count:" + count.getValue()
}
}
// bag资源:通俗的说,就是这类资源在赋值的时候,不能随便赋值,只能从事先定义好的值中选取一个赋值。
// 类型为values的资源除了是string之外,还有其它很多类型的资源,其中有一些比较特殊,如bag、style、plurals和array类的资源。
// 这些资源会给自己定义一些专用的值,这些带有专用值的资源就统称为Bag资源。
class ResTable_map(var name: ResTable_ref, // bag资源项ID,
var value: Res_value) { //bag资源项值
override fun toString(): String {
return "mapEntry: " + name.toString() + ",value = " + value.toString()
}
}
class Res_value(var size: uint16_t, // 大小
var res0: uint8_t, // 保留,0
var dataType: uint8_t, // 类型
var data: uint32_t) { // 数据,根据不同类型来展示不同数据,这部分基本上是网上拿来的
val typeStr: String
get() {
when (dataType.getValue().toInt()) {
TYPE_NULL -> return "TYPE_NULL"
TYPE_REFERENCE -> return "TYPE_REFERENCE"
TYPE_ATTRIBUTE -> return "TYPE_ATTRIBUTE"
TYPE_STRING -> return "TYPE_STRING"
TYPE_FLOAT -> return "TYPE_FLOAT"
TYPE_DIMENSION -> return "TYPE_DIMENSION"
TYPE_FRACTION -> return "TYPE_FRACTION"
TYPE_FIRST_INT -> return "TYPE_FIRST_INT"
TYPE_INT_HEX -> return "TYPE_INT_HEX"
TYPE_INT_BOOLEAN -> return "TYPE_INT_BOOLEAN"
TYPE_FIRST_COLOR_INT -> return "TYPE_FIRST_COLOR_INT"
TYPE_INT_COLOR_RGB8 -> return "TYPE_INT_COLOR_RGB8"
TYPE_INT_COLOR_ARGB4 -> return "TYPE_INT_COLOR_ARGB4"
TYPE_INT_COLOR_RGB4 -> return "TYPE_INT_COLOR_RGB4"
}
return ""
}
val dataStr: String
get() {
if (dataType.getValue().toInt() == TYPE_STRING) {
return getStringPoolStr(data.getValue())!!
}
if (dataType.getValue().toInt() == TYPE_ATTRIBUTE) {
return String.format("?%s%08X", getPackage(data.getValue()), data.getValue())
}
if (dataType.getValue().toInt() == TYPE_REFERENCE) {
return String.format("@%s%08X", getPackage(data.getValue()), data.getValue())
}
if (dataType.getValue().toInt() == TYPE_FLOAT) {
return java.lang.Float.intBitsToFloat(data.getValue()).toString()
}
if (dataType.getValue().toInt() == TYPE_INT_HEX) {
return String.format("0x%08X", data.getValue())
}
if (dataType.getValue().toInt() == TYPE_INT_BOOLEAN) {
return if (data.getValue() != 0) "true" else "false"
}
if (dataType.getValue().toInt() == TYPE_DIMENSION) {
return java.lang.Float.toString(complexToFloat(data.getValue())) + DIMENSION_UNITS[data.getValue() and COMPLEX_UNIT_MASK]
}
if (dataType.getValue().toInt() == TYPE_FRACTION) {
return java.lang.Float.toString(complexToFloat(data.getValue())) + FRACTION_UNITS[data.getValue() and COMPLEX_UNIT_MASK]
}
if (dataType.getValue() in TYPE_FIRST_COLOR_INT..TYPE_LAST_COLOR_INT) {
return String.format("#%08X", data.getValue())
}
if (dataType.getValue() in TYPE_FIRST_INT..TYPE_LAST_INT) {
return data.getValue().toString()
}
return String.format("<0x%X, type 0x%02X>", data.getValue(), dataType.getValue())
}
override fun toString(): String {
return """size = ${size.getValue()}, sizeHexValue = ${size.getHexValue()}
|res0 = ${res0.getValue()}, res0HexValue = ${res0.getHexValue()}
|dataType = ${dataType.getValue()}, dataTypeHexValue = ${dataType.getHexValue()}, typeStr = $typeStr
|data = ${data.getValue()}, dataHexValue = ${data.getHexValue()}, dataStr = $dataStr""".trimMargin()
}
companion object {
val TYPE_NULL = 0x00
val TYPE_REFERENCE = 0x01
val TYPE_ATTRIBUTE = 0x02
val TYPE_STRING = 0x03
val TYPE_FLOAT = 0x04
val TYPE_DIMENSION = 0x05
val TYPE_FRACTION = 0x06
val TYPE_FIRST_INT = 0x10
val TYPE_INT_DEC = 0x10
val TYPE_INT_HEX = 0x11
val TYPE_INT_BOOLEAN = 0x12
val TYPE_FIRST_COLOR_INT = 0x1c
val TYPE_INT_COLOR_ARGB8 = 0x1c
val TYPE_INT_COLOR_RGB8 = 0x1d
val TYPE_INT_COLOR_ARGB4 = 0x1e
val TYPE_INT_COLOR_RGB4 = 0x1f
val TYPE_LAST_COLOR_INT = 0x1f
val TYPE_LAST_INT = 0x1f
val COMPLEX_UNIT_PX = 0
val COMPLEX_UNIT_DIP = 1
val COMPLEX_UNIT_SP = 2
val COMPLEX_UNIT_PT = 3
val COMPLEX_UNIT_IN = 4
val COMPLEX_UNIT_MM = 5
val COMPLEX_UNIT_SHIFT = 0
val COMPLEX_UNIT_MASK = 15
val COMPLEX_UNIT_FRACTION = 0
val COMPLEX_UNIT_FRACTION_PARENT = 1
val COMPLEX_RADIX_23p0 = 0
val COMPLEX_RADIX_16p7 = 1
val COMPLEX_RADIX_8p15 = 2
val COMPLEX_RADIX_0p23 = 3
val COMPLEX_RADIX_SHIFT = 4
val COMPLEX_RADIX_MASK = 3
val COMPLEX_MANTISSA_SHIFT = 8
val COMPLEX_MANTISSA_MASK = 0xFFFFFF
private fun getPackage(id: Int): String {
if (id.ushr(24) == 1) {
return "android:"
}
return ""
}
fun complexToFloat(complex: Int): Float {
return (complex and 0xFFFFFF00.toInt()).toFloat() * RADIX_MULTS[complex shr 4 and 3]
}
private val RADIX_MULTS = floatArrayOf(0.00390625f, 3.051758E-005f, 1.192093E-007f, 4.656613E-010f)
private val DIMENSION_UNITS = arrayOf("px", "dip", "sp", "pt", "in", "mm", "", "")
private val FRACTION_UNITS = arrayOf("%", "%p", "", "", "", "", "", "")
}
}
上面的结构定义确实很多,有一部分是从先人的博客中摘抄的,但我并不认为他们的全是正确的,因为在我自己解析的过程中一次次肯定又一次次否定,慢慢自己发现规律并完成整个过程。
上面结构体ResTable_type定义了该资源项的资源id、总个数、配置信息以及偏移量,接着根据ResTable_entry的flags来判断具体是ResTable_map_entry还是ResTable_entry,这里面定义了size,即大小。如果类型是ResTable_map_entry,则根据ResTable_map_entry的count设置具体的ResTable_map,ResTable_map中包括了资源项的id和值(Res_value)。如果类型是ResTable_entry,那么这里会有一个资源项的值Res_value。Res_value中根据类型(dataType)来判断具体属于什么类型,并根据类型和数据(data)来获得具体的值。
10. 解析过程
了解了resources.arsc的文件结构,可以通过结构和二进制文件查看器来进行文件的解析。
10.1 工具类和类型定义
ibasic_unit:基础的接口
interface ibasic_unit<T> {
fun getValue(): T //value
fun getHexValue(): String // 返回16进制格式化字符串
}
basic_unit:基础的抽象实现类
abstract class basic_unit<T>(protected var relValue: T) : ibasic_unit<T> {
override fun getValue(): T {
return relValue
}
}
uint8_t:读取一个字节,对应类型是Byte
class uint8_t(value: Byte) : basic_unit<Byte>(value) {
override fun getHexValue(): String {
var toHexString = Integer.toHexString(getValue().toInt())
if (toHexString.length < 2) {
for (i in 1..2 - toHexString.length) {
toHexString = "0" + toHexString
}
}
return "0x" + toHexString
}
}
uint16_t:读取两个字节,对应的值的类型是Short
class uint16_t(value: Short) : basic_unit<Short>(value) {
override fun getValue(): Short {
return getShortValue(relValue)
}
override fun getHexValue(): String {
var toHexString = Integer.toHexString(com.nick.model.type.getShortValue(relValue).toInt())
if (toHexString.length < 4) {
for (i in 1..4 - toHexString.length) {
toHexString = "0" + toHexString
}
}
return "0x" + toHexString
}
}
uint32_t:读取四个字节的值,对应类型是Int
class uint32_t(var value: Int) : basic_unit<Int>(value) {
override fun getValue(): Int {
return getIntValue(relValue)
}
override fun getHexValue(): String {
var toHexString = Integer.toHexString(getValue())
if (toHexString.length < 8) {
for (i in 1..(8 - toHexString.length)) {
toHexString = "0" + toHexString
}
}
return "0x" + toHexString
}
}
Config:配置信息
class Config {
companion object {
val RESCHUNK_HEADER_SIZE = 8 //chunk头部大小
val RESTABLE_MAP_SIZE = 12 //res table map大小
val RES_VALUE_SIZE = 8 //res value大小
}
}
ReadUtils.kt:
var stringList: Array<String>? = null //字符串池数组
//读取uint8_t
fun read_uint8_t(byteArray: ByteArray, offset: Int): uint8_t {
return uint8_t(byteArray[offset])
}
fun byte22Uint16_t(byteArray: ByteArray): uint16_t {
var value: Short = 0
for (i in 0..1) {
value = (value.toInt() shl 8).toShort()
value = value or (byteArray[i].toInt() and 0xFF).toShort()
}
return uint16_t(value)
}
//读取uint16_t
fun read_uint16_t(stream: ByteArray, index: Int): uint16_t {
val byte = ByteArray(2)
byte[0] = stream[index]
byte[1] = stream[index + 1]
var value: Short = 0
for (i in 0..1) {
value = (value.toInt() shl 8).toShort()
value = value or (byte[i].toInt() and 0xFF).toShort()
}
return uint16_t(value)
}
//读取uint32_t
fun read_uint32_t(stream: ByteArray, index: Int): uint32_t {
val byte = ByteArray(4)
for (i in 0..byte.size - 1) {
byte[i] = stream[index + i]
}
var value: Int = 0
for (i in 0..3) {
value = value shl 8
value = value or (byte[i].toInt() and 0xFF)
}
return uint32_t(value)
}
//读取uint64_t
fun read_uint64_t(stream: ByteArray, index: Int): uint64_t {
val byte = ByteArray(8)
for (i in 0..byte.size - 1) {
byte[i] = stream[index + i]
}
var value: Long = 0
for (i in 0..7) {
value = value shl 8
value = value or (byte[i].toLong() and 0xFF)
}
return uint64_t(value)
}
//读取字符串
fun readString2(stream: ByteArray, size: Int, index: Int): Pair<String, Int> {
val bytes = ByteArray(1)
val byteArray = ByteArray(1)
var realSize = 0
bytes[0] = stream[index]
byteArray[0] = stream[index + 1]
var c = 1
if (bytes[0].toInt() and 0x80 != 0) {
c++
byteArray[0] = stream[index + c]
}
var result = ""
realSize = byteArray[0].toInt()
var i = 0
while (realSize and (0x80 shl i) != 0) {
c++
byteArray[0] = stream[index + c]
realSize = ((realSize and 0x7f) shl 8) or (byteArray[0].toInt() and 0xff)
i += 4
}
if (realSize > 0) {
val tempBytes = ByteArray(realSize)
for (j in 0..tempBytes.size - 1) {
tempBytes[j] = stream[index + c + 1 + j]
}
result += "${String(tempBytes)} realSize = $realSize size = $size c = $c"
}
val resultPair = Pair(result, realSize + c + i + 1)
return resultPair
}
//根据字符串size读取字符串
fun readStringWithSize(stream: ByteArray, offset: Int, size: Int): String {
var result = ""
if (size > 0) {
val tempBytes = ByteArray(size)
for (i in 0..tempBytes.size - 1) {
tempBytes[i] = stream[offset + i]
}
result = String(tempBytes)
}
return result
}
//读取chunk的头部
fun getResChunk_header(stream: ByteArray, index: Int): ResChunk_header {
return ResChunk_header(read_uint16_t(stream, index), read_uint16_t(stream, index + 2), read_uint32_t(stream, index + 4))
}
//得到int值
fun getIntValue(value: Int): Int {
val bytes = ByteArray(4)
bytes[0] = (value and 0x000000ff).toByte()
bytes[1] = ((value ushr 8) and 0x000000ff).toByte()
bytes[2] = ((value ushr 16) and 0x000000ff).toByte()
bytes[3] = ((value ushr 24) and 0x000000ff).toByte()
var result: Int = 0
for (i in 0..3) {
result = result shl 8
result = result or (bytes[i].toInt() and 0xFF)
}
return result
}
//得到long值
fun getLongValue(value: Long): Long {
val bytes = ByteArray(8)
bytes[0] = (value and 0x000000ff).toByte()
bytes[1] = ((value ushr 8) and 0x000000ff).toByte()
bytes[2] = ((value ushr 16) and 0x000000ff).toByte()
bytes[3] = ((value ushr 24) and 0x000000ff).toByte()
bytes[0] = ((value ushr 32) and 0x000000ff).toByte()
bytes[1] = ((value ushr 40) and 0x000000ff).toByte()
bytes[2] = ((value ushr 48) and 0x000000ff).toByte()
bytes[3] = ((value ushr 56) and 0x000000ff).toByte()
var result: Long = 0
for (i in 0..bytes.size - 1) {
result = result shl 8
result = result or (bytes[i].toLong() and 0xFF)
}
return result
}
//得到short值
fun getShortValue(value: Short): Short {
val bytes = ByteArray(2)
bytes[0] = (value and 0x00ff).toByte()
bytes[1] = ((value.toInt() shr 8) and 0x00ff).toByte()
var result: Short = 0
for (i in 0..1) {
result = (result.toInt() shl 8).toShort()
result = result or (bytes[i].toInt() and 0xFF).toShort()
}
return result
}
//得到字符串池中具体的值
fun getStringPoolStr(index: Int): String? {
if (stringList == null || stringList?.get(index).isNullOrEmpty()) {
return ""
}
return stringList?.get(index)
}
工具类里面主要包括了所有类型的读取,chunk头部信息的读取,字符串的读取以及真正值的转换。
10.2 文件读取和字节数组转换
val stream = File("H:\\javaworkpace\\idea\\resourcesAnalyzer\\src\\com\\nick\\resources.arsc").inputStream()
val os = ByteArrayOutputStream()
val bytes = ByteArray(1024)
var len = stream.read(bytes)
while (len != -1) {
os.write(bytes, 0, len)
len = stream.read(bytes)
}
10.3 解析ResTable_header信息
println("---------------------解析ResTable_header信息开始---------------------")
val resTable_header = ResTable_header(getResChunk_header(streamByte, 0), read_uint32_t(streamByte, Config.RESCHUNK_HEADER_SIZE))
println(resTable_header)
println("---------------------解析ResTable_header信息完毕---------------------")
有了类的定义,我们可以很简单的去解析头部的信息。输出如下:
10.4 解析字符串资源池
// 偏移量即headerSize
offset = resTable_header.header.headerSize.getValue().toInt()
//读取字符串池
val resStringPool_header = resStringPool_header(streamByte, offset)
//全局的字符串池数组保存
stringList = resStringPool_header.stringStringArray!!.stringArray
private fun resStringPool_header(streamByte: ByteArray, offset: Int): ResStringPool_header {
println("---------------------解析ResStringPool_header信息开始---------------------")
val resStringPool_header = ResStringPool_header(getResChunk_header(streamByte, offset),
read_uint32_t(streamByte, offset + Config.RESCHUNK_HEADER_SIZE),
read_uint32_t(streamByte, offset + Config.RESCHUNK_HEADER_SIZE + 4),
read_uint32_t(streamByte, offset + Config.RESCHUNK_HEADER_SIZE + 8),
read_uint32_t(streamByte, offset + Config.RESCHUNK_HEADER_SIZE + 12),
read_uint32_t(streamByte, offset + Config.RESCHUNK_HEADER_SIZE + 16),
null,
null,
null,
null)
println(resStringPool_header)
println("---------------------解析ResStringPool_header信息完毕---------------------")
//解析完成后会有两个偏移数组,一个是string偏移数组,另一个是style偏移数组
// string偏移数组
val stringCount = resStringPool_header.stringCount.getValue()
val resString_offset_array = ResString_offset_array(Array(stringCount, {
uint32_t(0)
}))
for (i in 0..stringCount - 1) {
resString_offset_array.offset_array[i] = read_uint32_t(streamByte, resStringPool_header.header.headerSize.getValue() + offset + i * 4)
}
// style偏移数组
val styleCount = resStringPool_header.styleCount.getValue()
val resStyle_offset_array = ResStyle_offset_array(Array(styleCount, {
uint32_t(0)
}))
for (i in 0..styleCount - 1) {
resStyle_offset_array.offset_array[i] = read_uint32_t(streamByte, resStringPool_header.header.headerSize.getValue() + stringCount * 4 + offset + i * 4)
}
//解析字符串池
val stringStartOffset = offset + resStringPool_header.stringsStart.getValue()
val stringArray: Array<String> = Array(resStringPool_header.stringCount.getValue(), {
""
})
for (i in 0..resStringPool_header.stringCount.getValue() - 1) {
var size: Int
if (i + 1 <= resStringPool_header.stringCount.getValue() - 1) {
size = resString_offset_array.offset_array[i + 1].getValue() - resString_offset_array.offset_array[i].getValue()
} else {
size = resStringPool_header.header.size.getValue() - resString_offset_array.offset_array[i].getValue() - resStringPool_header.stringCount.getValue() * 4 - 28
}
val resultPair = readString2(streamByte, size, stringStartOffset + resString_offset_array.offset_array[i].getValue())
stringArray[i] = resultPair.first
}
//资源字符串值
val resString_string_array = ResString_string_array(stringArray)
resString_string_array.stringArray.forEachIndexed { index, s ->
println("$index : $s")
}
println("----------------------读取style----------------------")
val styleStartOffset = offset + resStringPool_header.stylesStart.getValue()
val styleStringArray: Array<ResStringPool_span> = Array(resStringPool_header.styleCount.getValue(), {
ResStringPool_span(
ResStringPool_ref(uint32_t(0)),
uint32_t(0),
uint32_t(0)
)
})
for (i in 0..resStringPool_header.styleCount.getValue() - 1) {
styleStringArray[i] = ResStringPool_span(
ResStringPool_ref(read_uint32_t(streamByte, styleStartOffset + resStyle_offset_array.offset_array[i].getValue())),
read_uint32_t(streamByte, styleStartOffset + resStyle_offset_array.offset_array[i].getValue() + 4),
read_uint32_t(streamByte, styleStartOffset + resStyle_offset_array.offset_array[i].getValue() + 8)
)
}
for (i in 0..resStringPool_header.styleCount.getValue() - 1) {
println(styleStringArray[i])
}
resStringPool_header.stringOffsetArray = resString_offset_array
resStringPool_header.styleOffsetArray = resStyle_offset_array
resStringPool_header.stringStringArray = resString_string_array
resStringPool_header.styleStringArray = ResStyle_string_array(styleStringArray)
return resStringPool_header
}
这一部分其实解析并不难,难的是关于字符串长度的计算。正如我之前说的,长度计算我在网上查了不少资料,但是实际操作的过程中总会出现乱码(长度计算错误)的现象。说实话,这个问题我可以通过其他方法来跳过,就如代码里写的,通过偏移数组来判断具体长度,解析的时候根据长度来解析。但是这么做明显是个错误的做法。经过几天的探索,找出了自认为是正确的方法,最后完成了这部分的解析。解析结果(部分):
10.5 Package信息解析
println("读取Package信息")
// package的偏移量
val packageOffset = resStringPool_header.header.size.getValue() + offset
// 解析Package数据块
val resTable_package = ResTable_package(
getResChunk_header(streamByte, packageOffset),
read_uint32_t(streamByte, packageOffset + Config.RESCHUNK_HEADER_SIZE),
readStringWithSize(streamByte, packageOffset + Config.RESCHUNK_HEADER_SIZE + 4, 256),
read_uint32_t(streamByte, packageOffset + Config.RESCHUNK_HEADER_SIZE + 4 + 256),
read_uint32_t(streamByte, packageOffset + Config.RESCHUNK_HEADER_SIZE + 4 + 256 + 4),
read_uint32_t(streamByte, packageOffset + Config.RESCHUNK_HEADER_SIZE + 4 + 256 + 4 + 4),
read_uint32_t(streamByte, packageOffset + Config.RESCHUNK_HEADER_SIZE + 4 + 256 + 4 + 4 + 4))
println(resTable_package)
println("读取Package信息完毕")
// 解析资源类型
// 偏移量设置,
val resTypePoolOffset = resTable_package.header.headerSize.getValue().toInt() + packageOffset
val resTypeStringPool_header = resStringPool_header(streamByte, resTypePoolOffset)
// 解析资源类型值
val resInTypeStringPoolOffset = resTypePoolOffset + resTypeStringPool_header.header.size.getValue()
val resInTypeStringPool_header = resStringPool_header(streamByte, resInTypeStringPoolOffset)
var resTableTypeSpecOffset = resInTypeStringPoolOffset + resInTypeStringPool_header.header.size.getValue()
var resTypeSpecOffset = resTableTypeSpecOffset
这里面解析比较简单,先解析Package数据,接着解析了资源项以及资源项的值。结果如下:
10.6 类型规范数据块和资源类型项数据块解析
while (resTypeSpecOffset < streamByte.size) {
if (read_uint16_t(streamByte, resTypeSpecOffset).getValue().toInt() == ResChunk_header.ChunkType.RES_TABLE_TYPE_SPEC_TYPE.type) {
val resTable_typeSpec = ResTable_typeSpec(getResChunk_header(streamByte, resTypeSpecOffset),
read_uint8_t(streamByte, resTypeSpecOffset + Config.RESCHUNK_HEADER_SIZE),
read_uint8_t(streamByte, resTypeSpecOffset + Config.RESCHUNK_HEADER_SIZE + 1),
read_uint16_t(streamByte, resTypeSpecOffset + Config.RESCHUNK_HEADER_SIZE + 1 + 1),
read_uint32_t(streamByte, resTypeSpecOffset + Config.RESCHUNK_HEADER_SIZE + 1 + 1 + 2))
println("$resTable_typeSpec, idValue = ${resTypeStringPool_header.stringStringArray!!.stringArray[resTable_typeSpec.id.getValue().toInt() - 1]}")
val arrayOfUint32_ts = Array(resTable_typeSpec.entryCount.getValue(), {
uint32_t(0)
})
(0..resTable_typeSpec.entryCount.getValue() - 1).forEachIndexed { index, i ->
arrayOfUint32_ts[index] = read_uint32_t(streamByte, resTypeSpecOffset + Config.RESCHUNK_HEADER_SIZE + 1 + 1 + 2 + 4 + 4 * index)
}
val res_entry_array = Res_entry_array(arrayOfUint32_ts)
println(res_entry_array)
resTypeSpecOffset += resTable_typeSpec.header.size.getValue()
println("===========================================")
} else {
val resTable_type = ResTable_type(
getResChunk_header(streamByte, resTypeSpecOffset),
read_uint8_t(streamByte, resTypeSpecOffset + Config.RESCHUNK_HEADER_SIZE),
read_uint8_t(streamByte, resTypeSpecOffset + Config.RESCHUNK_HEADER_SIZE + 1),
read_uint16_t(streamByte, resTypeSpecOffset + Config.RESCHUNK_HEADER_SIZE + 1 + 1),
read_uint32_t(streamByte, resTypeSpecOffset + Config.RESCHUNK_HEADER_SIZE + 1 + 1 + 2),
read_uint32_t(streamByte, resTypeSpecOffset + Config.RESCHUNK_HEADER_SIZE + 1 + 1 + 2 + 4),
ResTable_config(
read_uint32_t(streamByte, resTypeSpecOffset + Config.RESCHUNK_HEADER_SIZE + 1 + 1 + 2 + 4 * 2),
read_uint32_t(streamByte, resTypeSpecOffset + Config.RESCHUNK_HEADER_SIZE + 1 + 1 + 2 + 4 * 3),
read_uint32_t(streamByte, resTypeSpecOffset + Config.RESCHUNK_HEADER_SIZE + 1 + 1 + 2 + 4 * 4),
read_uint32_t(streamByte, resTypeSpecOffset + Config.RESCHUNK_HEADER_SIZE + 1 + 1 + 2 + 4 * 5),
read_uint32_t(streamByte, resTypeSpecOffset + Config.RESCHUNK_HEADER_SIZE + 1 + 1 + 2 + 4 * 6),
read_uint32_t(streamByte, resTypeSpecOffset + Config.RESCHUNK_HEADER_SIZE + 1 + 1 + 2 + 4 * 7),
read_uint32_t(streamByte, resTypeSpecOffset + Config.RESCHUNK_HEADER_SIZE + 1 + 1 + 2 + 4 * 8),
read_uint32_t(streamByte, resTypeSpecOffset + Config.RESCHUNK_HEADER_SIZE + 1 + 1 + 2 + 4 * 9),
read_uint32_t(streamByte, resTypeSpecOffset + Config.RESCHUNK_HEADER_SIZE + 1 + 1 + 2 + 4 * 10),
read_uint32_t(streamByte, resTypeSpecOffset + Config.RESCHUNK_HEADER_SIZE + 1 + 1 + 2 + 4 * 11),
read_uint64_t(streamByte, resTypeSpecOffset + Config.RESCHUNK_HEADER_SIZE + 1 + 1 + 2 + 4 * 12)
))
println(resTable_type)
var tempOffset = resTypeSpecOffset + resTable_type.header.headerSize.getValue().toInt()
val arrayOfUint32_ts = Array(resTable_type.entryCount.getValue(), {
uint32_t(0)
})
(0..resTable_type.entryCount.getValue() - 1).forEachIndexed { index, i ->
arrayOfUint32_ts[index] = read_uint32_t(streamByte, tempOffset + 4 * index)
}
val res_entry_array2 = Res_entry_array(arrayOfUint32_ts)
println(res_entry_array2)
tempOffset += resTable_type.entriesStart.getValue() - resTable_type.header.headerSize.getValue().toInt()
val resString_string_array = resInTypeStringPool_header.stringStringArray
var count = 0
var resId = 0
while (count < resTable_type.header.size.getValue() - resTable_type.entriesStart.getValue()) {
println("resId = ${Integer.toHexString(resId)}")
val size = read_uint16_t(streamByte, tempOffset + count)
val flags = read_uint16_t(streamByte, tempOffset + count + 2)
if (flags.getValue().toInt() == 1) {
val resTable_map_entry = ResTable_map_entry(size,
flags,
ResStringPool_ref(read_uint32_t(streamByte, tempOffset + count + 2 + 2)),
ResTable_ref(read_uint32_t(streamByte, tempOffset + count + 2 + 2 + 4)),
read_uint32_t(streamByte, tempOffset + count + 2 + 2 + 4 + 4))
val mapStr = resTable_map_entry.key!!.index.getValue()
resTable_map_entry.dataStr = resInTypeStringPool_header.stringStringArray!!.stringArray[mapStr]
println(resTable_map_entry)
count += resTable_map_entry.size.getValue().toInt()
println(resString_string_array!!.stringArray[mapStr])
println("--------------------------resTable_map start------------------------------")
for (index in 0..resTable_map_entry.count.getValue() - 1) {
val resTable_map = ResTable_map(
ResTable_ref(
read_uint32_t(streamByte, tempOffset)),
Res_value(
read_uint16_t(streamByte, tempOffset + 4),
read_uint8_t(streamByte, tempOffset + 4 + 2),
read_uint8_t(streamByte, tempOffset + 4 + 2 + 1),
read_uint32_t(streamByte, tempOffset + 4 + 2 + 1 + 1)))
println(resTable_map)
count += Config.RESTABLE_MAP_SIZE
}
println("--------------------------resTable_map end------------------------------")
} else {
val resTable_entry = ResTable_entry(size, flags, ResStringPool_ref(read_uint32_t(streamByte, tempOffset + count + 2 + 2)))
val index = resTable_entry.key!!.index.getValue()
if (index > 0 && index <= resInTypeStringPool_header.stringStringArray!!.stringArray.size - 1) {
resTable_entry.dataStr = resInTypeStringPool_header.stringStringArray!!.stringArray[index]
}
count += 8
val res_value = Res_value(read_uint16_t(streamByte, tempOffset + count),
read_uint8_t(streamByte, tempOffset + count + 2)
, read_uint8_t(streamByte, tempOffset + count + 2 + 1),
read_uint32_t(streamByte, tempOffset + count + 2 + 1 + 1))
println("--------------------------resTable_entry start------------------------------")
println(resTable_entry)
println("--------------------------resTable_entry end------------------------------")
println("--------------------------res_value start------------------------------")
println(res_value)
println("--------------------------res_value end------------------------------")
count += Config.RES_VALUE_SIZE
}
resId++
}
resTypeSpecOffset += resTable_type.header.size.getValue()
println("===========================================")
}
}
这部分解析其实很繁琐,首先ResTable_typeSpec和ResTable_type是成对出现(通过现象得出结论。。),ResTable_typeSpec里面包括了该资源项对应的id(这个id对应的是解析资源项结果的索引+1)以及资源项个数,接着便是一个数组(具体用处不详),接着便是ResTable_type,这里面定义了资源的type id,资源项值的总个数,开始位置的偏移量以及配置信息。顺便说一句,我这个解析的时候,通过的是整体的size来判断是否解析完成,并没有根据entryCount来判断,这里和其他解析可能会有些出入。解析完ResTable_type,解析完成ResTable_type还需要根据entryCount来解析一个便宜数组,接着可以根据后面解析的flags来判断资源项的值是ResTable_entry还是ResTable_map_entry,如果是ResTable_entry的话,则添加对Res_value的解析,如果是ResTable_map_entry,则添加对ResTable_map的解析。
部分解析结果:
写在后面的话
整个解析过程已经完成了,代码这里可以看到。
总结下,还是不要瞎搞事情啊。本来自己有打算一步步来的,结果半路看到这个断断续续搞了两周时间,其中心酸的历程不多说了,不断的肯定自己又不断的否定自己。不过呢,最后终于算是将东西完成了。以前觉得吧,站在巨人的肩膀上看世界,很轻松。后来发现,巨人的肩膀可能也有漏洞。