傳奇源碼分析2


簡述:
     最近對高性能的服務器比較感興趣,讀過了DELPHI的Socker源碼WebService及RemObject之后,高性能的服務器感興趣。
你可能需要的以下知識才能更好的讀懂一個商業源碼:
1).SOCKET的I/O模型熟悉掌握。
2).面向對象技術的熟悉掌握。
3).Socket的API掌握。
4).多線程技術等。
5).一門熟悉的開發工具掌握,和多種語言的源碼閱讀能力。

我下的源碼 LegendOfMir2_Server:共包含AdminCmd, DBSrv, GameGate, GameSvr,LoginGate, LoginSvr, SelGate七個工程文件。傳奇的客戶端源代碼有兩個工程,WindHorn和Mir2Ex。
我分析的, 主要是VC SQL版本的, DELPHI翎風源碼不做分析, 另外下載了樂都WIL編輯器和樂都MPA地圖編輯器這些工具.

傳奇源碼分析-客戶端(WindHorn簡述和傳奇文件格式分析)

DirectX類庫分析(WindHorn):

1.     RegHandler.cpp 注冊表訪問(讀寫)。
2.     CWHApp派生CWHWindow,CWHWindow完成窗口的注冊和創建。CWHWindow派生出CWHDXGraphicWindow,CWHDXGraphicWindow調用CWHWindow完成創建窗口功能,然后再調用CreateDXG()來初始化DirectX。
3.     WHDefProcess.cpp在構造函數中獲得CWHDXGraphicWindow句柄。                 
Clear函數中調用在后台緩存上進行繪圖操作,換頁至屏幕。
    ShowStatus函數,顯示狀態信息。
DefMainWndProc函數,調用CWHDXGraphicWindow->MainWndProcDXG消息處理。
4.     WHImage.cpp圖象處理。加載位圖,位圖轉換。優化處理。
5.     WHSurface.cpp 主頁面處理。
6.     WHWilTexture.cpp 材質渲染。
WILTextureContainer: WIL容器類。m_pNext指向下一個WILTextureContainer,單鏈表。
7.     WHWilImage.cpp 從Data目錄中加載Wix文件(內存映射)。
8.     WHDXGraphic.cpp 處理DirectX效果。

文件類型格式探討:
Wix文件:索引文件,根據索引查找到相應數據地址(數據文件)。
// WIX 文件頭格式
typedef struct tagWIXFILEIMAGEINFO
{
    CHAR    szTmp[40];     // 庫文件標題 'WEMADE Entertainment inc.' WIL文件頭
    INT     nIndexCount;   // 圖片數量
    INT*    pnPosition;    // 位置
}WIXIMAGEINFO, *LPWIXIMAGEINFO;

我們下載一個Hedit編輯器打開一個Wil文件,分析一下。我們發現Wix文件中,0x23地址(含該地址)以前的內容是都相同的,即為:#INDX v1.0-WEMADE Entertainment inc.
Ofs44 0x2C的地方:存放着0B 00 00 00,高低位轉換后為:0xB轉換十進制數為11(圖片數量)Ofs48 0x30的地方:存放着38 04 00 00,高低位轉換后為:0x438 = 1080, 這個就是圖象數據的開始位置。

我們用Wil編輯打開對應的Wil文件,發現,果然有11張圖片。另外我們發現,在Ofs = 44 -47之間的數據總是38 04 00 00,終於明白,所有的圖片起始位置是相同的。

Wil文件: 數據文件。
前面我們說了圖象數據的開始位置為0x438 = 1080, 1080中有文件開頭的44字節都是相同的。所以,就是說有另外的1036字節是另有用途。1036中有1024是一個256色的調色板。
我們看到圖片位置數據為: 20 03 58 02, 轉化為十六進制: 0x320, 0x258 剛好就是800*600大小的圖片。07 00 D4 FF。圖片起始位置為:
Ofs 1088: 0x440 圖片大小為480000
起始位置:0x440 1088   終止位置:0x7573F 481087 為了驗證數據是否正確,我們通過Wil工具,把第一幅圖片導出來,然后用Hedit編輯器打開,經過對比,我們發現,數據一致。大小一致。
    第二張BMP圖片(圖片起始位置:0x436 10078) : F0 01 69 01 , 07 00 D4 FF
剛好大小。第二張Wil起始位置:Ofs:481096 0x75748
知道了圖片格式,我們可以寫一個抓圖片格式的程序了。

