Compare commits

..

10 Commits

Author SHA1 Message Date
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
59 changed files with 814 additions and 884 deletions

1
.gitignore vendored
View File

@ -229,3 +229,4 @@ bootstrap-custom.min.css
/DiscordWebhook/env
/DiscordWebhook/config.dev.json
/GameLogServer/env
launchSettings.json

View File

@ -3,9 +3,10 @@
<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.2</Version>
<Version>2.2</Version>
<Authors>RaidMax</Authors>
<Company>Forever None</Company>
<Product>IW4MAdmin</Product>
@ -30,6 +31,8 @@
<PropertyGroup>
<ServerGarbageCollection>true</ServerGarbageCollection>
<TieredCompilation>true</TieredCompilation>
<AssemblyVersion>2.2.0.0</AssemblyVersion>
<FileVersion>2.2.0.0</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,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

@ -67,7 +67,7 @@ namespace IW4MAdmin.Application.Core
{
if (AuthenticatedClients.Values.Count > 18)
{
Program.ServerManager.GetLogger().WriteWarning($"auth client count is {AuthenticatedClients.Values.Count}, this is bad");
Program.ServerManager.GetLogger(0).WriteError($"auth client count is {AuthenticatedClients.Values.Count}, this is bad");
return AuthenticatedClients.Values.Take(18).ToList();
}

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

@ -55,9 +55,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

@ -59,9 +59,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

@ -15,7 +15,7 @@ namespace IW4MAdmin.Application.Localization
public static void Initialize(string customLocale)
{
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

@ -10,19 +10,19 @@ using System.Text;
using System.Threading;
using System.Collections.Generic;
using SharedLibraryCore.Localization;
using IW4MAdmin.Application.Migration;
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 +39,17 @@ namespace IW4MAdmin.Application
try
{
loc = Utilities.CurrentLocalization.LocalizationIndex;
Console.CancelKeyPress += new ConsoleCancelEventHandler(OnCancelKey);
CheckDirectories();
// do any needed migrations
// todo: move out
ConfigurationMigration.MoveConfigFolder10518(null);
ServerManager = ApplicationManager.GetInstance();
Console.CancelKeyPress += new ConsoleCancelEventHandler(OnCancelKey);
Localization.Configure.Initialize(ServerManager.GetApplicationSettings().Configuration()?.CustomLocale);
loc = Utilities.CurrentLocalization.LocalizationIndex;
ServerManager.Logger.WriteInfo($"Version is {Version}");
@ -107,7 +112,7 @@ namespace IW4MAdmin.Application
var consoleTask = Task.Run(async () =>
{
String userInput;
Player Origin = Utilities.IW4MAdminClient;
Player Origin = Utilities.IW4MAdminClient(ServerManager.Servers[0]);
do
{
@ -124,7 +129,6 @@ namespace IW4MAdmin.Application
if (userInput?.Length > 0)
{
Origin.CurrentServer = ServerManager.Servers[0];
GameEvent E = new GameEvent()
{
Type = GameEvent.EventType.Command,
@ -149,7 +153,7 @@ namespace IW4MAdmin.Application
{
e = e.InnerException;
}
Console.WriteLine($"Exception: {e.Message}");
Console.WriteLine(e.Message);
Console.WriteLine(loc["MANAGER_EXIT"]);
Console.ReadKey();
return;
@ -169,15 +173,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

@ -18,6 +18,7 @@ using SharedLibraryCore.Database;
using SharedLibraryCore.Events;
using IW4MAdmin.Application.API.Master;
using IW4MAdmin.Application.Migration;
namespace IW4MAdmin.Application
{
@ -26,7 +27,7 @@ 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 ILogger Logger => GetLogger(0);
public bool Running { get; private set; }
public bool IsInitialized { get; private set; }
// define what the delagate function looks like
@ -48,10 +49,10 @@ namespace IW4MAdmin.Application
ManualResetEventSlim OnQuit;
readonly IPageList PageList;
readonly SemaphoreSlim ProcessingEvent = new SemaphoreSlim(1, 1);
readonly Dictionary<int, ILogger> Loggers = new Dictionary<int, ILogger>();
private ApplicationManager()
{
Logger = new Logger($@"{Utilities.OperatingDirectory}IW4MAdmin.log");
_servers = new List<Server>();
Commands = new List<Command>();
TaskStatuses = new List<AsyncStatus>();
@ -379,11 +380,10 @@ 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
@ -565,9 +565,29 @@ namespace IW4MAdmin.Application
Running = false;
}
public ILogger GetLogger()
public ILogger GetLogger(int 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()

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

@ -9,8 +9,6 @@ using SharedLibraryCore.Objects;
using SharedLibraryCore.RCon;
using SharedLibraryCore.Exceptions;
using System.Text;
using System.Linq;
using System.Net.Http;
namespace IW4MAdmin.Application.RconParsers
{

View File

@ -33,21 +33,30 @@ namespace IW4MAdmin
public override int GetHashCode()
{
// todo: make this better with collisions
int id = Math.Abs($"{IP}:{Port.ToString()}".Select(a => (int)a).Sum());
// hack: this is a nasty fix for get hashcode being changed
switch (id)
if (GameName == Game.IW4)
{
case 765:
return 886229536;
case 760:
return 1645744423;
case 761:
return 1645809959;
// todo: make this better with collisions
int id = Math.Abs($"{IP}:{Port.ToString()}".Select(a => (int)a).Sum());
// hack: this is a nasty fix for get hashcode being changed
switch (id)
{
case 765:
return 886229536;
case 760:
return 1645744423;
case 761:
return 1645809959;
}
return id;
}
return id;
else
{
int id = HashCode.Combine(IP, Port);
return id < 0 ? Math.Abs(id) : id;
}
}
public async Task OnPlayerJoined(Player logClient)
@ -201,12 +210,11 @@ namespace IW4MAdmin
{
player.Level = Player.Permission.User;
}
#if DEBUG == false
if (currentBan != null)
{
Logger.WriteInfo($"Banned client {player} trying to connect...");
var autoKickClient = Utilities.IW4MAdminClient;
autoKickClient.CurrentServer = this;
var autoKickClient = Utilities.IW4MAdminClient(this);
// the player is permanently banned
if (currentBan.Type == Penalty.PenaltyType.Ban)
@ -243,7 +251,6 @@ namespace IW4MAdmin
Players[player.ClientNumber] = null;
return false;
}
#endif
player.State = Player.ClientState.Connected;
return true;
@ -251,9 +258,9 @@ namespace IW4MAdmin
catch (Exception ex)
{
Manager.GetLogger().WriteError($"{loc["SERVER_ERROR_ADDPLAYER"]} {polledPlayer.Name}::{polledPlayer.NetworkId}");
Manager.GetLogger().WriteDebug(ex.Message);
Manager.GetLogger().WriteDebug(ex.StackTrace);
Logger.WriteError($"{loc["SERVER_ERROR_ADDPLAYER"]} {polledPlayer.Name}::{polledPlayer.NetworkId}");
Logger.WriteDebug(ex.Message);
Logger.WriteDebug(ex.StackTrace);
return false;
}
}
@ -335,7 +342,7 @@ namespace IW4MAdmin
(canExecuteCommand ||
E.Origin?.Level == Player.Permission.Console))
{
var _ = (((Command)E.Extra).ExecuteAsync(E));
await (((Command)E.Extra).ExecuteAsync(E));
}
}
@ -432,6 +439,11 @@ namespace IW4MAdmin
await Kick(E.Data, E.Target, E.Origin);
}
else if (E.Type == GameEvent.EventType.Warn)
{
await Warn(E.Data, E.Target, E.Origin);
}
else if (E.Type == GameEvent.EventType.Quit)
{
var origin = Players.FirstOrDefault(p => p != null && p.NetworkId == E.Origin.NetworkId);
@ -871,7 +883,7 @@ namespace IW4MAdmin
Logger.WriteError($"{logPath} {loc["SERVER_ERROR_DNE"]}");
#if !DEBUG
throw new ServerException($"{loc["SERVER_ERROR_LOG"]} {logPath}");
//#else
#else
LogEvent = new GameLogEventDetection(this, logPath, logfile.Value);
#endif
}
@ -888,7 +900,7 @@ namespace IW4MAdmin
#endif
}
public override async Task Warn(String Reason, Player Target, Player Origin)
protected override async Task Warn(String Reason, Player Target, Player Origin)
{
// ensure player gets warned if command not performed on them in game
if (Target.ClientNumber < 0)
@ -907,7 +919,7 @@ namespace IW4MAdmin
{
if (Target.Warnings >= 4)
{
Target.Kick(loc["SERVER_WARNLIMT_REACHED"], Utilities.IW4MAdminClient);
Target.Kick(loc["SERVER_WARNLIMT_REACHED"], Utilities.IW4MAdminClient(this));
return;
}
@ -963,7 +975,6 @@ namespace IW4MAdmin
Offender = Target,
Offense = Reason,
Punisher = Origin,
Active = true,
When = DateTime.UtcNow,
Link = Target.AliasLink
};

View File

@ -11,6 +11,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

View File

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

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

@ -25,7 +25,7 @@ namespace IW4MAdmin.Plugins.ProfanityDeterment
public Task OnEventAsync(GameEvent E, Server S)
{
if (!Settings.Configuration().EnableProfanityDeterment)
return Task.CompletedTask; ;
return Task.CompletedTask;
if (E.Type == GameEvent.EventType.Connect)
{
@ -50,7 +50,8 @@ namespace IW4MAdmin.Plugins.ProfanityDeterment
{
E.Origin.Kick(Settings.Configuration().ProfanityKickMessage, new Player()
{
ClientId = 1
ClientId = 1,
CurrentServer = E.Owner
});
};
}
@ -86,7 +87,8 @@ namespace IW4MAdmin.Plugins.ProfanityDeterment
{
clientProfanity.Client.Kick(Settings.Configuration().ProfanityKickMessage, new Player()
{
ClientId = 1
ClientId = 1,
CurrentServer = E.Owner
});
}
@ -96,7 +98,8 @@ namespace IW4MAdmin.Plugins.ProfanityDeterment
clientProfanity.Client.Warn(Settings.Configuration().ProfanityWarningMessage, new Player()
{
ClientId = 1
ClientId = 1,
CurrentServer = E.Owner
});
}
}

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,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

@ -26,8 +26,8 @@ namespace IW4MAdmin.Plugins.Stats.Cheat
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;
@ -53,16 +53,16 @@ namespace IW4MAdmin.Plugins.Stats.Cheat
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)
kill.HitLoc == IW4Info.HitLocation.none || kill.TimeOffset - LastOffset < 0 ||
// hack: prevents false positives
(LastWeapon != kill.Weapon && (kill.TimeOffset - LastOffset) == 50))
return new DetectionPenaltyResult()
{
ClientPenalty = Penalty.PenaltyType.Any,
};
DetectionPenaltyResult result = null;
if (LastHit == DateTime.MinValue)
LastHit = DateTime.UtcNow;
LastWeapon = kill.Weapon;
HitLocationCount[kill.HitLoc]++;
if (!isDamage)

View File

@ -6,6 +6,8 @@ using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using SharedLibraryCore.Database;
using Microsoft.EntityFrameworkCore;
namespace IW4MAdmin.Plugins.Stats.Commands
{
@ -17,23 +19,30 @@ namespace IW4MAdmin.Plugins.Stats.Commands
{
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;
EFClientStatistics clientStats;
using (var ctx = new DatabaseContext(disableTracking: true))
{
clientStats = await ctx.Set<EFClientStatistics>()
.Where(s => s.ClientId == E.Origin.ClientId && s.ServerId == serverId)
.FirstAsync();
// reset the cached version
Plugin.Manager.ResetStats(E.Origin.ClientId, E.Owner.GetHashCode());
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;
// fixme: this doesn't work properly when another context exists
await svc.SaveChangesAsync();
// reset the cached version
Plugin.Manager.ResetStats(E.Origin.ClientId, E.Owner.GetHashCode());
// fixme: this doesn't work properly when another context exists
await ctx.SaveChangesAsync();
}
E.Origin.Tell(Utilities.CurrentLocalization.LocalizationIndex["PLUGINS_STATS_COMMANDS_RESET_SUCCESS"]);
}

View File

@ -7,6 +7,8 @@ using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using SharedLibraryCore.Database;
using Microsoft.EntityFrameworkCore;
namespace IW4MAdmin.Plugins.Stats.Commands
{
@ -39,19 +41,21 @@ namespace IW4MAdmin.Plugins.Stats.Commands
}
}
var clientStats = new GenericRepository<EFClientStatistics>();
int serverId = E.Owner.GetHashCode();
if (E.Target != null)
using (var ctx = new DatabaseContext(disableTracking: true))
{
pStats = (await clientStats.FindAsync(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)
{
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()}";
}
else
{
pStats = (await clientStats.FindAsync(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()}";
else
{
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()}";
}
}
if (E.Message.IsBroadcastCommand())

View File

@ -24,20 +24,20 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
public class StatManager
{
private ConcurrentDictionary<int, ServerStats> Servers;
private ConcurrentDictionary<int, ThreadSafeStatsService> ContextThreads;
private ILogger Log;
private IManager Manager;
private readonly IManager Manager;
private readonly SemaphoreSlim OnProcessingPenalty;
private readonly SemaphoreSlim OnProcessingSensitive;
public StatManager(IManager mgr)
{
Servers = new ConcurrentDictionary<int, ServerStats>();
ContextThreads = new ConcurrentDictionary<int, ThreadSafeStatsService>();
Log = mgr.GetLogger();
Log = mgr.GetLogger(0);
Manager = mgr;
OnProcessingPenalty = new SemaphoreSlim(1, 1);
OnProcessingSensitive = new SemaphoreSlim(1, 1);
}
public EFClientStatistics GetClientStats(int clientId, int serverId) => Servers[serverId].PlayerStats[clientId];
@ -147,7 +147,7 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
});
#if DEBUG == true
var statsInfoSql = iqStatsInfo.ToSql();
var statsInfoSql = iqStatsInfo.ToSql();
#endif
var topPlayers = await iqStatsInfo.ToListAsync();
@ -188,33 +188,36 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
/// <param name="sv"></param>
public void AddServer(Server sv)
{
// insert the server if it does not exist
try
{
int serverId = sv.GetHashCode();
var statsSvc = new ThreadSafeStatsService();
ContextThreads.TryAdd(serverId, statsSvc);
EFServer server;
// get the server from the database if it exists, otherwise create and insert a new one
var server = statsSvc.ServerSvc.Find(c => c.ServerId == serverId).FirstOrDefault();
if (server == null)
using (var ctx = new DatabaseContext(disableTracking: true))
{
server = new EFServer()
{
Port = sv.GetPort(),
Active = true,
ServerId = serverId
};
var serverSet = ctx.Set<EFServer>();
// get the server from the database if it exists, otherwise create and insert a new one
server = serverSet.FirstOrDefault(c => c.ServerId == serverId);
statsSvc.ServerSvc.Insert(server);
if (server == null)
{
server = new EFServer()
{
Port = sv.GetPort(),
Active = true,
ServerId = serverId
};
server = serverSet.Add(server).Entity;
// this doesn't need to be async as it's during initialization
ctx.SaveChanges();
}
}
// this doesn't need to be async as it's during initialization
statsSvc.ServerSvc.SaveChanges();
// check to see if the stats have ever been initialized
InitializeServerStats(sv);
statsSvc.ServerStatsSvc.SaveChanges();
var serverStats = InitializeServerStats(sv);
var serverStats = statsSvc.ServerStatsSvc.Find(c => c.ServerId == serverId).FirstOrDefault();
Servers.TryAdd(serverId, new ServerStats(server, serverStats)
{
IsTeamBased = sv.Gametype != "dm"
@ -234,93 +237,135 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
/// <returns>EFClientStatistic of specified player</returns>
public async Task<EFClientStatistics> AddPlayer(Player pl)
{
int serverId = pl.CurrentServer.GetHashCode();
await OnProcessingSensitive.WaitAsync();
if (!Servers.ContainsKey(serverId))
try
{
Log.WriteError($"[Stats::AddPlayer] Server with id {serverId} could not be found");
return null;
}
int serverId = pl.CurrentServer.GetHashCode();
var playerStats = Servers[serverId].PlayerStats;
var detectionStats = Servers[serverId].PlayerDetections;
var statsSvc = ContextThreads[serverId];
if (playerStats.ContainsKey(pl.ClientId))
{
Log.WriteWarning($"Duplicate ClientId in stats {pl.ClientId}");
return null;
}
// get the client's stats from the database if it exists, otherwise create and attach a new one
// if this fails we want to throw an exception
var clientStatsSvc = statsSvc.ClientStatSvc;
var clientStats = clientStatsSvc.Find(c => c.ClientId == pl.ClientId && c.ServerId == serverId).FirstOrDefault();
if (clientStats == null)
{
clientStats = new EFClientStatistics()
if (!Servers.ContainsKey(serverId))
{
Active = true,
ClientId = pl.ClientId,
Deaths = 0,
Kills = 0,
ServerId = serverId,
Skill = 0.0,
SPM = 0.0,
EloRating = 200.0,
HitLocations = Enum.GetValues(typeof(IW4Info.HitLocation)).OfType<IW4Info.HitLocation>().Select(hl => new EFHitLocationCount()
Log.WriteError($"[Stats::AddPlayer] Server with id {serverId} could not be found");
return null;
}
var playerStats = Servers[serverId].PlayerStats;
var detectionStats = Servers[serverId].PlayerDetections;
if (playerStats.ContainsKey(pl.ClientId))
{
Log.WriteWarning($"Duplicate ClientId in stats {pl.ClientId}");
return playerStats[pl.ClientId];
}
// get the client's stats from the database if it exists, otherwise create and attach a new one
// if this fails we want to throw an exception
EFClientStatistics clientStats;
using (var ctx = new DatabaseContext(disableTracking: true))
{
var clientStatsSet = ctx.Set<EFClientStatistics>();
clientStats = clientStatsSet
.Include(cl => cl.HitLocations)
.FirstOrDefault(c => c.ClientId == pl.ClientId && c.ServerId == serverId);
if (clientStats == null)
{
Active = true,
HitCount = 0,
Location = hl
}).ToList()
};
clientStats = new EFClientStatistics()
{
Active = true,
ClientId = pl.ClientId,
Deaths = 0,
Kills = 0,
ServerId = serverId,
Skill = 0.0,
SPM = 0.0,
EloRating = 200.0,
HitLocations = Enum.GetValues(typeof(IW4Info.HitLocation)).OfType<IW4Info.HitLocation>().Select(hl => new EFHitLocationCount()
{
Active = true,
HitCount = 0,
Location = hl
}).ToList()
};
// insert if they've not been added
clientStats = clientStatsSvc.Insert(clientStats);
await clientStatsSvc.SaveChangesAsync();
// insert if they've not been added
clientStats = clientStatsSet.Add(clientStats).Entity;
await ctx.SaveChangesAsync();
if (!playerStats.TryAdd(clientStats.ClientId, clientStats))
{
Log.WriteWarning("Adding new client to stats failed");
}
}
else
{
if (!playerStats.TryAdd(clientStats.ClientId, clientStats))
{
Log.WriteWarning("Adding pre-existing client to stats failed");
}
}
// migration for previous existing stats
if (clientStats.HitLocations.Count == 0)
{
clientStats.HitLocations = Enum.GetValues(typeof(IW4Info.HitLocation)).OfType<IW4Info.HitLocation>()
.Select(hl => new EFHitLocationCount()
{
Active = true,
HitCount = 0,
Location = hl
})
.ToList();
ctx.Update(clientStats);
await ctx.SaveChangesAsync();
}
// for stats before rating
if (clientStats.EloRating == 0.0)
{
clientStats.EloRating = clientStats.Skill;
}
if (clientStats.RollingWeightedKDR == 0)
{
clientStats.RollingWeightedKDR = clientStats.KDR;
}
// set these on connecting
clientStats.LastActive = DateTime.UtcNow;
clientStats.LastStatCalculation = DateTime.UtcNow;
clientStats.SessionScore = pl.Score;
clientStats.LastScore = pl.Score;
if (!detectionStats.TryAdd(pl.ClientId, new Cheat.Detection(Log, clientStats)))
{
Log.WriteWarning("Could not add client to detection");
}
Log.WriteInfo($"Adding {pl} to stats");
}
return clientStats;
}
// migration for previous existing stats
if (clientStats.HitLocations.Count == 0)
catch (Exception ex)
{
clientStats.HitLocations = Enum.GetValues(typeof(IW4Info.HitLocation)).OfType<IW4Info.HitLocation>().Select(hl => new EFHitLocationCount()
{
Active = true,
HitCount = 0,
Location = hl
})
.ToList();
await statsSvc.ClientStatSvc.SaveChangesAsync();
Log.WriteWarning("Could not add client to stats");
Log.WriteDebug(ex.GetExceptionInfo());
}
// for stats before rating
if (clientStats.EloRating == 0.0)
finally
{
clientStats.EloRating = clientStats.Skill;
OnProcessingSensitive.Release(1);
}
if (clientStats.RollingWeightedKDR == 0)
{
clientStats.RollingWeightedKDR = clientStats.KDR;
}
// set these on connecting
clientStats.LastActive = DateTime.UtcNow;
clientStats.LastStatCalculation = DateTime.UtcNow;
clientStats.SessionScore = pl.Score;
clientStats.LastScore = pl.Score;
Log.WriteInfo($"Adding {pl} to stats");
if (!playerStats.TryAdd(pl.ClientId, clientStats))
Log.WriteDebug($"Could not add client to stats {pl}");
if (!detectionStats.TryAdd(pl.ClientId, new Cheat.Detection(Log, clientStats)))
Log.WriteDebug("Could not add client to detection");
return clientStats;
return null;
}
/// <summary>
@ -336,7 +381,6 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
var playerStats = Servers[serverId].PlayerStats;
var detectionStats = Servers[serverId].PlayerDetections;
var serverStats = Servers[serverId].ServerStatistics;
var statsSvc = ContextThreads[serverId];
if (!playerStats.ContainsKey(pl.ClientId))
{
@ -355,10 +399,13 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
detectionStats.TryRemove(pl.ClientId, out Cheat.Detection removedValue4);
// sync their stats before they leave
var clientStatsSvc = statsSvc.ClientStatSvc;
clientStats = UpdateStats(clientStats);
clientStatsSvc.Update(clientStats);
await clientStatsSvc.SaveChangesAsync();
using (var ctx = new DatabaseContext(disableTracking: true))
{
ctx.Update(clientStats);
await ctx.SaveChangesAsync();
}
// increment the total play time
serverStats.TotalPlayTime += (int)(DateTime.UtcNow - pl.LastConnection).TotalSeconds;
@ -389,7 +436,6 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
string damage, string weapon, string killOrigin, string deathOrigin, string viewAngles, string offset, string isKillstreakKill, string Ads,
string fraction, string visibilityPercentage, string snapAngles)
{
var statsSvc = ContextThreads[serverId];
Vector3 vDeathOrigin = null;
Vector3 vKillOrigin = null;
Vector3 vViewAngles = null;
@ -466,10 +512,20 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
return;
}
// incase the add palyer event get delayed
if (!Servers[serverId].PlayerStats.ContainsKey(attacker.ClientId))
{
await AddPlayer(attacker);
}
var clientDetection = Servers[serverId].PlayerDetections[attacker.ClientId];
var clientStats = Servers[serverId].PlayerStats[attacker.ClientId];
var clientStatsSvc = statsSvc.ClientStatSvc;
clientStatsSvc.Update(clientStats);
using (var ctx = new DatabaseContext(disableTracking: true))
{
ctx.Set<EFClientStatistics>().Update(clientStats);
await ctx.SaveChangesAsync();
}
// increment their hit count
if (hit.DeathType == IW4Info.MeansOfDeath.MOD_PISTOL_BULLET ||
@ -492,17 +548,18 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
if (Plugin.Config.Configuration().EnableAntiCheat)
{
await ApplyPenalty(clientDetection.ProcessKill(hit, isDamage), clientDetection, attacker, ctx);
await ApplyPenalty(clientDetection.ProcessTotalRatio(clientStats), clientDetection, attacker, ctx);
ApplyPenalty(clientDetection.ProcessKill(hit, isDamage), clientDetection, attacker, ctx);
ApplyPenalty(clientDetection.ProcessTotalRatio(clientStats), clientDetection, attacker, ctx);
}
await clientStatsSvc.SaveChangesAsync();
ctx.Set<EFHitLocationCount>().UpdateRange(clientStats.HitLocations);
await ctx.SaveChangesAsync();
}
catch (Exception ex)
{
Log.WriteError("AC ERROR");
Log.WriteError("Could not save hit or AC info");
Log.WriteDebug(ex.GetExceptionInfo());
}
@ -510,7 +567,7 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
}
}
async Task ApplyPenalty(Cheat.DetectionPenaltyResult penalty, Cheat.Detection clientDetection, Player attacker, DatabaseContext ctx)
void ApplyPenalty(Cheat.DetectionPenaltyResult penalty, Cheat.Detection clientDetection, Player attacker, DatabaseContext ctx)
{
switch (penalty.ClientPenalty)
{
@ -530,7 +587,9 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
$"{penalty.Type}-{(int)penalty.Location}-{Math.Round(penalty.Value, 2)}@{penalty.HitCount}" :
$"{penalty.Type}-{Math.Round(penalty.Value, 2)}@{penalty.HitCount}",
}
}
},
Level = Player.Permission.Console,
CurrentServer = attacker.CurrentServer
});
if (clientDetection.Tracker.HasChanges)
{
@ -542,30 +601,23 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
{
break;
}
var e = new GameEvent()
{
Data = penalty.Type == Cheat.Detection.DetectionType.Bone ?
string flagReason = penalty.Type == Cheat.Detection.DetectionType.Bone ?
$"{penalty.Type}-{(int)penalty.Location}-{Math.Round(penalty.Value, 2)}@{penalty.HitCount}" :
$"{penalty.Type}-{Math.Round(penalty.Value, 2)}@{penalty.HitCount}",
Origin = new Player()
{
ClientId = 1,
Level = Player.Permission.Console,
ClientNumber = -1,
CurrentServer = attacker.CurrentServer
},
Target = attacker,
Owner = attacker.CurrentServer,
Type = GameEvent.EventType.Flag
};
// because we created an event it must be processed by the manager
// even if it didn't really do anything
Manager.GetEventHandler().AddEvent(e);
await new CFlag().ExecuteAsync(e);
$"{penalty.Type}-{Math.Round(penalty.Value, 2)}@{penalty.HitCount}";
attacker.Flag(flagReason, new Player()
{
ClientId = 1,
Level = Player.Permission.Console,
CurrentServer = attacker.CurrentServer,
});
if (clientDetection.Tracker.HasChanges)
{
SaveTrackedSnapshots(clientDetection, ctx);
}
break;
}
}
@ -652,7 +704,7 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
}
#if DEBUG
Log.WriteDebug("Calculating standard kill");
Log.WriteDebug("Calculating standard kill");
#endif
// update the total stats
@ -704,7 +756,7 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
#if !DEBUG
if ((DateTime.UtcNow - attackerStats.LastStatHistoryUpdate).TotalMinutes >= 2.5)
#else
if ((DateTime.UtcNow - attackerStats.LastStatHistoryUpdate).TotalMinutes >= 0.1)
if ((DateTime.UtcNow - attackerStats.LastStatHistoryUpdate).TotalMinutes >= 0.1)
#endif
{
attackerStats.LastStatHistoryUpdate = DateTime.UtcNow;
@ -712,10 +764,14 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
}
// todo: do we want to save this immediately?
var clientStatsSvc = ContextThreads[serverId].ClientStatSvc;
clientStatsSvc.Update(attackerStats);
clientStatsSvc.Update(victimStats);
await clientStatsSvc.SaveChangesAsync();
using (var ctx = new DatabaseContext(disableTracking: true))
{
var clientStatsSet = ctx.Set<EFClientStatistics>();
clientStatsSet.Update(attackerStats);
clientStatsSet.Update(victimStats);
await ctx.SaveChangesAsync();
}
}
/// <summary>
@ -1048,39 +1104,43 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
return clientStats;
}
public void InitializeServerStats(Server sv)
public EFServerStatistics InitializeServerStats(Server sv)
{
int serverId = sv.GetHashCode();
var statsSvc = ContextThreads[serverId];
EFServerStatistics serverStats;
var serverStats = statsSvc.ServerStatsSvc.Find(s => s.ServerId == serverId).FirstOrDefault();
if (serverStats == null)
using (var ctx = new DatabaseContext(disableTracking: true))
{
Log.WriteDebug($"Initializing server stats for {sv}");
// server stats have never been generated before
serverStats = new EFServerStatistics()
var serverStatsSet = ctx.Set<EFServerStatistics>();
serverStats = serverStatsSet.FirstOrDefault(s => s.ServerId == serverId);
if (serverStats == null)
{
Active = true,
ServerId = serverId,
TotalKills = 0,
TotalPlayTime = 0,
};
Log.WriteDebug($"Initializing server stats for {sv}");
// server stats have never been generated before
serverStats = new EFServerStatistics()
{
ServerId = serverId,
TotalKills = 0,
TotalPlayTime = 0,
};
var ieClientStats = statsSvc.ClientStatSvc.Find(cs => cs.ServerId == serverId);
// set these incase we've imported settings
serverStats.TotalKills = ieClientStats.Sum(cs => cs.Kills);
serverStats.TotalPlayTime = Manager.GetClientService().GetTotalPlayTime().Result;
statsSvc.ServerStatsSvc.Insert(serverStats);
serverStats = serverStatsSet.Add(serverStats).Entity;
ctx.SaveChanges();
}
}
return serverStats;
}
public void ResetKillstreaks(int serverId)
{
var serverStats = Servers[serverId];
foreach (var stat in serverStats.PlayerStats.Values)
{
stat.StartNewSession();
}
}
public void ResetStats(int clientId, int serverId)
@ -1100,32 +1160,30 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
if (clientId < 1)
return;
var messageSvc = ContextThreads[serverId].MessageSvc;
messageSvc.Insert(new EFClientMessage()
using (var ctx = new DatabaseContext(disableTracking: true))
{
Active = true,
ClientId = clientId,
Message = message,
ServerId = serverId,
TimeSent = DateTime.UtcNow
});
await messageSvc.SaveChangesAsync();
ctx.Set<EFClientMessage>().Add(new EFClientMessage()
{
ClientId = clientId,
Message = message,
ServerId = serverId,
TimeSent = DateTime.UtcNow
});
await ctx.SaveChangesAsync();
}
}
public async Task Sync(Server sv)
{
int serverId = sv.GetHashCode();
var statsSvc = ContextThreads[serverId];
// Log.WriteDebug("Syncing stats contexts");
await statsSvc.ServerStatsSvc.SaveChangesAsync();
//await statsSvc.ClientStatSvc.SaveChangesAsync();
await statsSvc.KillStatsSvc.SaveChangesAsync();
await statsSvc.ServerSvc.SaveChangesAsync();
statsSvc = null;
// this should prevent the gunk from having a long lasting context.
ContextThreads[serverId] = new ThreadSafeStatsService();
using (var ctx = new DatabaseContext(disableTracking: true))
{
var serverSet = ctx.Set<EFServer>();
serverSet.Update(Servers[serverId].Server);
await ctx.SaveChangesAsync();
}
}
public void SetTeamBased(int serverId, bool isTeamBased)

