66.网络游戏逆向分析与漏洞攻防-利用数据包构建角色信息-重新规划游戏分析信息的输出

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

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

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

上一个内容:65.根据解析好的角色数据还原对应数据结构

码云版本号:11a1c72c384ec854c381698c92c6deed0d15ba80

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

以 64.解析角色初始化数据包输出角色数据内容 它的代码为基础进行修改

上一个内容里把游戏中角色的基本信息解读了出来,现在就要给它生成一个类,然后上一个内容里发现角色的基本信息的数据包并不是完整的,内容不是一个固定,这种不是一个固定的数据,该怎样去定义c++类?上一个内容里看到游戏中它是直接把28数据包解读出来,是一个英文+数据的格式,是一个非主流的结构。

这种不确定内容的类怎么创建,之前有分析到一个09数据包(传送门:61.逆向分析寻找数据类型解读表),09数据包是一个数据类型表,28数据包可以通过09的数据类型给解读出来,所以09数据包相对固定一点,所以直接根据09数据包创建一个头文件,如果后期游戏有改动就再根据09数据包把头文件改一下就行了

创建一个头文件

新加类:

本次代码只做了数据解析相关操作,未实现根据28数据包生成类,本次做的是一些底层的工作

GameProc.cpp文件的修改:修改 Init函数

#include "pch.h"
#include "GameProc.h"
#include "extern_all.h"

// typedef bool(GameWinSock::* U)(char*, unsigned);

bool _OnComprase(HOOKREFS2) {
	return WinSock->OnRecv((char*)_EDI, _EBP);
}

bool _OnRecv(HOOKREFS2) {
	_EAX = WinSock->RecvPoint;
	return true;
}

bool _OnConnect(HOOKREFS2) {
	/*
		根据虚函数表做HOOK的操作
		截取 ecx 获取 winsock 的值(指针)
	*/
	unsigned* vtable = (unsigned*)_EDX;
	//WinSock = (GameWinSock *)_ECX;
	/*
		联合体的特点是共用一个内存
		由于 GameWinSock::OnConnect 的 OnConnect函数是 GameWinSock类的成员函数
		直接 vtable[0x34 / 4] = (unsigned)&GameWinSock::OnConnect; 这样写语法不通过
		所以使用联合体,让语法通过
	*/
	union {
		unsigned value;
		bool(GameWinSock::* _proc)(char*, unsigned);
	} vproc;
	DWORD oldPro, backProc;
	VirtualProtect(vtable, 0x100, PAGE_EXECUTE_READWRITE, &oldPro);
	/*
		vproc._proc = &GameWinSock::OnConnect;  这一句是把我们自己写的调用connect函数的地址的出来
	*/ 
	vproc._proc = &GameWinSock::OnConnect; 
	/*
		InitClassProc函数里做的是给指针赋值的操作
		InitClassProc(&GameWinSock::_OnConnect, vtable[0x34/4]);这一句的意思是把
		GameWinSock类里的_OnConnect变量的值赋值成vtable[0x34/4],这个 vtable[0x34/4] 是虚表里的函数
		vtable[0x34/4]是游戏中调用connect函数的函数地址,经过之前的分析调用connect是先调用了虚表中的
		一个函数,然后从这个函数中调用了connect函数
	*/
	InitClassProc(&GameWinSock::_OnConnect, vtable[0x34/4]);
	vtable[0x34 / 4] = vproc.value;


	vproc._proc = &GameWinSock::OnSend;
	InitClassProc(&GameWinSock::_OnSend, vtable[0x3C / 4]);
	vtable[0x3C / 4] = vproc.value;


	VirtualProtect(vtable, 0x100, oldPro, &backProc);
	return true;
}

GameProc::GameProc()
{
	hooker = new htd::hook::htdHook2();
	Init();
	InitInterface();
}

void GameProc::LoadBase()
{
	LoadLibraryA("fxnet2.dll");
}

void GameProc::Init()
{
#ifdef  anly
	anly = new CAnly();
#endif
	/*
		这里的 WinSock 是0没有创建对象,但是还是能调用Init函数
		这是因为Init函数里面没有用到this,没用到就不会报错
	*/
	Client = new NetClient();
	GameAnlyer = new GameAnly();
	init_datadesc();
}



