EventScripts Forums
Mattie's EventScripts and Source Forums
Mattie recommends...
Premier Sponsor:Platinum Sponsor:
 
Script Categories
addon (49) admin (269) admins (14) advert (15) adverts (19) afk (15) alerts (12) ammo (23) anti (15) auth (16) automatic (12) awp (23) ban (18) ba_jail (17) bhop (19) block (19) bomb (40) bonus (23) bot (24) bot-management (19) bots (17) bunnyhop (11) Buy (21) cash (65) chat (57) cheat (11) clan (16) classes (22) color (21) colors (18) command (20) commands (18) config (22) connect (42) CS:S (16) css (2500) Damage (35) database (11) dead (15) death (19) deathmatch (70) deathrun (12) disconnect (18) Dissolve (11) dm (20) dod:s (11) dods (386) easy (17) effects (120) English (15) entity (21) es_tools (12) Eventscript (12) flashbang (25) français (99) french (36) fun (286) funny (16) Gabeee (11) gameplay (174) give (16) gravity (11) grenade (25) grenades (29) gun (14) GunGame (26) GunGame5 (11) guns (17) Hack (15) headshot (32) health (54) hegrenade (13) hl2dm (329) HP (13) info (48) information (16) jail (26) jailbreak (11) join (23) jump (12) kick (23) kill (44) kills (22) knife (57) leveling (36) management (14) mani (15) map (25) map-management (27) maps (15) match (17) menu (106) message (20) messaging (52) mod (109) model (14) models (32) money (42) motd (12) music (29) mute (18) nades (18) name (13) noblock (35) player (40) player-management (31) player-tracking (28) popup (55) props (23) protection (27) punishment (50) python (63) quake (18) radio (21) random (36) rank (35) rates (16) rcon (13) realism (21) respawn (61) restrict (36) round (20) rpg (24) rules (42) say (11) scout (19) Script (20) script-helper (25) scriptpack (33) sdk (149) server (44) server-tools (42) shop (12) silly (31) simple (27) skins (24) slay (13) soccer (11) sound (45) sounds (92) spawn (63) speed (25) statistics (13) stats (48) STEAMID (23) Superhero (13) surf (31) Team (25) team-balance (14) teleport (12) text (14) TF2 (143) time (17) timer (14) tools (15) tracers (17) triggers (11) uedi (18) uses_auth (18) utility (11) vip (11) vote (36) voting (22) war (20) wcs (24) wcs:Python (32) weapon (77) weapons (114) web (14) welcome (12) zm (24) zombie (90) zombiemod (51) zombies (11)
Script Authors
*XYZ*SaYnt (12) .:MiB:. (11) .eMko* (6) 101satoon101 (10) 3R10N (24) 4u571n91 (5) 7355608 (9) Absolute (8) Ace Rimmer (40) adminc (5) Adz (8) AgathaKnuppelkuh (8) aidden (8) ajax (5) ak_47 (6) Al3c Tr3v3lyan (6) allstareng (5) ashbash1987 (9) ATAMAH (7) aznone (5) B00M (5) BackRaw (41) BFH_RedBull (8) bigfabi (7) Bioko (7) Blade (10) bladesback (13) bobdole (11) bodzsar1 (8) bonbon (36) Brainsucker (30) cagemonkey (8) carbon-14 (10) CaskioUTF (5) cbirou (15) ChaCaLz2psy4 (5) CharlesT (5) Chrisber (5) chrismrulz (9) Chun (6) cladiron (10) clipz934 (8) Colster (21) Cookieman8 (7) craziest (10) DanielB (19) Darkness123 (8) Dave (9) dbozan99 (7) deathx9 (5) Deathyy (16) dhack (16) Di[M]aN (12) DoCky (27) Don (15) dordtcore (8) DragonFreddo (8) Drassil (5) Einlanzers (42) EmbouT (10) emilplov (7) Errant (10) Eun (6) Fantole (9) Franc1sco (9) freddukes (18) Frequency (6) Fulmine (5) GAMEREN2 (8) german9114 (13) GODJonez (38) GoodfellaDeal (5) Hansi (6) HitThePipe (7) HOLLDIDAY (9) Icetouch (6) ichthys (25) infamous1 (9) Jeff91 (45) JoeyT2006 (30) Juba_PornBorn (12) jxl180 (10) KDBFame (6) L'In20Cible (7) Largo Usagi (10) Lobe (11) loKkdoKk (7) lolo-le-haricot (7) LosNir (5) Lumpi@Work (9) M4rc3L-XCN (7) macshot (6) Matth (5) Mattie (20) MBchrono (15) McFly (20) Medda (5) Memphis-84 (10) Messiah93 (12) Mickyy (5) micmacx (6) Mitchell (5) Mordavolt (7) MrScriptaz (5) mryoung (6) Nicolous (26) ojii (31) Omega_K2 (6) Owned|Myself (8) P3N (7) pand3mic (5) parsimba (5) Pascal257 (9) PatPeter (7) PDrop (10) Phaedrus (18) PhantOm Fury (7) phoenix131 (8) pinkyyy -.- (5) pitbull0993 (7) randomknifer (9) Ratzee (6) ReaCtioN2oo9 (6) Rennnyyy (17) revolutionfighters (6) RideGuy (12) Rio (9) Roeliekt (6) runamagic (13) sandking220 (6) Sarcasm_Poisoning (10) saRs| Johnny-5 (13) Schubaal (10) sea212 (5) sega74rus (7) sgt.angel (6) sicilia (5) sicman_adrian (22) skillz92 (7) sn4k3 (18) snake38 (10) sonicsight (7) spoonman184 (8) stabby (22) stas (22) Strontium Dog (30) SumGuy14 (25) SuperDave (45) surfteam (5) TaCo (5) TanaToS (17) Tealk (6) teowow (16) TheCheeTaH (20) TheDonFather (5) theresthatguy (6) Tiny Tod (7) Totyahun (9) uedi (38) Undead (25) usernamesaretaken (15) Warren (13) westham (6) WhiteAvenger (7) Wonder (14) X-Mania (5) XE_ManUp (13) xfalcon61 (5) zSweetXz (5) [Cs]Lord_Inferno2 (11) [NATO]Hunter (28) ||Wolf|| (10)
Search

