45.网络游戏逆向分析与漏洞攻防-角色管理功能通信分析-解码发送数据内容输出到日志

news2025/1/13 6:22:05

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

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

现在的代码都是依据数据包来写的,如果看不懂代码,就说明没看懂数据包

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

上一个内容:44.角色创建服务器反馈数据包分析

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

码云版本号:75c763a02e224b8d293d71284044da98f53e2c19

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

以 44.角色创建服务器反馈数据包分析 它的代码为基础进行修改

根据上一个内容游戏登录之后的操作基本上都是0A开头,然后里面有数据参数个数,其中数据参数里就有02开头的数据,现在做的是把这些数据参数全部解读出来,打印到一个文件里,这样将来看这个文件的时候就能知道有多少种规则,大概都是怎样的

效果图:

阵营选择的数据包,解读,591就是阵营选择的操作码(也就是591表示阵营选择这个操作)

登录进游戏也可以正常解析发送0A开头的数据包

游戏进入游戏,然后返回到选择角色界面之后,会频繁发送一个0A开头的数据包,经过数据解码可以看出这个数据包的内容是GetTickCount函数的返回值,也就是系统启动以来经过的毫秒数,这个数据包应该是一个心跳包,所以现在就把心跳包顺便解决了

然后GetTickCount函数返回的是一个long long类型,从心跳包里看,03表示long long类型

然后心跳的时间大概5秒一次

然后这个心跳有时间间隔,可以通过两个心跳包的时间间隔来检测游戏是否被调试,如果被调试执行的时候必然会增加,所以通过心跳包做调试检测是可行的,但是这个东西要加密,如果不加密就跟现在一样让我们轻而易举的就知道了

效果图:

拦截591操作码,也就是拦截阵营选择操作

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

#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
	{
		char dataPool[0x8];
		int lenth;
		char byte;
		short stval;
		int val;
		float fval;
		double dbval;
		long long lval = 0;
	};
	char* pointer = 0;
	char* back = 0;
public:
	EnCode();
	EnCode(char*& buff, unsigned int& _len, char ExIndex = 0);
	EnCode& operator=(char*& buff);
	int Init(char*& buff, char EnIndex = 0);
	~EnCode();
};

class GBYTE :public EnCode {
public:
	using EnCode::EnCode;
	using EnCode::operator=;
	operator char();
	char value();
};
class GSHORT :public EnCode {
public:
	using EnCode::EnCode;
	using EnCode::operator=;
	operator short();
	short value();
};
class GINT :public EnCode {
public:
	using EnCode::EnCode;
	using EnCode::operator=;
	operator int();
	int value();
};
class GFLOAT :public EnCode {
public:
	using EnCode::EnCode;
	using EnCode::operator=;
	operator float();
	float value();
};
class GINT64 :public EnCode {
public:
	using EnCode::EnCode;
	using EnCode::operator=;
	operator long long();
	long long value();
};
class GDOUBLE :public EnCode {
public:
	using EnCode::EnCode;
	using EnCode::operator=;
	operator double();
	double value();
};
class GCHAR :public EnCode {
public:
	using EnCode::EnCode;
	using EnCode::operator=;
	operator const char*();
	const char* value();
};
class GUTF16 :public EnCode {
public:
	using EnCode::EnCode;
	using EnCode::operator=;
	operator const wchar_t*();
	const wchar_t* value();
};

NetClass.h文件的修改:新加 SC_CHOOSECAMP宏

#pragma once
#include "EnCode.h"
// 发送数据操作码定义
#define SC_CHOOSECAMP 591 // 阵营选择操作码
// 发送数据标准头
typedef struct NET_SEND_HEAD {
	// 为了内存对齐,当前结构不可能超过255,所以这样写是没问题的
	char len = sizeof(NET_SEND_HEAD) - 1;
	char op = 0x0A;
	short unst = 0;
	int unnt[4]{};
	short unst1 = 0;
	short count = 0;
}*PNET_SEND_HEAD;