void GameProc::InitInterface()
{
	LoadBase();
	// MessageBoxA(0, "1", "1", MB_OK);
	// 只会HOOK一次,一次性的HOOK
	hooker->SetHook((LPVOID)0x10617046, 0x1, _OnConnect, 0, true);
	/*
		第一个参数是HOOK的位置
		第二个参数是HOOK的位置的汇编代码的长度(用于保证执行的汇编代码完整)
		第三个参数是HOOK之后当游戏执行到第一个参数的位置的时候跳转的位置
		第四个参数是 _OnRecv 函数返回 false 之后跳转的位置
	*/
	hooker->SetHook((LPVOID)0x10618480, 0x1, _OnRecv, 0);
	hooker->SetHook((LPVOID)0x1061161D, 0x3, _OnComprase, (LPVOID)0x10611602);
	/*
		在这里绑定游戏处理数据包函数(0x10618480函数)
		然后0x10618480函数在上面一行代码已经进行了HOOK
		所以在调用_OnRecv函数指针时,它就会进入我们HOOK
	*/
	InitClassProc(&GameWinSock::_OnRecv, 0x10618480);

}

NetClient.cpp文件的修改:AnlyBuff函数移动到GameAnly.cpp文件中,修改了 OnClientCreateRole函数、OnClientSendCustom函数、OnSvrloginOk函数、OnSvrObjectInit函数

#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;
    RecvProc[S_OBJECT_INIT] = &NetClient::OnSvrObjectInit;
}

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);
#ifdef Anly
    GameAnlyer->AnlyBuff(buffStart, buff + len, buff[0], 1);// 翻译解析约定数据
#endif // Anly

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

#ifdef  Anly
    GameAnlyer->AnlyBuff(buffStart, buff + len, buff[0], 1);
#endif

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

#ifdef Anly
        GameAnlyer->AnlyBuff(buffStart, buff + len, buff[0]);
#endif // Anly

        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;
}
bool NetClient::OnSvrObjectInit(char*& buff, unsigned& len)
{
    // 初始化对象
    PNR_OBJINIT head = (PNR_OBJINIT)(buff - 7);
    char* buffStart = (char*)head + sizeof(NR_OBNJECT_INIT) - 2;
#ifdef  Anly
    GameAnlyer->AnlyData(buffStart, buff + len, head->icount );
#endif

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

#ifdef  Anly
    GameAnlyer->AnlyBuff(buffStart, buff + len, buff[0], 1);
#endif

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

NetClient.h文件的修改:AnlyBuff函数移动到 GameAnly.h文件里

#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);
	bool OnSvrObjectInit(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);

};


NetClass.h文件的修改:修改了 OBJECT_DESC结构体

#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 // 里面有聊天数据
#define S_OBJECT_INIT 0x28 // 里面有角色基础信息
/*
	最多的数据项, 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

// 角色初始化数据
/*
	28  op
	E6 44 00 01 39 0A 63 04 lId
	6F 3A 07 C4 x
	49 C8 81 41 h
	46 1E 40 44 y
	BC 74 03 40 face
	6F 3A 07 C4 tx
	49 C8 81 41 th
	46 1E 40 44 ty
	BC 74 03 40 tface
	00 00 00 00 un[0]
	00 00 00 00 un[1]
	00 00 00 00 un[2]
	00 00 00 00 un[3]
	00 00 00 00 un[4]
	5E 00 icount

	多出来的type是为了内存对齐才写的
*/
typedef struct NR_OBNJECT_INIT {
	char un1[6];
	char len = sizeof(NR_OBNJECT_INIT) - 9;
	char op = 0x28;
	long long lId;
	float x;
	float h;
	float y;
	float face;
	float tx;
	float th;
	float ty;
	float tface;

	float un2[5]{};
	short icount;
	short type;
}*PNR_OBJINIT;

// 保存对象数据描述信息的结构体(游戏数据类型表)
typedef struct OBJECT_DESC {
	char* name;
	short type;
	short size;
}*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;

新加 GameBaseT.h,它的作用看后续的代码实现,现在没想好,目前只有28数据包的雏形类

#pragma once

#define GAsc CStringA
#define Gutf CString
typedef class GAMEOBJECT{
public:
	char Type;
	char Set;
public:
	bool virtual SetType(char& value);
	bool virtual SetSet(char& value);
}*PGAMEOBJ;

新加 GameAnly.cpp文件,数据包分析相关的实现全部放在这个文件里

#include "pch.h"
#include "GameAnly.h"

