2018-11-25 21:00:36 -05:00
|
|
|
|
using SharedLibraryCore;
|
|
|
|
|
using SharedLibraryCore.Database.Models;
|
|
|
|
|
using SharedLibraryCore.Exceptions;
|
|
|
|
|
using SharedLibraryCore.Interfaces;
|
|
|
|
|
using SharedLibraryCore.RCon;
|
|
|
|
|
using System;
|
|
|
|
|
using System.Collections.Generic;
|
2018-04-11 18:24:21 -04:00
|
|
|
|
using System.Linq;
|
|
|
|
|
using System.Text.RegularExpressions;
|
|
|
|
|
using System.Threading.Tasks;
|
2019-02-05 19:02:45 -05:00
|
|
|
|
using static SharedLibraryCore.Server;
|
2018-04-11 18:24:21 -04:00
|
|
|
|
|
2018-08-07 14:43:09 -04:00
|
|
|
|
namespace IW4MAdmin.Application.RconParsers
|
2018-04-11 18:24:21 -04:00
|
|
|
|
{
|
2019-06-24 12:01:34 -04:00
|
|
|
|
public class BaseRConParser : IRConParser
|
2018-04-11 18:24:21 -04:00
|
|
|
|
{
|
2020-04-01 15:11:56 -04:00
|
|
|
|
public BaseRConParser(IParserRegexFactory parserRegexFactory)
|
2018-04-13 02:32:30 -04:00
|
|
|
|
{
|
2020-04-01 15:11:56 -04:00
|
|
|
|
Configuration = new DynamicRConParserConfiguration(parserRegexFactory)
|
2019-01-26 21:33:37 -05:00
|
|
|
|
{
|
|
|
|
|
CommandPrefixes = new CommandPrefix()
|
|
|
|
|
{
|
2019-02-05 12:14:43 -05:00
|
|
|
|
Tell = "tell {0} {1}",
|
|
|
|
|
Say = "say {0}",
|
2019-01-26 21:33:37 -05:00
|
|
|
|
Kick = "clientkick {0} \"{1}\"",
|
|
|
|
|
Ban = "clientkick {0} \"{1}\"",
|
2019-02-01 20:49:25 -05:00
|
|
|
|
TempBan = "tempbanclient {0} \"{1}\"",
|
2019-02-03 21:47:05 -05:00
|
|
|
|
RConCommand = "ÿÿÿÿrcon {0} {1}",
|
|
|
|
|
RConGetDvar = "ÿÿÿÿrcon {0} {1}",
|
|
|
|
|
RConSetDvar = "ÿÿÿÿrcon {0} set {1}",
|
2019-02-01 20:49:25 -05:00
|
|
|
|
RConGetStatus = "ÿÿÿÿgetstatus",
|
|
|
|
|
RConGetInfo = "ÿÿÿÿgetinfo",
|
|
|
|
|
RConResponse = "ÿÿÿÿprint",
|
2020-04-18 18:48:49 -04:00
|
|
|
|
RconGetInfoResponseHeader = "ÿÿÿÿinfoResponse"
|
2019-01-26 21:33:37 -05:00
|
|
|
|
},
|
2020-01-13 17:51:16 -05:00
|
|
|
|
ServerNotRunningResponse = "Server is not running."
|
2019-01-26 21:33:37 -05:00
|
|
|
|
};
|
2019-01-27 19:41:54 -05:00
|
|
|
|
|
2019-11-15 15:50:20 -05:00
|
|
|
|
Configuration.Status.Pattern = @"^ *([0-9]+) +-?([0-9]+) +((?:[A-Z]+|[0-9]+)) +((?:[a-z]|[0-9]){8,32}|(?:[a-z]|[0-9]){8,32}|bot[0-9]+|(?:[0-9]+)) *(.{0,32}) +([0-9]+) +(\d+\.\d+\.\d+.\d+\:-*\d{1,5}|0+.0+:-*\d{1,5}|loopback|unknown) +(-*[0-9]+) +([0-9]+) *$";
|
2019-02-02 20:40:37 -05:00
|
|
|
|
Configuration.Status.AddMapping(ParserRegex.GroupType.RConClientNumber, 1);
|
|
|
|
|
Configuration.Status.AddMapping(ParserRegex.GroupType.RConScore, 2);
|
|
|
|
|
Configuration.Status.AddMapping(ParserRegex.GroupType.RConPing, 3);
|
|
|
|
|
Configuration.Status.AddMapping(ParserRegex.GroupType.RConNetworkId, 4);
|
|
|
|
|
Configuration.Status.AddMapping(ParserRegex.GroupType.RConName, 5);
|
|
|
|
|
Configuration.Status.AddMapping(ParserRegex.GroupType.RConIpAddress, 7);
|
2019-02-01 20:49:25 -05:00
|
|
|
|
|
2019-02-05 12:14:43 -05:00
|
|
|
|
Configuration.Dvar.Pattern = "^\"(.+)\" is: \"(.+)?\" default: \"(.+)?\"\n(?:latched: \"(.+)?\"\n)? *(.+)$";
|
2019-02-02 20:40:37 -05:00
|
|
|
|
Configuration.Dvar.AddMapping(ParserRegex.GroupType.RConDvarName, 1);
|
|
|
|
|
Configuration.Dvar.AddMapping(ParserRegex.GroupType.RConDvarValue, 2);
|
|
|
|
|
Configuration.Dvar.AddMapping(ParserRegex.GroupType.RConDvarDefaultValue, 3);
|
|
|
|
|
Configuration.Dvar.AddMapping(ParserRegex.GroupType.RConDvarLatchedValue, 4);
|
|
|
|
|
Configuration.Dvar.AddMapping(ParserRegex.GroupType.RConDvarDomain, 5);
|
2019-11-18 15:02:35 -05:00
|
|
|
|
|
2020-04-17 16:05:16 -04:00
|
|
|
|
Configuration.StatusHeader.Pattern = "num +score +ping +guid +name +lastmsg +address +qport +rate *";
|
2020-08-05 10:30:02 -04:00
|
|
|
|
Configuration.GametypeStatus.Pattern = "";
|
2019-11-18 15:02:35 -05:00
|
|
|
|
Configuration.MapStatus.Pattern = @"map: (([a-z]|_|\d)+)";
|
|
|
|
|
Configuration.MapStatus.AddMapping(ParserRegex.GroupType.RConStatusMap, 1);
|
2020-06-16 18:16:12 -04:00
|
|
|
|
|
|
|
|
|
if (!Configuration.DefaultDvarValues.ContainsKey("mapname"))
|
|
|
|
|
{
|
|
|
|
|
Configuration.DefaultDvarValues.Add("mapname", "Unknown");
|
|
|
|
|
}
|
2019-01-26 21:33:37 -05:00
|
|
|
|
}
|
2018-04-29 16:44:04 -04:00
|
|
|
|
|
2019-01-26 21:33:37 -05:00
|
|
|
|
public IRConParserConfiguration Configuration { get; set; }
|
2019-06-24 12:01:34 -04:00
|
|
|
|
public virtual string Version { get; set; } = "CoD";
|
2019-02-05 19:02:45 -05:00
|
|
|
|
public Game GameName { get; set; } = Game.COD;
|
|
|
|
|
public bool CanGenerateLogPath { get; set; } = true;
|
2020-01-21 19:08:18 -05:00
|
|
|
|
public string Name { get; set; } = "Call of Duty";
|
2019-02-02 19:54:30 -05:00
|
|
|
|
|
2020-02-11 17:44:06 -05:00
|
|
|
|
public async Task<string[]> ExecuteCommandAsync(IRConConnection connection, string command)
|
2018-04-11 18:24:21 -04:00
|
|
|
|
{
|
2018-08-28 17:32:59 -04:00
|
|
|
|
var response = await connection.SendQueryAsync(StaticHelpers.QueryType.COMMAND, command);
|
2018-05-10 01:34:29 -04:00
|
|
|
|
return response.Skip(1).ToArray();
|
2018-04-11 18:24:21 -04:00
|
|
|
|
}
|
|
|
|
|
|
2020-04-17 16:05:16 -04:00
|
|
|
|
public async Task<Dvar<T>> GetDvarAsync<T>(IRConConnection connection, string dvarName, T fallbackValue = default)
|
2018-04-11 18:24:21 -04:00
|
|
|
|
{
|
2019-02-03 21:47:05 -05:00
|
|
|
|
string[] lineSplit = await connection.SendQueryAsync(StaticHelpers.QueryType.GET_DVAR, dvarName);
|
2020-04-17 16:05:16 -04:00
|
|
|
|
string response = string.Join('\n', lineSplit).TrimEnd('\0');
|
2019-02-01 20:49:25 -05:00
|
|
|
|
var match = Regex.Match(response, Configuration.Dvar.Pattern);
|
2018-04-11 18:24:21 -04:00
|
|
|
|
|
2020-04-17 16:05:16 -04:00
|
|
|
|
if (response.Contains("Unknown command") ||
|
2019-05-03 21:13:51 -04:00
|
|
|
|
!match.Success)
|
2018-04-11 18:24:21 -04:00
|
|
|
|
{
|
2020-04-17 16:05:16 -04:00
|
|
|
|
if (fallbackValue != null)
|
|
|
|
|
{
|
|
|
|
|
return new Dvar<T>()
|
|
|
|
|
{
|
|
|
|
|
Name = dvarName,
|
|
|
|
|
Value = fallbackValue
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
2019-05-03 21:13:51 -04:00
|
|
|
|
throw new DvarException(Utilities.CurrentLocalization.LocalizationIndex["SERVER_ERROR_DVAR"].FormatExt(dvarName));
|
2018-04-11 18:24:21 -04:00
|
|
|
|
}
|
|
|
|
|
|
2019-08-01 10:37:33 -04:00
|
|
|
|
string value = match.Groups[Configuration.Dvar.GroupMapping[ParserRegex.GroupType.RConDvarValue]].Value;
|
|
|
|
|
string defaultValue = match.Groups[Configuration.Dvar.GroupMapping[ParserRegex.GroupType.RConDvarDefaultValue]].Value;
|
|
|
|
|
string latchedValue = match.Groups[Configuration.Dvar.GroupMapping[ParserRegex.GroupType.RConDvarLatchedValue]].Value;
|
|
|
|
|
|
|
|
|
|
string removeTrailingColorCode(string input) => Regex.Replace(input, @"\^7$", "");
|
|
|
|
|
|
|
|
|
|
value = removeTrailingColorCode(value);
|
|
|
|
|
defaultValue = removeTrailingColorCode(defaultValue);
|
|
|
|
|
latchedValue = removeTrailingColorCode(latchedValue);
|
2018-04-11 18:24:21 -04:00
|
|
|
|
|
2019-02-01 20:49:25 -05:00
|
|
|
|
return new Dvar<T>()
|
2018-04-11 18:24:21 -04:00
|
|
|
|
{
|
2019-08-01 10:37:33 -04:00
|
|
|
|
Name = match.Groups[Configuration.Dvar.GroupMapping[ParserRegex.GroupType.RConDvarName]].Value,
|
2019-05-03 21:13:51 -04:00
|
|
|
|
Value = string.IsNullOrEmpty(value) ? default : (T)Convert.ChangeType(value, typeof(T)),
|
|
|
|
|
DefaultValue = string.IsNullOrEmpty(defaultValue) ? default : (T)Convert.ChangeType(defaultValue, typeof(T)),
|
|
|
|
|
LatchedValue = string.IsNullOrEmpty(latchedValue) ? default : (T)Convert.ChangeType(latchedValue, typeof(T)),
|
2019-08-01 10:37:33 -04:00
|
|
|
|
Domain = match.Groups[Configuration.Dvar.GroupMapping[ParserRegex.GroupType.RConDvarDomain]].Value
|
2018-04-11 18:24:21 -04:00
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
2020-08-05 10:30:02 -04:00
|
|
|
|
public virtual async Task<(List<EFClient>, string, string)> GetStatusAsync(IRConConnection connection)
|
2018-04-11 18:24:21 -04:00
|
|
|
|
{
|
2019-02-09 16:35:13 -05:00
|
|
|
|
string[] response = await connection.SendQueryAsync(StaticHelpers.QueryType.COMMAND_STATUS);
|
2019-10-18 14:39:21 -04:00
|
|
|
|
#if DEBUG
|
|
|
|
|
foreach (var line in response)
|
|
|
|
|
{
|
|
|
|
|
Console.WriteLine(line);
|
|
|
|
|
}
|
|
|
|
|
#endif
|
2020-08-05 10:30:02 -04:00
|
|
|
|
return (ClientsFromStatus(response), MapFromStatus(response), GameTypeFromStatus(response));
|
2019-11-18 15:02:35 -05:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private string MapFromStatus(string[] response)
|
|
|
|
|
{
|
|
|
|
|
string map = null;
|
|
|
|
|
foreach (var line in response)
|
|
|
|
|
{
|
|
|
|
|
var regex = Regex.Match(line, Configuration.MapStatus.Pattern);
|
|
|
|
|
if (regex.Success)
|
|
|
|
|
{
|
|
|
|
|
map = regex.Groups[Configuration.MapStatus.GroupMapping[ParserRegex.GroupType.RConStatusMap]].ToString();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return map;
|
2018-04-11 18:24:21 -04:00
|
|
|
|
}
|
|
|
|
|
|
2020-08-05 10:30:02 -04:00
|
|
|
|
private string GameTypeFromStatus(string[] response)
|
|
|
|
|
{
|
|
|
|
|
if (string.IsNullOrWhiteSpace(Configuration.GametypeStatus.Pattern))
|
|
|
|
|
{
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
string gametype = null;
|
|
|
|
|
foreach (var line in response)
|
|
|
|
|
{
|
|
|
|
|
var regex = Regex.Match(line, Configuration.GametypeStatus.Pattern);
|
|
|
|
|
if (regex.Success)
|
|
|
|
|
{
|
|
|
|
|
gametype = regex.Groups[Configuration.GametypeStatus.GroupMapping[ParserRegex.GroupType.RConStatusGametype]].ToString();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return gametype;
|
|
|
|
|
}
|
|
|
|
|
|
2020-02-11 17:44:06 -05:00
|
|
|
|
public async Task<bool> SetDvarAsync(IRConConnection connection, string dvarName, object dvarValue)
|
2018-04-11 18:24:21 -04:00
|
|
|
|
{
|
2020-04-01 15:11:56 -04:00
|
|
|
|
string dvarString = (dvarValue is string str)
|
|
|
|
|
? $"{dvarName} \"{str}\""
|
|
|
|
|
: $"{dvarName} {dvarValue.ToString()}";
|
|
|
|
|
|
|
|
|
|
return (await connection.SendQueryAsync(StaticHelpers.QueryType.SET_DVAR, dvarString)).Length > 0;
|
2018-04-11 18:24:21 -04:00
|
|
|
|
}
|
|
|
|
|
|
2018-11-05 22:01:29 -05:00
|
|
|
|
private List<EFClient> ClientsFromStatus(string[] Status)
|
2018-04-11 18:24:21 -04:00
|
|
|
|
{
|
2018-11-05 22:01:29 -05:00
|
|
|
|
List<EFClient> StatusPlayers = new List<EFClient>();
|
2018-04-11 18:24:21 -04:00
|
|
|
|
|
2020-04-17 16:05:16 -04:00
|
|
|
|
bool parsedHeader = false;
|
2019-04-02 21:20:37 -04:00
|
|
|
|
foreach (string statusLine in Status)
|
2018-04-11 18:24:21 -04:00
|
|
|
|
{
|
2019-04-02 21:20:37 -04:00
|
|
|
|
string responseLine = statusLine.Trim();
|
2018-04-11 18:24:21 -04:00
|
|
|
|
|
2020-04-17 16:05:16 -04:00
|
|
|
|
if (Configuration.StatusHeader.PatternMatcher.Match(responseLine).Success)
|
|
|
|
|
{
|
|
|
|
|
parsedHeader = true;
|
|
|
|
|
continue;
|
|
|
|
|
}
|
2018-05-03 01:25:49 -04:00
|
|
|
|
|
2020-04-17 16:05:16 -04:00
|
|
|
|
var match = Configuration.Status.PatternMatcher.Match(responseLine);
|
|
|
|
|
|
|
|
|
|
if (match.Success)
|
2018-04-11 18:24:21 -04:00
|
|
|
|
{
|
2020-04-17 16:05:16 -04:00
|
|
|
|
int clientNumber = int.Parse(match.Values[Configuration.Status.GroupMapping[ParserRegex.GroupType.RConClientNumber]]);
|
|
|
|
|
int score = int.Parse(match.Values[Configuration.Status.GroupMapping[ParserRegex.GroupType.RConScore]]);
|
2018-04-29 16:44:04 -04:00
|
|
|
|
|
|
|
|
|
int ping = 999;
|
|
|
|
|
|
|
|
|
|
// their state can be CNCT, ZMBI etc
|
2020-04-17 16:05:16 -04:00
|
|
|
|
if (match.Values[Configuration.Status.GroupMapping[ParserRegex.GroupType.RConPing]].Length <= 3)
|
2018-04-29 16:44:04 -04:00
|
|
|
|
{
|
2020-04-17 16:05:16 -04:00
|
|
|
|
ping = int.Parse(match.Values[Configuration.Status.GroupMapping[ParserRegex.GroupType.RConPing]]);
|
2018-04-29 16:44:04 -04:00
|
|
|
|
}
|
|
|
|
|
|
2019-05-02 23:33:38 -04:00
|
|
|
|
long networkId;
|
2020-05-04 17:50:02 -04:00
|
|
|
|
string name = match.Values[Configuration.Status.GroupMapping[ParserRegex.GroupType.RConName]].TrimNewLine();
|
2020-09-21 16:30:42 -04:00
|
|
|
|
string networkIdString;
|
2020-05-04 17:50:02 -04:00
|
|
|
|
|
2019-05-02 23:33:38 -04:00
|
|
|
|
try
|
|
|
|
|
{
|
2020-09-21 16:30:42 -04:00
|
|
|
|
networkIdString = match.Values[Configuration.Status.GroupMapping[ParserRegex.GroupType.RConNetworkId]];
|
2020-05-04 17:50:02 -04:00
|
|
|
|
|
|
|
|
|
networkId = networkIdString.IsBotGuid() ?
|
|
|
|
|
name.GenerateGuidFromString() :
|
|
|
|
|
networkIdString.ConvertGuidToLong(Configuration.GuidNumberStyle);
|
2019-05-02 23:33:38 -04:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
catch (FormatException)
|
|
|
|
|
{
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
2020-04-17 16:05:16 -04:00
|
|
|
|
int? ip = match.Values[Configuration.Status.GroupMapping[ParserRegex.GroupType.RConIpAddress]].Split(':')[0].ConvertToIP();
|
2018-04-29 16:44:04 -04:00
|
|
|
|
|
2018-12-16 22:16:56 -05:00
|
|
|
|
var client = new EFClient()
|
2018-04-11 18:24:21 -04:00
|
|
|
|
{
|
2018-11-25 21:00:36 -05:00
|
|
|
|
CurrentAlias = new EFAlias()
|
|
|
|
|
{
|
2019-05-29 17:55:35 -04:00
|
|
|
|
Name = name,
|
|
|
|
|
IPAddress = ip
|
2018-11-25 21:00:36 -05:00
|
|
|
|
},
|
2018-04-29 16:44:04 -04:00
|
|
|
|
NetworkId = networkId,
|
|
|
|
|
ClientNumber = clientNumber,
|
|
|
|
|
Ping = ping,
|
2018-04-21 18:18:20 -04:00
|
|
|
|
Score = score,
|
2018-11-05 22:01:29 -05:00
|
|
|
|
State = EFClient.ClientState.Connecting
|
2018-04-11 18:24:21 -04:00
|
|
|
|
};
|
2018-12-16 22:16:56 -05:00
|
|
|
|
|
2020-09-21 16:30:42 -04:00
|
|
|
|
client.SetAdditionalProperty("BotGuid", networkIdString);
|
|
|
|
|
|
2018-12-17 14:45:16 -05:00
|
|
|
|
StatusPlayers.Add(client);
|
2018-04-11 18:24:21 -04:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2020-04-17 16:05:16 -04:00
|
|
|
|
// this can happen if status is requested while map is rotating and we get a log dump back
|
|
|
|
|
if (!parsedHeader)
|
2018-05-03 01:25:49 -04:00
|
|
|
|
{
|
2020-04-17 16:05:16 -04:00
|
|
|
|
throw new ServerException(Utilities.CurrentLocalization.LocalizationIndex["SERVER_ERROR_UNEXPECTED_STATUS"]);
|
2018-05-03 01:25:49 -04:00
|
|
|
|
}
|
|
|
|
|
|
2018-04-11 18:24:21 -04:00
|
|
|
|
return StatusPlayers;
|
|
|
|
|
}
|
2020-06-16 18:16:12 -04:00
|
|
|
|
|
|
|
|
|
public string GetOverrideDvarName(string dvarName)
|
|
|
|
|
{
|
|
|
|
|
if (Configuration.OverrideDvarNameMapping.ContainsKey(dvarName))
|
|
|
|
|
{
|
|
|
|
|
return Configuration.OverrideDvarNameMapping[dvarName];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return dvarName;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public T GetDefaultDvarValue<T>(string dvarName) => Configuration.DefaultDvarValues.ContainsKey(dvarName) ?
|
|
|
|
|
(T)Convert.ChangeType(Configuration.DefaultDvarValues[dvarName], typeof(T)) :
|
|
|
|
|
default;
|
2019-11-18 15:02:35 -05:00
|
|
|
|
}
|
|
|
|
|
}
|