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)