#ifdef Anly
// 它会生成一个结构体,详情看效果图
void GameAnly::AnlyBuff(char* start, char* end, int MsgId, char index)
{
    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().GetBuffer());
        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);
}
void GameAnly::AnlyData(char* start, char* end, int count)
{
    int iProc = 0;
    while ((iProc < count) && (start <end)) {
        short* index = (short*)start;
    }

    //CStringA tmpA;
    //CStringA szTxt, szTmp;
    //szTmp.Format("id:%lld\r\n", head->lId);
    //szTxt = szTxt + szTmp;
    //szTmp.Format("x:%f h:%f y:%f\r\n", head->x, head->h, head->y);
    //szTxt = szTxt + szTmp;
    //char* buffStart = (char*)head + sizeof(NR_OBNJECT_INIT) - 2;
    //int icount = head->icount;
    //int iProc = 0;

    //while (iProc < icount) {
    //    short* type = (short*)buffStart;
    //    char* _name = ObjectTable[type[0]].name;
    //    int _type = ObjectTable[type[0]].type;

    //    char* _byte;
    //    short* _short;
    //    int* _int;
    //    float* _float;
    //    long long* _llong;
    //    double* _double;
    //    int lenth;

    //    CString _txt;
    //    /*
    //        1B 00 type[0] buffStart + 2;
    //        0C 00 00 00 CA 4E 5A 66 53 62 01 80 4E 86 00 00
    //        1D 00 type[0]
    //        00 00 48 42 buffStart + 2;
    //        01 00 type[0]
    //        02 buffStart + 2;
    //        02 00 type[0]
    //        01 buffStart + 2;
    //        03 00 2E 00 00 00 67 75 69 5C 42 47 5F 74 65 61 6D 5C 54 65 61 6D 52 6F 6C 65 5C 54 65 61 6D 72 6F 6C 65 5F 7A 71 5F 68 75 6D 46 5F 30 30 31 2E 50 4E 47 00
    //        04 00 01 00 00 00
    //        05 00 01 00 00 00
    //        06 00 01 00 00 00
    //        07 00 01 00 00 00
    //        08 00 00 B1 9E 00
    //    */
    //    buffStart = buffStart + 2;

    //    switch (_type)
    //    {
    //    case 0:
    //        AfxMessageBox(L"0号信息!"); break;
    //    case 1:
    //        _byte = buffStart;
    //        szTmp.Format("%s = %d", _name, _byte[0]);
    //        buffStart = buffStart + 1;
    //        break;
    //    case 2:
    //        _short = (short*)buffStart;
    //        szTmp.Format("%s = %d", _name, _short[0]);
    //        buffStart = buffStart + 2;
    //        break;
    //    case 3:
    //        _int = (int*)buffStart;
    //        szTmp.Format("%s = %d", _name, _int[0]);
    //        buffStart = buffStart + 4;
    //        break;
    //    case 4:
    //        _llong = (long long*)buffStart;
    //        szTmp.Format("%s = %lld", _name, _llong[0]);
    //        buffStart = buffStart + 8;
    //        break;
    //    case 5:
    //        _float = (float*)buffStart;
    //        szTmp.Format("%s = %f", _name, _float[0]);
    //        buffStart = buffStart + 4;
    //        break;
    //    case 6:
    //        _double = (double*)buffStart;
    //        szTmp.Format("%s = %lf", _name, _double[0]);
    //        buffStart = buffStart + 8;
    //        break;
    //    case 7:
    //        _int = (int*)buffStart;
    //        lenth = _int[0];
    //        // szTmp = buffStart + 4;
    //        szTmp.Format("%s = %s", _name, buffStart + 4);
    //        buffStart = buffStart + 4 + lenth;
    //        break;
    //    case 8:
    //        _int = (int*)buffStart;
    //        lenth = _int[0];
    //        _txt = (wchar_t*)(buffStart + 4);
    //        tmpA = _txt;
    //        szTmp.Format("%s = %s", _name, tmpA);
    //        buffStart = buffStart + 4 + lenth;
    //        break;
    //    case 9:MessageBoxA(0, buffStart, buffStart, MB_OK); return true;
    //    default:
    //        break;
    //    }
    //    szTxt = szTxt + szTmp + "\r\n";
    //    iProc++;
    //}
    //anly->SendData(TTYPE::I_DIS, S_OBJECT_INIT, szTxt.GetBuffer(), szTxt.GetAllocLength() + 1);
}
#endif // Anly

char GameAnly::ToChar(char*& start)
{
    char result = start[0];
    start = start + 3;
    return result;
}

