[tweaks and fixes]

reenable tekno support
address vagrant thread issue
refactor game log reader creation to follow better practices
fix bot issues/address how guids are generated for bots/none provided
This commit is contained in:
RaidMax 2020-05-04 16:50:02 -05:00
parent b49592d666
commit 267e0b8cbe
50 changed files with 775 additions and 233 deletions

1
.gitignore vendored
View File

@ -242,3 +242,4 @@ launchSettings.json
/WebfrontCore/wwwroot/font
/Plugins/Tests/TestSourceFiles
/Tests/ApplicationTests/Files/GameEvents.json
/Tests/ApplicationTests/Files/replay.json

View File

@ -10,7 +10,7 @@
<Company>Forever None</Company>
<Product>IW4MAdmin</Product>
<Description>IW4MAdmin is a complete server administration tool for IW4x and most Call of Duty® dedicated servers</Description>
<Copyright>2019</Copyright>
<Copyright>2020</Copyright>
<PackageLicenseUrl>https://github.com/RaidMax/IW4M-Admin/blob/master/LICENSE</PackageLicenseUrl>
<PackageProjectUrl>https://raidmax.org/IW4MAdmin</PackageProjectUrl>
<RepositoryUrl>https://github.com/RaidMax/IW4M-Admin</RepositoryUrl>

View File

@ -31,7 +31,7 @@ namespace IW4MAdmin.Application
private readonly ConcurrentBag<Server> _servers;
public List<Server> Servers => _servers.OrderByDescending(s => s.ClientNum).ToList();
public ILogger Logger => GetLogger(0);
public bool Running { get; private set; }
public bool IsRunning { get; private set; }
public bool IsInitialized { get; private set; }
public DateTime StartTime { get; private set; }
public string Version => Assembly.GetEntryAssembly().GetName().Version.ToString();
@ -50,7 +50,6 @@ namespace IW4MAdmin.Application
readonly AliasService AliasSvc;
readonly PenaltyService PenaltySvc;
public IConfigurationHandler<ApplicationConfiguration> ConfigHandler;
GameEventHandler Handler;
readonly IPageList PageList;
private readonly Dictionary<long, ILogger> _loggers = new Dictionary<long, ILogger>();
private readonly MetaService _metaService;
@ -62,11 +61,13 @@ namespace IW4MAdmin.Application
private readonly IGameServerInstanceFactory _serverInstanceFactory;
private readonly IParserRegexFactory _parserRegexFactory;
private readonly IEnumerable<IRegisterEvent> _customParserEvents;
private readonly IEventHandler _eventHandler;
public ApplicationManager(ILogger logger, IMiddlewareActionHandler actionHandler, IEnumerable<IManagerCommand> commands,
ITranslationLookup translationLookup, IConfigurationHandler<CommandConfiguration> commandConfiguration,
IConfigurationHandler<ApplicationConfiguration> appConfigHandler, IGameServerInstanceFactory serverInstanceFactory,
IEnumerable<IPlugin> plugins, IParserRegexFactory parserRegexFactory, IEnumerable<IRegisterEvent> customParserEvents)
IEnumerable<IPlugin> plugins, IParserRegexFactory parserRegexFactory, IEnumerable<IRegisterEvent> customParserEvents,
IEventHandler eventHandler)
{
MiddlewareActionHandler = actionHandler;
_servers = new ConcurrentBag<Server>();
@ -90,6 +91,7 @@ namespace IW4MAdmin.Application
_serverInstanceFactory = serverInstanceFactory;
_parserRegexFactory = parserRegexFactory;
_customParserEvents = customParserEvents;
_eventHandler = eventHandler;
Plugins = plugins;
}
@ -255,7 +257,7 @@ namespace IW4MAdmin.Application
public async Task Init()
{
Running = true;
IsRunning = true;
ExternalIPAddress = await Utilities.GetExternalIP();
#region PLUGINS
@ -589,9 +591,6 @@ namespace IW4MAdmin.Application
async Task Init(ServerConfiguration Conf)
{
// setup the event handler after the class is initialized
Handler = new GameEventHandler(this);
try
{
// todo: this might not always be an IW4MServer
@ -610,7 +609,7 @@ namespace IW4MAdmin.Application
Owner = ServerInstance
};
Handler.AddEvent(e);
AddEvent(e);
successServers++;
}
@ -726,7 +725,7 @@ namespace IW4MAdmin.Application
public void Stop()
{
_tokenSource.Cancel();
Running = false;
IsRunning = false;
}
public void Restart()
@ -782,9 +781,9 @@ namespace IW4MAdmin.Application
return ConfigHandler;
}
public IEventHandler GetEventHandler()
public void AddEvent(GameEvent gameEvent)
{
return Handler;
_eventHandler.HandleEvent(this, gameEvent);
}
public IPageList GetPageList()

View File