View File

@ -1,38 +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>(true);
}
}
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()
{
ServerSvc = new GenericRepository<EFServer>();
KillStatsSvc = new GenericRepository<EFClientKill>();
ServerStatsSvc = new GenericRepository<EFServerStatistics>();
}
}
}

View File

@ -23,6 +23,5 @@ namespace IW4MAdmin.Plugins.Stats.Models
public int ServerId { get; set; }
[ForeignKey("ServerId"), Column(Order = 1)]
public EFServer Server { get; set; }
}
}

View File

@ -13,6 +13,8 @@ using SharedLibraryCore.Services;
using IW4MAdmin.Plugins.Stats.Config;
using IW4MAdmin.Plugins.Stats.Helpers;
using IW4MAdmin.Plugins.Stats.Models;
using SharedLibraryCore.Database;
using Microsoft.EntityFrameworkCore;
namespace IW4MAdmin.Plugins.Stats
{
@ -122,8 +124,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);
@ -170,8 +175,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;
@ -231,7 +242,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()
@ -245,19 +257,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;
@ -274,16 +291,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)

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

@ -53,13 +53,15 @@ namespace Tests
Assert.False(client == null, "no client found to warn");
var warnEvent = client.Warn("test warn", new Player() { ClientId = 1, Level = Player.Permission.Console, CurrentServer = client.CurrentServer });
warnEvent.OnProcessed.Wait(TestTimeout);
warnEvent.OnProcessed.Wait();
Assert.True(client.Warnings == 1 ||
warnEvent.Failed, "warning did not get applied");
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 Player() { ClientId = 1, Level = Player.Permission.Banned, CurrentServer = client.CurrentServer });
warnEvent.OnProcessed.Wait(TestTimeout);
warnEvent.OnProcessed.Wait();
Assert.True(warnEvent.FailReason == GameEvent.EventFailReason.Permission &&
client.Warnings == 1, "warning was applied without proper permissions");
@ -86,8 +88,17 @@ namespace Tests
var client = Manager.Servers.First().GetPlayersAsList().FirstOrDefault();
Assert.False(client == null, "no client found to report");
// fail
var player = new Player() { ClientId = 1, Level = Player.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
var reportEvent = client.Report("test report", new Player() { ClientId = 1, Level = Player.Permission.Console, CurrentServer = client.CurrentServer });
reportEvent = client.Report("test report", new Player() { ClientId = 1, Level = Player.Permission.Console, CurrentServer = client.CurrentServer });
reportEvent.OnProcessed.Wait(TestTimeout);
Assert.True(!reportEvent.Failed &&

View File

@ -18,7 +18,9 @@ namespace Tests
{
File.WriteAllText("test_mp.log", "test_log_file");
Manager = Program.ServerManager;
IW4MAdmin.Application.Localization.Configure.Initialize("en-US");
Manager = ApplicationManager.GetInstance();
var config = new ApplicationConfiguration
{
@ -43,6 +45,7 @@ namespace Tests
Manager.ConfigHandler.Set(config);
Manager.Init().Wait();
Task.Run(() => Manager.Start());
}

View File

@ -188,6 +188,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

@ -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.

View File

@ -8,6 +8,11 @@ using SharedLibraryCore.Configuration;
using SharedLibraryCore.Services;
using SharedLibraryCore.Database.Models;
using System.Linq;
using SharedLibraryCore.Database;
using Microsoft.EntityFrameworkCore;
using System.Net;
using Newtonsoft.Json.Linq;
using System.Text.RegularExpressions;
namespace IW4MAdmin.Plugins.Welcome
{
@ -82,37 +87,66 @@ namespace IW4MAdmin.Plugins.Welcome
{
Player newPlayer = E.Origin;
if (newPlayer.Level >= Player.Permission.Trusted && !E.Origin.Masked)
E.Owner.Broadcast(ProcessAnnouncement(Config.Configuration().PrivilegedAnnouncementMessage, newPlayer));
E.Owner.Broadcast(await ProcessAnnouncement(Config.Configuration().PrivilegedAnnouncementMessage, newPlayer));
newPlayer.Tell(ProcessAnnouncement(Config.Configuration().UserWelcomeMessage, newPlayer));
newPlayer.Tell(await ProcessAnnouncement(Config.Configuration().UserWelcomeMessage, newPlayer));
if (newPlayer.Level == Player.Permission.Flagged)
{
var penalty = await new GenericRepository<EFPenalty>().FindAsync(p => p.OffenderId == newPlayer.ClientId && p.Type == Penalty.PenaltyType.Flag);
E.Owner.ToAdmins($"^1NOTICE: ^7Flagged player ^5{newPlayer.Name} ^7({penalty.FirstOrDefault()?.Offense}) has joined!");
string penaltyReason;
using (var ctx = new DatabaseContext(disableTracking: true))
{
penaltyReason = await ctx.Penalties
.Where(p => p.OffenderId == newPlayer.ClientId && p.Type == Penalty.PenaltyType.Flag)
.OrderByDescending(p => p.When)
.Select(p => p.AutomatedOffense ?? p.Offense)
.FirstOrDefaultAsync();
}
E.Owner.ToAdmins($"^1NOTICE: ^7Flagged player ^5{newPlayer.Name} ^7({penaltyReason}) has joined!");
}
else
E.Owner.Broadcast(ProcessAnnouncement(Config.Configuration().UserAnnouncementMessage, newPlayer));
E.Owner.Broadcast(await ProcessAnnouncement(Config.Configuration().UserAnnouncementMessage, newPlayer));
}
}
private string ProcessAnnouncement(string msg, Player joining)
private async Task<string> ProcessAnnouncement(string msg, Player joining)
{
msg = msg.Replace("{{ClientName}}", joining.Name);
msg = msg.Replace("{{ClientLevel}}", Utilities.ConvertLevelToColor(joining.Level, joining.ClientPermission.Name));
try
// this prevents it from trying to evaluate it every message
if (msg.Contains("{{ClientLocation}}"))
{
CountryLookupProj.CountryLookup CLT = new CountryLookupProj.CountryLookup($"{Utilities.OperatingDirectory}Plugins{System.IO.Path.DirectorySeparatorChar}GeoIP.dat");
msg = msg.Replace("{{ClientLocation}}", CLT.LookupCountryName(joining.IPAddressString));
}
catch (Exception)
{
joining.CurrentServer.Manager.GetLogger().WriteError("Could not open file Plugins\\GeoIP.dat for Welcome Plugin");
msg = msg.Replace("{{ClientLocation}}", await GetCountryName(joining.IPAddressString));
}
msg = msg.Replace("{{TimesConnected}}", TimesConnected(joining));
return msg;
}
/// <summary>
/// makes a webrequest to determine IP origin
/// </summary>
/// <param name="ip">IP address to get location of</param>
/// <returns></returns>
private async Task<string> GetCountryName(string ip)
{
using (var wc = new WebClient())
{
try
{
string response = await wc.DownloadStringTaskAsync(new Uri($"http://extreme-ip-lookup.com/json/{ip}"));
var responseObj = JObject.Parse(response);
return responseObj["country"].ToString();
}
catch
{
return "a third world country";
}
}
}
}
}

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.Welcome</PackageId>
@ -19,17 +20,11 @@
</ItemGroup>
<ItemGroup>
<None Update="MaxMind\GeoIP.dat">
<CopyToOutputDirectory>Never</CopyToOutputDirectory>
</None>
</ItemGroup>
<ItemGroup>
<PackageReference Update="Microsoft.NETCore.App" />
<PackageReference Update="Microsoft.NETCore.App" Version="2.1.5" />
</ItemGroup>
<Target Name="PostBuild" AfterTargets="PostBuildEvent">
<Exec Command="copy &quot;$(TargetPath)&quot; &quot;$(SolutionDir)BUILD\Plugins&quot;&#xD;&#xA;copy &quot;$(ProjectDir)MaxMind\GeoIP.dat&quot; &quot;$(SolutionDir)BUILD\Plugins\GeoIP.dat&quot;" />
<Exec Command="copy &quot;$(TargetPath)&quot; &quot;$(SolutionDir)BUILD\Plugins&quot;" />
</Target>
</Project>

8
RunPublishRelease.cmd Normal file
View File

@ -0,0 +1,8 @@
dotnet publish WebfrontCore/WebfrontCore.csproj -c Release -o C:\Projects\IW4M-Admin\Publish\Windows
dotnet publish Application/Application.csproj -c Release -o C:\Projects\IW4M-Admin\Publish\Windows
dotnet publish GameLogServer/GameLogServer.pyproj -c Release -o C:\Projects\IW4M-Admin\Publish\Windows\GameLogServer
call "C:\Program Files (x86)\Microsoft Visual Studio\2017\Enterprise\Common7\Tools\VsDevCmd.bat"
msbuild GameLogServer/GameLogServer.pyproj /p:PublishProfile=Stable /p:DeployOnBuild=true /p:PublishProfileRootFolder=C:\Projects\IW4M-Admin\GameLogServer\
msbuild DiscordWebhook/DiscordWebhook.pyproj /p:PublishProfile=Stable /p:DeployOnBuild=true /p:PublishProfileRootFolder=C:\Projects\IW4M-Admin\DiscordWebhook\
cd "C:\Projects\IW4M-Admin\DEPLOY\"
PowerShell ".\upload_release.ps1"

View File

@ -773,12 +773,12 @@ namespace SharedLibraryCore.Commands
else if (unflagEvent.FailReason == GameEvent.EventFailReason.Invalid)
{
E.Origin.Tell($"{Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_FLAG_UNFLAG"]} ^5{E.Target.Name}");
E.Origin.Tell(Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_UNFLAG_NOTFLAGGED"]);
}
else
{
E.Origin.Tell(Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_UNFLAG_NOTFLAGGED"]);
E.Origin.Tell($"{Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_FLAG_UNFLAG"]} ^5{E.Target.Name}");
}
return Task.CompletedTask;
@ -825,6 +825,11 @@ namespace SharedLibraryCore.Commands
commandEvent.Origin.Tell(Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_REPORT_FAIL_SELF"]);
}
else if (reportEvent.FailReason == GameEvent.EventFailReason.Throttle)
{
commandEvent.Origin.Tell(Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_REPORT_FAIL_TOOMANY"]);
}
else if (reportEvent.Failed)
{
commandEvent.Origin.Tell(Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_REPORT_FAIL_DUPLICATE"]);
@ -1179,6 +1184,8 @@ namespace SharedLibraryCore.Commands
}
}
}
return;
}
}
@ -1324,6 +1331,7 @@ namespace SharedLibraryCore.Commands
var nextMapMatch = currentMap.First().Index != lastMap.Index ?
regexMatches[regexMatches.IndexOf(currentMap.First()) + 1] :
regexMatches.First();
nextMap = s.Maps.FirstOrDefault(m => m.Name == nextMapMatch.Groups[3].ToString()) ?? nextMap;
string nextGametype = nextMapMatch.Groups[2].ToString().Length == 0 ?
Utilities.GetLocalizedGametype(s.Gametype) :

