登陆网关(LoginGate) + 角色网关(SelGate)详解
传奇这款游戏,一直对我的影响很大。当年为了玩传奇,逃课,被老师叫过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、HeroM2、HGEM2(原3K、IGE)、Legend、GEEM2、77M2…都是换汤不换药。变的就是 加密方式。这里不得不说,JSocket就是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通道,都是浮云…
好了.
接下来是SelGate
哈哈 一句话就可以KO它…
来来来…
其实,SelGate也就是 LoginGate,其源码实现完全相同。不必怀疑,市面上大家都是这么做的…
哈哈 哈哈…
19191NET资源网 » 登陆网关(LoginGate) + 角色网关(SelGate)详解