add initial CS:GO support
This commit is contained in:
308
Application/RConParsers/BaseRConParser.cs
Normal file
308
Application/RConParsers/BaseRConParser.cs
Normal file
@ -0,0 +1,308 @@
|
||||
using SharedLibraryCore;
|
||||
using SharedLibraryCore.Database.Models;
|
||||
using SharedLibraryCore.Exceptions;
|
||||
using SharedLibraryCore.Interfaces;
|
||||
using SharedLibraryCore.RCon;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading.Tasks;
|
||||
using Data.Models;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using static SharedLibraryCore.Server;
|
||||
using ILogger = Microsoft.Extensions.Logging.ILogger;
|
||||
|
||||
namespace IW4MAdmin.Application.RConParsers
|
||||
{
|
||||
public class BaseRConParser : IRConParser
|
||||
{
|
||||
private readonly ILogger _logger;
|
||||
|
||||
public BaseRConParser(ILogger<BaseRConParser> logger, IParserRegexFactory parserRegexFactory)
|
||||
{
|
||||
_logger = logger;
|
||||
Configuration = new DynamicRConParserConfiguration(parserRegexFactory)
|
||||
{
|
||||
CommandPrefixes = new CommandPrefix()
|
||||
{
|
||||
Tell = "tell {0} {1}",
|
||||
Say = "say {0}",
|
||||
Kick = "clientkick {0} \"{1}\"",
|
||||
Ban = "clientkick {0} \"{1}\"",
|
||||
TempBan = "tempbanclient {0} \"{1}\"",
|
||||
RConCommand = "ÿÿÿÿrcon {0} {1}",
|
||||
RConGetDvar = "ÿÿÿÿrcon {0} {1}",
|
||||
RConSetDvar = "ÿÿÿÿrcon {0} set {1}",
|
||||
RConGetStatus = "ÿÿÿÿgetstatus",
|
||||
RConGetInfo = "ÿÿÿÿgetinfo",
|
||||
RConResponse = "ÿÿÿÿprint",
|
||||
RconGetInfoResponseHeader = "ÿÿÿÿinfoResponse"
|
||||
},
|
||||
ServerNotRunningResponse = "Server is not running."
|
||||
};
|
||||
|
||||
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]+) *$";
|
||||
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);
|
||||
|
||||
Configuration.Dvar.Pattern = "^\"(.+)\" is: \"(.+)?\" default: \"(.+)?\"\n(?:latched: \"(.+)?\"\n)? *(.+)$";
|
||||
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);
|
||||
Configuration.Dvar.AddMapping(ParserRegex.GroupType.AdditionalGroup, int.MaxValue);
|
||||
|
||||
Configuration.StatusHeader.Pattern = "num +score +ping +guid +name +lastmsg +address +qport +rate *";
|
||||
Configuration.GametypeStatus.Pattern = "";
|
||||
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;
|
||||
public string Name { get; set; } = "Call of Duty";
|
||||
public string RConEngine { get; set; } = "COD";
|
||||
|
||||
public async Task<string[]> ExecuteCommandAsync(IRConConnection connection, string command)
|
||||
{
|
||||
var response = await connection.SendQueryAsync(StaticHelpers.QueryType.COMMAND, command);
|
||||
return response.Skip(1).ToArray();
|
||||
}
|
||||
|
||||
public async Task<Dvar<T>> GetDvarAsync<T>(IRConConnection connection, string dvarName, T fallbackValue = default)
|
||||
{
|
||||
string[] lineSplit;
|
||||
|
||||
try
|
||||
{
|
||||
lineSplit = await connection.SendQueryAsync(StaticHelpers.QueryType.GET_DVAR, dvarName);
|
||||
}
|
||||
catch
|
||||
{
|
||||
if (fallbackValue == null)
|
||||
{
|
||||
throw;
|
||||
}
|
||||
|
||||
lineSplit = new string[0];
|
||||
}
|
||||
|
||||
string response = string.Join('\n', lineSplit).TrimEnd('\0');
|
||||
var match = Regex.Match(response, Configuration.Dvar.Pattern);
|
||||
|
||||
if (response.Contains("Unknown command") ||
|
||||
!match.Success)
|
||||
{
|
||||
if (fallbackValue != null)
|
||||
{
|
||||
return new Dvar<T>()
|
||||
{
|
||||
Name = dvarName,
|
||||
Value = fallbackValue
|
||||
};
|
||||
}
|
||||
|
||||
throw new DvarException(Utilities.CurrentLocalization.LocalizationIndex["SERVER_ERROR_DVAR"].FormatExt(dvarName));
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
return new Dvar<T>()
|
||||
{
|
||||
Name = dvarName,
|
||||
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)),
|
||||
Domain = match.Groups[Configuration.Dvar.GroupMapping[ParserRegex.GroupType.RConDvarDomain]].Value
|
||||
};
|
||||
}
|
||||
|
||||
public virtual async Task<IStatusResponse> GetStatusAsync(IRConConnection connection)
|
||||
{
|
||||
var response = await connection.SendQueryAsync(StaticHelpers.QueryType.COMMAND_STATUS);
|
||||
_logger.LogDebug("Status Response {response}", string.Join(Environment.NewLine, response));
|
||||
return new StatusResponse
|
||||
{
|
||||
Clients = ClientsFromStatus(response).ToArray(),
|
||||
Map = GetValueFromStatus<string>(response, ParserRegex.GroupType.RConStatusMap, Configuration.MapStatus.Pattern),
|
||||
GameType = GetValueFromStatus<string>(response, ParserRegex.GroupType.RConStatusGametype, Configuration.GametypeStatus.Pattern),
|
||||
Hostname = GetValueFromStatus<string>(response, ParserRegex.GroupType.RConStatusHostname, Configuration.HostnameStatus.Pattern),
|
||||
MaxClients = GetValueFromStatus<int?>(response, ParserRegex.GroupType.RConStatusMaxPlayers, Configuration.MaxPlayersStatus.Pattern)
|
||||
};
|
||||
}
|
||||
|
||||
private T GetValueFromStatus<T>(IEnumerable<string> response, ParserRegex.GroupType groupType, string groupPattern)
|
||||
{
|
||||
if (string.IsNullOrEmpty(groupPattern))
|
||||
{
|
||||
return default;
|
||||
}
|
||||
|
||||
string value = null;
|
||||
foreach (var line in response)
|
||||
{
|
||||
var regex = Regex.Match(line, groupPattern);
|
||||
if (regex.Success)
|
||||
{
|
||||
value = regex.Groups[Configuration.MapStatus.GroupMapping[groupType]].ToString();
|
||||
}
|
||||
}
|
||||
|
||||
if (value == null)
|
||||
{
|
||||
return default;
|
||||
}
|
||||
|
||||
if (typeof(T) == typeof(int?))
|
||||
{
|
||||
return (T)Convert.ChangeType(int.Parse(value), Nullable.GetUnderlyingType(typeof(T)));
|
||||
}
|
||||
|
||||
return (T)Convert.ChangeType(value, typeof(T));
|
||||
}
|
||||
|
||||
public async Task<bool> SetDvarAsync(IRConConnection connection, string dvarName, object dvarValue)
|
||||
{
|
||||
string dvarString = (dvarValue is string str)
|
||||
? $"{dvarName} \"{str}\""
|
||||
: $"{dvarName} {dvarValue}";
|
||||
|
||||
return (await connection.SendQueryAsync(StaticHelpers.QueryType.SET_DVAR, dvarString)).Length > 0;
|
||||
}
|
||||
|
||||
private List<EFClient> ClientsFromStatus(string[] Status)
|
||||
{
|
||||
List<EFClient> StatusPlayers = new List<EFClient>();
|
||||
|
||||
bool parsedHeader = false;
|
||||
foreach (string statusLine in Status)
|
||||
{
|
||||
string responseLine = statusLine.Trim();
|
||||
|
||||
if (Configuration.StatusHeader.PatternMatcher.Match(responseLine).Success)
|
||||
{
|
||||
parsedHeader = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
var match = Configuration.Status.PatternMatcher.Match(responseLine);
|
||||
|
||||
if (match.Success)
|
||||
{
|
||||
if (match.Values[Configuration.Status.GroupMapping[ParserRegex.GroupType.RConPing]] == "ZMBI")
|
||||
{
|
||||
_logger.LogDebug("Ignoring detected client {client} because they are zombie state", string.Join(",", match.Values));
|
||||
continue;
|
||||
}
|
||||
|
||||
int clientNumber = int.Parse(match.Values[Configuration.Status.GroupMapping[ParserRegex.GroupType.RConClientNumber]]);
|
||||
int score = int.Parse(match.Values[Configuration.Status.GroupMapping[ParserRegex.GroupType.RConScore]]);
|
||||
|
||||
int ping = 999;
|
||||
|
||||
// their state can be CNCT, ZMBI etc
|
||||
if (match.Values[Configuration.Status.GroupMapping[ParserRegex.GroupType.RConPing]].Length <= 3)
|
||||
{
|
||||
ping = int.Parse(match.Values[Configuration.Status.GroupMapping[ParserRegex.GroupType.RConPing]]);
|
||||
}
|
||||
|
||||
long networkId;
|
||||
string name = match.Values[Configuration.Status.GroupMapping[ParserRegex.GroupType.RConName]].TrimNewLine();
|
||||
string networkIdString;
|
||||
var ip = match.Values[Configuration.Status.GroupMapping[ParserRegex.GroupType.RConIpAddress]].Split(':')[0].ConvertToIP();
|
||||
|
||||
try
|
||||
{
|
||||
networkIdString = match.Values[Configuration.Status.GroupMapping[ParserRegex.GroupType.RConNetworkId]];
|
||||
|
||||
networkId = networkIdString.IsBotGuid() || (ip == null && ping == 999) ?
|
||||
name.GenerateGuidFromString() :
|
||||
networkIdString.ConvertGuidToLong(Configuration.GuidNumberStyle);
|
||||
}
|
||||
|
||||
catch (FormatException)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var client = new EFClient()
|
||||
{
|
||||
CurrentAlias = new EFAlias()
|
||||
{
|
||||
Name = name,
|
||||
IPAddress = ip
|
||||
},
|
||||
NetworkId = networkId,
|
||||
ClientNumber = clientNumber,
|
||||
Ping = ping,
|
||||
Score = score,
|
||||
State = EFClient.ClientState.Connecting
|
||||
};
|
||||
|
||||
client.SetAdditionalProperty("BotGuid", networkIdString);
|
||||
var additionalGroupIndex = Configuration.Status.GroupMapping[ParserRegex.GroupType.AdditionalGroup];
|
||||
if (match.Values.Length > additionalGroupIndex)
|
||||
{
|
||||
client.SetAdditionalProperty("ConnectionClientId", match.Values[additionalGroupIndex]);
|
||||
}
|
||||
|
||||
StatusPlayers.Add(client);
|
||||
}
|
||||
}
|
||||
|
||||
// this can happen if status is requested while map is rotating and we get a log dump back
|
||||
if (!parsedHeader)
|
||||
{
|
||||
throw new ServerException(Utilities.CurrentLocalization.LocalizationIndex["SERVER_ERROR_UNEXPECTED_STATUS"]);
|
||||
}
|
||||
|
||||
return StatusPlayers;
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
public TimeSpan OverrideTimeoutForCommand(string command)
|
||||
{
|
||||
if (command.Contains("map_rotate", StringComparison.InvariantCultureIgnoreCase) ||
|
||||
command.StartsWith("map ", StringComparison.InvariantCultureIgnoreCase))
|
||||
{
|
||||
return TimeSpan.FromSeconds(30);
|
||||
}
|
||||
|
||||
return TimeSpan.Zero;
|
||||
}
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user