View File

@ -24,7 +24,6 @@ namespace SharedLibraryCore.Database
public DbSet<EFMeta> EFMeta { get; set; }
public DbSet<EFChangeHistory> EFChangeHistory { get; set; }
static string _ConnectionString;
static string _provider;
@ -52,21 +51,17 @@ namespace SharedLibraryCore.Database
{
if (string.IsNullOrEmpty(_ConnectionString))
{
string currentPath = Path.GetDirectoryName(Assembly.GetExecutingAssembly().GetName().CodeBase);
string currentPath = Utilities.OperatingDirectory;
// allows the application to find the database file
currentPath = !RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ?
$"{Path.DirectorySeparatorChar}{currentPath}" :
currentPath;
var connectionStringBuilder = new SqliteConnectionStringBuilder { DataSource = $"{currentPath}{Path.DirectorySeparatorChar}Database.db".Substring(6) };
var connectionStringBuilder = new SqliteConnectionStringBuilder { DataSource = $"{currentPath}{Path.DirectorySeparatorChar}Database{Path.DirectorySeparatorChar}Database.db" };
var connectionString = connectionStringBuilder.ToString();
var connection = new SqliteConnection(connectionString);
//#if DEBUG == true
//optionsBuilder.UseMySql("UserId=root;Password=dev;Host=127.0.0.1;port=3306;Database=IW4MAdmin");
// optionsBuilder.UseNpgsql("UserId=dev;Password=dev;Host=127.0.0.1;port=5432;Database=IW4MAdmin");
//#else
optionsBuilder.UseSqlite(connection);
//#endif
}
else
@ -130,15 +125,15 @@ namespace SharedLibraryCore.Database
// https://aleemkhan.wordpress.com/2013/02/28/dynamically-adding-dbset-properties-in-dbcontext-for-entity-framework-code-first/
IEnumerable<string> directoryFiles;
string pluginDir = $@"{Environment.CurrentDirectory}{Path.DirectorySeparatorChar}bin{Path.DirectorySeparatorChar}Debug{Path.DirectorySeparatorChar}netcoreapp2.0{Path.DirectorySeparatorChar}Plugins";
string pluginDir = $@"{Environment.CurrentDirectory}{Path.DirectorySeparatorChar}bin{Path.DirectorySeparatorChar}Debug{Path.DirectorySeparatorChar}netcoreapp2.1{Path.DirectorySeparatorChar}Plugins";
if (!Directory.Exists(pluginDir))
{
pluginDir = $@"{Environment.CurrentDirectory}{Path.DirectorySeparatorChar}Plugins";
pluginDir = Path.Join(Environment.CurrentDirectory, "Plugins");
if (!Directory.Exists(pluginDir))
{
pluginDir = Utilities.OperatingDirectory;
pluginDir = Path.Join(Utilities.OperatingDirectory, "Plugins");
}
}

View File

@ -26,7 +26,11 @@ namespace SharedLibraryCore
/// <summary>
/// executing the event would cause an invalid state
/// </summary>
Invalid
Invalid,
/// <summary>
/// client is doing too much of something
/// </summary>
Throttle
}
public enum EventType
@ -216,7 +220,8 @@ namespace SharedLibraryCore
{
return (queuedEvent.Target != null && queuedEvent.Target.ClientNumber != -1) &&
(queuedEvent.Target.State != Player.ClientState.Connected &&
queuedEvent.Target.NetworkId != 0);
queuedEvent.Target.NetworkId != 0 &&
queuedEvent.Origin?.ClientId != 1);
}
}
}

