From 2952e307b2b5c2b35f0af2041124c4c652bc6346 Mon Sep 17 00:00:00 2001 From: RaidMax Date: Mon, 2 Apr 2018 00:25:06 -0500 Subject: [PATCH] tweaked rcon throttle rate/made async increased cutoff for server overview messages dont print message if timed out --- Plugins/SimpleStats/Helpers/StatManager.cs | 11 +- Plugins/SimpleStats/StatsPlugin.csproj | 1 + Plugins/Welcome/Plugin.cs | 19 +- SharedLibrary/Commands/NativeCommands.cs | 69 ++---- SharedLibrary/Dtos/PlayerInfo.cs | 1 + SharedLibrary/RCON.cs | 163 ------------- SharedLibrary/RCon/Connection.cs | 226 ++++++++++++++++++ SharedLibrary/RCon/StaticHelpers.cs | 24 ++ SharedLibrary/Server.cs | 4 +- SharedLibrary/SharedLibrary.csproj | 3 +- SharedLibrary/Utilities.cs | 80 ++++--- WebfrontCore/Application/Manager.cs | 16 +- WebfrontCore/Application/Server.cs | 32 ++- WebfrontCore/Controllers/BaseController.cs | 19 +- WebfrontCore/Controllers/ClientController.cs | 13 + WebfrontCore/Controllers/ServerController.cs | 3 +- .../ViewComponents/ServerListViewComponent.cs | 3 +- .../Views/Client/Profile/Index.cshtml | 82 +++---- .../Views/Server/_ClientActivity.cshtml | 2 +- WebfrontCore/wwwroot/css/profile.css | 3 - WebfrontCore/wwwroot/js/profile.js | 4 + 21 files changed, 441 insertions(+), 337 deletions(-) delete mode 100644 SharedLibrary/RCON.cs create mode 100644 SharedLibrary/RCon/Connection.cs create mode 100644 SharedLibrary/RCon/StaticHelpers.cs diff --git a/Plugins/SimpleStats/Helpers/StatManager.cs b/Plugins/SimpleStats/Helpers/StatManager.cs index a6f66024d..0ef8070fc 100644 --- a/Plugins/SimpleStats/Helpers/StatManager.cs +++ b/Plugins/SimpleStats/Helpers/StatManager.cs @@ -255,8 +255,8 @@ namespace StatsPlugin.Helpers await statsSvc.ClientStatSvc.SaveChangesAsync(); } - statsSvc.KillStatsSvc.Insert(kill); - await statsSvc.KillStatsSvc.SaveChangesAsync(); + //statsSvc.KillStatsSvc.Insert(kill); + //await statsSvc.KillStatsSvc.SaveChangesAsync(); if (Plugin.Config.Configuration().EnableAntiCheat) { @@ -330,13 +330,10 @@ namespace StatsPlugin.Helpers if (streakMessage != string.Empty) await attacker.Tell(streakMessage); - - // immediately write changes in debug - //#if DEBUG + + // todo: do we want to save this immediately? var statsSvc = ContextThreads[serverId]; statsSvc.ClientStatSvc.SaveChanges(); - //statsSvc.ServerStatsSvc.SaveChanges(); - //#endif } /// diff --git a/Plugins/SimpleStats/StatsPlugin.csproj b/Plugins/SimpleStats/StatsPlugin.csproj index 17a78002d..48d1ee465 100644 --- a/Plugins/SimpleStats/StatsPlugin.csproj +++ b/Plugins/SimpleStats/StatsPlugin.csproj @@ -151,6 +151,7 @@ {d51eeceb-438a-47da-870f-7d7b41bc24d6} SharedLibrary + False diff --git a/Plugins/Welcome/Plugin.cs b/Plugins/Welcome/Plugin.cs index 0f2436199..fc685e66b 100644 --- a/Plugins/Welcome/Plugin.cs +++ b/Plugins/Welcome/Plugin.cs @@ -1,12 +1,9 @@ using System; -using SharedLibrary; -using System.Collections.Generic; -using SharedLibrary.Interfaces; using System.Threading.Tasks; -using SharedLibrary.Network; +using SharedLibrary; +using SharedLibrary.Interfaces; using SharedLibrary.Objects; -using SharedLibrary.Helpers; using SharedLibrary.Configuration; namespace Welcome_Plugin @@ -48,12 +45,12 @@ namespace Welcome_Plugin return "fourth"; case 5: return "fifth"; - /* case 100: - return "One-Hundreth (amazing!)"; - case 500: - return "you're really ^5dedicated ^7to this server! This is your ^5500th^7"; - case 1000: - return "you deserve a medal. it's your ^11000th^7";*/ + /* case 100: + return "One-Hundreth (amazing!)"; + case 500: + return "you're really ^5dedicated ^7to this server! This is your ^5500th^7"; + case 1000: + return "you deserve a medal. it's your ^11000th^7";*/ default: return connection.ToString() + Prefix; } diff --git a/SharedLibrary/Commands/NativeCommands.cs b/SharedLibrary/Commands/NativeCommands.cs index ca26dbd9b..54a6ce480 100644 --- a/SharedLibrary/Commands/NativeCommands.cs +++ b/SharedLibrary/Commands/NativeCommands.cs @@ -1,17 +1,14 @@ -using System; -using System.Collections.Generic; -using System.Text; -using System.Linq; -using System.Threading.Tasks; - -using SharedLibrary.Network; -using SharedLibrary.Helpers; -using SharedLibrary.Objects; -using SharedLibrary.Database; -using System.Data.Entity; +using SharedLibrary.Database; using SharedLibrary.Database.Models; -using SharedLibrary.Services; using SharedLibrary.Exceptions; +using SharedLibrary.Objects; +using SharedLibrary.Services; +using System; +using System.Collections.Generic; +using System.Data.Entity; +using System.Linq; +using System.Text; +using System.Threading.Tasks; namespace SharedLibrary.Commands { @@ -420,9 +417,6 @@ namespace SharedLibrary.Commands Player.Permission newPerm = Utilities.MatchPermission(E.Data); - if (newPerm == Player.Permission.Owner && E.Origin.Level != Player.Permission.Console) - newPerm = Player.Permission.Banned; - if (newPerm == Player.Permission.Owner && !E.Owner.Manager.GetApplicationSettings().Configuration().EnableMultipleOwners) { @@ -981,16 +975,16 @@ namespace SharedLibrary.Commands } } - public class CRestartServer : Command + public class CKillServer : Command { - public CRestartServer() : base("restartserver", "restart the server", "restart", Player.Permission.Administrator, false) + public CKillServer() : base("killserver", "kill the game server", "kill", Player.Permission.Administrator, false) { } public override async Task ExecuteAsync(Event E) { var gameserverProcesses = System.Diagnostics.Process.GetProcessesByName("iw4x"); - var currentProcess = gameserverProcesses.FirstOrDefault(g => g.GetCommandLine().Contains($"+set net_port {E.Owner.GetPort()}")); + var currentProcess = gameserverProcesses.FirstOrDefault(g => g.MainWindowTitle.Contains(E.Owner.Hostname)); if (currentProcess == null) { @@ -999,7 +993,6 @@ namespace SharedLibrary.Commands else { - var commandLine = currentProcess.GetCommandLine(); // attempt to kill it natively try { @@ -1021,51 +1014,17 @@ namespace SharedLibrary.Commands try { currentProcess.Kill(); + await E.Origin.Tell("Successfully killed server process"); } catch (Exception e) { - await E.Origin.Tell("Could not kill IW4x process"); + await E.Origin.Tell("Could not kill server process"); E.Owner.Logger.WriteDebug("Unable to kill process"); E.Owner.Logger.WriteDebug($"Exception: {e.Message}"); return; } - - try - { - - System.Diagnostics.Process process = new System.Diagnostics.Process(); - process.StartInfo.UseShellExecute = false; -#if !DEBUG - process.StartInfo.WorkingDirectory = E.Owner.WorkingDirectory; -#else - process.StartInfo.WorkingDirectory = @"C:\Users\User\Desktop\MW2"; -#endif - process.StartInfo.FileName = $"{process.StartInfo.WorkingDirectory}\\iw4x.exe"; - process.StartInfo.Arguments = commandLine.Substring(6); - - /*process.StartInfo.UserName = E.Owner.ServerConfig.RestartUsername; - - var pw = new System.Security.SecureString(); - foreach (char c in E.Owner.ServerConfig.RestartPassword) - pw.AppendChar(c); - - process.StartInfo.Password = pw; - */ - process.Start(); - } - - catch (Exception e) - { - await E.Origin.Tell("Could not start the IW4x process"); - E.Owner.Logger.WriteDebug("Unable to start process"); - E.Owner.Logger.WriteDebug($"Exception: {e.Message}"); - } } } - - } } } - - diff --git a/SharedLibrary/Dtos/PlayerInfo.cs b/SharedLibrary/Dtos/PlayerInfo.cs index 26489884e..c21bd6064 100644 --- a/SharedLibrary/Dtos/PlayerInfo.cs +++ b/SharedLibrary/Dtos/PlayerInfo.cs @@ -11,6 +11,7 @@ namespace SharedLibrary.Dtos public string Name { get; set; } public int ClientId { get; set; } public string Level { get; set; } + public int LevelInt { get; set; } public string IPAddress { get; set; } public long NetworkId { get; set; } public List Aliases { get; set; } diff --git a/SharedLibrary/RCON.cs b/SharedLibrary/RCON.cs deleted file mode 100644 index 6f2743226..000000000 --- a/SharedLibrary/RCON.cs +++ /dev/null @@ -1,163 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Net; -using System.Threading; -using System.Threading.Tasks; -using System.Text.RegularExpressions; -using System.Net.Sockets; - -using SharedLibrary.Objects; - -namespace SharedLibrary.Network -{ - public static class RCON - { - enum QueryType - { - GET_STATUS, - GET_INFO, - DVAR, - COMMAND, - } - - private static DateTime LastQuery; - - static string[] SendQuery(QueryType Type, Server QueryServer, string Parameters = "") - { - using (var ServerOOBConnection = new UdpClient()) - { - // prevent flooding - if ((DateTime.Now - LastQuery).TotalMilliseconds < 100) - Task.Delay(100).Wait(); - LastQuery = DateTime.Now; - - ServerOOBConnection.Client.SendTimeout = 1000; - ServerOOBConnection.Client.ReceiveTimeout = 1000; - var Endpoint = new IPEndPoint(IPAddress.Parse(QueryServer.GetIP()), QueryServer.GetPort()); - - string QueryString = String.Empty; - - switch (Type) - { - case QueryType.DVAR: - case QueryType.COMMAND: - QueryString = $"ÿÿÿÿrcon {QueryServer.Password} {Parameters}"; - break; - case QueryType.GET_STATUS: - QueryString = "ÿÿÿÿ getstatus"; - break; - } - - byte[] Payload = GetRequestBytes(QueryString); - - int attempts = 0; - retry: - - try - { - ServerOOBConnection.Connect(Endpoint); - ServerOOBConnection.Send(Payload, Payload.Length); - - byte[] ReceiveBuffer = new byte[8192]; - StringBuilder QueryResponseString = new StringBuilder(); - - do - { - ReceiveBuffer = ServerOOBConnection.Receive(ref Endpoint); - QueryResponseString.Append(Encoding.UTF7.GetString(ReceiveBuffer).TrimEnd('\0')); - } while (ServerOOBConnection.Available > 0 && ServerOOBConnection.Client.Connected); - - if (QueryResponseString.ToString().Contains("Invalid password")) - throw new Exceptions.NetworkException("RCON password is invalid"); - if (QueryResponseString.ToString().Contains("rcon_password")) - throw new Exceptions.NetworkException("RCON password has not been set"); - - int num = int.Parse("0a", System.Globalization.NumberStyles.AllowHexSpecifier); - string[] SplitResponse = QueryResponseString.ToString().Split(new char[] { (char)num }, StringSplitOptions.RemoveEmptyEntries); - return SplitResponse; - } - - catch (Exceptions.NetworkException e) - { - throw e; - } - - catch (Exception e) - { - attempts++; - if (attempts > 2) - { - var ne = new Exceptions.NetworkException("Could not communicate with the server"); - ne.Data["internal_exception"] = e.Message; - ne.Data["server_address"] = ServerOOBConnection.Client.RemoteEndPoint.ToString(); - throw ne; - } - - Thread.Sleep(1000); - goto retry; - } - } - } - - public static async Task> GetDvarAsync(this Server server, string dvarName) - { - string[] LineSplit = await Task.FromResult(SendQuery(QueryType.DVAR, server, dvarName)); - - if (LineSplit.Length != 3) - { - var e = new Exceptions.DvarException($"DVAR \"{dvarName}\" does not exist"); - e.Data["dvar_name"] = dvarName; - throw e; - } - - string[] ValueSplit = LineSplit[1].Split(new char[] { '"' }, StringSplitOptions.RemoveEmptyEntries); - - if (ValueSplit.Length != 5) - { - var e = new Exceptions.DvarException($"DVAR \"{dvarName}\" does not exist"); - e.Data["dvar_name"] = dvarName; - throw e; - } - - string DvarName = Regex.Replace(ValueSplit[0], @"\^[0-9]", ""); - string DvarCurrentValue = Regex.Replace(ValueSplit[2], @"\^[0-9]", ""); - string DvarDefaultValue = Regex.Replace(ValueSplit[4], @"\^[0-9]", ""); - - return new DVAR(DvarName) { Value = (T)Convert.ChangeType(DvarCurrentValue, typeof(T)) }; - } - - public static async Task SetDvarAsync(this Server server, string dvarName, object dvarValue) - { - await Task.FromResult(SendQuery(QueryType.DVAR, server, $"set {dvarName} {dvarValue}")); - } - - public static async Task ExecuteCommandAsync(this Server server, string commandName) - { - return await Task.FromResult(SendQuery(QueryType.COMMAND, server, commandName).Skip(1).ToArray()); - } - - public static async Task> GetStatusAsync(this Server server) - { -#if DEBUG && DEBUG_PLAYERS - string[] response = await Task.Run(() => System.IO.File.ReadAllLines("players.txt")); -#else - string[] response = await Task.FromResult(SendQuery(QueryType.DVAR, server, "status")); -#endif - return Utilities.PlayersFromStatus(response); - } - - static byte[] GetRequestBytes(string Request) - { - Byte[] initialRequestBytes = Encoding.Unicode.GetBytes(Request); - Byte[] fixedRequest = new Byte[initialRequestBytes.Length / 2]; - - for (int i = 0; i < initialRequestBytes.Length; i++) - if (initialRequestBytes[i] != 0) - fixedRequest[i / 2] = initialRequestBytes[i]; - - return fixedRequest; - } - } -} diff --git a/SharedLibrary/RCon/Connection.cs b/SharedLibrary/RCon/Connection.cs new file mode 100644 index 000000000..0b978c088 --- /dev/null +++ b/SharedLibrary/RCon/Connection.cs @@ -0,0 +1,226 @@ +using SharedLibrary.Exceptions; +using SharedLibrary.Interfaces; +using System; +using System.Net; +using System.Net.Sockets; +using System.Text; +using System.Threading; +using System.Threading.Tasks; + +namespace SharedLibrary.RCon +{ + class ConnectionState + { + public Socket Client { get; set; } + public const int BufferSize = 8192; + public byte[] Buffer = new byte[BufferSize]; + public StringBuilder ResponseString { get; set; } + + public ConnectionState() + { + ResponseString = new StringBuilder(); + } + } + + public class Connection + { + IPEndPoint Endpoint; + string RConPassword; + Socket ServerConnection; + ILogger Log; + int FailedConnections; + DateTime LastQuery; + string Response; + + ManualResetEvent OnConnected; + ManualResetEvent OnSent; + ManualResetEvent OnReceived; + + public Connection(string ipAddress, int port, string password, ILogger log) + { + Endpoint = new IPEndPoint(IPAddress.Parse(ipAddress), port); + RConPassword = password; + Log = log; + + OnConnected = new ManualResetEvent(false); + OnSent = new ManualResetEvent(false); + OnReceived = new ManualResetEvent(false); + + try + { + ServerConnection = new Socket(Endpoint.AddressFamily, SocketType.Dgram, ProtocolType.Udp); + ServerConnection.BeginConnect(Endpoint, new AsyncCallback(OnConnectedCallback), ServerConnection); + if (!OnConnected.WaitOne(StaticHelpers.SocketTimeout)) + throw new SocketException((int)SocketError.TimedOut); + FailedConnections = 0; + } + + catch (SocketException e) + { + throw new NetworkException(e.Message); + } + } + + ~Connection() + { + ServerConnection.Shutdown(SocketShutdown.Both); + ServerConnection.Close(); + ServerConnection.Dispose(); + } + + private void OnConnectedCallback(IAsyncResult ar) + { + var serverSocket = (Socket)ar.AsyncState; + + try + { + serverSocket.EndConnect(ar); +#if DEBUG + Log.WriteDebug($"Successfully initialized socket to {serverSocket.RemoteEndPoint}"); +#endif + OnConnected.Set(); + } + + catch (SocketException e) + { + throw new NetworkException($"Could not connect to RCon - {e.Message}"); + } + } + + private void OnSentCallback(IAsyncResult ar) + { + Socket serverConnection = (Socket)ar.AsyncState; + + try + { + int sentByteNum = serverConnection.EndSend(ar); + FailedConnections = 0; +#if DEBUG + Log.WriteDebug($"Sent {sentByteNum} bytes to server"); +#endif + OnSent.Set(); + } + + catch (Exception e) + { + FailedConnections++; + if (FailedConnections < 1) + Log.WriteWarning($"Could not send RCon data to server - {e.Message}"); + //throw new NetworkException($"Could not send RCon message to server - {e.Message}"); + } + } + + private void OnReceivedCallback(IAsyncResult ar) + { + var connectionState = (ConnectionState)ar.AsyncState; + var serverConnection = connectionState.Client; + + try + { + int bytesRead = serverConnection.EndReceive(ar); + FailedConnections = 0; + + if (bytesRead > 0) + { +#if DEBUG + Log.WriteDebug($"Received {bytesRead} bytes from server"); +#endif + connectionState.ResponseString.Append(Encoding.UTF7.GetString(connectionState.Buffer, 0, bytesRead).TrimEnd('\0')); + + if (serverConnection.Available > 0) + { + ServerConnection.BeginReceive(connectionState.Buffer, 0, connectionState.Buffer.Length, 0, + new AsyncCallback(OnReceivedCallback), connectionState); + } + else + { + Response = connectionState.ResponseString.ToString(); + OnReceived.Set(); + } + } + else + { + OnReceived.Set(); + } + } + + catch (Exception e) + { + FailedConnections++; + if (FailedConnections < 1) + Log.WriteWarning($"Could not receive data from server - {e.Message}"); + //throw new NetworkException($"Could not recieve message from server - {e.Message}"); + } + } + + public async Task SendQueryAsync(StaticHelpers.QueryType type, string parameters = "") + { + if ((DateTime.Now - LastQuery).TotalMilliseconds < 150) + { + await Task.Delay(150); + LastQuery = DateTime.Now; + } + + OnSent.Reset(); + OnReceived.Reset(); + string queryString = ""; + + switch (type) + { + case StaticHelpers.QueryType.DVAR: + case StaticHelpers.QueryType.COMMAND: + queryString = $"ÿÿÿÿrcon {RConPassword} {parameters}"; + break; + case StaticHelpers.QueryType.GET_STATUS: + queryString = "ÿÿÿÿgetstatus"; + break; + } + + byte[] payload = Encoding.Default.GetBytes(queryString); + retrySend: + ServerConnection.BeginSend(payload, 0, payload.Length, 0, new AsyncCallback(OnSentCallback), ServerConnection); + bool success = await Task.FromResult(OnSent.WaitOne(StaticHelpers.SocketTimeout)); + + if (!success) + { + FailedConnections++; + if (FailedConnections < 4) + goto retrySend; + else + throw new NetworkException($"Could not send data to server - {new SocketException((int)SocketError.TimedOut).Message}"); + } + + var connectionState = new ConnectionState + { + Client = ServerConnection + }; + + retryReceive: + ServerConnection.BeginReceive(connectionState.Buffer, 0, connectionState.Buffer.Length, 0, + new AsyncCallback(OnReceivedCallback), connectionState); + success = await Task.FromResult(OnReceived.WaitOne(StaticHelpers.SocketTimeout)); + + if (!success) + { + FailedConnections++; + if (FailedConnections < 4) + goto retryReceive; + else + throw new NetworkException($"Could not send data to server - {new SocketException((int)SocketError.TimedOut).Message}"); + } + + string queryResponse = Response;//connectionState.ResponseString.ToString(); + + if (queryResponse.Contains("Invalid password")) + throw new NetworkException("RCON password is invalid"); + if (queryResponse.ToString().Contains("rcon_password")) + throw new NetworkException("RCON password has not been set"); + + string[] splitResponse = queryResponse.Split(new char[] + { + StaticHelpers.SeperatorChar + }, StringSplitOptions.RemoveEmptyEntries); + return splitResponse; + } + } +} diff --git a/SharedLibrary/RCon/StaticHelpers.cs b/SharedLibrary/RCon/StaticHelpers.cs new file mode 100644 index 000000000..ac2759e6f --- /dev/null +++ b/SharedLibrary/RCon/StaticHelpers.cs @@ -0,0 +1,24 @@ +using SharedLibrary.Objects; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Text.RegularExpressions; +using System.Threading.Tasks; + +namespace SharedLibrary.RCon +{ + public static class StaticHelpers + { + public enum QueryType + { + GET_STATUS, + GET_INFO, + DVAR, + COMMAND, + } + + public static char SeperatorChar = (char)int.Parse("0a", System.Globalization.NumberStyles.AllowHexSpecifier); + public static readonly TimeSpan SocketTimeout = new TimeSpan(0, 0, 1); + } +} diff --git a/SharedLibrary/Server.cs b/SharedLibrary/Server.cs index 126063ce8..0bd28bc93 100644 --- a/SharedLibrary/Server.cs +++ b/SharedLibrary/Server.cs @@ -5,7 +5,7 @@ using System.Runtime.InteropServices; using System.Text; using System.Threading; -using SharedLibrary.Network; +using SharedLibrary.RCon; using SharedLibrary.Commands; using System.Threading.Tasks; using SharedLibrary.Helpers; @@ -36,6 +36,7 @@ namespace SharedLibrary Manager = mgr; Logger = Manager.GetLogger(); ServerConfig = config; + RemoteConnection = new RCon.Connection(IP, Port, Password, Logger); Players = new List(new Player[18]); Reports = new List(); @@ -312,6 +313,7 @@ namespace SharedLibrary public bool Throttled { get; protected set; } public bool CustomCallback { get; protected set; } public string WorkingDirectory { get; protected set; } + public RCon.Connection RemoteConnection { get; protected set; } // Internal protected string IP; diff --git a/SharedLibrary/SharedLibrary.csproj b/SharedLibrary/SharedLibrary.csproj index 758b9f1e6..c0cecb071 100644 --- a/SharedLibrary/SharedLibrary.csproj +++ b/SharedLibrary/SharedLibrary.csproj @@ -196,7 +196,8 @@ - + + diff --git a/SharedLibrary/Utilities.cs b/SharedLibrary/Utilities.cs index 355fcf7c9..aabf8aff3 100644 --- a/SharedLibrary/Utilities.cs +++ b/SharedLibrary/Utilities.cs @@ -11,6 +11,9 @@ using static SharedLibrary.Server; using System.Reflection; using System.IO; using System.Diagnostics; +using System.Threading.Tasks; +using static SharedLibrary.RCon.StaticHelpers; +using System.Runtime.InteropServices; namespace SharedLibrary { @@ -359,36 +362,6 @@ namespace SharedLibrary }; } - /*https://stackoverflow.com/questions/2633628/can-i-get-command-line-arguments-of-other-processes-from-net-c*/ - // Define an extension method for type System.Process that returns the command - // line via WMI. - public static string GetCommandLine(this Process process) - { - string cmdLine = null; - using (var searcher = new ManagementObjectSearcher( - $"SELECT CommandLine FROM Win32_Process WHERE ProcessId = {process.Id}")) - { - // By definition, the query returns at most 1 match, because the process - // is looked up by ID (which is unique by definition). - var matchEnum = searcher.Get().GetEnumerator(); - if (matchEnum.MoveNext()) // Move to the 1st item. - { - cmdLine = matchEnum.Current["CommandLine"]?.ToString(); - } - } - if (cmdLine == null) - { - // Not having found a command line implies 1 of 2 exceptions, which the - // WMI query masked: - // An "Access denied" exception due to lack of privileges. - // A "Cannot process request because the process () has exited." - // exception due to the process having terminated. - // We provoke the same exception again simply by accessing process.MainModule. - var dummy = process.MainModule; // Provoke exception. - } - return cmdLine; - } - public static bool IsPrivileged(this Player p) => p.Level > Player.Permission.User; public static bool PromptBool(string question) @@ -409,5 +382,52 @@ namespace SharedLibrary return response; } + + public static async Task> GetDvarAsync(this Server server, string dvarName) + { + string[] LineSplit = await server.RemoteConnection.SendQueryAsync(QueryType.DVAR, dvarName); + + if (LineSplit.Length != 3) + { + var e = new Exceptions.DvarException($"DVAR \"{dvarName}\" does not exist"); + e.Data["dvar_name"] = dvarName; + throw e; + } + + string[] ValueSplit = LineSplit[1].Split(new char[] { '"' }, StringSplitOptions.RemoveEmptyEntries); + + if (ValueSplit.Length != 5) + { + var e = new Exceptions.DvarException($"DVAR \"{dvarName}\" does not exist"); + e.Data["dvar_name"] = dvarName; + throw e; + } + + string DvarName = Regex.Replace(ValueSplit[0], @"\^[0-9]", ""); + string DvarCurrentValue = Regex.Replace(ValueSplit[2], @"\^[0-9]", ""); + string DvarDefaultValue = Regex.Replace(ValueSplit[4], @"\^[0-9]", ""); + + return new DVAR(DvarName) { Value = (T)Convert.ChangeType(DvarCurrentValue, typeof(T)) }; + } + + public static async Task SetDvarAsync(this Server server, string dvarName, object dvarValue) + { + await server.RemoteConnection.SendQueryAsync(QueryType.DVAR, $"set {dvarName} {dvarValue}"); + } + + public static async Task ExecuteCommandAsync(this Server server, string commandName) + { + return (await server.RemoteConnection.SendQueryAsync(QueryType.COMMAND, commandName)).Skip(1).ToArray(); + } + + public static async Task> GetStatusAsync(this Server server) + { +#if DEBUG && DEBUG_PLAYERS + string[] response = await Task.Run(() => System.IO.File.ReadAllLines("players.txt")); +#else + string[] response = await server.RemoteConnection.SendQueryAsync(QueryType.DVAR, "status"); +#endif + return Utilities.PlayersFromStatus(response); + } } } diff --git a/WebfrontCore/Application/Manager.cs b/WebfrontCore/Application/Manager.cs index ce20acbd8..16c5e8794 100644 --- a/WebfrontCore/Application/Manager.cs +++ b/WebfrontCore/Application/Manager.cs @@ -25,7 +25,7 @@ namespace IW4MAdmin { private List _servers; public List Servers => _servers.OrderByDescending(s => s.ClientNum).ToList(); - public Dictionary PrivilegedClients { get; set; } + public Dictionary PrivilegedClients { get; set; } public ILogger Logger { get; private set; } public bool Running { get; private set; } public EventHandler ServerEventOccurred { get; private set; } @@ -54,7 +54,7 @@ namespace IW4MAdmin ClientSvc = new ClientService(); AliasSvc = new AliasService(); PenaltySvc = new PenaltyService(); - PrivilegedClients = new Dictionary(); + PrivilegedClients = new Dictionary(); ServerEventOccurred += EventAPI.OnServerEventOccurred; ConfigHandler = new BaseConfigurationHandler("IW4MAdminSettings"); } @@ -78,13 +78,17 @@ namespace IW4MAdmin { #region DATABASE var ipList = (await ClientSvc.Find(c => c.Level > Player.Permission.Trusted)) - .Select(c => new { c.IPAddress, c.ClientId }); + .Select(c => new { c.IPAddress, c.ClientId, c.Level }); foreach (var a in ipList) { try { - PrivilegedClients.Add(a.IPAddress, a.ClientId); + PrivilegedClients.Add(a.IPAddress, new Player() + { + ClientId = a.ClientId, + Level = a.Level + }); } catch (ArgumentException) @@ -208,7 +212,7 @@ namespace IW4MAdmin Commands.Add(new CIP()); Commands.Add(new CMask()); Commands.Add(new CPruneAdmins()); - Commands.Add(new CRestartServer()); + Commands.Add(new CKillServer()); foreach (Command C in SharedLibrary.Plugins.PluginImporter.ActiveCommands) Commands.Add(C); @@ -238,7 +242,7 @@ namespace IW4MAdmin else { Status.Update(new Task(() => { return (Status.Dependant as Server).ProcessUpdatesAsync(Status.GetToken()).Result; })); - if (Status.RunAverage > 1000 + UPDATE_FREQUENCY) + if (Status.RunAverage > 1000 + UPDATE_FREQUENCY && !(Status.Dependant as Server).Throttled) Logger.WriteWarning($"Update task average execution is longer than desired for {(Status.Dependant as Server)} [{Status.RunAverage}ms]"); } } diff --git a/WebfrontCore/Application/Server.cs b/WebfrontCore/Application/Server.cs index aee4255d8..72d59b683 100644 --- a/WebfrontCore/Application/Server.cs +++ b/WebfrontCore/Application/Server.cs @@ -4,18 +4,18 @@ using System.Threading; using System.IO; using System.Linq; using System.Threading.Tasks; +using System.Text.RegularExpressions; using SharedLibrary; -using SharedLibrary.Network; using SharedLibrary.Interfaces; using SharedLibrary.Objects; -using System.Text.RegularExpressions; using SharedLibrary.Services; using SharedLibrary.Database.Models; using SharedLibrary.Dtos; -using WebfrontCore.Application.Misc; using SharedLibrary.Configuration; +using WebfrontCore.Application.Misc; + namespace IW4MAdmin { public class IW4MServer : Server @@ -386,7 +386,19 @@ namespace IW4MAdmin async Task PollPlayersAsync() { var now = DateTime.Now; - var CurrentPlayers = await this.GetStatusAsync(); + + List CurrentPlayers = null; + try + { + CurrentPlayers = await this.GetStatusAsync(); + } + + // when the server has lost connection + catch (SharedLibrary.Exceptions.NetworkException) + { + Throttled = true; + return ClientNum; + } #if DEBUG Logger.WriteInfo($"Polling players took {(DateTime.Now - now).TotalMilliseconds}ms"); #endif @@ -659,7 +671,7 @@ namespace IW4MAdmin //#else } #if DEBUG - LogFile = new RemoteFile("https://raidmax.org/IW4MAdmin/getlog.php"); + //LogFile = new RemoteFile("https://raidmax.org/IW4MAdmin/getlog.php"); #endif Logger.WriteInfo($"Log file is {logPath}"); #if !DEBUG @@ -776,16 +788,20 @@ namespace IW4MAdmin CurrentMap = Maps.Find(m => m.Name == mapname) ?? new Map() { Alias = mapname, Name = mapname }; // todo: make this more efficient - ((ApplicationManager)(Manager)).PrivilegedClients = new Dictionary(); + ((ApplicationManager)(Manager)).PrivilegedClients = new Dictionary(); var ClientSvc = new ClientService(); var ipList = (await ClientSvc.Find(c => c.Level > Player.Permission.Trusted)) - .Select(c => new { c.IPAddress, c.ClientId }); + .Select(c => new { c.IPAddress, c.ClientId, c.Level }); foreach (var a in ipList) { try { - ((ApplicationManager)(Manager)).PrivilegedClients.Add(a.IPAddress, a.ClientId); + ((ApplicationManager)(Manager)).PrivilegedClients.Add(a.IPAddress, new Player() + { + ClientId = a.ClientId, + Level = a.Level + }); } catch (ArgumentException) diff --git a/WebfrontCore/Controllers/BaseController.cs b/WebfrontCore/Controllers/BaseController.cs index ee8d1fb42..38a354ba2 100644 --- a/WebfrontCore/Controllers/BaseController.cs +++ b/WebfrontCore/Controllers/BaseController.cs @@ -24,7 +24,9 @@ namespace WebfrontCore.Controllers try { - User.ClientId = Manager.PrivilegedClients[context.HttpContext.Connection.RemoteIpAddress.ToString().ConvertToIP()]; + var client = Manager.PrivilegedClients[context.HttpContext.Connection.RemoteIpAddress.ToString().ConvertToIP()]; + User.ClientId = client.ClientId; + User.Level = client.Level; } catch (KeyNotFoundException) @@ -36,11 +38,16 @@ namespace WebfrontCore.Controllers User.ClientId >= 0; ViewBag.Authorized = Authorized; ViewBag.Url = Startup.Configuration["Web:Address"]; - string inviteLink = Manager.GetApplicationSettings().Configuration().DiscordInviteCode; - if (inviteLink != null) - ViewBag.DiscordLink = inviteLink.Contains("https") ? inviteLink : $"https://discordapp.com/invite/{inviteLink}"; - else - ViewBag.DiscordLink = ""; + ViewBag.User = User; + + if (Manager.GetApplicationSettings().Configuration().EnableDiscordLink) + { + string inviteLink = Manager.GetApplicationSettings().Configuration().DiscordInviteCode; + if (inviteLink != null) + ViewBag.DiscordLink = inviteLink.Contains("https") ? inviteLink : $"https://discordapp.com/invite/{inviteLink}"; + else + ViewBag.DiscordLink = ""; + } base.OnActionExecuting(context); } } diff --git a/WebfrontCore/Controllers/ClientController.cs b/WebfrontCore/Controllers/ClientController.cs index 7020b6f0e..910aa6ebd 100644 --- a/WebfrontCore/Controllers/ClientController.cs +++ b/WebfrontCore/Controllers/ClientController.cs @@ -18,6 +18,7 @@ namespace WebfrontCore.Controllers { Name = client.Name, Level = client.Level.ToString(), + LevelInt = (int)client.Level, ClientId = client.ClientId, IPAddress = client.IPAddressString, NetworkId = client.NetworkId, @@ -54,6 +55,17 @@ namespace WebfrontCore.Controllers When = DateTime.MinValue }); + if (Authorized) + { + clientDto.Meta.AddRange(client.AliasLink.Children.Select(a => new ProfileMeta() + { + Key = "AliasEvent", + Value = $"Connected with name {a.Name}", + Sensitive = true, + When = a.DateAdded + })); + } + clientDto.Meta.AddRange(Authorized ? meta : meta.Where(m => !m.Sensitive)); clientDto.Meta.AddRange(Authorized ? penaltyMeta : penaltyMeta.Where(m => !m.Sensitive)); clientDto.Meta.AddRange(Authorized ? administeredPenaltiesMeta : administeredPenaltiesMeta.Where(m => !m.Sensitive)); @@ -105,6 +117,7 @@ namespace WebfrontCore.Controllers { Name = c.Name, Level = c.Level.ToString(), + LevelInt = (int)c.Level, ClientId = c.ClientId, LastSeen = Utilities.GetTimePassed(c.LastConnection, false) }) diff --git a/WebfrontCore/Controllers/ServerController.cs b/WebfrontCore/Controllers/ServerController.cs index fadf0a61d..caad8ab9b 100644 --- a/WebfrontCore/Controllers/ServerController.cs +++ b/WebfrontCore/Controllers/ServerController.cs @@ -31,7 +31,8 @@ namespace WebfrontCore.Controllers { Name = p.Name, ClientId = p.ClientId, - Level = p.Level.ToString() + Level = p.Level.ToString(), + LevelInt = (int)p.Level }).ToList(), ChatHistory = s.ChatHistory.OrderBy(c => c.Time).Take((int)Math.Ceiling(s.ClientNum / 2.0)).ToArray(), PlayerHistory = s.PlayerHistory.ToArray() diff --git a/WebfrontCore/ViewComponents/ServerListViewComponent.cs b/WebfrontCore/ViewComponents/ServerListViewComponent.cs index 1a9559eea..41f80ecc1 100644 --- a/WebfrontCore/ViewComponents/ServerListViewComponent.cs +++ b/WebfrontCore/ViewComponents/ServerListViewComponent.cs @@ -27,7 +27,8 @@ namespace WebfrontCore.ViewComponents { Name = p.Name, ClientId = p.ClientId, - Level = p.Level.ToString() + Level = p.Level.ToString(), + LevelInt = (int)p.Level }).ToList(), ChatHistory = s.ChatHistory.ToArray() }).ToList(); diff --git a/WebfrontCore/Views/Client/Profile/Index.cshtml b/WebfrontCore/Views/Client/Profile/Index.cshtml index d9f237b08..070b75c51 100644 --- a/WebfrontCore/Views/Client/Profile/Index.cshtml +++ b/WebfrontCore/Views/Client/Profile/Index.cshtml @@ -2,7 +2,6 @@ @{ string match = System.Text.RegularExpressions.Regex.Match(Model.Name.ToUpper(), "[A-Z]").Value; string shortCode = match == string.Empty ? "?" : match; - string marginClass = Model.Aliases.Count > 0 ? "mr-4" : ""; }
@@ -12,49 +11,46 @@
-

