57.网络游戏逆向分析与漏洞攻防-基础数据分析筛选-获取他人发来的聊天信息

news2024/11/27 0:18:07

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

如果看不懂、不知道现在做的什么,那就跟着做完看效果

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

上一个内容:56.聊天数据的截取与模拟

码云地址(master 分支):https://gitee.com/dye_your_fingers/titan

码云版本号:b47527e75716c6cf3f90bb5c51b8e92ba1f56b7a

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

以 56.聊天数据的截取与模拟 它的代码为基础进行修改

首先上一个内容里模拟的聊天数据有一点问题

如下图公告频道第一个字节是chat*,而模拟的数据包时int类型(详情看上一个内容里的图)

下图是公会频道

然后带上标签发送的数据包发现,聊天数据包变成了27开头,27开头的数据包的结构于1E开头的数据包的结构一致

解析了0x27数据包

通过下图可以看出,下图是私聊的数据包,少了两个数字,所以那两个数据不重要,所以我们的代码也就只会解析下图红框里的内容,后面的两个数据不要了

然后公共频道聊天多处了,下图红框位置的数据,然后我们不去解读它,所以上一个内容里的结构体还是可以用的

CUIWnd_0.cpp文件也修改了,它只修改了私聊对方名,所以代码就不贴出来了,详情去看百度网盘的压缩包,或者码云

NetClient.cpp文件的修改:新加 OnSvrChat函数,修改了 OnSvrNotice函数

#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::DelRole(const wchar_t* rolename, unsigned _len)
{
    DATA_DELROLE _data;
    _data.op = 0x06;
    _data.len = _len;
    memcpy(_data.buff, rolename, _len);
    return WinSock->OnSend((char*)&_data, sizeof(DATA_DELROLE) - 1);
}

void NetClient::Init(GameWinSock* _winSock)
{
    this->WinSock = _winSock;
}

bool NetClient::OnDelRole(wchar_t* rolename, unsigned _len)
{
    // AfxMessageBox(rolename);
    return true;
}

void NetClient::Onlogin(const char* Id, const char* Pass)
{
    
    /*
    const int bufflen = sizeof(DATA_LOGIN) + 1;
    char buff[bufflen];
    DATA_LOGIN data;
    // 有些操作系统这样写会报错,因为内存不对齐,现在Windows下没事
    //PDATALOGIN _data = (PDATALOGIN)(buff + 1);
    // 这样写就能解决内存对齐问题
    PDATALOGIN _data =&data;
    int len = strlen(Id);
    memcpy(_data->Id, Id, len);
    len = strlen(Pass);
    memcpy(_data->Pass, Pass, len);
    memcpy(buff+1, _data, sizeof(DATA_LOGIN));
    buff[0] = I_LOGIN;
    return  WinSock->OnSend(buff, sizeof(buff));
    */
}

bool NetClient::OnStartCreateRole(int code)
{
    return true;
}

bool NetClient::OnCreateRole(PNS_CREATEROLE _header, PCREATE_ROLE_DATAS _body)
{
    return true;
}

bool NetClient::OnSendCustom(PNET_SEND_CHEAD _coder, char*& buffer, unsigned& len)
{
    switch (_coder->opcode.value())
    {
    case SC_CHOOSECAMP:
        return OnChooseCamp((PNS_CHOOSECAMP)_coder);
        break;
    case SC_CHAT:
        return OnChat((PCHAT_DATA)_coder);
        break;
    default:
        break;
    }
    return true;
}

bool NetClient::OnSelectRole(wchar_t* rolename)
{
    AfxMessageBox(rolename);
    return true;
}

bool NetClient::OnChooseCamp(PNS_CHOOSECAMP _coder)
{
    PNS_CHOOSECAMP _p = (PNS_CHOOSECAMP)_coder;
   
    return true;
}

