遊戲外掛基本原理及實現
阿新 • • 發佈:2018-12-20
遊戲外掛已經深深地影響著眾多網路遊戲玩家,今天在網上看到了一些關於遊戲外掛編寫的技術,於是轉載上供大家參考 1、遊戲外掛的原理 外掛現在分為好多種,比如模擬鍵盤的,滑鼠的,修改資料包的,還有修改本地記憶體的,但好像沒有修改伺服器記憶體的哦,呵呵。其實修改伺服器也是有辦法的,只是技術太高一般人沒有辦法入手而已。(比如請GM去夜總會、送禮、收黑錢等等辦法都可以修改伺服器資料,哈哈) 修改遊戲無非是修改一下本地記憶體的資料,或者截獲API函式等等。這裡我把所能想到的方法都作一個介紹,希望大家能做出很好的外掛來使遊戲廠商更好的完善自己的技術。我見到一篇文章是講魔力寶貝的理論分析,寫得不錯,大概是那個樣子。下來我就講解一下技術方面的東西,以作引玉之用。 2 技術分析部分 2.1 模擬鍵盤或滑鼠的響應 我們一般使用: UINT SendInput( UINT nInputs, // count of input events LPINPUT pInputs, // array of input events int cbSize // size of structure ); API函式。第一個引數是說明第二個引數的矩陣的維數的,第二個引數包含了響應事件,這個自己填充就可以,最後是這個結構的大小,非常簡單,這是最簡單的方法模擬鍵盤滑鼠了,呵呵。注意,這個函式還有個替代函式: VOID keybd_event( BYTE bVk, // 虛擬鍵碼 BYTE bScan, // 掃描碼 DWORD dwFlags, ULONG_PTR dwExtraInfo // 附加鍵狀態 ); 與 VOID mouse_event( DWORD dwFlags, // motion and click options DWORD dx, // horizontal position or change DWORD dy, // vertical position or change DWORD dwData, // wheel movement ULONG_PTR dwExtraInfo // application-defined information ); 這兩個函式非常簡單了,我想那些按鍵精靈就是用的這個吧。上面的是模擬鍵盤,下面的是模擬滑鼠的。這個僅僅是模擬部分,要和遊戲聯絡起來我們還需要找到遊戲的窗口才行,或者包含快捷鍵,就象按鍵精靈的那個啟用鍵一樣,我們可以用GetWindow函式來列舉視窗,也可以用Findwindow函式來查詢制定的視窗(注意,還有一個FindWindowEx),FindwindowEx可以找到視窗的子視窗,比如按鈕,等什麼東西。當遊戲切換場景的時候我們可以用FindWindowEx來確定一些當前視窗的特徵,從而判斷是否還在這個場景,方法很多了,比如可以GetWindowInfo來確定一些東西,比如當查詢不到某個按鈕的時候就說明遊戲場景已經切換了,等等辦法。有的遊戲沒有控制元件在裡面,這是對影象做座標變換的話,這種方法就要受到限制了。這就需要我們用別的辦法來輔助分析了。 至於快捷鍵我們要用動態連線庫實現了,裡面要用到hook技術了,這個也非常簡單。大家可能都會了,其實就是一個全域性的hook物件然後SetWindowHook就可以了,回撥函式都是現成的,而且現在網上的例子多如牛毛。這個實現在外掛中已經很普遍了。如果還有誰不明白,那就去看看MSDN查詢SetWindowHook就可以了。 不要低估了這個動態連線庫的作用,它可以切入所有的程序空間,也就是可以載入到所有的遊戲裡面哦,只要用對,你會發現很有用途的。這個需要你複習一下Win32程式設計的基礎知識了。呵呵,趕快去看書吧。 2.2 截獲訊息 有些遊戲的響應機制比較簡單,是基於訊息的,或者用什麼定時器的東西。這個時候你就可以用攔截訊息來實現一些有趣的功能了。 我們攔截訊息使用的也是hook技術,裡面包括了鍵盤訊息,滑鼠訊息,系統訊息,日誌等,別的對我們沒有什麼大的用處,我們只用攔截訊息的回撥函式就可以了,這個不會讓我寫例子吧。其實這個和上面的一樣,都是用SetWindowHook來寫的,看看就明白了很簡單的。 至於攔截了以後做什麼就是你的事情了,比如在每個定時器訊息裡面處理一些我們的資料判斷,或者在定時器裡面在模擬一次定時器,那麼有些資料就會處理兩次,呵呵。後果嘛,不一定是好事情哦,呵呵,不過如果資料計算放在客戶端的遊戲就可以真的改變資料了,呵呵,試試看吧。用途還有很多,自己想也可以想出來的,呵呵。 2.3 攔截Socket包 這個技術難度要比原來的高很多。 首先我們要替換WinSock.DLL或者WinSock32.DLL,我們寫的替換函式要和原來的函式一致才行,就是說它的函式輸出什麼樣的,我們也要輸出什麼樣子的函式,而且引數,引數順序都要一樣才行,然後在我們的函式裡面呼叫真正的WinSock32.DLL裡面的函式就可以了。 首先:我們可以替換動態庫到系統路徑。 其次:我們應用程式啟動的時候可以載入原有的動態庫,用這個函式LoadLibary然後定位函式入口用GetProcAddress函式獲得每個真正Socket函式的入口地址。 當遊戲進行的時候它會呼叫我們的動態庫,然後從我們的動態庫中處理完畢後才跳轉到真正動態庫的函式地址,這樣我們就可以在裡面處理自己的資料了,應該是一切資料。呵呵,興奮吧,攔截了資料包我們還要分析之後才能進行正確的應答,不要以為這樣工作就完成了,還早呢。等分析完畢以後我們還要模擬應答機制來和伺服器通訊,一個不小心就會被封號。 分析資料才是工作量的來源呢,遊戲每次升級有可能加密方式會有所改變,因此我們寫外掛的人都是亡命之徒啊,被人愚弄了還不知道。 2.4 截獲API 上面的技術如果可以靈活運用的話我們就不用截獲API函數了,其實這種技術是一種補充技術。比如我們需要截獲Socket以外的函式作為我們的用途,我們就要用這個技術了,其實我們也可以用它直接攔截在Socket中的函式,這樣更直接。 現在攔截API的教程到處都是,我就不列舉了,我用的比較習慣的方法是根據輸入節進行攔截的,這個方法可以用到任何一種作業系統上,比如Windows 98/2000等,有些方法不是跨平臺的,我不建議使用。這個技術大家可以參考《Windows核心程式設計》裡面的545頁開始的內容來學習,如果是Win98系統可以用“Windows系統奧祕”那個最後一章來學習。 網路遊戲外掛編寫基礎①要想在修改遊戲中做到百戰百勝,是需要相當豐富的計算機知識的。有很多計算機高手就是從玩遊戲,修改遊戲中,逐步對計算機產生濃厚的興趣,逐步成長起來的。不要在羨慕別人能夠做到的,因為別人能夠做的你也能夠!我相信你們看了本教程後,會對遊戲有一個全新的認識,呵呵,因為我是個好老師!(別拿雞蛋砸我呀,救命啊!#¥%……*) 不過要想從修改遊戲中學到知識,增加自己的計算機水平,可不能只是靠修改遊戲呀! 要知道,修改遊戲只是一個驗證你對你所瞭解的某些計算機知識的理解程度的場所,只能給你一些發現問題、解決問題的機會,只能起到幫助你提高學習計算機的興趣的作用,而決不是學習計算機的捷徑。 一:什麼叫外掛? 現在的網路遊戲多是基於Internet上客戶/伺服器模式,服務端程式執行在遊戲伺服器上,遊戲的設計者在其中創造一個龐大的遊戲空間,各地的玩家可以通過執行客戶端程式同時登入到遊戲中。簡單地說,網路遊戲實際上就是由遊戲開發商提供一個遊戲環境,而玩家們就是在這個環境中相對自由和開放地進行遊戲操作。那麼既然在網路遊戲中有了伺服器這個概念,我們以前傳統的修改遊戲方法就顯得無能為力了。記得我們在單機版的遊戲中,隨心所欲地通過記憶體搜尋來修改角色的各種屬性,這在網路遊戲中就沒有任何用處了。因為我們在網路遊戲中所扮演角色的各種屬性及各種重要資料都存放在伺服器上,在我們自己機器上(客戶端)只是顯示角色的狀態,所以通過修改客戶端記憶體裡有關角色的各種屬性是不切實際的。那麼是否我們就沒有辦法在網路遊戲中達到我們修改的目的?回答是"否"。 我們知道Internet客戶/伺服器模式的通訊一般採用TCP/IP通訊協議,資料交換是通過IP資料包的傳輸來實現的,一般來說我們客戶端向伺服器發出某些請求,比如移動、戰鬥等指令都是通過封包的形式和伺服器交換資料。那麼我們把本地發出訊息稱為SEND,意思就是傳送資料,伺服器收到我們SEND的訊息後,會按照既定的程式把有關的資訊反饋給客戶端,比如,移動的座標,戰鬥的型別。那麼我們把客戶端收到伺服器發來的有關訊息稱為RECV。知道了這個道理,接下來我們要做的工作就是分析客戶端和伺服器之間往來的資料(也就是封包),這樣我們就可以提取到對我們有用的資料進行修改,然後模擬伺服器發給客戶端,或者模擬客戶端傳送給伺服器,這樣就可以實現我們修改遊戲的目的了。 目前除了修改遊戲封包來實現修改遊戲的目的,我們也可以修改客戶端的有關程式來達到我們的要求。我們知道目前各個伺服器的運算能力是有限的,特別在遊戲中,遊戲伺服器要計算遊戲中所有玩家的狀況幾乎是不可能的,所以有一些運算還是要依靠我們客戶端來完成,這樣又給了我們修改遊戲提供了一些便利。比如我們可以通過將客戶端程式脫殼來發現一些程式的判斷分支,通過跟蹤除錯我們可以把一些對我們不利的判斷去掉,以此來滿足我們修改遊戲的需求。 在下幾個章節中,我們將給大家講述封包的概念,和修改跟蹤客戶端的有關知識。大家準備好了嗎? 遊戲資料格式和儲存: 在進行我們的工作之前,我們需要掌握一些關於計算機中儲存資料方式的知識和遊戲中儲存資料的特點。本章節是提供給菜鳥級的玩家看的,如果你是高手就可以跳過了,如果,你想成為無堅不摧的劍客,那麼,這些東西就會花掉你一些時間;如果,你只想作個江湖的遊客的話,那麼這些東西,瞭解與否無關緊要。是作劍客,還是作遊客,你選擇吧! 現在我們開始!首先,你要知道遊戲中儲存資料的幾種格式,這幾種格式是:位元組(BYTE)、字(WORD)和雙字(DOUBLE WORD),或者說是8位、16位和32位儲存方式。位元組也就是8位方式能儲存0~255的數字;字或說是16位儲存方式能儲存0~65535的數;雙字即32位方式能儲存0~4294967295的數。 為何要了解這些知識呢?在遊戲中各種引數的最大值是不同的,有些可能100左右就夠了,比如,金庸群俠傳中的角色的等級、隨機遇敵個數等等。而有些卻需要大於255甚至大於65535,象金庸群俠傳中角色的金錢值可達到數百萬。所以,在遊戲中各種不同的資料的型別是不一樣的。在我們修改遊戲時需要尋找準備修改的資料的封包,在這種時候,正確判斷資料的型別是迅速找到正確地址的重要條件。 在計算機中資料以位元組為基本的儲存單位,每個位元組被賦予一個編號,以確定各自的位置。這個編號我們就稱為地址。 在需要用到字或雙字時,計算機用連續的兩個位元組來組成一個字,連續的兩個字組成一個雙字。而一個字或雙字的地址就是它們的低位位元組的地址。 現在我們常用的Windows 9x作業系統中,地址是用一個32位的二進位制數表示的。而在平時我們用到記憶體地址時,總是用一個8位的16進位制數來表示它。 二進位制和十六進位制又是怎樣一回事呢? 簡單說來,二進位制數就是一種只有0和1兩個數碼,每滿2則進一位的計數進位法。同樣,16進位制就是每滿十六就進一位的計數進位法。16進位制有0--F十六個數字,它為表示十到十五的數字採用了A、B、C、D、E、F六個數字,它們和十進位制的對應關係是:A對應於10,B對應於11,C對應於12,D對應於13,E對應於14,F對應於15。而且,16進位制數和二進位制數間有一個簡單的對應關係,那就是;四位二進位制數相當於一位16進位制數。比如,一個四位的二進位制數1111就相當於16進位制的F,1010就相當於A。 瞭解這些基礎知識對修改遊戲有著很大的幫助,下面我就要談到這個問題。由於在計算機中資料是以二進位制的方式儲存的,同時16進位制數和二進位制間的轉換關係十分簡單,所以大部分的修改工具在顯示計算機中的資料時會顯示16進位制的程式碼,而且在你修改時也需要輸入16進位制的數字。你清楚了吧? 在遊戲中看到的資料可都是十進位制的,在要尋找並修改引數的值時,可以使用Windows提供的計算器來進行十進位制和16進位制的換算,我們可以在開始選單裡的程式組中的附件中找到它。 現在要了解的知識也差不多了!不過,有個問題在遊戲修改中是需要注意的。在計算機中資料的儲存方式一般是低位數儲存在低位位元組,高位數儲存在高位位元組。比如,十進位制數41715轉換為16進位制的數為A2F3,但在計算機中這個數被存為F3A2。 看了以上內容大家對資料的存貯和資料的對應關係都瞭解了嗎? 好了,接下來我們要告訴大家在遊戲中,封包到底是怎麼一回事了,來!大家把袖口捲起來,讓我們來幹活吧! 二:什麼是封包? 怎麼截獲一個遊戲的封包?怎麼去檢查遊戲伺服器的ip地址和埠號? Internet使用者使用的各種資訊服務,其通訊的資訊最終均可以歸結為以IP包為單位的資訊傳送,IP包除了包括要傳送的資料資訊外,還包含有資訊要傳送到的目的IP地址、資訊傳送的源IP地址、以及一些相關的控制資訊。當一臺路由器收到一個IP資料包時,它將根據資料包中的目的IP地址項查詢路由表,根據查詢的結果將此IP資料包送往對應埠。下一臺IP路由器收到此資料包後繼續轉發,直至發到目的地。路由器之間可以通過路由協議來進行路由資訊的交換,從而更新路由表。 那麼我們所關心的內容只是IP包中的資料資訊,我們可以使用許多監聽網路的工具來截獲客戶端與伺服器之間的交換資料,下面就向你介紹其中的一種工具:WPE。 WPE使用方法:執行WPE會有下列幾項功能可選擇: SELECT GAME選擇目前在記憶體中您想攔截的程式,您只需雙擊該程式名稱即可。 TRACE追蹤功能。用來追蹤擷取程式送收的封包。WPE必須先完成點選欲追蹤的程式名稱,才可以使用此專案。 按下Play鍵開始擷取程式收送的封包。您可以隨時按下 | | 暫停追蹤,想繼續時請再按下 | | 。按下正方形可以停止擷取封包並且顯示所有已擷取封包內容。若您沒按下正方形停止鍵,追蹤的動作將依照OPTION裡的設定值自動停止。如果您沒有擷取到資料,試試將OPTION裡調整為Winsock Version 2。WPE 及 Trainers 是設定在顯示至少16 bits 顏色下才可執行。 FILTER過濾功能。用來分析所擷取到的封包,並且予以修改。 SEND PACKET送出封包功能。能夠讓您送出假造的封包。 TRAINER MAKER製作修改器。 OPTIONS設定功能。讓您調整WPE的一些設定值。 FILTER的詳細教學 - 當FILTER在啟動狀態時 ,ON的按鈕會呈現紅色。- 當您啟動FILTER時,您隨時可以關閉這個視窗。FILTER將會保留在原來的狀態,直到您再按一次 on / off 鈕。- 只有FILTER啟用鈕在OFF的狀態下,才可以勾選Filter前的方框來編輯修改。- 當您想編輯某個Filter,只要雙擊該Filter的名字即可。 NORMAL MODE: 範例: 當您在 Street Fighter Online ﹝快打旋風線上版?#123;遊戲中,您使用了兩次火球而且擊中了對方,這時您會擷取到以下的封包:SEND-> 0000 08 14 21 06 01 04 SEND-> 0000 02 09 87 00 67 FF A4 AA 11 22 00 00 00 00 SEND-> 0000 03 84 11 09 11 09 SEND-> 0000 0A 09 C1 10 00 00 FF 52 44 SEND-> 0000 0A 09 C1 10 00 00 66 52 44 您的第一個火球讓對方減了16滴﹝16 = 10h?#123;的生命值,而您觀察到第4跟第5個封包的位置4有10h的值出現,應該就是這裡了。 您觀察10h前的0A 09 C1在兩個封包中都沒改變,可見得這3個數值是發出火球的關鍵。 因此您將0A 09 C1 10填在搜尋列﹝SEARCH?#123;,然後在修改列﹝MODIFY?#123;的位置4填上FF。如此一來,當您再度發出火球時,FF會取代之前的10,也就是攻擊力為255的火球了! ADVANCED MODE: 範例: 當您在一個遊戲中,您不想要用真實姓名,您想用修改過的假名傳送給對方。在您使用TRACE後,您會發現有些封包裡面有您的名字出現。假設您的名字是Shadow,換算成16進位則是﹝53 68 61 64 6F 77?#123;;而您打算用moon﹝6D 6F 6F 6E 20 20?#123;來取代他。1) SEND-> 0000 08 14 21 06 01 042) SEND-> 0000 01 06 99 53 68 61 64 6F 77 00 01 05 3) SEND-> 0000 03 84 11 09 11 094) SEND-> 0000 0A 09 C1 10 00 53 68 61 64 6F 77 00 11 5) SEND-> 0000 0A 09 C1 10 00 00 66 52 44 但是您仔細看,您的名字在每個封包中並不是出現在相同的位置上 - 在第2個封包裡,名字是出現在第4個位置上- 在第4個封包裡,名字是出現在第6個位置上 在這種情況下,您就需要使用ADVANCED MODE- 您在搜尋些zSEARCH?#123;填上:53 68 61 64 6F 77 ﹝請務必從位置1開始填?#123;- 您想要從原來名字Shadow的第一個字母開始置換新名字,因此您要選擇從數值被發現的位置開始替代連續數值﹝from the position of the chain found?#123;。- 現在,在修改列﹝MODIFY?#123;000的位置填上:6D 6F 6F 6E 20 20 ﹝此為相對應位置,也就是從原來搜尋欄的+001位置開始遞換?#123;- 如果您想從封包的第一個位置就修改數值,請選擇﹝from the beginning of the packet?#123; 瞭解一點TCP/IP協議常識的人都知道,網際網路是將資訊資料打包之後再傳送出去的。每個資料包分為頭部資訊和資料資訊兩部分。頭部資訊包括資料包的傳送地址和到達地址等。資料資訊包括我們在遊戲中相關操作的各項資訊。那麼在做截獲封包的過程之前我們先要知道遊戲伺服器的IP地址和埠號等各種資訊,實際上最簡單的是看看我們遊戲目錄下,是否有一個SERVER.INI的配置檔案,這個檔案裡你可以檢視到個遊戲伺服器的IP地址,比如金庸群俠傳就是如此,那麼除了這個我們還可以在DOS下使用NETSTAT這個命令, NETSTAT命令的功能是顯示網路連線、路由表和網路介面資訊,可以讓使用者得知目前都有哪些網路連線正在運作。或者你可以使用木馬客星等工具來檢視網路連線。工具是很多的,看你喜歡用哪一種了。 NETSTAT命令的一般格式為:NETSTAT [選項] 命令中各選項的含義如下:-a 顯示所有socket,包括正在監聽的。-c 每隔1秒就重新顯示一遍,直到使用者中斷它。-i 顯示所有網路介面的資訊。-n 以網路IP地址代替名稱,顯示出網路連線情形。-r 顯示核心路由表,格式同"route -e"。-t 顯示TCP協議的連線情況。-u 顯示UDP協議的連線情況。-v 顯示正在進行的工作。 網路遊戲外掛編寫基礎②三:怎麼來分析我們截獲的封包? 首先我們將WPE截獲的封包儲存為文字檔案,然後開啟它,這時會看到如下的資料(這裡我們以金庸群俠傳裡PK店小二客戶端傳送的資料為例來講解): 第一個檔案:SEND-> 0000 E6 56 0D 22 7E 6B E4 17 13 13 12 13 12 13 67 1BSEND-> 0010 17 12 DD 34 12 12 12 12 17 12 0E 12 12 12 9BSEND-> 0000 E6 56 1E F1 29 06 17 12 3B 0E 17 1ASEND-> 0000 E6 56 1B C0 68 12 12 12 5ASEND-> 0000 E6 56 02 C8 13 C9 7E 6B E4 17 10 35 27 13 12 12SEND-> 0000 E6 56 17 C9 12 第二個檔案:SEND-> 0000 83 33 68 47 1B 0E 81 72 76 76 77 76 77 76 02 7ESEND-> 0010 72 77 07 1C 77 77 77 77 72 77 72 77 77 77 6DSEND-> 0000 83 33 7B 94 4C 63 72 77 5E 6B 72 F3SEND-> 0000 83 33 7E A5 21 77 77 77 3FSEND-> 0000 83 33 67 AD 76 CF 1B 0E 81 72 75 50 42 76 77 77SEND-> 0000 83 33 72 AC 77 我們發現兩次PK店小二的資料格式一樣,但是內容卻不相同,我們是PK的同一個NPC,為什麼會不同呢? 原來金庸群俠傳的封包是經過了加密運算才在網路上傳輸的,那麼我們面臨的問題就是如何將密文解密成明文再分析了。 因為一般的資料包加密都是異或運算,所以這裡先講一下什麼是異或。 簡單的說,異或就是"相同為0,不同為1"(這是針對二進位制按位來講的),舉個例子,0001和0010異或,我們按位對比,得到異或結果是0011,計算的方法是:0001的第4位為0,0010的第4位為0,它們相同,則異或結果的第4位按照"相同為0,不同為1"的原則得到0,0001的第3位為0,0010的第3位為0,則異或結果的第3位得到0,0001的第2位為0,0010的第2位為1,則異或結果的第2位得到1,0001的第1位為1,0010的第1位為0,則異或結果的第1位得到1,組合起來就是0011。異或運算今後會遇到很多,大家可以先熟悉熟悉,熟練了對分析很有幫助的。 下面我們繼續看看上面的兩個檔案,按照常理,資料包的資料不會全部都有值的,遊戲開發時會預留一些位元組空間來便於日後的擴充,也就是說資料包裡會存在一些"00"的位元組,觀察上面的檔案,我們會發現檔案一里很多"12",檔案二里很多"77",那麼這是不是代表我們說的"00"呢?推理到這裡,我們就開始行動吧! 我們把檔案一與"12"異或,檔案二與"77"異或,當然用手算很費事,我們使用"M2M 1.0 加密封包分析工具"來計算就方便多了。得到下面的結果: 第一個檔案:1 SEND-> 0000 F4 44 1F 30 6C 79 F6 05 01 01 00 01 00 01 75 09SEND-> 0010 05 00 CF 26 00 00 00 00 05 00 1C 00 00 00 892 SEND-> 0000 F4 44 0C E3 3B 13 05 00 29 1C 05 083 SEND-> 0000 F4 44 09 D2 7A 00 00 00 484 SEND-> 0000 F4 44 10 DA 01 DB 6C 79 F6 05 02 27 35 01 00 005 SEND-> 0000 F4 44 05 DB 00 第二個檔案:1 SEND-> 0000 F4 44 1F 30 6C 79 F6 05 01 01 00 01 00 01 75 09SEND-> 0010 05 00 70 6B 00 00 00 00 05 00 05 00 00 00 1A2 SEND-> 0000 F4 44 0C E3 3B 13 05 00 29 1C 05 843 SEND-> 0000 F4 44 09 D2 56 00 00 00 484 SEND-> 0000 F4 44 10 DA 01 B8 6C 79 F6 05 02 27 35 01 00 005 SEND-> 0000 F4 44 05 DB 00 哈,這一下兩個檔案大部分都一樣啦,說明我們的推理是正確的,上面就是我們需要的明文! 接下來就是搞清楚一些關鍵的位元組所代表的含義,這就需要截獲大量的資料來分析。 首先我們會發現每個資料包都是"F4 44"開頭,第3個位元組是變化的,但是變化很有規律。我們來看看各個包的長度,發現什麼沒有?對了,第3個位元組就是包的長度! 通過截獲大量的資料包,我們判斷第4個位元組代表指令,也就是說客戶端告訴伺服器進行的是什麼操作。例如向伺服器請求戰鬥指令為"30",戰鬥中移動指令為"D4"等。 接下來,我們就需要分析一下上面第一個包"F4 44 1F 30 6C 79 F6 05 01 01 00 01 00 01 75 09 05 00 CF 26 00 00 00 00 05 00 1C 00 00 00 89",在這個包裡包含什麼資訊呢?應該有通知伺服器你PK的哪個NPC吧,我們就先來找找這個店小二的程式碼在什麼地方。 我們再PK一個小嘍羅(就是大理客棧外的那個咯):SEND-> 0000 F4 44 1F 30 D4 75 F6 05 01 01 00 01 00 01 75 09SEND-> 0010 05 00 8A 19 00 00 00 00 11 00 02 00 00 00 C0 我們根據常理分析,遊戲裡的NPC種類雖然不會超過65535(FFFF),但開發時不會把自己限制在字的範圍,那樣不利於遊戲的擴充,所以我們在雙字裡看看。通過"店小二"和"小嘍羅"兩個包的對比,我們把目標放在"6C 79 F6 05"和"CF 26 00 00"上。(對比一下很容易的,但你不能太遲鈍咯,呵呵)我們再看看後面的包,在後面的包裡應該還會出現NPC的程式碼,比如移動的包,遊戲允許觀戰,伺服器必然需要知道NPC的移動座標,再廣播給觀戰的其他玩家。在後面第4個包"SEND-> 0000 F4 44 10 DA 01 DB 6C 79 F6 05 02 27 35 01 00 00"裡我們又看到了"6C 79 F6 05",初步斷定店小二的程式碼就是它了!(這分析裡邊包含了很多工作的,大家可以用WPE截下資料來自己分析分析) 第一個包的分析暫時就到這裡(裡面還有的資訊我們暫時不需要完全清楚了) 我們看看第4個包"SEND-> 0000 F4 44 10 DA 01 DB 6C 79 F6 05 02 27 35 01 00 00",再截獲PK黃狗的包,(狗會出來2只哦)看看包的格式:SEND-> 0000 F4 44 1A DA 02 0B 4B 7D F6 05 02 27 35 01 00 00SEND-> 0010 EB 03 F8 05 02 27 36 01 00 00 根據上面的分析,黃狗的程式碼為"4B 7D F6 05"(100040011),不過兩隻黃狗伺服器怎樣分辨呢?看看"EB 03 F8 05"(100140011),是上一個程式碼加上100000,呵呵,這樣伺服器就可以認出兩隻黃狗了。我們再通過野外遇敵截獲的資料包來證實,果然如此。 那麼,這個包的格式應該比較清楚了:第3個位元組為包的長度,"DA"為指令,第5個位元組為NPC個數,從第7個位元組開始的10個位元組代表一個NPC的資訊,多一個NPC就多10個位元組來表示。 大家如果玩過網金,必然知道隨機遇敵有時會出現增援,我們就利用遊戲這個增援來讓每次戰鬥都會出現增援的NPC吧。 通過在戰鬥中出現增援截獲的資料包,我們會發現伺服器端傳送了這樣一個包:F4 44 12 E9 EB 03 F8 05 02 00 00 03 00 00 00 00 00 00 第5-第8個位元組為增援NPC的程式碼(這裡我們就簡單的以黃狗的程式碼來舉例)。 那麼,我們就利用單機代理技術來同時欺騙客戶端和伺服器吧! 好了,呼叫NPC的工作到這裡算是完成了一小半,接下來的事情,怎樣修改封包和傳送封包,我們下節繼續講解吧。 四:怎麼冒充"客戶端"向"伺服器"發我們需要的封包? 這裡我們需要使用一個工具,它位於客戶端和伺服器端之間,它的工作就是進行資料包的接收和轉發,這個工具我們稱為代理。如果代理的工作單純就是接收和轉發的話,這就毫無意義了,但是請注意:所有的資料包都要通過它來傳輸,這裡的意義就重大了。我們可以分析接收到的資料包,或者直接轉發,或者修改後轉發,或者壓住不轉發,甚至偽造我們需要的封包來發送。 下面我們繼續講怎樣來同時欺騙伺服器和客戶端,也就是修改封包和偽造封包。 通過我們上節的分析,我們已經知道了打多個NPC的封包格式,那麼我們就動手吧! 首先我們要查詢客戶端傳送的包,找到戰鬥的特徵,就是請求戰鬥的第1個包,我們找"F4 44 1F 30"這個特徵,這是不會改變的,當然是要解密後來查詢哦。 找到後,表示客戶端在向伺服器請求戰鬥,我們不動這個包,轉發。 繼續向下查詢,這時需要查詢的特徵碼不太好辦,我們先查詢"DA",這是客戶端傳送NPC資訊的資料包的指令,那麼可能其他包也有"DA",沒關係,我們看前3個位元組有沒有"F4 44"就行了。找到後,我們的工作就開始了! 我們確定要打的NPC數量。這個數量不能很大,原因在於網金的封包長度用一個位元組表示,那麼一個包可以有255個位元組,我們上面分析過,增加一個NPC要增加10個位元組,所以大家算算就知道,打20個NPC比較合適。 然後我們要把客戶端原來的NPC程式碼分析計算出來,因為增加的NPC程式碼要加上100000哦。再把我們增加的NPC程式碼計算出來,並且組合成新的封包,注意代表包長度的位元組要修改啊,然後轉發到伺服器,這一步在編寫程式的時候要注意演算法,不要造成較大延遲。 上面我們欺騙伺服器端完成了,欺騙客戶端就簡單了。 傳送了上面的封包後,我們根據新增NPC程式碼構造封包馬上發給客戶端,格式就是"F4 44 12 E9 NPC程式碼 02 00 00 03 00 00 00 00 00 00",把每個新增的NPC都構造這樣一個包,按順序連在一起傳送給客戶端,客戶端也就被我們騙過了,很簡單吧。 以後戰鬥中其他的事我們就不管了,盡情地開打吧。 網路遊戲通訊模型初探①序言 網路遊戲,作為遊戲與網路有機結合的產物,把玩家帶入了新的娛樂領域。網路遊戲在中國開始發展至今也僅有3,4年的歷史,跟已經擁有幾十年開發歷史的單機遊戲相比,網路遊戲還是非常年輕的。當然,它的形成也是根據歷史變化而產生的可以說沒有網際網路的興起,也就沒有網路遊戲的誕生。作為新興產物,網路遊戲的開發對廣大開發者來說更加神祕,對於一個未知領域,開發者可能更需要了解的是網路遊戲與普通單機遊戲有何區別,網路遊戲如何將玩家們連線起來,以及如何為玩家提供一個互動的娛樂環境。本文就將圍繞這三個主題來給大家講述一下網路遊戲的網路互連實現方法。 網路遊戲與單機遊戲 說到網路遊戲,不得不讓人聯想到單機遊戲,實際上網路遊戲的實質脫離不了單機遊戲的製作思想,網路遊戲和單機遊戲的差別大家可以很直接的想到:不就是可以多人連線嗎?沒錯,但如何實現這些功能,如何把網路連線合理的融合進單機遊戲,就是我們下面要討論的內容。在瞭解網路互連具體實現之前,我們先來了解一下單機與網路遊戲它們各自的執行流程,只有瞭解這些,你才能深入網路遊戲開發的核心。現在先讓我們來看一下普通單機遊戲的簡化執行流程:Initialize() // 初始化模組{ 初始化遊戲資料;}Game() // 遊戲迴圈部分{ 繪製遊戲場景、人物以及其它元素; 獲取使用者操作輸入; switch( 使用者輸入資料) { case 移動: { 處理人物移動; } break; case 攻擊: { 處理攻擊邏輯: } break; ... 其它處理響應; ... default: break; } 遊戲的NPC等邏輯AI處理;}Exit() // 遊戲結束{ 釋放遊戲資料; 離開遊戲;} 我們來說明一下上面單機遊戲的流程。首先,不管是遊戲軟體還是其他應用軟體,初始化部分必不可少,這裡需要對遊戲的資料進行初始化,包括影象、聲音以及一些必備的資料。接下來,我們的遊戲對場景、人物以及其他元素進行迴圈繪製,把遊戲世界展現給玩家,同時接收玩家的輸入操作,並根據操作來做出響應,此外,遊戲還需要對NPC以及一些邏輯AI進行處理。最後,遊戲資料被釋放,遊戲結束。 網路遊戲與單機遊戲有一個很顯著的差別,就是網路遊戲除了一個供操作遊戲的使用者介面平臺(如單機遊戲)外,還需要一個用於連線所有使用者,併為所有使用者提供資料服務的伺服器,從某些角度來看,遊戲伺服器就像一個大型的資料庫,提供資料以及資料邏輯互動的功能。讓我們來看看一個簡單的網路遊戲模型執行流程: 客戶機:Login()// 登入模組{ 初始化遊戲資料; 獲取使用者輸入的使用者和密碼; 與伺服器建立網路連線; 傳送至伺服器進行使用者驗證; ... 等待伺服器確認訊息; ... 獲得伺服器反饋的登入訊息; if( 成立 ) 進入遊戲; else 提示使用者登入錯誤並重新接受使用者登入;}Game()// 遊戲迴圈部分{ 繪製遊戲場景、人物以及其它元素; 獲取使用者操作輸入; 將使用者的操作傳送至伺服器; ... 等待伺服器的訊息; ... 接收伺服器的反饋資訊; switch( 伺服器反饋的訊息資料 ) { case 本地玩家移動的訊息: { if( 允許本地玩家移動 ) 客戶機處理人物移動; else 客戶機保持原有狀態; } break; case 其他玩家/NPC的移動訊息: { 根據伺服器的反饋資訊進行其他玩家或者NPC的移動處理; } break; case 新玩家加入遊戲: { 在客戶機中新增顯示此玩家; } break; case 玩家離開遊戲: { 在客戶機中銷燬此玩家資料; } break; ... 其它訊息型別處理; ... default: break; }}Exit()// 遊戲結束{ 傳送離開訊息給伺服器; ... 等待伺服器確認; ... 得到伺服器確認訊息; 與伺服器斷開連線; 釋放遊戲資料; 離開遊戲;} 伺服器: Listen() // 遊戲伺服器等待玩家連線模組{ ... 等待使用者的登入資訊; ... 接收到使用者登入資訊; 分析使用者名稱和密碼是否符合; if( 符合 ) { 傳送確認允許進入遊戲訊息給客戶機; 把此玩家進入遊戲的訊息釋出給場景中所有玩家; 把此玩家新增到伺服器場景中; } else { 斷開與客戶機的連線; }}Game() // 遊戲伺服器迴圈部分{ ... 等待場景中玩家的操作輸入; ... 接收到某玩家的移動輸入或NPC的移動邏輯輸入; // 此處只以移動為例 進行此玩家/NPC在地圖場景是否可移動的邏輯判斷; if( 可移動 ) { 對此玩家/NPC進行伺服器移動處理; 傳送移動訊息給客戶機; 傳送此玩家的移動訊息給場景上所有玩家; } else 傳送不可移動訊息給客戶機; }Exit() // 遊戲服務=器結束{ 接收到玩家離開訊息; 將此訊息傳送給場景中所有玩家; 傳送允許離開的資訊; 將玩家資料存入資料庫; 登出此玩家在伺服器記憶體中的資料;}} 讓我們來說明一下上面簡單網路遊戲模型的執行機制。先來講講伺服器端,這裡伺服器端分為三個部分(實際上一個完整的網路遊戲遠不止這些):登入模組、遊戲模組和登出模組。登入模組用於監聽網路遊戲客戶端傳送過來的網路連線訊息,並且驗證其合法性,然後在伺服器中建立這個玩家並且把玩家帶領到遊戲模組中; 遊戲模組則提供給玩家使用者實際的應用服務,我們在後面會詳細介紹這個部分; 在得到玩家要離開遊戲的訊息後,登出模組則會把玩家從伺服器中刪除,並且把玩家的屬性資料儲存到伺服器資料庫中,如: 經驗值、等級、生命值等。 接下來讓我們看看網路遊戲的客戶端。這時候,客戶端不再像單機遊戲一樣,初始化資料後直接進入遊戲,而是在與伺服器建立連線,並且獲得許可的前提下才進入遊戲。除此之外,網路遊戲的客戶端遊戲程序需要不斷與伺服器進行通訊,通過與伺服器交換資料來確定當前遊戲的狀態,例如其他玩家的位置變化、物品掉落情況。同樣,在離開遊戲時,客戶端會向伺服器告知此玩家使用者離開,以便於伺服器做出相應處理。以上用簡單的虛擬碼給大家闡述了單機遊戲與網路遊戲的執行流程,大家應該可以清楚看出兩者的差別,以及兩者間相互的關係。我們可以換個角度考慮,網路遊戲就是把單機遊戲的邏輯運算部分搬移到遊戲伺服器中進行處理,然後把處理結果(包括其他玩家資料)通過遊戲伺服器返回給連線的玩家。 網路互連 在瞭解了網路遊戲基本形態之後,讓我們進入真正的實際應用部分。首先,作為網路遊戲,除了常規的單機遊戲所必需的東西之外,我們還需要增加一個網路通訊模組,當然,這也是網路遊戲較為主要的部分,我們來討論一下如何實現網路的通訊模組。 一個完善的網路通訊模組涉及面相當廣,本文僅對較為基本的處理方式進行討論。網路遊戲是由客戶端和伺服器組成,相應也需要兩種不同的網路通訊處理方式,不過也有相同之處,我們先就它們的共同點來進行介紹。我們這裡以Microsoft Windows 2000 [2000 Server]作為開發平臺,並且使用Winsock作為網路介面(可能一些朋友會考慮使用DirectPlay來進行網路通訊,不過對於當前線上遊戲,DirectPlay並不適合,具體原因這裡就不做討論了)。 確定好平臺與介面後,我們開始進行網路連線建立之前的一些必要的初始化工作,這部分無論是客戶端或者伺服器都需要進行。讓我們看看下面的程式碼片段: WORD wVersionRequested; WSADATAwsaData; wVersionRequested MAKEWORD(1, 1); if( WSAStartup( wVersionRequested, &wsaData ) !0 ){ Failed( WinSock Version Error!" );} 上面通過呼叫Windows的socket API函式來初始化網路裝置,接下來進行網路Socket的建立,程式碼片段如下:SOCKET sSocket socket( AF_INET, m_lProtocol, 0 ); if( sSocket == INVALID_SOCKET ) { Failed( "WinSocket Create Error!" );} 這裡需要說明,客戶端和服務端所需要的Socket連線數量是不同的,客戶端只需要一個Socket連線足以滿足遊戲的需要,而服務端必須為每個玩家使用者建立一個用於通訊的Socket連線。當然,並不是說如果伺服器上沒有玩家那就不需要建立Socket連線,伺服器端在啟動之時會生成一個特殊的Socket用來對玩家建立與伺服器連線的請求進行響應,等介紹網路監聽部分後會有更詳細說明。 有初始化與建立必然就有釋放與刪除,讓我們看看下面的釋放部分:if( sSocket != INVALID_SOCKET ) { closesocket( sSocket );}if( WSACleanup() != 0 ){ Warning( "Can't release Winsocket" ); } 這裡兩個步驟分別對前面所作的建立初始化進行了相應釋放。 接下來看看伺服器端的一個網路執行處理,這裡我們假設伺服器端已經建立好一個Socket供使用,我們要做的就是讓這個Socket變成監聽網路連線請求的專用介面,看看下面程式碼片段: SOCKADDR_IN addr;memset( &addr, 0, sizeof(addr) );addr.sin_family = AF_INET;addr.sin_addr.s_addr = htonl( INADDR_ANY );addr.sin_port = htons( Port ); // Port為要監聽的埠號// 繫結socketif( bind( sSocket, (SOCKADDR*)&addr, sizeof(addr) ) == SOCKET_ERROR ){ Failed( "WinSocket Bind Error!");}// 進行監聽if( listen( sSocket, SOMAXCONN ) == SOCKET_ERROR ){ Failed( "WinSocket Listen Error!");} 這裡使用的是阻塞式通訊處理,此時程式將處於等待玩家使用者連線的狀態,倘若這時候有客戶端連線進來,則通過accept()來建立針對此玩家使用者的Socket連線,程式碼片段如下: sockaddraddrServer; int nLen sizeof( addrServer ); SOCKET sPlayerSocket accept( sSocket, &addrServer, &nLen ); if( sPlayerSocket == INVALID_SOCKET ){ Failed( "WinSocket Accept Error!"); } 這裡我們建立了sPlayerSocket連線,此後遊戲伺服器與這個玩家使用者的通訊全部通過此Socket進行,到這裡為止,我們伺服器已經有了接受玩家使用者連線的功能,現在讓我們來看看遊戲客戶端是如何連線到遊戲伺服器上,程式碼片段如下: SOCKADDR_IN addr;memset( &addr, 0, sizeof(addr) );addr.sin_family = AF_INET;// 要連線的遊戲伺服器埠號addr.sin_addr.s_addr = inet_addr( IP );// 要連線的遊戲伺服器IP地址,addr.sin_port = htons( Port );//到此,客戶端和伺服器已經有了通訊的橋樑,//接下來就是進行資料的傳送和接收:connect( sSocket, (SOCKADDR*)&addr, sizeof(addr) );if( send( sSocket, pBuffer, lLength, 0 ) == SOCKET_ERROR ){ Failed( "WinSocket Send Error!");} 這裡的pBuffer為要傳送的資料緩衝指標,lLength為需要傳送的資料長度,通過這支Socket API函式,我們無論在客戶端或者服務端都可以進行資料的傳送工作,同時,我們可以通過recv()這支Socket API函式來進行資料接收: if( recv( sSocket, pBuffer, lLength, 0 ) == SOCKET_ERROR ){ Failed( "WinSocket Recv Error!"); } 其中pBuffer用來儲存獲取的網路資料緩衝,lLength則為需要獲取的資料長度。 現在,我們已經瞭解了一些網路互連的基本知識,但作為網路遊戲,如此簡單的連線方式是無法滿足網路遊戲中百人千人同時線上的,我們需要更合理容錯性更強的網路通訊處理方式,當然,我們需要先了解一下網路遊戲對網路通訊的需求是怎樣的。rning收集整理(請*勿刪除)網路遊戲通訊模型初探②大家知道,遊戲需要不斷迴圈處理遊戲中的邏輯並進行遊戲世界的繪製,上面所介紹的Winsock處理方式均是以阻塞方式進行,這樣就違背了遊戲的執行本質,可以想象,在客戶端連線到伺服器的過程中,你的遊戲不能得到控制,這時如果玩家想取消連線或者做其他處理,甚至顯示一個最基本的動態連線提示都不行。 所以我們需要用其他方式來處理網路通訊,使其不會與遊戲主線相沖突,可能大家都會想到: 建立一個網路執行緒來處理不就可以了?沒錯,我們可以建立一個專門用於網路通訊的子執行緒來解決這個問題。當然,我們遊戲中多了一個執行緒,我們就需要做更多的考慮,讓我們來看看如何建立網路通訊執行緒。 在Windows系統中,我們可以通過CreateThread()函式來進行執行緒的建立,看看下面的程式碼片段: DWORD dwThreadID;HANDLE hThread = CreateThread( NULL, 0, NetThread/*網路執行緒函式*/, sSocket, 0, &dwThreadID );if( hThread == NULL ){ Failed( "WinSocket Thread Create Error!");} 這裡我們建立了一個執行緒,同時將我們的Socket傳入執行緒函式: DWORD WINAPINetThread(LPVOID lParam) { SOCKET sSocket (SOCKET)lParam; ... return 0; } NetThread就是我們將來用於處理網路通訊的網路執行緒。那麼,我們又如何把Socket的處理引入執行緒中? 看看下面的程式碼片段: HANDLE hEvent;hEvent = CreateEvent(NULL,0,0,0);// 設定非同步通訊if( WSAEventSelect( sSocket, hEvent,FD_ACCEPT|FD_CONNECT|FD_READ|FD_WRITE|FD_CLOSE ) ==SOCKET_ERROR ){ Failed( "WinSocket EventSelect Error!");} 通過上面的設定之後,WinSock API函式均會以非阻塞方式執行,也就是函式執行後會立即返回,這時網路通訊會以事件方式儲存於hEvent,而不會停頓整支程式。 完成了上面的步驟之後,我們需要對事件進行響應與處理,讓我們看看如何在網路執行緒中獲得網路通訊所產生的事件訊息: WSAEnumNetworkEvents( sSocket, hEvent, &SocketEvents );if( SocketEvents.lNetworkEvents != 0 ){ switch( SocketEvents.lNetworkEvents ) { case FD_ACCEPT: WSANETWORKEVENTS SocketEvents; break; case FD_CONNECT: { if( SocketEvents.iErrorCode[FD_CONNECT_BIT] == 0) // 連線成功 { // 連線成功後通知主執行緒(遊戲執行緒)進行處理 } } break; case FD_READ: // 獲取網路資料 { if( recv( sSocket, pBuffer, lLength, 0) == SOCKET_ERROR ) { Failed( "WinSocket Recv Error!"); } } break; case FD_WRITE: break; case FD_CLOSE: // 通知主執行緒(遊戲執行緒), 網路已經斷開 break; default: break; }} 這裡僅對網路連線(FD_CONNECT) 和讀取資料(FD_READ) 進行了簡單模擬操作,但實際中網路執行緒接收到事件訊息後,會對資料進行組織整理,然後再將資料回傳給我們的遊戲主執行緒使用,遊戲主執行緒再將處理過的資料傳送出去,這樣一個往返就構成了我們網路遊戲中的資料通訊,是讓網路遊戲動起來的最基本要素。 最後,我們來談談關於網路資料包(資料封包)的組織,網路遊戲的資料包是遊戲資料通訊的最基本單位,網路遊戲一般不會用位元組流的方式來進行資料傳輸,一個數據封包也可以看作是一條訊息指令,在遊戲進行中,伺服器和客戶端會不停的傳送和接收這些訊息包,然後將訊息包解析轉換為真正所要表達的指令意義並執行。 互動與管理 說到互動,對於玩家來說是與其他玩家的交流,但對於計算機而言,實現互動也就是實現資料訊息的相互傳遞。前面我們已經瞭解過網路通訊的基本概念,它構成了互動的最基本條件,接下來我們需要在這個網路層面上進行資料的通訊。遺憾的是,計算機並不懂得如何表達玩家之間的交流,因此我們需要提供一套可讓計算機瞭解的指令組織和解析機制,也就是對我們上面簡單提到的網路資料包(資料封包)的處理機制。為了能夠更簡單的給大家闡述網路資料包的組織形式,我們以一個聊天處理模組來進行討論,看看下面的程式碼結構: struct tagMessage{ long lType; long lPlayerID; }; // 訊息指令// 指令相關的玩家標識 char strTalk[256]; // 訊息內容 上面是抽象出來的一個極為簡單的訊息包結構,我們先來談談其各個資料域的用途: 首先,lType 是訊息指令的型別,這是最為基本的訊息標識,這個標識用來告訴伺服器或客戶端這條指令的具體用途,以便於伺服器或客戶端做出相應處理。lPlayerID 被作為玩家的標識。大家知道,一個玩家在機器內部實際上也就是一堆資料,特別是在遊戲伺服器中,可能有成千上萬個玩家,這時候我們需要一個標記來區分玩家,這樣就可以迅速找到特定玩家,並將通訊資料應用於其上。 strTalk 是我們要傳遞的聊天資料,這部分才是真正的資料實體,前面的引數只是資料實體應用範圍的限定。 在組織完資料之後,緊接著就是把這個結構體資料通過Socket 連線傳送出去和接收進來。這裡我們要了解,網路在進行資料傳輸過程中,它並不關心資料採用的資料結構,這就需要我們把資料結構轉換為二進位制資料碼進行傳送,在接收方,我們再將這些二進位制資料碼轉換回程式使用的相應資料結構。讓我們來看看如何實現: tagMessageMsg; Msg.lTypeMSG_CHAT; Msg.lPlayerID 1000; strcpy( &Msg.strTalk, "聊天資訊" ); 首先,我們假設已經組織好一個數據包,這裡MSG_CHAT 是我們自行定義的識別符號,當然,這個識別符號在伺服器和客戶端要統一。玩家的ID 則根據遊戲需要來進行設定,這裡1000 只作為假設,現在繼續: char* p = (char*)&Msg;long lLength = sizeof( tagMessage ); send( sSocket, p, lLength );// 獲取資料結構的長度 我們通過強行轉換把結構體轉變為char 型別的資料指標,這樣就可以通過這個指標來進行流式資料處理,這裡通過sizeof() 獲得結構體長度,然後用WinSock 的Send() 函式將資料傳送出去。 接下來看看如何接收資料: long lLength = sizeof( tagMessage );char* Buffer = new char[lLength];recv( sSocket, Buffer, lLength );tagMessage* p = (tagMessage*)Buffer;// 獲取資料 在通過WinSock 的recv() 函式獲取網路資料之後,我們同樣通過強行轉換把獲取出來的緩衝資料轉換為相應結構體,這樣就可以方便地對資料進行訪問。(注:強行轉換僅僅作為資料轉換的一種手段,實際應用中有更多可選方式,這裡只為簡潔地說明邏輯)談到此處,不得不提到伺服器/ 客戶端如何去篩選處理各種訊息以及如何對通訊資料包進行管理。無論是伺服器還是客戶端,在收到網路訊息的時候,通過上面的資料解析之後,還必須對訊息型別進行一次篩選和派分,簡單來說就是類似Windows 的訊息迴圈,不同訊息進行不同處理。這可以通過一個switch 語句(熟悉Windows 訊息迴圈的朋友相信已經明白此意),基於訊息封包裡的lType 資訊,對訊息進行區分處理,考慮如下程式碼片段: switch( p->lType ) // 這裡的p->lType為我們解析出來的訊息型別標識{ case MSG_CHAT: // 聊天訊息 break; case MSG_MOVE: // 玩家移動訊息 break; case MSG_EXIT: // 玩家離開訊息 break; default: break;} 上面片段中的MSG_MOVE 和MSG_EXIT 都是我們虛擬的訊息標識(一個真實遊戲中的標識可能會有上百個,這就需要考慮優化和優先訊息處理問題)。此外,一個網路遊戲伺服器面對的是成百上千的連線使用者,我們還需要一些合理的資料組織管理方式來進行相關處理。普通的單體遊戲伺服器,可能會因為當機或者使用者過多而導致整個遊戲網路癱瘓,而這也就引入分組伺服器機制,我們把伺服器分開進行資料的分散式處理。 我們把每個模組提取出來,做成專用的伺服器系統,然後建立一個連線所有伺服器的資料中心來進行資料互動,這裡每個模組均與資料中心建立了連線,保證了每個模組的相關性,同時玩家轉變為與當前提供服務的伺服器進行連線通訊,這樣就可以緩解單獨一臺伺服器所承受的負擔,把壓力分散到多臺伺服器上,同時保證了資料的統一,而且就算某臺伺服器因為異常而當機也不會影響其他模組的遊戲玩家,從而提高了整體穩定性。 分組式伺服器緩解了伺服器的壓力,但也帶來了伺服器排程問題,分組式伺服器需要對伺服器跳轉進行處理,就以一個玩家進行遊戲場景跳轉作為討論基礎:假設有一玩家處於遊戲場景A,他想從場景A 跳轉到場景B,在遊戲中,我們稱之場景切換,這時玩家就會觸發跳轉需求,比如走到了場景中的切換點,這樣伺服器就把玩家資料從"遊戲場景A 伺服器"刪除,同時在"遊戲場景B 伺服器"中把玩家建立起來。 這裡描述了場景切換的簡單模型,當中處理還有很多步驟,不過通過這樣的思考相信大家可以派生出很多應用技巧。不過需要注意的是,在場景切換或者說模組間切換的時候,需要切實考慮好資料的傳輸安全以及邏輯合理性,否則切換很可能會成為將來玩家複製物品的橋樑。 總結 本篇講述的都是通過一些簡單的過程來進行網路遊戲通訊,提供了一個製作的思路,雖然具體實現起來還有許多要做,但只要順著這個思路去擴充套件、去完善,相信大家很快就能夠編寫出自己的網路通訊模組。由於時間倉促,本文在很多細節方面都有省略,文中若有錯誤之處也望大家見諒。go*odmorning收集整理(請勿刪除)遊戲外掛設計技術探討①一、 前言 所謂遊戲外掛,其實是一種遊戲外輔程式,它可以協助玩家自動產生遊戲動作、修改遊戲網路資料包以及修改遊戲記憶體資料等,以實現玩家用最少的時間和金錢去完成功力升級和過關斬將。雖然,現在對遊戲外掛程式的“合法”身份眾說紛紜,在這裡我不想對此發表任何個人意見,讓時間去說明一切吧。 不管遊戲外掛程式是不是“合法”身份,但是它卻是具有一定的技術含量的,在這些小小程式中使用了許多高階技術,如攔截Sock技術、攔截API技術、模擬鍵盤與滑鼠技術、直接修改程式記憶體技術等等。本文將對常見的遊戲外掛中使用的技術進行全面剖析。 二、認識外掛 遊戲外掛的歷史可以追溯到單機版遊戲時代,只不過當時它使用了另一個更通俗易懂的名字??遊戲修改器。它可以在遊戲中追蹤鎖定遊戲主人公的各項能力數值。這樣玩家在遊戲中可以達到主角不掉血、不耗費魔法、不消耗金錢等目的。這樣降低了遊戲的難度,使得玩家更容易通關。 隨著網路遊戲的時代的來臨,遊戲外掛在原有的功能之上進行了新的發展,它變得更加多種多樣,功能更加強大,操作更加簡單,以至有些遊戲的外掛已經成為一個體系,比如《石器時代》,外掛品種達到了幾十種,自動戰鬥、自動行走、自動練級、自動補血、加速、不遇敵、原地遇敵、快速增加經驗值、按鍵精靈……幾乎無所不包。 遊戲外掛的設計主要是針對於某個遊戲開發的,我們可以根據它針對的遊戲的型別可大致可將外掛分為兩種大類。 一類是將遊戲中大量繁瑣和無聊的攻擊動作使用外掛自動完成,以幫助玩家輕鬆搞定攻擊物件並可以快速的增加玩家的經驗值。比如在《龍族》中有一種工作的設定,玩家的工作等級越高,就可以駕馭越好的裝備。但是增加工作等級卻不是一件有趣的事情,毋寧說是重複枯燥的機械勞動。如果你想做法師用的杖,首先需要做基本工作--?砍樹。砍樹的方法很簡單,在一棵大樹前不停的點滑鼠就可以了,每10000的經驗升一級。這就意味著玩家要在大樹前不停的點選滑鼠,這種無聊的事情通過"按鍵精靈"就可以解決。外掛的"按鍵精靈"功能可以讓玩家擺脫無趣的點選滑鼠的工作。 另一類是由外掛程式產生欺騙性的網路遊戲封包,並將這些封包傳送到網路遊戲伺服器,利用這些虛假資訊欺騙伺服器進行遊戲數值的修改,達到修改角色能力數值的目的。這類外掛程式針對性很強,一般在設計時都是針對某個遊戲某個版本來做的,因為每個網路遊戲伺服器與客戶端交流的資料包各不相同,外掛程式必須要對欺騙的網路遊戲伺服器的資料包進行分析,才能產生伺服器識別的資料包。這類外掛程式也是當前最流利的一類遊戲外掛程式。 另外,現在很多外掛程式功能強大,不僅實現了自動動作代理和封包功能,而且還提供了對網路遊戲的客戶端程式的資料進行修改,以達到欺騙網路遊戲伺服器的目的。我相信,隨著網路遊戲商家的反外掛技術的進展,遊戲外掛將會產生更多更優秀的技術,讓我們期待著看場技術大戰吧...... 三、外掛技術綜述 可以將開發遊戲外掛程式的過程大體上劃分為兩個部分: 前期部分工作是對外掛的主體遊戲進行分析,不同型別的外掛分析主體遊戲的內容也不相同。如外掛為上述談到的外掛型別中的第一類時,其分析過程常是針對遊戲的場景中的攻擊物件的位置和分佈情況進行分析,以實現外掛自動進行攻擊以及位置移動。如外掛為外掛型別中的第二類時,其分析過程常是針對遊戲伺服器與客戶端之間通訊包資料的結構、內容以及加密演算法的分析。因網路遊戲公司一般都不會公佈其遊戲產品的通訊包資料的結構、內容和加密演算法的資訊,所以對於開發第二類外掛成功的關鍵在於是否能正確分析遊戲包資料的結構、內容以及加密演算法,雖然可以使用一些工具輔助分析,但是這還是一種堅苦而複雜的工作。 後期部分工作主要是根據前期對遊戲的分析結果,使用大量的程式開發技術編寫外掛程式以實現對遊戲的控制或修改。如外掛程式為第一類外掛時,通常會使用到滑鼠模擬技術來實現遊戲角色的自動位置移動,使用鍵盤模擬技術來實現遊戲角色的自動攻擊。如外掛程式為第二類外掛時,通常會使用到擋截Sock和擋截API函式技術,以擋截遊戲伺服器傳來的網路資料包並將資料包修改後封包後傳給遊戲伺服器。另外,還有許多外掛使用對遊戲客戶端程式記憶體資料修改技術以及遊戲加速技術。 本文主要是針對開發遊戲外掛程式後期使用的程式開發技術進行探討,重點介紹的如下幾種在遊戲外掛中常使用的程式開發技術: ● 動作模擬技術:主要包括鍵盤模擬技術和滑鼠模擬技術。 ● 封包技術:主要包括擋截Sock技術和擋截API技術。四、動作模擬技術 我們在前面介紹過,幾乎所有的遊戲都有大量繁瑣和無聊的攻擊動作以增加玩家的功力,還有那些數不完的迷宮,這些好像已經成為了角色遊戲的代名詞。現在,外掛可以幫助玩家從這些繁瑣而無聊的工作中擺脫出來,專注於遊戲情節的進展。外掛程式為了實現自動角色位置移動和自動攻擊等功能,需要使用到鍵盤模擬技術和滑鼠模擬技術。下面我們將重點介紹這些技術並編寫一個簡單的例項幫助讀者理解動作模擬技術的實現過程。 1. 滑鼠模擬技術 幾乎所有的遊戲中都使用了滑鼠來改變角色的位置和方向,玩家僅用一個小小的滑鼠,就可以使角色暢遊天下。那麼,我們如何實現在沒有玩家的參與下角色也可以自動行走呢。其實實現這個並不難,僅僅幾個Windows API函式就可以搞定,讓我們先來認識認識這些API函式。 (1) 模擬滑鼠動作API函式mouse_event,它可以實現模擬滑鼠按下和放開等動作。 VOID mouse_event( DWORD dwFlags, // 滑鼠動作標識。 DWORD dx, // 滑鼠水平方向位置。 DWORD dy, // 滑鼠垂直方向位置。 DWORD dwData, // 滑鼠輪子轉動的數量。 DWORD dwExtraInfo // 一個關聯滑鼠動作輔加資訊。 ); 其中,dwFlags表示了各種各樣的滑鼠動作和點選活動,它的常用取值如下: MOUSEEVENTF_MOVE 表示模擬滑鼠移動事件。 MOUSEEVENTF_LEFTDOWN 表示模擬按下滑鼠左鍵。 MOUSEEVENTF_LEFTUP 表示模擬放開滑鼠左鍵。 MOUSEEVENTF_RIGHTDOWN 表示模擬按下滑鼠右鍵。 MOUSEEVENTF_RIGHTUP 表示模擬放開滑鼠右鍵。 MOUSEEVENTF_MIDDLEDOWN 表示模擬按下滑鼠中鍵。 MOUSEEVENTF_MIDDLEUP 表示模擬放開滑鼠中鍵。 (2)、設定和獲取當前滑鼠位置的API函式。獲取當前滑鼠位置使用GetCursorPos()函式,設定當前滑鼠位置使用SetCursorPos()函式。 BOOL GetCursorPos( LPPOINT lpPoint // 返回滑鼠的當前位置。 ); BOOL SetCursorPos( int X, // 滑鼠的水平方向位置。 int Y //滑鼠的垂直方向位置。 ); 通常遊戲角色的行走都是通過滑鼠移動至目的地,然後按一下滑鼠的按鈕就搞定了。下面我們使用上面介紹的API函式來模擬角色行走過程。 CPoint oldPoint,newPoint; GetCursorPos(&oldPoint); //儲存當前滑鼠位置。 newPoint.x = oldPoint.x+40; newPoint.y = oldPoint.y+10; SetCursorPos(newPoint.x,newPoint.y); //設定目的地位置。 mouse_event(MOUSEEVENTF_RIGHTDOWN,0,0,0,0);//模擬按下滑鼠右鍵。 mouse_event(MOUSEEVENTF_RIGHTUP,0,0,0,0);//模擬放開滑鼠右鍵。 2. 鍵盤模擬技術 在很多遊戲中,不僅提供了滑鼠的操作,而且還提供了鍵盤的操作,在對攻擊物件進行攻擊時還可以使用快捷鍵。為了使這些攻擊過程能夠自動進行,外掛程式需要使用鍵盤模擬技術。像滑鼠模擬技術一樣,Windows API也提供了一系列API函式來完成對鍵盤動作的模擬。 模擬鍵盤動作API函式keydb_event,它可以模擬對鍵盤上的某個或某些鍵進行按下或放開的動作。 VOID keybd_event( BYTE bVk, // 虛擬鍵值。 BYTE bScan, // 硬體掃描碼。 DWORD dwFlags, // 動作標識。 DWORD dwExtraInfo // 與鍵盤動作關聯的輔加資訊。 ); 其中,bVk表示虛擬鍵值,其實它是一個BYTE型別值的巨集,其取值範圍為1-254。有關虛擬鍵值表請在MSDN上使用關鍵字“Virtual-Key Codes”查詢相關資料。bScan表示當鍵盤上某鍵被按下和放開時,鍵盤系統硬體產生的掃描碼,我們可以MapVirtualKey()函式在虛擬鍵值與掃描碼之間進行轉換。dwFlags表示各種各樣的鍵盤動作,它有兩種取值:KEYEVENTF_EXTENDEDKEY和KEYEVENTF_KEYUP。 下面我們使用一段程式碼實現在遊戲中按下Shift+R快捷鍵對攻擊物件進行攻擊。 keybd_event(VK_CONTROL,MapVirtualKey(VK_CONTROL,0),0,0); //按下CTRL鍵。 keybd_event(0x52,MapVirtualKey(0x52,0),0,0);//鍵下R鍵。 keybd_event(0x52,MapVirtualKey(0x52,0), KEYEVENTF_KEYUP,0);//放開R鍵。 keybd_event(VK_CONTROL,MapVirtualKey(VK_CONTROL,0), KEYEVENTF_KEYUP,0);//放開CTRL鍵。 3. 啟用外掛 上面介紹的滑鼠和鍵盤模擬技術實現了對遊戲角色的動作部分的模擬,但要想外掛能工作於遊戲之上,還需要將其與遊戲的場景視窗聯絡起來或者使用一個啟用鍵,就象按鍵精靈的那個啟用鍵一樣。我們可以用GetWindow函式來列舉視窗,也可以用Findwindow函式來查詢特定的視窗。另外還有一個FindWindowEx函式可以找到視窗的子視窗,當遊戲切換場景的時候我們可以用FindWindowEx來確定一些當前視窗的特徵,從而判斷是否還在這個場景,方法很多了,比如可以GetWindowInfo來確定一些東西,比如當查詢不到某個按鈕的時候就說明遊戲場景已經切換了等等辦法。當使用啟用鍵進行關聯,需要使用Hook技術開發一個全域性鍵盤鉤子,在這裡就不具體介紹全域性鉤子的開發過程了,在後面的例項中我們將會使用到全域性鉤子,到時將學習到全域性鉤子的相關知識。遊戲外掛設計技術探討②4. 例項實現 通過上面的學習,我們已經基本具備了編寫動作式遊戲外掛的能力了。下面我們將建立一個畫筆程式外掛,它實現自動移動畫筆字游標的位置並寫下一個紅色的“R”字。以這個例項為基礎,加入相應的遊戲動作規則,就可以實現一個完整的遊戲外掛。這裡作者不想使用某個遊戲作為例子來開發外掛(因沒有遊戲商家的授權啊!),如讀者感興趣的話可以找