View File

@ -1,107 +0,0 @@
using System;
using System.Collections.Generic;
using System.Text;
using System.IO;
using System.Net;
using System.Net.Http;
using System.Threading.Tasks;
using System.Linq;
namespace SharedLibraryCore
{
public class RemoteFile : IFile
{
readonly string Location;
string[] FileCache = new string[0];
public RemoteFile(string location) : base(string.Empty)
{
Location = location;
}
private void Retrieve()
{
using (var cl = new HttpClient())
FileCache = cl.GetStringAsync(Location).Result.Split(Environment.NewLine.ToCharArray(), StringSplitOptions.RemoveEmptyEntries);
}
public override long Length()
{
Retrieve();
return FileCache.Sum(l => l.Length);
}
public override Task<string[]> Tail(int lineCount)
{
return Task.FromResult(FileCache);
}
}
public class IFile
{
public IFile(String fileName)
{
if (fileName != string.Empty)
{
Name = fileName;
Handle = new StreamReader(new FileStream(fileName, FileMode.Open, FileAccess.Read, FileShare.ReadWrite, 4096, true), Utilities.EncodingType);
sze = Handle.BaseStream.Length;
}
}
public virtual long Length()
{
sze = Handle.BaseStream.Length;
return sze;
}
public void Close()
{
Handle?.Close();
}
public String[] ReadAllLines()
{
return Handle?.ReadToEnd().Split(new char[] { '\n' }, StringSplitOptions.RemoveEmptyEntries);
}
public String GetText()
{
return Handle?.ReadToEnd();
}
public virtual async Task<String[]> Tail(int lineCount)
{
var buffer = new List<string>(lineCount);
string line;
for (int i = 0; i < lineCount; i++)
{
line = await Handle.ReadLineAsync();
if (line == null) return buffer.ToArray();
buffer.Add(line);
}
int lastLine = lineCount - 1; //The index of the last line read from the buffer. Everything > this index was read earlier than everything <= this indes
while (null != (line = await Handle.ReadLineAsync()))
{
lastLine++;
if (lastLine == lineCount) lastLine = 0;
buffer[lastLine] = line;
}
if (lastLine == lineCount - 1) return buffer.ToArray();
var retVal = new string[lineCount];
buffer.CopyTo(lastLine + 1, retVal, 0, lineCount - lastLine - 1);
buffer.CopyTo(0, retVal, lineCount - lastLine - 1, lastLine + 1);
return retVal;
}
//END
private long sze;
private readonly String Name;
private StreamReader Handle;
}
}

