[原]百度公交离线数据格式分析——5.读取离线文件

在 com.baidu.bus.offline 下面有一个 CAPI.smali 文件,里面定义了几个JNI的接口:

public class CAPI {
  static {
    System.loadLibrary("busoffline");
  }
  public native int closeDB();
  public native int getEngineVersion();
  public native int openDB(String filePath);
  public native List queryFullMatchStation(String[] stations);
  public native RLineInfo queryLineInfo(int lineId);
  public native List queryLinesByName(String[] lineName);
  public native List queryLinesBySid(int sid);
  public native List queryRoundStation(int p1, int p2, int p3);
  public native List queryStation(String[] stations);
  public native List queryTransferInfos(int p1, int p2, int p3);
}

首先查看 getEngineVersion() 的处理。用 IDA 打开 libbusoffline.so 之后找到函数:

Java_com_baidu_bus_offline_CAPI_getEngineVersion()

代码很简单:

LDR     R3, =(ENGINE_VERSION_ptr - 0x5DC0862A)
ADD     R3, PC ; ENGINE_VERSION_ptr
LDR     R3, [R3] ; ENGINE_VERSION
LDR     R0, [R3]
BX      LR

另外可以看到 ENGINE_VERSION 是 .rodata 段的一个常量,这段代码就是返回这个常量,查看 ENGINE_VERSION 的定义:

.rodata:5DC0D3A8 ENGINE_VERSION  DCB    3 
.rodata:5DC0D3A9                 DCB    0
.rodata:5DC0D3A9                 DCB    0
.rodata:5DC0D3A9                 DCB    0

百度公交离线文件下载后,保存在 $SDCARD/Android/data/com.baidu.bus/files/ 下面,以城市ID(数字)建立文件夹,然后在下面以 versionCode 为名建立文件,后缀为 .bdp,如下图:

跟踪 openDB() 函数,JNI中的 Java_com_baidu_bus_offline_CAPI_openDB() 函数,发现它简单调用了 OpenDB() 函数。
跟踪 OpenDB() 函数,读取的过程是这样的:
(1) 使用 fopen() 打开文件;
(2) 读取30个字节(诡异的是,这个30是以字符串形式保存在so中,在运行时使用 atoi() 函数转换为整数);
(3) 确保读出的内容以“-boundary-3-”开头(比较字符串的前12个字符),如果不是,则判定为文件格式错误,退出;
(4) 在“-boundary-3-”后面查找字符“.”(即英文的句点,ASCII值为0x2E);
(5) 在句点后面查找换行符“\n”;
(6) 将句点和换行符之间的字符,用atoi() 转换为数字,这个数字表示的是该条记录(一行)的长度;
(7) 读取换行后面的内容,长度为刚才得到的数字(在实际操作中,由于前面读取了30个字符,因此这里继续从文件的当前位置读取“行记录长度”减去30,加上截至到换行符前的长度);
(8) 调用 GetCodecString() 函数解码;
(9) 调用 LoadData() 函数将解码后的内容以特定的格式保存到内存中
(10) 调用 ReleaseCodecString() 函数释放 (8) 中申请的空间;
(11) 反复执行步骤 (2)~(10) 直到文件结束。

第 (8) 步的 GetCodecString() 是解密文件的关键步骤,通过查看这个函数的代码,发现是将文件的内容(乱码)与一个字符串中的字符逐一进行异或,这个字符串是:

\x84\xf4\xedKM~\x02\xa3\x0f\xaf\xf5A\x12s9M[

注:在so中,这个字符串并不是这样存储的,而是逆序保存的,并且每个字符的值再减去1,原始的字符串是:ZL8r\x11@\xf4\xae\x0e\xa2\x01}LJ\xec\xf3\x83

如此处理之后,文件就可以阅读了,以北京为例,前几行的内容(字符编码为UTF-8)为:

11911~2872~78062~19261~3565~9112~131~1.0
1~南站村~12923480~4879551~69303;69306~2557;2558~~0
2~安乐庄~13001600~4860283~126;161~5;6~~0
3~小胡营~12993797~4871232~35489;35576;67871;67808~2514;1393;1392;2513~~0
4~老才臣北厂~13039643~4862075~64681;64640~2428;2427~8595~0

继续查看 LoadData() 函数,它将每行的数据按“~”分隔成为一个字符串数组,然后交给 AnalyseRow() 来处理。
第一行有几个数字,这是索引该文件的关键,百度公交离线文件分为6个区域,这行的前6个数字分别表示每个区有几行,最好一个1.0含义未明。这6个区域的数据分别表示:
(1) 公交站点(Station Data);
(2) 公交线路(Line Data);
(3) 站点信息(Stop Data);
(4) 站点索引(Station Index Data);
(5) 线路索引(Line Index Data);
(6) 地图坐标(X-Y Map Data)。
仍以北京为例,可以知道,北京共有11911个站点,2872条线路(包括地铁,普通公交车往返线路算作两条不同的公交线路)。


每个区域都有固定的格式,下面做简单的说明:

1. 公交站点(Station Data):

序号 1
站点名称 南站村
未知1 12923480
未知2 4879551
在StopIndex区中的序号 69303;69306
经过该站点的公交线路 2557;2558
未知3 0(好像都是0)

 

 

 

 

 

 

 

 

2. 公交线路(Line Data):

序号 1
公交线路名称 571路(城铁望京西站-朝新嘉园)
它的返程路线在本区的索引 2
依次经过的公交站点 1;2;3;…;25
未知1  
未知2  
线路类型(0:公交车, 1:地铁, 6:公交环线) 0
总票价(单位:分) 100
首班时间 06:20
末班时间 22:00
总长度(单位:米) 15960
未知3 0(好像都是0)

 

 

 

 

 

 

 

 

 

 

 

 

3. 站点信息(Stop Data):

序号 1
未知1 12963798
未知2 4838276
在Station Data区的索引 2214
经过它的公交线路在Line Data中的索引 1
在经过它的攻击线路上它是第几站 1
未知3 0
未知4 524(好像都是524)

 

 

 

 

 

 

 

 

这个站点在Station Data区的索引是2214,搜索结果为:

2214~城铁望京西站~12963779~4838233~...

所以该站表示“城铁望京西站”。经过该站点的公交线路为1,搜索结果为:

1~571路(城铁望京西站-朝新嘉园)~2~...

 

4. 站点索引(Station Index Data):

站点名称拼音首字母索引 GDC
与该索引匹配的站点在 Station Data 区的索引 2740;2978;4034;4941;6995;11521
从第几个字开始匹配 0;0;0;0;0;0

 

 

 

例如GDC,与之匹配的站点有6个,分别为:

2740~沟东村~
2978~官道村东~
4034~高佃村北~
4941~郭翟村~
6995~官道村~
11521~高佃村~

 

5. 线路索引(Line Index Data):

线路索引 Z21L
该线路在LineData区的索引 808;809
从第几个字开始匹配 0;0

 

 

 

如:Z21L在Line Data 区的索引为808和809:

808~专21路(城铁霍营站-龙锦苑公交场站)~
809~专21路(龙锦苑公交场站-城铁霍营站)~

 

6. 地图坐标(X-Y Map Data):

未知1 25872_9650
未知2 880

 

 

 

至此,百度公交离线文件格式分析完成,虽然还有一些未知的数据意义,但是已经足以让我们了解百度公交的原理。

 

posted @ 2016-01-19 12:48  西北望长安  阅读(680)  评论(0编辑  收藏  举报