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