#include <Array.au3>

Const $S2C_CHALLENGE = Binary("0xFFFFFFFF56FFFFFFFF")
Const $A2S_RULES = Binary("0xFFFFFFFF56")
Const $A2S_PLAYER = Binary("0xFFFFFFFF55")
Const $A2S_INFO = Binary("0xFFFFFFFF54536F7572636520456E67696E6520517565727900")
Global $g_PING = 0, $socket


; ////////////////////// Functions \\\\\\\\\\\\\\\\\\\\\\
; ===================\
;_SourceConnect
;_SourceGetChallenge
;_SourceGetInfo
;_SourceGetPlayers
;_SourceGetRules
;_SourceClose
;
;__SourceReceiveData
;__SourceBinaryToArray
; ===================/

; #FUNCTION# ====================================================================================================================
; Name ..........: _SourceConnect
; Description ...: Baut ein Verbindung zum Server auf
; Syntax ........: _SourceConnect($sIP)
; Parameters ....: $sIP                 - A string value.
; Return values .: Socket
; ===============================================================================================================================
Func _SourceConnect($sIP)
	UDPStartup()
	$aIP = StringRegExp($sIP, "([0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3})\:([0-9]{1,6})", 3)
	If Not IsArray($aIP) Then
		MsgBox(4112, "Error", "Keine gültige Ip-Addresse!")
		Return
	EndIf
	Local $socket = UDPOpen($aIP[0], $aIP[1])
	If @error Then
		MsgBox(4112, "Error", "UDPOpen failed with WSA error: " & @error)
		Exit
	EndIf
	Return $socket
EndFunc   ;==>_SourceConnect

; #FUNCTION# ====================================================================================================================
; Name ..........: _SourceGetChallenge
; Description ...: Empfängt die Challenge Number
; Syntax ........: _SourceGetChallenge($socket[, $iTimeOut = 2000])
; Parameters ....: $socket              - A string value.
;                  $iTimeOut            - [optional] An integer value. Default is 2000.
; Return values .: On Success - Returns Binary
;                  Timeout - -1
; ===============================================================================================================================
Func _SourceGetChallenge($socket, $iTimeOut = 2000)
	Local $timer = TimerInit()
	UDPSend($socket, $S2C_CHALLENGE)
	Do
		Local $data = Binary(UDPRecv($socket, 50))
		If $data <> "" And BinaryLen($data) = 9 Then Return BinaryMid($data, 6)
		Sleep(10)
	Until TimerDiff($timer) > $iTimeOut
	Return -1
EndFunc   ;==>_SourceGetChallenge

; #FUNCTION# ====================================================================================================================
; Name ..........: _SourceGetInfo
; Description ...: Empfängt die Serverinfo
; Syntax ........: _SourceGetInfo($socket)
; Parameters ....: $socket              - A string value.
; Return values .: Array
Func _SourceGetInfo($socket)
	UDPSend($socket, $A2S_INFO)
	Return __SourceBinaryToArray(__SourceReceiveData($socket), 'nnnhhssssiniiicchhs')
EndFunc   ;==>_SourceGetInfo

; #FUNCTION# ====================================================================================================================
; Name ..........: _SourceGetPlayers
; Description ...: Empfängt die Spielerdaten
; Syntax ........: _SourceGetPlayers($socket, $bChallenge)
; Parameters ....: $socket              - A string value.
;                  $bChallenge          - A binary value.
; Return values .: Array
; ===============================================================================================================================
Func _SourceGetPlayers($socket, $bChallenge)
	UDPSend($socket, $A2S_PLAYER + $bChallenge)
	Local $sRecv = __SourceReceiveData($socket)
	Local $iCount = Int(BinaryMid($sRecv, 6, 1))
	If $iCount = 0 Then Return 0
	Local $sPattern = "nnnnn"
	For $iI = 1 To $iCount
		$sPattern &= "nslf"
	Next
	Return __SourceBinaryToArray($sRecv, $sPattern)
EndFunc   ;==>_SourceGetPlayers

; #FUNCTION# ====================================================================================================================
; Name ..........: _SourceGetRules
; Description ...: Empfängt die Server Rules
; Syntax ........: _SourceGetRules($socket, $bChallenge)
; Parameters ....: $socket              - A string value.
;                  $bChallenge          - A binary value.
; Return values .: Array
; ===============================================================================================================================
Func _SourceGetRules($socket, $bChallenge)
	UDPSend($socket, $A2S_RULES + $bChallenge)
	Local $iCount, $sPattern
	Local $sRecv = __SourceReceiveData($socket)
	If StringLeft($sRecv, 4) = "0xFE" Then
		$iCount = Int(BinaryMid($sRecv, 18, 1))
		$sPattern = "nnnnnnnnnnnnnnnnnn"
	Else
		$iCount = Int(BinaryMid($sRecv, 6, 1))
		$sPattern = "nn"
	EndIf
	For $iI = 1 To $iCount
		$sPattern &= "ss"
	Next
	Return __SourceBinaryToArray($sRecv, $sPattern)
