82.网络游戏逆向分析与漏洞攻防-移动系统分析-坐标修正数据包的处理与模拟

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

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

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

上一个内容:81.飞天遁地的实现与面向计算

码云版本号:12d218943d6fdb3ddf3ea6155cebe4fbb2d79255

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

以 81.飞天遁地的实现与面向计算 它的代码为基础进行修改

看到这就已经把移动相关的操作 移动(跟随)、穿墙、飞天、遁地、面向 都给分析过并且用C++代码实现了,然后在实现的过程中发现了1F数据包,如果我们给服务端发送的移动数据包不合法,服务端会给客户端一个1F开头的数据包把坐标修正,所以接下来就要把这个1F数据包给处理一下

下图1F数据包,可以看到它没有数量,它一次只能更改一个人

通过模拟1F数据包发现并不能完整瞬移,只能瞬移很短的距离,只能完成之前跟随功能只能在目标客户端看到我们移动了,在我们的客户端看不到移动,这个1F数据包可以用来修复这个问题,移动完使用1F数据包更新我们客户端的画面。

这里要提一点

游戏中按鼠标中键(按下滚轮键)可以瞬移,这个操作是服务器提供的测试接口,这种接口正式上线的产品里肯定不会有,如下图鼠标滚轮瞬移只是发了一个goto这样的东西,所以我们不能用它,还是只能用移动相关的数据包来实现瞬移

然后还有一个水中移动的数据包

在水中移动的数据包是15 5 8

在水下有一个憋气条,通过数据包可以看出憋气条的数据

然后进入水下的时候它会发送一个478来告诉服务器现在在水下

从水下上到水面的时候,会发送479,应该可以通过发送这个数据包实现无限憋气条或者屏蔽478数据包实现无限憋气条也就是水下不会死无限游

到这应该可以发现,游戏有很多东西其实都是在本地实现的(我们电脑上的游戏客户端里实现的)

然后屏蔽487

屏蔽487数据包的效果:r角色的客户端没有做拦截,今晚打老虎的客户端做了拦截,可以看出今晚打老虎在水下没有憋气条

CUIWnd_0.cpp文件的修改:修改了 OnBnClickedButton2函数

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

#include "pch.h"
#include "htdMfcDll.h"
#include "CUIWnd_0.h"
#include "afxdialogex.h"
#include "extern_all.h"
#include "GameOBJECTDef.h"

// CUIWnd_0 对话框
float _xNext = 0.0f;
float _yNext = 0.0f;
void _stdcall loops(HWND, UINT, UINT_PTR, DWORD) {
	PAIM aim = Client->GetAimByName(L"r");
	if (aim == nullptr) {
		return;
	}
	//AfxMessageBox(aim->Name);
	float xDis = fabs(Client->Player.x - aim->x);
	float hDis = fabs(Client->Player.h - aim->h);
	float yDis = fabs(Client->Player.y - aim->y);

	float xNext, yNext, thisx, thisy;

	int v[2]{ -1, 1 };

	if (xDis > 12) {
		// 强制修改角色x坐标的值
		thisx = Client->Player.x;
		Client->Player.x = Client->Player.x + 6 * v[Client->Player.x < aim->x];
		xNext = Client->Player.x;
	}
	else {
		xNext = aim->x;
	}
	if (yDis > 12) {
		// 强制修改角色y坐标的值
		thisy = Client->Player.y;
		Client->Player.y = Client->Player.y + 6 * v[Client->Player.y < aim->y];
		yNext = Client->Player.y;
	}else{
		yNext = aim->y;
	}
	if ((xDis < 2)&&(hDis < 2)&&(yDis < 2)) {
		Client->MoveStop(aim->x, aim->h, aim->y, 0.0f);
	}
	else {
		Client->MoveWalk(Client->Player.x,aim->h, Client->Player.x, 0.0f, 0.0f, xNext, yNext);
	}
}

IMPLEMENT_DYNAMIC(CUIWnd_0, CDialogEx)

CUIWnd_0::CUIWnd_0(CWnd* pParent /*=nullptr*/)
	: CDialogEx(IDD_PAGE_0, pParent)
{

}

CUIWnd_0::~CUIWnd_0()
{
}

void CUIWnd_0::DoDataExchange(CDataExchange* pDX)
{
	CDialogEx::DoDataExchange(pDX);
}


BEGIN_MESSAGE_MAP(CUIWnd_0, CDialogEx)
	ON_BN_CLICKED(IDC_BUTTON1, &CUIWnd_0::OnBnClickedButton1)
	ON_BN_CLICKED(IDC_BUTTON2, &CUIWnd_0::OnBnClickedButton2)
END_MESSAGE_MAP()


// CUIWnd_0 消息处理程序


