聲明:本文只是為了初學C++的,能夠做出一些實用的東西,跳出管理系統的束縛,提升學習的興趣,在這里選取了單機游戲,請不要嘗試在線游戲,違發而已未必可行。
序:首先我們需要一個Qt+VS環境
Qt從http://download.qt.io/archive/中下載,第一個和第三個,在里面選擇對應版本。然后就是配環境了,這里提供2013+Qt5.5.1的環境配置,如果環境不同,請自行百度。這點解決問題能力都沒有,就別學C++了...
我的環境是2013+Qt5.5.1,不同版本可能略有差異,不過大同小異。
首先打開VS創建工程,點確定,然后一直下一步
選擇QWidget,點完成
然后就創建了一個工程,我們來說說這個工程。
一:main函數和C++的很像
然后再看看剛才創建的Qt窗口類
先是頭文件
然后是CPP文件
再看看一些“奇怪”的文件
下面這兩個是Qt的moc文件,是編譯時自動生成和更新的,所以不用管
下面第一個是資源文件的代碼CPP,自動生成的,不用管
第二個是UI文件的頭文件,自動生成的,不用管
資源文件,用來加載圖片等一些資源,這里沒用到,不用管
這個就是上面提到的UI文件,相當於可視化界面設計器,用來設計界面的。
雙擊點開XXXXX.UI文件
二:接下來開始界面設計
在控件盒子中左鍵選中一個文件標簽,一個文字輸入框,一個按鈕,然后往界面設計器里面拖。文件標簽在左,文字輸入框在中,一個按鈕在右。
ctrl+鼠標左鍵點選三個控件,然后在任意一個控件上右鍵,選擇布局->水平布局
開始界面布局
右鍵大窗口,選擇布局->垂直布局
然后鼠標放到界面設計器的邊框邊緣,按住左鍵拖動到合適大小
在對象查看器里左鍵點選大窗口,然后屬性窗口往下拖,在WindowTitle里修改窗口標題
雙擊控件修改控件的文本
記錄控件的對應關系,把金錢技能和屬性對應的輸入框和按鈕記錄下來
在這里我金錢的輸入框是lineEdit,金錢修改按鈕是pushButton,技能點則分別是lineEdit_2和pushButton_2,屬性點則是lineEdit_3和pushButton_3
然后點保存,注意一定要保存
三:實現前的知識普及
1:游戲內存修改的知識普及
一般游戲數據有一個地址值,但是這個地址值是動態的,每次游戲重啟都會發生變化,所以我們要找到不變的一級基址,和兩個不變的偏移量,來得到最新的游戲數據地址。
2:Qt信號槽知識普及
①Qt信號
信號是指一種通知,形象地比喻下:比如你帶了許多巧克力去公司,然后在群里告訴大家,“我帶了很多巧克力,要的來我工位拿”,這里公司群就是你的應用程序,群員就是程序里的實例化對象,你說的話這就是一種信號;可能有些人會無視,有些人根本沒看見,有些人會來要,有些人會轉告其他人,你只負責發出一個通知,你不關心別人看到你的通知會作何反應。
② Qt槽函數
槽指的是一種行為函數,定義了收到信號通知后,應該做出何種反應,上面巧克力的例子,無視,轉告和要巧克力,都是一種對於信號通知的響應行為。
③ Qt的connect函數
就是對信號和槽進行關聯,A發的信號通知B做出某種響應行為。
④ Qt的QTimer
定時器,按照你設置的時間間隔,不間斷發出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); }