Difference between revisions of "How to query a CS Server"

From Scriptwiki
Jump to: navigation, search
m
m (nang update)
Line 4: Line 4:
 
The following script '''echo'''s 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.
 
The following script '''echo'''s 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.
  
  ; HL server query snippet by Saturn
+
  ; Source RCON control snippet by NaNg
 +
; Date: 10-02-2011
 
  ;
 
  ;
  ; 16-04-2009: update to protocol version 48 by NaNg
+
  ; Remote control window for Source engine based games (e.g. HL2, OrangeBox, CS:S, CSP etc.),
  ; 29-01-2011: update to fix UTF8 problem in mIRC 7.* and fixed name problems if last char is space by NaNg
+
  ; based on the Source RCON Protocol found at http://developer.valvesoftware.com/wiki/Source_RCON_Protocol
 
  ;
 
  ;
  ; usage:
+
  ; Usage:
  ; /hlinfo <ip> <port>
+
  ; * /sourcercon <ip> <port> <pass>
; or: /hlinfo <ip>:<port>
 
; /hlplay <ip> <port>
 
; or: /hlplay <ip>:<port>
 
 
    
 
    
  ; the alias that opens a new socket if you want to get general information about a running CS-server
+
  ; Define a prefix for sockent names.
  [[alias]] hlinfo {
+
  [[alias]] -l srcon.prefix { [[return]] SRcon }
  [[If-Then-Else|if]] (!$2) [[tokenize]] 58 [[$1-|$1]]
 
 
    
 
    
  ; using a dynamic socket name so we can use multiple instances at once
+
; Set the command requests and response types.
  [[var]] %sock = [[DollarPlus|$+]](hlinfo-,[[$ticks]])
+
[[alias]] -l srcon.req.execcmd { [[return]] 2 }
  ; construct the query to send to the server
+
[[alias]] -l srcon.req.auth { [[return]] 3 }
  [[bset]] &t 1 255 255 255 255
+
[[alias]] -l srcon.res.cmd { [[return]] 0 }
  bset -t &t 5 TSource Engine Query
+
[[alias]] -l srcon.res.auth { [[return]] 2 }
  bset &t 25 0
+
 
  ; actually open the socket, and send the query
+
; Turn the given number to a 4-byte number.
   [[sockudp]] -k %sock $1-2 &t
+
[[alias]] -l srcon.inttobytes {
  ; closing the socket after 60seconds if there is no response (else it will be closed by another event (see below))
+
   [[tokenize]] 46 [[$longip]]([[$1-|$1]])
   .[[timer]] $+ %sock 1 60 [[sockclose]] %sock
+
   [[return]] [[$1-|$4]] [[$1-|$3]] [[$1-|$2]] [[$1-|$1]]
 
  }
 
  }
 
    
 
    
  ; this event will get all data sent back from the server
