rcon tool (unfinished)

Share your advanced PureBasic knowledge/code with the community.
Nituvious
Addict
Addict
Posts: 1029
Joined: Sat Jul 11, 2009 4:57 am
Location: United States

rcon tool (unfinished)

Post by Nituvious »

Here is an unfinished rcon tool that I began to write. I don't think I will ever finish it so I figured if I released it, someone might find it interesting to look at if they were writing their own rcon tool.
I am releasing it as it is so maybe someone can learn from it if they are attempting to right their own.
Please excuse the bad code. I was going to eventually go over it once it "all worked".

Code: Select all

;-------------------------------------------------------
;	/*
;	name: rcon tool
;	date: jul/17/2010
;	created by: Nituvious
;	reason: learning experience
;	purpose: utilize rcon commands via
;	packets.
;	*/
;-------------------------------------------------------

Enumeration
	#Window_1
	#Button_Refresh_1
	#Button_Send_1
	#Button_Kick_1
	#Button_Ban_1
	#Button_ForceTeam_s_1
	#Button_ForceTeam_r_1
	#Button_ForceTeam_b_1
	#MenuBar_1
	#MenuItem_Refresh_1
	#MenuItem_Open_1
	#MenuItem_Save_1
	#MenuItem_Exit_1
	#MenuItem_Help_1
	#MenuItem_Credits_1
	#Status_1
	#StringSend_1
	#RconPassword_1
	#ServerIP_1
	#ServerPort_1
	#ServerList_1
EndEnumeration

Global wTitle$="RconTool",sv_HostName$="no server",mapname$="Map: none", SendString$,RconPassword$,ServerIP$,ServerPort$
Global PlayerName$="no player",ping.l=999,score.l=-1,playerPort.l=29070,playerRate.l=25000,playerSlot.l=0,playerIP$="127.0.0.1"
Global iwWidth.l=500,iwHeight.l=350,PlayersConnected.l=1,sv_MaxClients.l =0,sv_MaxClients$,ServerPort.l,fraglimit.l = 1000, timelimit.l = -1,EventID,Connected = 0
Global StatusLines.l=0,GetStatus = 0,NetworkEventID,GetNext=0,GetElapsedTime=ElapsedMilliseconds() + 750

Global *Q3Protocol_Status_Buffer1 = AllocateMemory(1000)
;Global *Q3Protocol_MaxClients_Buffer1 = AllocateMemory(50)
Declare GetServerStatus()
If InitNetwork() = 0 : MessageRequester("Error","Network failure!") : EndIf

