Thema: Delphi Query an Gameserver

Einzelnen Beitrag anzeigen

Benutzerbild von Sir Rufo
Sir Rufo

Registriert seit: 5. Jan 2005
Ort: Stadthagen
9.454 Beiträge
 
Delphi 10 Seattle Enterprise
 
#112

AW: Query an Gameserver

  Alt 26. Feb 2015, 21:40
Das die Daten vertauscht sind steht doch lang und breit in der Dokumentation:
Zitat:
Reply format
The reply always starts with FF FF FF FF 66 0A.
The format is then a series of these server address blocks:
TypeData
ByteFirst octet of IP address
ByteSecond octet of IP address
ByteThird octet of IP address
ByteFourth octet of IP address
Unsigned ShortPort number - usually 27015 (69 87) - this is network ordered, which is unlike every other Steam protocol.
Anyway, mit viel probieren bekommt man ja auch was heraus ... ok, Lesen geht manchmal schneller. Auch wenn man nicht versteht was network ordered ist, sollte der Hinweis which is unlike every other Steam protocol auf jeden Fall hellhörig machen. Da ist was anders als sonst (genau das steht da).

Da ich mir dieses Elend mit dem Auslesen und Schreiben nicht mehr ansehen kann, hier mal eine Unit, womit man dieses Pakete sehr einfach zusammenbauen und auch wieder auseinander nehmen kann.

Kleine Demo vorweg (alles was einem unbekannt vorkommt ist in der Unit SourceQuery definiert):
Delphi-Quellcode:
program SimpleTests;

{$APPTYPE CONSOLE}
{$R *.res}

uses
  IdGlobal, IdUDPClient,
  StrUtils, SysUtils,
  SourceQuery in 'SourceQuery.pas';

procedure ParseInfoResponse( AResponse: TSourceQueryBytes );
var
  LID: SQUShort;
  LEDF: SQByte;
begin
  Writeln( 'Parse-Data:' );
  Writeln;
  Writeln( 'Protocol-Header ', AResponse.ReadLong );
  Writeln( 'Packet-Header ', AResponse.ReadChar );
  Writeln( 'Protocol ', AResponse.ReadByte );
  Writeln( 'Name ', AResponse.ReadString );
  Writeln( 'Map ', AResponse.ReadString );
  Writeln( 'Folder ', AResponse.ReadString );
  Writeln( 'Game ', AResponse.ReadString );
  LID := AResponse.ReadUShort;
  Writeln( 'ID ', LID );
  Writeln( 'Players ', AResponse.ReadByte );
  Writeln( 'Max. Players ', AResponse.ReadByte );
  Writeln( 'Bots ', AResponse.ReadByte );
  Writeln( 'Server Type ', AResponse.ReadChar );
  Writeln( 'Environment ', AResponse.ReadChar );
  Writeln( 'Visibility ', AResponse.ReadByte );
  Writeln( 'VAC ', AResponse.ReadByte );

  // Testen auf "The Ship"
  if ( LID >= 2400 ) and ( LID <= 2499 )
  then
    begin
      Writeln( 'Mode ', AResponse.ReadByte );
      Writeln( 'Witnesses ', AResponse.ReadByte );
      Writeln( 'Duration ', AResponse.ReadByte );
    end;

  Writeln( 'Version ', AResponse.ReadString );
  if not AResponse.Eof
  then
    begin
      LEDF := AResponse.ReadByte;
      Writeln( 'EDF ', LEDF );
      if LEDF and $80 = $80
      then
        begin
          Writeln( 'Port ', AResponse.ReadShort );
        end;
      if LEDF and $10 = $10
      then
        begin
          Writeln( 'SteamID ', AResponse.ReadLongLong );
        end;
      if LEDF and $40 = $40
      then
        begin
          Writeln( 'Port ', AResponse.ReadShort );
          Writeln( 'Name ', AResponse.ReadString );
        end;
      if LEDF and $20 = $20
      then
        begin
          Writeln( 'Keywords ', AResponse.ReadString );
        end;
      if LEDF and $01 = $01
      then
        begin
          Writeln( 'GameID ', AResponse.ReadLongLong );
        end;
    end;
end;

procedure TestInfoResponse;
var
  LResponse: TSourceQueryBytes;
