68.网络游戏逆向分析与漏洞攻防-利用数据包构建角色信息-自动生成CPP函数解决数据更新的问题

news2025/1/23 17:35:37

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

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

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

上一个内容:67.游戏对象基类的头文件自动生成

码云版本号:1fe77647b82cfc854e67b19095a90759c863ebdd

代码下载地址,在 titan 目录下,文件名为:titan-自动生成CPP函数解决数据更新的问题.zip

链接:https://pan.baidu.com/s/1W-JpUcGOWbSJmMdmtMzYZg

提取码:q9n5

--来自百度网盘超级会员V4的分享

HOOK引擎,文件名为:黑兔sdk升级版.zip

链接:https://pan.baidu.com/s/1IB-Zs6hi3yU8LC2f-8hIEw

提取码:78h8

--来自百度网盘超级会员V4的分享

以 67.游戏对象基类的头文件自动生成 它的代码为基础进行修改

上一个内容里把游戏的基本类(根据09数据包生成的类)已经创建好了,是一个特别特别大的一个类,好在不用写一行代码,虽然有点浪废内存,但目前来说只能浪废,没办法不浪费,现在并不知道那些东西是属于角色,通过逆向的方式也很难知道,只能后期多看几个数据包,最简单的方式就是通过解析09数据包生成一个类,就是有点浪废内存,浪废了也没事,现在计算机很牛,浪废点内存一点事没有,然后本次做的事是要对生成的这个类进行填充,填充的方式就是本次做的事情

添加一个类

效果图:根据09数据包已经把相关的结构、函数、宏全部生成好,如下方的几张图,下一次就开始调试

删除了GameOBJECT.h文件

新加 AIM.h文件

#pragma once
typedef class AIM  {
public:
	long long lId;
	float x;
	float h;
	float y;
	float face;

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

	int endclass;
};

GameAnly.cpp文件的修改:修改了 CreateObjectfiles函数(添加生成上放图里的文件与文件内容)