bool NetClient::OnChat(PCHAT_DATA _coder)
{
    switch (_coder->ChartId)
    {
    case 3:// 私聊
        return OnChatPrivate((PCHAT_PRV)_coder);
    case 1:// 附近频道
    case 2:// 区域频道
    case 6:// 公会频道
    case 9:// 阵营频道
    case 21:// 喊话频道
        return OnChatPublic((PCHAT_PUB)_coder);
        break;
    }
}

bool NetClient::OnChatPublic(PCHAT_PUB _coder)
{
    return true;
}

bool NetClient::OnChatPrivate(PCHAT_PRV _coder)
{
    return true;
}

bool NetClient::OnSvrChat(PCHAT_PRV _coder)
{
    AfxMessageBox(_coder->name);
    AfxMessageBox(_coder->txt);
    //switch (_coder->ChartId)
    //{
    //case 3:// 私聊
    //    return OnChatPrivate((PCHAT_PRV)_coder);
    //case 1:// 附近频道
    //case 2:// 区域频道
    //case 6:// 公会频道
    //case 9:// 阵营频道
    //case 21:// 喊话频道
    //    return OnChatPublic((PCHAT_PUB)_coder);
    //    break;
    //}
    return true;
}

bool NetClient::Tips(int code)
{
#ifdef  Anly
    CString txt;
    if (code == 51001) {
        txt = L"登陆失败,易道云通行证不存在!";
    }else if (code == 51002) {
        txt = L"登录失败,密码错误!";
    }else if (code == 21101) {
        txt = L"人物重名!";
    }else if (code == 21109) {
        txt = L"名字过长或包含非法字符!";
    }
    else {
        txt.Format(L"未知登录错误:%d", code);
    }


    anly->SendData(TTYPE::I_LOG, 0, txt.GetBuffer(), (txt.GetLength() + 1)*2);
#endif
    return true;
}

void NetClient::loginok(ROLE_DATA* _roles, int count)
{
    logined = true;
    if(roles) delete[] roles;
    roles = _roles;
    rolecount = count;
}

bool NetClient::OnScrStartCreateRole(short code, wchar_t* _txt)
{
    return true;
}

bool NetClient::OnSvrNotice(PNET_SEND_CHEAD _coder, int count, char*& buffer, unsigned& len)
{
    if (_coder->msgHeader == "chat") {
        return OnSvrChat((PCHAT_PRV)_coder);
    }

    return true;
}

NetClient.h文件的修改:新加 OnSvrChat函数,修改了 OnSvrNotice函数

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

#define CAMP_NAME_QH "xuanrenQH"
#define CAMP_NAME_ZE "xuanrenZQ"
class NetClient // 监视客户端每一个操作
{
private:
	GameWinSock* WinSock;
	PROLEDATA roles;
	unsigned rolecount;
	bool logined = false;

	bool DelRole(const wchar_t* rolename, unsigned _len);
public:
	void Init(GameWinSock * _winSock);
	/*
		模拟登陆的方法
		Id是账号
		Pass是密码
		它要基于发送的方法实现,因为我们没有连接socket的操作
	*/
	bool login(const char* Id, const char* Pass);
	bool DelRole(const wchar_t* rolename);
	bool StartCreateRole();// 用于创建角色
	bool SelectCamp(const char* _campname);// 选择阵营
	bool CreateRole(wchar_t* name,double sex, double camp, double face, double occu, const char* photo, const char*infos, const char* txt, double faceShape);// 角色创建
	// 选择角色并且登录进游戏
	bool SelectRole(const wchar_t* rolename);
	// 根据角色名字获取一个登录成功数据包(选择角色列表里的一个数据)
	PROLEDATA GetRoleByName(const wchar_t* rolename);
	bool Talk(wchar_t* txt, int PdId = 1, double un = 0.0);
	bool TalkTo(wchar_t* name, wchar_t* txt, double un = 0.0);
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);
	// 针对Notice的单独处理
	bool virtual OnSvrChat(PCHAT_PRV _coder);

