AGB  ·  Datenschutz  ·  Impressum  







Anmelden
Nützliche Links
Registrieren
Zurück Delphi-PRAXiS Programmierung allgemein Netzwerke Delphi Query an Gameserver

Query an Gameserver

Ein Thema von Flodding · begonnen am 18. Feb 2015 · letzter Beitrag vom 1. Nov 2015
Antwort Antwort
Benutzerbild von Neutral General
Neutral General

Registriert seit: 16. Jan 2004
Ort: Bendorf
5.219 Beiträge
 
Delphi 10.2 Tokyo Professional
 
#1

AW: Query an Gameserver

  Alt 26. Feb 2015, 16:40
Wenn mich nicht alles täuscht kommt der Port als Big Endian an.
Du musst also die Bytereihenfolge tauschen nachdem du den Port ausgelesen hast.

Das geht z.B. mit dieser Funktion:

Delphi-Quellcode:
function SwapWord(W: Word): Word;
asm
  xchg al, ah
end;

// ....

Move(buffer[i], response.Port, SizeOf(response.Port)); inc(i, SizeOf(response.Port));
response.Port := SwapWord(response.Port);
Edit: Auch wenn mir das komisch erscheint weil alle anderen Werte doch als Little Endian ankamen oder nicht?
Aber laut
Code:
$a5 = ord(fread($socket,1))*256 + ord(fread($socket,1));
ist das erste Byte ja scheinbar das MSB, daher Big Endian.
(Das irritiert mich grad selbst etwas aber schau mal obs so funktioniert )
Michael
"Programmers talk about software development on weekends, vacations, and over meals not because they lack imagination,
but because their imagination reveals worlds that others cannot see."

Geändert von Neutral General (26. Feb 2015 um 16:45 Uhr)
  Mit Zitat antworten Zitat
Benutzerbild von Sir Rufo
Sir Rufo

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

AW: Query an Gameserver

  Alt 26. Feb 2015, 16:58
Den PHP-Code kann man nur dann richtig interpretieren, wenn man weiß, wie dieser Ausdruck denn abgearbeitet wird.
$a5 = ord(fread($socket,1))*256 + ord(fread($socket,1)); .

Ist die Ausführungs-Reihenfolge von rechts nach links, dann wird zuerst ord(fread($socket,1)) und dann ord(fread($socket,1))*256 ausgeführt.
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)
  Mit Zitat antworten Zitat
Flodding

Registriert seit: 26. Dez 2007
Ort: Kiel
121 Beiträge
 
Turbo Delphi für Win32
 
#3

AW: Query an Gameserver

  Alt 26. Feb 2015, 17:36
Mit der Swapword-Funktion klappt es. Danke Neutral General

Ich muss jetzt aber dazu sagen, dass ich die ganze Zeit den Verdacht hatte, dass die Bytes vertauscht sind. Drauf gekommen bin ich durch das "unsigned" davor; bei den Anderen stand es nicht davor. Aber ich war fest davon überzeugt, dass das nicht so sein wird, da die anderen "Word" ja auch nicht vertauscht waren.

Ich hätt ma früher fragen sollen. Jetzt kanns ja jeder sagen.

Ich meld mich wieder wenn ich die komplette Serverliste selbst hinbekommen habe, und ja ich weiss keiner glaubt dran, oder wenn ich es nicht geschafft habe.
Flo
  Mit Zitat antworten Zitat
BadenPower

Registriert seit: 17. Jun 2009
616 Beiträge
 
#4

AW: Query an Gameserver

  Alt 26. Feb 2015, 18:27
Drauf gekommen bin ich durch das "unsigned" davor; bei den Anderen stand es nicht davor.
Das "unsigned" hat allerdings nichts damit zu tun, das sagt nur aus, ob der Wert auch ein Vorzeichen haben kann.

Da bei einer Port-Angabe es sinnlos wäre negative Werte zu ermöglichen ist der Wert als "unsigned" vorgegeben.


Dass die Bytes vertaucht sind, hätte Dir allerdings die Zuhilfename von Windows-Bordmitteln, wie z.B. dem Taschenrechner gezeigt. Dort hättest Du einfach einmal die Zahl, welche Du erhalten hast eingeben können und in Hex umwandeln lassen. Dann eifach die Hex-Werte vertauchen und zurück nach Dezimal umwandeln.