short GameAnly::ToShort(char*& start)
{
    short* result = (short*)(start + 2);
    start = start + 2 + 2;
    return result[0];
}

int GameAnly::ToInt(char*& start)
{
    int* result = (int*)(start + 2);
    start = start + 2 + 4;
    return result[0];
}

float GameAnly::ToFloat(char*& start)
{
    float* result = (float*)(start + 2);
    start = start + 2 + 4;
    return result[0];
}

double GameAnly::ToDouble(char*& start)
{
    double* result = (double*)(start + 2);
    start = start + 2 + 8;
    return result[0];
}

long long GameAnly::ToLLong(char*& start)
{
    long long* result = (long long*)(start + 2);
    start = start + 2 + 8;
    return result[0];
}

char* GameAnly::ToAscii(char*& start, int len)
{
    int* lenth = (int*)(start + 2);
    char* result = start + 2;
    start = start + 2 + 4 + lenth[0];
    return result;
}

wchar_t* GameAnly::ToUniode(char*& start, int len)
{
    int* lenth = (int*)(start + 2);
    wchar_t* result = (wchar_t*)(start + 2);
    start = start + 2 + 4 + lenth[0];
    return result;
}

新加 GameAnly.h头文件,数据包分析相关的声明全部放在这个文件里

#pragma once
#include "extern_all.h"
#ifdef Anly
// 用来分析游戏数据包
class GameAnly
{	
public:
	char ToChar(char*& start);
	short ToShort(char*& start);
	int ToInt(char*& start);
	float ToFloat(char*& start);
	double ToDouble(char*& start);
	long long ToLLong(char*& start);
	char* ToAscii(char*& start, int len);
	wchar_t* ToUniode(char*& start, int len);
	// end为了避免超长解读
	void AnlyBuff(char* start, char* end, int MsgId, char index = 0);
	void AnlyData(char* start, char* end, int count);
};
#endif // Anly

extern_all.cpp文件的修改:修改了 data_desc变量、init_datadesc函数,新加 GameAnlyer变量初始化

/*
	此文件是用来存放全局变量、全局函数(通用函数)
*/
#include "pch.h"
#include "extern_all.h"
GameWinSock* WinSock = nullptr;
GameProc* PGameProc = nullptr;
NetClient* Client = nullptr;
#ifdef Anly
CAnly* anly = nullptr;
GameAnly* GameAnlyer = nullptr;
#endif

DATA_DESC data_desc[3][10]{};

void init_datadesc() {
	data_desc[0][0] = { "none", 0 };
	data_desc[0][1] = { "GSHORT", 2 };
	data_desc[0][2] = { "GINT", 4 };
	data_desc[0][3] = { "GINT64", 8 };
	data_desc[0][4] = { "GFLOAT", 4 };
	data_desc[0][5] = { "GDOUBLE", 8 };
	data_desc[0][6] = { "GBYTE", 1 };
	data_desc[0][7] = { "GUTF16", 4 };
	data_desc[0][8] = { "GINT64", 8 };

	data_desc[1][0] = { "none", 0 };
	data_desc[1][1] = { "GSHORT", 2 };
	data_desc[1][2] = { "GINT", 4 };
	data_desc[1][3] = { "GINT64", 8 };
	data_desc[1][4] = { "GFLOAT", 4 };
	data_desc[1][5] = { "GDOUBLE", 8 };
	data_desc[1][6] = { "GCHAR", 4 };
	data_desc[1][7] = { "GUTF16", 4 };
	data_desc[1][8] = { "GINT64", 8 };

	data_desc[2][0] = { "none", 0 };
	data_desc[2][1] = { "char", 1 };
	data_desc[2][2] = { "short", 2 };
	data_desc[2][3] = { "int", 4 };
	data_desc[2][4] = { "long long", 8 };
	data_desc[2][5] = { "float", 4 };
	data_desc[2][6] = { "double", 8 };
	data_desc[2][7] = { "GCHAR", 4 };
	data_desc[2][8] = { "GWCHAR", 4 };
	data_desc[2][9] = { "GOBJECT", 4 };
}

void InitClassProc(LPVOID proc_addr, unsigned value)
{
	unsigned* writer = (unsigned*)proc_addr;
	writer[0] = value;
}

extern_all.h文件的修改:修改了 DATA_DESC结构体,新加 GameAnlyer变量

