63.网络游戏逆向分析与漏洞攻防-利用数据包构建角色信息-设计对象类型数据表解读算法

免责声明:内容仅供学习参考,请合法利用知识,禁止进行违法犯罪活动!

如果看不懂、不知道现在做的什么,那就跟着做完看效果,代码看不懂是正常的,只要会抄就行,抄着抄着就能懂了

内容参考于:易道云信息技术研究院

上一个内容:62.NetClient类的维护与整理

码云版本号:32a3939334da3642ea3d8fbc4e512c3ddc44ef88

代码下载地址,在 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的分享

62.NetClient类的维护与整理 它的代码为基础进行修改

效果图:

09数据包里的数据类型表解读

之前追的1B,可以通过09数据包的翻译看出它是名字

有了09的数据类型表之后再来看接收的28数据包,根据28数据包猜测数字代表的数据类型

1B是Name,它的数字是8,所以8代表字符串wchar_t*类型的字符串

7是char*数据类型

3代表int类型

5是浮点数

通过找类型的过程中发现,它的类型都是有顺序的,但有的数字没有,如下图10、11、12等都没有,这可以猜测我们的角色现在没有这个数据,按理论来说角色应该有0x227个属性,属性的序号是0-0x227,

1代表char类型(1字节数据)

2代表short类型,2字节数据

4代表long long类型,8字节数据,与时间相关一般都是8字节

6就不用找了它只能是double类型了,还有一个9现在遇不到,等之后遇到了在说

总结数据类型:

1 char

2 short

3 int

4 long long

5 float

6 double

7 char*

8 wchar_t*

9 ?

下方是 tilib 项目里的代码

NetClient.cpp文件的修改:主要修改 OnSverObject函数,其余修改不重要,把下方代码复制全着重看OnSverObject函数就可以

#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::Backtoroles()
{
    // 返回角色
    NET_SEND_BUFF _buff;
    NSR_CHEAD _data;
    _data.opcode.Set(SC_REONLINE);

    /*
        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)
{
    for (int i = 0; i < 0x100; i++) {
        SendProc[i] = &NetClient::DefaultProc;
        RecvProc[i] = &NetClient::DefaultProc;
    }
    this->WinSock = _winSock;
    // 注册登录数据包处理函数
    SendProc[I_LOGIN] = &NetClient::OnClientlogin;
    SendProc[I_CREATEROLE_START] = &NetClient::OnClientStartCreateRole;
    SendProc[I_DELROLE] = &NetClient::OnClientDelRole;
    SendProc[I_SEND_CUSTOM] = &NetClient::OnClientSendCustom;
    SendProc[I_CREATEROLE] = &NetClient::OnClientCreateRole;
    SendProc[I_SELECT_ROLE] = &NetClient::OnClientSelectRole;
    // 注册数据登录失败数据包处理函数
    RecvProc[S_TIPS] = &NetClient::OnSvrTips;
    RecvProc[S_LOGINOK] = &NetClient::OnSverStruct;
    RecvProc[S_CREATEROLE_START] = &NetClient::OnSvrStartCreateRole;
    RecvProc[S_NOTICE] = &NetClient::OnSverNotice;
    RecvProc[S_NOTICE_COM] = &NetClient::OnSverNotice;
    RecvProc[S_OBJECT] = &NetClient::OnSverObject;
    RecvProc[S_STRUCT] = &NetClient::OnSverStruct;
}

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);
    case SC_INITED:
    // case SC_REONLINE:
    case SC_INIT_START:
    case SC_HAND:
    case SC_HAND_IN:
        return false;
    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;
    }
    return true;
}

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;
}

bool NetClient::OnRecvData(char*& buff, unsigned& len)
{
#ifdef  Anly
	anly->SendData(TTYPE::I_RECV, buff[0], buff, len);
#endif
    return (this->*RecvProc[buff[0]])(buff, len);
}

bool NetClient::OnSendData(char*& buff, unsigned& len)
{
#ifdef  Anly
	anly->SendData(TTYPE::I_SEND, buff[0], buff, len);
#endif
    return (this->*SendProc[buff[0]])(buff, len);
}

bool NetClient::OnConnect(char*& ip, unsigned& port)
{
#ifdef  Anly
    // 长度24的原因,它是宽字节要,一个文字要2个字节,一共是10个文字加上结尾的0是11个
    // 所以 11 乘以2,然后再加2 
    anly->SendData(TTYPE::I_LOG, 0, L"服务器正在连接。。。", 24);
#endif
    return true;
}


// 默认的数据处理函数
bool NetClient::DefaultProc(char*&, unsigned&)
{
    return true;
}

// 复制过来的内容
bool NetClient::OnClientlogin(char*& buff, unsigned& len)
{
    PDATALOGIN _data = (PDATALOGIN)(buff + 1);
    char* _id = _data->Id;
    _data = (PDATALOGIN)(buff + 1 + _data->lenId - 0x10);
    char* _pass = _data->Pass;

    Onlogin(_id, _pass);

    /* 修改账号密码
    len = sizeof(DATA_LOGIN) + 1;
    buff = new char[len];
    DATA_LOGIN data;
    PDATALOGIN _data = &data;
    buff[0] = 0x2;

    CStringA _id = "";// 补充账号
    CStringA _pass = "";// 补充密码
    memcpy(_data->Id, _id.GetBuffer(), _id.GetLength());
    memcpy(_data->Pass, _pass.GetBuffer(), _pass.GetLength());
    memcpy(buff + 1, _data, len - 1);
    */
    /* 监控登录数据
    PDATALOGIN _data = (PDATALOGIN)buff;
    CStringA _id = _data->Id;
    _data = (PDATALOGIN)(buff + _data->lenId - 0x10);
    CStringA _pass = _data->Pass;
    CStringA _tmp;
    // 请求登录 账号[% s]密码[% s] 这个内容别人在逆向的时候就会看到
    // 所以这种东西需要自己搞个编码来代替它

     _tmp.Format("请求登录 账号[%s]密码[%s]", _id, _pass);
#ifdef  Anly
    anly->SendData(TTYPE::I_DIS, 1, _tmp.GetBuffer(), _tmp.GetAllocLength());
#endif
    */

    /*
        返回false,游戏无法发送数据包
        原因看调用此此函数的位置 OnSend 函数(if (SendDealProc[buff[0]]((buff + 1), len - 1)))
    */
    return true;
}