#include "pch.h"
#include "GameAnly.h"
#include <iostream>
#include <fstream>

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

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



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

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

    int value;
    long long llvalue;
    float fvalue;
    double dbval;
    CStringA szTmp, _tmp, szTxt;
    CString wTmp;

    while ((iProc < count) && (start <end)) {
        short* index = (short*)start;
        int type = desc[index[0]].type;
        char* name = desc[index[0]].name;
   

    switch (type)
       {
       case 0:
           AfxMessageBox(L"0号信息!"); break;
       case 1:
           value = ToChar(start);
           szTmp.Format("%s = %d", name, value);
           break;
       case 2:
           value = ToShort(start);
           szTmp.Format("%s = %d", name, value);
           break;
       case 3:
           value = ToInt(start);
           szTmp.Format("%s = %d", name, value);
           break;
       case 4:
           llvalue = ToLLong(start);
           szTmp.Format("%s = %lld", name, llvalue);
           break;
       case 5:
           fvalue = ToFloat(start);
           szTmp.Format("%s = %f", name, fvalue);
           break;
       case 6:
           dbval = ToDouble(start);
           szTmp.Format("%s = %lf", name, dbval);
           break;
       case 7:
           _tmp = ToAscii(start);
           szTmp.Format("%s = %s", name, _tmp.GetBuffer());
           break;
       case 8:
           wTmp = ToUniode(start);
           _tmp = wTmp;
           szTmp.Format("%s = %s", name, _tmp.GetBuffer());
           break;
       case 9:AfxMessageBox(L"9号数据出现"); return;
       default:
           break;
       }
       szTxt = szTxt + szTmp + "\r\n";
       iProc++;
   }
   anly->SendData(TTYPE::I_DIS, MsgId, szTxt.GetBuffer(), szTxt.GetAllocLength() + 1);

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

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

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

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

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

    std::ofstream ofs(_GameOBJECThpp); // 根据09数据包生成类的头文件
    std::ofstream ofCpp(_GameOBJECTcpp); // 根据09数据包生成类的cpp文件
    std::ofstream ofDef(_GameOBJECTdef);// 生成宏

    if (ofs.bad() || ofCpp.bad() || ofDef.bad()) {
        ofs.close();
        ofCpp.close();
        ofDef.close();
        return;
    }
    else
    {
        // 定义CPP文件头部
        ofCpp << "#include \"GameOBJECT.h\"" << std::endl;
        ofCpp << "#include \"GameOBJECTDef.h\"" << std::endl;

        ofCpp << "void GAMEOBJECT::UpdateData(int id, int type, char*& buffStart)" << std::endl;
        ofCpp << "{                                                              " << std::endl;
        ofCpp << "    /*                                                         " << std::endl;
        ofCpp << "        1B 00 buffStart                                        " << std::endl;
        ofCpp << "        0C 00 00 00 buffStart + 2                              " << std::endl;
        ofCpp << "        CA 4E 5A 66 53 62 01 80 4E 86 00 00 1D 00              " << std::endl;
        ofCpp << "    */                                                         " << std::endl;
        ofCpp << "    switch (id)" << std::endl;
        ofCpp << "    {" << std::endl;


        // 定义文件头部区域
        ofDef << "#pragma once" << std::endl;


        // 头部生成区域
        ofs << "#pragma once" << std::endl;
        ofs << "#define GCHAR CStringA" << std::endl;
        ofs << "#define GWCHAR CString" << std::endl;
        ofs << "#define GOBJECT int" << std::endl;
        ofs << "typedef class GAMEOBJECT{" << std::endl;
        ofs << "public:" << std::endl;

        // 变量声明
        //  i = 1的原因是游戏的数据类型表里(接收的09数据包)第一个是NONE
        for (int i = 1; i < icount; i++)
        {
            char* valueName = desc[i].name;
            int valueType = desc[i].type;
            char* valueTypeName = data_desc[2][valueType].name;
            int valueSize = data_desc[2][valueType].lenth;

            ofs << "    " << valueTypeName << " " << valueName << ";" << std::endl;
            ofDef << "#define INDEX_" << valueName << " " << i << std::endl;

            ofCpp << "    case INDEX_" << valueName << ":" << std::endl;
            ofCpp << "      return Set" << valueName << "(buffStart);" << std::endl;

        }

        ofCpp << "    }" << std::endl;
        ofCpp << "    buffStart = buffStart + 2;" << std::endl;
        ofCpp << "}" << std::endl;
   
        // 函数声明
        ofs << "    void virtual UpdateData(int id, int type, char*& buffStart);" << std::endl;
        ofs << "protected:" << std::endl;

        for (int i = 1; i < icount; i++)
        {
            char* valueName = desc[i].name;
            int valueType = desc[i].type;
            char* valueTypeName = data_desc[2][valueType].name;
            int valueSize = data_desc[2][valueType].lenth;

            ofs << "    void virtual Set" << valueName << "(char*& buffStart);" << std::endl;

            ofCpp << "void GAMEOBJECT::Set" << valueName << "(char*& buffStart)" << std::endl;
            ofCpp << "{" << std::endl;

            if(valueType == 7){
                 ofCpp << "   buffStart += 4;" << std::endl;
                 ofCpp << "   " << valueName << " = (char*)buffStart; " << std::endl;
            }
            else if(valueType == 8) {
                ofCpp << "   buffStart += 4;" << std::endl;
                ofCpp << "   " << valueName << " = (wchar_t*)buffStart; " << std::endl;
            }
            else {
                ofCpp << "    " << valueTypeName << "* value = (" << valueTypeName << "*)buffStart;" << std::endl;
                ofCpp << "    buffStart += sizeof(" << valueTypeName << ");" << std::endl;
                ofCpp << "    " << valueName << " = value[0];" << std::endl;
            }
                  
            ofCpp << "}" << std::endl;
   
        }

        ofs << "}*PGAMEOBJ;" << std::endl;

    }

    ofs.close();
    ofCpp.close();
    ofDef.close();
}

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

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

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

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

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

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

char* GameAnly::ToAscii(char*& start)
{
    int* lenth = (int*)(start + 2);
    char* result = start + 2 + 4; // +4这个操作是跳过 lenth的值
    start = start + 2 + 4 + lenth[0];
    return result;
}

wchar_t* GameAnly::ToUniode(char*& start)
{
    int* lenth = (int*)(start + 2);
    wchar_t* result = (wchar_t*)(start + 2 + 4); // +4这个操作是跳过 lenth的值
    start = start + 2 + 4 + lenth[0];
    return result;
}
#endif // Anly