void CUIWnd_0::OnBnClickedButton1()
{
	// 发送坠落数据包
	Client->Fall();
	
	 设置移动速度
	//float Speed = 50.0;
	//Client->SetProperty(Client->Player.lId, INDEX_MoveSpeed, &Speed);
	/*
	// 修改血量
	int HP = 10000000;
	Client->SetProperty(Client->Player.lId, INDEX_HP, &HP);*/
	/*CString txt;
	txt.Format(L"通过AIM类获取角色信息 名字:%s,x坐标:%f", Client->Player.Name, Client->Player.x);
	AfxMessageBox(txt);*/
	// Client->HeartBeep();
	// Client->TalkTo(L"r", L"打架吗?");
	// Client->Talk(L"[欢迎来到麟科思]");
	// Client->SelectRole(L"今晚打老虎");
	/*Client->CreateRole(L"am4", 1.0, 2.0, 4.0, 8.0, "gui\BG_team\TeamRole\Teamrole_zq_humF_001.PNG",
		"Face,0;Hat,0;Eyes,0;Beard,0;Ears,0;Tail,0;Finger,0;Cloth,0;Pants,0;Gloves,0;Shoes,0;Trait,0;HairColor,0;SkinColor,0;SkinMtl,0;Tattoo,0;TattooColor,16777215;",
		"", 0.0);*/
	//Client->SelectCamp("xuanrenZQ");
	//Client->StartCreateRole();
	//Client->DelRole(L"ranzhi11111");
	/*
	char buff[] = {
		0xA, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
		0x00, 0x00, 0x00, 0x00, 0x00, 0x4, 0x00, 0x02, 0x01, 0x00, 0x00, 0x00, 0x02, 0x01, 00 ,0x00,
		0x00, 0x07, 0x0E, 0x00, 0x00, 0x00, 0x31, 0x00, 0x32, 0x00, 0x33, 0x00, 0x31, 0x00, 0x32 ,0x00,
		0x33, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
	};
	WinSock->OnSend(buff, sizeof(buff));
	*/
	/*char buff[] = {
		0x27, 0x46, 0x92, 0x02, 0x00, 0x00, 0x89, 0x02, 0x00, 0x00, 0x06, 0x00, 0x06, 0x05, 
		0x00, 0x00, 0x00, 0x63, 0x68, 0x61, 0x74, 0x00, 0x02, 0x01, 0x00, 0x00, 0x00, 0x07, 
		0x0A, 0x00, 0x00, 0x00, 0x34, 0x00, 0x33, 0x00, 0x39, 0x00, 0x39, 0x00, 0x00, 0x00, 
		0x07, 0x5A, 0x02, 0x00, 0x00, 0x31, 0x00, 0x32, 0x00, 0x31, 0x00, 0x31, 0x00, 0x31, 
		0x00, 0x31, 0x00, 0x31, 0x00, 0x31, 0x00, 0x31, 0x00, 0x31, 0x00, 0x31, 0x00, 0x31, 
		0x00, 0x31, 0x00, 0x31, 0x00, 0x31, 0x00, 0x31, 0x00, 0x31, 0x00, 0x31, 0x00, 0x31, 
		0x00, 0x31, 0x00, 0x31, 0x00, 0x31, 0x00, 0x31, 0x00, 0x31, 0x00, 0x31, 0x00, 0x31, 
		0x00, 0x31, 0x00, 0x31, 0x00, 0x31, 0x00, 0x31, 0x00, 0x31, 0x00, 0x31, 0x00, 0x31, 
		0x00, 0x31, 0x00, 0x31, 0x00, 0x31, 0x00, 0x31, 0x00, 0x31, 0x00, 0x31, 0x00, 0x31, 
		0x00, 0x31, 0x00, 0x31, 0x00, 0x31, 0x00, 0x31, 0x00, 0x31, 0x00, 0x31, 0x00, 0x31, 
		0x00, 0x31, 0x00, 0x31, 0x00, 0x31, 0x00, 0x31, 0x00, 0x31, 0x00, 0x31, 0x00, 0x31, 
		0x00, 0x31, 0x00, 0x31, 0x00, 0x31, 0x00, 0x31, 0x00, 0x31, 0x00, 0x31, 0x00, 0x31, 
		0x00, 0x31, 0x00, 0x31, 0x00, 0x31, 0x00, 0x31, 0x00, 0x32, 0x00, 0x32, 0x00, 0x32, 
		0x00, 0x32, 0x00, 0x32, 0x00, 0x32, 0x00, 0x32, 0x00, 0x32, 0x00, 0x32, 0x00, 0x32, 
		0x00, 0x32, 0x00, 0x32, 0x00, 0x32, 0x00, 0x32, 0x00, 0x32, 0x00, 0x32, 0x00, 0x32, 
		0x00, 0x32, 0x00, 0x32, 0x00, 0x32, 0x00, 0x32, 0x00, 0x32, 0x00, 0x32, 0x00, 0x32, 
		0x00, 0x32, 0x00, 0x32, 0x00, 0x32, 0x00, 0x32, 0x00, 0x32, 0x00, 0x32, 0x00, 0x32, 
		0x00, 0x32, 0x00, 0x33, 0x00, 0x33, 0x00, 0x33, 0x00, 0x33, 0x00, 0x33, 0x00, 0x33, 
		0x00, 0x33, 0x00, 0x33, 0x00, 0x33, 0x00, 0x33, 0x00, 0x33, 0x00, 0x33, 0x00, 0x33, 
		0x00, 0x33, 0x00, 0x33, 0x00, 0x33, 0x00, 0x33, 0x00, 0x33, 0x00, 0x33, 0x00, 0x33, 
		0x00, 0x33, 0x00, 0x33, 0x00, 0x33, 0x00, 0x33, 0x00, 0x33, 0x00, 0x33, 0x00, 0x33, 
		0x00, 0x33, 0x00, 0x33, 0x00, 0x33, 0x00, 0x33, 0x00, 0x33, 0x00, 0x33, 0x00, 0x33, 
		0x00, 0x33, 0x00, 0x33, 0x00, 0x33, 0x00, 0x33, 0x00, 0x33, 0x00, 0x33, 0x00, 0x33,
		0x00, 0x33, 0x00, 0x33, 0x00, 0x33, 0x00, 0x33, 0x00, 0x33, 0x00, 0x33, 0x00, 0x35, 
		0x00, 0x35, 0x00, 0x35, 0x00, 0x35, 0x00, 0x35, 0x00, 0x35, 0x00, 0x35, 0x00, 0x35, 
		0x00, 0x35, 0x00, 0x35, 0x00, 0x35, 0x00, 0x35, 0x00, 0x35, 0x00, 0x35, 0x00, 0x35, 
		0x00, 0x35, 0x00, 0x35, 0x00, 0x35, 0x00, 0x35, 0x00, 0x35, 0x00, 0x35, 0x00, 0x35, 
		0x00, 0x35, 0x00, 0x35, 0x00, 0x35, 0x00, 0x35, 0x00, 0x35, 0x00, 0x35, 0x00, 0x35, 
		0x00, 0x35, 0x00, 0x35, 0x00, 0x35, 0x00, 0x35, 0x00, 0x35, 0x00, 0x35, 0x00, 0x35, 
		0x00, 0x35, 0x00, 0x35, 0x00, 0x35, 0x00, 0x35, 0x00, 0x35, 0x00, 0x35, 0x00, 0x34, 
		0x00, 0x34, 0x00, 0x34, 0x00, 0x34, 0x00, 0x34, 0x00, 0x34, 0x00, 0x34, 0x00, 0x34, 
		0x00, 0x34, 0x00, 0x34, 0x00, 0x34, 0x00, 0x34, 0x00, 0x34, 0x00, 0x34, 0x00, 0x34, 
		0x00, 0x34, 0x00, 0x34, 0x00, 0x34, 0x00, 0x34, 0x00, 0x34, 0x00, 0x34, 0x00, 0x34, 
		0x00, 0x34, 0x00, 0x34, 0x00, 0x34, 0x00, 0x34, 0x00, 0x34, 0x00, 0x34, 0x00, 0x34, 
		0x00, 0x34, 0x00, 0x34, 0x00, 0x34, 0x00, 0x34, 0x00, 0x34, 0x00, 0x34, 0x00, 0x34, 0x00, 0x34, 0x00, 0x34, 0x00, 0x34, 0x00, 0x34, 0x00, 0x34, 0x00, 0x34, 
		0x00, 0x34, 0x00, 0x34, 0x00, 0x34, 0x00, 0x34, 0x00, 0x34, 0x00, 0x34, 0x00, 0x36, 0x00, 0x36, 0x00, 0x36, 0x00, 0x36, 0x00, 0x36, 0x00, 0x36, 0x00, 0x36,
		0x00, 0x36, 0x00, 0x36, 0x00, 0x36, 0x00, 0x36, 0x00, 0x36, 0x00, 0x36, 0x00, 0x36, 0x00, 0x36, 0x00, 0x36, 0x00, 0x36, 0x00, 0x36, 0x00, 0x36, 0x00, 0x36, 
		0x00, 0x36, 0x00, 0x36, 0x00, 0x36, 0x00, 0x36, 0x00, 0x36, 0x00, 0x36, 0x00, 0x36, 0x00, 0x36, 0x00, 0x36, 0x00, 0x36, 0x00, 0x36, 0x00, 0x36, 0x00, 0x36, 
		0x00, 0x36, 0x00, 0x36, 0x00, 0x36, 0x00, 0x36, 0x00, 0x36, 0x00, 0x36, 0x00, 0x36, 0x00, 0x36, 0x00, 0x36, 0x00, 0x36, 0x00, 0x36, 0x00, 0x36, 0x00, 0x36, 0x00, 0x36, 0x00, 0x36, 0x00, 0x37, 
		0x00, 0x37, 0x00, 0x37, 0x00, 0x37, 0x00, 0x37, 0x00, 0x37, 0x00, 0x37, 0x00, 0x37, 0x00, 0x37, 0x00, 0x37, 0x00, 0x37, 0x00, 0x37, 0x00, 0x37, 0x00, 0x37, 0x00, 0x37, 0x00, 0x37, 0x00, 0x37, 0x00, 
		0x38, 0x00, 0x00, 0x00, 0x02, 0x01, 0x00, 0x00, 0x00, 0x02, 0x01, 0x00, 0x00, 0x00
	};
	WinSock->OnRecv(buff, sizeof(buff));*/
}


