📄 qqproto.pas
字号:
{
QQ[OICQ] Runer Ver 1.01
Copyright (C) 2006 HellFish
E-Mail: hellfish@qq.com,sgfree@hotmail.com
QQ[OICQ]: 4427968
保留以上信息,保留对作者最基本的尊重
}
unit QQPROTO;
interface
uses Windows;
Const
QQ_UDPPORT='8000';
QQ_TCPPORT='80';
CLIENT_VERSION=$0E1B; //QQ 2005
SERVER_VERSION=$0100;
MAXPACKET=65535;
MAX_SEND_IM=700;
MAX_RESEND=5;
INTERVAL_KEEPALIVE=100000;
LENGTH05_LOGINPACKET=460;
LENGTH05_LOGINDATA=416;
LENGTH05_KEY=16;
LENGTH05_HEADER=13;
LENGTH05_TAIL=1;
LENGTH05_BASICIN=7;
LENGTH05_BASICOUT=11;
HEADER05_BASIC=$02;
HEADER05_P2P=$00;
HEADER05_05=$05;
HEADER05_TAIL=$03;
SENDTIME_NOPACK=4;
LOGINMODE_NORMAL=$0A;
LOGINMODE_HIDE=$28;
STATUS_ONLINE=$0A;
STATUS_OFFLINE=$14;
STATUS_AWAY=$1E;
STATUS_HIDE=$28;
REPLY_OK=$00;
REPLY_LOGINTOKENOK=$00;
REPLY_LOGINFAIL=$05;
QCMD_HEAD=$00;
QCMD_LOGIN=$22;
QCMD_LOGUT=$01;
QCMD_Login_Sucess=$00;
QCMD_Login_Redirect=$01;
QCMD_Login_PwdErr=$05;
QCMD_KEEPALIVE=$02;
QCMD_REQ_LOGINTOKEN=$62;
QCMD_CHANGESTATUS=$0D;
QCMD_SENDIM=$16;
QCMD_RECVIM=$17;
QCMD_REQKEY=$1D;
QCMD_RECVSYS=$80;
QCMD_RECVIM_FRIEND=$09;
QCMD_RECVIM_STRANGER=$0A;
QCMD_RECVIM_SMS=$0C;
QCMD_RECV_KICKOUT=$01;
QCMD_FAKECAM=$01;
QCMD_NFAKECAM=$00;
QCMD_UNKNOW00=$00;
QCMD_UNKNOW40=$40;
QCMD_CHANGESTATUSOK=$30;
IM_NORMAL_REPLY=$01;
IM_NORMAL_AUTO=$02;
IMFORM_USER=$00;
IMFORM_SYS=$01;
IMFORM_CLUSTER=$02;
IMFORM_SMS=$03;
IMFORM_TEMP=$04;
RECV_IM_HEAD=$00;
//** 来自好友的消息 */
RECV_IM_FRIEND=$09;
//**来自陌生人的消息 */
RECV_IM_STRANGER=$0A;
//**来自临时消息 */
RECV_IM_TEMP=$1F;
//**手机短消息 - 普通绑定用户 */
RECV_IM_BIND_USER=$0B;
//**手机短消息 - 普通手机 */
RECV_IM_MOBILE=$0C;
//**手机短消息 - 移动QQ用户 */
RECV_IM_MOBILE_QQ=$13;
//**手机短消息 - 移动QQ用户(使用手机号描述) */
RECV_IM_MOBILE_QQ_2=$14;
//** 系统消息 */
RECV_IM_SYS=$30;
//群消息
RECV_IM_GROUP1=$2A;
RECV_IM_GROUP2=$2B;
RECV_IM_GROUP3=$20; //不明确
//QQ直播
RECV_IM_10000=$18;
LOGIN_16_51:Array [0..35] of Byte=(
$00, $00, $00, $00, $00, $00, $00, $00,
$00, $00, $00, $00, $00, $00, $00, $00,
$00, $00, $00, $86, $CC, $4C, $35, $2C,
$D3, $73, $6C, $14, $F6, $F6, $AF, $C3,
$FA, $33, $A4, $01
);
LOGIN_53_68:Array [0..15] of Byte=(
$8D,$8B,$FA,$EC,$D5,$52,$17,$4A,
$86,$F9,$A7,$75,$E6,$32,$D1,$6D
);
LOGIN_SEGMENTS:Array [0..99] of Byte=(
$0B,$04,$02,$00,$01,$00,$00,$00,
$00,$00,$03,$09,$00,$00,$00,$00,
$00,$00,$00,$01,$E9,$03,$01,$00,
$00,$00,$00,$00,$01,$F3,$03,$00,
$00,$00,$00,$00,$00,$01,$ED,$03,
$00,$00,$00,$00,$00,$00,$01,$EC,
$03,$00,$00,$00,$00,$00,$00,$03,
$05,$00,$00,$00,$00,$00,$00,$00,
$03,$07,$00,$00,$00,$00,$00,$00,
$00,$01,$EE,$03,$00,$00,$00,$00,
$00,$00,$01,$EF,$03,$00,$00,$00,
$00,$00,$00,$01,$EB,$03,$00,$00,
$00,$00,$00,$00
);
{ * <pre>
* 基本协议族的输出包基类
* 基本协议族的包都具有以下的格式:
* 1. 包头标志,1字节,$02
* 2. 客户端版本代码,2字节
* 3. 命令,2字节
* 4. 包序号, 2字节
* 5. 用户QQ号,4字节
* 6. 包体
* 7. 包尾标志,1字节,$03
* Note: 6部分将用会话密钥加密, 登录包例外,6部分要用密码密钥加密。请求登录令牌包例外,6部分不需加密
* </pre>
}
Type
TPacketHead=Packed Record
FLAG:Byte;
Ver:Array [0..1] of Byte;
Cmd:Array [0..1] of Byte;
Serial:Array [0..1] of Byte;
QQnum:Array [0..3] of Byte;
end;
PPacketHead=^TPacketHead;
{ * <pre>
* 请求密钥包,格式为:
* 1. 头部
* 2. 密钥类型,一个字节,$3或者$4
* 3. 尾部
*
* 这个包用来请求得到一些操作的密钥,比如文件中转,或者语音视频之类的都有可能
* </pre>
}
TPacketReqKey=Packed Record
Head:TPacketHead;
KeyType:Byte;
PackEnd:Byte;
end;
PPacketReqKey=^TPacketReqKey;
{
* <pre>
* 请求登录令牌的包,格式为:
* 1. 头部
* 2. 未知的1字节,$00
* 3. 尾部
* </pre>
}
TPacketReqLoginToken=Packed Record
Head:TPacketHead;
Unknow:Byte;
PackEnd:Byte;
end;
PPacketReqLoginToken=^TPacketReqLoginToken;
{**
* <pre>
* QQ登录请求包,格式为
* 1. 头部
* 2. 初始密钥,16字节
* 3. 用户的密码密钥加密一个空串得到的16字节
* 4. 36字节的固定内容,未知含义
* 5. 登录状态,隐身登录还是什么,1字节
* 6. 16字节固定内容,未知含义
* 7. 登录令牌长度,1字节
* 8. 登录令牌
* 9. 登录模式,1字节,目前只支持普通模式
* 10. 未知1字节,$40
* 11. 后面段的个数,1字节,1个段9字节(猜测)
* 12. 段,每次基本都是固定内容,未知含义
* 13. 长度不足则全部填0知道符合登录包长度
* 14. 尾部
* </pre>
}
TPacketLogin=Packed Record
Head:TPacketHead;
InitKey:array [0..15] of Byte;
UserKey:array [0..15] of Byte;
St36:array [0..35] of Byte;
LoginMode:Byte;
St16:array [0..15] of Byte;
ToKenLen:Byte;
ToKen:array of Byte;
Mode:Byte;
Unknow00:Byte;
UnknowNum:Byte; //10
StUnknow:array [0..99] of Byte;
FillZero:array of Byte;
PackEnd:Byte;
end;
PPacketLogin=^TPacketLogin;
{* <pre>
* Keep Alive包,这个包的格式是
* 1. 头部
* 2. 用户QQ号的字符串形式
* 3. 尾部
* </pre>
}
TPacketKeepAlive=Packed Record
Head:TPacketHead;
QQnum:String;
PackEnd:Byte;
end;
PPacketKeepAlive=^TPacketKeepAlive;
{* <pre>
* Logout请求包,这个包不需要服务器的应答,格式为
* 1. 头部
* 2. password key
* 3. 尾部
* </pre>
}
TPacketLogout=Packed Record
Head:TPacketHead;
PwdKey:Array [0..15] of Byte;
PackEnd:Byte;
end;
PPacketLogout=^TPacketLogout;
{* 改变状态的包,格式为
* 1. 头部
* 2. 想要切换到的状态,一个字节
* 3. 是否显示虚拟摄像头,4字节
* 4. 尾部
* </pre>
}
TPacketChangeStatus=Packed Record
Head:TPacketHead;
ChangeStatus:Byte;
ShowFakeCam:DWord;
PackEnd:Byte;
end;
PPacketChangeStatus=^TPacketChangeStatus;
{* <pre>
* 收到消息之后我们发出的确认包
* 1. 头部
* 2. 消息发送者QQ号,4字节
* 3. 消息接收者QQ号,4字节,也就是我
* 4. 消息序号,4字节
* 5. 发送者IP,4字节
* 6. 尾部
* </pre>
}
TPacketRecvOk=Packed Record
Head:TPacketHead;
SendQQ:DWORD;
RecvQQ:DWord;
MsgSerial:Dword;
SendIP:Dword;
PackEnd:Byte;
end;
PPacketRecvOk=^TPacketRecvOk;
{* <pre>
* 发送消息的包,格式为
* 1. 头部
* 2. 发送者QQ号,4个字节
* 3. 接收者的QQ号,4个字节
* 4. 发送者QQ版本,2字节
* 5. 发送者QQ号,4字节
* 6. 接收者QQ号,4个字节(奇怪,为什么要搞两个在里面)
* 7. 发送者QQ号和session key合在一起用md5处理一次的结果,16字节
* 8. 消息类型,2字节
* 9. 会话ID,2字节,如果是一个操作需要发送多个包才能完成,则这个id必须一致
* 10. 发送时间,4字节
* 11. 发送者头像,2字节
* 12. 字体信息,4字节,设成$00000001吧,不懂具体意思
* 13. 消息分片数,1字节,如果消息比较长,这里要置一个分片值,QQ缺省是700字节一个分片,这个700字节是纯消息,
* 不包含其他部分
* 14. 分片序号,1字节,从0开始
* 15. 消息的id,2字节,同一条消息的不同分片id相同
* 16. 消息方式,是发送的,还是自动回复的,1字节
* 17. 消息内容,最后一个分片的结尾需要追加一个空格。
* Note: 结尾处的空格是必须的,如果不追加空格,会导致有些缺省表情显示为乱码
* 18. 消息的尾部,包含一些消息的参数,比如字体颜色啦,等等等等,顺序是
* 1. 字体修饰属性,bold,italic之类的,2字节,已知的位是
* i. bit0-bit4用来表示字体大小,所以最大是32
* ii. bit5表示是否bold
* iii. bit6表示是否italic
* iv. bit7表示是否underline
* 2. 颜色Red,1字节
* 3. 颜色Green,1字节
* 4. 颜色Blue,1字节
* 5. 1个未知字节,置0先
* 6. 消息编码,2字节,$8602为GB,$0000为EN,其他未知,好像可以自定义,因为服务器好像不干涉
* 7. 字体名,比如$cb, $ce, $cc, $e5表示宋体
* 19. 1字节,表示18和19部分的字节长度
* 20. 包尾部
* </pre>
}
TPacketSendIM=Packed Record
Head:TPacketHead;
SendQQ:DWORD;
RecvQQ:DWord;
SendVer:Word;
SendQQ2:DWORD;
RecvQQ2:DWord;
SendQQKey:array [0..15] of Byte;
MsgType:Byte;
SessionID:Word;
SendTime:Dword;
SendFace:Word;
FontMsg:Dword;
MsgNum:Byte;
MsgNumSerial:Byte;
MsgID:Word;
MsgMode:Byte;
MSgBody:Array of Byte;
FontSt:Word;
ColorR:Byte;
ColorG:Byte;
ColorB:Byte;
Unknow0:Byte;
MsgCode:Word;
MsgFontName:Array [0..3] of Byte; //字体名称,这里固定2个汉字,如 楷体
MsgStatusLen:Byte; //长度,固定为 12
PackEnd:Byte;
end;
PPacketSendIM=^TPacketSendIM;
////////////////////////////////
{
* <pre>
* 基本协议族的输入包基类:
* 1. 包头标志,1字节,$02
* 2. 服务器端版本代码, 2字节
* 3. 命令,2字节
* 4. 包序号,2字节
* 5. 包体
* 6. 包尾标志,1字节,$03
* <pre>
}
TInPacketHead=Packed Record
FLAG:Byte;
Ver:Array [0..1] of Byte;
Cmd:Array [0..1] of Byte;
Serial:Array [0..1] of Byte;
end;
PInPacketHead=^TInPacketHead;
{
* <pre>
* 请求登录令牌的回复包,这个包的source字段和其他包不同,为QQ.QQ_SERVER_0000
* 1. 头部
* 2. 回复码,1字节,$00表示成功
* 3. 登录令牌长度,1字节
* 4. 登录令牌
* 5. 尾部
* </pre>
}
TInPacketToken=Packed Record
Head:TInPacketHead;
Reply:Byte;
TokenLen:Byte;
Token:Array [0..23] of Byte; //长度未知
PackEnd:Byte;
end;
PInPacketToken=^TInPacketToken;
{* <pre>
* 请求密钥的回复包,格式为:
* 1. 头部
* 2. 子命令,1字节
* 3. 未知字节,应该是回复码,0表示成功
* 4. 密钥,16字节
* 5. 未知的8字节
* 6. 未知的4字节
* 7. 文件中转认证令牌字节长度
* 8. 令牌
* 9. 未知的4字节
* 10. 尾部
* </pre>
}
TInPacketKey=Packed Record
Head:TInPacketHead;
SubCmd:Byte;
UnknowReply:Byte;
Key:Array [0..15] of Byte;
Unknow8:Array [0..7] of Byte;
Unknow4_1:Array [0..3] of Byte;
FileTokenLen:Byte;
FileToken:Array of Byte; //长度未知
Unknow4_2:Array [0..3] of Byte;
PackEnd:Byte;
end;
PInPacketKey=^TInPacketKey;
{
* <pre>
* Keep Alive的应答包,格式为
* 1. 头部
* 2. 6个域,分别是"0", "0", 所有在线用户数,我的IP,我的端口,未知含义字段,用ascii码31分隔
* 3. 尾部
* </pre>
}
TInPacketKeepAlive=Packed Record
Head:TInPacketHead;
Reply:Array of Byte; //长度未知
PackEnd:Byte;
end;
PInPacketKeepAlive=^TInPacketKeepAlive;
{
* <pre>
* QQ登陆应答包
* 1. 头部
* 2. 回复码, 1字节
* 2部分如果是$00
* 3. session key, 16字节
* 4. 用户QQ号,4字节
* 5. 我的外部IP,4字节
* 6. 我的外部端口,2字节
* 7. 服务器IP,4字节
* 8. 服务器端口,2字节
* 9. 本次登录时间,4字节,为从1970-1-1开始的毫秒数除1000
* 10. 未知的2字节
* 11. 用户认证令牌,24字节
* 12. 一个未知服务器1的ip,4字节
* 13. 一个未知服务器1的端口,2字节
* 14. 一个未知服务器2的ip,4字节
* 15. 一个未知服务器2的端口,2字节
* 16. 两个未知字节
* 17. 两个未知字节
* 18. client key,32字节,这个key用在比如登录QQ家园之类的地方
* 19. 12个未知字节
* 20. 上次登陆的ip,4字节
* 21. 上次登陆的时间,4字节
* 22. 39个未知字节
* 2部分如果是$01,表示重定向
* 3. 用户QQ号,4字节
* 4. 重定向到的服务器IP,4字节
* 5. 重定向到的服务器的端口,2字节
* 2部分如果是$05,表示登录失败
* 3. 一个错误消息
* </pre>
}
TInPacketLoginHead=Packed Record
Head:TInPacketHead;
Reply:Byte;
end;
PInPacketLoginHead=^TInPacketLoginHead;
TInPacketLoginRd=Packed Record
QQnum:Dword;
SrvIp:Dword;
SrvPort:Word;
PackEnd:Byte;
end;
PInPacketLoginRd=^TInPacketLoginRd;
TInPacketLoginFail=Packed Record
ReplyMSg:Array Of Byte;
PackEnd:Byte;
end;
PInPacketLoginFail=^TInPacketLoginFail;
TInPacketLoginOK=Packed Record
SessionKey:array [0..15] of Byte;
QQnum:Dword;
IP:Dword;
Port:Word;
SrvIp:Dword;
SrvPort:Word;
LoginTime:Dword;
Unknow2_1:array [0..1] of Byte;
UserToKen:array [0..23] of Byte;
UnknowIP1:Dword;
UnknowPort1:Word;
UnknowIP2:Dword;
UnknowPort2:Word;
Unknow2_2:array [0..1] of Byte;
Unknow2_3:array [0..1] of Byte;
ClientKey:array [0..31] of Byte;
Unknow12:array [0..11] of Byte;
LastIp:Dword;
LastTime:Dword;
Unknow39:array [0..38] of Byte;
PackEnd:Byte;
end;
PInPacketLoginOK=^TInPacketLoginOK;
{
接受消息通知,只需要得到发送者的QQ号,多种方式.
目前只考虑 普通消息 临时消息 手机消息 移动QQ消息 群消息
这几种消息的 1-7 部分均相同
* <pre>
* 别人发来的消息包,如果是普通消息,格式为
* 1. 头部
* 2. 发送者QQ号,4字节
* 3. 接收者QQ号,4字节
* 4. 包序号(并非我们发送时候的序号,因为这个是4字节,可能是服务器端得总序号)
* 5. 发送者IP,如果是服务器转发的,那么ip就是服务器ip, 4字节
* 6. 发送者端口,如果是服务器转发的,那么就是服务器的端口,2字节
* 7. 消息类型,是好友发的,还是陌生人发的,还是系统消息等等, 2字节
* 8. 发送者QQ版本,2字节
* 9. 发送者的QQ号,4字节
* 10. 接受者的QQ号,4字节
* 11. md5处理的发送方的uid和session key,16字节
* 12. 普通消息类型,比如是文本消息还是其他什么消息,2字节
* 13. 会话ID,2字节,如果是一个操作需要发送多个包才能完成,则这个id必须一致
* 14. 发送时间,4字节
* 15. 发送者头像,2字节
* 16. 是否有字体属性,4字节,有一般是0x00000001
* 17. 消息的分片数,1字节
* 18. 分片序号,1字节,从0开始
* 19. 消息id,2字节,同一条消息的不同分片id相同
* 20. 消息类型,这里的类型表示是正常回复还是自动回复之类的信息, 1字节
* 21. 消息正文,长度 = 剩余字节数 - 包尾字体属性长度
* 22. 字体属性,和SendIMPacket中的相同
* 23. 尾部
}
TInPacketRecvMsg=Packed Record
Head:TInPacketHead;
SendQQ:Dword;
RecvQQ:Dword;
OtherReply:array of Byte;
PackEnd:Byte;
end;
PInPacketRecvMsg=^TInPacketRecvMsg;
implementation
end.
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -