diff --git a/Application/IW4MServer.cs b/Application/IW4MServer.cs
index 1b5f02a6..f3f8de0c 100644
--- a/Application/IW4MServer.cs
+++ b/Application/IW4MServer.cs
@@ -972,7 +972,7 @@ namespace IW4MAdmin
if (!string.IsNullOrEmpty(svRunning.Value) && svRunning.Value != "1")
{
- throw new ServerException(loc["SERVER_ERROR_NOT_RUNNING"]);
+ throw new ServerException(loc["SERVER_ERROR_NOT_RUNNING"].FormatExt(this.ToString()));
}
var infoResponse = RconParser.Configuration.CommandPrefixes.RConGetInfo != null ? await this.GetInfoAsync() : null;
@@ -1042,9 +1042,10 @@ namespace IW4MAdmin
}
if (needsRestart)
- {
- Logger.WriteWarning("Game log file not properly initialized, restarting map...");
- await this.ExecuteCommandAsync("map_restart");
+ {
+ // disabling this for the time being
+ /*Logger.WriteWarning("Game log file not properly initialized, restarting map...");
+ await this.ExecuteCommandAsync("map_restart");*/
}
// this DVAR isn't set until the a map is loaded
diff --git a/Application/RCon/RConConnection.cs b/Application/RCon/RConConnection.cs
index ca131f4c..8dbadbb3 100644
--- a/Application/RCon/RConConnection.cs
+++ b/Application/RCon/RConConnection.cs
@@ -49,9 +49,11 @@ namespace IW4MAdmin.Application.RCon
var connectionState = ActiveQueries[this.Endpoint];
-#if DEBUG == true
- _log.WriteDebug($"Waiting for semaphore to be released [{this.Endpoint}]");
-#endif
+ if (Utilities.IsDevelopment)
+ {
+ _log.WriteDebug($"Waiting for semaphore to be released [{this.Endpoint}]");
+ }
+
// enter the semaphore so only one query is sent at a time per server.
await connectionState.OnComplete.WaitAsync();
@@ -64,10 +66,11 @@ namespace IW4MAdmin.Application.RCon
connectionState.LastQuery = DateTime.Now;
-#if DEBUG == true
- _log.WriteDebug($"Semaphore has been released [{this.Endpoint}]");
- _log.WriteDebug($"Query [{this.Endpoint},{type.ToString()},{parameters}]");
-#endif
+ if (Utilities.IsDevelopment)
+ {
+ _log.WriteDebug($"Semaphore has been released [{Endpoint}]");
+ _log.WriteDebug($"Query [{Endpoint},{type},{parameters}]");
+ }
byte[] payload = null;
bool waitForResponse = config.WaitForResponse;
@@ -133,6 +136,7 @@ namespace IW4MAdmin.Application.RCon
connectionState.OnReceivedData.Reset();
connectionState.ConnectionAttempts++;
connectionState.BytesReadPerSegment.Clear();
+ bool exceptionCaught = false;
#if DEBUG == true
_log.WriteDebug($"Sending {payload.Length} bytes to [{this.Endpoint}] ({connectionState.ConnectionAttempts}/{StaticHelpers.AllowedConnectionFails})");
#endif
@@ -150,9 +154,11 @@ namespace IW4MAdmin.Application.RCon
catch
{
+ // we want to retry with a delay
if (connectionState.ConnectionAttempts < StaticHelpers.AllowedConnectionFails)
{
- await Task.Delay(StaticHelpers.FloodProtectionInterval);
+ exceptionCaught = true;
+ await Task.Delay(StaticHelpers.SocketTimeout(connectionState.ConnectionAttempts));
goto retrySend;
}
@@ -161,7 +167,8 @@ namespace IW4MAdmin.Application.RCon
finally
{
- if (connectionState.OnComplete.CurrentCount == 0)
+ // we don't want to release if we're going to retry the query
+ if (connectionState.OnComplete.CurrentCount == 0 && !exceptionCaught)
{
connectionState.OnComplete.Release(1);
}
@@ -170,13 +177,12 @@ namespace IW4MAdmin.Application.RCon
if (response.Length == 0)
{
- _log.WriteWarning($"Received empty response for request [{type.ToString()}, {parameters}, {Endpoint.ToString()}]");
+ _log.WriteWarning($"Received empty response for request [{type}, {parameters}, {Endpoint}]");
return new string[0];
}
string responseString = type == StaticHelpers.QueryType.COMMAND_STATUS ?
- ReassembleSegmentedStatus(response) :
- _gameEncoding.GetString(response[0]) + '\n';
+ ReassembleSegmentedStatus(response) : RecombineMessages(response);
// note: not all games respond if the pasword is wrong or not set
if (responseString.Contains("Invalid password") || responseString.Contains("rconpassword"))
@@ -234,6 +240,35 @@ namespace IW4MAdmin.Application.RCon
return string.Join("", splitStatusStrings);
}
+ ///
+ /// Recombines multiple game messages into one
+ ///
+ ///
+ ///
+ private string RecombineMessages(byte[][] payload)
+ {
+ if (payload.Length == 1)
+ {
+ return _gameEncoding.GetString(payload[0]).TrimEnd('\n') + '\n';
+ }
+
+ else
+ {
+ var builder = new StringBuilder();
+ for (int i = 0; i < payload.Length; i++)
+ {
+ string message = _gameEncoding.GetString(payload[i]).TrimEnd('\n') + '\n';
+ if (i > 0)
+ {
+ message = message.Replace(config.CommandPrefixes.RConResponse, "");
+ }
+ builder.Append(message);
+ }
+ builder.Append('\n');
+ return builder.ToString();
+ }
+ }
+
private async Task SendPayloadAsync(byte[] payload, bool waitForResponse)
{
var connectionState = ActiveQueries[this.Endpoint];
@@ -259,7 +294,8 @@ namespace IW4MAdmin.Application.RCon
if (sendDataPending)
{
// the send has not been completed asyncronously
- if (!await Task.Run(() => connectionState.OnSentData.Wait(StaticHelpers.SocketTimeout)))
+ // this really shouldn't ever happen because it's UDP
+ if (!await Task.Run(() => connectionState.OnSentData.Wait(StaticHelpers.SocketTimeout(1))))
{
rconSocket.Close();
throw new NetworkException("Timed out sending data", rconSocket);
@@ -278,7 +314,11 @@ namespace IW4MAdmin.Application.RCon
if (receiveDataPending)
{
- if (!await Task.Run(() => connectionState.OnReceivedData.Wait(10000)))
+ if (Utilities.IsDevelopment)
+ {
+ _log.WriteDebug($"Waiting to asynchrously receive data on attempt #{connectionState.ConnectionAttempts}");
+ }
+ if (!await Task.Run(() => connectionState.OnReceivedData.Wait(StaticHelpers.SocketTimeout(connectionState.ConnectionAttempts))))
{
rconSocket.Close();
throw new NetworkException("Timed out waiting for response", rconSocket);
@@ -287,6 +327,11 @@ namespace IW4MAdmin.Application.RCon
rconSocket.Close();
+ return GetResponseData(connectionState);
+ }
+
+ private byte[][] GetResponseData(ConnectionState connectionState)
+ {
var responseList = new List();
int totalBytesRead = 0;
@@ -305,9 +350,10 @@ namespace IW4MAdmin.Application.RCon
private void OnDataReceived(object sender, SocketAsyncEventArgs e)
{
-#if DEBUG == true
- _log.WriteDebug($"Read {e.BytesTransferred} bytes from {e.RemoteEndPoint.ToString()}");
-#endif
+ if (Utilities.IsDevelopment)
+ {
+ _log.WriteDebug($"Read {e.BytesTransferred} bytes from {e.RemoteEndPoint}");
+ }
// this occurs when we close the socket
if (e.BytesTransferred == 0)
@@ -330,9 +376,10 @@ namespace IW4MAdmin.Application.RCon
if (!sock.ReceiveAsync(state.ReceiveEventArgs))
{
-#if DEBUG == true
- _log.WriteDebug($"Read {state.ReceiveEventArgs.BytesTransferred} synchronous bytes from {e.RemoteEndPoint.ToString()}");
-#endif
+ if (Utilities.IsDevelopment)
+ {
+ _log.WriteDebug($"Read {state.ReceiveEventArgs.BytesTransferred} synchronous bytes from {e.RemoteEndPoint}");
+ }
// 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();
@@ -354,9 +401,10 @@ namespace IW4MAdmin.Application.RCon
private void OnDataSent(object sender, SocketAsyncEventArgs e)
{
-#if DEBUG == true
- _log.WriteDebug($"Sent {e.Buffer?.Length} bytes to {e.ConnectSocket?.RemoteEndPoint?.ToString()}");
-#endif
+ if (Utilities.IsDevelopment)
+ {
+ _log.WriteDebug($"Sent {e.Buffer?.Length} bytes to {e.ConnectSocket?.RemoteEndPoint?.ToString()}");
+ }
ActiveQueries[this.Endpoint].OnSentData.Set();
}
}
diff --git a/Application/RconParsers/BaseRConParser.cs b/Application/RconParsers/BaseRConParser.cs
index d1af19c5..d797111c 100644
--- a/Application/RconParsers/BaseRConParser.cs
+++ b/Application/RconParsers/BaseRConParser.cs
@@ -76,7 +76,22 @@ namespace IW4MAdmin.Application.RconParsers
public async Task> GetDvarAsync(IRConConnection connection, string dvarName, T fallbackValue = default)
{
- string[] lineSplit = await connection.SendQueryAsync(StaticHelpers.QueryType.GET_DVAR, dvarName);
+ 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);
diff --git a/IW4MAdmin.sln b/IW4MAdmin.sln
index 6190d1e4..c2dfee15 100644
--- a/IW4MAdmin.sln
+++ b/IW4MAdmin.sln
@@ -35,6 +35,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "ScriptPlugins", "ScriptPlug
Plugins\ScriptPlugins\ActionOnReport.js = Plugins\ScriptPlugins\ActionOnReport.js
Plugins\ScriptPlugins\ParserCoD4x.js = Plugins\ScriptPlugins\ParserCoD4x.js
Plugins\ScriptPlugins\ParserIW4x.js = Plugins\ScriptPlugins\ParserIW4x.js
+ Plugins\ScriptPlugins\ParserIW6x.js = Plugins\ScriptPlugins\ParserIW6x.js
Plugins\ScriptPlugins\ParserPIW5.js = Plugins\ScriptPlugins\ParserPIW5.js
Plugins\ScriptPlugins\ParserPT6.js = Plugins\ScriptPlugins\ParserPT6.js
Plugins\ScriptPlugins\ParserRektT5M.js = Plugins\ScriptPlugins\ParserRektT5M.js
diff --git a/Plugins/ScriptPlugins/ParserIW6x.js b/Plugins/ScriptPlugins/ParserIW6x.js
new file mode 100644
index 00000000..facfedab
--- /dev/null
+++ b/Plugins/ScriptPlugins/ParserIW6x.js
@@ -0,0 +1,43 @@
+var rconParser;
+var eventParser;
+
+var plugin = {
+ author: 'Xerxes, RaidMax',
+ version: 0.1,
+ name: 'IW6x Parser',
+ isParser: true,
+
+ onEventAsync: function (gameEvent, server) {
+ },
+
+ onLoadAsync: function (manager) {
+ rconParser = manager.GenerateDynamicRConParser(this.name);
+ eventParser = manager.GenerateDynamicEventParser(this.name);
+
+ rconParser.Configuration.CommandPrefixes.Tell = 'tell {0} {1}';
+ rconParser.Configuration.CommandPrefixes.Say = 'say {0}';
+ rconParser.Configuration.CommandPrefixes.Kick = 'clientkick {0} "{1}"';
+ rconParser.Configuration.CommandPrefixes.Ban = 'clientkick {0} "{1}"';
+ rconParser.Configuration.CommandPrefixes.TempBan = 'clientkick {0} "{1}"';
+ rconParser.Configuration.CommandPrefixes.RConResponse = '\xff\xff\xff\xffprint\n';
+ rconParser.Configuration.Dvar.Pattern = '^ *\\"(.+)\\" is: \\"(.+)?\\" default: \\"(.+)?\\"\\n(?:latched: \\"(.+)?\\"\\n)? *(.+)$';
+ rconParser.Configuration.Status.Pattern = '^ *([0-9]+) +-?([0-9]+) +(Yes|No) +((?:[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|bot) +(-*[0-9]+) *$';
+ rconParser.Configuration.StatusHeader.Pattern = 'num +score +bot +ping +guid +name +address +qport *';
+ rconParser.Configuration.WaitForResponse = false;
+ rconParser.Configuration.Status.AddMapping(102, 4);
+ rconParser.Configuration.Status.AddMapping(103, 5);
+ rconParser.Configuration.Status.AddMapping(104, 6);
+
+ rconParser.Version = 'IW6 MP 3.15 build 2 Sat Sep 14 2013 03:58:30PM win64';
+ rconParser.GameName = 4; // IW6
+ eventParser.Version = 'IW6 MP 3.15 build 2 Sat Sep 14 2013 03:58:30PM win64';
+ eventParser.GameName = 4; // IW6
+ eventParser.Configuration.GameDirectory = 'iw6x';
+ },
+
+ onUnloadAsync: function () {
+ },
+
+ onTickAsync: function (server) {
+ }
+};
diff --git a/SharedLibraryCore/RCon/StaticHelpers.cs b/SharedLibraryCore/RCon/StaticHelpers.cs
index eb1e7436..68625a5a 100644
--- a/SharedLibraryCore/RCon/StaticHelpers.cs
+++ b/SharedLibraryCore/RCon/StaticHelpers.cs
@@ -49,14 +49,23 @@ namespace SharedLibraryCore.RCon
///
/// timeout in seconds to wait for a socket send or receive before giving up
///
- public static readonly int SocketTimeout = 10000;
+ public static TimeSpan SocketTimeout(int retryAttempt)
+ {
+ return retryAttempt switch
+ {
+ 1 => TimeSpan.FromMilliseconds(550),
+ 2 => TimeSpan.FromMilliseconds(1000),
+ 3 => TimeSpan.FromMilliseconds(2000),
+ _ => TimeSpan.FromMilliseconds(5000),
+ };
+ }
///
/// interval in milliseconds to wait before sending the next RCon request
///
- public static readonly int FloodProtectionInterval = 650;
+ public static readonly int FloodProtectionInterval = 750;
///
/// how many failed connection attempts before aborting connection
///
- public static readonly int AllowedConnectionFails = 3;
+ public static readonly int AllowedConnectionFails = 4;
}
}
diff --git a/SharedLibraryCore/Server.cs b/SharedLibraryCore/Server.cs
index 58332bb9..2428ce30 100644
--- a/SharedLibraryCore/Server.cs
+++ b/SharedLibraryCore/Server.cs
@@ -269,7 +269,7 @@ namespace SharedLibraryCore
{
try
{
- return (await this.GetDvarAsync("sv_customcallbacks")).Value == "1";
+ return (await this.GetDvarAsync("sv_customcallbacks", "0")).Value == "1";
}
catch (Exceptions.DvarException)