void CUIWnd_0::OnBnClickedButton2()
{
	// 修正坐标
	PAIM aim = Client->GetAimByName(L"r");
	if (aim == nullptr) {
		return;
	}
	Client->SetCoord(Client->Player.lId, aim->x, aim->h, aim->y, aim->face);
	// 面向
	// Client->FaceTo(L"r");
	// 飞天
	// Client->HideMode = true;
	// 遁地
	//Client->MoveStop(Client->Player.x, Client->Player.h - 5, Client->Player.y, 0.0f);
	// 跟随
	// ::SetTimer(m_hWnd, 0x1000, 2000, loops);
	// 
	// Client->Backtoroles();
}

NetClient.cpp文件的修改:修改了 Init函数、OnSendCustom函数,新加 SetCoord函数

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

bool NetClient::Fall()
{
    NET_SEND_BUFF _buff;
    FALL_DATA_STOP _data2;
    _data2.opcode.Set(SC_FALL_HEADER);
    _data2.Mode.Set(0x2);
    _data2.StartH.Set(Player.h);
    _data2.NextH.Set(Player.h - 12);
    _data2.EndH.Set(Player.h - 120);
    /*
        sizeof(_data) / sizeof(EnCode)的原因
        NET_SEND_CHOOSECAMP结构体里面,没别 东西
        全是 EnCode 这个结构
    */
    short count = sizeof(_data2) / sizeof(EnCode);
    _buff.count = count;


    /*
        CodeMe函数给 _buff.buff里写数据参数的数据
        也就是给0A开头数据包里写,数据参数个数后面的内容
        然后返回值是写了多长的数据
        也就是给0A开头数据包里的数据参数个数后面的数据写了多长
    */
    int ilen = _data2.CodeMe(count, _buff.buff);
    ilen = ilen + sizeof(NET_SEND_HEAD) - 1;
    return WinSock->OnSend(&_buff.op, ilen);

}

