1 Plist 存储格式

Plist 文件有3种存储格式:

  • xml
  • JSON
  • 二进制

使用plutil -convert 命令可以在这3种格式之间相互转换。

分别对应上面3种存储格式:

  • xml1

  • json

  • binary1

同时如果输出到控制台,还可以选择是按照OC还是Swift的方式进行输出:

// 按照 OC 输出
MacBook-Pro:~ user$ plutil -convert objc -o - xxxx.plist
/// Generated from xxxx.plist
__attribute__((visibility("hidden")))
NSDictionary * const xxxx = @{
    @"BuildVersion" : @"50",
    @"CFBundleShortVersionString" : @"4.13",
    @"CFBundleVersion" : @"3146.1.2",
    @"ProjectName" : @"Notes",
    @"SourceVersion" : @"3146001002000000",
};

// 按照 Swift 输出
MacBook-Pro:~ user$ plutil -convert swift -o - xxxx.plist
/// Generated from xxxx.plist
let xxxx = [
    "BuildVersion" : "50",
    "CFBundleShortVersionString" : "4.13",
    "CFBundleVersion" : "3146.1.2",
    "ProjectName" : "Notes",
    "SourceVersion" : "3146001002000000",
]

上面命令中的-o -表示要输出到控制台。

2 Plist 二进制格式

Plist二进制格式描述在苹果CF代码库中的CFBinaryPList.c中。

Plist二进制格式整体如下:

Plist

从图上可以看到,HeaderTrailer的大小是固定的。

ObjectsOffset-Table部分是可变的。

下面给出了一个二进制Plist的内容,相关部分已经用不同颜色进行了区分:

Plist-Binary

将二进制Plist打印为xml输出到控制台的内容为:

MacBook-Pro:~ user$ plutil -convert xml1 -o - xxxx.plist




	BuildVersion
	50
	CFBundleShortVersionString
	4.13
	CFBundleVersion
	3146.1.2
	ProjectName
	Notes
	SourceVersion
	3146001002000000


可以将xml内容和二进制内容对照着看。

2.1 Header

Header总共有8字节,定义如下:

// ForFoundationOnly.h
typedef struct {
    uint8_t	_magic[6];
    uint8_t	_version[2];
} CFBinaryPlistHeader;

2.1.1 magic

magic固定为plist

2.1.2 version

plist常见的version00

同时还有1516

版本1516都没有文档说明。

2.2 Trailer

Trailer固定位于二进制Plist的尾部,总共32字节。

Trailer的定义如下:

// ForFoundationOnly.h
typedef struct {
    uint8_t	_unused[5];
    uint8_t _sortVersion;
    uint8_t	_offsetIntSize;
    uint8_t	_objectRefSize;
    uint64_t _numObjects;
    uint64_t _topObject;
    uint64_t _offsetTableOffset;
} CFBinaryPlistTrailer;

2.2.1 Unused

Trailer开始的头5字节没有使用,始终是0

2.2.2 SortVersion

SortVersion表是Plist中的Key是否排序,占用1字节。

0表示未排序。

2.2.3 OffsetIntSize

OffsetIntSize表示Offset-Table中每一项占用的字节数。

在上面例子中可以看到,OffsetIntSize1,表示Offset-Table中每项只占用1字节。

2.2.4 ObjectRefSize

ObjectRefSize表示DictArray中引用的对象索引占用的字节数。

在上面例子中ObjectRefSize1,表示DictArray中引用对象的索引占用1字节。

Objects区域的第1个字节d5为例。

d5的高4bit代表数据类型,d代表Dict

d5的低4bitDict场景代表key-value对的个数,也就是这个Dict5key-value对。

d5后面0x01-0x051个字节代表keyOffset-Table中的索引。

0x06-0x0a1个字节代表valueOffset-Table中的索引。

如果ObjectRefSize2,那么这些索引就会占用2个字节。

2.2.5 Num-Objects

Num-Objects代表二进制Plist中的对象个数。

在上面例子中,有一个Dict,这个Dict5key-value对,因此总共有11个对象。

2.2.6 Top-Object

Top-Object表示根对象偏移量,一般都为0

在上面例子中,根对象就是plist节点。

2.2.7 Offset-Table Offset

Offset-Table Offset表示Offset-Table的偏移量。

在上面例子中,Offset-Table位于0x93处。

2.2.8 Offset-Table

Offset-Table中的每一项存储对象在Plist文件中的偏移量。

Offset-Table的项从0开始计数。

上面例子中Offset-Table的第0项为0x08

Plist二进制偏移0x08处的值是d5

d5代表了数据类型。

d54bit代表这是一个Dict

d54bit代表这个Dict5key-value对。

2.2.9 Objects

Objects区域就是二进制Plist的数据区。

3 Plist 二进制中的数据类型

常见的Plist二进制中的数据类型如下:

  • 0x00

0x00代表null

  • 0x08

0x08代表布尔值false

  • 0x09

0x09代表布尔值true

  • 0x1n

0x1n表示是int类型。

0x1n中的n可以取0~7,表示这个int占用2^n个字节,这些字节是大端在前排列。

比如0x11 0x20 0x30,那么n=1,表示这个int占用2个字节,为0x2030

int

  • 0x2n

0x2n表示是real类型。

0x2n中的n可以取值0~7,表示这个real类型占用2^n个字节,这些字节是大端在前排列。

real

  • 0x5n

0x5n表示ASCII字符串。

0x5nn取值0-f,表示这个ASCII字符串占用的字节数。

ASCII字符串的内容接在0x5n的后面。

string

但是如果n=f,那么后面的1个字节表示一个int类型,定义了这个ASCII字符串占用的字节数。

string2

比如有一个字节序列0x5f 10 0f

0x5f表示这是一个ASCII字符串。

10按照上面int的定义,它后面的2^0=1个字节定义了这个整数,也就是0xf

那么,整个ASCII字符串就占用0xf个字节。

  • an

an表示是Array类型。

ann取值0-f,表示这个Array中元素的个数。

Array中每个元素在Offset-Table中的索引直接接在an后面。

array

如果n=f,那么就和0x5n中的一样。

array2

  • dn

dn表示是Dict类型。

dnn取值0-f,表示这个Dictkey-value对个数。

Dict后面先跟keyOffset-Table中的索引,接着跟着valueOffset-Table中的索引。

dict

如果n=f,那么就和0x5n中的一样

dict2

更多详细的类型参看CFBinaryPList.c


原文地址: https://www.cveoy.top/t/topic/qGs5 著作权归作者所有。请勿转载和采集!

免费AI点我,无需注册和登录