+
  ; Validates arguments and tries to open the socket to the game server for authentication.
  [[on_udpread|on *:UDPREAD]]:hlinfo-*:{
+
  [[alias]] sourcercon {
   ; save it in a binary variable
+
   ; Validate arguments (simple, no need for full validation).
   [[sockread]] -f &reply
+
   [[If-Then-Else|if]] ((![[$longip]]([[$1-|$1]])) || ([[$1-|$2]] !isnum 1-65536) || (![[$1-|$3]])) {
 +
    [[echo]] -ag Invalid parameters.
 +
    [[return]]
 +
  }
 
    
 
    
   ; set some local variables that we will use below
+
   ; Save the server's address.
   var %offset, %name, %map, %game, %num, %max, %ip, %dir
+
   [[Local_Variables|var]] %server.addr = [[DollarPlus|$+]]([[$1-|$1]],:,[[$1-|$2]])
 
    
 
    
   ; following the protocol, this "m" shows us that we queried a Half-Life 1 server
+
   ; Check that the user has not opened a Rcon window for the same server already.
   if ([[$chr]]([[$bvar]](&reply,5)) == m) {
+
  ; This makes sure that the user won't have a Rcon step over another Rcon for the same server.
     ; Half-Life 1 info reply
+
   [[If-Then-Else|if]] ([[$window_(remote)|$window(]]%server.addr)) {
 +
     [[echo]] -ag Rcon window for [[$1-|$1]] is already open! close it before trying.
 +
    [[return]]
 +
  }
 
    
 
    
    %offset = 6
+
  ; Open the Rcon window whilst activating it, with an editbox, hide the @ prefix and maximize it.
 +
  window -aek0x @ [[DollarPlus|$+]] %server.addr sourcercon.execcmd
 +
  [[aline]] 12 @ [[DollarPlus|$+]] %server.addr Source RCON window. If there's no error, you're connected!
 +
  [[aline]] 12 @ [[DollarPlus|$+]] %server.addr Type a command\cvar to send to the server, responses will be displayed here.
 +
  [[aline]] 12 @ [[DollarPlus|$+]] %server.addr Warning! commands like exit or quit will close the server! avoid using them!
 
    
 
    
    ; save the ip
+
  ; Set the socket name we'll use.
    %ip = $bvar(&reply,%offset,128).text
+
  [[Local_Variables|var]] %sockname = [[DollarPlus|$+]]($srcon.prefix,%server.addr)
    [[inc]] %offset [[$calc]]([[$len]]($bvar(&reply,%offset,128).text) + 1)
 
 
    
 
    
    ; the same
+
  ; Open the socket to the server.
    %name = $bvar(&reply,%offset,128).text
+
  [[sockopen]] %sockname [[$1-|$1]] [[$1-|$2]]
    inc %offset $calc($len($bvar(&reply,%offset,128).text) + 1)
 
 
    
 
    
    ; the current map
+
  ; Mark the socket with the Rcon password.
    %map = $bvar(&reply,%offset,128).text
+
  [[sockmark]] %sockname [[$1-|$3]]
    inc %offset $calc($len($bvar(&reply,%offset,128).text) + 1)
+
}
 
    
 
    
    ; the game directory
+
; Catch all sockopens with the Source Rcon sockets prefix.
    %dir = $bvar(&reply,%offset,128).text
+
; Build the authentication request packet and send it to the server.
    inc %offset $calc($len($bvar(&reply,%offset,128).text) + 1)
+
on *:SOCKOPEN:$([[DollarPlus|$+]]($srcon.prefix,*)): {
 +
  ; Initialize the request ID for the connection.
 +
  [[set]] %srcon.id. [[DollarPlus|$+]] [[$sockname]] 0
 
    
 
    
    ; the name of the game
+
  ; Constructed auth request to send to the server.
    %game = $bvar(&reply,%offset,128).text
+
  [[noop]] $srcon.getrequest(%srcon.id. [ [[DollarPlus|$+]] [ [[$sockname]] ] ], $srcon.req.auth, [[$sock]]([[$sockname]]).mark, &srcon.auth)
    inc %offset $calc($len($bvar(&reply,%offset,128).text) + 1)
 
 
    
 
    
    ; current and maximum players
+
  ; Send the authentication packet.
    %num = $bvar(&reply,%offset)
+
  [[sockwrite]] [[$sockname]] &srcon.auth
     %max = $bvar(&reply,$calc(%offset + 1))
+
}
 +
 
 +
; Read data from the game server.
 +
on *:SOCKREAD:$([[DollarPlus|$+]]($srcon.prefix,*)): {
 +
  ; Get the window name for the current game server.
 +
  [[Local_Variables|var]] %srcon.window = [[DollarPlus|$+]](@, [[$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-Then-Else|if]] ($sockerr) {
 +
     [[aline]] 04 %srcon.window * Error: Reading from socket: [[$sock]]([[$sockname]]).wsmsg
 +
    srcon.clear [[$sockname]]
 +
    [[return]]
 
   }
 
   }
 
    
 
    
   ; else we get data for a CS:Source game
+
   ; If a prior packet didn't finish receiving, continue reading it.
   else {
+
   [[If-Then-Else|if]] ([[$hget]]([[$sockname]])) {
     ; Source info reply
+
     ; Set the packet size.
     ; we do the same as above
+
    [[Local_Variables|var]] %srcon.pckt.size = [[$hget]]([[$sockname]], size)
     %offset = 7
+
 
 +
     ; Set the number of bytes left to read.
 +
     [[Local_Variables|var]] %srcon.pckt.left = [[$hget]]([[$sockname]], left)
 
    
 
    
     %name = $bvar(&reply,%offset,128).text
+
     ; Set the request id.
     inc %offset $calc($len($bvar(&reply,%offset,128).text) + 1)
+
     [[Local_Variables|var]] %srcon.pckt.id = [[$hget]]([[$sockname]], id)
 
    
 
    
     %map = $bvar(&reply,%offset,128).text
+
     ; Set the command response type.
     inc %offset $calc($len($bvar(&reply,%offset,128).text) + 1)
+
     [[Local_Variables|var]] %srcon.pckt.cmd = [[$hget]]([[$sockname]], cmd)
 
    
 
    
     %dir = $bvar(&reply,%offset,128).text
+
     ; Set the cut string1.
     inc %offset $calc($len($bvar(&reply,%offset,128).text) + 1)
+
     [[noop]] [[$hget]]([[$sockname]], string1, &srcon.pckt.string1)
 
    
 
    
     %game = $bvar(&reply,%offset,128).text
+
     ; Read the rest of string1.
     inc %offset $calc($len($bvar(&reply,%offset,128).text) + 1)
+
    [[Sockread|sockread]] %srcon.pckt.left &srcon.pckt.rest
 +
     [[dec]] %srcon.pckt.left $sockbr
 
    
 
    
     %num = $bvar(&reply,$calc(%offset + 2))
+
     ; Append the rest of string1 to the already received string1.
    %max = $bvar(&reply,$calc(%offset + 3))
+
    [[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.
 +
  [[If-Then-Else|else]] {
 +
    ; Read the packet size.
 +
    [[Sockread|sockread]] 4 &srcon.pckt.size
 +
    [[Local_Variables|var]] %srcon.pckt.size = [[$bvar]](&srcon.pckt.size, 1).long
 
    
 
    
  ; now we echo all stored details to the active window
+
    ; Initialize the number of bytes left to read.
  [[echo]] -a Info for [[$sock]]([[$sockname]]).saddr $+ : $+ $sock($sockname).sport
+
    [[Local_Variables|var]] %srcon.pckt.left = %srcon.pckt.size
  echo -a Name: %name
 
  echo -a Map: %map
 
  echo -a Game: %game
 
  echo -a Players: %num $+ / $+ %max
 
 
    
 
    
   ; and turn off the timer closing the socket
+
    ; Read the packet id.
   .timer $+ $sockname off
+
    [[Sockread|sockread]] 4 &srcon.pckt.id
   ; as we can close it now manually
+
    [[Local_Variables|var]] %srcon.pckt.id = [[$bvar]](&srcon.pckt.id, 1).long
   [[sockclose]] $sockname
+
    [[dec]] %srcon.pckt.left $sockbr
 +
 
 +
    ; If the request id is an error, stop processing, display an error and clear the connection.
 +
    [[If-Then-Else|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|sockread]] 4 &srcon.pckt.cmd
 +
    [[Local_Variables|var]] %srcon.pckt.cmd = [[$bvar]](&srcon.pckt.cmd, 1).long
 +
    [[dec]] %srcon.pckt.left $sockbr
 +
 
 +
    ; Read string1.
 +
    [[Sockread|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-Then-Else|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.
 +
   [[If-Then-Else|elseif]] ((%srcon.pckt.left == 0) && (%srcon.pckt.cmd == $srcon.res.cmd)) {
 +
    ; Convert the binary string1 to a usable variable.
 +
    [[Local_Variables|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:$([[DollarPlus|$+]]($srcon.prefix,*)): {
 +
  ; Get the window name for the current game server.
 +
  [[Local_Variables|var]] %srcon.window = [[DollarPlus|$+]](@, [[$remove]]([[$sockname]], $srcon.prefix))
 +
 
 +
  ; If the window and the current request id still exists, reopen the socket.
 +
  [[If-Then-Else|if]] (([[$window_(remote)|$window(]]%srcon.window)) && (%srcon.id. [ [[DollarPlus|$+]] [ [[$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
 
    
 
    
; this is the alias that will be called from hlplay to open the socket
+
    ; Mark the socket with the Rcon password.
alias hlchal {
+
    [[sockmark]] %srcon.name %srcon.pass
  ; $1 = ip, $2 = port, $3- = what to call when received
+
   }
  ; use dynamic socket name as above
 
  var %sock = $+(hlchal-,$ticks)
 
  ; construct the query to send to the server
 
  bset &t 1 255 255 255 255 85 255 255 255 255
 
  ; open the socket, and ask for a player challenge number
 
  sockudp -k %sock $1-2 &t
 
  ; mark it with the ip and port (and the query we want to call when we've received something from the server)
 
  [[sockmark]] %sock $3- $1-2
 
   ; and make a new timer to close it after 60 seconds
 
  .timer $+ %sock 1 60 sockclose %sock
 
 
  }
 
  }
 
    
 
    
  ; read everything that comes back from the hlchal-* query
+
  ; Sends user input to the server as is.
  on *:UDPREAD:hlchal-*:{
+
; NOTE: Commands like exit or quit will close the server!
   ; and save it in the binary variable called &reply
+
  on *:INPUT:@: {
   sockread -f &reply
+
   ; Get the socket name.
   ; actually call the alias saved in the sockmark (hlplay_query)
+
   [[Local_Variables|var]] %srcon.socket = [[DollarPlus|$+]]($srcon.prefix, [[$remove]]($target, @))
   $sock($sockname).mark $bvar(&reply,6,4)
+
 
   ; and turn the timer off because we can close it manually
+
   ; Check if the socket is still alive.
   .timer $+ $sockname off
+
   [[If-Then-Else|if]] (![[$sock]](%srcon.socket)) {
   sockclose $sockname
+
    [[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. [[DollarPlus|$+]] %srcon.socket
 +
 
 +
   ; Construct the request to send to the game server.
 +
   [[noop]] $srcon.getrequest(%srcon.id. [ [[DollarPlus|$+]] [ %srcon.socket ] ], $srcon.req.execcmd, [[$1-|$1-]], &srcon.cmd)
 +
 
 +
   [[aline]] $target -> [[$1-|$1-]]
 
    
 
    
; the alias we can call to make everything easier
+
  ; Send the command request.
; it will call other aliases to open sockets etc.
+
   [[sockwrite]] %srcon.socket &srcon.cmd
alias hlplay {
 
   if (!$2) tokenize 58 $1
 
  hlchal $1-2 hlplay_query
 
 
  }
 
  }
 
    
 
    
  ; the alias that opens the _real_ socket to receive data (everything before was just to challenge the server (read the website mentioned above))
+
  ; If the user has closed the rcon window, clear the socket.
  alias hlplay_query {
+
  on *:CLOSE:@: {
   ; set a binary variable
+
   srcon.clear [[DollarPlus|$+]]($srcon.prefix, [[$remove]]($target, @))
  bset &query 1 255 255 255 255 85 $3-
 
  ; use dynamic socket names again
 
  var %sock = $+(hlplay-,$ticks)
 
  ; open the socket
 
  sockudp -k %sock $1-2 &query
 
  ; and turn on the timer closing the socket after 60seconds
 
  .timer $+ %sock 1 60 sockclose %sock
 
 
  }
 
  }
 
    
 
    
  ; this event will receive all data from the server as response to our query
+
  ; Constructs a request with the given parameters:
  on *:UDPREAD:hlplay-*:{
+
; $1 = Request ID
   ; we save it in &reply
+
; $2 = Request type
   sockread -f &reply
+
; $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]] [[$1-|$4]]
 
    
 
    
   ; set some local variables we will use later
+
   ; Set the given request ID in the request.
   var %i = 1, %num = $bvar(&reply,6), %offset = 7
+
   [[bset]] [[$1-|$4]] 5 $srcon.inttobytes([[$1-|$1]])
  var %count = 0, %name, %kills
 
 
    
 
    
   ; and echo the number of players
+
   ; Set the given request type.
   echo -a Players on $sock($sockname).saddr $+ : $+ $sock($sockname).sport
+
   [[bset]] [[$1-|$4]] 9 $srcon.inttobytes([[$1-|$2]])
 
    
 
    
   ; now we can loop through all player and save some details about them
+
   ; Set string1 the request data.
   [[while]] (%i <= %num) {
+
   [[bset]] -t [[$1-|$4]] 13 [[$1-|$3]]
    inc %offset
 
 
    
 
    
    ; the name
+
  ; Terminate string1 with 0x00 and set string2 to 0x00.
    %name = $bvar(&reply,%offset,128).text
+
  [[bset]] [[$1-|$4]] [[$calc]]([[$bvar]]([[$1-|$4]],0) + 1) 00 00
    inc %offset $calc($len($bvar(&reply,%offset,128).text) + 1)
 
 
    
 
    
    ; the kills
+
  ; Set the packet size.
    %kills = $bvar(&reply,%offset).long
+
  [[bset]] [[$1-|$4]] 1 $srcon.inttobytes([[$calc]]([[$bvar]]([[$1-|$4]], 0) - 4))
    if ($isbit(%kills,32)) dec %kills $calc(2^32)
+
}
    inc %offset 8
 
 
 
    ; and if there was a player
 
    if (%name != $null) {
 
      ; increase the variable that shows us the number of the player
 
      inc %count
 
      ; and echo it to the active window with the kills saved above
 
      echo -a %count $+ . %name - %kills
 
    }
 
 
    
 
    
    ; increase %i so we go on with the next player
+
; Clears the socket and the request id.
     inc %i
+
[[alias]] -l srcon.clear {
 +
  ; If there's a socket named accordingly, close it.
 +
  [[If-Then-Else|if]] ([[$sock]]([[$1-|$1]])) {
 +
     [[sockclose]] [[$1-|$1]]
 
   }
 
   }
 
    
 
    
   ; if there are no players online, echo it aswell
+
   ; Clear the id var.
   if (%count == 0) echo -a No players found!
+
   [[Unset|unset]] %srcon.id. [[DollarPlus|$+]] [[$1-|$1]]
 
 
  ; turn off the timer
 
  .timer $+ $sockname off
 
  ; and close the socket manually
 
  sockclose $sockname
 
 
  }
 
  }
 
  
 
[[Category:Tutorials]]
 
[[Category:Tutorials]]
 
[[Category:Socket]]
 
[[Category:Socket]]

Revision as of 13:40, 11 February 2011

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
}