Post new topic Reply to topic
Go to page 1, 2, 3  Next
Author Message
User avatar
EventScripts Administrator
EventScripts Administrator
Profile

Posts: 2889
Location:
Tampa, FL
 
New postPosted: 2008-06-07, 11:18 am 
   Post subject: Source Server Query Protocol Script

Hello, all of you 2.0 scripters! I found a really neat script online (although I can't seem to get it to work). Anyhow, I believe that if we put or combined coding skills together, we could code this as a library for ES2.0.

I can honestly say that I do not understand it all that well. I figured I would post it here so that others could have a look.

The Script I Found: (http://www.uselesspython.com/showcontent.php?author=1)
PYTHON:
    #SRCDS.py
    #Half-Life 2 Dedicated Server Interface for Python
    #Released under the LGPL (<!-- m --><a class="postlink" href="http://www.gnu.org/licenses/lgpl.html">http://www.gnu.org/licenses/lgpl.html</a><!-- m -->)
    #
    #Sean C. Steeg
    #High level RCON Protocol from <!-- m --><a class="postlink" href="http://wikki.kquery.net/index.php/Other:SourceRcon">http://wikki.kquery.net/index.php/Other:SourceRcon</a><!-- m -->
    #Original hlds.py from <!-- m --><a class="postlink" href="http://www.wsu.edu/~gerber/pyspy/pyspy.htm">http://www.wsu.edu/~gerber/pyspy/pyspy.htm</a><!-- m -->

    __author__ = 'Sean Steeg <sean at steegness dot com>'
    __license__ = 'http://www.gnu.org/licenses/lgpl.html'
    __date__ = '13 May 2005'
    __version__ = '1.01'
    __credits__ = """Bryan Gerber, for the original HLDS.py.
    The players and staff of TacticalGamer.com, who make me want to do stuff like this."""

    import socket, string, time, StringIO, random, xdrlib

    #RCON Constants
    SERVERDATA_RESPONSE_VALUE = 0
    SERVERDATA_AUTH_RESPONSE = 2
    SERVERDATA_EXECCOMMAND = 2
    SERVERDATA_AUTH = 3
    #Server Query Constants
    DETAILS = 'T'; DETAILS_RESP = 'I'
    RULES = 'V'; RULES_RESP = 'E'
    PLAYERS = 'U'; PLAYERS_RESP = 'D'

    def hldsunpack_int(data):
        """
    Network traffic is big endian, and xdrlib wants little endian, meaning the
    bytes need to be reversed in order for xdrlib to work its magic."""
        s = ""
        for c in data:
            s = c + s
        p = xdrlib.Unpacker(s)
        return p.unpack_int()

    def hldsunpack_float(data):
        """
    Network traffic is big endian, and xdrlib wants little endian, meaning the
    bytes need to be reversed in order for xdrlib to work its magic."""
        s = ""
        for c in data:
            s = c + s
        p = xdrlib.Unpacker(s)
        return p.unpack_float()

    def hldspack_int(integer):
        s = ""
        p = xdrlib.Packer()
        p.pack_int(integer)
        data = p.get_buffer()
        for c in data:
            s = c + s
        return s

    def make_packet(command, string1, string2='', req_id=1):
        """Crafts a packet to send to the server."""
        #Make the packet from the end going backwards
        packet = string1 + '\x00' + string2 + '\x00'
        #Add command
        packet = hldspack_int(command) + packet
        #Add request_id
        packet = hldspack_int(req_id) + packet
        #Add an int of the packet length
        packet = hldspack_int(len(packet)) + packet
        #Return result
        return packet

    def parse_packet(data):
        """Parses a response packet from the server."""
        packetlen = hldsunpack_int(data[0:4])
        req_id = hldsunpack_int(data[4:8])
        command_resp = hldsunpack_int(data[4:12])
        rest = str(data[12:])
        if len(rest) == 2:
            str1 = ''
            str2 = ''
        else:
            strs = string.split(rest, '\x00', 1)
            str1 = strs[0]
            str2 = strs[1]
            if len(str2) == 1: str2 = ''
        return (packetlen, req_id, command_resp, str1, str2)

    def read_byte(data):
        return (str(ord(data[0])), data[1:])

    def read_char(data):
        return (str(data[0]), data[1:])

    def read_string(data):
        s = ''
        i = 0
        while 1:
            if str(data[i]) != '\x00':
                s = s + str(data[i])
                i += 1
            else:
                break
        return (s, data[i+1:])

    def read_int(data):
        ret = hldsunpack_int(data[0:4])
        return (ret, data[4:])

    def read_float(data):
        ret = hldsunpack_float(data[0:4])
        return (ret, data[4:])

    ##################################################
    class SRCDS_Error(Exception):
        """Base error."""
        pass

    class RCON_Error(Exception):
        """Raised when a command requiring RCON is given, but the RCON password is missing or incorrect."""
        pass

    ##################################################
    class SRCDS:
        """
    HL2DS interface class.
    Used to retrieve information, as well as send RCON commands to the server.

    Initialization: OBJ = SRCDS(ip, [port=27015], [rconpass])
        """

        def __init__(self, ip, port=27015, rconpass=''):
            self.ip, self.port, self.rconpass = ip, port, rconpass
            self.empty_resp = (10,0,0,'','')
            self.tcpsock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
            self.udpsock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
            self.tcpsock.connect((self.ip, self.port))
            self.udpsock.connect((self.ip, self.port))
            if self.rconpass: self._authenticate_rcon()

        def set_rconpass(self, password):
            self.rconpass = password
            self._authenticate_rcon()

        def _authenticate_rcon(self):
            if not self.rconpass: raise RCON_Error, 'You need an RCON password'
            to_send = make_packet(SERVERDATA_AUTH, self.rconpass, '', 1234)
            self.tcpsock.send(to_send)

            i = 0
            result = self.empty_resp
            while result == self.empty_resp:
                data = self.tcpsock.recv(4108)
                result = parse_packet(data)
                if result != (10,1234,3,'','') and i > 2:
                    raise RCON_Error, 'Invalid RCON or RCON Error'
                elif i <= 2:
                    i += 1
                    continue
                else:
                    return True

    ###################################
    #####Base Command Section##########
    ###################################            
    ##    def _any_rcon_command(self, command):
    ##        '''
    ##This function only returns True if it worked, and throws and error when it doesn't.
    ##No useful responses may be received from the server; in fact, anything besides an
    ##empty 14 byte response set will cause this to error out.
    ##        '''
    ##        self._authenticate_rcon()
    ##        to_send = make_packet(SERVERDATA_EXECCOMMAND, '%s' %command)
    ##        self.tcpsock.send(to_send)
    ##        data = self.tcpsock.recv(4108)
    ##        result = parse_packet(data)
    ##        if result == self.empty_resp:
    ##            return True
    ##        else:
    ##            raise SRCDS_Error, 'Command Error'

        def _any_rcon_response(self, command):
            '''
    This function returns the raw response (sans the four \xFF's) for commands requiring RCON.
    No parsing is done by this function.
            '''
            self._authenticate_rcon()
            to_send = make_packet(SERVERDATA_EXECCOMMAND, '%s' %command)
            self.tcpsock.send(to_send)
            data = self.tcpsock.recv(4108)
            result = parse_packet(data)
            return result
    ##        if result != self.empty_resp:
    ##            return result
    ##        else:
    ##            raise SRCDS_Error, 'Command Response Failure (Empty Response Set)'

        def _any_response(self, query):
            '''
    This returns the raw response (sans the four \xFF's).  No parsing is done by this function.
            '''
            self.udpsock.send('\xFF\xFF\xFF\xFF' + query)
            data = self.udpsock.recv(4096)
            return data[4:]
           
    ###################################
    #####RCON Section##################
    ###################################
        def changelevel(self, map):
            self._any_rcon_response('changelevel %s' %map)

        def say(self, statement):
            self._any_rcon_response('say %s' %statement)

        def quit(self):
            self._any_rcon_response('quit')

        def status(self):
            '''
    Returns a list of dictionaries.  Each dictionary is the status info on a player.
            '''
            if not self.players(): return []
            raw_status = self._any_rcon_response('status')[3]
            data = StringIO.StringIO(raw_status)
            lines = status.readlines()
            i = 0
            while lines[i] != '\n':
                i += 1
            i += 1

            keys = lines[i].split()
            keys.pop(0); keys.pop(0)    #need to do 'name' by hand to account for spaces.
            i += 1
            players = []
            while lines[i][-6:] != ' users':
                player = {}
                namespot = 5    #The start of the name, after the first '"'
                name = ''
                while lines[i][namespot] != '"':
                    namespot += 1
                player['name'] = lines[i][5:namespot]

                values = lines[i][namespot+2:].split()
                j = 0
                for key in keys:
                    player[key] = values[j]
                    j += 1
                players.append(player)
                i += 1
            return players

    ###################################
    #####No RCON Section###############
    ###################################
        def details(self):
            raw_details = self._any_response(DETAILS)
            if raw_details[0] != DETAILS_RESP: raise SRCDS_Error, 'Detail Query Error'
            detaildict = {}
            data = raw_details[1:]
            detaildict['protocol_version'], data = read_byte(data)
            detaildict['server_name'], data = read_string(data)
            detaildict['current_map'], data = read_string(data)
            detaildict['game_directory'], data = read_string(data)
            detaildict['game_description'], data = read_string(data)
            detaildict['unknown1'], data = read_byte(data)
            detaildict['unknown2'], data = read_byte(data)
            detaildict['current_playercount'], data = read_byte(data)
            detaildict['max_players'], data = read_byte(data)
            detaildict['current_botcount'], data = read_byte(data)
            ded, data = read_char(data)
            if ded == 'd':
                detaildict['server_type'] = 'Dedicated'
            else:
                detaildict['server_type'] = 'Listen'
            os, data = read_char(data)
            if os == 'w':
                detaildict['server_os'] = 'Windows'
            else:
                detaildict['server_os'] = 'Linux'
            pworded, data = read_byte(data)
            detaildict['passworded'] = bool(int(pworded))
            secured, data = read_byte(data)
            detaildict['secure'] = bool(int(secured))
            detaildict['exe_version'], data = read_string(data)
           
            return detaildict

        def players(self):
            raw_players = self._any_response(PLAYERS)
            if raw_players[0] != PLAYERS_RESP: raise SRCDS_Error, 'Player Query Error'
            data = raw_players[1:]
            playerlist = []

            playercount, data = read_byte(data)
            playercount = int(playercount)

            for i in range(0, playercount):
                currentplayer = {}
                cn, data = read_byte(data)
                currentplayer['index'] = int(cn)
                currentplayer['name'], data = read_string(data)
                currentplayer['frags'], data = read_int(data)
                currentplayer['time_on'], data = read_float(data)
                playerlist.append(currentplayer)
            return playerlist

        def rules(self):
            raw_rules = self._any_response(RULES)
            if raw_rules[0] != RULES_RESP: raise SRCDS_Error, 'Rules Query Error'
            data = raw_rules[1:]
            rulescount, data = read_byte(data)
            rulescount = int(rulescount)
            nada, data = read_byte(data)    #placeholder to move up one byte
            ruleslist = string.split(str(data), '\x00')
            ruleslist.pop()
            rulesdict =  {}
            for everyother in ruleslist[::2]:
                rulesdict[everyother] = ruleslist[ruleslist.index(everyother) + 1]
            return rulesdict
               
        def disconnect(self):
            self.tcpsock.close()
            self.udpsock.close()

    #########################################
    if __name__ == '__main__':
        t = SRCDS('207.210.236.75')
        #t = SRCDS('67.19.132.154')
        print t.details()
        print
        print t.players()
        print
        print t.rules()
        t.disconnect()


Basically, the above script allows you to perform queries of Source servers, and retrieve information from them. The below links show what all can be queried, which is what he is using:

http://developer.valvesoftware.com/wiki/Server_Queries
http://developer.valvesoftware.com/wiki/Master_Server_Query_Protocol

Any thoughts on attempting to throw this together?

_________________
"If life had an SDK, only the smart people would survive."
"I got tired of my wife being my page file, so I ordered more memory from Newegg."
Image
Image
Image





User avatar
Guru
Guru
Profile

Posts: 1268
 
New postPosted: 2008-06-07, 2:39 pm 

If it helps:
http://www.phpclasses.org/browse/package/1899.htm
http://www.phpclasses.org/browse/file/7537.html
http://webscripts.softpedia.com/script/ ... 11626.html


User avatar
Experienced
Profile

Posts: 446
Location:
Yes.
 
New postPosted: 2008-06-08, 2:03 am 

This seems way more complicated than it should be. I have a php script that is very simple and implements the same thing.

_________________
Image


User avatar
EventScripts Administrator
EventScripts Administrator
Profile

Posts: 3081
Location:
UK
 
New postPosted: 2008-06-08, 11:35 am 

That is for 2 reasons.

1) this script adds wrappers for commonly needed "functions"
2) PHP is a a big brother language that will sort out data manipulation and error handling itself....