View File

@ -25,7 +25,7 @@ namespace SharedLibraryCore.Configuration
public void Build()
{
ConfigurationRoot = new ConfigurationBuilder()
.AddJsonFile($"{AppDomain.CurrentDomain.BaseDirectory}{Filename}.json", true)
.AddJsonFile(Path.Join(Utilities.OperatingDirectory, "Configuration", $"{Filename}.json"), true)
.Build();
_configuration = ConfigurationRoot.Get<T>();
@ -37,10 +37,7 @@ namespace SharedLibraryCore.Configuration
public Task Save()
{
var appConfigJSON = JsonConvert.SerializeObject(_configuration, Formatting.Indented);
return Task.Factory.StartNew(() =>
{
File.WriteAllText($"{AppDomain.CurrentDomain.BaseDirectory}{Filename}.json", appConfigJSON);
});
return File.WriteAllTextAsync(Path.Join(Utilities.OperatingDirectory, "Configuration", $"{Filename}.json"), appConfigJSON);
}
public T Configuration() => _configuration;

View File

@ -13,7 +13,7 @@ namespace SharedLibraryCore.Interfaces
Task Init();
void Start();
void Stop();
ILogger GetLogger();
ILogger GetLogger(int serverId);
IList<Server> GetServers();
IList<Command> GetCommands();
IList<Helpers.MessageToken> GetMessageTokens();

View File

@ -81,7 +81,10 @@ namespace SharedLibraryCore.Objects
ConnectionTime = DateTime.UtcNow;
ClientNumber = -1;
DelayedEvents = new Queue<GameEvent>();
_additionalProperties = new Dictionary<string, object>();
_additionalProperties = new Dictionary<string, object>
{
{ "_reportCount", 0 }
};
}
public override string ToString() => $"{Name}::{NetworkId}";
@ -116,19 +119,22 @@ namespace SharedLibraryCore.Objects
{
Type = GameEvent.EventType.Warn,
Message = warnReason,
Data = warnReason,
Origin = sender,
Target = this,
Owner = sender.CurrentServer
};
// enforce level restrictions
if (sender.Level <= this.Level)
if (this.Level > sender.Level)
{
e.FailReason = GameEvent.EventFailReason.Permission;
return e;
}
this.Warnings++;
else
{
this.Warnings++;
}
sender.CurrentServer.Manager.GetEventHandler().AddEvent(e);
return e;
@ -181,6 +187,8 @@ namespace SharedLibraryCore.Objects
Owner = sender.CurrentServer
};
int reportCount = sender.GetAdditionalProperty<int>("_reportCount");
if (this.Level > sender.Level)
{
e.FailReason = GameEvent.EventFailReason.Permission;
@ -191,12 +199,18 @@ namespace SharedLibraryCore.Objects
e.FailReason = GameEvent.EventFailReason.Invalid;
}
else if (reportCount > 2)
{
e.FailReason = GameEvent.EventFailReason.Throttle;
}
else if (CurrentServer.Reports.Count(report => (report.Origin.NetworkId == sender.NetworkId &&
report.Target.NetworkId == this.NetworkId)) > 0)
{
e.FailReason = GameEvent.EventFailReason.Exception;
}
sender.SetAdditionalProperty("_reportCount", reportCount + 1);
sender.CurrentServer.Manager.GetEventHandler().AddEvent(e);
return e;
}
@ -325,7 +339,6 @@ namespace SharedLibraryCore.Objects
if (sender.Level <= this.Level)
{
e.FailReason = GameEvent.EventFailReason.Permission;
return e;
}
sender.CurrentServer.Manager.GetEventHandler().AddEvent(e);
@ -353,7 +366,6 @@ namespace SharedLibraryCore.Objects
if (sender.Level <= this.Level)
{
e.FailReason = GameEvent.EventFailReason.Permission;
return e;
}
sender.CurrentServer.Manager.GetEventHandler().AddEvent(e);
@ -391,7 +403,18 @@ namespace SharedLibraryCore.Objects
[NotMapped]
Dictionary<string, object> _additionalProperties;
public T GetAdditionalProperty<T>(string name) => (T)_additionalProperties[name];
public void SetAdditionalProperty(string name, object value) => _additionalProperties.Add(name, value);
public void SetAdditionalProperty(string name, object value)
{
if (_additionalProperties.ContainsKey(name))
{
_additionalProperties[name] = value;
}
else
{
_additionalProperties.Add(name, value);
}
}
[NotMapped]
public int ClientNumber { get; set; }
[NotMapped]

