#include-once
;~ #include "../V.au3"
; #INDEX# =======================================================================================================================
; Title .........: SrcDSQLib
; Version........: 1.5
; AutoIt Version : 3.3.0.0++
; Language ......: German
; Description ...: Dieses Modul erhält Funktionen zur Abfrage eines Source Servers.
; Licence........: GPL 2.0 http://creativecommons.org/licenses/GPL/2.0/deed.de
; ===============================================================================================================================
; #CURRENT# =====================================================================================================================
;_SrcDSQ_Challenge
;_SrcDSQ_Info
;_SrcDSQ_Init
;_SrcDSQ_Ping
;_SrcDSQ_Player
;_SrcDSQ_RCon
;_SrcDSQ_RCon_Init
;_SrcDSQ_RCon_Shutdown
;_SrcDSQ_Rules
;_SrcDSQ_GetServerList
;_SrcDSQ_Shutdown
; ===============================================================================================================================
; #INTERNAL_USE_ONLY#============================================================================================================
;_SrcDSQ_Request
;_SrcDSQ_SetType
;__CheckBytes
;__ParseMasterServerQuery
;__RCONPackageCreate
;__ReadHexNum
;__ReadHexStr
;__ToHex
; ===============================================================================================================================
Global Const $__BYTE = 1
Global Const $__SHORT = 2
Global Const $__LONG = 4
; Source versions
Global Const $__SrcDSQ_GoldSrc = 48
Global Const $__SrcDSQ_Src06 = 7
;~ Global Const $__SrcDSQ_SrcMac = 15
; Queries
Global Const $__SrcDSQ_QR = "FFFFFFFF" ; -1
Global Const $__SrcDSQ_RM = "FEFFFFFF" ; -2
Global Const $__SrcDSQ_Challenge_Q = "56FFFFFFFF"; __ToHex("V") & __ToHex(-1)
Global Const $__SrcDSQ_Challenge_R = "41" ;__ToHex("A")
Global Const $__SrcDSQ_Info_Q = "54536F7572636520456E67696E6520517565727900" ;__ToHex("TSource Engine Query") & "00"
Global Const $__SrcDSQ_Info_R = "49" ;__ToHex("I")
Global Const $__SrcDSQ_Ping_Q = "69" ;__ToHex("i")
Global Const $__SrcDSQ_Ping_R_Src = "6A303030303030303030303030303000" ;__ToHex("j00000000000000") & "00"
Global Const $__SrcDSQ_Ping_R_GSrc = "6A00" ;__ToHex("j") & "00"
Global Const $__SrcDSQ_Ping_R_Ban = "6C42616E6E6564206279207365727665720A00" ;__ToHex("lBanned by server" & @LF) & "00"
Global Const $__SrcDSQ_Player_Q = "55" ;__ToHex("U")
Global Const $__SrcDSQ_Player_R = "44" ;__ToHex("D")
Global Const $__SrcDSQ_Rules_Q = "56" ;__ToHex("V")
Global Const $__SrcDSQ_Rules_R = "45" ;__ToHex("E")
; RCon
Global Const $__SrcDSQ_RCon_AUTH = 3
Global Const $__SrcDSQ_RCon_AUTH_RESP = 2
Global Const $__SrcDSQ_RCon_EXEC = 2
Global Const $__SrcDSQ_RCon_RESP_VAL = 0
; #GLOBAL_USE# ==================================================================================================================
; Name...........: _SrcDSQ_Timeout
; Description ...: Wartezeit für ein Packet in Millisekunden.
; Remarks .......: Valve Standard liegt bei 2000 ms.
; ===============================================================================================================================
Global $_SrcDSQ_Timeout = 2000
#Region SrcDSQ
; #FUNCTION# ====================================================================================================================
; Name...........: _SrcDSQ_Challenge
; Description ...: Liefert die Challenge Nummer für _SrcDSQ_Player() und _SrcDSQ_Rules()
; Syntax.........: _SrcDSQ_Challenge($vIP[, $iPort = 27015])
; Parameters ....: $vIP   - Eine gültige IP-Adresse z.B."192.168.0.1" oder ein Socket, der von _SrcDSQ_Init erstellt wurde
;                  $iPort - Serverport
; Return values .: Erfolg - Challenge ID
;                  Fehler - 0, setzt @error
;                  |1 - Ungültige Parameter
;                  |2 - Verbindungsprobleme, @extended enthält WSA Error
;                  |3 - Source Server ist nicht vorhanden oder antwortet nicht
;                  |4 - Deine IP wurde vom Server verbannt
;                  |5 - Datenpacket kann nicht gelesen werden
; Author ........: A.Ix0 http://steamcommunity.com/id/A-I http://twitter.com/A_I7
; ===============================================================================================================================
Func _SrcDSQ_Challenge(ByRef $vIP, $iPort = 27015)
    If IsArray($vIP) Then
        Local $ahSrcDSQSocket = $vIP
    Else
        Local $ahSrcDSQSocket = _SrcDSQ_Init($vIP, $iPort, 0)
        If @error Then Return SetError(@error, @extended, "")
        $ahSrcDSQSocket[2] = 1
    EndIf

    Local $sRecv = _SrcDSQ_Request($ahSrcDSQSocket, $__SrcDSQ_Challenge_Q)
    If @error Then Return SetError(@error, @extended, "")
    If Not __CheckBytes($sRecv, $__SrcDSQ_Challenge_R) Then SetError(5, @extended, "")

    Return StringLeft($sRecv, 8)
EndFunc   ;==>_SrcDSQ_Challenge
Func _A2S_SERVERQUERY_GETCHALLENGE($vIP, $iPort = 27015)
    Return _SrcDSQ_Challenge($vIP, $iPort)
