partial T7 (BO3) support. includes rcon communication improvements and a small fix for displaying live radar tab
This commit is contained in:
parent
5bc1ad5926
commit
8c29027b3f
@ -911,7 +911,8 @@ namespace IW4MAdmin
|
|||||||
Version = RconParser.Version;
|
Version = RconParser.Version;
|
||||||
}
|
}
|
||||||
|
|
||||||
var svRunning = await this.GetDvarAsync<string>("sv_running");
|
// 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);
|
||||||
|
|
||||||
if (!string.IsNullOrEmpty(svRunning.Value) && svRunning.Value != "1")
|
if (!string.IsNullOrEmpty(svRunning.Value) && svRunning.Value != "1")
|
||||||
{
|
{
|
||||||
@ -924,7 +925,7 @@ namespace IW4MAdmin
|
|||||||
(await this.GetDvarAsync<string>("sv_hostname")).Value :
|
(await this.GetDvarAsync<string>("sv_hostname")).Value :
|
||||||
infoResponse.Where(kvp => kvp.Key.Contains("hostname")).Select(kvp => kvp.Value).First();
|
infoResponse.Where(kvp => kvp.Key.Contains("hostname")).Select(kvp => kvp.Value).First();
|
||||||
var mapname = infoResponse == null ?
|
var mapname = infoResponse == null ?
|
||||||
(await this.GetDvarAsync<string>("mapname")).Value :
|
(await this.GetDvarAsync("mapname", "Unknown")).Value :
|
||||||
infoResponse["mapname"];
|
infoResponse["mapname"];
|
||||||
int maxplayers = (GameName == Game.IW4) ? // gotta love IW4 idiosyncrasies
|
int maxplayers = (GameName == Game.IW4) ? // gotta love IW4 idiosyncrasies
|
||||||
(await this.GetDvarAsync<int>("party_maxplayers")).Value :
|
(await this.GetDvarAsync<int>("party_maxplayers")).Value :
|
||||||
@ -932,12 +933,12 @@ namespace IW4MAdmin
|
|||||||
(await this.GetDvarAsync<int>("sv_maxclients")).Value :
|
(await this.GetDvarAsync<int>("sv_maxclients")).Value :
|
||||||
Convert.ToInt32(infoResponse["sv_maxclients"]);
|
Convert.ToInt32(infoResponse["sv_maxclients"]);
|
||||||
var gametype = infoResponse == null ?
|
var gametype = infoResponse == null ?
|
||||||
(await this.GetDvarAsync<string>("g_gametype")).Value :
|
(await this.GetDvarAsync("g_gametype", GameName == Game.T7 ? "" : null)).Value :
|
||||||
infoResponse.Where(kvp => kvp.Key.Contains("gametype")).Select(kvp => kvp.Value).First();
|
infoResponse.Where(kvp => kvp.Key.Contains("gametype")).Select(kvp => kvp.Value).First();
|
||||||
var basepath = await this.GetDvarAsync<string>("fs_basepath");
|
var basepath = await this.GetDvarAsync("fs_basepath", GameName == Game.T7 ? "" : null);
|
||||||
var basegame = await this.GetDvarAsync<string>("fs_basegame");
|
var basegame = await this.GetDvarAsync("fs_basegame", GameName == Game.T7 ? "" : null);
|
||||||
var game = infoResponse == null || !infoResponse.ContainsKey("fs_game") ?
|
var game = infoResponse == null || !infoResponse.ContainsKey("fs_game") ?
|
||||||
(await this.GetDvarAsync<string>("fs_game")).Value :
|
(await this.GetDvarAsync("fs_game", GameName == Game.T7 ? "" : null)).Value :
|
||||||
infoResponse["fs_game"];
|
infoResponse["fs_game"];
|
||||||
var logfile = await this.GetDvarAsync<string>("g_log");
|
var logfile = await this.GetDvarAsync<string>("g_log");
|
||||||
var logsync = await this.GetDvarAsync<int>("g_logsync");
|
var logsync = await this.GetDvarAsync<int>("g_logsync");
|
||||||
@ -1002,9 +1003,14 @@ namespace IW4MAdmin
|
|||||||
CustomCallback = await ScriptLoaded();
|
CustomCallback = await ScriptLoaded();
|
||||||
|
|
||||||
// they've manually specified the log path
|
// they've manually specified the log path
|
||||||
if (!string.IsNullOrEmpty(ServerConfig.ManualLogPath))
|
if (!string.IsNullOrEmpty(ServerConfig.ManualLogPath) || !RconParser.CanGenerateLogPath)
|
||||||
{
|
{
|
||||||
LogPath = ServerConfig.ManualLogPath;
|
LogPath = ServerConfig.ManualLogPath;
|
||||||
|
|
||||||
|
if (string.IsNullOrEmpty(LogPath) && !RconParser.CanGenerateLogPath)
|
||||||
|
{
|
||||||
|
throw new ServerException(loc["SERVER_ERROR_REQUIRES_PATH"].FormatExt(GameName.ToString()));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
else
|
else
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
using System;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
using System.Net.Sockets;
|
using System.Net.Sockets;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
|
|
||||||
@ -22,6 +23,7 @@ namespace IW4MAdmin.Application.RCon
|
|||||||
public readonly SemaphoreSlim OnComplete = new SemaphoreSlim(1, 1);
|
public readonly SemaphoreSlim OnComplete = new SemaphoreSlim(1, 1);
|
||||||
public readonly ManualResetEventSlim OnSentData = new ManualResetEventSlim(false);
|
public readonly ManualResetEventSlim OnSentData = new ManualResetEventSlim(false);
|
||||||
public readonly ManualResetEventSlim OnReceivedData = new ManualResetEventSlim(false);
|
public readonly ManualResetEventSlim OnReceivedData = new ManualResetEventSlim(false);
|
||||||
|
public List<int> BytesReadPerSegment { get; set; } = new List<int>();
|
||||||
public SocketAsyncEventArgs SendEventArgs { get; set; } = new SocketAsyncEventArgs();
|
public SocketAsyncEventArgs SendEventArgs { get; set; } = new SocketAsyncEventArgs();
|
||||||
public SocketAsyncEventArgs ReceiveEventArgs { get; set; } = new SocketAsyncEventArgs();
|
public SocketAsyncEventArgs ReceiveEventArgs { get; set; } = new SocketAsyncEventArgs();
|
||||||
public DateTime LastQuery { get; set; } = DateTime.Now;
|
public DateTime LastQuery { get; set; } = DateTime.Now;
|
||||||
|
@ -4,6 +4,7 @@ using SharedLibraryCore.Interfaces;
|
|||||||
using SharedLibraryCore.RCon;
|
using SharedLibraryCore.RCon;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Concurrent;
|
using System.Collections.Concurrent;
|
||||||
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Net;
|
using System.Net;
|
||||||
using System.Net.Sockets;
|
using System.Net.Sockets;
|
||||||
@ -116,7 +117,7 @@ namespace IW4MAdmin.Application.RCon
|
|||||||
throw new NetworkException($"Invalid character encountered when converting encodings - {parameters}");
|
throw new NetworkException($"Invalid character encountered when converting encodings - {parameters}");
|
||||||
}
|
}
|
||||||
|
|
||||||
byte[] response = null;
|
byte[][] response = null;
|
||||||
|
|
||||||
retrySend:
|
retrySend:
|
||||||
using (var socket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp)
|
using (var socket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp)
|
||||||
@ -130,6 +131,7 @@ namespace IW4MAdmin.Application.RCon
|
|||||||
connectionState.OnSentData.Reset();
|
connectionState.OnSentData.Reset();
|
||||||
connectionState.OnReceivedData.Reset();
|
connectionState.OnReceivedData.Reset();
|
||||||
connectionState.ConnectionAttempts++;
|
connectionState.ConnectionAttempts++;
|
||||||
|
connectionState.BytesReadPerSegment.Clear();
|
||||||
#if DEBUG == true
|
#if DEBUG == true
|
||||||
_log.WriteDebug($"Sending {payload.Length} bytes to [{this.Endpoint}] ({connectionState.ConnectionAttempts}/{StaticHelpers.AllowedConnectionFails})");
|
_log.WriteDebug($"Sending {payload.Length} bytes to [{this.Endpoint}] ({connectionState.ConnectionAttempts}/{StaticHelpers.AllowedConnectionFails})");
|
||||||
#endif
|
#endif
|
||||||
@ -137,7 +139,7 @@ namespace IW4MAdmin.Application.RCon
|
|||||||
{
|
{
|
||||||
response = await SendPayloadAsync(payload, waitForResponse);
|
response = await SendPayloadAsync(payload, waitForResponse);
|
||||||
|
|
||||||
if (response.Length == 0 && waitForResponse)
|
if ((response.Length == 0 || response[0].Length == 0) && waitForResponse)
|
||||||
{
|
{
|
||||||
throw new NetworkException("Expected response but got 0 bytes back");
|
throw new NetworkException("Expected response but got 0 bytes back");
|
||||||
}
|
}
|
||||||
@ -165,7 +167,9 @@ namespace IW4MAdmin.Application.RCon
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
string responseString = _gameEncoding.GetString(response, 0, response.Length) + '\n';
|
string responseString = type == StaticHelpers.QueryType.COMMAND_STATUS ?
|
||||||
|
ReassembleSegmentedStatus(response) :
|
||||||
|
_gameEncoding.GetString(response[0]) + '\n';
|
||||||
|
|
||||||
// note: not all games respond if the pasword is wrong or not set
|
// note: not all games respond if the pasword is wrong or not set
|
||||||
if (responseString.Contains("Invalid password") || responseString.Contains("rconpassword"))
|
if (responseString.Contains("Invalid password") || responseString.Contains("rconpassword"))
|
||||||
@ -183,13 +187,46 @@ namespace IW4MAdmin.Application.RCon
|
|||||||
throw new ServerException(Utilities.CurrentLocalization.LocalizationIndex["SERVER_ERROR_NOT_RUNNING"].FormatExt(Endpoint.ToString()));
|
throw new ServerException(Utilities.CurrentLocalization.LocalizationIndex["SERVER_ERROR_NOT_RUNNING"].FormatExt(Endpoint.ToString()));
|
||||||
}
|
}
|
||||||
|
|
||||||
string[] splitResponse = responseString.Split(new char[] { '\n' }, StringSplitOptions.RemoveEmptyEntries)
|
string[] headerSplit = responseString.Split(config.CommandPrefixes.RConResponse);
|
||||||
.Select(line => line.Trim())
|
|
||||||
.ToArray();
|
if (headerSplit.Length != 2 && type != StaticHelpers.QueryType.GET_INFO)
|
||||||
|
{
|
||||||
|
throw new NetworkException("Unexpected response header from server");
|
||||||
|
}
|
||||||
|
|
||||||
|
string[] splitResponse = headerSplit.Last().Split(new char[] { '\n' }, StringSplitOptions.RemoveEmptyEntries);
|
||||||
return splitResponse;
|
return splitResponse;
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task<byte[]> SendPayloadAsync(byte[] payload, bool waitForResponse)
|
/// <summary>
|
||||||
|
/// reassembles broken status segments into the 'correct' ordering
|
||||||
|
/// <remarks>this is primarily for T7, and is really only reliable for 2 segments</remarks>
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="segments">array of segmented byte arrays</param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public string ReassembleSegmentedStatus(byte[][] segments)
|
||||||
|
{
|
||||||
|
var splitStatusStrings = new List<string>();
|
||||||
|
|
||||||
|
foreach (byte[] segment in segments)
|
||||||
|
{
|
||||||
|
string responseString = _gameEncoding.GetString(segment, 0, segment.Length);
|
||||||
|
var statusHeaderMatch = config.StatusHeader.PatternMatcher.Match(responseString);
|
||||||
|
if (statusHeaderMatch.Success)
|
||||||
|
{
|
||||||
|
splitStatusStrings.Insert(0, responseString);
|
||||||
|
}
|
||||||
|
|
||||||
|
else
|
||||||
|
{
|
||||||
|
splitStatusStrings.Add(responseString.Replace(config.CommandPrefixes.RConResponse, ""));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return string.Join("", splitStatusStrings);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task<byte[][]> SendPayloadAsync(byte[] payload, bool waitForResponse)
|
||||||
{
|
{
|
||||||
var connectionState = ActiveQueries[this.Endpoint];
|
var connectionState = ActiveQueries[this.Endpoint];
|
||||||
var rconSocket = (Socket)connectionState.SendEventArgs.UserToken;
|
var rconSocket = (Socket)connectionState.SendEventArgs.UserToken;
|
||||||
@ -223,7 +260,7 @@ namespace IW4MAdmin.Application.RCon
|
|||||||
|
|
||||||
if (!waitForResponse)
|
if (!waitForResponse)
|
||||||
{
|
{
|
||||||
return new byte[0];
|
return new byte[0][];
|
||||||
}
|
}
|
||||||
|
|
||||||
connectionState.ReceiveEventArgs.SetBuffer(connectionState.ReceiveBuffer);
|
connectionState.ReceiveEventArgs.SetBuffer(connectionState.ReceiveBuffer);
|
||||||
@ -233,7 +270,7 @@ namespace IW4MAdmin.Application.RCon
|
|||||||
|
|
||||||
if (receiveDataPending)
|
if (receiveDataPending)
|
||||||
{
|
{
|
||||||
if (!await Task.Run(() => connectionState.OnReceivedData.Wait(StaticHelpers.SocketTimeout)))
|
if (!await Task.Run(() => connectionState.OnReceivedData.Wait(10000)))
|
||||||
{
|
{
|
||||||
rconSocket.Close();
|
rconSocket.Close();
|
||||||
throw new NetworkException("Timed out waiting for response", rconSocket);
|
throw new NetworkException("Timed out waiting for response", rconSocket);
|
||||||
@ -242,11 +279,20 @@ namespace IW4MAdmin.Application.RCon
|
|||||||
|
|
||||||
rconSocket.Close();
|
rconSocket.Close();
|
||||||
|
|
||||||
byte[] response = connectionState.ReceiveBuffer
|
var responseList = new List<byte[]>();
|
||||||
.Take(connectionState.ReceiveEventArgs.BytesTransferred)
|
int totalBytesRead = 0;
|
||||||
.ToArray();
|
|
||||||
|
|
||||||
return response;
|
foreach (int bytesRead in connectionState.BytesReadPerSegment)
|
||||||
|
{
|
||||||
|
responseList.Add(connectionState.ReceiveBuffer
|
||||||
|
.Skip(totalBytesRead)
|
||||||
|
.Take(bytesRead)
|
||||||
|
.ToArray());
|
||||||
|
|
||||||
|
totalBytesRead += bytesRead;
|
||||||
|
}
|
||||||
|
|
||||||
|
return responseList.ToArray();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnDataReceived(object sender, SocketAsyncEventArgs e)
|
private void OnDataReceived(object sender, SocketAsyncEventArgs e)
|
||||||
@ -254,7 +300,48 @@ namespace IW4MAdmin.Application.RCon
|
|||||||
#if DEBUG == true
|
#if DEBUG == true
|
||||||
_log.WriteDebug($"Read {e.BytesTransferred} bytes from {e.RemoteEndPoint.ToString()}");
|
_log.WriteDebug($"Read {e.BytesTransferred} bytes from {e.RemoteEndPoint.ToString()}");
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
// this occurs when we close the socket
|
||||||
|
if (e.BytesTransferred == 0)
|
||||||
|
{
|
||||||
ActiveQueries[this.Endpoint].OnReceivedData.Set();
|
ActiveQueries[this.Endpoint].OnReceivedData.Set();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (sender is Socket sock)
|
||||||
|
{
|
||||||
|
var state = ActiveQueries[this.Endpoint];
|
||||||
|
state.BytesReadPerSegment.Add(e.BytesTransferred);
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// we still have available data so the payload was segmented
|
||||||
|
if (sock.Available > 0)
|
||||||
|
{
|
||||||
|
state.ReceiveEventArgs.SetBuffer(state.ReceiveBuffer, e.BytesTransferred, state.ReceiveBuffer.Length - e.BytesTransferred);
|
||||||
|
|
||||||
|
if (!sock.ReceiveAsync(state.ReceiveEventArgs))
|
||||||
|
{
|
||||||
|
#if DEBUG == true
|
||||||
|
_log.WriteDebug($"Read {state.ReceiveEventArgs.BytesTransferred} synchronous bytes from {e.RemoteEndPoint.ToString()}");
|
||||||
|
#endif
|
||||||
|
// we need to increment this here because the callback isn't executed if there's no pending IO
|
||||||
|
state.BytesReadPerSegment.Add(state.ReceiveEventArgs.BytesTransferred);
|
||||||
|
ActiveQueries[this.Endpoint].OnReceivedData.Set();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
else
|
||||||
|
{
|
||||||
|
ActiveQueries[this.Endpoint].OnReceivedData.Set();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
catch (ObjectDisposedException)
|
||||||
|
{
|
||||||
|
ActiveQueries[this.Endpoint].OnReceivedData.Set();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnDataSent(object sender, SocketAsyncEventArgs e)
|
private void OnDataSent(object sender, SocketAsyncEventArgs e)
|
||||||
|
@ -52,6 +52,7 @@ namespace IW4MAdmin.Application.RconParsers
|
|||||||
Configuration.Dvar.AddMapping(ParserRegex.GroupType.RConDvarLatchedValue, 4);
|
Configuration.Dvar.AddMapping(ParserRegex.GroupType.RConDvarLatchedValue, 4);
|
||||||
Configuration.Dvar.AddMapping(ParserRegex.GroupType.RConDvarDomain, 5);
|
Configuration.Dvar.AddMapping(ParserRegex.GroupType.RConDvarDomain, 5);
|
||||||
|
|
||||||
|
Configuration.StatusHeader.Pattern = "num +score +ping +guid +name +lastmsg +address +qport +rate *";
|
||||||
Configuration.MapStatus.Pattern = @"map: (([a-z]|_|\d)+)";
|
Configuration.MapStatus.Pattern = @"map: (([a-z]|_|\d)+)";
|
||||||
Configuration.MapStatus.AddMapping(ParserRegex.GroupType.RConStatusMap, 1);
|
Configuration.MapStatus.AddMapping(ParserRegex.GroupType.RConStatusMap, 1);
|
||||||
}
|
}
|
||||||
@ -69,16 +70,24 @@ namespace IW4MAdmin.Application.RconParsers
|
|||||||
return response.Skip(1).ToArray();
|
return response.Skip(1).ToArray();
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<Dvar<T>> GetDvarAsync<T>(IRConConnection connection, string dvarName)
|
public async Task<Dvar<T>> GetDvarAsync<T>(IRConConnection connection, string dvarName, T fallbackValue = default)
|
||||||
{
|
{
|
||||||
string[] lineSplit = await connection.SendQueryAsync(StaticHelpers.QueryType.GET_DVAR, dvarName);
|
string[] lineSplit = await connection.SendQueryAsync(StaticHelpers.QueryType.GET_DVAR, dvarName);
|
||||||
string response = string.Join('\n', lineSplit.Skip(1));
|
string response = string.Join('\n', lineSplit).TrimEnd('\0');
|
||||||
var match = Regex.Match(response, Configuration.Dvar.Pattern);
|
var match = Regex.Match(response, Configuration.Dvar.Pattern);
|
||||||
|
|
||||||
if (!lineSplit[0].Contains(Configuration.CommandPrefixes.RConResponse) ||
|
if (response.Contains("Unknown command") ||
|
||||||
response.Contains("Unknown command") ||
|
|
||||||
!match.Success)
|
!match.Success)
|
||||||
{
|
{
|
||||||
|
if (fallbackValue != null)
|
||||||
|
{
|
||||||
|
return new Dvar<T>()
|
||||||
|
{
|
||||||
|
Name = dvarName,
|
||||||
|
Value = fallbackValue
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
throw new DvarException(Utilities.CurrentLocalization.LocalizationIndex["SERVER_ERROR_DVAR"].FormatExt(dvarName));
|
throw new DvarException(Utilities.CurrentLocalization.LocalizationIndex["SERVER_ERROR_DVAR"].FormatExt(dvarName));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -142,36 +151,36 @@ namespace IW4MAdmin.Application.RconParsers
|
|||||||
{
|
{
|
||||||
List<EFClient> StatusPlayers = new List<EFClient>();
|
List<EFClient> StatusPlayers = new List<EFClient>();
|
||||||
|
|
||||||
if (Status.Length < 4)
|
bool parsedHeader = false;
|
||||||
{
|
|
||||||
throw new ServerException(Utilities.CurrentLocalization.LocalizationIndex["SERVER_ERROR_UNEXPECTED_STATUS"]);
|
|
||||||
}
|
|
||||||
|
|
||||||
int validMatches = 0;
|
|
||||||
foreach (string statusLine in Status)
|
foreach (string statusLine in Status)
|
||||||
{
|
{
|
||||||
string responseLine = statusLine.Trim();
|
string responseLine = statusLine.Trim();
|
||||||
|
|
||||||
var regex = Regex.Match(responseLine, Configuration.Status.Pattern, RegexOptions.IgnoreCase);
|
if (Configuration.StatusHeader.PatternMatcher.Match(responseLine).Success)
|
||||||
|
|
||||||
if (regex.Success)
|
|
||||||
{
|
{
|
||||||
validMatches++;
|
parsedHeader = true;
|
||||||
int clientNumber = int.Parse(regex.Groups[Configuration.Status.GroupMapping[ParserRegex.GroupType.RConClientNumber]].Value);
|
continue;
|
||||||
int score = int.Parse(regex.Groups[Configuration.Status.GroupMapping[ParserRegex.GroupType.RConScore]].Value);
|
}
|
||||||
|
|
||||||
|
var match = Configuration.Status.PatternMatcher.Match(responseLine);
|
||||||
|
|
||||||
|
if (match.Success)
|
||||||
|
{
|
||||||
|
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;
|
int ping = 999;
|
||||||
|
|
||||||
// their state can be CNCT, ZMBI etc
|
// their state can be CNCT, ZMBI etc
|
||||||
if (regex.Groups[Configuration.Status.GroupMapping[ParserRegex.GroupType.RConPing]].Value.Length <= 3)
|
if (match.Values[Configuration.Status.GroupMapping[ParserRegex.GroupType.RConPing]].Length <= 3)
|
||||||
{
|
{
|
||||||
ping = int.Parse(regex.Groups[Configuration.Status.GroupMapping[ParserRegex.GroupType.RConPing]].Value);
|
ping = int.Parse(match.Values[Configuration.Status.GroupMapping[ParserRegex.GroupType.RConPing]]);
|
||||||
}
|
}
|
||||||
|
|
||||||
long networkId;
|
long networkId;
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
networkId = regex.Groups[Configuration.Status.GroupMapping[ParserRegex.GroupType.RConNetworkId]].Value.ConvertGuidToLong(Configuration.GuidNumberStyle);
|
networkId = match.Values[Configuration.Status.GroupMapping[ParserRegex.GroupType.RConNetworkId]].ConvertGuidToLong(Configuration.GuidNumberStyle);
|
||||||
}
|
}
|
||||||
|
|
||||||
catch (FormatException)
|
catch (FormatException)
|
||||||
@ -179,8 +188,8 @@ namespace IW4MAdmin.Application.RconParsers
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
string name = regex.Groups[Configuration.Status.GroupMapping[ParserRegex.GroupType.RConName]].Value.TrimNewLine();
|
string name = match.Values[Configuration.Status.GroupMapping[ParserRegex.GroupType.RConName]].TrimNewLine();
|
||||||
int? ip = regex.Groups[Configuration.Status.GroupMapping[ParserRegex.GroupType.RConIpAddress]].Value.Split(':')[0].ConvertToIP();
|
int? ip = match.Values[Configuration.Status.GroupMapping[ParserRegex.GroupType.RConIpAddress]].Split(':')[0].ConvertToIP();
|
||||||
|
|
||||||
var client = new EFClient()
|
var client = new EFClient()
|
||||||
{
|
{
|
||||||
@ -208,10 +217,10 @@ namespace IW4MAdmin.Application.RconParsers
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// this happens if status is requested while map is rotating
|
// this can happen if status is requested while map is rotating and we get a log dump back
|
||||||
if (Status.Length > MAX_FAULTY_STATUS_LINES && validMatches == 0)
|
if (!parsedHeader)
|
||||||
{
|
{
|
||||||
throw new ServerException(Utilities.CurrentLocalization.LocalizationIndex["SERVER_ERROR_ROTATING_MAP"]);
|
throw new ServerException(Utilities.CurrentLocalization.LocalizationIndex["SERVER_ERROR_UNEXPECTED_STATUS"]);
|
||||||
}
|
}
|
||||||
|
|
||||||
return StatusPlayers;
|
return StatusPlayers;
|
||||||
|
@ -15,6 +15,7 @@ namespace IW4MAdmin.Application.RconParsers
|
|||||||
public ParserRegex Status { get; set; }
|
public ParserRegex Status { get; set; }
|
||||||
public ParserRegex MapStatus { get; set; }
|
public ParserRegex MapStatus { get; set; }
|
||||||
public ParserRegex Dvar { get; set; }
|
public ParserRegex Dvar { get; set; }
|
||||||
|
public ParserRegex StatusHeader { get; set; }
|
||||||
public string ServerNotRunningResponse { get; set; }
|
public string ServerNotRunningResponse { get; set; }
|
||||||
public bool WaitForResponse { get; set; } = true;
|
public bool WaitForResponse { get; set; } = true;
|
||||||
public NumberStyles GuidNumberStyle { get; set; } = NumberStyles.HexNumber;
|
public NumberStyles GuidNumberStyle { get; set; } = NumberStyles.HexNumber;
|
||||||
@ -24,6 +25,7 @@ namespace IW4MAdmin.Application.RconParsers
|
|||||||
Status = parserRegexFactory.CreateParserRegex();
|
Status = parserRegexFactory.CreateParserRegex();
|
||||||
MapStatus = parserRegexFactory.CreateParserRegex();
|
MapStatus = parserRegexFactory.CreateParserRegex();
|
||||||
Dvar = parserRegexFactory.CreateParserRegex();
|
Dvar = parserRegexFactory.CreateParserRegex();
|
||||||
|
StatusHeader = parserRegexFactory.CreateParserRegex();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -43,6 +43,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "ScriptPlugins", "ScriptPlug
|
|||||||
Plugins\ScriptPlugins\ParserPIW5.js = Plugins\ScriptPlugins\ParserPIW5.js
|
Plugins\ScriptPlugins\ParserPIW5.js = Plugins\ScriptPlugins\ParserPIW5.js
|
||||||
Plugins\ScriptPlugins\ParserPT6.js = Plugins\ScriptPlugins\ParserPT6.js
|
Plugins\ScriptPlugins\ParserPT6.js = Plugins\ScriptPlugins\ParserPT6.js
|
||||||
Plugins\ScriptPlugins\ParserRektT5M.js = Plugins\ScriptPlugins\ParserRektT5M.js
|
Plugins\ScriptPlugins\ParserRektT5M.js = Plugins\ScriptPlugins\ParserRektT5M.js
|
||||||
|
Plugins\ScriptPlugins\ParserT7.js = Plugins\ScriptPlugins\ParserT7.js
|
||||||
Plugins\ScriptPlugins\ParserTeknoMW3.js = Plugins\ScriptPlugins\ParserTeknoMW3.js
|
Plugins\ScriptPlugins\ParserTeknoMW3.js = Plugins\ScriptPlugins\ParserTeknoMW3.js
|
||||||
Plugins\ScriptPlugins\SharedGUIDKick.js = Plugins\ScriptPlugins\SharedGUIDKick.js
|
Plugins\ScriptPlugins\SharedGUIDKick.js = Plugins\ScriptPlugins\SharedGUIDKick.js
|
||||||
Plugins\ScriptPlugins\VPNDetection.js = Plugins\ScriptPlugins\VPNDetection.js
|
Plugins\ScriptPlugins\VPNDetection.js = Plugins\ScriptPlugins\VPNDetection.js
|
||||||
|
@ -25,6 +25,15 @@ namespace LiveRadar
|
|||||||
|
|
||||||
public Task OnEventAsync(GameEvent E, Server S)
|
public Task OnEventAsync(GameEvent E, Server S)
|
||||||
{
|
{
|
||||||
|
// if it's an IW4 game, with custom callbacks, we want to
|
||||||
|
// enable the live radar page
|
||||||
|
if (E.Type == GameEvent.EventType.Start &&
|
||||||
|
S.GameName == Server.Game.IW4 &&
|
||||||
|
S.CustomCallback)
|
||||||
|
{
|
||||||
|
E.Owner.Manager.GetPageList().Pages.Add(Utilities.CurrentLocalization.LocalizationIndex["WEBFRONT_RADAR_TITLE"], "/Radar/All");
|
||||||
|
}
|
||||||
|
|
||||||
if (E.Type == GameEvent.EventType.Unknown)
|
if (E.Type == GameEvent.EventType.Unknown)
|
||||||
{
|
{
|
||||||
if (E.Data?.StartsWith("LiveRadar") ?? false)
|
if (E.Data?.StartsWith("LiveRadar") ?? false)
|
||||||
@ -59,11 +68,6 @@ namespace LiveRadar
|
|||||||
_configurationHandler.Set((LiveRadarConfiguration)new LiveRadarConfiguration().Generate());
|
_configurationHandler.Set((LiveRadarConfiguration)new LiveRadarConfiguration().Generate());
|
||||||
await _configurationHandler.Save();
|
await _configurationHandler.Save();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (manager.GetServers().Any(_server => _server.GameName == Server.Game.IW4))
|
|
||||||
{
|
|
||||||
manager.GetPageList().Pages.Add(Utilities.CurrentLocalization.LocalizationIndex["WEBFRONT_RADAR_TITLE"], "/Radar/All");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public Task OnTickAsync(Server S)
|
public Task OnTickAsync(Server S)
|
||||||
|
@ -3,7 +3,7 @@ var eventParser;
|
|||||||
|
|
||||||
var plugin = {
|
var plugin = {
|
||||||
author: 'FrenchFry, RaidMax',
|
author: 'FrenchFry, RaidMax',
|
||||||
version: 0.6,
|
version: 0.7,
|
||||||
name: 'CoD4x Parser',
|
name: 'CoD4x Parser',
|
||||||
isParser: true,
|
isParser: true,
|
||||||
|
|
||||||
@ -14,6 +14,7 @@ var plugin = {
|
|||||||
rconParser = manager.GenerateDynamicRConParser(this.name);
|
rconParser = manager.GenerateDynamicRConParser(this.name);
|
||||||
eventParser = manager.GenerateDynamicEventParser(this.name);
|
eventParser = manager.GenerateDynamicEventParser(this.name);
|
||||||
|
|
||||||
|
rconParser.Configuration.StatusHeader.Pattern = 'num +score +ping +playerid +steamid +name +lastmsg +address +qport +rate *';
|
||||||
rconParser.Configuration.Status.Pattern = '^ *([0-9]+) +-?([0-9]+) +((?:[A-Z]+|[0-9]+)) +((?:[a-z]|[0-9]){16,32}|(?:[a-z]|[0-9]){32}|bot[0-9]+) ([0-9+]) *(.{0,32}) +([0-9]+) +(\\d+\\.\\d+\\.\\d+.\\d+\\:-*\\d{1,5}|0+.0+:-*\\d{1,5}|loopback) +(-*[0-9]+) +([0-9]+) *$'
|
rconParser.Configuration.Status.Pattern = '^ *([0-9]+) +-?([0-9]+) +((?:[A-Z]+|[0-9]+)) +((?:[a-z]|[0-9]){16,32}|(?:[a-z]|[0-9]){32}|bot[0-9]+) ([0-9+]) *(.{0,32}) +([0-9]+) +(\\d+\\.\\d+\\.\\d+.\\d+\\:-*\\d{1,5}|0+.0+:-*\\d{1,5}|loopback) +(-*[0-9]+) +([0-9]+) *$'
|
||||||
rconParser.Configuration.Status.AddMapping(104, 6); // RConName
|
rconParser.Configuration.Status.AddMapping(104, 6); // RConName
|
||||||
rconParser.Configuration.Status.AddMapping(105, 8); // RConIPAddress
|
rconParser.Configuration.Status.AddMapping(105, 8); // RConIPAddress
|
||||||
|
@ -3,7 +3,7 @@ var eventParser;
|
|||||||
|
|
||||||
var plugin = {
|
var plugin = {
|
||||||
author: 'RaidMax',
|
author: 'RaidMax',
|
||||||
version: 0.1,
|
version: 0.2,
|
||||||
name: 'Plutonium IW5 Parser',
|
name: 'Plutonium IW5 Parser',
|
||||||
isParser: true,
|
isParser: true,
|
||||||
|
|
||||||
@ -24,9 +24,10 @@ var plugin = {
|
|||||||
rconParser.Configuration.Dvar.Pattern = '^(.+) is "(.+)?"';
|
rconParser.Configuration.Dvar.Pattern = '^(.+) is "(.+)?"';
|
||||||
rconParser.Configuration.Dvar.AddMapping(106, 1);
|
rconParser.Configuration.Dvar.AddMapping(106, 1);
|
||||||
rconParser.Configuration.Dvar.AddMapping(107, 2);
|
rconParser.Configuration.Dvar.AddMapping(107, 2);
|
||||||
rconParser.Configuration.WaitForResponse = false;
|
rconParser.Configuration.WaitForResponse = true;
|
||||||
rconParser.Configuration.CanGenerateLogPath = true;
|
rconParser.Configuration.CanGenerateLogPath = true;
|
||||||
|
|
||||||
|
rconParser.Configuration.StatusHeader.Pattern = 'num +score +bot +ping +guid +name +lastmsg +address +qport +rate *';
|
||||||
rconParser.Configuration.Status.Pattern = '^ *([0-9]+) +([0-9]+) +(?:[0-1]{1}) +([0-9]+) +([A-F0-9]+) +(.+?) +(?:[0-9]+) +(\\d+\\.\\d+\\.\\d+\\.\\d+\\:-?\\d{1,5}|0+\\.0+:-?\\d{1,5}|loopback) +(?:-?[0-9]+) +(?:[0-9]+) *$';
|
rconParser.Configuration.Status.Pattern = '^ *([0-9]+) +([0-9]+) +(?:[0-1]{1}) +([0-9]+) +([A-F0-9]+) +(.+?) +(?:[0-9]+) +(\\d+\\.\\d+\\.\\d+\\.\\d+\\:-?\\d{1,5}|0+\\.0+:-?\\d{1,5}|loopback) +(?:-?[0-9]+) +(?:[0-9]+) *$';
|
||||||
rconParser.Configuration.Status.AddMapping(100, 1);
|
rconParser.Configuration.Status.AddMapping(100, 1);
|
||||||
rconParser.Configuration.Status.AddMapping(101, 2);
|
rconParser.Configuration.Status.AddMapping(101, 2);
|
||||||
|
@ -3,7 +3,7 @@ var eventParser;
|
|||||||
|
|
||||||
var plugin = {
|
var plugin = {
|
||||||
author: 'RaidMax, Xerxes',
|
author: 'RaidMax, Xerxes',
|
||||||
version: 0.6,
|
version: 0.7,
|
||||||
name: 'Plutonium T6 Parser',
|
name: 'Plutonium T6 Parser',
|
||||||
isParser: true,
|
isParser: true,
|
||||||
|
|
||||||
@ -26,6 +26,7 @@ var plugin = {
|
|||||||
rconParser.Configuration.Dvar.AddMapping(107, 2);
|
rconParser.Configuration.Dvar.AddMapping(107, 2);
|
||||||
rconParser.Configuration.WaitForResponse = false;
|
rconParser.Configuration.WaitForResponse = false;
|
||||||
|
|
||||||
|
rconParser.Configuration.StatusHeader.Patter = 'num +score +bot +ping +guid +name +lastmsg +address +qport +rate *';
|
||||||
rconParser.Configuration.Status.Pattern = '^ *([0-9]+) +([0-9]+) +(?:[0-1]{1}) +([0-9]+) +([A-F0-9]+) +(.+?) +(?:[0-9]+) +(\\d+\\.\\d+\\.\\d+\\.\\d+\\:-?\\d{1,5}|0+\\.0+:-?\\d{1,5}|loopback) +(?:-?[0-9]+) +(?:[0-9]+) *$';
|
rconParser.Configuration.Status.Pattern = '^ *([0-9]+) +([0-9]+) +(?:[0-1]{1}) +([0-9]+) +([A-F0-9]+) +(.+?) +(?:[0-9]+) +(\\d+\\.\\d+\\.\\d+\\.\\d+\\:-?\\d{1,5}|0+\\.0+:-?\\d{1,5}|loopback) +(?:-?[0-9]+) +(?:[0-9]+) *$';
|
||||||
rconParser.Configuration.Status.AddMapping(100, 1);
|
rconParser.Configuration.Status.AddMapping(100, 1);
|
||||||
rconParser.Configuration.Status.AddMapping(101, 2);
|
rconParser.Configuration.Status.AddMapping(101, 2);
|
||||||
|
45
Plugins/ScriptPlugins/ParserT7.js
Normal file
45
Plugins/ScriptPlugins/ParserT7.js
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
var rconParser;
|
||||||
|
var eventParser;
|
||||||
|
|
||||||
|
var plugin = {
|
||||||
|
author: 'RaidMax',
|
||||||
|
version: 0.1,
|
||||||
|
name: 'Black Ops 3 Parser',
|
||||||
|
isParser: true,
|
||||||
|
|
||||||
|
onEventAsync: function (gameEvent, server) {
|
||||||
|
},
|
||||||
|
|
||||||
|
onLoadAsync: function (manager) {
|
||||||
|
rconParser = manager.GenerateDynamicRConParser(this.name);
|
||||||
|
eventParser = manager.GenerateDynamicEventParser(this.name);
|
||||||
|
|
||||||
|
rconParser.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}) +(\\d+\\.\\d+\\.\\d+.\\d+\\:-*\\d{1,5}|0+.0+:-*\\d{1,5}|loopback|unknown)(?:\\([0-9]+\\)) +(-*[0-9]+) *$'
|
||||||
|
rconParser.Configuration.StatusHeader.Pattern = 'num +score +ping +xuid +name +address +qport';
|
||||||
|
rconParser.Configuration.CommandPrefixes.Kick = 'clientkick {0}';
|
||||||
|
rconParser.Configuration.CommandPrefixes.Ban = 'clientkick {0}';
|
||||||
|
rconParser.Configuration.CommandPrefixes.TempBan = 'tempbanclient {0}';
|
||||||
|
rconParser.Configuration.CommandPrefixes.RConCommand = '\xff\xff\xff\xff\x00{0} {1}';
|
||||||
|
rconParser.Configuration.CommandPrefixes.RConGetDvar = '\xff\xff\xff\xff\x00{0} {1}';
|
||||||
|
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.Status.AddMapping(105, 6); // ip address
|
||||||
|
rconParser.Version = '[local] ship win64 CODBUILD8-764 (3421987) Mon Dec 16 10:44:20 2019 10d27bef';
|
||||||
|
rconParser.GameName = 8; // BO3
|
||||||
|
rconParser.CanGenerateLogPath = false;
|
||||||
|
|
||||||
|
eventParser.Version = '[local] ship win64 CODBUILD8-764 (3421987) Mon Dec 16 10:44:20 2019 10d27bef';
|
||||||
|
eventParser.GameName = 8; // BO3
|
||||||
|
eventParser.Configuration.GameDirectory = 'usermaps';
|
||||||
|
eventParser.Configuration.Say.Pattern = '^(chat|chatteam);(?:[0-9]+);([0-9]+);([0-9]+);(.+);(.*)$';
|
||||||
|
|
||||||
|
},
|
||||||
|
|
||||||
|
onUnloadAsync: function () {
|
||||||
|
},
|
||||||
|
|
||||||
|
onTickAsync: function (server) {
|
||||||
|
}
|
||||||
|
};
|
@ -13,8 +13,9 @@ namespace SharedLibraryCore.Interfaces
|
|||||||
/// <typeparam name="T">type of DVAR expected (string, int, float etc...)</typeparam>
|
/// <typeparam name="T">type of DVAR expected (string, int, float etc...)</typeparam>
|
||||||
/// <param name="connection">RCon connection to retrieve with</param>
|
/// <param name="connection">RCon connection to retrieve with</param>
|
||||||
/// <param name="dvarName">name of DVAR</param>
|
/// <param name="dvarName">name of DVAR</param>
|
||||||
|
/// <param name="fallbackValue">default value to return if dvar retrieval fails</param>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
Task<Dvar<T>> GetDvarAsync<T>(IRConConnection connection, string dvarName);
|
Task<Dvar<T>> GetDvarAsync<T>(IRConConnection connection, string dvarName, T fallbackValue = default);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// set value of DVAR by name
|
/// set value of DVAR by name
|
||||||
|
@ -25,6 +25,11 @@ namespace SharedLibraryCore.Interfaces
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
ParserRegex Dvar { get; set; }
|
ParserRegex Dvar { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// stores the regex info for parsing the header of a status response
|
||||||
|
/// </summary>
|
||||||
|
ParserRegex StatusHeader { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Specifies the expected response message from rcon when the server is not running
|
/// Specifies the expected response message from rcon when the server is not running
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -720,9 +720,9 @@ namespace SharedLibraryCore
|
|||||||
return Convert.ToBase64String(src.Select(c => Convert.ToByte(c)).ToArray()).Replace('+', '-').Replace('/', '_');
|
return Convert.ToBase64String(src.Select(c => Convert.ToByte(c)).ToArray()).Replace('+', '-').Replace('/', '_');
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Task<Dvar<T>> GetDvarAsync<T>(this Server server, string dvarName)
|
public static Task<Dvar<T>> GetDvarAsync<T>(this Server server, string dvarName, T fallbackValue = default)
|
||||||
{
|
{
|
||||||
return server.RconParser.GetDvarAsync<T>(server.RemoteConnection, dvarName);
|
return server.RconParser.GetDvarAsync<T>(server.RemoteConnection, dvarName, fallbackValue);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Task SetDvarAsync(this Server server, string dvarName, object dvarValue)
|
public static Task SetDvarAsync(this Server server, string dvarName, object dvarValue)
|
||||||
@ -754,7 +754,8 @@ namespace SharedLibraryCore
|
|||||||
}
|
}
|
||||||
|
|
||||||
var response = await server.RemoteConnection.SendQueryAsync(RCon.StaticHelpers.QueryType.GET_INFO);
|
var response = await server.RemoteConnection.SendQueryAsync(RCon.StaticHelpers.QueryType.GET_INFO);
|
||||||
return response.FirstOrDefault(r => r[0] == '\\')?.DictionaryFromKeyValue();
|
string combinedResponse = string.Join('\\', response.Where(r => r.Length > 0 && r[0] == '\\'));
|
||||||
|
return combinedResponse.DictionaryFromKeyValue();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static double GetVersionAsDouble()
|
public static double GetVersionAsDouble()
|
||||||
@ -883,7 +884,7 @@ namespace SharedLibraryCore
|
|||||||
{
|
{
|
||||||
foreach (char separator in DirectorySeparatorChars)
|
foreach (char separator in DirectorySeparatorChars)
|
||||||
{
|
{
|
||||||
path = path.Replace(separator, Path.DirectorySeparatorChar);
|
path = (path ?? "").Replace(separator, Path.DirectorySeparatorChar);
|
||||||
}
|
}
|
||||||
|
|
||||||
return path;
|
return path;
|
||||||
|
Loading…
Reference in New Issue
Block a user