Compare commits

..

52 Commits

Author SHA1 Message Date
ce02f5dd68 Move T6 parser to javascript parser 2019-02-03 20:47:05 -06:00
e6bfa408f8 move IW3 parser to javascript 2019-02-02 20:19:24 -06:00
0a1dc46760 Add commenting for parsers
rename IW4*Parser to Base*Parser
2019-02-02 19:40:37 -06:00
97ba6aae2e put parser in right location :P 2019-02-02 19:13:17 -06:00
a456fab0e5 Increment version #
Add TeknoMW3 parser file
2019-02-02 19:11:34 -06:00
3e5282df87 Finish preliminary parser for TeknoMW3 2019-02-02 18:54:30 -06:00
59e0072744 Finish dynamic dvar parsing for IW4x 2019-02-01 19:49:25 -06:00
f1dd4f7c7f Fix IP parsing bug introduced with IW4Parser
Additional fix for Webfront Index OoB on _ClientActivity
2019-01-28 18:21:56 -06:00
760d3026ce Fixes for PR 2.3.4.0 2019-01-27 19:45:35 -06:00
07df6dbf79 Update version number and small plugin fix 2019-01-27 18:54:18 -06:00
ca535019c6 Finish RCON dynamic parser impl
Fix configuration generation bug
2019-01-27 18:41:54 -06:00
e6154822f6 Implement more dynamic parser stuff 2019-01-27 16:40:08 -06:00
7a6dccc26a Fix bug with webfront spamming issues when running
Remove IW5 parser
Begin implementation of dynamic parsers
2019-01-26 20:33:37 -06:00
08c883e0ff fix duplicate bot welcomes
fix prompt bool incorrect default value
rename GameEvent.Remote to GameEvent.IsRemote
include NetworkId in webfront claims
fix non descript error message appearing when something fails and localization is not initialized
2019-01-03 14:39:22 -06:00
aaf9eb09b6 more alias changes :(
fix flag penalty coming from wrong user
2019-01-02 18:32:39 -06:00
7b75c35c9b fix aliases for real (hopefully)
fix bug with flag not being applied
fix level being set based on IP instead of IP and name
2018-12-31 20:52:19 -06:00
07ec5cf52f update assembly version 2018-12-30 20:52:26 -06:00
cf5ee8765d finish alias fixes
add manual webfront bind url
2018-12-30 20:48:07 -06:00
9494a17997 update prompt utility functions for issue #65
tweaks to alias stuff
fix bug with bots not showing
2018-12-30 18:13:13 -06:00
5f4171ccf4 hopefulyl fix aliasing issue
bans are applied to an account if the accounts are linked but penallty on a different accounts
2018-12-29 12:43:40 -06:00
a10746d5ff Fix small issue with log reading 2018-12-19 19:24:31 -06:00
8dca05a442 Small fixes 2018-12-17 13:45:16 -06:00
8aa0d204f4 Delete stupid 2018-12-16 21:52:11 -06:00
12cf2e8247 Add server version to master api
Add IsEvadedOffense to EFPenalty
Fix remote log reading in not Windows
2018-12-16 21:16:56 -06:00
b77bdbe793 minor fixed 2018-12-03 19:21:13 -06:00
4522992c0e fix remote commands
user clientkick_for_reason for T6 parsers
small bug fixes
2018-12-01 12:17:53 -06:00
9d6cbee69c update stats
change server id
fIx change log server complaining when empty read
2018-11-27 18:31:48 -06:00
abf0609e2e fix only one administrator showing on admins page
fix profanity determent not applying penalties.
2018-11-25 21:11:55 -06:00
5ac8a55c72 fixes for new polling setup
update database model for alias (nullable ip)
heartbeats now send ip to master server
2018-11-25 20:00:36 -06:00
9bdd7d1b8a More work modifying client stuff 2018-11-07 20:30:11 -06:00
ed83c4c011 started work on getting the restart functionality in the gamelogserver
fix bug with unbanned players still showing as banned via lock icon
move player based stuff into client class
finally renamed Player to EFClient via partial class
don't try to run this build because it's in between stages
2018-11-05 21:01:29 -06:00
d9d548ea18 Small anti-cheat update 2018-10-28 20:47:56 -05:00
1779bf821d more work on skill based team balance.
added on player disconnect to custom callbacks
2018-10-25 08:14:39 -05:00
d50e6c8030 change penalty expiration datetime to null for perm bans
add tempban max time
allow searching for GUID
stats returns ranking as well
fix for promotion/demotion text
2018-10-15 19:51:04 -05:00
a58726d872 add gsc api controller for communicating with gsc
add ignore bots option
fix first localization message not working
2018-10-13 18:51:07 -05:00
dded60a6ef add test to print out all commands 2018-10-12 21:32:30 -05:00
305817d00c version 2.2 stable 2018-10-12 21:28:22 -05:00
65cf3566db fix publish for release
fix bug with localization issue crashing app if config is wrong
2018-10-12 18:59:17 -05:00
e91d076b41 curtail lost connection messages from RCon
verify still compatible with T6
fix potential null reference exception during configuration reading/setup
2018-10-10 19:22:08 -05:00
b5e9519f0c update shared guid kick plugin
script plugins reload without error using the correct file share mode when opening
increase socket timeout to 10 seconds
2018-10-09 20:19:06 -05:00
7caee55a7e refactored the welcome plugin to use a web api instead of a hard coded file
removed deprecated file class
don't wait for response when setting dvar/sending command in T6
potential fix for duplicate kick message in JS plugin
2018-10-08 21:15:59 -05:00
b289917319 update project to .net core 2.1.5
got rid of "threadsafe" stats service in stats plugin
2018-10-07 21:34:30 -05:00
c8366a22e5 fixed the vpn detection plugin method signature call
added some fixes for stats/ac
2018-10-06 15:31:05 -05:00
de902a58ac write individual server log files and main log file seperately
log writing is thread safe now
2018-10-06 11:47:14 -05:00
c7547f1ad5 clean up publish folder output to have a less cluttered structure.
add migration class to perform the migration on update
2018-10-05 22:10:39 -05:00
9d946d1bad more stability changes 2018-10-03 21:20:49 -05:00
f4ac815d07 hopefully finished with RCon changes.
added more tests.
fixed issues from event changes (there's most definitely still issues related to that)
2018-10-02 12:39:08 -05:00
7fa0b52543 more rcon tweaks, and starting on unit tests for commands bleh 2018-09-29 21:49:12 -05:00
d45729d7e1 clean up rcon, fix a bunch of little things 2018-09-29 14:52:22 -05:00
5d93e7ac57 a ton of stuff and fix migations 2018-09-23 19:45:54 -05:00
0f9d2e92e1 fix for issue #50 2018-09-16 17:51:11 -05:00
4a46abc46d add index to time sent in EFCLientMessage, so we can retrieve faster in context view
set the maximum height of the
add link to profile on client chat
move change history into a seperate service
move around AC penalty processing
2018-09-16 15:34:16 -05:00
211 changed files with 11344 additions and 5201 deletions

4
.gitignore vendored
View File

@ -229,3 +229,7 @@ bootstrap-custom.min.css
/DiscordWebhook/env
/DiscordWebhook/config.dev.json
/GameLogServer/env
launchSettings.json
/VpnDetectionPrivate.js
/Plugins/ScriptPlugins/VpnDetectionPrivate.js
**/Master/env_master

View File

@ -8,9 +8,13 @@ namespace IW4MAdmin.Application.API.Master
public class ApiServer
{
[JsonProperty("id")]
public int Id { get; set; }
public long Id { get; set; }
[JsonProperty("ip")]
public string IPAddress { get; set; }
[JsonProperty("port")]
public short Port { get; set; }
[JsonProperty("version")]
public string Version { get; set; }
[JsonProperty("gametype")]
public string Gametype { get; set; }
[JsonProperty("map")]

View File

@ -39,12 +39,14 @@ namespace IW4MAdmin.Application.API.Master
{
ClientNum = s.ClientNum,
Game = s.GameName.ToString(),
Version = s.Version,
Gametype = s.Gametype,
Hostname = s.Hostname,
Map = s.CurrentMap.Name,
MaxClientNum = s.MaxClients,
Id = s.GetHashCode(),
Port = (short)s.GetPort()
Id = s.EndPoint,
Port = (short)s.GetPort(),
IPAddress = s.IP
}).ToList()
};

View File

@ -3,14 +3,15 @@
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>netcoreapp2.1</TargetFramework>
<RuntimeFrameworkVersion>2.1.5</RuntimeFrameworkVersion>
<MvcRazorExcludeRefAssembliesFromPublish>false</MvcRazorExcludeRefAssembliesFromPublish>
<PackageId>RaidMax.IW4MAdmin.Application</PackageId>
<Version>2.1.9.1</Version>
<Version>2.2.4.3</Version>
<Authors>RaidMax</Authors>
<Company>Forever None</Company>
<Product>IW4MAdmin</Product>
<Description>IW4MAdmin is a complete server administration tool for IW4x and most Call of Duty® dedicated server</Description>
<Copyright>2018</Copyright>
<Copyright>2019</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>
@ -30,6 +31,8 @@
<PropertyGroup>
<ServerGarbageCollection>true</ServerGarbageCollection>
<TieredCompilation>true</TieredCompilation>
<AssemblyVersion>2.2.4.3</AssemblyVersion>
<FileVersion>2.2.4.3</FileVersion>
</PropertyGroup>
<ItemGroup>
@ -76,7 +79,7 @@
</ItemGroup>
<ItemGroup>
<PackageReference Update="Microsoft.NETCore.App" />
<PackageReference Update="Microsoft.NETCore.App" Version="2.1.5" />
</ItemGroup>
<Target Name="PreBuild" BeforeTargets="PreBuildEvent">
@ -91,6 +94,6 @@
</Target>
<Target Name="PostPublish" AfterTargets="Publish">
<Exec Command="call $(ProjectDir)BuildScripts\PostPublish.bat $(ProjectDir)..\ $(ProjectDir) $(TargetDir) $(OutDir)" />
<Exec Command="call $(ProjectDir)BuildScripts\PostPublish.bat $(ProjectDir)..\ $(ProjectDir) $(TargetDir) $(ConfigurationName)" />
</Target>
</Project>

View File

@ -1,28 +1,23 @@
using System;
using IW4MAdmin.Application.API.Master;
using IW4MAdmin.Application.EventParsers;
using IW4MAdmin.Application.RconParsers;
using SharedLibraryCore;
using SharedLibraryCore.Commands;
using SharedLibraryCore.Configuration;
using SharedLibraryCore.Database;
using SharedLibraryCore.Database.Models;
using SharedLibraryCore.Events;
using SharedLibraryCore.Exceptions;
using SharedLibraryCore.Helpers;
using SharedLibraryCore.Interfaces;
using SharedLibraryCore.Services;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.IO;
using System.Threading.Tasks;
using SharedLibraryCore;
using SharedLibraryCore.Interfaces;
using SharedLibraryCore.Commands;
using SharedLibraryCore.Helpers;
using SharedLibraryCore.Exceptions;
using SharedLibraryCore.Objects;
using SharedLibraryCore.Services;
using IW4MAdmin.Application.API;
using Microsoft.Extensions.Configuration;
using WebfrontCore;
using SharedLibraryCore.Configuration;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using System.Text;
using IW4MAdmin.Application.API.Master;
using System.Reflection;
using SharedLibraryCore.Database;
using SharedLibraryCore.Events;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
namespace IW4MAdmin.Application
{
@ -30,8 +25,8 @@ namespace IW4MAdmin.Application
{
private List<Server> _servers;
public List<Server> Servers => _servers.OrderByDescending(s => s.ClientNum).ToList();
public Dictionary<int, Player> PrivilegedClients { get; set; }
public ILogger Logger { get; private set; }
public Dictionary<int, EFClient> PrivilegedClients { get; set; }
public ILogger Logger => GetLogger(0);
public bool Running { get; private set; }
public bool IsInitialized { get; private set; }
// define what the delagate function looks like
@ -39,6 +34,10 @@ namespace IW4MAdmin.Application
// expose the event handler so we can execute the events
public OnServerEventEventHandler OnServerEvent { get; set; }
public DateTime StartTime { get; private set; }
public string Version => Assembly.GetEntryAssembly().GetName().Version.ToString();
public IList<IRConParser> AdditionalRConParsers { get; }
public IList<IEventParser> AdditionalEventParsers { get; }
static ApplicationManager Instance;
readonly List<AsyncStatus> TaskStatuses;
@ -51,10 +50,11 @@ namespace IW4MAdmin.Application
GameEventHandler Handler;
ManualResetEventSlim OnQuit;
readonly IPageList PageList;
readonly SemaphoreSlim ProcessingEvent = new SemaphoreSlim(1, 1);
readonly Dictionary<long, ILogger> Loggers = new Dictionary<long, ILogger>();
private ApplicationManager()
{
Logger = new Logger($@"{Utilities.OperatingDirectory}IW4MAdmin.log");
_servers = new List<Server>();
Commands = new List<Command>();
TaskStatuses = new List<AsyncStatus>();
@ -62,90 +62,38 @@ namespace IW4MAdmin.Application
ClientSvc = new ClientService();
AliasSvc = new AliasService();
PenaltySvc = new PenaltyService();
PrivilegedClients = new Dictionary<int, Player>();
ConfigHandler = new BaseConfigurationHandler<ApplicationConfiguration>("IW4MAdminSettings");
StartTime = DateTime.UtcNow;
OnQuit = new ManualResetEventSlim();
PageList = new PageList();
OnServerEvent += OnServerEventAsync;
AdditionalEventParsers = new List<IEventParser>();
AdditionalRConParsers = new List<IRConParser>();
OnServerEvent += OnGameEvent;
OnServerEvent += EventApi.OnGameEvent;
}
private async void OnServerEventAsync(object sender, GameEventArgs args)
private async void OnGameEvent(object sender, GameEventArgs args)
{
#if DEBUG == true
Logger.WriteDebug($"Entering event process for {args.Event.Id}");
#endif
var newEvent = args.Event;
// the event has failed already
if (newEvent.Failed)
{
goto skip;
}
try
{
// if the origin client is not in an authorized state (detected by RCon) don't execute the event
if (GameEvent.ShouldOriginEventBeDelayed(newEvent))
{
Logger.WriteDebug($"Delaying origin execution of event type {newEvent.Type} for {newEvent.Origin} because they are not authed");
if (newEvent.Type == GameEvent.EventType.Command)
{
await newEvent.Origin.Tell(Utilities.CurrentLocalization.LocalizationIndex["SERVER_DELAYED_EVENT_WAIT"]);
}
// offload it to the player to keep
newEvent.Origin.DelayedEvents.Enqueue(newEvent);
newEvent.OnProcessed.Set();
return;
}
// if the target client is not in an authorized state (detected by RCon) don't execute the event
if (GameEvent.ShouldTargetEventBeDelayed(newEvent))
{
Logger.WriteDebug($"Delaying target execution of event type {newEvent.Type} for {newEvent.Target} because they are not authed");
// offload it to the player to keep
newEvent.Target.DelayedEvents.Enqueue(newEvent);
newEvent.OnProcessed.Set();
return;
}
// todo: this is a hacky mess
if (newEvent.Origin?.DelayedEvents.Count > 0 &&
newEvent.Origin?.State == Player.ClientState.Connected)
{
var events = newEvent.Origin.DelayedEvents;
// add the delayed event to the queue
while (events.Count > 0)
{
var oldEvent = events.Dequeue();
var e = new GameEvent()
{
Type = oldEvent.Type,
Origin = newEvent.Origin,
Data = oldEvent.Data,
Extra = oldEvent.Extra,
Owner = oldEvent.Owner,
Message = oldEvent.Message,
Target = oldEvent.Target,
Remote = oldEvent.Remote
};
e.Origin = newEvent.Origin;
// check if the target was assigned
if (e.Target != null)
{
// update the target incase they left or have newer info
e.Target = newEvent.Owner.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;
}
}
Logger.WriteDebug($"Adding delayed event of type {e.Type} for {e.Origin} back for processing");
this.GetEventHandler().AddEvent(e);
}
}
await newEvent.Owner.ExecuteEvent(newEvent);
// save the event info to the database
var changeHistorySvc = new ChangeHistoryService();
await changeHistorySvc.Add(args.Event);
#if DEBUG
Logger.WriteDebug($"Processed event with id {newEvent.Id}");
#endif
@ -154,25 +102,32 @@ namespace IW4MAdmin.Application
// this happens if a plugin requires login
catch (AuthorizationException ex)
{
await newEvent.Origin.Tell($"{Utilities.CurrentLocalization.LocalizationIndex["COMMAND_NOTAUTHORIZED"]} - {ex.Message}");
newEvent.FailReason = GameEvent.EventFailReason.Permission;
newEvent.Origin.Tell($"{Utilities.CurrentLocalization.LocalizationIndex["COMMAND_NOTAUTHORIZED"]} - {ex.Message}");
}
catch (NetworkException ex)
{
newEvent.FailReason = GameEvent.EventFailReason.Exception;
Logger.WriteError(ex.Message);
Logger.WriteDebug(ex.GetExceptionInfo());
}
catch (ServerException ex)
{
newEvent.FailReason = GameEvent.EventFailReason.Exception;
Logger.WriteWarning(ex.Message);
}
catch (Exception ex)
{
newEvent.FailReason = GameEvent.EventFailReason.Exception;
Logger.WriteError($"{Utilities.CurrentLocalization.LocalizationIndex["SERVER_ERROR_EXCEPTION"]} {newEvent.Owner}");
Logger.WriteDebug("Error Message: " + ex.Message);
Logger.WriteDebug("Error Trace: " + ex.StackTrace);
Logger.WriteDebug(ex.GetExceptionInfo());
}
skip:
// tell anyone waiting for the output that we're done
newEvent.OnProcessed.Set();
}
@ -195,7 +150,7 @@ namespace IW4MAdmin.Application
public async Task UpdateServerStates()
{
// store the server hash code and task for it
var runningUpdateTasks = new Dictionary<int, Task>();
var runningUpdateTasks = new Dictionary<long, Task>();
while (Running)
{
@ -215,16 +170,16 @@ namespace IW4MAdmin.Application
}
// remove the update tasks as they have completd
foreach (int serverId in serverTasksToRemove)
foreach (long serverId in serverTasksToRemove)
{
runningUpdateTasks.Remove(serverId);
}
// select the servers where the tasks have completed
var serverIds = Servers.Select(s => s.GetHashCode()).Except(runningUpdateTasks.Select(r => r.Key)).ToList();
foreach (var server in Servers.Where(s => serverIds.Contains(s.GetHashCode())))
var serverIds = Servers.Select(s => s.EndPoint).Except(runningUpdateTasks.Select(r => r.Key)).ToList();
foreach (var server in Servers.Where(s => serverIds.Contains(s.EndPoint)))
{
runningUpdateTasks.Add(server.GetHashCode(), Task.Run(async () =>
runningUpdateTasks.Add(server.EndPoint, Task.Run(async () =>
{
try
{
@ -234,8 +189,7 @@ namespace IW4MAdmin.Application
catch (Exception e)
{
Logger.WriteWarning($"Failed to update status for {server}");
Logger.WriteDebug($"Exception: {e.Message}");
Logger.WriteDebug($"StackTrace: {e.StackTrace}");
Logger.WriteDebug(e.GetExceptionInfo());
}
}));
}
@ -245,56 +199,17 @@ namespace IW4MAdmin.Application
ThreadPool.GetAvailableThreads(out int availableThreads, out int m);
Logger.WriteDebug($"There are {workerThreads - availableThreads} active threading tasks");
#endif
#if DEBUG
await Task.Delay(10000);
#else
await Task.Delay(ConfigHandler.Configuration().RConPollRate);
#endif
}
// trigger the event processing loop to end
SetHasEvent();
}
public async Task Init()
{
Running = true;
#region DATABASE
using (var db = new DatabaseContext(GetApplicationSettings().Configuration()?.ConnectionString))
{
await new ContextSeed(db).Seed();
}
// todo: optimize this
var ipList = (await ClientSvc.Find(c => c.Level > Player.Permission.Trusted))
.Select(c => new
{
c.Password,
c.PasswordSalt,
c.ClientId,
c.Level,
c.Name
});
foreach (var a in ipList)
{
try
{
PrivilegedClients.Add(a.ClientId, new Player()
{
Name = a.Name,
ClientId = a.ClientId,
Level = a.Level,
PasswordSalt = a.PasswordSalt,
Password = a.Password
});
}
catch (ArgumentException)
{
continue;
}
}
#endregion
#region CONFIG
var config = ConfigHandler.Configuration();
@ -335,18 +250,31 @@ namespace IW4MAdmin.Application
if (string.IsNullOrEmpty(config.WebfrontBindUrl))
{
config.WebfrontBindUrl = "http://127.0.0.1:1624";
config.WebfrontBindUrl = "http://0.0.0.0:1624";
await ConfigHandler.Save();
}
}
else if (config.Servers.Count == 0)
{
throw new ServerException("A server configuration in IW4MAdminSettings.json is invalid");
}
Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);
Utilities.EncodingType = Encoding.GetEncoding(!string.IsNullOrEmpty(config.CustomParserEncoding) ? config.CustomParserEncoding : "windows-1252");
#endregion
#region DATABASE
using (var db = new DatabaseContext(GetApplicationSettings().Configuration()?.ConnectionString,
GetApplicationSettings().Configuration()?.DatabaseProvider))
{
await new ContextSeed(db).Seed();
}
PrivilegedClients = (await ClientSvc.GetPrivilegedClients()).ToDictionary(_client => _client.ClientId);
#endregion
#region PLUGINS
SharedLibraryCore.Plugins.PluginImporter.Load(this);
@ -357,18 +285,19 @@ namespace IW4MAdmin.Application
await Plugin.OnLoadAsync(this);
}
catch (Exception e)
catch (Exception ex)
{
Logger.WriteError($"{Utilities.CurrentLocalization.LocalizationIndex["SERVER_ERROR_PLUGIN"]} {Plugin.Name}");
Logger.WriteDebug($"Exception: {e.Message}");
Logger.WriteDebug($"Stack Trace: {e.StackTrace}");
Logger.WriteDebug(ex.GetExceptionInfo());
}
}
#endregion
#region COMMANDS
if (ClientSvc.GetOwners().Result.Count == 0)
{
Commands.Add(new COwner());
}
Commands.Add(new CQuit());
Commands.Add(new CKick());
@ -409,7 +338,9 @@ namespace IW4MAdmin.Application
Commands.Add(new CNextMap());
foreach (Command C in SharedLibraryCore.Plugins.PluginImporter.ActiveCommands)
{
Commands.Add(C);
}
#endregion
#region INIT
@ -444,7 +375,9 @@ namespace IW4MAdmin.Application
{
Logger.WriteError($"{Utilities.CurrentLocalization.LocalizationIndex["SERVER_ERROR_UNFIXABLE"]} [{Conf.IPAddress}:{Conf.Port}]");
if (e.GetType() == typeof(DvarException))
{
Logger.WriteDebug($"{Utilities.CurrentLocalization.LocalizationIndex["SERVER_ERROR_DVAR"]} {(e as DvarException).Data["dvar_name"]} ({Utilities.CurrentLocalization.LocalizationIndex["SERVER_ERROR_DVAR_HELP"]})");
}
else if (e.GetType() == typeof(NetworkException))
{
Logger.WriteDebug(e.Message);
@ -527,13 +460,8 @@ namespace IW4MAdmin.Application
public void Start()
{
// this needs to be run seperately from the main thread
#pragma warning disable CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed
#if !DEBUG
// start heartbeat
Task.Run(() => SendHeartbeat(new HeartbeatState()));
#endif
Task.Run(() => UpdateServerStates());
#pragma warning restore CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed
var _ = Task.Run(() => SendHeartbeat(new HeartbeatState()));
_ = Task.Run(() => UpdateServerStates());
while (Running)
{
@ -546,14 +474,31 @@ namespace IW4MAdmin.Application
public void Stop()
{
Running = false;
// trigger the event processing loop to end
SetHasEvent();
}
public ILogger GetLogger()
public ILogger GetLogger(long serverId)
{
return Logger;
if (Loggers.ContainsKey(serverId))
{
return Loggers[serverId];
}
else
{
Logger newLogger;
if (serverId == 0)
{
newLogger = new Logger("IW4MAdmin-Manager");
}
else
{
newLogger = new Logger($"IW4MAdmin-Server-{serverId}");
}
Loggers.Add(serverId, newLogger);
return newLogger;
}
}
public IList<MessageToken> GetMessageTokens()
@ -561,31 +506,69 @@ namespace IW4MAdmin.Application
return MessageTokens;
}
public IList<Player> GetActiveClients()
public IList<EFClient> GetActiveClients()
{
var ActiveClients = new List<Player>();
foreach (var server in _servers)
ActiveClients.AddRange(server.Players.Where(p => p != null));
return ActiveClients;
return _servers.SelectMany(s => s.Clients).Where(p => p != null).ToList();
}
public ClientService GetClientService() => ClientSvc;
public AliasService GetAliasService() => AliasSvc;
public PenaltyService GetPenaltyService() => PenaltySvc;
public IConfigurationHandler<ApplicationConfiguration> GetApplicationSettings() => ConfigHandler;
public IDictionary<int, Player> GetPrivilegedClients() => PrivilegedClients;
public bool ShutdownRequested() => !Running;
public IEventHandler GetEventHandler() => Handler;
public ClientService GetClientService()
{
return ClientSvc;
}
public AliasService GetAliasService()
{
return AliasSvc;
}
public PenaltyService GetPenaltyService()
{
return PenaltySvc;
}
public IConfigurationHandler<ApplicationConfiguration> GetApplicationSettings()
{
return ConfigHandler;
}
public IDictionary<int, EFClient> GetPrivilegedClients()
{
return PrivilegedClients;
}
public bool ShutdownRequested()
{
return !Running;
}
public IEventHandler GetEventHandler()
{
return Handler;
}
public void SetHasEvent()
{
OnQuit.Set();
}
public IList<Assembly> GetPluginAssemblies() => SharedLibraryCore.Plugins.PluginImporter.PluginAssemblies;
public IList<Assembly> GetPluginAssemblies()
{
return SharedLibraryCore.Plugins.PluginImporter.PluginAssemblies;
}
public IPageList GetPageList() => PageList;
public IPageList GetPageList()
{
return PageList;
}
public IRConParser GenerateDynamicRConParser()
{
return new DynamicRConParser();
}
public IEventParser GenerateDynamicEventParser()
{
return new DynamicEventParser();
}
}
}

View File

@ -1,6 +1,8 @@
set SolutionDir=%1
set ProjectDir=%2
set TargetDir=%3
set CurrentConfiguration=%4
SET COPYCMD=/Y
echo Deleting extra language files
@ -54,9 +56,52 @@ del "%SolutionDir%Publish\Windows\*pdb"
if exist "%SolutionDir%Publish\WindowsPrerelease\web.config" del "%SolutionDir%Publish\WindowsPrerelease\web.config"
del "%SolutionDir%Publish\WindowsPrerelease\*pdb"
echo making start scripts
@echo dotnet IW4MAdmin.dll > "%SolutionDir%Publish\WindowsPrerelease\StartIW4MAdmin.cmd"
@echo dotnet IW4MAdmin.dll > "%SolutionDir%Publish\Windows\StartIW4MAdmin.cmd"
echo setting up library folders
@(echo #!/bin/bash && echo dotnet IW4MAdmin.dll) > "%SolutionDir%Publish\WindowsPrerelease\StartIW4MAdmin.sh"
@(echo #!/bin/bash && echo dotnet IW4MAdmin.dll) > "%SolutionDir%Publish\Windows\StartIW4MAdmin.sh"
if "%CurrentConfiguration%" == "Prerelease" (
echo PR-Config
if not exist "%SolutionDir%Publish\WindowsPrerelease\Configuration" md "%SolutionDir%Publish\WindowsPrerelease\Configuration"
move "%SolutionDir%Publish\WindowsPrerelease\DefaultSettings.json" "%SolutionDir%Publish\WindowsPrerelease\Configuration\"
)
if "%CurrentConfiguration%" == "Release" (
echo R-Config
if not exist "%SolutionDir%Publish\Windows\Configuration" md "%SolutionDir%Publish\Windows\Configuration"
if exist "%SolutionDir%Publish\Windows\DefaultSettings.json" move "%SolutionDir%Publish\Windows\DefaultSettings.json" "%SolutionDir%Publish\Windows\Configuration\DefaultSettings.json"
)
if "%CurrentConfiguration%" == "Prerelease" (
echo PR-LIB
if not exist "%SolutionDir%Publish\WindowsPrerelease\Lib\" md "%SolutionDir%Publish\WindowsPrerelease\Lib\"
move "%SolutionDir%Publish\WindowsPrerelease\*.dll" "%SolutionDir%Publish\WindowsPrerelease\Lib\"
move "%SolutionDir%Publish\WindowsPrerelease\*.json" "%SolutionDir%Publish\WindowsPrerelease\Lib\"
)
if "%CurrentConfiguration%" == "Release" (
echo R-LIB
if not exist "%SolutionDir%Publish\Windows\Lib\" md "%SolutionDir%Publish\Windows\Lib\"
move "%SolutionDir%Publish\Windows\*.dll" "%SolutionDir%Publish\Windows\Lib\"
move "%SolutionDir%Publish\Windows\*.json" "%SolutionDir%Publish\Windows\Lib\"
)
if "%CurrentConfiguration%" == "Prerelease" (
echo PR-RT
move "%SolutionDir%Publish\WindowsPrerelease\runtimes" "%SolutionDir%Publish\WindowsPrerelease\Lib\runtimes"
if exist "%SolutionDir%Publish\WindowsPrerelease\refs" move "%SolutionDir%Publish\WindowsPrerelease\refs" "%SolutionDir%Publish\WindowsPrerelease\Lib\refs"
)
if "%CurrentConfiguration%" == "Release" (
echo R-RT
move "%SolutionDir%Publish\Windows\runtimes" "%SolutionDir%Publish\Windows\Lib\runtimes"
if exist "%SolutionDir%Publish\Windows\refs" move "%SolutionDir%Publish\Windows\refs" "%SolutionDir%Publish\Windows\Lib\refs"
)
echo making start scripts
@(echo dotnet Lib/IW4MAdmin.dll && echo pause) > "%SolutionDir%Publish\WindowsPrerelease\StartIW4MAdmin.cmd"
@(echo dotnet Lib/IW4MAdmin.dll && echo pause) > "%SolutionDir%Publish\Windows\StartIW4MAdmin.cmd"
@(echo #!/bin/bash && echo dotnet Lib\IW4MAdmin.dll) > "%SolutionDir%Publish\WindowsPrerelease\StartIW4MAdmin.sh"
@(echo #!/bin/bash && echo dotnet Lib\IW4MAdmin.dll) > "%SolutionDir%Publish\Windows\StartIW4MAdmin.sh"
eCHO "%CurrentConfiguration%"

View File

@ -1,82 +0,0 @@
using SharedLibraryCore.Interfaces;
using SharedLibraryCore.Objects;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace IW4MAdmin.Application.Core
{
class ClientAuthentication : IClientAuthentication
{
private Queue<Player> ClientAuthenticationQueue;
private Dictionary<long, Player> AuthenticatedClients;
public ClientAuthentication()
{
ClientAuthenticationQueue = new Queue<Player>();
AuthenticatedClients = new Dictionary<long, Player>();
}
public void AuthenticateClients(IList<Player> clients)
{
// we need to un-auth all the clients that have disconnected
var clientNetworkIds = clients.Select(c => c.NetworkId);
var clientsToRemove = AuthenticatedClients.Keys.Where(c => !clientNetworkIds.Contains(c));
// remove them
foreach (long Id in clientsToRemove.ToList())
{
AuthenticatedClients.Remove(Id);
}
// loop through the polled clients to see if they've been authenticated yet
foreach (var client in clients)
{
// they've not been authenticated
if (!AuthenticatedClients.TryGetValue(client.NetworkId, out Player value))
{
// authenticate them
client.State = Player.ClientState.Authenticated;
AuthenticatedClients.Add(client.NetworkId, client);
}
else
{
// this update their ping
// todo: this seems kinda hacky
value.Ping = client.Ping;
value.Score = client.Score;
}
}
// empty out the queue of clients detected through log
while (ClientAuthenticationQueue.Count > 0)
{
// grab each client that's connected via log
var clientToAuthenticate = ClientAuthenticationQueue.Dequeue();
// if they're not already authed, auth them
if (!AuthenticatedClients.TryGetValue(clientToAuthenticate.NetworkId, out Player value))
{
// authenticate them
clientToAuthenticate.State = Player.ClientState.Authenticated;
AuthenticatedClients.Add(clientToAuthenticate.NetworkId, clientToAuthenticate);
}
}
}
public IList<Player> GetAuthenticatedClients()
{
if (AuthenticatedClients.Values.Count > 18)
{
Program.ServerManager.GetLogger().WriteWarning($"auth client count is {AuthenticatedClients.Values.Count}, this is bad");
return AuthenticatedClients.Values.Take(18).ToList();
}
return AuthenticatedClients.Values.ToList();
}
public void RequestClientAuthentication(Player client)
{
ClientAuthenticationQueue.Enqueue(client);
}
}
}

View File

@ -25,11 +25,6 @@
"Name": "mp_rust"
},
{
"Alias": "Highrise",
"Name": "mp_highrise"
},
{
"Alias": "Terminal",
"Name": "mp_terminal"
@ -40,16 +35,6 @@
"Name": "mp_crash"
},
{
"Alias": "Skidrow",
"Name": "mp_nightshift"
},
{
"Alias": "Quarry",
"Name": "mp_quarry"
},
{
"Alias": "Afghan",
"Name": "mp_afghan"

View File

@ -0,0 +1,300 @@
using SharedLibraryCore;
using SharedLibraryCore.Database.Models;
using SharedLibraryCore.Interfaces;
using System;
using System.Linq;
using System.Text.RegularExpressions;
namespace IW4MAdmin.Application.EventParsers
{
class BaseEventParser : IEventParser
{
public BaseEventParser()
{
Configuration = new DynamicEventParserConfiguration()
{
GameDirectory = "userraw",
};
Configuration.Say.Pattern = @"^(say|sayteam);(.{8,32});([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);(.{8,32}|bot[0-9]+);([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);(.{8,32}|bot[0-9]+);([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_]{8,32}|bot[0-9]+);(-?[0-9]+);(axis|allies|world);(.{1,24});([A-Fa-f0-9_]{8,32}|bot[0-9]+)?;-?([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);
Configuration.Damage.AddMapping(ParserRegex.GroupType.TargetTeam, 4);
Configuration.Damage.AddMapping(ParserRegex.GroupType.TargetName, 5);
Configuration.Damage.AddMapping(ParserRegex.GroupType.OriginNetworkId, 6);
Configuration.Damage.AddMapping(ParserRegex.GroupType.OriginClientNumber, 7);
Configuration.Damage.AddMapping(ParserRegex.GroupType.OriginTeam, 8);
Configuration.Damage.AddMapping(ParserRegex.GroupType.OriginName, 9);
Configuration.Damage.AddMapping(ParserRegex.GroupType.Weapon, 10);
Configuration.Damage.AddMapping(ParserRegex.GroupType.Damage, 11);
Configuration.Damage.AddMapping(ParserRegex.GroupType.MeansOfDeath, 12);
Configuration.Damage.AddMapping(ParserRegex.GroupType.HitLocation, 13);
Configuration.Kill.Pattern = @"^(K);([A-Fa-f0-9_]{8,32}|bot[0-9]+);(-?[0-9]+);(axis|allies|world);(.{1,24});([A-Fa-f0-9_]{8,32}|bot[0-9]+)?;-?([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);
Configuration.Kill.AddMapping(ParserRegex.GroupType.TargetTeam, 4);
Configuration.Kill.AddMapping(ParserRegex.GroupType.TargetName, 5);
Configuration.Kill.AddMapping(ParserRegex.GroupType.OriginNetworkId, 6);
Configuration.Kill.AddMapping(ParserRegex.GroupType.OriginClientNumber, 7);
Configuration.Kill.AddMapping(ParserRegex.GroupType.OriginTeam, 8);
Configuration.Kill.AddMapping(ParserRegex.GroupType.OriginName, 9);
Configuration.Kill.AddMapping(ParserRegex.GroupType.Weapon, 10);
Configuration.Kill.AddMapping(ParserRegex.GroupType.Damage, 11);
Configuration.Kill.AddMapping(ParserRegex.GroupType.MeansOfDeath, 12);
Configuration.Kill.AddMapping(ParserRegex.GroupType.HitLocation, 13);
}
public IEventParserConfiguration Configuration { get; set; }
public string Version { get; set; } = "IW4x (v0.6.0)";
public virtual GameEvent GetEvent(Server server, string logLine)
{
logLine = Regex.Replace(logLine, @"([0-9]+:[0-9]+ |^[0-9]+ )", "").Trim();
string[] lineSplit = logLine.Split(';');
string eventType = lineSplit[0];
if (eventType == "JoinTeam")
{
var origin = server.GetClientsAsList()
.FirstOrDefault(c => c.NetworkId == lineSplit[1].ConvertLong());
return new GameEvent()
{
Type = GameEvent.EventType.JoinTeam,
Data = eventType,
Origin = origin,
Owner = server
};
}
if (eventType == "say" || eventType == "sayteam")
{
var matchResult = Regex.Match(logLine, Configuration.Say.Pattern);
if (matchResult.Success)
{
string message = matchResult
.Groups[Configuration.Say.GroupMapping[ParserRegex.GroupType.Message]]
.ToString()
.Replace("\x15", "")
.Trim();
var origin = server.GetClientsAsList()
.First(c => c.NetworkId == matchResult.Groups[Configuration.Say.GroupMapping[ParserRegex.GroupType.OriginNetworkId]].ToString().ConvertLong());
if (message[0] == '!' || message[0] == '@')
{
return new GameEvent()
{
Type = GameEvent.EventType.Command,
Data = message,
Origin = origin,
Owner = server,
Message = message
};
}
return new GameEvent()
{
Type = GameEvent.EventType.Say,
Data = message,
Origin = origin,
Owner = server,
Message = message
};
}
}
if (eventType == "K")
{
if (!server.CustomCallback)
{
var match = Regex.Match(logLine, Configuration.Kill.Pattern);
if (match.Success)
{
var origin = server.GetClientsAsList()
.First(c => c.NetworkId == match.Groups[Configuration.Kill.GroupMapping[ParserRegex.GroupType.OriginNetworkId]].ToString().ConvertLong());
var target = server.GetClientsAsList()
.First(c => c.NetworkId == match.Groups[Configuration.Kill.GroupMapping[ParserRegex.GroupType.TargetNetworkId]].ToString().ConvertLong());
return new GameEvent()
{
Type = GameEvent.EventType.Kill,
Data = logLine,
Origin = origin,
Target = target,
Owner = server
};
}
}
}
if (eventType == "ScriptKill")
{
var origin = server.GetClientsAsList().First(c => c.NetworkId == lineSplit[1].ConvertLong());
var target = server.GetClientsAsList().First(c => c.NetworkId == lineSplit[2].ConvertLong());
return new GameEvent()
{
Type = GameEvent.EventType.ScriptKill,
Data = logLine,
Origin = origin,
Target = target,
Owner = server
};
}
if (eventType == "ScriptDamage")
{
var origin = server.GetClientsAsList().First(c => c.NetworkId == lineSplit[1].ConvertLong());
var target = server.GetClientsAsList().First(c => c.NetworkId == lineSplit[2].ConvertLong());
return new GameEvent()
{
Type = GameEvent.EventType.ScriptDamage,
Data = logLine,
Origin = origin,
Target = target,
Owner = server
};
}
// damage
if (eventType == "D")
{
if (!server.CustomCallback)
{
var regexMatch = Regex.Match(logLine, Configuration.Damage.Pattern);
if (regexMatch.Success)
{
var origin = server.GetClientsAsList()
.First(c => c.NetworkId == regexMatch.Groups[Configuration.Damage.GroupMapping[ParserRegex.GroupType.OriginNetworkId]].ToString().ConvertLong());
var target = server.GetClientsAsList()
.First(c => c.NetworkId == regexMatch.Groups[Configuration.Damage.GroupMapping[ParserRegex.GroupType.TargetNetworkId]].ToString().ConvertLong());
return new GameEvent()
{
Type = GameEvent.EventType.Damage,
Data = eventType,
Origin = origin,
Target = target,
Owner = server
};
}
}
}
// join
if (eventType == "J")
{
var regexMatch = Regex.Match(logLine, Configuration.Join.Pattern);
if (regexMatch.Success)
{
return new GameEvent()
{
Type = GameEvent.EventType.PreConnect,
Data = logLine,
Owner = server,
Origin = new EFClient()
{
CurrentAlias = new EFAlias()
{
Active = false,
Name = regexMatch.Groups[Configuration.Join.GroupMapping[ParserRegex.GroupType.OriginName]].ToString().StripColors(),
},
NetworkId = regexMatch.Groups[Configuration.Join.GroupMapping[ParserRegex.GroupType.OriginNetworkId]].ToString().ConvertLong(),
ClientNumber = Convert.ToInt32(regexMatch.Groups[Configuration.Join.GroupMapping[ParserRegex.GroupType.OriginClientNumber]].ToString()),
State = EFClient.ClientState.Connecting,
CurrentServer = server
}
};
}
}
if (eventType == "Q")
{
var regexMatch = Regex.Match(logLine, Configuration.Quit.Pattern);
if (regexMatch.Success)
{
return new GameEvent()
{
Type = GameEvent.EventType.PreDisconnect,
Data = logLine,
Owner = server,
Origin = new EFClient()
{
CurrentAlias = new EFAlias()
{
Active = false,
Name = regexMatch.Groups[Configuration.Quit.GroupMapping[ParserRegex.GroupType.OriginName]].ToString().StripColors()
},
NetworkId = regexMatch.Groups[Configuration.Quit.GroupMapping[ParserRegex.GroupType.OriginNetworkId]].ToString().ConvertLong(),
ClientNumber = Convert.ToInt32(regexMatch.Groups[Configuration.Quit.GroupMapping[ParserRegex.GroupType.OriginClientNumber]].ToString()),
State = EFClient.ClientState.Disconnecting
}
};
}
}
if (eventType.Contains("ExitLevel"))
{
return new GameEvent()
{
Type = GameEvent.EventType.MapEnd,
Data = lineSplit[0],
Origin = Utilities.IW4MAdminClient(server),
Target = Utilities.IW4MAdminClient(server),
Owner = server
};
}
if (eventType.Contains("InitGame"))
{
string dump = eventType.Replace("InitGame: ", "");
return new GameEvent()
{
Type = GameEvent.EventType.MapChange,
Data = lineSplit[0],
Origin = Utilities.IW4MAdminClient(server),
Target = Utilities.IW4MAdminClient(server),
Owner = server,
Extra = dump.DictionaryFromKeyValue()
};
}
return new GameEvent()
{
Type = GameEvent.EventType.Unknown,
Origin = Utilities.IW4MAdminClient(server),
Target = Utilities.IW4MAdminClient(server),
Owner = server
};
}
}
}

View File

@ -0,0 +1,15 @@
using System;
using System.Collections.Generic;
using System.Text;
using static SharedLibraryCore.Server;
namespace IW4MAdmin.Application.EventParsers
{
/// <summary>
/// empty generic implementation of the IEventParserConfiguration
/// allows script plugins to generate dynamic event parsers
/// </summary>
sealed internal class DynamicEventParser : BaseEventParser
{
}
}

View File

@ -0,0 +1,19 @@
using SharedLibraryCore.Interfaces;
namespace IW4MAdmin.Application.EventParsers
{
/// <summary>
/// generic implementation of the IEventParserConfiguration
/// allows script plugins to generate dynamic configurations
/// </summary>
sealed internal class DynamicEventParserConfiguration : IEventParserConfiguration
{
public string GameDirectory { get; set; }
public ParserRegex Say { get; set; } = new ParserRegex();
public ParserRegex Join { get; set; } = new ParserRegex();
public ParserRegex Quit { get; set; } = new ParserRegex();
public ParserRegex Kill { get; set; } = new ParserRegex();
public ParserRegex Damage { get; set; } = new ParserRegex();
public ParserRegex Action { get; set; } = new ParserRegex();
}
}

View File

@ -1,11 +0,0 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace IW4MAdmin.Application.EventParsers
{
class IW3EventParser : IW4EventParser
{
public override string GetGameDir() => "main";
}
}

View File

@ -1,239 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text.RegularExpressions;
using SharedLibraryCore;
using SharedLibraryCore.Interfaces;
using SharedLibraryCore.Objects;
namespace IW4MAdmin.Application.EventParsers
{
class IW4EventParser : IEventParser
{
private const string SayRegex = @"(say|sayteam);(.{1,32});([0-9]+)(.*);(.*)";
public virtual GameEvent GetEvent(Server server, string logLine)
{
logLine = Regex.Replace(logLine, @"([0-9]+:[0-9]+ |^[0-9]+ )", "").Trim();
string[] lineSplit = logLine.Split(';');
string eventType = lineSplit[0];
if (eventType == "JoinTeam")
{
var origin = server.GetPlayersAsList().FirstOrDefault(c => c.NetworkId == lineSplit[1].ConvertLong());
return new GameEvent()
{
Type = GameEvent.EventType.JoinTeam,
Data = eventType,
Origin = origin,
Owner = server
};
}
if (eventType == "say" || eventType == "sayteam")
{
var matchResult = Regex.Match(logLine, SayRegex);
if (matchResult.Success)
{
string message = matchResult.Groups[5].ToString()
.Replace("\x15", "")
.Trim();
var origin = server.GetPlayersAsList().First(c => c.ClientNumber == Utilities.ClientIdFromString(lineSplit, 2));
if (message[0] == '!' || message[0] == '@')
{
return new GameEvent()
{
Type = GameEvent.EventType.Command,
Data = message,
Origin = origin,
Owner = server,
Message = message
};
}
return new GameEvent()
{
Type = GameEvent.EventType.Say,
Data = message,
Origin = origin,
Owner = server,
Message = message
};
}
}
if (eventType == "K")
{
if (!server.CustomCallback)
{
var origin = server.GetPlayersAsList().First(c => c.ClientNumber == Utilities.ClientIdFromString(lineSplit, 6));
var target = server.GetPlayersAsList().First(c => c.ClientNumber == Utilities.ClientIdFromString(lineSplit, 2));
return new GameEvent()
{
Type = GameEvent.EventType.Kill,
Data = logLine,
Origin = origin,
Target = target,
Owner = server
};
}
}
if (eventType == "ScriptKill")
{
var origin = server.GetPlayersAsList().First(c => c.NetworkId == lineSplit[1].ConvertLong());
var target = server.GetPlayersAsList().First(c => c.NetworkId == lineSplit[2].ConvertLong());
return new GameEvent()
{
Type = GameEvent.EventType.ScriptKill,
Data = logLine,
Origin = origin,
Target = target,
Owner = server
};
}
if (eventType == "ScriptDamage")
{
var origin = server.GetPlayersAsList().First(c => c.NetworkId == lineSplit[1].ConvertLong());
var target = server.GetPlayersAsList().First(c => c.NetworkId == lineSplit[2].ConvertLong());
return new GameEvent()
{
Type = GameEvent.EventType.ScriptDamage,
Data = logLine,
Origin = origin,
Target = target,
Owner = server
};
}
// damage
if (eventType == "D")
{
if (!server.CustomCallback)
{
if (Regex.Match(eventType, @"^(D);((?:bot[0-9]+)|(?:[A-Z]|[0-9])+);([0-9]+);(axis|allies);(.+);((?:[A-Z]|[0-9])+);([0-9]+);(axis|allies);(.+);((?:[0-9]+|[a-z]+|_)+);([0-9]+);((?:[A-Z]|_)+);((?:[a-z]|_)+)$").Success)
{
var origin = server.GetPlayersAsList().First(c => c.NetworkId == lineSplit[5].ConvertLong());
var target = server.GetPlayersAsList().First(c => c.NetworkId == lineSplit[1].ConvertLong());
return new GameEvent()
{
Type = GameEvent.EventType.Damage,
Data = eventType,
Origin = origin,
Target = target,
Owner = server
};
}
}
}
// join
if (eventType == "J")
{
var regexMatch = Regex.Match(logLine, @"^(J;)(.{1,32});([0-9]+);(.*)$");
if (regexMatch.Success)
{
return new GameEvent()
{
Type = GameEvent.EventType.Join,
Data = logLine,
Owner = server,
Origin = new Player()
{
Name = regexMatch.Groups[4].ToString().StripColors(),
NetworkId = regexMatch.Groups[2].ToString().ConvertLong(),
ClientNumber = Convert.ToInt32(regexMatch.Groups[3].ToString()),
State = Player.ClientState.Connecting,
CurrentServer = server
}
};
}
}
//if (eventType == "Q")
//{
// var regexMatch = Regex.Match(logLine, @"^(Q;)(.{1,32});([0-9]+);(.*)$");
// if (regexMatch.Success)
// {
// return new GameEvent()
// {
// Type = GameEvent.EventType.Quit,
// Data = logLine,
// Owner = server,
// Origin = new Player()
// {
// Name = regexMatch.Groups[4].ToString().StripColors(),
// NetworkId = regexMatch.Groups[2].ToString().ConvertLong(),
// ClientNumber = Convert.ToInt32(regexMatch.Groups[3].ToString()),
// State = Player.ClientState.Connecting
// }
// };
// }
//}
if (eventType.Contains("ExitLevel"))
{
return new GameEvent()
{
Type = GameEvent.EventType.MapEnd,
Data = lineSplit[0],
Origin = new Player()
{
ClientId = 1
},
Target = new Player()
{
ClientId = 1
},
Owner = server
};
}
if (eventType.Contains("InitGame"))
{
string dump = eventType.Replace("InitGame: ", "");
return new GameEvent()
{
Type = GameEvent.EventType.MapChange,
Data = lineSplit[0],
Origin = new Player()
{
ClientId = 1
},
Target = new Player()
{
ClientId = 1
},
Owner = server,
Extra = dump.DictionaryFromKeyValue()
};
}
return new GameEvent()
{
Type = GameEvent.EventType.Unknown,
Origin = new Player()
{
ClientId = 1
},
Target = new Player()
{
ClientId = 1
},
Owner = server
};
}
// other parsers can derive from this parser so we make it virtual
public virtual string GetGameDir() => "userraw";
}
}

View File

@ -1,53 +0,0 @@
using SharedLibraryCore;
using SharedLibraryCore.Interfaces;
using SharedLibraryCore.Objects;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
namespace IW4MAdmin.Application.EventParsers
{
class IW5EventParser : IW4EventParser
{
public override string GetGameDir() => "logs";
public override GameEvent GetEvent(Server server, string logLine)
{
string cleanedEventLine = Regex.Replace(logLine, @"[0-9]+:[0-9]+\ ", "").Trim();
if (cleanedEventLine.Contains("J;"))
{
string[] lineSplit = cleanedEventLine.Split(';');
int clientNum = Int32.Parse(lineSplit[2]);
var player = new Player()
{
NetworkId = lineSplit[1].ConvertLong(),
ClientNumber = clientNum,
Name = lineSplit[3]
};
return new GameEvent()
{
Type = GameEvent.EventType.Join,
Origin = new Player()
{
ClientId = 1
},
Target = new Player()
{
ClientId = 1
},
Owner = server,
Extra = player
};
}
else
return base.GetEvent(server, logLine);
}
}
}

View File

@ -1,11 +0,0 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace IW4MAdmin.Application.EventParsers
{
class T5MEventParser : IW4EventParser
{
public override string GetGameDir() => "v2";
}
}

View File

@ -1,16 +0,0 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text.RegularExpressions;
using SharedLibraryCore;
using SharedLibraryCore.Interfaces;
using SharedLibraryCore.Objects;
namespace IW4MAdmin.Application.EventParsers
{
class T6MEventParser : IW4EventParser
{
public override string GetGameDir() => $"t6r{Path.DirectorySeparatorChar}data";
}
}

View File

@ -9,112 +9,15 @@ namespace IW4MAdmin.Application
{
class GameEventHandler : IEventHandler
{
static long NextEventId = 1;
readonly IManager Manager;
readonly SortedList<long, GameEvent> OutOfOrderEvents;
readonly SemaphoreSlim IsProcessingEvent;
public GameEventHandler(IManager mgr)
{
Manager = mgr;
OutOfOrderEvents = new SortedList<long, GameEvent>();
IsProcessingEvent = new SemaphoreSlim(1, 1);
}
public void AddEvent(GameEvent gameEvent)
{
// IsProcessingEvent.Wait();
((Manager as ApplicationManager).OnServerEvent)(this, new GameEventArgs(null, false, gameEvent));
// IsProcessingEvent.Release(1);
//if (gameEvent.Type == GameEvent.EventType.Connect)
//{
// IsProcessingEvent.Wait();
// if (!gameEvent.OnProcessed.Wait(10 * 1000))
// {
// Manager.GetLogger().WriteError($"{Utilities.CurrentLocalization.LocalizationIndex["SERVER_ERROR_COMMAND_TIMEOUT"]} [{gameEvent.Id}, {gameEvent.Type}]");
// }
// IsProcessingEvent.Release(1);
//}
return;
#if DEBUG
Manager.GetLogger().WriteDebug($"Got new event of type {gameEvent.Type} for {gameEvent.Owner} with id {gameEvent.Id}");
#endif
GameEvent sortedEvent = null;
lock (OutOfOrderEvents)
{
sortedEvent = OutOfOrderEvents.Values.FirstOrDefault();
while (sortedEvent?.Id == Interlocked.Read(ref NextEventId))
{
if (OutOfOrderEvents.Count > 0)
{
OutOfOrderEvents.RemoveAt(0);
}
AddEvent(sortedEvent);
sortedEvent = OutOfOrderEvents.Values.FirstOrDefault();
}
}
// both the gameEvent Id and the LastEventId are thread safe because we want to synchronize when the
// event occurs
if (gameEvent.Id == Interlocked.Read(ref NextEventId))
{
//#if DEBUG == true
// Manager.GetLogger().WriteDebug($"sent event with id {gameEvent.Id} to be processed");
// IsProcessingEvent.Wait();
//#else
// if (GameEvent.IsEventTimeSensitive(gameEvent) &&
// !IsProcessingEvent.Wait(30 * 1000))
// {
// Manager.GetLogger().WriteError($"{Utilities.CurrentLocalization.LocalizationIndex["SERVER_ERROR_COMMAND_TIMEOUT"]} [{gameEvent.Id}, {gameEvent.Type}]");
// }
//#endif
((Manager as ApplicationManager).OnServerEvent)(this, new GameEventArgs(null, false, gameEvent));
//if (GameEvent.IsEventTimeSensitive(gameEvent))
//{
// if( !gameEvent.OnProcessed.Wait(30 * 1000))
// {
// Manager.GetLogger().WriteError($"{Utilities.CurrentLocalization.LocalizationIndex["SERVER_ERROR_EVENT_TIMEOUT"]} [{gameEvent.Id}, {gameEvent.Type}]");
// }
//}
Interlocked.Increment(ref NextEventId);
//#if DEBUG == true
// gameEvent.OnProcessed.Wait();
//#else
// if (GameEvent.IsEventTimeSensitive(gameEvent) &&
// !gameEvent.OnProcessed.Wait(30 * 1000))
// {
// Manager.GetLogger().WriteError($"{Utilities.CurrentLocalization.LocalizationIndex["SERVER_ERROR_EVENT_TIMEOUT"]} [{gameEvent.Id}, {gameEvent.Type}]");
// }
//#endif
// Interlocked.Increment(ref NextEventId);
// if (GameEvent.IsEventTimeSensitive(gameEvent))
// {
// IsProcessingEvent.Release();
// }
}
// a "newer" event has been added before and "older" one has been added (due to threads and context switching)
// so me must wait until the next expected one arrives
else
{
#if DEBUG == true
Manager.GetLogger().WriteWarning("Incoming event is out of order");
Manager.GetLogger().WriteDebug($"Expected event Id is {Interlocked.Read(ref NextEventId)}, but got {gameEvent.Id} instead");
#endif
// this prevents multiple threads from adding simultaneously
lock (OutOfOrderEvents)
{
if (!OutOfOrderEvents.TryGetValue(gameEvent.Id, out GameEvent discard))
{
OutOfOrderEvents.Add(gameEvent.Id, gameEvent);
}
}
}
}
}
}

View File

@ -1,8 +1,6 @@
using SharedLibraryCore;
using SharedLibraryCore.Interfaces;
using System;
using System.Collections.Generic;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
@ -37,7 +35,7 @@ namespace IW4MAdmin.Application.IO
Server = server;
}
public void PollForChanges()
public async Task PollForChanges()
{
while (!Server.Manager.ShutdownRequested())
{
@ -45,12 +43,12 @@ namespace IW4MAdmin.Application.IO
{
try
{
UpdateLogEvents();
await UpdateLogEvents();
}
catch (Exception e)
{
Server.Logger.WriteWarning($"Failed to update log event for {Server.GetHashCode()}");
Server.Logger.WriteWarning($"Failed to update log event for {Server.EndPoint}");
Server.Logger.WriteDebug($"Exception: {e.Message}");
Server.Logger.WriteDebug($"StackTrace: {e.StackTrace}");
}
@ -59,7 +57,7 @@ namespace IW4MAdmin.Application.IO
}
}
private void UpdateLogEvents()
private async Task UpdateLogEvents()
{
long fileSize = Reader.Length;
@ -74,11 +72,12 @@ namespace IW4MAdmin.Application.IO
PreviousFileSize = fileSize;
var events = Reader.ReadEventsFromLog(Server, fileDiff, 0);
var events = await Reader.ReadEventsFromLog(Server, fileDiff, 0);
foreach (var ev in events)
{
Server.Manager.GetEventHandler().AddEvent(ev);
await ev.WaitAsync();
}
PreviousFileSize = fileSize;

View File

@ -4,6 +4,7 @@ using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
using System.Threading.Tasks;
namespace IW4MAdmin.Application.IO
{
@ -22,7 +23,7 @@ namespace IW4MAdmin.Application.IO
Parser = parser;
}
public ICollection<GameEvent> ReadEventsFromLog(Server server, long fileSizeDiff, long startPosition)
public async Task<ICollection<GameEvent>> ReadEventsFromLog(Server server, long fileSizeDiff, long startPosition)
{
// allocate the bytes for the new log lines
List<string> logLines = new List<string>();
@ -30,6 +31,7 @@ namespace IW4MAdmin.Application.IO
// open the file as a stream
using (var rd = new StreamReader(new FileStream(LogFile, FileMode.Open, FileAccess.Read, FileShare.ReadWrite), Utilities.EncodingType))
{
// todo: max async
// take the old start position and go back the number of new characters
rd.BaseStream.Seek(-fileSizeDiff, SeekOrigin.End);
// the difference should be in the range of a int :P
@ -55,9 +57,9 @@ namespace IW4MAdmin.Application.IO
catch (Exception e)
{
Program.ServerManager.GetLogger().WriteWarning("Could not properly parse event line");
Program.ServerManager.GetLogger().WriteDebug(e.Message);
Program.ServerManager.GetLogger().WriteDebug(eventLine);
server.Logger.WriteWarning("Could not properly parse event line");
server.Logger.WriteDebug(e.Message);
server.Logger.WriteDebug(eventLine);
}
}
}

View File

@ -5,6 +5,7 @@ using SharedLibraryCore.Interfaces;
using System;
using System.Collections.Generic;
using System.Net.Http;
using System.Threading.Tasks;
using static SharedLibraryCore.Utilities;
namespace IW4MAdmin.Application.IO
@ -27,20 +28,21 @@ namespace IW4MAdmin.Application.IO
public long Length => -1;
public int UpdateInterval => 1000;
public int UpdateInterval => 350;
public ICollection<GameEvent> ReadEventsFromLog(Server server, long fileSizeDiff, long startPosition)
public async Task<ICollection<GameEvent>> ReadEventsFromLog(Server server, long fileSizeDiff, long startPosition)
{
#if DEBUG == true
server.Logger.WriteDebug($"Begin reading {fileSizeDiff} from http log");
server.Logger.WriteDebug($"Begin reading from http log");
#endif
var events = new List<GameEvent>();
string b64Path = server.LogPath.ToBase64UrlSafeString();
var response = Api.Log(b64Path).Result;
var response = await Api.Log(b64Path);
if (!response.Success)
{
server.Logger.WriteError($"Could not get log server info of {LogFile}/{b64Path} ({server.LogPath})");
return events;
}
// parse each line
@ -59,9 +61,9 @@ namespace IW4MAdmin.Application.IO
catch (Exception e)
{
Program.ServerManager.GetLogger().WriteWarning("Could not properly parse event line");
Program.ServerManager.GetLogger().WriteDebug(e.Message);
Program.ServerManager.GetLogger().WriteDebug(eventLine);
server.Logger.WriteWarning("Could not properly parse event line");
server.Logger.WriteDebug(e.Message);
server.Logger.WriteDebug(eventLine);
}
}
}

File diff suppressed because it is too large Load Diff

View File

@ -12,10 +12,10 @@ namespace IW4MAdmin.Application.Localization
{
public class Configure
{
public static void Initialize(string customLocale)
public static void Initialize(string customLocale = null)
{
string currentLocale = string.IsNullOrEmpty(customLocale) ? CultureInfo.CurrentCulture.Name : customLocale;
string[] localizationFiles = Directory.GetFiles("Localization", $"*.{currentLocale}.json");
string[] localizationFiles = Directory.GetFiles(Path.Join(Utilities.OperatingDirectory, "Localization"), $"*.{currentLocale}.json");
try
{
@ -33,13 +33,13 @@ namespace IW4MAdmin.Application.Localization
// culture doesn't exist so we just want language
if (localizationFiles.Length == 0)
{
localizationFiles = Directory.GetFiles("Localization", $"*.{currentLocale.Substring(0, 2)}*.json");
localizationFiles = Directory.GetFiles(Path.Join(Utilities.OperatingDirectory, "Localization"), $"*.{currentLocale.Substring(0, 2)}*.json");
}
// language doesn't exist either so defaulting to english
if (localizationFiles.Length == 0)
{
localizationFiles = Directory.GetFiles("Localization", "*.en-US.json");
localizationFiles = Directory.GetFiles(Path.Join(Utilities.OperatingDirectory, "Localization"), "*.en-US.json");
}
// this should never happen unless the localization folder is empty
@ -59,12 +59,12 @@ namespace IW4MAdmin.Application.Localization
{
if (!localizationDict.TryAdd(item.Key, item.Value))
{
Program.ServerManager.GetLogger().WriteError($"Could not add locale string {item.Key} to localization");
Program.ServerManager.GetLogger(0).WriteError($"Could not add locale string {item.Key} to localization");
}
}
}
string localizationFile = $"Localization{Path.DirectorySeparatorChar}IW4MAdmin.{currentLocale}-{currentLocale.ToUpper()}.json";
string localizationFile = $"{Path.Join(Utilities.OperatingDirectory, "Localization")}{Path.DirectorySeparatorChar}IW4MAdmin.{currentLocale}-{currentLocale.ToUpper()}.json";
Utilities.CurrentLocalization = new SharedLibraryCore.Localization.Layout(localizationDict)
{

View File

@ -2,6 +2,7 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Threading;
namespace IW4MAdmin.Application
{
@ -18,18 +19,44 @@ namespace IW4MAdmin.Application
}
readonly string FileName;
readonly object ThreadLock;
readonly SemaphoreSlim OnLogWriting;
static readonly short MAX_LOG_FILES = 10;
public Logger(string fn)
{
FileName = fn;
ThreadLock = new object();
if (File.Exists(fn))
File.Delete(fn);
FileName = Path.Join(Utilities.OperatingDirectory, "Log", $"{fn}.log");
OnLogWriting = new SemaphoreSlim(1, 1);
RotateLogs();
}
/// <summary>
/// rotates logs when log is initialized
/// </summary>
private void RotateLogs()
{
string maxLog = FileName + MAX_LOG_FILES;
if (File.Exists(maxLog))
{
File.Delete(maxLog);
}
for (int i = MAX_LOG_FILES - 1; i >= 0; i--)
{
string logToMove = i == 0 ? FileName : FileName + i;
string movedLogName = FileName + (i + 1);
if (File.Exists(logToMove))
{
File.Move(logToMove, movedLogName);
}
}
}
void Write(string msg, LogType type)
{
OnLogWriting.Wait();
string stringType = type.ToString();
try
@ -40,13 +67,12 @@ namespace IW4MAdmin.Application
catch (Exception) { }
string LogLine = $"[{DateTime.Now.ToString("MM.dd.yyy HH:mm:ss.fff")}] - {stringType}: {msg}";
lock (ThreadLock)
try
{
#if DEBUG
// lets keep it simple and dispose of everything quickly as logging wont be that much (relatively)
Console.WriteLine(LogLine);
File.AppendAllText(FileName, LogLine + Environment.NewLine);
// lets keep it simple and dispose of everything quickly as logging wont be that much (relatively)
Console.WriteLine(LogLine);
File.AppendAllText(FileName, LogLine + Environment.NewLine);
#else
if (type == LogType.Error || type == LogType.Verbose)
Console.WriteLine(LogLine);
@ -54,6 +80,14 @@ namespace IW4MAdmin.Application
File.AppendAllText(FileName, $"{LogLine}{Environment.NewLine}");
#endif
}
catch (Exception ex)
{
Console.WriteLine("Well.. It looks like your machine can't event write to the log file. That's something else...");
Console.WriteLine(ex.GetExceptionInfo());
}
OnLogWriting.Release(1);
}
public void WriteVerbose(string msg)

View File

@ -1,29 +1,23 @@
using System;
using System.Threading.Tasks;
using System.IO;
using System.Reflection;
using IW4MAdmin.Application.Migration;
using SharedLibraryCore;
using SharedLibraryCore.Objects;
using SharedLibraryCore.Database;
using SharedLibraryCore.Localization;
using System;
using System.IO;
using System.Text;
using System.Threading;
using System.Collections.Generic;
using SharedLibraryCore.Localization;
using System.Threading.Tasks;
namespace IW4MAdmin.Application
{
public class Program
{
static public double Version { get; private set; }
static public ApplicationManager ServerManager = ApplicationManager.GetInstance();
public static string OperatingDirectory = Path.GetDirectoryName(Assembly.GetEntryAssembly().Location) + Path.DirectorySeparatorChar;
static public ApplicationManager ServerManager;
private static ManualResetEventSlim OnShutdownComplete = new ManualResetEventSlim();
public static void Main(string[] args)
{
AppDomain.CurrentDomain.SetData("DataDirectory", OperatingDirectory);
AppDomain.CurrentDomain.SetData("DataDirectory", Utilities.OperatingDirectory);
Console.OutputEncoding = Encoding.UTF8;
Console.ForegroundColor = ConsoleColor.Gray;
@ -39,12 +33,25 @@ namespace IW4MAdmin.Application
try
{
CheckDirectories();
ServerManager = ApplicationManager.GetInstance();
Console.CancelKeyPress += new ConsoleCancelEventHandler(OnCancelKey);
Localization.Configure.Initialize(ServerManager.GetApplicationSettings().Configuration()?.CustomLocale);
if (ServerManager.GetApplicationSettings().Configuration() != null)
{
Localization.Configure.Initialize(ServerManager.GetApplicationSettings().Configuration().CustomLocale);
}
else
{
Localization.Configure.Initialize();
}
loc = Utilities.CurrentLocalization.LocalizationIndex;
Console.CancelKeyPress += new ConsoleCancelEventHandler(OnCancelKey);
CheckDirectories();
// do any needed migrations
// todo: move out
ConfigurationMigration.MoveConfigFolder10518(null);
ServerManager.Logger.WriteInfo($"Version is {Version}");
@ -106,15 +113,17 @@ namespace IW4MAdmin.Application
var consoleTask = Task.Run(async () =>
{
String userInput;
Player Origin = Utilities.IW4MAdminClient;
string userInput;
var Origin = Utilities.IW4MAdminClient(ServerManager.Servers[0]);
do
{
userInput = Console.ReadLine();
if (userInput?.ToLower() == "quit")
{
ServerManager.Stop();
}
if (ServerManager.Servers.Count == 0)
{
@ -124,7 +133,6 @@ namespace IW4MAdmin.Application
if (userInput?.Length > 0)
{
Origin.CurrentServer = ServerManager.Servers[0];
GameEvent E = new GameEvent()
{
Type = GameEvent.EventType.Command,
@ -144,13 +152,16 @@ namespace IW4MAdmin.Application
catch (Exception e)
{
Console.WriteLine(loc["MANAGER_INIT_FAIL"]);
string failMessage = loc == null ? "Failed to initalize IW4MAdmin" : loc["MANAGER_INIT_FAIL"];
string exitMessage = loc == null ? "Press any key to exit..." : loc["MANAGER_EXIT"];
Console.WriteLine(failMessage);
while (e.InnerException != null)
{
e = e.InnerException;
}
Console.WriteLine($"Exception: {e.Message}");
Console.WriteLine(loc["MANAGER_EXIT"]);
Console.WriteLine(e.Message);
Console.WriteLine(exitMessage);
Console.ReadKey();
return;
}
@ -169,15 +180,25 @@ namespace IW4MAdmin.Application
private static void OnCancelKey(object sender, ConsoleCancelEventArgs e)
{
ServerManager.Stop();
OnShutdownComplete.Wait(5000);
OnShutdownComplete.Wait();
}
static void CheckDirectories()
{
string curDirectory = Path.GetDirectoryName(Assembly.GetEntryAssembly().Location) + Path.DirectorySeparatorChar;
if (!Directory.Exists(Path.Join(Utilities.OperatingDirectory, "Plugins")))
{
Directory.CreateDirectory(Path.Join(Utilities.OperatingDirectory, "Plugins"));
}
if (!Directory.Exists($"{curDirectory}Plugins"))
Directory.CreateDirectory($"{curDirectory}Plugins");
if (!Directory.Exists(Path.Join(Utilities.OperatingDirectory, "Database")))
{
Directory.CreateDirectory(Path.Join(Utilities.OperatingDirectory, "Database"));
}
if (!Directory.Exists(Path.Join(Utilities.OperatingDirectory, "Log")))
{
Directory.CreateDirectory(Path.Join(Utilities.OperatingDirectory, "Log"));
}
}
}
}

View File

@ -0,0 +1,60 @@
using SharedLibraryCore;
using SharedLibraryCore.Interfaces;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
namespace IW4MAdmin.Application.Migration
{
/// <summary>
/// helps facilitate the migration of configs from one version and or location
/// to another
/// </summary>
class ConfigurationMigration
{
/// <summary>
/// moves existing configs from the root folder into a configs folder
/// </summary>
public static void MoveConfigFolder10518(ILogger log)
{
string currentDirectory = Utilities.OperatingDirectory;
// we don't want to do this for migrations or tests where the
// property isn't initialized or it's wrong
if (currentDirectory != null)
{
string configDirectory = Path.Join(currentDirectory, "Configuration");
if (!Directory.Exists(configDirectory))
{
log?.WriteDebug($"Creating directory for configs {configDirectory}");
Directory.CreateDirectory(configDirectory);
}
var configurationFiles = Directory.EnumerateFiles(currentDirectory, "*.json")
.Select(f => f.Split(Path.DirectorySeparatorChar).Last())
.Where(f => f.Count(c => c == '.') == 1);
foreach (var configFile in configurationFiles)
{
log?.WriteDebug($"Moving config file {configFile}");
string destinationPath = Path.Join("Configuration", configFile);
if (!File.Exists(destinationPath))
{
File.Move(configFile, destinationPath);
}
}
if (!File.Exists(Path.Join("Database", "Database.db")) &&
File.Exists("Database.db"))
{
log?.WriteDebug("Moving database file");
File.Move("Database.db", Path.Join("Database", "Database.db"));
}
}
}
}
}

View File

@ -0,0 +1,178 @@
using SharedLibraryCore;
using SharedLibraryCore.Database.Models;
using SharedLibraryCore.Exceptions;
using SharedLibraryCore.Interfaces;
using SharedLibraryCore.RCon;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
namespace IW4MAdmin.Application.RconParsers
{
class BaseRConParser : IRConParser
{
public BaseRConParser()
{
Configuration = new DynamicRConParserConfiguration()
{
CommandPrefixes = new CommandPrefix()
{
Tell = "tellraw {0} {1}",
Say = "sayraw {0}",
Kick = "clientkick {0} \"{1}\"",
Ban = "clientkick {0} \"{1}\"",
TempBan = "tempbanclient {0} \"{1}\"",
RConCommand = "ÿÿÿÿrcon {0} {1}",
RConGetDvar = "ÿÿÿÿrcon {0} {1}",
RConSetDvar = "ÿÿÿÿrcon {0} set {1}",
RConGetStatus = "ÿÿÿÿgetstatus",
RConGetInfo = "ÿÿÿÿgetinfo",
RConResponse = "ÿÿÿÿprint",
},
GameName = Server.Game.IW4
};
Configuration.Status.Pattern = @"^ *([0-9]+) +-?([0-9]+) +((?:[A-Z]+|[0-9]+)) +((?:[a-z]|[0-9]){16}|(?:[a-z]|[0-9]){32}|bot[0-9]+|(?:[0-9]+)) *(.{0,32}) +([0-9]+) +(\d+\.\d+\.\d+.\d+\:-*\d{1,5}|0+.0+:-*\d{1,5}|loopback) +(-*[0-9]+) +([0-9]+) *$";
Configuration.Status.AddMapping(ParserRegex.GroupType.RConClientNumber, 1);
Configuration.Status.AddMapping(ParserRegex.GroupType.RConScore, 2);
Configuration.Status.AddMapping(ParserRegex.GroupType.RConPing, 3);
Configuration.Status.AddMapping(ParserRegex.GroupType.RConNetworkId, 4);
Configuration.Status.AddMapping(ParserRegex.GroupType.RConName, 5);
Configuration.Status.AddMapping(ParserRegex.GroupType.RConIpAddress, 7);
Configuration.Dvar.Pattern = "^\"(.+)\" is: \"(.+)\" default: \"(.+)\"\n(?:latched: \"(.+)\"\n)? *(.+)$";
Configuration.Dvar.AddMapping(ParserRegex.GroupType.RConDvarName, 1);
Configuration.Dvar.AddMapping(ParserRegex.GroupType.RConDvarValue, 2);
Configuration.Dvar.AddMapping(ParserRegex.GroupType.RConDvarDefaultValue, 3);
Configuration.Dvar.AddMapping(ParserRegex.GroupType.RConDvarLatchedValue, 4);
Configuration.Dvar.AddMapping(ParserRegex.GroupType.RConDvarDomain, 5);
}
public IRConParserConfiguration Configuration { get; set; }
public string Version { get; set; } = "IW4x (v0.6.0)";
public async Task<string[]> ExecuteCommandAsync(Connection connection, string command)
{
var response = await connection.SendQueryAsync(StaticHelpers.QueryType.COMMAND, command);
return response.Skip(1).ToArray();
}
public async Task<Dvar<T>> GetDvarAsync<T>(Connection connection, string dvarName)
{
string[] lineSplit = await connection.SendQueryAsync(StaticHelpers.QueryType.GET_DVAR, dvarName);
string response = string.Join('\n', lineSplit.Skip(1));
if (!lineSplit[0].Contains(Configuration.CommandPrefixes.RConResponse))
{
throw new DvarException($"Could not retrieve DVAR \"{dvarName}\"");
}
if (response.Contains("Unknown command"))
{
throw new DvarException($"DVAR \"{dvarName}\" does not exist");
}
var match = Regex.Match(response, Configuration.Dvar.Pattern);
if (!match.Success)
{
throw new DvarException($"Could not retrieve DVAR \"{dvarName}\"");
}
string value = match.Groups[Configuration.Dvar.GroupMapping[ParserRegex.GroupType.RConDvarValue]].Value.StripColors();
string defaultValue = match.Groups[Configuration.Dvar.GroupMapping[ParserRegex.GroupType.RConDvarDefaultValue]].Value.StripColors();
string latchedValue = match.Groups[Configuration.Dvar.GroupMapping[ParserRegex.GroupType.RConDvarLatchedValue]].Value.StripColors();
return new Dvar<T>()
{
Name = match.Groups[Configuration.Dvar.GroupMapping[ParserRegex.GroupType.RConDvarName]].Value.StripColors(),
Value = string.IsNullOrEmpty(value) ? default(T) : (T)Convert.ChangeType(value, typeof(T)),
DefaultValue = string.IsNullOrEmpty(defaultValue) ? default(T) : (T)Convert.ChangeType(defaultValue, typeof(T)),
LatchedValue = string.IsNullOrEmpty(latchedValue) ? default(T) : (T)Convert.ChangeType(latchedValue, typeof(T)),
Domain = match.Groups[Configuration.Dvar.GroupMapping[ParserRegex.GroupType.RConDvarDomain]].Value.StripColors()
};
}
public async Task<List<EFClient>> GetStatusAsync(Connection connection)
{
string[] response = await connection.SendQueryAsync(StaticHelpers.QueryType.COMMAND, "status");
return ClientsFromStatus(response);
}
public async Task<bool> SetDvarAsync(Connection connection, string dvarName, object dvarValue)
{
return (await connection.SendQueryAsync(StaticHelpers.QueryType.SET_DVAR, $"{dvarName} {dvarValue}")).Length > 0;
}
private List<EFClient> ClientsFromStatus(string[] Status)
{
List<EFClient> StatusPlayers = new List<EFClient>();
if (Status.Length < 4)
{
throw new ServerException("Unexpected status response received");
}
int validMatches = 0;
foreach (string S in Status)
{
string responseLine = S.Trim();
var regex = Regex.Match(responseLine, Configuration.Status.Pattern, RegexOptions.IgnoreCase);
if (regex.Success)
{
validMatches++;
int clientNumber = int.Parse(regex.Groups[Configuration.Status.GroupMapping[ParserRegex.GroupType.RConClientNumber]].Value);
int score = int.Parse(regex.Groups[Configuration.Status.GroupMapping[ParserRegex.GroupType.RConScore]].Value);
int ping = 999;
// their state can be CNCT, ZMBI etc
if (regex.Groups[Configuration.Status.GroupMapping[ParserRegex.GroupType.RConPing]].Value.Length <= 3)
{
ping = int.Parse(regex.Groups[Configuration.Status.GroupMapping[ParserRegex.GroupType.RConPing]].Value);
}
long networkId = regex.Groups[Configuration.Status.GroupMapping[ParserRegex.GroupType.RConNetworkId]].Value.ConvertLong();
string name = regex.Groups[Configuration.Status.GroupMapping[ParserRegex.GroupType.RConName]].Value.StripColors().Trim();
int? ip = regex.Groups[Configuration.Status.GroupMapping[ParserRegex.GroupType.RConIpAddress]].Value.Split(':')[0].ConvertToIP();
var client = new EFClient()
{
CurrentAlias = new EFAlias()
{
Name = name
},
NetworkId = networkId,
ClientNumber = clientNumber,
IPAddress = ip,
Ping = ping,
Score = score,
IsBot = ip == null,
State = EFClient.ClientState.Connecting
};
// they've not fully connected yet
if (!client.IsBot && ping == 999)
{
continue;
}
StatusPlayers.Add(client);
}
}
// this happens if status is requested while map is rotating
if (Status.Length > 5 && validMatches == 0)
{
throw new ServerException("Server is rotating map");
}
return StatusPlayers;
}
}
}

View File

@ -0,0 +1,10 @@
namespace IW4MAdmin.Application.RconParsers
{
/// <summary>
/// empty implementation of the IW4RConParser
/// allows script plugins to generate dynamic RCon parsers
/// </summary>
sealed internal class DynamicRConParser : BaseRConParser
{
}
}

View File

@ -0,0 +1,19 @@
using SharedLibraryCore;
using SharedLibraryCore.Interfaces;
using SharedLibraryCore.RCon;
namespace IW4MAdmin.Application.RconParsers
{
/// <summary>
/// generic implementation of the IRConParserConfiguration
/// allows script plugins to generate dynamic RCon configurations
/// </summary>
sealed internal class DynamicRConParserConfiguration : IRConParserConfiguration
{
public CommandPrefix CommandPrefixes { get; set; }
public Server.Game GameName { get; set; }
public ParserRegex Status { get; set; } = new ParserRegex();
public ParserRegex Dvar { get; set; } = new ParserRegex();
public bool WaitForResponse { get; set; } = true;
}
}

View File

@ -1,21 +0,0 @@
using SharedLibraryCore.RCon;
using System;
using System.Collections.Generic;
using System.Text;
namespace IW4MAdmin.Application.RconParsers
{
class IW3RConParser : IW4RConParser
{
private static readonly CommandPrefix Prefixes = new CommandPrefix()
{
Tell = "tell {0} {1}",
Say = "say {0}",
Kick = "clientkick {0} \"{1}\"",
Ban = "clientkick {0} \"{1}\"",
TempBan = "tempbanclient {0} \"{1}\""
};
public override CommandPrefix GetCommandPrefixes() => Prefixes;
}
}

View File

@ -1,133 +0,0 @@
using System;
using System.Linq;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using System.Collections.Generic;
using System.Text;
using SharedLibraryCore.Interfaces;
using SharedLibraryCore.Objects;
using SharedLibraryCore;
using SharedLibraryCore.RCon;
using SharedLibraryCore.Exceptions;
namespace IW4MAdmin.Application.RconParsers
{
class IW4RConParser : IRConParser
{
private static readonly CommandPrefix Prefixes = new CommandPrefix()
{
Tell = "tellraw {0} {1}",
Say = "sayraw {0}",
Kick = "clientkick {0} \"{1}\"",
Ban = "clientkick {0} \"{1}\"",
TempBan = "tempbanclient {0} \"{1}\""
};
private static readonly string StatusRegex = @"^( *[0-9]+) +-*([0-9]+) +((?:[A-Z]+|[0-9]+)) +((?:[a-z]|[0-9]){16}|(?:[a-z]|[0-9]){32}|bot[0-9]+|(?:[0-9]+)) *(.{0,32}) +([0-9]+) +(\d+\.\d+\.\d+.\d+\:-*\d{1,5}|0+.0+:-*\d{1,5}|loopback) +(-*[0-9]+) +([0-9]+) *$";
public async Task<string[]> ExecuteCommandAsync(Connection connection, string command)
{
var response = await connection.SendQueryAsync(StaticHelpers.QueryType.COMMAND, command);
return response.Skip(1).ToArray();
}
public async Task<Dvar<T>> GetDvarAsync<T>(Connection connection, string dvarName)
{
string[] LineSplit = await connection.SendQueryAsync(StaticHelpers.QueryType.DVAR, dvarName);
if (LineSplit.Length < 3)
{
var e = new DvarException($"DVAR \"{dvarName}\" does not exist");
e.Data["dvar_name"] = dvarName;
throw e;
}
string[] ValueSplit = LineSplit[1].Split(new char[] { '"' }, StringSplitOptions.RemoveEmptyEntries);
if (ValueSplit.Length < 5)
{
var e = new DvarException($"DVAR \"{dvarName}\" does not exist");
e.Data["dvar_name"] = dvarName;
throw e;
}
string DvarName = Regex.Replace(ValueSplit[0], @"\^[0-9]", "");
string DvarCurrentValue = Regex.Replace(ValueSplit[2], @"\^[0-9]", "");
string DvarDefaultValue = Regex.Replace(ValueSplit[4], @"\^[0-9]", "");
return new Dvar<T>(DvarName)
{
Value = (T)Convert.ChangeType(DvarCurrentValue, typeof(T))
};
}
public async Task<List<Player>> GetStatusAsync(Connection connection)
{
string[] response = await connection.SendQueryAsync(StaticHelpers.QueryType.COMMAND, "status");
return ClientsFromStatus(response);
}
public async Task<bool> SetDvarAsync(Connection connection, string dvarName, object dvarValue)
{
return (await connection.SendQueryAsync(StaticHelpers.QueryType.COMMAND, $"set {dvarName} {dvarValue}")).Length > 0;
}
public virtual CommandPrefix GetCommandPrefixes() => Prefixes;
private List<Player> ClientsFromStatus(string[] Status)
{
List<Player> StatusPlayers = new List<Player>();
if (Status.Length < 4)
throw new ServerException("Unexpected status response received");
int validMatches = 0;
foreach (String S in Status)
{
String responseLine = S.Trim();
var regex = Regex.Match(responseLine, StatusRegex, RegexOptions.IgnoreCase);
if (regex.Success)
{
validMatches++;
int clientNumber = int.Parse(regex.Groups[1].Value);
int score = int.Parse(regex.Groups[2].Value);
int ping = 999;
// their state can be CNCT, ZMBI etc
if (regex.Groups[3].Value.Length <= 3)
{
ping = int.Parse(regex.Groups[3].Value);
}
long networkId = regex.Groups[4].Value.ConvertLong();
string name = regex.Groups[5].Value.StripColors().Trim();
int ip = regex.Groups[7].Value.Split(':')[0].ConvertToIP();
Player P = new Player()
{
Name = name,
NetworkId = networkId,
ClientNumber = clientNumber,
IPAddress = ip == 0 ? int.MinValue : ip,
Ping = ping,
Score = score,
IsBot = ip == 0,
State = Player.ClientState.Connecting
};
StatusPlayers.Add(P);
}
}
// this happens if status is requested while map is rotating
if (Status.Length > 5 && validMatches == 0)
{
throw new ServerException("Server is rotating map");
}
return StatusPlayers;
}
}
}

View File

@ -1,170 +0,0 @@
using System;
using System.Collections.Generic;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using System.Text;
using SharedLibraryCore;
using SharedLibraryCore.Interfaces;
using SharedLibraryCore.Objects;
using SharedLibraryCore.RCon;
using SharedLibraryCore.Exceptions;
namespace IW4MAdmin.Application.RconParsers
{
public class IW5MRConParser : IRConParser
{
private static readonly CommandPrefix Prefixes = new CommandPrefix()
{
Tell = "tell {0} {1}",
Say = "say {0}",
Kick = "dropClient {0} \"{1}\"",
Ban = "dropClient {0} \"{1}\"",
TempBan = "dropClient {0} \"{1}\""
};
public CommandPrefix GetCommandPrefixes() => Prefixes;
public async Task<string[]> ExecuteCommandAsync(Connection connection, string command)
{
await connection.SendQueryAsync(StaticHelpers.QueryType.COMMAND, command, false);
return new string[] { "Command Executed" };
}
public async Task<Dvar<T>> GetDvarAsync<T>(Connection connection, string dvarName)
{
// why can't this be real :(
if (dvarName == "version")
return new Dvar<T>(dvarName)
{
Value = (T)Convert.ChangeType("IW5 MP 1.9 build 461 Fri Sep 14 00:04:28 2012 win-x86", typeof(T))
};
if (dvarName == "shortversion")
return new Dvar<T>(dvarName)
{
Value = (T)Convert.ChangeType("1.9", typeof(T))
};
if (dvarName == "mapname")
return new Dvar<T>(dvarName)
{
Value = (T)Convert.ChangeType("Unknown", typeof(T))
};
if (dvarName == "g_gametype")
return new Dvar<T>(dvarName)
{
Value = (T)Convert.ChangeType("Unknown", typeof(T))
};
if (dvarName == "fs_game")
return new Dvar<T>(dvarName)
{
Value = (T)Convert.ChangeType("", typeof(T))
};
if (dvarName == "g_logsync")
return new Dvar<T>(dvarName)
{
Value = (T)Convert.ChangeType(1, typeof(T))
};
if (dvarName == "fs_basepath")
return new Dvar<T>(dvarName)
{
Value = (T)Convert.ChangeType("", typeof(T))
};
string[] LineSplit = await connection.SendQueryAsync(StaticHelpers.QueryType.DVAR, dvarName);
if (LineSplit.Length < 4)
{
var e = new DvarException($"DVAR \"{dvarName}\" does not exist");
e.Data["dvar_name"] = dvarName;
throw e;
}
string[] ValueSplit = LineSplit[1].Split(new char[] { '"' });
if (ValueSplit.Length == 0)
{
var e = new DvarException($"DVAR \"{dvarName}\" does not exist");
e.Data["dvar_name"] = dvarName;
throw e;
}
string DvarName = dvarName;
string DvarCurrentValue = Regex.Replace(ValueSplit[3].StripColors(), @"\^[0-9]", "");
return new Dvar<T>(DvarName)
{
Value = (T)Convert.ChangeType(DvarCurrentValue, typeof(T))
};
}
public async Task<List<Player>> GetStatusAsync(Connection connection)
{
string[] response = await connection.SendQueryAsync(StaticHelpers.QueryType.COMMAND, "status");
return ClientsFromStatus(response);
}
public async Task<bool> SetDvarAsync(Connection connection, string dvarName, object dvarValue)
{
// T6M doesn't respond with anything when a value is set, so we can only hope for the best :c
await connection.SendQueryAsync(StaticHelpers.QueryType.DVAR, $"set {dvarName} {dvarValue}", false);
return true;
}
private List<Player> ClientsFromStatus(string[] status)
{
List<Player> StatusPlayers = new List<Player>();
foreach (string statusLine in status)
{
String responseLine = statusLine;
if (Regex.Matches(responseLine, @"^ *\d+", RegexOptions.IgnoreCase).Count > 0) // its a client line!
{
String[] playerInfo = responseLine.Split(new char[] { ' ' }, StringSplitOptions.RemoveEmptyEntries);
// this happens when the client is in a zombie state
if (playerInfo.Length < 5)
continue;
int clientId = -1;
int Ping = -1;
Int32.TryParse(playerInfo[2], out Ping);
string name = Encoding.UTF8.GetString(Encoding.Convert(Utilities.EncodingType, Encoding.UTF8, Utilities.EncodingType.GetBytes(responseLine.Substring(23, 15).StripColors().Trim())));
long networkId = 0;//playerInfo[4].ConvertLong();
int.TryParse(playerInfo[0], out clientId);
var regex = Regex.Match(responseLine, @"\d+\.\d+\.\d+.\d+\:\d{1,5}");
int ipAddress = regex.Value.Split(':')[0].ConvertToIP();
regex = Regex.Match(responseLine, @" +(\d+ +){3}");
int score = Int32.Parse(regex.Value.Split(' ', StringSplitOptions.RemoveEmptyEntries)[0]);
var p = new Player()
{
Name = name,
NetworkId = networkId,
ClientNumber = clientId,
IPAddress = ipAddress,
Ping = Ping,
Score = score,
IsBot = false,
State = Player.ClientState.Connecting
};
StatusPlayers.Add(p);
if (p.IsBot)
p.NetworkId = -p.ClientNumber;
}
}
return StatusPlayers;
}
}
}

View File

@ -1,202 +0,0 @@
using System;
using System.Collections.Generic;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using SharedLibraryCore;
using SharedLibraryCore.Interfaces;
using SharedLibraryCore.Objects;
using SharedLibraryCore.RCon;
using SharedLibraryCore.Exceptions;
using System.Text;
using System.Linq;
using System.Net.Http;
namespace IW4MAdmin.Application.RconParsers
{
public class T6MRConParser : IRConParser
{
class T6MResponse
{
public class SInfo
{
public short Com_maxclients { get; set; }
public string Game { get; set; }
public string Gametype { get; set; }
public string Mapname { get; set; }
public short NumBots { get; set; }
public short NumClients { get; set; }
public short Round { get; set; }
public string Sv_hostname { get; set; }
}
public class PInfo
{
public short Assists { get; set; }
public string Clan { get; set; }
public short Deaths { get; set; }
public short Downs { get; set; }
public short Headshots { get; set; }
public short Id { get; set; }
public bool IsBot { get; set; }
public short Kills { get; set; }
public string Name { get; set; }
public short Ping { get; set; }
public short Revives { get; set; }
public int Score { get; set; }
public long Xuid { get; set; }
public string Ip { get; set; }
}
public SInfo Info { get; set; }
public PInfo[] Players { get; set; }
}
private static readonly CommandPrefix Prefixes = new CommandPrefix()
{
Tell = "tell {0} {1}",
Say = "say {0}",
Kick = "clientKick {0}",
Ban = "clientKick {0}",
TempBan = "clientKick {0}"
};
public CommandPrefix GetCommandPrefixes() => Prefixes;
public async Task<string[]> ExecuteCommandAsync(Connection connection, string command)
{
await connection.SendQueryAsync(StaticHelpers.QueryType.COMMAND, command, false);
return new string[] { "Command Executed" };
}
public async Task<Dvar<T>> GetDvarAsync<T>(Connection connection, string dvarName)
{
string[] LineSplit = await connection.SendQueryAsync(StaticHelpers.QueryType.COMMAND, $"get {dvarName}");
if (LineSplit.Length < 2)
{
var e = new DvarException($"DVAR \"{dvarName}\" does not exist");
e.Data["dvar_name"] = dvarName;
throw e;
}
string[] ValueSplit = LineSplit[1].Split(new char[] { '"' });
if (ValueSplit.Length == 0)
{
var e = new DvarException($"DVAR \"{dvarName}\" does not exist");
e.Data["dvar_name"] = dvarName;
throw e;
}
string DvarName = dvarName;
string DvarCurrentValue = Regex.Replace(ValueSplit[1], @"\^[0-9]", "");
return new Dvar<T>(DvarName)
{
Value = (T)Convert.ChangeType(DvarCurrentValue, typeof(T))
};
}
public async Task<List<Player>> GetStatusAsync(Connection connection)
{
string[] response = await connection.SendQueryAsync(StaticHelpers.QueryType.COMMAND, "status");
return ClientsFromStatus(response);
//return ClientsFromResponse(connection);
}
public async Task<bool> SetDvarAsync(Connection connection, string dvarName, object dvarValue)
{
// T6M doesn't respond with anything when a value is set, so we can only hope for the best :c
await connection.SendQueryAsync(StaticHelpers.QueryType.DVAR, $"set {dvarName} {dvarValue}", false);
return true;
}
private async Task<List<Player>> ClientsFromResponse(Connection conn)
{
using (var client = new HttpClient())
{
client.BaseAddress = new Uri($"http://{conn.Endpoint.Address}:{conn.Endpoint.Port}/");
try
{
var parameters = new FormUrlEncodedContent(new[]
{
new KeyValuePair<string, string>("rcon_password", conn.RConPassword)
});
var serverResponse = await client.PostAsync("/info", parameters);
var serverResponseObject = Newtonsoft.Json.JsonConvert.DeserializeObject<T6MResponse>(await serverResponse.Content.ReadAsStringAsync());
return serverResponseObject.Players.Select(p => new Player()
{
Name = p.Name,
NetworkId = p.Xuid,
ClientNumber = p.Id,
IPAddress = p.Ip.Split(':')[0].ConvertToIP(),
Ping = p.Ping,
Score = p.Score,
IsBot = p.IsBot,
}).ToList();
}
catch (HttpRequestException e)
{
throw new NetworkException(e.Message);
}
}
}
private List<Player> ClientsFromStatus(string[] status)
{
List<Player> StatusPlayers = new List<Player>();
foreach (string statusLine in status)
{
String responseLine = statusLine;
if (Regex.Matches(responseLine, @"^ *\d+", RegexOptions.IgnoreCase).Count > 0) // its a client line!
{
String[] playerInfo = responseLine.Split(new char[] { ' ' }, StringSplitOptions.RemoveEmptyEntries);
int clientId = -1;
int Ping = -1;
Int32.TryParse(playerInfo[3], out Ping);
var regex = Regex.Match(responseLine, @"\^7.*\ +0 ");
string name = Encoding.UTF8.GetString(Encoding.Convert(Utilities.EncodingType, Encoding.UTF8, Utilities.EncodingType.GetBytes(regex.Value.Substring(0, regex.Value.Length - 2).StripColors().Trim())));
long networkId = playerInfo[4].ConvertLong();
int.TryParse(playerInfo[0], out clientId);
regex = Regex.Match(responseLine, @"\d+\.\d+\.\d+.\d+\:\d{1,5}");
#if DEBUG
Ping = 1;
#endif
int ipAddress = regex.Value.Split(':')[0].ConvertToIP();
regex = Regex.Match(responseLine, @"[0-9]{1,2}\s+[0-9]+\s+");
int score = 0;
// todo: fix this when T6M score is valid ;)
//int score = Int32.Parse(playerInfo[1]);
var p = new Player()
{
Name = name,
NetworkId = networkId,
ClientNumber = clientId,
IPAddress = ipAddress,
Ping = Ping,
Score = score,
State = Player.ClientState.Connecting,
IsBot = networkId == 0
};
if (p.IsBot)
p.NetworkId = -p.ClientNumber;
StatusPlayers.Add(p);
}
}
return StatusPlayers;
}
}
}

View File

@ -0,0 +1,31 @@
var plugin = {
author: 'RaidMax',
version: 1.1,
name: 'Shared GUID Kicker Plugin',
onEventAsync: function (gameEvent, server) {
// make sure we only check for IW4(x)
if (server.GameName !== 2) {
return false;
}
// connect or join event
if (gameEvent.Type === 3) {
// this GUID seems to have been packed in a IW4 torrent and results in an unreasonable amount of people using the same GUID
if (gameEvent.Origin.NetworkId === -805366929435212061) {
gameEvent.Origin.Kick('Your GUID is generic. Delete players/guids.dat and rejoin', _IW4MAdminClient);
}
}
},
onLoadAsync: function (manager) {
},
onUnloadAsync: function () {
},
onTickAsync: function (server) {
}
};

View File

@ -0,0 +1,62 @@
var plugin = {
author: 'RaidMax',
version: 1.0,
name: 'VPN Detection Plugin',
manager: null,
logger: null,
vpnExceptionIds: [],
checkForVpn: function (origin) {
var exempt = false;
// prevent players that are exempt from being kicked
this.vpnExceptionIds.forEach(function (id) {
if (id === origin.ClientId) {
exempt = true;
return false;
}
});
if (exempt) {
return;
}
var usingVPN = false;
try {
var cl = new System.Net.Http.HttpClient();
var re = cl.GetAsync('https://api.xdefcon.com/proxy/check/?ip=' + origin.IPAddressString).Result;
var co = re.Content;
var parsedJSON = JSON.parse(co.ReadAsStringAsync().Result);
co.Dispose();
re.Dispose();
cl.Dispose();
usingVPN = parsedJSON.success && parsedJSON.proxy;
} catch (e) {
this.logger.WriteError(e.message);
}
if (usingVPN) {
this.logger.WriteInfo(origin + ' is using a VPN (' + origin.IPAddressString + ')');
origin.Kick(_localization.LocalizationIndex["SERVER_KICK_VPNS_NOTALLOWED"], _IW4MAdminClient);
}
},
onEventAsync: function (gameEvent, server) {
// connect event
if (gameEvent.Type === 3) {
this.checkForVpn(gameEvent.Origin);
}
},
onLoadAsync: function (manager) {
this.manager = manager;
this.logger = manager.GetLogger(0);
},
onUnloadAsync: function () {
},
onTickAsync: function (server) {
}
};

View File

@ -27,6 +27,11 @@
<DebugSymbols>true</DebugSymbols>
<EnableUnmanagedDebugging>false</EnableUnmanagedDebugging>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)' == 'Prerelease' ">
<DebugSymbols>true</DebugSymbols>
<EnableUnmanagedDebugging>false</EnableUnmanagedDebugging>
<OutputPath>bin\Prerelease\</OutputPath>
</PropertyGroup>
<ItemGroup>
<Compile Include="DiscordWebhook.py" />
</ItemGroup>
@ -42,7 +47,6 @@
</Interpreter>
</ItemGroup>
<ItemGroup>
<Content Include="config.dev.json" />
<Content Include="config.json">
<Publish>True</Publish>
</Content>
@ -50,7 +54,7 @@
<Publish>True</Publish>
</Content>
</ItemGroup>
<Import Project="$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)\Python Tools\Microsoft.PythonTools.Web.targets" />
<Import Project="$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)\Python Tools\Microsoft.PythonTools.Web.targets" />
<!-- Uncomment the CoreCompile target to enable the Build command in
Visual Studio and specify your pre- and post-build commands in
the BeforeBuild and AfterBuild targets below. -->
@ -59,7 +63,7 @@
</Target>
<Target Name="AfterBuild">
</Target>
<ProjectExtensions>
<ProjectExtensions>
<VisualStudio>
<FlavorProperties GUID="{349c5851-65df-11da-9384-00065b846f21}">
<WebProjectProperties>

View File

@ -27,10 +27,16 @@
<DebugSymbols>true</DebugSymbols>
<EnableUnmanagedDebugging>false</EnableUnmanagedDebugging>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)' == 'Prerelease' ">
<DebugSymbols>true</DebugSymbols>
<EnableUnmanagedDebugging>false</EnableUnmanagedDebugging>
<OutputPath>bin\Prerelease\</OutputPath>
</PropertyGroup>
<ItemGroup>
<Compile Include="GameLogServer\log_reader.py">
<SubType>Code</SubType>
</Compile>
<Compile Include="GameLogServer\restart_resource.py" />
<Compile Include="GameLogServer\server.py">
<SubType>Code</SubType>
</Compile>
@ -42,8 +48,8 @@
<Folder Include="GameLogServer\" />
</ItemGroup>
<ItemGroup>
<None Include="FolderProfile.pubxml" />
<Content Include="requirements.txt" />
<None Include="Stable.pubxml" />
</ItemGroup>
<ItemGroup>
<Interpreter Include="env\">

View File

@ -5,26 +5,27 @@ import time
class LogReader(object):
def __init__(self):
self.log_file_sizes = {}
# (if the file changes more than this, ignore ) - 1 MB
self.max_file_size_change = 1000000
# (if the file changes more than this, ignore ) - 0.125 MB
self.max_file_size_change = 125000
# (if the time between checks is greater, ignore ) - 5 minutes
self.max_file_time_change = 300
self.max_file_time_change = 60
def read_file(self, path):
# prevent traversing directories
if re.search('r^.+\.\.\\.+$', path):
return False
# must be a valid log path and log file
if not re.search(r'^.+[\\|\/](userraw|mods)[\\|\/].+.log$', path):
if not re.search(r'^.+[\\|\/](userraw|mods|main)[\\|\/].+.log$', path):
return False
# set the initialze size to the current file size
file_size = 0
if path not in self.log_file_sizes:
self.log_file_sizes[path] = {
'length' : self.file_length(path),
'read': time.time()
}
return ''
return True
# grab the previous values
last_length = self.log_file_sizes[path]['length']
@ -50,9 +51,9 @@ class LogReader(object):
# if it's been too long since we read and the amount changed is too great, discard it
# todo: do we really want old events? maybe make this an "or"
if file_size_difference > self.max_file_size_change and time_difference > self.max_file_time_change:
return ''
if file_size_difference > self.max_file_size_change or time_difference > self.max_file_time_change:
return True
new_log_info = self.get_file_lines(path, file_size_difference)
return new_log_info

View File

@ -9,9 +9,11 @@ class LogResource(Resource):
if log_info is False:
print('could not read log file ' + path)
empty_read = (log_info == False) or (log_info == True)
return {
'success' : log_info is not False,
'length': -1 if log_info is False else len(log_info),
'length': -1 if empty_read else len(log_info),
'data': log_info
}

View File

@ -0,0 +1,29 @@
from flask_restful import Resource
from flask import request
import requests
import os
import subprocess
import re
def get_pid_of_server_windows(port):
process = subprocess.Popen('netstat -aon', shell=True, stdout=subprocess.PIPE)
output = process.communicate()[0]
matches = re.search(' *(UDP) +([0-9]{1,3}.[0-9]{1,3}.[0-9]{1,3}.[0-9]{1,3}):'+ str(port) + ' +[^\w]*([0-9]+)', output.decode('utf-8'))
if matches is not None:
return matches.group(3)
else:
return 0
class RestartResource(Resource):
def get(self):
try:
response = requests.get('http://' + request.remote_addr + ':1624/api/restartapproved')
if response.status_code == 200:
pid = get_pid_of_server_windows(response.json()['port'])
subprocess.check_output("Taskkill /PID %s /F" % pid)
else:
return {}, 400
except Exception as e:
print(e)
return {}, 500
return {}, 200

View File

@ -1,9 +1,11 @@
from flask import Flask
from flask_restful import Api
from .log_resource import LogResource
from .restart_resource import RestartResource
app = Flask(__name__)
def init():
api = Api(app)
api.add_resource(LogResource, '/log/<string:path>')
api.add_resource(RestartResource, '/restart')

View File

@ -1,12 +1,26 @@
Flask==1.0.2
aniso8601==3.0.2
APScheduler==3.5.3
certifi==2018.10.15
chardet==3.0.4
click==6.7
Flask==1.0.2
Flask-JWT==0.3.2
Flask-JWT-Extended==3.8.1
Flask-RESTful==0.3.6
idna==2.7
itsdangerous==0.24
Jinja2==2.10
MarkupSafe==1.0
marshmallow==3.0.0b8
pip==9.0.3
pytz==2018.5
setuptools==39.0.1
psutil==5.4.8
pygal==2.4.0
PyJWT==1.4.2
pytz==2018.7
requests==2.20.0
setuptools==40.5.0
six==1.11.0
timeago==1.0.8
tzlocal==1.5.1
urllib3==1.24
Werkzeug==0.14.1

View File

@ -12,4 +12,4 @@ if __name__ == '__main__':
except ValueError:
PORT = 5555
init()
app.run(HOST, PORT, debug=True)
app.run(HOST, PORT, debug=False)

View File

@ -1,4 +1,3 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 15
VisualStudioVersion = 15.0.26730.16
@ -11,6 +10,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution
_customcallbacks.gsc = _customcallbacks.gsc
README.md = README.md
RunPublishPre.cmd = RunPublishPre.cmd
RunPublishRelease.cmd = RunPublishRelease.cmd
version.txt = version.txt
EndProjectSection
EndProject
@ -38,6 +38,9 @@ Project("{888888A0-9F3D-457C-B088-3A5042F75D52}") = "DiscordWebhook", "DiscordWe
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "ScriptPlugins", "ScriptPlugins", "{3F9ACC27-26DB-49FA-BCD2-50C54A49C9FA}"
ProjectSection(SolutionItems) = preProject
Plugins\ScriptPlugins\ParserIW3.js = Plugins\ScriptPlugins\ParserIW3.js
Plugins\ScriptPlugins\ParserPT6.js = Plugins\ScriptPlugins\ParserPT6.js
Plugins\ScriptPlugins\ParserTeknoMW3.js = Plugins\ScriptPlugins\ParserTeknoMW3.js
Plugins\ScriptPlugins\SharedGUIDKick.js = Plugins\ScriptPlugins\SharedGUIDKick.js
Plugins\ScriptPlugins\VPNDetection.js = Plugins\ScriptPlugins\VPNDetection.js
EndProjectSection

View File

@ -17,7 +17,7 @@
<SuppressCollectPythonCloudServiceFiles>true</SuppressCollectPythonCloudServiceFiles>
<Name>Master</Name>
<RootNamespace>Master</RootNamespace>
<InterpreterId>MSBuild|dev_env|$(MSBuildProjectFullPath)</InterpreterId>
<InterpreterId>MSBuild|env_master|$(MSBuildProjectFullPath)</InterpreterId>
<IsWindowsApplication>False</IsWindowsApplication>
<PythonRunWebServerCommand>
</PythonRunWebServerCommand>
@ -73,6 +73,9 @@
<Compile Include="master\resources\null.py">
<SubType>Code</SubType>
</Compile>
<Compile Include="Master\resources\server.py">
<SubType>Code</SubType>
</Compile>
<Compile Include="master\resources\version.py">
<SubType>Code</SubType>
</Compile>
@ -109,15 +112,17 @@
</ItemGroup>
<ItemGroup>
<Content Include="master\config\master.json" />
<Content Include="master\templates\serverlist.html" />
<None Include="Release.pubxml" />
<Content Include="requirements.txt" />
<Content Include="master\templates\index.html" />
<Content Include="master\templates\layout.html" />
</ItemGroup>
<ItemGroup>
<Interpreter Include="dev_env\">
<Id>dev_env</Id>
<Interpreter Include="env_master\">
<Id>env_master</Id>
<Version>3.6</Version>
<Description>dev_env (Python 3.6 (64-bit))</Description>
<Description>env_master (Python 3.6 (64-bit))</Description>
<InterpreterPath>Scripts\python.exe</InterpreterPath>
<WindowsInterpreterPath>Scripts\pythonw.exe</WindowsInterpreterPath>
<PathEnvironmentVariable>PYTHONPATH</PathEnvironmentVariable>

View File

@ -1,14 +1,16 @@
class ServerModel(object):
def __init__(self, id, port, game, hostname, clientnum, maxclientnum, map, gametype):
def __init__(self, id, port, game, hostname, clientnum, maxclientnum, map, gametype, ip, version):
self.id = id
self.port = port
self.version = version
self.game = game
self.hostname = hostname
self.clientnum = clientnum
self.maxclientnum = maxclientnum
self.map = map
self.gametype = gametype
self.ip = ip
def __repr__(self):
return '<ServerModel(id={id})>'.format(id=self.id)

View File

@ -4,6 +4,7 @@ from flask_jwt_extended import jwt_required
from marshmallow import ValidationError
from master.schema.instanceschema import InstanceSchema
from master import ctx
import json
class Instance(Resource):
def get(self, id=None):
@ -21,6 +22,11 @@ class Instance(Resource):
@jwt_required
def put(self, id):
try:
for server in request.json['servers']:
if 'ip' not in server or server['ip'] == 'localhost':
server['ip'] = request.remote_addr
if 'version' not in server:
server['version'] = 'Unknown'
instance = InstanceSchema().load(request.json)
except ValidationError as err:
return {'message' : err.messages }, 400
@ -30,6 +36,11 @@ class Instance(Resource):
@jwt_required
def post(self):
try:
for server in request.json['servers']:
if 'ip' not in server or server['ip'] == 'localhost':
server['ip'] = request.remote_addr
if 'version' not in server:
server['version'] = 'Unknown'
instance = InstanceSchema().load(request.json)
except ValidationError as err:
return {'message' : err.messages }, 400

View File

@ -0,0 +1,6 @@
from flask_restful import Resource
class Server(Resource):
"""description of class"""

View File

@ -6,10 +6,12 @@ from master.resources.authenticate import Authenticate
from master.resources.version import Version
from master.resources.history_graph import HistoryGraph
from master.resources.localization import Localization
from master.resources.server import Server
api.add_resource(Null, '/null')
api.add_resource(Instance, '/instance/', '/instance/<string:id>')
api.add_resource(Version, '/version')
api.add_resource(Authenticate, '/authenticate')
api.add_resource(HistoryGraph, '/history/', '/history/<int:history_count>')
api.add_resource(Localization, '/localization/', '/localization/<string:language_tag>')
api.add_resource(Localization, '/localization/', '/localization/<string:language_tag>')
api.add_resource(Server, '/server')

View File

@ -4,19 +4,26 @@ from master.models.servermodel import ServerModel
class ServerSchema(Schema):
id = fields.Int(
required=True,
validate=validate.Range(1, 2147483647, 'invalid id')
validate=validate.Range(1, 25525525525565535, 'invalid id')
)
ip = fields.Str(
required=True
)
port = fields.Int(
required=True,
validate=validate.Range(1, 665535, 'invalid port')
validate=validate.Range(1, 65535, 'invalid port')
)
version = fields.String(
required=False,
validate=validate.Length(0, 128, 'invalid server version')
)
game = fields.String(
required=True,
validate=validate.Length(1, 8, 'invalid game name')
validate=validate.Length(1, 5, 'invalid game name')
)
hostname = fields.String(
required=True,
validate=validate.Length(1, 48, 'invalid hostname')
validate=validate.Length(1, 64, 'invalid hostname')
)
clientnum = fields.Int(
required=True,

View File

@ -39,6 +39,7 @@
<script src="https://code.jquery.com/jquery-3.3.1.min.js"
integrity="sha256-FgpCb/KJQlLNfOu91ta32o/NMZxltwRo8QtmkMRdAu8="
crossorigin="anonymous"></script>
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/js/bootstrap.min.js" integrity="sha384-JZR6Spejh4U02d8jOt6vLEHfe/JQGiRRSQQxSfFWpi1MquVdAyjUar5+76PVCmYl" crossorigin="anonymous"></script>
{% block scripts %}{% endblock %}
</body>
</html>

View File

@ -0,0 +1,113 @@
{% extends "layout.html" %}
{% block content %}
<!-- todo: move this! -->
<style>
.server-row {
cursor: pointer;
}
.modal-content, .nav-item {
background-color: #212529;
color: #fff;
}
.modal-header, .modal-footer {
border-color: #32383e !important;
}
.modal-dark button.close, a.nav-link {
color: #fff;
}
</style>
<div class="modal modal-dark" id="serverModalCenter" tabindex="-1" role="dialog" aria-labelledby="serverModalCenterTitle" aria-hidden="true">
<div class="modal-dialog modal-dialog-centered" role="document">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="serverModalTitle">Modal title</h5>
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="modal-body">
<div class="h5" id="server_socket"></div>
</div>
<div class="modal-footer">
<button id="connect_button" type="button" class="btn btn-dark">Connect</button>
<button type="button" class="btn btn-dark" data-dismiss="modal">Close</button>
</div>
</div>
</div>
</div>
<div class="row">
<div class="col-12">
<nav>
<div class="nav nav-tabs" id="server_game_tabs" role="tablist">
{% for game in games %}
<a class="nav-item nav-link {{'active' if loop.first else ''}}" id="{{game}}_servers_tab" data-toggle="tab" href="#{{game}}_servers" role="tab" aria-controls="{{game}}_servers" aria-selected="{{'true' if loop.first else 'false' }}">{{game}}</a>
{% endfor %}
</div>
</nav>
<div class="tab-content" id="server_game_tabs_content">
{% for game, servers in games.items() %}
<div class="tab-pane {{'show active' if loop.first else ''}}" id="{{game}}_servers" role="tabpanel" aria-labelledby="{{game}}_servers_tab">
<table class="table table-dark table-striped table-hover table-responsive-lg">
<thead>
<tr>
<th>Server Name</th>
<th>Map Name</th>
<th>Players</th>
<th>Mode</th>
<th class="text-center">Connect</th>
</tr>
</thead>
<tbody>
{% for server in servers %}
<tr class="server-row" data-toggle="modal" data-target="#serverModalCenter"
data-ip="{{server.ip}}" data-port="{{server.port}}">
<td data-hostname="{{server.hostname}}" class="server-hostname">{{server.hostname}}</td>
<td data-map="{{server.map}} " class="server-map">{{server.map}}</td>
<td data-clientnum="{{server.clientnum}}" data-maxclientnum="{{server.maxclientnum}}"
class="server-clientnum">
{{server.clientnum}}/{{server.maxclientnum}}
</td>
<td data-gametype="{{server.gametype}}" class="server-gametype">{{server.gametype}}</td>
<td class="text-center"><span class="oi oi-play-circle"></span></td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
{% endfor %}
</div>
<div class="w-100 small text-right text-muted">
<span>Developed by RaidMax</span><br />
<span>PRERELEASE</span>
</div>
</div>
</div>
{% endblock %}
{% block scripts %}
<script>
$(document).ready(() => {
$('.server-row').off('click');
$('.server-row').on('click', function (e) {
$('#serverModalTitle').text($(this).find('.server-hostname').text());
$('#server_socket').text(`/connect ${$(this).data('ip')}:${$(this).data('port')}`);
});
$('#connect_button').off('click');
$('#connect_button').on('click', (e) => alert('soon...'));
});
</script>
{% endblock %}

View File

@ -4,8 +4,9 @@ Routes and views for the flask application.
from datetime import datetime
from flask import render_template
from master import app
from master import app, ctx
from master.resources.history_graph import HistoryGraph
from collections import defaultdict
@app.route('/')
def home():
@ -19,3 +20,16 @@ def home():
client_count = _history_graph[0]['client_count'],
server_count = _history_graph[0]['server_count']
)
@app.route('/servers')
def servers():
servers = defaultdict(list)
if len(ctx.instance_list.values()) > 0:
ungrouped_servers = [server for instance in ctx.instance_list.values() for server in instance.servers]
for server in ungrouped_servers:
servers[server.game].append(server)
return render_template(
'serverlist.html',
title = 'Server List',
games = servers
)

View File

@ -1,16 +1,26 @@
aniso8601==3.0.0
aniso8601==3.0.2
APScheduler==3.5.3
certifi==2018.10.15
chardet==3.0.4
click==6.7
Flask==0.12.2
Flask==1.0.2
Flask-JWT==0.3.2
Flask-JWT-Extended==3.8.1
Flask-RESTful==0.3.6
idna==2.7
itsdangerous==0.24
Jinja2==2.10
MarkupSafe==1.0
marshmallow==3.0.0b8
pip==9.0.1
pip==9.0.3
psutil==5.4.8
pygal==2.4.0
PyJWT==1.4.2
pytz==2018.4
setuptools==39.0.1
pytz==2018.7
requests==2.20.0
setuptools==40.5.0
six==1.11.0
timeago==1.0.8
tzlocal==1.5.1
urllib3==1.24
Werkzeug==0.14.1

View File

@ -1,4 +1,5 @@
using SharedLibraryCore;
using SharedLibraryCore.Database.Models;
using SharedLibraryCore.Objects;
using System;
using System.Collections.Generic;
@ -8,7 +9,7 @@ using System.Threading.Tasks;
namespace IW4ScriptCommands.Commands
{
class Balance : Command
class Balance
{
private class TeamAssignment
{
@ -16,39 +17,40 @@ namespace IW4ScriptCommands.Commands
public int Num { get; set; }
public IW4MAdmin.Plugins.Stats.Models.EFClientStatistics Stats { get; set; }
}
public Balance() : base("balance", "balance teams", "bal", Player.Permission.Trusted, false, null)
{
}
public override async Task ExecuteAsync(GameEvent E)
public static string GetTeamAssignments(EFClient client, bool isDisconnect, Server server, string teamsString = "")
{
string teamsString = (await E.Owner.GetDvarAsync<string>("sv_iw4madmin_teams")).Value;
var scriptClientTeams = teamsString.Split(';', StringSplitOptions.RemoveEmptyEntries)
.Select(c => c.Split(','))
.Select(c => new TeamAssignment()
{
CurrentTeam = (IW4MAdmin.Plugins.Stats.IW4Info.Team)Enum.Parse(typeof(IW4MAdmin.Plugins.Stats.IW4Info.Team), c[1]),
Num = E.Owner.Players.FirstOrDefault(p => p?.NetworkId == c[0].ConvertLong())?.ClientNumber ?? -1,
Stats = IW4MAdmin.Plugins.Stats.Plugin.Manager.GetClientStats(E.Owner.Players.FirstOrDefault(p => p?.NetworkId == c[0].ConvertLong()).ClientId, E.Owner.GetHashCode())
Num = server.GetClientsAsList().FirstOrDefault(p => p.ClientNumber == Int32.Parse(c[0]))?.ClientNumber ?? -1,
Stats = IW4MAdmin.Plugins.Stats.Plugin.Manager.GetClientStats(server.Clients.FirstOrDefault(p => p.ClientNumber == Int32.Parse(c[0])).ClientId, server.EndPoint)
})
.ToList();
// at least one team is full so we can't balance
if (scriptClientTeams.Count(ct => ct.CurrentTeam == IW4MAdmin.Plugins.Stats.IW4Info.Team.Axis) >= Math.Floor(E.Owner.MaxClients / 2.0)
|| scriptClientTeams.Count(ct => ct.CurrentTeam == IW4MAdmin.Plugins.Stats.IW4Info.Team.Allies) >= Math.Floor(E.Owner.MaxClients / 2.0))
if (scriptClientTeams.Count(ct => ct.CurrentTeam == IW4MAdmin.Plugins.Stats.IW4Info.Team.Axis) >= Math.Floor(server.MaxClients / 2.0)
|| scriptClientTeams.Count(ct => ct.CurrentTeam == IW4MAdmin.Plugins.Stats.IW4Info.Team.Allies) >= Math.Floor(server.MaxClients / 2.0))
{
await E.Origin?.Tell(Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_BALANCE_FAIL"]);
return;
// E.Origin?.Tell(Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_BALANCE_FAIL"]);
return string.Empty;
}
List<string> teamAssignments = new List<string>();
var activeClients = E.Owner.GetPlayersAsList().Select(c => new TeamAssignment()
var _c = server.GetClientsAsList();
if (isDisconnect && client != null)
{
_c = _c.Where(c => c.ClientNumber != client.ClientNumber).ToList();
}
var activeClients = _c.Select(c => new TeamAssignment()
{
Num = c.ClientNumber,
Stats = IW4MAdmin.Plugins.Stats.Plugin.Manager.GetClientStats(c.ClientId, E.Owner.GetHashCode()),
CurrentTeam = IW4MAdmin.Plugins.Stats.Plugin.Manager.GetClientStats(c.ClientId, E.Owner.GetHashCode()).Team
Stats = IW4MAdmin.Plugins.Stats.Plugin.Manager.GetClientStats(c.ClientId, server.EndPoint),
CurrentTeam = IW4MAdmin.Plugins.Stats.Plugin.Manager.GetClientStats(c.ClientId, server.EndPoint).Team
})
.Where(c => scriptClientTeams.FirstOrDefault(sc => sc.Num == c.Num)?.CurrentTeam != IW4MAdmin.Plugins.Stats.IW4Info.Team.Spectator)
.Where(c => c.CurrentTeam != scriptClientTeams.FirstOrDefault(p => p.Num == c.Num)?.CurrentTeam)
@ -170,28 +172,29 @@ namespace IW4ScriptCommands.Commands
teamAssignments.Add($"{assignment.Num},2");
assignment.Stats.Team = IW4MAdmin.Plugins.Stats.IW4Info.Team.Allies;
}
foreach (var assignment in axisTeam)
{
teamAssignments.Add($"{assignment.Num},3");
assignment.Stats.Team = IW4MAdmin.Plugins.Stats.IW4Info.Team.Axis;
}
if (alliesTeam.Count(ac => scriptClientTeams.First(sc => sc.Num == ac.Num).CurrentTeam != ac.CurrentTeam) == 0 &&
axisTeam.Count(ac => scriptClientTeams.First(sc => sc.Num == ac.Num).CurrentTeam != ac.CurrentTeam) == 0)
{
await E.Origin.Tell(Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_BALANCE_FAIL_BALANCED"]);
return;
}
//if (alliesTeam.Count(ac => scriptClientTeams.First(sc => sc.Num == ac.Num).CurrentTeam != ac.CurrentTeam) == 0 &&
// axisTeam.Count(ac => scriptClientTeams.First(sc => sc.Num == ac.Num).CurrentTeam != ac.CurrentTeam) == 0)
//{
// //E.Origin.Tell(Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_BALANCE_FAIL_BALANCED"]);
// return string.Empty;
//}
if (E.Origin?.Level > Player.Permission.Administrator)
{
await E.Origin.Tell($"Allies Elo: {(alliesTeam.Count > 0 ? alliesTeam.Average(t => t.Stats.Performance) : 0)}");
await E.Origin.Tell($"Axis Elo: {(axisTeam.Count > 0 ? axisTeam.Average(t => t.Stats.Performance) : 0)}");
}
//if (E.Origin?.Level > Player.Permission.Administrator)
//{
// E.Origin.Tell($"Allies Elo: {(alliesTeam.Count > 0 ? alliesTeam.Average(t => t.Stats.Performance) : 0)}");
// E.Origin.Tell($"Axis Elo: {(axisTeam.Count > 0 ? axisTeam.Average(t => t.Stats.Performance) : 0)}");
//}
//E.Origin.Tell("Balance command sent");
string args = string.Join(",", teamAssignments);
await E.Owner.ExecuteCommandAsync($"sv_iw4madmin_command \"balance:{args}\"");
await E.Origin.Tell("Balance command sent");
return args;
}
}
}

View File

@ -0,0 +1,54 @@
using IW4ScriptCommands.Commands;
using Microsoft.AspNetCore.Mvc;
using SharedLibraryCore;
using SharedLibraryCore.Objects;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace WebfrontCore.Controllers.API
{
[Route("api/gsc/[action]")]
public class GscApiController : ApiController
{
[HttpGet("{networkId}")]
public IActionResult ClientInfo(string networkId)
{
var clientInfo = Manager.GetActiveClients()
.FirstOrDefault(c => c.NetworkId == networkId.ConvertLong());
if (clientInfo != null)
{
var sb = new StringBuilder();
sb.AppendLine($"admin={clientInfo.IsPrivileged()}");
sb.AppendLine($"level={(int)clientInfo.Level}");
sb.AppendLine($"levelstring={clientInfo.Level.ToLocalizedLevelName()}");
sb.AppendLine($"connections={clientInfo.Connections}");
sb.AppendLine($"authenticated={clientInfo.GetAdditionalProperty<bool>("IsLoggedIn") == true}");
return Content(sb.ToString());
}
return Content("");
}
[HttpGet("{networkId}")]
public IActionResult GetTeamAssignments(string networkId, int serverId, string teams = "", bool isDisconnect = false)
{
return Unauthorized();
var client = Manager.GetActiveClients()
.FirstOrDefault(c => c.NetworkId == networkId.ConvertLong());
var server = Manager.GetServers().First(c => c.EndPoint == serverId);
teams = teams ?? string.Empty;
string assignments = Balance.GetTeamAssignments(client, isDisconnect, server, teams);
return Content(assignments);
}
}
}

View File

@ -3,8 +3,10 @@
<PropertyGroup>
<OutputType>Library</OutputType>
<TargetFramework>netcoreapp2.1</TargetFramework>
<RuntimeFrameworkVersion>2.1.5</RuntimeFrameworkVersion>
<ApplicationIcon />
<StartupObject />
<Configurations>Debug;Release;Prerelease</Configurations>
</PropertyGroup>
<Target Name="PostBuild" AfterTargets="PostBuildEvent">
@ -17,7 +19,7 @@
</ItemGroup>
<ItemGroup>
<PackageReference Update="Microsoft.NETCore.App"/>
<PackageReference Update="Microsoft.NETCore.App" Version="2.1.5" />
</ItemGroup>
</Project>

View File

@ -17,15 +17,10 @@ namespace IW4ScriptCommands
public Task OnEventAsync(GameEvent E, Server S)
{
//if (E.Type == GameEvent.EventType.JoinTeam || E.Type == GameEvent.EventType.Disconnect)
//{
// E.Origin = new SharedLibraryCore.Objects.Player()
// {
// ClientId = 1,
// CurrentServer = E.Owner
// };
// return new Commands.Balance().ExecuteAsync(E);
//}
if (E.Type == GameEvent.EventType.Start)
{
return S.SetDvarAsync("sv_iw4madmin_serverid", S.EndPoint);
}
if (E.Type == GameEvent.EventType.Warn)
{

View File

@ -1,4 +1,5 @@
using SharedLibraryCore;
using SharedLibraryCore.Database.Models;
using SharedLibraryCore.Objects;
using System;
using System.Collections.Generic;
@ -8,14 +9,15 @@ namespace IW4MAdmin.Plugins.Login.Commands
{
public class CLogin : Command
{
public CLogin() : base("login", Utilities.CurrentLocalization.LocalizationIndex["PLUGINS_LOGIN_COMMANDS_LOGIN_DESC"], "li", Player.Permission.Trusted, false, new CommandArgument[]
public CLogin() : base("login", Utilities.CurrentLocalization.LocalizationIndex["PLUGINS_LOGIN_COMMANDS_LOGIN_DESC"], "li", EFClient.Permission.Trusted, false, new CommandArgument[]
{
new CommandArgument()
{
Name = Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_ARGS_PASSWORD"],
Required = true
}
}){ }
})
{ }
public override async Task ExecuteAsync(GameEvent E)
{
@ -25,12 +27,12 @@ namespace IW4MAdmin.Plugins.Login.Commands
if (hashedPassword[0] == client.Password)
{
Plugin.AuthorizedClients[E.Origin.ClientId] = true;
await E.Origin.Tell(Utilities.CurrentLocalization.LocalizationIndex["PLUGINS_LOGIN_COMMANDS_LOGIN_SUCCESS"]);
E.Origin.Tell(Utilities.CurrentLocalization.LocalizationIndex["PLUGINS_LOGIN_COMMANDS_LOGIN_SUCCESS"]);
}
else
{
await E.Origin.Tell(Utilities.CurrentLocalization.LocalizationIndex["PLUGINS_LOGIN_COMMANDS_LOGIN_FAIL"]);
E.Origin.Tell(Utilities.CurrentLocalization.LocalizationIndex["PLUGINS_LOGIN_COMMANDS_LOGIN_FAIL"]);
}
}
}

View File

@ -3,6 +3,7 @@
<PropertyGroup>
<OutputType>Library</OutputType>
<TargetFramework>netcoreapp2.1</TargetFramework>
<RuntimeFrameworkVersion>2.1.5</RuntimeFrameworkVersion>
<ApplicationIcon />
<StartupObject />
<PackageId>RaidMax.IW4MAdmin.Plugins.Login</PackageId>
@ -21,7 +22,7 @@
</ItemGroup>
<ItemGroup>
<PackageReference Update="Microsoft.NETCore.App"/>
<PackageReference Update="Microsoft.NETCore.App" Version="2.1.5" />
</ItemGroup>
<Target Name="PostBuild" AfterTargets="PostBuildEvent">

View File

@ -3,9 +3,9 @@ using System.Reflection;
using System.Threading.Tasks;
using SharedLibraryCore;
using SharedLibraryCore.Configuration;
using SharedLibraryCore.Database.Models;
using SharedLibraryCore.Exceptions;
using SharedLibraryCore.Interfaces;
using SharedLibraryCore.Objects;
namespace IW4MAdmin.Plugins.Login
{
@ -22,12 +22,13 @@ namespace IW4MAdmin.Plugins.Login
public Task OnEventAsync(GameEvent E, Server S)
{
if (E.Remote || Config.RequirePrivilegedClientLogin == false)
if (E.IsRemote || Config.RequirePrivilegedClientLogin == false)
return Task.CompletedTask;
if (E.Type == GameEvent.EventType.Connect)
{
AuthorizedClients.TryAdd(E.Origin.ClientId, false);
E.Origin.SetAdditionalProperty("IsLoggedIn", false);
}
if (E.Type == GameEvent.EventType.Disconnect)
@ -37,11 +38,11 @@ namespace IW4MAdmin.Plugins.Login
if (E.Type == GameEvent.EventType.Command)
{
if (E.Origin.Level < Player.Permission.Moderator ||
E.Origin.Level == Player.Permission.Console)
if (E.Origin.Level < EFClient.Permission.Moderator ||
E.Origin.Level == EFClient.Permission.Console)
return Task.CompletedTask;
E.Owner.Manager.GetPrivilegedClients().TryGetValue(E.Origin.ClientId, out Player client);
E.Owner.Manager.GetPrivilegedClients().TryGetValue(E.Origin.ClientId, out EFClient client);
if (((Command)E.Extra).Name == new SharedLibraryCore.Commands.CSetPassword().Name &&
client?.Password == null)
@ -51,7 +52,14 @@ namespace IW4MAdmin.Plugins.Login
return Task.CompletedTask;
if (!AuthorizedClients[E.Origin.ClientId])
{
throw new AuthorizationException(Utilities.CurrentLocalization.LocalizationIndex["PLUGINS_LOGIN_AUTH"]);
}
else
{
E.Origin.SetAdditionalProperty("IsLoggedIn", true);
}
}
return Task.CompletedTask;

View File

@ -16,9 +16,9 @@ namespace IW4MAdmin.Plugins.ProfanityDeterment
{
OffensiveWords = new List<string>()
{
"nigger",
"nigga",
"fuck"
@"\s*n+.*i+.*g+.*e+.*r+\s*",
@"\s*n+.*i+.*g+.*a+\s*",
@"\s*f+u+.*c+.*k+.*\s*"
};
var loc = Utilities.CurrentLocalization.LocalizationIndex;

View File

@ -5,6 +5,7 @@ using System.Text.RegularExpressions;
using System.Threading.Tasks;
using SharedLibraryCore;
using SharedLibraryCore.Configuration;
using SharedLibraryCore.Database.Models;
using SharedLibraryCore.Interfaces;
using SharedLibraryCore.Objects;
@ -22,10 +23,10 @@ namespace IW4MAdmin.Plugins.ProfanityDeterment
ConcurrentDictionary<int, Tracking> ProfanityCounts;
IManager Manager;
public async Task OnEventAsync(GameEvent E, Server S)
public Task OnEventAsync(GameEvent E, Server S)
{
if (!Settings.Configuration().EnableProfanityDeterment)
return;
return Task.CompletedTask;
if (E.Type == GameEvent.EventType.Connect)
{
@ -48,9 +49,10 @@ namespace IW4MAdmin.Plugins.ProfanityDeterment
if (containsObjectionalWord)
{
await E.Origin.Kick(Settings.Configuration().ProfanityKickMessage, new Player()
E.Origin.Kick(Settings.Configuration().ProfanityKickMessage, new EFClient()
{
ClientId = 1
ClientId = 1,
CurrentServer = E.Owner
});
};
}
@ -84,23 +86,18 @@ namespace IW4MAdmin.Plugins.ProfanityDeterment
var clientProfanity = ProfanityCounts[E.Origin.ClientId];
if (clientProfanity.Infringements >= Settings.Configuration().KickAfterInfringementCount)
{
await clientProfanity.Client.Kick(Settings.Configuration().ProfanityKickMessage, new Player()
{
ClientId = 1
});
clientProfanity.Client.Kick(Settings.Configuration().ProfanityKickMessage, Utilities.IW4MAdminClient(E.Owner));
}
else if (clientProfanity.Infringements < Settings.Configuration().KickAfterInfringementCount)
{
clientProfanity.Infringements++;
await clientProfanity.Client.Warn(Settings.Configuration().ProfanityWarningMessage, new Player()
{
ClientId = 1
});
clientProfanity.Client.Warn(Settings.Configuration().ProfanityWarningMessage, Utilities.IW4MAdminClient(E.Owner));
}
}
}
return Task.CompletedTask;
}
public async Task OnLoadAsync(IManager manager)

View File

@ -3,6 +3,7 @@
<PropertyGroup>
<OutputType>Library</OutputType>
<TargetFramework>netcoreapp2.1</TargetFramework>
<RuntimeFrameworkVersion>2.1.5</RuntimeFrameworkVersion>
<ApplicationIcon />
<StartupObject />
<PackageId>RaidMax.IW4MAdmin.Plugins.ProfanityDeterment</PackageId>
@ -19,7 +20,7 @@
</ItemGroup>
<ItemGroup>
<PackageReference Update="Microsoft.NETCore.App"/>
<PackageReference Update="Microsoft.NETCore.App" Version="2.1.5" />
</ItemGroup>
<Target Name="PostBuild" AfterTargets="PostBuildEvent">

View File

@ -1,16 +1,17 @@
using System;
using System.Collections.Generic;
using System.Text;
using SharedLibraryCore.Database.Models;
using SharedLibraryCore.Objects;
namespace IW4MAdmin.Plugins.ProfanityDeterment
{
class Tracking
{
public Player Client { get; private set; }
public EFClient Client { get; private set; }
public int Infringements { get; set; }
public Tracking(Player client)
public Tracking(EFClient client)
{
Client = client;
Infringements = 0;

View File

@ -0,0 +1,33 @@
var rconParser;
var eventParser;
var plugin = {
author: 'RaidMax',
version: 0.1,
name: 'IW3 Parser',
isParser: true,
onEventAsync: function (gameEvent, server) {
},
onLoadAsync: function (manager) {
rconParser = manager.GenerateDynamicRConParser();
eventParser = manager.GenerateDynamicEventParser();
rconParser.Configuration.CommandPrefixes.Tell = 'tell {0} {1}';
rconParser.Configuration.CommandPrefixes.Say = 'say {0}';
rconParser.Configuration.CommandPrefixes.Kick = 'clientkick {0} "{1}"';
rconParser.Configuration.CommandPrefixes.Ban = 'clientkick {0} "{1}"';
rconParser.Configuration.CommandPrefixes.TempBan = 'tempbanclient {0} "{1}"';
rconParser.Version = 'CoD4 MP 1.8 build 13620 Thu Oct 04 00:43:04 2007 win-x86';
eventParser.Configuration.GameDirectory = 'main';
eventParser.Version = 'CoD4 MP 1.8 build 13620 Thu Oct 04 00:43:04 2007 win-x86';
},
onUnloadAsync: function () {
},
onTickAsync: function (server) {
}
};

View File

@ -0,0 +1,49 @@
var rconParser;
var eventParser;
var plugin = {
author: 'RaidMax',
version: 0.1,
name: 'Plutoniun T6 Parser',
isParser: true,
onEventAsync: function (gameEvent, server) {
},
onLoadAsync: function (manager) {
rconParser = manager.GenerateDynamicRConParser();
eventParser = manager.GenerateDynamicEventParser();
rconParser.Configuration.CommandPrefixes.Tell = 'tell {0} {1}';
rconParser.Configuration.CommandPrefixes.Say = 'say {0}';
rconParser.Configuration.CommandPrefixes.Kick = 'clientkick_for_reason {0} "{1}"';
rconParser.Configuration.CommandPrefixes.Ban = 'clientkick_for_reason {0} "{1}"';
rconParser.Configuration.CommandPrefixes.TempBan = 'clientkick_for_reason {0} "{1}"';
rconParser.Configuration.CommandPrefixes.RConGetDvar = '\xff\xff\xff\xffrcon {0} get {1}';
rconParser.Configuration.Dvar.Pattern = '^(.+) is "(.+)?"$';
rconParser.Configuration.Dvar.AddMapping(106, 1);
rconParser.Configuration.Dvar.AddMapping(107, 2);
rconParser.Configuration.WaitForResponse = false;
rconParser.Configuration.Status.Pattern = '^ *([0-9]+) +([0-9]+) +(.+) +((?:[A-Z]+|[0-9]+)) +((?:[A-Z]|[0-9]){8,16}) +(.{0,16}) +([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, 4);
rconParser.Configuration.Status.AddMapping(103, 5);
rconParser.Configuration.Status.AddMapping(104, 6);
rconParser.Configuration.Status.AddMapping(105, 8);
eventParser.Configuration.GameDirectory = 't6r\\\\data';
rconParser.Version = 'Call of Duty Multiplayer - Ship COD_T6_S MP build 1.0.44 CL(1759941) CODPCAB2 CEG Fri May 9 19:19:19 2014 win-x86 813e66d5';
eventParser.Version = 'Call of Duty Multiplayer - Ship COD_T6_S MP build 1.0.44 CL(1759941) CODPCAB2 CEG Fri May 9 19:19:19 2014 win-x86 813e66d5';
},
onUnloadAsync: function () {
},
onTickAsync: function (server) {
}
};

View File

@ -0,0 +1,40 @@
var rconParser;
var eventParser;
var plugin = {
author: 'RaidMax',
version: 0.1,
name: 'Tekno MW3 Parser',
isParser: true,
onEventAsync: function (gameEvent, server) {
},
onLoadAsync: function (manager) {
rconParser = manager.GenerateDynamicRConParser();
eventParser = manager.GenerateDynamicEventParser();
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.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.Tell = 'tell {0} {1}';
rconParser.Configuration.CommandPrefixes.Say = 'say {0}';
rconParser.Configuration.CommandPrefixes.Kick = 'dropclient {0} "{1}"';
rconParser.Configuration.CommandPrefixes.Ban = 'dropclient {0} "{1}"';
rconParser.Configuration.CommandPrefixes.TempBan = 'tempbanclient {0} "{1}"';
rconParser.Configuration.Dvar.AddMapping(107, 1); // RCon DvarValue
rconParser.Configuration.Dvar.Pattern = '^(.*)$';
rconParser.Version = 'IW5 MP 1.4 build 382 latest Thu Jan 19 2012 11:09:49AM win-x86';
eventParser.Configuration.GameDirectory = 'scripts';
eventParser.Version = 'IW5 MP 1.4 build 382 latest Thu Jan 19 2012 11:09:49AM win-x86';
},
onUnloadAsync: function () {
},
onTickAsync: function (server) {
}
};

View File

@ -1,6 +1,6 @@
var plugin = {
author: 'RaidMax',
version: 1.0,
version: 1.1,
name: 'Shared GUID Kicker Plugin',
onEventAsync: function (gameEvent, server) {
@ -10,8 +10,7 @@ var plugin = {
}
// connect or join event
if (gameEvent.Type === 3 ||
gameEvent.Type === 4) {
if (gameEvent.Type === 3) {
// this GUID seems to have been packed in a IW4 torrent and results in an unreasonable amount of people using the same GUID
if (gameEvent.Origin.NetworkId === -805366929435212061) {
gameEvent.Origin.Kick('Your GUID is generic. Delete players/guids.dat and rejoin', _IW4MAdminClient);

View File

@ -28,7 +28,6 @@ var plugin = {
var re = cl.GetAsync('https://api.xdefcon.com/proxy/check/?ip=' + origin.IPAddressString).Result;
var co = re.Content;
var parsedJSON = JSON.parse(co.ReadAsStringAsync().Result);
// todo: does this work as expected now?
co.Dispose();
re.Dispose();
cl.Dispose();
@ -39,10 +38,7 @@ var plugin = {
if (usingVPN) {
this.logger.WriteInfo(origin + ' is using a VPN (' + origin.IPAddressString + ')');
var library = importNamespace('SharedLibraryCore');
var kickOrigin = new library.Objects.Player();
kickOrigin.ClientId = 1;
origin.Kick(_localization.LocalizationIndex["SERVER_KICK_VPNS_NOTALLOWED"], kickOrigin);
origin.Kick(_localization.LocalizationIndex["SERVER_KICK_VPNS_NOTALLOWED"], _IW4MAdminClient);
}
},
@ -55,7 +51,7 @@ var plugin = {
onLoadAsync: function (manager) {
this.manager = manager;
this.logger = manager.GetLogger();
this.logger = manager.GetLogger(0);
},
onUnloadAsync: function () {

View File

@ -1,7 +1,7 @@
using SharedLibraryCore.Helpers;
using IW4MAdmin.Plugins.Stats.Models;
using SharedLibraryCore.Helpers;
using SharedLibraryCore.Interfaces;
using SharedLibraryCore.Objects;
using IW4MAdmin.Plugins.Stats.Models;
using System;
using System.Collections.Generic;
using System.Linq;
@ -20,14 +20,16 @@ namespace IW4MAdmin.Plugins.Stats.Cheat
};
public ChangeTracking<EFACSnapshot> Tracker { get; private set; }
public const int QUEUE_COUNT = 10;
public List<EFClientKill> QueuedHits { get; set; }
int Kills;
int HitCount;
Dictionary<IW4Info.HitLocation, int> HitLocationCount;
double AngleDifferenceAverage;
EFClientStatistics ClientStats;
DateTime LastHit;
long LastOffset;
IW4Info.WeaponName LastWeapon;
ILogger Log;
Strain Strain;
readonly DateTime ConnectionTime = DateTime.UtcNow;
@ -37,49 +39,55 @@ namespace IW4MAdmin.Plugins.Stats.Cheat
Log = log;
HitLocationCount = new Dictionary<IW4Info.HitLocation, int>();
foreach (var loc in Enum.GetValues(typeof(IW4Info.HitLocation)))
{
HitLocationCount.Add((IW4Info.HitLocation)loc, 0);
}
ClientStats = clientStats;
Strain = new Strain();
Tracker = new ChangeTracking<EFACSnapshot>();
QueuedHits = new List<EFClientKill>();
}
/// <summary>
/// Analyze kill and see if performed by a cheater
/// </summary>
/// <param name="kill">kill performed by the player</param>
/// <param name="hit">kill performed by the player</param>
/// <returns>true if detection reached thresholds, false otherwise</returns>
public DetectionPenaltyResult ProcessKill(EFClientKill kill, bool isDamage)
public DetectionPenaltyResult ProcessHit(EFClientKill hit, bool isDamage)
{
if ((kill.DeathType != IW4Info.MeansOfDeath.MOD_PISTOL_BULLET &&
kill.DeathType != IW4Info.MeansOfDeath.MOD_RIFLE_BULLET &&
kill.DeathType != IW4Info.MeansOfDeath.MOD_HEAD_SHOT) ||
kill.HitLoc == IW4Info.HitLocation.none || kill.TimeOffset - LastOffset < 0)
if ((hit.DeathType != IW4Info.MeansOfDeath.MOD_PISTOL_BULLET &&
hit.DeathType != IW4Info.MeansOfDeath.MOD_RIFLE_BULLET &&
hit.DeathType != IW4Info.MeansOfDeath.MOD_HEAD_SHOT) ||
hit.HitLoc == IW4Info.HitLocation.none || hit.TimeOffset - LastOffset < 0 ||
// hack: prevents false positives
(LastWeapon != hit.Weapon && (hit.TimeOffset - LastOffset) == 50))
{
return new DetectionPenaltyResult()
{
ClientPenalty = Penalty.PenaltyType.Any,
};
}
DetectionPenaltyResult result = null;
LastWeapon = hit.Weapon;
if (LastHit == DateTime.MinValue)
LastHit = DateTime.UtcNow;
HitLocationCount[hit.HitLoc]++;
HitCount++;
HitLocationCount[kill.HitLoc]++;
if (!isDamage)
{
Kills++;
}
HitCount++;
#region VIEWANGLES
if (kill.AnglesList.Count >= 2)
if (hit.AnglesList.Count >= 2)
{
double realAgainstPredict = Vector3.ViewAngleDistance(kill.AnglesList[0], kill.AnglesList[1], kill.ViewAngles);
double realAgainstPredict = Vector3.ViewAngleDistance(hit.AnglesList[0], hit.AnglesList[1], hit.ViewAngles);
// LIFETIME
var hitLoc = ClientStats.HitLocations
.First(hl => hl.Location == kill.HitLoc);
.First(hl => hl.Location == hit.HitLoc);
float previousAverage = hitLoc.HitOffsetAverage;
double newAverage = (previousAverage * (hitLoc.HitCount - 1) + realAgainstPredict) / hitLoc.HitCount;
@ -88,11 +96,11 @@ namespace IW4MAdmin.Plugins.Stats.Cheat
if (hitLoc.HitOffsetAverage > Thresholds.MaxOffset(hitLoc.HitCount) &&
hitLoc.HitCount > 100)
{
Log.WriteDebug("*** Reached Max Lifetime Average for Angle Difference ***");
Log.WriteDebug($"Lifetime Average = {newAverage}");
Log.WriteDebug($"Bone = {hitLoc.Location}");
Log.WriteDebug($"HitCount = {hitLoc.HitCount}");
Log.WriteDebug($"ID = {kill.AttackerId}");
//Log.WriteDebug("*** Reached Max Lifetime Average for Angle Difference ***");
//Log.WriteDebug($"Lifetime Average = {newAverage}");
//Log.WriteDebug($"Bone = {hitLoc.Location}");
//Log.WriteDebug($"HitCount = {hitLoc.HitCount}");
//Log.WriteDebug($"ID = {hit.AttackerId}");
result = new DetectionPenaltyResult()
{
@ -110,10 +118,10 @@ namespace IW4MAdmin.Plugins.Stats.Cheat
if (sessAverage > Thresholds.MaxOffset(HitCount) &&
HitCount > 30)
{
Log.WriteDebug("*** Reached Max Session Average for Angle Difference ***");
Log.WriteDebug($"Session Average = {sessAverage}");
Log.WriteDebug($"HitCount = {HitCount}");
Log.WriteDebug($"ID = {kill.AttackerId}");
//Log.WriteDebug("*** Reached Max Session Average for Angle Difference ***");
//Log.WriteDebug($"Session Average = {sessAverage}");
//Log.WriteDebug($"HitCount = {HitCount}");
//Log.WriteDebug($"ID = {hit.AttackerId}");
result = new DetectionPenaltyResult()
{
@ -130,8 +138,11 @@ namespace IW4MAdmin.Plugins.Stats.Cheat
#endif
}
double currentStrain = Strain.GetStrain(isDamage, kill.Damage, kill.Distance / 0.0254, kill.ViewAngles, Math.Max(50, kill.TimeOffset - LastOffset));
LastOffset = kill.TimeOffset;
double currentStrain = Strain.GetStrain(isDamage, hit.Damage, hit.Distance / 0.0254, hit.ViewAngles, Math.Max(50, hit.TimeOffset - LastOffset));
#if DEBUG == true
Log.WriteDebug($"Current Strain: {currentStrain}");
#endif
LastOffset = hit.TimeOffset;
if (currentStrain > ClientStats.MaxStrain)
{
@ -139,8 +150,7 @@ namespace IW4MAdmin.Plugins.Stats.Cheat
}
// flag
if (currentStrain > Thresholds.MaxStrainFlag
&& HitCount >= 10)
if (currentStrain > Thresholds.MaxStrainFlag)
{
result = new DetectionPenaltyResult()
{
@ -153,7 +163,7 @@ namespace IW4MAdmin.Plugins.Stats.Cheat
// ban
if (currentStrain > Thresholds.MaxStrainBan &&
HitCount >= 15)
HitCount >= 5)
{
result = new DetectionPenaltyResult()
{
@ -163,11 +173,6 @@ namespace IW4MAdmin.Plugins.Stats.Cheat
Type = DetectionType.Strain
};
}
#if DEBUG
Log.WriteDebug($"Current Strain: {currentStrain}");
#endif
#endregion
#region SESSION_RATIOS
@ -197,13 +202,16 @@ namespace IW4MAdmin.Plugins.Stats.Cheat
if (currentHeadshotRatio > maxHeadshotLerpValueForBan)
{
Log.WriteDebug("**Maximum Headshot Ratio Reached For Ban**");
Log.WriteDebug($"ClientId: {kill.AttackerId}");
Log.WriteDebug($"ClientId: {hit.AttackerId}");
Log.WriteDebug($"**HitCount: {HitCount}");
Log.WriteDebug($"**Ratio {currentHeadshotRatio}");
Log.WriteDebug($"**MaxRatio {maxHeadshotLerpValueForFlag}");
var sb = new StringBuilder();
foreach (var kvp in HitLocationCount)
{
sb.Append($"HitLocation: {kvp.Key} -> {kvp.Value}\r\n");
}
Log.WriteDebug(sb.ToString());
result = new DetectionPenaltyResult()
@ -218,13 +226,16 @@ namespace IW4MAdmin.Plugins.Stats.Cheat
else
{
Log.WriteDebug("**Maximum Headshot Ratio Reached For Flag**");
Log.WriteDebug($"ClientId: {kill.AttackerId}");
Log.WriteDebug($"ClientId: {hit.AttackerId}");
Log.WriteDebug($"**HitCount: {HitCount}");
Log.WriteDebug($"**Ratio {currentHeadshotRatio}");
Log.WriteDebug($"**MaxRatio {maxHeadshotLerpValueForFlag}");
var sb = new StringBuilder();
foreach (var kvp in HitLocationCount)
{
sb.Append($"HitLocation: {kvp.Key} -> {kvp.Value}\r\n");
}
Log.WriteDebug(sb.ToString());
result = new DetectionPenaltyResult()
@ -247,13 +258,16 @@ namespace IW4MAdmin.Plugins.Stats.Cheat
if (currentMaxBoneRatio > maxBoneRatioLerpValueForBan)
{
Log.WriteDebug("**Maximum Bone Ratio Reached For Ban**");
Log.WriteDebug($"ClientId: {kill.AttackerId}");
Log.WriteDebug($"ClientId: {hit.AttackerId}");
Log.WriteDebug($"**HitCount: {HitCount}");
Log.WriteDebug($"**Ratio {currentMaxBoneRatio}");
Log.WriteDebug($"**MaxRatio {maxBoneRatioLerpValueForBan}");
var sb = new StringBuilder();
foreach (var kvp in HitLocationCount)
{
sb.Append($"HitLocation: {kvp.Key} -> {kvp.Value}\r\n");
}
Log.WriteDebug(sb.ToString());
result = new DetectionPenaltyResult()
@ -268,13 +282,16 @@ namespace IW4MAdmin.Plugins.Stats.Cheat
else
{
Log.WriteDebug("**Maximum Bone Ratio Reached For Flag**");
Log.WriteDebug($"ClientId: {kill.AttackerId}");
Log.WriteDebug($"ClientId: {hit.AttackerId}");
Log.WriteDebug($"**HitCount: {HitCount}");
Log.WriteDebug($"**Ratio {currentMaxBoneRatio}");
Log.WriteDebug($"**MaxRatio {maxBoneRatioLerpValueForFlag}");
var sb = new StringBuilder();
foreach (var kvp in HitLocationCount)
{
sb.Append($"HitLocation: {kvp.Key} -> {kvp.Value}\r\n");
}
Log.WriteDebug(sb.ToString());
result = new DetectionPenaltyResult()
@ -308,15 +325,15 @@ namespace IW4MAdmin.Plugins.Stats.Cheat
if (currentChestAbdomenRatio > chestAbdomenLerpValueForBan && chestHits >= Thresholds.MediumSampleMinKills + 30)
{
Log.WriteDebug("**Maximum Chest/Abdomen Ratio Reached For Ban**");
Log.WriteDebug($"ClientId: {kill.AttackerId}");
Log.WriteDebug($"**Chest Hits: {chestHits}");
Log.WriteDebug($"**Ratio {currentChestAbdomenRatio}");
Log.WriteDebug($"**MaxRatio {chestAbdomenLerpValueForBan}");
var sb = new StringBuilder();
foreach (var kvp in HitLocationCount)
sb.Append($"HitLocation: {kvp.Key} -> {kvp.Value}\r\n");
Log.WriteDebug(sb.ToString());
//Log.WriteDebug("**Maximum Chest/Abdomen Ratio Reached For Ban**");
//Log.WriteDebug($"ClientId: {hit.AttackerId}");
//Log.WriteDebug($"**Chest Hits: {chestHits}");
//Log.WriteDebug($"**Ratio {currentChestAbdomenRatio}");
//Log.WriteDebug($"**MaxRatio {chestAbdomenLerpValueForBan}");
//var sb = new StringBuilder();
//foreach (var kvp in HitLocationCount)
// sb.Append($"HitLocation: {kvp.Key} -> {kvp.Value}\r\n");
//Log.WriteDebug(sb.ToString());
result = new DetectionPenaltyResult()
{
@ -329,16 +346,15 @@ namespace IW4MAdmin.Plugins.Stats.Cheat
}
else
{
Log.WriteDebug("**Maximum Chest/Abdomen Ratio Reached For Flag**");
Log.WriteDebug($"ClientId: {kill.AttackerId}");
Log.WriteDebug($"**Chest Hits: {chestHits}");
Log.WriteDebug($"**Ratio {currentChestAbdomenRatio}");
Log.WriteDebug($"**MaxRatio {chestAbdomenRatioLerpValueForFlag}");
var sb = new StringBuilder();
foreach (var kvp in HitLocationCount)
sb.Append($"HitLocation: {kvp.Key} -> {kvp.Value}\r\n");
Log.WriteDebug(sb.ToString());
// Log.WriteDebug($"ThresholdReached: {AboveThresholdCount}");
//Log.WriteDebug("**Maximum Chest/Abdomen Ratio Reached For Flag**");
//Log.WriteDebug($"ClientId: {hit.AttackerId}");
//Log.WriteDebug($"**Chest Hits: {chestHits}");
//Log.WriteDebug($"**Ratio {currentChestAbdomenRatio}");
//Log.WriteDebug($"**MaxRatio {chestAbdomenRatioLerpValueForFlag}");
//var sb = new StringBuilder();
//foreach (var kvp in HitLocationCount)
// sb.Append($"HitLocation: {kvp.Key} -> {kvp.Value}\r\n");
//Log.WriteDebug(sb.ToString());
result = new DetectionPenaltyResult()
{
@ -356,30 +372,31 @@ namespace IW4MAdmin.Plugins.Stats.Cheat
Tracker.OnChange(new EFACSnapshot()
{
Active = true,
When = kill.When,
When = hit.When,
ClientId = ClientStats.ClientId,
SessionAngleOffset = AngleDifferenceAverage,
CurrentSessionLength = (int)(DateTime.UtcNow - ConnectionTime).TotalSeconds,
CurrentStrain = currentStrain,
CurrentViewAngle = kill.ViewAngles,
CurrentViewAngle = hit.ViewAngles,
Hits = HitCount,
Kills = Kills,
Deaths = ClientStats.SessionDeaths,
HitDestination = kill.DeathOrigin,
HitOrigin = kill.KillOrigin,
HitDestinationId = hit.DeathOrigin.Vector3Id,
HitDestination = hit.DeathOrigin,
HitOriginId = hit.KillOrigin.Vector3Id,
HitOrigin = hit.KillOrigin,
EloRating = ClientStats.EloRating,
HitLocation = kill.HitLoc,
HitLocation = hit.HitLoc,
LastStrainAngle = Strain.LastAngle,
PredictedViewAngles = kill.AnglesList,
PredictedViewAngles = hit.AnglesList,
// this is in "meters"
Distance = kill.Distance,
Distance = hit.Distance,
SessionScore = ClientStats.SessionScore,
HitType = kill.DeathType,
HitType = hit.DeathType,
SessionSPM = ClientStats.SessionSPM,
StrainAngleBetween = Strain.LastDistance,
TimeSinceLastEvent = (int)Strain.LastDeltaTime,
WeaponId = kill.Weapon
WeaponId = hit.Weapon
});
return result ?? new DetectionPenaltyResult()
@ -408,15 +425,15 @@ namespace IW4MAdmin.Plugins.Stats.Cheat
if (currentChestAbdomenRatio > chestAbdomenLerpValueForBan)
{
Log.WriteDebug("**Maximum Lifetime Chest/Abdomen Ratio Reached For Ban**");
Log.WriteDebug($"ClientId: {stats.ClientId}");
Log.WriteDebug($"**Total Chest Hits: {totalChestHits}");
Log.WriteDebug($"**Ratio {currentChestAbdomenRatio}");
Log.WriteDebug($"**MaxRatio {chestAbdomenLerpValueForBan}");
var sb = new StringBuilder();
foreach (var location in stats.HitLocations)
sb.Append($"HitLocation: {location.Location} -> {location.HitCount}\r\n");
Log.WriteDebug(sb.ToString());
//Log.WriteDebug("**Maximum Lifetime Chest/Abdomen Ratio Reached For Ban**");
//Log.WriteDebug($"ClientId: {stats.ClientId}");
//Log.WriteDebug($"**Total Chest Hits: {totalChestHits}");
//Log.WriteDebug($"**Ratio {currentChestAbdomenRatio}");
//Log.WriteDebug($"**MaxRatio {chestAbdomenLerpValueForBan}");
//var sb = new StringBuilder();
//foreach (var location in stats.HitLocations)
// sb.Append($"HitLocation: {location.Location} -> {location.HitCount}\r\n");
//Log.WriteDebug(sb.ToString());
return new DetectionPenaltyResult()
{
@ -429,15 +446,15 @@ namespace IW4MAdmin.Plugins.Stats.Cheat
}
else
{
Log.WriteDebug("**Maximum Lifetime Chest/Abdomen Ratio Reached For Flag**");
Log.WriteDebug($"ClientId: {stats.ClientId}");
Log.WriteDebug($"**Total Chest Hits: {totalChestHits}");
Log.WriteDebug($"**Ratio {currentChestAbdomenRatio}");
Log.WriteDebug($"**MaxRatio {chestAbdomenRatioLerpValueForFlag}");
var sb = new StringBuilder();
foreach (var location in stats.HitLocations)
sb.Append($"HitLocation: {location.Location} -> {location.HitCount}\r\n");
Log.WriteDebug(sb.ToString());
//Log.WriteDebug("**Maximum Lifetime Chest/Abdomen Ratio Reached For Flag**");
//Log.WriteDebug($"ClientId: {stats.ClientId}");
//Log.WriteDebug($"**Total Chest Hits: {totalChestHits}");
//Log.WriteDebug($"**Ratio {currentChestAbdomenRatio}");
//Log.WriteDebug($"**MaxRatio {chestAbdomenRatioLerpValueForFlag}");
//var sb = new StringBuilder();
//foreach (var location in stats.HitLocations)
// sb.Append($"HitLocation: {location.Location} -> {location.HitCount}\r\n");
//Log.WriteDebug(sb.ToString());
return new DetectionPenaltyResult()
{

View File

@ -14,8 +14,6 @@ namespace IW4MAdmin.Plugins.Stats.Cheat
public Vector3 LastAngle { get; private set; }
public double LastDeltaTime { get; private set; }
public int TimesReachedMaxStrain { get; private set; }
public double GetStrain(bool isDamage, int damage, double killDistance, Vector3 newAngle, double deltaTime)
{
if (LastAngle == null)
@ -25,14 +23,15 @@ namespace IW4MAdmin.Plugins.Stats.Cheat
double decayFactor = GetDecay(deltaTime);
CurrentStrain *= decayFactor;
#if DEBUG
Console.WriteLine($"Decay Factor = {decayFactor} ");
#endif
double[] distance = Helpers.Extensions.AngleStuff(newAngle, LastAngle);
LastDistance = distance[0] + distance[1];
#if DEBUG == true
Console.WriteLine($"Angle Between = {LastDistance}");
Console.WriteLine($"Distance From Target = {killDistance}");
Console.WriteLine($"Time Offset = {deltaTime}");
Console.WriteLine($"Decay Factor = {decayFactor} ");
#endif
// this happens on first kill
if ((distance[0] == 0 && distance[1] == 0) ||
deltaTime == 0 ||
@ -41,14 +40,11 @@ namespace IW4MAdmin.Plugins.Stats.Cheat
return CurrentStrain;
}
double newStrain = Math.Pow(distance[0] + distance[1], 0.99) / deltaTime;
double newStrain = Math.Pow(LastDistance, 0.99) / deltaTime;
newStrain *= killDistance / 1000.0;
CurrentStrain += newStrain;
if (CurrentStrain > Thresholds.MaxStrainBan)
TimesReachedMaxStrain++;
LastAngle = newAngle;
return CurrentStrain;
}

View File

@ -27,9 +27,8 @@ namespace IW4MAdmin.Plugins.Stats.Cheat
public const int HighSampleMinKills = 100;
public const double KillTimeThreshold = 0.2;
public const double MaxStrainBan = 1.12;
public const double MaxStrainBan = 0.9;
//=exp((MAX(-3.07+(-3.07/sqrt(J20)),-3.07-(-3.07/sqrt(J20))))+(4*(0.869)))
public static double MaxOffset(int sampleSize) => Math.Exp(Math.Max(-3.07 + (-3.07 / Math.Sqrt(sampleSize)), -3.07 - (-3.07 / Math.Sqrt(sampleSize))) + 4 * (0.869));
public const double MaxStrainFlag = 0.36;

View File

@ -8,6 +8,8 @@ using SharedLibraryCore.Objects;
using IW4MAdmin.Plugins.Stats.Models;
using SharedLibraryCore.Database;
using System.Collections.Generic;
using SharedLibraryCore.Database.Models;
using IW4MAdmin.Plugins.Stats.Helpers;
namespace IW4MAdmin.Plugins.Stats.Commands
{
@ -15,7 +17,8 @@ namespace IW4MAdmin.Plugins.Stats.Commands
{
public static async Task<List<string>> GetMostPlayed(Server s)
{
int serverId = s.GetHashCode();
long serverId = await StatManager.GetIdForServer(s);
List<string> mostPlayed = new List<string>()
{
$"^5--{Utilities.CurrentLocalization.LocalizationIndex["PLUGINS_STATS_COMMANDS_MOSTPLAYED_TEXT"]}--"
@ -34,7 +37,7 @@ namespace IW4MAdmin.Plugins.Stats.Commands
join alias in db.Aliases
on client.CurrentAliasId equals alias.AliasId
where stats.ServerId == serverId
where client.Level != Player.Permission.Banned
where client.Level != EFClient.Permission.Banned
where client.LastConnection >= thirtyDaysAgo
orderby stats.Kills descending
select new
@ -55,7 +58,7 @@ namespace IW4MAdmin.Plugins.Stats.Commands
return mostPlayed;
}
public MostPlayed() : base("mostplayed", Utilities.CurrentLocalization.LocalizationIndex["PLUGINS_STATS_COMMANDS_MOSTPLAYED_DESC"], "mp", Player.Permission.User, false) { }
public MostPlayed() : base("mostplayed", Utilities.CurrentLocalization.LocalizationIndex["PLUGINS_STATS_COMMANDS_MOSTPLAYED_DESC"], "mp", EFClient.Permission.User, false) { }
public override async Task ExecuteAsync(GameEvent E)
{
@ -63,12 +66,16 @@ namespace IW4MAdmin.Plugins.Stats.Commands
if (!E.Message.IsBroadcastCommand())
{
foreach (var stat in topStats)
await E.Origin.Tell(stat);
{
E.Origin.Tell(stat);
}
}
else
{
foreach (var stat in topStats)
await E.Owner.Broadcast(stat);
{
E.Owner.Broadcast(stat);
}
}
}
}

View File

@ -1,45 +1,52 @@
using SharedLibraryCore;
using SharedLibraryCore.Objects;
using IW4MAdmin.Plugins.Stats.Models;
using System;
using System.Collections.Generic;
using IW4MAdmin.Plugins.Stats.Models;
using Microsoft.EntityFrameworkCore;
using SharedLibraryCore;
using SharedLibraryCore.Database;
using SharedLibraryCore.Database.Models;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace IW4MAdmin.Plugins.Stats.Commands
{
public class ResetStats : Command
{
public ResetStats() : base("resetstats", Utilities.CurrentLocalization.LocalizationIndex["PLUGINS_STATS_COMMANDS_RESET_DESC"], "rs", Player.Permission.User, false) { }
public ResetStats() : base("resetstats", Utilities.CurrentLocalization.LocalizationIndex["PLUGINS_STATS_COMMANDS_RESET_DESC"], "rs", EFClient.Permission.User, false) { }
public override async Task ExecuteAsync(GameEvent E)
{
if (E.Origin.ClientNumber >= 0)
{
var svc = new SharedLibraryCore.Services.GenericRepository<EFClientStatistics>();
int serverId = E.Owner.GetHashCode();
var stats = svc.Find(s => s.ClientId == E.Origin.ClientId && s.ServerId == serverId).First();
stats.Deaths = 0;
stats.Kills = 0;
stats.SPM = 0.0;
stats.Skill = 0.0;
stats.TimePlayed = 0;
// todo: make this more dynamic
stats.EloRating = 200.0;
long serverId = await Helpers.StatManager.GetIdForServer(E.Owner);
// reset the cached version
Plugin.Manager.ResetStats(E.Origin.ClientId, E.Owner.GetHashCode());
EFClientStatistics clientStats;
using (var ctx = new DatabaseContext(disableTracking: true))
{
clientStats = await ctx.Set<EFClientStatistics>()
.Where(s => s.ClientId == E.Origin.ClientId)
.Where(s => s.ServerId == serverId)
.FirstAsync();
// fixme: this doesn't work properly when another context exists
await svc.SaveChangesAsync();
await E.Origin.Tell(Utilities.CurrentLocalization.LocalizationIndex["PLUGINS_STATS_COMMANDS_RESET_SUCCESS"]);
clientStats.Deaths = 0;
clientStats.Kills = 0;
clientStats.SPM = 0.0;
clientStats.Skill = 0.0;
clientStats.TimePlayed = 0;
// todo: make this more dynamic
clientStats.EloRating = 200.0;
// reset the cached version
Plugin.Manager.ResetStats(E.Origin.ClientId, serverId);
// fixme: this doesn't work properly when another context exists
await ctx.SaveChangesAsync();
}
E.Origin.Tell(Utilities.CurrentLocalization.LocalizationIndex["PLUGINS_STATS_COMMANDS_RESET_SUCCESS"]);
}
else
{
await E.Origin.Tell(Utilities.CurrentLocalization.LocalizationIndex["PLUGINS_STATS_COMMANDS_RESET_FAIL"]);
E.Origin.Tell(Utilities.CurrentLocalization.LocalizationIndex["PLUGINS_STATS_COMMANDS_RESET_FAIL"]);
}
}
}

View File

@ -9,26 +9,24 @@ using SharedLibraryCore.Services;
using IW4MAdmin.Plugins.Stats.Models;
using SharedLibraryCore.Database;
using System.Collections.Generic;
using SharedLibraryCore.Database.Models;
using IW4MAdmin.Plugins.Stats.Helpers;
namespace IW4MAdmin.Plugins.Stats.Commands
{
class TopStats : Command
{
public static async Task<List<string>> GetTopStats(Server s)
{
int serverId = s.GetHashCode();
long serverId = await StatManager.GetIdForServer(s);
List<string> topStatsText = new List<string>()
{
$"^5--{Utilities.CurrentLocalization.LocalizationIndex["PLUGINS_STATS_COMMANDS_TOP_TEXT"]}--"
};
using (var db = new DatabaseContext())
using (var db = new DatabaseContext(true))
{
db.ChangeTracker.AutoDetectChangesEnabled = false;
db.ChangeTracker.QueryTrackingBehavior = QueryTrackingBehavior.NoTracking;
var thirtyDaysAgo = DateTime.UtcNow.AddMonths(-1);
var fifteenDaysAgo = DateTime.UtcNow.AddDays(-15);
var iqStats = (from stats in db.Set<EFClientStatistics>()
join client in db.Clients
@ -36,9 +34,9 @@ namespace IW4MAdmin.Plugins.Stats.Commands
join alias in db.Aliases
on client.CurrentAliasId equals alias.AliasId
where stats.ServerId == serverId
where stats.TimePlayed >= 3600
where client.Level != Player.Permission.Banned
where client.LastConnection >= thirtyDaysAgo
where stats.TimePlayed >= Plugin.Config.Configuration().TopPlayersMinPlayTime
where client.Level != EFClient.Permission.Banned
where client.LastConnection >= fifteenDaysAgo
orderby stats.Performance descending
select new
{
@ -48,6 +46,10 @@ namespace IW4MAdmin.Plugins.Stats.Commands
})
.Take(5);
#if DEBUG == true
var statsSql = iqStats.ToSql();
#endif
var statsList = (await iqStats.ToListAsync())
.Select(stats => $"^3{stats.Name}^7 - ^5{stats.KDR} ^7{Utilities.CurrentLocalization.LocalizationIndex["PLUGINS_STATS_TEXT_KDR"]} | ^5{stats.Performance} ^7{Utilities.CurrentLocalization.LocalizationIndex["PLUGINS_STATS_COMMANDS_PERFORMANCE"]}");
@ -66,7 +68,7 @@ namespace IW4MAdmin.Plugins.Stats.Commands
return topStatsText;
}
public TopStats() : base("topstats", Utilities.CurrentLocalization.LocalizationIndex["PLUGINS_STATS_COMMANDS_TOP_DESC"], "ts", Player.Permission.User, false) { }
public TopStats() : base("topstats", Utilities.CurrentLocalization.LocalizationIndex["PLUGINS_STATS_COMMANDS_TOP_DESC"], "ts", EFClient.Permission.User, false) { }
public override async Task ExecuteAsync(GameEvent E)
{
@ -74,12 +76,17 @@ namespace IW4MAdmin.Plugins.Stats.Commands
if (!E.Message.IsBroadcastCommand())
{
foreach (var stat in topStats)
await E.Origin.Tell(stat);
{
E.Origin.Tell(stat);
}
}
else
{
foreach (var stat in topStats)
await E.Owner.Broadcast(stat);
{
E.Owner.Broadcast(stat);
}
}
}
}

View File

@ -7,12 +7,16 @@ using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using SharedLibraryCore.Database;
using Microsoft.EntityFrameworkCore;
using IW4MAdmin.Plugins.Stats.Helpers;
using SharedLibraryCore.Database.Models;
namespace IW4MAdmin.Plugins.Stats.Commands
{
public class CViewStats : Command
{
public CViewStats() : base("stats", Utilities.CurrentLocalization.LocalizationIndex["PLUGINS_STATS_COMMANDS_VIEW_DESC"], "xlrstats", Player.Permission.User, false, new CommandArgument[]
public CViewStats() : base("stats", Utilities.CurrentLocalization.LocalizationIndex["PLUGINS_STATS_COMMANDS_VIEW_DESC"], "xlrstats", EFClient.Permission.User, false, new CommandArgument[]
{
new CommandArgument()
{
@ -35,39 +39,48 @@ namespace IW4MAdmin.Plugins.Stats.Commands
if (E.Target == null)
{
await E.Origin.Tell(loc["PLUGINS_STATS_COMMANDS_VIEW_FAIL"]);
return;
E.Origin.Tell(loc["PLUGINS_STATS_COMMANDS_VIEW_FAIL"]);
}
}
var clientStats = new GenericRepository<EFClientStatistics>();
int serverId = E.Owner.GetHashCode();
long serverId = await StatManager.GetIdForServer(E.Owner);
if (E.Target != null)
using (var ctx = new DatabaseContext(disableTracking: true))
{
pStats = clientStats.Find(c => c.ServerId == serverId && c.ClientId == E.Target.ClientId).First();
statLine = $"^5{pStats.Kills} ^7{loc["PLUGINS_STATS_TEXT_KILLS"]} | ^5{pStats.Deaths} ^7{loc["PLUGINS_STATS_TEXT_DEATHS"]} | ^5{pStats.KDR} ^7KDR | ^5{pStats.Performance} ^7{loc["PLUGINS_STATS_COMMANDS_PERFORMANCE"].ToUpper()}";
}
if (E.Target != null)
{
int performanceRanking = await StatManager.GetClientOverallRanking(E.Target.ClientId);
string performanceRankingString = performanceRanking == 0 ? loc["WEBFRONT_STATS_INDEX_UNRANKED"] : $"{loc["WEBFRONT_STATS_INDEX_RANKED"]} #{performanceRanking}";
else
{
pStats = pStats = clientStats.Find(c => c.ServerId == serverId && c.ClientId == E.Origin.ClientId).First();
statLine = $"^5{pStats.Kills} ^7{loc["PLUGINS_STATS_TEXT_KILLS"]} | ^5{pStats.Deaths} ^7{loc["PLUGINS_STATS_TEXT_DEATHS"]} | ^5{pStats.KDR} ^7KDR | ^5{pStats.Performance} ^7{loc["PLUGINS_STATS_COMMANDS_PERFORMANCE"].ToUpper()}";
pStats = (await ctx.Set<EFClientStatistics>().FirstAsync(c => c.ServerId == serverId && c.ClientId == E.Target.ClientId));
statLine = $"^5{pStats.Kills} ^7{loc["PLUGINS_STATS_TEXT_KILLS"]} | ^5{pStats.Deaths} ^7{loc["PLUGINS_STATS_TEXT_DEATHS"]} | ^5{pStats.KDR} ^7KDR | ^5{pStats.Performance} ^7{loc["PLUGINS_STATS_COMMANDS_PERFORMANCE"].ToUpper()} | {performanceRankingString}";
}
else
{
int performanceRanking = await StatManager.GetClientOverallRanking(E.Origin.ClientId);
string performanceRankingString = performanceRanking == 0 ? loc["WEBFRONT_STATS_INDEX_UNRANKED"] : $"{loc["WEBFRONT_STATS_INDEX_RANKED"]} #{performanceRanking}";
pStats = (await ctx.Set<EFClientStatistics>().FirstAsync((c => c.ServerId == serverId && c.ClientId == E.Origin.ClientId)));
statLine = $"^5{pStats.Kills} ^7{loc["PLUGINS_STATS_TEXT_KILLS"]} | ^5{pStats.Deaths} ^7{loc["PLUGINS_STATS_TEXT_DEATHS"]} | ^5{pStats.KDR} ^7KDR | ^5{pStats.Performance} ^7{loc["PLUGINS_STATS_COMMANDS_PERFORMANCE"].ToUpper()} | {performanceRankingString}";
}
}
if (E.Message.IsBroadcastCommand())
{
string name = E.Target == null ? E.Origin.Name : E.Target.Name;
await E.Owner.Broadcast($"{loc["PLUGINS_STATS_COMMANDS_VIEW_SUCCESS"]} ^5{name}^7");
await E.Owner.Broadcast(statLine);
E.Owner.Broadcast($"{loc["PLUGINS_STATS_COMMANDS_VIEW_SUCCESS"]} ^5{name}^7");
E.Owner.Broadcast(statLine);
}
else
{
if (E.Target != null)
await E.Origin.Tell($"{loc["PLUGINS_STATS_COMMANDS_VIEW_SUCCESS"]} ^5{E.Target.Name}^7");
await E.Origin.Tell(statLine);
{
E.Origin.Tell($"{loc["PLUGINS_STATS_COMMANDS_VIEW_SUCCESS"]} ^5{E.Target.Name}^7");
}
E.Origin.Tell(statLine);
}
}
}

View File

@ -10,6 +10,7 @@ namespace IW4MAdmin.Plugins.Stats.Config
public List<StreakMessageConfiguration> KillstreakMessages { get; set; }
public List<StreakMessageConfiguration> DeathstreakMessages { get; set; }
public int TopPlayersMinPlayTime { get; set; }
public bool StoreClientKills { get; set; }
public string Name() => "Stats";
public IBaseConfiguration Generate()
{
@ -49,6 +50,7 @@ namespace IW4MAdmin.Plugins.Stats.Config
};
TopPlayersMinPlayTime = 3600 * 3;
StoreClientKills = false;
return this;
}

File diff suppressed because it is too large Load Diff

View File

@ -1,40 +0,0 @@
using SharedLibraryCore.Services;
using IW4MAdmin.Plugins.Stats.Models;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace IW4MAdmin.Plugins.Stats.Helpers
{
public class ThreadSafeStatsService
{
public GenericRepository<EFClientStatistics> ClientStatSvc
{
get
{
return new GenericRepository<EFClientStatistics>();
}
}
public GenericRepository<EFServer> ServerSvc { get; private set; }
public GenericRepository<EFClientKill> KillStatsSvc { get; private set; }
public GenericRepository<EFServerStatistics> ServerStatsSvc { get; private set; }
public GenericRepository<EFClientMessage> MessageSvc
{
get
{
return new GenericRepository<EFClientMessage>();
}
}
public ThreadSafeStatsService()
{
//ClientStatSvc = new GenericRepository<EFClientStatistics>();
ServerSvc = new GenericRepository<EFServer>();
KillStatsSvc = new GenericRepository<EFClientKill>();
ServerStatsSvc = new GenericRepository<EFServerStatistics>();
//MessageSvc = new GenericRepository<EFClientMessage>();
}
}
}

View File

@ -65,6 +65,7 @@ namespace IW4MAdmin.Plugins.Stats
right_foot,
left_foot,
gun,
shield
}
public enum WeaponName
@ -1369,7 +1370,9 @@ namespace IW4MAdmin.Plugins.Stats
dragunov_mp,
cobra_player_minigun_mp,
destructible_car,
sentry_minigun_mp
sentry_minigun_mp,
cobra_20mm_mp,
shield
}
public enum MapName

View File

@ -30,10 +30,18 @@ namespace IW4MAdmin.Plugins.Stats.Models
public double CurrentStrain { get; set; }
public double StrainAngleBetween { get; set; }
public double SessionAngleOffset { get; set; }
public int LastStrainAngleId { get; set; }
[ForeignKey("LastStrainAngleId")]
public Vector3 LastStrainAngle { get; set; }
public int HitOriginId { get; set; }
[ForeignKey("HitOriginId")]
public Vector3 HitOrigin { get; set; }
public int HitDestinationId { get; set; }
[ForeignKey("HitDestinationId")]
public Vector3 HitDestination { get; set; }
public double Distance { get; set; }
public int CurrentViewAngleId { get; set; }
[ForeignKey("CurrentViewAngleId")]
public Vector3 CurrentViewAngle { get; set; }
public IW4Info.WeaponName WeaponId { get; set; }
public IW4Info.HitLocation HitLocation { get; set; }

View File

@ -18,7 +18,7 @@ namespace IW4MAdmin.Plugins.Stats.Models
public int AttackerId { get; set; }
[ForeignKey("AttackerId")]
public virtual EFClient Attacker { get; set; }
public int ServerId { get; set; }
public long ServerId { get; set; }
[ForeignKey("ServerId")]
public virtual EFServer Server { get; set; }
public IW4Info.HitLocation HitLoc { get; set; }

View File

@ -13,7 +13,7 @@ namespace IW4MAdmin.Plugins.Stats.Models
{
[Key]
public long MessageId { get; set; }
public int ServerId { get; set; }
public long ServerId { get; set; }
[ForeignKey("ServerId")]
public virtual EFServer Server { get; set; }
public int ClientId { get; set; }

View File

@ -15,7 +15,7 @@ namespace IW4MAdmin.Plugins.Stats.Models
public int ClientId { get; set; }
[ForeignKey("ClientId")]
public virtual EFClient Client { get; set; }
public int ServerId { get; set; }
public long ServerId { get; set; }
[ForeignKey("ServerId")]
public virtual EFServer Server { get; set; }
[Required]

View File

@ -20,9 +20,8 @@ namespace IW4MAdmin.Plugins.Stats.Models
public int ClientId { get; set; }
[ForeignKey("ClientId"), Column(Order = 0 )]
public EFClient Client { get; set; }
public int ServerId { get; set; }
public long ServerId { get; set; }
[ForeignKey("ServerId"), Column(Order = 1)]
public EFServer Server { get; set; }
}
}

View File

@ -13,7 +13,7 @@ namespace IW4MAdmin.Plugins.Stats.Models
[ForeignKey("RatingHistoryId")]
public virtual EFClientRatingHistory RatingHistory { get; set; }
// if null, indicates that the rating is an average rating
public int? ServerId { get; set; }
public long? ServerId { get; set; }
// [ForeignKey("ServerId")] can't make this nullable if this annotation is set
public virtual EFServer Server { get; set; }
[Required]

View File

@ -9,8 +9,9 @@ namespace IW4MAdmin.Plugins.Stats.Models
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.None)]
public int ServerId { get; set; }
public long ServerId { get; set; }
[Required]
public int Port { get; set; }
public string EndPoint { get; set; }
}
}

View File

@ -8,7 +8,7 @@ namespace IW4MAdmin.Plugins.Stats.Models
{
[Key]
public int StatisticId { get; set; }
public int ServerId { get; set; }
public long ServerId { get; set; }
[ForeignKey("ServerId")]
public virtual EFServer Server { get; set; }
public long TotalKills { get; set; }

View File

@ -1,7 +1,6 @@
using Microsoft.EntityFrameworkCore;
using IW4MAdmin.Plugins.Stats.Models;
using Microsoft.EntityFrameworkCore;
using SharedLibraryCore.Interfaces;
using IW4MAdmin.Plugins.Stats.Models;
namespace Stats.Models
{
@ -30,6 +29,9 @@ namespace Stats.Models
builder.Entity<EFRating>()
.HasIndex(p => p.When);
builder.Entity<EFClientMessage>()
.HasIndex(p => p.TimeSent);
// force pluralization
builder.Entity<EFClientKill>().ToTable("EFClientKills");
builder.Entity<EFClientMessage>().ToTable("EFClientMessages");

View File

@ -1,18 +1,19 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using System.Reflection;
using IW4MAdmin.Plugins.Stats.Config;
using IW4MAdmin.Plugins.Stats.Helpers;
using IW4MAdmin.Plugins.Stats.Models;
using Microsoft.EntityFrameworkCore;
using SharedLibraryCore;
using SharedLibraryCore.Configuration;
using SharedLibraryCore.Database;
using SharedLibraryCore.Dtos;
using SharedLibraryCore.Helpers;
using SharedLibraryCore.Interfaces;
using SharedLibraryCore.Services;
using IW4MAdmin.Plugins.Stats.Config;
using IW4MAdmin.Plugins.Stats.Helpers;
using IW4MAdmin.Plugins.Stats.Models;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Threading.Tasks;
namespace IW4MAdmin.Plugins.Stats
{
@ -42,16 +43,19 @@ namespace IW4MAdmin.Plugins.Stats
break;
case GameEvent.EventType.Disconnect:
await Manager.RemovePlayer(E.Origin);
await Manager.Sync(S);
break;
case GameEvent.EventType.Say:
if (!string.IsNullOrEmpty(E.Data) &&
E.Origin.ClientId > 1)
await Manager.AddMessageAsync(E.Origin.ClientId, E.Owner.GetHashCode(), E.Data);
{
await Manager.AddMessageAsync(E.Origin.ClientId, await StatManager.GetIdForServer(E.Owner), E.Data);
}
break;
case GameEvent.EventType.MapChange:
Manager.SetTeamBased(E.Owner.GetHashCode(), E.Owner.Gametype != "dm");
Manager.ResetKillstreaks(S.GetHashCode());
await Manager.Sync(S);
Manager.SetTeamBased(await StatManager.GetIdForServer(E.Owner), E.Owner.Gametype != "dm");
Manager.ResetKillstreaks(await StatManager.GetIdForServer(E.Owner));
break;
case GameEvent.EventType.MapEnd:
break;
@ -75,23 +79,27 @@ namespace IW4MAdmin.Plugins.Stats
string[] killInfo = (E.Data != null) ? E.Data.Split(';') : new string[0];
if (killInfo.Length >= 14)
{
await Manager.AddScriptHit(false, E.Time, E.Origin, E.Target, S.GetHashCode(), S.CurrentMap.Name, killInfo[7], killInfo[8],
await Manager.AddScriptHit(false, E.Time, E.Origin, E.Target, await StatManager.GetIdForServer(E.Owner), S.CurrentMap.Name, killInfo[7], killInfo[8],
killInfo[5], killInfo[6], killInfo[3], killInfo[4], killInfo[9], killInfo[10], killInfo[11], killInfo[12], killInfo[13], killInfo[14], killInfo[15]);
}
break;
case GameEvent.EventType.Kill:
if (!E.Owner.CustomCallback)
{
await Manager.AddStandardKill(E.Origin, E.Target);
}
break;
case GameEvent.EventType.Damage:
if (!E.Owner.CustomCallback)
Manager.AddDamageEvent(E.Data, E.Origin.ClientId, E.Target.ClientId, E.Owner.GetHashCode());
{
Manager.AddDamageEvent(E.Data, E.Origin.ClientId, E.Target.ClientId, await StatManager.GetIdForServer(E.Owner));
}
break;
case GameEvent.EventType.ScriptDamage:
killInfo = (E.Data != null) ? E.Data.Split(';') : new string[0];
if (killInfo.Length >= 14)
{
await Manager.AddScriptHit(true, E.Time, E.Origin, E.Target, S.GetHashCode(), S.CurrentMap.Name, killInfo[7], killInfo[8],
await Manager.AddScriptHit(true, E.Time, E.Origin, E.Target, await StatManager.GetIdForServer(E.Owner), S.CurrentMap.Name, killInfo[7], killInfo[8],
killInfo[5], killInfo[6], killInfo[3], killInfo[4], killInfo[9], killInfo[10], killInfo[11], killInfo[12], killInfo[13], killInfo[14], killInfo[15]);
}
break;
@ -118,8 +126,11 @@ namespace IW4MAdmin.Plugins.Stats
// meta data info
async Task<List<ProfileMeta>> getStats(int clientId)
{
var statsSvc = new GenericRepository<EFClientStatistics>();
var clientStats = await statsSvc.FindAsync(c => c.ClientId == clientId);
IList<EFClientStatistics> clientStats;
using (var ctx = new DatabaseContext(disableTracking: true))
{
clientStats = await ctx.Set<EFClientStatistics>().Where(c => c.ClientId == clientId).ToListAsync();
}
int kills = clientStats.Sum(c => c.Kills);
int deaths = clientStats.Sum(c => c.Deaths);
@ -134,7 +145,7 @@ namespace IW4MAdmin.Plugins.Stats
new ProfileMeta()
{
Key = Utilities.CurrentLocalization.LocalizationIndex["WEBFRONT_CLIENT_META_RANKING"],
Value = "#" + await Manager.GetClientOverallRanking(clientId),
Value = "#" + await StatManager.GetClientOverallRanking(clientId),
},
new ProfileMeta()
{
@ -166,8 +177,14 @@ namespace IW4MAdmin.Plugins.Stats
async Task<List<ProfileMeta>> getAnticheatInfo(int clientId)
{
var statsSvc = new GenericRepository<EFClientStatistics>();
var clientStats = await statsSvc.FindAsync(c => c.ClientId == clientId);
IList<EFClientStatistics> clientStats;
using (var ctx = new DatabaseContext(disableTracking: true))
{
clientStats = await ctx.Set<EFClientStatistics>()
.Include(c => c.HitLocations)
.Where(c => c.ClientId == clientId)
.ToListAsync();
}
double headRatio = 0;
double chestRatio = 0;
@ -227,7 +244,8 @@ namespace IW4MAdmin.Plugins.Stats
new ProfileMeta()
{
Key = "Hit Offset Average",
Value = $"{Math.Round(((float)hitOffsetAverage), 4)}°",
// todo: make sure this is wrapped somewhere else
Value = $"{Math.Round(((float)hitOffsetAverage), 4).ToString(new System.Globalization.CultureInfo(Utilities.CurrentLocalization.LocalizationName))}°",
Sensitive = true
},
new ProfileMeta()
@ -241,19 +259,24 @@ namespace IW4MAdmin.Plugins.Stats
async Task<List<ProfileMeta>> getMessages(int clientId)
{
var messageSvc = new GenericRepository<EFClientMessage>();
var messages = await messageSvc.FindAsync(m => m.ClientId == clientId);
var messageMeta = messages.Select(m => new ProfileMeta()
List<ProfileMeta> messageMeta;
using (var ctx = new DatabaseContext(disableTracking: true))
{
Key = "EventMessage",
Value = m.Message,
When = m.TimeSent,
Extra = m.ServerId.ToString()
}).ToList();
var messages = ctx.Set<EFClientMessage>().Where(m => m.ClientId == clientId);
messageMeta = await messages.Select(m => new ProfileMeta()
{
Key = "EventMessage",
Value = m.Message,
When = m.TimeSent,
Extra = m.ServerId.ToString()
}).ToListAsync();
}
messageMeta.Add(new ProfileMeta()
{
Key = Utilities.CurrentLocalization.LocalizationIndex["WEBFRONT_PROFILE_MESSAGES"],
Value = messages.Count
Value = messageMeta.Count
});
return messageMeta;
@ -270,16 +293,20 @@ namespace IW4MAdmin.Plugins.Stats
string totalKills(Server server)
{
var serverStats = new GenericRepository<EFServerStatistics>();
return serverStats.Find(s => s.Active)
.Sum(c => c.TotalKills).ToString("#,##0");
using (var ctx = new DatabaseContext(disableTracking: true))
{
long kills = ctx.Set<EFServerStatistics>().Where(s => s.Active).Sum(s => s.TotalKills);
return kills.ToString("#,##0");
}
}
string totalPlayTime(Server server)
{
var serverStats = new GenericRepository<EFServerStatistics>();
return Math.Ceiling((serverStats.GetQuery(s => s.Active)
.Sum(c => c.TotalPlayTime) / 3600.0)).ToString("#,##0");
using (var ctx = new DatabaseContext(disableTracking: true))
{
long playTime = ctx.Set<EFServerStatistics>().Where(s => s.Active).Sum(s => s.TotalPlayTime);
return (playTime / 3600.0).ToString("#,##0");
}
}
string topStats(Server s)
@ -302,12 +329,17 @@ namespace IW4MAdmin.Plugins.Stats
Manager = new StatManager(manager);
}
public Task OnTickAsync(Server S) => Task.CompletedTask;
public Task OnTickAsync(Server S)
{
return Task.CompletedTask;
}
public async Task OnUnloadAsync()
{
foreach (var sv in ServerManager.GetServers())
{
await Manager.Sync(sv);
}
}
}
}

View File

@ -3,6 +3,7 @@
<PropertyGroup>
<OutputType>Library</OutputType>
<TargetFramework>netcoreapp2.1</TargetFramework>
<RuntimeFrameworkVersion>2.1.5</RuntimeFrameworkVersion>
<ApplicationIcon />
<StartupObject />
<PackageId>RaidMax.IW4MAdmin.Plugins.Stats</PackageId>
@ -26,7 +27,7 @@
</ItemGroup>
<ItemGroup>
<PackageReference Update="Microsoft.NETCore.App" />
<PackageReference Update="Microsoft.NETCore.App" Version="2.1.5" />
</ItemGroup>
<Target Name="PostBuild" AfterTargets="PostBuildEvent">

View File

@ -40,11 +40,16 @@ namespace IW4MAdmin.Plugins.Stats.Web.Controllers
where message.TimeSent <= whenUpper
select new SharedLibraryCore.Dtos.ChatInfo()
{
ClientId = message.ClientId,
Message = message.Message,
Name = message.Client.CurrentAlias.Name,
Time = message.TimeSent
};
#if DEBUG == true
var messagesSql = iqMessages.ToSql();
#endif
var messages = await iqMessages.ToListAsync();
return View("_MessageContext", messages);

View File

@ -7,6 +7,6 @@
<h5>@Model.First().Time.ToString()</h5>
@foreach (var message in Model)
{
<span class="text-white">@message.Name</span><span> &mdash; @message.Message</span><br />
<span class="text-white">@Html.ActionLink(@message.Name, "ProfileAsync", "Client", new { id = message.ClientId})</span><span> &mdash; @message.Message</span><br />
}
</div>

View File

@ -1,17 +1,32 @@
using SharedLibraryCore.Objects;
using IW4MAdmin.Application;
using SharedLibraryCore;
using SharedLibraryCore.Commands;
using SharedLibraryCore.Database.Models;
using SharedLibraryCore.Objects;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using Xunit;
namespace Tests
{
[Collection("ManagerCollection")]
public class ClientTests
{
readonly ApplicationManager Manager;
const int TestTimeout = 10000;
public ClientTests(ManagerFixture fixture)
{
Manager = fixture.Manager;
}
[Fact]
public void SetAdditionalPropertyShouldSucceed()
{
var client = new Player();
var client = new EFClient();
int newProp = 5;
client.SetAdditionalProperty("NewProp", newProp);
}
@ -19,11 +34,231 @@ namespace Tests
[Fact]
public void GetAdditionalPropertyShouldSucceed()
{
var client = new Player();
var client = new EFClient();
int newProp = 5;
client.SetAdditionalProperty("NewProp", newProp);
Assert.True(client.GetAdditionalProperty<int>("NewProp") == 5, "added property does not match retrieved property");
}
[Fact]
public void WarnClientShouldSucceed()
{
while (!Manager.IsInitialized)
{
Thread.Sleep(100);
}
var client = Manager.Servers.First().GetClientsAsList().FirstOrDefault();
Assert.False(client == null, "no client found to warn");
var warnEvent = client.Warn("test warn", new EFClient() { ClientId = 1, Level = EFClient.Permission.Console, CurrentServer = client.CurrentServer });
warnEvent.OnProcessed.Wait();
Assert.True((client.Warnings == 1 ||
warnEvent.Failed) &&
Manager.GetPenaltyService().GetClientPenaltiesAsync(client.ClientId).Result.Count(p => p.Type == Penalty.PenaltyType.Warning) == 1,
"warning did not get applied");
warnEvent = client.Warn("test warn", new EFClient() { ClientId = 1, Level = EFClient.Permission.Banned, CurrentServer = client.CurrentServer });
warnEvent.OnProcessed.Wait();
Assert.True(warnEvent.FailReason == GameEvent.EventFailReason.Permission &&
client.Warnings == 1, "warning was applied without proper permissions");
// warn clear
var warnClearEvent = client.WarnClear(new EFClient { ClientId = 1, Level = EFClient.Permission.Banned, CurrentServer = client.CurrentServer });
Assert.True(warnClearEvent.FailReason == GameEvent.EventFailReason.Permission &&
client.Warnings == 1, "warning was removed without proper permissions");
warnClearEvent = client.WarnClear(new EFClient { ClientId = 1, Level = EFClient.Permission.Console, CurrentServer = client.CurrentServer });
Assert.True(!warnClearEvent.Failed && client.Warnings == 0, "warning was not cleared");
}
[Fact]
public void ReportClientShouldSucceed()
{
while (!Manager.IsInitialized)
{
Thread.Sleep(100);
}
var client = Manager.Servers.First().GetClientsAsList().FirstOrDefault();
Assert.False(client == null, "no client found to report");
// fail
var player = new EFClient() { ClientId = 1, Level = EFClient.Permission.Console, CurrentServer = client.CurrentServer };
player.SetAdditionalProperty("_reportCount", 3);
var reportEvent = client.Report("test report", player);
reportEvent.OnProcessed.Wait(TestTimeout);
Assert.True(reportEvent.FailReason == GameEvent.EventFailReason.Throttle &
client.CurrentServer.Reports.Count(r => r.Target.NetworkId == client.NetworkId) == 0, $"too many reports were applied [{reportEvent.FailReason.ToString()}]");
// succeed
reportEvent = client.Report("test report", new EFClient() { ClientId = 1, Level = EFClient.Permission.Console, CurrentServer = client.CurrentServer });
reportEvent.OnProcessed.Wait(TestTimeout);
Assert.True(!reportEvent.Failed &&
client.CurrentServer.Reports.Count(r => r.Target.NetworkId == client.NetworkId) == 1, $"report was not applied [{reportEvent.FailReason.ToString()}]");
// fail
reportEvent = client.Report("test report", new EFClient() { ClientId = 1, NetworkId = 1, Level = EFClient.Permission.Banned, CurrentServer = client.CurrentServer });
Assert.True(reportEvent.FailReason == GameEvent.EventFailReason.Permission &&
client.CurrentServer.Reports.Count(r => r.Target.NetworkId == client.NetworkId) == 1,
$"report was applied without proper permission [{reportEvent.FailReason.ToString()},{ client.CurrentServer.Reports.Count(r => r.Target.NetworkId == client.NetworkId)}]");
// fail
reportEvent = client.Report("test report", client);
Assert.True(reportEvent.FailReason == GameEvent.EventFailReason.Invalid &&
client.CurrentServer.Reports.Count(r => r.Target.NetworkId == client.NetworkId) == 1, $"report was applied to self");
// fail
reportEvent = client.Report("test report", new EFClient() { ClientId = 1, Level = EFClient.Permission.Console, CurrentServer = client.CurrentServer });
Assert.True(reportEvent.FailReason == GameEvent.EventFailReason.Exception &&
client.CurrentServer.Reports.Count(r => r.Target.NetworkId == client.NetworkId) == 1, $"duplicate report was applied");
}
[Fact]
public void FlagClientShouldSucceed()
{
while (!Manager.IsInitialized)
{
Thread.Sleep(100);
}
var client = Manager.Servers.First().GetClientsAsList().FirstOrDefault();
Assert.False(client == null, "no client found to flag");
var flagEvent = client.Flag("test flag", new EFClient { ClientId = 1, Level = EFClient.Permission.Console, CurrentServer = client.CurrentServer });
flagEvent.OnProcessed.Wait();
// succeed
Assert.True(!flagEvent.Failed &&
client.Level == EFClient.Permission.Flagged, $"player is not flagged [{flagEvent.FailReason.ToString()}]");
Assert.False(client.ReceivedPenalties.FirstOrDefault(p => p.Offense == "test flag") == null, "flag was not applied");
flagEvent = client.Flag("test flag", new EFClient { ClientId = 1, Level = EFClient.Permission.Banned, CurrentServer = client.CurrentServer });
flagEvent.OnProcessed.Wait();
// fail
Assert.True(client.ReceivedPenalties.Count == 1, "flag was applied without permisions");
flagEvent = client.Flag("test flag", new EFClient { ClientId = 1, Level = EFClient.Permission.Console, CurrentServer = client.CurrentServer });
flagEvent.OnProcessed.Wait();
// fail
Assert.True(client.ReceivedPenalties.Count == 1, "duplicate flag was applied");
var unflagEvent = client.Unflag("test unflag", new EFClient { ClientId = 1, Level = EFClient.Permission.Banned, CurrentServer = client.CurrentServer });
unflagEvent.OnProcessed.Wait();
// fail
Assert.False(client.Level == EFClient.Permission.User, "user was unflagged without permissions");
unflagEvent = client.Unflag("test unflag", new EFClient { ClientId = 1, Level = EFClient.Permission.Console, CurrentServer = client.CurrentServer });
unflagEvent.OnProcessed.Wait();
// succeed
Assert.True(client.Level == EFClient.Permission.User, "user was not unflagged");
unflagEvent = client.Unflag("test unflag", new EFClient { ClientId = 1, Level = EFClient.Permission.Console, CurrentServer = client.CurrentServer });
unflagEvent.OnProcessed.Wait();
// succeed
Assert.True(unflagEvent.FailReason == GameEvent.EventFailReason.Invalid, "user was not flagged");
}
[Fact]
void KickClientShouldSucceed()
{
while (!Manager.IsInitialized)
{
Thread.Sleep(100);
}
var client = Manager.Servers.First().GetClientsAsList().FirstOrDefault();
Assert.False(client == null, "no client found to kick");
var kickEvent = client.Kick("test kick", new EFClient() { ClientId = 1, Level = EFClient.Permission.Banned, CurrentServer = client.CurrentServer });
kickEvent.OnProcessed.Wait();
Assert.True(kickEvent.FailReason == GameEvent.EventFailReason.Permission, "client was kicked without permission");
kickEvent = client.Kick("test kick", new EFClient() { ClientId = 1, Level = EFClient.Permission.Console, CurrentServer = client.CurrentServer });
kickEvent.OnProcessed.Wait();
Assert.True(Manager.Servers.First().GetClientsAsList().FirstOrDefault(c => c.NetworkId == client.NetworkId) == null, "client was not kicked");
}
[Fact]
void TempBanClientShouldSucceed()
{
while (!Manager.IsInitialized)
{
Thread.Sleep(100);
}
var client = Manager.Servers.First().GetClientsAsList().FirstOrDefault();
Assert.False(client == null, "no client found to tempban");
var tbCommand = new CTempBan();
tbCommand.ExecuteAsync(new GameEvent()
{
Origin = new EFClient() { ClientId = 1, Level = EFClient.Permission.Console, CurrentServer = client.CurrentServer },
Target = client,
Data = "5days test tempban",
Type = GameEvent.EventType.Command,
Owner = client.CurrentServer
}).Wait();
Assert.True(Manager.GetPenaltyService().GetActivePenaltiesAsync(client.AliasLinkId).Result.Count(p => p.Type == Penalty.PenaltyType.TempBan) == 1,
"tempban was not added");
}
[Fact]
void BanUnbanClientShouldSucceed()
{
while (!Manager.IsInitialized)
{
Thread.Sleep(100);
}
var client = Manager.Servers.First().GetClientsAsList().FirstOrDefault();
Assert.False(client == null, "no client found to ban");
var banCommand = new CBan();
banCommand.ExecuteAsync(new GameEvent()
{
Origin = new EFClient() { ClientId = 1, Level = EFClient.Permission.Console, CurrentServer = client.CurrentServer },
Target = client,
Data = "test ban",
Type = GameEvent.EventType.Command,
Owner = client.CurrentServer
}).Wait();
Assert.True(Manager.GetPenaltyService().GetActivePenaltiesAsync(client.AliasLinkId).Result.Count(p => p.Type == Penalty.PenaltyType.Ban) == 1,
"ban was not added");
var unbanCommand = new CUnban();
unbanCommand.ExecuteAsync(new GameEvent()
{
Origin = new EFClient() { ClientId = 1, Level = EFClient.Permission.Console, CurrentServer = client.CurrentServer },
Target = Manager.GetClientService().Find(c => c.NetworkId == client.NetworkId).Result.First(),
Data = "test unban",
Type = GameEvent.EventType.Command,
Owner = client.CurrentServer
}).Wait();
Assert.True(Manager.GetPenaltyService().GetActivePenaltiesAsync(client.AliasLinkId).Result.Count(p => p.Type == Penalty.PenaltyType.Ban) == 0,
"ban was not removed");
}
}
}

View File

@ -16,10 +16,11 @@ namespace Tests
public ManagerFixture()
{
File.WriteAllText("test_mp.log", "test_log_file");
File.WriteAllText("test_mp.log", "TEST_LOG_FILE");
IW4MAdmin.Application.Localization.Configure.Initialize("en-US");
Manager = Program.ServerManager;
Manager = ApplicationManager.GetInstance();
var config = new ApplicationConfiguration
{
@ -32,7 +33,7 @@ namespace Tests
Password = "test",
Port = 28963,
Rules = new List<string>(),
ManualLogPath = "https://raidmax.org/IW4MAdmin/getlog.php"
ManualLogPath = "http://google.com"
}
},
AutoMessages = new List<string>(),
@ -40,10 +41,11 @@ namespace Tests
Maps = new List<MapConfiguration>(),
RConPollRate = 10000
};
Manager.ConfigHandler = new BaseConfigurationHandler<ApplicationConfiguration>("Test.json");
Manager.ConfigHandler = new BaseConfigurationHandler<ApplicationConfiguration>("test.json");
Manager.ConfigHandler.Set(config);
Manager.Init().Wait();
Task.Run(() => Manager.Start());
}

View File

@ -1,5 +1,6 @@
using IW4MAdmin.Application;
using SharedLibraryCore;
using SharedLibraryCore.Database.Models;
using SharedLibraryCore.Interfaces;
using SharedLibraryCore.Objects;
using System;
@ -53,7 +54,7 @@ namespace Tests
var e = new GameEvent()
{
Type = GameEvent.EventType.Join,
Origin = new Player()
Origin = new EFClient()
{
Name = $"Player{i}",
NetworkId = i,
@ -78,7 +79,7 @@ namespace Tests
var e = new GameEvent()
{
Type = GameEvent.EventType.Disconnect,
Origin = new Player()
Origin = new EFClient()
{
Name = $"Player{i}",
NetworkId = i,
@ -113,7 +114,7 @@ namespace Tests
var e = new GameEvent()
{
Type = GameEvent.EventType.Connect,
Origin = new Player()
Origin = new EFClient()
{
Name = $"Player{i}",
NetworkId = i,
@ -134,7 +135,7 @@ namespace Tests
waiters.Dequeue().OnProcessed.Wait();
}
int actualClientNum = server.GetPlayersAsList().Count(p => p.State == Player.ClientState.Connected);
int actualClientNum = server.GetClientsAsList().Count(p => p.State == EFClient.ClientState.Connected);
Assert.True(actualClientNum == clientNum, $"client connected states don't match [{actualClientNum}:{clientNum}");
for (int i = clientIndexStart; i < clientNum + clientIndexStart; i++)
@ -142,7 +143,7 @@ namespace Tests
var e = new GameEvent()
{
Type = GameEvent.EventType.Disconnect,
Origin = new Player()
Origin = new EFClient()
{
Name = $"Player{i}",
NetworkId = i,
@ -188,6 +189,22 @@ namespace Tests
resetEvent.Wait(5000);
}
[Fact]
public void PrintCommands()
{
var sb = new StringBuilder();
sb.AppendLine("|Name |Alias|Description |Requires Target|Syntax |Required Level|");
sb.AppendLine("|--------------| -----| --------------------------------------------------------| -----------------| -------------| ----------------|");
foreach (var command in Manager.GetCommands().OrderByDescending(c => c.Permission).ThenBy(c => c.Name))
{
sb.AppendLine($"|{command.Name}|{command.Alias}|{command.Description}|{command.RequiresTarget}|{command.Syntax.Substring(8).EscapeMarkdown()}|{command.Permission}|");
}
Assert.True(false, sb.ToString());
}
}
}

View File

@ -1,5 +1,6 @@
using IW4MAdmin.Application;
using SharedLibraryCore;
using SharedLibraryCore.Database.Models;
using SharedLibraryCore.Objects;
using Xunit;
@ -21,7 +22,7 @@ namespace Tests
var e = new GameEvent()
{
Type = GameEvent.EventType.Connect,
Origin = new Player()
Origin = new EFClient()
{
Name = $"Player1",
NetworkId = 1,
@ -33,7 +34,7 @@ namespace Tests
Manager.GetEventHandler().AddEvent(e);
e.OnProcessed.Wait();
var client = Manager.GetServers()[0].Players[0];
var client = Manager.GetServers()[0].Clients[0];
e = new GameEvent()
{

View File

@ -3,6 +3,7 @@
<PropertyGroup>
<OutputType>Library</OutputType>
<TargetFramework>netcoreapp2.1</TargetFramework>
<RuntimeFrameworkVersion>2.1.5</RuntimeFrameworkVersion>
<ApplicationIcon />
<StartupObject />
</PropertyGroup>
@ -22,7 +23,7 @@
</ItemGroup>
<ItemGroup>
<PackageReference Update="Microsoft.NETCore.App" />
<PackageReference Update="Microsoft.NETCore.App" Version="2.1.5" />
</ItemGroup>
</Project>

View File

@ -1,142 +0,0 @@
/* CountryLookup.cs
*
* Copyright (C) 2008 MaxMind, Inc. All Rights Reserved.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
using SharedLibraryCore;
using System;
using System.IO;
using System.Net;
namespace CountryLookupProj
{
/// <summary>
/// Summary description for CountryLookup.
/// </summary>
public class CountryLookup
{
private FileStream fileInput;
private static long COUNTRY_BEGIN = 16776960;
private static string[] countryCode =
{ "--","AP","EU","AD","AE","AF","AG","AI","AL","AM","AN","AO","AQ","AR","AS","AT","AU","AW","AZ","BA","BB","BD","BE","BF","BG","BH","BI","BJ","BM","BN","BO","BR","BS","BT","BV","BW","BY","BZ","CA","CC","CD","CF","CG","CH","CI","CK","CL","CM","CN","CO","CR","CU","CV","CX","CY","CZ","DE","DJ","DK","DM","DO","DZ",
"EC","EE","EG","EH","ER","ES","ET","FI","FJ","FK","FM","FO","FR","FX","GA","GB","GD","GE","GF","GH","GI","GL","GM","GN","GP","GQ","GR","GS","GT","GU","GW","GY","HK","HM","HN","HR","HT","HU","ID","IE","IL","IN","IO","IQ","IR","IS","IT","JM","JO","JP","KE","KG","KH","KI","KM","KN","KP","KR","KW","KY","KZ",
"LA","LB","LC","LI","LK","LR","LS","LT","LU","LV","LY","MA","MC","MD","MG","MH","MK","ML","MM","MN","MO","MP","MQ","MR","MS","MT","MU","MV","MW","MX","MY","MZ","NA","NC","NE","NF","NG","NI","NL","NO","NP","NR","NU","NZ","OM","PA","PE","PF","PG","PH","PK","PL","PM","PN","PR","PS","PT","PW","PY","QA",
"RE","RO","RU","RW","SA","SB","SC","SD","SE","SG","SH","SI","SJ","SK","SL","SM","SN","SO","SR","ST","SV","SY","SZ","TC","TD","TF","TG","TH","TJ","TK","TM","TN","TO","TL","TR","TT","TV","TW","TZ","UA","UG","UM","US","UY","UZ","VA","VC","VE","VG","VI","VN","VU","WF","WS","YE","YT","RS","ZA","ZM","ME","ZW","A1","A2",
"O1","AX","GG","IM","JE","BL","MF"
};
private static string[] countryName =
{"a third world country","Asia/Pacific Region","Europe","Andorra","United Arab Emirates","Afghanistan","Antigua and Barbuda","Anguilla","Albania","Armenia","Netherlands Antilles","Angola","Antarctica","Argentina","American Samoa","Austria","Australia","Aruba","Azerbaijan","Bosnia and Herzegovina","Barbados","Bangladesh","Belgium",
"Burkina Faso","Bulgaria","Bahrain","Burundi","Benin","Bermuda","Brunei Darussalam","Bolivia","Brazil","Bahamas","Bhutan","Bouvet Island","Botswana","Belarus","Belize","Canada","Cocos (Keeling) Islands","Congo, The Democratic Republic of the","Central African Republic","Congo","Switzerland","Cote D'Ivoire",
"Cook Islands","Chile","Cameroon","China","Colombia","Costa Rica","Cuba","Cape Verde","Christmas Island","Cyprus","Czech Republic","Germany","Djibouti","Denmark","Dominica","Dominican Republic","Algeria","Ecuador","Estonia","Egypt","Western Sahara","Eritrea","Spain","Ethiopia","Finland","Fiji","Falkland Islands (Malvinas)",
"Micronesia, Federated States of","Faroe Islands","France","France, Metropolitan","Gabon","United Kingdom","Grenada","Georgia","French Guiana","Ghana","Gibraltar","Greenland","Gambia","Guinea","Guadeloupe","Equatorial Guinea","Greece","South Georgia and the South Sandwich Islands","Guatemala","Guam","Guinea-Bissau","Guyana",
"Hong Kong","Heard Island and McDonald Islands","Honduras","Croatia","Haiti","Hungary","Indonesia","Ireland","Israel","India","British Indian Ocean Territory","Iraq","Iran, Islamic Republic of","Iceland","Italy","Jamaica","Jordan","Japan","Kenya","Kyrgyzstan","Cambodia","Kiribati","Comoros","Saint Kitts and Nevis",
"Korea, Democratic People's Republic of","Korea, Republic of","Kuwait","Cayman Islands","Kazakstan","Lao People's Democratic Republic","Lebanon","Saint Lucia","Liechtenstein","Sri Lanka","Liberia","Lesotho","Lithuania","Luxembourg","Latvia","Libyan Arab Jamahiriya","Morocco","Monaco","Moldova, Republic of","Madagascar",
"Marshall Islands","Macedonia","Mali","Myanmar","Mongolia","Macau","Northern Mariana Islands","Martinique","Mauritania","Montserrat","Malta","Mauritius","Maldives","Malawi","Mexico","Malaysia","Mozambique","Namibia","New Caledonia","Niger","Norfolk Island","Nigeria","Nicaragua","Netherlands",
"Norway","Nepal","Nauru","Niue","New Zealand","Oman","Panama","Peru","French Polynesia","Papua New Guinea","Philippines","Pakistan","Poland","Saint Pierre and Miquelon","Pitcairn Islands","Puerto Rico","Palestinian Territory","Portugal","Palau","Paraguay","Qatar","Reunion","Romania","Russian Federation","Rwanda","Saudi Arabia",
"Solomon Islands","Seychelles","Sudan","Sweden","Singapore","Saint Helena","Slovenia","Svalbard and Jan Mayen","Slovakia","Sierra Leone","San Marino","Senegal","Somalia","Suriname","Sao Tome and Principe","El Salvador","Syrian Arab Republic","Swaziland","Turks and Caicos Islands","Chad","French Southern Territories","Togo",
"Thailand","Tajikistan","Tokelau","Turkmenistan","Tunisia","Tonga","Timor-Leste","Turkey","Trinidad and Tobago","Tuvalu","Taiwan","Tanzania, United Republic of","Ukraine","Uganda","United States Minor Outlying Islands","United States","Uruguay","Uzbekistan","Holy See (Vatican City State)","Saint Vincent and the Grenadines",
"Venezuela","Virgin Islands, British","Virgin Islands, U.S.","Vietnam","Vanuatu","Wallis and Futuna","Samoa","Yemen","Mayotte","Serbia","South Africa","Zambia","Montenegro","Zimbabwe","Anonymous Proxy","Satellite Provider",
"Other","Aland Islands","Guernsey","Isle of Man","Jersey","Saint Barthelemy","Saint Martin"};
public CountryLookup(string fileName)
{
fileInput = new FileStream(fileName, FileMode.Open, FileAccess.Read);
}
private long AddrToNum(IPAddress addr)
{
long ipnum = 0;
byte[] b = BitConverter.GetBytes((UInt32)addr.ToString().ConvertToIP());
for (int i = 0; i < 4; ++i)
{
long y = b[i];
if (y < 0)
{
y += 256;
}
ipnum += y << ((3 - i) * 8);
}
return ipnum;
}
public string LookupCountryCode(IPAddress addr)
{
return (countryCode[(int)SeekCountry(0, AddrToNum(addr), 31)]);
}
public string LookupCountryName(string str)
{
IPAddress addr;
try
{
addr = IPAddress.Parse(str);
}
catch (FormatException)
{
return "a third world country";
}
return LookupCountryName(addr);
}
public string LookupCountryName(IPAddress addr)
{
return (countryName[(int)SeekCountry(0, AddrToNum(addr), 31)]);
}
private long SeekCountry(long offset, long ipnum, int depth)
{
byte[] buf = new byte[6];
long[] x = new long[2];
fileInput.Seek(6 * offset, 0);
fileInput.Read(buf, 0, 6);
for (int i = 0; i < 2; i++)
{
x[i] = 0;
for (int j = 0; j < 3; j++)
{
int y = buf[i * 3 + j];
if (y < 0)
{
y += 256;
}
x[i] += (y << (j * 8));
}
}
if ((ipnum & (1 << depth)) > 0)
{
if (x[1] >= COUNTRY_BEGIN)
{
return x[1] - COUNTRY_BEGIN;
}
return SeekCountry(x[1], ipnum, depth - 1);
}
else
{
if (x[0] >= COUNTRY_BEGIN)
{
return x[0] - COUNTRY_BEGIN;
}
return SeekCountry(x[0], ipnum, depth - 1);
}
}
}
}

Binary file not shown.

Some files were not shown because too many files have changed in this diff Show More