diff --git a/Application/IW4MServer.cs b/Application/IW4MServer.cs
index 671f0aa4..72891253 100644
--- a/Application/IW4MServer.cs
+++ b/Application/IW4MServer.cs
@@ -1042,8 +1042,8 @@ namespace IW4MAdmin
EventParser = Manager.AdditionalEventParsers
.FirstOrDefault(_parser => _parser.Version == ServerConfig.EventParserVersion);
- RconParser = RconParser ?? Manager.AdditionalRConParsers[0];
- EventParser = EventParser ?? Manager.AdditionalEventParsers[0];
+ RconParser ??= Manager.AdditionalRConParsers[0];
+ EventParser ??= Manager.AdditionalEventParsers[0];
RemoteConnection = RConConnectionFactory.CreateConnection(IP, Port, Password, RconParser.RConEngine);
RemoteConnection.SetConfiguration(RconParser);
diff --git a/IW4MAdmin.sln b/IW4MAdmin.sln
index 9f2f3205..06e2edea 100644
--- a/IW4MAdmin.sln
+++ b/IW4MAdmin.sln
@@ -378,8 +378,6 @@ Global
{A9348433-58C1-4B9C-8BB7-088B02529D9D}.Debug|x64.Build.0 = Debug|Any CPU
{A9348433-58C1-4B9C-8BB7-088B02529D9D}.Debug|x86.ActiveCfg = Debug|Any CPU
{A9348433-58C1-4B9C-8BB7-088B02529D9D}.Debug|x86.Build.0 = Debug|Any CPU
- {A9348433-58C1-4B9C-8BB7-088B02529D9D}.Prerelease|Any CPU.ActiveCfg = Debug|Any CPU
- {A9348433-58C1-4B9C-8BB7-088B02529D9D}.Prerelease|Any CPU.Build.0 = Debug|Any CPU
{A9348433-58C1-4B9C-8BB7-088B02529D9D}.Prerelease|Mixed Platforms.ActiveCfg = Debug|Any CPU
{A9348433-58C1-4B9C-8BB7-088B02529D9D}.Prerelease|Mixed Platforms.Build.0 = Debug|Any CPU
{A9348433-58C1-4B9C-8BB7-088B02529D9D}.Prerelease|x64.ActiveCfg = Debug|Any CPU
@@ -394,6 +392,8 @@ Global
{A9348433-58C1-4B9C-8BB7-088B02529D9D}.Release|x64.Build.0 = Release|Any CPU
{A9348433-58C1-4B9C-8BB7-088B02529D9D}.Release|x86.ActiveCfg = Release|Any CPU
{A9348433-58C1-4B9C-8BB7-088B02529D9D}.Release|x86.Build.0 = Release|Any CPU
+ {A9348433-58C1-4B9C-8BB7-088B02529D9D}.Prerelease|Any CPU.ActiveCfg = Prerelease|Any CPU
+ {A9348433-58C1-4B9C-8BB7-088B02529D9D}.Prerelease|Any CPU.Build.0 = Prerelease|Any CPU
{9512295B-3045-40E0-9B7E-2409F2173E9D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{9512295B-3045-40E0-9B7E-2409F2173E9D}.Debug|Any CPU.Build.0 = Debug|Any CPU
{9512295B-3045-40E0-9B7E-2409F2173E9D}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
@@ -402,8 +402,6 @@ Global
{9512295B-3045-40E0-9B7E-2409F2173E9D}.Debug|x64.Build.0 = Debug|Any CPU
{9512295B-3045-40E0-9B7E-2409F2173E9D}.Debug|x86.ActiveCfg = Debug|Any CPU
{9512295B-3045-40E0-9B7E-2409F2173E9D}.Debug|x86.Build.0 = Debug|Any CPU
- {9512295B-3045-40E0-9B7E-2409F2173E9D}.Prerelease|Any CPU.ActiveCfg = Debug|Any CPU
- {9512295B-3045-40E0-9B7E-2409F2173E9D}.Prerelease|Any CPU.Build.0 = Debug|Any CPU
{9512295B-3045-40E0-9B7E-2409F2173E9D}.Prerelease|Mixed Platforms.ActiveCfg = Debug|Any CPU
{9512295B-3045-40E0-9B7E-2409F2173E9D}.Prerelease|Mixed Platforms.Build.0 = Debug|Any CPU
{9512295B-3045-40E0-9B7E-2409F2173E9D}.Prerelease|x64.ActiveCfg = Debug|Any CPU
@@ -418,6 +416,8 @@ Global
{9512295B-3045-40E0-9B7E-2409F2173E9D}.Release|x64.Build.0 = Release|Any CPU
{9512295B-3045-40E0-9B7E-2409F2173E9D}.Release|x86.ActiveCfg = Release|Any CPU
{9512295B-3045-40E0-9B7E-2409F2173E9D}.Release|x86.Build.0 = Release|Any CPU
+ {9512295B-3045-40E0-9B7E-2409F2173E9D}.Prerelease|Any CPU.ActiveCfg = Prerelease|Any CPU
+ {9512295B-3045-40E0-9B7E-2409F2173E9D}.Prerelease|Any CPU.Build.0 = Prerelease|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
diff --git a/Integrations/Cod/Integrations.Cod.csproj b/Integrations/Cod/Integrations.Cod.csproj
index 26f66bae..fa9b57d7 100644
--- a/Integrations/Cod/Integrations.Cod.csproj
+++ b/Integrations/Cod/Integrations.Cod.csproj
@@ -4,6 +4,12 @@
netcoreapp3.1
Integrations.Cod
Integrations.Cod
+ Debug;Release;Prerelease
+ AnyCPU
+
+
+
+ true
diff --git a/Integrations/Source/Extensions/SourceExtensions.cs b/Integrations/Source/Extensions/SourceExtensions.cs
new file mode 100644
index 00000000..c373e286
--- /dev/null
+++ b/Integrations/Source/Extensions/SourceExtensions.cs
@@ -0,0 +1,48 @@
+using System.Text;
+
+namespace Integrations.Source.Extensions
+{
+ public static class SourceExtensions
+ {
+ public static string ReplaceUnfriendlyCharacters(this string source)
+ {
+ var result = new StringBuilder();
+ var quoteStart = false;
+ var quoteIndex = 0;
+ var index = 0;
+
+ foreach (var character in source)
+ {
+ if (character == '%')
+ {
+ result.Append('‰');
+ }
+
+ else if ((character == '"' || character == '\'') && index + 1 != source.Length)
+ {
+ if (quoteIndex > 0)
+ {
+ result.Append(!quoteStart ? "«" : "»");
+ quoteStart = !quoteStart;
+ }
+
+ else
+ {
+ result.Append('"');
+ }
+
+ quoteIndex++;
+ }
+
+ else
+ {
+ result.Append(character);
+ }
+
+ index++;
+ }
+
+ return result.ToString();
+ }
+ }
+}
\ No newline at end of file
diff --git a/Integrations/Source/Integrations.Source.csproj b/Integrations/Source/Integrations.Source.csproj
index 9835107f..da504ccd 100644
--- a/Integrations/Source/Integrations.Source.csproj
+++ b/Integrations/Source/Integrations.Source.csproj
@@ -4,6 +4,12 @@
netcoreapp3.1
Integrations.Source
Integrations.Source
+ Debug;Release;Prerelease
+ AnyCPU
+
+
+
+ true
diff --git a/Integrations/Source/SourceRConConnection.cs b/Integrations/Source/SourceRConConnection.cs
index 5b7fe232..cfab5682 100644
--- a/Integrations/Source/SourceRConConnection.cs
+++ b/Integrations/Source/SourceRConConnection.cs
@@ -1,6 +1,9 @@
-using System.Linq;
+using System;
+using System.Linq;
using System.Net.Sockets;
+using System.Threading;
using System.Threading.Tasks;
+using Integrations.Source.Extensions;
using Integrations.Source.Interfaces;
using Microsoft.Extensions.Logging;
using RconSharp;
@@ -20,82 +23,145 @@ namespace Integrations.Source
private readonly string _hostname;
private readonly int _port;
private readonly IRConClientFactory _rconClientFactory;
+ private readonly SemaphoreSlim _activeQuery;
+ private static readonly TimeSpan FloodDelay = TimeSpan.FromMilliseconds(250);
+
+ private DateTime _lastQuery = DateTime.Now;
private RconClient _rconClient;
public SourceRConConnection(ILogger logger, IRConClientFactory rconClientFactory,
string hostname, int port, string password)
{
- _rconClient = rconClientFactory.CreateClient(hostname, port);
_rconClientFactory = rconClientFactory;
_password = password;
_hostname = hostname;
_port = port;
_logger = logger;
+ _rconClient = _rconClientFactory.CreateClient(_hostname, _port);
+ _activeQuery = new SemaphoreSlim(1, 1);
+ }
+
+ ~SourceRConConnection()
+ {
+ _activeQuery.Dispose();
}
public async Task SendQueryAsync(StaticHelpers.QueryType type, string parameters = "")
{
- await _rconClient.ConnectAsync();
-
- bool authenticated;
-
try
{
- authenticated = await _rconClient.AuthenticateAsync(_password);
- }
- catch (SocketException ex)
- {
- // occurs when the server comes back from hibernation
- // this is probably a bug in the library
- if (ex.ErrorCode == 10053 || ex.ErrorCode == 10054)
+ await _activeQuery.WaitAsync();
+ var diff = DateTime.Now - _lastQuery;
+ if (diff < FloodDelay)
+ {
+ await Task.Delay(FloodDelay - diff);
+ }
+
+ using (LogContext.PushProperty("Server", $"{_hostname}:{_port}"))
+ {
+ _logger.LogDebug("Connecting to RCon socket");
+ }
+
+ await _rconClient.ConnectAsync();
+
+ bool authenticated;
+
+ try
{
using (LogContext.PushProperty("Server", $"{_hostname}:{_port}"))
{
- _logger.LogWarning(ex,
- "Server appears to resumed from hibernation, so we are using a new socket");
+ _logger.LogDebug("Authenticating to RCon socket");
}
- _rconClient = _rconClientFactory.CreateClient(_hostname, _port);
+ authenticated = await _rconClient.AuthenticateAsync(_password);
}
+ catch (SocketException ex)
+ {
+ // occurs when the server comes back from hibernation
+ // this is probably a bug in the library
+ if (ex.ErrorCode == 10053 || ex.ErrorCode == 10054)
+ {
+ using (LogContext.PushProperty("Server", $"{_hostname}:{_port}"))
+ {
+ _logger.LogWarning(ex,
+ "Server appears to resumed from hibernation, so we are using a new socket");
+ }
+
+ try
+ {
+ _rconClient.Disconnect();
+ }
+ catch
+ {
+ // ignored
+ }
+
+ _rconClient = _rconClientFactory.CreateClient(_hostname, _port);
+ }
+
+ using (LogContext.PushProperty("Server", $"{_hostname}:{_port}"))
+ {
+ _logger.LogError(ex, "Error occurred authenticating with server");
+ }
+
+ throw new NetworkException("Error occurred authenticating with server");
+ }
+
+ if (!authenticated)
+ {
+ using (LogContext.PushProperty("Server", $"{_hostname}:{_port}"))
+ {
+ _logger.LogError("Could not login to server");
+ }
+
+ throw new ServerException("Could not authenticate to server with provided password");
+ }
+
+ if (type == StaticHelpers.QueryType.COMMAND_STATUS)
+ {
+ parameters = "status";
+ }
+
+ parameters = parameters.ReplaceUnfriendlyCharacters();
+ parameters = parameters.StripColors();
using (LogContext.PushProperty("Server", $"{_hostname}:{_port}"))
{
- _logger.LogError("Could not login to server");
+ _logger.LogDebug("Sending query {Type} with parameters \"{Parameters}\"", type, parameters);
}
- throw new NetworkException("Could not authenticate with server");
+ var response = await _rconClient.ExecuteCommandAsync(parameters, true);
+
+ using (LogContext.PushProperty("Server", $"{_rconClient.Host}:{_rconClient.Port}"))
+ {
+ _logger.LogDebug("Received RCon response {Response}", response);
+ }
+
+ var split = response.TrimEnd('\n').Split('\n');
+ return split.Take(split.Length - 1).ToArray();
}
- if (!authenticated)
+ catch (Exception ex) when (ex.GetType() != typeof(NetworkException) &&
+ ex.GetType() != typeof(ServerException))
{
using (LogContext.PushProperty("Server", $"{_hostname}:{_port}"))
{
- _logger.LogError("Could not login to server");
+ _logger.LogError(ex, "Could not execute RCon query {Parameters}", parameters);
}
- throw new ServerException("Could not authenticate to server with provided password");
+ throw new NetworkException("Unable to communicate with server");
}
- if (type == StaticHelpers.QueryType.COMMAND_STATUS)
+ finally
{
- parameters = "status";
+ if (_activeQuery.CurrentCount == 0)
+ {
+ _activeQuery.Release();
+ }
+
+ _lastQuery = DateTime.Now;
}
-
- using (LogContext.PushProperty("Server", $"{_hostname}:{_port}"))
- {
- _logger.LogDebug("Sending query {Type} with parameters {Parameters}", type, parameters);
- }
-
- var response = await _rconClient.ExecuteCommandAsync(parameters.StripColors(), true);
-
- using (LogContext.PushProperty("Server", $"{_rconClient.Host}:{_rconClient.Port}"))
- {
- _logger.LogDebug("Received RCon response {Response}", response);
- }
-
- var split = response.TrimEnd('\n').Split('\n');
- return split.Take(split.Length - 1).ToArray();
}
public void SetConfiguration(IRConParser config)
diff --git a/Plugins/ScriptPlugins/ParserCSGO.js b/Plugins/ScriptPlugins/ParserCSGO.js
index 53f96b85..37e0f4fa 100644
--- a/Plugins/ScriptPlugins/ParserCSGO.js
+++ b/Plugins/ScriptPlugins/ParserCSGO.js
@@ -3,7 +3,7 @@ let eventParser;
const plugin = {
author: 'RaidMax',
- version: 0.1,
+ version: 0.2,
name: 'CS:GO Parser',
engine: 'Source',
isParser: true,
@@ -12,8 +12,8 @@ const plugin = {
},
onLoadAsync: function (manager) {
- rconParser = manager.GenerateDynamicRConParser(this.engine);
- eventParser = manager.GenerateDynamicEventParser(this.engine);
+ rconParser = manager.GenerateDynamicRConParser(this.name);
+ eventParser = manager.GenerateDynamicEventParser(this.name);
rconParser.RConEngine = this.engine;
rconParser.Configuration.StatusHeader.Pattern = 'userid +name +uniqueid +connected +ping +loss +state +rate +adr';
@@ -24,16 +24,16 @@ const plugin = {
rconParser.Configuration.HostnameStatus.Pattern = '^hostname: +(.+)$';
rconParser.Configuration.MapStatus.AddMapping(113, 1);
- rconParser.Configuration.MaxPlayersStatus.Pattern = '^players *: +\\d humans, \\d bots \\((\\d+).+';
+ rconParser.Configuration.MaxPlayersStatus.Pattern = '^players *: +\\d+ humans, \\d+ bots \\((\\d+).+';
rconParser.Configuration.MapStatus.AddMapping(114, 1);
- rconParser.Configuration.Dvar.Pattern = '^"(.+)" = (?:"(.+)" (?:\\( def\\. "(.*)" \\))|"(.+)" +(.+)) +- (.*)$';
+ rconParser.Configuration.Dvar.Pattern = '^"(.+)" = "(.+)" (?:\\( def. "(.*)" \\))?(?: |\\w)+- (.+)$';
rconParser.Configuration.Dvar.AddMapping(106, 1);
rconParser.Configuration.Dvar.AddMapping(107, 2);
rconParser.Configuration.Dvar.AddMapping(108, 3);
rconParser.Configuration.Dvar.AddMapping(109, 3);
- rconParser.Configuration.Status.Pattern = '^#\\s*(\\d+) (\\d+) "(.+)" (\\S+) (\\d+:\\d+) (\\d+) (\\S+) (\\S+) (\\d+) (\\d+\\.\\d+\\.\\d+\\.\\d+:\\d+)$';
+ rconParser.Configuration.Status.Pattern = '^#\\s*(\\d+) (\\d+) "(.+)" (\\S+) +(\\d+:\\d+(?::\\d+)?) (\\d+) (\\S+) (\\S+) (\\d+) (\\d+\\.\\d+\\.\\d+\\.\\d+:\\d+)$';
rconParser.Configuration.Status.AddMapping(100, 2);
rconParser.Configuration.Status.AddMapping(101, 7);
rconParser.Configuration.Status.AddMapping(102, 6);
diff --git a/Plugins/ScriptPlugins/ParserCSGOSM.js b/Plugins/ScriptPlugins/ParserCSGOSM.js
index 2a97bd2c..01e00410 100644
--- a/Plugins/ScriptPlugins/ParserCSGOSM.js
+++ b/Plugins/ScriptPlugins/ParserCSGOSM.js
@@ -3,7 +3,7 @@ let eventParser;
const plugin = {
author: 'RaidMax',
- version: 0.1,
+ version: 0.2,
name: 'CS:GO (SourceMod) Parser',
engine: 'Source',
isParser: true,
@@ -12,8 +12,8 @@ const plugin = {
},
onLoadAsync: function (manager) {
- rconParser = manager.GenerateDynamicRConParser(this.engine);
- eventParser = manager.GenerateDynamicEventParser(this.engine);
+ rconParser = manager.GenerateDynamicRConParser(this.name);
+ eventParser = manager.GenerateDynamicEventParser(this.name);
rconParser.RConEngine = this.engine;
rconParser.Configuration.StatusHeader.Pattern = 'userid +name +uniqueid +connected +ping +loss +state +rate +adr';
@@ -24,16 +24,16 @@ const plugin = {
rconParser.Configuration.HostnameStatus.Pattern = '^hostname: +(.+)$';
rconParser.Configuration.MapStatus.AddMapping(113, 1);
- rconParser.Configuration.MaxPlayersStatus.Pattern = '^players *: +\\d humans, \\d bots \\((\\d+).+';
+ rconParser.Configuration.MaxPlayersStatus.Pattern = '^players *: +\\d+ humans, \\d+ bots \\((\\d+).+';
rconParser.Configuration.MapStatus.AddMapping(114, 1);
- rconParser.Configuration.Dvar.Pattern = '^"(.+)" = (?:"(.+)" (?:\\( def\\. "(.*)" \\))|"(.+)" +(.+)) +- (.*)$';
+ rconParser.Configuration.Dvar.Pattern = '^"(.+)" = "(.+)" (?:\\( def. "(.*)" \\))?(?: |\\w)+- (.+)$';
rconParser.Configuration.Dvar.AddMapping(106, 1);
rconParser.Configuration.Dvar.AddMapping(107, 2);
rconParser.Configuration.Dvar.AddMapping(108, 3);
rconParser.Configuration.Dvar.AddMapping(109, 3);
- rconParser.Configuration.Status.Pattern = '^#\\s*(\\d+) (\\d+) "(.+)" (\\S+) (\\d+:\\d+) (\\d+) (\\S+) (\\S+) (\\d+) (\\d+\\.\\d+\\.\\d+\\.\\d+:\\d+)$';
+ rconParser.Configuration.Status.Pattern = '^#\\s*(\\d+) (\\d+) "(.+)" (\\S+) +(\\d+:\\d+(?::\\d+)?) (\\d+) (\\S+) (\\S+) (\\d+) (\\d+\\.\\d+\\.\\d+\\.\\d+:\\d+)$';
rconParser.Configuration.Status.AddMapping(100, 2);
rconParser.Configuration.Status.AddMapping(101, 7);
rconParser.Configuration.Status.AddMapping(102, 6);
@@ -64,7 +64,7 @@ const plugin = {
rconParser.Configuration.CommandPrefixes.Ban = 'sm_kick #{0} {1}';
rconParser.Configuration.CommandPrefixes.TempBan = 'sm_kick #{0} {1}';
rconParser.Configuration.CommandPrefixes.Say = 'sm_say {0}';
- rconParser.Configuration.CommandPrefixes.Tell = 'sm_psay #{0} {1}';
+ rconParser.Configuration.CommandPrefixes.Tell = 'sm_psay #{0} "{1}"';
eventParser.Configuration.Say.Pattern = '^"(.+)<(\\d+)><(.+)><(.*?)>" say "(.*)"$';
eventParser.Configuration.Say.AddMapping(5, 1);