@ -23,26 +23,26 @@ namespace IW4MAdmin.Application.EventParsers
GameDirectory = "main",
};
Configuration.Say.Pattern = @"^(say|sayteam);(-?[A-Fa-f0-9_]{1,32});([0-9]+);(.+);(.*)$";
Configuration.Say.Pattern = @"^(say|sayteam);(-?[A-Fa-f0-9_]{1,32}|bot[0-9]+|0);([0-9]+);(.+);(.*)$";
Configuration.Say.AddMapping(ParserRegex.GroupType.EventType, 1);
Configuration.Say.AddMapping(ParserRegex.GroupType.OriginNetworkId, 2);
Configuration.Say.AddMapping(ParserRegex.GroupType.OriginClientNumber, 3);
Configuration.Say.AddMapping(ParserRegex.GroupType.OriginName, 4);
Configuration.Say.AddMapping(ParserRegex.GroupType.Message, 5);
Configuration.Quit.Pattern = @"^(Q);(-?[A-Fa-f0-9_]{1,32}|bot[0-9]+);([0-9]+);(.*)$";
Configuration.Quit.Pattern = @"^(Q);(-?[A-Fa-f0-9_]{1,32}|bot[0-9]+|0);([0-9]+);(.*)$";
Configuration.Quit.AddMapping(ParserRegex.GroupType.EventType, 1);
Configuration.Quit.AddMapping(ParserRegex.GroupType.OriginNetworkId, 2);
Configuration.Quit.AddMapping(ParserRegex.GroupType.OriginClientNumber, 3);
Configuration.Quit.AddMapping(ParserRegex.GroupType.OriginName, 4);
Configuration.Join.Pattern = @"^(J);(-?[A-Fa-f0-9_]{1,32}|bot[0-9]+);([0-9]+);(.*)$";
Configuration.Join.Pattern = @"^(J);(-?[A-Fa-f0-9_]{1,32}|bot[0-9]+|0);([0-9]+);(.*)$";
Configuration.Join.AddMapping(ParserRegex.GroupType.EventType, 1);
Configuration.Join.AddMapping(ParserRegex.GroupType.OriginNetworkId, 2);
Configuration.Join.AddMapping(ParserRegex.GroupType.OriginClientNumber, 3);
Configuration.Join.AddMapping(ParserRegex.GroupType.OriginName, 4);
Configuration.Damage.Pattern = @"^(D);(-?[A-Fa-f0-9_]{1,32}|bot[0-9]+);(-?[0-9]+);(axis|allies|world)?;([^;]{1,24});(-?[A-Fa-f0-9_]{1,32}|bot[0-9]+)?;-?([0-9]+);(axis|allies|world)?;([^;]{1,24})?;((?:[0-9]+|[a-z]+|_|\+)+);([0-9]+);((?:[A-Z]|_)+);((?:[a-z]|_)+)$";
Configuration.Damage.Pattern = @"^(D);(-?[A-Fa-f0-9_]{1,32}|bot[0-9]+|0);(-?[0-9]+);(axis|allies|world)?;([^;]{1,24});(-?[A-Fa-f0-9_]{1,32}|bot[0-9]+|0)?;(-?[0-9]+);(axis|allies|world)?;([^;]{1,24})?;((?:[0-9]+|[a-z]+|_|\+)+);([0-9]+);((?:[A-Z]|_)+);((?:[a-z]|_)+)$";
Configuration.Damage.AddMapping(ParserRegex.GroupType.EventType, 1);
Configuration.Damage.AddMapping(ParserRegex.GroupType.TargetNetworkId, 2);
Configuration.Damage.AddMapping(ParserRegex.GroupType.TargetClientNumber, 3);
@ -57,7 +57,7 @@ namespace IW4MAdmin.Application.EventParsers
Configuration.Damage.AddMapping(ParserRegex.GroupType.MeansOfDeath, 12);
Configuration.Damage.AddMapping(ParserRegex.GroupType.HitLocation, 13);
Configuration.Kill.Pattern = @"^(K);(-?[A-Fa-f0-9_]{1,32}|bot[0-9]+);(-?[0-9]+);(axis|allies|world)?;([^;]{1,24});(-?[A-Fa-f0-9_]{1,32}|bot[0-9]+)?;-?([0-9]+);(axis|allies|world)?;([^;]{1,24})?;((?:[0-9]+|[a-z]+|_|\+)+);([0-9]+);((?:[A-Z]|_)+);((?:[a-z]|_)+)$";
Configuration.Kill.Pattern = @"^(K);(-?[A-Fa-f0-9_]{1,32}|bot[0-9]+|0);(-?[0-9]+);(axis|allies|world)?;([^;]{1,24});(-?[A-Fa-f0-9_]{1,32}|bot[0-9]+|0)?;(-?[0-9]+);(axis|allies|world)?;([^;]{1,24})?;((?:[0-9]+|[a-z]+|_|\+)+);([0-9]+);((?:[A-Z]|_)+);((?:[a-z]|_)+)$";
Configuration.Kill.AddMapping(ParserRegex.GroupType.EventType, 1);
Configuration.Kill.AddMapping(ParserRegex.GroupType.TargetNetworkId, 2);
Configuration.Kill.AddMapping(ParserRegex.GroupType.TargetClientNumber, 3);
@ -118,7 +118,13 @@ namespace IW4MAdmin.Application.EventParsers
if (message.Length > 0)
{
long originId = matchResult.Values[Configuration.Say.GroupMapping[ParserRegex.GroupType.OriginNetworkId]].ToString().ConvertGuidToLong(Configuration.GuidNumberStyle);
string originIdString = matchResult.Values[Configuration.Say.GroupMapping[ParserRegex.GroupType.OriginNetworkId]].ToString();
string originName = matchResult.Values[Configuration.Say.GroupMapping[ParserRegex.GroupType.OriginName]].ToString();
long originId = originIdString.IsBotGuid() ?
originName.GenerateGuidFromString() :
originIdString.ConvertGuidToLong(Configuration.GuidNumberStyle);
int clientNumber = int.Parse(matchResult.Values[Configuration.Say.GroupMapping[ParserRegex.GroupType.OriginClientNumber]]);
// todo: these need to defined outside of here
@ -132,7 +138,8 @@ namespace IW4MAdmin.Application.EventParsers
Message = message,
Extra = logLine,
RequiredEntity = GameEvent.EventRequiredEntity.Origin,
GameTime = gameTime
GameTime = gameTime,
Source = GameEvent.EventSource.Log
};
}
@ -144,7 +151,8 @@ namespace IW4MAdmin.Application.EventParsers
Message = message,
Extra = logLine,
RequiredEntity = GameEvent.EventRequiredEntity.Origin,
GameTime = gameTime
GameTime = gameTime,
Source = GameEvent.EventSource.Log
};
}
}
@ -156,8 +164,18 @@ namespace IW4MAdmin.Application.EventParsers
if (match.Success)
{
long originId = match.Values[Configuration.Kill.GroupMapping[ParserRegex.GroupType.OriginNetworkId]].ToString().ConvertGuidToLong(Configuration.GuidNumberStyle, 1);
long targetId = match.Values[Configuration.Kill.GroupMapping[ParserRegex.GroupType.TargetNetworkId]].ToString().ConvertGuidToLong(Configuration.GuidNumberStyle, 1);
string originIdString = match.Values[Configuration.Kill.GroupMapping[ParserRegex.GroupType.OriginNetworkId]].ToString();
string targetIdString = match.Values[Configuration.Kill.GroupMapping[ParserRegex.GroupType.TargetNetworkId]].ToString();
string originName = match.Values[Configuration.Kill.GroupMapping[ParserRegex.GroupType.OriginName]].ToString();
string targetName = match.Values[Configuration.Kill.GroupMapping[ParserRegex.GroupType.TargetName]].ToString();
long originId = originIdString.IsBotGuid() ?
originName.GenerateGuidFromString() :
originIdString.ConvertGuidToLong(Configuration.GuidNumberStyle, Utilities.WORLD_ID);
long targetId = targetIdString.IsBotGuid() ?
targetName.GenerateGuidFromString() :
targetIdString.ConvertGuidToLong(Configuration.GuidNumberStyle, Utilities.WORLD_ID);
int originClientNumber = int.Parse(match.Values[Configuration.Kill.GroupMapping[ParserRegex.GroupType.OriginClientNumber]]);
int targetClientNumber = int.Parse(match.Values[Configuration.Kill.GroupMapping[ParserRegex.GroupType.TargetClientNumber]]);
@ -168,7 +186,8 @@ namespace IW4MAdmin.Application.EventParsers
Origin = new EFClient() { NetworkId = originId, ClientNumber = originClientNumber },
Target = new EFClient() { NetworkId = targetId, ClientNumber = targetClientNumber },
RequiredEntity = GameEvent.EventRequiredEntity.Origin | GameEvent.EventRequiredEntity.Target,
GameTime = gameTime
GameTime = gameTime,
Source = GameEvent.EventSource.Log
};
}
}
@ -179,8 +198,18 @@ namespace IW4MAdmin.Application.EventParsers
if (match.Success)
{
long originId = match.Values[Configuration.Damage.GroupMapping[ParserRegex.GroupType.OriginNetworkId]].ToString().ConvertGuidToLong(Configuration.GuidNumberStyle, 1);
long targetId = match.Values[Configuration.Damage.GroupMapping[ParserRegex.GroupType.TargetNetworkId]].ToString().ConvertGuidToLong(Configuration.GuidNumberStyle, 1);
string originIdString = match.Values[Configuration.Damage.GroupMapping[ParserRegex.GroupType.OriginNetworkId]].ToString();
string targetIdString = match.Values[Configuration.Damage.GroupMapping[ParserRegex.GroupType.TargetNetworkId]].ToString();
string originName = match.Values[Configuration.Damage.GroupMapping[ParserRegex.GroupType.OriginName]].ToString();
string targetName = match.Values[Configuration.Damage.GroupMapping[ParserRegex.GroupType.TargetName]].ToString();
long originId = originIdString.IsBotGuid() ?
originName.GenerateGuidFromString() :
originIdString.ConvertGuidToLong(Configuration.GuidNumberStyle, Utilities.WORLD_ID);
long targetId = targetIdString.IsBotGuid() ?
targetName.GenerateGuidFromString() :
targetIdString.ConvertGuidToLong(Configuration.GuidNumberStyle, Utilities.WORLD_ID);
int originClientNumber = int.Parse(match.Values[Configuration.Damage.GroupMapping[ParserRegex.GroupType.OriginClientNumber]]);
int targetClientNumber = int.Parse(match.Values[Configuration.Damage.GroupMapping[ParserRegex.GroupType.TargetClientNumber]]);
@ -191,7 +220,8 @@ namespace IW4MAdmin.Application.EventParsers
Origin = new EFClient() { NetworkId = originId, ClientNumber = originClientNumber },
Target = new EFClient() { NetworkId = targetId, ClientNumber = targetClientNumber },
RequiredEntity = GameEvent.EventRequiredEntity.Origin | GameEvent.EventRequiredEntity.Target,
GameTime = gameTime
GameTime = gameTime,
Source = GameEvent.EventSource.Log
};
}
}
@ -202,6 +232,13 @@ namespace IW4MAdmin.Application.EventParsers
if (match.Success)
{
string originIdString = match.Values[Configuration.Join.GroupMapping[ParserRegex.GroupType.OriginNetworkId]].ToString();
string originName = match.Values[Configuration.Join.GroupMapping[ParserRegex.GroupType.OriginName]].ToString();
long networkId = originIdString.IsBotGuid() ?
originName.GenerateGuidFromString() :
originIdString.ConvertGuidToLong(Configuration.GuidNumberStyle);
return new GameEvent()
{
Type = GameEvent.EventType.PreConnect,
@ -212,13 +249,14 @@ namespace IW4MAdmin.Application.EventParsers
{
Name = match.Values[Configuration.Join.GroupMapping[ParserRegex.GroupType.OriginName]].ToString().TrimNewLine(),
},
NetworkId = match.Values[Configuration.Join.GroupMapping[ParserRegex.GroupType.OriginNetworkId]].ToString().ConvertGuidToLong(Configuration.GuidNumberStyle),
NetworkId = networkId,
ClientNumber = Convert.ToInt32(match.Values[Configuration.Join.GroupMapping[ParserRegex.GroupType.OriginClientNumber]].ToString()),
State = EFClient.ClientState.Connecting,
},
RequiredEntity = GameEvent.EventRequiredEntity.None,
IsBlocking = true,
GameTime = gameTime
GameTime = gameTime,
Source = GameEvent.EventSource.Log
};
}
}
@ -229,6 +267,13 @@ namespace IW4MAdmin.Application.EventParsers
if (match.Success)
{
string originIdString = match.Values[Configuration.Quit.GroupMapping[ParserRegex.GroupType.OriginNetworkId]].ToString();
string originName = match.Values[Configuration.Quit.GroupMapping[ParserRegex.GroupType.OriginName]].ToString();
long networkId = originIdString.IsBotGuid() ?
originName.GenerateGuidFromString() :
originIdString.ConvertGuidToLong(Configuration.GuidNumberStyle);
return new GameEvent()
{
Type = GameEvent.EventType.PreDisconnect,
@ -239,13 +284,14 @@ namespace IW4MAdmin.Application.EventParsers
{
Name = match.Values[Configuration.Quit.GroupMapping[ParserRegex.GroupType.OriginName]].ToString().TrimNewLine()
},
NetworkId = match.Values[Configuration.Quit.GroupMapping[ParserRegex.GroupType.OriginNetworkId]].ToString().ConvertGuidToLong(Configuration.GuidNumberStyle),
NetworkId = networkId,
ClientNumber = Convert.ToInt32(match.Values[Configuration.Quit.GroupMapping[ParserRegex.GroupType.OriginClientNumber]].ToString()),
State = EFClient.ClientState.Disconnecting
},
RequiredEntity = GameEvent.EventRequiredEntity.None,
IsBlocking = true,
GameTime = gameTime
GameTime = gameTime,
Source = GameEvent.EventSource.Log
};
}
}
@ -259,7 +305,8 @@ namespace IW4MAdmin.Application.EventParsers
Origin = Utilities.IW4MAdminClient(),
Target = Utilities.IW4MAdminClient(),
RequiredEntity = GameEvent.EventRequiredEntity.None,
GameTime = gameTime
GameTime = gameTime,
Source = GameEvent.EventSource.Log
};
}
@ -275,7 +322,8 @@ namespace IW4MAdmin.Application.EventParsers
Target = Utilities.IW4MAdminClient(),
Extra = dump.DictionaryFromKeyValue(),
RequiredEntity = GameEvent.EventRequiredEntity.None,
GameTime = gameTime
GameTime = gameTime,
Source = GameEvent.EventSource.Log
};
}
@ -290,7 +338,8 @@ namespace IW4MAdmin.Application.EventParsers
Type = GameEvent.EventType.Other,
Data = logLine,
Subtype = eventModifier.Item1,
GameTime = gameTime
GameTime = gameTime,
Source = GameEvent.EventSource.Log
});
}
@ -307,7 +356,8 @@ namespace IW4MAdmin.Application.EventParsers
Origin = Utilities.IW4MAdminClient(),
Target = Utilities.IW4MAdminClient(),
RequiredEntity = GameEvent.EventRequiredEntity.None,
GameTime = gameTime
GameTime = gameTime,
Source = GameEvent.EventSource.Log
};
}

View File