bool NetClient::OnClientStartCreateRole(char*& buff, unsigned& len)
{
    // 申请进入创建角色界面
    int* code = (int*)&buff[1];
    return OnStartCreateRole(code[0]);
}

bool NetClient::OnClientCreateRole(char*& buff, unsigned& len) {
    PNS_CREATEROLE head = (PNS_CREATEROLE)(buff - 3);
    int icount = head->count;
    if (icount < 1)return true;
    char* buffStart = (char*)head + sizeof(NET_SEHD_CREATEROLE_HEAD);

    AnlyBuff(buffStart, buff + len, buff[0], 1);// 翻译解析约定数据

    EnCode codes[sizeof(CREATE_ROLE_DATAS) / sizeof(EnCode)]{};
    int stDecode = 0;
    while (stDecode < icount) {
        codes[stDecode++] = buffStart;
    }


    /*
        Client->OnCreateRole(head, (PCREATE_ROLE_DATAS)codes) 数据包传给虚函数
        如果想对发送创建角色数据包做些什么直接继承NetClient重写OnCreateRole函数既可以了
    */
    return Client->OnCreateRole(head, (PCREATE_ROLE_DATAS)codes);// 返回false屏蔽05开头的数据包,也就是创建角色发送的数据包
}

bool NetClient::OnClientSendCustom(char*& buff, unsigned& len) {
    PNET_SEND_HEAD head = (PNET_SEND_HEAD)(buff - 1);
    int icount = head->count;
    if (icount < 1)return true;
    char* buffStart = (char*)head + sizeof(NET_SEND_HEAD);
    if (buffStart[0] != 0x02) {

#ifdef  Anly
        if (icount < MAX_SEND_COUNT)
            anly->SendData(TTYPE::I_DIS, I_SEND_CUSTOM, "SEND_CUSTOM MAX_SEND_COUNT 内存解码器空间不足", 46);

        anly->SendData(TTYPE::I_DIS, I_SEND_CUSTOM, "SEND_CUSTOM 发现异常数据", 25);
#endif
        return true;
    }

    AnlyBuff(buffStart, buff + len, buff[0], 1);

    int stDecode = 0;
    EnCode codes[MAX_SEND_COUNT]{};
    while (stDecode < icount) {
        codes[stDecode++] = buffStart;
    }

    /*
        Client->OnSendCustom((PNET_SEND_CHEAD)codes, buff, len); 数据包传给虚函数
        如果想对发送数据的0A开头的据包做些什么直接继承NetClient重写OnSendCustom函数既可以了
    */
    return Client->OnSendCustom((PNET_SEND_CHEAD)codes, buff, len);

}

bool NetClient::OnClientSelectRole(char*& buff, unsigned& len) {
    PNS_SELECTROLE p = (PNS_SELECTROLE)buff;
    return Client->OnSelectRole((wchar_t*)(p->buff));
}

bool NetClient::OnClientDelRole(char*& buff, unsigned& len) {

    PDATADELROLE p = (PDATADELROLE)buff;
    return Client->OnDelRole((wchar_t*)(p->buff), p->len);



    // 返回值改为false将拦截发送的删除角色数据包
    // 详情看注册 OnDelRole 函数的位置,Init函数
    // return true;
}

// 接收数据截取区

bool NetClient::OnSvrTips(char*& buff, unsigned& len) {
    int* code = (int*)&buff[1];
    return Client->Tips(code[0]);
}

