Hi AutoIt.de,
vor ein paar Monaten wollte ich ein Programm zum Überwachen eines Source Servers schreiben.
Mir fehlte allerdings viel Erfahrung in (Netzwerk-) Programmierung und deswegen hab ich nach etwas gesucht, das die Informationen aus ihm rausholen kann.
Viele Beispiele waren entweder in PHP, VB.net, Java, Ruby oder C++ vorhanden. Lösungen, die es für AutoIt gab, konnten mich nicht zufriedenstellen und so hab ich das Query Protokoll studiert und meine eigene erstellt!
Spoiler anzeigen
#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
Alles anzeigen
Download: SrcDSQLib.au3 - 1.5
Damit lassen sich von jedem Source Server die wichtigsten Daten auslesen wie
Ping, Servername, Map, Spiel, System, Tags, Spieler, ihre Punkte und Verbindungszeit, Regeln wie "sv_cheats 1" etc.
-
- Unterstützt geteilte Packete [Blockierte Grafik: http://stuff.aix0.eu/Pics/famfamfam/icons/tick.png]
- Unterstützt alle Source-Versionen (2006, 2007, 2009) [Blockierte Grafik: http://stuff.aix0.eu/Pics/famfamfam/icons/tick.png]
- Unterstützt GoldSrc (TFC, HL: DS) [Blockierte Grafik: http://stuff.aix0.eu/Pics/famfamfam/icons/tick.png]
- Master Server Query [Blockierte Grafik: http://stuff.aix0.eu/Pics/famfamfam/icons/tick.png]
- RCon Support [Blockierte Grafik: http://stuff.aix0.eu/Pics/famfamfam/icons/tick.png]
- Alles Nötige im typischen UDF-Stil dokumentiert [Blockierte Grafik: http://stuff.aix0.eu/Pics/famfamfam/icons/tick.png]
Wer also schon immer einen eigenen HLSW Klon schreiben wollte braucht jetzt nur noch 'ne GUI.
Update 1.5
[Blockierte Grafik: http://stuff.aix0.eu/Pics/famfamfam/icons/add.png] RCon-Funktionen hinzugefügt
[Blockierte Grafik: http://stuff.aix0.eu/Pics/famfamfam/icons/add.png] MasterServerQuery-Funktion hinzugefügt
[Blockierte Grafik: http://stuff.aix0.eu/Pics/famfamfam…fresh_small.png] Funktionen einheitlicher gemacht
[Blockierte Grafik: http://stuff.aix0.eu/Pics/famfamfam…fresh_small.png] Flexibilität der Nutzung erhöht
[Blockierte Grafik: http://stuff.aix0.eu/Pics/famfamfam/icons/arrow_right.png] Performance verbessert
Bugs/Vorschläge bitte hier oder als Kommentar auf meinem Blog (Link unten) melden.
Offizielle UDF-Seite
PS: Das ist meine erste UDF