千千静听播放列表分析
好久没发什么东西了,今天发个前段时间分析的东西吧。当作是保存一下吧。
注意:这里还有一些情况没有处理:
1、目前分析的都是添加本地文件。
2、此次分析并不包括通过千千下载的歌曲。
3、此次分析并不包括添加URL歌曲的情况。
4、此次分析并不包括把文件当URL添加到播放列表的情况。
千千静听播放列表分析
我们先来看一下空的“默认”播放列表。
用UltraEdit打开可以看到如下的数据。
00000000h: 54 54 42 4C 04 00 00 00 FF FF FF FF FF FF FF FF ; TTBL....????????
00000010h: 08 00 00 00 5B 00 D8 9E A4 8B 5D 00 0E 00 00 00 ; ....[.貫?].....
00000020h: 25 00 41 00 20 00 2D 00 20 00 25 00 54 00 04 00 ; %.A. .-. .%.T...
00000030h: 00 00 25 00 46 00 ; ..%.F.
这里有一个比较明示的的头4个字节“TTBL”,和播放列表的扩展名一样。
这个是千千静听播放列表自定义格式的标记。
大体看了一下,发现文件里有很多“00”。而且有规律,基本都是一个字节的数据后面跟一个“00”。其实这是Unicode编码的一个特征,Unicode把每一字符的数据用2个字节存储。
单字节字符直接存储第二字节补0,主要是ASCII字符。双字节字符直接按双字节Unicode编码存储,如汉字、其他双字节文字。
如第三行数据:
00000020h: 25 00 41 00 20 00 2D 00 20 00 25 00 54 00 04 00 ; %.A. .-. .%.T...
按Unicode编码分析之后得到的数据是:“%A - %T”。这里像是格式化的表达式,在音乐领域里%A一般代表Artist、艺术家、歌手,%T一般代表Title、歌曲名称。
第五行也可以按Unicode编码分析之后得到的数据是:“%F”。
既然这样,我们后面分析数据可以优先选用Unicode编码分析。
我们回到先前的位置接着看一下后面的4个字节“04 00 00 00”,做过文件格式分析的同学都知道,这样一个典型的4字节通常都是一个Integer类型的数据。具体代表什么还不得知。
再往后看“FF FF FF FF FF FF FF FF”,其实这里是两个数据,据测试。前4字节通常为“FF FF FF FF”。后4字节有可能是“FF FF FF FF”、“00 00 00 00”、“01 00 00 00”或其他(这里所列3种是我分析过程可见的)。具体代表什么还不得知。(经张日智同学测试和分析,最后4字节也是一个Integer型数据,存储当前播放的位置)
因为分析到这里还没有见到什么有用的数据,所以这里的数据可以暂时先不管。等整个列表文件分析完之后再作定夺。
我们开始看第二行,前4字节“08 00 00 00”。和前面一样,这里是一个Integer型的数据。数据是8,猜想后面跟着8个字节的数据。
那我们试着看看后面8个字节的内容是否能看清楚是什么东西。
“5B 00 D8 9E A4 8B 5D 00”,看16进制代码可能头晕一点。我们来看看右边的ASCII字符。当我们选择这8个字节的时候,我们可以看到右边ASCII字符区选择的内容是:“[.貫?].”。
这里大家应该有点眼熟吧?没错,这里就是播放列表的名称“[默认]”。在Unicode状态下,英文字母占2个字节,一个中文也占2个字节。所以播放列表的名称“[默认]”刚好占8个字节。
当然,现在我们只是猜想,那我们去验证一下吧!
在UltraEdit新建一个文件,把“[默认]”复制过去。然后点菜单“文件->转换->ASCII转Unicode”,转换成Unicode之后我们按Ctrl+H查看16进制模式:
00000000h: FF FE 5B 00 D8 9E A4 8B 5D 00 ; ??.貫?].
这里说明一下,“FFFE”是Unicode编码的一个标记,就像我们开始分析到的TTBL是千千静听播放列表的标记类似。
这里显示的跟我们从播放列表里看到的不一样吧?其实是UltraEdit显示的一点小问题,我们把光标定位到第三字节“5B”处。让UltraEdit跳过前面2个字节的文件标记。现在看到的呢?不正是“[.貫?].”吗?
我们的验证结果说明,播放列表里的“[.貫?].”正是播放列表的名称“[默认]”!
这也证实了我们前面的分析,播放列表的数据是以Unicode编码存储的!
回看我们上面分析到的Integer型数据正是这标题所占的字节数。
我们也可以去播放软件修改一下播放列表的名称试试下,试验得到的结果是修改名称后这里的数据变了。它前面的Integer型也变成相应的字节数。证实了我们的分析。
我们继续分析后面的数据“0E 00 00 00”,这里也是一个Integer型数据。存储的数据是14,以16进制形式存储的。
我们继续看后面的14个字节的数据“25 00 41 00 20 00 2D 00 20 00 25 00 54 00”。这不正是我们先前分析Unicode的时候分析到的数据:“%A - %T”吗?
这里的情况跟前面的播放列表名称一样。不在多分析。
继续看第三行最后2字节和第四行头两字节:“04 00 00 00”,这里也很明显是一个Integer型数据。而且我们也看到文件最后只剩4字节数据了,对应了这里的字节数。分析后得到的数据是:“%F”,这和我们前面分析Unicode的时候一致。
基于以上分析,我们可以定义一个简单的Record类来读取播放列表的文件头:
TTTPlayerListHeader = packed record
Flag1: array[0..3] of Char; // TTBL
Flag2Size: Integer;
Flag2: array[0..3] of Char; // FFFFFF ($FF$FF$FF$FF)
PlayIndex: Integer; // 当前播放的位置
TitleLen: Integer; // 播放列表标题长度
Title: WideString; // 播放列表标题
Setting1Len: Integer; // 格式设置1长度
Setting1: WideString; // 格式设置1
Setting2Len: Integer; // 格式设置2长度
Setting2: WideString; // 格式设置2
end;
我们可以使用这个Record类通过TFileStream.Read()函数直接读取播放列表头的内容。当然我们也可以使用TMemoryStream类把整个播放列表读取到内存后再读取数据,以便提高性能。同学们分析过程中也知道了,千千静听也是在退出的时候才保存播放列表的。
当然,我们现在分析的只是空列表。我们可以添加两首歌进去再看看列表文件,我这里添加了3首。我们会发现文件头并没有变,但是文件后面增加了以下内容:
00000036h: 2C 00 00 00 46 00 3A 00 5C 00 4B 00 75 00 47 00 ; ,...F.:.\.K.u.G.
00000046h: 6F 00 75 00 5C 00 CF 82 67 61 26 4F 2D 00 C4 9E ; o.u.\.蟼ga&O-.臑
00000056h: 72 82 84 76 08 67 AE 4E 2E 00 6D 00 70 00 33 00 ; r倓v.g甆..m.p.3.
00000066h: 46 00 12 00 00 00 CF 82 67 61 26 4F 2D 00 C4 9E ; F.....蟼ga&O-.臑
00000076h: 72 82 84 76 08 67 AE 4E D6 2F 04 00 02 00 00 00 ; r倓v.g甆?......
00000086h: 2C 00 00 00 46 00 3A 00 5C 00 4B 00 75 00 47 00 ; ,...F.:.\.K.u.G.
00000096h: 6F 00 75 00 5C 00 B8 8B 8E 7F 59 97 2D 00 CE 57 ; o.u.\.笅?Y?.蜽
000000a6h: CC 91 84 76 08 67 49 51 2E 00 6D 00 70 00 33 00 ; 虘剉.gIQ..m.p.3.
000000b6h: 46 00 16 00 00 00 B8 8B 8E 7F 59 97 20 00 2D 00 ; F.....笅?Y?.-.
000000c6h: 20 00 CE 57 CC 91 84 76 08 67 49 51 E5 DC 04 00 ; .蜽虘剉.gIQ遘..
000000d6h: 02 00 00 00 28 00 00 00 46 00 3A 00 5C 00 4B 00 ; ....(...F.:.\.K.
000000e6h: 75 00 47 00 6F 00 75 00 5C 00 20 5F E1 4F F2 54 ; u.G.o.u.\. _酧騎
000000f6h: 2D 00 7D 76 08 67 49 51 2E 00 6D 00 70 00 33 00 ; -.}v.gIQ..m.p.3.
00000106h: 46 00 18 00 00 00 20 5F E1 4F F2 54 20 00 2D 00 ; F..... _酧騎 .-.
00000116h: 20 00 30 00 31 00 20 00 7D 76 08 67 49 51 CC 14 ; .0.1. .}v.gIQ?
00000126h: 04 00 02 00 00 00 ; ......
我们看一下头4字节“2C 00 00 00”,这里也是一个典型的Integer型数据。数值是:44。
那我们看看后面的44字节数据:
0000003ah: 46 00 3A 00 5C 00 4B 00 75 00 47 00 6F 00 75 00 ; F.:.\.K.u.G.o.u.
0000004ah: 5C 00 CF 82 67 61 26 4F 2D 00 C4 9E 72 82 84 76 ; \.蟼ga&O-.臑r倓v
0000005ah: 08 67 AE 4E 2E 00 6D 00 70 00 33 00 ; .g甆..m.p.3.
这里一看很明显就是Unicode数据,正是我们的歌曲路径。我们可以利用UltraEdit再看清楚一点!
在UltraEdit里新建一个文件,随便输入一个字符“a”。然后到菜单“文件->转换->ASCII转Unicode”,转换后按Ctrl+H转换到16进制。我们看到数据如下,我们把光标定位到61这个字节。
00000000h: FF FE 61 00 ; ??.
按Ctrl+D,选择删除,下面的数字框填2,最后确定。我们可以看到以下数据。
00000000h: FF FE ; ?
现在我们把刚刚的44字节数据复制过去,看起来应该这样:
00000000h: FF FE 46 00 3A 00 5C 00 4B 00 75 00 47 00 6F 00 ; ?﨔.:.\.K.u.G.o.
00000010h: 75 00 5C 00 CF 82 67 61 26 4F 2D 00 C4 9E 72 82 ; u.\.蟼ga&O-.臑r?
00000020h: 84 76 08 67 AE 4E 2E 00 6D 00 70 00 33 00 ; 剉.g甆..m.p.3.
好了,我们再按Ctrl+H看看?我们老大要求找的歌曲路径出来了吧?得到的数据是“F:\KuGou\苏慧伦-黄色的月亮.mp3”
我们接着看,后面就是“46 00 12 00 00 00”。这里“12 00 00 00”是一个Integer型数据,数据是18。至于“46 00”是什么我们先别管它。我们直接看后面的18字节,和上面一样,我们复制到另一个窗口看看是什么东西。分析后的结果是“苏慧伦-黄色的月亮”,这不正是我们播放列表窗口里的标题吗?这也是老大要求找的数据之一!
在这里我们可以猜想一下刚才的“46 00”是歌曲路径和歌曲标题之间的分隔一会儿我们多分析几首歌之后再作定论。
后面跟着的8个字节“D6 2F 04 00 02 00 00 00”,我们并不知道是什么数据。我尝试的对比了一下歌曲的长度、文件大小、CRC32都没找到相同。我们可以暂时不管这8个字节的数据,因为我们要取的数据已经取到了。
我们继续看,这里应该开始是下一首歌的数据了。我们再取一段类似的数据分析:
00000088h: 2C 00 00 00 46 00 3A 00 5C 00 4B 00 75 00 47 00 ; ,...F.:.\.K.u.G.
00000098h: 6F 00 75 00 5C 00 B8 8B 8E 7F 59 97 2D 00 CE 57 ; o.u.\.笅?Y?.蜽
000000a8h: CC 91 84 76 08 67 49 51 2E 00 6D 00 70 00 33 00 ; 虘剉.gIQ..m.p.3.
000000b8h: 46 00 16 00 00 00 B8 8B 8E 7F 59 97 20 00 2D 00 ; F.....笅?Y?.-.
000000c8h: 20 00 CE 57 CC 91 84 76 08 67 49 51 E5 DC 04 00 ; .蜽虘剉.gIQ遘..
000000d8h: 02 00 00 00 ; ....
这里也一样,头4字节是Integer型,存储了路径的长度。同学们可以继续用前面的方法验证一下是否正确。取完路径,我们又发现了熟悉的“46 00”。下面就是标题的长度的标题数据。到这里我们可以肯定“46 00”是歌曲路径和标题之间的分隔符了。当然,有兴趣同学的可以继续验证。
我们再看一下第二首歌后面的8个字节的数据:“E5 DC 04 00 02 00 00 00”,跟第一首歌最后的8个字节“D6 2F 04 00 02 00 00 00”有点相像。只有头两个字节的内容不同。我们再找一下第三首歌最后8个字节:“CC 14 04 00 02 00 00 00”,也是这样的规律。
这样我们也可以确定“04 00 02 00 00 00”是歌曲之间的分隔符,是“46 00”是路径和标题之间的分隔符一下。
到这里,播放列表已经分析完毕了。歌曲列表的数据结构定义如下:
const
LISTITEM_SPLIT = #$46#$00; // 路径和标题的分隔
LIST_SPLIT = #$04#$00#$02#$00#$00#$00; // 歌曲分隔
TTplayerListItem = packet record
Path: WideString; // 文件的路径
Title: WideString; // 列表显示的标题
NonUse: array[0..1] of Char; // 未知的数据,对于我们并没有用处
end;
|