bool NetClient::OnSvrloginOk(char*& buff, unsigned& len) {

    PDATALOGINOK _p = (PDATALOGINOK)&buff[1];
    ROLE_DATA* roleDatas = nullptr;
    if (_p->RoleCount > 0) {
        char* buffStart = buff + 1 + sizeof(DATA_LOGIN_OK);
        AnlyBuff(buffStart, buff + len, buff[0]);
        roleDatas = new ROLE_DATA[_p->RoleCount];
        for (int i = 0; i < _p->RoleCount; i++)
        {
            roleDatas[i].byte.Init(buffStart, 0);
            roleDatas[i].index.Init(buffStart, 0);
            roleDatas[i].un1.Init(buffStart, 0);
            roleDatas[i].name.Init(buffStart, 0);
            roleDatas[i].infos.Init(buffStart, 0);
            roleDatas[i].un2.Init(buffStart, 0);
            roleDatas[i].un3.Init(buffStart, 0);
        }
        Client->loginok(roleDatas, _p->RoleCount);
    }
    return true;
}

bool NetClient::OnSverObject(char*& buff, unsigned& len) {
    PNR_HEAD head = (PNR_HEAD)(buff - 1);
    //head->count;

    if (ObjectTable) {
        delete[] ObjectTable;
    }

    if (ObjectTxt) {
        delete[] ObjectTxt;
    }

    ObjectTable = new OBJECT_DESC[head->count];
    ObjectTxt = new char[len];
    memcpy(ObjectTxt, buff, len);// 这里怕 buff 的内容被游戏释放掉,后面我们用的时候没法用,所以把buff的内容复制到我们的变量里
    char* buffStart = ObjectTxt + sizeof(NR_NOTICE_HEAD)-1;
#ifdef Anly
    CStringA szTxtA;
    CStringA szTmp;
#endif // Anly
    for (int i = 0; i < head->count; i++)
    {
        ObjectTable[i].name = buffStart;
        buffStart = buffStart + strlen(ObjectTable[i].name) + 1;
        ObjectTable[i].type = buffStart[0];
        buffStart++;
#ifdef Anly
        szTmp.Format("[%X]%s:%d\r\n", i, ObjectTable[i].name, ObjectTable[i].type);
        szTxtA += szTmp;
#endif // Anly

    }
#ifdef  Anly
    anly->SendData(TTYPE::I_DIS, S_OBJECT, szTxtA.GetBuffer(), szTxtA.GetAllocLength() + 1);
#endif // Anly
    return true;
}
bool NetClient::OnSverStruct(char*& buff, unsigned& len) {
    return true;
}
/*
 OnSverrNotice函数处理的数据包格式如下
    1E 06 00
    06 11 00 00 00 70 6C 61 79 5F 70 6F 69 6E 74 5F 73 6F 75 6E 64 00
    06 01 00 00 00 00
    04 2C 92 87 C5
    04 FA 03 BF 42
    04 33 14 BD 45
    02 00 00 00 00
    1E 06 00 是 PNR_NOTICE_HEAD
    06 11 00 00 00 70 6C 61 79 5F 70 6F 69 6E 74 5F 73 6F 75 6E 64 00是一个EnCode
    06 01 00 00 00 00是一个EnCode
    04 2C 92 87 C5是一个EnCode
    04 FA 03 BF 42是一个EnCode
    04 33 14 BD 45是一个EnCode
    02 00 00 00 00是一个EnCode
*/
bool NetClient::OnSverNotice(char*& buff, unsigned& len) {
    PNR_NOTICE_HEAD head = (PNR_NOTICE_HEAD)(buff - 1);
    int icount = head->count;
    char* buffStart = (char*)head + sizeof(NR_NOTICE_HEAD);
    if (icount < 1) {
        return true;
    }
    if (icount > MAX_RECV_COUNT) {
#ifdef  Anly
        anly->SendData(TTYPE::I_DIS, S_NOTICE, "S_NOTICE 解码器内存不足", 24);
#endif
        return true;
    }

    AnlyBuff(buffStart, buff + len, buff[0], 1);

    int stDecode = 0;
    EnCode codes[MAX_RECV_COUNT]{};
    while (stDecode < icount) {
        codes[stDecode++] = buffStart;
    }
    return Client->OnSvrNotice((PNET_SEND_CHEAD)codes, icount, buff, len);
}
bool NetClient::OnSvrStartCreateRole(char*& buff, unsigned& len) {
    short* _st = (short*)&buff[1];
    wchar_t* _txt = (wchar_t*)&buff[3];
#ifdef  Anly
    CString txt;
    CStringA txtA;
    txt.Format(L"code:%d\r\n%s", _st[0], _txt);
    txtA = txt;
    //AfxMessageBox(txtA);
    anly->SendData(TTYPE::I_DIS, S_CREATEROLE_START, txtA.GetBuffer(), txt.GetAllocLength() + 1);
#endif
    /*
        Client->OnSendCustom((PNET_SEND_CHEAD)codes, buff, len); 数据包传给虚函数
        如果想对0A开头的据包做些什么直接继承NetClient重写OnSendCustom函数既可以了
    */
    return Client->OnScrStartCreateRole(_st[0], _txt);
}


