diff --git a/Application/ApplicationManager.cs b/Application/ApplicationManager.cs index 2ff9edb54..693ee5bdb 100644 --- a/Application/ApplicationManager.cs +++ b/Application/ApplicationManager.cs @@ -36,12 +36,8 @@ namespace IW4MAdmin.Application public DateTime StartTime { get; private set; } public string Version => Assembly.GetEntryAssembly().GetName().Version.ToString(); - public IList AdditionalRConParsers => _additionalRConParsers.ToList(); - - public IList AdditionalEventParsers => _additionalEventParsers.ToList(); - - private readonly IList _additionalRConParsers; - private readonly IList _additionalEventParsers; + public IList AdditionalRConParsers { get; } + public IList AdditionalEventParsers { get; } static ApplicationManager Instance; readonly List TaskStatuses; @@ -70,8 +66,8 @@ namespace IW4MAdmin.Application StartTime = DateTime.UtcNow; OnQuit = new ManualResetEventSlim(); PageList = new PageList(); - _additionalEventParsers = new List(); - _additionalRConParsers = new List(); + AdditionalEventParsers = new List(); + AdditionalRConParsers = new List(); OnServerEvent += OnGameEvent; OnServerEvent += EventApi.OnGameEvent; } diff --git a/Application/IW4MServer.cs b/Application/IW4MServer.cs index 87f43a72a..4496a162d 100644 --- a/Application/IW4MServer.cs +++ b/Application/IW4MServer.cs @@ -665,13 +665,17 @@ namespace IW4MAdmin public async Task Initialize() { + //RemoteConnection.SetConfiguration(Manager.AdditionalRConParsers.First().Configuration); + RconParser = ServerConfig.UseT6MParser ? (IRConParser)new T6MRConParser() : - new IW3RConParser(); + new IW4RConParser(); + + RemoteConnection.SetConfiguration(RconParser.Configuration); var version = await this.GetDvarAsync("version"); Version = version.Value; - GameName = Utilities.GetGame(version.Value); + GameName = Utilities.GetGame(version?.Value); if (GameName == Game.IW4) { @@ -702,7 +706,7 @@ namespace IW4MAdmin RconParser = Manager.AdditionalRConParsers.FirstOrDefault(_parser => (_parser as DynamicRConParser).Version == version.Value) ?? RconParser; } - var infoResponse = await this.GetInfoAsync(); + var infoResponse = RconParser.Configuration.CommandPrefixes.RConGetInfo != null ? await this.GetInfoAsync() : null; // this is normally slow, but I'm only doing it because different games have different prefixes var hostname = infoResponse == null ? (await this.GetDvarAsync("sv_hostname")).Value : diff --git a/Application/RconParsers/DynamicRConParserConfiguration.cs b/Application/RconParsers/DynamicRConParserConfiguration.cs index fa3b490f0..dae52b0fe 100644 --- a/Application/RconParsers/DynamicRConParserConfiguration.cs +++ b/Application/RconParsers/DynamicRConParserConfiguration.cs @@ -9,5 +9,6 @@ namespace IW4MAdmin.Application.RconParsers public CommandPrefix CommandPrefixes { get; set; } public Server.Game GameName { get; set; } public ParserRegex Status { get; set; } = new ParserRegex(); + public ParserRegex Dvar { get; set; } = new ParserRegex(); } } diff --git a/Application/RconParsers/IW4RConParser.cs b/Application/RconParsers/IW4RConParser.cs index 014c3eedb..17fe16290 100644 --- a/Application/RconParsers/IW4RConParser.cs +++ b/Application/RconParsers/IW4RConParser.cs @@ -23,7 +23,11 @@ namespace IW4MAdmin.Application.RconParsers Say = "sayraw {0}", Kick = "clientkick {0} \"{1}\"", Ban = "clientkick {0} \"{1}\"", - TempBan = "tempbanclient {0} \"{1}\"" + TempBan = "tempbanclient {0} \"{1}\"", + RConQuery = "ÿÿÿÿrcon {0} {1}", + RConGetStatus = "ÿÿÿÿgetstatus", + RConGetInfo = "ÿÿÿÿgetinfo", + RConResponse = "ÿÿÿÿprint", }, GameName = Server.Game.IW4 }; @@ -35,6 +39,13 @@ namespace IW4MAdmin.Application.RconParsers Configuration.Status.GroupMapping.Add(ParserRegex.GroupType.RConNetworkId, 4); Configuration.Status.GroupMapping.Add(ParserRegex.GroupType.RConName, 5); Configuration.Status.GroupMapping.Add(ParserRegex.GroupType.RConIpAddress, 7); + + Configuration.Dvar.Pattern = "^\"(.+)\" is: \"(.+)\" default: \"(.+)\"\n(?:latched: \"(.+)\"\n)? *(.+)$"; + Configuration.Dvar.GroupMapping.Add(ParserRegex.GroupType.RConDvarName, 1); + Configuration.Dvar.GroupMapping.Add(ParserRegex.GroupType.RConDvarValue, 2); + Configuration.Dvar.GroupMapping.Add(ParserRegex.GroupType.RConDvarDefaultValue, 3); + Configuration.Dvar.GroupMapping.Add(ParserRegex.GroupType.RConDvarLatchedValue, 4); + Configuration.Dvar.GroupMapping.Add(ParserRegex.GroupType.RConDvarDomain, 5); } public IRConParserConfiguration Configuration { get; set; } @@ -47,32 +58,37 @@ namespace IW4MAdmin.Application.RconParsers public async Task> GetDvarAsync(Connection connection, string dvarName) { - string[] LineSplit = await connection.SendQueryAsync(StaticHelpers.QueryType.DVAR, dvarName); + string[] lineSplit = await connection.SendQueryAsync(StaticHelpers.QueryType.DVAR, dvarName); + string response = string.Join('\n', lineSplit.Skip(1)); - if (LineSplit.Length < 3) + if (lineSplit[0] != Configuration.CommandPrefixes.RConResponse) { - var e = new DvarException($"DVAR \"{dvarName}\" does not exist"); - e.Data["dvar_name"] = dvarName; - throw e; + throw new DvarException($"Could not retrieve DVAR \"{dvarName}\""); } - // todo: can this be made more portable and modifiable from plugin - string[] ValueSplit = LineSplit[1].Split(new char[] { '"' }, StringSplitOptions.RemoveEmptyEntries); - - if (ValueSplit.Length < 5) + if (response.Contains("Unknown command")) { - var e = new DvarException($"DVAR \"{dvarName}\" does not exist"); - e.Data["dvar_name"] = dvarName; - throw e; + throw new DvarException($"DVAR \"{dvarName}\" does not exist"); } - string DvarName = Regex.Replace(ValueSplit[0], @"\^[0-9]", ""); - string DvarCurrentValue = Regex.Replace(ValueSplit[2], @"\^[0-9]", ""); - string DvarDefaultValue = Regex.Replace(ValueSplit[4], @"\^[0-9]", ""); + var match = Regex.Match(response, Configuration.Dvar.Pattern); - return new Dvar(DvarName) + if (!match.Success) { - Value = (T)Convert.ChangeType(DvarCurrentValue, typeof(T)) + throw new DvarException($"Could not retrieve DVAR \"{dvarName}\""); + } + + string value = match.Groups[Configuration.Dvar.GroupMapping[ParserRegex.GroupType.RConDvarValue]].Value.StripColors(); + string defaultValue = match.Groups[Configuration.Dvar.GroupMapping[ParserRegex.GroupType.RConDvarDefaultValue]].Value.StripColors(); + string latchedValue = match.Groups[Configuration.Dvar.GroupMapping[ParserRegex.GroupType.RConDvarLatchedValue]].Value.StripColors(); + + return new Dvar() + { + Name = match.Groups[Configuration.Dvar.GroupMapping[ParserRegex.GroupType.RConDvarName]].Value.StripColors(), + Value = string.IsNullOrEmpty(value) ? default(T) : (T)Convert.ChangeType(value, typeof(T)), + DefaultValue = string.IsNullOrEmpty(defaultValue) ? default(T) : (T)Convert.ChangeType(defaultValue, typeof(T)), + LatchedValue = string.IsNullOrEmpty(latchedValue) ? default(T) : (T)Convert.ChangeType(latchedValue, typeof(T)), + Domain = match.Groups[Configuration.Dvar.GroupMapping[ParserRegex.GroupType.RConDvarDomain]].Value.StripColors() }; } diff --git a/Application/RconParsers/T6MRConParser.cs b/Application/RconParsers/T6MRConParser.cs index 35dd41b8e..d32a5ab7a 100644 --- a/Application/RconParsers/T6MRConParser.cs +++ b/Application/RconParsers/T6MRConParser.cs @@ -60,9 +60,10 @@ namespace IW4MAdmin.Application.RconParsers string DvarName = dvarName; string DvarCurrentValue = Regex.Replace(ValueSplit[1], @"\^[0-9]", ""); - return new Dvar(DvarName) + return new Dvar() { - Value = (T)Convert.ChangeType(DvarCurrentValue, typeof(T)) + Value = (T)Convert.ChangeType(DvarCurrentValue, typeof(T)), + Name = DvarName }; } diff --git a/SharedLibraryCore/Dvar.cs b/SharedLibraryCore/Dvar.cs index 8c8929c87..dcbe09707 100644 --- a/SharedLibraryCore/Dvar.cs +++ b/SharedLibraryCore/Dvar.cs @@ -2,12 +2,10 @@ { public class Dvar { - public string Name { get; private set; } - public T Value; - - public Dvar(string name) - { - Name = name; - } + public string Name { get; set; } + public T Value { get; set; } + public T DefaultValue { get; set; } + public T LatchedValue { get; set; } + public string Domain { get; set; } } } diff --git a/SharedLibraryCore/Helpers/ParserRegex.cs b/SharedLibraryCore/Helpers/ParserRegex.cs index b57c4f370..390e1ef62 100644 --- a/SharedLibraryCore/Helpers/ParserRegex.cs +++ b/SharedLibraryCore/Helpers/ParserRegex.cs @@ -28,6 +28,11 @@ namespace SharedLibraryCore.Interfaces RConNetworkId = 103, RConName = 104, RConIpAddress = 105, + RConDvarName = 106, + RConDvarValue = 107, + RConDvarDefaultValue = 108, + RConDvarLatchedValue = 109, + RConDvarDomain = 110, AdditionalGroup = 200 } public string Pattern { get; set; } diff --git a/SharedLibraryCore/Interfaces/IRConParserConfiguration.cs b/SharedLibraryCore/Interfaces/IRConParserConfiguration.cs index 59853e5a2..18d412443 100644 --- a/SharedLibraryCore/Interfaces/IRConParserConfiguration.cs +++ b/SharedLibraryCore/Interfaces/IRConParserConfiguration.cs @@ -7,5 +7,6 @@ namespace SharedLibraryCore.Interfaces CommandPrefix CommandPrefixes { get; set; } Server.Game GameName { get; set; } ParserRegex Status { get; set; } + ParserRegex Dvar { get; set; } } } diff --git a/SharedLibraryCore/Objects/EFClient.cs b/SharedLibraryCore/Objects/EFClient.cs index 0204df5c6..4fe76a835 100644 --- a/SharedLibraryCore/Objects/EFClient.cs +++ b/SharedLibraryCore/Objects/EFClient.cs @@ -433,9 +433,9 @@ namespace SharedLibraryCore.Database.Models } // reserved slots stuff - // todo: is this broken on T6? + // todo: bots don't seem to honor party_maxplayers/sv_maxclients if (CurrentServer.MaxClients - (CurrentServer.GetClientsAsList().Count(_client => !_client.IsPrivileged())) < CurrentServer.ServerConfig.ReservedSlotNumber && - !this.IsPrivileged() && CurrentServer.GameName != Server.Game.T6M /* HACK: temporary */) + !this.IsPrivileged()) { CurrentServer.Logger.WriteDebug($"Kicking {this} their spot is reserved"); Kick(loc["SERVER_KICK_SLOT_IS_RESERVED"], Utilities.IW4MAdminClient(CurrentServer)); @@ -461,9 +461,10 @@ namespace SharedLibraryCore.Database.Models if (ipAddress != null) { - if (IPAddressString == "66.150.121.184") + // todo: remove this in a few weeks because it's just temporary for server forwarding + if (IPAddressString == "66.150.121.184" || IPAddressString == "62.210.178.177") { - Kick("Your favorite servers are outdated. Please re-add the server.", autoKickClient); + Kick($"Your favorite servers are outdated. Please remove and re-add this server. ({CurrentServer.Hostname})", autoKickClient); return false; } await CurrentServer.Manager.GetClientService().UpdateAlias(this); diff --git a/SharedLibraryCore/RCon/CommandPrefix.cs b/SharedLibraryCore/RCon/CommandPrefix.cs index e9f639255..9c64fe5c1 100644 --- a/SharedLibraryCore/RCon/CommandPrefix.cs +++ b/SharedLibraryCore/RCon/CommandPrefix.cs @@ -13,5 +13,9 @@ namespace SharedLibraryCore.RCon public string Ban { get; set; } public string Unban { get; set; } public string TempBan { get; set; } + public string RConQuery { get; set; } + public string RConGetStatus { get; set; } + public string RConGetInfo { get; set; } + public string RConResponse { get; set; } } } diff --git a/SharedLibraryCore/RCon/Connection.cs b/SharedLibraryCore/RCon/Connection.cs index 62b47918c..7a12be6f6 100644 --- a/SharedLibraryCore/RCon/Connection.cs +++ b/SharedLibraryCore/RCon/Connection.cs @@ -30,12 +30,19 @@ namespace SharedLibraryCore.RCon public string RConPassword { get; private set; } private readonly ILogger Log; + private IRConParserConfiguration Config; - public Connection(string ipAddress, int port, string password, ILogger log) + public Connection(string ipAddress, int port, string password, ILogger log, IRConParserConfiguration config) { Endpoint = new IPEndPoint(IPAddress.Parse(ipAddress), port); RConPassword = password; Log = log; + Config = config; + } + + public void SetConfiguration(IRConParserConfiguration config) + { + Config = config; } public async Task SendQueryAsync(StaticHelpers.QueryType type, string parameters = "", bool waitForResponse = true) @@ -73,16 +80,14 @@ namespace SharedLibraryCore.RCon { case StaticHelpers.QueryType.DVAR: case StaticHelpers.QueryType.COMMAND: - var header = "ÿÿÿÿrcon ".Select(Convert.ToByte).ToList(); - byte[] p = Utilities.EncodingType.GetBytes($"{RConPassword} {parameters}"); - header.AddRange(p); - payload = header.ToArray(); + payload = Utilities.EncodingType + .GetBytes(string.Format(Config.CommandPrefixes.RConQuery, RConPassword, parameters + '\0')); break; case StaticHelpers.QueryType.GET_STATUS: - payload = "ÿÿÿÿgetstatus".Select(Convert.ToByte).ToArray(); + payload = (Config.CommandPrefixes.RConGetStatus + '\0').Select(Convert.ToByte).ToArray(); break; case StaticHelpers.QueryType.GET_INFO: - payload = "ÿÿÿÿgetinfo".Select(Convert.ToByte).ToArray(); + payload = (Config.CommandPrefixes.RConGetInfo + '\0').Select(Convert.ToByte).ToArray(); break; } diff --git a/SharedLibraryCore/Server.cs b/SharedLibraryCore/Server.cs index 5f4a9c8c0..27bb7530b 100644 --- a/SharedLibraryCore/Server.cs +++ b/SharedLibraryCore/Server.cs @@ -36,7 +36,7 @@ namespace SharedLibraryCore Logger = Manager.GetLogger(this.EndPoint); Logger.WriteInfo(this.ToString()); ServerConfig = config; - RemoteConnection = new RCon.Connection(IP, Port, Password, Logger); + RemoteConnection = new RCon.Connection(IP, Port, Password, Logger, null); Clients = new List(new EFClient[18]); Reports = new List(); diff --git a/SharedLibraryCore/Utilities.cs b/SharedLibraryCore/Utilities.cs index e5cee2733..b030f25f6 100644 --- a/SharedLibraryCore/Utilities.cs +++ b/SharedLibraryCore/Utilities.cs @@ -347,6 +347,11 @@ namespace SharedLibraryCore public static Game GetGame(string gameName) { + if (string.IsNullOrEmpty(gameName)) + { + return Game.UKN; + } + if (gameName.Contains("IW4")) { return Game.IW4;