Nice script find XE, VERY useful.

_________________
Cheers,
Tom
Image
NCS: kill chat spam dead! - | - Get Live IRC help - | - Addon Utils - View and manage addons in game
(No IRC client? Use this sites simple online version [Requires Java])

eXtensible Admin: the #1 open source admin tool

Addons: Uptime; server uptime logger - loglib; logging utility - Block Chat; limit chat to teamonlly - Last Five; view the last few players to leave the server, with ban opts - Backup; coming SOON

My startup: social network v3.0!!


User avatar
Mentat
Mentat
Profile

Posts: 4727
Location:
C:/ProgramFiles/Bonbon/Bonbon.exe
 
New postPosted: 2008-06-08, 11:39 am 

yes, this is. I was trying to figure out how I could send a message to the server without getting IP banned, every time I tried sending like rcon_password bonbon or rcon_password bonbon \n it always IP banned me, even if I sent "moo"

I can't believe this has been out for three years, nice find.

_________________
SicmanAdrian wrote:
I don't think it is possible but maybe SuperDave could try?

abcdefghijklmnopqrstuvwxyz
"It doesn't work" doesn't help us out, help us help you, be more specific!
Remember, when posting non Python code, post in the ES 1.x discussion forum, you'll get better help!
SuperDave wrote:
It's very difficult to see errors in that script because it is some of the worst looking code I have ever seen. And I've seen bonbon's code