@ -0,0 +1,33 @@
using IW4MAdmin.Application.IO;
using Microsoft.Extensions.DependencyInjection;
using SharedLibraryCore.Interfaces;
using System;
namespace IW4MAdmin.Application.Factories
{
public class GameLogReaderFactory : IGameLogReaderFactory
{
private readonly IServiceProvider _serviceProvider;
public GameLogReaderFactory(IServiceProvider serviceProvider)
{
_serviceProvider = serviceProvider;
}
public IGameLogReader CreateGameLogReader(Uri[] logUris, IEventParser eventParser)
{
var baseUri = logUris[0];
if (baseUri.Scheme == Uri.UriSchemeHttp)
{
return new GameLogReaderHttp(logUris, eventParser, _serviceProvider.GetRequiredService<ILogger>());
}
else if (baseUri.Scheme == Uri.UriSchemeFile)
{
return new GameLogReader(baseUri.LocalPath, eventParser, _serviceProvider.GetRequiredService<ILogger>());
}
throw new NotImplementedException($"No log reader implemented for Uri scheme \"{baseUri.Scheme}\"");
}
}
}

View File

@ -12,16 +12,18 @@ namespace IW4MAdmin.Application.Factories
{
private readonly ITranslationLookup _translationLookup;
private readonly IRConConnectionFactory _rconConnectionFactory;
private readonly IGameLogReaderFactory _gameLogReaderFactory;
/// <summary>
/// base constructor
/// </summary>
/// <param name="translationLookup"></param>
/// <param name="rconConnectionFactory"></param>
public GameServerInstanceFactory(ITranslationLookup translationLookup, IRConConnectionFactory rconConnectionFactory)
public GameServerInstanceFactory(ITranslationLookup translationLookup, IRConConnectionFactory rconConnectionFactory, IGameLogReaderFactory gameLogReaderFactory)
{
_translationLookup = translationLookup;
_rconConnectionFactory = rconConnectionFactory;
_gameLogReaderFactory = gameLogReaderFactory;
}
/// <summary>
@ -32,7 +34,7 @@ namespace IW4MAdmin.Application.Factories
/// <returns></returns>
public Server CreateServer(ServerConfiguration config, IManager manager)
{
return new IW4MServer(manager, config, _translationLookup, _rconConnectionFactory);
return new IW4MServer(manager, config, _translationLookup, _rconConnectionFactory, _gameLogReaderFactory);
}
}
}

View File

@ -1,19 +1,19 @@
using IW4MAdmin.Application.Misc;
using Newtonsoft.Json;
using SharedLibraryCore;
using SharedLibraryCore.Events;
using SharedLibraryCore.Interfaces;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
namespace IW4MAdmin.Application
{
class GameEventHandler : IEventHandler
public class GameEventHandler : IEventHandler
{
private const int MAX_CONCURRENT_EVENTS = 10;
private readonly ApplicationManager _manager;
private readonly SemaphoreSlim _processingEvents;
private readonly EventLog _eventLog;
private static readonly GameEvent.EventType[] overrideEvents = new[]
{
GameEvent.EventType.Connect,
@ -22,39 +22,12 @@ namespace IW4MAdmin.Application
GameEvent.EventType.Stop
};
public GameEventHandler(IManager mgr)
public GameEventHandler()
{
_manager = (ApplicationManager)mgr;
_processingEvents = new SemaphoreSlim(MAX_CONCURRENT_EVENTS, MAX_CONCURRENT_EVENTS);
_eventLog = new EventLog();
}
private Task GameEventHandler_GameEventAdded(object sender, GameEventArgs args)
{
try
{
// this is not elegant and there's probably a much better way to do it, but it works for now
_processingEvents.Wait();
EventApi.OnGameEvent(sender, args);
return _manager.ExecuteEvent(args.Event);
}
catch
{
}
finally
{
if (_processingEvents.CurrentCount < MAX_CONCURRENT_EVENTS)
{
_processingEvents.Release();
}
}
return Task.CompletedTask;
}
public void AddEvent(GameEvent gameEvent)
public void HandleEvent(IManager manager, GameEvent gameEvent)
{
#if DEBUG
ThreadPool.GetMaxThreads(out int workerThreads, out int n);
@ -62,12 +35,23 @@ namespace IW4MAdmin.Application
gameEvent.Owner.Logger.WriteDebug($"There are {workerThreads - availableThreads} active threading tasks");
#endif
if (_manager.Running || overrideEvents.Contains(gameEvent.Type))
if (manager.IsRunning || overrideEvents.Contains(gameEvent.Type))
{
#if DEBUG
gameEvent.Owner.Logger.WriteDebug($"Adding event with id {gameEvent.Id}");
#endif
Task.Run(() => GameEventHandler_GameEventAdded(this, new GameEventArgs(null, false, gameEvent)));
EventApi.OnGameEvent(gameEvent);
Task.Factory.StartNew(() => manager.ExecuteEvent(gameEvent));
/*if (!_eventLog.ContainsKey(gameEvent.Owner.EndPoint))
{
_eventLog.Add(gameEvent.Owner.EndPoint,new List<GameEvent>());
}
_eventLog[gameEvent.Owner.EndPoint].Add(gameEvent);
string serializedEvents = JsonConvert.SerializeObject(_eventLog, EventLog.BuildVcrSerializationSettings());
System.IO.File.WriteAllText("output.json", serializedEvents);*/
//Task.Run(() => GameEventHandler_GameEventAdded(this, new GameEventArgs(null, false, gameEvent)));
}
#if DEBUG
else

View File

@ -13,17 +13,9 @@ namespace IW4MAdmin.Application.IO
private readonly IGameLogReader _reader;
private readonly bool _ignoreBots;
class EventState
public GameLogEventDetection(Server server, Uri[] gameLogUris, IGameLogReaderFactory gameLogReaderFactory)
{
public ILogger Log { get; set; }
public string ServerId { get; set; }
}
public GameLogEventDetection(Server server, string gameLogPath, Uri gameLogServerUri, IGameLogReader reader = null)
{
_reader = gameLogServerUri != null
? reader ?? new GameLogReaderHttp(gameLogServerUri, gameLogPath, server.EventParser)
: reader ?? new GameLogReader(gameLogPath, server.EventParser);
_reader = gameLogReaderFactory.CreateGameLogReader(gameLogUris, server.EventParser);
_server = server;
_ignoreBots = server?.Manager.GetApplicationSettings().Configuration().IgnoreBots ?? false;
}
@ -70,7 +62,7 @@ namespace IW4MAdmin.Application.IO
return;
}
var events = await _reader.ReadEventsFromLog(_server, fileDiff, previousFileSize);
var events = await _reader.ReadEventsFromLog(fileDiff, previousFileSize);
foreach (var gameEvent in events)
{
@ -84,9 +76,9 @@ namespace IW4MAdmin.Application.IO
// we don't want to add the event if ignoreBots is on and the event comes from a bot
if (!_ignoreBots || (_ignoreBots && !((gameEvent.Origin?.IsBot ?? false) || (gameEvent.Target?.IsBot ?? false))))
{
if ((gameEvent.RequiredEntity & GameEvent.EventRequiredEntity.Origin) == GameEvent.EventRequiredEntity.Origin && gameEvent.Origin.NetworkId != 1)
if ((gameEvent.RequiredEntity & GameEvent.EventRequiredEntity.Origin) == GameEvent.EventRequiredEntity.Origin && gameEvent.Origin.NetworkId != Utilities.WORLD_ID)
{
gameEvent.Origin = _server.GetClientsAsList().First(_client => _client.NetworkId == gameEvent.Origin?.NetworkId);
gameEvent.Origin = _server.GetClientsAsList().First(_client => _client.NetworkId == gameEvent.Origin?.NetworkId);;
}
if ((gameEvent.RequiredEntity & GameEvent.EventRequiredEntity.Target) == GameEvent.EventRequiredEntity.Target)
@ -104,7 +96,7 @@ namespace IW4MAdmin.Application.IO
gameEvent.Target.CurrentServer = _server;
}
_server.Manager.GetEventHandler().AddEvent(gameEvent);
_server.Manager.AddEvent(gameEvent);
}
}

View File

@ -13,18 +13,20 @@ namespace IW4MAdmin.Application.IO
{
private readonly IEventParser _parser;
private readonly string _logFile;
private readonly ILogger _logger;
public long Length => new FileInfo(_logFile).Length;
public int UpdateInterval => 300;
public GameLogReader(string logFile, IEventParser parser)
public GameLogReader(string logFile, IEventParser parser, ILogger logger)
{
_logFile = logFile;
_parser = parser;
_logger = logger;
}
public async Task<IEnumerable<GameEvent>> ReadEventsFromLog(Server server, long fileSizeDiff, long startPosition)
public async Task<IEnumerable<GameEvent>> ReadEventsFromLog(long fileSizeDiff, long startPosition)
{
// allocate the bytes for the new log lines
List<string> logLines = new List<string>();
@ -34,7 +36,7 @@ namespace IW4MAdmin.Application.IO
{
byte[] buff = new byte[fileSizeDiff];
fs.Seek(startPosition, SeekOrigin.Begin);
await fs.ReadAsync(buff, 0, (int)fileSizeDiff, server.Manager.CancellationToken);
await fs.ReadAsync(buff, 0, (int)fileSizeDiff);
var stringBuilder = new StringBuilder();
char[] charBuff = Utilities.EncodingType.GetChars(buff);
@ -71,9 +73,9 @@ namespace IW4MAdmin.Application.IO
catch (Exception e)
{
server.Logger.WriteWarning("Could not properly parse event line");
server.Logger.WriteDebug(e.Message);
server.Logger.WriteDebug(eventLine);
_logger.WriteWarning("Could not properly parse event line");
_logger.WriteDebug(e.Message);
_logger.WriteDebug(eventLine);
}
}

View File

@ -5,43 +5,42 @@ using SharedLibraryCore.Interfaces;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Http;
using System.Threading.Tasks;
using static SharedLibraryCore.Utilities;
namespace IW4MAdmin.Application.IO
{
/// <summary>
/// provides capibility of reading log files over HTTP
/// provides capability of reading log files over HTTP
/// </summary>
class GameLogReaderHttp : IGameLogReader
{
private readonly IEventParser _eventParser;
private readonly IGameLogServer _logServerApi;
readonly string logPath;
private readonly ILogger _logger;
private readonly string _safeLogPath;
private string lastKey = "next";
public GameLogReaderHttp(Uri gameLogServerUri, string logPath, IEventParser parser)
public GameLogReaderHttp(Uri[] gameLogServerUris, IEventParser parser, ILogger logger)
{
this.logPath = logPath.ToBase64UrlSafeString();
_eventParser = parser;
_logServerApi = RestClient.For<IGameLogServer>(gameLogServerUri);
_logServerApi = RestClient.For<IGameLogServer>(gameLogServerUris[0].ToString());
_safeLogPath = gameLogServerUris[1].LocalPath.ToBase64UrlSafeString();
_logger = logger;
}
public long Length => -1;
public int UpdateInterval => 500;
public async Task<IEnumerable<GameEvent>> ReadEventsFromLog(Server server, long fileSizeDiff, long startPosition)
public async Task<IEnumerable<GameEvent>> ReadEventsFromLog(long fileSizeDiff, long startPosition)
{
var events = new List<GameEvent>();
string b64Path = logPath;
var response = await _logServerApi.Log(b64Path, lastKey);
var response = await _logServerApi.Log(_safeLogPath, lastKey);
lastKey = response.NextKey;
if (!response.Success && string.IsNullOrEmpty(lastKey))
{
server.Logger.WriteError($"Could not get log server info of {logPath}/{b64Path} ({server.LogPath})");
_logger.WriteError($"Could not get log server info of {_safeLogPath}");
return events;
}
@ -63,9 +62,9 @@ namespace IW4MAdmin.Application.IO
catch (Exception e)
{
server.Logger.WriteError("Could not properly parse event line from http");
server.Logger.WriteDebug(e.Message);
server.Logger.WriteDebug(eventLine);
_logger.WriteError("Could not properly parse event line from http");
_logger.WriteDebug(e.Message);
_logger.WriteDebug(eventLine);
}
}
}

View File

@ -13,6 +13,7 @@ using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading;
using System.Threading.Tasks;
@ -31,7 +32,7 @@ namespace IW4MAdmin
public int Id { get; private set; }
public IW4MServer(IManager mgr, ServerConfiguration cfg, ITranslationLookup lookup,
IRConConnectionFactory connectionFactory) : base(mgr, connectionFactory, cfg)
IRConConnectionFactory connectionFactory, IGameLogReaderFactory gameLogReaderFactory) : base(cfg, mgr, connectionFactory, gameLogReaderFactory)
{
_translationLookup = lookup;
}
@ -77,7 +78,7 @@ namespace IW4MAdmin
Type = GameEvent.EventType.Connect
};
Manager.GetEventHandler().AddEvent(e);
Manager.AddEvent(e);
return client;
}
@ -104,7 +105,7 @@ namespace IW4MAdmin
Type = GameEvent.EventType.Disconnect
};
Manager.GetEventHandler().AddEvent(e);
Manager.AddEvent(e);
#if DEBUG == true
}
#endif
@ -176,6 +177,8 @@ namespace IW4MAdmin
await command.ExecuteAsync(E);
}
var pluginTasks = Manager.Plugins.Where(_plugin => _plugin.Name != "Login").Select(async _plugin =>
{
try
@ -186,7 +189,11 @@ namespace IW4MAdmin
return;
}
await _plugin.OnEventAsync(E, this);
using (var tokenSource = new CancellationTokenSource())
{
tokenSource.CancelAfter(Utilities.DefaultCommandTimeout);
await (_plugin.OnEventAsync(E, this)).WithWaitCancellation(tokenSource.Token);
}
}
catch (Exception Except)
{
@ -195,7 +202,7 @@ namespace IW4MAdmin
}
});
Parallel.ForEach(pluginTasks, async (_task) => await _task);
await Task.WhenAny(pluginTasks);
}
catch (Exception e)
@ -314,7 +321,8 @@ namespace IW4MAdmin
// this happens for some reason rarely where the client spots get out of order
// possible a connect/reconnect game event before we get to process it here
// it appears that new games decide to switch client slots between maps (even if the clients aren't disconnecting)
else if (existingClient != null && existingClient.ClientNumber != E.Origin.ClientNumber)
// bots can have duplicate names which causes conflicting GUIDs
else if (existingClient != null && existingClient.ClientNumber != E.Origin.ClientNumber && !E.Origin.IsBot)
{
Logger.WriteWarning($"client {E.Origin} is trying to connect in client slot {E.Origin.ClientNumber}, but they are already registed in client slot {existingClient.ClientNumber}, swapping...");
// we need to remove them so the client spots can swap
@ -727,7 +735,7 @@ namespace IW4MAdmin
Origin = client
};
Manager.GetEventHandler().AddEvent(e);
Manager.AddEvent(e);
await e.WaitAsync(Utilities.DefaultCommandTimeout, new CancellationTokenRegistration().Token);
}
@ -771,10 +779,11 @@ namespace IW4MAdmin
{
Type = GameEvent.EventType.PreDisconnect,
Origin = disconnectingClient,
Owner = this
Owner = this,
Source = GameEvent.EventSource.Status
};
Manager.GetEventHandler().AddEvent(e);
Manager.AddEvent(e);
await e.WaitAsync(Utilities.DefaultCommandTimeout, Manager.CancellationToken);
}
@ -793,10 +802,11 @@ namespace IW4MAdmin
Type = GameEvent.EventType.PreConnect,
Origin = client,
Owner = this,
IsBlocking = true
IsBlocking = true,
Source = GameEvent.EventSource.Status
};
Manager.GetEventHandler().AddEvent(e);
Manager.AddEvent(e);
await e.WaitAsync(Utilities.DefaultCommandTimeout, Manager.CancellationToken);
}
@ -811,7 +821,7 @@ namespace IW4MAdmin
Owner = this
};
Manager.GetEventHandler().AddEvent(e);
Manager.AddEvent(e);
}
if (ConnectionErrors > 0)
@ -824,7 +834,7 @@ namespace IW4MAdmin
Target = Utilities.IW4MAdminClient(this)
};
Manager.GetEventHandler().AddEvent(_event);
Manager.AddEvent(_event);
}
ConnectionErrors = 0;
@ -846,7 +856,7 @@ namespace IW4MAdmin
Data = ConnectionErrors.ToString()
};
Manager.GetEventHandler().AddEvent(_event);
Manager.AddEvent(_event);
}
return true;
}
@ -1071,7 +1081,7 @@ namespace IW4MAdmin
}
}
LogEvent = new GameLogEventDetection(this, LogPath, ServerConfig.GameLogServerUrl);
LogEvent = new GameLogEventDetection(this, GenerateUriForLog(LogPath, ServerConfig.GameLogServerUrl?.AbsoluteUri), gameLogReaderFactory);
Logger.WriteInfo($"Log file is {LogPath}");
_ = Task.Run(() => LogEvent.PollForChanges());
@ -1080,6 +1090,21 @@ namespace IW4MAdmin
#endif
}
public Uri[] GenerateUriForLog(string logPath, string gameLogServerUrl)
{
var logUri = new Uri(logPath);
if (string.IsNullOrEmpty(gameLogServerUrl))
{
return new[] { logUri };
}
else
{
return new[] { new Uri(gameLogServerUrl), logUri };
}
}
public static string GenerateLogPath(LogPathGeneratorInfo logInfo)
{
string logPath;
@ -1179,7 +1204,7 @@ namespace IW4MAdmin
Owner = this
};
Manager.GetEventHandler().AddEvent(e);
Manager.AddEvent(e);
string formattedKick = string.Format(RconParser.Configuration.CommandPrefixes.Kick, targetClient.ClientNumber, $"{loc["SERVER_KICK_TEXT"]} - ^5{Reason}^7");
await targetClient.CurrentServer.ExecuteCommandAsync(formattedKick);