Wenn Du dann die erhaltene Zahl als Portadresse in Deinem Programm benutzt hättest. dann hättest Du gesehen, dass Daten empfangen werden.
Programmieren ist die Kunst aus Nullen und Einsen etwas sinnvollen zu gestalten.
Der bessere Künstler ist allerdings der Anwender, denn dieser findet Fehler, welche sich der Programmierer nicht vorstellen konnte.
  Mit Zitat antworten Zitat
Benutzerbild von Sir Rufo
Sir Rufo

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

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
BadenPower

Registriert seit: 17. Jun 2009
616 Beiträge
 
#6

AW: Query an Gameserver

  Alt 28. Feb 2015, 11:55
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.
Wow, da hat ja die Unit SouceQuery.pas, welche nur die Hilfsfunktionen enhält, schon fast so viele Zeilen Code, wie mein komplett funktionsfähiges Programm mit allen Parametereinstellmöglichkeiten für die Abfragen.


Da wundert es mich nicht mehr, dass viele Programme, welche nur ein Fenster haben zum Teil und das ohne Daten geladen zu haben, bereits 50kb im Hauptspeicher belegen, statt den eigentlich erforderlichen 4kb.
Programmieren ist die Kunst aus Nullen und Einsen etwas sinnvollen zu gestalten.
Der bessere Künstler ist allerdings der Anwender, denn dieser findet Fehler, welche sich der Programmierer nicht vorstellen konnte.
  Mit Zitat antworten Zitat
Benutzerbild von Sir Rufo
Sir Rufo

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

AW: Query an Gameserver

  Alt 28. Feb 2015, 14:57
Jeder wie er will von Spaghetticode bis ordentlich strukturiert. Irgendwo dazwischen sortiert man sich ein. Wo siehst du dich da (rhetorische Frage, es reicht wenn du dir die selber beantwortest).
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)
  Mit Zitat antworten Zitat
BadenPower

Registriert seit: 17. Jun 2009
616 Beiträge
 
#8

AW: Query an Gameserver

  Alt 26. Feb 2015, 17:27
Wenn mich nicht alles täuscht kommt der Port als Big Endian an.
Du musst also die Bytereihenfolge tauschen nachdem du den Port ausgelesen hast.

Das geht z.B. mit dieser Funktion:

Delphi-Quellcode:
function SwapWord(W: Word): Word;
asm
  xchg al, ah
end;
Du täuschst dich nicht.


Oder als direkte Übersetzung aus dem PHP-Code so:

Delphi-Quellcode:
          Move(lBuffer[lPos], lResponseServer.Fourth, SizeOf(lResponseServer.Fourth));
          Inc(lPos, SizeOf(lResponseServer.Fourth));
          //Move(lBuffer[lPos], lResponseServer.Port, SizeOf(lResponseServer.Port));
          lResponseServer.Port := lBuffer[lPos] * 256 + lBuffer[lPos+1];
          Inc(lPos, SizeOf(lResponseServer.Port));
Einfach nur eine Zeile im Quellcode geändert und schon kommt der richtige Port-Wert heraus.
Programmieren ist die Kunst aus Nullen und Einsen etwas sinnvollen zu gestalten.
Der bessere Künstler ist allerdings der Anwender, denn dieser findet Fehler, welche sich der Programmierer nicht vorstellen konnte.

Geändert von BadenPower (26. Feb 2015 um 17:31 Uhr)
  Mit Zitat antworten Zitat
Antwort Antwort

 
Themen-Optionen Thema durchsuchen
Thema durchsuchen:

Erweiterte Suche
Ansicht

Forumregeln

Es ist dir nicht erlaubt, neue Themen zu verfassen.
Es ist dir nicht erlaubt, auf Beiträge zu antworten.
Es ist dir nicht erlaubt, Anhänge hochzuladen.
Es ist dir nicht erlaubt, deine Beiträge zu bearbeiten.

BB-Code ist an.
Smileys sind an.
[IMG] Code ist an.
HTML-Code ist aus.
Trackbacks are an
Pingbacks are an
Refbacks are aus

Gehe zu:

Impressum · AGB · Datenschutz · Nach oben
Alle Zeitangaben in WEZ +1. Es ist jetzt 22:45 Uhr.
Powered by vBulletin® Copyright ©2000 - 2025, Jelsoft Enterprises Ltd.
LinkBacks Enabled by vBSEO © 2011, Crawlability, Inc.
Delphi-PRAXiS (c) 2002 - 2023 by Daniel R. Wolf, 2024-2025 by Thomas Breitkreuz