bool NetClient::MoveStop(float x, float h, float y, float face)
{
    NET_SEND_BUFF _buff;
    MOVE_DATA_STOP _data;
    _data.opcode.Set(SC_MOVE_HEADER);
    _data.Mode.Set(0x0);
    _data.Count.Set(4);
    _data.x.Set(x);
    _data.h.Set(h);
    _data.y.Set(y);
    _data.face.Set(face);
    /*
        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::MoveWalk(float x, float h, float y, float face, float oldy, float xNext, float yNext)
{
    NET_SEND_BUFF _buff;
    MOVE_DATA_WALK _data;
    _data.opcode.Set(SC_MOVE_HEADER);
    _data.Mode.Set(0x1);
    _data.Count.Set(8);
    _data.x.Set(x);
    _data.h.Set(h);
    _data.y.Set(y);

    _data.xNext.Set(xNext);
    _data.yNext.Set(yNext);
    _data.MoveSpeed.Set(Player.MoveSpeed);
    _data.GSpeed.Set(0.0f);
    _data.face.Set(face);

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

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

PAIM NetClient::GetAimById(long long lId)
{
    if (Player.lId == lId)return &Player;
    for (int i = 0; i < MAX_AIM; i++)
    {
        if ((Aimlst[i] != nullptr) && (Aimlst[i]->lId == lId)) {
            return Aimlst[i];
        }
    }
    return CreateAim();
}

PAIM NetClient::CreateAim()
{
    for (int i = 0; i < MAX_AIM; i++)
    {
        if (Aimlst[i] == nullptr) {
            Aimlst[i] = new AIM();
            return Aimlst[i];
        }
        else if (Aimlst[i]->Isfree) {
            return Aimlst[i];
        }
    }
    return nullptr;
}

void NetClient::RemoveAimById(long long lId)
{
    for (int i = 0; i < MAX_AIM; i++)
    {
        if ((Aimlst[i] != nullptr) && (Aimlst[i]->lId == lId)) {
            // CString _txt;
            // _txt.Format(L"附近的 %s 消失", Aimlst[i]->Name);
            // AfxMessageBox(_txt);
            Aimlst[i]->Release();
        }
    }
}

PAIM NetClient::GetAimByName(const wchar_t* name)
{
    for (int i = 0; i < MAX_AIM; i++)
    {
        if ((Aimlst[i] != nullptr) && (!Aimlst[i]->Isfree) && (Aimlst[i]->Name == name)) {
            return Aimlst[i];
        }
    }
    return nullptr;
}

// x,y是玩家的坐标,targetX,targetY是目标的坐标
float NetClient::GetFace(float x, float y, float targetX, float targetY)
{
    // 计算差值
    x = targetX - x;
    y = targetY - y;
    double pi = 3.14159265358979323846;

    double p = atan2(x, y); // atan2是计算三角形弧度atan2函数返回值-pi ~ pi,负的3.1415926...到正的3.1415926...
    
    // 如果x是负数atan2函数返回值必定是在三四象限里,也就是一个负π
    if (x < 0) {
        p = pi * 2 + p;
    }
    return p;
}

void NetClient::FaceTo(const wchar_t* name)
{
    PAIM _aim = GetAimByName(name);
    if (_aim) {
        float _face = GetFace(Player.x, Player.y, _aim->x, _aim->y);
        MoveStop(Player.x, Player.h, Player.y, _face);
    }
}

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;
    RecvProc[S_OBJECT_INITEX] = &NetClient::OnSvrObjectInitEx;
    RecvProc[S_OBJECT_INITEX_UCOM] = &NetClient::OnSvrObjectInitEx;
    RecvProc[S_UPDATECORD] = &NetClient::OnSvrUpdateCord;
    RecvProc[S_UPDATEPRO] = &NetClient::OnSvrUpdateProperty;
    RecvProc[S_UPDATEPROMU] = &NetClient::OnSvrUpdatePropertyMu;
    RecvProc[S_UPDATEPROMU_COM] = &NetClient::OnSvrUpdatePropertyMu;
    RecvProc[S_OBJECT_REMOVE] = &NetClient::OnSvrRemoveObjectMu;
    RecvProc[S_UPDATECORDEX] = &NetClient::OnSvrUpdateCordEx;
}

bool NetClient::SetCoord(long long lId, float x, float h, float y, float face)
{
    NR_UPDATECOORD head;
    head.lId = lId;
    head.x = x;
    head.h = h;
    head.y = y;
    head.face = face;
    return WinSock->Recv(&head.op, head.len);
}

bool NetClient::SetProperty(long long lId, int ProType, void* value)
{
    NR_OBJECT_UPDATEPRO head;
    head.lId = lId;
    head.itype = ProType;

    int valueType = ObjectTable[ProType].type;
    int valueSize = data_desc[2][valueType].lenth;
    int bufflen = 14;
    switch (valueType)
    {
        case 1:
        case 2:
        case 3:
        case 4:
        case 5:
        case 6:
        case 9:
            memcpy(head.buff, value, valueSize);
            bufflen = bufflen + valueSize;
            break;
        case 7:
            head.lenth = strlen((char*)value) + 1;
            memcpy(head.buffEnd, value, head.lenth);
            bufflen = bufflen + 4 + head.lenth;
            break;
        case 8:
            head.lenth = wcslen((wchar_t*)value) + 1;
            head.lenth = head.lenth + 2;
            memcpy(head.buffEnd, value, head.lenth);
            bufflen = bufflen + 4 + head.lenth;
            break;
    default:
        break;
    }

    return WinSock->Recv(&head.op, bufflen);
}

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;
    case SC_MOVE_HEADER:
        return OnMove((PMOVE_DATA)_coder);
    case SC_FALL_HEADER:
        return OnFall((PFALL_DATA_START)_coder);
    case SC_INWATER:
      return false;
    default:
        return true;
    }
    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::OnMove(PMOVE_DATA _coder)
{
    switch (_coder->Mode)
    {
        case 0:
            return OnMoveStop((PMOVE_DATA_STOP)_coder);
        case 1:
            return OnMoveWalk((PMOVE_DATA_WALK)_coder);
        case 2:
            return OnMoveJump((PMOVE_DATA_JUMP)_coder);
        case 3:
            return OnMoveWJump((PMOVE_DATA_WJUMP)_coder);
    }
    return false;
}

// 移动中处理函数
bool NetClient::OnMoveWalk(PMOVE_DATA_WALK _coder)
{
    /*float* MoveSpeed = (float*)_coder->MoveSpeed.oldPointer;
    MoveSpeed[0] = 5.0f;*/
    if (HideMode) {
        float* f = (float*)_coder->h.oldPointer;
        f[0] = f[0] + 5;
    }
    return true;
}

// 停止移动处理函数
bool NetClient::OnMoveStop(PMOVE_DATA_STOP _coder)
{
    if (HideMode) {
        float* f = (float*)_coder->h.oldPointer;
        f[0] = f[0] + 5;
    }
    return true;
}

// 跳跃处理函数
bool NetClient::OnMoveJump(PMOVE_DATA_JUMP _coder)
{
    /*float* MoveSpeed = (float*)_coder->MoveSpeed.oldPointer;
    MoveSpeed[0] = 5.0f;*/
    if(HideMode) {
        float* f = (float*)_coder->h.oldPointer;
        f[0] = f[0] + 5;
    }
    return true;
}

// 移动时跳跃
bool NetClient::OnMoveWJump(PMOVE_DATA_WJUMP _coder)
{
    /*float* MoveSpeed = (float*)_coder->MoveSpeed.oldPointer;
    MoveSpeed[0] = 5.0f;*/
    if(HideMode) {
        float* f = (float*)_coder->h.oldPointer;
        f[0] = f[0] + 5;
    }
    return true;
}

bool NetClient::OnFall(PFALL_DATA_START _coder)
{
    return true;
}

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
//#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
    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
    GameAnlyer->CreateObjectfiles(ObjectTable, head->count);