/*
	此文件是用来存放全局变量、全局函数(通用函数)
*/
#pragma once
#include "GameWinSock.h"
#include "GameProc.h"
#include "CAnly.h"
#include "NetClient.h"
#include "GameAnly.h"

typedef struct DATA_DESC {
	char* name;
	short type;
	short lenth;
}*PDATADESC;

extern 	GameWinSock* WinSock;
extern GameProc* PGameProc;
extern NetClient* Client;
extern DATA_DESC data_desc[2][9];
extern void InitClassProc(LPVOID proc_addr, unsigned value);
extern void init_datadesc();
class GameAnly; // 不加这一个会因为引用编译不通过
#ifdef Anly
extern CAnly* anly;
extern GameAnly* GameAnlyer;
#endif

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/578689.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Apache Seata的可观测实践

title: Seata的可观测实践 keywords: [Seata、分布式事务、数据一致性、微服务、可观测] description: 本文介绍Seata在可观测领域的探索和实践 author: 刘戎-Seata 本文来自 Apache Seata官方文档&#xff0c;欢迎访问官网&#xff0c;查看更多深度文章。 Seata简介 Seata的…

STM32单片机通过ST-Link 烧录和调试

系列文章目录 STM32单片机系列专栏 C语言术语和结构总结专栏 1. ST-LINK V2 ST LINK v2下载器用于STM32单片机&#xff0c;可以下载程序、调试程序、读取芯片数据&#xff0c;解除芯片读写保护等等&#xff0c;辅助软件用的是STM32 ST-LINK Utility。 STM32 ST-LINK Utility…

电脑上的任务管理器不见了?如何把它打开?

前言 今天小白在睡觉的时候突然梦见回到了学校的电脑教室…… 相信大家都会有体验&#xff1a;每次上电脑课的时候&#xff0c;老师都会通过某些软件监控和控制学生的电脑。 想退出被控端的软件&#xff1f;没机会&#xff01;毕竟任务管理器也被禁用了&#xff0c;想整活都…

算法学习之单调栈

发射站 题目描述 某地有 N N N 个能量发射站排成一行&#xff0c;每个发射站 i i i 都有不相同的高度 H i H_i Hi​&#xff0c;并能向两边&#xff08;两端的发射站只能向一边&#xff09;同时发射能量值为 V i V_i Vi​ 的能量&#xff0c;发出的能量只被两边最近的且比…

Opencv_14_多边形填充与绘制

绘制多边形&#xff1a; 1&#xff09;coInvert.polyline_drawing(src); 2&#xff09;void ColorInvert::polyline_drawing(Mat& image) { Mat canvas Mat::zeros(Size(512, 512), CV_8UC3); Point p1(100, 100); Point p2(150, 100); Point p3(200…

TR6 - Transformer实战 单词预测

&#x1f368; 本文为&#x1f517;365天深度学习训练营 中的学习记录博客&#x1f356; 原作者&#xff1a;K同学啊 目录 理论知识关于数据集 Wikitext-2 模型结构代码实现0. 环境1. 加载数据集2. 模型搭建3. 创建模型4. 单轮训练和评估的流程5. 训练 模型效果总结与心得体会 …

Openharmony - 设备异常关机Power Down问题分析

By: fulinux E-mail: fulinux@sina.com Blog: https://blog.csdn.net/fulinus 喜欢的盆友欢迎点赞和订阅! 你的喜欢就是我写作的动力! 目录 1.问题描述1.1出现power down的原因1.1.1硬件故障或信号1.1.2软件错误或系统崩溃2.抓日志信息2.1.抓日志方法2.2.问题初步分析3.问题排…

React复习笔记

基础语法 创建项目 借助脚手架&#xff0c;新建一个React项目(可以使用vite或者cra&#xff0c;这里使用cra) npx create-react-app 项目名 create-react-app是React脚手架的名称 启动项目 npm start 或者 yarn start src是源文件index.js相当于Vue的main.js文件。整个…

OC类与对象

OC类与对象 本篇是对上一篇的内容的继续学习。从单例模式开始继续学习 文章目录 单例模式定义应用场景特点单例模式的创建 隐藏与封装理解什么是封装目的访问控制符合成存取方法特性的指示符点语法访问属性 对象初始化便利的初始化方法 类的继承特点语法格式重写父类方法super关…

SimpleDateFormat类.Java