public:
	// 处理失败,参数是错误码
	bool virtual Tips(int code);
	void virtual loginok(ROLE_DATA* _roles, int count);
	bool virtual OnScrStartCreateRole(short code,wchar_t* _txt);
	bool virtual OnSvrNotice(PNET_SEND_CHEAD _coder, int count, char*& buffer, unsigned& len);
};


NetClass.h文件的修改:修改了 NET_SEND_CHEAD类

#pragma once
#include "EnCode.h"
/*
	最多的数据项, 0A开头的数据包里有数据参数个数,这个宏就是用来设定
	数据参数个数最大数量是多少,0A开头的数据包目前见过最大
	数据参数个数是十几个,这里写20应该完全够用,如果不够用
	到时候再增加
*/
#define MAX_SEND_COUNT 20
#define MAX_RECV_COUNT 30
// 发送数据操作码定义
#define SC_CHOOSECAMP 591 // 阵营选择操作码
#define SC_CHAT 1
typedef struct NR_NOTICE_HEAD {
	char un;
	char op;
	short count;
}*PNR_NOTICE_HEAD;

typedef struct NET_SEND_SELECTROLE {
	char op = 0x04;
	char buff[83]{};
}NS_SELECTROLE, * PNS_SELECTROLE;

typedef class NET_SEND_BASE {
public:
	// 实现编码操作,模拟游戏数据包给服务端发送数据,这个模拟的数据包通过调用 CodeMe函数得到
	unsigned CodeMe(int size, char* buffer);
}*PNET_SEND_BASE, * PNSR_BASE;

// 创建角色数据头部
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 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;


// 发送数据标准头
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;

// 发送数据包含内存空间,也就是数据参数,数据参数的意思看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() {};
}*PNET_SEND_CHEAD;

typedef class NET_SEND_CHOOSECAMP :public NET_SEND_CHEAD {
public:
	GCHAR camps;
}*PNS_CHOOSECAMP;

// 聊天数据包的数据结构
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 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;
typedef class ROLE_DATA {// 登录成功数据包
public:
	GBYTE byte;
	GINT index;
	GINT un1;
	GUTF16 name;
	GUTF16 infos;
	GINT un2;
	GINT64 un3;
}*PROLEDATA;

GameWinSock.cpp文件的修改:修改了 OnSverrNotice函数、Init函数

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

typedef bool(*DealProc)(char*&, unsigned&);

DealProc SendDealProc[0x100];
DealProc RecvDealProc[0x100];


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

bool DeafaultDeal(char*&, unsigned&) { return true; }

// 登录数据包的处理
bool Onlogin(char*& buff, unsigned& len) {
	PDATALOGIN _data = (PDATALOGIN)(buff + 1);
	char* _id = _data->Id;
	_data = (PDATALOGIN)(buff + 1 + _data->lenId - 0x10);
	char* _pass = _data->Pass;

	Client->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 OnTips(char*& buff, unsigned& len) {
	int* code = (int*)&buff[1];
	return Client->Tips(code[0]);
}

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

bool OnDelRole(char*& buff, unsigned& len) {

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



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

bool OnloginOk(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);
		WinSock->AnlyBuff(buffStart, buff + len, buff[0]);
		roleDatas = new ROLE_DATA[_p->RoleCount];
		for (int i = 0; i < _p->RoleCount; i++)
		{
			roleDatas[i].byte.Init(buffStart, 0);
			roleDatas[i].index.Init(buffStart, 0);
			roleDatas[i].un1.Init(buffStart, 0);
			roleDatas[i].name.Init(buffStart, 0);
			roleDatas[i].infos.Init(buffStart, 0);
			roleDatas[i].un2.Init(buffStart, 0);
			roleDatas[i].un3.Init(buffStart, 0);
		}
		Client->loginok(roleDatas, _p->RoleCount);
	}
	return true;
}

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

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

	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 OnSendCustom(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
	WinSock->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);

}

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

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

	vproc._proc = _OnConnect;

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

    return b;
}