// 它会生成一个结构体,详情看效果图
void NetClient::AnlyBuff(char* start, char* end, int MsgId, char index)
{
#ifdef  Anly
    CStringA txt;
    CStringA tmp;
    CString utmp;
    EnCode _coder;

    GBYTE* _bytecoder;
    GSHORT* _shortcoder;
    GINT* _intcoder;
    GFLOAT* _floatcoder;
    GDOUBLE* _doublecoder;
    GCHAR* _asccoder;
    GUTF16* _utfcoder;
    GINT64* _int64coder;



    while (start < end) {
        _coder.Init(start, index);
        CStringA _opname = data_desc[_coder.index][_coder.op].name;
        // _opname.MakeLower()是变为小写字母,会影响 _opname它的值
        // 所以又写了一边 data_desc[_coder.index][_coder.op].name
        tmp.Format("%s %s;//", data_desc[_coder.index][_coder.op].name, _opname.MakeLower());
        txt = txt + tmp;
        if (_coder.index == 0) {
            switch (_coder.op)
            {
            case 1:
                _shortcoder = (GSHORT*)&_coder;
                tmp.Format("%d\r\n", _shortcoder->value());
                txt = txt + tmp;
                break;
            case 2:
                _intcoder = (GINT*)&_coder;
                tmp.Format("%d\r\n", _intcoder->value());
                txt = txt + tmp;
                break;
            case 4:
                _floatcoder = (GFLOAT*)&_coder;
                tmp.Format("%f\r\n", _floatcoder->value());
                txt = txt + tmp;
                break;
            case 6:
                _bytecoder = (GBYTE*)&_coder;
                tmp.Format("%d\r\n", _bytecoder->value());
                txt = txt + tmp;
                break;
            case 7:
                _utfcoder = (GUTF16*)&_coder;
                utmp.Format(L"[%s]\r\n", _utfcoder->value());
                tmp = utmp;
                txt = txt + tmp;
                break;
                // 5号之前分析的忘记截图了,现在找不到它的数据包了,如果后面再见到05的时候再详细补充说明
                // 之前的分析05就是double类型
            case 5:
                _doublecoder = (GDOUBLE*)&_coder;
                tmp.Format("%lf\r\n", _doublecoder->value());
                txt = txt + tmp;
                break;
            case 8:
            case 3:
                _int64coder = (GINT64*)&_coder;
                tmp.Format("%lld\r\n", _int64coder->value());
                txt = txt + tmp;
                break;
            default:
                break;
            }
        }

        if (_coder.index == 1) {
            switch (_coder.op)
            {
            case 1:
                _shortcoder = (GSHORT*)&_coder;
                tmp.Format("%d\r\n", _shortcoder->value());
                txt = txt + tmp;
                break;
            case 2:
                _intcoder = (GINT*)&_coder;
                tmp.Format("%d\r\n", _intcoder->value());
                txt = txt + tmp;
                break;
            case 4:
                _floatcoder = (GFLOAT*)&_coder;
                tmp.Format("%f\r\n", _floatcoder->value());
                txt = txt + tmp;
                break;
            case 6:
                _asccoder = (GCHAR*)&_coder;
                tmp.Format("%s\r\n", _asccoder->value());
                txt = txt + tmp;
                break;
            case 7:
                _utfcoder = (GUTF16*)&_coder;
                utmp.Format(L"[%s]\r\n", _utfcoder->value());
                tmp = utmp;
                txt = txt + tmp;
                break;
            case 5:
                _doublecoder = (GDOUBLE*)&_coder;
                tmp.Format("%lf\r\n", _doublecoder->value());
                txt = txt + tmp;
                break;
            case 8:
            case 3:
                _int64coder = (GINT64*)&_coder;
                tmp.Format("%lld\r\n", _int64coder->value());
                txt = txt + tmp;
                break;
            default:
                break;
            }
        }
    }
    anly->SendData(TTYPE::I_DIS, MsgId, txt.GetBuffer(), txt.GetAllocLength() + 1);
#endif
}

NetClient.h文件的修改:新加 ObjectTable变量、ObjectTxt变量、AnlyBuff函数

#pragma once
#include "NetClass.h"
#include "GameWinSock.h"

#define CAMP_NAME_QH "xuanrenQH"
#define CAMP_NAME_ZE "xuanrenZQ"
class NetClient // 监视客户端每一个操作
{
	typedef bool (NetClient::* DATAPROC)(char*&, unsigned&);
protected:
	POBJ_DESC ObjectTable = nullptr;// 游戏的数据类型表
	char* ObjectTxt = nullptr;
	DATAPROC SendProc[0x100];
	DATAPROC RecvProc[0x100];
	bool DefaultProc(char*&, unsigned&);
protected: // 消息处理函数-SEND
	bool OnClientlogin(char*& buff, unsigned& len); // 登录数据包的处理 I_LOGIN
	bool OnClientStartCreateRole(char*& buff, unsigned& len);  // 申请进入创建角色界面 I_CREATEROLE_START
	bool OnClientDelRole(char*& buff, unsigned& len);
	bool OnClientSendCustom(char*& buff, unsigned& len);
	bool OnClientCreateRole(char*& buff, unsigned& len);
	bool OnClientSelectRole(char*& buff, unsigned& len);
protected: // 消息处理函数-RECV
	bool OnSvrTips(char*& buff, unsigned& len);
	bool OnSvrloginOk(char*& buff, unsigned& len);
	bool OnSvrStartCreateRole(char*& buff, unsigned& len);
	bool OnSverNotice(char*& buff, unsigned& len);
	bool OnSverObject(char*& buff, unsigned& len);
	bool OnSverStruct(char*& buff, unsigned& len);
private:
	GameWinSock* WinSock;
	PROLEDATA roles;
	unsigned rolecount;
	bool logined = false;