View File

@ -34,7 +34,7 @@ namespace SharedLibraryCore.Plugins
if (dllFileNames.Length == 0 &&
scriptFileNames.Length == 0)
{
Manager.GetLogger().WriteDebug(Utilities.CurrentLocalization.LocalizationIndex["PLUGIN_IMPORTER_NOTFOUND"]);
Manager.GetLogger(0).WriteDebug(Utilities.CurrentLocalization.LocalizationIndex["PLUGIN_IMPORTER_NOTFOUND"]);
return true;
}
@ -43,7 +43,7 @@ namespace SharedLibraryCore.Plugins
{
var plugin = new ScriptPlugin(fileName);
plugin.Initialize(Manager).Wait();
Manager.GetLogger().WriteDebug($"Loaded script plugin \"{ plugin.Name }\" [{plugin.Version}]");
Manager.GetLogger(0).WriteDebug($"Loaded script plugin \"{ plugin.Name }\" [{plugin.Version}]");
ActivePlugins.Add(plugin);
}
@ -66,7 +66,7 @@ namespace SharedLibraryCore.Plugins
Object commandObject = Activator.CreateInstance(assemblyType);
Command newCommand = (Command)commandObject;
ActiveCommands.Add(newCommand);
Manager.GetLogger().WriteDebug($"{Utilities.CurrentLocalization.LocalizationIndex["PLUGIN_IMPORTER_REGISTERCMD"]} \"{newCommand.Name}\"");
Manager.GetLogger(0).WriteDebug($"{Utilities.CurrentLocalization.LocalizationIndex["PLUGIN_IMPORTER_REGISTERCMD"]} \"{newCommand.Name}\"");
LoadedCommands++;
continue;
}
@ -82,18 +82,18 @@ namespace SharedLibraryCore.Plugins
{
ActivePlugins.Add(newNotify);
PluginAssemblies.Add(Plugin);
Manager.GetLogger().WriteDebug($"Loaded plugin \"{ newNotify.Name }\" [{newNotify.Version}]");
Manager.GetLogger(0).WriteDebug($"Loaded plugin \"{ newNotify.Name }\" [{newNotify.Version}]");
}
}
catch (Exception E)
{
Manager.GetLogger().WriteWarning($"{Utilities.CurrentLocalization.LocalizationIndex["PLUGIN_IMPORTER_ERROR"]} {Plugin.Location} - {E.Message}");
Manager.GetLogger(0).WriteWarning($"{Utilities.CurrentLocalization.LocalizationIndex["PLUGIN_IMPORTER_ERROR"]} {Plugin.Location} - {E.Message}");
}
}
}
}
Manager.GetLogger().WriteInfo($"Loaded {ActivePlugins.Count} plugins and registered {LoadedCommands} commands.");
Manager.GetLogger(0).WriteInfo($"Loaded {ActivePlugins.Count} plugins and registered {LoadedCommands} commands.");
return true;
}
}