Please do not PM for free private scripts/help!


User avatar
EventScripts Administrator
EventScripts Administrator
Profile

Posts: 2889
Location:
Tampa, FL
 
New postPosted: 2008-06-08, 6:24 pm 

I would love to see this integrated as a library, I just don't understand how the hack it works. :oops:

It would be neat to "pre-format" queries using this as a library so you can query a single server, or multiple servers. Imagine being able to use this to find all servers running a particular script. It would be quite awesome. Or, find out how many people are on another server, and offer to redirect them to the server that has more players. :o

Needless to say, possibilities are truly endless. 8)

_________________
"If life had an SDK, only the smart people would survive."
"I got tired of my wife being my page file, so I ordered more memory from Newegg."
Image
Image
Image


User avatar
EventScripts Administrator
EventScripts Administrator
Profile

Posts: 3081
Location:
UK
 
New postPosted: 2008-06-09, 2:25 am 

I'll look into hacking up both an explanation and some sort of library tonight :)

_________________
Cheers,
Tom
Image
NCS: kill chat spam dead! - | - Get Live IRC help - | - Addon Utils - View and manage addons in game
(No IRC client? Use this sites simple online version [Requires Java])

eXtensible Admin: the #1 open source admin tool

Addons: Uptime; server uptime logger - loglib; logging utility - Block Chat; limit chat to teamonlly - Last Five; view the last few players to leave the server, with ban opts - Backup; coming SOON

