/*** GameSpy Query *** *** queries a game server to retrieve current status * * Copyright (c) 2012 * Randy Webster (http://www.techraw.com/) * All Rights Reserved. * * This notice MUST stay intact for legal use. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * any later version. View the license at http://www.gnu.org/licenses * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * */ unit SSUNGspyQuery; interface uses SysUtils, Classes, Forms, IdBaseComponent, IdComponent, IdUDPBase, IdUDPServer, IdSocketHandle, SyncObjs, SSUNShell; type TGspyQuery = class(TIdUDPServer) private NewPktCnt,OldPktCnt: integer; Packets: array of string; ChallengeToken: string; GspyLock: TCriticalSection; function AssemblePackets: string; procedure UdpRead(Sender: TObject; AData: TStream; ABinding: TIdSocketHandle); procedure SendQuery(const Query: string); public ServerAddr: string; GspyPort: string; GspyText: TStringList; function GetValue(const ValueName: string): string; function GetValueRange(const StartLine,StopLine: string): string; procedure QueryServer(const Address,Port: string); constructor Create(AOwner: TComponent); override; destructor Destroy; override; end; implementation const CHALQRY: string = 'FE,FD,09,10,20,30,40,FF,FF,FF,01'; const DATAQRY: string = 'FE,FD,00,10,20,30,40,FF,FF,FF,01'; function TGspyQuery.GetValue(const ValueName: string): string; begin Result:=''; if (GspyText.Count > 1) then Result:=Trim(GspyText.Strings[GspyText.IndexOf(ValueName)+1]); end; function TGspyQuery.GetValueRange(const StartLine,StopLine: string): string; var i,Str,Stp: integer; begin Result:=''; if ((GspyText.Count > 1) and (GspyText.IndexOf(StartLine) > -1) and (GspyText.IndexOf(StopLine) > -1)) then begin Str:=GspyText.IndexOf(StartLine) + 1; Stp:=GspyText.IndexOf(StopLine) - 1; for i:=Str to Stp do if (Length(Trim(GspyText.Strings[i])) > 0) then Result:=Result+GspyText.Strings[i]+#13#10; end; end; procedure TGspyQuery.QueryServer(const Address,Port: string); var CN,CH: string; begin try GspyLock.Acquire; try GspyText.Clear; Active:=false; ServerAddr:=Address; GspyPort:=Port; //send challenge ChallengeToken:=''; CN:=''; CH:=''; SendQuery(CHALQRY); if (Length(Packets) > 0) then begin //attempted challenge CN:=Copy(Packets[0],6,Length(Packets[0]) - 5); //drop first 5 bytes CH:=IntToHex(SSUNShell.StrToNum(CN),0); //convert to int, then hex CH:=Copy(CH,Length(CH) - 7,8); //get last 8 bytes if (CH <> '0') then ChallengeToken:=HexToStr(Copy(CH,1,2))+HexToStr(Copy(CH,3,2))+ HexToStr(Copy(CH,5,2))+HexToStr(Copy(CH,7,2)) else //no challenge required ChallengeToken:='0'; end; //get data SendQuery(DATAQRY); GspyText.Text:=AssemblePackets; finally GspyLock.Release; end; except raise; end; end; procedure TGspyQuery.SendQuery(const Query: string); function GetQuery: string; var i: integer; begin try with TStringList.Create do try CommaText:=Query; for i:=0 to Count - 1 do begin Result:=Result+SSUNShell.HexToStr(Strings[i]); if ((i = 6) and (ChallengeToken <> '0')) then Result:=Result+ChallengeToken; end; finally Free; end; except raise; end; end; begin NewPktCnt:=0; OldPktCnt:=-1; Packets:=nil; ReceiveTimeout:=1000; Send(ServerAddr,StrToIntDef(GspyPort,29900),GetQuery); //we keep track of the packets and only give .5 sec to read each packet //if after that .5 sec the count hasn't changed, we assume no more packets while (NewPktCnt > OldPktCnt) do begin Sleep(500); OldPktCnt:=NewPktCnt; Application.ProcessMessages end; end; procedure TGspyQuery.UdpRead(Sender: TObject; AData: TStream; ABinding: TIdSocketHandle); var buf: array[0..2048] of char; i: integer; data: string; begin AData.Read(buf,AData.Size); for i:=0 to AData.Size do if (buf[i] <> #0) then data:=data+buf[i] else if (ChallengeToken <> '') then //receiving data, not ChallengeToken data:=data+#13#10; SetLength(Packets,High(Packets)+2); Packets[High(Packets)]:=data; inc(NewPktCnt); end; function TGspyQuery.AssemblePackets: string; var i,x: integer; function TrimEnd(const Packet: string): string; begin try Result:=''; if (Length(Trim(Packet)) > 0) then with TStringList.Create do try Text:=Packet; if (Count > 0) then Delete(Count - 1); if (Count > 0) then Delete(Count - 1); Result:=Text; finally Free; end; except raise; end; end; begin Result:=''; if (Length(Packets) > 0) then begin if (pos('hostname',Packets[0]) > 0) then //normal packet order begin Result:=Result+Copy(Packets[0],20,Length(Packets[0]) - 19); for i:=Low(Packets) + 1 to High(Packets) do begin x:=pos('_',Packets[i])+4; Result:=TrimEnd(Result)+Copy(Packets[i],x,Length(Packets[i]) - x); end; end else //reverse packet order begin x:=High(Packets); Result:=Result+Copy(Packets[x],21,Length(Packets[x]) - 20); for i:=High(Packets) - 1 downto Low(Packets) do begin x:=pos('_',Packets[i])+4; Result:=TrimEnd(Result)+Copy(Packets[i],x,Length(Packets[i]) - x); end; end; end; end; constructor TGspyQuery.Create(AOwner: TComponent); begin inherited Create(AOwner); OnUDPRead:=UdpRead; GspyText:=TStringList.Create; GspyLock:=TCriticalSection.Create; end; destructor TGspyQuery.Destroy; begin GspyLock.Free; GspyText.Free; Finalize(Packets); inherited; end; end.