// 申请创建角色结构体
typedef struct NET_CREATEROLE_START {
	char un[0x02];// 用来做内存对齐
	char len = sizeof(NET_CREATEROLE_START) - 3;// 用来做内存对齐
	char op = 0x03;
	int code = 0x01;
}*PNET_CREATEROLE_START;

// 删除角色数据结构
typedef struct DATA_DELROLE {
	char op;
	char buff[0x1F];
	int len;
	int un[8] = { 0x01 , 0x03};

}*PDATADELROLE;

/*
	数据包还原结构体要注意内存对齐,如果数据不满4字节,它字段会补齐
	比如结构体里有一个char变量,它是1字节,在内存里它可能会为了内存对齐
	让它变成4字节,所以这要注意
*/
// 登录数据
typedef struct DATA_LOGIN {
	int op = 0x0300;
	char buff[0x10]{};
	int lenId = 0x10;
	/*
		这个是登录的账号,它可能会变成0x20或更长,现在默认让它0x10
		读的时候以长度为准就好了
	*/
	char Id[0x10]{};
	int lenPass = 0x10;
	/*
		这个是登录的密码,它可能会变成0x20或更长,现在默认让它0x10
		读的时候以长度为准就好了
	*/
	char Pass[0x10]{};
	int lenCode = 0x10;
	char Code[0x10]{};
	int eop = 0x01;
}*PDATALOGIN;
typedef struct DATA_LOGIN_OK {// 登录成功数据包头
	int un[8] = { 0, 0, 0x76B, 0x0C, 0x1E,0, 0, 0 };
	int index = 0;
	int RoleCount = 0;
}*PDATALOGINOK;
typedef class ROLE_DATA {// 登录成功数据包
public:
	GBYTE byte;
	GINT un;
	GINT un1;
	GUTF16 name;
	GUTF16 infos;
	GINT un2;
	GINT64 un3;
}*PROLEDATA;

NetClient.h文件的修改:新加 OnSendCustom函数、DecodeChooseCamp函数

#pragma once
#include "NetClass.h"
class NetClient // 监视客户端每一个操作
{
private:
	PROLEDATA roles;
	unsigned rolecount;
	bool logined = false;

	bool DelRole(wchar_t* rolename, unsigned _len);
public:
	/*
		模拟登陆的方法
		Id是账号
		Pass是密码
		它要基于发送的方法实现,因为我们没有连接socket的操作
	*/
	bool login(const char* Id, const char* Pass);
	bool DelRole(wchar_t* rolename);
	bool StartCreateRole();// 用于创建角色


	// 根据角色名字获取一个登录成功数据包(选择角色列表里的一个数据)
	PROLEDATA GetRoleByName(wchar_t* rolename);
public:
	// 用于拦截游戏删除角色功能
	bool virtual OnDelRole(wchar_t* rolename, unsigned _len);
	// 用于拦截游戏登录功能
	void virtual Onlogin(const char* Id, const char*Pass);
	// 用于拦截游戏创建角色功能
	bool virtual OnStartCreateRole(int code);
	// opcode意思是操作码,count意思是数量,buffStart意思是解码的内容开始,buffend意思是解码的内容结束,buffer是原始的数据,len是原始数据的长度
	// char& buffer, int& len这俩参数带&的原因是,在 OnSendCustom 里进行修改之后,通过&的方式传递回去
	bool virtual OnSendCustom(int opcode, short count, char* buffStart, char* buffend, char*& buffer, unsigned& len);
public:
	bool virtual DecodeChooseCamp(char* buffStart, char* buffend, short count);
public:
	// 处理失败,参数是错误码
	bool virtual Tips(int code);
	void virtual loginok(ROLE_DATA* _roles, int count);
	bool virtual OnScrStartCreateRole(short code,wchar_t* _txt);
};