bool GameWinSock::OnSend(char* buff, unsigned len)
{
	
	/*
		这里就可以监控游戏发送的数据了
	*/

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

}

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

void GameWinSock::Init()
{
	for (int i = 0; i < 0x100; i++) {
		SendDealProc[i] = &DeafaultDeal;
		RecvDealProc[i] = &DeafaultDeal;
	}
	// 注册登录数据包处理函数
	SendDealProc[I_LOGIN] = &Onlogin;
	SendDealProc[I_DELROLE] = &OnDelRole;
	SendDealProc[I_CREATEROLE_START] = &OnStartCreateRole;
	SendDealProc[I_SEND_CUSTOM] = &OnSendCustom;
	SendDealProc[I_CREATEROLE] = &OnCreateRole;
	SendDealProc[I_SELECT_ROLE] = &OnSelectRole;
	// 注册数据登录失败数据包处理函数
	RecvDealProc[S_TIPS] = &OnTips;
	RecvDealProc[S_LOGINOK] = &OnloginOk;
	RecvDealProc[S_CREATEROLE_START] = &OnSvrStartCreateRole;
	RecvDealProc[S_NOTICE] = &OnSverrNotice;
	RecvDealProc[S_NOTICE_COM] = &OnSverrNotice;
}

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

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



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

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

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

GameWinSock.h文件的修改:新加 S_NOTICE_COM宏

#pragma once

#define I_LOGIN 0x2 // 登录
#define I_CREATEROLE_START 0x3 // 创建角色发送数据包的头
#define I_SELECT_ROLE 0x04 // 选择角色进入游戏的数据包头
#define I_CREATEROLE 0x05 // 创建角色给服务端发送的数据包头
#define I_DELROLE 0x6 // 删除角色发送数据包的头
#define I_SEND_CUSTOM 0x0A // 进入游戏之后大部分数据包的头


#define S_TIPS 0x3 // 服务端返回错误的数据包头
#define S_LOGINOK 0x4 // 登录成功的返回数据包的头
// 创建角色服务端反馈的数据(发送03 01 00 00 00这个数据包服务端返回的数据包头)
#define S_CREATEROLE_START 0x05
#define S_NOTICE 0x1E // 里面有聊天数据
#define S_NOTICE_COM 0x27 // 里面有聊天数据