View File

@ -30,7 +30,7 @@ namespace IW4MAdmin.Application
/// entrypoint of the application
/// </summary>
/// <returns></returns>
public static async Task Main()
public static async Task Main(string[] args)
{
AppDomain.CurrentDomain.SetData("DataDirectory", Utilities.OperatingDirectory);
@ -45,7 +45,7 @@ namespace IW4MAdmin.Application
Console.WriteLine($" Version {Utilities.GetVersionAsString()}");
Console.WriteLine("=====================================================");
await LaunchAsync();
await LaunchAsync(args);
}
/// <summary>
@ -64,7 +64,7 @@ namespace IW4MAdmin.Application
/// task that initializes application and starts the application monitoring and runtime tasks
/// </summary>
/// <returns></returns>
private static async Task LaunchAsync()
private static async Task LaunchAsync(string[] args)
{
restart:
ITranslationLookup translationLookup = null;
@ -74,7 +74,7 @@ namespace IW4MAdmin.Application
ConfigurationMigration.MoveConfigFolder10518(null);
ConfigurationMigration.CheckDirectories();
var services = ConfigureServices();
var services = ConfigureServices(args);
serviceProvider = services.BuildServiceProvider();
ServerManager = (ApplicationManager)serviceProvider.GetRequiredService<IManager>();
translationLookup = serviceProvider.GetRequiredService<ITranslationLookup>();
@ -252,7 +252,7 @@ namespace IW4MAdmin.Application
Owner = ServerManager.Servers[0]
};
ServerManager.GetEventHandler().AddEvent(E);
ServerManager.AddEvent(E);
await E.WaitAsync(Utilities.DefaultCommandTimeout, ServerManager.CancellationToken);
Console.Write('>');
}
@ -266,7 +266,7 @@ namespace IW4MAdmin.Application
/// <summary>
/// Configures the dependency injection services
/// </summary>
private static IServiceCollection ConfigureServices()
private static IServiceCollection ConfigureServices(string[] args)
{
var defaultLogger = new Logger("IW4MAdmin-Manager");
var pluginImporter = new PluginImporter(defaultLogger);
@ -285,6 +285,7 @@ namespace IW4MAdmin.Application
.AddSingleton<IConfigurationHandlerFactory, ConfigurationHandlerFactory>()
.AddSingleton<IParserRegexFactory, ParserRegexFactory>()
.AddSingleton<IDatabaseContextFactory, DatabaseContextFactory>()
.AddSingleton<IGameLogReaderFactory, GameLogReaderFactory>()
.AddSingleton<IAuditInformationRepository, AuditInformationRepository>()
.AddTransient<IParserPatternMatcher, ParserPatternMatcher>()
.AddSingleton(_serviceProvider =>
@ -295,6 +296,15 @@ namespace IW4MAdmin.Application
})
.AddSingleton<IManager, ApplicationManager>();
if (args.Contains("serialevents"))
{
serviceCollection.AddSingleton<IEventHandler, SerialGameEventHandler>();
}
else
{
serviceCollection.AddSingleton<IEventHandler, GameEventHandler>();
}
// register the native commands
foreach (var commandType in typeof(SharedLibraryCore.Commands.QuitCommand).Assembly.GetTypes()
.Where(_command => _command.BaseType == typeof(Command)))

