源文件下载地址:http://download.csdn.net/source/2248555最近把手机QQ从200版换到了2009版,于是以前的聊天记录也就看不到了。有时候想起和好友们开开心心聊天的点点滴滴,于是想把手机QQ里面的聊天记录导出到文本文件中方便以后没事儿时看看。参考了文章:http://blog.csdn.net/wenwu500/archive/2009/10/14/4668534.aspx里的介绍,自己用VC++ 写了一个手机QQ2008和2009聊天记录提取器,该篇文章中对已聊天记录文件的存储格式介绍的不全面,这里再补充一下。首先是聊天记录的存放位置:2008版:X:/QQ/你的QQ号码/msg/好友的QQ号码.db 文件中2009版:X:/System/data/Tencent/QQ/自己的QQ号码/好友的QQ号码/msg.info 文件中接下来分析聊天记录的格式,首先是2008版,用UltraEdit打开聊天记录db文件(打开方式是ASCII Escaped Unicode),右键选择十六进制编辑回到字符显示状态,你就可以看到聊天记录了,不过每一条记录之间存在乱码,接下来分析去除这些乱码(接下来都是以16进制来说明):手机QQ2008版聊天记录中,每一条记录都是以25 08开头,之后两个字节表示这条消息的长度:len=第三个字节×162+第二个字节-0x4e,接着四个字节是这条消息发送者的QQ号,例如你的QQ号是453244328,如果这条消息是你发送的,则这四个字节的值就是a8 f5 03 1b(用Windows自带的计算器可以计算出来,不过这里说明一下,文件中字节的存放顺序和变量中时反着的,例如,一个long型变量的值是453244328,则用十六进制表示它应该是1b03f5a8(四个字节),而在文件中存放的顺序是a8 f5 03 1b。因此如果你一个字节一个字节的读读到的顺序是反的);之后两个字节的值是05 00,没什么特殊含义。接下来的34个字节是这条消息发送或者接收到的时间,是一个UNICODE编码格式的字符串;然后是两个无意义字节 00 00;接着32个字节是这条消息发送者的昵称,也是UNICODE编码格式的字符串最后是len个字节的消息内容,同样还是UNICODE格式的字符串,不过有时在消息中也包含有表情,如果消息字符串中出现14 xx,则这两个字节就表示一个表情,其中14是前缀,xx是表情序号,对应着手机QQ中的一个表情根据以上的格式就能读取聊天记录了,读取出来后将UNICODE格式的字符串转换成ANSI格式然后保存到文本文档里就成了:void Export2008(FILE* fr, FILE* fw){ fseek(fr,-1,SEEK_CUR); BYTE mark[15],null[4]; char timestr[36],str[40],msg[2048]; wchar_t wtimestr[18],wstr[20],buff[1024]; memset(wtimestr,0x0000,36); memset(wstr,0x0000,40); memset(buff,0x0000,2048); long int len; fread(mark,sizeof(BYTE),10,fr); fread(wtimestr,sizeof(wchar_t),17,fr); fread(null,sizeof(BYTE),2,fr); fread(wstr,sizeof(wchar_t),16,fr); len=(((long int)mark[3])*16*16+(long int)mark[2])-0x4e; fread(buff,sizeof(char),len,fr); CString cstr(buff); wchar_t enterc[2]={0}; enterc[0]=0x0c; cstr.Replace(enterc,L"/r/n"); wchar_t face[8]={0}; int clen=cstr.GetLength(); for(int i=0;i<clen;i++) { if(cstr.GetAt(i)==0x14&&i+1) { short int nIDFace=(short int)cstr.GetAt(i+1); memset(face,0x00,14); if(GetUserFace(nIDFace,face)) { wchar_t wface[3]={0}; wface[0]=cstr.GetAt(i); wface[1]=cstr.GetAt(i+1); cstr.Replace(wface,face); clen=cstr.GetLength(); } } } memset(buff,0x00,2048); wcsncpy(buff,cstr.GetBuffer(),cstr.GetLength()); cstr.ReleaseBuffer(); char c; fread(&c,sizeof(char),1,fr); memset(msg,0,2048); WCharToAChar(buff,msg,2048); memset(timestr,0,36); WCharToAChar(wtimestr,timestr,36); memset(str,0,40); WCharToAChar(wstr,str,40); fwrite(timestr,sizeof(BYTE),strlen(timestr),fw); fwrite(" ",sizeof(BYTE),strlen(" "),fw); fwrite(str,sizeof(BYTE),strlen(str),fw); fwrite("/r/n",sizeof(BYTE),strlen("/r/n"),fw); fwrite(msg,sizeof(BYTE),strlen(msg),fw); fwrite("/r/n/r/n",sizeof(BYTE),strlen("/r/n/r/n"),fw);}手机QQ2009版的聊天记录中,每一条记录都是以a8 xx(或者 A9 XX) 开头,然后以a8 xx(或者 A9 XX)结尾,例如某条消息如下:A8 3A 4B 13 ED 06 00 00 00 00 00 00 80 D8 8F A1 59 2F 66 60 4F 84 76 C4 9E D1 91 F6 65 3B 52 27 54 26 20 14 00 4F 00 A8 3A 它就是以A8 3A 开头,又以A8 3A结尾;但是A8 3A这两个字节并不是随机的,实际上它们代表的是这一条消息的长度:len=第二个字节 +(第一个字节-0xa8)×162,注意:这个len不包括末尾的A8 XX 。之后的四个字节是c++里面的time_t型变量值,有关这个time_t数据类型资料在百度上可以查到,这里就不多说了。这个值表示的是这条消息发送或者接收的时间;接下来六个字节是无用字节,值永远是00 00 00 00 00 00;然后得一个字节是发送/接收标志,如果该字节是00,则代表是接收到的消息;如果是80,则是发送出去的消息;之后的len字节便是消息的内容,同样也是UNICODE格式;最后是和开头对应的两个字节A8 XX (或者 A9 XX);同样根据分析出来的格式便可以读取出消息的内容,然后保存到文本文件中:void Export2009(FILE* fr, FILE* fw){ fseek(fr,-1,SEEK_CUR); BYTE markstr[2],flag; char timestr[4],nullstr[6],msg[2048]; wchar_t buff[1024]; memset(buff,0x0000,2048); long int len,time; fread(markstr,sizeof(BYTE),2,fr); fread(timestr,sizeof(char),4,fr); char c; c=timestr[0]; timestr[0]=timestr[3]; timestr[3]=c; c=timestr[1]; timestr[1]=timestr[2]; timestr[2]=c; memcpy(&time,timestr,4); fread(nullstr,sizeof(char),6,fr); fread(&flag,sizeof(BYTE),1,fr); len=(long int)markstr[1]+((long int)markstr[0]-0xa8)*16*16; fread(buff,sizeof(BYTE),len,fr); CString cstr(buff); wchar_t enterc[2]={0}; enterc[0]=0x0c; cstr.Replace(enterc,L"/r/n"); wchar_t face[8]={0}; int clen=cstr.GetLength(); for(int i=0;i<clen;i++) { if(cstr.GetAt(i)==0x14&&i+1) { short int nIDFace=(short int)cstr.GetAt(i+1); memset(face,0x00,14); if(GetUserFace(nIDFace,face)) { wchar_t wface[3]={0}; wface[0]=cstr.GetAt(i); wface[1]=cstr.GetAt(i+1); cstr.Replace(wface,face); clen=cstr.GetLength(); } } } memset(buff,0x00,2048); wcsncpy(buff,cstr.GetBuffer(),cstr.GetLength()); cstr.ReleaseBuffer(); fread(markstr,sizeof(BYTE),2,fr); fread(&c,sizeof(char),1,fr); memset(msg,0,2048); WCharToAChar(buff,msg,2048); time_t t=time; struct tm *ptrtime=gmtime(&t); char strtime[26]; sprintf(strtime,"%d-%d-%d %d:%d:%d",ptrtime->tm_year+1900,ptrtime->tm_mon+1,ptrtime->tm_mday,ptrtime->tm_hour+8,ptrtime->tm_min,ptrtime->tm_sec); fwrite(strtime,sizeof(char),strlen(strtime),fw); fwrite(" ",sizeof(char),strlen(" "),fw); if(flag==0x80) fwrite(m_MyName,sizeof(char),strlen(m_MyName),fw); if(flag==0x00) fwrite(m_OpName,sizeof(char),strlen(m_OpName),fw); fwrite("/r/n",sizeof(char),strlen("/r/n"),fw); fwrite(msg,sizeof(BYTE),strlen(msg),fw); fwrite("/r/n/r/n",sizeof(char),strlen("/r/n/r/n"),fw);}下面是代表QQ表情值的对照表:BOOL GetUserFace(short int nIDFace,wchar_t* face){ if(0x4e==nIDFace) wcsncpy_s(face,8,L"[#呲牙#]",8); else if(0x4d==nIDFace) wcsncpy_s(face,8,L"[#调皮#]",8); else if(0x79==nIDFace) wcsncpy_s(face,8,L"[#流汗#]",8); else if(0x8a==nIDFace) wcsncpy_s(face,8,L"[#偷笑#]",8); else if(0x99==nIDFace) wcsncpy_s(face,8,L"[#再见#]",8); else if(0x98==nIDFace) wcsncpy_s(face,8,L"[#敲打#]",8); else if(0xa2==nIDFace) wcsncpy_s(face,8,L"[#擦汗#]",8); else if(0x7c==nIDFace) wcsncpy_s(face,8,L"[#猪头#]",8); else if(0x62==nIDFace) wcsncpy_s(face,8,L"[#玫瑰#]",8); else if(0xb1==nIDFace) wcsncpy_s(face,8,L"[#菜刀#]",8); else if(0x9d==nIDFace) wcsncpy_s(face,8,L"[#炸弹#]",8); else if(0x89==nIDFace) wcsncpy_s(face,8,L"[#便便#]",8); else if(0xab==nIDFace) wcsncpy_s(face,8,L"[#委屈#]",8); else if(0x76==nIDFace) wcsncpy_s(face,8,L"[#抓狂#]",8); else if(0x74==nIDFace) wcsncpy_s(face,8,L"[#酷#]",8); else if(0x93==nIDFace) wcsncpy_s(face,8,L"[#嘘#]",8); else if(0x4a==nIDFace) wcsncpy_s(face,8,L"[#大哭#]",8); else if(0x46==nIDFace) wcsncpy_s(face,8,L"[#流泪#]",8); else if(0x8b==nIDFace) wcsncpy_s(face,8,L"[#可爱#]",8); else if(0x43==nIDFace) wcsncpy_s(face,8,L"[#色#]",8); else if(0x47==nIDFace) wcsncpy_s(face,8,L"[#害羞#]",8); else if(0x45==nIDFace) wcsncpy_s(face,8,L"[#得意#]",8); else if(0x77==nIDFace) wcsncpy_s(face,8,L"[#吐#]",8); else if(0x4f==nIDFace) wcsncpy_s(face,8,L"[#微笑#]",8); else if(0x4c==nIDFace) wcsncpy_s(face,8,L"[#发怒#]",8); else if(0x4b==nIDFace) wcsncpy_s(face,8,L"[#尴尬#]",8); else if(0x78==nIDFace) wcsncpy_s(face,8,L"[#惊怒#]",8); else if(0x49==nIDFace) wcsncpy_s(face,8,L"[#睡#]",8); else if(0x92==nIDFace) wcsncpy_s(face,8,L"[#疑问#]",8); else if(0x41==nIDFace) wcsncpy_s(face,8,L"[#惊讶#]",8); else if(0x73==nIDFace) wcsncpy_s(face,8,L"[#难过#]",8); else if(0x8d==nIDFace) wcsncpy_s(face,8,L"[#傲慢#]",8); else if(0x8c==nIDFace) wcsncpy_s(face,8,L"[#白眼#]",8); else if(0xb5==nIDFace) wcsncpy_s(face,8,L"[#示爱#]",8); else if(0x65==nIDFace) wcsncpy_s(face,8,L"[#爱心#]",8); else if(0xa1==nIDFace) wcsncpy_s(face,8,L"[#冷汗#]",8); else if(0xae==nIDFace) wcsncpy_s(face,8,L"[#亲亲#]",8); else if(0x7a==nIDFace) wcsncpy_s(face,8,L"[#憨笑#]",8); else if(0x5c==nIDFace) wcsncpy_s(face,8,L"[#爱情#]",8); else if(0x96==nIDFace) wcsncpy_s(face,8,L"[#衰#]",8); else if(0x42==nIDFace) wcsncpy_s(face,8,L"[#撇嘴#]",8); else if(0xad==nIDFace) wcsncpy_s(face,8,L"[#阴险#]",8); else if(0x90==nIDFace) wcsncpy_s(face,8,L"[#奋斗#]",8); else if(0x44==nIDFace) wcsncpy_s(face,8,L"[#发呆#]",8); else if(0xa8==nIDFace) wcsncpy_s(face,8,L"[#右哼哼#]",8); else if(0x70==nIDFace) wcsncpy_s(face,8,L"[#弱#]",8); else if(0x6f==nIDFace) wcsncpy_s(face,8,L"[#强#]",8); else if(0xb0==nIDFace) wcsncpy_s(face,8,L"[#可怜#]",8); else if(0x7b==nIDFace) wcsncpy_s(face,8,L"[#大兵#]",8); else if(0x94==nIDFace) wcsncpy_s(face,8,L"[#晕#]",8); else if(0xaa==nIDFace) wcsncpy_s(face,8,L"[#鄙视#]",8); else if(0x56==nIDFace) wcsncpy_s(face,8,L"[#飞吻#]",8); else if(0xa6==nIDFace) wcsncpy_s(face,8,L"[#坏笑#]",8); else if(0x7f==nIDFace) wcsncpy_s(face,8,L"[#拥抱#]",8); else if(0x88==nIDFace) wcsncpy_s(face,8,L"[#握手#]",8); else if(0xa0==nIDFace) wcsncpy_s(face,8,L"[#胜利#]",8); else if(0xb7==nIDFace) wcsncpy_s(face,8,L"[#抱拳#]",8); else if(0x63==nIDFace) wcsncpy_s(face,8,L"[#凋谢#]",8); else if(0x81==nIDFace) wcsncpy_s(face,8,L"[#饭#]",8); else if(0x67==nIDFace) wcsncpy_s(face,8,L"[#蛋糕#]",8); else if(0x61==nIDFace) wcsncpy_s(face,8,L"[#西瓜#]",8); else if(0xb2==nIDFace) wcsncpy_s(face,8,L"[#啤酒#]",8); else if(0xb6==nIDFace) wcsncpy_s(face,8,L"[#瓢虫#]",8); else if(0xb9==nIDFace) wcsncpy_s(face,8,L"[#拳头#]",8); else if(0xba==nIDFace) wcsncpy_s(face,8,L"[#差劲#]",8); else if(0x5a==nIDFace) wcsncpy_s(face,8,L"[#发抖#]",8); else if(0x9e==nIDFace) wcsncpy_s(face,8,L"[#刀#]",8); else if(0x6e==nIDFace) wcsncpy_s(face,8,L"[#月亮#]",8); else if(0x80==nIDFace) wcsncpy_s(face,8,L"[#咖啡#]",8); else if(0xbb==nIDFace) wcsncpy_s(face,8,L"[#爱你#]",8); else if(0xbd==nIDFace) wcsncpy_s(face,8,L"[#OK#]",8); else if(0xb8==nIDFace) wcsncpy_s(face,8,L"[#勾引#]",8); else if(0x66==nIDFace) wcsncpy_s(face,8,L"[#心碎#]",8); else if(0x6b==nIDFace) wcsncpy_s(face,8,L"[#太阳#]",8); else if(0x68==nIDFace) wcsncpy_s(face,8,L"[#礼物#]",8); else if(0x5e==nIDFace) wcsncpy_s(face,8,L"[#足球#]",8); else if(0x97==nIDFace) wcsncpy_s(face,8,L"[#骷髅#]",8); else if(0xc2==nIDFace) wcsncpy_s(face,8,L"[#挥手#]",8); else if(0x9c==nIDFace) wcsncpy_s(face,8,L"[#闪电#]",8); else if(0x8e==nIDFace) wcsncpy_s(face,8,L"[#饥饿#]",8); else if(0x8f==nIDFace) wcsncpy_s(face,8,L"[#困#]",8); else if(0xaf==nIDFace) wcsncpy_s(face,8,L"[#吓#]",8); else if(0xac==nIDFace) wcsncpy_s(face,8,L"[#快哭了#]",8); else if(0xa9==nIDFace) wcsncpy_s(face,8,L"[#哈欠#]",8); else if(0xa7==nIDFace) wcsncpy_s(face,8,L"[#左哼哼#]",8); else if(0xa5==nIDFace) wcsncpy_s(face,8,L"[#糗大了#]",8); else if(0xa4==nIDFace) wcsncpy_s(face,8,L"[#鼓掌#]",8); else if(0xa3==nIDFace) wcsncpy_s(face,8,L"[#抠鼻#]",8); else if(0x95==nIDFace) wcsncpy_s(face,8,L"[#折磨#]",8); else if(0x91==nIDFace) wcsncpy_s(face,8,L"[#咒骂#]",8); else if(0xb3==nIDFace) wcsncpy_s(face,8,L"[#篮球#]",8); else if(0xb4==nIDFace) wcsncpy_s(face,8,L"[#乒乓#]",8); else if(0xbc==nIDFace) wcsncpy_s(face,8,L"[#NO#]",8); else if(0x58==nIDFace) wcsncpy_s(face,8,L"[#跳跳#]",8); else if(0x5b==nIDFace) wcsncpy_s(face,8,L"[#怄火#]",8); else if(0xBE==nIDFace) wcsncpy_s(face,8,L"[#转圈#]",8); else if(0xBF==nIDFace) wcsncpy_s(face,8,L"[#磕头#]",8); else if(0xc0==nIDFace) wcsncpy_s(face,8,L"[#回头#]",8); else if(0xc1==nIDFace) wcsncpy_s(face,8,L"[#跳绳#]",8); else if(0x48==nIDFace) wcsncpy_s(face,8,L"[#闭嘴#]",8); else if(0xc7==nIDFace) wcsncpy_s(face,8,L"[#右太极#]",8); else if(0xc6==nIDFace) wcsncpy_s(face,8,L"[#左太极#]",8); else if(0xc5==nIDFace) wcsncpy_s(face,8,L"[#献吻#]",8); else if(0xc4==nIDFace) wcsncpy_s(face,8,L"[#街舞#]",8); else if(0xc3==nIDFace) wcsncpy_s(face,8,L"[#激动#]",8); else return false; return true;}将上面说的综合起来就是本程序了,本程序的功能只负责将聊天记录导出到文本文档中,其实稍加修改就可以做到将2008和2009的聊天记录相互转换。现在手机QQ出2010版了,不过只要腾讯它不像电脑上使用的QQ一样将聊天记录加密,应该也能分析出其格式从而将其导出或者转换。如果哪位朋友发现程序中有错误或者不妥的地方希望能留言指出,谢谢……