	bool DelRole(const wchar_t* rolename, unsigned _len);
public:
	void virtual 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();// 心跳数据包(5秒)
	bool HeartLoop();// 延迟心跳数据包(20秒)
	bool Backtoroles(); // 返回到选择角色界面
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);
public:
	bool virtual OnRecvData(char*& buff, unsigned& len);
	bool virtual OnSendData(char*& buff, unsigned& len);
	bool virtual OnConnect(char*& ip, unsigned& port);
protected:

	// end为了避免超长解读
	void AnlyBuff(char* start, char* end, int MsgId, char index = 0);


};


NetClass.h文件的修改:新加 OBJECT_DESC结构体,修改了 MAX_SEND_COUNT宏、MAX_RECV_COUNT宏

#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_OBJECT 0x9
#define S_STRUCT 0xA
#define S_NOTICE 0x1E // 里面有聊天数据
#define S_NOTICE_COM 0x27 // 里面有聊天数据

/*
	最多的数据项, 0A开头的数据包里有数据参数个数,这个宏就是用来设定
	数据参数个数最大数量是多少,0A开头的数据包目前见过最大
	数据参数个数是十几个,这里写20应该完全够用,如果不够用
	到时候再增加
*/
#define MAX_SEND_COUNT 100
#define MAX_RECV_COUNT 100
// 发送数据操作码定义
#define SC_CHOOSECAMP 591 // 阵营选择操作码
#define SC_CHAT 1
#define SC_BEEP 81
#define SC_LOOP 1015
// 游戏客户端处理完进入游戏按钮触发的数据包之后,发送的请求
#define SC_INITED 964
#define SC_REONLINE 860
#define SC_INIT_START 661
#define SC_HAND 962
#define SC_HAND_IN 102

// 保存对象数据描述信息的结构体(游戏数据类型表)
typedef struct OBJECT_DESC {
	char* name;
	int type;
}*POBJ_DESC;

// 数据头结构体 数据结构约定
// 也就是接收到1E包的头部
typedef struct NR_NOTICE_HEAD {
	char un;
	char op;
	short count;
}NR_HEAD, *PNR_HEAD, *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 struct ONLINE_HAND: public NSR_CHEAD{
	GINT step; // 2
	GINT state; // 1
}*PONLINE_HAND;

typedef struct ONLINE_INIT : public NSR_CHEAD {
	GINT state;//0
	GDOUBLE un;//180000000.000000
}*PONLINE_INIT;
// 登陆后角色数据区
// 聊天数据包的数据结构
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.cpp文件的修改:删除 AnlyBuff函数(挪到 NetClient.cpp文件里了)

#include "pch.h"
#include "GameWinSock.h"
#include "extern_all.h"
#include "NetClass.h"
#include "EnCode.h"

GameWinSock::PROC GameWinSock::_OnConnect{};
GameWinSock::PROC GameWinSock::_OnSend{};
GameWinSock::PROC GameWinSock::_OnRecv{};

// 这个函数拦截了游戏的连接
bool GameWinSock::OnConnect(char* ip, unsigned port)
{
//#ifdef  Anly
//	// 长度24的原因,它是宽字节要,一个文字要2个字节,一共是10个文字加上结尾的0是11个
//	// 所以 11 乘以2,然后再加2 
//	anly->SendData(TTYPE::I_LOG, 0, L"服务器正在连接。。。", 24);
//#endif
    // this是ecx,HOOK的点已经有ecx了
    WinSock = this;
	Client->Init(this);
	Client->OnConnect(ip, port);
	return (this->*_OnConnect)(ip, port);
	// 下方注释的代码时为了防止多次注入,导致虚函数地址不恢复问题导致死循环,通过一次性HOOK也能解决
	/*unsigned* vtable = (unsigned*)this;
	vtable = (unsigned*)vtable[0];
	union {
		unsigned value;
		bool(GameWinSock::* _proc)(char*, unsigned);
	} vproc;

	vproc._proc = _OnConnect;

	DWORD oldPro, backProc;
	VirtualProtect(vtable, 0x10x00, PAGE_EXECUTE_READWRITE, &oldPro);
	vtable[0x34 / 4] = vproc.value;
	VirtualProtect(vtable, 0x10x00, oldPro, &backProc);*/

}