- - @Model.Name - @if (Model.Aliases.Count > 0 || ViewBag.Authorized) - { - - } - - @{ - if (ViewBag.Authorized) - { - if (Model.Level == SharedLibrary.Objects.Player.Permission.User.ToString()) - { - - - } - - if (Model.Level == SharedLibrary.Objects.Player.Permission.Banned.ToString()) - { - - } - } - } - - -

-
- @{ - foreach (string alias in Model.Aliases) - { - @alias
- } - - if (ViewBag.Authorized) - { - foreach (string ip in Model.IPs) - { - @ip
- } - - } - } +
+ @Model.Name
+ @{ + if (ViewBag.Authorized) + { +
+
+ + @if (Model.LevelInt < (int)ViewBag.User.Level && + (SharedLibrary.Objects.Player.Permission)Model.LevelInt != SharedLibrary.Objects.Player.Permission.Banned) + { + + } + + @if (Model.LevelInt < (int)ViewBag.User.Level && + (SharedLibrary.Objects.Player.Permission)Model.LevelInt == SharedLibrary.Objects.Player.Permission.Banned) + { + + } +
+ +
+ @{ + foreach (string alias in Model.Aliases) + { + @alias
+ } + + if (ViewBag.Authorized) + { + foreach (string ip in Model.IPs) + { + @ip
+ } + } + } +
+ } + }
@Model.Level
diff --git a/WebfrontCore/Views/Server/_ClientActivity.cshtml b/WebfrontCore/Views/Server/_ClientActivity.cshtml index 2a40512d7..4c798c6a9 100644 --- a/WebfrontCore/Views/Server/_ClientActivity.cshtml +++ b/WebfrontCore/Views/Server/_ClientActivity.cshtml @@ -20,7 +20,7 @@ } if (Model.ChatHistory[i].Message != "CONNECTED" && Model.ChatHistory[i].Message != "DISCONNECTED") { - @Model.ChatHistory[i].Name — @message.Substring(0, Math.Min(50, message.Length))
+ @Model.ChatHistory[i].Name — @message.Substring(0, Math.Min(65, message.Length))
} } } diff --git a/WebfrontCore/wwwroot/css/profile.css b/WebfrontCore/wwwroot/css/profile.css index fd0894c29..0f7a9cdf5 100644 --- a/WebfrontCore/wwwroot/css/profile.css +++ b/WebfrontCore/wwwroot/css/profile.css @@ -96,9 +96,6 @@ } #profile_aliases_btn { - position: relative; - top: -2px; - font-size: 0.5em; color: rgb(0, 122, 204); cursor: pointer; } diff --git a/WebfrontCore/wwwroot/js/profile.js b/WebfrontCore/wwwroot/js/profile.js index 6f9e5cd66..635b4380f 100644 --- a/WebfrontCore/wwwroot/js/profile.js +++ b/WebfrontCore/wwwroot/js/profile.js @@ -9,6 +9,7 @@ $(document).ready(function () { const aliases = $('#profile_aliases').text().trim(); if (aliases && aliases.length !== 0) { $('#profile_aliases').slideToggle(150); + $(this).toggleClass('oi-caret-top'); } }); @@ -177,6 +178,9 @@ function loadMeta(meta) { eventString = `
${penaltyToName(meta.value.type)} ${meta.value.offenderName} for ${meta.value.offense}
`; } } + else if (meta.key.includes("Alias")) { + eventString = `
${meta.value}
`; + } // it's a message else if (meta.key.includes("Event")) { eventString = `
> ${meta.value}
`;