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

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

; HL server query snippet by Saturn
;
; 16-04-2009: update to protocol version 48 by NaNg
; 29-01-2011: update to fix UTF8 problem in mIRC 7.* and fixed name problems if last char is space by NaNg
;
; usage:
; /hlinfo <ip> <port>
; 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
alias hlinfo {
  if (!$2) tokenize 58 $1
 
  ; using a dynamic socket name so we can use multiple instances at once
  var %sock = $+(hlinfo-,$ticks)
  ; construct the query to send to the server
  bset &t 1 255 255 255 255
  bset -t &t 5 TSource Engine Query
  bset &t 25 0
  ; actually open the socket, and send the query
  sockudp -k %sock $1-2 &t
  ; closing the socket after 60seconds if there is no response (else it will be closed by another event (see below))
  .timer $+ %sock 1 60 sockclose %sock
}
 
; this event will get all data sent back from the server
on *:UDPREAD:hlinfo-*:{
  ; save it in a binary variable
  sockread -f &reply
 
  ; set some local variables that we will use below
  var %offset, %name, %map, %game, %num, %max, %ip, %dir
 
  ; following the protocol, this "m" shows us that we queried a Half-Life 1 server
  if ($chr($bvar(&reply,5)) == m) {
    ; Half-Life 1 info reply
 
    %offset = 6
 
    ; save the ip
    %ip = $bvar(&reply,%offset,128).text
    inc %offset $calc($len($bvar(&reply,%offset,128).text) + 1)
 
    ; the same
    %name = $bvar(&reply,%offset,128).text
    inc %offset $calc($len($bvar(&reply,%offset,128).text) + 1)
 
    ; the current map
    %map = $bvar(&reply,%offset,128).text
    inc %offset $calc($len($bvar(&reply,%offset,128).text) + 1)
 
    ; the game directory
    %dir = $bvar(&reply,%offset,128).text
    inc %offset $calc($len($bvar(&reply,%offset,128).text) + 1)
 
    ; the name of the game
    %game = $bvar(&reply,%offset,128).text
    inc %offset $calc($len($bvar(&reply,%offset,128).text) + 1)
 
    ; current and maximum players
    %num = $bvar(&reply,%offset)
    %max = $bvar(&reply,$calc(%offset + 1))
  }
 
  ; else we get data for a CS:Source game
  else {
    ; Source info reply
    ; we do the same as above
    %offset = 7
 
    %name = $bvar(&reply,%offset,128).text
    inc %offset $calc($len($bvar(&reply,%offset,128).text) + 1)
 
    %map = $bvar(&reply,%offset,128).text
    inc %offset $calc($len($bvar(&reply,%offset,128).text) + 1)
 
    %dir = $bvar(&reply,%offset,128).text
    inc %offset $calc($len($bvar(&reply,%offset,128).text) + 1)
 
    %game = $bvar(&reply,%offset,128).text
    inc %offset $calc($len($bvar(&reply,%offset,128).text) + 1)
 
    %num = $bvar(&reply,$calc(%offset + 2))
    %max = $bvar(&reply,$calc(%offset + 3))
  }
 
  ; now we echo all stored details to the active window
  echo -a Info for $sock($sockname).saddr $+ : $+ $sock($sockname).sport
  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
  .timer $+ $sockname off
  ; as we can close it now manually
  sockclose $sockname
}
 
 
; this is the alias that will be called from hlplay to open the socket
alias hlchal {
  ; $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
on *:UDPREAD:hlchal-*:{
  ; and save it in the binary variable called &reply
  sockread -f &reply
  ; actually call the alias saved in the sockmark (hlplay_query)
  $sock($sockname).mark $bvar(&reply,6,4)
  ; and turn the timer off because we can close it manually
  .timer $+ $sockname off
  sockclose $sockname
}
 
; the alias we can call to make everything easier
; it will call other aliases to open sockets etc.
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))
alias hlplay_query {
  ; set a binary variable
  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
on *:UDPREAD:hlplay-*:{
  ; we save it in &reply
  sockread -f &reply
 
  ; set some local variables we will use later
  var %i = 1, %num = $bvar(&reply,6), %offset = 7
  var %count = 0, %name, %kills
 
  ; and echo the number of players
  echo -a Players on $sock($sockname).saddr $+ : $+ $sock($sockname).sport
 
  ; now we can loop through all player and save some details about them
  while (%i <= %num) {
    inc %offset
 
    ; the name
    %name = $bvar(&reply,%offset,128).text
    inc %offset $calc($len($bvar(&reply,%offset,128).text) + 1)
 
    ; the kills
    %kills = $bvar(&reply,%offset).long
    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
    inc %i
  }
 
  ; if there are no players online, echo it aswell
  if (%count == 0) echo -a No players found!
 
  ; turn off the timer
  .timer $+ $sockname off
  ; and close the socket manually
  sockclose $sockname
}