NetClient.cpp文件的修改:新加 OnSendCustom函数、DecodeChooseCamp函数

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

PROLEDATA NetClient::GetRoleByName(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::DelRole(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);
}

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::OnSendCustom(int opcode, short count, char* buffStart, char* buffend, char*& buffer, unsigned& len)
{
    switch (opcode)
    {
    case SC_CHOOSECAMP:
        return DecodeChooseCamp(buffStart, buffend, count);
        break;
    default:
        break;
    }
    return true;
}

bool NetClient::DecodeChooseCamp(char* buffStart, char* buffend, short count)
{
    GCHAR coder;
    coder.Init(buffStart, 1);
   
    return true;
}

bool NetClient::Tips(int code)
{
#ifdef  Anly
    CString txt;
    if (code == 51001) {
        txt = L"登陆失败,易道云通行证不存在!";
    }else if (code == 51002) {
        txt = L"登录失败,密码错误!";
    }
    else {
        txt.Format(L"未知登录错误:%d", code);
    }


    anly->SendData(TTYPE::I_LOG, 0, txt.GetBuffer(), txt.GetLength()*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;
}

GameWinSock.cpp文件的修改:修改了 OnSendCustom函数、AnlyBuff函数

#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 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 = buffStart;
			roleDatas[i].un = buffStart;
			roleDatas[i].un1 = buffStart;
			roleDatas[i].name = buffStart;
			roleDatas[i].infos = buffStart;
			roleDatas[i].un2 = buffStart;
			roleDatas[i].un3 = buffStart;
		}
		Client->loginok(roleDatas, _p->RoleCount);
	}
	return true;
}

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

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

#ifdef  Anly
		anly->SendData(TTYPE::I_DIS, I_SEND_CUSTOM, "SEND_CUSTOM 发现异常数据", 25);
#endif
		return true;
	}
	// 解析一个长度后面的数据
	EnCode coder(buffStart, len, 1);
	GINT* intCoder = (GINT*)&coder;
	// 得到数据
	int opcode = intCoder->value();
#ifdef  Anly
	WinSock->AnlyBuff(buffStart, buff + len, 1);
#endif
	return Client->OnSendCustom(opcode, icount - 1, buffStart, buff + len, 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
	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;
	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::OnRecving(char* buff, unsigned len)
{
	// MessageBoxA(0, "11111111111111", "0", MB_OK);
	/*
		监控游戏接收的数据包
	*/
#ifdef  Anly
	anly->SendData(TTYPE::I_RECV, buff[0], buff, len);
#endif
	return RecvDealProc[buff[0]](buff, len);
}

bool GameWinSock::OnRecv(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;
	// 注册数据登录失败数据包处理函数
	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;

	CStringA _opname = data_desc[_coder.index][_coder.op].name;

	while (start < end) {
		_coder.Init(start, index);
		// _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("%l64d\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("%l64d\r\n", _int64coder->value());
				txt = txt + tmp;
				break;
			default:
				break;
			}
		}
	} 
	anly->SendData(TTYPE::I_DIS, 0, txt.GetBuffer(), txt.GetAllocLength() + 1);
#endif
}

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

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

相关文章

VUE3和SpringBoot实现ChatGPT页面打字效果SSE流式数据展示

在做这个功能之前&#xff0c;本人也是走了很多弯路&#xff08;花了好几天才搞好&#xff09;&#xff0c;你能看到本篇博文&#xff0c;那你就是找对地方了。百度上很多都是使用SseEmitter这种方式&#xff0c;这种方式使用的是websocket&#xff0c;使用这种方式就搞复杂了&…

58 vue-cli 以及 webpack 提供的默认的插件, 配置

前言 vue-cli 这边作为驱动 webpack 的一个应用 它需要构造 webpack 所需要的上下文, 以及参数 这里 我们来关注一下 vue-cli 这边为 webpack 构造的参数 的相关处理 webpack 这边上下文的配置, 主要分为了几个部分, Entry, Output, Module, Resolve, Plugin, DevServer, O…

