From 0a1a8d5a297774f03e7c9dcaf697d40a56b9002e Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Mon, 2 Oct 2017 10:09:00 -0400 Subject: [PATCH] Adds player counting --- Config.dm | 1 + DMAPI/server_tools.dm | 249 ++++---- DMAPI/st_interface.dm | 2 + TGCommandLine/DDCommands.cs | 625 ++++++++++---------- TGCommandLine/Program.cs | 440 +++++++------- TGControlPanel/Main.cs | 90 +-- TGControlPanel/ServerPage.cs | 951 +++++++++++++++--------------- TGServerService/Interop.cs | 479 +++++++-------- TGServiceInterface/DreamDaemon.cs | 371 ++++++------ Test.dm | 10 + 10 files changed, 1630 insertions(+), 1588 deletions(-) diff --git a/Config.dm b/Config.dm index 6ea3f64cc3..2a65e3b036 100644 --- a/Config.dm +++ b/Config.dm @@ -5,3 +5,4 @@ #define SERVER_TOOLS_WORLD_ANNOUNCE(message) world << ##message #define SERVER_TOOLS_LOG(message) world.log << ##message #define SERVER_TOOLS_NOTIFY_ADMINS(event) message_admins(event) +#define SERVER_TOOLS_CLIENT_COUNT clients.len \ No newline at end of file diff --git a/DMAPI/server_tools.dm b/DMAPI/server_tools.dm index 6640ff13df..6f40d9827a 100644 --- a/DMAPI/server_tools.dm +++ b/DMAPI/server_tools.dm @@ -1,123 +1,126 @@ -// /tg/station 13 server tools API v3.1 - -//CONFIGURATION -//use this define if you want to do configuration outside of this file -#ifndef SERVER_TOOLS_EXTERNAL_CONFIGURATION -//Comment this out once you've filled in the below -#error /tg/station server tools interface unconfigured - -//Required interfaces (fill in with your codebase equivalent): - -//create a global variable named `Name` and set it to `Value` -//These globals must not be modifiable from anywhere outside of the server tools -#define SERVER_TOOLS_DEFINE_AND_SET_GLOBAL(Name, Value) -//Read the value in the global variable `Name` -#define SERVER_TOOLS_READ_GLOBAL(Name) -//Set the value in the global variable `Name` to `Value` -#define SERVER_TOOLS_WRITE_GLOBAL(Name, Value) -//display an announcement `message` from the server to all players -#define SERVER_TOOLS_WORLD_ANNOUNCE(message) -//Write a string `message` to a server log -#define SERVER_TOOLS_LOG(message) -//Notify current in-game administrators of a string `event` -#define SERVER_TOOLS_NOTIFY_ADMINS(event) -#endif - -//Required hooks: - -//Put this somewhere in /world/New() that is always run -#define SERVER_TOOLS_ON_NEW ServiceInit() -//Put this somewhere in /world/Topic(T, Addr, Master, Keys) that is always run before T is modified -#define SERVER_TOOLS_ON_TOPIC var/service_topic_return = ServiceCommand(params2list(T)); if(service_topic_return) return service_topic_return -//Put at the beginning of world/Reboot(reason) -#define SERVER_TOOLS_ON_REBOOT ServiceReboot() - -//Optional callable functions: - -//Returns the string version of the API -#define SERVER_TOOLS_API_VERSION ServiceAPIVersion() -//Returns TRUE if the world was launched under the server tools and the API matches, FALSE otherwise -//No function below this succeed if this is FALSE -#define SERVER_TOOLS_PRESENT RunningService() -//Gets the current version of the service running the server -#define SERVER_TOOLS_VERSION ServiceVersion() -//Forces a hard reboot of BYOND by ending the process -//unlike del(world) clients will try to reconnect -//If the service has not requested a shutdown, the world will reboot shortly after -#define SERVER_TOOLS_REBOOT_BYOND world.ServiceEndProcess() -/* - Gets the list of any testmerged github pull requests - - "[PR Number]" => list( - "title" -> PR title - "commit" -> Full hash of commit merged - "author" -> Github username of the author of the PR - ) -*/ -#define SERVER_TOOLS_PR_LIST GetTestMerges() -//Sends a message to connected game chats -#define SERVER_TOOLS_CHAT_BROADCAST(message) world.ChatBroadcast(message) -//Sends a message to connected admin chats -#define SERVER_TOOLS_RELAY_BROADCAST(message) world.AdminBroadcast(message) - -//IMPLEMENTATION - -#define SERVICE_API_VERSION_STRING "3.1.0.0" - -#define REBOOT_MODE_NORMAL 0 -#define REBOOT_MODE_HARD 1 -#define REBOOT_MODE_SHUTDOWN 2 - -#define SERVICE_WORLD_PARAM "server_service" -#define SERVICE_VERSION_PARAM "server_service_version" -#define SERVICE_PR_TEST_JSON "prtestjob.json" -#define SERVICE_INTERFACE_DLL "TGServiceInterface.dll" -#define SERVICE_INTERFACE_FUNCTION "DDEntryPoint" - -#define SERVICE_CMD_HARD_REBOOT "hard_reboot" -#define SERVICE_CMD_GRACEFUL_SHUTDOWN "graceful_shutdown" -#define SERVICE_CMD_WORLD_ANNOUNCE "world_announce" -#define SERVICE_CMD_LIST_CUSTOM "list_custom_commands" - -#define SERVICE_CMD_PARAM_KEY "serviceCommsKey" -#define SERVICE_CMD_PARAM_COMMAND "command" -#define SERVICE_CMD_PARAM_SENDER "sender" -#define SERVICE_CMD_PARAM_CUSTOM "custom" -#define SERVICE_CMD_API_COMPATIBLE "api_compat" - -#define SERVICE_JSON_PARAM_HELPTEXT "help_text" -#define SERVICE_JSON_PARAM_ADMINONLY "admin_only" -#define SERVICE_JSON_PARAM_REQUIREDPARAMETERS "required_parameters" - -#define SERVICE_REQUEST_KILL_PROCESS "killme" -#define SERVICE_REQUEST_IRC_BROADCAST "irc" -#define SERVICE_REQUEST_IRC_ADMIN_CHANNEL_MESSAGE "send2irc" -#define SERVICE_REQUEST_WORLD_REBOOT "worldreboot" -#define SERVICE_REQUEST_API_VERSION "api_ver" - -/* -The MIT License - -Copyright (c) 2011 Dominic Tarr - -Permission is hereby granted, free of charge, -to any person obtaining a copy of this software and -associated documentation files (the "Software"), to -deal in the Software without restriction, including -without limitation the rights to use, copy, modify, -merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom -the Software is furnished to do so, -subject to the following conditions: - -The above copyright notice and this permission notice -shall be included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES -OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. -IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR -ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, -TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE -SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -*/ +// /tg/station 13 server tools API v3.1.0.1 + +//CONFIGURATION +//use this define if you want to do configuration outside of this file +#ifndef SERVER_TOOLS_EXTERNAL_CONFIGURATION +//Comment this out once you've filled in the below +#error /tg/station server tools interface unconfigured + +//Required interfaces (fill in with your codebase equivalent): + +//create a global variable named `Name` and set it to `Value` +//These globals must not be modifiable from anywhere outside of the server tools +#define SERVER_TOOLS_DEFINE_AND_SET_GLOBAL(Name, Value) +//Read the value in the global variable `Name` +#define SERVER_TOOLS_READ_GLOBAL(Name) +//Set the value in the global variable `Name` to `Value` +#define SERVER_TOOLS_WRITE_GLOBAL(Name, Value) +//display an announcement `message` from the server to all players +#define SERVER_TOOLS_WORLD_ANNOUNCE(message) +//Write a string `message` to a server log +#define SERVER_TOOLS_LOG(message) +//Notify current in-game administrators of a string `event` +#define SERVER_TOOLS_NOTIFY_ADMINS(event) +//The current amount of connected clients +#define SERVER_TOOLS_CLIENT_COUNT +#endif + +//Required hooks: + +//Put this somewhere in /world/New() that is always run +#define SERVER_TOOLS_ON_NEW ServiceInit() +//Put this somewhere in /world/Topic(T, Addr, Master, Keys) that is always run before T is modified +#define SERVER_TOOLS_ON_TOPIC var/service_topic_return = ServiceCommand(params2list(T)); if(service_topic_return) return service_topic_return +//Put at the beginning of world/Reboot(reason) +#define SERVER_TOOLS_ON_REBOOT ServiceReboot() + +//Optional callable functions: + +//Returns the string version of the API +#define SERVER_TOOLS_API_VERSION ServiceAPIVersion() +//Returns TRUE if the world was launched under the server tools and the API matches, FALSE otherwise +//No function below this succeed if this is FALSE +#define SERVER_TOOLS_PRESENT RunningService() +//Gets the current version of the service running the server +#define SERVER_TOOLS_VERSION ServiceVersion() +//Forces a hard reboot of BYOND by ending the process +//unlike del(world) clients will try to reconnect +//If the service has not requested a shutdown, the world will reboot shortly after +#define SERVER_TOOLS_REBOOT_BYOND world.ServiceEndProcess() +/* + Gets the list of any testmerged github pull requests + + "[PR Number]" => list( + "title" -> PR title + "commit" -> Full hash of commit merged + "author" -> Github username of the author of the PR + ) +*/ +#define SERVER_TOOLS_PR_LIST GetTestMerges() +//Sends a message to connected game chats +#define SERVER_TOOLS_CHAT_BROADCAST(message) world.ChatBroadcast(message) +//Sends a message to connected admin chats +#define SERVER_TOOLS_RELAY_BROADCAST(message) world.AdminBroadcast(message) + +//IMPLEMENTATION + +#define SERVICE_API_VERSION_STRING "3.1.0.1" + +#define REBOOT_MODE_NORMAL 0 +#define REBOOT_MODE_HARD 1 +#define REBOOT_MODE_SHUTDOWN 2 + +#define SERVICE_WORLD_PARAM "server_service" +#define SERVICE_VERSION_PARAM "server_service_version" +#define SERVICE_PR_TEST_JSON "prtestjob.json" +#define SERVICE_INTERFACE_DLL "TGServiceInterface.dll" +#define SERVICE_INTERFACE_FUNCTION "DDEntryPoint" + +#define SERVICE_CMD_HARD_REBOOT "hard_reboot" +#define SERVICE_CMD_GRACEFUL_SHUTDOWN "graceful_shutdown" +#define SERVICE_CMD_WORLD_ANNOUNCE "world_announce" +#define SERVICE_CMD_LIST_CUSTOM "list_custom_commands" +#define SERVICE_CMD_API_COMPATIBLE "api_compat" +#define SERVICE_CMD_PLAYER_COUNT "client_count" + +#define SERVICE_CMD_PARAM_KEY "serviceCommsKey" +#define SERVICE_CMD_PARAM_COMMAND "command" +#define SERVICE_CMD_PARAM_SENDER "sender" +#define SERVICE_CMD_PARAM_CUSTOM "custom" + +#define SERVICE_JSON_PARAM_HELPTEXT "help_text" +#define SERVICE_JSON_PARAM_ADMINONLY "admin_only" +#define SERVICE_JSON_PARAM_REQUIREDPARAMETERS "required_parameters" + +#define SERVICE_REQUEST_KILL_PROCESS "killme" +#define SERVICE_REQUEST_IRC_BROADCAST "irc" +#define SERVICE_REQUEST_IRC_ADMIN_CHANNEL_MESSAGE "send2irc" +#define SERVICE_REQUEST_WORLD_REBOOT "worldreboot" +#define SERVICE_REQUEST_API_VERSION "api_ver" + +/* +The MIT License + +Copyright (c) 2011 Dominic Tarr + +Permission is hereby granted, free of charge, +to any person obtaining a copy of this software and +associated documentation files (the "Software"), to +deal in the Software without restriction, including +without limitation the rights to use, copy, modify, +merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom +the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice +shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR +ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ diff --git a/DMAPI/st_interface.dm b/DMAPI/st_interface.dm index 237946a2b3..f1fd8af563 100644 --- a/DMAPI/st_interface.dm +++ b/DMAPI/st_interface.dm @@ -89,6 +89,8 @@ SERVER_TOOLS_DEFINE_AND_SET_GLOBAL(server_tools_api_compatible, FALSE) return "No message set!" SERVER_TOOLS_WORLD_ANNOUNCE(msg) return "SUCCESS" + if(SERVICE_CMD_PLAYER_COUNT) + return "[SERVER_TOOLS_CLIENT_COUNT]" if(SERVICE_CMD_LIST_CUSTOM) return json_encode(ListServiceCustomCommands(FALSE)) else diff --git a/TGCommandLine/DDCommands.cs b/TGCommandLine/DDCommands.cs index 09f6bda7dc..8d263f12f5 100644 --- a/TGCommandLine/DDCommands.cs +++ b/TGCommandLine/DDCommands.cs @@ -1,311 +1,314 @@ -using System; -using System.Collections.Generic; -using TGServiceInterface; - -namespace TGCommandLine -{ - class DDCommand : RootCommand - { - public DDCommand() - { - Keyword = "dd"; - Children = new Command[] { new DDStartCommand(), new DDStopCommand(), new DDRestartCommand(), new DDStatusCommand(), new DDAutostartCommand(), new DDPortCommand(), new DDSecurityCommand(), new DDWorldAnnounceCommand(), new DDWebclientCommand() }; - } - public override string GetHelpText() - { - return "Manage DreamDaemon"; - } - } - - class DDWorldAnnounceCommand : Command - { - public DDWorldAnnounceCommand() - { - Keyword = "announce"; - RequiredParameters = 1; - } - - public override string GetHelpText() - { - return "Sends a message all players on the server"; - } - - public override string GetArgumentString() - { - return ""; - } - - protected override ExitCode Run(IList parameters) - { - var res = Server.GetComponent().WorldAnnounce(String.Join(" ", parameters)); - OutputProc(res ?? "Success!"); - return res == null ? ExitCode.Normal : ExitCode.ServerError; - } - } - - class DDStartCommand : Command - { - public DDStartCommand() - { - Keyword = "start"; - } - - public override string GetHelpText() - { - return "Starts the server and watchdog"; - } - - protected override ExitCode Run(IList parameters) - { - var res = Server.GetComponent().Start(); - OutputProc(res ?? "Success!"); - return res == null ? ExitCode.Normal : ExitCode.ServerError; - } - } - - class DDStopCommand : Command - { - public DDStopCommand() - { - Keyword = "stop"; - } - public override string GetArgumentString() - { - return "[--graceful]"; - } - - public override string GetHelpText() - { - return "Stops the server and watchdog optionally waiting for the current round to end"; - } - - protected override ExitCode Run(IList parameters) - { - var DD = Server.GetComponent(); - if (parameters.Count > 0 && parameters[0].ToLower() == "--graceful") - { - if (DD.DaemonStatus() != TGDreamDaemonStatus.Online) - { - OutputProc("Error: The game is not currently running!"); - return ExitCode.ServerError; - } - DD.RequestStop(); - return ExitCode.Normal; - } - var res = DD.Stop(); - OutputProc(res ?? "Success!"); - return res == null ? ExitCode.Normal : ExitCode.ServerError; - } - } - class DDRestartCommand : Command - { - public DDRestartCommand() - { - Keyword = "restart"; - } - - public override string GetArgumentString() - { - return "[--graceful]"; - } - protected override ExitCode Run(IList parameters) - { - var DD = Server.GetComponent(); - if (parameters.Count > 0 && parameters[0].ToLower() == "--graceful") - { - if (DD.DaemonStatus() != TGDreamDaemonStatus.Online) - { - OutputProc("Error: The game is not currently running!"); - return ExitCode.ServerError; - } - DD.RequestRestart(); - return ExitCode.Normal; - } - var res = DD.Restart(); - OutputProc(res ?? "Success!"); - return res == null ? ExitCode.Normal : ExitCode.ServerError; - } - - public override string GetHelpText() - { - return "Restarts the server and watchdog optionally waiting for the current round to end"; - } - } - class DDStatusCommand : Command - { - public DDStatusCommand() - { - Keyword = "status"; - } - - public override string GetHelpText() - { - return "Gets the current status of the watchdog and server"; - } - - protected override ExitCode Run(IList parameters) - { - var DD = Server.GetComponent(); - OutputProc(DD.StatusString(true)); - if (DD.ShutdownInProgress()) - OutputProc("The server will shutdown once the current round completes."); - return ExitCode.Normal; - } - } - - class DDAutostartCommand : Command - { - public DDAutostartCommand() - { - Keyword = "autostart"; - RequiredParameters = 1; - } - - protected override ExitCode Run(IList parameters) - { - var DD = Server.GetComponent(); - switch (parameters[0].ToLower()) - { - case "on": - DD.SetAutostart(true); - break; - case "off": - DD.SetAutostart(false); - break; - case "check": - OutputProc("Autostart is: " + (DD.Autostart() ? "On" : "Off")); - break; - default: - OutputProc("Invalid parameter: " + parameters[0]); - return ExitCode.BadCommand; - } - return ExitCode.Normal; - } - - public override string GetArgumentString() - { - return ""; - } - public override string GetHelpText() - { - return "Change or check autostarting of the game server with the service"; - } - } - class DDWebclientCommand : Command - { - public DDWebclientCommand() - { - Keyword = "webclient"; - RequiredParameters = 1; - } - - protected override ExitCode Run(IList parameters) - { - var DD = Server.GetComponent(); - switch (parameters[0].ToLower()) - { - case "on": - DD.SetWebclient(true); - break; - case "off": - DD.SetWebclient(false); - break; - case "check": - OutputProc("Webclient is: " + (DD.Webclient() ? "Enabled" : "Disabled")); - break; - default: - OutputProc("Invalid parameter: " + parameters[0]); - return ExitCode.BadCommand; - } - return ExitCode.Normal; - } - - public override string GetArgumentString() - { - return ""; - } - public override string GetHelpText() - { - return "Change or check if the BYOND webclient is enabled for the game server"; - } - } - - class DDPortCommand : Command - { - public DDPortCommand() - { - Keyword = "set-port"; - RequiredParameters = 1; - } - - protected override ExitCode Run(IList parameters) - { - ushort port; - try - { - port = Convert.ToUInt16(parameters[0]); - } - catch - { - OutputProc("Invalid port number!"); - return ExitCode.BadCommand; - } - - Server.GetComponent().SetPort(port); - return ExitCode.Normal; - } - - public override string GetArgumentString() - { - return ""; - } - - public override string GetHelpText() - { - return "Sets the port DreamDaemon will open the server on. Requires a server restart to apply and queues a graceful one up"; - } - } - - class DDSecurityCommand : Command - { - public DDSecurityCommand() - { - Keyword = "set-security"; - RequiredParameters = 1; - } - - protected override ExitCode Run(IList parameters) - { - TGDreamDaemonSecurity sec; - switch (parameters[0].ToLower()) - { - case "safe": - sec = TGDreamDaemonSecurity.Safe; - break; - case "ultra": - case "ultrasafe": - sec = TGDreamDaemonSecurity.Ultrasafe; - break; - case "trust": - case "trusted": - sec = TGDreamDaemonSecurity.Trusted; - break; - default: - OutputProc("Invalid security word!"); - return ExitCode.BadCommand; - } - Server.GetComponent().SetSecurityLevel(sec); - return ExitCode.Normal; - } - - public override string GetArgumentString() - { - return ""; - } - - public override string GetHelpText() - { - return "Sets the visibility option for the DreamDaemon world"; - } - } -} +using System; +using System.Collections.Generic; +using TGServiceInterface; + +namespace TGCommandLine +{ + class DDCommand : RootCommand + { + public DDCommand() + { + Keyword = "dd"; + Children = new Command[] { new DDStartCommand(), new DDStopCommand(), new DDRestartCommand(), new DDStatusCommand(), new DDAutostartCommand(), new DDPortCommand(), new DDSecurityCommand(), new DDWorldAnnounceCommand(), new DDWebclientCommand() }; + } + public override string GetHelpText() + { + return "Manage DreamDaemon"; + } + } + + class DDWorldAnnounceCommand : Command + { + public DDWorldAnnounceCommand() + { + Keyword = "announce"; + RequiredParameters = 1; + } + + public override string GetHelpText() + { + return "Sends a message all players on the server"; + } + + public override string GetArgumentString() + { + return ""; + } + + protected override ExitCode Run(IList parameters) + { + var res = Server.GetComponent().WorldAnnounce(String.Join(" ", parameters)); + OutputProc(res ?? "Success!"); + return res == null ? ExitCode.Normal : ExitCode.ServerError; + } + } + + class DDStartCommand : Command + { + public DDStartCommand() + { + Keyword = "start"; + } + + public override string GetHelpText() + { + return "Starts the server and watchdog"; + } + + protected override ExitCode Run(IList parameters) + { + var res = Server.GetComponent().Start(); + OutputProc(res ?? "Success!"); + return res == null ? ExitCode.Normal : ExitCode.ServerError; + } + } + + class DDStopCommand : Command + { + public DDStopCommand() + { + Keyword = "stop"; + } + public override string GetArgumentString() + { + return "[--graceful]"; + } + + public override string GetHelpText() + { + return "Stops the server and watchdog optionally waiting for the current round to end"; + } + + protected override ExitCode Run(IList parameters) + { + var DD = Server.GetComponent(); + if (parameters.Count > 0 && parameters[0].ToLower() == "--graceful") + { + if (DD.DaemonStatus() != TGDreamDaemonStatus.Online) + { + OutputProc("Error: The game is not currently running!"); + return ExitCode.ServerError; + } + DD.RequestStop(); + return ExitCode.Normal; + } + var res = DD.Stop(); + OutputProc(res ?? "Success!"); + return res == null ? ExitCode.Normal : ExitCode.ServerError; + } + } + class DDRestartCommand : Command + { + public DDRestartCommand() + { + Keyword = "restart"; + } + + public override string GetArgumentString() + { + return "[--graceful]"; + } + protected override ExitCode Run(IList parameters) + { + var DD = Server.GetComponent(); + if (parameters.Count > 0 && parameters[0].ToLower() == "--graceful") + { + if (DD.DaemonStatus() != TGDreamDaemonStatus.Online) + { + OutputProc("Error: The game is not currently running!"); + return ExitCode.ServerError; + } + DD.RequestRestart(); + return ExitCode.Normal; + } + var res = DD.Restart(); + OutputProc(res ?? "Success!"); + return res == null ? ExitCode.Normal : ExitCode.ServerError; + } + + public override string GetHelpText() + { + return "Restarts the server and watchdog optionally waiting for the current round to end"; + } + } + class DDStatusCommand : Command + { + public DDStatusCommand() + { + Keyword = "status"; + } + + public override string GetHelpText() + { + return "Gets the current status of the watchdog and server"; + } + + protected override ExitCode Run(IList parameters) + { + var DD = Server.GetComponent(); + OutputProc(DD.StatusString(true)); + if (DD.ShutdownInProgress()) + OutputProc("The server will shutdown once the current round completes."); + var pc = DD.PlayerCount(); + if (pc != -1) + OutputProc(pc + " connected clients"); + return ExitCode.Normal; + } + } + + class DDAutostartCommand : Command + { + public DDAutostartCommand() + { + Keyword = "autostart"; + RequiredParameters = 1; + } + + protected override ExitCode Run(IList parameters) + { + var DD = Server.GetComponent(); + switch (parameters[0].ToLower()) + { + case "on": + DD.SetAutostart(true); + break; + case "off": + DD.SetAutostart(false); + break; + case "check": + OutputProc("Autostart is: " + (DD.Autostart() ? "On" : "Off")); + break; + default: + OutputProc("Invalid parameter: " + parameters[0]); + return ExitCode.BadCommand; + } + return ExitCode.Normal; + } + + public override string GetArgumentString() + { + return ""; + } + public override string GetHelpText() + { + return "Change or check autostarting of the game server with the service"; + } + } + class DDWebclientCommand : Command + { + public DDWebclientCommand() + { + Keyword = "webclient"; + RequiredParameters = 1; + } + + protected override ExitCode Run(IList parameters) + { + var DD = Server.GetComponent(); + switch (parameters[0].ToLower()) + { + case "on": + DD.SetWebclient(true); + break; + case "off": + DD.SetWebclient(false); + break; + case "check": + OutputProc("Webclient is: " + (DD.Webclient() ? "Enabled" : "Disabled")); + break; + default: + OutputProc("Invalid parameter: " + parameters[0]); + return ExitCode.BadCommand; + } + return ExitCode.Normal; + } + + public override string GetArgumentString() + { + return ""; + } + public override string GetHelpText() + { + return "Change or check if the BYOND webclient is enabled for the game server"; + } + } + + class DDPortCommand : Command + { + public DDPortCommand() + { + Keyword = "set-port"; + RequiredParameters = 1; + } + + protected override ExitCode Run(IList parameters) + { + ushort port; + try + { + port = Convert.ToUInt16(parameters[0]); + } + catch + { + OutputProc("Invalid port number!"); + return ExitCode.BadCommand; + } + + Server.GetComponent().SetPort(port); + return ExitCode.Normal; + } + + public override string GetArgumentString() + { + return ""; + } + + public override string GetHelpText() + { + return "Sets the port DreamDaemon will open the server on. Requires a server restart to apply and queues a graceful one up"; + } + } + + class DDSecurityCommand : Command + { + public DDSecurityCommand() + { + Keyword = "set-security"; + RequiredParameters = 1; + } + + protected override ExitCode Run(IList parameters) + { + TGDreamDaemonSecurity sec; + switch (parameters[0].ToLower()) + { + case "safe": + sec = TGDreamDaemonSecurity.Safe; + break; + case "ultra": + case "ultrasafe": + sec = TGDreamDaemonSecurity.Ultrasafe; + break; + case "trust": + case "trusted": + sec = TGDreamDaemonSecurity.Trusted; + break; + default: + OutputProc("Invalid security word!"); + return ExitCode.BadCommand; + } + Server.GetComponent().SetSecurityLevel(sec); + return ExitCode.Normal; + } + + public override string GetArgumentString() + { + return ""; + } + + public override string GetHelpText() + { + return "Sets the visibility option for the DreamDaemon world"; + } + } +} diff --git a/TGCommandLine/Program.cs b/TGCommandLine/Program.cs index fa76df494f..c20bde1676 100644 --- a/TGCommandLine/Program.cs +++ b/TGCommandLine/Program.cs @@ -1,220 +1,220 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using TGServiceInterface; - -namespace TGCommandLine -{ - - class Program - { - static ExitCode RunCommandLine(IList argsAsList) - { - //first lookup the connection string - bool badConnectionString = false; - for (var I = 0; I < argsAsList.Count - 1; ++I) { - var lowerarg = argsAsList[I].ToLower(); - if (lowerarg == "-c" || lowerarg == "--connect") - { - var connectionString = argsAsList[I + 1]; - var splits = connectionString.Split('@'); - var userpass = splits[0].Split(':'); - if (splits.Length != 2 || userpass.Length != 2) - { - badConnectionString = true; - break; - } - var addrport = splits[1].Split(':'); - if (addrport.Length != 2) - { - badConnectionString = true; - break; - } - var username = userpass[0]; - var password = userpass[1]; - var address = addrport[0]; - ushort port; - try - { - port = Convert.ToUInt16(addrport[1]); - } - catch - { - badConnectionString = true; - break; - } - if(String.IsNullOrWhiteSpace(username) || String.IsNullOrWhiteSpace(password) || String.IsNullOrWhiteSpace(address)) - { - badConnectionString = true; - break; - } - argsAsList.RemoveAt(I); - argsAsList.RemoveAt(I); - Server.SetRemoteLoginInformation(address, port, username, password); - break; - } - } - - if (badConnectionString) - { - Console.WriteLine("Remote connection usage: <-c/--connect> username:password@address:port"); - return ExitCode.BadCommand; - } - - var res = Server.VerifyConnection(); - if (res != null) - { - Console.WriteLine("Unable to connect to service: " + res); - Console.WriteLine("Remote connection usage: <-c/--connect> username:password@address:port"); - return ExitCode.ConnectionError; - } - - if (!Server.Authenticate()) - { - Console.WriteLine("Authentication error: Username/password/windows identity is not authorized!"); - return ExitCode.ConnectionError; - } - - try - { - return new CLICommand().DoRun(argsAsList); - } - catch (Exception e) - { - Console.WriteLine("Error: " + e.ToString()); - return ExitCode.ConnectionError; - }; - } - public static string ReadLineSecure() - { - string result = ""; - while (true) - { - ConsoleKeyInfo i = Console.ReadKey(true); - if (i.Key == ConsoleKey.Enter) - { - break; - } - else if (i.Key == ConsoleKey.Backspace) - { - if (result.Length > 0) - { - result = result.Substring(0, result.Length - 1); - Console.Write("\b \b"); - } - } - else - { - result += i.KeyChar; - Console.Write("*"); - } - } - Console.WriteLine(); - return result; - } - - static string AcceptedBadCert; - static bool BadCertificateInteractive(string message) - { - if (AcceptedBadCert == message) - return true; - Console.WriteLine(message); - Console.Write("Do you wish to continue? NOT RECCOMENDED! (y/N): "); - var result = Console.ReadLine().Trim().ToLower(); - if (result == "y" || result == "yes") - { - AcceptedBadCert = message; - return true; - } - return false; - } - - static int Main(string[] args) - { - Command.OutputProcVar.Value = Console.WriteLine; - if (args.Length != 0) - { - Server.SetBadCertificateHandler((message) => { - foreach (var I in args) - if (I.ToLower() == "--disable-ssl-verification") //im just not even going to document this because i hate it so much - return true; - return false; - }); - //allow self signed certs in debug mode - return (int)RunCommandLine(new List(args)); - } - - Server.SetBadCertificateHandler(BadCertificateInteractive); - - Console.WriteLine("Type 'remote' to connect to a remote service"); - //interactive mode - while (true) - { - Console.Write("Enter command: "); - var NextCommand = Console.ReadLine(); - switch (NextCommand.ToLower()) - { - case "remote": - Console.Write("Enter server address: "); - var address = Console.ReadLine(); - Console.Write("Enter server port: "); - ushort port; - try{ - port = Convert.ToUInt16(Console.ReadLine()); - } - catch - { - Console.WriteLine("Error: Bad port!"); - break; - } - Console.Write("Enter username: "); - var username = Console.ReadLine(); - Console.Write("Enter password: "); - var password = ReadLineSecure(); - Server.SetRemoteLoginInformation(address, port, username, password); - var res = Server.VerifyConnection(); - if (res != null) - { - Console.WriteLine("Unable to connect: " + res); - Server.MakeLocalConnection(); - } - else if (!Server.Authenticate()) - { - Console.WriteLine("Authentication error: Username/password/windows identity is not authorized! Returning to local mode..."); - Server.MakeLocalConnection(); - } - else - { - Console.WriteLine("Connected remotely"); - Console.WriteLine("Type 'disconnect' to return to local mode"); - } - break; - case "disconnect": - Server.MakeLocalConnection(); - Console.WriteLine("Switch to local mode"); - break; - case "quit": - case "exit": - return (int)ExitCode.Normal; -#if DEBUG - case "debug-upgrade": - Server.GetComponent().PrepareForUpdate(); - return (int)ExitCode.Normal; -#endif - default: - //linq voodoo to get quoted strings - var formattedCommand = NextCommand.Split('"') - .Select((element, index) => index % 2 == 0 // If even index - ? element.Split(new[] { ' ' }, StringSplitOptions.RemoveEmptyEntries) // Split the item - : new string[] { element }) // Keep the entire item - .SelectMany(element => element).ToList(); - - formattedCommand = formattedCommand.Select(x => x.Trim()).ToList(); - formattedCommand.Remove(""); - RunCommandLine(formattedCommand); - break; - } - } - } - } -} +using System; +using System.Collections.Generic; +using System.Linq; +using TGServiceInterface; + +namespace TGCommandLine +{ + + class Program + { + static ExitCode RunCommandLine(IList argsAsList) + { + //first lookup the connection string + bool badConnectionString = false; + for (var I = 0; I < argsAsList.Count - 1; ++I) { + var lowerarg = argsAsList[I].ToLower(); + if (lowerarg == "-c" || lowerarg == "--connect") + { + var connectionString = argsAsList[I + 1]; + var splits = connectionString.Split('@'); + var userpass = splits[0].Split(':'); + if (splits.Length != 2 || userpass.Length != 2) + { + badConnectionString = true; + break; + } + var addrport = splits[1].Split(':'); + if (addrport.Length != 2) + { + badConnectionString = true; + break; + } + var username = userpass[0]; + var password = userpass[1]; + var address = addrport[0]; + ushort port; + try + { + port = Convert.ToUInt16(addrport[1]); + } + catch + { + badConnectionString = true; + break; + } + if(String.IsNullOrWhiteSpace(username) || String.IsNullOrWhiteSpace(password) || String.IsNullOrWhiteSpace(address)) + { + badConnectionString = true; + break; + } + argsAsList.RemoveAt(I); + argsAsList.RemoveAt(I); + Server.SetRemoteLoginInformation(address, port, username, password); + break; + } + } + + if (badConnectionString) + { + Console.WriteLine("Remote connection usage: <-c/--connect> username:password@address:port"); + return ExitCode.BadCommand; + } + + var res = Server.VerifyConnection(); + if (res != null) + { + Console.WriteLine("Unable to connect to service: " + res); + Console.WriteLine("Remote connection usage: <-c/--connect> username:password@address:port"); + return ExitCode.ConnectionError; + } + + if (!Server.Authenticate()) + { + Console.WriteLine("Authentication error: Username/password/windows identity is not authorized!"); + return ExitCode.ConnectionError; + } + + try + { + return new CLICommand().DoRun(argsAsList); + } + catch (Exception e) + { + Console.WriteLine("Error: " + e.ToString()); + return ExitCode.ConnectionError; + }; + } + public static string ReadLineSecure() + { + string result = ""; + while (true) + { + ConsoleKeyInfo i = Console.ReadKey(true); + if (i.Key == ConsoleKey.Enter) + { + break; + } + else if (i.Key == ConsoleKey.Backspace) + { + if (result.Length > 0) + { + result = result.Substring(0, result.Length - 1); + Console.Write("\b \b"); + } + } + else + { + result += i.KeyChar; + Console.Write("*"); + } + } + Console.WriteLine(); + return result; + } + + static string AcceptedBadCert; + static bool BadCertificateInteractive(string message) + { + if (AcceptedBadCert == message) + return true; + Console.WriteLine(message); + Console.Write("Do you wish to continue? NOT RECCOMENDED! (y/N): "); + var result = Console.ReadLine().Trim().ToLower(); + if (result == "y" || result == "yes") + { + AcceptedBadCert = message; + return true; + } + return false; + } + + static int Main(string[] args) + { + Command.OutputProcVar.Value = Console.WriteLine; + if (args.Length != 0) + { + Server.SetBadCertificateHandler((message) => { + foreach (var I in args) + if (I.ToLower() == "--disable-ssl-verification") //im just not even going to document this because i hate it so much + return true; + return false; + }); + //allow self signed certs in debug mode + return (int)RunCommandLine(new List(args)); + } + + Server.SetBadCertificateHandler(BadCertificateInteractive); + + Console.WriteLine("Type 'remote' to connect to a remote service"); + //interactive mode + while (true) + { + Console.Write("Enter command: "); + var NextCommand = Console.ReadLine(); + switch (NextCommand.ToLower()) + { + case "remote": + Console.Write("Enter server address: "); + var address = Console.ReadLine(); + Console.Write("Enter server port: "); + ushort port; + try{ + port = Convert.ToUInt16(Console.ReadLine()); + } + catch + { + Console.WriteLine("Error: Bad port!"); + break; + } + Console.Write("Enter username: "); + var username = Console.ReadLine(); + Console.Write("Enter password: "); + var password = ReadLineSecure(); + Server.SetRemoteLoginInformation(address, port, username, password); + var res = Server.VerifyConnection(); + if (res != null) + { + Console.WriteLine("Unable to connect: " + res); + Server.MakeLocalConnection(); + } + else if (!Server.Authenticate()) + { + Console.WriteLine("Authentication error: Username/password/windows identity is not authorized! Returning to local mode..."); + Server.MakeLocalConnection(); + } + else + { + Console.WriteLine("Connected remotely"); + Console.WriteLine("Type 'disconnect' to return to local mode"); + } + break; + case "disconnect": + Server.MakeLocalConnection(); + Console.WriteLine("Switch to local mode"); + break; + case "quit": + case "exit": + return (int)ExitCode.Normal; +#if DEBUG + case "debug-upgrade": + Server.GetComponent().PrepareForUpdate(); + return (int)ExitCode.Normal; +#endif + default: + //linq voodoo to get quoted strings + var formattedCommand = NextCommand.Split('"') + .Select((element, index) => index % 2 == 0 // If even index + ? element.Split(new[] { ' ' }, StringSplitOptions.RemoveEmptyEntries) // Split the item + : new string[] { element }) // Keep the entire item + .SelectMany(element => element).ToList(); + + formattedCommand = formattedCommand.Select(x => x.Trim()).ToList(); + formattedCommand.Remove(""); + RunCommandLine(formattedCommand); + break; + } + } + } + } +} diff --git a/TGControlPanel/Main.cs b/TGControlPanel/Main.cs index c464af6535..d112687f55 100644 --- a/TGControlPanel/Main.cs +++ b/TGControlPanel/Main.cs @@ -1,45 +1,45 @@ -using System; -using System.Drawing; -using System.Windows.Forms; - -namespace TGControlPanel -{ - public partial class Main : Form - { - public Main() - { - InitializeComponent(); - Panels.SelectedIndexChanged += Panels_SelectedIndexChanged; - Panels.SelectedIndex += Math.Min(Properties.Settings.Default.LastPageIndex, Panels.TabCount - 1); - InitRepoPage(); - InitBYONDPage(); - InitServerPage(); - LoadChatPage(); - InitStaticPage(); - } - - private void Main_Resize(object sender, EventArgs e) - { - Panels.Location = new Point(10, 10); - Panels.Width = ClientSize.Width - 20; - Panels.Height = ClientSize.Height - 20; - } - - private void Panels_SelectedIndexChanged(object sender, EventArgs e) - { - switch (Panels.SelectedIndex) - { - case 1: //byond - UpdateBYONDButtons(); - break; - case 2: //scp - LoadServerPage(); - break; - case 3: //chat - LoadChatPage(); - break; - } - Properties.Settings.Default.LastPageIndex = Panels.SelectedIndex; - } - } -} +using System; +using System.Drawing; +using System.Windows.Forms; + +namespace TGControlPanel +{ + public partial class Main : Form + { + public Main() + { + InitializeComponent(); + Panels.SelectedIndexChanged += Panels_SelectedIndexChanged; + Panels.SelectedIndex += Math.Min(Properties.Settings.Default.LastPageIndex, Panels.TabCount - 1); + InitRepoPage(); + InitBYONDPage(); + InitServerPage(); + LoadChatPage(); + InitStaticPage(); + } + + private void Main_Resize(object sender, EventArgs e) + { + Panels.Location = new Point(10, 10); + Panels.Width = ClientSize.Width - 20; + Panels.Height = ClientSize.Height - 20; + } + + private void Panels_SelectedIndexChanged(object sender, EventArgs e) + { + switch (Panels.SelectedIndex) + { + case 1: //byond + UpdateBYONDButtons(); + break; + case 2: //scp + LoadServerPage(); + break; + case 3: //chat + LoadChatPage(); + break; + } + Properties.Settings.Default.LastPageIndex = Panels.SelectedIndex; + } + } +} diff --git a/TGControlPanel/ServerPage.cs b/TGControlPanel/ServerPage.cs index 4e911b22f5..69b868a547 100644 --- a/TGControlPanel/ServerPage.cs +++ b/TGControlPanel/ServerPage.cs @@ -1,474 +1,477 @@ -using System; -using System.ComponentModel; -using System.Windows.Forms; -using TGServiceInterface; - -namespace TGControlPanel -{ - partial class Main - { - enum FullUpdateAction - { - UpdateHard, - UpdateMerge, - UpdateHardTestmerge, - Reset, - Testmerge, - } - - FullUpdateAction fuAction; - ushort testmergePR; - string updateError; - bool updatingFields = false; - - void InitServerPage() - { - LoadServerPage(); - if (!Server.AuthenticateAdmin()) - { - ServerPathTextbox.Enabled = false; - ServerPathTextbox.ReadOnly = true; - } - FullUpdateWorker.RunWorkerCompleted += FullUpdateWorker_RunWorkerCompleted; - ServerPathTextbox.LostFocus += ServerPathTextbox_LostFocus; - ServerPathTextbox.KeyDown += ServerPathTextbox_KeyDown; - projectNameText.LostFocus += ProjectNameText_LostFocus; - projectNameText.KeyDown += ProjectNameText_KeyDown; - ServerStartBGW.RunWorkerCompleted += ServerStartBGW_RunWorkerCompleted; - } - - private void ServerStartBGW_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e) - { - if (e.Result != null) - MessageBox.Show((string)e.Result); - LoadServerPage(); - } - - private void ProjectNameText_KeyDown(object sender, KeyEventArgs e) - { - if (e.KeyCode == Keys.Enter) - UpdateProjectName(); - } - - private void ServerPathTextbox_KeyDown(object sender, KeyEventArgs e) - { - if (e.KeyCode == Keys.Enter) - UpdateServerPath(); - } - - private void FullUpdateWorker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e) - { - if (updateError != null) - MessageBox.Show(updateError); - UpdateHardButton.Enabled = true; - UpdateMergeButton.Enabled = true; - TestmergeButton.Enabled = true; - UpdateTestmergeButton.Enabled = true; - LoadServerPage(); - } - - private void CompileCancelButton_Click(object sender, EventArgs e) - { - var res = Server.GetComponent().Cancel(); - if (res != null) - MessageBox.Show(res); - LoadServerPage(); - } - - private void ServerPathTextbox_LostFocus(object sender, EventArgs e) - { - UpdateServerPath(); - } - - void UpdateServerPath() - { - if (!Program.CheckAdminWithWarning()) - { - ServerPathTextbox.Enabled = false; - ServerPathTextbox.ReadOnly = true; - return; - } - if (updatingFields || ServerPathTextbox.Text.Trim() == Server.GetComponent().ServerDirectory()) - return; - var DialogResult = MessageBox.Show("This will move the entire server installation.", "Confim", MessageBoxButtons.YesNo); - if (DialogResult != DialogResult.Yes) - return; - - if (!Program.CheckAdminWithWarning()) - { - ServerPathTextbox.Enabled = false; - ServerPathTextbox.ReadOnly = true; - return; - } - MessageBox.Show(Server.GetComponent().MoveServer(ServerPathTextbox.Text) ?? "Success!"); - } - - void LoadServerPage() - { - var RepoExists = Server.GetComponent().Exists(); - compileButton.Visible = RepoExists; - initializeButton.Visible = RepoExists; - AutostartCheckbox.Visible = RepoExists; - WebclientCheckBox.Visible = RepoExists; - PortSelector.Visible = RepoExists; - projectNameText.Visible = RepoExists; - CompilerStatusLabel.Visible = RepoExists; - CompileCancelButton.Visible = RepoExists; - CompilerLabel.Visible = RepoExists; - ProjectPathLabel.Visible = RepoExists; - ServerPRLabel.Visible = RepoExists; - ServerGStopButton.Visible = RepoExists; - ServerStartButton.Visible = RepoExists; - ServerGRestartButton.Visible = RepoExists; - ServerRestartButton.Visible = RepoExists; - PortLabel.Visible = RepoExists; - ServerStopButton.Visible = RepoExists; - TestmergeButton.Visible = RepoExists; - ServerTestmergeInput.Visible = RepoExists; - UpdateHardButton.Visible = RepoExists; - UpdateMergeButton.Visible = RepoExists; - UpdateTestmergeButton.Visible = RepoExists; - ResetTestmerge.Visible = RepoExists; - WorldAnnounceField.Visible = RepoExists; - WorldAnnounceButton.Visible = RepoExists; - WorldAnnounceLabel.Visible = RepoExists; - - var DM = Server.GetComponent(); - var DD = Server.GetComponent(); - var Config = Server.GetComponent(); - - if (updatingFields) - return; - - try - { - updatingFields = true; - - if (!ServerPathTextbox.Focused) - ServerPathTextbox.Text = Config.ServerDirectory(); - - SecuritySelector.SelectedIndex = (int)DD.SecurityLevel(); - - if (!RepoExists) - return; - - var DaeStat = DD.DaemonStatus(); - var Online = DaeStat == TGDreamDaemonStatus.Online; - ServerStartButton.Enabled = !Online; - ServerGStopButton.Enabled = Online; - ServerGRestartButton.Enabled = Online; - ServerStopButton.Enabled = Online; - ServerRestartButton.Enabled = Online; - - switch (DaeStat) - { - case TGDreamDaemonStatus.HardRebooting: - ServerStatusLabel.Text = "REBOOTING"; - break; - case TGDreamDaemonStatus.Offline: - ServerStatusLabel.Text = "OFFLINE"; - break; - case TGDreamDaemonStatus.Online: - ServerStatusLabel.Text = "ONLINE"; - break; - } - - ServerGStopButton.Checked = DD.ShutdownInProgress(); - - AutostartCheckbox.Checked = DD.Autostart(); - WebclientCheckBox.Checked = DD.Webclient(); - if (!PortSelector.Focused) - PortSelector.Value = DD.Port(); - if (!projectNameText.Focused) - projectNameText.Text = DM.ProjectName(); - - switch (DM.GetStatus()) - { - case TGCompilerStatus.Compiling: - CompilerStatusLabel.Text = "Compiling..."; - compileButton.Enabled = false; - initializeButton.Enabled = false; - CompileCancelButton.Enabled = true; - break; - case TGCompilerStatus.Initializing: - CompilerStatusLabel.Text = "Initializing..."; - compileButton.Enabled = false; - initializeButton.Enabled = false; - CompileCancelButton.Enabled = false; - break; - case TGCompilerStatus.Initialized: - CompilerStatusLabel.Text = "Idle"; - initializeButton.Enabled = true; - compileButton.Enabled = true; - CompileCancelButton.Enabled = false; - break; - case TGCompilerStatus.Uninitialized: - CompilerStatusLabel.Text = "Uninitialized"; - compileButton.Enabled = false; - initializeButton.Enabled = true; - CompileCancelButton.Enabled = false; - break; - default: - CompilerStatusLabel.Text = "Unknown!"; - initializeButton.Enabled = true; - compileButton.Enabled = true; - CompileCancelButton.Enabled = true; - break; - } - var error = DM.CompileError(); - if (error != null) - MessageBox.Show("Error: " + error); - } - finally - { - updatingFields = false; - } - } - - private void ProjectNameText_LostFocus(object sender, EventArgs e) - { - UpdateProjectName(); - } - - void UpdateProjectName() - { - if (!updatingFields) - Server.GetComponent().SetProjectName(projectNameText.Text); - } - - private void PortSelector_ValueChanged(object sender, EventArgs e) - { - if (!updatingFields) - Server.GetComponent().SetPort((ushort)PortSelector.Value); - } - - private void RunServerUpdate(FullUpdateAction fua, ushort tm = 0) - { - if (FullUpdateWorker.IsBusy) - return; - testmergePR = tm; - fuAction = fua; - initializeButton.Enabled = false; - compileButton.Enabled = false; - UpdateHardButton.Enabled = false; - UpdateMergeButton.Enabled = false; - TestmergeButton.Enabled = false; - UpdateTestmergeButton.Enabled = false; - switch (fuAction) - { - case FullUpdateAction.Testmerge: - CompilerStatusLabel.Text = String.Format("Testmerging pull request #{0}...", testmergePR); - break; - case FullUpdateAction.UpdateHard: - CompilerStatusLabel.Text = String.Format("Updating Server (RESET)..."); - break; - case FullUpdateAction.UpdateMerge: - CompilerStatusLabel.Text = String.Format("Updating Server (MERGE)..."); - break; - case FullUpdateAction.UpdateHardTestmerge: - CompilerStatusLabel.Text = String.Format("Updating and testmerging pull request #{0}...", testmergePR); - break; - } - FullUpdateWorker.RunWorkerAsync(); - } - - private void ServerPageRefreshButton_Click(object sender, EventArgs e) - { - LoadServerPage(); - } - - private void InitializeButton_Click(object sender, EventArgs e) - { - if (!Server.GetComponent().Initialize()) - MessageBox.Show("Unable to start initialization!"); - LoadServerPage(); - } - private void CompileButton_Click(object sender, EventArgs e) - { - if (!Server.GetComponent().Compile()) - MessageBox.Show("Unable to start compilation!"); - LoadServerPage(); - } - - private void AutostartCheckbox_CheckedChanged(object sender, System.EventArgs e) - { - if (!updatingFields) - Server.GetComponent().SetAutostart(AutostartCheckbox.Checked); - } - private void ServerStartButton_Click(object sender, System.EventArgs e) - { - if (!ServerStartBGW.IsBusy) - ServerStartBGW.RunWorkerAsync(); - } - - private void ServerStartBGW_DoWork(object sender, DoWorkEventArgs e) - { - try - { - e.Result = Server.GetComponent().Start(); - } - catch (Exception ex) - { - e.Result = ex.ToString(); - } - } - - private void ServerStopButton_Click(object sender, EventArgs e) - { - var DialogResult = MessageBox.Show("This will immediately shut down the server. Continue?", "Confim", MessageBoxButtons.YesNo); - if (DialogResult == DialogResult.No) - return; - var res = Server.GetComponent().Stop(); - if (res != null) - MessageBox.Show(res); - } - - private void ServerRestartButton_Click(object sender, EventArgs e) - { - var DialogResult = MessageBox.Show("This will immediately restart the server. Continue?", "Confim", MessageBoxButtons.YesNo); - if (DialogResult == DialogResult.No) - return; - var res = Server.GetComponent().Restart(); - if (res != null) - MessageBox.Show(res); - } - - private void ServerGStopButton_Checked(object sender, EventArgs e) - { - if (updatingFields) - return; - var DialogResult = MessageBox.Show("This will shut down the server when the current round ends. Continue?", "Confim", MessageBoxButtons.YesNo); - if (DialogResult == DialogResult.No) - return; - Server.GetComponent().RequestStop(); - LoadServerPage(); - } - - private void ServerGRestartButton_Click(object sender, EventArgs e) - { - var DialogResult = MessageBox.Show("This will restart the server when the current round ends. Continue?", "Confim", MessageBoxButtons.YesNo); - if (DialogResult == DialogResult.No) - return; - Server.GetComponent().RequestRestart(); - } - - - private void FullUpdateWorker_DoWork(object sender, DoWorkEventArgs e) - { - try - { - var Repo = Server.GetComponent(); - var DM = Server.GetComponent(); - switch (fuAction) - { - case FullUpdateAction.Testmerge: - updateError = Repo.MergePullRequest(testmergePR); - if (updateError == null) - { - Repo.GenerateChangelog(out updateError); - updateError = DM.Compile(true) ? updateError : "Compilation failed!"; - } - break; - case FullUpdateAction.UpdateHard: - updateError = Repo.Update(true); - if (updateError == null) - { - Repo.GenerateChangelog(out updateError); - if (updateError == null) - updateError = Repo.PushChangelog(); - updateError = DM.Compile(true) ? updateError : "Compilation failed!"; - } - break; - case FullUpdateAction.UpdateHardTestmerge: - updateError = Repo.Update(true); - if (updateError == null) - { - Repo.GenerateChangelog(out updateError); - if (updateError == null) - updateError = Repo.PushChangelog(); - updateError = Repo.MergePullRequest(testmergePR); - if (updateError == null) - { - Repo.GenerateChangelog(out updateError); - updateError = DM.Compile(true) ? updateError : "Compilation failed!"; - } - } - break; - case FullUpdateAction.UpdateMerge: - updateError = Repo.Update(false); - if (updateError == null) - { - Repo.GenerateChangelog(out updateError); - if (updateError == null) - Repo.PushChangelog(); //not an error 99% of the time if this fails, just a dirty tree - updateError = DM.Compile(true) ? updateError : "Compilation failed!"; - } - break; - case FullUpdateAction.Reset: - updateError = Repo.Reset(true); - if (updateError == null) - { - Repo.GenerateChangelog(out updateError); - updateError = DM.Compile(true) ? updateError : "Compilation failed!"; - } - break; - } - } - catch (Exception ex) - { - Program.ServiceDisconnectException(ex); - } - } - private void ResetTestmerge_Click(object sender, EventArgs e) - { - RunServerUpdate(FullUpdateAction.Reset); - } - - private void UpdateHardButton_Click(object sender, System.EventArgs e) - { - RunServerUpdate(FullUpdateAction.UpdateHard); - } - - private void UpdateTestmergeButton_Click(object sender, System.EventArgs e) - { - RunServerUpdate(FullUpdateAction.UpdateHardTestmerge, (ushort)ServerTestmergeInput.Value); - } - - private void UpdateMergeButton_Click(object sender, System.EventArgs e) - { - RunServerUpdate(FullUpdateAction.UpdateMerge); - } - private void TestmergeButton_Click(object sender, System.EventArgs e) - { - RunServerUpdate(FullUpdateAction.Testmerge, (ushort)ServerTestmergeInput.Value); - } - - private void SecuritySelector_SelectedIndexChanged(object sender, EventArgs e) - { - if (!updatingFields) - if (!Server.GetComponent().SetSecurityLevel((TGDreamDaemonSecurity)SecuritySelector.SelectedIndex)) - MessageBox.Show("Security change will be applied after next server reboot."); - } - - private void WorldAnnounceButton_Click(object sender, EventArgs e) - { - var msg = WorldAnnounceField.Text; - if (!String.IsNullOrWhiteSpace(msg)) - { - var res = Server.GetComponent().WorldAnnounce(msg); - if (res != null) - { - MessageBox.Show(res); - return; - } - } - WorldAnnounceField.Text = ""; - } - - private void WebclientCheckBox_CheckedChanged(object sender, EventArgs e) - { - if (!updatingFields) - Server.GetComponent().SetWebclient(WebclientCheckBox.Checked); - } - } -} +using System; +using System.ComponentModel; +using System.Windows.Forms; +using TGServiceInterface; + +namespace TGControlPanel +{ + partial class Main + { + enum FullUpdateAction + { + UpdateHard, + UpdateMerge, + UpdateHardTestmerge, + Reset, + Testmerge, + } + + FullUpdateAction fuAction; + ushort testmergePR; + string updateError; + bool updatingFields = false; + + void InitServerPage() + { + LoadServerPage(); + if (!Server.AuthenticateAdmin()) + { + ServerPathTextbox.Enabled = false; + ServerPathTextbox.ReadOnly = true; + } + FullUpdateWorker.RunWorkerCompleted += FullUpdateWorker_RunWorkerCompleted; + ServerPathTextbox.LostFocus += ServerPathTextbox_LostFocus; + ServerPathTextbox.KeyDown += ServerPathTextbox_KeyDown; + projectNameText.LostFocus += ProjectNameText_LostFocus; + projectNameText.KeyDown += ProjectNameText_KeyDown; + ServerStartBGW.RunWorkerCompleted += ServerStartBGW_RunWorkerCompleted; + } + + private void ServerStartBGW_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e) + { + if (e.Result != null) + MessageBox.Show((string)e.Result); + LoadServerPage(); + } + + private void ProjectNameText_KeyDown(object sender, KeyEventArgs e) + { + if (e.KeyCode == Keys.Enter) + UpdateProjectName(); + } + + private void ServerPathTextbox_KeyDown(object sender, KeyEventArgs e) + { + if (e.KeyCode == Keys.Enter) + UpdateServerPath(); + } + + private void FullUpdateWorker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e) + { + if (updateError != null) + MessageBox.Show(updateError); + UpdateHardButton.Enabled = true; + UpdateMergeButton.Enabled = true; + TestmergeButton.Enabled = true; + UpdateTestmergeButton.Enabled = true; + LoadServerPage(); + } + + private void CompileCancelButton_Click(object sender, EventArgs e) + { + var res = Server.GetComponent().Cancel(); + if (res != null) + MessageBox.Show(res); + LoadServerPage(); + } + + private void ServerPathTextbox_LostFocus(object sender, EventArgs e) + { + UpdateServerPath(); + } + + void UpdateServerPath() + { + if (!Program.CheckAdminWithWarning()) + { + ServerPathTextbox.Enabled = false; + ServerPathTextbox.ReadOnly = true; + return; + } + if (updatingFields || ServerPathTextbox.Text.Trim() == Server.GetComponent().ServerDirectory()) + return; + var DialogResult = MessageBox.Show("This will move the entire server installation.", "Confim", MessageBoxButtons.YesNo); + if (DialogResult != DialogResult.Yes) + return; + + if (!Program.CheckAdminWithWarning()) + { + ServerPathTextbox.Enabled = false; + ServerPathTextbox.ReadOnly = true; + return; + } + MessageBox.Show(Server.GetComponent().MoveServer(ServerPathTextbox.Text) ?? "Success!"); + } + + void LoadServerPage() + { + var RepoExists = Server.GetComponent().Exists(); + compileButton.Visible = RepoExists; + initializeButton.Visible = RepoExists; + AutostartCheckbox.Visible = RepoExists; + WebclientCheckBox.Visible = RepoExists; + PortSelector.Visible = RepoExists; + projectNameText.Visible = RepoExists; + CompilerStatusLabel.Visible = RepoExists; + CompileCancelButton.Visible = RepoExists; + CompilerLabel.Visible = RepoExists; + ProjectPathLabel.Visible = RepoExists; + ServerPRLabel.Visible = RepoExists; + ServerGStopButton.Visible = RepoExists; + ServerStartButton.Visible = RepoExists; + ServerGRestartButton.Visible = RepoExists; + ServerRestartButton.Visible = RepoExists; + PortLabel.Visible = RepoExists; + ServerStopButton.Visible = RepoExists; + TestmergeButton.Visible = RepoExists; + ServerTestmergeInput.Visible = RepoExists; + UpdateHardButton.Visible = RepoExists; + UpdateMergeButton.Visible = RepoExists; + UpdateTestmergeButton.Visible = RepoExists; + ResetTestmerge.Visible = RepoExists; + WorldAnnounceField.Visible = RepoExists; + WorldAnnounceButton.Visible = RepoExists; + WorldAnnounceLabel.Visible = RepoExists; + + var DM = Server.GetComponent(); + var DD = Server.GetComponent(); + var Config = Server.GetComponent(); + + if (updatingFields) + return; + + try + { + updatingFields = true; + + if (!ServerPathTextbox.Focused) + ServerPathTextbox.Text = Config.ServerDirectory(); + + SecuritySelector.SelectedIndex = (int)DD.SecurityLevel(); + + if (!RepoExists) + return; + + var DaeStat = DD.DaemonStatus(); + var Online = DaeStat == TGDreamDaemonStatus.Online; + ServerStartButton.Enabled = !Online; + ServerGStopButton.Enabled = Online; + ServerGRestartButton.Enabled = Online; + ServerStopButton.Enabled = Online; + ServerRestartButton.Enabled = Online; + + switch (DaeStat) + { + case TGDreamDaemonStatus.HardRebooting: + ServerStatusLabel.Text = "REBOOTING"; + break; + case TGDreamDaemonStatus.Offline: + ServerStatusLabel.Text = "OFFLINE"; + break; + case TGDreamDaemonStatus.Online: + ServerStatusLabel.Text = "ONLINE"; + var pc = DD.PlayerCount(); + if (pc != -1) + ServerStatusLabel.Text += " (" + pc + " players)"; + break; + } + + ServerGStopButton.Checked = DD.ShutdownInProgress(); + + AutostartCheckbox.Checked = DD.Autostart(); + WebclientCheckBox.Checked = DD.Webclient(); + if (!PortSelector.Focused) + PortSelector.Value = DD.Port(); + if (!projectNameText.Focused) + projectNameText.Text = DM.ProjectName(); + + switch (DM.GetStatus()) + { + case TGCompilerStatus.Compiling: + CompilerStatusLabel.Text = "Compiling..."; + compileButton.Enabled = false; + initializeButton.Enabled = false; + CompileCancelButton.Enabled = true; + break; + case TGCompilerStatus.Initializing: + CompilerStatusLabel.Text = "Initializing..."; + compileButton.Enabled = false; + initializeButton.Enabled = false; + CompileCancelButton.Enabled = false; + break; + case TGCompilerStatus.Initialized: + CompilerStatusLabel.Text = "Idle"; + initializeButton.Enabled = true; + compileButton.Enabled = true; + CompileCancelButton.Enabled = false; + break; + case TGCompilerStatus.Uninitialized: + CompilerStatusLabel.Text = "Uninitialized"; + compileButton.Enabled = false; + initializeButton.Enabled = true; + CompileCancelButton.Enabled = false; + break; + default: + CompilerStatusLabel.Text = "Unknown!"; + initializeButton.Enabled = true; + compileButton.Enabled = true; + CompileCancelButton.Enabled = true; + break; + } + var error = DM.CompileError(); + if (error != null) + MessageBox.Show("Error: " + error); + } + finally + { + updatingFields = false; + } + } + + private void ProjectNameText_LostFocus(object sender, EventArgs e) + { + UpdateProjectName(); + } + + void UpdateProjectName() + { + if (!updatingFields) + Server.GetComponent().SetProjectName(projectNameText.Text); + } + + private void PortSelector_ValueChanged(object sender, EventArgs e) + { + if (!updatingFields) + Server.GetComponent().SetPort((ushort)PortSelector.Value); + } + + private void RunServerUpdate(FullUpdateAction fua, ushort tm = 0) + { + if (FullUpdateWorker.IsBusy) + return; + testmergePR = tm; + fuAction = fua; + initializeButton.Enabled = false; + compileButton.Enabled = false; + UpdateHardButton.Enabled = false; + UpdateMergeButton.Enabled = false; + TestmergeButton.Enabled = false; + UpdateTestmergeButton.Enabled = false; + switch (fuAction) + { + case FullUpdateAction.Testmerge: + CompilerStatusLabel.Text = String.Format("Testmerging pull request #{0}...", testmergePR); + break; + case FullUpdateAction.UpdateHard: + CompilerStatusLabel.Text = String.Format("Updating Server (RESET)..."); + break; + case FullUpdateAction.UpdateMerge: + CompilerStatusLabel.Text = String.Format("Updating Server (MERGE)..."); + break; + case FullUpdateAction.UpdateHardTestmerge: + CompilerStatusLabel.Text = String.Format("Updating and testmerging pull request #{0}...", testmergePR); + break; + } + FullUpdateWorker.RunWorkerAsync(); + } + + private void ServerPageRefreshButton_Click(object sender, EventArgs e) + { + LoadServerPage(); + } + + private void InitializeButton_Click(object sender, EventArgs e) + { + if (!Server.GetComponent().Initialize()) + MessageBox.Show("Unable to start initialization!"); + LoadServerPage(); + } + private void CompileButton_Click(object sender, EventArgs e) + { + if (!Server.GetComponent().Compile()) + MessageBox.Show("Unable to start compilation!"); + LoadServerPage(); + } + + private void AutostartCheckbox_CheckedChanged(object sender, System.EventArgs e) + { + if (!updatingFields) + Server.GetComponent().SetAutostart(AutostartCheckbox.Checked); + } + private void ServerStartButton_Click(object sender, System.EventArgs e) + { + if (!ServerStartBGW.IsBusy) + ServerStartBGW.RunWorkerAsync(); + } + + private void ServerStartBGW_DoWork(object sender, DoWorkEventArgs e) + { + try + { + e.Result = Server.GetComponent().Start(); + } + catch (Exception ex) + { + e.Result = ex.ToString(); + } + } + + private void ServerStopButton_Click(object sender, EventArgs e) + { + var DialogResult = MessageBox.Show("This will immediately shut down the server. Continue?", "Confim", MessageBoxButtons.YesNo); + if (DialogResult == DialogResult.No) + return; + var res = Server.GetComponent().Stop(); + if (res != null) + MessageBox.Show(res); + } + + private void ServerRestartButton_Click(object sender, EventArgs e) + { + var DialogResult = MessageBox.Show("This will immediately restart the server. Continue?", "Confim", MessageBoxButtons.YesNo); + if (DialogResult == DialogResult.No) + return; + var res = Server.GetComponent().Restart(); + if (res != null) + MessageBox.Show(res); + } + + private void ServerGStopButton_Checked(object sender, EventArgs e) + { + if (updatingFields) + return; + var DialogResult = MessageBox.Show("This will shut down the server when the current round ends. Continue?", "Confim", MessageBoxButtons.YesNo); + if (DialogResult == DialogResult.No) + return; + Server.GetComponent().RequestStop(); + LoadServerPage(); + } + + private void ServerGRestartButton_Click(object sender, EventArgs e) + { + var DialogResult = MessageBox.Show("This will restart the server when the current round ends. Continue?", "Confim", MessageBoxButtons.YesNo); + if (DialogResult == DialogResult.No) + return; + Server.GetComponent().RequestRestart(); + } + + + private void FullUpdateWorker_DoWork(object sender, DoWorkEventArgs e) + { + try + { + var Repo = Server.GetComponent(); + var DM = Server.GetComponent(); + switch (fuAction) + { + case FullUpdateAction.Testmerge: + updateError = Repo.MergePullRequest(testmergePR); + if (updateError == null) + { + Repo.GenerateChangelog(out updateError); + updateError = DM.Compile(true) ? updateError : "Compilation failed!"; + } + break; + case FullUpdateAction.UpdateHard: + updateError = Repo.Update(true); + if (updateError == null) + { + Repo.GenerateChangelog(out updateError); + if (updateError == null) + updateError = Repo.PushChangelog(); + updateError = DM.Compile(true) ? updateError : "Compilation failed!"; + } + break; + case FullUpdateAction.UpdateHardTestmerge: + updateError = Repo.Update(true); + if (updateError == null) + { + Repo.GenerateChangelog(out updateError); + if (updateError == null) + updateError = Repo.PushChangelog(); + updateError = Repo.MergePullRequest(testmergePR); + if (updateError == null) + { + Repo.GenerateChangelog(out updateError); + updateError = DM.Compile(true) ? updateError : "Compilation failed!"; + } + } + break; + case FullUpdateAction.UpdateMerge: + updateError = Repo.Update(false); + if (updateError == null) + { + Repo.GenerateChangelog(out updateError); + if (updateError == null) + Repo.PushChangelog(); //not an error 99% of the time if this fails, just a dirty tree + updateError = DM.Compile(true) ? updateError : "Compilation failed!"; + } + break; + case FullUpdateAction.Reset: + updateError = Repo.Reset(true); + if (updateError == null) + { + Repo.GenerateChangelog(out updateError); + updateError = DM.Compile(true) ? updateError : "Compilation failed!"; + } + break; + } + } + catch (Exception ex) + { + Program.ServiceDisconnectException(ex); + } + } + private void ResetTestmerge_Click(object sender, EventArgs e) + { + RunServerUpdate(FullUpdateAction.Reset); + } + + private void UpdateHardButton_Click(object sender, System.EventArgs e) + { + RunServerUpdate(FullUpdateAction.UpdateHard); + } + + private void UpdateTestmergeButton_Click(object sender, System.EventArgs e) + { + RunServerUpdate(FullUpdateAction.UpdateHardTestmerge, (ushort)ServerTestmergeInput.Value); + } + + private void UpdateMergeButton_Click(object sender, System.EventArgs e) + { + RunServerUpdate(FullUpdateAction.UpdateMerge); + } + private void TestmergeButton_Click(object sender, System.EventArgs e) + { + RunServerUpdate(FullUpdateAction.Testmerge, (ushort)ServerTestmergeInput.Value); + } + + private void SecuritySelector_SelectedIndexChanged(object sender, EventArgs e) + { + if (!updatingFields) + if (!Server.GetComponent().SetSecurityLevel((TGDreamDaemonSecurity)SecuritySelector.SelectedIndex)) + MessageBox.Show("Security change will be applied after next server reboot."); + } + + private void WorldAnnounceButton_Click(object sender, EventArgs e) + { + var msg = WorldAnnounceField.Text; + if (!String.IsNullOrWhiteSpace(msg)) + { + var res = Server.GetComponent().WorldAnnounce(msg); + if (res != null) + { + MessageBox.Show(res); + return; + } + } + WorldAnnounceField.Text = ""; + } + + private void WebclientCheckBox_CheckedChanged(object sender, EventArgs e) + { + if (!updatingFields) + Server.GetComponent().SetWebclient(WebclientCheckBox.Checked); + } + } +} diff --git a/TGServerService/Interop.cs b/TGServerService/Interop.cs index f176a8a374..1904881e3f 100644 --- a/TGServerService/Interop.cs +++ b/TGServerService/Interop.cs @@ -1,233 +1,246 @@ -using System; -using System.Collections.Generic; -using System.Net; -using System.Net.Sockets; -using System.Text; -using System.Threading; -using System.Web.Script.Serialization; -using System.Web.Security; -using TGServiceInterface; - -namespace TGServerService -{ - //handles talking between the world and us - partial class TGStationServer : ITGInterop - { - - object topicLock = new object(); - const int CommsKeyLen = 64; - string serviceCommsKey; //regenerated every DD restart - - //range of supported api versions - readonly Version MinAPIVersion = new Version("3.1.0.0"); - readonly Version MaxAPIVersion = new Version("3.1.0.99"); - Version GameAPIVersion; - - //See code/modules/server_tools/server_tools.dm for command switch - const string SCHardReboot = "hard_reboot"; //requests that dreamdaemon restarts when the round ends - const string SCGracefulShutdown = "graceful_shutdown"; //requests that dreamdaemon stops when the round ends - const string SCWorldAnnounce = "world_announce"; //sends param 'message' to the world - const string SCListCustomCommands = "list_custom_commands"; //Get a list of commands supported by the server - const string SCAPICompat = "api_compat"; //Tells the server we understand each other - - const string SRKillProcess = "killme"; - const string SRIRCBroadcast = "irc"; - const string SRIRCAdminChannelMessage = "send2irc"; - const string SRWorldReboot = "worldreboot"; - const string SRAPIVersion = "api_ver"; - - const string CCPHelpText = "help_text"; - const string CCPAdminOnly = "admin_only"; - const string CCPRequiredParameters = "required_parameters"; - - List ServerChatCommands; - - void LoadServerChatCommands() - { - if (DaemonStatus() != TGDreamDaemonStatus.Online) - return; - var json = SendCommand(SCListCustomCommands); - if (String.IsNullOrWhiteSpace(json)) - return; - List tmp = new List(); - try - { - foreach(var I in new JavaScriptSerializer().Deserialize>>(json)) - tmp.Add(new ServerChatCommand(I.Key, (string)I.Value[CCPHelpText], ((int)I.Value[CCPAdminOnly]) == 1, (int)I.Value[CCPRequiredParameters])); - ServerChatCommands = tmp; - } - catch { } - } - - //raw command string sent here via world.ExportService - void HandleCommand(string cmd) - { - var splits = new List(cmd.Split(' ')); - cmd = splits[0]; - splits.RemoveAt(0); - - bool APIValid; - lock (topicLock) - { - APIValid = CheckAPIVersionConstraints(); - } - - if (!APIValid && cmd != SRAPIVersion) - return; //SPEAK THE LANGUAGE!!! - - switch (cmd) - { - case SRIRCBroadcast: - SendMessage("GAME: " + String.Join(" ", splits), ChatMessageType.GameInfo); - break; - case SRKillProcess: - KillMe(); - break; - case SRIRCAdminChannelMessage: - SendMessage("RELAY: " + String.Join(" ", splits), ChatMessageType.AdminInfo); - break; - case SRWorldReboot: - TGServerService.WriteInfo("World Rebooted", TGServerService.EventID.WorldReboot); - ServerChatCommands = null; - ChatConnectivityCheck(); - lock (CompilerLock) - { - if (UpdateStaged) - { - UpdateStaged = false; - lock (topicLock) - { - GameAPIVersion = null; //needs updating - } - TGServerService.WriteInfo("Staged update applied", TGServerService.EventID.ServerUpdateApplied); - } - } - break; - case SRAPIVersion: - lock (topicLock) - { - try - { - GameAPIVersion = new Version(splits[0]); - if (!CheckAPIVersionConstraints()) - throw new Exception(); - } - catch - { - TGServerService.WriteWarning(String.Format("API version of the game ({0}) is incompatible with the current supported API versions (Min: {1}. Max: {2}). Interop disabled.", splits.Count > 1 ? splits[1] : "NULL", MinAPIVersion, MaxAPIVersion), TGServerService.EventID.APIVersionMismatch); - GameAPIVersion = null; - break; - } - } - //This needs to be done asyncronously otherwise DD won't be able to process it, because it's waiting for THIS THREAD to return - ThreadPool.QueueUserWorkItem(_ => SendCommand(SCAPICompat)); - break; - } - } - - public string SendCommand(string cmd) - { - lock (watchdogLock) - { - if (currentStatus != TGDreamDaemonStatus.Online) - return "Error: Server Offline!"; - return SendTopic(String.Format("serviceCommsKey={0};command={1}", serviceCommsKey, cmd), currentPort); - } - } - - //requires topiclock - bool CheckAPIVersionConstraints() - { - return !(GameAPIVersion == null || GameAPIVersion < MinAPIVersion || GameAPIVersion > MaxAPIVersion); - } - - //Fuckery to diddle byond with the right packet to accept our girth - string SendTopic(string topicdata, ushort port) - { - lock (topicLock) { - if (!CheckAPIVersionConstraints()) - return "Incompatible API!"; - using (var topicSender = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp) { SendTimeout = 5000, ReceiveTimeout = 5000 }) - { - try - { - topicSender.Connect(IPAddress.Loopback, port); - - StringBuilder stringPacket = new StringBuilder(); - stringPacket.Append((char)'\x00', 8); - stringPacket.Append('?' + topicdata); - stringPacket.Append((char)'\x00'); - string fullString = stringPacket.ToString(); - var packet = Encoding.ASCII.GetBytes(fullString); - packet[1] = 0x83; - var FinalLength = packet.Length - 4; - if (FinalLength > UInt16.MaxValue) - return "Error: Topic too long"; - - var lengthBytes = BitConverter.GetBytes((ushort)FinalLength); - - packet[2] = lengthBytes[1]; //fucking endianess - packet[3] = lengthBytes[0]; - - topicSender.Send(packet); - - string returnedString = "NULL"; - try - { - var returnedData = new byte[UInt16.MaxValue]; - topicSender.Receive(returnedData); - var raw_string = Encoding.ASCII.GetString(returnedData).TrimEnd(new char[] { (char)0 }).Trim(); - if (raw_string.Length > 6) - returnedString = raw_string.Substring(5, raw_string.Length - 5).Trim(); - } - catch - { - returnedString = "Topic recieve error!"; - } - finally - { - topicSender.Shutdown(SocketShutdown.Both); - } - - return returnedString; - } - catch - { - return "Topic delivery failed!"; - } - } - } - } - - //Every time we make a new DD process we generate a new comms key for security - //It's in world.params['server_service'] - void GenCommsKey() - { - var charsToRemove = new string[] { "!", "@", "#", "$", "%", "^", "&", "*", "(", ")", "_", "-", "+", "=", "[", "{", "]", "}", ";", ":", "<", ">", "|", ".", "/", "?" }; - serviceCommsKey = String.Empty; - do { - var tmp = Membership.GeneratePassword(CommsKeyLen, 0); - foreach (var c in charsToRemove) - tmp = tmp.Replace(c, String.Empty); - serviceCommsKey += tmp; - } while (serviceCommsKey.Length < CommsKeyLen); - serviceCommsKey = serviceCommsKey.Substring(0, CommsKeyLen); - TGServerService.WriteInfo("Service Comms Key set to: " + serviceCommsKey, TGServerService.EventID.CommsKeySet); - } - - /// - public bool InteropMessage(string command) - { - try - { - HandleCommand(command); - return true; - } - catch(Exception e) - { - TGServerService.WriteWarning(String.Format("Handle command for \"{0}\" failed: {1}", command, e.ToString()), TGServerService.EventID.InteropCallException); - return false; - } - } - } -} +using System; +using System.Collections.Generic; +using System.Net; +using System.Net.Sockets; +using System.Text; +using System.Threading; +using System.Web.Script.Serialization; +using System.Web.Security; +using TGServiceInterface; + +namespace TGServerService +{ + //handles talking between the world and us + partial class TGStationServer : ITGInterop + { + + object topicLock = new object(); + const int CommsKeyLen = 64; + string serviceCommsKey; //regenerated every DD restart + + //range of supported api versions + readonly Version MinAPIVersion = new Version("3.1.0.0"); + readonly Version MaxAPIVersion = new Version("3.1.0.99"); + Version GameAPIVersion; + + //See code/modules/server_tools/server_tools.dm for command switch + const string SCHardReboot = "hard_reboot"; //requests that dreamdaemon restarts when the round ends + const string SCGracefulShutdown = "graceful_shutdown"; //requests that dreamdaemon stops when the round ends + const string SCWorldAnnounce = "world_announce"; //sends param 'message' to the world + const string SCListCustomCommands = "list_custom_commands"; //Get a list of commands supported by the server + const string SCAPICompat = "api_compat"; //Tells the server we understand each other + const string SCPlayerCount = "client_count"; //Gets the number of connected clients + + const string SRKillProcess = "killme"; + const string SRIRCBroadcast = "irc"; + const string SRIRCAdminChannelMessage = "send2irc"; + const string SRWorldReboot = "worldreboot"; + const string SRAPIVersion = "api_ver"; + + const string CCPHelpText = "help_text"; + const string CCPAdminOnly = "admin_only"; + const string CCPRequiredParameters = "required_parameters"; + + List ServerChatCommands; + + void LoadServerChatCommands() + { + if (DaemonStatus() != TGDreamDaemonStatus.Online) + return; + var json = SendCommand(SCListCustomCommands); + if (String.IsNullOrWhiteSpace(json)) + return; + List tmp = new List(); + try + { + foreach(var I in new JavaScriptSerializer().Deserialize>>(json)) + tmp.Add(new ServerChatCommand(I.Key, (string)I.Value[CCPHelpText], ((int)I.Value[CCPAdminOnly]) == 1, (int)I.Value[CCPRequiredParameters])); + ServerChatCommands = tmp; + } + catch { } + } + + //raw command string sent here via world.ExportService + void HandleCommand(string cmd) + { + var splits = new List(cmd.Split(' ')); + cmd = splits[0]; + splits.RemoveAt(0); + + bool APIValid; + lock (topicLock) + { + APIValid = CheckAPIVersionConstraints(); + } + + if (!APIValid && cmd != SRAPIVersion) + return; //SPEAK THE LANGUAGE!!! + + switch (cmd) + { + case SRIRCBroadcast: + SendMessage("GAME: " + String.Join(" ", splits), ChatMessageType.GameInfo); + break; + case SRKillProcess: + KillMe(); + break; + case SRIRCAdminChannelMessage: + SendMessage("RELAY: " + String.Join(" ", splits), ChatMessageType.AdminInfo); + break; + case SRWorldReboot: + TGServerService.WriteInfo("World Rebooted", TGServerService.EventID.WorldReboot); + ServerChatCommands = null; + ChatConnectivityCheck(); + lock (CompilerLock) + { + if (UpdateStaged) + { + UpdateStaged = false; + lock (topicLock) + { + GameAPIVersion = null; //needs updating + } + TGServerService.WriteInfo("Staged update applied", TGServerService.EventID.ServerUpdateApplied); + } + } + break; + case SRAPIVersion: + lock (topicLock) + { + try + { + GameAPIVersion = new Version(splits[0]); + if (!CheckAPIVersionConstraints()) + throw new Exception(); + } + catch + { + TGServerService.WriteWarning(String.Format("API version of the game ({0}) is incompatible with the current supported API versions (Min: {1}. Max: {2}). Interop disabled.", splits.Count > 1 ? splits[1] : "NULL", MinAPIVersion, MaxAPIVersion), TGServerService.EventID.APIVersionMismatch); + GameAPIVersion = null; + break; + } + } + //This needs to be done asyncronously otherwise DD won't be able to process it, because it's waiting for THIS THREAD to return + ThreadPool.QueueUserWorkItem(_ => SendCommand(SCAPICompat)); + break; + } + } + + public string SendCommand(string cmd) + { + lock (watchdogLock) + { + if (currentStatus != TGDreamDaemonStatus.Online) + return "Error: Server Offline!"; + return SendTopic(String.Format("serviceCommsKey={0};command={1}", serviceCommsKey, cmd), currentPort); + } + } + + public int PlayerCount() + { + try + { + return Convert.ToInt32(SendCommand(SCPlayerCount)); + } + catch + { + return -1; + } + } + + //requires topiclock + bool CheckAPIVersionConstraints() + { + return !(GameAPIVersion == null || GameAPIVersion < MinAPIVersion || GameAPIVersion > MaxAPIVersion); + } + + //Fuckery to diddle byond with the right packet to accept our girth + string SendTopic(string topicdata, ushort port) + { + lock (topicLock) { + if (!CheckAPIVersionConstraints()) + return "Incompatible API!"; + using (var topicSender = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp) { SendTimeout = 5000, ReceiveTimeout = 5000 }) + { + try + { + topicSender.Connect(IPAddress.Loopback, port); + + StringBuilder stringPacket = new StringBuilder(); + stringPacket.Append((char)'\x00', 8); + stringPacket.Append('?' + topicdata); + stringPacket.Append((char)'\x00'); + string fullString = stringPacket.ToString(); + var packet = Encoding.ASCII.GetBytes(fullString); + packet[1] = 0x83; + var FinalLength = packet.Length - 4; + if (FinalLength > UInt16.MaxValue) + return "Error: Topic too long"; + + var lengthBytes = BitConverter.GetBytes((ushort)FinalLength); + + packet[2] = lengthBytes[1]; //fucking endianess + packet[3] = lengthBytes[0]; + + topicSender.Send(packet); + + string returnedString = "NULL"; + try + { + var returnedData = new byte[UInt16.MaxValue]; + topicSender.Receive(returnedData); + var raw_string = Encoding.ASCII.GetString(returnedData).TrimEnd(new char[] { (char)0 }).Trim(); + if (raw_string.Length > 6) + returnedString = raw_string.Substring(5, raw_string.Length - 5).Trim(); + } + catch + { + returnedString = "Topic recieve error!"; + } + finally + { + topicSender.Shutdown(SocketShutdown.Both); + } + + return returnedString; + } + catch + { + return "Topic delivery failed!"; + } + } + } + } + + //Every time we make a new DD process we generate a new comms key for security + //It's in world.params['server_service'] + void GenCommsKey() + { + var charsToRemove = new string[] { "!", "@", "#", "$", "%", "^", "&", "*", "(", ")", "_", "-", "+", "=", "[", "{", "]", "}", ";", ":", "<", ">", "|", ".", "/", "?" }; + serviceCommsKey = String.Empty; + do { + var tmp = Membership.GeneratePassword(CommsKeyLen, 0); + foreach (var c in charsToRemove) + tmp = tmp.Replace(c, String.Empty); + serviceCommsKey += tmp; + } while (serviceCommsKey.Length < CommsKeyLen); + serviceCommsKey = serviceCommsKey.Substring(0, CommsKeyLen); + TGServerService.WriteInfo("Service Comms Key set to: " + serviceCommsKey, TGServerService.EventID.CommsKeySet); + } + + /// + public bool InteropMessage(string command) + { + try + { + HandleCommand(command); + return true; + } + catch(Exception e) + { + TGServerService.WriteWarning(String.Format("Handle command for \"{0}\" failed: {1}", command, e.ToString()), TGServerService.EventID.InteropCallException); + return false; + } + } + } +} diff --git a/TGServiceInterface/DreamDaemon.cs b/TGServiceInterface/DreamDaemon.cs index b370551c42..6162ad3ae8 100644 --- a/TGServiceInterface/DreamDaemon.cs +++ b/TGServiceInterface/DreamDaemon.cs @@ -1,182 +1,189 @@ -using System.ServiceModel; - -namespace TGServiceInterface -{ - /// - /// The status of the DD instance - /// - public enum TGDreamDaemonStatus - { - /// - /// Server is not running - /// - Offline, - /// - /// Server is being rebooted - /// - HardRebooting, - /// - /// Server is running - /// - Online, - } - - /// - /// DreamDaemon's security level - /// - public enum TGDreamDaemonSecurity - { - /// - /// Server is unrestricted in terms of file access and shell commands - /// - Trusted = 0, - /// - /// Server will not be able to run shell commands or access files outside it's working directory - /// - Safe, - /// - /// Server will not be able to run shell commands or access anything but temporary files - /// - Ultrasafe - } - - /// - /// Interface for managing the actual BYOND game server - /// - [ServiceContract] - public interface ITGDreamDaemon - { - /// - /// Gets the status of DreamDaemon - /// - /// The appropriate TGDreamDaemonStatus - [OperationContract] - TGDreamDaemonStatus DaemonStatus(); - - /// - /// Returns a human readable string of the current server status - /// - /// If true, the status will include the server's current visibility and security levels - /// A human readable string of the current server status - [OperationContract] - string StatusString(bool includeMetaInfo); - - /// - /// Check if a call to Start will fail - /// Of course, be aware of race conditions with other control panels - /// - /// returns the error that would occur, null otherwise - [OperationContract] - string CanStart(); - - /// - /// Starts the server if it isn't running - /// - /// null on success or error message on failure - [OperationContract] - string Start(); - - /// - /// Immediately kills the server - /// - /// null on success or error message on failure - [OperationContract] - string Stop(); - - /// - /// Immediately kills and restarts the server - /// - /// null on success or error message on failure - [OperationContract] - string Restart(); - - /// - /// Restart the server after the currently running round ends - /// Has no effect if the server isn't running - /// - [OperationContract] - void RequestRestart(); - - /// - /// Stop the server after the currently running round ends - /// Has no effect if the server isn't running - /// - [OperationContract] - void RequestStop(); - - /// - /// Get the configured (not running) security level - /// - /// The configured (not running) security level - [OperationContract] - TGDreamDaemonSecurity SecurityLevel(); - - /// - /// Sets the security level of the server. Requires reboot to apply - /// Implies a call to RequestRestart() - /// note that anything higher than Trusted will disable interop from DD - /// - /// The new security level - /// True if the change was immediately applied, false if a graceful restart was queued - [OperationContract] - bool SetSecurityLevel(TGDreamDaemonSecurity level); - - /// - /// Get the configured port. Not necessarily the running port if it has since changed - /// - /// The configured port - [OperationContract] - ushort Port(); - - /// - /// Set the port to host DD on. Requires reboot to apply - /// Implies a call to RequestRestart() - /// - /// The new port - [OperationContract] - void SetPort(ushort new_port); - - /// - /// Check if the watchdog will start when the service starts - /// - /// true if autostart is enabled, false otherwise - [OperationContract] - bool Autostart(); - - /// - /// Set the autostart config - /// - /// true to start the watchdog with the service, false otherwise - [OperationContract] - void SetAutostart(bool on); - - /// - /// Check if the byond webclient is currently enabled for the server - /// - /// true if the webclient is enabled, false otherwise - [OperationContract] - bool Webclient(); - - /// - /// Set the webclient config. Calls - /// - /// true to enable the byond webclient for the server, false otherwise - [OperationContract] - void SetWebclient(bool on); - - /// - /// Checks if a server stop has bee requested - /// - /// true if RequestStop has been called since the last server start, false otherwise - [OperationContract] - bool ShutdownInProgress(); - - /// - /// Sends a message to everyone on the server - /// - /// The message to send - /// null on success, error message on failure - [OperationContract] - string WorldAnnounce(string msg); - } -} +using System.ServiceModel; + +namespace TGServiceInterface +{ + /// + /// The status of the DD instance + /// + public enum TGDreamDaemonStatus + { + /// + /// Server is not running + /// + Offline, + /// + /// Server is being rebooted + /// + HardRebooting, + /// + /// Server is running + /// + Online, + } + + /// + /// DreamDaemon's security level + /// + public enum TGDreamDaemonSecurity + { + /// + /// Server is unrestricted in terms of file access and shell commands + /// + Trusted = 0, + /// + /// Server will not be able to run shell commands or access files outside it's working directory + /// + Safe, + /// + /// Server will not be able to run shell commands or access anything but temporary files + /// + Ultrasafe + } + + /// + /// Interface for managing the actual BYOND game server + /// + [ServiceContract] + public interface ITGDreamDaemon + { + /// + /// Gets the status of DreamDaemon + /// + /// The appropriate TGDreamDaemonStatus + [OperationContract] + TGDreamDaemonStatus DaemonStatus(); + + /// + /// Returns a human readable string of the current server status + /// + /// If true, the status will include the server's current visibility and security levels + /// A human readable string of the current server status + [OperationContract] + string StatusString(bool includeMetaInfo); + + /// + /// Check if a call to Start will fail + /// Of course, be aware of race conditions with other control panels + /// + /// returns the error that would occur, null otherwise + [OperationContract] + string CanStart(); + + /// + /// Starts the server if it isn't running + /// + /// null on success or error message on failure + [OperationContract] + string Start(); + + /// + /// Immediately kills the server + /// + /// null on success or error message on failure + [OperationContract] + string Stop(); + + /// + /// Immediately kills and restarts the server + /// + /// null on success or error message on failure + [OperationContract] + string Restart(); + + /// + /// Restart the server after the currently running round ends + /// Has no effect if the server isn't running + /// + [OperationContract] + void RequestRestart(); + + /// + /// Stop the server after the currently running round ends + /// Has no effect if the server isn't running + /// + [OperationContract] + void RequestStop(); + + /// + /// Get the configured (not running) security level + /// + /// The configured (not running) security level + [OperationContract] + TGDreamDaemonSecurity SecurityLevel(); + + /// + /// Sets the security level of the server. Requires reboot to apply + /// Implies a call to RequestRestart() + /// note that anything higher than Trusted will disable interop from DD + /// + /// The new security level + /// True if the change was immediately applied, false if a graceful restart was queued + [OperationContract] + bool SetSecurityLevel(TGDreamDaemonSecurity level); + + /// + /// Get the configured port. Not necessarily the running port if it has since changed + /// + /// The configured port + [OperationContract] + ushort Port(); + + /// + /// Set the port to host DD on. Requires reboot to apply + /// Implies a call to RequestRestart() + /// + /// The new port + [OperationContract] + void SetPort(ushort new_port); + + /// + /// Check if the watchdog will start when the service starts + /// + /// true if autostart is enabled, false otherwise + [OperationContract] + bool Autostart(); + + /// + /// Set the autostart config + /// + /// true to start the watchdog with the service, false otherwise + [OperationContract] + void SetAutostart(bool on); + + /// + /// Check if the byond webclient is currently enabled for the server + /// + /// true if the webclient is enabled, false otherwise + [OperationContract] + bool Webclient(); + + /// + /// Set the webclient config. Calls + /// + /// true to enable the byond webclient for the server, false otherwise + [OperationContract] + void SetWebclient(bool on); + + /// + /// Checks if a server stop has bee requested + /// + /// true if RequestStop has been called since the last server start, false otherwise + [OperationContract] + bool ShutdownInProgress(); + + /// + /// Sends a message to everyone on the server + /// + /// The message to send + /// null on success, error message on failure + [OperationContract] + string WorldAnnounce(string msg); + + /// + /// Returns the number of connected players. Requires game to use API version >= 3.1.0.1 + /// + /// The number of connected players or -1 on error + [OperationContract] + int PlayerCount(); + } +} diff --git a/Test.dm b/Test.dm index 7df062b584..b6ae09dfd0 100644 --- a/Test.dm +++ b/Test.dm @@ -34,3 +34,13 @@ SERVER_TOOLS_CHAT_BROADCAST(checks_complete) SERVER_TOOLS_RELAY_BROADCAST(checks_complete) world.Reboot() + +var/list/clients = list() + +/client/New() + clients += src + return ..() + +/client/Del() + clients -= src + return ..()