#endif // Anly



    return true;
}
bool NetClient::OnSverStruct(char*& buff, unsigned& len) {
    return true;
}
bool NetClient::OnSvrObjectInit(char*& buff, unsigned& len)
{
    /*
        00 00 00 00 00 00 00 为了内存对齐补充了7个0,也就是 un1[6] 和 len
        28 op
        CD 48 00 01 62 A7 DE 04 PNR_OBJINIT:lId
        C1 AA FB C3 PNR_OBJINIT:x;
        3D FF 22 41 PNR_OBJINIT:h;
        D7 0B 4A 44 PNR_OBJINIT:y;
        52 B8 06 40 PNR_OBJINIT:face;
        C1 AA FB C3 PNR_OBJINIT:tx;
        3D FF 22 41 PNR_OBJINIT:th;
        D7 0B 4A 44 PNR_OBJINIT:ty;
        52 B8 06 40 PNR_OBJINIT:tface;
        00 00 00 00 PNR_OBJINIT:un2[0]
        00 00 00 00 PNR_OBJINIT:un2[1]
        00 00 00 00 PNR_OBJINIT:un2[2]
        00 00 00 00 PNR_OBJINIT:un2[3]
        00 00 00 00 PNR_OBJINIT:un2[4]
        61 00       PNR_OBJINIT:icount;

        
        1B 00 (char*)head + sizeof(NR_OBNJECT_INIT) - 2;也就是指向到了 PNR_OBJINIT:type 这个位置
        0C 00 00 00 CA 4E 5A 66 53 62 01 80 4E 86 00 00 1D 00
    */
    // 初始化对象
    PNR_OBJINIT head = (PNR_OBJINIT)(buff - 7);
    char* buffStart = (char*)head + sizeof(NR_OBNJECT_INIT) - 2;

    //int nStart = (int)&Player.lId;
    //int nEnd = (int)&Player.endclass;
    //memcpy(&Player.lId, &head->lId, nEnd - nStart);
    Player.SetHeadDatas(&head->lId);
#ifdef  Anly
    GameAnlyer->AnlyData(buffStart, buff + len, head->icount, S_OBJECT_INIT, ObjectTable);
#endif

    int iProc = 0;
    while (iProc < head->icount)
    {
        Player.UpdateData(buffStart);
        iProc++;
    }

    return true;
}
bool NetClient::OnSvrObjectInitEx(char*& buff, unsigned& len)
{
    // 初始化对象
    PNR_OBJINITEX head = (PNR_OBJINITEX)(buff - 5);
    char* buffStart;

    /*int nStart = (int)&Player.lId;
    int nEnd = (int)&Player.endclass;
    memcpy(&Player.lId, &head->lId, nEnd - nStart);*/

    int iObjectCount = 0;
    short objCount = head->obj_count; // 可以理解为一共有多少个接收的28数据
    while (iObjectCount < objCount) {
        PAIM _paim = GetAimById(head->lId);
        _paim->SetHeadDatas(&head->lId);
       
        buffStart = (char*)head + sizeof(NR_OBNJECT_INITEX) - 2;
        short refCount = head->icount; // 接收的28数据里的数据参数个数
        short iref = 0;
#ifdef  Anly
        GameAnlyer->AnlyData(buffStart, buff + len, refCount, S_OBJECT_INITEX, ObjectTable);
#endif
        while (iref < refCount) {
            _paim->UpdateData(buffStart);
            iref++;
        }
        head = (PNR_OBJINITEX)(buffStart - 8);

        iObjectCount++;
    }

    return true;
}
bool NetClient::OnSvrUpdateCord(char*& buff, unsigned& len)
{
    /*
    00 00 00 00 00 head
    21 02 00 
    CD 14 00 01 CD 14 00 00 第一个坐标数据
    6D BF 54 43 
    A6 FA C7 3F 
    8C 52 A9 C1 
    CB 30 06 40 
    00 00 00 00 
    DB 0F C9 41 
    00 00 00 00 
    00 00 00 00 
    01 00 00 00 State

    CD 14 00 01 CD 14 00 00 第二个坐标数据
    61 41 5B 43 
    A6 FA C7 3F 
    C5 8A C7 C1 
    CB 30 06 40 
    9A 99 99 3E 
    DB 0F C9 40 
    00 00 00 00 
    00 00 00 00 
    01 00 00 00 
    
    */
    // 初始化对象
    PNR_OBJINITEX head = (PNR_OBJINITEX)(buff - 5);

    /*int nStart = (int)&Player.lId;
    int nEnd = (int)&Player.endclass;
    memcpy(&Player.lId, &head->lId, nEnd - nStart);*/

    int iObjectCount = 0;
    short objCount = head->obj_count; // 可以理解为一共有多少个接收的28数据
    while (iObjectCount < objCount) {
        PAIM _paim = GetAimById(head->lId);
        _paim->SetHeadDatas(&head->lId);
        CStringA txtA;
        txtA.Format("x:%f h:%f y:%f", head->x, head->h, head->y);
#ifdef  Anly
        anly->SendData(TTYPE::I_DIS, S_UPDATECORD, txtA.GetBuffer(), txtA.GetAllocLength() + 1);
#endif
        head = (PNR_OBJINITEX)((char*)&head->State - 4);
        iObjectCount++;
    }
    return true;
}
bool NetClient::OnSvrUpdateProperty(char*& buff, unsigned& len)
{
    // 初始化对象
    PNR_OBJECT_UP head = (PNR_OBJECT_UP)(buff - 6);

    PAIM _paim = GetAimById(head->lId);
    int iref = 0;
    short refcount = head->icount;
#ifdef  Anly
    char* buffStart = (char*)&head->itype;
    GameAnlyer->AnlyData(buffStart, buff + len, refcount, S_UPDATEPRO, ObjectTable);
#endif
    while (iref < refcount) {
        _paim->UpdateData(buffStart);
        iref++;
    }
    return true;
}
bool NetClient::OnSvrUpdatePropertyMu(char*& buff, unsigned& len)
{
    // 初始化对象
    PNR_OBJECT_UPMU head = (PNR_OBJECT_UPMU)(buff - 5);

    int iobjCount = 0;
    short objCount = head->objCount; // 可以理解为一共有多少个接收的28数据
    char* buffStart;
    /*
      数据包是下方的样子,
        2D 
        01 00 objCount
        9B 49 00 01 D5 8C 98 05  GetAimById(head->lId);
        02 00 
        25 00 (char*)&head->itype
        18 00 00 00 
        29 00 BA 01 00 00 
        
        while (iobjCount < objCount) 里获取的是
        9B 49 00 01 D5 8C 98 05
        02 00
        25 00 18 00 00 00
        29 00 BA 01 00 00 这一块数据

         while (iref++ < refCount) 里获取的是
         25 00 18 00 00 00

        (PNR_OBJECT_UPMU)(buffStart - 8); 移动到下一个
        9B 49 00 01 D5 8C 98 05  GetAimById(head->lId);
        02 00
        25 00 (char*)&head->itype
        18 00 00 00
        29 00 BA 01 00 00 这个数据

    */
    while (iobjCount < objCount) {
        PAIM _paim = GetAimById(head->lId);
        buffStart = (char*)&head->itype;

        short refCount = head->icount; // 接收的28数据里的数据参数个数
        short iref = 0;

#ifdef  Anly
        GameAnlyer->AnlyData(buffStart, buff + len, refCount, S_UPDATEPROMU, ObjectTable);
#endif

        while (iref++ < refCount) {
            _paim->UpdateData(buffStart);
        }
        head = (PNR_OBJECT_UPMU)(buffStart - 8);
        iobjCount++;
    }

    return true;
}
bool NetClient::OnSvrRemoveObjectMu(char*& buff, unsigned& len)
{
    // 初始化对象
    PNR_OBJECT_REMOVEMU head = nullptr;
    head = (PNR_OBJECT_REMOVEMU)(buff - &head->op);

    int iobjCount = head->objCount;
    
    for (int i = 0; i < iobjCount; i++)
    {
        RemoveAimById(head->lId[i]);
    }

    return true;
}

