免责声明:内容仅供学习参考,请合法利用知识,禁止进行违法犯罪活动!
如果看不懂、不知道现在做的什么,那就跟着做完看效果
内容参考于:易道云信息技术研究院
上一个内容:57.获取他人发来的聊天信息
码云地址(master 分支):https://gitee.com/dye_your_fingers/titan
码云版本号:6c28124eeda87aee1ff268843dffd0eede5d8b51
代码下载地址,在 titan 目录下,文件名为:titan-心跳数据的分析与模拟.zip
链接:https://pan.baidu.com/s/1W-JpUcGOWbSJmMdmtMzYZg
提取码:q9n5
--来自百度网盘超级会员V4的分享
HOOK引擎,文件名为:黑兔sdk升级版.zip
链接:https://pan.baidu.com/s/1IB-Zs6hi3yU8LC2f-8hIEw
提取码:78h8
--来自百度网盘超级会员V4的分享
以 57.获取他人发来的聊天信息 它的代码为基础进行修改
正常来讲很多东西是没必要分析的,但是毕竟是为了学习,所以就要把它能过一遍的数据包就过一遍,不要想着做一个自动升级自动打怪的东西,并不是为了搞着种违法的事情,而是为了学习,然后接下来就开始分析它心跳包的设计合理不合理,或者说它有一些什么防范的方法
首先截取它的心跳数据
心跳的数据不要看游戏中的数据,应该要找在登录角色界面不停收到或发送的数据包才是心跳的数据包,泰坦这个游戏,想要在角色选择界面看到心态数据包,需要进入游戏一次,如下图登录账号进入角色选择界面,是没有心跳包的
![]()
然后进入游戏之后,在选择 返回角色选择 选项,如下图
![]()
然后回到角色选择界面之后就有了心跳包,如下图,但是正常来讲,在角色选择界面是不可能会有心跳包的,这说明这个游戏有问题,然后如果在游戏中找心跳包,要找一个没人的地方,这样的地方造成的干扰少可以很好的分析数据包,然后观察重复的数据包,这个重复的数据包大概就是心跳包了,然后下图中的数据包之前分析过,它的数据是一个时间戳,然后间隔是5秒左右
![]()
然后还发现了一个813的数据包秒,如下图红框,它的间隔时间是20秒,这个813应该是做了一个定时器
![]()
然后心跳包的作用,第一是检测客户端脱机没有(脱机的意思可以理解成客户端和服务端没有网络请求了),第二比如下图红框的数据包,数据包里的数字是 GetTickCount64() 函数的返回值,它是返回电脑开始时间的时间戳,可以检测客户端与服务端的延迟,如果延迟特别大就说明有问题
![]()
然后还有一个心跳包,下图红框的心跳包,它间隔20秒,如果它间隔变成了18秒,这样就能说明使用的加速之类的外挂
![]()
然后分析好了,开始创建结构体,下图是要创建的结构体,详情看代码
心跳拦截之后不给服务端发送
如下图,经过一段时间之后会掉线,这说明服务端实现了容错的功能,比如服务端卡了,就是收到不的心跳包,这样的情况在容错时间内可以让玩家正常游玩
![]()
然后测试,连续发送813数据包会掉线,发送1015的时间戳不会,这个说明1015数据包是测试延迟的,81数据包才是心跳包
![]()
然后这种连续两次就会掉线的功能,可以用来做,比如游戏中随意打人变成了红名,然后在野外别人群殴的时候,可以利用这个连续两次会掉线的机制躲避被群殴
总结:
通过拦截心跳包发现,没有心跳包就掉线,心跳包过快也掉线(为了防止加速)
CUIWnd_0.cpp文件的修改:新加 OnBnClickedButton2函数(发送时间戳心跳包 按钮),修改了 OnBnClickedButton1函数
// CUIWnd_0.cpp: 实现文件
//
#include "pch.h"
#include "htdMfcDll.h"
#include "CUIWnd_0.h"
#include "afxdialogex.h"
#include "extern_all.h"
// CUIWnd_0 对话框
IMPLEMENT_DYNAMIC(CUIWnd_0, CDialogEx)
CUIWnd_0::CUIWnd_0(CWnd* pParent /*=nullptr*/)
: CDialogEx(IDD_PAGE_0, pParent)
{
}
CUIWnd_0::~CUIWnd_0()
{
}
void CUIWnd_0::DoDataExchange(CDataExchange* pDX)
{
CDialogEx::DoDataExchange(pDX);
}
BEGIN_MESSAGE_MAP(CUIWnd_0, CDialogEx)
ON_BN_CLICKED(IDC_BUTTON1, &CUIWnd_0::OnBnClickedButton1)
ON_BN_CLICKED(IDC_BUTTON2, &CUIWnd_0::OnBnClickedButton2)
END_MESSAGE_MAP()
// CUIWnd_0 消息处理程序
void CUIWnd_0::OnBnClickedButton1()
{
Client->HeartBeep();
// Client->TalkTo(L"r", L"打架吗?");
// Client->Talk(L"[欢迎来到麟科思]");
// Client->SelectRole(L"今晚打老虎");
/*Client->CreateRole(L"am4", 1.0, 2.0, 4.0, 8.0, "gui\BG_team\TeamRole\Teamrole_zq_humF_001.PNG",
"Face,0;Hat,0;Eyes,0;Beard,0;Ears,0;Tail,0;Finger,0;Cloth,0;Pants,0;Gloves,0;Shoes,0;Trait,0;HairColor,0;SkinColor,0;SkinMtl,0;Tattoo,0;TattooColor,16777215;",
"", 0.0);*/
//Client->SelectCamp("xuanrenZQ");
//Client->StartCreateRole();
//Client->DelRole(L"ranzhi11111");
/*
char buff[] = {
0xA, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x4, 0x00, 0x02, 0x01, 0x00, 0x00, 0x00, 0x02, 0x01, 00 ,0x00,
0x00, 0x07, 0x0E, 0x00, 0x00, 0x00, 0x31, 0x00, 0x32, 0x00, 0x33, 0x00, 0x31, 0x00, 0x32 ,0x00,
0x33, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
};
WinSock->OnSend(buff, sizeof(buff));
*/
/*char buff[] = {
0x27, 0x46, 0x92, 0x02, 0x00, 0x00, 0x89, 0x02, 0x00, 0x00, 0x06, 0x00, 0x06, 0x05,
0x00, 0x00, 0x00, 0x63, 0x68, 0x61, 0x74, 0x00, 0x02, 0x01, 0x00, 0x00, 0x00, 0x07,
0x0A, 0x00, 0x00, 0x00, 0x34, 0x00, 0x33, 0x00, 0x39, 0x00, 0x39, 0x00, 0x00, 0x00,
0x07, 0x5A, 0x02, 0x00, 0x00, 0x31, 0x00, 0x32, 0x00, 0x31, 0x00, 0x31, 0x00, 0x31,
0x00, 0x31, 0x00, 0x31, 0x00, 0x31, 0x00, 0x31, 0x00, 0x31, 0x00, 0x31, 0x00, 0x31,
0x00, 0x31, 0x00, 0x31, 0x00, 0x31, 0x00, 0x31, 0x00, 0x31, 0x00, 0x31, 0x00, 0x31,
0x00, 0x31, 0x00, 0x31, 0x00, 0x31, 0x00, 0x31, 0x00, 0x31, 0x00, 0x31, 0x00, 0x31,
0x00, 0x31, 0x00, 0x31, 0x00, 0x31, 0x00, 0x31, 0x00, 0x31, 0x00, 0x31, 0x00, 0x31,
0x00, 0x31, 0x00, 0x31, 0x00, 0x31, 0x00, 0x31, 0x00, 0x31, 0x00, 0x31, 0x00, 0x31,
0x00, 0x31, 0x00, 0x31, 0x00, 0x31, 0x00, 0x31, 0x00, 0x31, 0x00, 0x31, 0x00, 0x31,
0x00, 0x31, 0x00, 0x31, 0x00, 0x31, 0x00, 0x31, 0x00, 0x31, 0x00, 0x31, 0x00, 0x31,
0x00, 0x31, 0x00, 0x31, 0x00, 0x31, 0x00, 0x31, 0x00, 0x31, 0x00, 0x31, 0x00, 0x31,
0x00, 0x31, 0x00, 0x31, 0x00, 0x31, 0x00, 0x31, 0x00, 0x32, 0x00, 0x32, 0x00, 0x32,
0x00, 0x32, 0x00, 0x32, 0x00, 0x32, 0x00, 0x32, 0x00, 0x32, 0x00, 0x32, 0x00, 0x32,
0x00, 0x32, 0x00, 0x32, 0x00, 0x32, 0x00, 0x32, 0x00, 0x32, 0x00, 0x32, 0x00, 0x32,
0x00, 0x32, 0x00, 0x32, 0x00, 0x32, 0x00, 0x32, 0x00, 0x32, 0x00, 0x32, 0x00, 0x32,
0x00, 0x32, 0x00, 0x32, 0x00, 0x32, 0x00, 0x32, 0x00, 0x32, 0x00, 0x32, 0x00, 0x32,
0x00, 0x32, 0x00, 0x33, 0x00, 0x33, 0x00, 0x33, 0x00, 0x33, 0x00, 0x33, 0x00, 0x33,
0x00, 0x33, 0x00, 0x33, 0x00, 0x33, 0x00, 0x33, 0x00, 0x33, 0x00, 0x33, 0x00, 0x33,
0x00, 0x33, 0x00, 0x33, 0x00, 0x33, 0x00, 0x33, 0x00, 0x33, 0x00, 0x33, 0x00, 0x33,
0x00, 0x33, 0x00, 0x33, 0x00, 0x33, 0x00, 0x33, 0x00, 0x33, 0x00, 0x33, 0x00, 0x33,
0x00, 0x33, 0x00, 0x33, 0x00, 0x33, 0x00, 0x33, 0x00, 0x33, 0x00, 0x33, 0x00, 0x33,
0x00, 0x33, 0x00, 0x33, 0x00, 0x33, 0x00, 0x33, 0x00, 0x33, 0x00, 0x33, 0x00, 0x33,
0x00, 0x33, 0x00, 0x33, 0x00, 0x33, 0x00, 0x33, 0x00, 0x33, 0x00, 0x33, 0x00, 0x35,
0x00, 0x35, 0x00, 0x35, 0x00, 0x35, 0x00, 0x35, 0x00, 0x35, 0x00, 0x35, 0x00, 0x35,
0x00, 0x35, 0x00, 0x35, 0x00, 0x35, 0x00, 0x35, 0x00, 0x35, 0x00, 0x35, 0x00, 0x35,
0x00, 0x35, 0x00, 0x35, 0x00, 0x35, 0x00, 0x35, 0x00, 0x35, 0x00, 0x35, 0x00, 0x35,
0x00, 0x35, 0x00, 0x35, 0x00, 0x35, 0x00, 0x35, 0x00, 0x35, 0x00, 0x35, 0x00, 0x35,
0x00, 0x35, 0x00, 0x35, 0x00, 0x35, 0x00, 0x35, 0x00, 0x35, 0x00, 0x35, 0x00, 0x35,
0x00, 0x35, 0x00, 0x35, 0x00, 0x35, 0x00, 0x35, 0x00, 0x35, 0x00, 0x35, 0x00, 0x34,
0x00, 0x34, 0x00, 0x34, 0x00, 0x34, 0x00, 0x34, 0x00, 0x34, 0x00, 0x34, 0x00, 0x34,
0x00, 0x34, 0x00, 0x34, 0x00, 0x34, 0x00, 0x34, 0x00, 0x34, 0x00, 0x34, 0x00, 0x34,
0x00, 0x34, 0x00, 0x34, 0x00, 0x34, 0x00, 0x34, 0x00, 0x34, 0x00, 0x34, 0x00, 0x34,
0x00, 0x34, 0x00, 0x34, 0x00, 0x34, 0x00, 0x34, 0x00, 0x34, 0x00, 0x34, 0x00, 0x34,
0x00, 0x34, 0x00, 0x34, 0x00, 0x34, 0x00, 0x34, 0x00, 0x34, 0x00, 0x34, 0x00, 0x34, 0x00, 0x34, 0x00, 0x34, 0x00, 0x34, 0x00, 0x34, 0x00, 0x34, 0x00, 0x34,
0x00, 0x34, 0x00, 0x34, 0x00, 0x34, 0x00, 0x34, 0x00, 0x34, 0x00, 0x34, 0x00, 0x36, 0x00, 0x36, 0x00, 0x36, 0x00, 0x36, 0x00, 0x36, 0x00, 0x36, 0x00, 0x36,
0x00, 0x36, 0x00, 0x36, 0x00, 0x36, 0x00, 0x36, 0x00, 0x36, 0x00, 0x36, 0x00, 0x36, 0x00, 0x36, 0x00, 0x36, 0x00, 0x36, 0x00, 0x36, 0x00, 0x36, 0x00, 0x36,
0x00, 0x36, 0x00, 0x36, 0x00, 0x36, 0x00, 0x36, 0x00, 0x36, 0x00, 0x36, 0x00, 0x36, 0x00, 0x36, 0x00, 0x36, 0x00, 0x36, 0x00, 0x36, 0x00, 0x36, 0x00, 0x36,
0x00, 0x36, 0x00, 0x36, 0x00, 0x36, 0x00, 0x36, 0x00, 0x36, 0x00, 0x36, 0x00, 0x36, 0x00, 0x36, 0x00, 0x36, 0x00, 0x36, 0x00, 0x36, 0x00, 0x36, 0x00, 0x36, 0x00, 0x36, 0x00, 0x36, 0x00, 0x37,
0x00, 0x37, 0x00, 0x37, 0x00, 0x37, 0x00, 0x37, 0x00, 0x37, 0x00, 0x37, 0x00, 0x37, 0x00, 0x37, 0x00, 0x37, 0x00, 0x37, 0x00, 0x37, 0x00, 0x37, 0x00, 0x37, 0x00, 0x37, 0x00, 0x37, 0x00, 0x37, 0x00,
0x38, 0x00, 0x00, 0x00, 0x02, 0x01, 0x00, 0x00, 0x00, 0x02, 0x01, 0x00, 0x00, 0x00
};
WinSock->OnRecv(buff, sizeof(buff));*/
}
void CUIWnd_0::OnBnClickedButton2()
{
Client->HeartLoop();
}
NetClient.cpp文件的修改:新加 HeartBeep函数、HeartLoop函数、OnHeartBeep函数、OnHeartLoop函数,修改了 OnSendCustom函数
#include "pch.h"
#include "NetClient.h"
#include "extern_all.h"
bool NetClient::login(const char* Id, const char* Pass)
{
const int bufflen = sizeof(DATA_LOGIN) + 1;
char buff[bufflen];
DATA_LOGIN data;
// 有些操作系统这样写会报错,因为内存不对齐,现在Windows下没事
//PDATALOGIN _data = (PDATALOGIN)(buff + 1);
// 这样写就能解决内存对齐问题
PDATALOGIN _data =&data;
int len = strlen(Id);
memcpy(_data->Id, Id, len);
len = strlen(Pass);
memcpy(_data->Pass, Pass, len);
memcpy(buff+1, _data, sizeof(DATA_LOGIN));
buff[0] = I_LOGIN;
return WinSock->OnSend(buff, sizeof(buff));
}
bool NetClient::DelRole(const wchar_t* rolename)
{
PROLEDATA _role = GetRoleByName(rolename);
if (_role == nullptr) {
return false;
}
else {
return DelRole(rolename, _role->name.lenth);
}
return false;
}
bool NetClient::StartCreateRole()
{
NET_CREATEROLE_START _data;
return WinSock->OnSend(&_data.op, _data.len);
}
bool NetClient::SelectCamp(const char* _campname)
{
NET_SEND_BUFF _buff;
NET_SEND_CHOOSECAMP _data;
_data.opcode.Set(SC_CHOOSECAMP);
_data.camps.Set(_campname);
/*
sizeof(_data) / sizeof(EnCode)的原因
NET_SEND_CHOOSECAMP结构体里面,没别 东西
全是 EnCode 这个结构
*/
short count = sizeof(_data) / sizeof(EnCode);
_buff.count = count;
/*
CodeMe函数给 _buff.buff里写数据参数的数据
也就是给0A开头数据包里写,数据参数个数后面的内容
然后返回值是写了多长的数据
也就是给0A开头数据包里的数据参数个数后面的数据写了多长
*/
int ilen = _data.CodeMe(count, _buff.buff);
ilen = ilen + sizeof(NET_SEND_HEAD) - 1;
return WinSock->OnSend(&_buff.op, ilen);
}
bool NetClient::CreateRole(wchar_t* name, double sex, double camp, double face, double occu, const char* photo, const char* infos, const char* txt, double faceShape)
{
// rolecount > 4说明角色的数量够了
if (rolecount > 4)return false;
int index = 0;
bool roleindex[5]{true, true, true, true, true };
for (int i = 0; i < rolecount; i++) {
roleindex[roles[i].index] = false;
}
for (int i = 0; i < 5; i++)
{
if (roleindex[i]) {
index = i;
break;
}
}
// wchar_t _name[] = L"am52111";
NS_CREATEROLE_HEAD_BUFF _buff;
CREATE_ROLE_DATAS _data;
_data.sex.Set(sex);
_data.camp.Set(camp);
_data.face.Set(face);
_data.occu.Set(occu);
_data.faceSahpe.Set(faceShape);
//_data.Photo.Set("gui\BG_team\TeamRole\Teamrole_zq_humF_001.PNG");
_data.Photo.Set(photo);
//_data.Infos.Set("Face,0;Hat,0;Eyes,0;Beard,0;Ears,0;Tail,0;Finger,0;Cloth,0;Pants,0;Gloves,0;Shoes,0;Trait,0;HairColor,0;SkinColor,0;SkinMtl,0;Tattoo,0;TattooColor,16777215;");
_data.Infos.Set(infos);
_data.Txt.Set(txt);
short count = sizeof(_data) / sizeof(EnCode);
_buff.count = count;
_buff.index = index;
int lenth = wcslen(name) + 1;
lenth = lenth * 2;
memcpy(_buff.name, name, lenth);
int ilen = _data.CodeMe(count, _buff.buff);
ilen = ilen + sizeof(NET_SEHD_CREATEROLE_HEAD) - 3;
return WinSock->OnSend(&_buff.op, ilen);
}
bool NetClient::SelectRole(const wchar_t* rolename)
{
PROLEDATA roles = GetRoleByName(rolename);
if (roles == nullptr)return false;
NS_SELECTROLE _data;
memcpy(_data.buff, roles->name.value(), roles->name.lenth);
return WinSock->OnSend((char*)&_data, sizeof(_data));
}
PROLEDATA NetClient::GetRoleByName(const wchar_t* rolename)
{
//PROLEDATA result = nullptr;
for (int i = 0; i < rolecount; i++)
{
// StrCmpW判断两个字符串是否相同
// 比较时区分大小写,如果字符串相同返回0
if (StrCmpW(roles[i].name.value(), rolename) == 0) {
return &roles[i];
}
}
return nullptr;
}
bool NetClient::Talk(wchar_t* txt, int PdId, double un)
{
NET_SEND_BUFF _buff;
CHAT_PUBLIC _data;
_data.opcode.Set(SC_CHAT);
_data.ChartId.Set(PdId);
_data.txt.Set(txt);
_data.un.Set(un);
/*
sizeof(_data) / sizeof(EnCode)的原因
NET_SEND_CHOOSECAMP结构体里面,没别 东西
全是 EnCode 这个结构
*/
short count = sizeof(_data) / sizeof(EnCode);
_buff.count = count;
/*
CodeMe函数给 _buff.buff里写数据参数的数据
也就是给0A开头数据包里写,数据参数个数后面的内容
然后返回值是写了多长的数据
也就是给0A开头数据包里的数据参数个数后面的数据写了多长
*/
int ilen = _data.CodeMe(count, _buff.buff);
ilen = ilen + sizeof(NET_SEND_HEAD) - 1;
return WinSock->OnSend(&_buff.op, ilen);
}
bool NetClient::TalkTo(wchar_t* name, wchar_t* txt, double un)
{
NET_SEND_BUFF _buff;
CHAT_PRIVATE _data;
_data.opcode.Set(SC_CHAT);
_data.ChartId.Set(3);
_data.txt.Set(txt);
_data.name.Set(name);
_data.un.Set(un);
/*
sizeof(_data) / sizeof(EnCode)的原因
NET_SEND_CHOOSECAMP结构体里面,没别 东西
全是 EnCode 这个结构
*/
short count = sizeof(_data) / sizeof(EnCode);
_buff.count = count;
/*
CodeMe函数给 _buff.buff里写数据参数的数据
也就是给0A开头数据包里写,数据参数个数后面的内容
然后返回值是写了多长的数据
也就是给0A开头数据包里的数据参数个数后面的数据写了多长
*/
int ilen = _data.CodeMe(count, _buff.buff);
ilen = ilen + sizeof(NET_SEND_HEAD) - 1;
return WinSock->OnSend(&_buff.op, ilen);
}
bool NetClient::HeartBeep()
{
NET_SEND_BUFF _buff;
HEART_BEEP _data;
_data.opcode.Set(SC_BEEP);
_data.tick.Set(3);
/*
sizeof(_data) / sizeof(EnCode)的原因
NET_SEND_CHOOSECAMP结构体里面,没别 东西
全是 EnCode 这个结构
*/
short count = sizeof(_data) / sizeof(EnCode);
_buff.count = count;
/*
CodeMe函数给 _buff.buff里写数据参数的数据
也就是给0A开头数据包里写,数据参数个数后面的内容
然后返回值是写了多长的数据
也就是给0A开头数据包里的数据参数个数后面的数据写了多长
*/
int ilen = _data.CodeMe(count, _buff.buff);
ilen = ilen + sizeof(NET_SEND_HEAD) - 1;
return WinSock->OnSend(&_buff.op, ilen);
}
bool NetClient::HeartLoop()
{
NET_SEND_BUFF _buff;
HEART_LOOP _data;
_data.opcode.Set(SC_LOOP);
_data.tick.Set(GetTickCount64());
_data.txt.Set("");
/*
sizeof(_data) / sizeof(EnCode)的原因
NET_SEND_CHOOSECAMP结构体里面,没别 东西
全是 EnCode 这个结构
*/
short count = sizeof(_data) / sizeof(EnCode);
_buff.count = count;
/*
CodeMe函数给 _buff.buff里写数据参数的数据
也就是给0A开头数据包里写,数据参数个数后面的内容
然后返回值是写了多长的数据
也就是给0A开头数据包里的数据参数个数后面的数据写了多长
*/
int ilen = _data.CodeMe(count, _buff.buff);
ilen = ilen + sizeof(NET_SEND_HEAD) - 1;
return WinSock->OnSend(&_buff.op, ilen);
}
bool NetClient::DelRole(const wchar_t* rolename, unsigned _len)
{
DATA_DELROLE _data;
_data.op = 0x06;
_data.len = _len;
memcpy(_data.buff, rolename, _len);
return WinSock->OnSend((char*)&_data, sizeof(DATA_DELROLE) - 1);
}
void NetClient::Init(GameWinSock* _winSock)
{
this->WinSock = _winSock;
}
bool NetClient::OnDelRole(wchar_t* rolename, unsigned _len)
{
// AfxMessageBox(rolename);
return true;
}
void NetClient::Onlogin(const char* Id, const char* Pass)
{
/*
const int bufflen = sizeof(DATA_LOGIN) + 1;
char buff[bufflen];
DATA_LOGIN data;
// 有些操作系统这样写会报错,因为内存不对齐,现在Windows下没事
//PDATALOGIN _data = (PDATALOGIN)(buff + 1);
// 这样写就能解决内存对齐问题
PDATALOGIN _data =&data;
int len = strlen(Id);
memcpy(_data->Id, Id, len);
len = strlen(Pass);
memcpy(_data->Pass, Pass, len);
memcpy(buff+1, _data, sizeof(DATA_LOGIN));
buff[0] = I_LOGIN;
return WinSock->OnSend(buff, sizeof(buff));
*/
}
bool NetClient::OnStartCreateRole(int code)
{
return true;
}
bool NetClient::OnCreateRole(PNS_CREATEROLE _header, PCREATE_ROLE_DATAS _body)
{
return true;
}
bool NetClient::OnSendCustom(PNET_SEND_CHEAD _coder, char*& buffer, unsigned& len)
{
switch (_coder->opcode.value())
{
case SC_CHOOSECAMP:
return OnChooseCamp((PNS_CHOOSECAMP)_coder);
case SC_CHAT:
return OnChat((PCHAT_DATA)_coder);
case SC_BEEP:
return OnHeartBeep((PHEART_BEEP)_coder);
case SC_LOOP:
return OnHeartLoop((PHEART_LOOP)_coder);
default:
break;
}
return true;
}
bool NetClient::OnSelectRole(wchar_t* rolename)
{
AfxMessageBox(rolename);
return true;
}
bool NetClient::OnChooseCamp(PNS_CHOOSECAMP _coder)
{
PNS_CHOOSECAMP _p = (PNS_CHOOSECAMP)_coder;
return true;
}
bool NetClient::OnChat(PCHAT_DATA _coder)
{
switch (_coder->ChartId)
{
case 3:// 私聊
return OnChatPrivate((PCHAT_PRV)_coder);
case 1:// 附近频道
case 2:// 区域频道
case 6:// 公会频道
case 9:// 阵营频道
case 21:// 喊话频道
return OnChatPublic((PCHAT_PUB)_coder);
break;
}
}
bool NetClient::OnChatPublic(PCHAT_PUB _coder)
{
return true;
}
bool NetClient::OnChatPrivate(PCHAT_PRV _coder)
{
return true;
}
bool NetClient::OnHeartBeep(PHEART_BEEP _coder)
{
return true; // 返回false会拦截81心跳包不给服务端发送
}
bool NetClient::OnHeartLoop(PHEART_LOOP _coder)
{
return true; // 返回false会拦截SC_LOOP心跳包不给服务端发送
}
bool NetClient::OnSvrChat(PCHAT_PRV _coder)
{
AfxMessageBox(_coder->name);
AfxMessageBox(_coder->txt);
//switch (_coder->ChartId)
//{
//case 3:// 私聊
// return OnChatPrivate((PCHAT_PRV)_coder);
//case 1:// 附近频道
//case 2:// 区域频道
//case 6:// 公会频道
//case 9:// 阵营频道
//case 21:// 喊话频道
// return OnChatPublic((PCHAT_PUB)_coder);
// break;
//}
return true;
}
bool NetClient::Tips(int code)
{
#ifdef Anly
CString txt;
if (code == 51001) {
txt = L"登陆失败,易道云通行证不存在!";
}else if (code == 51002) {
txt = L"登录失败,密码错误!";
}else if (code == 21101) {
txt = L"人物重名!";
}else if (code == 21109) {
txt = L"名字过长或包含非法字符!";
}
else {
txt.Format(L"未知登录错误:%d", code);
}
anly->SendData(TTYPE::I_LOG, 0, txt.GetBuffer(), (txt.GetLength() + 1)*2);
#endif
return true;
}
void NetClient::loginok(ROLE_DATA* _roles, int count)
{
logined = true;
if(roles) delete[] roles;
roles = _roles;
rolecount = count;
}
bool NetClient::OnScrStartCreateRole(short code, wchar_t* _txt)
{
return true;
}
bool NetClient::OnSvrNotice(PNET_SEND_CHEAD _coder, int count, char*& buffer, unsigned& len)
{
if (_coder->msgHeader == "chat") {
return OnSvrChat((PCHAT_PRV)_coder);
}
return true;
}
NetClient.h文件的修改:新加 HeartBeep函数、HeartLoop函数、OnHeartBeep函数、OnHeartLoop函数
#pragma once
#include "NetClass.h"
#include "GameWinSock.h"
#define CAMP_NAME_QH "xuanrenQH"
#define CAMP_NAME_ZE "xuanrenZQ"
class NetClient // 监视客户端每一个操作
{
private:
GameWinSock* WinSock;
PROLEDATA roles;
unsigned rolecount;
bool logined = false;
bool DelRole(const wchar_t* rolename, unsigned _len);
public:
void Init(GameWinSock * _winSock);
/*
模拟登陆的方法
Id是账号
Pass是密码
它要基于发送的方法实现,因为我们没有连接socket的操作
*/
bool login(const char* Id, const char* Pass);
bool DelRole(const wchar_t* rolename);
bool StartCreateRole();// 用于创建角色
bool SelectCamp(const char* _campname);// 选择阵营
bool CreateRole(wchar_t* name,double sex, double camp, double face, double occu, const char* photo, const char*infos, const char* txt, double faceShape);// 角色创建
// 选择角色并且登录进游戏
bool SelectRole(const wchar_t* rolename);
// 根据角色名字获取一个登录成功数据包(选择角色列表里的一个数据)
PROLEDATA GetRoleByName(const wchar_t* rolename);
bool Talk(wchar_t* txt, int PdId = 1, double un = 0.0);
bool TalkTo(wchar_t* name, wchar_t* txt, double un = 0.0);
bool HeartBeep();
bool HeartLoop();
public:
// 用于拦截游戏删除角色功能
bool virtual OnDelRole(wchar_t* rolename, unsigned _len);
// 用于拦截游戏登录功能
void virtual Onlogin(const char* Id, const char*Pass);
// 用于拦截游戏创建角色功能
bool virtual OnStartCreateRole(int code);
// 拦截创建角色
bool virtual OnCreateRole(PNS_CREATEROLE _header, PCREATE_ROLE_DATAS _body);
// opcode意思是操作码,count意思是数量,buffStart意思是解码的内容开始,buffend意思是解码的内容结束,buffer是原始的数据,len是原始数据的长度
// char& buffer, int& len这俩参数带&的原因是,在 OnSendCustom 里进行修改之后,通过&的方式传递回去
bool virtual OnSendCustom(PNET_SEND_CHEAD _coder, char*& buffer, unsigned& len);
bool virtual OnSelectRole(wchar_t* rolename);
public:
bool virtual OnChooseCamp(PNS_CHOOSECAMP _coder);
bool virtual OnChat(PCHAT_DATA _coder);
bool virtual OnChatPublic(PCHAT_PUB _coder);
bool virtual OnChatPrivate(PCHAT_PRV _coder);
bool virtual OnHeartBeep(PHEART_BEEP _coder);
bool virtual OnHeartLoop(PHEART_LOOP _coder);
// 针对Notice的单独处理
bool virtual OnSvrChat(PCHAT_PRV _coder);
public:
// 处理失败,参数是错误码
bool virtual Tips(int code);
void virtual loginok(ROLE_DATA* _roles, int count);
bool virtual OnScrStartCreateRole(short code,wchar_t* _txt);
bool virtual OnSvrNotice(PNET_SEND_CHEAD _coder, int count, char*& buffer, unsigned& len);
};
NetClass.h文件的修改:新加 数据包头部宏、SC_BEEP宏(心跳包)、SC_LOOP宏(检测延迟数据包)、HEART_BEEP类、HEART_LOOP类,整理了文件内容(不整理也没事)把数据解析约定的结构都放在了一起,数据类型约定放在了一起
#pragma once
#include "EnCode.h"
// 数据包头
// 发送
#define I_LOGIN 0x2 // 登录
#define I_CREATEROLE_START 0x3 // 创建角色发送数据包的头
#define I_SELECT_ROLE 0x04 // 选择角色进入游戏的数据包头
#define I_CREATEROLE 0x05 // 创建角色给服务端发送的数据包头
#define I_DELROLE 0x6 // 删除角色发送数据包的头
#define I_SEND_CUSTOM 0x0A // 进入游戏之后大部分数据包的头
// 接收
#define S_TIPS 0x3 // 服务端返回错误的数据包头
#define S_LOGINOK 0x4 // 登录成功的返回数据包的头
// 创建角色服务端反馈的数据(发送03 01 00 00 00这个数据包服务端返回的数据包头)
#define S_CREATEROLE_START 0x05
#define S_NOTICE 0x1E // 里面有聊天数据
#define S_NOTICE_COM 0x27 // 里面有聊天数据
/*
最多的数据项, 0A开头的数据包里有数据参数个数,这个宏就是用来设定
数据参数个数最大数量是多少,0A开头的数据包目前见过最大
数据参数个数是十几个,这里写20应该完全够用,如果不够用
到时候再增加
*/
#define MAX_SEND_COUNT 20
#define MAX_RECV_COUNT 30
// 发送数据操作码定义
#define SC_CHOOSECAMP 591 // 阵营选择操作码
#define SC_CHAT 1
#define SC_BEEP 81
#define SC_LOOP 1015
// 数据头结构体 数据结构约定
// 也就是接收到1E包的头部
typedef struct NR_NOTICE_HEAD {
char un;
char op;
short count;
}*PNR_NOTICE_HEAD;
// 选择角色头部
typedef struct NET_SEND_SELECTROLE {
char op = 0x04;
char buff[83]{};
}NS_SELECTROLE, * PNS_SELECTROLE;
// 创建角色数据头部
typedef struct NET_SEHD_CREATEROLE_HEAD {
/*
char un[0x2];
char len = sizeof(NET_SEHD_CREATEROLE_HEAD) - 0x03;
这俩代码是为了解决内存对齐的问题,创建角色给服务端发送的
05开头的数据包,把05变成00 00 00 05
*/
char un[0x2];
char len = sizeof(NET_SEHD_CREATEROLE_HEAD) - 0x03;
char op = 0x05;
int index = 0x0; // 选择角色列表索引
/*
宽字节是2字节0x10是16两个是32,正好满足
给服务端发送的创建角色数据包,也就是05开头的数据包
*/
wchar_t name[0x20];
short unst = 0x00;
short count = 0x08;
}*PNS_CREATEROLE;
// 基类
typedef class NET_SEND_BASE {
public:
// 实现编码操作,模拟游戏数据包给服务端发送数据,这个模拟的数据包通过调用 CodeMe函数得到
unsigned CodeMe(int size, char* buffer);
}NSR_BASE, *PNET_SEND_BASE, * PNSR_BASE;
typedef struct NS_CREATEROLE_HEAD_BUFF :public NET_SEHD_CREATEROLE_HEAD {
char buff[0x300]{};
}*PNS_CREATEROLEBUFF;
// 解析约定结构体
// 创建角色数据结构体
typedef class CREATE_ROLE_DATAS:public NET_SEND_BASE {
public:
GDOUBLE sex;//1.000000 性别 0 男 1 女
GDOUBLE camp;//1.000000 阵营 1 艾森赫特 2 格兰蒂尔
GDOUBLE face;//1.000000 种族 1 布冯特人 3 格洛玛人 4 尤恩图人 6 喀什人
GDOUBLE occu;//3.000000 职业 1 仲裁者 3秘法师 6 猎魔人 8 元素法师
GCHAR Photo;//gui\BG_team\TeamRole\Teamrole_zq_humF_001.PNG
GCHAR Infos;//Face,0;Hat,0;Eyes,0;Beard,0;Ears,0;Tail,0;Finger,0;Cloth,0;Pants,0;Gloves,0;Shoes,0;Trait,0;HairColor,0;SkinColor,0;SkinMtl,0;Tattoo,0;TattooColor,16777215;
GCHAR Txt;// 细节编辑捏脸
GDOUBLE faceSahpe;//0.000000 脸型 0 1 2 3
}* PCREATE_ROLE_DATAS;
// 发送数据标准头 0xA数据包
typedef struct NET_SEND_HEAD {
// 为了内存对齐,当前结构不可能超过255,所以这样写是没问题的
char len = sizeof(NET_SEND_HEAD) - 1;
char op = 0x0A;
short unst = 0;
int unnt[4]{};
short unst1 = 0;
short count = 0;
}*PNET_SEND_HEAD;
// 开始创建角色的数据结构
// 申请创建角色结构体
typedef struct NET_CREATEROLE_START {
char un[0x02];// 用来做内存对齐
char len = sizeof(NET_CREATEROLE_START) - 3;// 用来做内存对齐
char op = 0x03;
int code = 0x01;
}*PNET_CREATEROLE_START;
// 删除角色数据结构
typedef struct DATA_DELROLE {
char op;
char buff[0x1F];
int len;
int un[8] = { 0x01 , 0x03 };
}*PDATADELROLE;
/*
数据包还原结构体要注意内存对齐,如果数据不满4字节,它字段会补齐
比如结构体里有一个char变量,它是1字节,在内存里它可能会为了内存对齐
让它变成4字节,所以这要注意
*/
// 登录数据
typedef struct DATA_LOGIN {
int op = 0x0300;
char buff[0x10]{};
int lenId = 0x10;
/*
这个是登录的账号,它可能会变成0x20或更长,现在默认让它0x10
读的时候以长度为准就好了
*/
char Id[0x10]{};
int lenPass = 0x10;
/*
这个是登录的密码,它可能会变成0x20或更长,现在默认让它0x10
读的时候以长度为准就好了
*/
char Pass[0x10]{};
int lenCode = 0x10;
char Code[0x10]{};
int eop = 0x01;
}*PDATALOGIN;
typedef struct DATA_LOGIN_OK {// 登录成功数据包头
int un[8] = { 0, 0, 0x76B, 0x0C, 0x1E,0, 0, 0 };
int index = 0;
int RoleCount = 0;
}*PDATALOGINOK;
// 发送数据包含内存空间,也就是数据参数,数据参数的意思看44文章里的内容
typedef struct NET_SEND_BUFF:public NET_SEND_HEAD {
char buff[0x1000]{};
}*PNET_SEND_BUFF;
typedef class NET_SEND_CHEAD:public NET_SEND_BASE {
public:
union {
GCHAR msgHeader;
GINT opcode;
};
public:
NET_SEND_CHEAD() {};
~NET_SEND_CHEAD() {};
}NSR_CHEAD, *PNSR_CHEAD, *PNET_SEND_CHEAD;
// 选择角色
typedef class NET_SEND_CHOOSECAMP :public NET_SEND_CHEAD {
public:
GCHAR camps;
}*PNS_CHOOSECAMP;
// 心跳包数据结构
typedef class HEART_BEEP :public NET_SEND_CHEAD {
public:
// 20秒一次
GINT tick;//3
}*PHEART_BEEP;
typedef class HEART_LOOP :public NET_SEND_CHEAD {
public:
// 5秒一次
GINT64 tick;//9320695
GCHAR txt;//
}*PHEART_LOOP;
// 聊天数据包的数据结构
typedef class CHAT_DATA :public NET_SEND_CHEAD {
public:
GINT ChartId;//1
}*PCHAT_DATA;
typedef class CHAT_PUBLIC :public CHAT_DATA {
public:
GUTF16 txt;//[222222222222222222222222222222222222222222<img src="Face37" valign="bottom" only="line"/>]
GDOUBLE un;//0.000000
}*PCHAT_PUB;
typedef class CHAT_PRIVATE :public CHAT_DATA {
public:
GUTF16 txt;//[222222222222222222222222222222222222222222<img src="Face37" valign="bottom" only="line"/>]
GUTF16 name;//[皮革兔]
GDOUBLE un;//0.000000
}*PCHAT_PRV;
// 登陆后角色数据包
typedef class ROLE_DATA {// 登录成功数据包
public:
GBYTE byte;
GINT index;
GINT un1;
GUTF16 name;
GUTF16 infos;
GINT un2;
GINT64 un3;
}*PROLEDATA;
GameWinSock.h文件的修改:删除了 数据包头部宏,把宏移动到了NetClass.h文件里了
#pragma once
class GameWinSock
{
typedef bool(GameWinSock::* PROC)(char*, unsigned);
public:
unsigned* vatble;// 虚函数表
unsigned un[17];
unsigned RecvPoint; // 游戏recv之后调用处理一个数据包函数时的eax,这里偏移是0x48
public:
static PROC _OnConnect;
static PROC _OnSend;
static PROC _OnRecv;
bool OnConnect(char* ip, unsigned port);
bool OnSend(char* buff, unsigned len);
// bool OnRecving(char* buff, unsigned len);
bool Recv(char* buff, unsigned len);
void Init();
// end为了避免超长解读
void AnlyBuff(char* start, char* end, int MsgId, char index = 0);
bool OnRecv(char* buff, unsigned len);
};