OpenWindow(#Window_1,0,0,iwWidth.l,iwHeight.l,wTitle$,#PB_Window_ScreenCentered|#PB_Window_MaximizeGadget|#PB_Window_SizeGadget)
; -- Buttons that simplify things
ButtonGadget(#Button_Refresh_1,0,0,0,0,"Refresh")
ButtonGadget(#Button_Send_1,0,0,0,0,"Send")

; -- I always wanted to have a cheesy menu in a program :-)
CreateMenu(#MenuBar_1,WindowID(#Window_1))
MenuTitle("File") : MenuItem(#MenuItem_Refresh_1,"Refresh") : MenuItem(#MenuItem_Open_1,"Open") : MenuItem(#MenuItem_Save_1,"Save") : MenuItem(#MenuItem_Exit_1,"Exit")
MenuTitle("About") : MenuItem(#MenuItem_Help_1,"Help") : MenuItem(#MenuItem_Credits_1,"Support")

 ; --  We will use this to display extra information about the server such as name, maxclients and map information
CreateStatusBar(#Status_1,WindowID(#Window_1)) : AddStatusBarField(40) :  AddStatusBarField(100) : AddStatusBarField(#PB_Ignore)

StringGadget(#ServerIP_1,0,0,0,0,"127.0.0.1") : StringGadget(#ServerPort_1,0,0,0,0,"2110")	; -- Server ip and port field
StringGadget(#StringSend_1,0,0,0,0,"")	; -- This will provide the user with an option to interact with the server
StringGadget(#RconPassword_1,0,0,0,0,"pewpz",#PB_String_Password)	; -- Input for the rcon password

; -- A place to display server information such as player names, their client slot, their score and their ping.
ListIconGadget(#ServerList_1,0,0,0,0,"Slot",30,#PB_ListIcon_FullRowSelect|#PB_ListIcon_AlwaysShowSelection)
AddGadgetColumn(#ServerList_1,1,"Name",130) : AddGadgetColumn(#ServerList_1,2,"Score",40) : AddGadgetColumn(#ServerList_1,3,"Ping",33) 
AddGadgetColumn(#ServerList_1,4,"IP Address",95) : AddGadgetColumn(#ServerList_1,5,"Port",50) : AddGadgetColumn(#ServerList_1,6,"Rate",50)
AddGadgetItem(#ServerList_1,0,Str(playerSlot.l)+Chr(10)+PlayerName$+Chr(10)+Str(score.l)+Chr(10)+Str(ping.l)+Chr(10)+playerIP$+Chr(10)+Str(playerPort.l)+Chr(10)+Str(playerRate.l))

Procedure GetUpdatedWindow()
	SmartWindowRefresh(#Window_1,1)
	; -- We'll use the GetUpdatedWindow function to also store any interactive information
	SendString$ = GetGadgetText(#StringSend_1)
	ServerIP$ = GetGadgetText(#ServerIP_1) : ServerPort$ = GetGadgetText(#ServerPort_1)
	RconPassword$ = GetGadgetText(#RconPassword_1)
	iwWidth.l = WindowWidth(#Window_1) : iwHeight.l = WindowHeight(#Window_1) 
	If EventID = #PB_Event_SizeWindow
		ResizeGadget(#ServerList_1,1,1,iwWidth-132,iwHeight-68)	; -- Reposition the server info list to match the window	
		
		ResizeGadget(#ServerIP_1,iwWidth.l-130,1,88,20) : ResizeGadget(#ServerPort_1,iwWidth.l-41,1,40,20)	; -- Reposition the IP and Port gadget to match the window
		ResizeGadget(#StringSend_1,1,iwHeight.l-65,iwWidth.l-57,20)	; -- Reposition the sendstring gadget to match the window
		ResizeGadget(#RconPassword_1,iwWidth.l-129,21,128,20)		; -- Reposition the rconpassword gadget to match the window
		ResizeGadget(#Button_Refresh_1,iwWidth.l-129,41,128,30)
		ResizeGadget(#Button_Send_1,iwWidth.l-54,iwHeight.l-66,53,22)
	EndIf	
	; -- Update the extra information
	StatusBarText(#Status_1,0,"offline") : StatusBarText(#Status_1,1,sv_HostName$) : StatusBarText(#Status_1,2,"("+mapname$+") (Fraglimit: "+Str(fraglimit.l)+") (Timelimit: "+Str(timelimit.l)+") ("+Str(PlayersConnected.l)+" of "+Str(sv_MaxClients.l)+" connected)")
EndProcedure

Procedure GetWindowEvents()
	If EventID = #PB_Event_Gadget
		Select EventGadget()
			Case #Button_Refresh_1
				GetStatus = 1
		EndSelect
	EndIf
	If EventID = #PB_Event_Menu
		Select EventMenu()
			Case #MenuItem_Refresh_1
				Debug "refreshing..."
				GetStatus = 1
			Case #MenuItem_Open_1
				Debug "Open"
			Case #MenuItem_Save_1
				Debug "Save"
			Case #MenuItem_Exit_1
				Debug "Exit"
				End
			Case #MenuItem_Help_1
				Debug "Help"
			Case #MenuItem_Credits_1
				Debug "Support"
		EndSelect
	EndIf
EndProcedure

Procedure GetServerStatus()
	;GetStatus = 0
	If GetStatus = 1
		;GetElapsedTime = ElapsedMilliseconds() + 500
		ConnectionID = OpenNetworkConnection(ServerIP$,Val(ServerPort$),#PB_Network_UDP)
		If ConnectionID
			; -- This really ugly code just makes sure the For loop waits for each package to be responded to.
			; -- It will keep "missed" responses to a minimum, I hope. Should find a better way of doing this!
			
			If GetElapsedTime < ElapsedMilliseconds()
			WaitWindowEvent(10)
			If GetNext = 0 ; -- Start...
				SendNetworkString(ConnectionID,"ÿÿÿÿrcon "+RconPassword$+" sv_maxclients ")
				;GetNext = 1
			ElseIf GetNext = 1
				SendNetworkString(ConnectionID,"ÿÿÿÿrcon "+RconPassword$+" mapname ")
				;GetNext = 2
			ElseIf GetNext = 2
				SendNetworkString(ConnectionID,"ÿÿÿÿrcon "+RconPassword$+" sv_hostname ")
				;GetNext = 3
			ElseIf GetNext = 3
				SendNetworkString(ConnectionID,"ÿÿÿÿrcon "+RconPassword$+" fraglimit ")
				;GetNext = 4
			ElseIf GetNext = 4
				SendNetworkString(ConnectionID,"ÿÿÿÿrcon "+RconPassword$+" timelimit ")
				;GetNext = 5
			ElseIf GetNext = 5
				;GetStatus = 0
				SendNetworkString(ConnectionID,"ÿÿÿÿrcon "+RconPassword$+" status ")
				GetNext = 0 ; -- End... The last request we send to the server should always contain this
			EndIf
			GetElapsedTime = ElapsedMilliseconds() + 500
			EndIf
			For StatusLines = 0 To 4+sv_MaxClients ; -- The reason I am doing it like this is because when we send the Status request, the server will respond with
			; -- 4 or 5 additional lines of useless information and we don't want to miss any possible clients so we can ignore the last few "extra" lines if they're duplicates.
				WaitWindowEvent(10)
				NetworkEventID = NetworkClientEvent(ConnectionID)
				If NetworkEventID
					Select NetworkEventID
						Case #PB_NetworkEvent_Data
							ReceiveNetworkData(ConnectionID,*Q3Protocol_Status_Buffer1,1000)
							Q3Protocol_Status_Buffer$ = PeekS(*Q3Protocol_Status_Buffer1)
							Debug Q3Protocol_Status_Buffer$
							; -- A better(and quicker) solution for truncating a response package might be to count up to the 
							; -- reoccuring string, remove all before it, and then reverse and count up to that point.
							; -- e.g. "sv_maxclients" is:"32^7" default:"8^7" 20 characters until we reach "3", remove them then
							; -- reverse it and count 17 characters until we reach "2". We know the correct positions will be at the " and ^.
							; ÿÿÿÿprint "sv_hostname" is:"ETHost^7" Default:"ETHost^7"~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
							If FindString(Q3Protocol_Status_Buffer$,"ÿÿÿÿprint"+Chr(10)+Chr(34)+"sv_maxclients"+Chr(34)+" is:"+Chr(34),0) ; -- Check for the response "sv_maxclients"
								truncate1_maxclients$ = ReplaceString(Q3Protocol_Status_Buffer$,"ÿÿÿÿprint"+Chr(10)+Chr(34)+"sv_maxclients"+Chr(34)+" is:"+Chr(34),"")
								;truncate2_maxclients$ = ReplaceString(truncate1_maxclients$,"^7"+Chr(34)+" default:"+Chr(34)+"20^7"+Chr(34),"") ; -- Enemy Territory default sv_maxclients
								truncate2_maxclients$ = ReplaceString(truncate1_maxclients$,"^7"+Chr(34)+" default:"+Chr(34)+"8^7"+Chr(34),"")
								sv_MaxClients$ = ReplaceString(truncate2_maxclients$,"~","")
								sv_MaxClients = Val(sv_MaxClients$) ; -- Finally, sv_MaxClients. We will use this later to tell our For loop to return every connected client
								GetNext = 1
							ElseIf FindString(Q3Protocol_Status_Buffer$,"ÿÿÿÿprint"+Chr(10)+Chr(34)+"mapname"+Chr(34)+" is:"+Chr(34),0) ; -- Check for the response to "mapname"
								truncate1_name_map$ = ReplaceString(Q3Protocol_Status_Buffer$,"ÿÿÿÿprint"+Chr(10)+Chr(34)+"mapname"+Chr(34)+" is:"+Chr(34),"")
								truncate2_name_map$ = ReplaceString(truncate1_name_map$,"^7"+Chr(34)+" default:"+Chr(34)+"nomap^7"+Chr(34),"")
								mapname$ = "Map: "+ReplaceString(truncate2_name_map$,"~","") ; -- Add the "Map: " string for asthetic purposes
								GetNext = 2
							ElseIf FindString(Q3Protocol_Status_Buffer$,"ÿÿÿÿprint"+Chr(10)+Chr(34)+"sv_hostname"+Chr(34)+" is:"+Chr(34),0) ; -- Check for the response to "sv_hostname"
								truncate1_hostname$ = ReplaceString(Q3Protocol_Status_Buffer$,"ÿÿÿÿprint"+Chr(10)+Chr(34)+"sv_hostname"+Chr(34)+" is:"+Chr(34),"")
								;truncate2_hostname$ = ReplaceString(truncate1_hostname$,"^7"+Chr(34)+" default:"+Chr(34)+"ETHost^7"+Chr(34),"") ; -- Enemy Territory default name
								truncate2_hostname$ = ReplaceString(truncate1_hostname$,"^7"+Chr(34)+" default:"+Chr(34)+"*Jedi*^7"+Chr(34),"")
								; -- Below removes all occurances of Numerical Color codes. Will need to add alphabetic color codes at some point...
								; -- This is only used so we don't get really large and useless names. Will be useful for player names too.
								truncate3_hostname$ = ReplaceString(truncate2_hostname$,"^0","") : truncate4_hostname$ = ReplaceString(truncate3_hostname$,"^1","")
								truncate5_hostname$ = ReplaceString(truncate4_hostname$,"^2","") : truncate6_hostname$ = ReplaceString(truncate5_hostname$,"^3","")
								truncate7_hostname$ = ReplaceString(truncate6_hostname$,"^4","") : truncate8_hostname$ = ReplaceString(truncate7_hostname$,"^5","")
								truncate9_hostname$ = ReplaceString(truncate8_hostname$,"^6","") : truncate10_hostname$ = ReplaceString(truncate9_hostname$,"^7","")
								truncate11_hostname$ = ReplaceString(truncate10_hostname$,"^8","") : truncate12_hostname$ = ReplaceString(truncate11_hostname$,"^9","")
								sv_HostName$ = ReplaceString(truncate12_hostname$,"~","") ; -- And finally, display the servers name.
								GetNext = 3
							ElseIf FindString(Q3Protocol_Status_Buffer$,"ÿÿÿÿprint"+Chr(10)+Chr(34)+"fraglimit"+Chr(34)+" is:"+Chr(34),0) ; -- Fraglimit, just copy/paste from above code :(
								truncate1_fraglimit$ = ReplaceString(Q3Protocol_Status_Buffer$,"ÿÿÿÿprint"+Chr(10)+Chr(34)+"fraglimit"+Chr(34)+" is:"+Chr(34),"")
								truncate2_fraglimit$ = ReplaceString(truncate1_fraglimit$,"^7"+Chr(34)+" default:"+Chr(34)+"20^7"+Chr(34),"")
								fraglimit$ = ReplaceString(truncate2_fraglimit$,"~","")
								fraglimit = Val(fraglimit$) ; -- TODO: Remove this conversaion code, when it is needed we can just val(fraglimit$) directly instead of adding an extra line
								GetNext = 4
							ElseIf FindString(Q3Protocol_Status_Buffer$,"ÿÿÿÿprint"+Chr(10)+Chr(34)+"timelimit"+Chr(34)+" is:"+Chr(34),0)
								truncate1_timelimit$ = ReplaceString(Q3Protocol_Status_Buffer$,"ÿÿÿÿprint"+Chr(10)+Chr(34)+"timelimit"+Chr(34)+" is:"+Chr(34),"")
								truncate2_timelimit$ = ReplaceString(truncate1_timelimit$,"^7"+Chr(34)+" default:"+Chr(34)+"0^7"+Chr(34),"")
								timelimit$ = ReplaceString(truncate2_timelimit$,"~","")
								timelimit = Val(timelimit$)	; -- See Fraglimit code
								GetNext = 5
							ElseIf FindString(Q3Protocol_Status_Buffer$,"ÿÿÿÿprint"+Chr(10)+"--- ----- ---- --------------- ------- --------------------- ----- -----",0)
								; -- The "status" response will always be 72 characters in length. I am not counting the yyyyprint + chr(10) information.
								; -- TODO - become less lazy and finish this.
								; -- TOTODO - remind self after self becomes less lazy to fix the above code so we're not reusing the same thing over and over again. 
								; -- IT'S A BAD THING!! It can be simplied since we're reusing the same thing over and over again! It may also make the procedure reusable for different
								; -- game protocols maybe?
								GetStatus = 0
							EndIf
					EndSelect
				EndIf
			Next	
			FillMemory(*Q3Protocol_Status_Buffer1,700,$7E) ; -- Fill the buffer with ~ to make it easier for us to truncate, should probably put this at the beginning of the procedure.
		EndIf
			CloseNetworkConnection(ConnectionID)	; -- Close the connection
			StatusLines = 0										; -- And reset StatusLines
	EndIf
EndProcedure

Repeat : Delay(5) 
	EventID = WaitWindowEvent(10)
	GetWindowEvents()
	GetUpdatedWindow()
	GetServerStatus()
Until EventID = #PB_Event_CloseWindow
▓▓▓▓▓▒▒▒▒▒░░░░░