My startup: social network v3.0!!


User avatar
Guru
Guru
Profile

Posts: 1268
 
New postPosted: 2008-06-09, 2:52 am 

XE_ManUp wrote:
I would love to see this integrated as a library, I just don't understand how the hack it works. :oops:

It would be neat to "pre-format" queries using this as a library so you can query a single server, or multiple servers. Imagine being able to use this to find all servers running a particular script. It would be quite awesome. Or, find out how many people are on another server, and offer to redirect them to the server that has more players. :o

Needless to say, possibilities are truly endless. 8)


Well, you could use Game-monitor.
Or, because Venjax said he could not get the number of servers running an addon easily through there, mattie.info could have their own stats-monitor, SourceMod style!


User avatar
EventScripts Administrator
EventScripts Administrator
Profile

Posts: 12890
Location:
irc://irc.gamesurge.net/eventscripts
 
New postPosted: 2008-06-09, 5:52 am 

I've been putzing around with this:
PYTHON:
    # ./addons/eventscripts/_libs/python/serverlib.py

    import socket
    import struct
    import time


    class SourceServerError(Exception): pass


    class SourceServer(object):
       # <!-- m --><a class="postlink" href="http://developer.valvesoftware.com/wiki/Source_Server_Query_Library">http://developer.valvesoftware.com/wiki ... ry_Library</a><!-- m -->
       S2C_CHALLENGE = '\x41'
       S2A_PLAYER = '\x44'
       S2A_RULES = '\x45'
       S2A_INFO = '\x49'
       A2A_ACK = '\x6A'

       A2S_INFO = '\xFF\xFF\xFF\xFF\x54Source Engine Query'
       A2S_PLAYER = '\xFF\xFF\xFF\xFF\x55'
       A2S_RULES = '\xFF\xFF\xFF\xFF\x56'
       A2S_SERVERQUERY_GETCHALLENGE = '\xFF\xFF\xFF\xFF\x57'
       A2A_PING = '\xFF\xFF\xFF\xFF\x69'

       SERVERDATA_EXECCOMMAND = 2
       SERVERDATA_AUTH = 3
       SERVERDATA_RESPONSE_VALUE = 0
       SERVERDATA_AUTH_RESPONSE = 2

       """ Class functions """
       # <!-- m --><a class="postlink" href="http://developer.valvesoftware.com/wiki/Server_Queries">http://developer.valvesoftware.com/wiki/Server_Queries</a><!-- m -->

       def __init__(self, network, port):
          self.tcp = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
          self.tcp.connect((network, port))

          self.udp = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
          self.udp.connect((network, port))

          self.password = ''

       def settimeout(self, value):
          self.tcp.settimeout(value)
          self.udp.settimeout(value)

       def disconnect(self):
          """Disconnects from server"""
          self.tcp.close()
          self.udp.close()

       def raw_send_tcp(self, data):
          """Sends raw tcp data to the server"""
          if data:
             self.tcp.send(data)
          return self.parsePacket(self.tcp.recv(4108))

       def raw_send_udp(self, data):
          """Sends raw udp data to the server"""
          if data:
             self.udp.send(data)
          return self.udp.recv(4096)

       """ TCP """

       def setRconPassword(self, password):
          """Sets the server RCON password"""
          self.password = password

          self.raw_send_tcp(self.packet(self.SERVERDATA_AUTH, password, 1234))
          return self.raw_send_tcp(None)[1] == 1234

       def rcon(self, command):
          """Sends an RCON command to the server and returns the result"""
          # Authenticate
          if not self.setRconPassword(self.password): raise SourceServerError, 'Bad RCON password'

          # Send RCON command
          result = filter(bool, self.raw_send_tcp(self.packet(self.SERVERDATA_EXECCOMMAND, command, 1))[~0])
          return result[~0] if result else None

       """ UDP """

       def ping(self):
          """Returns the server ping in seconds"""
          starttime = time.time()
          result = self.raw_send_udp(self.A2A_PING)

          if result.startswith('\xFF\xFF\xFF\xFF') and result[4] == self.A2A_ACK:
             return time.time() - starttime

          raise SourceServerError, 'Unexpected server response \'%s\'' % result[4]

       def getChallenge(self):
          """Returns a challenge value for querying the server"""
          result = self.raw_send_udp(self.A2S_SERVERQUERY_GETCHALLENGE)
          if result.startswith('\xFF\xFF\xFF\xFF') and result[4] == self.S2C_CHALLENGE:
             return result[5:]

          raise SourceServerError, 'Unexpected server response \'%s\'' % result[4]

       def getRules(self):
          """Returns a dictionary of server rules"""
          result = self.raw_send_udp(self.A2S_RULES + self.getChallenge())

          if result.startswith('\xFF\xFF\xFF\xFF') and result[4] == self.S2A_RULES:
             rules = {}
             lines = result[7:].split('\x00')
             for x in range(0, len(lines) - 1, 2):
                rules[lines[x]] = lines[x + 1]

             return rules

          raise SourceServerError, 'Unexpected server response \'%s\'' % result[4]

       def getDetails(self):
          """Returns a dictionary of server details"""
          result = self.raw_send_udp(self.A2S_INFO)

          if result.startswith('\xFF\xFF\xFF\xFF') and result[4] == self.S2A_INFO:
             details = {}
             details['version'] = struct.unpack('<B', result[6])[0]
             lines = result[6:].split('\x00', 4)

             for name in ('server name', 'map', 'game directory', 'game description'):
                details[name] = lines.pop(0)

             line = lines.pop(0)
             (details['appid'], details['number of players'], details['maximum players'], details['number of bots'], details['dedicated'],
                details['os'], details['password'], details['secure']) = struct.unpack('<H3BccBB', line[:9])
             details['game version'] = line[9:].split('\x00')[0]

             return details

          raise SourceServerError, 'Unexpected server response \'%s\'' % result[4]

       def getPlayers(self):
          """Returns a dictionary of player information"""
          result = self.raw_send_udp(self.A2S_PLAYER + self.getChallenge())

          if result.startswith('\xFF\xFF\xFF\xFF') and result[4] == self.S2A_PLAYER:
             playercount = struct.unpack('<B', result[5])[0]

             index, x = 0, 6
             players = {}
             resultlen = len(result) # So we don't have to re-evaluate len()
             while x < resultlen:
                index = struct.unpack('<B', result[x])[0]
                if index in players:
                   x += 5
                   continue

                currentplayer = players[index] = {}
                y = result.find('\x00', x + 1)
                if y == -1: raise SourceServerError, 'Error parsing player information'

                currentplayer['player name'] = result[x + 1:y]
                currentplayer['kills'], currentplayer['time connected'] = struct.unpack('<BB', result[y + 1:y + 3])
                x = y + 4

             return players

          raise SourceServerError, 'Unexpected server response \'%s\'' % result[4]

       """ Data format functions """

       @staticmethod
       def packet(command, strings, request):
          """Compiles a raw packet string to send to the server"""
          if isinstance(strings, str): strings = (strings,)
          result = struct.pack('<II', request, command) + ''.join([x + '\x00' for x in strings])
          return struct.pack('<I', len(result)) + result

       @staticmethod
       def parsePacket(data):
          # Lengeth, Request, Command, Strings
          if not data: return None
          return struct.unpack('<3I', data[:12]) + (data[12:].split('\x00'),)


    class MasterServerQuery(object):
       # <!-- m --><a class="postlink" href="http://developer.valvesoftware.com/wiki/Master_Server_Query_Protocol">http://developer.valvesoftware.com/wiki ... y_Protocol</a><!-- m -->
       M2A_SERVER_BATCH = '\x66'

       REGION_US_EAST_COAST = '\x00'
       REGION_US_WEST_COAST = '\x01'
       REGION_SOUTH_AMERICA = '\x02'
       REGION_EUROPE = '\x03'
       REGION_ASIA = '\x04'
       REGION_AUSTRALIA = '\x05'
       REGION_MIDDLE_EAST = '\x06'
       REGION_AFRICA = '\x07'
       REGION_WORLD = '\xFF'

       ZERO_IP = '0.0.0.0:0'

       def __init__(self, network=('hl2master.steampowered.com', 27011)):
          self.server = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
          self.server.connect(network)

       def disconnect(self):
          self.server.close()

       def getServerList(self, region=None, server_filter='\x00'):
          """Returns a list of active Source servers (ip, port)"""
          if region is None: region = self.REGION_WORLD
          queryformat = '\x31' + region + '%s' + '\x00' + server_filter + '\x00'
          self.server.send(queryformat % self.ZERO_IP)

          result = []
          ip = ''
          data = self.server.recv(8192)[6:]
          while True:
             for x in range(0, len(data) - 1, 6):
                ip = struct.unpack('>BBBBH', data[x:x + 6])
                if ip == (0, 0, 0, 0, 0): return result

                result.append(('%d.%d.%d.%d' % ip[:4], ip[4]))

             self.server.send(queryformat % ('%d.%d.%d.%d:%d' % ip))
             data = self.server.recv(8192)

       @staticmethod
       def getFilterString(dedicated=False, secure=False, gamedir='', mapname='', linux=False, not_empty=False, not_full=False, proxy=False):
          """Returns a filter string for filtering query results"""
          result = ''
          if dedicated:
             result += '\\type\\d'

          if secure:
             result += '\\secure\\1'

          if gamedir:
             result += '\\gamedir\\' + gamedir

          if mapname:
             result += '\\map\\' + mapname

          if linux:
             result += '\\linux\\1'

          if not_empty:
             result += '\\empty\\1'

          if proxy:
             result += '\\full\\1'

          if proxy:
             result += '\\proxy\\1'

          return result if result else '\x00'
