How to query a CS Server

From Scriptwiki
Revision as of 12:40, 11 February 2011 by Vliedel (talk | contribs) (nang update)

Jump to: navigation, search

If you want to e.g. get the number and names of all player who are currently playing on a CS-server, you have to send a so-called "query" to this server (using UDP) and handle everything the server sends back to you. This isn't as easy as it sounds. At first, you would have to 'learn', or, at least, understand the protocol a CS-server uses (e.g. what do I have to send to the server to get a 'good' reply and what's the structure of this reply). If you want to learn more about these protocols, take a look at http://dev.kquery.com/ or http://www.valve-erc.com/srcsdk/Code/Networking/serverqueries.html or http://developer.valvesoftware.com/wiki/Server_Queries .


The following script echos the output to your active window. You will have to modify it to be able to use it as 'bot'. It's a more complex script and you don't need to understand it (completely) to be able to use it.

; Source RCON control snippet by NaNg
; Date: 10-02-2011
;
; Remote control window for Source engine based games (e.g. HL2, OrangeBox, CS:S, CSP etc.),
; based on the Source RCON Protocol found at http://developer.valvesoftware.com/wiki/Source_RCON_Protocol
;
; Usage:
; * /sourcercon <ip> <port> <pass>
 
; Define a prefix for sockent names.
alias -l srcon.prefix { return SRcon }
 
; Set the command requests and response types.
alias -l srcon.req.execcmd { return 2 }
alias -l srcon.req.auth { return 3 }
alias -l srcon.res.cmd { return 0 }
alias -l srcon.res.auth { return 2 }
 
; Turn the given number to a 4-byte number.
alias -l srcon.inttobytes {
  tokenize 46 $longip($1)
  return $4 $3 $2 $1
}
 
; Validates arguments and tries to open the socket to the game server for authentication.
alias sourcercon {
  ; Validate arguments (simple, no need for full validation).
  if ((!$longip($1)) || ($2 !isnum 1-65536) || (!$3)) {
    echo -ag Invalid parameters.
    return
  }
 
  ; Save the server's address.
  var %server.addr = $+($1,:,$2)
 
  ; Check that the user has not opened a Rcon window for the same server already.
  ; This makes sure that the user won't have a Rcon step over another Rcon for the same server.
  if ($window(%server.addr)) {
    echo -ag Rcon window for $1 is already open! close it before trying.
    return
  }
 
  ; Open the Rcon window whilst activating it, with an editbox, hide the @ prefix and maximize it.
  window -aek0x @ $+ %server.addr sourcercon.execcmd
  aline 12 @ $+ %server.addr Source RCON window. If there's no error, you're connected!
  aline 12 @ $+ %server.addr Type a command\cvar to send to the server, responses will be displayed here.
  aline 12 @ $+ %server.addr Warning! commands like exit or quit will close the server! avoid using them!
 
  ; Set the socket name we'll use.
  var %sockname = $+($srcon.prefix,%server.addr)
 
  ; Open the socket to the server.
  sockopen %sockname $1 $2
 
  ; Mark the socket with the Rcon password.
  sockmark %sockname $3
}
 
; Catch all sockopens with the Source Rcon sockets prefix.
; Build the authentication request packet and send it to the server.
on *:SOCKOPEN:$($+($srcon.prefix,*)): {
  ; Initialize the request ID for the connection.
  set %srcon.id. $+ $sockname 0
 
  ; Constructed auth request to send to the server.
  noop $srcon.getrequest(%srcon.id. [ $+ [ $sockname ] ], $srcon.req.auth, $sock($sockname).mark, &srcon.auth)
 
  ; Send the authentication packet.
  sockwrite $sockname &srcon.auth
}
 
