1
0
mirror of https://github.com/RaidMax/IW4M-Admin.git synced 2025-06-08 06:08:20 -05:00

fix parsing of certain chat messages

print out the correct exception message when a server is not responding.
prevent log reader from reading before the servers have initialized
This commit is contained in:
RaidMax 2018-07-04 21:09:42 -05:00
parent dd86087336
commit d4cc01e3ba
8 changed files with 173 additions and 111 deletions

View File

@ -10,6 +10,8 @@ namespace IW4MAdmin.Application.EventParsers
{ {
class IW4EventParser : IEventParser class IW4EventParser : IEventParser
{ {
private const string SayRegex = @"(say|sayteam);(.{16,32});([0-9]+)(.*);(.*)";
public virtual GameEvent GetEvent(Server server, string logLine) public virtual GameEvent GetEvent(Server server, string logLine)
{ {
logLine = Regex.Replace(logLine, @"([0-9]+:[0-9]+ |^[0-9]+ )", "").Trim(); logLine = Regex.Replace(logLine, @"([0-9]+:[0-9]+ |^[0-9]+ )", "").Trim();
@ -45,28 +47,35 @@ namespace IW4MAdmin.Application.EventParsers
if (eventType == "say" || eventType == "sayteam") if (eventType == "say" || eventType == "sayteam")
{ {
string message = lineSplit[4].Replace("\x15", ""); var matchResult = Regex.Match(logLine, SayRegex);
if (message[0] == '!' || message[0] == '@') if (matchResult.Success)
{ {
string message = matchResult.Groups[5].ToString()
.Replace("\x15", "")
.Trim();
if (message[0] == '!' || message[0] == '@')
{
return new GameEvent()
{
Type = GameEvent.EventType.Command,
Data = message,
Origin = server.GetPlayersAsList().First(c => c.ClientNumber == Utilities.ClientIdFromString(lineSplit, 2)),
Owner = server,
Message = message
};
}
return new GameEvent() return new GameEvent()
{ {
Type = GameEvent.EventType.Command, Type = GameEvent.EventType.Say,
Data = message, Data = message,
Origin = server.GetPlayersAsList().First(c => c.ClientNumber == Utilities.ClientIdFromString(lineSplit, 2)), Origin = server.GetPlayersAsList().First(c => c.ClientNumber == Utilities.ClientIdFromString(lineSplit, 2)),
Owner = server, Owner = server,
Message = message Message = message
}; };
} }
return new GameEvent()
{
Type = GameEvent.EventType.Say,
Data = message,
Origin = server.GetPlayersAsList().First(c => c.ClientNumber == Utilities.ClientIdFromString(lineSplit, 2)),
Owner = server,
Message = message
};
} }
if (eventType == "ScriptKill") if (eventType == "ScriptKill")

View File

@ -3,9 +3,6 @@ using SharedLibraryCore.Interfaces;
using System; using System;
using System.Collections.Concurrent; using System.Collections.Concurrent;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
namespace IW4MAdmin.Application namespace IW4MAdmin.Application
{ {
@ -14,8 +11,6 @@ namespace IW4MAdmin.Application
private ConcurrentQueue<GameEvent> EventQueue; private ConcurrentQueue<GameEvent> EventQueue;
private Queue<GameEvent> DelayedEventQueue; private Queue<GameEvent> DelayedEventQueue;
private IManager Manager; private IManager Manager;
private const int DelayAmount = 5000;
private DateTime LastDelayedEvent;
public GameEventHandler(IManager mgr) public GameEventHandler(IManager mgr)
{ {
@ -50,25 +45,7 @@ namespace IW4MAdmin.Application
} }
public GameEvent GetNextEvent() public GameEvent GetNextEvent()
{ {
if (DelayedEventQueue.Count > 0 &&
(DateTime.Now - LastDelayedEvent).TotalMilliseconds > DelayAmount)
{
LastDelayedEvent = DateTime.Now;
#if DEBUG
Manager.GetLogger().WriteDebug("Getting next delayed event to be processed");
#endif
if (!DelayedEventQueue.TryDequeue(out GameEvent newEvent))
{
Manager.GetLogger().WriteWarning("Could not dequeue delayed event for processing");
}
else
{
return newEvent;
}
}
if (EventQueue.Count > 0) if (EventQueue.Count > 0)
{ {
#if DEBUG #if DEBUG
@ -76,7 +53,7 @@ namespace IW4MAdmin.Application
#endif #endif
if (!EventQueue.TryDequeue(out GameEvent newEvent)) if (!EventQueue.TryDequeue(out GameEvent newEvent))
{ {
Manager.GetLogger().WriteWarning("Could not dequeue event for processing"); Manager.GetLogger().WriteError("Could not dequeue event for processing");
} }
else else

View File

@ -29,11 +29,14 @@ namespace IW4MAdmin.Application.IO
{ {
while (!server.Manager.ShutdownRequested()) while (!server.Manager.ShutdownRequested())
{ {
OnEvent(new EventState() if ((server.Manager as ApplicationManager).IsInitialized)
{ {
Log = server.Manager.GetLogger(), OnEvent(new EventState()
ServerId = server.ToString() {
}); Log = server.Manager.GetLogger(),
ServerId = server.ToString()
});
}
await Task.Delay(100); await Task.Delay(100);
} }
}); });
@ -42,7 +45,7 @@ namespace IW4MAdmin.Application.IO
private void OnEvent(object state) private void OnEvent(object state)
{ {
long newLength = new FileInfo(GameLogFile).Length; long newLength = new FileInfo(GameLogFile).Length;
try try
{ {
UpdateLogEvents(newLength); UpdateLogEvents(newLength);

View File

@ -31,6 +31,7 @@ namespace IW4MAdmin.Application
public Dictionary<int, Player> PrivilegedClients { get; set; } public Dictionary<int, Player> PrivilegedClients { get; set; }
public ILogger Logger { get; private set; } public ILogger Logger { get; private set; }
public bool Running { get; private set; } public bool Running { get; private set; }
public bool IsInitialized { get; private set; }
public EventHandler<GameEvent> ServerEventOccurred { get; private set; } public EventHandler<GameEvent> ServerEventOccurred { get; private set; }
public DateTime StartTime { get; private set; } public DateTime StartTime { get; private set; }
@ -92,6 +93,13 @@ namespace IW4MAdmin.Application
.Select(ut => ut.Key) .Select(ut => ut.Key)
.ToList(); .ToList();
// this is to prevent the log reader from starting before the initial
// query of players on the server
if (serverTasksToRemove.Count > 0)
{
IsInitialized = true;
}
// remove the update tasks as they have completd // remove the update tasks as they have completd
foreach (int serverId in serverTasksToRemove) foreach (int serverId in serverTasksToRemove)
{ {
@ -122,7 +130,11 @@ namespace IW4MAdmin.Application
ThreadPool.GetAvailableThreads(out int availableThreads, out int m); ThreadPool.GetAvailableThreads(out int availableThreads, out int m);
Logger.WriteDebug($"There are {workerThreads - availableThreads} active threading tasks"); Logger.WriteDebug($"There are {workerThreads - availableThreads} active threading tasks");
#endif #endif
#if DEBUG
await Task.Delay(30000);
#else
await Task.Delay(ConfigHandler.Configuration().RConPollRate); await Task.Delay(ConfigHandler.Configuration().RConPollRate);
#endif
} }
} }
@ -130,7 +142,7 @@ namespace IW4MAdmin.Application
{ {
Running = true; Running = true;
#region DATABASE #region DATABASE
var ipList = (await ClientSvc.Find(c => c.Level > Player.Permission.Trusted)) var ipList = (await ClientSvc.Find(c => c.Level > Player.Permission.Trusted))
.Select(c => new .Select(c => new
{ {
@ -160,9 +172,9 @@ namespace IW4MAdmin.Application
continue; continue;
} }
} }
#endregion #endregion
#region CONFIG #region CONFIG
var config = ConfigHandler.Configuration(); var config = ConfigHandler.Configuration();
// copy over default config if it doesn't exist // copy over default config if it doesn't exist
@ -213,8 +225,8 @@ namespace IW4MAdmin.Application
Encoding.RegisterProvider(CodePagesEncodingProvider.Instance); Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);
Utilities.EncodingType = Encoding.GetEncoding(!string.IsNullOrEmpty(config.CustomParserEncoding) ? config.CustomParserEncoding : "windows-1252"); Utilities.EncodingType = Encoding.GetEncoding(!string.IsNullOrEmpty(config.CustomParserEncoding) ? config.CustomParserEncoding : "windows-1252");
#endregion #endregion
#region PLUGINS #region PLUGINS
SharedLibraryCore.Plugins.PluginImporter.Load(this); SharedLibraryCore.Plugins.PluginImporter.Load(this);
foreach (var Plugin in SharedLibraryCore.Plugins.PluginImporter.ActivePlugins) foreach (var Plugin in SharedLibraryCore.Plugins.PluginImporter.ActivePlugins)
@ -231,9 +243,9 @@ namespace IW4MAdmin.Application
Logger.WriteDebug($"Stack Trace: {e.StackTrace}"); Logger.WriteDebug($"Stack Trace: {e.StackTrace}");
} }
} }
#endregion #endregion
#region COMMANDS #region COMMANDS
if (ClientSvc.GetOwners().Result.Count == 0) if (ClientSvc.GetOwners().Result.Count == 0)
Commands.Add(new COwner()); Commands.Add(new COwner());
@ -277,9 +289,9 @@ namespace IW4MAdmin.Application
foreach (Command C in SharedLibraryCore.Plugins.PluginImporter.ActiveCommands) foreach (Command C in SharedLibraryCore.Plugins.PluginImporter.ActiveCommands)
Commands.Add(C); Commands.Add(C);
#endregion #endregion
#region INIT #region INIT
async Task Init(ServerConfiguration Conf) async Task Init(ServerConfiguration Conf)
{ {
// setup the event handler after the class is initialized // setup the event handler after the class is initialized
@ -315,7 +327,7 @@ namespace IW4MAdmin.Application
} }
await Task.WhenAll(config.Servers.Select(c => Init(c)).ToArray()); await Task.WhenAll(config.Servers.Select(c => Init(c)).ToArray());
#endregion #endregion
} }
private async Task SendHeartbeat(object state) private async Task SendHeartbeat(object state)
@ -414,8 +426,7 @@ namespace IW4MAdmin.Application
catch (NetworkException e) catch (NetworkException e)
{ {
Logger.WriteError(Utilities.CurrentLocalization.LocalizationIndex["SERVER_ERROR_COMMUNICATION"]); Logger.WriteError(e.Message);
Logger.WriteDebug(e.Message);
} }
catch (Exception E) catch (Exception E)
@ -434,24 +445,21 @@ namespace IW4MAdmin.Application
{ {
// wait for new event to be added // wait for new event to be added
OnEvent.Wait(); OnEvent.Wait();
var taskList = new List<Task>(); while((queuedEvent = Handler.GetNextEvent()) != null)
// todo: sequencially or parallelize?
while ((queuedEvent = Handler.GetNextEvent()) != null)
{ {
if (queuedEvent.Origin != null && if (GameEvent.ShouldOriginEventBeDelayed(queuedEvent))
!queuedEvent.Origin.IsAuthenticated &&
// we want to allow join events
queuedEvent.Type != GameEvent.EventType.Join &&
queuedEvent.Type != GameEvent.EventType.Quit &&
// we don't care about unknown events
queuedEvent.Origin.NetworkId != 0)
{ {
Logger.WriteDebug($"Delaying execution of event type {queuedEvent.Type} for {queuedEvent.Origin} because they are not authed"); Logger.WriteDebug($"Delaying origin execution of event type {queuedEvent.Type} for {queuedEvent.Origin} because they are not authed");
// update the event origin for possible authed client // offload it to the player to keep
queuedEvent.Origin = queuedEvent.Owner.Players.FirstOrDefault(p => p != null && p.NetworkId == queuedEvent.Origin.NetworkId); queuedEvent.Origin.DelayedEvents.Enqueue(queuedEvent);
queuedEvent.Target = queuedEvent.Target == null ? null : queuedEvent.Owner.Players.FirstOrDefault(p => p != null && p.NetworkId == queuedEvent.Target.NetworkId); continue;
// add it back to the queue for reprocessing }
Handler.AddEvent(queuedEvent, true);
if (GameEvent.ShouldTargetEventBeDelayed(queuedEvent))
{
Logger.WriteDebug($"Delaying target execution of event type {queuedEvent.Type} for {queuedEvent.Target} because they are not authed");
// offload it to the player to keep
queuedEvent.Target.DelayedEvents.Enqueue(queuedEvent);
continue; continue;
} }
await processEvent(queuedEvent); await processEvent(queuedEvent);

View File

@ -79,9 +79,7 @@ namespace IW4MAdmin
if (Players[polledPlayer.ClientNumber] != null && if (Players[polledPlayer.ClientNumber] != null &&
Players[polledPlayer.ClientNumber].NetworkId == polledPlayer.NetworkId && Players[polledPlayer.ClientNumber].NetworkId == polledPlayer.NetworkId &&
// only update if they're unauthenticated Players[polledPlayer.ClientNumber].State == Player.ClientState.Connected)
Players[polledPlayer.ClientNumber].IsAuthenticated &&
Players[polledPlayer.ClientNumber].State == Player.ClientState.Connected)
{ {
// update their ping & score // update their ping & score
Players[polledPlayer.ClientNumber].Ping = polledPlayer.Ping; Players[polledPlayer.ClientNumber].Ping = polledPlayer.Ping;
@ -89,18 +87,19 @@ namespace IW4MAdmin
return true; return true;
} }
if (Players[polledPlayer.ClientNumber] != null && if (Players[polledPlayer.ClientNumber] == null)
Players[polledPlayer.ClientNumber].State == Player.ClientState.Connected) {
Players[polledPlayer.ClientNumber] = polledPlayer;
}
if (!polledPlayer.IsAuthenticated)
{ {
return true; return true;
} }
// if they're authenticated but haven't been added yet
// we want to set their delayed events
var delayedEventQueue = Players[polledPlayer.ClientNumber].DelayedEvents;
if (Players[polledPlayer.ClientNumber] == null)
{
//prevent duplicates from being added
polledPlayer.State = Player.ClientState.Connecting;
Players[polledPlayer.ClientNumber] = polledPlayer;
}
#if !DEBUG #if !DEBUG
if (polledPlayer.Name.Length < 3) if (polledPlayer.Name.Length < 3)
{ {
@ -175,15 +174,20 @@ namespace IW4MAdmin
await Manager.GetClientService().Update(client); await Manager.GetClientService().Update(client);
} }
else if (existingAlias.Name == polledPlayer.Name) else if (existingAlias.Name == polledPlayer.Name ||
// fixme: why would this be null?
client.CurrentAlias == null)
{ {
client.CurrentAlias = existingAlias; client.CurrentAlias = existingAlias;
client.CurrentAliasId = existingAlias.AliasId; client.CurrentAliasId = existingAlias.AliasId;
client = await Manager.GetClientService().Update(client); client = await Manager.GetClientService().Update(client);
} }
player = client.AsPlayer(); player = client.AsPlayer();
} }
Logger.WriteInfo($"Client {player} connected...");
// Do the player specific stuff // Do the player specific stuff
player.ClientNumber = polledPlayer.ClientNumber; player.ClientNumber = polledPlayer.ClientNumber;
player.IsBot = polledPlayer.IsBot; player.IsBot = polledPlayer.IsBot;
@ -230,8 +234,6 @@ namespace IW4MAdmin
return true; return true;
} }
Logger.WriteInfo($"Client {player} connecting...");
if (!Manager.GetApplicationSettings().Configuration().EnableClientVPNs && if (!Manager.GetApplicationSettings().Configuration().EnableClientVPNs &&
await VPNCheck.UsingVPN(player.IPAddressString, Manager.GetApplicationSettings().Configuration().IPHubAPIKey)) await VPNCheck.UsingVPN(player.IPAddressString, Manager.GetApplicationSettings().Configuration().IPHubAPIKey))
{ {
@ -247,12 +249,33 @@ namespace IW4MAdmin
}; };
Manager.GetEventHandler().AddEvent(e); Manager.GetEventHandler().AddEvent(e);
// add the delayed event to the queue
while (delayedEventQueue?.Count > 0)
{
e = delayedEventQueue.Dequeue();
e.Origin = player;
// check if the target was assigned
if (e.Target != null)
{
// update the target incase they left or have newer info
e.Target = GetPlayersAsList().FirstOrDefault(p => p.NetworkId == e.Target.NetworkId);
// we have to throw out the event because they left
if (e.Target == null)
{
Logger.WriteWarning($"Delayed event for {e.Origin} was removed because the target has left");
continue;
}
}
Manager.GetEventHandler().AddEvent(e);
}
return true; return true;
} }
catch (Exception E) catch (Exception E)
{ {
Manager.GetLogger().WriteError($"{loc["SERVER_ERROR_ADDPLAYER"]} {polledPlayer.Name}::{polledPlayer.NetworkId}"); Manager.GetLogger().WriteError($"{loc["SERVER_ERROR_ADDPLAYER"]} {polledPlayer.Name}::{polledPlayer.NetworkId}");
Manager.GetLogger().WriteDebug(E.Message);
Manager.GetLogger().WriteDebug(E.StackTrace); Manager.GetLogger().WriteDebug(E.StackTrace);
return false; return false;
} }
@ -381,33 +404,42 @@ namespace IW4MAdmin
else if (E.Type == GameEvent.EventType.Quit) else if (E.Type == GameEvent.EventType.Quit)
{ {
var e = new GameEvent() var origin = Players.FirstOrDefault(p => p != null && p.NetworkId == E.Origin.NetworkId);
{
Type = GameEvent.EventType.Disconnect,
Origin = Players.FirstOrDefault(p => p != null && p.NetworkId == E.Origin.NetworkId),
Owner = this
};
if (e.Origin != null) if (origin != null &&
// we only want to forward the event if they are connected.
origin.State == Player.ClientState.Connected)
{ {
e.Origin.State = Player.ClientState.Disconnecting; var e = new GameEvent()
{
Type = GameEvent.EventType.Disconnect,
Origin = origin,
Owner = this
};
if (e.Origin != null)
{
e.Origin.State = Player.ClientState.Disconnecting;
}
Manager.GetEventHandler().AddEvent(e);
} }
Manager.GetEventHandler().AddEvent(e); else if (origin != null &&
origin.State == Player.ClientState.Connecting)
{
await RemovePlayer(origin.ClientNumber);
}
} }
else if (E.Type == GameEvent.EventType.Disconnect) else if (E.Type == GameEvent.EventType.Disconnect)
{ {
// this may be a fix for a hard to reproduce null exception error ChatHistory.Add(new ChatInfo()
lock (ChatHistory)
{ {
ChatHistory.Add(new ChatInfo() Name = E.Origin.Name,
{ Message = "DISCONNECTED",
Name = E.Origin.Name, Time = DateTime.UtcNow
Message = "DISCONNECTED", });
Time = DateTime.UtcNow
});
}
await RemovePlayer(E.Origin.ClientNumber); await RemovePlayer(E.Origin.ClientNumber);
} }
@ -498,7 +530,6 @@ namespace IW4MAdmin
ChatHistory.Clear(); ChatHistory.Clear();
} }
async Task<int> PollPlayersAsync() async Task<int> PollPlayersAsync()
{ {
var now = DateTime.Now; var now = DateTime.Now;
@ -546,14 +577,14 @@ namespace IW4MAdmin
AuthQueue.AuthenticateClients(CurrentPlayers); AuthQueue.AuthenticateClients(CurrentPlayers);
// all polled players should be authenticated // all polled players should be authenticated
foreach (var client in AuthQueue.GetAuthenticatedClients()) var addPlayerTasks = AuthQueue.GetAuthenticatedClients()
{ .Where(client => Players[client.ClientNumber] == null ||
if (Players[client.ClientNumber] == null || Players[client.ClientNumber].State == Player.ClientState.Connecting) Players[client.ClientNumber].State == Player.ClientState.Connecting)
{ .Select(client => AddPlayer(client));
await AddPlayer(client);
} await Task.WhenAll(addPlayerTasks);
}
return CurrentPlayers.Count; return CurrentPlayers.Count;
} }

View File

@ -9,7 +9,7 @@ namespace SharedLibraryCore
public enum EventType public enum EventType
{ {
Unknown, Unknown,
// events "generated" by the server // events "generated" by the server
Start, Start,
Stop, Stop,
@ -76,5 +76,35 @@ namespace SharedLibraryCore
public ManualResetEventSlim OnProcessed { get; set; } public ManualResetEventSlim OnProcessed { get; set; }
public DateTime Time { get; private set; } public DateTime Time { get; private set; }
public long Id { get; private set; } public long Id { get; private set; }
/// <summary>
/// determine whether an event should be delayed or not
/// applies only to the origin entity
/// </summary>
/// <param name="queuedEvent">event to determine status for</param>
/// <returns>true if event should be delayed, false otherwise</returns>
public static bool ShouldOriginEventBeDelayed(GameEvent queuedEvent)
{
return queuedEvent.Origin != null &&
!queuedEvent.Origin.IsAuthenticated &&
// we want to allow join and quit events
queuedEvent.Type != EventType.Join &&
queuedEvent.Type != EventType.Quit &&
// we don't care about unknown events
queuedEvent.Origin.NetworkId != 0;
}
/// <summary>
/// determine whether an event should be delayed or not
/// applies only to the target entity
/// </summary>
/// <param name="queuedEvent">event to determine status for</param>
/// <returns>true if event should be delayed, false otherwise</returns>
public static bool ShouldTargetEventBeDelayed(GameEvent queuedEvent)
{
return queuedEvent.Target != null &&
!queuedEvent.Target.IsAuthenticated &&
queuedEvent.Target.NetworkId != 0;
}
} }
} }

View File

@ -33,6 +33,7 @@ namespace SharedLibraryCore.Objects
{ {
ConnectionTime = DateTime.UtcNow; ConnectionTime = DateTime.UtcNow;
ClientNumber = -1; ClientNumber = -1;
DelayedEvents = new Queue<GameEvent>();
} }
public override string ToString() public override string ToString()
@ -120,6 +121,8 @@ namespace SharedLibraryCore.Objects
public bool IsAuthenticated { get; set; } public bool IsAuthenticated { get; set; }
[NotMapped] [NotMapped]
public ClientState State { get; set; } public ClientState State { get; set; }
[NotMapped]
public Queue<GameEvent> DelayedEvents { get; set; }
public override bool Equals(object obj) public override bool Equals(object obj)
{ {

View File

@ -327,6 +327,7 @@ namespace SharedLibraryCore
LastConnection = client.LastConnection == DateTime.MinValue ? DateTime.UtcNow : client.LastConnection, LastConnection = client.LastConnection == DateTime.MinValue ? DateTime.UtcNow : client.LastConnection,
CurrentAlias = client.CurrentAlias, CurrentAlias = client.CurrentAlias,
CurrentAliasId = client.CurrentAlias.AliasId, CurrentAliasId = client.CurrentAlias.AliasId,
// todo: make sure this is up to date
IsBot = client.NetworkId == -1, IsBot = client.NetworkId == -1,
Password = client.Password, Password = client.Password,
PasswordSalt = client.PasswordSalt PasswordSalt = client.PasswordSalt