傳奇源碼分析-客戶端(全局變量與總體執行流程)
客戶端:
傳奇的客戶端源代碼有兩個工程,WindHorn和Mir2Ex。
先剖析一下WindHorn工程。
1.CWHApp、CWHWindow和CWHDXGraphicWindow。Window程序窗口的創建。
         CWHApp派生CWHWindow,CWHWindow又派生CWHDXGraphicWindow。CWHWindow類          
中完成窗口的注冊和創建。CWHDXGraphicWindow調用CWHWindow完成創建窗口功能,然后再調用CreateDXG()來初始化DirectX。

2.CWHDefProcess派生出CloginProcess、CcharacterProcess、CgameProcess三個類。
   這三個類是客戶端處理的核心類。

3. 全局變量:
   CWHDXGraphicWindow    g_xMainWnd; 主窗口類。
   CLoginProcess         g_xLoginProc; 登錄處理。
   CCharacterProcess     g_xChrSelProc; 角色選擇處理。
   CgameProcess       g_xGameProc; 游戲邏輯處理。

4.代碼分析:
1.首先從LoginGate.cpp WinMain分析:
g_xMainWnd定義為CWHDXGraphicWindow調用CWHWindow完成創建窗口功能,然后
調用DirectDrawEnumerateEx枚舉顯示設備,(執行回調函數DXGDriverEnumCallbackEx) 再調用CreateDXG()來初始化DirectX(創建DirectDraw對象, 取得獨占和全屏模式, 設置顯示模式等)。
    g_xSound.InitMirSound創建CSound對象。
    g_xSpriteInfo.SetInfo();
     初始化聲音,加載Socket庫之后,進行CWHDefProcess*指針賦值(事件綁定)。g_bProcState變量反應了當前游戲的狀態(登錄,角色選擇,游戲邏輯處理)。調用Load初始化一些操作(登錄,角色選擇,游戲邏輯處理)。進行消息循環。
    case _LOGIN_PROC:
        g_xLoginProc.RenderScene(dwDelay);
    case _CHAR_SEL_PROC:
        g_xChrSelProc.RenderScene(dwDelay);
    case _GAME_PROC:
g_xGameProc.RenderScene(dwDelay);
    根據g_bProcState變量標志,選擇顯示相應的畫面。

2.接收處理網絡消息和接收處理窗口消息。
     在不同的狀態下(登錄,角色選擇,游戲邏輯處理),接收到的消息(網絡,窗口消息)會分派到不同的函數中處理的。這里是用虛函數處理(調用子類方法,由實際的父類完成相應的處理)。
OnMessageReceive主要處理網絡消息。DefMainWndProc則處理窗體消息(按鍵,重繪等),創建窗體類為CWHDXGraphicWindow,回調函數為:
MainWndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
if ( m_pxDefProcess )
m_pxDefProcess->DefMainWndProc(hWnd, uMsg, wParam, lParam);
else   
return MainWndProcDXG(hWnd, uMsg, wParam, lParam);

m_pxDefProcess->DefMainWndProc調用父類的實際處理。
在WM_PAINT事件里: g_xClientSocket

.ConnectToServer連接登陸服務器。

傳奇源碼分析-客戶端(傳奇2文件格式分析)
傳奇文件類型格式探討(一):
Wix文件:索引文件,根據索引查找到相應數據地址(數據文件)。
// WIX 文件頭格式
typedef struct tagWIXFILEIMAGEINFO
{
    CHAR    szTmp[40];     // 庫文件標題 'WEMADE Entertainment inc.' WIL文件頭
    INT     nIndexCount;   // 圖片數量
    INT*    pnPosition;    // 位置
}WIXIMAGEINFO, *LPWIXIMAGEINFO;

我們下載一個Hedit編輯器打開一個Wil文件,分析一下。我們發現Wix文件中,0x23地址(含該地址)以前的內容是都相同的,即為:#INDX v1.0-WEMADE Entertainment inc.
Ofs44 0x2C的地方:存放着0B 00 00 00,高低位轉換后為:0xB轉換十進制數為11(圖片數量)Ofs48 0x30的地方:存放着38 04 00 00,高低位轉換后為:0x438 = 1080, 這個就是圖象數據的開始位置。

我們用Wil編輯打開對應的Wil文件,發現,果然有11張圖片。另外我們發現,在Ofs = 44 -47之間的數據總是38 04 00 00,終於明白,所有的圖片起始位置是相同的。