begin
  Writeln( 'Example response for Counter Strike: Source:' );
  Writeln;
  LResponse.SetData(
    {AData} HexStrToBytes(
      {AHexStr} StringReplace(
        {} 'FF FF FF FF 49 02 67 61 6D 65 32 78 73 2E 63 6F' +
        {} '6D 20 43 6F 75 6E 74 65 72 2D 53 74 72 69 6B 65' +
        {} '20 53 6F 75 72 63 65 20 23 31 00 64 65 5F 64 75' +
        {} '73 74 00 63 73 74 72 69 6B 65 00 43 6F 75 6E 74' +
        {} '65 72 2D 53 74 72 69 6B 65 3A 20 53 6F 75 72 63' +
        {} '65 00 F0 00 05 10 04 64 6C 00 00 31 2E 30 2E 30' +
        {} '2E 32 32 00',
        {OldPattern} ' ',
        {NewPattern} '',
        {Flags} [rfReplaceAll] ) ) );

  ParseInfoResponse( LResponse );
end;

procedure RealInfoRequest;
var
  LRequest, LResponse: TSourceQueryBytes;
  LUdp: TIdUDPClient;
  LBuffer: TIdBytes;
  LResponseSize: Integer;
begin

  // Prepare Request

  LRequest.WriteLong( SQ_SIMPLEPACKET_PROTOCOL_HEADER );
  LRequest.WriteByte( SQ_INFO_REQUEST_HEADER );
  LRequest.WriteString( 'Source Engine Query' );

  Writeln( 'REQUEST-DATA:' );
  Writeln;
  Writeln( LRequest.ToString );
  Writeln;

  LBuffer := LRequest.GetData;

  LUdp := TIdUDPClient.Create( nil );
  try

    // Send Request

    Write( 'Sending ... ' );
    LUdp.SendBuffer( '5.45.97.44', 2301, LBuffer );

    // Receive Response

    SetLength( LBuffer, SQ_PACKET_MAXSIZE );
    Write( 'Receiving ... ' );
    LResponseSize := LUdp.ReceiveBuffer( LBuffer );
    Writeln( LResponseSize, ' Bytes' );
    SetLength( LBuffer, LResponseSize );
    Writeln;
  finally
    LUdp.Free;
  end;

  Writeln( 'RESPONSE-DATA:' );
  Writeln;
  Writeln( DumpBuffer( LBuffer ) );
  Writeln;

  // Parse Response

  LResponse.SetData( LBuffer );

  ParseInfoResponse( LResponse );
end;

procedure RealMasterQueryRequest;
var
  LRequest, LResponse: TSourceQueryBytes;
  LBuffer: TIdBytes;
  LBufferSize: Integer;
  LUdp: TIdUDPClient;
  LServerAddress: TSQServerAddressBlock;
  LCount, LRetries: Integer;
begin

  LServerAddress.IP1 := 0;
  LServerAddress.IP2 := 0;
  LServerAddress.IP3 := 0;
  LServerAddress.IP4 := 0;
  LServerAddress.Port := 0;

  LUdp := TIdUDPClient.Create( nil );
  try

    LCount := 0;

    repeat // Daten abrufen ...

      LRequest.Clear;
      LRequest.WriteByte( $31 ); // Message Type
      LRequest.WriteByte( SQ_REGIONCODE_RESTOFTHEWORLD ); // Region Code
      LRequest.WriteString( LServerAddress.ToString ); // IP-Address
      LRequest.WriteString( '\gamedir\arma2arrowpc' ); // Filter

      LBuffer := LRequest.GetData;

      // Send Request

      LUdp.SendBuffer( 'hl2master.steampowered.com', 27011, LBuffer );

      LRetries := 0;
      repeat
        if LRetries >= 3
        then
          raise Exception.Create( 'Too many retries' );

        SetLength( LBuffer, SQ_PACKET_MAXSIZE );
        LBufferSize := LUdp.ReceiveBuffer( LBuffer, 2000 );
        Inc( LRetries );
      until LBufferSize > 0;
      SetLength( LBuffer, LBufferSize );

      LResponse.SetData( LBuffer );

      Assert( LResponse.ReadLong = SQ_SIMPLEPACKET_PROTOCOL_HEADER );
      Assert( LResponse.ReadByte = $66 );
      Assert( LResponse.ReadByte = $0A );

      // Parse the Server-Addresses
      while not LResponse.Eof do
        begin
          Inc( LCount );
          LServerAddress.IP1 := LResponse.ReadByte;
          LServerAddress.IP2 := LResponse.ReadByte;
          LServerAddress.IP3 := LResponse.ReadByte;
          LServerAddress.IP4 := LResponse.ReadByte;
          LServerAddress.Port := swap( LResponse.ReadUShort ); // swap weil die Doku das sagt

          Writeln( LCount:7, '. ', LServerAddress.ToString );
        end;

    until ( LServerAddress.ToString = '0.0.0.0:0' ); // ... bis die leere Adresse zurückkommt

  finally
    LUdp.Free;
  end;

