partial support of IW6x until the game log is implemented
This commit is contained in:
parent
e76976799b
commit
fd7bd7e0da
@ -972,7 +972,7 @@ namespace IW4MAdmin
|
|||||||
|
|
||||||
if (!string.IsNullOrEmpty(svRunning.Value) && svRunning.Value != "1")
|
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;
|
var infoResponse = RconParser.Configuration.CommandPrefixes.RConGetInfo != null ? await this.GetInfoAsync() : null;
|
||||||
@ -1042,9 +1042,10 @@ namespace IW4MAdmin
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (needsRestart)
|
if (needsRestart)
|
||||||
{
|
{
|
||||||
Logger.WriteWarning("Game log file not properly initialized, restarting map...");
|
// disabling this for the time being
|
||||||
await this.ExecuteCommandAsync("map_restart");
|
/*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
|
// this DVAR isn't set until the a map is loaded
|
||||||
|
@ -49,9 +49,11 @@ namespace IW4MAdmin.Application.RCon
|
|||||||
|
|
||||||
var connectionState = ActiveQueries[this.Endpoint];
|
var connectionState = ActiveQueries[this.Endpoint];
|
||||||
|
|
||||||
#if DEBUG == true
|
if (Utilities.IsDevelopment)
|
||||||
_log.WriteDebug($"Waiting for semaphore to be released [{this.Endpoint}]");
|
{
|
||||||
#endif
|
_log.WriteDebug($"Waiting for semaphore to be released [{this.Endpoint}]");
|
||||||
|
}
|
||||||
|
|
||||||
// enter the semaphore so only one query is sent at a time per server.
|
// enter the semaphore so only one query is sent at a time per server.
|
||||||
await connectionState.OnComplete.WaitAsync();
|
await connectionState.OnComplete.WaitAsync();
|
||||||
|
|
||||||
@ -64,10 +66,11 @@ namespace IW4MAdmin.Application.RCon
|
|||||||
|
|
||||||
connectionState.LastQuery = DateTime.Now;
|
connectionState.LastQuery = DateTime.Now;
|
||||||
|
|
||||||
#if DEBUG == true
|
if (Utilities.IsDevelopment)
|
||||||
_log.WriteDebug($"Semaphore has been released [{this.Endpoint}]");
|
{
|
||||||
_log.WriteDebug($"Query [{this.Endpoint},{type.ToString()},{parameters}]");
|
_log.WriteDebug($"Semaphore has been released [{Endpoint}]");
|
||||||
#endif
|
_log.WriteDebug($"Query [{Endpoint},{type},{parameters}]");
|
||||||
|
}
|
||||||
|
|
||||||
byte[] payload = null;
|
byte[] payload = null;
|
||||||
bool waitForResponse = config.WaitForResponse;
|
bool waitForResponse = config.WaitForResponse;
|
||||||
@ -133,6 +136,7 @@ namespace IW4MAdmin.Application.RCon
|
|||||||
connectionState.OnReceivedData.Reset();
|
connectionState.OnReceivedData.Reset();
|
||||||
connectionState.ConnectionAttempts++;
|
connectionState.ConnectionAttempts++;
|
||||||
connectionState.BytesReadPerSegment.Clear();
|
connectionState.BytesReadPerSegment.Clear();
|
||||||
|
bool exceptionCaught = false;
|
||||||
#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
|
||||||
@ -150,9 +154,11 @@ namespace IW4MAdmin.Application.RCon
|
|||||||
|
|
||||||
catch
|
catch
|
||||||
{
|
{
|
||||||
|
// we want to retry with a delay
|
||||||
if (connectionState.ConnectionAttempts < StaticHelpers.AllowedConnectionFails)
|
if (connectionState.ConnectionAttempts < StaticHelpers.AllowedConnectionFails)
|
||||||
{
|
{
|
||||||
await Task.Delay(StaticHelpers.FloodProtectionInterval);
|
exceptionCaught = true;
|
||||||
|
await Task.Delay(StaticHelpers.SocketTimeout(connectionState.ConnectionAttempts));
|
||||||
goto retrySend;
|
goto retrySend;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -161,7 +167,8 @@ namespace IW4MAdmin.Application.RCon
|
|||||||
|
|
||||||
finally
|
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);
|
connectionState.OnComplete.Release(1);
|
||||||
}
|
}
|
||||||
@ -170,13 +177,12 @@ namespace IW4MAdmin.Application.RCon
|
|||||||
|
|
||||||
if (response.Length == 0)
|
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];
|
return new string[0];
|
||||||
}
|
}
|
||||||
|
|
||||||
string responseString = type == StaticHelpers.QueryType.COMMAND_STATUS ?
|
string responseString = type == StaticHelpers.QueryType.COMMAND_STATUS ?
|
||||||
ReassembleSegmentedStatus(response) :
|
ReassembleSegmentedStatus(response) : RecombineMessages(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"))
|
||||||
@ -234,6 +240,35 @@ namespace IW4MAdmin.Application.RCon
|
|||||||
return string.Join("", splitStatusStrings);
|
return string.Join("", splitStatusStrings);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Recombines multiple game messages into one
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="payload"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
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<byte[][]> SendPayloadAsync(byte[] payload, bool waitForResponse)
|
private async Task<byte[][]> SendPayloadAsync(byte[] payload, bool waitForResponse)
|
||||||
{
|
{
|
||||||
var connectionState = ActiveQueries[this.Endpoint];
|
var connectionState = ActiveQueries[this.Endpoint];
|
||||||
@ -259,7 +294,8 @@ namespace IW4MAdmin.Application.RCon
|
|||||||
if (sendDataPending)
|
if (sendDataPending)
|
||||||
{
|
{
|
||||||
// the send has not been completed asyncronously
|
// 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();
|
rconSocket.Close();
|
||||||
throw new NetworkException("Timed out sending data", rconSocket);
|
throw new NetworkException("Timed out sending data", rconSocket);
|
||||||
@ -278,7 +314,11 @@ namespace IW4MAdmin.Application.RCon
|
|||||||
|
|
||||||
if (receiveDataPending)
|
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();
|
rconSocket.Close();
|
||||||
throw new NetworkException("Timed out waiting for response", rconSocket);
|
throw new NetworkException("Timed out waiting for response", rconSocket);
|
||||||
@ -287,6 +327,11 @@ namespace IW4MAdmin.Application.RCon
|
|||||||
|
|
||||||
rconSocket.Close();
|
rconSocket.Close();
|
||||||
|
|
||||||
|
return GetResponseData(connectionState);
|
||||||
|
}
|
||||||
|
|
||||||
|
private byte[][] GetResponseData(ConnectionState connectionState)
|
||||||
|
{
|
||||||
var responseList = new List<byte[]>();
|
var responseList = new List<byte[]>();
|
||||||
int totalBytesRead = 0;
|
int totalBytesRead = 0;
|
||||||
|
|
||||||
@ -305,9 +350,10 @@ namespace IW4MAdmin.Application.RCon
|
|||||||
|
|
||||||
private void OnDataReceived(object sender, SocketAsyncEventArgs e)
|
private void OnDataReceived(object sender, SocketAsyncEventArgs e)
|
||||||
{
|
{
|
||||||
#if DEBUG == true
|
if (Utilities.IsDevelopment)
|
||||||
_log.WriteDebug($"Read {e.BytesTransferred} bytes from {e.RemoteEndPoint.ToString()}");
|
{
|
||||||
#endif
|
_log.WriteDebug($"Read {e.BytesTransferred} bytes from {e.RemoteEndPoint}");
|
||||||
|
}
|
||||||
|
|
||||||
// this occurs when we close the socket
|
// this occurs when we close the socket
|
||||||
if (e.BytesTransferred == 0)
|
if (e.BytesTransferred == 0)
|
||||||
@ -330,9 +376,10 @@ namespace IW4MAdmin.Application.RCon
|
|||||||
|
|
||||||
if (!sock.ReceiveAsync(state.ReceiveEventArgs))
|
if (!sock.ReceiveAsync(state.ReceiveEventArgs))
|
||||||
{
|
{
|
||||||
#if DEBUG == true
|
if (Utilities.IsDevelopment)
|
||||||
_log.WriteDebug($"Read {state.ReceiveEventArgs.BytesTransferred} synchronous bytes from {e.RemoteEndPoint.ToString()}");
|
{
|
||||||
#endif
|
_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
|
// we need to increment this here because the callback isn't executed if there's no pending IO
|
||||||
state.BytesReadPerSegment.Add(state.ReceiveEventArgs.BytesTransferred);
|
state.BytesReadPerSegment.Add(state.ReceiveEventArgs.BytesTransferred);
|
||||||
ActiveQueries[this.Endpoint].OnReceivedData.Set();
|
ActiveQueries[this.Endpoint].OnReceivedData.Set();
|
||||||
@ -354,9 +401,10 @@ namespace IW4MAdmin.Application.RCon
|
|||||||
|
|
||||||
private void OnDataSent(object sender, SocketAsyncEventArgs e)
|
private void OnDataSent(object sender, SocketAsyncEventArgs e)
|
||||||
{
|
{
|
||||||
#if DEBUG == true
|
if (Utilities.IsDevelopment)
|
||||||
_log.WriteDebug($"Sent {e.Buffer?.Length} bytes to {e.ConnectSocket?.RemoteEndPoint?.ToString()}");
|
{
|
||||||
#endif
|
_log.WriteDebug($"Sent {e.Buffer?.Length} bytes to {e.ConnectSocket?.RemoteEndPoint?.ToString()}");
|
||||||
|
}
|
||||||
ActiveQueries[this.Endpoint].OnSentData.Set();
|
ActiveQueries[this.Endpoint].OnSentData.Set();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -76,7 +76,22 @@ namespace IW4MAdmin.Application.RconParsers
|
|||||||
|
|
||||||
public async Task<Dvar<T>> GetDvarAsync<T>(IRConConnection connection, string dvarName, T fallbackValue = default)
|
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;
|
||||||
|
|
||||||
|
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');
|
string response = string.Join('\n', lineSplit).TrimEnd('\0');
|
||||||
var match = Regex.Match(response, Configuration.Dvar.Pattern);
|
var match = Regex.Match(response, Configuration.Dvar.Pattern);
|
||||||
|
|
||||||
|
@ -35,6 +35,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "ScriptPlugins", "ScriptPlug
|
|||||||
Plugins\ScriptPlugins\ActionOnReport.js = Plugins\ScriptPlugins\ActionOnReport.js
|
Plugins\ScriptPlugins\ActionOnReport.js = Plugins\ScriptPlugins\ActionOnReport.js
|
||||||
Plugins\ScriptPlugins\ParserCoD4x.js = Plugins\ScriptPlugins\ParserCoD4x.js
|
Plugins\ScriptPlugins\ParserCoD4x.js = Plugins\ScriptPlugins\ParserCoD4x.js
|
||||||
Plugins\ScriptPlugins\ParserIW4x.js = Plugins\ScriptPlugins\ParserIW4x.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\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
|
||||||
|
43
Plugins/ScriptPlugins/ParserIW6x.js
Normal file
43
Plugins/ScriptPlugins/ParserIW6x.js
Normal file
@ -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) {
|
||||||
|
}
|
||||||
|
};
|
@ -49,14 +49,23 @@ namespace SharedLibraryCore.RCon
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// timeout in seconds to wait for a socket send or receive before giving up
|
/// timeout in seconds to wait for a socket send or receive before giving up
|
||||||
/// </summary>
|
/// </summary>
|
||||||
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),
|
||||||
|
};
|
||||||
|
}
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// interval in milliseconds to wait before sending the next RCon request
|
/// interval in milliseconds to wait before sending the next RCon request
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static readonly int FloodProtectionInterval = 650;
|
public static readonly int FloodProtectionInterval = 750;
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// how many failed connection attempts before aborting connection
|
/// how many failed connection attempts before aborting connection
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static readonly int AllowedConnectionFails = 3;
|
public static readonly int AllowedConnectionFails = 4;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -269,7 +269,7 @@ namespace SharedLibraryCore
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
return (await this.GetDvarAsync<string>("sv_customcallbacks")).Value == "1";
|
return (await this.GetDvarAsync("sv_customcallbacks", "0")).Value == "1";
|
||||||
}
|
}
|
||||||
|
|
||||||
catch (Exceptions.DvarException)
|
catch (Exceptions.DvarException)
|
||||||
|
Loading…
Reference in New Issue
Block a user