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

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

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

上一个内容:53.逆向分析寻找消息数据解压缩过程

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

码云版本号:6685f3162cbb162a0ecb4ab32c6de9a2c86684e4

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

以 53.逆向分析寻找消息数据解压缩过程 它的代码为基础进行修改

最终选择在下图红框位置做HOOK,下图红框位置,EDI的值是解压之后的数据包,EBP的值是数据包大小,详情看下图中的内容

下图是hook之后的代码,0x10611611

这个位置原本的代码

如果要屏蔽数据包就让它跳转到,0x10611602位置

拦截解压之后的数据包

可以很明显的看到,数据接收与处理在不同的位置,下图是数据接收

数据解压,过了两秒才处理了0x28数据包

然后要做的事情是,通过数据包整体的数据,建立起一个庞大的信息结构,就是通过数据包把游戏现在做的事情给描述出来,这时数据的顺序就特别重要,如下图0x28比0x14开头的数据优先接收到,但游戏在处理数据包的时候0x14开头数据包比0x28数据包处理的早,这样就影响了用数据来包描述游戏的结果,所以现在hook的0x10611611位置不行,还要换一个位置

接下来改为0x1061161D位置做HOOk,在0x1061161D位置做HOOK的原因是0x1061161D后面的代码是数据包对应的实现函数,也就是游戏真实处理数据包的位置,按照游戏处理数据包的顺序来描述游戏就没问题了

经过与0x10618480位置的接收作比较,发现0x1061161D位置接收的数据顺序与0x10618480位置一直,这也说明上方推断是正确的,初步判断把0x10618480位置改为0x1061161D接收数据是没问题的,如果后续在游戏中做了一个操作,但是收不到数据包时,再回头把0x10618480改回去,看看0x10618480位置有没有接收到,如果0x10618480位置接收到了0x1061161D位置没有接收到,到时候在想办法,如果不知道0x10618480位置是做什么的,看 11.接管游戏接收网络数据包的操作

现在的配置

CAnly.h文件的修改:使用 DataAnly.exe 工具新加I_ZIP变量

#define Anly

#ifdef Anly

#pragma once

enum class TTYPE :int {

I_SEND=0,

I_RECV=1,

I_LOG=2,

I_DIS=3,

I_ZIP=4,

MAX

};

#define _TITLE L"titan_data"

typedef struct COPY_DATA {

time_t ltime; // 时间

unsigned MsgType; // 数据类型

unsigned len; // 数据长度

char buff[0x2000]; // 数据

}*PCOPYDATA;

class CAnly{

public:

LRESULT SendData(TTYPE type, unsigned MsgType, void* buff, unsigned len);

};

#endif

GameProc.cpp文件的修改:新加 _OnComprase函数,修改了 InitInterface函数

#include "pch.h"

#include "GameProc.h"

#include "extern_all.h"

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

bool _OnComprase(HOOKREFS2) {

return WinSock->OnRecv((char*)_EDI, _EBP + 1);

}

bool _OnRecv(HOOKREFS2) {

_EAX = WinSock->RecvPoint;

return true;

}

bool _OnConnect(HOOKREFS2) {

/*

根据虚函数表做HOOK的操作

截取 ecx 获取 winsock 的值(指针)

*/

unsigned* vtable = (unsigned*)_EDX;

//WinSock = (GameWinSock *)_ECX;

/*

联合体的特点是共用一个内存

由于 GameWinSock::OnConnect 的 OnConnect函数是 GameWinSock类的成员函数

直接 vtable[0x34 / 4] = (unsigned)&GameWinSock::OnConnect; 这样写语法不通过

所以使用联合体,让语法通过

*/

union {

unsigned value;

bool(GameWinSock::* _proc)(char*, unsigned);

} vproc;

DWORD oldPro, backProc;

VirtualProtect(vtable, 0x100, PAGE_EXECUTE_READWRITE, &oldPro);

/*

vproc._proc = &GameWinSock::OnConnect; 这一句是把我们自己写的调用connect函数的地址的出来

*/

vproc._proc = &GameWinSock::OnConnect;

/*

InitClassProc函数里做的是给指针赋值的操作

InitClassProc(&GameWinSock::_OnConnect, vtable[0x34/4]);这一句的意思是把

GameWinSock类里的_OnConnect变量的值赋值成vtable[0x34/4],这个 vtable[0x34/4] 是虚表里的函数

vtable[0x34/4]是游戏中调用connect函数的函数地址,经过之前的分析调用connect是先调用了虚表中的

一个函数,然后从这个函数中调用了connect函数

*/

InitClassProc(&GameWinSock::_OnConnect, vtable[0x34/4]);

vtable[0x34 / 4] = vproc.value;

vproc._proc = &GameWinSock::OnSend;

InitClassProc(&GameWinSock::_OnSend, vtable[0x3C / 4]);

vtable[0x3C / 4] = vproc.value;

VirtualProtect(vtable, 0x100, oldPro, &backProc);

return true;

}

GameProc::GameProc()

{

hooker = new htd::hook::htdHook2();

Init();

InitInterface();

}

void GameProc::LoadBase()

{

LoadLibraryA("fxnet2.dll");

}

void GameProc::Init()

{

#ifdef anly

anly = new CAnly();

#endif

/*

这里的 WinSock 是0没有创建对象,但是还是能调用Init函数

这是因为Init函数里面没有用到this,没用到就不会报错

*/

WinSock->Init();

Client = new NetClient();

init_datadesc();

}