bool GameWinSock::OnSend(char* buff, unsigned len)
{
	if(Client->OnSendData(buff, len))return (this->*_OnSend)(buff, len);
	else return true;
	// if (buff[0] == 0x09)return true;
	
	/*
		这里就可以监控游戏发送的数据了
	*/

//#ifdef  Anly
//	anly->SendData(TTYPE::I_SEND, buff[0], buff, len);
//#endif
//	/*
//		数据包的头只有一字节所以它的取值范围就是0x0-0xFF
//	*/
//	if (SendDealProc[buff[0]]((buff), len)) {// 执行失败不让游戏发送数据包
//		return (this->*_OnSend)(buff, len);
//	}
//	else {// 发送失败屏蔽消息
//		return true;// 屏蔽消息
//	}

}

bool GameWinSock::Recv(char* buff, unsigned len)
{
//#ifdef  Anly
//	anly->SendData(1, buff, len);
//#endif
	return (this->*_OnRecv)(buff, len);
}



bool GameWinSock::OnRecv(char* buff, unsigned len)
{
	return Client->OnRecvData(buff, len);
	// 解除压缩后的数据
//#ifdef  Anly
//	anly->SendData(TTYPE::I_RECV, buff[0], buff, len);
//#endif
//	return RecvDealProc[buff[0]](buff, len);
}

GameWinSock.h文件的修改:删除 AnlyBuff函数(挪到 NetClient.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);



	bool OnRecv(char* buff, unsigned len);
};


CAnly.h文件的修改:修改了 COPY_DATA结构体

#define Anly
#ifdef Anly
#pragma once
enum class TTYPE :int {
	I_SEND=0,
	I_RECV=1,
	I_LOG=2,
	I_DIS=3,
	I_ZIP=4,
	MAX
};
#define _TITLE L"titan_data"
typedef struct COPY_DATA {
	time_t ltime; // 时间
	unsigned MsgType; // 数据类型
	unsigned len; // 数据长度
	char buff[0x4000]; // 数据,数据如果超长会闪退
}*PCOPYDATA;
class CAnly{
public:
LRESULT SendData(TTYPE type, unsigned MsgType, void* buff, unsigned len);
};
#endif

下方是 DataAnly 项目里的代码

CWndData.cpp文件的修改:修改了 ShowTips函数

// CWndData.cpp: 实现文件
//

#include "pch.h"
#include "DataAnly.h"
#include "CWndData.h"
#include "afxdialogex.h"


// CWndData 对话框

IMPLEMENT_DYNAMIC(CWndData, CDialogEx)

CWndData::CWndData(CWnd* pParent /*=nullptr*/)
	: CDialogEx(IDD_DIALOG1, pParent)
{

}

CWndData::~CWndData()
{
}

void CWndData::DoDataExchange(CDataExchange* pDX)
{
	CDialogEx::DoDataExchange(pDX);
	DDX_Control(pDX, IDC_EDIT1, m_Edit);
}

BOOL CWndData::OnInitDialog()
{
	/*
		为了处理数据方便,我要给定时器一个成员函数
		然后成员函数它不满足 TIMERPROC 这个类型,编译器也不让我们强制转换
		所以要用一个联合体封装一下,联合体里的变量都共用一个内存
		内存大小由联合体里最大的变量决定
	*/
	union {
		TIMERPROC _address;
		void (CWndData::* _classProc)(HWND, UINT, CWndData*, DWORD);
	}v;
	v._classProc = &CWndData::loops;
	CDialogEx::OnInitDialog();
	//hTips = 0;
	/*
		定时器执行的函数stdcall,让它调用的函数是类的成员函数
		定时器调用的时候可能不会有this(也就是ecx的值不是类对象地址)
		所以这里要手动的把this传递过去
		这样可以方便在函数里对数据进行操作
	*/ 
	::SetTimer(m_hWnd, (UINT_PTR)this, 100, v._address);

	return TRUE;
}

void CWndData::loops(HWND, UINT, CWndData* _this, DWORD)
{
	int nstart = 0;// 选中的内容起始下标
	int nend = 0; // 选中的内容结束下标
	_this->m_Edit.GetSel(nstart, nend); // 获取选中的文字
	int ncount = nend - nstart;
	if (ncount > 1) {
		CString txt;
		CString tmp;
		_this->m_Edit.GetWindowText(txt); // 获取编辑框里的内容
		tmp = txt.Mid(nstart, ncount);
		tmp.Replace(L" ", L""); // 把空格替换成空字符
	
		if (_this->lastTxt != tmp) {
			int lenth = tmp.GetLength();
			if (lenth % 2 == 0) { // 必须是2的倍数,如果不是就说明没有选择全
				_this->lastTxt = tmp;
				_this->ShowTips();
			}
		}
	}
}

TOOLINFO tips; // 提示框结构体

