只需要一點點C++基礎,新手也可以制作單機游戲內存修改器


聲明:本文只是為了初學C++的,能夠做出一些實用的東西,跳出管理系統的束縛,提升學習的興趣,在這里選取了單機游戲,請不要嘗試在線游戲,違發而已未必可行。
序:首先我們需要一個Qt+VS環境
Qt從http://download.qt.io/archive/中下載,第一個和第三個,在里面選擇對應版本。然后就是配環境了,這里提供2013+Qt5.5.1的環境配置,如果環境不同,請自行百度。這點解決問題能力都沒有,就別學C++了...
我的環境是2013+Qt5.5.1,不同版本可能略有差異,不過大同小異。
首先打開VS創建工程,點確定,然后一直下一步

 

選擇QWidget,點完成

然后就創建了一個工程,我們來說說這個工程。

 

一:main函數和C++的很像

 

然后再看看剛才創建的Qt窗口類

先是頭文件

然后是CPP文件

 

再看看一些“奇怪”的文件

下面這兩個是Qtmoc文件,是編譯時自動生成和更新的,所以不用管

 

下面第一個是資源文件的代碼CPP,自動生成的,不用管

第二個是UI文件的頭文件,自動生成的,不用管

 

資源文件,用來加載圖片等一些資源,這里沒用到,不用管

這個就是上面提到的UI文件,相當於可視化界面設計器,用來設計界面的。

 

雙擊點開XXXXX.UI文件

 

 

二:接下來開始界面設計

在控件盒子中左鍵選中一個文件標簽,一個文字輸入框,一個按鈕,然后往界面設計器里面拖。文件標簽在左,文字輸入框在中,一個按鈕在右。

ctrl+鼠標左鍵點選三個控件,然后在任意一個控件上右鍵,選擇布局->水平布局

 

開始界面布局

右鍵大窗口,選擇布局->垂直布局

然后鼠標放到界面設計器的邊框邊緣,按住左鍵拖動到合適大小

在對象查看器里左鍵點選大窗口,然后屬性窗口往下拖,在WindowTitle里修改窗口標題

雙擊控件修改控件的文本

記錄控件的對應關系,把金錢技能和屬性對應的輸入框和按鈕記錄下來

在這里我金錢的輸入框是lineEdit,金錢修改按鈕是pushButton,技能點則分別是lineEdit_2pushButton_2,屬性點則是lineEdit_3pushButton_3

然后點保存,注意一定要保存

 

三:實現前的知識普及

1:游戲內存修改的知識普及

一般游戲數據有一個地址值,但是這個地址值是動態的,每次游戲重啟都會發生變化,所以我們要找到不變的一級基址,和兩個不變的偏移量,來得到最新的游戲數據地址。

2Qt信號槽知識普及

Qt信號

信號是指一種通知,形象地比喻下:比如你帶了許多巧克力去公司,然后在群里告訴大家,“我帶了很多巧克力,要的來我工位拿”,這里公司群就是你的應用程序,群員就是程序里的實例化對象,你說的話這就是一種信號;可能有些人會無視,有些人根本沒看見,有些人會來要,有些人會轉告其他人,你只負責發出一個通知,你不關心別人看到你的通知會作何反應。

Qt槽函數

槽指的是一種行為函數,定義了收到信號通知后,應該做出何種反應,上面巧克力的例子,無視,轉告和要巧克力,都是一種對於信號通知的響應行為。

Qtconnect函數

就是對信號和槽進行關聯,A發的信號通知B做出某種響應行為。

QtQTimer

定時器,按照你設置的時間間隔,不間斷發出timeout()信號通知。

QMessageBox::information()

顯示一個提示窗口

ui控件的指針怎么找

UI控件的指針和objectname同名,而objectname就是在界面設計器點選對應控件,屬性里第一個

 

使用的時候用ui.objectname或者ui->objectname,用哪種取決於h文件里的ui變量是對象還是指針。這里就是ui.objectname

 

 

四:基址和偏移量查找

①現在我們要用到一個軟件,名字叫cheat engine,我的是6.6中文版。游戲以騎馬與砍殺為例,首先修改金錢。