EndFunc   ;==>_A2S_SERVERQUERY_GETCHALLENGE
; #FUNCTION# ====================================================================================================================
; Name...........: _SrcDSQ_Info
; Description ...: Fragt allgemeine Serverdaten wie Servername, Map, Spieleranzahl, etc. ab.
; Syntax.........: _SrcDSQ_Info($vIP[, $iPort = 27015])
; Parameters ....: $vIP   - Eine gültige IP-Adresse z.B."192.168.0.1" oder ein Socket, der von _SrcDSQ_Init erstellt wurde
;                  $iPort - Serverport
; Return values .: Erfolg - Array mit Daten
;                  [1] <- Protokollversion      -- number
;                  [2] <- Servername            -- string
;                  [3] <- Mapname               -- string
;                  [4] <- Spielverzeichnis      -- string
;                  [5] <- Spielname             -- string
;                  [6] <- AppID                 -- number
;                  [7] <- Spieleranzahl         -- number
;                  [8] <- Maximale Spieleranzahl-- number
;                  [9] <- Botanzahl             -- number
;                  [10] <- Servertyp            -- string: "d"-dedicated / "l"-listened / "p"-SourceTV
;                  [11] <- Betriebssystem       -- string: "w"-Windows / "l"-Linux
;                  [12] <- Passwortstatus       -- boolean
;                  [13] <- VAC Status           -- boolean
;                  [14] <- Spielversion         -- string
;                  [15] <- Erweitert            -- boolean
;                  [16] <- Port                 -- number
;                  [17] <- SteamID              -- number
;                  [18] <- HLTV                 -- string: HLTV Port mit Servernamen durch ":" getrennt z.b.'30001:"War live!"'
;                  [19] <- Tags                 -- string: Sind durch ein Komma (,) getrennt
;
;                  Fehler - 0, setzt @error
;                  |1 - Ungültige Parameter
;                  |2 - Verbindungsprobleme, @extended enthält WSA Error
;                  |3 - Source Server ist nicht vorhanden oder antwortet nicht
;                  |4 - Deine IP wurde vom Server verbannt
;                  |5 - Datenpacket kann nicht gelesen werden
; Remarks .......: Alle Daten sind in Strings konvertiert.
;                  Indizies [16] - [19] sind nur dann vorhanden, wenn [15] "1" ist. Ob diese einen Wert haben, ist
;                  von der Serverversion abhängig.
; Author ........: A.Ix0 http://steamcommunity.com/id/A-I http://twitter.com/A_I7
; ===============================================================================================================================
Func _SrcDSQ_Info($vIP, $iPort = 27015)
    If IsArray($vIP) Then
        Local $ahSrcDSQSocket = $vIP
    Else
        Local $ahSrcDSQSocket = _SrcDSQ_Init($vIP, $iPort, 0)
        If @error Then Return SetError(@error, @extended, 0)
        $ahSrcDSQSocket[2] = 1
    EndIf

    Local $sRecv = _SrcDSQ_Request($ahSrcDSQSocket, $__SrcDSQ_Info_Q)
    If @error Then Return SetError(@error, @extended, 0)
    If Not __CheckBytes($sRecv, $__SrcDSQ_Info_R) Then Return SetError(5, @extended, 0) ;Header check

    Local $asData[16]
    $asData[0] = 15
    $asData[1] = __ReadHexNum($sRecv, $__BYTE); version -- byte
    $asData[2] = __ReadHexStr($sRecv) ; name -- string
    $asData[3] = __ReadHexStr($sRecv) ; map name -- string
    $asData[4] = __ReadHexStr($sRecv) ; game dir -- string
    $asData[5] = __ReadHexStr($sRecv) ; game name -- string
    $asData[6] = __ReadHexNum($sRecv, $__SHORT); AppID -- short
    $asData[7] = __ReadHexNum($sRecv, $__BYTE); players -- byte
    $asData[8] = __ReadHexNum($sRecv, $__BYTE); Max players -- byte
    $asData[9] = __ReadHexNum($sRecv, $__BYTE); Bots -- byte
    $asData[10] = __ReadHexStr($sRecv, $__BYTE) ;Type -- byte
    $asData[11] = __ReadHexStr($sRecv, $__BYTE) ; OS -- byte
    $asData[12] = __ReadHexNum($sRecv, $__BYTE); Password status -- byte
    $asData[13] = __ReadHexNum($sRecv, $__BYTE); VAC status -- byte
    $asData[14] = __ReadHexStr($sRecv) ; Game version -- string
    If StringLeft($sRecv, 6) <> "" Then ; Extended -- byte
        Local $iEDF = __ReadHexNum($sRecv, $__BYTE)
        ReDim $asData[20]
        $asData[15] = "1"
        $asData[0] = 19
        If BitAND($iEDF, 0x80) Then $asData[16] = __ReadHexNum($sRecv, $__SHORT); Port -- short
        If BitAND($iEDF, 0x10) Then $asData[17] = __ReadHexNum($sRecv, $__LONG + $__LONG); SteamID -- long long
        If BitAND($iEDF, 0x40) Then $asData[18] = __ReadHexNum($sRecv, $__SHORT) & ':"' & _ ;Spec Port -- short
                __ReadHexStr($sRecv) & '"' ; Spec Name -- string
        If BitAND($iEDF, 0x20) Then $asData[19] = __ReadHexStr($sRecv) ; Tags -- string
    Else
        $asData[15] = "0"
    EndIf
    Return $asData
EndFunc   ;==>_SrcDSQ_Info
Func _A2S_INFO($sIP, $iPort = 27015)
    Return _SrcDSQ_Info($sIP, $iPort)