class GameWinSock
{
	typedef bool(GameWinSock::* PROC)(char*, unsigned);
public:
	unsigned* vatble;// 虚函数表
	unsigned un[17];
	unsigned RecvPoint; // 游戏recv之后调用处理一个数据包函数时的eax,这里偏移是0x48
public:
	static PROC _OnConnect;
	static PROC _OnSend;
	static PROC _OnRecv;
	bool OnConnect(char* ip, unsigned port);
	bool OnSend(char* buff, unsigned len);
	// bool OnRecving(char* buff, unsigned len);
	bool Recv(char* buff, unsigned len);

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

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


EnCode.cpp文件的修改:修改了 EnCode类的析构函数

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



void EnCode::Init(char*& buff, char EnIndex)
{
	index = EnIndex;
	op = buff[0];
	int len = data_desc[index][op].lenth;
	/*
		比如 07 0A 00 00 00 34 00 33 00 39 00 39 00 00 00
		buff + 1 的结果是 0A 00 00 00 34 00 33 00 39 00 39 00 00 00
		len是 07 所代表的类型的长度,也就是0A 00 00 00 这个东西
		memcpy(dataPool, buff + 1, len);也就是把 0A 00 00 00 复制到 dataPool里
		然后从下方的if ((op == 0x7))得到它的字符串
	*/
	memcpy(dataPool, buff + 1, len);
	buff = buff + 1 + len;// 下一个结构

	if (index == 0) {
		if ((op == 0x7)) {
			AllocPointer(lenth);
			// if (pointer)delete[]pointer;
			/*
				dataPool 与 lenth是一个联合体,联合体的特性就是
				所有变量共用一个内存,所以 dataPool 的值就是lenth的值
				从上方的注释得知现在dataPool的值是0A 00 00 00
				然后通过 lenth去读 0A 00 00 00结果就是十进制的 10
				然后创建一个10字节的空间给pointer
				然后在通过 memcpy(pointer, buff, lenth); 把字符串赋值给pointer
			*/
			// pointer = new char[lenth];
			memcpy(pointer, buff, lenth);
			buff = buff + lenth;// 指向下一个字符串
		}
	}

	if (index == 1) {
		if ((op == 0x06) || (op == 0x7)) {
			AllocPointer(lenth);
			memcpy(pointer, buff, lenth);
			buff = buff + lenth;
		}
	}
}
/*
	buff是数据包
	_len暂时没用
	ExIndex是解析方式,因为分析的时候发现6有时是char类型有时是char*类型
	看懂此方法需要分析手动分析一次数据包(数据解析约定的数据包)
	然后带着数据包去看这个函数
*/
EnCode::EnCode(char*& buff,char EnIndex)
{
	Init(buff,EnIndex);
}

EnCode::~EnCode()
{
	if (pointer) {
		delete[] pointer;
		pointer = nullptr;
	}
}

void EnCode::AllocPointer(int size)
{
	if (size > AllocCount) {
		if (pointer) delete[]pointer;
		pointer = new char[size];
		AllocCount = size;
	}
}

EnCode::EnCode()
{
}

EnCode& EnCode::operator=(char*& buff)
{
	Init(buff);
	return *this;
}

GBYTE::operator char()
{
	return this->byte;
}

char GBYTE::value()
{
	return this->byte;
}

GSHORT::operator short()
{
	return this->stval;
}

short GSHORT::value()
{
	return this->stval;
}

GINT::operator int()
{
	return this->val;
}

int GINT::value()
{
	return this->val;
}

/*
	GINT::Set函数得到的数据是
	02 03 00 00 00
	02是数据类型,表示int类型
	03 00 00 00 int类型的值
*/
void GINT::Set(int _invalue, char EnIndex)
{
	index = EnIndex;// 处理类型
	op = 2; // 数据解析约定的数据02表示int
	val = _invalue; // 数据
}

GFLOAT::operator float()
{
	return this->fval;
}

float GFLOAT::value()
{
	return this->fval;
}

void GFLOAT::Set(float invalue, char EnIndex)
{
	index = EnIndex;
	op = 0x04 ;
	fval = invalue;
}

GINT64::operator long long()
{
	return this->lval;
}

long long GINT64::value()
{
	return this->lval;
}

void GINT64::Set(long long invalue, char EnIndex)
{
	index = EnIndex;
	op = 0x03;
	lval = invalue;
}


GDOUBLE::operator double()
{
	return this->dbval;
}

double GDOUBLE::value()
{
	return this->dbval;
}

void GDOUBLE::Set(double invalue, char EnIndex)
{
	index = EnIndex;
	op = 0x05;
	dbval = invalue;
}


bool GCHAR::operator==(const char* _txt)
{
	return strcmp(_txt, pointer) == 0;
}

GCHAR::operator const char*()
{
	return this->pointer;
}

const char* GCHAR::value()
{
	return this->pointer;
}

/*
	GCHAR::Set函数得到的数据是
	06 03 00 00 00 77 65 72 00
	06是数据类型,表示char*类型
	03 00 00 00 表示字符串长度
	77 65 72 00 是字符串
*/
void GCHAR::Set(const char* _txt, char EnIndex)
{
	index = EnIndex;
	op = 6;
	lenth = strlen(_txt) + 1;
	AllocPointer(lenth);
	memcpy(pointer, _txt, lenth);
}

bool GCHAR::Safe()
{
	return op == 0x06;
}

GUTF16::operator const wchar_t*()
{
	return (wchar_t*)this->pointer;
}

const wchar_t* GUTF16::value()
{
	return (wchar_t*)this->pointer;
}

/*
	GUTF16::Set函数得到的数据是
	07 03 00 00 00 77 00 65 00 72 00 
	07是数据类型,表示wchar_t*类型
	03 00 00 00 表示字符串长度
	77 00 65 00 72 00 是字符串
*/
void GUTF16::Set(const wchar_t* _txt, char EnIndex)
{
	index = EnIndex;
	op = 7;
	lenth = wcslen(_txt) + 1;
	lenth = lenth * 2;
	AllocPointer(lenth);
	memcpy(pointer, _txt, lenth);
}


EnCode.h文件的修改:修改了 GCHAR类,

#pragma once

/*
	EnCode结构体 
		un[6]、index、op是8字节
		union里也是8字节
		pointer、back它俩虽然是char类型,但它俩是指针类型,所以它们一共是8字节
		EnCode结构体一共是24字节
*/
class EnCode// 用于表示数据解析约定的数据
{
private:

	char un[6]{};// 
public:
	char index = 0;
	// op + un[3]是四字节,op与buffer是6字节,如果自动内存对齐op与buffer可能不会挨在一起
	char op = 0;

	/*
		这个位置会导致编译器代码生成出现问题
		就是在当前文件一个类里写一个新的函数声明
		这个时候会因为下方的 union 导致编译器无法
		通过函数声明在cpp实现函数,这是因为编译器
		不知道下方的 union 是什么东西导致的
	*/
	union
	{
		char dataPool[0x8];
		int lenth;
		char byte;
		short stval;
		int val;
		float fval;
		double dbval;
		long long lval = 0;
	};
	char* pointer = 0;
	int AllocCount = 0;// 记录内存大小
protected:
	void AllocPointer(int size);
public:
	EnCode();
	EnCode(char*& buff, char ExIndex = 1);
	EnCode& operator=(char*& buff);
	void Init(char*& buff, char EnIndex = 1);
	~EnCode();
};

class GBYTE :public EnCode {
public:
	using EnCode::EnCode;
	using EnCode::operator=;
public:
	operator char();
	char value();
};
class GSHORT :public EnCode {
public:
	using EnCode::EnCode;
	using EnCode::operator=;
public:
	operator short();
	short value();
};
class GINT :public EnCode {
public:
	using EnCode::EnCode;
	using EnCode::operator=;
public:
	operator int();
	int value();
	void Set(int  _invalue, char EnIndex = 1);
};
class GFLOAT :public EnCode {
public:
	using EnCode::EnCode;
	using EnCode::operator=;
	operator float();
	float value();
	void Set(float invalue, char EnIndex = 1);
};
class GINT64 :public EnCode {
public:
	using EnCode::EnCode;
	using EnCode::operator=;
	operator long long();
	long long value();
	void Set(long long invalue, char EnIndex = 1);
};
class GDOUBLE :public EnCode {
public:
	using EnCode::EnCode;
	using EnCode::operator=;
	operator double();
	double value();
	void Set(double invalue, char EnIndex = 1);
};
class GCHAR :public EnCode {
public:
	using EnCode::EnCode;
	using EnCode::operator=;
	bool operator==(const char* _txt);
	operator const char*();
	const char* value();
	void Set(const char* _txt, char EnIndex = 1);
	bool Safe();
};
class GUTF16 :public EnCode {
public:
	using EnCode::EnCode;
	using EnCode::operator=;
	operator const wchar_t*();
	const wchar_t* value();
	void Set(const wchar_t* _txt, char EnIndex = 1);
};

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

从二本调剂到上海互联网公司算法工程师:我的成长故事

探讨选择成为一名程序员的原因&#xff0c;是出于兴趣还是职业发展&#xff1f; 在这个科技飞速发展的时代&#xff0c;程序员这一职业无疑成为了许多人眼中的香饽饽。那么&#xff0c;是什么驱使着越来越多的人选择投身于这一行业呢&#xff1f;是出于对编程的热爱&#xff0…

计算机网络基础:宏观认识

目录 一、网络发展背景与基本概念 二、网络协议的意义与TCP/IP五层结构模型 三、网络传输的基本流程与封装分用 四、ip地址和mac地址 随着信息技术的飞速发展&#xff0c;计算机网络已经成为了现代社会不可或缺的一部分。无论是工作、学习还是娱乐&#xff0c;我们几乎都离…

MySql中truncate,delete有什么区别?什么情况下id会不会连续呢?

TRUNCATE和DELETE都是用来删除表中数据的SQL命令&#xff0c;但它们的工作方式和使用场景有所不同&#xff1a; DELETE命令&#xff1a;DELETE命令用于从表中删除一行、多行或所有行。你可以添加WHERE子句来指定要删除的行。例如&#xff0c;DELETE FROM table_name WHERE cond…

Java项目实现Excel导出(Hutool)

官网&#xff1a; Excel生成-ExcelWriter (hutool.cn) 1.使用Hutool工具实现Excel导出&#xff08;.xlsx格式&#xff09; 业务场景&#xff1a; 使用SpringCloudmysqlmybatis-plus需要将数据库中的数据导出到Excel文件中 前端为Vue2 第零步&#xff1a;导入依赖 <!-…

[Win11·Copilot] Win11 系统更新重启后任务栏 Copilot 图标突然消失 | 解决方案

文章目录 前言Copilot介绍产生异常的原因解决方案总结 前言 在 Windows 11 的最新系统更新之后&#xff0c;一些用户报告了任务栏中 Copilot 图标消失的问题。这篇技术博文将为您提供详细的解决方案&#xff0c;帮助您恢复 Copilot 图标&#xff0c;并确保您能够继续享受 Copi…

计算机网络-IS-IS基础概念二

前面已经学习了IS-IS的定义、组成、NET地址标识以及路由器级别分类等&#xff0c;今天继续学习IS-IS基础概念知识。 参考链接&#xff1a;IS-IS路由协议基础概念 一、IS-IS支持的网络类型 IS-IS会自动根据接口的数据链路层封装决定该接口的缺省网络类型&#xff0c; IS-IS支持两…

list基础知识

list 1.list 的定义和结构 list 是双向链表&#xff0c;是C的容器模板&#xff0c;其接收两个参数&#xff0c;即 list(a,b) 其中 a 表示指定容器中存储的数据类型&#xff0c;b 表示用于分配器内存的分配器类型&#xff0c;默认为 list <int>; list 的特点&#xff1a;…

机器学习和深度学习--李宏毅 (笔记与个人理解)Day 16

Day 16 deep Learning – 鱼与熊掌兼得 最近在减重&#xff0c; 昨天跑了个一公里&#xff0c;然后今天上午又打了个篮球&#xff0c;真是老胳膊老腿了&#xff0c;运动完给我困得不行 Review 见前面的笔记 这里说dl 会提供一个足够大的模型&#xff0c; 来使得Dall loss 足够小…

Mac 利用Homebrew安装JDK

一、安装JDK17 1.安装openjdk17 2.把homebrew安装的openjdk17软链接到系统目录&#xff1a; brew install openjdk17 sudo ln -sfn $(brew --prefix)/opt/openjdk17/libexec/openjdk.jdk /Library/Java/JavaVirtualMachines/openjdk-17.jdk 一、检查是否安装成功 在Termina…

python使用uiautomator2操作真机(oppo a9x)

环境&#xff1a; python3.8.10&#xff0c;oppo a9x(6G,128g)&#xff0c;版本android 11。 之前写过文章&#xff1a; python使用uiautomator2操作真机&#xff08;荣耀10青春版&#xff09;_python uiautomator2 控制真机-CSDN博客 python使用uiautomator2操作真机&…

每日两题 / 189. 轮转数组 560. 和为 K 的子数组(LeetCode热题100)

189. 轮转数组 - 力扣&#xff08;LeetCode&#xff09; 向右轮转将使尾部k个元素顶到头部 将整个数组反转&#xff0c;再分别反转前k个元素和剩下的元素即可 class Solution { public:void rotate(vector<int>& nums, int k) {k % nums.size();reverse(nums.begi…

RHCE2

一.配置server主机要求如下&#xff1a; 1.server主机的主机名称为 ntp_server.example.com //修改主机名 hostnamectl set-hostname ntp_server.example.comreboot 修改主机名后一定要记住重启。 2.server主机的IP为&#xff1a; 172.25.254.100 //主机ip配置 [rootntpse…

Java实现生成中间带图标的二维码

Java实现生成中间带图标的二维码 生成Base64格式的二维码&#xff0c;返回html渲染 package your.package;import com.google.zxing.*; import com.google.zxing.client.j2se.MatrixToImageWriter; import com.google.zxing.common.BitMatrix; import com.google.zxing.qrcod…

【Go语言快速上手(二)】 分支与循环函数讲解

&#x1f493;博主CSDN主页:杭电码农-NEO&#x1f493;   ⏩专栏分类:Go语言专栏⏪   &#x1f69a;代码仓库:NEO的学习日记&#x1f69a;   &#x1f339;关注我&#x1faf5;带你学习更多Go语言知识   &#x1f51d;&#x1f51d; Go快速上手 1. 前言2. 分支与循环2.1…

传统零售行业如何做数字化转型?

传统零售行业的数字化转型是一个系统性的过程&#xff0c;涉及到企业的多个方面。以下是一些关键步骤和策略&#xff0c;帮助传统零售企业实现数字化转型&#xff1a; 1、明确转型目标和战略 首先&#xff0c;企业需要明确数字化转型的目标和战略。包括确定企业的核心竞争力、…

生成对抗网络GAN的扩展应用理解

注&#xff1a;本文仅个人学习理解&#xff0c;如需详细内容请看原论文&#xff01; 一、cycleGAN 1.整体架构&#xff1a; 将图片A通过生成器生成图片B&#xff0c;然后通过判别器判别图片B的真假&#xff1b;并且图片B通过另一个生成器生成A‘&#xff0c;让A和A’近似。 2…

Linux 操作系统指令和Vscdoe安装

1、Linux系统介绍 Linux系统的背景介绍我就不介绍了&#xff0c;有兴趣的可以去看看其发展史。 1.1 Linux操作系统的主要特点 Linux操作系统的重要思想&#xff1a;一切皆文件 Linux操作系统的特性&#xff1a; 完全免费 支持多平台 支持多用户、多任务 有良好的界面 完美兼容…

EVI增强型植被指数

​随着遥感技术的发展&#xff0c;我们对地球上的植被状况有了更深入的了解&#xff0c;而其中一种重要的工具就是EVI&#xff08;Enhanced Vegetation Index&#xff0c;增强型植被指数&#xff09;。EVI不仅是一种植被指数&#xff0c;更是一种对植被生态系统健康和生产力评估…

Biome 1.7 发布,支持从 ESLint 和 Prettier 迁移

近日&#xff0c;Biome v1.7 正式发布&#xff01;这个新版本提供了从 ESLint 和 Prettier 迁移的简单路径。它还引入了格式化程序和 linter 的实验性机器可读报告、新的 linter 规则和许多修复。 使用以下命令更新 Biome&#xff1a; npm install --save-dev --save-exact b…

中科国声携新品亮相北京InfoComm China 2024展

4月17日&#xff0c;北京InfoComm China 2024展&#xff08;北京专业视听技术和集成体验解决方案展览会&#xff09;在北京的国家会议中心盛大开幕。展会为期三天。作为备受瞩目的”会议系统国家队“&#xff0c;中科国声携众多优质会议音频产品及全新会议系统解决方案精彩亮相…