View File

@ -0,0 +1,27 @@
using Newtonsoft.Json;
using SharedLibraryCore;
using System;
using System.Collections.Generic;
using System.Text;
namespace IW4MAdmin.Application.Misc
{
public class EventLog : Dictionary<long, IList<GameEvent>>
{
private static JsonSerializerSettings serializationSettings;
public static JsonSerializerSettings BuildVcrSerializationSettings()
{
if (serializationSettings == null)
{
serializationSettings = new JsonSerializerSettings() { Formatting = Formatting.Indented, ReferenceLoopHandling = ReferenceLoopHandling.Ignore };
serializationSettings.Converters.Add(new IPAddressConverter());
serializationSettings.Converters.Add(new IPEndPointConverter());
serializationSettings.Converters.Add(new GameEventConverter());
serializationSettings.Converters.Add(new ClientEntityConverter());
}
return serializationSettings;
}
}
}

View File

@ -0,0 +1,149 @@
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using SharedLibraryCore;
using SharedLibraryCore.Database.Models;
using System;
using System.Net;
using static SharedLibraryCore.Database.Models.EFClient;
using static SharedLibraryCore.GameEvent;
namespace IW4MAdmin.Application.Misc
{
class IPAddressConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return (objectType == typeof(IPAddress));
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
writer.WriteValue(value.ToString());
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
return IPAddress.Parse((string)reader.Value);
}
}
class IPEndPointConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return (objectType == typeof(IPEndPoint));
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
IPEndPoint ep = (IPEndPoint)value;
JObject jo = new JObject();
jo.Add("Address", JToken.FromObject(ep.Address, serializer));
jo.Add("Port", ep.Port);
jo.WriteTo(writer);
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
JObject jo = JObject.Load(reader);
IPAddress address = jo["Address"].ToObject<IPAddress>(serializer);
int port = (int)jo["Port"];
return new IPEndPoint(address, port);
}
}
class ClientEntityConverter : JsonConverter
{
public override bool CanConvert(Type objectType) => objectType == typeof(EFClient);
public override object ReadJson(JsonReader reader, Type objectType,object existingValue, JsonSerializer serializer)
{
if (reader.Value == null)
{
return null;
}
var jsonObject = JObject.Load(reader);
return new EFClient
{
NetworkId = (long)jsonObject["NetworkId"],
ClientNumber = (int)jsonObject["ClientNumber"],
State = Enum.Parse<ClientState>(jsonObject["state"].ToString()),
CurrentAlias = new EFAlias()
{
IPAddress = (int?)jsonObject["IPAddress"],
Name = jsonObject["Name"].ToString()
}
};
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
var client = value as EFClient;
var jsonObject = new JObject
{
{ "NetworkId", client.NetworkId },
{ "ClientNumber", client.ClientNumber },
{ "IPAddress", client.CurrentAlias?.IPAddress },
{ "Name", client.CurrentAlias?.Name },
{ "State", (int)client.State }
};
jsonObject.WriteTo(writer);
}
}
class GameEventConverter : JsonConverter
{
public override bool CanConvert(Type objectType) =>objectType == typeof(GameEvent);
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
var jsonObject = JObject.Load(reader);
return new GameEvent
{
Type = Enum.Parse<EventType>(jsonObject["Type"].ToString()),
Subtype = jsonObject["Subtype"]?.ToString(),
Source = Enum.Parse<EventSource>(jsonObject["Source"].ToString()),
RequiredEntity = Enum.Parse<EventRequiredEntity>(jsonObject["RequiredEntity"].ToString()),
Data = jsonObject["Data"].ToString(),
Message = jsonObject["Message"].ToString(),
GameTime = (int?)jsonObject["GameTime"],
Origin = jsonObject["Origin"]?.ToObject<EFClient>(serializer),
Target = jsonObject["Target"]?.ToObject<EFClient>(serializer),
ImpersonationOrigin = jsonObject["ImpersonationOrigin"]?.ToObject<EFClient>(serializer),
IsRemote = (bool)jsonObject["IsRemote"],
Extra = null, // fix
Time = (DateTime)jsonObject["Time"],
IsBlocking = (bool)jsonObject["IsBlocking"]
};
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
var gameEvent = value as GameEvent;
var jsonObject = new JObject
{
{ "Type", (int)gameEvent.Type },
{ "Subtype", gameEvent.Subtype },
{ "Source", (int)gameEvent.Source },
{ "RequiredEntity", (int)gameEvent.RequiredEntity },
{ "Data", gameEvent.Data },
{ "Message", gameEvent.Message },
{ "GameTime", gameEvent.GameTime },
{ "Origin", gameEvent.Origin != null ? JToken.FromObject(gameEvent.Origin, serializer) : null },
{ "Target", gameEvent.Target != null ? JToken.FromObject(gameEvent.Target, serializer) : null },
{ "ImpersonationOrigin", gameEvent.ImpersonationOrigin != null ? JToken.FromObject(gameEvent.ImpersonationOrigin, serializer) : null},
{ "IsRemote", gameEvent.IsRemote },
{ "Extra", gameEvent.Extra?.ToString() },
{ "Time", gameEvent.Time },
{ "IsBlocking", gameEvent.IsBlocking }
};
jsonObject.WriteTo(writer);
}
}
}

View File

@ -9,6 +9,7 @@ using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
namespace IW4MAdmin.Application.RCon
@ -193,7 +194,8 @@ namespace IW4MAdmin.Application.RCon
throw new ServerException(Utilities.CurrentLocalization.LocalizationIndex["SERVER_ERROR_NOT_RUNNING"].FormatExt(Endpoint.ToString()));
}
string[] headerSplit = responseString.Split(type == StaticHelpers.QueryType.GET_INFO ? config.CommandPrefixes.RconGetInfoResponseHeader : config.CommandPrefixes.RConResponse);
string responseHeaderMatch = Regex.Match(responseString, config.CommandPrefixes.RConResponse).Value;
string[] headerSplit = responseString.Split(type == StaticHelpers.QueryType.GET_INFO ? config.CommandPrefixes.RconGetInfoResponseHeader : responseHeaderMatch);
if (headerSplit.Length != 2)
{

View File

@ -179,9 +179,15 @@ namespace IW4MAdmin.Application.RconParsers
}
long networkId;
string name = match.Values[Configuration.Status.GroupMapping[ParserRegex.GroupType.RConName]].TrimNewLine();
try
{
networkId = match.Values[Configuration.Status.GroupMapping[ParserRegex.GroupType.RConNetworkId]].ConvertGuidToLong(Configuration.GuidNumberStyle);
string networkIdString = match.Values[Configuration.Status.GroupMapping[ParserRegex.GroupType.RConNetworkId]];
networkId = networkIdString.IsBotGuid() ?
name.GenerateGuidFromString() :
networkIdString.ConvertGuidToLong(Configuration.GuidNumberStyle);
}
catch (FormatException)
@ -189,7 +195,6 @@ namespace IW4MAdmin.Application.RconParsers
continue;
}
string name = match.Values[Configuration.Status.GroupMapping[ParserRegex.GroupType.RConName]].TrimNewLine();
int? ip = match.Values[Configuration.Status.GroupMapping[ParserRegex.GroupType.RConIpAddress]].Split(':')[0].ConvertToIP();
var client = new EFClient()
@ -206,13 +211,13 @@ namespace IW4MAdmin.Application.RconParsers
State = EFClient.ClientState.Connecting
};
#if DEBUG
if (client.NetworkId < 1000 && client.NetworkId > 0)
{
client.IPAddress = 2147483646;
client.Ping = 0;
}
#endif
//#if DEBUG
// if (client.NetworkId < 1000 && client.NetworkId > 0)
// {
// client.IPAddress = 2147483646;
// client.Ping = 0;
// }
//#endif
StatusPlayers.Add(client);
}

View File

@ -0,0 +1,41 @@
using SharedLibraryCore;
using SharedLibraryCore.Events;
using SharedLibraryCore.Interfaces;
using System;
using System.Linq;
namespace IW4MAdmin.Application
{
class SerialGameEventHandler : IEventHandler
{
private delegate void GameEventAddedEventHandler(object sender, GameEventArgs args);
private event GameEventAddedEventHandler GameEventAdded;
private static readonly GameEvent.EventType[] overrideEvents = new[]
{
GameEvent.EventType.Connect,
GameEvent.EventType.Disconnect,
GameEvent.EventType.Quit,
GameEvent.EventType.Stop
};
public SerialGameEventHandler()
{
GameEventAdded += GameEventHandler_GameEventAdded;
}
private async void GameEventHandler_GameEventAdded(object sender, GameEventArgs args)
{
await (sender as IManager).ExecuteEvent(args.Event);
EventApi.OnGameEvent(args.Event);
}
public void HandleEvent(IManager manager, GameEvent gameEvent)
{
if (manager.IsRunning || overrideEvents.Contains(gameEvent.Type))
{
GameEventAdded?.Invoke(manager, new GameEventArgs(null, false, gameEvent));
}
}
}
}

View File