NetClient.cpp文件的修改:修改了 OnSvrObjectInit函数(添加了注释与进一步封装函数)

#include "pch.h"
#include "NetClient.h"
#include "extern_all.h"

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

bool NetClient::DelRole(const wchar_t* rolename)
{
    PROLEDATA _role = GetRoleByName(rolename);
    if (_role == nullptr) {
        return false;
    }
    else {
        return DelRole(rolename, _role->name.lenth);
    }
    return false;
}

bool NetClient::StartCreateRole()
{
    NET_CREATEROLE_START _data;
    return WinSock->OnSend(&_data.op, _data.len);
}

bool NetClient::SelectCamp(const char* _campname)
{
    NET_SEND_BUFF _buff;
    NET_SEND_CHOOSECAMP _data;
    _data.opcode.Set(SC_CHOOSECAMP);
    _data.camps.Set(_campname);
    /* 
        sizeof(_data) / sizeof(EnCode)的原因
        NET_SEND_CHOOSECAMP结构体里面,没别 东西
        全是 EnCode 这个结构
    */
    short count = sizeof(_data) / sizeof(EnCode);
    _buff.count = count;

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

bool NetClient::CreateRole(wchar_t* name, double sex, double camp, double face, double occu, const char* photo, const char* infos, const char* txt, double faceShape)
{
    // rolecount > 4说明角色的数量够了
    if (rolecount > 4)return false;
    int index = 0;
    bool roleindex[5]{true, true, true, true, true };
    for (int i = 0; i < rolecount; i++) {
        roleindex[roles[i].index] = false;
    }
   
    for (int i = 0; i < 5; i++)
    {
        if (roleindex[i]) {
            index = i;
            break;
        }
    }

    // wchar_t _name[] = L"am52111";
    NS_CREATEROLE_HEAD_BUFF _buff;
    CREATE_ROLE_DATAS _data;
    _data.sex.Set(sex);
    _data.camp.Set(camp);
    _data.face.Set(face);
    _data.occu.Set(occu);
    _data.faceSahpe.Set(faceShape);
    //_data.Photo.Set("gui\BG_team\TeamRole\Teamrole_zq_humF_001.PNG");
    _data.Photo.Set(photo);
    //_data.Infos.Set("Face,0;Hat,0;Eyes,0;Beard,0;Ears,0;Tail,0;Finger,0;Cloth,0;Pants,0;Gloves,0;Shoes,0;Trait,0;HairColor,0;SkinColor,0;SkinMtl,0;Tattoo,0;TattooColor,16777215;");
    _data.Infos.Set(infos);
    _data.Txt.Set(txt);
    short count = sizeof(_data) / sizeof(EnCode);
    _buff.count = count;
    _buff.index = index;
    int lenth = wcslen(name) + 1;
    lenth = lenth * 2;
    memcpy(_buff.name, name, lenth);
    int ilen = _data.CodeMe(count, _buff.buff);
    ilen = ilen + sizeof(NET_SEHD_CREATEROLE_HEAD) - 3;
    return WinSock->OnSend(&_buff.op, ilen);
}

bool NetClient::SelectRole(const wchar_t* rolename)
{
    PROLEDATA roles = GetRoleByName(rolename);
    if (roles == nullptr)return false;
    NS_SELECTROLE _data;
    memcpy(_data.buff, roles->name.value(), roles->name.lenth);
    
    return WinSock->OnSend((char*)&_data, sizeof(_data));
}

PROLEDATA NetClient::GetRoleByName(const wchar_t* rolename)
{
    //PROLEDATA result = nullptr;
    for (int i = 0; i < rolecount; i++)
    {
        // StrCmpW判断两个字符串是否相同
        // 比较时区分大小写,如果字符串相同返回0
        if (StrCmpW(roles[i].name.value(), rolename) == 0) {
            return &roles[i];
        }

    }
    return nullptr;
}

bool NetClient::Talk(wchar_t* txt, int PdId, double un)
{
    NET_SEND_BUFF _buff;
    CHAT_PUBLIC _data;
    _data.opcode.Set(SC_CHAT);
    _data.ChartId.Set(PdId);
    _data.txt.Set(txt);
    _data.un.Set(un);
    /*
        sizeof(_data) / sizeof(EnCode)的原因
        NET_SEND_CHOOSECAMP结构体里面,没别 东西
        全是 EnCode 这个结构
    */
    short count = sizeof(_data) / sizeof(EnCode);
    _buff.count = count;

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

bool NetClient::TalkTo(wchar_t* name, wchar_t* txt, double un)
{
    NET_SEND_BUFF _buff;
    CHAT_PRIVATE _data;
    _data.opcode.Set(SC_CHAT);
    _data.ChartId.Set(3);
    _data.txt.Set(txt);
    _data.name.Set(name);
    _data.un.Set(un);
    /*
        sizeof(_data) / sizeof(EnCode)的原因
        NET_SEND_CHOOSECAMP结构体里面,没别 东西
        全是 EnCode 这个结构
    */
    short count = sizeof(_data) / sizeof(EnCode);
    _buff.count = count;

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

bool NetClient::HeartBeep()
{
    NET_SEND_BUFF _buff;
    HEART_BEEP _data;
    _data.opcode.Set(SC_BEEP);
    _data.tick.Set(3);
    /*
        sizeof(_data) / sizeof(EnCode)的原因
        NET_SEND_CHOOSECAMP结构体里面,没别 东西
        全是 EnCode 这个结构
    */
    short count = sizeof(_data) / sizeof(EnCode);
    _buff.count = count;

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

bool NetClient::HeartLoop()
{
    NET_SEND_BUFF _buff;
    HEART_LOOP _data;
    _data.opcode.Set(SC_LOOP);
    _data.tick.Set(GetTickCount64());
    _data.txt.Set("");
    /*
        sizeof(_data) / sizeof(EnCode)的原因
        NET_SEND_CHOOSECAMP结构体里面,没别 东西
        全是 EnCode 这个结构
    */
    short count = sizeof(_data) / sizeof(EnCode);
    _buff.count = count;

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

bool NetClient::Backtoroles()
{
    // 返回角色
    NET_SEND_BUFF _buff;
    NSR_CHEAD _data;
    _data.opcode.Set(SC_REONLINE);

    /*
        sizeof(_data) / sizeof(EnCode)的原因
        NET_SEND_CHOOSECAMP结构体里面,没别 东西
        全是 EnCode 这个结构
    */
    short count = sizeof(_data) / sizeof(EnCode);
    _buff.count = count;

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

bool NetClient::DelRole(const wchar_t* rolename, unsigned _len)
{
    DATA_DELROLE _data;
    _data.op = 0x06;
    _data.len = _len;
    memcpy(_data.buff, rolename, _len);
    return WinSock->OnSend((char*)&_data, sizeof(DATA_DELROLE) - 1);
}

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

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

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

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

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

bool NetClient::OnSendCustom(PNET_SEND_CHEAD _coder, char*& buffer, unsigned& len)
{
    switch (_coder->opcode.value())
    {
    case SC_CHOOSECAMP:
        return OnChooseCamp((PNS_CHOOSECAMP)_coder);
    case SC_CHAT:
        return OnChat((PCHAT_DATA)_coder);
    case SC_BEEP:
        return OnHeartBeep((PHEART_BEEP)_coder);
    case SC_LOOP:
        return OnHeartLoop((PHEART_LOOP)_coder);
    case SC_INITED:
    // case SC_REONLINE:
    case SC_INIT_START:
    case SC_HAND:
    case SC_HAND_IN:
        return false;
    default:
        break;
    }
    return true;
}

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

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

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

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

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

bool NetClient::OnHeartBeep(PHEART_BEEP _coder)
{
    return true; // 返回false会拦截81心跳包不给服务端发送
}

bool NetClient::OnHeartLoop(PHEART_LOOP _coder)
{
    return true; // 返回false会拦截SC_LOOP心跳包不给服务端发送
}

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

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


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

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

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

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

    return true;
}

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

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

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


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

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

    Onlogin(_id, _pass);

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

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

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

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

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

bool NetClient::OnClientCreateRole(char*& buff, unsigned& len) {
    PNS_CREATEROLE head = (PNS_CREATEROLE)(buff - 3);
    int icount = head->count;
    if (icount < 1)return true;
    char* buffStart = (char*)head + sizeof(NET_SEHD_CREATEROLE_HEAD);
#ifdef Anly
    GameAnlyer->AnlyBuff(buffStart, buff + len, buff[0], 1);// 翻译解析约定数据
#endif // Anly

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


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

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

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

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

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

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

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

}

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

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

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



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

// 接收数据截取区

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

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

    PDATALOGINOK _p = (PDATALOGINOK)&buff[1];
    ROLE_DATA* roleDatas = nullptr;
    if (_p->RoleCount > 0) {
        char* buffStart = buff + 1 + sizeof(DATA_LOGIN_OK);

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

        roleDatas = new ROLE_DATA[_p->RoleCount];
        for (int i = 0; i < _p->RoleCount; i++)
        {
            roleDatas[i].byte.Init(buffStart, 0);
            roleDatas[i].index.Init(buffStart, 0);
            roleDatas[i].un1.Init(buffStart, 0);
            roleDatas[i].name.Init(buffStart, 0);
            roleDatas[i].infos.Init(buffStart, 0);
            roleDatas[i].un2.Init(buffStart, 0);
            roleDatas[i].un3.Init(buffStart, 0);
        }
        Client->loginok(roleDatas, _p->RoleCount);
    }
    return true;
}

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

    if (ObjectTable) {
        delete[] ObjectTable;
    }

    if (ObjectTxt) {
        delete[] ObjectTxt;
    }

    ObjectTable = new OBJECT_DESC[head->count];
    ObjectTxt = new char[len];
    memcpy(ObjectTxt, buff, len);// 这里怕 buff 的内容被游戏释放掉,后面我们用的时候没法用,所以把buff的内容复制到我们的变量里
    char* buffStart = ObjectTxt + sizeof(NR_NOTICE_HEAD)-1;
//#ifdef Anly
//    CStringA szTxtA;
//    CStringA szTmp;
//#endif // Anly
//#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);

#ifdef  Anly
    GameAnlyer->AnlyData(buffStart, buff + len, head->icount, S_OBJECT_INIT, ObjectTable);
#endif
    return true;
    int iProc = 0;
    while (iProc < head->icount)
    {
        short* index = (short*)buffStart;
        int type = ObjectTable[index[0]].type;
        // Player.UpdateData(index[0], type, buffStart);
        iProc++;
    }

    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文件的修改:新加 Player变量、AIM.h头文件

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

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

	bool DelRole(const wchar_t* rolename, unsigned _len);
public:
	void virtual Init(GameWinSock * _winSock);
	/*
		模拟登陆的方法
		Id是账号
		Pass是密码
		它要基于发送的方法实现,因为我们没有连接socket的操作
	*/
	bool login(const char* Id, const char* Pass);
	bool DelRole(const wchar_t* rolename);
	bool StartCreateRole();// 用于创建角色
	bool SelectCamp(const char* _campname);// 选择阵营
	bool CreateRole(wchar_t* name,double sex, double camp, double face, double occu, const char* photo, const char*infos, const char* txt, double faceShape);// 角色创建
	// 选择角色并且登录进游戏
	bool SelectRole(const wchar_t* rolename);
	// 根据角色名字获取一个登录成功数据包(选择角色列表里的一个数据)
	PROLEDATA GetRoleByName(const wchar_t* rolename);
	bool Talk(wchar_t* txt, int PdId = 1, double un = 0.0);
	bool TalkTo(wchar_t* name, wchar_t* txt, double un = 0.0);
	bool HeartBeep();// 心跳数据包(5秒)
	bool HeartLoop();// 延迟心跳数据包(20秒)
	bool Backtoroles(); // 返回到选择角色界面
public:
	// 用于拦截游戏删除角色功能
	bool virtual OnDelRole(wchar_t* rolename, unsigned _len);
	// 用于拦截游戏登录功能
	void virtual Onlogin(const char* Id, const char*Pass);
	// 用于拦截游戏创建角色功能
	bool virtual OnStartCreateRole(int code);
	// 拦截创建角色
	bool virtual OnCreateRole(PNS_CREATEROLE _header, PCREATE_ROLE_DATAS _body);
	// opcode意思是操作码,count意思是数量,buffStart意思是解码的内容开始,buffend意思是解码的内容结束,buffer是原始的数据,len是原始数据的长度
	// char& buffer, int& len这俩参数带&的原因是,在 OnSendCustom 里进行修改之后,通过&的方式传递回去
	bool virtual OnSendCustom(PNET_SEND_CHEAD _coder, char*& buffer, unsigned& len);
	bool virtual OnSelectRole(wchar_t* rolename);
public:
	bool virtual OnChooseCamp(PNS_CHOOSECAMP _coder);
	bool virtual OnChat(PCHAT_DATA _coder);
	bool virtual OnChatPublic(PCHAT_PUB _coder);
	bool virtual OnChatPrivate(PCHAT_PRV _coder);
	bool virtual OnHeartBeep(PHEART_BEEP _coder);
	bool virtual OnHeartLoop(PHEART_LOOP _coder);
	// 针对Notice的单独处理
	bool virtual OnSvrChat(PCHAT_PRV _coder);

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

};



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

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

相关文章

Seata-server配置

首先先兼容一下版本看看所用的版本是否都兼容 版本兼容查看 建立seata-server数据库 数据库DDL 给每个业务库建立undo.log表 undo.log 然后在虚拟机安装seata-server 创建文件路径&#xff0c;并创建docker-compose.yml文件 创建完成后先启动一下seata docker run -rm seata…

linux远程访问及控制

一、SSH远程管理 1.SSH的简介 SSH远程管理是一种通过 SSH 协议安全地管理远程计算机的方法。允许管理员通过加密的连接从本地计算机或其他远程位置连接到远程计算机&#xff0c;并执行管理任务、配置设置、故障排除等操作。 远程链接的两种方法&#xff1a;SSH 、Telnet S…

07_for循环返回值while循环

文章目录 1.循环返回值2.yield接收for返回值3.scala调用yield方法创建线程对象4.scala中的while循环5.scala中的流程控制 1.循环返回值 for循环返回值是Unit 原因是防止产生歧义&#xff1b; 2.yield接收for返回值 // 2.yield关键字打破循环&#xff0c;可以使for循环输出…

力扣HOT100 - 207. 课程表

解题思路&#xff1a; class Solution {public boolean canFinish(int numCourses, int[][] prerequisites) {int[] inDegree new int[numCourses];//存每个结点的入度List<List<Integer>> res new ArrayList<>();//存结点之间依赖关系Queue<Integer>…

Spring Security(学习笔记)--漏洞保护(csrf攻击与防御以及源码分析)!

重点标识 csrf 攻击防御演示&#xff01; 源码分析&#xff01; CSRF攻击与防御 CSRF是什么 &#xff0c;跨站请求伪造&#xff0c;简单解释一下&#xff0c;就是用户登录某个界面&#xff0c;如银行界面&#xff0c;进行转账&#xff0c;完了之后并没有注销登录&#xff0…

Scrapy 爬虫教程:从原理到实战

Scrapy 爬虫教程&#xff1a;从原理到实战 一、Scrapy框架简介 Scrapy是一个由Python开发的高效网络爬虫框架&#xff0c;用于从网站上抓取数据并提取结构化信息。它采用异步IO处理请求&#xff0c;能够同时发送多个请求&#xff0c;极大地提高了爬虫效率。 二、Scrapy运行原…

蒸镀的氧化硅薄膜为什么有时候是绿色有时候是棕色的?

知识星球&#xff08;星球名&#xff1a;芯片制造与封测社区&#xff0c;星球号&#xff1a;63559049&#xff09;里的学员问&#xff1a;我们用热阻式蒸镀设备镀氧化硅薄膜&#xff0c;出来的颜色有时候会发生变化是什么原因呀&#xff1f;有时候薄膜是绿色有时候是棕色。 氧…

Excel 中用于在一个范围中查找特定的值,并返回同一行中指定列的值 顺序不一样 可以处理吗

一、需求 Excel 中&#xff0c;在一列&#xff08;某范围内&#xff09;查找另一列特定的值&#xff0c;并返回同一行中另一指定列的值&#xff0c; 查找列和返回列的顺序不一样 二、 实现 1、下面是一个使用 INDEX 和 MATCH 函数的例子&#xff1a; 假设你有以下数据&…

网络服务SSH-远程访问及控制

一.SSH远程管理 1.SSH介绍 SSH&#xff08;Secure Shell&#xff09;是一种安全通道协议&#xff0c;最早是由芬兰的一家公司开发出来&#xff0c;并且在IETF &#xff08;Internet Engineering Task Force&#xff09;的网络草案基础上制定而成的标准协议。主要用来实现字符…

深度学习pytorch实战-运动鞋识别P5周

向大佬学习大地之灯第P5周&#xff1a;Pytorch实现运动鞋识别http://t.csdnimg.cn/eVVAG >- **&#x1f368; 本文为[&#x1f517;365天深度学习训练营](https://mp.weixin.qq.com/s/0dvHCaOoFnW8SCp3JpzKxg) 中的学习记录博客** >- **&#x1f356; 原作者&#xff1a…

MySQL8.0新特性

1、新增降序索引 MySQL 5.7&#xff1a;在语法上支持降序索引&#xff0c;但实际上创建的仍然是升序索引 MySQL 8.0&#xff1a;真正支持降序索引&#xff08;只有Innodb存储引擎支持降序索引&#xff09; # MySQL 5.7演示 mysql> create table t1(c1 int,c2 int,index i…

pytest教程-34-钩子函数-pytest_configure

领取资料&#xff0c;咨询答疑&#xff0c;请➕wei: June__Go 上一小节我们学习了pytest_addoption钩子函数的使用方法&#xff0c;本小节我们讲解一下pytest_configure钩子函数的使用方法。 pytest_configure(config) 是一个 pytest 钩子函数&#xff0c;它在 pytest 配置完…

资产管理软件价格 一套固定资产管理系统多少钱

固定资产管理系统作为一款帮助工厂、事业单位、政府机关等企业管理资产设备的管理工具&#xff0c;其实&#xff0c;一直在向企业提供着各种相关的服务&#xff0c;对于才接触固定资产管理系统的朋友们来说&#xff0c;首先想要了解的这个付费工具的收费问题&#xff0c;那这个…

关于谷歌浏览器对于https的证书不通过校验的无法跳转的问题

谷歌浏览器对于https的证书问题会出现如下提示: 解决方法: 直接在页面输入 thisisunsafe 就能跳转了.

精彩回顾|从 AI 到银幕:顶尖对话揭秘 AI 如何塑造影视新格局

4月17日&#xff0c;由万合天宜、三次元影业、NOVATECH、微软中国极客天团、微软 Reactor 共同推出的「从 AI 到银幕」顶尖对话在上海微软紫竹园区举办。中国内地著名导演、编剧、监制黄建新&#xff0c;微软&#xff08;中国&#xff09;有限公司首席技术官韦青&#xff0c;与…

孕线反转形态,Anzo Capital10年交易经验一眼看穿

很多投资者都知道Inside-bar孕线是一种很好的反转信号&#xff0c;但是不了解Inside-bar孕线如何一眼发现孕线反转形态&#xff0c;今天Anzo Capital昂首资本平台上的10年交易者进行免费分享&#xff0c;希望各位投资者能够一眼看穿孕线反转形态: 首先各位投资者发散一下思维&…

美国站群服务器上常见的操作系统选择指南

美国站群服务器上常见的操作系统选择指南 美国站群服务器的选择操作系统对于服务器的性能和功能至关重要。本文将为您介绍在美国站群服务器上常见的操作系统选择指南&#xff0c;以帮助您做出明智的决策。 在选择美国站群服务器时&#xff0c;选择合适的操作系统是至关重要的…

Qt QLineEdit详解

1.简介 QLineEdit是一个单行文本编辑器。 行编辑允许用户使用一组有用的编辑功能输入和编辑单行纯文本&#xff0c;包括撤消和重做、剪切和粘贴以及拖放。 通过更改行编辑的echoMode&#xff0c;它也可以用作“只写”字段&#xff0c;用于密码等输入。 文本的长度可以限制为ma…

【每日刷题】Day28

【每日刷题】Day28 &#x1f955;个人主页&#xff1a;开敲&#x1f349; &#x1f525;所属专栏&#xff1a;每日刷题&#x1f34d; &#x1f33c;文章目录&#x1f33c; ​ 1. 121. 买卖股票的最佳时机 - 力扣&#xff08;LeetCode&#xff09; 2. 205. 同构字符串 - 力扣…

充电桩---ISO15118协议详细介绍

一、ISO15118介绍 1、发展背景 标准于 2010 年由国际标准化组织&#xff08;ISO&#xff09;和国际电工委员会&#xff08;IEC&#xff09;通过&#xff0c;电动汽车和充电站之间的通信&#xff0c;改善了不同品牌、型号和充电类型&#xff08;交流或直流&#xff09;之间的互…