; Read data from the game server.
on *:SOCKREAD:$($+($srcon.prefix,*)): {
  ; Get the window name for the current game server.
  var %srcon.window = $+(@, $remove($sockname, $srcon.prefix))
 
  ; If there were errors, $sockerr will tell us so.
  ; Write the error message to the window and clear the connection.
  if ($sockerr) {
    aline 04 %srcon.window * Error: Reading from socket: $sock($sockname).wsmsg
    srcon.clear $sockname
    return
  }
 
  ; If a prior packet didn't finish receiving, continue reading it.
  if ($hget($sockname)) {
    ; Set the packet size.
    var %srcon.pckt.size = $hget($sockname, size)
 
    ; Set the number of bytes left to read.
    var %srcon.pckt.left = $hget($sockname, left)
 
    ; Set the request id.
    var %srcon.pckt.id = $hget($sockname, id)
 
    ; Set the command response type.
    var %srcon.pckt.cmd = $hget($sockname, cmd)
 
    ; Set the cut string1.
    noop $hget($sockname, string1, &srcon.pckt.string1)
 
    ; Read the rest of string1.
    sockread %srcon.pckt.left &srcon.pckt.rest
    dec %srcon.pckt.left $sockbr
 
    ; Append the rest of string1 to the already received string1.
    bcopy &srcon.pckt.string1 $calc($bvar(&srcon.pckt.string1, 0) + 1) &srcon.pckt.rest 1 -1
 
    ; Clear the un-needed variables.
    hfree $sockname
  }
  ; Else, if the packet wasn't splitted.
  else {
    ; Read the packet size.
    sockread 4 &srcon.pckt.size
    var %srcon.pckt.size = $bvar(&srcon.pckt.size, 1).long
 
    ; Initialize the number of bytes left to read.
    var %srcon.pckt.left = %srcon.pckt.size
 
    ; Read the packet id.
    sockread 4 &srcon.pckt.id
    var %srcon.pckt.id = $bvar(&srcon.pckt.id, 1).long
    dec %srcon.pckt.left $sockbr
 
    ; If the request id is an error, stop processing, display an error and clear the connection.
    if ($srcon.inttobytes(%srcon.pckt.id) == 255 255 255 255) {
      aline 04 %srcon.window * Error: Failed authentication (probably wrong password).
      srcon.clear $sockname
      return
    }
 
    ; Read the packet command response type.
    sockread 4 &srcon.pckt.cmd
    var %srcon.pckt.cmd = $bvar(&srcon.pckt.cmd, 1).long
    dec %srcon.pckt.left $sockbr
 
    ; Read string1.
    sockread %srcon.pckt.left &srcon.pckt.string1
    dec %srcon.pckt.left $sockbr
  }
 
  ; If the number of bytes left to read is still positive, the packet was splitted.
  ; This means that we'll save the variables to globals so it can be used in the next read.
  if (%srcon.pckt.left > 0) {
    hmake $sockname
    hadd $sockname size %srcon.pckt.size
    hadd $sockname left %srcon.pckt.left
    hadd $sockname id %srcon.pckt.id
    hadd $sockname cmd %srcon.pckt.cmd
    hadd -b $sockname string1 &srcon.pckt.string1
  }
  ; Only if we've finished reading all the packet and the response type was a command response, display it.
  ; NOTE: At this point, string1 also contains string2 (the one 0x00 byte), however it doesn't matter to us
  ; since we're only reading the text which is terminated in string1 by a 0x00 byte.
  elseif ((%srcon.pckt.left == 0) && (%srcon.pckt.cmd == $srcon.res.cmd)) {
    ; Convert the binary string1 to a usable variable.
    var %srcon.pckt.string1 = $bvar(&srcon.pckt.string1, 1, $bvar(&srcon.pckt.string1, 0)).text
 
    ; Tokenize the lines to be printed and print them.
    tokenize $asc($lf) %srcon.pckt.string1
    aline %srcon.window $*
  }
}
 
; Sockets may close on their own if not kept alive.
; In case that happens when the window is still open, the socket will be reopened.
on *:SOCKCLOSE:$($+($srcon.prefix,*)): {
  ; Get the window name for the current game server.
  var %srcon.window = $+(@, $remove($sockname, $srcon.prefix))
 
  ; If the window and the current request id still exists, reopen the socket.
  if (($window(%srcon.window)) && (%srcon.id. [ $+ [ $sockname ] ])) {
    ; Save the socket details before terminating it.
    %srcon.name = $sockname
    %srcon.ip = $sock($sockname).ip
    %srcon.port = $sock($sockname).port
    %srcon.pass = $sock($sockname).mark
 
    ; Terminate the socket so it could be reopened.
    sockclose $sockname
 
    ; Reopen the socket to the server.
    sockopen %srcon.name %srcon.ip %srcon.port
 
    ; Mark the socket with the Rcon password.
    sockmark %srcon.name %srcon.pass
  }
}
 
; Sends user input to the server as is.
; NOTE: Commands like exit or quit will close the server!
on *:INPUT:@: {
  ; Get the socket name.
  var %srcon.socket = $+($srcon.prefix, $remove($target, @))
 
  ; Check if the socket is still alive.
  if (!$sock(%srcon.socket)) {
    aline 04 $target * Error: Socket is dead. Close the window and try again.
    return
  }
 
  ; Build the execute command request packet and send it to the server.
 
  ; Increment the current request id.
  inc %srcon.id. $+ %srcon.socket
 
  ; Construct the request to send to the game server.
  noop $srcon.getrequest(%srcon.id. [ $+ [ %srcon.socket ] ], $srcon.req.execcmd, $1-, &srcon.cmd)
 
  aline $target -> $1-
 
  ; Send the command request.
  sockwrite %srcon.socket &srcon.cmd
}
 
; If the user has closed the rcon window, clear the socket.
on *:CLOSE:@: {
  srcon.clear $+($srcon.prefix, $remove($target, @))
}
 
; Constructs a request with the given parameters:
; $1 = Request ID
; $2 = Request type
; $3 = Request data (password/command)
; $4 = Bvar to store the request
alias -l srcon.getrequest {
  ; Clear the given bvar to make sure it doesn't contain any preset values.
  bunset $4
 
  ; Set the given request ID in the request.
  bset $4 5 $srcon.inttobytes($1)
 
  ; Set the given request type.
  bset $4 9 $srcon.inttobytes($2)
 
  ; Set string1 the request data.
  bset -t $4 13 $3
 
  ; Terminate string1 with 0x00 and set string2 to 0x00.
  bset $4 $calc($bvar($4,0) + 1) 00 00
 
  ; Set the packet size.
  bset $4 1 $srcon.inttobytes($calc($bvar($4, 0) - 4))
}
 
; Clears the socket and the request id.
alias -l srcon.clear {
  ; If there's a socket named accordingly, close it.
  if ($sock($1)) {
    sockclose $1
  }
 
  ; Clear the id var.
  unset %srcon.id. $+ $1
}