Wil文件: 數據文件。
前面我們說了圖象數據的開始位置為0x438 = 1080, 1080中有文件開頭的44字節都是相同的。所以,就是說有另外的1036字節是另有用途。1036中有1024是一個256色的調色板。而Wil里面的圖片格式都是256色的位圖儲存。
我們看到圖片位置數據為: 20 03 58 02, 轉化為十六進制: 0x320, 0x258 剛好就是800*600大小的圖片。07 00 D4 FF為固定值(標識)。圖片起始位置為:
Ofs 1088: 0x440 圖片大小為480000
起始位置:0x440 1088   終止位置:0x7573F 481087 為了驗證數據是否正確,我們通過Wil工具,把第一幅圖片導出來,然后用Hedit編輯器打開,經過對比,我們發現,數據一致。大小一致。
    大家看到圖片1的結束位置為0fs 481077,減去1080+1 = 480000剛好800*600大小。
我們用Wil抓圖工具打開看一下(確定是800*600大小):

我們導出第二張BMP圖片
圖片的大小為:496* 361, 我們從Wix中讀出第二張圖片的索引位置:
根據貼圖,我們發現第二張圖片的索引位置為: 40 57 07 00,轉換為十六進制:0x75740,即為:481088,前面我們講到第一張圖片的結束位置是: 0fs 481077,從Wix中讀出來的也剛好為第二張圖片的起始位置:
(我們分析Wil中的第二張圖片,起始位置:0x75740 481088) : F0 01 69 01為圖片長寬: 0x1F0, 0x169 為496* 361 。 07 00 D4 FF為固定值(標識)。
我們用工具打開第二張BMP圖片,從起始位置,一直選取中至結束,發現剛好選496* 361字節大小。兩邊數據對比之后發現一致。知道了圖片格式,我們可以寫一個抓圖片格式的程序了。

傳奇源碼分析-客戶端(傳奇2和3 文件格式分析比較)
貼這個貼子,希望大家少走彎路。網上下載的那個版本應該是從傳奇2改的,傳奇3的格式。分析一下源碼吧,g_xLoginProc.Load(); 之后就加載m_Image.NewLoad(IMAGE_INTERFACE_1, TRUE, TRUE);
    繼續讀Wix文件,
    ReadFile(hWixFile, &m_stNewWixImgaeInfo, sizeof(NEWWIXIMAGEINFO)-sizeof(INT*), &dwReadLen, NULL);
    // WIX 文件頭格式 (56Byte)(NEW)
typedef struct tagNEWWIXFILEIMAGEINFO
{
CHAR szTitle[20];   // 庫文件標題 'WEMADE Entertainment inc.' WIL文件頭
INT nIndexCount;   // 圖片數量
INT* pnPosition;    // 位置
}NEWWIXIMAGEINFO, *LPNEWWIXIMAGEINFO;
    不看不知道,一看嚇一跳,大家看到了吧,這個是新的WIX的定義,不是傳奇2的,前面分析過傳奇2的圖片: 0x23地址(含該地址)以前的內容是都相同的,即為:#INDX v1.0-WEMADE Entertainment inc.   Ofs44 0x2C的地方:存放着0B 00 00 00,高低位轉換后為:0xB轉換十進制數為11(圖片數量)Ofs48 0x30的地方:存放着38 04 00 00,高低位轉換后為:0x438 = 1080, 這個就是圖象數據的開始位置。這里才20個標題長度。 一看就不對。所以如果你下了網上的傳奇3的格式,試着讀傳奇2的圖片,是不正確的。具體大家可以調試一下,我調試過了,里面的圖片數量根本不對。
        汗,居然讓人郁悶的是, // WIX 文件頭格式 (56Byte)
typedef struct tagWIXFILEIMAGEINFO
{
CHAR szTmp[40];     // 庫文件標題 'WEMADE Entertainment inc.' WIL文件頭
INT nIndexCount;   // 圖片數量
INT* pnPosition;    // 位置
}WIXIMAGEINFO, *LPWIXIMAGEINFO;我用了這種格式也不對。為什么不對,因為我前面分析過了,0xB轉換十進制數為11(圖片數量)Ofs48 0x30的地方, 看到沒有,圖片數量的存放地方。 所以趕快改一下數據結構吧,不知道為什么,難道是我版本有問題,我下了幾個資源文件,結果發現問題依然存在。看來不是圖片的問題。
另外,下面的工程里的圖片,如果要運行,不用改數據結構,請到傳奇3客戶端官方網站下載。我下載的是1.5版的資源文件。 是傳奇2的資源文件。祝大家好運吧!