end;

begin
  try
    TestInfoResponse;
    RealInfoRequest;
    RealMasterQueryRequest;
  except
    on E: Exception do
      Writeln( E.ClassName, ': ', E.Message );
  end;
  ReadLn;

end.
Ausgabe von RealInfoRequest :
Code:
REQUEST-DATA:

FF FF FF FF 54 53 6F 75 72 63 65 20 45 6E 67 69   ˙˙˙˙TSource Engi
6E 65 20 51 75 65 72 79 00                        ne Query.

Sending ... Receiving ... 196 Bytes

RESPONSE-DATA:

FF FF FF FF 49 11 5B 4C 2D 54 2D 53 5D 20 45 70   ˙˙˙˙I.[L-T-S] Ep
6F 63 68 20 4F 72 69 67 69 6E 73 20 28 31 2E 30   och Origins (1.0
2E 35 2E 31 2F 31 32 35 35 34 38 29 00 54 61 76   .5.1/125548).Tav
69 00 61 72 6D 61 32 61 72 72 6F 77 70 63 00 44   i.arma2arrowpc.D
61 79 5A 20 45 70 6F 63 68 20 4F 72 69 67 69 6E   ayZ Epoch Origin
73 00 8A 84 01 19 00 64 77 00 00 31 2E 36 33 2E   s.??...dw..1.63.
31 32 35 35 34 38 00 B1 FE 08 01 CC 2C AF 75 14   125548.±ž..Ģ,Æu.
40 01 62 74 2C 72 31 36 33 2C 6E 31 32 35 35 34   @.bt,r163,n12554
38 2C 73 37 2C 69 31 2C 6D 66 2C 6C 66 2C 76 66   8,s7,i1,mf,lf,vf
2C 64 74 2C 74 63 6F 6F 70 2C 67 36 35 35 34 35   ,dt,tcoop,g65545
2C 63 32 31 34 37 34 38 33 36 34 37 2D 32 31 34   ,c2147483647-214
37 34 38 33 36 34 37 2C 70 77 2C 00 8A 84 00 00   7483647,pw,.??..
00 00 00 00                                       ....

Parse-Data:

Protocol-Header -1
Packet-Header  I
Protocol       17
Name           [L-T-S] Epoch Origins (1.0.5.1/125548)
Map            Tavi
Folder         arma2arrowpc
Game           DayZ Epoch Origins
ID             33930
Players        1
Max. Players   25
Bots           0
Server Type    d
Environment    w
Visibility     0
VAC            0
Version        1.63.125548
EDF            177
Port           2302
SteamID        90094488230087681
Keywords       bt,r163,n125548,s7,i1,mf,lf,vf,dt,tcoop,g65545,c2147483647-21474
83647,pw,
GameID         33930
In der ganz kurzen Form (ohne Dump und Kommentare)) sieht ein Request dann wie folgt aus
Delphi-Quellcode:
procedure InfoRequest;
var
  LRequest, LResponse: TSourceQueryBytes;
  LUdp: TIdUDPClient;
  LBuffer: TIdBytes;
  LResponseSize: Integer;
begin
  LRequest.WriteLong( SQ_SIMPLEPACKET_PROTOCOL_HEADER );
  LRequest.WriteByte( SQ_INFO_REQUEST_HEADER );
  LRequest.WriteString( 'Source Engine Query' );

  LBuffer := LRequest.GetData;

  LUdp := TIdUDPClient.Create( nil );
  try
    LUdp.SendBuffer( '5.45.97.44', 2301, LBuffer );

    SetLength( LBuffer, SQ_PACKET_MAXSIZE );
    LResponseSize := LUdp.ReceiveBuffer( LBuffer );
    SetLength( LBuffer, LResponseSize );
  finally
    LUdp.Free;
  end;

  LResponse.SetData( LBuffer );

  ParseInfoResponse( LResponse );
end;
Der komplette Source im Anhang
Angehängte Dateien
Dateityp: zip SourceQuery.zip (4,4 KB, 18x aufgerufen)
Kaum macht man's richtig - schon funktioniert's
Zertifikat: Sir Rufo (Fingerprint: ‎ea 0a 4c 14 0d b6 3a a4 c1 c5 b9 dc 90 9d f0 e9 de 13 da 60)

Geändert von Sir Rufo (26. Feb 2015 um 21:42 Uhr)
  Mit Zitat antworten Zitat