EndFunc   ;==>_A2S_INFO
; #FUNCTION# ====================================================================================================================
; Name...........: _SrcDSQ_Init
; Description ...: Erstellt einen Socket, der für weitere SrcDSQ-Funktionen benötigt wird.
; Syntax.........: _SrcDSQ_Init($sIP[, $iPort = 27015[, $iCheck = 1]])
; Parameters ....: $sIP - Eine gültige IP-Adresse z.B."192.168.0.1"
;                  $iPort - Serverport
;                  $iCheck - Schalter zur überprüfung der Version der Server Engine
; Return values .: Erfolg - Socket
;                  Fehler - 0, setzt @error
;                  |1 - Ungültige Parameter
;                  |2 - Verbindungsprobleme, @extended enthält WSA Error
;                  |3 - Source Server ist nicht vorhanden oder antwortet nicht
;                  |4 - Deine IP wurde vom Server verbannt
; Remarks .......: Bei $iCheck < 1 werden die Überprüfungen der Serverversion übersprungen (nicht empfehlenswert,
;                 es sei denn, man weis über den Server bescheid)
; Related .......: _SrcDSQ_Shutdown
; Author ........: A.Ix0 http://steamcommunity.com/id/A-I http://twitter.com/A_I7
; ===============================================================================================================================
Func _SrcDSQ_Init($sIP, $iPort = 27015, $iCheck = 1)
    If Not (IsString($sIP) And IsInt($iPort) And IsInt($iCheck)) Then Return SetError(1, 0, 0)
    Local $ahSrcDSQSocket[3] = [0, 0, 0]
    UDPStartup()

    $ahSrcDSQSocket[0] = UDPOpen($sIP, $iPort) ; Creating Socket
    If @error Then ; and checking for WSA errors
        Local $_err = @error
        UDPShutdown()
        Return SetError(2, $_err, 0)
    EndIf

    If $iCheck >= 0 Then
        _SrcDSQ_Ping($sIP, $iPort) ; Is there a source server? A ping test should find it out!
        If @error Then
            Switch @error
                Case 2; Something wrong with connection
                    _SrcDSQ_Shutdown($ahSrcDSQSocket)
                    Return SetError(2, 0, 0)
                Case 3; Server not responding
                    _SrcDSQ_Shutdown($ahSrcDSQSocket)
                    Return SetError(3, 0, 0)
                Case 4; You have a banned IP :P
                    _SrcDSQ_Shutdown($ahSrcDSQSocket)
                    Return SetError(4, 0, 0)
            EndSwitch
        EndIf
        If @extended Then $ahSrcDSQSocket[1] = @extended
        If $iCheck And Not $ahSrcDSQSocket[1] Then
            _SrcDSQ_SetType($ahSrcDSQSocket)
        EndIf
    EndIf
    ; No errors? Nice, have a socket!
    Return $ahSrcDSQSocket
EndFunc   ;==>_SrcDSQ_Init
; #FUNCTION# ====================================================================================================================
; Name...........: _SrcDSQ_Ping
; Description ...: Misst die Geschwindigkeit, die ein Packet zum Server und zurück braucht.
; Syntax.........: _SrcDSQ_Ping($vIP[, $iPort = 27015])
; Parameters ....: $vIP - Eine gültige IP-Adresse z.B."192.168.0.1" oder ein Socket, der von _SrcDSQ_Init erstellt wurde
;                  $iPort - Serverport
; Return values .: Erfolg - Der Ping in Millisekunden
;                  Fehler - -1, setzt @error
;                  |1 - Ungültige Parameter
;                  |2 - Verbindungsprobleme, @extended enthält WSA Error
;                  |3 - Source Server ist nicht vorhanden oder antwortet nicht
;                  |4 - Deine IP wurde vom Server verbannt, der Ping wird trotzdem zurückgegeben
; Author ........: A.Ix0 http://steamcommunity.com/id/A-I http://twitter.com/A_I7
; ===============================================================================================================================
Func _SrcDSQ_Ping($vIP, $iPort = 27015)
    If IsArray($vIP) Then
        Local $ahSrcDSQSocket = $vIP
    Else
        Local $ahSrcDSQSocket = _SrcDSQ_Init($vIP, $iPort, -1)
        If @error Then Return SetError(@error, @extended, -1)
        $ahSrcDSQSocket[2] = 1
    EndIf

    Local $t = TimerInit() ; Go!
    Local $sRecv = _SrcDSQ_Request($ahSrcDSQSocket, $__SrcDSQ_Ping_Q)
    If @error Then Return SetError(@error, @extended, -1)
    $t = TimerDiff($t) ; Stop!

    Switch $sRecv ; Check the validity of the server
        Case $__SrcDSQ_Ping_R_Src ; Source
            Return $t ; If header is ok, return the time
        Case $__SrcDSQ_Ping_R_GSrc ; GoldSrc
            SetExtended($__SrcDSQ_GoldSrc)
            Return $t
        Case $__SrcDSQ_Ping_R_Ban ; Valid too, but your IP is banned!
            Return SetError(4, 0, $t)
    EndSwitch

    Return SetError(3, @extended, -1) ; Even if there is a server, he didn't respond :(
EndFunc   ;==>_SrcDSQ_Ping
Func _A2A_PING($sIP, $iPort = 27015)
    _SrcDSQ_Ping($sIP, $iPort)
EndFunc   ;==>_A2A_PING
; #FUNCTION# ====================================================================================================================
; Name...........: _SrcDSQ_Player
; Description ...: Liefert Daten über Spieler, die sich auf dem Server befinden.
; Syntax.........: _SrcDSQ_Player($vIP[, $iPort = 27015])
; Parameters ....: $vIP   - Eine gültige IP-Adresse z.B."192.168.0.1" oder ein Socket, der von _SrcDSQ_Init erstellt wurde
;                  $iPort - Serverport
; Return values .: Erfolg - 2D Array mit Daten
;                  [0][0] <- Anzahl der Spieler -- number
;                  [i][0] <- Index              -- number
;                     [1] <- Spielername        -- string
;                     [2] <- Punkte             -- number
;                     [3] <- Verbindungszeit    -- number: in Sekunden
;
;                  Fehler - 0, setzt @error
;                  |1 - Ungültige Parameter
;                  |2 - Verbindungsprobleme, @extended enthält WSA Error
;                  |3 - Source Server ist nicht vorhanden oder antwortet nicht
;                  |4 - Deine IP wurde vom Server verbannt
;                  |5 - Datenpacket kann nicht gelesen werden
;                  |6 - Komprimierte Packete werden nicht unterstützt
; Remarks .......:; Alle Daten sind in Strings konvertiert.
;                   Spieler, die sich zum Server verbunden haben, aber noch nicht erschienen sind, haben nur Verbindungszeit [3].
; Author ........: A.Ix0 http://steamcommunity.com/id/A-I http://twitter.com/A_I7
; ===============================================================================================================================
Func _SrcDSQ_Player($vIP, $iPort = 27015)
    If IsArray($vIP) Then
        Local $ahSrcDSQSocket = $vIP
    Else
        Local $ahSrcDSQSocket = _SrcDSQ_Init($vIP, $iPort, 1)
        If @error Then Return SetError(@error, @extended, 0)
        $ahSrcDSQSocket[2] = 1
    EndIf

    Local $sRecv = _SrcDSQ_Request($ahSrcDSQSocket, $__SrcDSQ_Player_Q & _SrcDSQ_Challenge($vIP, $iPort))
    If @error Then Return SetError(@error, @extended, 0)
    If Not __CheckBytes($sRecv, $__SrcDSQ_Player_R) Then Return SetError(5, @extended, 0)

    Local $iNum = __ReadHexNum($sRecv, $__BYTE) ; Player number -- byte
    Local $asData[$iNum + 1][4]
    $asData[0][0] = $iNum
    For $i = 1 To $iNum
        $asData[$i][0] = __ReadHexNum($sRecv, $__BYTE) ;Index -- byte
        $asData[$i][1] = __ReadHexStr($sRecv) ;Name -- string
        $asData[$i][2] = __ReadHexNum($sRecv, $__LONG) ;Kills -- long
        $asData[$i][3] = __ReadHexNum($sRecv, $__LONG, True) ;Time -- float
    Next
    Return $asData
