From 02ef5a0bf8688fbef6f22de6f8f7fef24b0f00b3 Mon Sep 17 00:00:00 2001 From: RaidMax Date: Mon, 23 Apr 2018 00:43:48 -0500 Subject: [PATCH] adding IW5m parsers reduce status polling rate adding preliminary russian localization small rcon tweak to attempt to send custom encoded messages removed exception handling in ConvertLong throttled servers will still attempt to execute events --- Application/Application.csproj | 3 + Application/EventParsers/IW4EventParser.cs | 13 +- Application/EventParsers/IW5EventParser.cs | 45 ++++- Application/EventParsers/T6MEventParser.cs | 24 +-- Application/Localization/Configure.cs | 8 +- Application/Localization/IW4MAdmin.ru-RU.json | 109 ++++++++++++ Application/Main.cs | 4 + Application/RconParsers/IW5MRConParser.cs | 166 ++++++++++++++++++ Application/RconParsers/T6MRConParser.cs | 1 - Application/Server.cs | 94 ++++++---- .../Configuration/ServerConfiguration.cs | 7 + SharedLibraryCore/RCon/Connection.cs | 10 +- SharedLibraryCore/Server.cs | 4 +- SharedLibraryCore/Utilities.cs | 13 +- 14 files changed, 438 insertions(+), 63 deletions(-) create mode 100644 Application/Localization/IW4MAdmin.ru-RU.json create mode 100644 Application/RconParsers/IW5MRConParser.cs diff --git a/Application/Application.csproj b/Application/Application.csproj index 8a9554a8b..1b867d24c 100644 --- a/Application/Application.csproj +++ b/Application/Application.csproj @@ -58,6 +58,9 @@ PreserveNewest + + PreserveNewest + diff --git a/Application/EventParsers/IW4EventParser.cs b/Application/EventParsers/IW4EventParser.cs index 3225514fe..d9cccdfa2 100644 --- a/Application/EventParsers/IW4EventParser.cs +++ b/Application/EventParsers/IW4EventParser.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.Linq; using System.Text.RegularExpressions; using SharedLibraryCore; @@ -9,7 +10,7 @@ namespace Application.EventParsers { class IW4EventParser : IEventParser { - public GameEvent GetEvent(Server server, string logLine) + public virtual GameEvent GetEvent(Server server, string logLine) { string[] lineSplit = logLine.Split(';'); string cleanedEventLine = Regex.Replace(lineSplit[0], @"[0-9]+:[0-9]+\ ", "").Trim(); @@ -73,6 +74,13 @@ namespace Application.EventParsers if (cleanedEventLine.Contains("InitGame")) { + string dump = cleanedEventLine.Replace("InitGame: ", ""); + string[] values = dump.Split('\\', StringSplitOptions.RemoveEmptyEntries); + var dict = new Dictionary(); + + for (int i = 0; i < values.Length; i += 2) + dict.Add(values[i], values[i + 1]); + return new GameEvent() { Type = GameEvent.EventType.MapChange, @@ -85,7 +93,8 @@ namespace Application.EventParsers { ClientId = 1 }, - Owner = server + Owner = server, + Extra = dict }; } diff --git a/Application/EventParsers/IW5EventParser.cs b/Application/EventParsers/IW5EventParser.cs index da25779ba..de5e97110 100644 --- a/Application/EventParsers/IW5EventParser.cs +++ b/Application/EventParsers/IW5EventParser.cs @@ -1,12 +1,53 @@ -using SharedLibraryCore.Interfaces; +using SharedLibraryCore; +using SharedLibraryCore.Interfaces; +using SharedLibraryCore.Objects; using System; using System.Collections.Generic; +using System.Linq; using System.Text; +using System.Text.RegularExpressions; namespace Application.EventParsers { class IW5EventParser : IW4EventParser { - public override string GetGameDir() => "rzodemo"; + public override string GetGameDir() => "logs"; + + public override GameEvent GetEvent(Server server, string logLine) + { + string cleanedEventLine = Regex.Replace(logLine, @"[0-9]+:[0-9]+\ ", "").Trim(); + + if (cleanedEventLine.Contains("J;")) + { + string[] lineSplit = cleanedEventLine.Split(';'); + + int clientNum = Int32.Parse(lineSplit[2]); + + var player = new Player() + { + NetworkId = lineSplit[1].ConvertLong(), + ClientNumber = clientNum, + Name = lineSplit[3] + }; + + return new GameEvent() + { + Type = GameEvent.EventType.Connect, + Origin = new Player() + { + ClientId = 1 + }, + Target = new Player() + { + ClientId = 1 + }, + Owner = server, + Extra = player + }; + } + + else + return base.GetEvent(server, logLine); + } } } diff --git a/Application/EventParsers/T6MEventParser.cs b/Application/EventParsers/T6MEventParser.cs index 050cf87ca..07b6a4ceb 100644 --- a/Application/EventParsers/T6MEventParser.cs +++ b/Application/EventParsers/T6MEventParser.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.IO; using System.Linq; using System.Text.RegularExpressions; @@ -12,15 +13,15 @@ namespace Application.EventParsers { public GameEvent GetEvent(Server server, string logLine) { - string cleanedLogLine = Regex.Replace(logLine, @"^ *[0-9]+:[0-9]+ *", ""); - string[] lineSplit = cleanedLogLine.Split(';'); + string cleanedEventLine = Regex.Replace(logLine, @"^ *[0-9]+:[0-9]+ *", "").Trim(); + string[] lineSplit = cleanedEventLine.Split(';'); if (lineSplit[0][0] == 'K') { return new GameEvent() { Type = GameEvent.EventType.Script, - Data = cleanedLogLine, + Data = cleanedEventLine, Origin = server.GetPlayersAsList().First(c => c.ClientNumber == Utilities.ClientIdFromString(lineSplit, 6)), Target = server.GetPlayersAsList().First(c => c.ClientNumber == Utilities.ClientIdFromString(lineSplit, 2)), Owner = server @@ -32,7 +33,7 @@ namespace Application.EventParsers return new GameEvent() { Type = GameEvent.EventType.Damage, - Data = cleanedLogLine, + Data = cleanedEventLine, Origin = server.GetPlayersAsList().First(c => c.ClientNumber == Utilities.ClientIdFromString(lineSplit, 6)), Target = server.GetPlayersAsList().First(c => c.ClientNumber == Utilities.ClientIdFromString(lineSplit, 2)), Owner = server @@ -69,13 +70,15 @@ namespace Application.EventParsers }; } - /*if (lineSplit[0].Contains("ShutdownGame")) - { - - }*/ - if (lineSplit[0].Contains("InitGame")) { + string dump = cleanedEventLine.Replace("InitGame: ", ""); + string[] values = dump.Split('\\', StringSplitOptions.RemoveEmptyEntries); + var dict = new Dictionary(); + + for (int i = 0; i < values.Length; i += 2) + dict.Add(values[i], values[i + 1]); + return new GameEvent() { Type = GameEvent.EventType.MapChange, @@ -88,7 +91,8 @@ namespace Application.EventParsers { ClientId = 1 }, - Owner = server + Owner = server, + Extra = dict }; } diff --git a/Application/Localization/Configure.cs b/Application/Localization/Configure.cs index 3933b8ba3..2badf5bc2 100644 --- a/Application/Localization/Configure.cs +++ b/Application/Localization/Configure.cs @@ -12,19 +12,21 @@ namespace IW4MAdmin.Application.Localization public static void Initialize() { string currentLocal = CultureInfo.CurrentCulture.Name; +#if DEBUG + currentLocal = "ru-RU"; +#endif string localizationFile = $"Localization{Path.DirectorySeparatorChar}IW4MAdmin.{currentLocal}.json"; string localizationContents; if (File.Exists(localizationFile)) { - localizationContents = File.ReadAllText(localizationFile); - + localizationContents = File.ReadAllText(localizationFile, Encoding.UTF8); } else { localizationFile = $"Localization{Path.DirectorySeparatorChar}IW4MAdmin.en-US.json"; - localizationContents = File.ReadAllText(localizationFile); + localizationContents = File.ReadAllText(localizationFile, Encoding.UTF8); } Utilities.CurrentLocalization = Newtonsoft.Json.JsonConvert.DeserializeObject(localizationContents); diff --git a/Application/Localization/IW4MAdmin.ru-RU.json b/Application/Localization/IW4MAdmin.ru-RU.json new file mode 100644 index 000000000..01b5f9663 --- /dev/null +++ b/Application/Localization/IW4MAdmin.ru-RU.json @@ -0,0 +1,109 @@ +{ + "LocalizationName": "ru-RU", + "LocalizationSet": { + "MANAGER_VERSION_FAIL": "Не удалось получить последнюю версию IW4MAdmin", + "MANAGER_VERSION_UPDATE": "имеет обновление. Последняя версия", + "MANAGER_VERSION_CURRENT": "Ваша версия", + "MANAGER_VERSION_SUCCESS": "IW4MAdmin обновлен", + "MANAGER_INIT_FAIL": "Неустранимая ошибка при инициализации", + "MANAGER_EXIT": "Нажмите любую клавишу чтобы выйти ...", + "SETUP_ENABLE_WEBFRONT": "Включить веб-интерфейс", + "SETUP_ENABLE_MULTIOWN": "Включить поддержку нескольких владельцев", + "SETUP_ENABLE_STEPPEDPRIV": "Включить последовательную иерархию прав", + "SETUP_ENABLE_CUSTOMSAY": "Включить серверное имя для чата", + "SETUP_SAY_NAME": "Введите серверное имя для чата", + "SETUP_USE_CUSTOMENCODING": "Использовать иную кодировку текста", + "SETUP_ENCODING_STRING": "Введите желаемую кодировку", + "SETUP_ENABLE_VPNS": "Разрешить игрокам подключаться с VPN", + "SETUP_IPHUB_KEY": "Введите iphub.info api-ключ", + "SETUP_DISPLAY_DISCORD": "Отображать ссылку на Discord в веб-интерфейсе", + "SETUP_DISCORD_INVITE": "Введите ссылку-приглашение в Discord", + "SETUP_SERVER_USET6M": "Использовать T6M парсер для Black Ops 2", + "SETUP_SERVER_IP": "Введите IP-адрес сервера", + "SETUP_SERVER_PORT": "введите порт сервера", + "SETUP_SERVER_RCON": "Введите RCon пароль сервера", + "SETUP_SERVER_SAVE": "Конфигурация сохранена, добавить еще?", + "SERVER_KICK_VPNS_NOTALLOWED": "Использование VPN не разрешено на этом сервере", + "SERVER_KICK_TEXT": "Вы исключены", + "SERVER_KICK_MINNAME": "Ваше имя должно содержать хотя бы 3 символа", + "SERVER_KICK_NAME_INUSE": "Ваше имя используется кем-то другим", + "SERVER_KICK_GENERICNAME": "Пожалуйста, смените ваше имя, используя /name", + "SERVER_KICK_CONTROLCHARS": "Ваше имя не должно содержать спецсимволы", + "SERVER_TB_TEXT": "Вы временно забанены", + "SERVER_TB_REMAIN": "Вы временно забанены", + "SERVER_BAN_TEXT": "Вы забанены", + "SERVER_BAN_PREV": "Ранее забанены за", + "SERVER_BAN_APPEAL": "оспорить:", + "SERVER_REPORT_COUNT": "Имеется ^5{0} ^7жалоб за последнее время", + "SERVER_WARNLIMT_REACHED": "Слишком много предупреждений", + "SERVER_WARNING": "предупреждение", + "SERVER_WEBSITE_GENERIC": "веб-сайт этого сервера", + "BROADCAST_ONLINE": "^5IW4MADMIN ^7сейчас ^2ОНЛАЙН", + "BROADCAST_OFFLINE": "IW4MAdmin отключается", + "COMMAND_HELP_SYNTAX": "синтаксис:", + "COMMAND_HELP_OPTIONAL": "опционально", + "COMMAND_UNKNOWN": "Вы ввели неизвестную команду", + "COMMAND_NOACCESS": "У вас нет доступа к этой команде", + "COMMAND_NOTAUTHORIZED": "У вас нет разрешения выполнить эту команду", + "COMMAND_MISSINGARGS": "Приведено недостаточно аргументов", + "COMMAND_TARGET_MULTI": "Это имя использует не один игрок", + "COMMAND_TARGET_NOTFOUND": "Невозможно найти указанного игрока", + "PLUGIN_IMPORTER_NOTFOUND": "Нет загружаемых плагинов", + "PLUGIN_IMPORTER_REGISTERCMD": "Зарегистрированная команда", + "COMMANDS_OWNER_SUCCESS": "Поздравляем, Вы стали владельцем этого сервера!", + "COMMANDS_OWNER_FAIL": "Этот сервер уже имеет владельца", + "COMMANDS_WARN_FAIL": "У вас недостаточно прав чтобы предупреждать!", + "COMMANDS_WARNCLEAR_SUCCESS": "Все предупреждения очищены за", + "COMMANDS_KICK_SUCCESS": "был исключен", + "COMMANDS_KICK_FAIL": "У вас недостаточно прав чтобы исключать!", + "COMMANDS_TEMPBAN_SUCCESS": "был временно забанен за", + "COMMANDS_TEMPBAN_FAIL": "Вы не можете временно банить!", + "COMMANDS_BAN_SUCCESS": "был забанен навсегда", + "COMMANDS_BAN_FAIL": "Вы не можете банить!", + "COMMANDS_UNBAN_SUCCESS": "Успешно разбанен", + "COMMANDS_UNBAN_FAIL": "не забанен", + "COMMANDS_HELP_NOTFOUND": "Не удалось найти эту команду", + "COMMANDS_HELP_MOREINFO": "Введите !help <имя команды>, чтобы узнать синтаксис команды", + "COMMANDS_FASTRESTART_UNMASKED": "перезапуск карты", + "COMMANDS_FASTRESTART_MASKED": "Карта перезапущена", + "COMMANDS_MAPROTATE": "Смена карты через ^55 ^7секунд", + "COMMANDS_SETLEVEL_SELF": "Вы не можете изменить свой уровень", + "COMMANDS_SETLEVEL_OWNER": "Возможен только 1 владелец. Включите возможность нескольких владельцев!", + "COMMANDS_SETLEVEL_STEPPEDDISABLED": "Этот сервер не разрешает вам повыситься", + "COMMANDS_SETLEVEL_LEVELTOOHIGH": "Вы только можете повысить ^5{0} ^7до ^5{1} ^7или понизиться в правах", + "COMMANDS_SETLEVEL_SUCCESS_TARGET": "Поздравляем! Вы были повышены до", + "COMMANDS_SETLEVEL_SUCCESS": "был успешно повышен", + "COMMANDS_SETLEVEL_FAIL": "Указана неверная группа", + "COMMANDS_ADMINS_NONE": "Нет администраторов в сети", + "COMMANDS_MAP_SUCCESS": "Смена карты на", + "COMMANDS_MAP_UKN": "Попытка сменить на неизвестную карту", + "COMMANDS_FIND_MIN": "Пожалуйста, введите хотя бы 3 символа", + "COMMANDS_FIND_EMPTY": "Не найдено игроков", + "COMMANDS_RULES_NONE": "Владелец сервера не установил никаких правил", + "COMMANDS_FLAG_SUCCESS": "Вы были отмечены", + "COMMANDS_FLAG_UNFLAG": "С вас сняли отметку", + "COMMANDS_FLAG_FAIL": "Вы не можете ставить отметки", + "COMMANDS_REPORT_FAIL_CAMP": "Вы не можете пожаловаться на игрока за кемперство", + "COMMANDS_REPORT_FAIL_DUPLICATE": "Вы уже пожаловались на этого игрока", + "COMMANDS_REPORT_FAIL_SELF": "Вы не можете пожаловаться на самого себя", + "COMMANDS_REPORT_FAIL": "Вы не можете пожаловаться", + "COMMANDS_REPORT_SUCCESS": "Спасибо за вашу жалобу, администратор оповещен", + "COMMANDS_REPORTS_CLEAR_SUCCESS": "Жалобы полностью очищены", + "COMMANDS_REPORTS_NONE": "Пока нет жалоб на игроков", + "COMMANDS_MASK_ON": "Вы замаскированы", + "COMMANDS_MASK_OFF": "Маскировка снята", + "COMMANDS_BANINFO_NONE": "Нет активного запрета для этого игрока", + "COMMANDS_BANINO_SUCCESS": "был забанен ^5{0} ^7на:", + "COMMANDS_ALIAS_ALIASES": "Псевдонимы", + "COMMANDS_ALIAS_IPS": "IP", + "COMMANDS_RCON_SUCCESS": "​​ RCon команда успешно отправлена", + "COMMANDS_PLUGINS_LOADED": "Загруженные плагины", + "COMMANDS_IP_SUCCESS": "Ваш внешний IP-адрес", + "COMMANDS_PRUNE_FAIL": "Недопустимое количество неактивных дней", + "COMMANDS_PRUNE_SUCCESS": "неактивные привилегированные пользователи были разжалованы", + "COMMANDS_PASSWORD_FAIL": "Ваш пароль должен содержать не менее 5 символов", + "COMMANDS_PASSWORD_SUCCESS": "Ваш пароль успешно установлен", + "COMMANDS_PING_TARGET": "пинг", + "COMMANDS_PING_SELF": "Ваш пинг" + } +} \ No newline at end of file diff --git a/Application/Main.cs b/Application/Main.cs index 3e3d3650e..a4209269b 100644 --- a/Application/Main.cs +++ b/Application/Main.cs @@ -6,6 +6,8 @@ using System.Reflection; using SharedLibraryCore; using SharedLibraryCore.Objects; using SharedLibraryCore.Database; +using System.Text; +using System.Threading; namespace IW4MAdmin.Application { @@ -21,8 +23,10 @@ namespace IW4MAdmin.Application System.Diagnostics.Process.GetCurrentProcess().PriorityClass = System.Diagnostics.ProcessPriorityClass.BelowNormal; Localization.Configure.Initialize(); var loc = Utilities.CurrentLocalization.LocalizationSet; + Console.OutputEncoding = Encoding.UTF8; Version = Assembly.GetExecutingAssembly().GetName().Version.Major + Assembly.GetExecutingAssembly().GetName().Version.Minor / 10.0f; + Version = Math.Round(Version, 2); Console.WriteLine("====================================================="); Console.WriteLine(" IW4M ADMIN"); diff --git a/Application/RconParsers/IW5MRConParser.cs b/Application/RconParsers/IW5MRConParser.cs new file mode 100644 index 000000000..31a88fc61 --- /dev/null +++ b/Application/RconParsers/IW5MRConParser.cs @@ -0,0 +1,166 @@ +using System; +using System.Collections.Generic; +using System.Text.RegularExpressions; +using System.Threading.Tasks; + +using SharedLibraryCore; +using SharedLibraryCore.Interfaces; +using SharedLibraryCore.Objects; +using SharedLibraryCore.RCon; +using SharedLibraryCore.Exceptions; +using System.Text; +using System.Linq; +using System.Net.Http; + +namespace Application.RconParsers +{ + public class IW5MRConParser : IRConParser + { + private static CommandPrefix Prefixes = new CommandPrefix() + { + Tell = "tell {0} {1}", + Say = "say {0}", + Kick = "dropClient {0} \"{1}\"", + Ban = "dropClient {0} \"{1}\"", + TempBan = "dropClient {0} \"{1}\"" + }; + + public CommandPrefix GetCommandPrefixes() => Prefixes; + + public async Task ExecuteCommandAsync(Connection connection, string command) + { + await connection.SendQueryAsync(StaticHelpers.QueryType.COMMAND, command, false); + return new string[] { "Command Executed" }; + } + + public async Task> GetDvarAsync(Connection connection, string dvarName) + { + // why can't this be real :( + if (dvarName == "version") + return new Dvar(dvarName) + { + Value = (T)Convert.ChangeType("IW5 MP 1.9 build 461 Fri Sep 14 00:04:28 2012 win-x86", typeof(T)) + }; + + if (dvarName == "shortversion") + return new Dvar(dvarName) + { + Value = (T)Convert.ChangeType("1.9", typeof(T)) + }; + + if (dvarName == "mapname") + return new Dvar(dvarName) + { + Value = (T)Convert.ChangeType("Unknown", typeof(T)) + }; + + if (dvarName == "g_gametype") + return new Dvar(dvarName) + { + Value = (T)Convert.ChangeType("Unknown", typeof(T)) + }; + + if (dvarName == "fs_game") + return new Dvar(dvarName) + { + Value = (T)Convert.ChangeType("", typeof(T)) + }; + + if (dvarName == "g_logsync") + return new Dvar(dvarName) + { + Value = (T)Convert.ChangeType(1, typeof(T)) + }; + + if (dvarName == "fs_basepath") + return new Dvar(dvarName) + { + Value = (T)Convert.ChangeType("", typeof(T)) + }; + + + + string[] LineSplit = await connection.SendQueryAsync(StaticHelpers.QueryType.DVAR, dvarName); + + if (LineSplit.Length < 4) + { + var e = new DvarException($"DVAR \"{dvarName}\" does not exist"); + e.Data["dvar_name"] = dvarName; + throw e; + } + + string[] ValueSplit = LineSplit[1].Split(new char[] { '"' }); + + if (ValueSplit.Length == 0) + { + var e = new DvarException($"DVAR \"{dvarName}\" does not exist"); + e.Data["dvar_name"] = dvarName; + throw e; + } + + string DvarName = dvarName; + string DvarCurrentValue = Regex.Replace(ValueSplit[3].StripColors(), @"\^[0-9]", ""); + + return new Dvar(DvarName) + { + Value = (T)Convert.ChangeType(DvarCurrentValue, typeof(T)) + }; + } + + public async Task> GetStatusAsync(Connection connection) + { + string[] response = await connection.SendQueryAsync(StaticHelpers.QueryType.COMMAND, "status"); + return ClientsFromStatus(response); + } + + public async Task SetDvarAsync(Connection connection, string dvarName, object dvarValue) + { + // T6M doesn't respond with anything when a value is set, so we can only hope for the best :c + await connection.SendQueryAsync(StaticHelpers.QueryType.DVAR, $"set {dvarName} {dvarValue}", false); + return true; + } + + private List ClientsFromStatus(string[] status) + { + List StatusPlayers = new List(); + + foreach (string statusLine in status) + { + String responseLine = statusLine; + + if (Regex.Matches(responseLine, @"^ *\d+", RegexOptions.IgnoreCase).Count > 0) // its a client line! + { + String[] playerInfo = responseLine.Split(new char[] { ' ' }, StringSplitOptions.RemoveEmptyEntries); + + // this happens when the client is in a zombie state + if (playerInfo.Length < 5) + continue; + int clientId = -1; + int Ping = -1; + + Int32.TryParse(playerInfo[2], out Ping); + string name = Encoding.UTF8.GetString(Encoding.Convert(Utilities.EncodingType, Encoding.UTF8, Utilities.EncodingType.GetBytes(responseLine.Substring(23, 15).StripColors().Trim()))); + long networkId = playerInfo[4].ConvertLong(); + int.TryParse(playerInfo[0], out clientId); + var regex = Regex.Match(responseLine, @"\d+\.\d+\.\d+.\d+\:\d{1,5}"); + int ipAddress = regex.Value.Split(':')[0].ConvertToIP(); + regex = Regex.Match(responseLine, @" +(\d+ +){3}"); + int score = Int32.Parse(regex.Value.Split(' ', StringSplitOptions.RemoveEmptyEntries)[0]); + + StatusPlayers.Add(new Player() + { + Name = name, + NetworkId = networkId, + ClientNumber = clientId, + IPAddress = ipAddress, + Ping = Ping, + Score = score, + IsBot = networkId < 1 + }); + } + } + + return StatusPlayers; + } + } +} diff --git a/Application/RconParsers/T6MRConParser.cs b/Application/RconParsers/T6MRConParser.cs index 0bbdb58b4..54204830f 100644 --- a/Application/RconParsers/T6MRConParser.cs +++ b/Application/RconParsers/T6MRConParser.cs @@ -149,7 +149,6 @@ namespace Application.RconParsers } } - private List ClientsFromStatus(string[] status) { List StatusPlayers = new List(); diff --git a/Application/Server.cs b/Application/Server.cs index bb3dd07cc..7eca0373b 100644 --- a/Application/Server.cs +++ b/Application/Server.cs @@ -51,8 +51,8 @@ namespace IW4MAdmin override public async Task AddPlayer(Player polledPlayer) { - if ((polledPlayer.Ping == 999 && !polledPlayer.IsBot)|| - polledPlayer.Ping < 1 || polledPlayer.ClientNumber > (MaxClients) || + if ((polledPlayer.Ping == 999 && !polledPlayer.IsBot) || + polledPlayer.Ping < 1 || polledPlayer.ClientNumber > (MaxClients) || polledPlayer.ClientNumber < 0) { //Logger.WriteDebug($"Skipping client not in connected state {P}"); @@ -360,8 +360,8 @@ namespace IW4MAdmin public override async Task ExecuteEvent(GameEvent E) { - if (Throttled) - return; + //if (Throttled) + // return; await ProcessEvent(E); Manager.GetEventApi().OnServerEvent(this, E); @@ -422,7 +422,9 @@ namespace IW4MAdmin for (int i = 0; i < CurrentPlayers.Count; i++) { - await AddPlayer(CurrentPlayers[i]); + // todo: wait til GUID is included in status to fix this + if (GameName != Game.IW5) + await AddPlayer(CurrentPlayers[i]); } return CurrentPlayers.Count; @@ -457,15 +459,19 @@ namespace IW4MAdmin try { - int polledPlayerCount = await PollPlayersAsync(); - - if (ConnectionErrors > 0) + // trying to reduce the polling rate as every 450ms is unnecessary + if ((DateTime.Now - LastPoll).TotalSeconds >= 10) { - Logger.WriteVerbose($"Connection has been reestablished with {IP}:{Port}"); - Throttled = false; + int polledPlayerCount = await PollPlayersAsync(); + + if (ConnectionErrors > 0) + { + Logger.WriteVerbose($"Connection has been reestablished with {IP}:{Port}"); + Throttled = false; + } + ConnectionErrors = 0; + LastPoll = DateTime.Now; } - ConnectionErrors = 0; - LastPoll = DateTime.Now; } catch (NetworkException e) @@ -587,6 +593,8 @@ namespace IW4MAdmin public async Task Initialize() { RconParser = ServerConfig.UseT6MParser ? (IRConParser)new T6MRConParser() : new IW4RConParser(); + if (ServerConfig.UseIW5MParser) + RconParser = new IW5MRConParser(); var version = await this.GetDvarAsync("version"); GameName = Utilities.GetGame(version.Value); @@ -632,9 +640,9 @@ namespace IW4MAdmin this.CurrentMap = Maps.Find(m => m.Name == mapname.Value) ?? new Map() { Alias = mapname.Value, Name = mapname.Value }; this.MaxClients = maxplayers.Value; this.FSGame = game.Value; - this.Gametype = (await this.GetDvarAsync("g_gametype")).Value; + this.Gametype = gametype.Value; - await this.SetDvarAsync("sv_kickbantime", 60); + //wait this.SetDvarAsync("sv_kickbantime", 60); if (logsync.Value == 0 || logfile.Value == string.Empty) { @@ -650,11 +658,19 @@ namespace IW4MAdmin CustomCallback = await ScriptLoaded(); string mainPath = EventParser.GetGameDir(); #if DEBUG - basepath.Value = @"\\192.168.88.253\Call of Duty Black Ops II"; + // basepath.Value = @"\\192.168.88.253\Call of Duty Black Ops II"; #endif - string logPath = game.Value == string.Empty ? - $"{basepath.Value.Replace('\\', Path.DirectorySeparatorChar)}{Path.DirectorySeparatorChar}{mainPath}{Path.DirectorySeparatorChar}{logfile.Value}" : - $"{basepath.Value.Replace('\\', Path.DirectorySeparatorChar)}{Path.DirectorySeparatorChar}{game.Value.Replace('/', Path.DirectorySeparatorChar)}{Path.DirectorySeparatorChar}{logfile.Value}"; + string logPath; + if (GameName == Game.IW5) + { + logPath = ServerConfig.ManualLogPath; + } + else + { + logPath = game.Value == string.Empty ? + $"{basepath.Value.Replace('\\', Path.DirectorySeparatorChar)}{Path.DirectorySeparatorChar}{mainPath}{Path.DirectorySeparatorChar}{logfile.Value}" : + $"{basepath.Value.Replace('\\', Path.DirectorySeparatorChar)}{Path.DirectorySeparatorChar}{game.Value.Replace('/', Path.DirectorySeparatorChar)}{Path.DirectorySeparatorChar}{logfile.Value}"; + } // hopefully fix wine drive name mangling if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) @@ -676,7 +692,7 @@ namespace IW4MAdmin Logger.WriteInfo($"Log file is {logPath}"); #if DEBUG - // LogFile = new RemoteFile("https://raidmax.org/IW4MAdmin/getlog.php"); + // LogFile = new RemoteFile("https://raidmax.org/IW4MAdmin/getlog.php"); #else await Broadcast(loc["BROADCAST_ONLINE"]); #endif @@ -687,15 +703,33 @@ namespace IW4MAdmin { if (E.Type == GameEvent.EventType.Connect) { - ChatHistory.Add(new ChatInfo() + // special case for IW5 when connect is from the log + if (E.Extra != null) { - Name = E.Origin.Name, - Message = "CONNECTED", - Time = DateTime.UtcNow - }); + var logClient = (Player)E.Extra; + var client = (await this.GetStatusAsync()) + .Single(c => c.ClientNumber == logClient.ClientNumber && + c.Name == logClient.Name); + client.NetworkId = logClient.NetworkId; - if (E.Origin.Level > Player.Permission.Moderator) - await E.Origin.Tell(string.Format(loc["SERVER_REPORT_COUNT"], E.Owner.Reports.Count)); + await AddPlayer(client); + + // hack: to prevent plugins from registering it as a real connect + E.Type = GameEvent.EventType.Unknown; + } + + else + { + ChatHistory.Add(new ChatInfo() + { + Name = E.Origin.Name, + Message = "CONNECTED", + Time = DateTime.UtcNow + }); + + if (E.Origin.Level > Player.Permission.Moderator) + await E.Origin.Tell(string.Format(loc["SERVER_REPORT_COUNT"], E.Owner.Reports.Count)); + } } else if (E.Type == GameEvent.EventType.Disconnect) @@ -790,11 +824,11 @@ namespace IW4MAdmin { Logger.WriteInfo($"New map loaded - {ClientNum} active players"); - Gametype = (await this.GetDvarAsync("g_gametype")).Value.StripColors(); - Hostname = (await this.GetDvarAsync("sv_hostname")).Value.StripColors(); - FSGame = (await this.GetDvarAsync("fs_game")).Value.StripColors(); + var dict = (Dictionary)E.Extra; + Gametype = dict["g_gametype"].StripColors(); + Hostname = dict["sv_hostname"].StripColors(); - string mapname = this.GetDvarAsync("mapname").Result.Value; + string mapname = dict["mapname"].StripColors(); CurrentMap = Maps.Find(m => m.Name == mapname) ?? new Map() { Alias = mapname, Name = mapname }; } diff --git a/SharedLibraryCore/Configuration/ServerConfiguration.cs b/SharedLibraryCore/Configuration/ServerConfiguration.cs index f8d456fd7..4edd1c5c2 100644 --- a/SharedLibraryCore/Configuration/ServerConfiguration.cs +++ b/SharedLibraryCore/Configuration/ServerConfiguration.cs @@ -11,10 +11,17 @@ namespace SharedLibraryCore.Configuration public List Rules { get; set; } public List AutoMessages { get; set; } public bool UseT6MParser { get; set; } + public bool UseIW5MParser { get; set; } + public string ManualLogPath { get; set; } public IBaseConfiguration Generate() { UseT6MParser = Utilities.PromptBool(Utilities.CurrentLocalization.LocalizationSet["SETUP_SERVER_USET6M"]); + if (!UseT6MParser) + UseIW5MParser = Utilities.PromptBool(Utilities.CurrentLocalization.LocalizationSet["SETUP_SERVER_USEIW5M"]); + if (UseIW5MParser) + ManualLogPath = Utilities.PromptString(Utilities.CurrentLocalization.LocalizationSet["SETUP_SERVER_MANUALLOG"]); + return this; } diff --git a/SharedLibraryCore/RCon/Connection.cs b/SharedLibraryCore/RCon/Connection.cs index 9d1e8c6fb..1644d387b 100644 --- a/SharedLibraryCore/RCon/Connection.cs +++ b/SharedLibraryCore/RCon/Connection.cs @@ -172,19 +172,23 @@ namespace SharedLibraryCore.RCon OnSent.Reset(); OnReceived.Reset(); string queryString = ""; + byte[] payload = null; switch (type) { case StaticHelpers.QueryType.DVAR: case StaticHelpers.QueryType.COMMAND: - queryString = $"ÿÿÿÿrcon {RConPassword} {parameters}"; + var header = "ÿÿÿÿrcon ".Select(Convert.ToByte).ToList(); + byte[] p = Utilities.EncodingType.GetBytes($"{RConPassword} {parameters}"); + header.AddRange(p); + payload = header.ToArray(); break; case StaticHelpers.QueryType.GET_STATUS: - queryString = "ÿÿÿÿgetstatus"; + payload = "ÿÿÿÿgetstatus".Select(Convert.ToByte).ToArray(); break; } - byte[] payload = queryString.Select(Convert.ToByte).ToArray(); + // byte[] payload = Utilities.EncodingType.GetBytes(queryString); // queryString.Select(Convert.ToByte).ToArray(); retrySend: try diff --git a/SharedLibraryCore/Server.cs b/SharedLibraryCore/Server.cs index 9df932c8b..ede04b296 100644 --- a/SharedLibraryCore/Server.cs +++ b/SharedLibraryCore/Server.cs @@ -290,8 +290,8 @@ namespace SharedLibraryCore // Info public string Hostname { get; protected set; } public string Website { get; protected set; } - public string Gametype { get; protected set; } - public Map CurrentMap { get; protected set; } + public string Gametype { get; set; } + public Map CurrentMap { get; set; } public int ClientNum { get diff --git a/SharedLibraryCore/Utilities.cs b/SharedLibraryCore/Utilities.cs index d2d113799..f051c02c9 100644 --- a/SharedLibraryCore/Utilities.cs +++ b/SharedLibraryCore/Utilities.cs @@ -9,6 +9,7 @@ using static SharedLibraryCore.Server; using System.Reflection; using System.IO; using System.Threading.Tasks; +using System.Globalization; namespace SharedLibraryCore { @@ -181,16 +182,8 @@ namespace SharedLibraryCore public static long ConvertLong(this string str) { - try - { - return Int64.Parse(str, System.Globalization.NumberStyles.HexNumber); - } - - - catch (FormatException) - { - return -1; - } + Int64.TryParse(str, NumberStyles.HexNumber, CultureInfo.InvariantCulture, out long id); + return id; } public static int ConvertToIP(this string str)