/*
    1F
    87 44 00 01 7F B0 D6 05
    FE AD DE C3
    6C F5 46 42
    07 FD 36 C4
    90 D9 9C 40
*/
bool NetClient::OnSvrUpdateCordEx(char*& buff, unsigned& len)
{
    // 初始化对象
    PNR_UPDATECOORD head = nullptr;
    head = (PNR_UPDATECOORD)(buff - &head->op);
    PAIM aim = GetAimById(head->lId);

    if (aim) {
        return aim->SetHeadCord(&head->lId);
    }

    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文件的修改:新加 OnSvrUpdateCordEx函数、SetCoord函数

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

#define CAMP_NAME_QH "xuanrenQH"
#define CAMP_NAME_ZE "xuanrenZQ"

#define MAX_AIM 0x200

class NetClient // 监视客户端每一个操作
{
	typedef bool (NetClient::* DATAPROC)(char*&, unsigned&);
public:
	AIM Player; // 玩家角色
	/* 
		怪物列表,最好排序,使用lId排序,然后使用二分查找实现快速查询
		结构用链表或者数组都可以
		怪物或附近玩家这种东西不可能会很多
		如果太多电脑处理不过来,所以数组设置为0x100大小差不多够用
	*/
	PAIM Aimlst[MAX_AIM]{};
	// 用来控制飞天
	bool HideMode;
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);
	bool OnSvrObjectInitEx(char*& buff, unsigned& len);
	bool OnSvrUpdateCord(char*& buff, unsigned& len);
	bool OnSvrUpdateProperty(char*& buff, unsigned& len);
	bool OnSvrUpdatePropertyMu(char*& buff, unsigned& len);
	bool OnSvrRemoveObjectMu(char*& buff, unsigned& len);
	bool OnSvrUpdateCordEx(char*& buff, unsigned& len);

private:
	GameWinSock* WinSock;
	PROLEDATA roles;
	unsigned rolecount;
	bool logined = false;

	bool DelRole(const wchar_t* rolename, unsigned _len);
	PAIM GetAimById(long long lId);
	PAIM CreateAim();
	void RemoveAimById(long long lId);
public:
	// 通过角色名获取附近角色的信息
	PAIM GetAimByName(const wchar_t* name);
	// 获取面向
	float GetFace(float x, float y, float targetX, float targetY);
	// 通过附近角色名获取它的信息,然后通过它的信息里的坐标,再通过我们角色坐标计算面向,效果就是面向 GetAimByName(name); 这个角色
	void FaceTo(const wchar_t* name);
public:
	void virtual Init(GameWinSock * _winSock);
	// 模拟接收的数据包
	bool SetCoord(long long lId, float x, float h, float y, float face);
	bool SetProperty(long long lId, int ProType, void* value);
	/*
		模拟登陆的方法
		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);
	// 坠落
	bool Fall();
	// 模拟停止
	bool MoveStop(float x, float h, float y, float face);
	// 移动开始
	bool MoveWalk(float x, float h, float y, float face, float oldy, float xNext, float yNext);
	// 根据角色名字获取一个登录成功数据包(选择角色列表里的一个数据)
	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:
	// 针对SendCustom的单独处理
	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);
	// 分发移动处理函数
	bool virtual OnMove(PMOVE_DATA _coder);
	// 移动中处理函数
	bool virtual OnMoveWalk(PMOVE_DATA_WALK _coder);
	// 停止移动处理函数
	bool virtual OnMoveStop(PMOVE_DATA_STOP _coder);
	// 跳跃处理函数
	bool virtual OnMoveJump(PMOVE_DATA_JUMP _coder);
	// 移动时跳跃
	bool virtual OnMoveWJump(PMOVE_DATA_WJUMP _coder);
	//  分发坠落函数
	bool virtual OnFall(PFALL_DATA_START _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文件的修改:新加 S_UPDATECORDEX宏、SC_INWATER宏、NR_UPDATECOORD结构体

#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_UPDATEPRO 0x10 // 更新角色怪物信息
#define S_NOTICE 0x1E // 里面有聊天数据
#define S_UPDATECORDEX 0x1F // 坐标修正
#define S_UPDATECORD 0x21 // 坐标更新
#define S_NOTICE_COM 0x27 // 里面有聊天数据
#define S_OBJECT_INIT 0x28 // 里面有角色基础信息
#define S_UPDATEPROMU 0x2D // 更新信息
#define S_UPDATEPROMU_COM 0x2E // 更新信息
#define S_OBJECT_INITEX_UCOM 0x2F // 里面有附近NPC、玩家初始化信息,与0x30数据包一样只是它未压缩
#define S_OBJECT_INITEX 0x30 // 里面有附近NPC、玩家初始化信息
#define S_OBJECT_REMOVE 0x31 // 附近角色或怪物消失

/*
	最多的数据项, 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

#define SC_MOVE_HEADER 15 // 移动
#define SC_FALL_HEADER 890 // 坠入摔血
#define SC_INWATER 478 // 只要发了这个数据包就说明在水下了

/*
	1F
	87 44 00 01 7F B0 D6 05
	FE AD DE C3
	6C F5 46 42
	07 FD 36 C4
	90 D9 9C 40
*/
typedef struct NR_UPDATECOORD {
	char un[6];
	char len = sizeof(NR_UPDATECOORD) - 7;
	char op = S_UPDATECORDEX;
	long long lId;
	float x;
	float h;
	float y;
	float face;
}*PNR_UPDATECOORD;

/*
	31 
	01 00 个数
	9B 49 00 01 D5 8C 98 05 id
	内存对齐
	00 00 00 00 00 31 01 00 // 补五个0与id的8字节对齐
	9B 49 00 01 D5 8C 98 05 
*/
typedef struct NR_OBJECT_REMOVEMU {
	char un[4];
	char len;
	char op = 0x31;
	short objCount = 0;
	long long lId[2];// 这里是一个指针,[]里写什么都行
}*PNR_OBJECT_REMOVEMU;
/*
	角色怪物数据更新结构 2D和2E
	00 00 00 00 00 2D 01 00
	9B 49 00 01 D5 8C 98 05
	02 00
	25 00 18 00 00 00
	29 00 BA 01 00 00
*/
typedef struct NR_OBJECT_UPDATEPROMU {
	char un1[4]; // 为了向id数据的8字节对齐
	char len;
	char op = 0x2D;
	short objCount = 0;
	long long lId;
	short icount;
	short itype;
}NR_OBJECT_UPMU, * PNR_OBJECT_UPMU;

/*角色怪物基本信息更新 接收的10数据包结构体*/
/*
	00 00 为了内存对齐补零
	10 00
	78 48 00 01 FC 61 79 05
	01 00
	29 00 65 09 00 00
*/
typedef struct NR_OBJECT_UPDATEPRO {
	char un1[5]; // 为了向id数据的8字节对齐
	char len;
	char op = 0x10;
	char MsgId = 0x00;
	long long lId;
	short icount = 1;
	short itype;
	union
	{
		char buff[0x04];
		int lenth;
	};
	char buffEnd[0x196];
}NR_OBJECT_UP, *PNR_OBJECT_UP;
/*
  角色怪物初始化结构
	00 00 00 00 00 30 0B 00
	82 0B 00 01 82 0B 00 00
	69 59 F8 C3
	67 49 FF 41
	FE 55 9C 42
	C6 70 BA 40
	A8 8F 07 C4
	06 52 58 41
	3B D0 2A 43
	C6 70 BA 40
	00 00 20 41
	00 00 00 00
	00 00 00 00
	00 00 00 00
	04 00 00 00
	11 00
*/

typedef struct NR_OBNJECT_INITEX {
	char un1[4];
	char len = sizeof(NR_OBNJECT_INITEX) - 7;
	char op = 0x30;
	short obj_count;
	long long lId;
	float x;
	float h;
	float y;
	float face;
	float tx;
	float th;
	float ty;
	float tface;
	int State;
	float un2[4]{};
	short icount;
	short type;
}*PNR_OBJINITEX;

// 角色初始化数据
/*
	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;
	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;

typedef class MOVE_DATA :public NET_SEND_CHEAD {
public:
	GINT Mode; // 0停止 1移动中 2跳跃
	GINT Count; // 带入场景这里就是一共有几个与坐标相关的数据
}*PMOVE_DATA;

// 停止移动
typedef class MOVE_DATA_STOP :public MOVE_DATA {
public:
	GFLOAT x;//-584.845459
	GFLOAT h;//65.004677
	GFLOAT y;//708.458740 
	GFLOAT face;//4.912942
}*PMOVE_DATA_STOP;

// 移动
typedef class MOVE_DATA_WALK :public MOVE_DATA {
public:
	GFLOAT x;//-584.845459 当前坐标 x
	GFLOAT h;//65.004677 当前坐标 h
	GFLOAT y;//708.458740 当前坐标 y
	GFLOAT xNext;//-596.604919 预计移动到的坐标
	GFLOAT yNext;//710.849304 预计移动到的坐标
	GFLOAT MoveSpeed;//5.000000 移动速度
	GFLOAT GSpeed;//0.000000 根据跳跃的数据包猜测它是重力加速(也就是坠落的速度)
	GFLOAT face;//4.912942 面向
}*PMOVE_DATA_WALK;

// 跳跃或往前跳(前后左右跳)
typedef class MOVE_DATA_JUMP:public MOVE_DATA {
public:
	GFLOAT x;//-599.624939
	GFLOAT h;//65.004105
	GFLOAT y;//711.464600
	GFLOAT xNext;//-599.624939
	GFLOAT yNext;//711.464600
	GFLOAT MoveSpeed;//5.000000
	GFLOAT JumpSpeed;//4.500000
	GFLOAT GSpeed;//9.800000 重力加速(也就是坠落的速度)
	GFLOAT face;//4.912942
}*PMOVE_DATA_JUMP;

// 移动时跳跃
typedef class MOVE_DATA_WJUMP :public MOVE_DATA {
public:
	GFLOAT x;//-394.622162
	GFLOAT h;//8.719633
	GFLOAT y;//912.655151
	GFLOAT xNext;//-400.995270
	GFLOAT yNext;//902.487366
	GFLOAT MoveSpeed;//50.000000
	GFLOAT face;//3.701483
}*PMOVE_DATA_WJUMP;

// 开始坠落
typedef class FALL_DATA_START :public NET_SEND_CHEAD {
public:
	GINT Mode;//890
	GINT StartH;//1
	GFLOAT NextH;//58.223557
	GINT EndH;//0

}*PFALL_DATA_START;

// 坠落结束
typedef class FALL_DATA_STOP :public NET_SEND_CHEAD {
public:
	GINT Mode;//2
	GFLOAT StartH;//58.223557
	GFLOAT NextH;//48.128914
	GFLOAT EndH;//24.581387
}*PFALL_DATA_STOP;

AIM.cpp文件的修改:新加 SetHeadCord函数

#include "pch.h"
#include "AIM.h"
#include "AIM.h"

// 复制游戏数据包到自己的内存中
// Create是预留的一个接口暂时没用到
void AIM::SetHeadDatas(void* buffStart, bool Create)
{
	Isfree = false;
	unsigned lStart = (unsigned)&lId;
	unsigned lEnd = (unsigned)&endclass;
	memcpy(&lId, buffStart, lEnd - lStart);
}

bool AIM::SetHeadCord(void* buffStart)
{
	Isfree = false;
	unsigned lStart = (unsigned)&lId;
	unsigned lEnd = (unsigned)&tx;
	memcpy(&lId, buffStart, lEnd - lStart);
	return true;
}

AIM.h文件的修改:新加 SetHeadCord函数

#pragma once
#include "GameOBJECT.h"
typedef class AIM:public GAMEOBJECT {
public:
	long long lId;
	float x;
	float h;
	float y;
	float face;

	float tx;
	float th;
	float ty;
	float tface;

	int IState;
	int endclass;

	// Create是预留的一个接口暂时没用到
	void virtual SetHeadDatas(void* buffStart, bool Create = true);
	bool virtual SetHeadCord(void* buffStart);
}*PAIM;

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

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

相关文章

采油厂职工向媒体投稿的好方法找到了

作为一名采油厂的职工,我深知在媒体上定期投稿的重要性。这不仅是我们展示工作成果、传播企业文化的重要途径,更是上级考核我们工作表现的一项指标。然而,在投稿的过程中,我经历了不少心酸与困扰。 起初,我采用传统的邮箱投稿方式。每天,我都会花费大量时间在网络上搜索合适的媒…

kafka 图形化

介绍 idea 中的一个插件 kafkalytic,kafka 图形化 简单又强大 安装 使用界面 总体信息 数据查看

Python管理PVE(Proxmox VE)云平台--节点资源统计

一、前言 写本脚本的初衷是因手动查看统计已分配的PVE资源过于耗时&#xff0c;因此写一个脚本一劳永逸&#xff0c;具体实现方法&#xff1a;利用Python的paramiko模块进行远程命令查看、统计PVE平台各节点已分配的cpu、内存、磁盘空间。 二、步骤 1.构建shell脚本 1.1 统计…

每日一题 城市群的数量

题目解析 城市群数量_牛客题霸_牛客网 当解决这个问题时&#xff0c;首先需要理解题目要求。题目中给出了一个城市之间的邻接矩阵&#xff0c;矩阵中的元素表示城市之间是否直接相连。如果两个城市直接相连&#xff0c;或者通过其他城市间接相连&#xff0c;它们就属于同一个城…

进程间的IPC通信机制

一、介绍 进程与进程间的用户空间相互独立&#xff0c;内核空间共享。 1.传统的进程间通信机制 a.无名管道 pipe b.有名管道 fifo c.信号 signal 2.system V中的IPC对象 a.消息队列 message queue b.共享内存 shared memory c.信号灯集 semaphoare 3.可用于跨主机传输…

Weblogic 任意文件上传漏洞(CVE-2018-2894)

1 漏洞描述 CVE-2018-2894漏洞存在于Oracle WebLogic Server的Web服务测试页面&#xff08;Web Service Test Page&#xff09;中。这个页面允许用户测试Web服务的功能&#xff0c;但在某些版本中&#xff0c;它包含了一个未经授权的文件上传功能。攻击者可以利用这个漏洞&…

变频器通过Modbus转Profinet网关接电机与PLC通讯在自动化的应用

Modbus转Profinet网关&#xff08;XD-MDPN100/300/600&#xff09;的作用是将Modbus协议转换为Profinet协议&#xff0c;支持Modbus RTU主站/从站&#xff0c;并且Modbus转Profinet网关设备自带网口和串口&#xff0c;既可以实现协议转换的同时&#xff0c;也可以实现接口的转换…

ARM架构安全特性之隔离技术

安全之安全(security)博客目录导读 目录 一、保护代码和数据 二、TrustZone 三、安全世界之间的隔离 四、Secure-EL2扩展 五、保护主流计算工作负载 六、领域管理扩展(RME) 七、内存密集型可信应用程序 八、Arm动态TrustZone技术 强制执行明确定义的安全边界是安全工程…

变现 5w+,一个被严重低估的 AI 蓝海赛道,居然用这个免费国产AI工具就能做!

大家好&#xff0c;我是程序员X小鹿&#xff0c;前互联网大厂程序员&#xff0c;自由职业2年&#xff0c;也一名 AIGC 爱好者&#xff0c;持续分享更多前沿的「AI 工具」和「AI副业玩法」&#xff0c;欢迎一起交流~ 文章首发于公众号&#xff1a;X小鹿AI副业 之前X小鹿一直在各…

HTML与cgi程序的数据交互

1. Html通过ajax获取cgi返回的数据 function HtmlGetCgiData() {$.ajax({type: POST, //提交方法url: cgi-bin/wg67_key_in/wg67_key_in_reflush.cgi, //调用到的cgi程序data: "ajax", //发送的数据&#xff0c;不可缺失该项&#xff0c;不能为空&#xff08;空&…

从“金事通”带给我意想不到的来说--“数据是架构的中心”

背景 上周一个保险的销售人员来找我完成一定的售后流程。其中有一项是请我下载一个叫 金事通的 APP。说实在的我根本没听过。她说这是政治任务。我想不是有你们保险公司的APP了嘛。为什么还要我安装。没办法先安装吧。 经历了注册、人脸识别的步骤后。可以登录了。注册短信发…

AR系列路由器配置本地同一网段互通

A R 路由器是华为公司推出的企业级路由器产品系列&#xff0c;具有高可靠性、高性能和易管理等特点。AR 系列路由器提供的功能包括路由转发、安全接入、语音、视频、无线等多种业务&#xff0c;支持各种接入方式和协议&#xff0c;并且可以方便地进行扩展和升级。 实验拓扑图&…

Spring:@Async注解使用注意事项及九大失效场景

前言 原文作者&#xff1a;微信公众号&#xff1a;苏三说技术 场景举例 代码案例 点击此处可观看&#xff1a;Async注解使用注意事项及九大失效场景

浪潮信息联合SAP助力玉柴集团实现数字化转型的飞跃

数字化时代下&#xff0c;企业面临着前所未有的机遇和挑战。为顺应这一趋势&#xff0c;众多企业纷纷踏上了数字化转型的征程&#xff0c;其中就包括玉柴集团。值得一提的是&#xff0c;在玉柴集团转型过程中&#xff0c;SAP、浪潮信息等国际一流厂商予以了强大的算力支持&…

SSH远程管理 远程访问及控制

SSH远程管理 SSH(Secure Shell) 是一种安全通道协议&#xff0c;主要用来实现字符界面的远程登录、远程复制等功 能。SSH 协议对通信双方的数据传输进行了加密处理&#xff0c;其中包括用户登录时输入的用户口令。与早 期的 Telent (远程登录)、RSH(Remote Shell, 远程执行命…

蓝桥青少一月 STEMA-Python 测评第一题

第一题&#xff08;难度系数 2&#xff0c;18 个计分点&#xff09; (注.input()输入函数的括号中不允许添加任何信息) 编程实现&#xff1a; 给定一个正整数 N&#xff0c;输出 N 除以 3 的商。 输入描述&#xff1a;输入一个正整数 N 输出描述&#xff1a;输出 N 除以 3 的商…

快团团新人怎么找供货团长?免费教程一学就会!

作为快团团的新手&#xff0c;想要寻找供货团长&#xff0c;可以按照以下步骤进行&#xff1a; 打开微信&#xff1a;首先&#xff0c;在您的手机上打开微信应用。 搜索快团团&#xff1a;在微信顶部的搜索框中输入“团长运营之家”&#xff0c;选择出现的“团长运营之家”公号…

纯血鸿蒙APP第三方库——MpChart运动健康场景实践案例

介绍 MpChart是一个包含各种类型图表的图表库&#xff0c;主要用于业务数据汇总&#xff0c;例如销售数据走势图&#xff0c;股价走势图等场景中使用&#xff0c;方便开发者快速实现图表UI&#xff0c;MpChart主要包括线形图、柱状图、饼状图、蜡烛图、气泡图、雷达图、瀑布图…

【JAVA进阶篇教学】第十五篇:Java中AQS讲解

博主打算从0-1讲解下java进阶篇教学&#xff0c;今天教学第十五篇&#xff1a;Java中AQS讲解。 在Java并发编程中&#xff0c;AQS&#xff08;AbstractQueuedSynchronizer&#xff09;是一个重要的框架&#xff0c;用于实现同步器和锁的基础。它提供了一种灵活的方式来实现各种…

SpringSecurity6实现动态权限,rememberMe、OAuth2.0授权登录,退出登录等功能

本文章对应视频可在B站查看SpringSecurity6对应视频教程&#xff0c;记得三连哦&#xff0c;这对我很重要呢&#xff01; 温馨提示&#xff1a;视频与文章相辅相成&#xff0c;结合学习效果更强哦&#xff01; 系列文章链接 1、初识SpringSecurity&#xff0c;认识主流Java权限…