diff --git a/Application/EventParsers/BaseEventParser.cs b/Application/EventParsers/BaseEventParser.cs index 72144d572..b7d8e6948 100644 --- a/Application/EventParsers/BaseEventParser.cs +++ b/Application/EventParsers/BaseEventParser.cs @@ -42,7 +42,7 @@ namespace IW4MAdmin.Application.EventParsers Configuration.Join.AddMapping(ParserRegex.GroupType.OriginClientNumber, 3); Configuration.Join.AddMapping(ParserRegex.GroupType.OriginName, 4); - Configuration.Damage.Pattern = @"^(D);(-?[A-Fa-f0-9_]{1,32}|bot[0-9]+|0);(-?[0-9]+);(axis|allies|world)?;([^;]{1,24});(-?[A-Fa-f0-9_]{1,32}|bot[0-9]+|0)?;(-?[0-9]+);(axis|allies|world)?;([^;]{1,24})?;((?:[0-9]+|[a-z]+|_|\+)+);([0-9]+);((?:[A-Z]|_)+);((?:[a-z]|_)+)$"; + Configuration.Damage.Pattern = @"^(D);(-?[A-Fa-f0-9_]{1,32}|bot[0-9]+|0);(-?[0-9]+);(axis|allies|world|none)?;([^;]{1,24});(-?[A-Fa-f0-9_]{1,32}|bot[0-9]+|0)?;(-?[0-9]+);(axis|allies|world|none)?;([^;]{1,24})?;((?:[0-9]+|[a-z]+|_|\+)+);([0-9]+);((?:[A-Z]|_)+);((?:[a-z]|_)+)$"; Configuration.Damage.AddMapping(ParserRegex.GroupType.EventType, 1); Configuration.Damage.AddMapping(ParserRegex.GroupType.TargetNetworkId, 2); Configuration.Damage.AddMapping(ParserRegex.GroupType.TargetClientNumber, 3); @@ -57,7 +57,7 @@ namespace IW4MAdmin.Application.EventParsers Configuration.Damage.AddMapping(ParserRegex.GroupType.MeansOfDeath, 12); Configuration.Damage.AddMapping(ParserRegex.GroupType.HitLocation, 13); - Configuration.Kill.Pattern = @"^(K);(-?[A-Fa-f0-9_]{1,32}|bot[0-9]+|0);(-?[0-9]+);(axis|allies|world)?;([^;]{1,24});(-?[A-Fa-f0-9_]{1,32}|bot[0-9]+|0)?;(-?[0-9]+);(axis|allies|world)?;([^;]{1,24})?;((?:[0-9]+|[a-z]+|_|\+)+);([0-9]+);((?:[A-Z]|_)+);((?:[a-z]|_)+)$"; + Configuration.Kill.Pattern = @"^(K);(-?[A-Fa-f0-9_]{1,32}|bot[0-9]+|0);(-?[0-9]+);(axis|allies|world|none)?;([^;]{1,24});(-?[A-Fa-f0-9_]{1,32}|bot[0-9]+|0)?;(-?[0-9]+);(axis|allies|world|none)?;([^;]{1,24})?;((?:[0-9]+|[a-z]+|_|\+)+);([0-9]+);((?:[A-Z]|_)+);((?:[a-z]|_)+)$"; Configuration.Kill.AddMapping(ParserRegex.GroupType.EventType, 1); Configuration.Kill.AddMapping(ParserRegex.GroupType.TargetNetworkId, 2); Configuration.Kill.AddMapping(ParserRegex.GroupType.TargetClientNumber, 3); diff --git a/Application/IW4MServer.cs b/Application/IW4MServer.cs index 0e132629a..702590445 100644 --- a/Application/IW4MServer.cs +++ b/Application/IW4MServer.cs @@ -667,7 +667,7 @@ namespace IW4MAdmin } } - else if ((client.IPAddress != null && client.State == ClientState.Disconnecting) || + else if ((client.IPAddress != null && client.State == ClientState.Disconnecting) || client.Level == Permission.Banned) { Logger.WriteWarning($"{client} state is Unknown (probably kicked), but they are still connected. trying to kick again..."); @@ -939,7 +939,7 @@ namespace IW4MAdmin RemoteConnection.SetConfiguration(RconParser.Configuration); - var version = await this.GetDvarAsync("version"); + var version = await this.GetMappedDvarValueOrDefaultAsync("version"); Version = version.Value; GameName = Utilities.GetGame(version?.Value ?? RconParser.Version); @@ -956,8 +956,7 @@ namespace IW4MAdmin Version = RconParser.Version; } - // these T7 specific things aren't ideal , but it's a quick fix - var svRunning = await this.GetDvarAsync("sv_running", GameName == Game.T7 ? "1" : null); + var svRunning = await this.GetMappedDvarValueOrDefaultAsync("sv_running"); if (!string.IsNullOrEmpty(svRunning.Value) && svRunning.Value != "1") { @@ -965,29 +964,17 @@ namespace IW4MAdmin } 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 : - infoResponse.Where(kvp => kvp.Key.Contains("hostname")).Select(kvp => kvp.Value).First(); - var mapname = infoResponse == null ? - (await this.GetDvarAsync("mapname", "Unknown")).Value : - infoResponse["mapname"]; - int maxplayers = (GameName == Game.IW4) ? // gotta love IW4 idiosyncrasies - (await this.GetDvarAsync("party_maxplayers")).Value : - infoResponse == null || !infoResponse.ContainsKey("sv_maxclients") ? - (await this.GetDvarAsync("sv_maxclients")).Value : - Convert.ToInt32(infoResponse["sv_maxclients"]); - var gametype = infoResponse == null ? - (await this.GetDvarAsync("g_gametype", GameName == Game.T7 ? "" : null)).Value : - infoResponse.Where(kvp => kvp.Key.Contains("gametype")).Select(kvp => kvp.Value).First(); - var basepath = await this.GetDvarAsync("fs_basepath", GameName == Game.T7 ? "" : null); - var basegame = await this.GetDvarAsync("fs_basegame", GameName == Game.T7 ? "" : null); - var game = infoResponse == null || !infoResponse.ContainsKey("fs_game") ? - (await this.GetDvarAsync("fs_game", GameName == Game.T7 ? "" : null)).Value : - infoResponse["fs_game"]; - var logfile = await this.GetDvarAsync("g_log"); - var logsync = await this.GetDvarAsync("g_logsync"); - var ip = await this.GetDvarAsync("net_ip"); + + string hostname = (await this.GetMappedDvarValueOrDefaultAsync("sv_hostname", "hostname", infoResponse)).Value; + string mapname = (await this.GetMappedDvarValueOrDefaultAsync("mapname", infoResponse: infoResponse)).Value; + int maxplayers = (await this.GetMappedDvarValueOrDefaultAsync("sv_maxclients", infoResponse: infoResponse)).Value; + string gametype = (await this.GetMappedDvarValueOrDefaultAsync("g_gametype", "gametype", infoResponse)).Value; + var basepath = (await this.GetMappedDvarValueOrDefaultAsync("fs_basepath")); + var basegame = (await this.GetMappedDvarValueOrDefaultAsync("fs_basegame")); + var game = (await this.GetMappedDvarValueOrDefaultAsync("fs_game", infoResponse: infoResponse)); + var logfile = await this.GetMappedDvarValueOrDefaultAsync("g_log"); + var logsync = await this.GetMappedDvarValueOrDefaultAsync("g_logsync"); + var ip = await this.GetMappedDvarValueOrDefaultAsync("net_ip"); if (Manager.GetApplicationSettings().Configuration().EnableCustomSayName) { @@ -996,7 +983,7 @@ namespace IW4MAdmin try { - var website = await this.GetDvarAsync("_website"); + var website = await this.GetMappedDvarValueOrDefaultAsync("_website"); // this occurs for games that don't give us anything back when // the dvar is not set @@ -1018,7 +1005,7 @@ namespace IW4MAdmin WorkingDirectory = basepath.Value; this.Hostname = hostname; this.MaxClients = maxplayers; - this.FSGame = game; + this.FSGame = game.Value; this.Gametype = gametype; this.IP = ip.Value == "localhost" ? ServerConfig.IPAddress : ip.Value ?? ServerConfig.IPAddress; UpdateMap(mapname); @@ -1070,7 +1057,7 @@ namespace IW4MAdmin BaseGameDirectory = basegame.Value, BasePathDirectory = basepath.Value, GameDirectory = EventParser.Configuration.GameDirectory ?? "", - ModDirectory = game ?? "", + ModDirectory = game.Value ?? "", LogFile = logfile.Value, IsWindows = RuntimeInformation.IsOSPlatform(OSPlatform.Windows) }; diff --git a/Application/RconParsers/BaseRConParser.cs b/Application/RconParsers/BaseRConParser.cs index d170bef78..0350763fd 100644 --- a/Application/RconParsers/BaseRConParser.cs +++ b/Application/RconParsers/BaseRConParser.cs @@ -14,8 +14,6 @@ namespace IW4MAdmin.Application.RconParsers { public class BaseRConParser : IRConParser { - private const int MAX_FAULTY_STATUS_LINES = 7; - public BaseRConParser(IParserRegexFactory parserRegexFactory) { Configuration = new DynamicRConParserConfiguration(parserRegexFactory) @@ -56,10 +54,14 @@ namespace IW4MAdmin.Application.RconParsers Configuration.StatusHeader.Pattern = "num +score +ping +guid +name +lastmsg +address +qport +rate *"; Configuration.MapStatus.Pattern = @"map: (([a-z]|_|\d)+)"; Configuration.MapStatus.AddMapping(ParserRegex.GroupType.RConStatusMap, 1); + + if (!Configuration.DefaultDvarValues.ContainsKey("mapname")) + { + Configuration.DefaultDvarValues.Add("mapname", "Unknown"); + } } public IRConParserConfiguration Configuration { get; set; } - public virtual string Version { get; set; } = "CoD"; public Game GameName { get; set; } = Game.COD; public bool CanGenerateLogPath { get; set; } = true; @@ -211,14 +213,6 @@ namespace IW4MAdmin.Application.RconParsers State = EFClient.ClientState.Connecting }; -//#if DEBUG -// if (client.NetworkId < 1000 && client.NetworkId > 0) -// { -// client.IPAddress = 2147483646; -// client.Ping = 0; -// } -//#endif - StatusPlayers.Add(client); } } @@ -231,5 +225,19 @@ namespace IW4MAdmin.Application.RconParsers return StatusPlayers; } + + public string GetOverrideDvarName(string dvarName) + { + if (Configuration.OverrideDvarNameMapping.ContainsKey(dvarName)) + { + return Configuration.OverrideDvarNameMapping[dvarName]; + } + + return dvarName; + } + + public T GetDefaultDvarValue(string dvarName) => Configuration.DefaultDvarValues.ContainsKey(dvarName) ? + (T)Convert.ChangeType(Configuration.DefaultDvarValues[dvarName], typeof(T)) : + default; } } diff --git a/Application/RconParsers/DynamicRConParserConfiguration.cs b/Application/RconParsers/DynamicRConParserConfiguration.cs index 79df12504..b043843b3 100644 --- a/Application/RconParsers/DynamicRConParserConfiguration.cs +++ b/Application/RconParsers/DynamicRConParserConfiguration.cs @@ -1,6 +1,6 @@ -using IW4MAdmin.Application.Factories; -using SharedLibraryCore.Interfaces; +using SharedLibraryCore.Interfaces; using SharedLibraryCore.RCon; +using System.Collections.Generic; using System.Globalization; namespace IW4MAdmin.Application.RconParsers @@ -19,6 +19,8 @@ namespace IW4MAdmin.Application.RconParsers public string ServerNotRunningResponse { get; set; } public bool WaitForResponse { get; set; } = true; public NumberStyles GuidNumberStyle { get; set; } = NumberStyles.HexNumber; + public IDictionary OverrideDvarNameMapping { get; set; } = new Dictionary(); + public IDictionary DefaultDvarValues { get; set; } = new Dictionary(); public DynamicRConParserConfiguration(IParserRegexFactory parserRegexFactory) { diff --git a/Plugins/ScriptPlugins/ParserT7.js b/Plugins/ScriptPlugins/ParserT7.js index 2bdfa0c0c..e462576cd 100644 --- a/Plugins/ScriptPlugins/ParserT7.js +++ b/Plugins/ScriptPlugins/ParserT7.js @@ -3,7 +3,7 @@ var eventParser; var plugin = { author: 'RaidMax', - version: 0.1, + version: 0.2, name: 'Black Ops 3 Parser', isParser: true, @@ -24,6 +24,14 @@ var plugin = { rconParser.Configuration.CommandPrefixes.RConSetDvar = '\xff\xff\xff\xff\x00{0} set {1}'; rconParser.Configuration.CommandPrefixes.RConResponse = '\xff\xff\xff\xff\x01'; rconParser.Configuration.CommandPrefixes.RConGetInfo = undefined; // disables this, because it's useless on T7 + rconParser.Configuration.ServerNotRunningResponse = 'this is here to prevent a hiberating server from being detected as not running'; + + rconParser.Configuration.OverrideDvarNameMapping.Add('sv_hostname', 'live_steam_server_name'); + rconParser.Configuration.DefaultDvarValues.Add('sv_running', '1'); + rconParser.Configuration.DefaultDvarValues.Add('g_gametype', ''); + rconParser.Configuration.DefaultDvarValues.Add('fs_basepath', ''); + rconParser.Configuration.DefaultDvarValues.Add('fs_basegame', ''); + rconParser.Configuration.DefaultDvarValues.Add('fs_game', ''); rconParser.Configuration.Status.AddMapping(105, 6); // ip address rconParser.Version = '[local] ship win64 CODBUILD8-764 (3421987) Mon Dec 16 10:44:20 2019 10d27bef'; @@ -34,7 +42,6 @@ var plugin = { eventParser.GameName = 8; // BO3 eventParser.Configuration.GameDirectory = 'usermaps'; eventParser.Configuration.Say.Pattern = '^(chat|chatteam);(?:[0-9]+);([0-9]+);([0-9]+);(.+);(.*)$'; - }, onUnloadAsync: function () { diff --git a/Plugins/ScriptPlugins/ParserTeknoMW3.js b/Plugins/ScriptPlugins/ParserTeknoMW3.js index 8f9edece9..4d0238b16 100644 --- a/Plugins/ScriptPlugins/ParserTeknoMW3.js +++ b/Plugins/ScriptPlugins/ParserTeknoMW3.js @@ -3,7 +3,7 @@ var eventParser; var plugin = { author: 'RaidMax', - version: 0.5, + version: 0.6, name: 'Tekno MW3 Parser', isParser: true, @@ -27,6 +27,9 @@ var plugin = { rconParser.Configuration.CommandPrefixes.TempBan = 'tempbanclient {0} "{1}"'; rconParser.Configuration.Dvar.AddMapping(107, 1); // RCon DvarValue rconParser.Configuration.Dvar.Pattern = '^(.*)$'; + + rconParser.Configuration.DefaultDvarValues.Add('sv_running', '1'); + rconParser.Version = 'IW5 MP 1.4 build 382 latest Thu Jan 19 2012 11:09:49AM win-x86'; rconParser.GameName = 3; // IW5 rconParser.CanGenerateLogPath = false; diff --git a/SharedLibraryCore/Interfaces/IRConParser.cs b/SharedLibraryCore/Interfaces/IRConParser.cs index f7768e252..3d1a4dbe2 100644 --- a/SharedLibraryCore/Interfaces/IRConParser.cs +++ b/SharedLibraryCore/Interfaces/IRConParser.cs @@ -63,8 +63,24 @@ namespace SharedLibraryCore.Interfaces bool CanGenerateLogPath { get; set; } /// - /// Specifies the name of the parser + /// specifies the name of the parser /// string Name { get; set; } + + /// + /// retrieves the value of given dvar key if it exists in the override dict + /// otherwise returns original + /// + /// name of dvar key + /// + string GetOverrideDvarName(string dvarName); + + /// + /// retrieves the configuration value of a dvar key for + /// games that do not support the given dvar + /// + /// dvar key name + /// + T GetDefaultDvarValue(string dvarName); } } diff --git a/SharedLibraryCore/Interfaces/IRConParserConfiguration.cs b/SharedLibraryCore/Interfaces/IRConParserConfiguration.cs index 571f168d8..757bab4ca 100644 --- a/SharedLibraryCore/Interfaces/IRConParserConfiguration.cs +++ b/SharedLibraryCore/Interfaces/IRConParserConfiguration.cs @@ -1,4 +1,5 @@ using SharedLibraryCore.RCon; +using System.Collections.Generic; using System.Globalization; namespace SharedLibraryCore.Interfaces @@ -45,5 +46,16 @@ namespace SharedLibraryCore.Interfaces /// indicates the format expected for parsed guids /// NumberStyles GuidNumberStyle { get; set; } + + /// + /// specifies simple mappings for dvar names in scenarios where the needed + /// information is not stored in a traditional dvar name + /// + IDictionary OverrideDvarNameMapping { get; set; } + + /// + /// specifies the default dvar values for games that don't support certain dvars + /// + IDictionary DefaultDvarValues { get; set; } } } diff --git a/SharedLibraryCore/Utilities.cs b/SharedLibraryCore/Utilities.cs index bb8dde88a..daaeafffa 100644 --- a/SharedLibraryCore/Utilities.cs +++ b/SharedLibraryCore/Utilities.cs @@ -1,4 +1,5 @@ -using SharedLibraryCore.Database.Models; + +using SharedLibraryCore.Database.Models; using SharedLibraryCore.Helpers; using SharedLibraryCore.Interfaces; using System; @@ -747,7 +748,27 @@ namespace SharedLibraryCore public static Task> GetDvarAsync(this Server server, string dvarName, T fallbackValue = default) { - return server.RconParser.GetDvarAsync(server.RemoteConnection, dvarName, fallbackValue); + return server.RconParser.GetDvarAsync(server.RemoteConnection, dvarName, fallbackValue); + } + + public static async Task> GetMappedDvarValueOrDefaultAsync(this Server server, string dvarName, string infoResponseName = null, IDictionary infoResponse = null) + { + // todo: unit test this + string mappedKey = server.RconParser.GetOverrideDvarName(dvarName); + var defaultValue = server.RconParser.GetDefaultDvarValue(mappedKey); + + string foundKey = infoResponse?.Keys.Where(_key => new[] { mappedKey, dvarName, infoResponseName ?? dvarName }.Contains(_key)).FirstOrDefault(); + + if (!string.IsNullOrEmpty(foundKey)) + { + return new Dvar + { + Value = (T)Convert.ChangeType(infoResponse[foundKey], typeof(T)), + Name = foundKey + }; + } + + return await server.GetDvarAsync(mappedKey, defaultValue); } public static Task SetDvarAsync(this Server server, string dvarName, object dvarValue) @@ -944,6 +965,8 @@ namespace SharedLibraryCore public static bool ShouldHideLevel(this Permission perm) => perm == Permission.Flagged; + + /// /// replaces any directory separator chars with the platform specific character ///