Simple RCON example:
PYTHON:
    >>> server = SourceServer('127.0.0.1', 27015) # Connect to server (network, port)
    >>> server.setRconPassword('mypassword') # Submit the RCON password
    True # Password accepted
    >>> server.rcon('es_msg Hello, world') # Send an RCON command
    'Hello, world\n' # Server response
    >>> server.disconnect() # Disconnect from server

SourceServer example:
PYTHON:
    server = SourceServer('127.0.0.1', 27015)


    ### RCON ###
    # To use RCON we must first define our password


    # Send rcon password
    if server.setRconPassword('mypassword', passwordResponse):
       print 'Password accepted'
    else:
       print 'Password not recognized'


    # Now we have access to the server console
    server.rcon('es_msg Hello, world') # Send a message to the server


    result = server.rcon('echo EventScripts FTW!') # Returns 'EventScripts FTW! \n'
    result = server.rcon('status') # Returns the text displayed by the status command


    ### Server queries ###
    # These functions DO NOT require an RCON password


    result = server.ping() # Returns the ping of the server in seconds relative to this connection

    result = server.getRules() # Returns a dictionary of server rules

    result = server.getDetails() # Returns a dictionary of general server information
    # version / server name / map / game directory / game description / number of players /
    # maximum players / number of bots / dedicated / os / password / secure / game version
    # More info: <!-- m --><a class="postlink" href="http://developer.valvesoftware.com/wiki/Server_Queries#Source_servers_2">http://developer.valvesoftware.com/wiki ... _servers_2</a><!-- m -->

    result = server.getPlayers() # Returns a dictionary of players on the server
    # player name / kills / time connected
    # More info: <!-- m --><a class="postlink" href="http://developer.valvesoftware.com/wiki/Server_Queries#Reply_format_4">http://developer.valvesoftware.com/wiki ... y_format_4</a><!-- m -->