目录 1.1构造方法 1.2格式规则 1.3常用方法 1.4练习1&#xff08; 出生日期&#xff09; 1.5练习2(秒杀活动) java.text.SimpleDateFormat 是日期/时间格式化类&#xff0c;我们通过这个类可以帮我们完成日期和文本之间的转换,也就是可以在Date对象与String对象之间进行来…

机器学习理论基础—集成学习(1)

机器学习理论基础—集成学习 个体与集成 集成学习通过构建并结合多个学习器来完成学习任务&#xff0c;有时也称为多分类系统等。 分类&#xff1a; 根据集成学习中的个体学习器的不同可以分为同质集成&#xff08;集成的学习器相同例如全部是决策树&#xff09;&#xff0c…

上市公司专利数据、专利申请、专利授权和质量指标计算面板数据(1990-2022年)

01、数据简介 专利作为企业创新能力和核心竞争力的体现&#xff0c;越来越受到上市公司的重视。了解上市公司的专利数据、专利申请、专利授权和质量指标计算&#xff0c;有助于投资者更好地评估公司的创新能力和长期发展潜力。 通过分析上市公司的专利数据、专利申请、专利授…

【国标语音对讲】EasyCVR视频汇聚平台海康/大华/宇视摄像头GB28181语音对讲配置

一、背景分析 近年来&#xff0c;国内视频监控应用发展迅猛&#xff0c;系统接入规模不断扩大&#xff0c;涌现了大量平台提供商&#xff0c;平台提供商的接入协议各不相同&#xff0c;终端制造商需要给每款终端维护提供各种不同平台的软件版本&#xff0c;造成了极大的资源浪…

[C++ QT项目实战]----系统实现双击表格某一行,表格数据不再更新,可以查看该行所有信息,选中表更新之后,数据可以继续更新

前言 在需要庞大的数据量的系统中&#xff0c;基于合适的功能对数据进行观察和使用至关重要&#xff0c;本篇在自己项目实战的基础上&#xff0c;基于C QT编程语言&#xff0c;对其中一个数据功能进行分析和代码实现&#xff0c;希望可以有所帮助。一些特殊原因&#xff0c;图片…

回溯-单词搜索

给定一个 m x n 二维字符网格 board 和一个字符串单词 word 。如果 word 存在于网格中&#xff0c;返回 true &#xff1b;否则&#xff0c;返回 false 。 单词必须按照字母顺序&#xff0c;通过相邻的单元格内的字母构成&#xff0c;其中“相邻”单元格是那些水平相邻或垂直相…

压测--混合场景设置

1、设计测试场景 性能测试是通过自动化的测试工具模拟多种正常、峰值以及异常负载条件来对系统的各项性能指标满足需求定义的检验活动。一般有以下场景&#xff1a; 基准场景&#xff1a;单接口少量并发用户下压测&#xff0c;评估单个功能点性能。负载场景&#xff1a;逐步增…

Python实践应用|NC文件读取

import netCDF4 as nc import numpy as np import matplotlib.pyplot as plt# 打开NC文件 nc_file E:/NC_file/air.sig995.2012.nc # 将your_file.nc替换为你的NC文件路径 nc_data nc.Dataset(nc_file, r)# 查看NC文件中包含的变量 print("Variables in the NC file:&q…

免费简单好用的内网穿透工具(ngrok、natapp),微信回调地址配置

B站视频地址 文章目录 Natapp1、登录注册账号、下载软件2、使用2-1、购买隧道、查看token2-2、端口穿透 Ngrok1、登录注册账号、下载软件2、使用2-1、获取并设置 token2-2、使用 3、隧道 微信回调配置1、注册测试公众号2、回调代码3、回调配置 在一些特殊的场景下&#xff0c;需…

C#基础之结构体

结构体 文章目录 1、概念2、基本语法3、示例4、结构体的使用5、访问修饰符6、结构体的构造函数思考1 描述矩形信息思考2 职业名字释放了技能思考3 小怪兽思考4 多个小怪兽思考5 奥特曼打小怪兽 1、概念 结构体是一种一定义变量类型 它是数据和函数的集合&#xff0c;可以在结…

PCIe总线-MPS MRRS RCB参数介绍(四)

1.概述 PCIe总线的存储器写请求、存储器读完成等TLP中含有数据负载&#xff0c;即Data Payload。Data Payload的长度和MPS&#xff08;Max Payload Size&#xff09;、MRRS&#xff08;Max Read Request Size&#xff09;和RCB&#xff08;Read Completion Boundary&#xff0…