open-cd框架调试记录

源于论文Changer: Feature Interaction Is What You Need forChange Detection 源码位置&#xff1a;open-cd/README.md at main likyoo/open-cd (github.com) 同样是基于MMSegmentation框架的代码&#xff0c;不符合本人编程习惯所以一直也没有研究这东西&#xff0c;近期打…

第15章 File类与IO流

一 java.io.File类的使用 1.1 概述 File类及本章下的各种流&#xff0c;都定义在java.io包下。一个File对象代表硬盘或网络中可能存在的一个文件或者文件目录&#xff08;俗称文件夹&#xff09;&#xff0c;与平台无关。&#xff08;体会万事万物皆对象&#xff09;File 能新…

AcWing 788. 逆序对的数量——算法基础课题解

AcWing 788. 逆序对的数量 题目描述 给定一个长度为 n 的整数数列&#xff0c;请你计算数列中的逆序对的数量。 逆序对的定义如下&#xff1a;对于数列的第 i 个和第 j 个元素&#xff0c;如果满足 i<j且 a[i]>a[j]&#xff0c;则其为一个逆序对&#xff1b;否则不是。…

基于 Rust 标准库 API 使用 200 行代码实现 Http 1.1 协议简易服务

1. 背景 早在之前学过一波 Rust&#xff0c;但是由于没用武之地&#xff0c;没过多久又荒废了&#xff0c;最近想捡起来下。刚好看见有群里小伙伴说学习 Http 网络协议太难怎么办&#xff1f;其实很多技术都是相通的&#xff0c;只要你理解了技术的本质就可以自己实现它&#…

buu刷题(2)

[护网杯 2018]easy_tornado web buuctf [护网杯 2018]easy_tornado1_[护网杯 2018]easy_tornado 1-CSDN博客 render是渲染HTML页面用到的函数 这应该是一个模板注入漏洞 访问/fllllllllllllag&#xff0c;自动跳到了这个页面&#xff0c;可以看到 url 上有个msgError, 尝试将…

Node.js------模块化

◆ 能够说出模块化的好处◆ 能够知道 CommonJS 规定了哪些内容◆ 能够说出 Node.js 中模块的三大分类各自是什么◆ 能够使用 npm 管理包◆ 能够了解什么是规范的包结构◆ 能够了解模块的加载机制 一.模块化的基本概念 1.模块化 模块化是指解决一个复杂问题时&#xff0c…

大商创多用户商城系统 多处SQL注入漏洞复现

0x01 产品简介 大商创多用户商城系统是一个功能强大、灵活多变的新零售电商系统服务商。该系统支持平台自营和商家入驻,实现多元化经营模式,能够全面整合供应商、生产商、经销商和消费者等产业链资源,提高产品多样性,加快资金流动速度,并有助于减少不必要的成本输出。 0…

交换机特性解析

​1. 端口数量和类型: RJ-45端口: 最常见的端口类型,用于连接网线。 铜缆类型: 超五类、六类、七类等,影响传输速率和距离。 PoE功能: 支持为连接的设备供电,如IP电话、无线AP等。 光纤端口: 用于连接光纤,支持更长的传输距离和更高的速率。 光纤类型: 单模、多模等,影响传…

【核弹级安全事件】XZ Utils库中发现秘密后门,影响主要Linux发行版,软件供应链安全大事件

Red Hat 发布了一份“紧急安全警报”&#xff0c;警告称两款流行的数据压缩库XZ Utils&#xff08;先前称为LZMA Utils&#xff09;的两个版本已被植入恶意代码后门&#xff0c;这些代码旨在允许未授权的远程访问。 此次软件供应链攻击被追踪为CVE-2024-3094&#xff0c;其CVS…

