Watch, Follow, &
Connect with Us

For forums, blogs and more please visit our
Developer Tools Community.


Welcome, Guest
Guest Settings
Help

Thread: Reading from the RCON with IdTCPClient



Permlink Replies: 7 - Last Post: Oct 22, 2016 3:16 PM Last Post By: Remy Lebeau (Te...
Martin Ocando

Posts: 4
Registered: 8/11/14
Reading from the RCON with IdTCPClient
Click to report abuse...   Click to reply to this thread Reply
  Posted: Oct 21, 2016 10:53 AM
Hi there,
A good man helped me making a code for stablish a successfull conection to my Minecraft RCON.
Here is the code. Which works very nice.

procedure TForm1.Timer1Timer(Sender: TObject);
var
  RespID: Int32;
  PktType: Int32;
  Payload: string;
begin
  if not IdTCPClient1.Connected then Exit;
  if IdTCPClient1.IOHandler.InputBufferIsEmpty then Exit;
  RespID := ReadRECONPacket(PktType, Payload);
  //ShowMessage('Reconociendo paquete '+IntToStr(PktType));
  case PktType of
    SERVERDATA_AUTH_RESPONSE: begin
      if RespID = -1 then begin
        // authentication failed...
        ShowMessage('Coneccion fallida');
        IdTCPClient1.Disconnect;
      end else begin
        // authentication successful...
        ShowMessage('Conectado exitosamente');
        SendRECONCommand('say Hello');
      end;
    end;
    SERVERDATA_RESPONSE_VALUE: begin
      // match RespID to previously sent ReqID
      // and handle Payload as needed...
      if RespID = 0 then begin
        ShowMessage(Payload);
      end
    end;
  end;
end;


But I do not have the enought knowledge to continue with this and get the console message in realtime like this:
[http://prntscr.com/cx7ocv|http://prntscr.com/cx7ocv]

I think using a memo, reading from RCON and add the strings into the memo would be a good start,
but I do not know how to read from the RCON...

After the connection, the IdTCPClient1.IOHandler.InputBuffer is allways empty, so it do not continue to the case structure.

I do not really know how Indy works, and from the documentation can no understand very well.. Do not know from where start to reading these strings.

Would you help me again good man?

Thanks

Edited by: Martin Ocando on Oct 21, 2016 11:11 AM
Remy Lebeau (Te...


Posts: 9,447
Registered: 12/23/01
Re: Reading from the RCON with IdTCPClient [Edit]
Click to report abuse...   Click to reply to this thread Reply
  Posted: Oct 21, 2016 3:45 PM   in response to: Martin Ocando in response to: Martin Ocando
Martin wrote:

A good man helped me making a code for stablish a successfull
conection to my Minecraft RCON.

That was me.

But I do not have the enought knowledge to continue with this and
get the console message in realtime like this:

[http://prntscr.com/cx7ocv|http://prntscr.com/cx7ocv]

I think using a memo, reading from RCON and add the strings into the
memo would be a good start, but I do not know how to read from the
RCON...

You already have all of the code needed to handle that. When a connection
is established, a timer is started that calls ReadRCONPacket() whenever the
socket has data to be read. You have the response payload in a String, just
Add() it to the Memo whenever ReadRCONPacket() is called:

procedure TForm1.Timer1Timer(Sender: TObject);
var
  RespID: Int32;
  PktType: Int32;
  Payload: string;
begin
  if not IdTCPClient1.Connected then Exit;
 
  if IdTCPClient1.IOHandler.InputBufferIsEmpty then
  begin
    IdTCPClient1.IOHandler.CheckForDataOnSource(0);
    if IdTCPClient1.IOHandler.InputBufferIsEmpty then Exit;
  end;
 
  RespID := ReadRECONPacket(PktType, Payload);
  case PktType of
    SERVERDATA_AUTH_RESPONSE: begin
      if RespID = -1 then begin
        IdTCPClient1.Disconnect;
        ShowMessage('Coneccion fallida');
      end else begin
        ShowMessage('Conectado exitosamente');
        SendRECONCommand('say Hello');
      end;
    end;
    SERVERDATA_RESPONSE_VALUE: begin
      Memo1.Lines.Add('[' + TimeToStr(Time) + '] Response: ' + Payload);
    end;
  end;
end;


If you actually READ THE RCON PROTOCOL SPEC (https://developer.valvesoftware.com/wiki/Source_RCON_Protocol),
you would know that RCON is an asynchronous protocol. Each command contains
a request ID, which is echoed back in the response. Multiple commands can
be sent to the server without waiting for their replies. That is why I wrote
SendRCONPacket() to return the request ID actually used, so you can save
it off somewhere and match it to the response ID returned by ReadRCONPacket().

On the other hand, if you are not planning on ever having multiple commands
being processed in parallel, then you could get rid of the timer altogether
and do something more like this instead:

const
  SERVERDATA_AUTH = 3;
  SERVERDATA_AUTH_RESPONSE = 2;
  SERVERDATA_EXECCOMMAND = 2;
  SERVERDATA_RESPONSE_VALUE = 0;
 
procedure TForm1.Button1Click(Sender: TObject);
var
  Reply: string;
begin
  IdTCPClient1.Host := '127.0.0.1';
  IdTCPClient1.Port := 20001;
  IdTCPClient1.Connect;
  SendRECONLogin('1234');
  ShowMessage('Conectado exitosamente');
  Reply := SendRECONCommand('say Hello');
  // use Reply as needed...
  ShowMessage(Reply);
end;
 
procedure TForm1.Button2Click(Sender: TObject);
begin
  IdTCPClient1.Disconnect;
end;
 
procedure TForm1.Button3Click(Sender: TObject);
var
  Reply: string;
begin
  Reply := SendRECONCommand(Edit1.Text);
  // use Reply as needed...
  ShowMessage(Reply);
end;
 
var
  gReqID: Int32 = 0;
 
function TForm1.SendRECONPacket(PktType: Int32; const Payload: string = ''): 
Int32;
var
  Bytes: TIdBytes;
begin
  Bytes := IndyTextEncoding_ASCII.GetBytes(Payload);
  try
    if gReqID < MaxInt then Inc(gReqID)
    else gReqID := 1;
    Result := gReqID;
 
    IdTCPClient1.IOHandler.Write(Int32(Length(Bytes)+10), False);
    IdTCPClient1.IOHandler.Write(Result, False);
    IdTCPClient1.IOHandler.Write(PktType, False);
    IdTCPClient1.IOHandler.Write(Bytes);
    IdTCPClient1.IOHandler.Write(UInt16(0), False);
  except
    IdTCPClient1.Disconnect;
    raise;
  end;    
end;
 
procedure TForm1.SendRECONLogin(const Password: String);
var
  ReqID, RespID, PktType: Int32;
  Reply: String;
begin
  {
  From https://developer.valvesoftware.com/wiki/Source_RCON_Protocol#SERVERDATA_AUTH_RESPONSE:
 
  When the server receives an auth request, it will respond with an empty 
SERVERDATA_RESPONSE_VALUE,
  followed immediately by a SERVERDATA_AUTH_RESPONSE indicating whether authentication 
succeeded or
  failed. Note that the status code is returned in the packet id field, so 
when pairing the response with the
  original auth request, you may need to look at the packet id of the preceeding 
SERVERDATA_RESPONSE_VALUE. 
  }
 
  ReqID := SendRECONPacket(SERVERDATA_AUTH, Password);
 
  RespID := ReadRECONPacket(PktType, Reply);
  if (PktType <> SERVERDATA_RESPONSE_VALUE) or (RespID <> ReqID) then
    raise Exception.Create('Received unexpected packet');
 
  RespID := ReadRECONPacket(PktType, Reply);
  if PktType <> SERVERDATA_AUTH_RESPONSE then
    raise Exception.Create('Received unexpected packet');
 
  if RespID <> ReqID then
  begin
    if RespID <> -1 then
      raise Exception.Create('Received unexpected packet');
    raise Exception.Create('Authentication failed');
  end;
end;
 
function TForm1.SendRECONCommand(const Cmd: String): string;
var
  ReqID, TermReqID, RespID, PktType: Int32;
  Reply: string;
begin
  {
  From https://developer.valvesoftware.com/wiki/Source_RCON_Protocol#Multiple-packet_Responses:
 
  Most responses are small enough to fit within the maximum possible packet 
size of 4096 bytes.
  However, a few commands such as cvarlist and, occasionally, status produce 
responses too
  long to be sent in one response packet. When this happens, the server will 
split the response
  into multiple SERVERDATA_RESPONSE_VALUE packets. Unfortunately, it can 
be difficult to
  accurately determine from the first packet alone whether the response has 
been split. 
 
  One common workaround is for the client to send an empty SERVERDATA_RESPONSE_VALUE
  packet after every SERVERDATA_EXECCOMMAND request. Rather than throwing 
out the
  erroneous request, SRCDS mirrors it back to the client, followed by another 
RESPONSE_VALUE
  packet containing 0x0000 0001 0000 0000 in the packet body field. Because 
SRCDS always
  responds to requests in the order it receives them, receiving a response 
packet with an empty
  packet body guarantees that all of the meaningful response packets have 
already been received.
  Then, the response bodies can simply be concatenated to build the full 
response. 
  }
 
  ReqID := SendRECONPacket(SERVERDATA_EXECCOMMAND, Cmd);
  TermReqID := SendRECONPacket(SERVERDATA_RESPONSE_VALUE, '');
 
  repeat
    RespID := ReadRECONPacket(PktType, Reply);
    if PktType <> SERVERDATA_RESPONSE_VALUE then
      raise Exception.Create('Received unexpected packet');
 
    if RespID <> ReqID then
    begin
      if RespID <> TermReqID then
        raise Exception.Create('Received unexpected packet');
 
      RespID := ReadRECONPacket(PktType, Reply);
      if (PktType <> SERVERDATA_RESPONSE_VALUE) or (RespID <> TermReqID) then
        raise Exception.Create('Received unexpected packet');
 
      Break;
    end;
 
    Result := Result + Reply;
  until False;
end;
 
function TForm1.ReadRECONPacket(var PktType: Integer; var Payload: String): 
Int32;
var
  Len: Int32;
begin
  try
    Len     := IdTCPClient1.IOHandler.ReadInt32(False);
    Result  := IdTCPClient1.IOHandler.ReadInt32(False);
    PktType := IdTCPClient1.IOHandler.ReadInt32(False);
    Payload := IdTCPClient1.IOHandler.ReadString(Len-10, IndyTextEncoding_ASCII);
    IdTCPClient1.IOHandler.Discard(2);
  except
    IdTCPClient1.Disconnect;
    raise;
  end;
end;


After the connection, the IdTCPClient1.IOHandler.InputBuffer is
allways empty, so it do not continue to the case structure.

You are using the first revision of the code I posted on StackOverflow.
As you can see above (and on StackOverflow), a newer revision of the code
calls CheckDataForSource() if the InputBuffer is empty. That shouldn't be
needed, since Connected() can also populate the InputBuffer with data, but
I added the call to CheckForDataOnSource() anyway, just in case.

--
Remy Lebeau (TeamB)
Martin Ocando

Posts: 4
Registered: 8/11/14
Re: Reading from the RCON with IdTCPClient [Edit]
Click to report abuse...   Click to reply to this thread Reply
  Posted: Oct 21, 2016 7:12 PM   in response to: Remy Lebeau (Te... in response to: Remy Lebeau (Te...
I will keep the timer, because I have a plan to add a command queue,
I am learning how this work, I tested your new version,
but for some reason it does not work very well for me unless I comment some sections,
anyway I think the new version is a very elegant solution.

The unique issue I am having is:
With localhost:
IdTCPClient1.Host := '127.0.0.1';
IdTCPClient1.Port := 20001;

Works very well, as needed.
But for a remote host not. (Discard firewalls)

IdTCPClient1.Host := '190.5.200.196';
IdTCPClient1.Port := 20001;

I tried another rcon application [https://github.com/Tiiffi/mcrcon/blob/master/mcrcon.c|https://github.com/Tiiffi/mcrcon/blob/master/mcrcon.c]
and with that I can get connected to the remote host without any problem.

I do not know if there is something missing for IdTCPClient1,
but when it try to connect it get stucked on 1*

And if I try to send a cmd it justs say 2*

The timer sleep is set on 1ms but I tried with more highest values like 500ms

Finally with the remote host it never reach 3* even using the same config as my localhost.

Sorry for my disorganized way to code, but it is just for testing purpouses, I will fix that later.

procedure TForm1.Timer1Timer(Sender: TObject);
var
  RespID: Int32;
  PktType: Int32;
  Payload: string;
begin
  memo1.Lines.Add('Timer');                                                    // 1*
  if not IdTCPClient1.Connected then
  begin
    memo1.Lines.Add('No conectado');                                      // 2*
    IdTCPClient1.Connect;
    //Exit;
  end;
  if IdTCPClient1.IOHandler.InputBufferIsEmpty then
    begin
      memo1.Lines.Add('InputBufferIsEmpty: Si, exit..');
      Exit;
    end;
  RespID := ReadRECONPacket(PktType, Payload);
  memo1.Lines.Add('Reconociendo paquete '+IntToStr(PktType));
  case PktType of
    SERVERDATA_AUTH_RESPONSE: begin
      if RespID = -1 then begin
        // authentication failed...
        ShowMessage('Coneccion fallida');
        IdTCPClient1.Disconnect;
      end else begin
        // authentication successful...
        //ShowMessage('Conectado exitosamente');
        memo1.Lines.Add('Conectado exitosamente');                     // 3*
        SendRECONCommand('say Hello');
        //IdTCPClient1.Disconnect;
      end;
    end;
    SERVERDATA_RESPONSE_VALUE: begin
      // match RespID to previously sent ReqID
      // and handle Payload as needed...
      //RespID := ReadRECONPacket(PktType, Payload);
      //ShowMessage(IntToStr(RespID));
      memo1.Lines.Add('SERVERDATA_RESPONSE_VALUE - '+IntToStr(RespID));
      memo1.Lines.Add('PktType: '+IntToStr(PktType));
      memo1.Lines.Add('Payload: '+Payload);
    end;
  end;
end;
 


Edited by: Martin Ocando on Oct 21, 2016 7:13 PM
Remy Lebeau (Te...


Posts: 9,447
Registered: 12/23/01
Re: Reading from the RCON with IdTCPClient [Edit] [Edit]
Click to report abuse...   Click to reply to this thread Reply
  Posted: Oct 22, 2016 11:37 AM   in response to: Martin Ocando in response to: Martin Ocando
Martin wrote:

I will keep the timer, because I have a plan to add a command queue

I would suggest using a dedicated reading thread instead. The timer was
just an example, but it is not the best option for production code.

I tested your new version, but for some reason it does not work very
well for me unless I comment some sections

Such as? I can't help you if you don't explain what is wrong. And I don't
have a Minecraft server to test against, so I'm coding blind here, you are
going to have to be more pro-active in your debugging.

The unique issue I am having is:
With localhost:
IdTCPClient1.Host := '127.0.0.1';
IdTCPClient1.Port := 20001;
Works very well, as needed.

But for a remote host not. (Discard firewalls)
IdTCPClient1.Host := '190.5.200.196';
IdTCPClient1.Port := 20001;

And what is the actual problem? Socket I/O doesn't care if the server is
running on localhost or not. So it has to be an issue with the data exchange
instead.

I tried another rcon application
https://github.com/Tiiffi/mcrcon/blob/master/mcrcon.c
and with that I can get connected to the remote host without any problem.

That code is written in C, not Delphi, so there are major coding differences.
But from a logic perspective, it is doing basically the same thing that
the latest code I gave you is doing, just with some minor logic differences:

- the C code is using the same request ID for all outgoing packets, whereas
my code uses dynamic request IDs.

- the C code doesn't handle the gotcha mentioned in the RCON protocol spec
regarding multi-packet responses, whereas my code does.

- when receiving a packet, the C code returns the raw packet to the caller,
whereas my code parses out the packet and returns just the relaeant pieces.

I do not know if there is something missing for IdTCPClient1, but when
it try to connect it get stucked on 1*

Connected() is a non-blocking function, it should never get stuck. And in
the code I gave you, the timer should not be running at all unless the socket
is connected, so actually you could remove the call to Connected() altogether.

And you are STILL not using the latest timer code I gave you, as you are
not calling CheckForDataOnSource() when the InputBuffer is empty.

And if I try to send a cmd it justs say 2*

Finally with the remote host it never reach 3* even using the same
config as my localhost.

The only way that could happen is if you are not receiving a SERVERDATA_AUTH_RESPONSE
packet.

--
Remy Lebeau (TeamB)
Martin Ocando

Posts: 4
Registered: 8/11/14
Re: Reading from the RCON with IdTCPClient [Edit] [Edit]
Click to report abuse...   Click to reply to this thread Reply
  Posted: Oct 22, 2016 2:15 PM   in response to: Remy Lebeau (Te... in response to: Remy Lebeau (Te...
Well, my last reply was not up to date with the latest timer,
I was checking constantly your code on StackOverflow and I noticed you updated it several times,
and I was following your steeps, look at the unit:

unit UPrincipal;
 
interface
 
uses
  Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
  Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls, Vcl.ExtCtrls,
  IdBaseComponent, IdComponent, IdTCPConnection, IdTCPClient, IdGlobal;
 
type
  TForm1 = class(TForm)
    IdTCPClient1: TIdTCPClient;
    Timer1: TTimer;
    Memo1: TMemo;
    Memo2: TMemo;
    Button1: TButton;
    Button2: TButton;
    Button3: TButton;
    procedure IdTCPClient1Connected(Sender: TObject);
    procedure IdTCPClient1Disconnected(Sender: TObject);
    procedure Timer1Timer(Sender: TObject);
    procedure Button1Click(Sender: TObject);
    procedure Button2Click(Sender: TObject);
    procedure Button3Click(Sender: TObject);
    function SendRECONPacket(PktType: Int32; const Payload: string = ''): Int32;
    function SendRECONLogin(const Password: String): Int32;
    function SendRECONCommand(const Cmd: String): Int32;
    function ReadRECONPacket(var PktType: Integer; var Payload: String): Int32;
  private
    { Private declarations }
  public
    { Public declarations }
  end;
 
const
  SERVERDATA_AUTH = 3;
  SERVERDATA_AUTH_RESPONSE = 2;
  SERVERDATA_EXECCOMMAND = 2;
  SERVERDATA_RESPONSE_VALUE = 0;
 
var
  Form1: TForm1;
 
implementation
 
{$R *.dfm}
 
 
procedure TForm1.Button1Click(Sender: TObject);
begin
  IdTCPClient1.Host := '127.0.0.1';
  IdTCPClient1.Host := '198.100.146.196';
  //IdTCPClient1.Host := '192.168.1.101';
  IdTCPClient1.Port := 20001;
  IdTCPClient1.Connect;
  SendRECONLogin('1234');
end;
 
procedure TForm1.Button2Click(Sender: TObject);
begin
  IdTCPClient1.Disconnect;
end;
 
procedure TForm1.Button3Click(Sender: TObject);
begin
  SendRECONCommand('list');
end;
 
procedure TForm1.IdTCPClient1Connected(Sender: TObject);
begin
  Timer1.Enabled := True;
end;
 
procedure TForm1.IdTCPClient1Disconnected(Sender: TObject);
begin
  Timer1.Enabled := False;
end;
 
procedure TForm1.Timer1Timer(Sender: TObject);
var
  RespID: Int32;
  PktType: Int32;
  Payload: string;
begin
  if not IdTCPClient1.Connected then Exit;
 
  if IdTCPClient1.IOHandler.InputBufferIsEmpty then
  begin
    IdTCPClient1.IOHandler.CheckForDataOnSource(0);
    if IdTCPClient1.IOHandler.InputBufferIsEmpty then Exit;
  end;
 
  RespID := ReadRECONPacket(PktType, Payload);
  case PktType of
    SERVERDATA_AUTH_RESPONSE: begin
      if RespID = -1 then begin
        // authentication failed...
        IdTCPClient1.Disconnect;
      end else begin
        // authentication successful...
        memo1.Lines.Add('Conectado exitosamente');
        SendRECONCommand('list');
      end;
    end;
    SERVERDATA_RESPONSE_VALUE: begin
      // match RespID to previously sent ReqID
      // and handle Payload as needed...
      memo1.Lines.Add('Payload: '+Payload);
    end;
  end;
end;
 
var
  gReqID: Int32 = 0;
 
function TForm1.SendRECONPacket(PktType: Int32; const Payload: string = ''): Int32;
var
  Bytes: TIdBytes;
begin
  Bytes := IndyTextEncoding_ASCII.GetBytes(Payload);
  try
    if gReqID < MaxInt then Inc(gReqID)
    else gReqID := 1;
    Result := gReqID;
 
    IdTCPClient1.IOHandler.Write(Int32(Length(Bytes)+10), False);
    IdTCPClient1.IOHandler.Write(Result, False);
    IdTCPClient1.IOHandler.Write(PktType, False);
    IdTCPClient1.IOHandler.Write(Bytes);
    IdTCPClient1.IOHandler.Write(UInt16(0), False);
  except
    IdTCPClient1.Disconnect;
    raise;
  end;
end;
 
function TForm1.SendRECONLogin(const Password: String): Int32;
begin
  Result := SendRECONPacket(SERVERDATA_AUTH, Password);
end;
 
function TForm1.SendRECONCommand(const Cmd: String): Int32;
begin
  Result := SendRECONPacket(SERVERDATA_EXECCOMMAND, Cmd);
end;
 
function TForm1.ReadRECONPacket(var PktType: Integer; var Payload: String): Int32;
var
  Len: Int32;
begin
  try
    Len     := IdTCPClient1.IOHandler.ReadInt32(False);
    Result  := IdTCPClient1.IOHandler.ReadInt32(False);
    PktType := IdTCPClient1.IOHandler.ReadInt32(False);
    Payload := IdTCPClient1.IOHandler.ReadString(Len-10, IndyTextEncoding_ASCII);
    IdTCPClient1.IOHandler.Discard(2);
  except
    IdTCPClient1.Disconnect;
    raise;
  end;
end;
 
end.
 


There is a minecraft server on that IP if you want to test it.
I decide to use the timer example, because you told me:

+*On the other hand, if you are not planning on ever having multiple commands
being processed in parallel, then you could get rid of the timer altogether
and do something more like this instead:*+

Do not know how to add quotes..

I would like to having multiple commands running.

I will try the other code again, and will tell you something.

Thanks
Remy Lebeau (Te...


Posts: 9,447
Registered: 12/23/01
Re: Reading from the RCON with IdTCPClient [Edit] [Edit]
Click to report abuse...   Click to reply to this thread Reply
  Posted: Oct 22, 2016 2:57 PM   in response to: Martin Ocando in response to: Martin Ocando
Martin wrote:

Well, my last reply was not up to date with the latest timer,
I was checking constantly your code on StackOverflow and I
noticed you updated it several times,

And still am.

There is a minecraft server on that IP if you want to test it.

I have done so now, and the following code works:

unit Unit1;
 
interface
 
uses
  Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, 
Vcl.Graphics,
  Vcl.Controls, Vcl.Forms, Vcl.Dialogs, IdBaseComponent, IdComponent,
  IdTCPConnection, IdTCPClient, Vcl.ExtCtrls, Vcl.StdCtrls;
 
type
  TForm1 = class(TForm)
    Button1: TButton;
    Button2: TButton;
    Button3: TButton;
    Memo1: TMemo;
    Timer1: TTimer;
    IdTCPClient1: TIdTCPClient;
    procedure Button1Click(Sender: TObject);
    procedure Button2Click(Sender: TObject);
    procedure Button3Click(Sender: TObject);
    procedure IdTCPClient1Connected(Sender: TObject);
    procedure IdTCPClient1Disconnected(Sender: TObject);
    procedure Timer1Timer(Sender: TObject);
  private
    { Private declarations }
    function SendRECONPacket(PktType: Int32; const Payload: string = ''): 
Int32;
    function SendRECONLogin(const Password: String): Int32;
    function SendRECONCommand(const Cmd: String): Int32;
    function ReadRECONPacket(var PktType: Integer; var Payload: String): 
Int32;
  public
    { Public declarations }
  end;
 
var
  Form1: TForm5;
 
implementation
 
{$R *.dfm}
 
uses
  IdGlobal;
 
const
  SERVERDATA_AUTH = 3;
  SERVERDATA_AUTH_RESPONSE = 2;
  SERVERDATA_EXECCOMMAND = 2;
  SERVERDATA_RESPONSE_VALUE = 0;
 
procedure TForm1.Button1Click(Sender: TObject);
begin
  //IdTCPClient1.Host := '127.0.0.1';
  IdTCPClient1.Host := '198.100.146.196';
  //IdTCPClient1.Host := '192.168.1.101';
  IdTCPClient1.Port := 20001;
  IdTCPClient1.Connect;
  SendRECONLogin('1234');
end;
 
procedure TForm1.Button2Click(Sender: TObject);
begin
  IdTCPClient1.Disconnect;
end;
 
procedure TForm1.Button3Click(Sender: TObject);
begin
  SendRECONCommand('list');
end;
 
procedure TForm1.IdTCPClient1Connected(Sender: TObject);
begin
  Caption := 'Connected';
  Timer1.Enabled := True;
end;
 
procedure TForm1.IdTCPClient1Disconnected(Sender: TObject);
begin
  Caption := 'Disconnected';
  Timer1.Enabled := False;
end;
 
procedure TForm1.Timer1Timer(Sender: TObject);
var
  RespID: Int32;
  PktType: Int32;
  Payload: string;
begin
  try
    if not IdTCPClient1.Connected then
      Exit;
 
    if IdTCPClient1.IOHandler.InputBufferIsEmpty then
    begin
      IdTCPClient1.IOHandler.CheckForDataOnSource(0);
      IdTCPClient1.IOHandler.CheckForDisconnect(True, False);
      if IdTCPClient1.IOHandler.InputBufferIsEmpty then Exit;
    end;
 
    RespID := ReadRECONPacket(PktType, Payload);
    case PktType of
      SERVERDATA_AUTH_RESPONSE: begin
        if RespID = -1 then begin
          // authentication failed...
          Memo1.Lines.Add('Login rejected');
          IdTCPClient1.Disconnect;
        end else begin
          // authentication successful...
          Memo1.Lines.Add('Login OK');
        end;
      end;
      SERVERDATA_RESPONSE_VALUE: begin
        // match RespID to previously sent ReqID
        // and handle Payload as needed...
      end;
    end;
  except
    IdTCPClient1.Disconnect;
  end;
end;
 
var
  gReqID: Int32 = 0;
 
function TForm1.SendRECONPacket(PktType: Int32; const Payload: string = ''): 
Int32;
var
  Bytes: TIdBytes;
begin
  Bytes := IndyTextEncoding_ASCII.GetBytes(Payload);
  try
    if gReqID < MaxInt then Inc(gReqID)
    else gReqID := 1;
    Result := gReqID;
 
    IdTCPClient1.IOHandler.WriteBufferOpen;
    try
      IdTCPClient1.IOHandler.Write(Int32(Length(Bytes)+10), False);
      IdTCPClient1.IOHandler.Write(Result, False);
      IdTCPClient1.IOHandler.Write(PktType, False);
      IdTCPClient1.IOHandler.Write(Bytes);
      IdTCPClient1.IOHandler.Write(UInt16(0), False);
      IdTCPClient1.IOHandler.WriteBufferClose;
    except
      IdTCPClient1.IOHandler.WriteBufferCancel;
      raise;
    end;
    Memo1.Lines.Add(Format('[C] %d: (%d) %s', [Result, PktType, Payload]));
  except
    IdTCPClient1.Disconnect;
    raise;
  end;
end;
 
function TForm1.SendRECONLogin(const Password: String): Int32;
begin
  Result := SendRECONPacket(SERVERDATA_AUTH, Password);
end;
 
function TForm1.SendRECONCommand(const Cmd: String): Int32;
begin
  Result := SendRECONPacket(SERVERDATA_EXECCOMMAND, Cmd);
end;
 
function TForm1.ReadRECONPacket(var PktType: Integer; var Payload: String): 
Int32;
var
  Len: Int32;
begin
  try
    Len     := IdTCPClient1.IOHandler.ReadInt32(False);
    Result  := IdTCPClient1.IOHandler.ReadInt32(False);
    PktType := IdTCPClient1.IOHandler.ReadInt32(False);
    Payload := IdTCPClient1.IOHandler.ReadString(Len-10, IndyTextEncoding_ASCII);
    IdTCPClient1.IOHandler.Discard(2);
  except
    IdTCPClient1.Disconnect;
    raise;
  end;
  Memo1.Lines.Add(Format('[S] %d: (%d) %s', [Result, PktType, Payload]));
end;
 
end.


I decide to use the timer example, because you told me:
<snip>

I also told you using a timer was just an example, using a dedicated reading
thread would be a better choice.

--
Remy Lebeau (TeamB)
Martin Ocando

Posts: 4
Registered: 8/11/14
Re: Reading from the RCON with IdTCPClient [Edit] [Edit]
Click to report abuse...   Click to reply to this thread Reply
  Posted: Oct 22, 2016 3:11 PM   in response to: Remy Lebeau (Te... in response to: Remy Lebeau (Te...
Works very well,
I will make the thread maybe working with loop constantly reading, as a timer?

Thanks for help me good man!
Have a nice day
Remy Lebeau (Te...


Posts: 9,447
Registered: 12/23/01
Re: Reading from the RCON with IdTCPClient [Edit] [Edit]
Click to report abuse...   Click to reply to this thread Reply
  Posted: Oct 22, 2016 3:16 PM   in response to: Remy Lebeau (Te... in response to: Remy Lebeau (Te...
Remy wrote,

Well, my last reply was not up to date with the latest timer, I was
checking constantly your code on StackOverflow and I noticed you
updated it several times,
And still am.

I have updated the StackOverflow answer with working code for both timer
and non-timer examples.

--
Remy Lebeau (TeamB)
Legend
Helpful Answer (5 pts)
Correct Answer (10 pts)

Server Response from: ETNAJIVE02