傳奇文件類型格式探討(二):
// WIX 文件頭格式 (NEW)
typedef struct tagNEWWIXFILEIMAGEINFO
{
    CHAR    szTitle[20];   // 庫文件標題 'WEMADE Entertainment inc.' WIL文件頭
    INT     nIndexCount;   // 圖片數量
    INT*    pnPosition;    // 位置
}NEWWIXIMAGEINFO, *LPNEWWIXIMAGEINFO;

我們下載一個Hedit編輯器打開一個Wil文件,分析一下。我們發現Wix文件中,0x13地址(含該地址)以前的內容是都相同的,即為: ‘                   ’20個空格。
圖片數量: nIndexCount 18


Ofs 20, 0x14的位置,存放的數據為12 00 00 00,高低位轉換后為:0x12十制數為18(圖片數量)。Ofs28 0x1C的地方:存放着20 00 00 00,高低位轉換后為:0x20 = 32, 這個就是圖象數據的開始位置。
我們用Wil編輯打開對應的Wil文件,發現,果然有17張圖片(減1)。另外我們發現,在Ofs28 0x1C的地方= 28 -31之間的數據總是20 00 00 00,終於明白,所有的圖片起始位置是相同的。

抓圖分析,自己就再分析一下吧,和傳奇2的結構差不多。

傳奇源碼分析-客戶端(游戲邏輯處理源分析一)
登錄處理事件:
0.WinMain主函數調用g_xLoginProc.Load();加載圖片等初始化,設置g_bProcState 的狀態。
1.CLoginProcess::OnKeyDown-> m_xLogin.OnKeyDown->g_xClientSocket.OnLogin;
WSAAsyncSelect模型ID_SOCKCLIENT_EVENT_MSG,因此,(登錄,

角色選擇,游戲邏輯處理)都回調g_xClientSocket.OnSocketMessage(wParam, lParam)進行處理。
OnSocketMessage函數中:FD_READ事件中:
2.g_bProcState判斷當前狀態,_GAME_PROC時,把GameGate的發送過來的消息壓入PacketQ隊列中,再進行處理。否則則調用OnMessageReceive(虛方法,根據g_bProcState狀態,調用CloginProcess或者是CcharacterProcess的OnMessageReceive方法)。
3.CloginProcess:調用OnSocketMessageRecieve處理返回情況。如果服務器驗證失敗(SM_ID_NOTFOUND, SM_PASSWD_FAIL)消息,否則收到SM_PASSOK_SELECTSERVER消息(SelGate服務器列表消息)。m_Progress = PRG_SERVER_SELE;進行下一步選擇SelGate服務器操作。
4. m_xSelectSrv.OnButtonDown->CselectSrv. OnButtonUp->
g_xClientSocket.OnSelectServer(CM_SELECTSERVER),得到真正的IP地址。調用OnSocketMessageRecieve處理返回的SM_SELECTSERVER_OK消息。並且斷開與loginSrv服務器連接。 g_xClientSocket.DisconnectToServer();設置狀態為PRG_TO_SELECT_CHR狀態。

角色選擇處理:
1. WinMain消息循環處理:g_xLoginProc.RenderScene(dwDelay)-> RenderScroll->
SetNextProc調用
g_xClientSocket.m_pxDefProc = g_xMainWnd.m_pxDefProcess = &g_xChrSelProc;
g_xChrSelProc.Load();
g_bProcState = _CHAR_SEL_PROC;

   2.g_xChrSelProc.Load();連接SelGate服務器(從LoginGate服務器得到IP地址)。
g_xClientSocket.OnQueryChar();查詢用戶角色信息,發送消息:CM_QUERYCHR,設置狀態為_CHAR_SEL_PROC, m_Progress = PRG_CHAR_SELE; 在OnSocketMessageRecieve函數中接收到SelGate服務器發送的消息。

   3.點擊ChrStart按鈕:g_xChrSelProc.OnLButtonDown-> CSelectChr::OnButtonUp->
g_xClientSocket.OnSelChar->發送CM_SELCHR消息到SelGate服務器。

4.CClientSocket::OnSocketMessage->CCharacterProcess::OnMessageReceive
(SM_STARTPLAY) 接受到SelGate服務器發送的GameGate服務器IP地址,並斷開與SelGate服務器的連接。m_xSelectChr.m_nRenderState = 2;
  
   5. WinMain消息循環處理:g_xLoginProc.RenderScene ->