View File

@ -95,6 +95,7 @@ namespace SharedLibraryCore.RCon
Ttl = 42,
ExclusiveAddressUse = true,
};
connectionState.OnSentData.Reset();
connectionState.OnReceivedData.Reset();
connectionState.ConnectionAttempts++;
@ -103,22 +104,22 @@ namespace SharedLibraryCore.RCon
#endif
try
{
response = await SendPayloadAsync(payload);
response = await SendPayloadAsync(payload, waitForResponse);
connectionState.OnComplete.Release(1);
connectionState.ConnectionAttempts = 0;
}
catch (Exception ex)
catch/* (Exception ex)*/
{
if (connectionState.ConnectionAttempts < StaticHelpers.AllowedConnectionFails)
{
Log.WriteWarning($"{Utilities.CurrentLocalization.LocalizationIndex["SERVER_ERROR_COMMUNICATION"]} [{this.Endpoint}] ({connectionState.ConnectionAttempts}/{StaticHelpers.AllowedConnectionFails})");
// Log.WriteWarning($"{Utilities.CurrentLocalization.LocalizationIndex["SERVER_ERROR_COMMUNICATION"]} [{this.Endpoint}] ({connectionState.ConnectionAttempts}/{StaticHelpers.AllowedConnectionFails})");
await Task.Delay(StaticHelpers.FloodProtectionInterval);
goto retrySend;
}
connectionState.OnComplete.Release(1);
Log.WriteDebug(ex.GetExceptionInfo());
//Log.WriteDebug(ex.GetExceptionInfo());
throw new NetworkException($"{Utilities.CurrentLocalization.LocalizationIndex["SERVER_ERROR_COMMUNICATION"]} [{this.Endpoint}]");
}
@ -139,7 +140,7 @@ namespace SharedLibraryCore.RCon
return splitResponse;
}
private async Task<byte[]> SendPayloadAsync(byte[] payload)
private async Task<byte[]> SendPayloadAsync(byte[] payload, bool waitForResponse)
{
var connectionState = ActiveQueries[this.Endpoint];
var rconSocket = (Socket)connectionState.SendEventArgs.UserToken;
@ -171,6 +172,11 @@ namespace SharedLibraryCore.RCon
}
}
if (!waitForResponse)
{
return new byte[0];
}
connectionState.ReceiveEventArgs.SetBuffer(connectionState.ReceiveBuffer);
// get our response back

View File

@ -39,7 +39,7 @@ namespace SharedLibraryCore.RCon
/// <summary>
/// timeout in seconds to wait for a socket send or receive before giving up
/// </summary>
public static readonly int SocketTimeout = 1000;
public static readonly int SocketTimeout = 10000;
/// <summary>
/// interval in milliseconds to wait before sending the next RCon request
/// </summary>

View File

@ -42,8 +42,8 @@ namespace SharedLibraryCore
}
catch (Exception ex)
{
Manager.GetLogger().WriteError($"{Utilities.CurrentLocalization.LocalizationIndex["PLUGIN_IMPORTER_ERROR"]} {Name}");
Manager.GetLogger().WriteDebug(ex.Message);
Manager.GetLogger(0).WriteError($"{Utilities.CurrentLocalization.LocalizationIndex["PLUGIN_IMPORTER_ERROR"]} {Name}");
Manager.GetLogger(0).WriteDebug(ex.Message);
}
}
@ -57,7 +57,16 @@ namespace SharedLibraryCore
}
Manager = mgr;
string script = File.ReadAllText(FileName);
string script;
using (var stream = new FileStream(FileName, FileMode.Open, FileAccess.Read, FileShare.ReadWrite))
{
using (var reader = new StreamReader(stream, Encoding.Default))
{
script = await reader.ReadToEndAsync();
}
}
ScriptEngine = new Jint.Engine(cfg =>
cfg.AllowClr(new[]
{
@ -68,7 +77,6 @@ namespace SharedLibraryCore
ScriptEngine.Execute(script);
ScriptEngine.SetValue("_localization", Utilities.CurrentLocalization);
ScriptEngine.SetValue("_IW4MAdminClient", Utilities.IW4MAdminClient);
dynamic pluginObject = ScriptEngine.GetValue("plugin").ToObject();
this.Author = pluginObject.author;
@ -87,6 +95,7 @@ namespace SharedLibraryCore
{
ScriptEngine.SetValue("_gameEvent", E);
ScriptEngine.SetValue("_server", S);
ScriptEngine.SetValue("_IW4MAdminClient", Utilities.IW4MAdminClient(S));
return Task.FromResult(ScriptEngine.Execute("plugin.onEventAsync(_gameEvent, _server)").GetCompletionValue());
}
}

View File

@ -32,7 +32,8 @@ namespace SharedLibraryCore
IP = config.IPAddress;
Port = config.Port;
Manager = mgr;
Logger = Manager.GetLogger();
Logger = Manager.GetLogger(this.GetHashCode());
Logger.WriteInfo(this.ToString());
ServerConfig = config;
RemoteConnection = new RCon.Connection(IP, Port, Password, Logger);
@ -209,7 +210,7 @@ namespace SharedLibraryCore
/// <param name="Origin">The person who banned the target</param>
abstract protected Task Ban(String Reason, Player Target, Player Origin);
abstract public Task Warn(String Reason, Player Target, Player Origin);
abstract protected Task Warn(String Reason, Player Target, Player Origin);
/// <summary>
/// Unban a player by npID / GUID
@ -263,7 +264,7 @@ namespace SharedLibraryCore
public override string ToString()
{
return $"{IP}_{Port}";
return $"{IP}-{Port}";
}
protected async Task<bool> ScriptLoaded()
@ -320,7 +321,6 @@ namespace SharedLibraryCore
protected int ConnectionErrors;
protected List<string> BroadcastMessages;
protected TimeSpan LastMessage;
protected IFile LogFile;
protected DateTime LastPoll;
protected ManualResetEventSlim OnRemoteCommandResponse;

View File

@ -1,154 +0,0 @@
using Microsoft.EntityFrameworkCore;
using SharedLibraryCore.Database;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Threading.Tasks;
namespace SharedLibraryCore.Services
{
// https://stackoverflow.com/questions/43677906/crud-operations-with-entityframework-using-generic-type
public class GenericRepository<TEntity> where TEntity : class
{
private DatabaseContext _context;
private DbSet<TEntity> _dbSet;
private readonly bool ShouldTrack;
public GenericRepository(bool shouldTrack)
{
this.ShouldTrack = shouldTrack;
}
public GenericRepository() { }
protected DbContext Context
{
get
{
if (_context == null)
{
_context = new DatabaseContext(ShouldTrack);
}
return _context;
}
}
protected DbSet<TEntity> DBSet
{
get
{
if (_dbSet == null)
{
_dbSet = this.Context.Set<TEntity>();
}
return _dbSet;
}
}
public virtual async Task<IList<TEntity>> FindAsync(Expression<Func<TEntity, bool>> predicate, Func<IQueryable<TEntity>, IOrderedQueryable<TEntity>> orderExpression = null)
{
return await this.GetQuery(predicate, orderExpression).ToListAsync();
}
public virtual IEnumerable<TEntity> Find(Expression<Func<TEntity, bool>> predicate, Func<IQueryable<TEntity>, IOrderedQueryable<TEntity>> orderExpression = null)
{
return this.GetQuery(predicate, orderExpression).AsEnumerable();
}
public virtual IQueryable<TEntity> GetQuery(Expression<Func<TEntity, bool>> predicate = null, Func<IQueryable<TEntity>, IOrderedQueryable<TEntity>> orderExpression = null)
{
IQueryable<TEntity> qry = this.DBSet;
foreach (var property in this.Context.Model.FindEntityType(typeof(TEntity)).GetNavigations())
qry = qry.Include(property.Name);
if (predicate != null)
qry = qry.Where(predicate);
if (orderExpression != null)
return orderExpression(qry);
return qry;
}
public virtual void Insert<T>(T entity) where T : class
{
DbSet<T> dbSet = this.Context.Set<T>();
dbSet.Add(entity);
}
public virtual TEntity Insert(TEntity entity)
{
return DBSet.Add(entity).Entity;
}
public virtual void Update<T>(T entity) where T : class
{
DbSet<T> dbSet = this.Context.Set<T>();
dbSet.Attach(entity);
this.Context.Entry(entity).State = EntityState.Modified;
}
public virtual void Update(TEntity entity)
{
this.Attach(entity);
this.Context.Entry(entity).State = EntityState.Modified;
}
public virtual void Delete<T>(T entity) where T : class
{
DbSet<T> dbSet = this.Context.Set<T>();
if (this.Context.Entry(entity).State == EntityState.Detached)
dbSet.Attach(entity);
dbSet.Remove(entity);
}
public virtual void Delete(TEntity entity)
{
if (this.Context.Entry(entity).State == EntityState.Detached)
this.Attach(entity);
this.DBSet.Remove(entity);
}
public virtual void Delete<T>(object[] id) where T : class
{
DbSet<T> dbSet = this.Context.Set<T>();
T entity = dbSet.Find(id);
dbSet.Attach(entity);
dbSet.Remove(entity);
}
public virtual void Delete(object id)
{
TEntity entity = this.DBSet.Find(id);
this.Delete(entity);
}
public virtual void Attach(TEntity entity)
{
if (this.Context.Entry(entity).State == EntityState.Detached)
this.DBSet.Attach(entity);
}
public virtual void SaveChanges()
{
this.Context.SaveChanges();
}
public virtual Task SaveChangesAsync()
{
return this.Context.SaveChangesAsync();
}
}
}

View File

@ -24,7 +24,6 @@ namespace SharedLibraryCore.Services
PunisherId = newEntity.Punisher.ClientId,
LinkId = newEntity.Link.AliasLinkId,
Type = newEntity.Type,
Active = true,
Expires = newEntity.Expires,
Offense = newEntity.Offense,
When = newEntity.When,
@ -69,19 +68,7 @@ namespace SharedLibraryCore.Services
public async Task<IList<EFPenalty>> Find(Func<EFPenalty, bool> expression)
{
throw await Task.FromResult(new Exception());
/*
return await Task.FromResult(new List<EFPenalty>());
// fixme: this is so slow!
return await Task.Run(() =>
{
using (var context = new DatabaseContext())
return context.Penalties
.Include(p => p.Offender)
.Include(p => p.Punisher)
.Where(expression)
.Where(p => p.Active)
.ToList();
});*/
}
public Task<EFPenalty> Get(int entityID)
@ -225,8 +212,6 @@ namespace SharedLibraryCore.Services
return list;
}
}
}

