傳奇這款游戲,一直對我的影響很大。當年為了玩傳奇,逃課,被老師叫過N次家長。言歸正傳,網上有很多源碼,當然了,都是delphi的。並且很多源碼還不全,
由於一直學習的c、c++。delphi還真不懂。無奈硬着頭皮上。好了。廢話不多說。開始。
登錄網關,負責游戲最開始的登錄處理(與賬戶服務器LoginSvr通訊)。驗證登錄器輸入的賬戶密碼是否正確。

界面上的控件很多。其實干活的就 就 三個:“TServerSocket”、“TClientSocket”、“DecodeTimer”這三個控件。
ServerSocket:負責與登錄器進行通訊,它做的操作:
1、接收連接,代碼如下:
{
函數功能:接受客戶端連接,發送消息到 登錄服務器
}
procedure TFrmMain.ServerSocketClientConnect(Sender: TObject;
Socket: TCustomWinSocket);
var
UserSession:pTUserSession;
sRemoteIPaddr,sLocalIPaddr:String;
nSockIndex:Integer;
IPaddr :pTSockaddr;
begin
Socket.nIndex:=-1;
// 客戶端IP地址
sRemoteIPaddr:=Socket.RemoteAddress;
if g_boDynamicIPDisMode then begin
sLocalIPaddr:=ClientSocket.Socket.RemoteAddress;
end else begin
sLocalIPaddr:=Socket.LocalAddress;
end;
// 過濾ip
if IsBlockIP(sRemoteIPaddr) then begin
MainOutMessage('過濾連接: ' + sRemoteIPaddr,1);
Socket.Close;
exit;
end;
// 當前IP是否可以連接
if IsConnLimited(sRemoteIPaddr) then begin
case BlockMethod of
// 斷開
mDisconnect: begin
Socket.Close;
end;
// 動態過濾
mBlock: begin
New(IPaddr);
IPaddr.nIPaddr:=inet_addr(PChar(sRemoteIPaddr));
TempBlockIPList.Add(IPaddr);
CloseConnect(sRemoteIPaddr);
end;
// 永久過濾
mBlockList: begin
New(IPaddr);
IPaddr.nIPaddr:=inet_addr(PChar(sRemoteIPaddr));
BlockIPList.Add(IPaddr);
CloseConnect(sRemoteIPaddr);
end;
end;
MainOutMessage('端口攻擊: ' + sRemoteIPaddr,1);
exit;
end;
// 如果網關准備好了
if boGateReady then begin
for nSockIndex:= 0 to GATEMAXSESSION - 1 do begin
UserSession:=@g_SessionArray[nSockIndex];
if UserSession.Socket = nil then begin
UserSession.Socket:=Socket;
UserSession.sRemoteIPaddr:=sRemoteIPaddr;
UserSession.nSendMsgLen:=0;
UserSession.bo0C:=False;
UserSession.dw10Tick:=GetTickCount();
UserSession.dwConnctCheckTick:=GetTickCount();
UserSession.boSendAvailable:=True;
UserSession.boSendCheck:=False;
UserSession.nCheckSendLength:=0;
UserSession.n20:=0;
UserSession.dwUserTimeOutTick:=GetTickCount();
UserSession.SocketHandle:=Socket.SocketHandle;
UserSession.sIP:=sRemoteIPaddr;
UserSession.MsgList.Clear;
Socket.nIndex:=nSockIndex;
Inc(nSessionCount);
break;
end;
end;
// 和本地登錄服務器進行通訊
if Socket.nIndex >= 0 then begin
ClientSocket.Socket.SendText('%O' +
IntToStr(Socket.SocketHandle) +
'/' +
sRemoteIPaddr +
'/' +
sLocalIPaddr +
'$');
MainOutMessage('Connect: ' + sRemoteIPaddr,5);
end else begin
Socket.Close;
MainOutMessage('Kick Off: ' + sRemoteIPaddr,1);
end;
end else begin //0x004529EF
Socket.Close;
MainOutMessage('Kick Off: ' + sRemoteIPaddr,1);
end;
end;
說白了,別看 那些IP過濾規則和連接限制什么的。就是 來了一用戶,直接保存到一個 UserSession中。然后通知LoginSvr,有人連接了。。現在市面上的什么:GOM引擎、Hero、HGE(原3K、IGE)、Legend、GEEM2、77M2都是換湯不換葯。變的就是 加密方式。這里不得不說,JsocketJ就是TServerSocket、TClientSocket的控件,懶得看源碼,看了下其屬性,大致就可以了解到。
是采用的線程池的select模型。早期的游戲,都采用這種方式,不過,對於私*服,連接量不大,完全足夠應付。其實有更好的解決辦法,那就是IOCP來管理。效率更高。windows下最適合的模型了。
2、斷開連接,代碼如下:
procedure TFrmMain.ServerSocketClientDisconnect(Sender: TObject;
Socket: TCustomWinSocket);
var
I:Integer;
UserSession:pTUserSession;
nSockIndex:Integer;
sRemoteIPaddr:String;
IPaddr :pTSockaddr;
nIPaddr :Integer;
begin
sRemoteIPaddr:=Socket.RemoteAddress;
nIPaddr:=inet_addr(PChar(sRemoteIPaddr));
nSockIndex:=Socket.nIndex;
for I := 0 to CurrIPaddrList.Count - 1 do begin
IPaddr:=CurrIPaddrList.Items[I];
if IPaddr.nIPaddr = nIPaddr then begin
Dec(IPaddr.nCount);
if IPaddr.nCount <= 0 then begin
Dispose(IPaddr);
CurrIPaddrList.Delete(I);
end;
Break;
end;
end;
if (nSockIndex >= 0) and (nSockIndex < GATEMAXSESSION) then begin
UserSession:=@g_SessionArray[nSockIndex];
UserSession.Socket:=nil;
UserSession.sRemoteIPaddr:='';
UserSession.SocketHandle:=-1;
UserSession.MsgList.Clear;
Dec(nSessionCount);
if boGateReady then begin
ClientSocket.Socket.SendText('%X' +
IntToStr(Socket.SocketHandle) +
'$');
MainOutMessage('DisConnect: ' + sRemoteIPaddr,5);
end;
end;
end;
刪除,釋放,並通知 LoginSvr,有人斷開連接了。。。
3、接收數據,代碼如下:
procedure TFrmMain.ServerSocketClientRead(Sender: TObject;
Socket: TCustomWinSocket);
var
UserSession:pTUserSession;
nSockIndex:Integer;
sReviceMsg,s10,s1C:String;
nPos:Integer;
nMsgLen:Integer;
begin
nSockIndex:=Socket.nIndex;
if (nSockIndex >= 0) and (nSockIndex < GATEMAXSESSION) then begin
UserSession:=@g_SessionArray[nSockIndex];
sReviceMsg:=Socket.ReceiveText;
if (sReviceMsg <> '') and (boServerReady) then begin
nPos:=Pos('*',sReviceMsg);
if nPos > 0 then begin
UserSession.boSendAvailable:=True;
UserSession.boSendCheck:=False;
UserSession.nCheckSendLength:=0;
s10:=Copy(sReviceMsg,1,nPos -1);
s1C:=Copy(sReviceMsg,nPos + 1,Length(sReviceMsg) - nPos);
sReviceMsg:=s10 + s1C;
end;
nMsgLen:=length(sReviceMsg);
if (sReviceMsg <> '') and (boGateReady) and (not boKeepAliveTimcOut)then begin
UserSession.dwConnctCheckTick:=GetTickCount();
if (GetTickCount - UserSession.dwUserTimeOutTick) < 1000 then begin
Inc(UserSession.n20,nMsgLen);
end else UserSession.n20:= nMsgLen;
ClientSocket.Socket.SendText('%A' +
IntToStr(Socket.SocketHandle) +
'/' +
sReviceMsg +
'$');
end;
end;
end;
end;
拿到數據,轉發到 登錄賬戶服務器。。。。。它主要干的事完了。。
ClientSocket:負責與LoginSvr進行通訊,它做的主要工作就是,
procedure TFrmMain.ClientSocketRead(Sender: TObject; Socket: TCustomWinSocket); var sRecvMsg:String; begin sRecvMsg:=Socket.ReceiveText; ClientSockeMsgList.Add(sRecvMsg); end;
你沒有看錯,就是接收到數據。然后保存到鏈表,然后等待定時器來解析操作。。
DecodeTimer 定時器的工作。源碼中設置的 時間精度是 1毫秒:
procedure TFrmMain.DecodeTimerTimer(Sender : TObject);
var
sProcessMsg :String;
sSocketMsg :String;
sSocketHandle :String;
nSocketIndex :Integer;
nMsgCount :Integer;
nSendRetCode :Integer;
nSocketHandle :Integer;
dwDecodeTick :LongWord;
dwDecodeTime :LongWord;
sRemoteIPaddr :String;
UserSession :pTUserSession;
IPaddr :pTSockaddr;
begin
ShowMainLogMsg();
if boDecodeLock or (not boGateReady)then exit;
try
dwDecodeTick:=GetTickCount();
boDecodeLock:=True;
sProcessMsg:='';
while (True) do begin
if ClientSockeMsgList.Count <= 0 then break;
sProcessMsg:=sProcMsg + ClientSockeMsgList.Strings[0];
sProcMsg:='';
ClientSockeMsgList.Delete(0);
while (True) do begin
if TagCount(sProcessMsg,'$') < 1 then break;
sProcessMsg:=ArrestStringEx(sProcessMsg,'%','$',sSocketMsg);
if sSocketMsg = ''then break;
if sSocketMsg[1] = '+' then begin
if sSocketMsg[2] = '-' then begin
CloseSocket(Str_ToInt(Copy(sSocketMsg,3,Length(sSocketMsg) - 2),0));
Continue;
end else begin //0x004521B7
dwKeepAliveTick:=GetTickCount();
boKeepAliveTimcOut:=False;
Continue;
end;
end; //0x004521CD
sSocketMsg:=GetValidStr3(sSocketMsg,sSocketHandle,['/']);
nSocketHandle:=Str_ToInt(sSocketHandle,-1);
if nSocketHandle < 0 then
Continue;
for nSocketIndex:= 0 to GATEMAXSESSION - 1 do begin
if g_SessionArray[nSocketIndex].SocketHandle = nSocketHandle then begin
g_SessionArray[nSocketIndex].MsgList.Add(sSocketMsg);
break;
end;
end;
end; //0x00452246
end; //0x452252
//if sProcessMsg <> '' then ClientSockeMsgList.Add(sProcessMsg);
if sProcessMsg <> '' then sProcMsg:=sProcessMsg;
nSendMsgCount:=0;
n456A2C:=0;
StringList318.Clear;
for nSocketIndex:= 0 to GATEMAXSESSION - 1 do begin
if g_SessionArray[nSocketIndex].SocketHandle <= -1 then Continue;
//踢除超時無數據傳輸連接
if (GetTickCount - g_SessionArray[nSocketIndex].dwConnctCheckTick) > dwKeepConnectTimeOut then begin
sRemoteIPaddr:=g_SessionArray[nSocketIndex].sRemoteIPaddr;
case BlockMethod of //
mDisconnect: begin
g_SessionArray[nSocketIndex].Socket.Close;
end;
mBlock: begin
New(IPaddr);
IPaddr.nIPaddr:=inet_addr(PChar(sRemoteIPaddr));
TempBlockIPList.Add(IPaddr);
CloseConnect(sRemoteIPaddr);
end;
mBlockList: begin
New(IPaddr);
IPaddr.nIPaddr:=inet_addr(PChar(sRemoteIPaddr));
BlockIPList.Add(IPaddr);
CloseConnect(sRemoteIPaddr);
end;
end;
MainOutMessage('端口空連接攻擊: ' + sRemoteIPaddr,1);
Continue;
end;
while (True) do begin;
if g_SessionArray[nSocketIndex].MsgList.Count <= 0 then break;
UserSession:=@g_SessionArray[nSocketIndex];
nSendRetCode:=SendUserMsg(UserSession,UserSession.MsgList.Strings[0]);
if (nSendRetCode >= 0) then
begin
if nSendRetCode = 1 then begin
UserSession.dwConnctCheckTick:=GetTickCount();
UserSession.MsgList.Delete(0);
Continue;
end;
if UserSession.MsgList.Count > 100 then begin
nMsgCount:=0;
while nMsgCount <> 51 do begin
UserSession.MsgList.Delete(0);
Inc(nMsgCount);
end;
end;
Inc(n456A2C,UserSession.MsgList.Count);
MainOutMessage(UserSession.sIP +
' : ' +
IntToStr(UserSession.MsgList.Count),5);
Inc(nSendMsgCount);
end else begin //0x004523A4
UserSession.SocketHandle:= -1;
UserSession.Socket:= nil;
UserSession.MsgList.Clear;
end;
end;
end;
if (GetTickCount - dwSendKeepAliveTick) > 2 * 1000 then begin
dwSendKeepAliveTick:=GetTickCount();
if boGateReady then
ClientSocket.Socket.SendText('%--$');
end;
if (GetTickCount - dwKeepAliveTick) > 10 * 1000 then begin
boKeepAliveTimcOut:=True;
ClientSocket.Close;
end;
finally
boDecodeLock:=False;
end;
dwDecodeTime:=GetTickCount - dwDecodeTick;
if dwDecodeMsgTime < dwDecodeTime then dwDecodeMsgTime:=dwDecodeTime;
if dwDecodeMsgTime > 50 then Dec(dwDecodeMsgTime,50);
end;
又是一坨代碼,簡而言之,就是把剛才保存接收到的數據。分發到 每個用戶的自己的消息鏈表中,然后遍歷,發送出去,
代碼如下:
// 發送用戶消息
function TFrmMain.SendUserMsg(UserSession:pTUserSession;sSendMsg:String):Integer;
begin
Result:= -1;
// 如果
if UserSession.Socket <> nil then begin
// 取反
if not UserSession.bo0C then begin
// 如果不能發送,則置可用
if not UserSession.boSendAvailable and (GetTickCount > UserSession.dwSendLockTimeOut) then begin
UserSession.boSendAvailable := True;
UserSession.nCheckSendLength := 0;
boSendHoldTimeOut := True;
dwSendHoldTick := GetTickCount();
end; //004525DD
if UserSession.boSendAvailable then begin
if UserSession.nCheckSendLength >= 250 then begin
if not UserSession.boSendCheck then begin
UserSession.boSendCheck:=True;
sSendMsg:='*' + sSendMsg;
end;
if UserSession.nCheckSendLength >= 512 then begin
UserSession.boSendAvailable:=False;
UserSession.dwSendLockTimeOut:=GetTickCount + 3 * 1000;
end;
end; //00452620
UserSession.Socket.SendText(sSendMsg);
Inc(UserSession.nSendMsgLen,length(sSendMsg));
Inc(UserSession.nCheckSendLength,length(sSendMsg));
Result:= 1;
end else begin //0x0045264A
Result:= 0;
end;
end else begin //0x00452651
Result:= 0;
end;
end;
end;
登錄網關,是不是很簡單,這不是 重點,重點是市面上的很多引擎的登錄網關都基於這套機制,只需要逆向分析下其加密算法,一個自定義網關則出來了。至於過濾規則,什么IP通道,都是浮雲。。。。