void CWndData::ShowTips()
{
	tips.cbSize = sizeof(tips);// 固定写法,也就是必须这样写,必须有这一句
	unsigned char ExDataBuff[0x1000]{};
	int ilenth = lastTxt.GetLength() / 2; // 字符串是用2字节显示一个内容,因为内容有空格
	if (ilenth > 30)ilenth = 30;
	if (ilenth<=0) {
		
	}
	CString tmp;
	for (int i = 0; i < ilenth; i++) {
		tmp = lastTxt.Mid(i*2, 2);// 这里的Mid函数的意思是从i*2下标位置往后取2个字符
		// wcstol函数降字符串转成long类型,这里将字符转成16进制的long类型
		ExDataBuff[i] = (unsigned char)wcstol(tmp, NULL, 16);
	}

	wchar_t* wBuff = (wchar_t*)ExDataBuff;
	CStringA Buff = (char*)ExDataBuff;

	time_t* _time;
	long long* llRead;
	double* dbRead;
	int* ntRead;
	float* fRead;
	short* stRead;

	CString txtUnicode, txtAscii,txttm, txtll, txtdb, txtnt, txtfloat, txtst,tmpR;
	txtUnicode.Format(L"%s", wBuff);
	txtAscii = Buff;
	// tmp.Format(L"\r\nUTF16:%s\r\nAscII:%s", wBuff, aBuff.GetBuffer());

	for (INT i = 0; i < ilenth; i++)
	{
		// 推测8字节数据
		if ((ilenth - i > 7)&&(i%8==0)) {
			llRead = (long long*)&ExDataBuff[i];
			dbRead = (double*)&ExDataBuff[i];
			_time = (time_t*)&ExDataBuff[i];
			tmpR.Format(L"[%lld]", llRead[0]);
			txtll += tmpR;
			tmpR.Format(L"[%1f]", dbRead[0]);
			txtdb += tmpR;
			tmpR.Format(L"[%s]", TimeToTxt(_time));
			txttm += tmpR;
		}
		// 推测4字节数据
		if ((ilenth - i > 3) && (i % 4 == 0)) {
			fRead = (float*)&ExDataBuff[i];
			ntRead = (int*)&ExDataBuff[i];
			tmpR.Format(L"[%d]", ntRead[0]);
			txtnt += tmpR;
			tmpR.Format(L"[%f]", fRead[0]);
			txtfloat += tmpR;
		}
		// 推测2字节数据
		if ((ilenth - i > 1) && (i % 2 == 0)) {
			stRead = (short*)&ExDataBuff[i];
			tmpR.Format(L"[%d]", stRead[0]);
			txtst += tmpR;
		}
	
	}
	if (txttm != "")tmp = tmp + L"\r\n[time]" + txttm;
	if (txtll != "")tmp = tmp + L"\r\n[int64]" + txtll;
	if (txtdb != "")tmp = tmp + L"\r\n[double]" + txtdb;
	if (txtfloat != "")tmp = tmp + L"\r\n[float]" + txtfloat;
	if (txtnt != "")tmp = tmp + L"\r\n[int]" + txtnt;
	if (txtst != "")tmp = tmp + L"\r\n[short]" + txtst;
	tmp = L"";
	int imax = txtUnicode.GetLength();
	if (imax > 0) {
		tmp = tmp + L"\r\n[Utf16]:" + txtUnicode;
		imax += 8;
	}

	int icount = txtAscii.GetLength();
	if (icount > 0) {
		tmp = tmp + L"\r\n[Ascii]:" + txtAscii;
		icount += 8;
		if (icount > imax) {
			imax = icount;
		}
	}
	icount = txttm.GetLength();
	if (icount > 0) {
		tmp = tmp + L"\r\n[time]:" + txttm;
		icount += 7;
		if (icount > imax) {
			imax = icount;
		}
	}
	icount = txtll.GetLength();
	if (icount > 0) {
		tmp = tmp + L"\r\n[int64]:" + txtll;
		icount += 8;
		if (icount > imax) {
			imax = icount;
		}
	}
	icount = txtdb.GetLength();
	if (icount > 0) {
		tmp = tmp + L"\r\n[double]:" + txtdb;
		icount += 9;
		if (icount > imax) {
			imax = icount;
		}
	}
	icount = txtfloat.GetLength();
	if (icount > 0) {
		tmp = tmp + L"\r\n[float]:" + txtfloat;
		icount += 8;
		if (icount > imax) {
			imax = icount;
		}
	}
	icount = txtnt.GetLength();
	if (icount > 0) {
		tmp = tmp + L"\r\n[int]:" + txtnt;
		icount += 6;
		if (icount > imax) {
			imax = icount;
		}
	}
	icount = txtst.GetLength();
	if (icount > 0) {
		tmp = tmp + L"\r\n[short]:" + txtst;
		icount += 8;
		if (icount > imax) {
			imax = icount;
		}
	}
	
	CString _head('=', imax);
	_head = _head + tmp;

	tips.lpszText = _head.GetBuffer();// 设置提示框的内容
	DWORD lPoint = GetMessagePos();// 获取鼠标位置,GetMessagePos函数返回值是一个DWORD类型,高位是x坐标,低位是y坐标
	
	if (!hTips) {
		/*
			CreateWindow函数的参数说明:
			 第一个参数是窗口注册的类名,是一个字符串,现在写的 TOOLTIPS_CLASS 是一个提示框的类名,由Windows提供的公共控件
			 由Windows对它们进行 RegisterClass 或 RegisterClassEx操作,所以这里可以直接执行CreateWindow操作
			 第二个参数是窗口名称(就是窗口左上角的文字),由于是提示框用来显示描述的提示框,所以写的NULL
			 第三个参数是正在创建的窗口的样式,详情看MSDN(MSDN是微软文档)(去MSDN里搜索 CreateWindowW或者CreateWindowA)
			 第四个参数是窗口初始水平位置,也就是x坐标
			 第五个参数是窗口垂直位置,也就是y坐标
			 第六个参数是窗口的宽度
			 第七个参数是窗口的高度
			 第八个参数是所创建的窗口的父窗口或所有者窗口的句柄,也就是用来给它指定父窗口
			 第九个参数菜单的句柄,没有菜单所以写0
			 第十个参数是要与窗口关联的模块实例的句柄,这里传递的是AfxGetInstanceHandle函数,它会返回当前程序的句柄
			 第十一个参数是给窗口传递的数据,是一个结构体,详情去MSDN看
			 返回值是创建好的窗口句柄
		*/
		hTips = CreateWindow(
			TOOLTIPS_CLASS, NULL, WS_POPUP | TTS_NOPREFIX | TTS_ALWAYSTIP,
			0,0,0,0,m_Edit.m_hWnd,0,AfxGetInstanceHandle(), 0
			);
		if (hTips) {
			// 修改窗口,修改的目的是为了防止提示框被其它窗口遮盖,要确保这个提示框要在最顶层
			::SetWindowPos(hTips, HWND_TOPMOST, 0, 0, 0, 0, SWP_NOACTIVATE | SWP_NOMOVE | SWP_NOSIZE);
			/*
				给提示框发送 TTM_ADDTOOL 消息展示TOOLINFO里的lpszText它的内容
				详细去MSDN搜索 TTM_ADDTOOL 查看详细介绍
				SendMessage的第三个参数和第四个参数可以看做成是 TTM_ADDTOOL 宏的第一个参数和第二个参数
				也就是把 TTM_ADDTOOL它当作函数看待,忽略SendMessage这样的思路去看MSDN
				TTM_ADDTOOL消息处理Windows已经做好了,只需要按照MSDN文档写的说明去传参就好
				效果就是设置第二行-第N行的数据
			*/
			::SendMessage(hTips, TTM_ADDTOOL, (WPARAM)1, (LPARAM)&tips);
			/*
				发送 TTM_SETTITLE 消息,提示框会设置图标和标题,第四个参数是图标,详情去MSDN搜索 TTM_SETTITLE
				MSDN里面介绍了发送 TTM_SETTITLE 消息时,SendMessage函数第三个参数与第四个参数是什么
				SendMessage的第三个参数和第四个参数可以看做成是 TTM_SETTITLE 宏的第一个参数和第二个参数
				TTM_SETTITLE消息处理Windows已经做好了,只需要按照MSDN文档写的说明去传参就好
				效果就是设置第一行的内容
			*/
			::SendMessage(hTips, TTM_SETTITLE, 0, (LPARAM)L"可能的内容");
		}
	}
	if (hTips) {

		// GetWindowRect(&tips.rect);

		// 设置提示框提示的文本(或者说是修改文本)
		::SendMessage(hTips, TTM_UPDATETIPTEXT, (WPARAM)FALSE, (LPARAM)&tips);

		/*
			设置提示框的坐标,详细信息去MSDN搜索 TTM_TRACKPOSITION,
			扩展:
				MAKELONG宏可以设置高位数据与低位数据
				使用例子:MAKELONG(500, 50) 500就是高位数据,50就是低位数据
				MAKELONG宏返回一个DWORD类型(4字节的数字),它的第一个参数是这个4字节数字高位2字节的数据
			第二个参数是低位2字节的数据
			效果就是设置显示位置
		*/
		::SendMessage(hTips, TTM_TRACKPOSITION, (WPARAM)FALSE, lPoint);
		/*
			显示窗口,详细说明还是去MSDN,MSDN操作方式还是搜索 TTM_TRACKACTIVATE
			说明的看法与上面三个一样(TTM_SETTITLE、TTM_ADDTOOL、HWND_TOPMOST)
		*/
		::SendMessage(hTips, TTM_TRACKACTIVATE, (WPARAM)TRUE, (LPARAM)&tips);
	}
}

CString CWndData::TimeToTxt(time_t* _tm)
{
	CString rt;
	struct tm newtiem {};
	localtime_s(&newtiem, _tm); // 获取时间

	rt.Format(L"%.4d-%.2d-%.2d %.2d:%.2d:%.2d", newtiem.tm_year + 1900, newtiem.tm_mon + 1, newtiem.tm_mday, newtiem.tm_hour, newtiem.tm_min, newtiem.tm_sec);

	return rt;
}


BEGIN_MESSAGE_MAP(CWndData, CDialogEx)
	ON_WM_LBUTTONUP()
END_MESSAGE_MAP()


// CWndData 消息处理程序


void CWndData::OnLButtonUp(UINT nFlags, CPoint point)
{
	if (hTips) {
		// 停用提示框(隐藏提示框)
		::SendMessage(hTips, TTM_TRACKACTIVATE, (WPARAM)FALSE, (LPARAM)&tips);

	}
	CDialogEx::OnLButtonUp(nFlags, point);
}

  • 11
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值