计算机服务器中了halo勒索病毒怎么办,halo勒索病毒解密流程步骤

随着网络技术的不断应用&#xff0c;企业的生产运营得到了快速发展&#xff0c;越来越多的企业开始利用服务器数据库存储企业的重要信息文件&#xff0c;数据库为企业的生产运营提供了极大便利&#xff0c;但网络技术的不断发展也为企业的数据安全带来严重威胁。近日&#xff0…

IP-guard WebServer 任意文件读取漏洞复现

0x01 产品简介 IP-guard是由溢信科技股份有限公司开发的一款终端安全管理软件,旨在帮助企业保护终端设备安全、数据安全、管理网络使用和简化IT系统管理。 0x02 漏洞概述 由于IP-guard WebServer /ipg/static/appr/lib/flexpaper/php/view.php接口处未对用户输入的数据进行严…

C语言------冒泡法排序

一.前情提要 1.介绍 冒泡法排序法&#xff1a; 1)冒泡排序&#xff08;Bubble Sort&#xff09;是一种简单的排序算法&#xff0c;它重复地遍历要排序的列表&#xff0c;一次比较相邻的两个元素&#xff0c;并且如果它们的顺序错误就将它们交换过来。重复这个过程直到没有需…

聚酰亚胺PI材料难于粘接,用什么胶水粘接?那么让我们先一步步的从认识它开始(十八): 聚酰亚胺PI泡沫有哪些应用领域

聚酰亚胺PI泡沫有哪些应用领域 聚酰亚胺&#xff08;PI&#xff09;泡沫由于其一系列优异的特性&#xff0c;在许多高性能应用领域中都有广泛的应用&#xff0c;包括但不限于&#xff1a; 航空航天领域&#xff1a;聚酰亚胺PI泡沫由于其出色的耐高温、隔热和阻燃性能&#xff0…

C语言 循环控制——计数控制的循环

目录 循环语句 for语句 小结 循环结构有什么用&#xff1f; 国外某男子攻打自己的女友&#xff0c;并导致女友受伤&#xff0c;法官除判处他监禁和提供金钱补偿外&#xff0c;还处罚他抄写5000遍道歉辞&#xff1a; “Boys do not hit girls.” 循环的控制方法 循环语句 f…

Java NIO是New IO还是Non-blocking IO

文章目录 前言NIO到底叫啥通过对比理解NIO传统IO网络编程NIO引入的新概念NIO网络编程两者区别NIO的事件驱动 总结 前言 很多小伙伴对Java NIO的一些概念和编程不是很理解&#xff0c;希望通过本文对Java NIO与传统IO的对比&#xff0c;可以帮助大家更好地理解和掌握Java NIO。…

k8s存储卷 PV与PVC 理论学习

介绍 存储的管理是一个与计算实例的管理完全不同的问题。PersistentVolume 子系统为用户和管理员提供了一组 API&#xff0c;将存储如何制备的细节从其如何被使用中抽象出来。为了实现这点&#xff0c;我们引入了两个新的 API 资源&#xff1a;PersistentVolume 和 Persistent…

intellij idea 使用git撤销(取消)commit

git撤销(取消) 未 push的 commit Git&#xff0c;选择分支后&#xff0c;右键 Undo Commit &#xff0c;会把这个 commit 撤销。 git撤销(取消) 已经 push 的 commit 备份分支内容&#xff1a; 选中分支&#xff0c; 新建 分支&#xff0c;避免后续因为操作不当&#xff0c;导…

网络原理 - HTTP / HTTPS(4)——构造http请求

目录 一、postman 的下载安装以及简单介绍 1、下载安装 2、postman的介绍 二、通过 Java socket 构造 HTTP 请求 构造http请求的方式有两种&#xff1a;&#xff08;1&#xff09;通过代码构造&#xff08;有一点难度&#xff09; &#xff08;2&#xff09;通过第三…