MasterServerQuery example:
PYTHON:
    masterserver = MasterServerQuery()

    # Returns a list of all Source servers
    result = masterserver.getServerList()

    # Returns a list of Source servers in Europe
    result = masterserver.getServerList(masterserver.REGION_EUROPE)

    # Returns a list of dedicated CS Source servers in the eastern US
    result = masterserver.getServerList(masterserver.REGION_US_EAST_COAST, masterserver.getFilterString(dedicated=True, gamedir='cstrike'))

    masterserver.disconnect() # Disconnect from master server

Combining MasterServerQuery and SourceServer:
PYTHON:
    # This function returns the ES version if the server is running ES otherwise returns None
    def isRunningES(ip):
       import socket

       running_es = None
       try:
          network, port = ip
          server = SourceServer(network, port)
          server.settimeout(3) # We don't want to wait forever for servers to respond

          rules = server.getRules()
          if 'eventscripts_ver' in rules:
             running_es = rules['eventscripts_ver']
          server.disconnect()
       except (socket.error, SourceServerError): pass # Ingores general Internet-related exceptions and random server weirdness

       return running_es

    masterserver = MasterServerQuery()

    # Compile a list of CS Source servers running ES (this operation will probably take quite a while)
    fun_servers = filter(isRunningES, masterserver.getServerList(server_filter=masterserver.getFilterString(gamedir='cstrike')))

    masterserver.disconnect()
Thanks to Mattie for his master server code!

_________________
Image
EventScripts Wiki --- #eventscripts --- Python.org --- es_install FAQ


Last edited by SuperDave on 2008-06-25, 6:07 pm, edited 4 times in total.

User avatar
EventScripts Administrator
EventScripts Administrator
Profile

Posts: 3081
Location:
UK
 
New postPosted: 2008-06-09, 8:07 am 

Damn you :P ya always beat me :D

RE: threading... how do you mean do away with it... it would be interesting to see if it lagged much w/o the threading.....

_________________
Cheers,
Tom
Image
NCS: kill chat spam dead! - | - Get Live IRC help - | - Addon Utils - View and manage addons in game
(No IRC client? Use this sites simple online version [Requires Java])

eXtensible Admin: the #1 open source admin tool

Addons: Uptime; server uptime logger - loglib; logging utility - Block Chat; limit chat to teamonlly - Last Five; view the last few players to leave the server, with ban opts - Backup; coming SOON

My startup: social network v3.0!!


User avatar
Site Administrator
Site Administrator
Profile

Posts: 7438
Location:
At Work
 
New postPosted: 2008-06-09, 12:23 pm 

Heh, I was playing with this April of last year in order to get master server queries for looking up addon usage. It's old code and hacked together for private usage, but might be useful to someone though I wouldn't ever recommend it to be used in an ES2 addon.

Anyway, here's the master server query part for getting the IP addresses for all HL2-based Steam game servers:

PYTHON:
    import socket

    import traceback

    from struct import *





    class MasterSourceServerQuery:

        port = "27011"

        host = "hl2master.steampowered.com"

        def connect(self):

            self.s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)

            self.s.connect((self.host, int(self.port)))



        def disconnect(self):

            self.s.close()



        def getServerListData(self,region,ipstart=''):

            if len(ipstart) < 1:

                ipstart = "0.0.0.0:0"

            self.s.send(pack('BB', 0x31, region) + str(ipstart) + pack('BB',0x00,0x00))

            response = ''

            self.s.settimeout(1)

            while 1:

                try:

                    data = self.s.recv(8192)

                except:

                    break

                if not len(data):

                    break

                response = response + data

            return response



        def getServerList(self,response,debug=False):

            returnlist = []

            q = len(response)

            i = 0

            while q > 0:

                data = response[i:i+6]

                tup1 = []

                tup1.append("%d.%d.%d.%d" % unpack('>BBBBH', data)[:4])

                tup1.append("%d" % unpack('>BBBBH', data)[4:5])

                returnlist.append(tup1)

                if debug:

                    print "%d.%d.%d.%d:%d" % unpack('>BBBBH', data)

                i = i + 6

                q = len(response[i:])

            return returnlist



        def getEntireServerList(self, region, debug=False):

            ipstart = "0.0.0.0:0"

            moreservers = True

            tlist = []

            loopcount = 0

            while moreservers:

                results = self.getServerListData(region, ipstart)

                l = self.getServerList(results,debug)

                try:

                    if l[-1:][0][0] != '0.0.0.0':

                        ipstart = str(l[-1:][0][0]) + ":" + str(l[-1:][0][1])

                        tlist = tlist + l

                        loopcount = 0

                    else:

                        tlist = tlist + l[:-1]

                        moreservers = False

                except:

                    #tlist = tlist + l[:-1]

                    if loopcount > 5:

                        moreservers = False

                        traceback.print_last()

                    else:

                        moreservers = True

                    loopcount = loopcount + 1

                    #print "except" + str(len(tlist))

            return tlist

     


