36.网络游戏逆向分析与漏洞攻防-游戏网络通信数据解析-数据解码器的实现

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

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

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

上一个内容:35.登录成功数据包内容分析

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

码云版本号:9474c79b07d63defaf0ca32e3cd3df21f8625505

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

以 34.登录数据包的监视与模拟 它的代码为基础进行修改

首先添加一个类:

然后把所有角色都删了,看看没有角色登录之后的数据包,如下图没有角色的登录成功数据包

效果图:本次实现了,解读数据解析约定的数据包

新加 EnCode.h文件:用于数据解析约定数据包解析,一次只能解析一个

#pragma once

class EnCode
{
private:

	char un[2]{};// 这个是为了内存对齐
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;
public:
	EnCode(char*& buff, unsigned int& _len, char ExIndex = 0);
	~EnCode();
};


新加 EnCode.cpp文件:用于数据解析约定数据包解析,一次只能解析一个

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


/*
	buff是数据包
	_len暂时没用
	ExIndex是解析方式,因为分析的时候发现6有时是char类型有时是char*类型
	看懂此方法需要分析手动分析一次数据包(数据解析约定的数据包)
	然后带着数据包去看这个函数
*/
EnCode::EnCode(char*& buff, unsigned int& _len,char ExIndex)
{
	index = ExIndex;
	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)) {
			/*
				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)) {
			pointer = new char[lenth];
			memcpy(pointer, buff, lenth);
			buff = buff + lenth;
		}
	}
}

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

GameWinSock.cpp文件的修改:新加 OnloginOk函数,修改了 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) { 
	/* 修改账号密码
	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 Onloginfailed(char*&buff, unsigned& len) { 
	int* code = (int*)&buff[1];
	Client->loginfailed(code[0]);
	return true; 
}

bool OnloginOk(char*& buff, unsigned& len) {
	CStringA txt;
	CStringA tmp;
	CString utmp;
	PDATALOGINOK _p = (PDATALOGINOK)&buff[1];
	if (_p->RoleCount > 0) {
		char* buffStart = buff + 1 + sizeof(DATA_LOGIN_OK);
		char* buffEnd = buff + len;
		while (buffStart < buffEnd) {
			/*
				由于 EnCode 的入参带了& 所以可以在EnCode里直接对buffStart或len进行操作
				也就是说只要入参带了&在函数里的操作会影响它原本的值
				比如 
				char*a = 0;
				aaa(a)
				aaa(char*&AA){
					AA = new char[10]; // 它就会把a的值改成new char[10]
				}
			*/
			EnCode* _coder = new EnCode(buffStart, len);
			tmp.Format("[%s]",data_desc[_coder->index][_coder->op].name);
			txt = txt + tmp;

			if (_coder->op == 0x7) {
				utmp = (wchar_t*)_coder->pointer;
				tmp = utmp;
				txt = txt + tmp;
			}

			if (_coder->op == 0x3) {
				tmp.Format("[%f]", _coder->fval);
				txt = txt + tmp;
			}
			else {
				tmp.Format("[%f]", _coder->val);
				txt = txt + tmp;
			}
		}

#ifdef  Anly
		// 长度24的原因,它是宽字节要,一个文字要2个字节,一共是10个文字加上结尾的0是11个
		// 所以 11 乘以2,然后再加2 
		anly->SendData(TTYPE::I_DIS, 0, txt.GetBuffer(), txt.GetAllocLength() + 1);
#endif
	}
	return true;
}

// 这个函数拦截了游戏的连接
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;
	// 注册数据登录失败数据包处理函数
	 RecvDealProc[S_LOGINFAIL] = &Onloginfailed;
	 RecvDealProc[S_LOGINOK] = &OnloginOk;
}

GameProc.cpp文件的修改:修改了 Init函数

#include "pch.h"
#include "GameProc.h"
#include "extern_all.h"

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


bool _OnRecv(HOOKREFS2) {
	unsigned* _esp = (unsigned*)_ESP;
	_EAX = WinSock->RecvPoint;
	WinSock->OnRecving((char*)_esp[1], _esp[2]);
	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之后当游戏执行到第一个参数的位置的时候跳转的位置
	*/
	hooker->SetHook((LPVOID)0x10618480, 0x1, _OnRecv);
	/*
		在这里绑定游戏处理数据包函数(0x10618480函数)
		然后0x10618480函数在上面一行代码已经进行了HOOK
		所以在调用_OnRecv函数指针时,它就会进入我们HOOK
	*/
	InitClassProc(&GameWinSock::_OnRecv, 0x10618480);
}

extern_all.cpp文件的修改:新加 data_desc变量初始化、init_datadesc函数

/*
	此文件是用来存放全局变量、全局函数(通用函数)
*/
#include "pch.h"
#include "extern_all.h"
GameWinSock* WinSock = nullptr;
GameProc* PGameProc = nullptr;
NetClient* Client = nullptr;
#ifdef Anly
CAnly* anly = nullptr;
#endif

DATA_DESC data_desc[2][9]{};

void init_datadesc() {
	data_desc[0][0] = { "none", 0 };
	data_desc[0][1] = { "short", 2 };
	data_desc[0][2] = { "int", 4 };
	data_desc[0][3] = { "int64", 8 };
	data_desc[0][4] = { "float", 4 };
	data_desc[0][5] = { "int64", 8 };
	data_desc[0][6] = { "char", 1 };
	data_desc[0][7] = { "wchar_t*", 4 };
	data_desc[0][8] = { "int64", 8 };

	data_desc[1][0] = { "none", 0 };
	data_desc[1][1] = { "short", 2 };
	data_desc[1][2] = { "int", 4 };
	data_desc[1][3] = { "int64", 8 };
	data_desc[1][4] = { "float", 4 };
	data_desc[1][5] = { "int64", 8 };
	data_desc[1][6] = { "char*", 4 };
	data_desc[1][7] = { "wchar_t*", 4 };
	data_desc[1][8] = { "int64", 8 };
}

void InitClassProc(LPVOID proc_addr, unsigned value)
{
	unsigned* writer = (unsigned*)proc_addr;
	writer[0] = value;
}

extern_all.h文件的修改:新加 DATA_DESC结构体、data_desc变量、init_datadesc函数

/*
	此文件是用来存放全局变量、全局函数(通用函数)
*/
#pragma once
#include "GameWinSock.h"
#include "GameProc.h"
#include "CAnly.h"
#include "NetClient.h"

typedef struct DATA_DESC {
	char* name;
	short lenth;
}*PDATADESC;

extern 	GameWinSock* WinSock;
extern GameProc* PGameProc;
extern NetClient* Client;
extern DATA_DESC data_desc[2][9];
extern void InitClassProc(LPVOID proc_addr, unsigned value);
extern void init_datadesc();
#ifdef Anly
extern CAnly* anly;
#endif

NetClient.h文件的修改:写了一个 loginok函数,这个loginok函数暂无用

#pragma once
#include "NetClass.h"
class NetClient // 监视客户端每一个操作
{
public:
	/*
		模拟登陆的方法
		Id是账号
		Pass是密码
		它要基于发送的方法实现,因为我们没有连接socket的操作
	*/
	bool login(const char* Id, const char*Pass);
public:
	// 登陆失败,参数是错误码
	void loginfailed(int code);
	// void loginok();
};


NetClass.h文件的修改:

#pragma once
/*
	数据包还原结构体要注意内存对齐,如果数据不满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;


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

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

相关文章

Oracle 19cADG集群补丁升级

Oracle 19cADG集群补丁升级 文章目录 Oracle 19cADG集群补丁升级1.备库备份2.备库升级Opatch3.备库应用补丁4.主库备份 oracle_home目录5.主库升级Opatch6.注册补丁7.编译无效对象8.检查主库的补丁注册情况9.备库切换主库完成补丁注册 1.备库备份 su - oracle cd $ORACLE_HOME…

3、创建项目,什么是路由

一、创建项目 第一次全局安装脚手架 npm install -g vue/clivue create 项目名 二、什么是路由&#xff1f; 路由就是一组 key-value 的对应关系多个路由&#xff0c;需要经过路由器的管理 1、后端路由&#xff1a; 每个url地址都对应着不同的静态资源对于普通的网站。所有…

【Godot4自学手册】第二十九节使用Shader来实现敌人受伤的闪白效果

在Godot 4中&#xff0c;Shader是用来为材质提供自定义渲染效果的程序。材质可以应用于MeshInstance、CanvasItem和ParticleEmitter等节点。Shader可以影响顶点的变换、片段&#xff08;像素&#xff09;的颜色&#xff0c;以及光照与物体的交互。 在Godot中&#xff0c;Shader…

ROS 2边学边练(1)-- 安装Iron Irwini

其实是从去年的一个机缘巧合才开始了解到ROS&#xff0c;但也仅限于此了&#xff08;看了古月居的入门21讲&#xff09;&#xff0c;今年买了几本关于ROS相关的书籍&#xff0c;比如《精通ROS机器人编程 Lentin Joseph&Jonathan Cacace》、《ROS 2机器人编程实战-基于现代C…

AI入侵游戏业:是颠覆者还是创新助手?揭秘未来游戏新趋势!

在科技日新月异的今天&#xff0c;人工智能&#xff08;AI&#xff09;已经成为各行各业的关注焦点。而在娱乐产业中&#xff0c;AI技术的引入也让人们对电子游戏的未来发展产生了无限遐想。那么&#xff0c;AI究竟会给电子游戏行业带来怎样的变革&#xff1f;它会成为行业的颠…

【消息队列开发】 实现BrokerServer类——本体服务器

文章目录 &#x1f343;前言&#x1f38b;创建 BrokerServer 类&#x1f38d;启动与停止服务器&#x1f340;实现处理连接&#x1f384;实现 readRequest 与 writeResponse&#x1f334;实现处理请求&#x1f332;实现 clearClosedSession⭕总结 &#x1f343;前言 本次开发任…

java的ArrayList类

ArrayList<E>E是自定义数据类型 ArrayList类&#xff1a; 构造函数&#xff1a; 成员方法&#xff1a; public boolean add(E e)&#xff1a; 将指定元素加到集合末尾 Appends the specified element to the end of this list. public class Array {public static…

就业班 第二阶段 2401--3.26 day6 Shell初识 连接vscode

远程连接vs_code可能出现的问题 C:\Users\41703\.ssh 验证远程主机的身份&#xff0c;如果连不上vscode&#xff0c;可以尝试删除这里面的公钥代码。 重新安装那个扩展&#xff0c;排除扩展本身的问题 谁连过我&#xff0c;并操作了什么 curl https://gitea.beyourself.org.c…

【软件测试】功能测试/接口测试/自动化测试/性能测试/验收测试

软件测试的主要流程 一、测试主要的四个阶段 1.测试计划设计阶段&#xff1a;产品立项之后&#xff0c;进行需求分析&#xff0c;需求评审&#xff0c;业务需求评级&#xff0c;绘制业务流程图。确定测试负责人&#xff0c;开始制定测试计划&#xff1b; 2.测试准备阶段&…

C++11与thread相关使用(纯代码)

多线程创建 //多线程创建 void print(string s) {cout << "i am a new thread&#xff1a;" << s << endl; } int main() {//move将左值变成右值(右值引用过后的属性是左值)//thread t1(print, "t1");//thread t2(move(t1));//调用移动…

javaWeb校园二手平台项目

一、系统分析 1.1开发背景 随着全世界互联网技术的不断发展&#xff0c;各种基于互联网技术的网络应用不断涌现,网络技术正在不断的深入人们的生活。人们从Internet上获取信息、享受生活、交流感情、网上工作等。Internet正在迅速改变着人们的生活方式。 经过我国改革开放多年…

CI/CD实战-jenkins流水线 6

现最新版本没有该问题的出现 基于RBAC的身份授权&#xff1a; 安装插件&#xff1a; 新建测试用户 修改默认授权策略 新建的用户就没有任何权限 新建角色并授权 添加用户角色身份 pipeline 安装ssh agent插件 由于最新版的插件是有问题的&#xff0c;会有以下报错&#xff…

「媒体宣传」财经类媒体邀约资源有哪些?-51媒体

传媒如春雨&#xff0c;润物细无声&#xff0c;大家好&#xff0c;我是51媒体网胡老师。 财经类媒体邀约资源包括但不限于以下几类&#xff1a; 商业杂志和报纸&#xff1a;可以邀请如《财经》、《新财富》、《经济观察报》等主流商业杂志和报纸。这些媒体通常具有较强的品牌影…

自信当众讲话:从紧张到自如的转变之路

自信当众讲话&#xff1a;从紧张到自如的转变之路 在人生的舞台上&#xff0c;当众讲话是每个人都可能面对的挑战。然而&#xff0c;对于许多人来说&#xff0c;站在众人面前讲话却是一件令人紧张甚至恐惧的事情。这种紧张感往往源于对自我能力的怀疑&#xff0c;对未知的恐惧…

【Node.js从基础到高级运用】十八、Node.js的安全性加固

引言 在Web开发中&#xff0c;安全性是一个不可忽视的话题。Node.js作为一个流行的后端平台&#xff0c;同样需要关注各种潜在的安全威胁&#xff0c;并采取措施加以防御。本文将介绍如何在Node.js应用中防御常见的Web攻击&#xff0c;以及如何使用安全相关的中间件来加固安全性…

Windows 频繁失去焦点分析

原文&#xff1a;https://blog.iyatt.com/?p14383 1 前言 刚才在打字的时候发现会随机失去焦点&#xff0c;然后又要用鼠标点一下正在输入的位置才能继续输入&#xff0c;特别烦。开始我怀疑是手碰到触摸板导致失去焦点&#xff0c;但是我用了差不多十年带触摸板的笔记本电脑…

[ C++ ] STL---仿函数与priority_queue

目录 仿函数 示例一&#xff1a; 示例二 : 常见的仿函数 priority_queue简介 priority_queue的常用接口 priority_queue的模拟实现 基础接口 push() 堆的向上调整算法 堆的插入 pop() 堆的向下调整算法 堆的删除 priority_queue最终实现 仿函数 仿函数&#xff…

基于stm32与TJC3224T124_011串口屏的PID调参器(附完整工程)

电赛在即&#xff0c;每次比赛调PID都是一件比较繁琐的事。每次都要在程序中改完再烧录到板子上&#xff0c;特别耗时。正好最近发现实验室的一块串口屏比较好玩。 于是就做了这个调PID的东西。它可以通过串口直接修改PID的值&#xff0c;从而达到快速调PID的目的。下面我将完整…

【Python】学习率调整策略详解和示例

学习率调整得当将有助于算法快速收敛和获取全局最优&#xff0c;以获得更好的性能。本文对学习率调度器进行示例介绍。 学习率调整的意义基础示例无学习率调整方法学习率调整方法一多因子调度器余弦调度器 结论 学习率调整的意义 首先&#xff0c;学习率的大小很重要。如果它…

音乐制作利器 :FL Studio21中文编曲音乐制作软件免费下载

一、引言 在音乐的世界里&#xff0c;每个人都有自己独特的音色和表达方式。而今天&#xff0c;我们要为你推荐一款能让您的音乐创作更上一层楼的神器——FL Studio21中文编曲音乐制作软件。这款功能强大的音乐制作软件&#xff0c;不仅拥有丰富的音色库和高效的编辑功能&#…