EndFunc   ;==>_SourceGetRules

; #FUNCTION# ====================================================================================================================
; Name ..........: __SourceReceiveData
; Description ...: Empfängt ein oder mehrere Pakete und gibt diese als String aus
; Syntax ........: __SourceReceiveData($socket[, $iTimeOut = 2000])
; Parameters ....: $socket              - A string value.
;                  $iTimeOut            - [optional] An integer value. Default is 2000.
; Return values .: On Success - Returns String
;                  Timeout - -1
; ===============================================================================================================================
Func __SourceReceiveData($socket, $iTimeOut = 2000)
	Local $timer = TimerInit()
	Do
		Local $sString = Binary(UDPRecv($socket, 2048))
		If $sString <> '' Then
			$g_PING = Round(TimerDiff($timer), 2) & " ms"
			If BinaryMid($sString, 1, 4) = "0xFEFFFFFF" Then ; Falls das Paket in mehrere Parts gesplittet ist
				Local $iPacketCount = Int(BinaryMid($sString, 9, 1))
				For $iCurrentPacket = 1 To $iPacketCount ; Alle Pakete zusammenfügen
					$sChunk = UDPRecv($socket, 2048)
					If $sChunk <> '' Then $sString &= BinaryMid(Binary($sChunk), 0xD)
					If TimerDiff($timer) > $iTimeOut Then Return -1
				Next
				Return $sString
			Else
				Return $sString
			EndIf
		EndIf
		Sleep(10)
	Until TimerDiff($timer) > $iTimeOut
	Return -1
EndFunc   ;==>__SourceReceiveData

; #FUNCTION# ====================================================================================================================
; Name ..........: __SourceBinaryToArray
; Description ...: Splittet einen BinaryString nach einem bestimmten Pattern
; Syntax ........: __SourceBinaryToArray($sString, $sPattern)
; Parameters ....: $sString             - A string value.
;                  $sPattern            - Zeichenfolge
;								i = 1 Byte als Ganzzahl
;								h = 1 Byte in Hexschreibweise
;								c = 1 Byte als Char
;								l = long (32bit signed BigEndian)
;								f = float (32bit BigEndian)
;								s = Null-terminated String
;								n = Null Byte (ein Byte überspringen)
; Return values .: None
; Author ........: Your Name
; Modified ......:
; Remarks .......:
; Related .......:
; Link ..........:
; Example .......: No
; ===============================================================================================================================
Func __SourceBinaryToArray($sString, $sPattern)
	Local $iByte = 3, $aBytes = StringSplit(StringTrimLeft($sString, 2), ""), $aPattern = StringSplit($sPattern, ""), $aResult[1]
	For $iType = 1 To $aPattern[0]
		Switch $aPattern[$iType]
			Case "i"
				_ArrayAdd($aResult, Int("0x" & $aBytes[$iByte] & $aBytes[$iByte + 1]))
				$iByte += 2
			Case "h"
				_ArrayAdd($aResult, "0x" & $aBytes[$iByte] & $aBytes[$iByte + 1])
				$iByte += 2
			Case "c"
				_ArrayAdd($aResult, Chr("0x" & $aBytes[$iByte] & $aBytes[$iByte + 1]))
				$iByte += 2
			Case "l"
				Local $iLong = 0
				For $iI = 6 To 0 Step -2
					$iLong &= $aBytes[$iByte + $iI] & $aBytes[$iByte + $iI + 1]
				Next
				_ArrayAdd($aResult, Dec($iLong))
				$iByte += 8
			Case "f"
				Local $iFloat = ""
				For $iI = 6 To 0 Step -2
					$iFloat &= $aBytes[$iByte + $iI] & $aBytes[$iByte + $iI + 1]
				Next
				Local $tInt = DllStructCreate("int")
				Local $tFloat = DllStructCreate("float", DllStructGetPtr($tInt))
				DllStructSetData($tInt, 1, Dec($iFloat))
				_ArrayAdd($aResult, DllStructGetData($tFloat, 1))
				$iByte += 8
			Case "s"
				Local $sChunk = "0x"
				Local $sCurrentChar = $aBytes[$iByte] & $aBytes[$iByte + 1]
				While $sCurrentChar <> "00"
					$sChunk &= $sCurrentChar
					$iByte += 2
					$sCurrentChar = $aBytes[$iByte] & $aBytes[$iByte + 1]
				WEnd
				_ArrayAdd($aResult, BinaryToString($sChunk, 4))
				$iByte += 2
			Case "n"
				$iByte += 2
		EndSwitch
	Next
	$aResult[0] = UBound($aResult) - 1
	Return $aResult
EndFunc   ;==>__SourceBinaryToArray

; #FUNCTION# ====================================================================================================================
; Name ..........: _SourceClose
; Description ...: Schließt die offene UDP-Verbindung
; Syntax ........: _SourceClose()
; ===============================================================================================================================
Func _SourceClose($socket)
	UDPCloseSocket($socket)
	UDPShutdown()
EndFunc   ;==>_SourceClose