@ -3,7 +3,7 @@ var eventParser;
var plugin = {
author: 'RaidMax',
version: 0.2,
version: 0.3,
name: 'Plutonium IW5 Parser',
isParser: true,
@ -28,7 +28,7 @@ var plugin = {
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) +(.+?) +(?:[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(101, 2);
rconParser.Configuration.Status.AddMapping(102, 3);

View File

@ -3,7 +3,7 @@ var eventParser;
var plugin = {
author: 'RaidMax, Xerxes',
version: 0.7,
version: 0.8,
name: 'Plutonium T6 Parser',
isParser: true,
@ -27,7 +27,7 @@ var plugin = {
rconParser.Configuration.WaitForResponse = false;
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) +(.+?) +(?:[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(101, 2);
rconParser.Configuration.Status.AddMapping(102, 3);

View File

@ -3,7 +3,7 @@ var eventParser;
var plugin = {
author: 'RaidMax',
version: 0.3,
version: 0.4,
name: 'Tekno MW3 Parser',
isParser: true,
@ -14,11 +14,11 @@ var plugin = {
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]){16,32})\t +(.{0,16}) +([0-9]+) +(\\d+\\.\\d+\\.\\d+\\.\\d+\\:-?\\d{1,5}|0+\\.0+\\:-?\\d{1,5}|loopback) *$';
rconParser.Configuration.Status.Pattern = '^ *([0-9]+) +([0-9]+) +((?:[A-Z]+|[0-9]+)) +((?:[A-Z]|[0-9]){16,32}|0)\t +(.{0,16}) +([0-9]+) +(\\d+\\.\\d+\\.\\d+\\.\\d+\\:-?\\d{1,5}|0+\\.0+\\:-?\\d{1,5}|loopback) *$';
rconParser.Configuration.Status.AddMapping(104, 5); // RConName
rconParser.Configuration.Status.AddMapping(103, 4); // RConNetworkId
rconParser.Configuration.CommandPrefixes.RConGetInfo = undefined;
rconParser.Configuration.CommandPrefixes.RConResponse = '\xff\xff\xff\xff';
rconParser.Configuration.CommandPrefixes.RConResponse = '\xff\xff\xff\xff(print)?';
rconParser.Configuration.CommandPrefixes.Tell = 'tell {0} {1}';
rconParser.Configuration.CommandPrefixes.Say = 'say {0}';
rconParser.Configuration.CommandPrefixes.Kick = 'dropclient {0} "{1}"';

View File

@ -24,6 +24,12 @@ namespace IW4MAdmin.Plugins.Stats.Events
return (EVENT_SCRIPTKILL, EVENT_SCRIPTKILL, (string eventLine, IEventParserConfiguration config, GameEvent autoEvent) =>
{
string[] lineSplit = eventLine.Split(";");
if (lineSplit[1].IsBotGuid() || lineSplit[2].IsBotGuid())
{
return autoEvent;
}
long originId = lineSplit[1].ConvertGuidToLong(config.GuidNumberStyle, 1);
long targetId = lineSplit[2].ConvertGuidToLong(config.GuidNumberStyle, 1);
@ -48,6 +54,12 @@ namespace IW4MAdmin.Plugins.Stats.Events
return (EVENT_SCRIPTDAMAGE, EVENT_SCRIPTDAMAGE, (string eventLine, IEventParserConfiguration config, GameEvent autoEvent) =>
{
string[] lineSplit = eventLine.Split(";");
if (lineSplit[1].IsBotGuid() || lineSplit[2].IsBotGuid())
{
return autoEvent;
}
long originId = lineSplit[1].ConvertGuidToLong(config.GuidNumberStyle, 1);
long targetId = lineSplit[2].ConvertGuidToLong(config.GuidNumberStyle, 1);
@ -71,6 +83,12 @@ namespace IW4MAdmin.Plugins.Stats.Events
return (EVENT_JOINTEAM, EVENT_JOINTEAM, (string eventLine, IEventParserConfiguration config, GameEvent autoEvent) =>
{
string[] lineSplit = eventLine.Split(";");
if (lineSplit[1].IsBotGuid() || lineSplit[2].IsBotGuid())
{
return autoEvent;
}
long originId = lineSplit[1].ConvertGuidToLong(config.GuidNumberStyle, 1);
long targetId = lineSplit[2].ConvertGuidToLong(config.GuidNumberStyle, 1);

View File

@ -530,7 +530,7 @@ namespace IW4MAdmin.Plugins.Stats
/// </summary>
/// <param name="origin"></param>
/// <returns></returns>
private bool IsWorldDamage(EFClient origin) => origin?.NetworkId == 1;
private bool IsWorldDamage(EFClient origin) => origin?.NetworkId == Utilities.WORLD_ID || origin?.ClientId == Utilities.WORLD_ID;
/// <summary>
/// Indicates if we should try to use anticheat even if sv_customcallbacks is not defined

View File

@ -16,7 +16,7 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="RaidMax.IW4MAdmin.SharedLibraryCore" Version="2.2.11" PrivateAssets="All" />
<PackageReference Include="RaidMax.IW4MAdmin.SharedLibraryCore" Version="2.2.12" PrivateAssets="All" />
</ItemGroup>
<Target Name="PostBuild" AfterTargets="PostBuildEvent">

View File

@ -72,7 +72,7 @@ namespace Tests
}
};
_manager.GetEventHandler().AddEvent(e);
_manager.AddEvent(e);
e.Complete();
e = new GameEvent()
@ -91,7 +91,7 @@ namespace Tests
}
};
_manager.GetEventHandler().AddEvent(e);
_manager.AddEvent(e);
e.Complete();
e = new GameEvent()
@ -110,7 +110,7 @@ namespace Tests
}
};
_manager.GetEventHandler().AddEvent(e);
_manager.AddEvent(e);
e.Complete();
}

View File

@ -30,7 +30,7 @@ namespace Tests
Owner = Manager.GetServers()[0]
};
Manager.GetEventHandler().AddEvent(e);
Manager.AddEvent(e);
e.Complete();
var client = Manager.GetServers()[0].Clients[0];
@ -43,7 +43,7 @@ namespace Tests
Owner = e.Owner
};
Manager.GetEventHandler().AddEvent(e);
Manager.AddEvent(e);
e.Complete();
Assert.True(client.Warnings == 1, "client wasn't warned for objectional language");

View File

@ -49,7 +49,7 @@ namespace SharedLibraryCore.Commands
Data = cmd,
Owner = E.Owner
};
E.Owner.Manager.GetEventHandler().AddEvent(impersonatedCommandEvent);
E.Owner.Manager.AddEvent(impersonatedCommandEvent);
var result = await impersonatedCommandEvent.WaitAsync(Utilities.DefaultCommandTimeout, E.Owner.Manager.CancellationToken);
var response = E.Owner.CommandResult.Where(c => c.ClientId == E.Target.ClientId).ToList();

View File

@ -23,9 +23,9 @@ namespace SharedLibraryCore.Events
return eventList;
}
public static void OnGameEvent(object sender, GameEventArgs eventState)
public static void OnGameEvent(GameEvent gameEvent)
{
var E = eventState.Event;
var E = gameEvent;
// don't want to clog up the api with unknown events
if (E.Type == GameEvent.EventType.Unknown)
return;

View File

@ -194,6 +194,14 @@ namespace SharedLibraryCore
Target = 4
}
public enum EventSource
{
Unspecified,
Log,
Status,
Internal
}
static long NextEventId;
static long GetNextEventId()
{
@ -214,6 +222,7 @@ namespace SharedLibraryCore
}
public EventType Type;
public EventSource Source { get; set; }
/// <summary>
/// suptype of the event for more detailed classification
/// </summary>
@ -229,7 +238,7 @@ namespace SharedLibraryCore
public EFClient Target;
public EFClient ImpersonationOrigin { get; set; }
public Server Owner;
public bool IsRemote { get; set; } = false;
public bool IsRemote { get; set; }
public object Extra { get; set; }
private readonly ManualResetEvent _eventFinishedWaiter;
public DateTime Time { get; set; }

View File

@ -1,18 +1,15 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace SharedLibraryCore.Interfaces
namespace SharedLibraryCore.Interfaces
{
/// <summary>
/// This class handle games events (from log, manual events, etc)
/// handles games events (from log, manual events, etc)
/// </summary>
public interface IEventHandler
{
/// <summary>
/// Add a game event event to the queue to be processed
/// </summary>
/// <param name="gameEvent">Game event</param>
void AddEvent(GameEvent gameEvent);
/// <param name="manager">application manager instance</param>
/// <param name="gameEvent">game event</param>
void HandleEvent(IManager manager, GameEvent gameEvent);
}
}

View File

@ -4,18 +4,17 @@ using System.Threading.Tasks;
namespace SharedLibraryCore.Interfaces
{
/// <summary>
/// represents the abtraction of game log reading
/// represents the abstraction of game log reading
/// </summary>
public interface IGameLogReader
{
/// <summary>
/// get new events that have occured since the last poll
/// </summary>
/// <param name="server"></param>
/// <param name="fileSizeDiff"></param>
/// <param name="startPosition"></param>
/// <returns></returns>
Task<IEnumerable<GameEvent>> ReadEventsFromLog(Server server, long fileSizeDiff, long startPosition);
Task<IEnumerable<GameEvent>> ReadEventsFromLog(long fileSizeDiff, long startPosition);
/// <summary>
/// how long the log file is

View File

@ -0,0 +1,18 @@
using System;
namespace SharedLibraryCore.Interfaces
{
/// <summary>
/// factory interface to create game log readers based on the log file uri
/// </summary>
public interface IGameLogReaderFactory
{
/// <summary>
/// generates a new game log reader based on the provided Uri
/// </summary>
/// <param name="logUris">collection of log uri used to generate the log reader</param>
/// <param name="eventParser">event parser for the log reader</param>
/// <returns></returns>
IGameLogReader CreateGameLogReader(Uri[] logUris, IEventParser eventParser);
}
}

View File

@ -24,11 +24,6 @@ namespace SharedLibraryCore.Interfaces
AliasService GetAliasService();
PenaltyService GetPenaltyService();
/// <summary>
/// Get the event handlers
/// </summary>
/// <returns>EventHandler for the manager</returns>
IEventHandler GetEventHandler();
/// <summary>
/// enumerates the registered plugin instances
/// </summary>
IEnumerable<IPlugin> Plugins { get; }
@ -68,7 +63,12 @@ namespace SharedLibraryCore.Interfaces
string ExternalIPAddress { get; }
CancellationToken CancellationToken { get; }
bool IsRestartRequested { get; }
//OnServerEventEventHandler OnServerEvent { get; set; }
bool IsRunning { get; }
Task ExecuteEvent(GameEvent gameEvent);
/// <summary>
/// queues an event for processing
/// </summary>
/// <param name="gameEvent">event to be processed</param>
void AddEvent(GameEvent gameEvent);
}
}

View File