EndFunc   ;==>_SrcDSQ_Player
Func _A2S_PLAYER($sIP, $iPort = 27015)
    Return _SrcDSQ_Player($sIP, $iPort)
EndFunc   ;==>_A2S_PLAYER
; #FUNCTION# ====================================================================================================================
; Name...........: _SrcDSQ_Rules
; Description ...: Gibt einige Servereinstellungen zurück.
; Syntax.........: _SrcDSQ_Rules($vIP[, $iPort = 27015])
; Parameters ....: $vIP   - Eine gültige IP-Adresse z.B."192.168.0.1" oder ein Socket, der von _SrcDSQ_Init erstellt wurde
;                  $iPort - Serverport
; Return values .: Erfolg - 2D Array mit Daten.
;                  [0][0] <- Anzahl der Regeln  -- number
;                  [i][0] <- Konsolenbefehl     -- string (z.B. "sv_cheats")
;                     [1] <- Sein Wert          -- string (z.B. "0")
;                  Fehler - 0, setzt @error
;                  |1 - Ungültige Parameter
;                  |2 - Verbindungsprobleme, @extended enthält WSA Error
;                  |3 - Source Server ist nicht vorhanden oder antwortet nicht
;                  |4 - Deine IP wurde vom Server verbannt
;                  |5 - Datenpacket kann nicht gelesen werden
;                  |6 - Komprimierte Packete werden nicht unterstützt
; Remarks .......:; Alle Daten sind in Strings konvertiert.
; Remarks .......: Die Regelanzahl stimmt nicht immer mit der tatsächlichen überein.
;                  Manche Befehle enthalten keinen ("") Wert.
; Author ........: A.Ix0 http://steamcommunity.com/id/A-I http://twitter.com/A_I7
; ===============================================================================================================================
Func _SrcDSQ_Rules($vIP, $iPort = 27015)
    If IsArray($vIP) Then
        Local $ahSrcDSQSocket = $vIP
    Else
        Local $ahSrcDSQSocket = _SrcDSQ_Init($vIP, $iPort, 1)
        If @error Then Return SetError(@error, @extended, 0)
        $ahSrcDSQSocket[2] = 1
    EndIf


    Local $sRecv = _SrcDSQ_Request($ahSrcDSQSocket, $__SrcDSQ_Rules_Q & _SrcDSQ_Challenge($vIP, $iPort))
    If @error Then Return SetError(@error, @extended, 0)
    If Not __CheckBytes($sRecv, $__SrcDSQ_Rules_R) Then Return SetError(5, @extended, 0)

    Local $iNum = __ReadHexNum($sRecv, $__SHORT) ; rules count -- short
    Local $asData[$iNum + 1][2]
    $asData[0][0] = $iNum ;...goes into first place.
    For $i = 1 To $iNum
        $asData[$i][0] = __ReadHexStr($sRecv) ; Rule -- string
        $asData[$i][1] = __ReadHexStr($sRecv) ; Value -- string
    Next
    Return $asData
EndFunc   ;==>_SrcDSQ_Rules
Func _A2S_RULES($sIP, $iPort = 27015)
    Return _SrcDSQ_Rules($sIP, $iPort)
EndFunc   ;==>_A2S_RULES
; #INTERNAL_USE_ONLY# ===========================================================================================================
; Name...........: _SrcDSQ_Request
; Description ...: Kernfunktion. Sendet Serveranfragen, erwartet Antworten und setzt mehrere Packete zusammen.
; Syntax.........: _SrcDSQ_Request($ahSrcDSQSocket, $sReq)
; Parameters ....: $ahSrcDSQSocket - Das Socket, welches von _SrcDSQ_Init erstellt wurde.
;                  $sReq - Anfragedaten
; Return values .: Erfolg - Serverantwort als Hexadezimaler String
;                  Fehler - 0, setzt @error
;                  |1 - Verbindungsprobleme, @extended enthält WSA ERROR
;                  |2 - Source Server ist nicht vorhanden oder antwortet nicht
;                  |5 - Komprimierte Packete werden (noch) nicht unterstützt
; Remarks .......: Wenn @extended > 0 ist, wurden nicht alle Packete empfangen.
; Author ........: A.Ix0 http://steamcommunity.com/id/A-I http://twitter.com/A_I7
; ===============================================================================================================================
Func _SrcDSQ_Request(ByRef $ahSrcDSQSocket, $sReq)
    Local $sRecv = "", $fFristPacket = True, $iPacketSize = 1420
    Local $iPacketID, $iPacketCount, $iPacketNum

    UDPSend($ahSrcDSQSocket[0], "0x" & $__SrcDSQ_QR & $sReq)
    If @error Then
        Local $_err = @error
        If $ahSrcDSQSocket[2] Then _SrcDSQ_Shutdown($ahSrcDSQSocket)
        Return SetError(2, $_err, 0)
    EndIf

    Local $i = 1, $t = TimerInit()
    While TimerDiff($t) < $_SrcDSQ_Timeout And $i > 0

        $sRecv = UDPRecv($ahSrcDSQSocket[0], $iPacketSize, 1)
        If @error Then
            Local $_err = @error
            If $ahSrcDSQSocket[2] Then _SrcDSQ_Shutdown($ahSrcDSQSocket)
            Return SetError(2, $_err, 0)
        EndIf