Used like this:
PYTHON:
    # example usage

    #

    mssq = MasterSourceServerQuery()

    mssq.connect()

    # region 0xFF is all servers.

    l = mssq.getEntireServerList(0xFF)

    mssq.disconnect()

    print l[:10]

    # [['255.255.255.255', '26122'], ['76.93.243.88', '27015'], ['76.94.64.129', '27015'], ['76.89.172.74', '27015'], ['76.89.137.156', '2700'], ['76.88.85.205', '27015'], ['76.87.100.56', '27017'], ['76.98.183.45', '27015'], ['76.87.100.56', '27015'], ['76.83.27.20', '27015']]


I then had code that took that list and tossed each of these to workers in a thread pool for doing the server information queries. That code is a bit of a mess, and it looks like you guys have some good examples of working with that data above.

-Mattie

_________________
Got errors? Try searching ErrorHelp first.
EventScripts won't load? Read Known Issues first.


User avatar
EventScripts Administrator
EventScripts Administrator
Profile

Posts: 12890
Location:
irc://irc.gamesurge.net/eventscripts
 
New postPosted: 2008-06-09, 2:47 pm 

Very cool code, Mattie! I definitely need to check out that method of packing/unpacking.

Errant wrote:
RE: threading... how do you mean do away with it... it would be interesting to see if it lagged much w/o the threading.....
Well, I consider it more "Pythonic" (that's quickly becoming a word) for the functions to return data than to mess with a callback or event. It would be more direct usage and would leave the decision to use threading up to authors. Here's a coincidence: similar Mattie's code :P

_________________
Image
EventScripts Wiki --- #eventscripts --- Python.org --- es_install FAQ


Last edited by SuperDave on 2008-06-09, 9:39 pm, edited 1 time in total.

User avatar
Guru
Guru
Profile

Posts: 1268
 
New postPosted: 2008-06-09, 4:19 pm 

Are you telling me there are no python lib's out there that handle sending data this way, like PHP does, "Big brother style"?


User avatar
Expert
Profile

Posts: 1090
 
New postPosted: 2008-06-09, 5:43 pm 

By exemple here ;)

http://www.phpclasses.org/browse/packag ... html#files

But you must create a PHP page with a field for you RCON Commands

Also possible, create a field for IP and PORT :)

Me i have a other PHP script, i don't have test this script.

_________________
Absent jusqu'au 06 Septembre Inclus

Team GHOST
Css-Ressource, skin, sons, etc...
ImageImage


User avatar
EventScripts Administrator
EventScripts Administrator
Profile

Posts: 12890
Location:
irc://irc.gamesurge.net/eventscripts
 
New postPosted: 2008-06-09, 10:00 pm 

I updated my post above to remove threading and add support for querying the master server :)

_________________
Image
EventScripts Wiki --- #eventscripts --- Python.org --- es_install FAQ


User avatar
EventScripts Administrator
EventScripts Administrator
Profile

Posts: 1662
Location:
#eventscripts ¬_¬
 
New postPosted: 2008-06-12, 7:34 am 

Is it possible to get SteamIDs using this, without an rcon password?

I'm sure it must be possible somehow (HLSW and game-monitor manage it, I think)

_________________
BlueSunCorp
Image
Image


User avatar
EventScripts Administrator
EventScripts Administrator
Profile

Posts: 3081
Location:
UK
 
New postPosted: 2008-06-12, 8:03 am 

yeh the status reply should hand back the steamids.... (I think)

_________________
Cheers,
Tom
Image
NCS: kill chat spam dead! - | - Get Live IRC help - | - Addon Utils - View and manage addons in game
(No IRC client? Use this sites simple online version [Requires Java])

eXtensible Admin: the #1 open source admin tool

Addons: Uptime; server uptime logger - loglib; logging utility - Block Chat; limit chat to teamonlly - Last Five; view the last few players to leave the server, with ban opts - Backup; coming SOON

My startup: social network v3.0!!


User avatar
Mentat
Mentat
Profile

Posts: 4222
Location:
Graveyard...?

Steam Friends Name: undead46
 
New postPosted: 2008-06-12, 8:15 am 

Yep, all you have to do is type "status" in your client console.

HLSW will also do the trick.

_________________
Venjax wrote:
Try again grasshopper.

Image


Display posts from previous:  Sort by  
Post new topic Reply to topic  [ 50 posts ]  Go to page 1, 2, 3  Next


Who is online

Users browsing this forum: No registered users and 3 guests


You cannot post new topics in this forum
You cannot reply to topics in this forum
You cannot edit your posts in this forum
You cannot delete your posts in this forum
You cannot post attachments in this forum

Search for:
Jump to:  
Powered by phpBB © 2000, 2002, 2005, 2007 phpBB Group