@ -142,7 +142,7 @@ namespace SharedLibraryCore.Database.Models
Data = message
};
CurrentServer?.Manager.GetEventHandler().AddEvent(e);
CurrentServer?.Manager.AddEvent(e);
return e;
}
@ -174,7 +174,7 @@ namespace SharedLibraryCore.Database.Models
Warnings++;
}
sender.CurrentServer.Manager.GetEventHandler().AddEvent(e);
sender.CurrentServer.Manager.AddEvent(e);
return e;
}
@ -202,7 +202,7 @@ namespace SharedLibraryCore.Database.Models
Warnings = 0;
sender.CurrentServer.Manager.GetEventHandler().AddEvent(e);
sender.CurrentServer.Manager.AddEvent(e);
return e;
}
@ -243,7 +243,7 @@ namespace SharedLibraryCore.Database.Models
}
sender.SetAdditionalProperty("_reportCount", reportCount + 1);
sender.CurrentServer.Manager.GetEventHandler().AddEvent(e);
sender.CurrentServer.Manager.AddEvent(e);
return e;
}
@ -276,7 +276,7 @@ namespace SharedLibraryCore.Database.Models
e.FailReason = GameEvent.EventFailReason.Invalid;
}
sender.CurrentServer.Manager.GetEventHandler().AddEvent(e);
sender.CurrentServer.Manager.AddEvent(e);
return e;
}
@ -308,7 +308,7 @@ namespace SharedLibraryCore.Database.Models
e.FailReason = GameEvent.EventFailReason.Invalid;
}
sender.CurrentServer.Manager.GetEventHandler().AddEvent(e);
sender.CurrentServer.Manager.AddEvent(e);
return e;
}
@ -336,7 +336,7 @@ namespace SharedLibraryCore.Database.Models
}
State = ClientState.Disconnecting;
sender.CurrentServer.Manager.GetEventHandler().AddEvent(e);
sender.CurrentServer.Manager.AddEvent(e);
return e;
}
@ -366,7 +366,7 @@ namespace SharedLibraryCore.Database.Models
}
State = ClientState.Disconnecting;
sender.CurrentServer.Manager.GetEventHandler().AddEvent(e);
sender.CurrentServer.Manager.AddEvent(e);
return e;
}
@ -400,7 +400,7 @@ namespace SharedLibraryCore.Database.Models
}
State = ClientState.Disconnecting;
sender.CurrentServer.Manager.GetEventHandler().AddEvent(e);
sender.CurrentServer.Manager.AddEvent(e);
return e;
}
@ -428,7 +428,7 @@ namespace SharedLibraryCore.Database.Models
e.FailReason = GameEvent.EventFailReason.Permission;
}
sender.CurrentServer.Manager.GetEventHandler().AddEvent(e);
sender.CurrentServer.Manager.AddEvent(e);
return e;
}
@ -464,7 +464,7 @@ namespace SharedLibraryCore.Database.Models
Level = newPermission;
}
sender.CurrentServer.Manager.GetEventHandler().AddEvent(e);
sender.CurrentServer.Manager.AddEvent(e);
return e;
}
@ -565,7 +565,7 @@ namespace SharedLibraryCore.Database.Models
Owner = CurrentServer,
};
CurrentServer.Manager.GetEventHandler().AddEvent(e);
CurrentServer.Manager.AddEvent(e);
}
}
@ -655,7 +655,7 @@ namespace SharedLibraryCore.Database.Models
[NotMapped]
public int Score { get; set; }
[NotMapped]
public bool IsBot => NetworkId == -1;
public bool IsBot => NetworkId == Name.GenerateGuidFromString();
[NotMapped]
public ClientState State { get; set; }
@ -694,7 +694,7 @@ namespace SharedLibraryCore.Database.Models
public override bool Equals(object obj)
{
return ((EFClient)obj).NetworkId == this.NetworkId;
return obj.GetType() == typeof(EFClient) && ((EFClient)obj).NetworkId == this.NetworkId;
}
public override int GetHashCode()

View File

@ -28,7 +28,7 @@ namespace SharedLibraryCore
T7 = 8
}
public Server(IManager mgr, IRConConnectionFactory rconConnectionFactory, ServerConfiguration config)
public Server(ServerConfiguration config, IManager mgr, IRConConnectionFactory rconConnectionFactory, IGameLogReaderFactory gameLogReaderFactory)
{
Password = config.Password;
IP = config.IPAddress;
@ -46,6 +46,7 @@ namespace SharedLibraryCore
NextMessage = 0;
CustomSayEnabled = Manager.GetApplicationSettings().Configuration().EnableCustomSayName;
CustomSayName = Manager.GetApplicationSettings().Configuration().CustomSayName;
this.gameLogReaderFactory = gameLogReaderFactory;
InitializeTokens();
InitializeAutoMessages();
}
@ -134,7 +135,7 @@ namespace SharedLibraryCore
Origin = sender,
};
Manager.GetEventHandler().AddEvent(e);
Manager.AddEvent(e);
return e;
}
@ -296,7 +297,7 @@ namespace SharedLibraryCore
{
get
{
return Clients.Where(p => p != null && !p.IsBot).Count();
return Clients.Where(p => p != null/* && !p.IsBot*/).Count();
}
}
public int MaxClients { get; protected set; }
@ -325,6 +326,7 @@ namespace SharedLibraryCore
protected TimeSpan LastMessage;
protected DateTime LastPoll;
protected ManualResetEventSlim OnRemoteCommandResponse;
protected IGameLogReaderFactory gameLogReaderFactory;
// only here for performance
private readonly bool CustomSayEnabled;

View File

@ -6,7 +6,7 @@
<ApplicationIcon />
<StartupObject />
<PackageId>RaidMax.IW4MAdmin.SharedLibraryCore</PackageId>
<Version>2.2.11</Version>
<Version>2.2.12</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.11.0</AssemblyVersion>
<FileVersion>2.2.11.0</FileVersion>
<AssemblyVersion>2.2.12.0</AssemblyVersion>
<FileVersion>2.2.12.0</FileVersion>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Prerelease|AnyCPU'">

View File

@ -16,6 +16,7 @@ using System.Net;
using System.Reflection;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading;
using System.Threading.Tasks;
using static SharedLibraryCore.Database.Models.EFClient;
using static SharedLibraryCore.Database.Models.EFPenalty;
@ -50,6 +51,10 @@ namespace SharedLibraryCore
AdministeredPenalties = new List<EFPenalty>()
};
}
/// <summary>
/// fallback id for world events
/// </summary>
public const long WORLD_ID = -1;
public static string HttpRequest(string location, string header, string headerValue)
{
@ -295,39 +300,46 @@ namespace SharedLibraryCore
}
}
/// <summary>
/// converts a string to numerical guid
/// </summary>
/// <param name="str">source string for guid</param>
/// <param name="numberStyle">how to parse the guid</param>
/// <param name="fallback">value to use if string is empty</param>
/// <returns></returns>
public static long ConvertGuidToLong(this string str, NumberStyles numberStyle, long? fallback = null)
{
str = str.Substring(0, Math.Min(str.Length, 19));
var bot = Regex.Match(str, @"bot[0-9]+").Value;
var parsableAsNumber = Regex.Match(str, @"([A-F]|[a-f]|[0-9])+").Value;
if (string.IsNullOrWhiteSpace(str) && fallback.HasValue)
{
return fallback.Value;
}
long id = 0;
if (numberStyle == NumberStyles.Integer)
long id;
if (!string.IsNullOrEmpty(parsableAsNumber))
{
long.TryParse(str, numberStyle, CultureInfo.InvariantCulture, out id);
if (id < 0)
if (numberStyle == NumberStyles.Integer)
{
id = (uint)id;
long.TryParse(str, numberStyle, CultureInfo.InvariantCulture, out id);
if (id < 0)
{
id = (uint)id;
}
}
else
{
long.TryParse(str.Length > 16 ? str.Substring(0, 16) : str, numberStyle, CultureInfo.InvariantCulture, out id);
}
}
else
{
long.TryParse(str.Length > 16 ? str.Substring(0, 16) : str, numberStyle, CultureInfo.InvariantCulture, out id);
}
if (!string.IsNullOrEmpty(bot))
{
id = -1;
#if DEBUG
id = str.Sum(_c => _c);
#endif
// this is a special case for when a real guid is not provided, so we generated it from another source
id = str.GenerateGuidFromString();
}
if (id == 0)
@ -338,6 +350,23 @@ namespace SharedLibraryCore
return id;
}
/// <summary>
/// determines if the guid provided appears to be a bot guid
/// </summary>
/// <param name="guid">value of the guid</param>
/// <returns>true if is bot guid, otherwise false</returns>
public static bool IsBotGuid(this string guid)
{
return guid.Contains("bot") || guid == "0";
}
/// <summary>
/// generates a numerical hashcode from a string value
/// </summary>
/// <param name="value">value string</param>
/// <returns></returns>
public static long GenerateGuidFromString(this string value) => string.IsNullOrEmpty(value) ? -1 : HashCode.Combine(value.StripColors());
public static int? ConvertToIP(this string str)
{
bool success = IPAddress.TryParse(str, out IPAddress ip);
@ -900,6 +929,24 @@ namespace SharedLibraryCore
return false;
}
/// <summary>
/// https://www.planetgeek.ch/2016/12/08/async-method-without-cancellation-support-do-it-my-way/
/// </summary>
public static async Task WithWaitCancellation(this Task task,
CancellationToken cancellationToken)
{
Task completedTask = await Task.WhenAny(task, Task.Delay(Timeout.Infinite, cancellationToken));
if (completedTask == task)
{
await task;
}
else
{
cancellationToken.ThrowIfCancellationRequested();
throw new InvalidOperationException("Infinite delay task completed.");
}
}
public static bool ShouldHideLevel(this Permission perm) => perm == Permission.Flagged;
/// <summary>

View File

@ -23,6 +23,9 @@
<None Update="Files\GameEvents.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Update="Files\replay.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Update="Files\T6Game.log">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>

View File

@ -37,12 +37,10 @@ namespace ApplicationTests
cmdConfig = new CommandConfiguration();
serviceProvider = new ServiceCollection()
.BuildBase()
.BuildBase(new MockEventHandler(true))
.BuildServiceProvider();
mockEventHandler = new MockEventHandler(true);
A.CallTo(() => serviceProvider.GetRequiredService<IManager>().GetEventHandler())
.Returns(mockEventHandler);
mockEventHandler = serviceProvider.GetRequiredService<MockEventHandler>();
var mgr = serviceProvider.GetRequiredService<IManager>();
transLookup = serviceProvider.GetRequiredService<ITranslationLookup>();
@ -54,7 +52,9 @@ namespace ApplicationTests
new NonImpersonatableCommand(cmdConfig, transLookup)
});
//Utilities.DefaultCommandTimeout = new TimeSpan(0, 0, 2);
A.CallTo(() => mgr.AddEvent(A<GameEvent>.Ignored))
.Invokes((fakeCall) => mockEventHandler.HandleEvent(mgr, fakeCall.Arguments[0] as GameEvent));
}
#region RUNAS

View File