m_xSelectChr.Render(nLoopTime);-> CSelectChr::Render(INT   nLoopTime)-> m_nRenderState = m_nRenderState + 10; 為12-> CCharacterProcess::RenderScene執行

m_Progress = PRG_SEL_TO_GAME;
    m_Progress = PRG_PLAY_GAME;                           
SetNextProc();

6.SetNextProc();執行: g_xGameProc.Load(); g_bProcState = _GAME_PROC;進行游戲狀態。

游戲邏輯處理:
1.客戶端處理:
CGameProcess::Load() 初始化游戲環境,加載地圖等操作,調用ConnectToServer(m_pxDefProc->OnConnectToServer)連接到GameGate游戲網關服務器(DBSrv處理后經SelGate服務器返回的GameGate服務器IP地址)。
     CClientSocket->ConnectToServer調用connect時,由GameGate服務器發送GM_OPEN消息到GameSrv服務器。WSAAsyncSelect I/O模型回調函數 g_xClientSocket.OnSocketMessage。然后由m_pxDefProc->OnConnectToServer()調用CGameProcess::OnConnectToServer()函數,調用:g_xClientSocket.SendRunLogin。

2. GameGate服務器ServerWorkerThread處理:
GameGate服務器ServerWorkerThread收到消息,ThreadFuncForMsg處理數據,生成MsgHdr結構,並設置
MsgHdr.nCode    = 0xAA55AA55; //數據標志
MsgHdr.wIdent   = GM_DATA;    //數據類型

3. GameSrv服務器ServerWorkerThread線程處理
   GameSrv服務器ServerWorkerThread線程處理調用DoClientCertification設置用戶信息,及USERMODE_LOGIN的狀態。並且調用LoadPlayer(CUserInfo* pUserInfo)函數-> LoadHumanFromDB-> SendRDBSocket發送DB_LOADHUMANRCD請求,返回該玩家的所有數據信息。

4. 客戶端登錄驗證(GameSrv服務器的線程ProcessLogin處理)
用戶的驗證是由GameSrv服務器的線程ProcessLogin處理。g_xReadyUserInfoList2列表中搜索,判斷用戶是否已經登錄,一旦登錄就調用LoadPlayer(這里兩個參數):
a. 設置玩家游戲狀態。m_btCurrentMode狀態為USERMODE_PLAYGAME
b. 加載物品,個人設置,魔法等。
c. pUserInfo->m_pxPlayerObject->Initialize();初始化用戶信息,加載用戶坐標,方向,地圖。
   Initialize執行流程:
1)       AddProcess(this, RM_LOGON, 0, 0, 0, 0, NULL);加入登錄消息。
2)       m_pMap->AddNewObject 地圖中單元格(玩家列表)加入該游戲玩家。OS_MOVINGOBJECT玩家狀態。
3)       AddRefMsg(RM_TURN 向周圍玩家群發 RM_TURN消息。以玩家自己為中心,以24*24的區域里,向這個區域所屬的塊里的所有玩家列表發送消息)廣播 AddProcess。
4)       RecalcAbilitys 設置玩家的能力屬性(攻擊力(手,衣服),武器力量等)。
5)       循環處理本游戲玩家的附屬物品,把這些物品的力量加到(手,衣服等)的攻擊力量里。
6)       RM_CHARSTATUSCHANGED消息,通知玩家狀態改變消息。
7)       AddProcess(this, RM_ABILITY, 0, 0, 0, 0, NULL); 等級
AddProcess(this, RM_SUBABILITY, 0, 0, 0, 0, NULL);
AddProcess(this, RM_DAYCHANGING, 0, 0, 0, 0, NULL); 校時
AddProcess(this, RM_SENDUSEITEMS, 0, 0, 0, 0, NULL); 裝備
AddProcess(this, RM_SENDMYMAGIC, 0, 0, 0, 0, NULL); 魔法
         SysMsg(szMsg, 1) 攻擊力
並把用戶數據從g_xReadyUserInfoList2列表中刪除。

   說明:
一旦通過驗證,就從驗證列表中該玩家,改變玩家狀態,LoadPlayer加載用戶資源(地圖中加入用戶信息,向用戶24*24區域內的塊內玩家發送上線消息GameSrv廣播新玩家上線(坐標)的消息。向該新玩家發送玩家信息(等級,裝備,魔法,攻擊力等)。


免責聲明!

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



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