View File

@ -3,6 +3,7 @@
<PropertyGroup>
<OutputType>Library</OutputType>
<TargetFramework>netcoreapp2.1</TargetFramework>
<RuntimeFrameworkVersion>2.1.5</RuntimeFrameworkVersion>
<ApplicationIcon />
<StartupObject />
<PackageId>RaidMax.IW4MAdmin.SharedLibraryCore</PackageId>
@ -12,20 +13,11 @@
<Configurations>Debug;Release;Prerelease</Configurations>
</PropertyGroup>
<ItemGroup>
<Compile Remove="Migrations\20180502195240_Update.cs" />
<Compile Remove="Migrations\20180923025324_FixForPostgreSQL.cs" />
<Compile Remove="Migrations\20180923025702_FixForPostgreSQL.cs" />
<Compile Remove="Migrations\20180923030248_FixForPostgreSQL.cs" />
<Compile Remove="Migrations\20180923030426_FixForPostgreSQL.cs" />
<Compile Remove="Migrations\20180923030528_FixForPostgreSQL.cs" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Jint" Version="2.11.58" />
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="2.1.3" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="2.1.3" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="2.1.3" />
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="2.1.4" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="2.1.4" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="2.1.4" />
<PackageReference Include="Microsoft.Extensions.Configuration" Version="2.1.1" />
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="2.1.1" />
<PackageReference Include="Microsoft.Extensions.Localization" Version="2.1.1" />
@ -39,7 +31,7 @@
</ItemGroup>
<ItemGroup>
<PackageReference Update="Microsoft.NETCore.App" />
<PackageReference Update="Microsoft.NETCore.App" Version="2.1.5" />
</ItemGroup>
<Target Name="PreBuild" BeforeTargets="PreBuildEvent">

View File

@ -20,10 +20,20 @@ namespace SharedLibraryCore
{
public static class Utilities
{
public static string OperatingDirectory = Path.GetDirectoryName(Assembly.GetEntryAssembly().Location) + Path.DirectorySeparatorChar;
#if DEBUG == true
public static string OperatingDirectory => $"{Path.GetDirectoryName(Assembly.GetEntryAssembly().Location)}{Path.DirectorySeparatorChar}";
#else
public static string OperatingDirectory => $"{Path.GetDirectoryName(Assembly.GetEntryAssembly().Location)}{Path.DirectorySeparatorChar}..{Path.DirectorySeparatorChar}";
#endif
public static Encoding EncodingType;
public static Localization.Layout CurrentLocalization = new Localization.Layout(new Dictionary<string, string>());
public static Player IW4MAdminClient = new Player() { ClientId = 1, Level = Player.Permission.Console };
public static Player IW4MAdminClient(Server server = null) => new Player()
{
ClientId = 1,
State = Player.ClientState.Connected,
Level = Player.Permission.Console,
CurrentServer = server
};
public static string HttpRequest(string location, string header, string headerValue)
{

View File

@ -23,9 +23,9 @@ namespace WebfrontCore.Controllers
public IActionResult Error()
{
var exceptionFeature = HttpContext.Features.Get<IExceptionHandlerPathFeature>();
Manager.GetLogger().WriteError($"[Webfront] {exceptionFeature.Error.Message}");
Manager.GetLogger().WriteDebug(exceptionFeature.Path);
Manager.GetLogger().WriteDebug(exceptionFeature.Error.StackTrace);
Manager.GetLogger(0).WriteError($"[Webfront] {exceptionFeature.Error.Message}");
Manager.GetLogger(0).WriteDebug(exceptionFeature.Path);
Manager.GetLogger(0).WriteDebug(exceptionFeature.Error.StackTrace);
ViewBag.Description = Localization["WEBFRONT_ERROR_DESC"];
ViewBag.Title = Localization["WEBFRONT_ERROR_TITLE"];

View File

@ -1,5 +1,7 @@
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using SharedLibraryCore;
using SharedLibraryCore.Database;
using SharedLibraryCore.Database.Models;
using SharedLibraryCore.Dtos;
using SharedLibraryCore.Services;
@ -33,8 +35,14 @@ namespace WebfrontCore.Controllers
public async Task<IActionResult> PublicAsync()
{
var penalties = await (new GenericRepository<EFPenalty>())
.FindAsync(p => p.Type == SharedLibraryCore.Objects.Penalty.PenaltyType.Ban && p.Active);
IList<EFPenalty> penalties;
using (var ctx = new DatabaseContext(disableTracking: true))
{
penalties = await ctx.Penalties
.Where(p => p.Type == SharedLibraryCore.Objects.Penalty.PenaltyType.Ban && p.Active)
.ToListAsync();
}
var penaltiesDto = penalties.Select(p => new PenaltyInfo()
{

View File

@ -30,7 +30,7 @@ namespace WebfrontCore
#if DEBUG
.UseContentRoot(Path.GetFullPath(Path.Combine(Directory.GetCurrentDirectory(), @"..\..\..\..\", "WebfrontCore")))
#else
.UseContentRoot(Directory.GetCurrentDirectory())
.UseContentRoot(SharedLibraryCore.Utilities.OperatingDirectory)
#endif
.UseUrls(Manager.GetApplicationSettings().Configuration().WebfrontBindUrl)
.UseKestrel()

View File

@ -20,7 +20,6 @@ namespace WebfrontCore
public Startup(IHostingEnvironment env)
{
var builder = new ConfigurationBuilder()
.SetBasePath(env.ContentRootPath)
.AddEnvironmentVariables();
Configuration = builder.Build();
@ -32,10 +31,10 @@ namespace WebfrontCore
public void ConfigureServices(IServiceCollection services)
{
// Add framework services.
var mvcBulder = services.AddMvc();
var mvcBuilder = services.AddMvc();
foreach (var asm in Program.Manager.GetPluginAssemblies())
mvcBulder.AddApplicationPart(asm);
mvcBuilder.AddApplicationPart(asm);
services.Configure<RazorViewEngineOptions>(o =>
{

View File

@ -2,6 +2,7 @@
<PropertyGroup>
<TargetFramework>netcoreapp2.1</TargetFramework>
<RuntimeFrameworkVersion>2.1.5</RuntimeFrameworkVersion>
<RazorCompileOnBuild>false</RazorCompileOnBuild>
<RazorCompileOnPublish>false</RazorCompileOnPublish>
<PreserveCompilationContext>true</PreserveCompilationContext>
@ -63,7 +64,7 @@
<PackageReference Include="Microsoft.AspNetCore.Mvc.Core" Version="2.1.1" />
<PackageReference Include="Microsoft.AspNetCore.Server.Kestrel" Version="2.1.2" />
<PackageReference Include="Microsoft.AspNetCore.StaticFiles" Version="2.1.1" />
<PackageReference Update="Microsoft.NETCore.App" />
<PackageReference Update="Microsoft.NETCore.App" Version="2.1.5" />
<PackageReference Update="Microsoft.AspNetCore" Version="2.1.2" />
</ItemGroup>