@ -9,10 +9,22 @@ using SharedLibraryCore.Services;
namespace ApplicationTests
{
static class DepedencyInjectionExtensions
static class DependencyInjectionExtensions
{
public static IServiceCollection BuildBase(this IServiceCollection serviceCollection)
public static IServiceCollection BuildBase(this IServiceCollection serviceCollection, IEventHandler eventHandler = null)
{
if (eventHandler == null)
{
eventHandler = new MockEventHandler();
serviceCollection.AddSingleton(eventHandler as MockEventHandler);
}
else if (eventHandler is MockEventHandler mockEventHandler)
{
serviceCollection.AddSingleton(mockEventHandler);
}
var manager = A.Fake<IManager>();
var logger = A.Fake<ILogger>();
@ -27,10 +39,13 @@ namespace ApplicationTests
.AddSingleton(A.Fake<ITranslationLookup>())
.AddSingleton(A.Fake<IRConParser>())
.AddSingleton(A.Fake<IParserRegexFactory>())
.AddSingleton(A.Fake<ClientService>());
.AddSingleton<DataFileLoader>()
.AddSingleton(A.Fake<ClientService>())
.AddSingleton(A.Fake<IGameLogReaderFactory>())
.AddSingleton(eventHandler);
serviceCollection.AddSingleton(_sp => new IW4MServer(_sp.GetRequiredService<IManager>(), ConfigurationGenerators.CreateServerConfiguration(),
_sp.GetRequiredService<ITranslationLookup>(), _sp.GetRequiredService<IRConConnectionFactory>())
_sp.GetRequiredService<ITranslationLookup>(), _sp.GetRequiredService<IRConConnectionFactory>(), _sp.GetRequiredService<IGameLogReaderFactory>())
{
RconParser = _sp.GetRequiredService<IRConParser>()
});

View File

@ -0,0 +1,16 @@
using IW4MAdmin.Application.Misc;
using Newtonsoft.Json;
using System.IO;
using System.Threading.Tasks;
namespace ApplicationTests.Fixtures
{
class DataFileLoader
{
public async Task<T> Load<T>(string fileName)
{
string data = await File.ReadAllTextAsync($"{fileName}.json");
return JsonConvert.DeserializeObject<T>(data, EventLog.BuildVcrSerializationSettings());
}
}
}

View File

@ -0,0 +1,17 @@
using IW4MAdmin;
using IW4MAdmin.Application;
using Microsoft.Extensions.DependencyInjection;
using NUnit.Framework;
using SharedLibraryCore;
using System;
using System.Collections.Generic;
using System.Text;
using System.Threading.Tasks;
namespace ApplicationTests
{
[TestFixture]
public class GameEventHandlerTests
{
}
}

View File

@ -1,5 +1,7 @@
using FakeItEasy;
using IW4MAdmin;
using IW4MAdmin.Application.IO;
using Microsoft.Extensions.DependencyInjection;
using NUnit.Framework;
using SharedLibraryCore;
using SharedLibraryCore.Interfaces;
@ -12,11 +14,24 @@ namespace ApplicationTests
public class IOTests
{
private IServiceProvider serviceProvider;
[SetUp]
public void Setup()
{
serviceProvider = new ServiceCollection().BuildBase().BuildServiceProvider();
}
[Test]
public async Task GameLogEventDetection_WorksAfterFileSizeReset()
{
var reader = A.Fake<IGameLogReader>();
var detect = new GameLogEventDetection(null, "", A.Fake<Uri>(), reader);
var factory = A.Fake<IGameLogReaderFactory>();
A.CallTo(() => factory.CreateGameLogReader(A<Uri[]>.Ignored, A<IEventParser>.Ignored))
.Returns(reader);
var detect = new GameLogEventDetection(serviceProvider.GetService<IW4MServer>(), new Uri[] { new Uri("C:\\test.log") }, factory);
A.CallTo(() => reader.Length)
.Returns(100)
@ -35,7 +50,7 @@ namespace ApplicationTests
await detect.UpdateLogEvents();
}
A.CallTo(() => reader.ReadEventsFromLog(A<Server>.Ignored, A<long>.Ignored, A<long>.Ignored))
A.CallTo(() => reader.ReadEventsFromLog(A<long>.Ignored, A<long>.Ignored))
.MustHaveHappenedTwiceExactly();
}
}

View File

@ -36,6 +36,7 @@ namespace ApplicationTests
fakeManager = serviceProvider.GetRequiredService<IManager>();
fakeRConConnection = serviceProvider.GetRequiredService<IRConConnection>();
fakeRConParser = serviceProvider.GetRequiredService<IRConParser>();
mockEventHandler = serviceProvider.GetRequiredService<MockEventHandler>();
var rconConnectionFactory = serviceProvider.GetRequiredService<IRConConnectionFactory>();
@ -45,10 +46,9 @@ namespace ApplicationTests
A.CallTo(() => fakeRConParser.Configuration)
.Returns(ConfigurationGenerators.CreateRConParserConfiguration(serviceProvider.GetRequiredService<IParserRegexFactory>()));
mockEventHandler = new MockEventHandler();
A.CallTo(() => fakeManager.GetEventHandler())
.Returns(mockEventHandler);
A.CallTo(() => fakeManager.AddEvent(A<GameEvent>.Ignored))
.Invokes((fakeCall) => mockEventHandler.HandleEvent(fakeManager, fakeCall.Arguments[0] as GameEvent));
}
#region LOG

View File

@ -14,7 +14,7 @@ namespace ApplicationTests.Mocks
_autoExecute = autoExecute;
}
public void AddEvent(GameEvent gameEvent)
public void HandleEvent(IManager manager, GameEvent gameEvent)
{
Events.Add(gameEvent);

View File

@ -0,0 +1,21 @@
using SharedLibraryCore;
using SharedLibraryCore.Interfaces;
using System;
using System.Collections.Generic;
using System.Text;
using System.Threading.Tasks;
namespace ApplicationTests.Mocks
{
class VcrEventReader : IGameLogReader
{
public long Length => throw new NotImplementedException();
public int UpdateInterval => throw new NotImplementedException();
public Task<IEnumerable<GameEvent>> ReadEventsFromLog(long fileSizeDiff, long startPosition)
{
throw new NotImplementedException();
}
}
}

View File

@ -31,9 +31,7 @@ namespace ApplicationTests
{
serviceProvider = new ServiceCollection().BuildBase().BuildServiceProvider();
fakeManager = serviceProvider.GetRequiredService<IManager>();
mockEventHandler = new MockEventHandler();
A.CallTo(() => fakeManager.GetEventHandler())
.Returns(mockEventHandler);
mockEventHandler = serviceProvider.GetRequiredService<MockEventHandler>();
var rconConnectionFactory = serviceProvider.GetRequiredService<IRConConnectionFactory>();
@ -42,6 +40,9 @@ namespace ApplicationTests
A.CallTo(() => serviceProvider.GetRequiredService<IRConParser>().Configuration)
.Returns(ConfigurationGenerators.CreateRConParserConfiguration(serviceProvider.GetRequiredService<IParserRegexFactory>()));
A.CallTo(() => fakeManager.AddEvent(A<GameEvent>.Ignored))
.Invokes((fakeCall) => mockEventHandler.HandleEvent(fakeManager, fakeCall.Arguments[0] as GameEvent));
}
[Test]

View File

@ -33,7 +33,7 @@ namespace ApplicationTests
var mgr = A.Fake<IManager>();
var server = new IW4MServer(mgr,
new SharedLibraryCore.Configuration.ServerConfiguration() { IPAddress = "127.0.0.1", Port = 28960 },
A.Fake<ITranslationLookup>(), A.Fake<IRConConnectionFactory>());
A.Fake<ITranslationLookup>(), A.Fake<IRConConnectionFactory>(), A.Fake<IGameLogReaderFactory>());
var parser = new BaseEventParser(A.Fake<IParserRegexFactory>(), A.Fake<ILogger>());
parser.Configuration.GuidNumberStyle = System.Globalization.NumberStyles.Integer;
@ -59,7 +59,7 @@ namespace ApplicationTests
var server = new IW4MServer(mgr,
new SharedLibraryCore.Configuration.ServerConfiguration() { IPAddress = "127.0.0.1", Port = 28960 },
A.Fake<ITranslationLookup>(), A.Fake<IRConConnectionFactory>());
A.Fake<ITranslationLookup>(), A.Fake<IRConConnectionFactory>(), A.Fake<IGameLogReaderFactory>());
var parser = new BaseEventParser(A.Fake<IParserRegexFactory>(), A.Fake<ILogger>());
parser.Configuration.GuidNumberStyle = System.Globalization.NumberStyles.Integer;

View File

@ -63,7 +63,7 @@ namespace ApplicationTests
var server = new IW4MServer(mgr,
new SharedLibraryCore.Configuration.ServerConfiguration() { IPAddress = "127.0.0.1", Port = 28960 },
A.Fake<ITranslationLookup>(),
A.Fake<IRConConnectionFactory>());
A.Fake<IRConConnectionFactory>(), A.Fake<IGameLogReaderFactory>());
var parser = new BaseEventParser(A.Fake<IParserRegexFactory>(), A.Fake<ILogger>());
parser.Configuration.GuidNumberStyle = System.Globalization.NumberStyles.Integer;

View File

@ -0,0 +1,43 @@
using ApplicationTests.Fixtures;
using IW4MAdmin;
using IW4MAdmin.Application;
using IW4MAdmin.Application.Misc;
using Microsoft.Extensions.DependencyInjection;
using NUnit.Framework;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace ApplicationTests
{
[TestFixture]
public class VcrTests
{
private IServiceProvider serviceProvider;
[SetUp]
public void Setup()
{
serviceProvider = new ServiceCollection().BuildBase()
.BuildServiceProvider();
}
[Test]
[TestCase("replay")]
public async Task ReplayEvents(string source)
{
var sourceData = await serviceProvider
.GetRequiredService<DataFileLoader>()
.Load<EventLog>(source);
var server = serviceProvider.GetRequiredService<IW4MServer>();
foreach (var gameEvent in sourceData.Values.First())
{
await server.ExecuteEvent(gameEvent);
}
}
}
}

View File

@ -56,7 +56,7 @@ namespace WebfrontCore.Controllers
IsRemote = true
};
Manager.GetEventHandler().AddEvent(remoteEvent);
Manager.AddEvent(remoteEvent);
List<CommandResponseInfo> response = null;
try