;~          If $sRecv Then v($sRecv)

        If __CheckBytes($sRecv, "0x" & $__SrcDSQ_QR) Then Return $sRecv
        If __CheckBytes($sRecv, "0x" & $__SrcDSQ_RM) Then
            If $fFristPacket Then
                If BinaryMid("0x" & $sRecv, 4, 1) = 0x80 Then
                    If $ahSrcDSQSocket[2] Then _SrcDSQ_Shutdown($ahSrcDSQSocket)
                    Return SetError(6, 0, 0) ; No support for compressed packets yet, sorry :(
                EndIf
                $fFristPacket = False
                $iPacketID = __ReadHexNum($sRecv, $__LONG) ; packet chain ID
                Switch $ahSrcDSQSocket[1]
                    Case $__SrcDSQ_Src06
                        $iPacketCount = __ReadHexNum($sRecv, $__BYTE) ; packet count
                        $iPacketNum = __ReadHexNum($sRecv, $__BYTE) ; packet number
                    Case $__SrcDSQ_GoldSrc
                        $iPacketNum = __ReadHexNum($sRecv, $__BYTE / 2) ; packet number
                        $iPacketCount = __ReadHexNum($sRecv, $__BYTE / 2) ; packet count
                    Case Else
                        $iPacketCount = __ReadHexNum($sRecv, $__BYTE) ; packet count
                        $iPacketNum = __ReadHexNum($sRecv, $__BYTE) ; packet number
                        $iPacketSize = __ReadHexNum($sRecv, $__SHORT) + 12; packet size
                EndSwitch

                $i = $iPacketCount
                Local $asRecv[$iPacketCount]
                $asRecv[$iPacketNum] = $sRecv ; Capture it!
            Else
                If $iPacketID = __ReadHexNum($sRecv, $__LONG) Then ; Has the right ID?
                    Switch $ahSrcDSQSocket[1] ; $asRecv[$PacketNum] -> Put the packets in the right order
                        Case $__SrcDSQ_Src06 ;Source 2006 workaround
                            $sRecv = StringTrimLeft($sRecv, $__BYTE * 2) ; packet count
                            $asRecv[__ReadHexNum($sRecv, $__BYTE)] = $sRecv
                        Case $__SrcDSQ_GoldSrc ;GoldSrc workaround
                            $asRecv[Int(StringLeft($sRecv, 1))] = StringTrimLeft($sRecv, $__BYTE * 2)
                        Case Else
                            $sRecv = StringTrimLeft($sRecv, $__BYTE * 2) ; The count of packets... again, no need for it
                            $asRecv[__ReadHexNum($sRecv, $__BYTE)] = StringTrimLeft($sRecv, $__SHORT * 2)
                    EndSwitch
                EndIf
            EndIf
            $i -= 1
            $t = TimerInit() ;Reset timeout for other packet
        EndIf
    WEnd

    If IsDeclared("asRecv") Then
        $sRecv = ""
        Local $i_lost = 0
        For $j = 0 To $iPacketCount - 1
            If $asRecv[$j] = "" Then $i_lost += 1
            $sRecv &= $asRecv[$j]
        Next
        SetExtended($i_lost)
        __CheckBytes($sRecv, "FFFFFFFF")
        Return $sRecv
    EndIf

    If $ahSrcDSQSocket[2] Then _SrcDSQ_Shutdown($ahSrcDSQSocket)
    Return SetError(3, 0, 0)
EndFunc   ;==>_SrcDSQ_Request
; #FUNCTION# ====================================================================================================================
; Name...........: _SrcDSQ_Shutdown
; Description ...: Schließt und annulliert das Socket.
; Syntax.........: _SrcDSQ_Shutdown(ByRef $ahSrcDSQSocket)
; Parameters ....: $ahSrcDSQSocket - Das Socket, welches von _SrcDSQ_Init erstellt wurde.
; Return values .: Erfolg - 1
;                  Fehler - 0, setzt @error
;                  | nicht 0 - Socket konnte nicht geschlossen werden, WSA ERROR
; Related .......: _SrcDSQ_Init
; Author ........: A.Ix0 http://steamcommunity.com/id/A-I http://twitter.com/A_I7
; ===============================================================================================================================
Func _SrcDSQ_Shutdown(ByRef $ahSrcDSQSocket) ; Closes the socket and UDP-instance.
    If Not IsArray($ahSrcDSQSocket) Then Return 0
    UDPCloseSocket($ahSrcDSQSocket[0])
    If @error Then Return SetError(@error, 0, 0) ;Well, someone has failed.

    $ahSrcDSQSocket = 0
    UDPShutdown()
    Return 1
EndFunc   ;==>_SrcDSQ_Shutdown

; #INTERNAL_USE_ONLY# ===========================================================================================================
Func _SrcDSQ_SetType(ByRef $ahSrcDSQSocket)
    Local $Info = _SrcDSQ_Info($ahSrcDSQSocket)
    If IsArray($Info) Then
        $ahSrcDSQSocket[1] = $Info[1]
    EndIf
EndFunc   ;==>_SrcDSQ_SetType
#EndRegion SrcDSQ
#Region MasterQ
; #FUNCTION# ====================================================================================================================
; Name...........: _SrcDSQ_GetServerlist
; Description ...: Holt vom MasterServer bestimmte Source Server-IPs.
; Syntax.........: _SrcDSQ_GetServerlist($iInit[, $iRegion = 255[, $sType = ""[, $iSecure = 0[, $sGamedir = ""[, $sMap = ""
;                                        [, $iLinux = 0[, $iNotEmpty = 0[, $iNotFull = 0[, $iSpecProxy = 0[, $iNotApp = 0
;                                        [, $iEmpty = 0[, $iWhite = 0]]]]]]]]]]]])
; Parameters ....: $iInit - Siehe Remarks.
;                  - Filtereinstellungen -
;                  $iRegion - Die Region, wo der Server sich befindet, Standard ist 255 - Alle (siehe Remarks für mehr)
;                  $sType - Typ des Server: "d" für dediziert, "l" für "listened"
;                  $iSecure - Ob VAC auf den Server aktiv ist.
;                  $sGamedir - Welche Mod/Spiel der Server benutzt Bsp. "cstrike", "swarm", "dystopia"
;                  $sMap - Name der Map. Darf auch unvollständig sein Bsp. "de_dust", "cs_offi"
;                  $iLinux - Ob der Server auf Linux läuft
;                  $iNotEmpty - Spieler müssen vorhanden sein
;                  $iNotFull - Server soll nicht voll sein
;                  $iSpecProxy - Es werden nur HLTV Server gelistet
;                  $iNotApp - Server mit bestimmten AppID eines Spiels werden nicht gelistet (wie Left4Dead)
;                  $iEmpty - Keine Spieler auf dem Server
;                  $iWhite - Nur Server die auf der Whitelist stehen
; Return values .: Erfolg - 2D Array mit IP und Port
;                  [0][0] <- Anzahl der Server  -- number
;                  [i][0] <- IP                 -- string (z.B. "78.125.158.6")
;                    [1] <- Port                -- number (z.B. "27016")
;                  Fehler - 0, setzt @error
;                  |1 - Verbindungsprobleme, @extended enthält WSA Error
;                  |2 - Datenpacket kann nicht gelesen werden
;                  |3 - MasterServer antwortet nicht
; Remarks .......: $iInit:
;                  * =1: Re-/Initialization des Sockets und festlegung des Filters. Die Filterparameter können nur in diesem Fall
;                        gesetzt werden. Es wird das erste Packet mit IP-Adressen zerückgegeben.
;                  * =0: Mit jedem Aufruf wird ein Packet mit weiteren IP-Adressen zurückgegeben.
;                  * =-1: Schließt das Socket.
;
;                  Regioncodes: 0 - US Ostküste, 1 - US Westküste, 2 - Südamerika, 3 - Europa, 4 - Asien, 5 - Australien,
;                                6 - Mittlerer Osten, 7 - Afrika, 255 - Rest der Welt (Alle)
;
; Author ........: A.Ix0 http://steamcommunity.com/id/A-I http://twitter.com/A_I7
; ===============================================================================================================================
Func _SrcDSQ_GetServerlist($iInit, $iRegion = 255, $sType = "", $iSecure = 0, $sGamedir = "", $sMap = "", _
        $iLinux = 0, $iNotEmpty = 0, $iNotFull = 0, $iSpecProxy = 0, $iNotApp = 0, $iEmpty = 0, $iWhite = 0)

    Local Static $hMasterServer, $sReqIP = "0.0.0.0:0", $sFilter = "", $_iRegion = 255, $iStarted = 0
    If $iInit = 1 Then
        UDPCloseSocket($hMasterServer)
        If $iStarted Then
            UDPShutdown()
            $iStarted = 0
        Else
            $iStarted = UDPStartup()
            If @error Then Return SetError(1, @error, 0)
        EndIf
        $_iRegion = $iRegion
        $sReqIP = "0.0.0.0:0"
        $sFilter = ""
        $hMasterServer = UDPOpen(TCPNameToIP("hl2master.steampowered.com"), 27011)
        If @error Then
            Local $_err = @error
            UDPShutdown()
            Return SetError(1, $_err, 0)
        EndIf
        If $sType Then $sFilter &= "\type\" & $sType
        If $iSecure Then $sFilter &= "\secure\" & $iSecure
        If $sGamedir Then $sFilter &= "\gamedir\" & $sGamedir
        If $sMap Then $sFilter &= "\map\" & $sMap
        If $iLinux Then $sFilter &= "\linux\" & $iLinux
        If $iNotEmpty Then $sFilter &= "\empty\" & $iNotEmpty
        If $iNotFull Then $sFilter &= "\full\" & $iNotFull
        If $iSpecProxy Then $sFilter &= "\proxy\" & $iSpecProxy
        If $iNotApp Then $sFilter &= "\napp\" & $iNotApp
        If $iEmpty Then $sFilter &= "\noplayers\" & $iEmpty
        If $iWhite Then $sFilter &= "\white\" & $iWhite
    ElseIf $iInit = -1 Then
        UDPCloseSocket($hMasterServer)
        $hMasterServer = 0
        UDPShutdown()
        Return 0
    EndIf

    If Not IsArray($hMasterServer) Then Return SetError(1, 0, 0)
    UDPSend($hMasterServer, Binary("1" & Chr($_iRegion) & $sReqIP & Chr(00) & $sFilter & Chr(00)))
    If @error Then
        Local $_err = @error
        UDPCloseSocket($hMasterServer)
        If $iStarted Then
            UDPShutdown()
            $iStarted = 0
        EndIf
        Return SetError(1, $_err, 0)
    EndIf

    Local $sRecv = "", $t = TimerInit()
    Do
        $sRecv &= UDPRecv($hMasterServer, 1392, 1)
    Until $sRecv Or TimerDiff($t) >= 500
    If Not $sRecv Then Return SetError(3, 0, 0)

    If Not __CheckBytes($sRecv, "0xFFFFFFFF660A") Then Return SetError(2, 0, 0)
    Local $asData = __ParseMasterServerPacket($sRecv)

    Local $last = UBound($asData) - 1
    $sReqIP = $asData[$last][0] & ":" & $asData[$last][1]

    If IsArray($asData) Then
        Return $asData
    EndIf
    Return SetError(3, 0, 0)
EndFunc   ;==>_SrcDSQ_GetServerlist

; #INTERNAL_USE_ONLY# ===========================================================================================================
Func __ParseMasterServerPacket(ByRef $sHex)
    Local $asData[256][2], $i = 1
    While StringLeft($sHex, 2) <> ""
        For $j = 0 To 3
            $asData[$i][0] &= String(Int("0x" & StringLeft($sHex, 2)))
            $sHex = StringTrimLeft($sHex, 2)
            If $j < 3 Then $asData[$i][0] &= "."
        Next
        If $asData[$i][0] = "0.0.0.0" Or $asData[$i][0] = "" Then ExitLoop
        $asData[$i][1] = String(Int("0x" & StringLeft($sHex, 4)))
        $sHex = StringTrimLeft($sHex, 4)
        $i += 1
    WEnd
    $asData[0][0] = $i - 1
    ReDim $asData[$i][2]
    Return $asData
EndFunc   ;==>__ParseMasterServerPacket
#EndRegion MasterQ


#Region RCon
; #FUNCTION# ====================================================================================================================
; Name...........: _SrcDSQ_RCon_Init
; Description ...: Stellt eine Verbindung zum Server her und prüft die Richtigkeit des Passwortes.
; Syntax.........: _SrcDSQ_RCon_Init($sIP, $iPort, $sPW)
; Parameters ....: $sIP - Eine gültige IP-Adresse z.B."192.168.0.1"
;                  $iPort - Serverport
;                  $sPW - Das Rcon-Passwort, dass beim Server durch rcon_password festgelegt wurde
; Return values .: Erfolg - Socket für weitere Funktionen
;                  Fehler - 0, setzt @error
;                  |1 - Ungültige Parameter
;                  |2 - Verbindungsprobleme, @extended enthält WSA ERROR
;                  |3 - Server antwortet nicht
;                  |4 - Das Passwort ist falsch
; Remarks .......: Bei öfteren Eingeben eines falschen Passwortes wird die IP des Clienten verbannt (herauszufinden mit
;                  _SrcDSQ_Ping()). Jedoch wird das richtige Passwort weiterhin akzeptiert.
; Author ........: A.Ix0 http://steamcommunity.com/id/A-I http://twitter.com/A_I7
; ===============================================================================================================================
Func _SrcDSQ_RCon_Init($sIP, $iPort, $sPW)
    If Not (IsString($sIP) And IsInt($iPort) And IsString($sPW)) Then Return SetError(1, 0, 0)
    ;============== Starup and connect
    TCPStartup()
    If @error Then Return SetError(1, @error, 0)
    Local $ahRCONSocket[2]
    $ahRCONSocket[0] = TCPConnect($sIP, $iPort)
    If @error Then
        Local $_err = @error
        TCPShutdown()
        Return SetError(2, $_err, 0)
    EndIf
    ;============== Create auth packet and send it
    Local $iRCONID = Random(0, 2 ^ 16, 1)
    TCPSend($ahRCONSocket[0], __RCONPackageCreate($sPW, $iRCONID, $__SrcDSQ_RCon_AUTH))
    If @error Then
        Local $_err = @error
        _SrcDSQ_RCon_Shutdown($ahRCONSocket)
        Return SetError(2, $_err, 0)
    EndIf
    ;============== Wait for response packet
    Local $sRecv, $nt = TimerInit()
    While TimerDiff($nt) <= $_SrcDSQ_Timeout

        $sRecv = TCPRecv($ahRCONSocket[0], 14, 1)
        If @error Then
            Local $_err = @error
            _SrcDSQ_RCon_Shutdown($ahRCONSocket)
            Return SetError(2, $_err, 0)
        EndIf
        ;============== Have one!
        If $sRecv Then
            Switch $sRecv
                Case "0x0A000000" & __ToHex($iRCONID) & __ToHex($__SrcDSQ_RCon_RESP_VAL) & "0000" ;"junk" packet, can be ignored
                    ContinueLoop
                Case "0x0A000000" & __ToHex($iRCONID) & __ToHex($__SrcDSQ_RCon_AUTH_RESP) & "0000" ; Access granted
                    $ahRCONSocket[1] = $sPW
                    Return $ahRCONSocket
                Case "0x0A000000" & __ToHex(-1) & __ToHex($__SrcDSQ_RCon_AUTH_RESP) & "0000" ; Access denied
                    _SrcDSQ_RCon_Shutdown($ahRCONSocket)
                    Return SetError(4, 0, 0)
                Case Else ; not a Source server?
                    ExitLoop
            EndSwitch
        EndIf
    WEnd
    _SrcDSQ_RCon_Shutdown($ahRCONSocket)
    Return SetError(3, 0, 0)
EndFunc   ;==>_SrcDSQ_RCon_Init
; #FUNCTION# ====================================================================================================================
; Name...........: _SrcDSQ_RCon
; Description ...: Sendet eine Anweisung an den Server und gibt dessen Antwort zurück.
; Syntax.........: _SrcDSQ_RCon($vIP[, $vPort = 27015[, $sPW = ""[, $sCom = ""]]])
; Parameters ....: $vIP - Eine gültige IP-Adresse z.B."192.168.0.1" oder ein Socket, dass von _SrcDSQ_RCon_Init erstellt wurde.
;                  $vPort - Serverport oder Anweisungen falls über $vIP ein Socket übergeben wurde.
;                  $sPW - Das Rcon-Passwort, dass beim Server durch rcon_password festgelegt wurde
;                  $sCon - Die Anweisung, die beim Server ausgeführt werden soll.
; Return values .: Erfolg - Serverantwort
;                  Fehler - 0, setzt @error
;                  |1 - Ungültige Parameter
;                  |2 - Verbindungsprobleme, @extended enthält WSA ERROR
;                  |3 - Server antwortet nicht
;                  |4 - Das Passwort ist falsch
;                  |5 - Nicht alle Packete kamen an, die (unvollständige) Serverantwort wird dennoch zurückgegeben
; Remarks .......: Bei Nutzung des Sockets, statt einer IP-Adresse, erfolgen mehrere Abfragen auf einen einzelnen Server deutlich
;                  schneller. Das Kommando darf dabei an $vPort übergeben und $sPW und $sCom leergelassen werden.
; Author ........: A.Ix0 http://steamcommunity.com/id/A-I http://twitter.com/A_I7
; ===============================================================================================================================
Func _SrcDSQ_RCon($vIP, $vPort = 27015, $sPW = "", $sCom = "")
    If IsString($vIP) Then
        If Not IsString($sCom) Then Return SetError(1, 0, 0)
        Local $ahRCONSocket = _SrcDSQ_RCon_Init($vIP, $vPort, $sPW)
        If @error Then Return SetError(@error, 0, 0)
        Local $hRCONSocket = $ahRCONSocket[0]
    ElseIf IsArray($vIP) Then
        Local $hRCONSocket = $vIP[0]
        $sPW = $vIP[1]
        $sCom = $vPort
    Else
        Return SetError(1, 0, 0)
    EndIf
    ;============== Make ID and send request
    Local $iRCONID = Random(0, 2 ^ 16, 1)
    TCPSend($hRCONSocket, __RCONPackageCreate($sCom, $iRCONID, $__SrcDSQ_RCon_EXEC))
    If @error Then
        Local $_err = @error
        If IsString($vIP) Then _SrcDSQ_RCon_Shutdown($hRCONSocket)
        Return SetError(2, $_err, 0)
    EndIf
    ;============== Wait for answer
    Local $sRecv, $iSize, $iTerminatorID, $sRet = "", $nFirst = True
    Local $t = TimerInit()
    While TimerDiff($t) <= $_SrcDSQ_Timeout

        $sRecv = TCPRecv($hRCONSocket, $__LONG, 1)
        If @error Then
            Local $_err = @error
            If IsString($vIP) Then _SrcDSQ_RCon_Shutdown($hRCONSocket)
            Return SetError(2, $_err, 0)
        EndIf
        ;============== Get the size
        If $sRecv Then
            $sRecv = StringTrimLeft($sRecv, 2)
            $iSize = __ReadHexNum($sRecv, $__LONG)
            ;============== Get the the whole packet
            $sRecv = TCPRecv($hRCONSocket, $iSize, 1)
            If @error Then
                Local $_err = @error
                If IsString($vIP) Then _SrcDSQ_RCon_Shutdown($hRCONSocket)
                Return SetError(2, $_err, 0)
            EndIf
            $sRecv = StringTrimLeft($sRecv, 2)
            ;============== Catch the terminator packet
            If Not $nFirst And $iSize < 30 And $sRecv = __ToHex($iTerminatorID) & __ToHex($__SrcDSQ_RCon_RESP_VAL) & "0000" Then ExitLoop
            ;============== The actual packet processing
            If __ReadHexNum($sRecv, $__LONG) = $iRCONID Then
                __CheckBytes($sRecv, __ToHex($__SrcDSQ_RCon_RESP_VAL))
                $sRet &= __ReadHexStr($sRecv)
            EndIf
            ;============== send the terminator packet
            If $nFirst And $iSize > 3000 Then
                $iTerminatorID = Random(0, 2 ^ 16, 1)
                TCPSend($hRCONSocket, __RCONPackageCreate($sPW, $iTerminatorID, $__SrcDSQ_RCon_AUTH))
                $nFirst = False
            EndIf
            $t = TimerInit()
        EndIf
    WEnd
    If IsString($vIP) Then _SrcDSQ_RCon_Shutdown($hRCONSocket)
    If Not $nFirst And TimerDiff($t) > $_SrcDSQ_Timeout Then SetError(5)
    Return $sRet
EndFunc   ;==>_SrcDSQ_RCon
; #FUNCTION# ====================================================================================================================
; Name...........: _SrcDSQ_RCon_Shutdown
; Description ...: Schließt und annulliert das Socket.
; Syntax.........: _SrcDSQ_RCon_Shutdown(ByRef $hRCONSocket)
; Parameters ....: $hRCONSocket - Das Socket, welches von _SrcDSQ_RCon_Init erstellt wurde.
; Return values .: Erfolg - 1
;                  Fehler - 0, setzt @error
;                  | nicht 0 - Socket konnte nicht geschlossen werden, WSA ERROR
; Author ........: A.Ix0 http://steamcommunity.com/id/A-I http://twitter.com/A_I7
; ===============================================================================================================================
Func _SrcDSQ_RCon_Shutdown(ByRef $ahRCONSocket)
    If IsArray($ahRCONSocket) Then
        TCPCloseSocket($ahRCONSocket[0])
    Else
        TCPCloseSocket($ahRCONSocket)
    EndIf
    If @error Then Return SetError(@error, 0, 0)
    $ahRCONSocket = 0
    TCPShutdown()
    Return 1
EndFunc   ;==>_SrcDSQ_RCon_Shutdown

; #INTERNAL_USE_ONLY# ===========================================================================================================
Func __RCONPackageCreate($sCom, $iRCONID, $iCom)
    Return Binary("0x" & __ToHex(StringLen($sCom) + 10) & __ToHex($iRCONID) & __ToHex($iCom) & __ToHex($sCom) & "0000")
EndFunc   ;==>__RCONPackageCreate

#EndRegion RCon
#Region Helpfuncs
; #INTERNAL_USE_ONLY# ===========================================================================================================
Func __ReadHexStr(ByRef $sHex, $iLen = -1)
    If Not IsString($sHex) Then Return SetError(1, 0, 0)
    Local $sRet = ""
    Do
        $sRet &= StringLeft($sHex, 2)
        $sHex = StringTrimLeft($sHex, 2)
        $iLen -= 1
    Until StringRight($sRet, 2) = "00" Or StringLeft($sHex, 2) = "" Or $iLen = 0
    If StringRight($sRet, 2) = "00" Then $sRet = StringTrimRight($sRet, 2)
    Return BinaryToString("0x" & $sRet, 4)
EndFunc   ;==>__ReadHexStr
; #INTERNAL_USE_ONLY# ===========================================================================================================
Func __ReadHexNum(ByRef $sHex, $iLen, $fFloat = False)
    If Not IsString($sHex) Then Return SetError(1, 0, 0)
    If $iLen = $__BYTE / 2 Then
        Local $sNum = StringLeft($sHex, $iLen * 2)
    Else
        If not ($iLen > 0) Then Return SetError(2, 0, 0)
        Local $sNum, $iL = $iLen * 2
        Do
            $sNum &= StringMid($sHex, $iL - 1, 2)
            $iL -= 2
        Until $iL = 0
    EndIf
    $sHex = StringTrimLeft($sHex, $iLen * 2)
    If $fFloat Then
        Local $tInt = DllStructCreate("int")
        Local $tFloat = DllStructCreate("float", DllStructGetPtr($tInt))
        DllStructSetData($tInt, 1, Int("0x" & $sNum))
        Return DllStructGetData($tFloat, 1)
    EndIf
    If $iLen < 5 Then
        Return String(Dec($sNum))
    Else
        Return String(Int("0x" & $sNum))
    EndIf
EndFunc   ;==>__ReadHexNum
; #INTERNAL_USE_ONLY# ===========================================================================================================
Func __CheckBytes(ByRef $sHex, $sBytes)
    Local $iLen = StringLen($sBytes)
    If StringLeft($sHex, $iLen) <> $sBytes Then
        Return 0
    Else
        $sHex = StringTrimLeft($sHex, $iLen)
        Return 1
    EndIf
EndFunc   ;==>__CheckBytes
; #INTERNAL_USE_ONLY# ===========================================================================================================
Func __ToHex($in)
    If IsInt($in) Then
        $in = Hex($in)
        Local $bNum, $l = StringLen($in)
        Do
            $bNum &= StringMid($in, $l - 1, 2)
            $l -= 2
        Until $l <= 0
        Return $bNum
    EndIf
    If IsString($in) Then
        Return StringMid(StringToBinary($in, 4), 3)
    EndIf
EndFunc   ;==>__ToHex
#EndRegion Helpfuncs