refactor a good bit of stuff for better dependency injection
fix regular expression for T6 log parsing
This commit is contained in:
@ -500,18 +500,18 @@ namespace SharedLibraryCore.Commands
|
||||
|
||||
public override Task ExecuteAsync(GameEvent E)
|
||||
{
|
||||
String cmd = E.Data.Trim();
|
||||
string cmd = E.Data.Trim();
|
||||
|
||||
if (cmd.Length > 2)
|
||||
{
|
||||
bool found = false;
|
||||
foreach (Command C in E.Owner.Manager.GetCommands())
|
||||
foreach (var command in E.Owner.Manager.GetCommands())
|
||||
{
|
||||
if (C.Name == cmd.ToLower() ||
|
||||
C.Alias == cmd.ToLower())
|
||||
if (command.Name == cmd.ToLower() ||
|
||||
command.Alias == cmd.ToLower())
|
||||
{
|
||||
E.Origin.Tell($"[^3{C.Name}^7] {C.Description}");
|
||||
E.Origin.Tell(C.Syntax);
|
||||
E.Origin.Tell($"[^3{command.Name}^7] {command.Description}");
|
||||
E.Origin.Tell(command.Syntax);
|
||||
found = true;
|
||||
}
|
||||
}
|
||||
|
@ -56,11 +56,11 @@ namespace SharedLibraryCore.Configuration
|
||||
{
|
||||
var loc = Utilities.CurrentLocalization.LocalizationIndex;
|
||||
var parserVersions = rconParsers.Select(_parser => _parser.Name).ToArray();
|
||||
var selection = Utilities.PromptSelection($"{loc["SETUP_SERVER_RCON_PARSER_VERSION"]} ({IPAddress}:{Port})", $"{loc["SETUP_PROMPT_DEFAULT"]} (Call of Duty)", null, parserVersions);
|
||||
var selection = Utilities.PromptSelection($"{loc["SETUP_SERVER_RCON_PARSER_VERSION"]} ({IPAddress}:{Port})", parserVersions[0], null, parserVersions);
|
||||
|
||||
if (selection.Item1 >= 0)
|
||||
{
|
||||
RConParserVersion = rconParsers.First(_parser => _parser.Name == selection.Item2).Version;
|
||||
RConParserVersion = rconParsers.FirstOrDefault(_parser => _parser.Name == selection.Item2)?.Version;
|
||||
|
||||
if (selection.Item1 > 0 && !rconParsers[selection.Item1 - 1].CanGenerateLogPath)
|
||||
{
|
||||
@ -70,11 +70,11 @@ namespace SharedLibraryCore.Configuration
|
||||
}
|
||||
|
||||
parserVersions = eventParsers.Select(_parser => _parser.Name).ToArray();
|
||||
selection = Utilities.PromptSelection($"{loc["SETUP_SERVER_EVENT_PARSER_VERSION"]} ({IPAddress}:{Port})", $"{loc["SETUP_PROMPT_DEFAULT"]} (Call of Duty)", null, parserVersions);
|
||||
selection = Utilities.PromptSelection($"{loc["SETUP_SERVER_EVENT_PARSER_VERSION"]} ({IPAddress}:{Port})", parserVersions[0], null, parserVersions);
|
||||
|
||||
if (selection.Item1 >= 0)
|
||||
{
|
||||
EventParserVersion = eventParsers.First(_parser => _parser.Name == selection.Item2).Version;
|
||||
EventParserVersion = eventParsers.FirstOrDefault(_parser => _parser.Name == selection.Item2)?.Version;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -26,12 +26,6 @@ namespace SharedLibraryCore.Configuration.Validation
|
||||
RuleForEach(_server => _server.AutoMessages)
|
||||
.NotEmpty();
|
||||
|
||||
RuleFor(_server => _server.RConParserVersion)
|
||||
.NotEmpty();
|
||||
|
||||
RuleFor(_server => _server.EventParserVersion)
|
||||
.NotEmpty();
|
||||
|
||||
RuleFor(_server => _server.ReservedSlotNumber)
|
||||
.InclusiveBetween(0, 32);
|
||||
}
|
||||
|
@ -1,67 +0,0 @@
|
||||
using Newtonsoft.Json;
|
||||
using SharedLibraryCore.Exceptions;
|
||||
using SharedLibraryCore.Interfaces;
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace SharedLibraryCore.Configuration
|
||||
{
|
||||
public class BaseConfigurationHandler<T> : IConfigurationHandler<T> where T : IBaseConfiguration
|
||||
{
|
||||
T _configuration;
|
||||
|
||||
public BaseConfigurationHandler(string fn)
|
||||
{
|
||||
FileName = Path.Join(Utilities.OperatingDirectory, "Configuration", $"{fn}.json");
|
||||
Build();
|
||||
}
|
||||
|
||||
public string FileName { get; }
|
||||
|
||||
public void Build()
|
||||
{
|
||||
try
|
||||
{
|
||||
var configContent = File.ReadAllText(FileName);
|
||||
_configuration = JsonConvert.DeserializeObject<T>(configContent);
|
||||
}
|
||||
|
||||
catch (FileNotFoundException)
|
||||
{
|
||||
_configuration = default;
|
||||
}
|
||||
|
||||
catch (Exception e)
|
||||
{
|
||||
throw new ConfigurationException("MANAGER_CONFIGURATION_ERROR")
|
||||
{
|
||||
Errors = new[] { e.Message },
|
||||
ConfigurationFileName = FileName
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
public Task Save()
|
||||
{
|
||||
var settings = new JsonSerializerSettings()
|
||||
{
|
||||
Formatting = Formatting.Indented
|
||||
};
|
||||
settings.Converters.Add(new Newtonsoft.Json.Converters.StringEnumConverter());
|
||||
|
||||
var appConfigJSON = JsonConvert.SerializeObject(_configuration, settings);
|
||||
return File.WriteAllTextAsync(FileName, appConfigJSON);
|
||||
}
|
||||
|
||||
public T Configuration()
|
||||
{
|
||||
return _configuration;
|
||||
}
|
||||
|
||||
public void Set(T config)
|
||||
{
|
||||
_configuration = config;
|
||||
}
|
||||
}
|
||||
}
|
14
SharedLibraryCore/Interfaces/IBasePathProvider.cs
Normal file
14
SharedLibraryCore/Interfaces/IBasePathProvider.cs
Normal file
@ -0,0 +1,14 @@
|
||||
namespace SharedLibraryCore.Interfaces
|
||||
{
|
||||
/// <summary>
|
||||
/// defines the capabilities for providing a base path
|
||||
/// unused as of now, will be used later during refactorying
|
||||
/// </summary>
|
||||
public interface IBasePathProvider
|
||||
{
|
||||
/// <summary>
|
||||
/// working directory of IW4MAdmin
|
||||
/// </summary>
|
||||
string BasePath { get; }
|
||||
}
|
||||
}
|
17
SharedLibraryCore/Interfaces/IConfigurationHandlerFactory.cs
Normal file
17
SharedLibraryCore/Interfaces/IConfigurationHandlerFactory.cs
Normal file
@ -0,0 +1,17 @@
|
||||
namespace SharedLibraryCore.Interfaces
|
||||
{
|
||||
/// <summary>
|
||||
/// defines the capabilities of the configuration handler factory
|
||||
/// used to generate new instance of configuration handlers
|
||||
/// </summary>
|
||||
public interface IConfigurationHandlerFactory
|
||||
{
|
||||
/// <summary>
|
||||
/// generates a new configuration handler
|
||||
/// </summary>
|
||||
/// <typeparam name="T">base configuration type</typeparam>
|
||||
/// <param name="name">file name of configuration</param>
|
||||
/// <returns>new configuration handler instance</returns>
|
||||
IConfigurationHandler<T> GetConfigurationHandler<T>(string name) where T : IBaseConfiguration;
|
||||
}
|
||||
}
|
18
SharedLibraryCore/Interfaces/IGameServerInstanceFactory.cs
Normal file
18
SharedLibraryCore/Interfaces/IGameServerInstanceFactory.cs
Normal file
@ -0,0 +1,18 @@
|
||||
using SharedLibraryCore.Configuration;
|
||||
|
||||
namespace SharedLibraryCore.Interfaces
|
||||
{
|
||||
/// <summary>
|
||||
/// defines the capabilities of game server instance factory
|
||||
/// </summary>
|
||||
public interface IGameServerInstanceFactory
|
||||
{
|
||||
/// <summary>
|
||||
/// creates the instance of a game server
|
||||
/// </summary>
|
||||
/// <param name="config">server configuration</param>
|
||||
/// <param name="manager">application manager</param>
|
||||
/// <returns></returns>
|
||||
Server CreateServer(ServerConfiguration config, IManager manager);
|
||||
}
|
||||
}
|
@ -2,7 +2,6 @@
|
||||
using System.Threading.Tasks;
|
||||
using SharedLibraryCore.Services;
|
||||
using SharedLibraryCore.Configuration;
|
||||
using System.Reflection;
|
||||
using SharedLibraryCore.Database.Models;
|
||||
using System.Threading;
|
||||
using System.Collections;
|
||||
@ -29,7 +28,10 @@ namespace SharedLibraryCore.Interfaces
|
||||
/// </summary>
|
||||
/// <returns>EventHandler for the manager</returns>
|
||||
IEventHandler GetEventHandler();
|
||||
IList<Assembly> GetPluginAssemblies();
|
||||
/// <summary>
|
||||
/// enumerates the registered plugin instances
|
||||
/// </summary>
|
||||
IEnumerable<IPlugin> Plugins { get; }
|
||||
/// <summary>
|
||||
/// provides a page list to add and remove from
|
||||
/// </summary>
|
||||
|
@ -1,32 +1,23 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Reflection;
|
||||
|
||||
namespace SharedLibraryCore.Interfaces
|
||||
{
|
||||
/// <summary>
|
||||
/// Defines the capabilities of the plugin importer
|
||||
/// defines the capabilities of the plugin importer
|
||||
/// </summary>
|
||||
public interface IPluginImporter
|
||||
{
|
||||
{
|
||||
/// <summary>
|
||||
/// Command types that are defined in plugin assemblies
|
||||
/// discovers C# assembly plugin and command types
|
||||
/// </summary>
|
||||
IList<Type> CommandTypes { get; }
|
||||
/// <returns>tuple of IPlugin implementation type definitions, and IManagerCommand type definitions</returns>
|
||||
(IEnumerable<Type>, IEnumerable<Type>) DiscoverAssemblyPluginImplementations();
|
||||
|
||||
/// <summary>
|
||||
/// The loaded plugins from plugin assemblies
|
||||
/// discovers the script plugins
|
||||
/// </summary>
|
||||
IList<IPlugin> ActivePlugins { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Assemblies that contain plugins
|
||||
/// </summary>
|
||||
IList<Assembly> PluginAssemblies { get; }
|
||||
|
||||
/// <summary>
|
||||
/// All assemblies in the plugin folder
|
||||
/// </summary>
|
||||
IList<Assembly> Assemblies { get; }
|
||||
/// <returns>initialized script plugin collection</returns>
|
||||
IEnumerable<IPlugin> DiscoverScriptPlugins();
|
||||
}
|
||||
}
|
||||
|
25
SharedLibraryCore/Interfaces/IRConConnection.cs
Normal file
25
SharedLibraryCore/Interfaces/IRConConnection.cs
Normal file
@ -0,0 +1,25 @@
|
||||
using SharedLibraryCore.RCon;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace SharedLibraryCore.Interfaces
|
||||
{
|
||||
/// <summary>
|
||||
/// defines the capabilities of an RCon connection
|
||||
/// </summary>
|
||||
public interface IRConConnection
|
||||
{
|
||||
/// <summary>
|
||||
/// sends a query with the instance of the rcon connection
|
||||
/// </summary>
|
||||
/// <param name="type">type of RCon query to perform</param>
|
||||
/// <param name="parameters">optional parameter list</param>
|
||||
/// <returns></returns>
|
||||
Task<string[]> SendQueryAsync(StaticHelpers.QueryType type, string parameters = "");
|
||||
|
||||
/// <summary>
|
||||
/// sets the rcon parser configuration
|
||||
/// </summary>
|
||||
/// <param name="config">parser config</param>
|
||||
void SetConfiguration(IRConParserConfiguration config);
|
||||
}
|
||||
}
|
17
SharedLibraryCore/Interfaces/IRConConnectionFactory.cs
Normal file
17
SharedLibraryCore/Interfaces/IRConConnectionFactory.cs
Normal file
@ -0,0 +1,17 @@
|
||||
namespace SharedLibraryCore.Interfaces
|
||||
{
|
||||
/// <summary>
|
||||
/// defines the capabilities of an RCon connection factory
|
||||
/// </summary>
|
||||
public interface IRConConnectionFactory
|
||||
{
|
||||
/// <summary>
|
||||
/// creates an rcon connection instance
|
||||
/// </summary>
|
||||
/// <param name="ipAddress">ip address of the server</param>
|
||||
/// <param name="port">port of the server</param>
|
||||
/// <param name="password"> password of the server</param>
|
||||
/// <returns>instance of rcon connection</returns>
|
||||
IRConConnection CreateConnection(string ipAddress, int port, string password);
|
||||
}
|
||||
}
|
@ -1,7 +1,6 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using SharedLibraryCore.Database.Models;
|
||||
using SharedLibraryCore.RCon;
|
||||
using static SharedLibraryCore.Server;
|
||||
|
||||
namespace SharedLibraryCore.Interfaces
|
||||
@ -15,7 +14,7 @@ namespace SharedLibraryCore.Interfaces
|
||||
/// <param name="connection">RCon connection to retrieve with</param>
|
||||
/// <param name="dvarName">name of DVAR</param>
|
||||
/// <returns></returns>
|
||||
Task<Dvar<T>> GetDvarAsync<T>(Connection connection, string dvarName);
|
||||
Task<Dvar<T>> GetDvarAsync<T>(IRConConnection connection, string dvarName);
|
||||
|
||||
/// <summary>
|
||||
/// set value of DVAR by name
|
||||
@ -24,7 +23,7 @@ namespace SharedLibraryCore.Interfaces
|
||||
/// <param name="dvarName">name of DVAR to set</param>
|
||||
/// <param name="dvarValue">value to set DVAR to</param>
|
||||
/// <returns></returns>
|
||||
Task<bool> SetDvarAsync(Connection connection, string dvarName, object dvarValue);
|
||||
Task<bool> SetDvarAsync(IRConConnection connection, string dvarName, object dvarValue);
|
||||
|
||||
/// <summary>
|
||||
/// executes a console command on the server
|
||||
@ -32,14 +31,14 @@ namespace SharedLibraryCore.Interfaces
|
||||
/// <param name="connection">RCon connection to use</param>
|
||||
/// <param name="command">console command to execute</param>
|
||||
/// <returns></returns>
|
||||
Task<string[]> ExecuteCommandAsync(Connection connection, string command);
|
||||
Task<string[]> ExecuteCommandAsync(IRConConnection connection, string command);
|
||||
|
||||
/// <summary>
|
||||
/// get the list of connected clients from status response
|
||||
/// </summary>
|
||||
/// <param name="connection">RCon connection to use</param>
|
||||
/// <returns>list of clients, and current map</returns>
|
||||
Task<(List<EFClient>, string)> GetStatusAsync(Connection connection);
|
||||
Task<(List<EFClient>, string)> GetStatusAsync(IRConConnection connection);
|
||||
|
||||
/// <summary>
|
||||
/// stores the RCon configuration
|
||||
|
@ -1,8 +1,4 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace SharedLibraryCore.RCon
|
||||
namespace SharedLibraryCore.RCon
|
||||
{
|
||||
public class CommandPrefix
|
||||
{
|
||||
|
@ -1,285 +0,0 @@
|
||||
using SharedLibraryCore.Exceptions;
|
||||
using SharedLibraryCore.Interfaces;
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Net.Sockets;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace SharedLibraryCore.RCon
|
||||
{
|
||||
class ConnectionState
|
||||
{
|
||||
~ConnectionState()
|
||||
{
|
||||
OnComplete.Dispose();
|
||||
OnSentData.Dispose();
|
||||
OnReceivedData.Dispose();
|
||||
}
|
||||
|
||||
public int ConnectionAttempts { get; set; }
|
||||
const int BufferSize = 4096;
|
||||
public readonly byte[] ReceiveBuffer = new byte[BufferSize];
|
||||
public readonly SemaphoreSlim OnComplete = new SemaphoreSlim(1, 1);
|
||||
public readonly ManualResetEventSlim OnSentData = new ManualResetEventSlim(false);
|
||||
public readonly ManualResetEventSlim OnReceivedData = new ManualResetEventSlim(false);
|
||||
public SocketAsyncEventArgs SendEventArgs { get; set; } = new SocketAsyncEventArgs();
|
||||
public SocketAsyncEventArgs ReceiveEventArgs { get; set; } = new SocketAsyncEventArgs();
|
||||
public DateTime LastQuery { get; set; } = DateTime.Now;
|
||||
}
|
||||
|
||||
public class Connection
|
||||
{
|
||||
static readonly ConcurrentDictionary<EndPoint, ConnectionState> ActiveQueries = new ConcurrentDictionary<EndPoint, ConnectionState>();
|
||||
public IPEndPoint Endpoint { get; private set; }
|
||||
public string RConPassword { get; private set; }
|
||||
|
||||
private readonly ILogger Log;
|
||||
private IRConParserConfiguration Config;
|
||||
private readonly Encoding defaultEncoding;
|
||||
|
||||
public Connection(string ipAddress, int port, string password, ILogger log, IRConParserConfiguration config)
|
||||
{
|
||||
Endpoint = new IPEndPoint(IPAddress.Parse(ipAddress), port);
|
||||
defaultEncoding = Encoding.GetEncoding("windows-1252");
|
||||
RConPassword = password;
|
||||
Log = log;
|
||||
Config = config;
|
||||
}
|
||||
|
||||
public void SetConfiguration(IRConParserConfiguration config)
|
||||
{
|
||||
Config = config;
|
||||
}
|
||||
|
||||
public async Task<string[]> SendQueryAsync(StaticHelpers.QueryType type, string parameters = "")
|
||||
{
|
||||
if (!ActiveQueries.ContainsKey(this.Endpoint))
|
||||
{
|
||||
ActiveQueries.TryAdd(this.Endpoint, new ConnectionState());
|
||||
}
|
||||
|
||||
var connectionState = ActiveQueries[this.Endpoint];
|
||||
|
||||
#if DEBUG == true
|
||||
Log.WriteDebug($"Waiting for semaphore to be released [{this.Endpoint}]");
|
||||
#endif
|
||||
// enter the semaphore so only one query is sent at a time per server.
|
||||
await connectionState.OnComplete.WaitAsync();
|
||||
|
||||
var timeSinceLastQuery = (DateTime.Now - connectionState.LastQuery).TotalMilliseconds;
|
||||
|
||||
if (timeSinceLastQuery < StaticHelpers.FloodProtectionInterval)
|
||||
{
|
||||
await Task.Delay(StaticHelpers.FloodProtectionInterval - (int)timeSinceLastQuery);
|
||||
}
|
||||
|
||||
connectionState.LastQuery = DateTime.Now;
|
||||
|
||||
#if DEBUG == true
|
||||
Log.WriteDebug($"Semaphore has been released [{this.Endpoint}]");
|
||||
Log.WriteDebug($"Query [{this.Endpoint},{type.ToString()},{parameters}]");
|
||||
#endif
|
||||
|
||||
byte[] payload = null;
|
||||
bool waitForResponse = Config.WaitForResponse;
|
||||
|
||||
string convertEncoding(string text)
|
||||
{
|
||||
byte[] convertedBytes = Utilities.EncodingType.GetBytes(text);
|
||||
return defaultEncoding.GetString(convertedBytes);
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
string convertedRConPassword = convertEncoding(RConPassword);
|
||||
string convertedParameters = convertEncoding(parameters);
|
||||
|
||||
switch (type)
|
||||
{
|
||||
case StaticHelpers.QueryType.GET_DVAR:
|
||||
waitForResponse |= true;
|
||||
payload = string.Format(Config.CommandPrefixes.RConGetDvar, convertedRConPassword, convertedParameters + '\0').Select(Convert.ToByte).ToArray();
|
||||
break;
|
||||
case StaticHelpers.QueryType.SET_DVAR:
|
||||
payload = string.Format(Config.CommandPrefixes.RConSetDvar, convertedRConPassword, convertedParameters + '\0').Select(Convert.ToByte).ToArray();
|
||||
break;
|
||||
case StaticHelpers.QueryType.COMMAND:
|
||||
payload = string.Format(Config.CommandPrefixes.RConCommand, convertedRConPassword, convertedParameters + '\0').Select(Convert.ToByte).ToArray();
|
||||
break;
|
||||
case StaticHelpers.QueryType.GET_STATUS:
|
||||
waitForResponse |= true;
|
||||
payload = (Config.CommandPrefixes.RConGetStatus + '\0').Select(Convert.ToByte).ToArray();
|
||||
break;
|
||||
case StaticHelpers.QueryType.GET_INFO:
|
||||
waitForResponse |= true;
|
||||
payload = (Config.CommandPrefixes.RConGetInfo + '\0').Select(Convert.ToByte).ToArray();
|
||||
break;
|
||||
case StaticHelpers.QueryType.COMMAND_STATUS:
|
||||
waitForResponse |= true;
|
||||
payload = string.Format(Config.CommandPrefixes.RConCommand, convertedRConPassword, "status\0").Select(Convert.ToByte).ToArray();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// this happens when someone tries to send something that can't be converted into a 7 bit character set
|
||||
// e.g: emoji -> windows-1252
|
||||
catch (OverflowException)
|
||||
{
|
||||
connectionState.OnComplete.Release(1);
|
||||
throw new NetworkException($"Invalid character encountered when converting encodings - {parameters}");
|
||||
}
|
||||
|
||||
byte[] response = null;
|
||||
|
||||
retrySend:
|
||||
using (var socket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp)
|
||||
{
|
||||
DontFragment = true,
|
||||
Ttl = 100,
|
||||
ExclusiveAddressUse = true,
|
||||
})
|
||||
{
|
||||
connectionState.SendEventArgs.UserToken = socket;
|
||||
connectionState.OnSentData.Reset();
|
||||
connectionState.OnReceivedData.Reset();
|
||||
connectionState.ConnectionAttempts++;
|
||||
#if DEBUG == true
|
||||
Log.WriteDebug($"Sending {payload.Length} bytes to [{this.Endpoint}] ({connectionState.ConnectionAttempts}/{StaticHelpers.AllowedConnectionFails})");
|
||||
#endif
|
||||
try
|
||||
{
|
||||
response = await SendPayloadAsync(payload, waitForResponse);
|
||||
|
||||
if (response.Length == 0 && waitForResponse)
|
||||
{
|
||||
throw new NetworkException("Expected response but got 0 bytes back");
|
||||
}
|
||||
|
||||
connectionState.ConnectionAttempts = 0;
|
||||
}
|
||||
|
||||
catch
|
||||
{
|
||||
if (connectionState.ConnectionAttempts < StaticHelpers.AllowedConnectionFails)
|
||||
{
|
||||
await Task.Delay(StaticHelpers.FloodProtectionInterval);
|
||||
goto retrySend;
|
||||
}
|
||||
|
||||
throw new NetworkException(Utilities.CurrentLocalization.LocalizationIndex["SERVER_ERROR_COMMUNICATION"].FormatExt(Endpoint));
|
||||
}
|
||||
|
||||
finally
|
||||
{
|
||||
if (connectionState.OnComplete.CurrentCount == 0)
|
||||
{
|
||||
connectionState.OnComplete.Release(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
string responseString = defaultEncoding.GetString(response, 0, response.Length) + '\n';
|
||||
|
||||
// note: not all games respond if the pasword is wrong or not set
|
||||
if (responseString.Contains("Invalid password") || responseString.Contains("rconpassword"))
|
||||
{
|
||||
throw new NetworkException(Utilities.CurrentLocalization.LocalizationIndex["SERVER_ERROR_RCON_INVALID"]);
|
||||
}
|
||||
|
||||
if (responseString.Contains("rcon_password"))
|
||||
{
|
||||
throw new NetworkException(Utilities.CurrentLocalization.LocalizationIndex["SERVER_ERROR_RCON_NOTSET"]);
|
||||
}
|
||||
|
||||
if (responseString.Contains(Config.ServerNotRunningResponse))
|
||||
{
|
||||
throw new ServerException(Utilities.CurrentLocalization.LocalizationIndex["SERVER_ERROR_NOT_RUNNING"].FormatExt(Endpoint.ToString()));
|
||||
}
|
||||
|
||||
string[] splitResponse = responseString.Split(new char[] { '\n' }, StringSplitOptions.RemoveEmptyEntries)
|
||||
.Select(line => line.Trim())
|
||||
.ToArray();
|
||||
return splitResponse;
|
||||
}
|
||||
|
||||
private async Task<byte[]> SendPayloadAsync(byte[] payload, bool waitForResponse)
|
||||
{
|
||||
var connectionState = ActiveQueries[this.Endpoint];
|
||||
var rconSocket = (Socket)connectionState.SendEventArgs.UserToken;
|
||||
|
||||
if (connectionState.ReceiveEventArgs.RemoteEndPoint == null &&
|
||||
connectionState.SendEventArgs.RemoteEndPoint == null)
|
||||
{
|
||||
// setup the event handlers only once because we're reusing the event args
|
||||
connectionState.SendEventArgs.Completed += OnDataSent;
|
||||
connectionState.ReceiveEventArgs.Completed += OnDataReceived;
|
||||
connectionState.SendEventArgs.RemoteEndPoint = this.Endpoint;
|
||||
connectionState.ReceiveEventArgs.RemoteEndPoint = this.Endpoint;
|
||||
connectionState.ReceiveEventArgs.DisconnectReuseSocket = true;
|
||||
connectionState.SendEventArgs.DisconnectReuseSocket = true;
|
||||
}
|
||||
|
||||
connectionState.SendEventArgs.SetBuffer(payload);
|
||||
|
||||
// send the data to the server
|
||||
bool sendDataPending = rconSocket.SendToAsync(connectionState.SendEventArgs);
|
||||
|
||||
if (sendDataPending)
|
||||
{
|
||||
// the send has not been completed asyncronously
|
||||
if (!await Task.Run(() => connectionState.OnSentData.Wait(StaticHelpers.SocketTimeout)))
|
||||
{
|
||||
rconSocket.Close();
|
||||
throw new NetworkException("Timed out sending data", rconSocket);
|
||||
}
|
||||
}
|
||||
|
||||
if (!waitForResponse)
|
||||
{
|
||||
return new byte[0];
|
||||
}
|
||||
|
||||
connectionState.ReceiveEventArgs.SetBuffer(connectionState.ReceiveBuffer);
|
||||
|
||||
// get our response back
|
||||
bool receiveDataPending = rconSocket.ReceiveFromAsync(connectionState.ReceiveEventArgs);
|
||||
|
||||
if (receiveDataPending)
|
||||
{
|
||||
if (!await Task.Run(() => connectionState.OnReceivedData.Wait(StaticHelpers.SocketTimeout)))
|
||||
{
|
||||
rconSocket.Close();
|
||||
throw new NetworkException("Timed out waiting for response", rconSocket);
|
||||
}
|
||||
}
|
||||
|
||||
rconSocket.Close();
|
||||
|
||||
byte[] response = connectionState.ReceiveBuffer
|
||||
.Take(connectionState.ReceiveEventArgs.BytesTransferred)
|
||||
.ToArray();
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
private void OnDataReceived(object sender, SocketAsyncEventArgs e)
|
||||
{
|
||||
#if DEBUG == true
|
||||
Log.WriteDebug($"Read {e.BytesTransferred} bytes from {e.RemoteEndPoint.ToString()}");
|
||||
#endif
|
||||
ActiveQueries[this.Endpoint].OnReceivedData.Set();
|
||||
}
|
||||
|
||||
private void OnDataSent(object sender, SocketAsyncEventArgs e)
|
||||
{
|
||||
#if DEBUG == true
|
||||
Log.WriteDebug($"Sent {e.Buffer?.Length} bytes to {e.ConnectSocket?.RemoteEndPoint?.ToString()}");
|
||||
#endif
|
||||
ActiveQueries[this.Endpoint].OnSentData.Set();
|
||||
}
|
||||
}
|
||||
}
|
@ -1,186 +0,0 @@
|
||||
using Jint;
|
||||
using SharedLibraryCore.Database.Models;
|
||||
using SharedLibraryCore.Interfaces;
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace SharedLibraryCore
|
||||
{
|
||||
public class ScriptPlugin : IPlugin
|
||||
{
|
||||
public string Name { get; set; }
|
||||
|
||||
public float Version { get; set; }
|
||||
|
||||
public string Author { get; set; }
|
||||
|
||||
public FileSystemWatcher Watcher { get; private set; }
|
||||
|
||||
private Engine _scriptEngine;
|
||||
private readonly string _fileName;
|
||||
private readonly SemaphoreSlim _onProcessing;
|
||||
private bool successfullyLoaded;
|
||||
|
||||
public ScriptPlugin(string filename)
|
||||
{
|
||||
_fileName = filename;
|
||||
Watcher = new FileSystemWatcher()
|
||||
{
|
||||
Path = $"{Utilities.OperatingDirectory}Plugins{Path.DirectorySeparatorChar}",
|
||||
NotifyFilter = NotifyFilters.Size,
|
||||
Filter = _fileName.Split(Path.DirectorySeparatorChar).Last()
|
||||
};
|
||||
|
||||
Watcher.EnableRaisingEvents = true;
|
||||
_onProcessing = new SemaphoreSlim(1, 1);
|
||||
}
|
||||
|
||||
~ScriptPlugin()
|
||||
{
|
||||
Watcher.Dispose();
|
||||
_onProcessing.Dispose();
|
||||
}
|
||||
|
||||
|
||||
public async Task Initialize(IManager manager)
|
||||
{
|
||||
await _onProcessing.WaitAsync();
|
||||
|
||||
try
|
||||
{
|
||||
// for some reason we get an event trigger when the file is not finished being modified.
|
||||
// this must have been a change in .NET CORE 3.x
|
||||
// so if the new file is empty we can't process it yet
|
||||
if (new FileInfo(_fileName).Length == 0L)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
bool firstRun = _scriptEngine == null;
|
||||
|
||||
// it's been loaded before so we need to call the unload event
|
||||
if (!firstRun)
|
||||
{
|
||||
await OnUnloadAsync();
|
||||
}
|
||||
|
||||
successfullyLoaded = false;
|
||||
string script;
|
||||
|
||||
using (var stream = new FileStream(_fileName, FileMode.Open, FileAccess.Read, FileShare.ReadWrite))
|
||||
{
|
||||
using (var reader = new StreamReader(stream, Encoding.Default))
|
||||
{
|
||||
script = await reader.ReadToEndAsync();
|
||||
}
|
||||
}
|
||||
|
||||
_scriptEngine = new Engine(cfg =>
|
||||
cfg.AllowClr(new[]
|
||||
{
|
||||
typeof(System.Net.Http.HttpClient).Assembly,
|
||||
typeof(EFClient).Assembly,
|
||||
typeof(Utilities).Assembly,
|
||||
typeof(Encoding).Assembly
|
||||
})
|
||||
.CatchClrExceptions());
|
||||
|
||||
_scriptEngine.Execute(script);
|
||||
_scriptEngine.SetValue("_localization", Utilities.CurrentLocalization);
|
||||
dynamic pluginObject = _scriptEngine.GetValue("plugin").ToObject();
|
||||
|
||||
Author = pluginObject.author;
|
||||
Name = pluginObject.name;
|
||||
Version = (float)pluginObject.version;
|
||||
|
||||
try
|
||||
{
|
||||
if (pluginObject.isParser)
|
||||
{
|
||||
await OnLoadAsync(manager);
|
||||
IEventParser eventParser = (IEventParser)_scriptEngine.GetValue("eventParser").ToObject();
|
||||
IRConParser rconParser = (IRConParser)_scriptEngine.GetValue("rconParser").ToObject();
|
||||
manager.AdditionalEventParsers.Add(eventParser);
|
||||
manager.AdditionalRConParsers.Add(rconParser);
|
||||
}
|
||||
}
|
||||
catch { }
|
||||
|
||||
|
||||
if (!firstRun)
|
||||
{
|
||||
await OnLoadAsync(manager);
|
||||
}
|
||||
|
||||
successfullyLoaded = true;
|
||||
}
|
||||
|
||||
catch
|
||||
{
|
||||
throw;
|
||||
}
|
||||
|
||||
finally
|
||||
{
|
||||
if (_onProcessing.CurrentCount == 0)
|
||||
{
|
||||
_onProcessing.Release(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public async Task OnEventAsync(GameEvent E, Server S)
|
||||
{
|
||||
if (successfullyLoaded)
|
||||
{
|
||||
await _onProcessing.WaitAsync();
|
||||
|
||||
try
|
||||
{
|
||||
_scriptEngine.SetValue("_gameEvent", E);
|
||||
_scriptEngine.SetValue("_server", S);
|
||||
_scriptEngine.SetValue("_IW4MAdminClient", Utilities.IW4MAdminClient(S));
|
||||
await Task.FromResult(_scriptEngine.Execute("plugin.onEventAsync(_gameEvent, _server)").GetCompletionValue());
|
||||
}
|
||||
|
||||
catch
|
||||
{
|
||||
throw;
|
||||
}
|
||||
|
||||
finally
|
||||
{
|
||||
if (_onProcessing.CurrentCount == 0)
|
||||
{
|
||||
_onProcessing.Release(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public Task OnLoadAsync(IManager manager)
|
||||
{
|
||||
manager.GetLogger(0).WriteDebug($"OnLoad executing for {Name}");
|
||||
_scriptEngine.SetValue("_manager", manager);
|
||||
return Task.FromResult(_scriptEngine.Execute("plugin.onLoadAsync(_manager)").GetCompletionValue());
|
||||
}
|
||||
|
||||
public Task OnTickAsync(Server S)
|
||||
{
|
||||
_scriptEngine.SetValue("_server", S);
|
||||
return Task.FromResult(_scriptEngine.Execute("plugin.onTickAsync(_server)").GetCompletionValue());
|
||||
}
|
||||
|
||||
public async Task OnUnloadAsync()
|
||||
{
|
||||
if (successfullyLoaded)
|
||||
{
|
||||
await Task.FromResult(_scriptEngine.Execute("plugin.onUnloadAsync()").GetCompletionValue());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -28,7 +28,7 @@ namespace SharedLibraryCore
|
||||
T7 = 8
|
||||
}
|
||||
|
||||
public Server(IManager mgr, ServerConfiguration config)
|
||||
public Server(IManager mgr, IRConConnectionFactory rconConnectionFactory, ServerConfiguration config)
|
||||
{
|
||||
Password = config.Password;
|
||||
IP = config.IPAddress;
|
||||
@ -37,8 +37,7 @@ namespace SharedLibraryCore
|
||||
Logger = Manager.GetLogger(this.EndPoint);
|
||||
Logger.WriteInfo(this.ToString());
|
||||
ServerConfig = config;
|
||||
RemoteConnection = new RCon.Connection(IP, Port, Password, Logger, null);
|
||||
|
||||
RemoteConnection = rconConnectionFactory.CreateConnection(IP, Port, Password);
|
||||
EventProcessing = new SemaphoreSlim(1, 1);
|
||||
Clients = new List<EFClient>(new EFClient[18]);
|
||||
Reports = new List<Report>();
|
||||
@ -283,7 +282,7 @@ namespace SharedLibraryCore
|
||||
public IManager Manager { get; protected set; }
|
||||
public ILogger Logger { get; private set; }
|
||||
public ServerConfiguration ServerConfig { get; private set; }
|
||||
public List<Map> Maps { get; protected set; }
|
||||
public List<Map> Maps { get; protected set; } = new List<Map>();
|
||||
public List<Report> Reports { get; set; }
|
||||
public List<ChatInfo> ChatHistory { get; protected set; }
|
||||
public Queue<PlayerHistory> ClientHistory { get; private set; }
|
||||
@ -307,7 +306,7 @@ namespace SharedLibraryCore
|
||||
public bool Throttled { get; protected set; }
|
||||
public bool CustomCallback { get; protected set; }
|
||||
public string WorkingDirectory { get; protected set; }
|
||||
public RCon.Connection RemoteConnection { get; protected set; }
|
||||
public IRConConnection RemoteConnection { get; protected set; }
|
||||
public IRConParser RconParser { get; protected set; }
|
||||
public IEventParser EventParser { get; set; }
|
||||
public string LogPath { get; protected set; }
|
||||
|
@ -6,7 +6,7 @@
|
||||
<ApplicationIcon />
|
||||
<StartupObject />
|
||||
<PackageId>RaidMax.IW4MAdmin.SharedLibraryCore</PackageId>
|
||||
<Version>2.2.6</Version>
|
||||
<Version>2.2.7</Version>
|
||||
<Authors>RaidMax</Authors>
|
||||
<Company>Forever None</Company>
|
||||
<Configurations>Debug;Release;Prerelease</Configurations>
|
||||
@ -20,8 +20,8 @@
|
||||
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
|
||||
<PackageLicenseExpression>MIT</PackageLicenseExpression>
|
||||
<Description>Shared Library for IW4MAdmin</Description>
|
||||
<AssemblyVersion>2.2.6.0</AssemblyVersion>
|
||||
<FileVersion>2.2.6.0</FileVersion>
|
||||
<AssemblyVersion>2.2.7.0</AssemblyVersion>
|
||||
<FileVersion>2.2.7.0</FileVersion>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Prerelease|AnyCPU'">
|
||||
@ -68,6 +68,10 @@
|
||||
<PackageReference Include="Pomelo.EntityFrameworkCore.MySql" Version="3.1.0" />
|
||||
<PackageReference Include="SimpleCrypto.NetCore" Version="1.0.0" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup Condition="'$(Configuration)'=='Debug'">
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.InMemory" Version="3.1.1" />
|
||||
</ItemGroup>
|
||||
|
||||
<Target Name="PreBuild" BeforeTargets="PreBuildEvent">
|
||||
<Exec Command="if not exist "$(ProjectDir)..\BUILD" (
if $(ConfigurationName) == Debug (
md "$(ProjectDir)..\BUILD"
)
)
if not exist "$(ProjectDir)..\BUILD\Plugins" (
if $(ConfigurationName) == Debug (
md "$(ProjectDir)..\BUILD\Plugins"
)
)" />
|
||||
|
Reference in New Issue
Block a user