void GameProc::InitInterface()

{

LoadBase();

// MessageBoxA(0, "1", "1", MB_OK);

// 只会HOOK一次,一次性的HOOK

hooker->SetHook((LPVOID)0x10617046, 0x1, _OnConnect, 0, true);

/*

第一个参数是HOOK的位置

第二个参数是HOOK的位置的汇编代码的长度(用于保证执行的汇编代码完整)

第三个参数是HOOK之后当游戏执行到第一个参数的位置的时候跳转的位置

第四个参数是 _OnRecv 函数返回 false 之后跳转的位置

*/

hooker->SetHook((LPVOID)0x10618480, 0x1, _OnRecv, 0);

hooker->SetHook((LPVOID)0x1061161D, 0x3, _OnComprase, (LPVOID)0x10611602);

/*

在这里绑定游戏处理数据包函数(0x10618480函数)

然后0x10618480函数在上面一行代码已经进行了HOOK

所以在调用_OnRecv函数指针时,它就会进入我们HOOK

*/

InitClassProc(&GameWinSock::_OnRecv, 0x10618480);

}

GameWinSock.h文件的修改:删除 OnRecving函数,原本名为 OnRecv函数改名为Recv,新加 OnRecv函数

#include "pch.h"

#include "GameProc.h"

#include "extern_all.h"

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

bool _OnComprase(HOOKREFS2) {

return WinSock->OnRecv((char*)_EDI, _EBP + 1);

}

bool _OnRecv(HOOKREFS2) {

_EAX = WinSock->RecvPoint;

return true;

}

bool _OnConnect(HOOKREFS2) {

/*

根据虚函数表做HOOK的操作

截取 ecx 获取 winsock 的值(指针)

*/

unsigned* vtable = (unsigned*)_EDX;

//WinSock = (GameWinSock *)_ECX;

/*

联合体的特点是共用一个内存

由于 GameWinSock::OnConnect 的 OnConnect函数是 GameWinSock类的成员函数

直接 vtable[0x34 / 4] = (unsigned)&GameWinSock::OnConnect; 这样写语法不通过

所以使用联合体,让语法通过

*/

union {

unsigned value;

bool(GameWinSock::* _proc)(char*, unsigned);

} vproc;

DWORD oldPro, backProc;

VirtualProtect(vtable, 0x100, PAGE_EXECUTE_READWRITE, &oldPro);

/*

vproc._proc = &GameWinSock::OnConnect; 这一句是把我们自己写的调用connect函数的地址的出来

*/

vproc._proc = &GameWinSock::OnConnect;

/*

InitClassProc函数里做的是给指针赋值的操作

InitClassProc(&GameWinSock::_OnConnect, vtable[0x34/4]);这一句的意思是把

GameWinSock类里的_OnConnect变量的值赋值成vtable[0x34/4],这个 vtable[0x34/4] 是虚表里的函数

vtable[0x34/4]是游戏中调用connect函数的函数地址,经过之前的分析调用connect是先调用了虚表中的

一个函数,然后从这个函数中调用了connect函数

*/

InitClassProc(&GameWinSock::_OnConnect, vtable[0x34/4]);

vtable[0x34 / 4] = vproc.value;

vproc._proc = &GameWinSock::OnSend;

InitClassProc(&GameWinSock::_OnSend, vtable[0x3C / 4]);

vtable[0x3C / 4] = vproc.value;

VirtualProtect(vtable, 0x100, oldPro, &backProc);

return true;

}

GameProc::GameProc()

{

hooker = new htd::hook::htdHook2();

Init();

InitInterface();

}

void GameProc::LoadBase()

{

LoadLibraryA("fxnet2.dll");

}

void GameProc::Init()

{

#ifdef anly

anly = new CAnly();

#endif

/*

这里的 WinSock 是0没有创建对象,但是还是能调用Init函数

这是因为Init函数里面没有用到this,没用到就不会报错

*/

WinSock->Init();

Client = new NetClient();

init_datadesc();

}

void GameProc::InitInterface()

{

LoadBase();

// MessageBoxA(0, "1", "1", MB_OK);

// 只会HOOK一次,一次性的HOOK

hooker->SetHook((LPVOID)0x10617046, 0x1, _OnConnect, 0, true);

/*

第一个参数是HOOK的位置

第二个参数是HOOK的位置的汇编代码的长度(用于保证执行的汇编代码完整)

第三个参数是HOOK之后当游戏执行到第一个参数的位置的时候跳转的位置

第四个参数是 _OnRecv 函数返回 false 之后跳转的位置

*/

hooker->SetHook((LPVOID)0x10618480, 0x1, _OnRecv, 0);

hooker->SetHook((LPVOID)0x1061161D, 0x3, _OnComprase, (LPVOID)0x10611602);

/*

在这里绑定游戏处理数据包函数(0x10618480函数)

然后0x10618480函数在上面一行代码已经进行了HOOK

所以在调用_OnRecv函数指针时,它就会进入我们HOOK

*/

InitClassProc(&GameWinSock::_OnRecv, 0x10618480);

}

GameWinSock.cpp文件的修改:删除 OnRecving函数,原本名为 OnRecv函数改名为Recv,新加 OnRecv函数

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

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

}

// 它会生成一个结构体,详情看效果图

void GameWinSock::AnlyBuff(char* start, char* end, 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, 0, 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);

}

推荐阅读

评论可见,请评论后查看内容,谢谢!!!评论后请刷新页面。