②把金錢數據輸入ce,點擊新的掃描

③想辦法改變金錢數,輸入CE,點擊再次掃描,不斷重復這條,直到數據只有一個

(注意:有可能會遇到一直有2個的情況,這樣的情況試着改下數據就行了,哪個生效就是哪個)

④這里得到的就是一個游戲數據內存,可以改游戲數據值,但他是動態的,游戲重啟就失效了,我們需要找的是基址。

⑤鼠標右鍵這個游戲數據地址,查找什么改變了這個值。

⑥然后出現這個界面,一開始是沒有數據的,需要改變下游戲數據(這里是金錢數)

⑦雙擊這條數據,這里的5D0就是第一個偏移量,4B4C1024就是下一個要查找的地址。

⑧開啟一個新的掃描

⑨選擇需要的的地址查找是什么訪問了這個地址,有時候有很多個,一般是比較特殊的那個(就是其他地址開頭都是一樣的,就他不一樣),或者一個個看,有數據的就是我們需要的那個地址(注意無需改變游戲數據就有數據)

⑩隨便雙擊一個mov指令數據,這里的140EC就是第二個偏移值,48D2E010就是下一個要查找的地址

①①用新拿到的推薦地址重復第⑧步,查找的綠色地址就是一級基址了

①②開始效驗這個基址

①③ 修改這個地址的數值,如果錢發生變化的話就找對了

同理,用這個方法查找技能點

找出來的一級基址是009D5E2C,偏移是5D0 2BC,發現沒有,一級基址和第二次偏移是一樣的,所以之后查找,找一次偏移就可以了。

【特別注意】網上有些攻略說一級基址+第二次偏移量+第一次偏移量就是游戲數據地址,其實是錯的,應該是一級基址里保存的值+第二次偏移量得到二級基址,二級基址里保存的值+第一次偏移量才是游戲數據地址。

 

五:代碼實現部分,教程以注釋展現

只實現了技能點和金錢

#ifndef GAMEEDITOR_H
#define GAMEEDITOR_H

#include <QtWidgets/QWidget>
#include "ui_gameeditor.h"
//讀寫游戲內存所必須的頭文件
#include <windows.h>

class QTimer;
class GameEditor : public QWidget
{
	Q_OBJECT

public:
	GameEditor(QWidget *parent = 0);
	~GameEditor();

//slots就是表示槽函數
protected slots:
	void connectGame();
	void updateGameMoney();
	void updateGamePerks();

private:
	Ui::GameEditorClass ui;
	//計時器指針,見5-2-4
	QTimer* timer;
	//金錢地址
	DWORD moueyAdress;
	//技能點地址
	DWORD perksAdress;
	//進程PID
	DWORD pid;
	HWND hwnd;
	//進程句柄
	HANDLE handle;
};

#endif // GAMEEDITOR_H

 

  

#include "gameeditor.h"
//定時器頭文件
#include <QTimer>
//提示框頭文件
#include <QMessageBox>

GameEditor::GameEditor(QWidget *parent)
: QWidget(parent)
{
	ui.setupUi(this);
	pid = 0;
	hwnd = 0;
	handle = 0;
	//金錢一級基址
	moueyAdress = 0x009D5E2C;
	//技能點一級基址
	perksAdress = 0x009D5E2C;
	//創建一個定時器,見5-2-4
	timer = new QTimer;
	//設置時間間隔為1000毫秒
	timer->setInterval(1000);
	//timeout為計時器內置信號,時間一到自動發送
	//connect為關聯信號槽,詳細見前面的知識普及內容
	//connect(信號發送者指針,SIGNAL(信號), this, SLOT(槽實現函數));
	connect(timer, SIGNAL(timeout()), this, SLOT(connectGame()));
	//clicked為按鈕內置信號,點擊自動發送
	//還記得嗎,ui.pushButton是金錢修改按鈕,ui.pushButton_2是技能點修改按鈕
	connect(ui.pushButton, SIGNAL(clicked()), this, SLOT(updateGameMoney()));
	connect(ui.pushButton_2, SIGNAL(clicked()), this, SLOT(updateGamePerks()));
	ui.label_4->setText(QString::fromLocal8Bit("正在等待游戲程序...."));
	//計時器開始計時
	timer->start();
}

GameEditor::~GameEditor()
{

}

void GameEditor::connectGame()
{
	//查找窗口並返回窗口句柄
	hwnd = FindWindow(0, L"Mount&Blade Warband");

	if (!hwnd)
	{
		return;
	}

	//通過窗口句柄獲取pid
	GetWindowThreadProcessId(hwnd, &pid);

	if (!pid)
	{
		return;
	}

	//通過pid打開一個進程並獲取進程句柄
	handle = OpenProcess(PROCESS_ALL_ACCESS, false, pid);

	if (!handle)
	{
		return;
	}

	//通過金錢一級基址讀取里面的數據
	DWORD newMoneyAdress = 0;
	ReadProcessMemory(handle, (LPVOID)moueyAdress, &newMoneyAdress, sizeof(newMoneyAdress), 0);
	//讀取不到說明你開了游戲,但是沒有開始
	if (moueyAdress <= 0)
	{
		return;
	}
	ui.label_4->setText(QString::fromLocal8Bit("游戲程序連接成功!"));
	//停止計時器
	timer->stop();

	//金錢一級基址里的值+第二個偏移量=金錢二級基址
	moueyAdress = newMoneyAdress;
	moueyAdress += 0x140EC;
	//通過金錢二級基址讀取里面的數據
	ReadProcessMemory(handle, (LPVOID)moueyAdress, &newMoneyAdress, sizeof(newMoneyAdress), 0);

	//金錢二級基址里的值+第一個偏移量=游戲數據地址(動態),游戲數據地址里的值就是游戲數據
	moueyAdress = newMoneyAdress;
	moueyAdress += 0x5D0;

	//同理通過技能點一級基址和偏移量獲取游戲數據地址
	DWORD newPerksAdress = 0;
	ReadProcessMemory(handle, (LPVOID)perksAdress, &newPerksAdress, sizeof(newPerksAdress), 0);
	perksAdress = newPerksAdress;
	perksAdress += 0x140EC;
	ReadProcessMemory(handle, (LPVOID)perksAdress, &newPerksAdress, sizeof(newPerksAdress), 0);
	perksAdress = newPerksAdress;
	perksAdress += 0x2BC;
}

//金錢的修改按鈕被點擊
void GameEditor::updateGameMoney()
{
	//還記得嗎,ui.lineEdit就是金錢的文本輸入框,text()表示獲取輸入框的文本,toInt()表示轉化為int數據
	DWORD money = ui.lineEdit->text().toInt();
	//修改游戲數據地址里的值
	DWORD pref=WriteProcessMemory(handle, (LPVOID)moueyAdress, &money, sizeof(money), 0);
	//QString是Qt里的字符串
	QString informationStr;
	if (pref)
	{
		informationStr = QString::fromLocal8Bit("修改成功");
	}
	else
	{
		informationStr = QString::fromLocal8Bit("修改失敗");
	}
	//信息提示框,第一個參數是父窗口,填寫this或者0都可以,第二個參數是標題,第三個參數是提示內容
	QMessageBox::information(this, QString::fromLocal8Bit("提示"), informationStr);
}

//技能點的修改按鈕被點擊
void GameEditor::updateGamePerks()
{
	DWORD perks = ui.lineEdit_2->text().toInt();
	DWORD pref = WriteProcessMemory(handle, (LPVOID)perksAdress, &perks, sizeof(perks), 0);

	QString informationStr;
	if (pref)
	{
		informationStr = QString::fromLocal8Bit("修改成功");
	}
	else
	{
		informationStr = QString::fromLocal8Bit("修改失敗");
	}
	//信息提示框,第一個參數是父窗口,填寫this或者0都可以,第二個參數是標題,第三個參數是提示內容
	QMessageBox::information(this, QString::fromLocal8Bit("提示"), informationStr);
}

  

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM