diff --git a/Application/EventParsers/IW4EventParser.cs b/Application/EventParsers/IW4EventParser.cs index e6f3c424e..3225514fe 100644 --- a/Application/EventParsers/IW4EventParser.cs +++ b/Application/EventParsers/IW4EventParser.cs @@ -12,7 +12,7 @@ namespace Application.EventParsers public GameEvent GetEvent(Server server, string logLine) { string[] lineSplit = logLine.Split(';'); - string cleanedEventLine = Regex.Replace(lineSplit[0], @"[0-9]+:[0-9]+\ ", ""); + string cleanedEventLine = Regex.Replace(lineSplit[0], @"[0-9]+:[0-9]+\ ", "").Trim(); if (cleanedEventLine[0] == 'K') { @@ -29,7 +29,7 @@ namespace Application.EventParsers } } - if (lineSplit[0].Substring(lineSplit[0].Length - 3).Trim() == "say") + if (cleanedEventLine == "say" || cleanedEventLine == "sayteam") { return new GameEvent() { @@ -37,7 +37,7 @@ namespace Application.EventParsers Data = lineSplit[4].Replace("\x15", ""), Origin = server.GetPlayersAsList().First(c => c.ClientNumber == Utilities.ClientIdFromString(lineSplit, 2)), Owner = server, - Message = lineSplit[4] + Message = lineSplit[4].Replace("\x15", "") }; } diff --git a/Application/EventParsers/T6MEventParser.cs b/Application/EventParsers/T6MEventParser.cs index 3b42c3043..050cf87ca 100644 --- a/Application/EventParsers/T6MEventParser.cs +++ b/Application/EventParsers/T6MEventParser.cs @@ -39,7 +39,7 @@ namespace Application.EventParsers }; } - if (lineSplit[0] == "say") + if (lineSplit[0] == "say" || lineSplit[0] == "sayteam") { return new GameEvent() { diff --git a/Application/Server.cs b/Application/Server.cs index d029a1172..8283f4cf3 100644 --- a/Application/Server.cs +++ b/Application/Server.cs @@ -648,7 +648,7 @@ namespace IW4MAdmin CustomCallback = await ScriptLoaded(); string mainPath = EventParser.GetGameDir(); #if DEBUG - basepath.Value = @"\\192.168.88.253\Call of Duty Black Ops II"; + basepath.Value = @"D:\"; #endif string logPath = game.Value == string.Empty ? $"{basepath.Value.Replace('\\', Path.DirectorySeparatorChar)}{Path.DirectorySeparatorChar}{mainPath}{Path.DirectorySeparatorChar}{logfile.Value}" : diff --git a/IW4MAdmin.sln b/IW4MAdmin.sln index 28473b105..652f08792 100644 --- a/IW4MAdmin.sln +++ b/IW4MAdmin.sln @@ -12,8 +12,6 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution version.txt = version.txt EndProjectSection EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Tests", "Plugins\Tests\Tests.csproj", "{B8C2A759-8663-4F6F-9BA4-19595F5E12C1}" -EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SharedLibraryCore", "SharedLibraryCore\SharedLibraryCore.csproj", "{AA0541A2-8D51-4AD9-B0AC-3D1F5B162481}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WebfrontCore", "WebfrontCore\WebfrontCore.csproj", "{D59AC1F1-2FB9-4BE7-813E-0CCCC4FE9067}" @@ -30,6 +28,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Login", "Plugins\Login\Logi EndProject Project("{888888A0-9F3D-457C-B088-3A5042F75D52}") = "Master", "Master\Master.pyproj", "{F5051A32-6BD0-4128-ABBA-C202EE15FC5C}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Tests", "Plugins\Tests\Tests.csproj", "{B72DEBFB-9D48-4076-8FF5-1FD72A830845}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -46,26 +46,6 @@ Global Release|x86 = Release|x86 EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution - {B8C2A759-8663-4F6F-9BA4-19595F5E12C1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {B8C2A759-8663-4F6F-9BA4-19595F5E12C1}.Debug|Any CPU.Build.0 = Debug|Any CPU - {B8C2A759-8663-4F6F-9BA4-19595F5E12C1}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU - {B8C2A759-8663-4F6F-9BA4-19595F5E12C1}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU - {B8C2A759-8663-4F6F-9BA4-19595F5E12C1}.Debug|x64.ActiveCfg = Debug|Any CPU - {B8C2A759-8663-4F6F-9BA4-19595F5E12C1}.Debug|x64.Build.0 = Debug|Any CPU - {B8C2A759-8663-4F6F-9BA4-19595F5E12C1}.Debug|x86.ActiveCfg = Debug|x86 - {B8C2A759-8663-4F6F-9BA4-19595F5E12C1}.Debug|x86.Build.0 = Debug|x86 - {B8C2A759-8663-4F6F-9BA4-19595F5E12C1}.Prerelease|Any CPU.ActiveCfg = Release-Stable|Any CPU - {B8C2A759-8663-4F6F-9BA4-19595F5E12C1}.Prerelease|Mixed Platforms.ActiveCfg = Release-Stable|x86 - {B8C2A759-8663-4F6F-9BA4-19595F5E12C1}.Prerelease|x64.ActiveCfg = Release-Stable|Any CPU - {B8C2A759-8663-4F6F-9BA4-19595F5E12C1}.Prerelease|x86.ActiveCfg = Release-Stable|x86 - {B8C2A759-8663-4F6F-9BA4-19595F5E12C1}.Release|Any CPU.ActiveCfg = Release-Stable|Any CPU - {B8C2A759-8663-4F6F-9BA4-19595F5E12C1}.Release|Any CPU.Build.0 = Release-Stable|Any CPU - {B8C2A759-8663-4F6F-9BA4-19595F5E12C1}.Release|Mixed Platforms.ActiveCfg = Release-Stable|x86 - {B8C2A759-8663-4F6F-9BA4-19595F5E12C1}.Release|Mixed Platforms.Build.0 = Release-Stable|x86 - {B8C2A759-8663-4F6F-9BA4-19595F5E12C1}.Release|x64.ActiveCfg = Release-Stable|Any CPU - {B8C2A759-8663-4F6F-9BA4-19595F5E12C1}.Release|x64.Build.0 = Release-Stable|Any CPU - {B8C2A759-8663-4F6F-9BA4-19595F5E12C1}.Release|x86.ActiveCfg = Release-Stable|x86 - {B8C2A759-8663-4F6F-9BA4-19595F5E12C1}.Release|x86.Build.0 = Release-Stable|x86 {AA0541A2-8D51-4AD9-B0AC-3D1F5B162481}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {AA0541A2-8D51-4AD9-B0AC-3D1F5B162481}.Debug|Any CPU.Build.0 = Debug|Any CPU {AA0541A2-8D51-4AD9-B0AC-3D1F5B162481}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU @@ -258,16 +238,38 @@ Global {F5051A32-6BD0-4128-ABBA-C202EE15FC5C}.Release|x64.Build.0 = Release|Any CPU {F5051A32-6BD0-4128-ABBA-C202EE15FC5C}.Release|x86.ActiveCfg = Release|Any CPU {F5051A32-6BD0-4128-ABBA-C202EE15FC5C}.Release|x86.Build.0 = Release|Any CPU + {B72DEBFB-9D48-4076-8FF5-1FD72A830845}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {B72DEBFB-9D48-4076-8FF5-1FD72A830845}.Debug|Any CPU.Build.0 = Debug|Any CPU + {B72DEBFB-9D48-4076-8FF5-1FD72A830845}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU + {B72DEBFB-9D48-4076-8FF5-1FD72A830845}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU + {B72DEBFB-9D48-4076-8FF5-1FD72A830845}.Debug|x64.ActiveCfg = Debug|Any CPU + {B72DEBFB-9D48-4076-8FF5-1FD72A830845}.Debug|x64.Build.0 = Debug|Any CPU + {B72DEBFB-9D48-4076-8FF5-1FD72A830845}.Debug|x86.ActiveCfg = Debug|Any CPU + {B72DEBFB-9D48-4076-8FF5-1FD72A830845}.Debug|x86.Build.0 = Debug|Any CPU + {B72DEBFB-9D48-4076-8FF5-1FD72A830845}.Prerelease|Any CPU.ActiveCfg = Debug|Any CPU + {B72DEBFB-9D48-4076-8FF5-1FD72A830845}.Prerelease|Mixed Platforms.ActiveCfg = Debug|Any CPU + {B72DEBFB-9D48-4076-8FF5-1FD72A830845}.Prerelease|Mixed Platforms.Build.0 = Debug|Any CPU + {B72DEBFB-9D48-4076-8FF5-1FD72A830845}.Prerelease|x64.ActiveCfg = Debug|Any CPU + {B72DEBFB-9D48-4076-8FF5-1FD72A830845}.Prerelease|x64.Build.0 = Debug|Any CPU + {B72DEBFB-9D48-4076-8FF5-1FD72A830845}.Prerelease|x86.ActiveCfg = Debug|Any CPU + {B72DEBFB-9D48-4076-8FF5-1FD72A830845}.Prerelease|x86.Build.0 = Debug|Any CPU + {B72DEBFB-9D48-4076-8FF5-1FD72A830845}.Release|Any CPU.ActiveCfg = Release|Any CPU + {B72DEBFB-9D48-4076-8FF5-1FD72A830845}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU + {B72DEBFB-9D48-4076-8FF5-1FD72A830845}.Release|Mixed Platforms.Build.0 = Release|Any CPU + {B72DEBFB-9D48-4076-8FF5-1FD72A830845}.Release|x64.ActiveCfg = Release|Any CPU + {B72DEBFB-9D48-4076-8FF5-1FD72A830845}.Release|x64.Build.0 = Release|Any CPU + {B72DEBFB-9D48-4076-8FF5-1FD72A830845}.Release|x86.ActiveCfg = Release|Any CPU + {B72DEBFB-9D48-4076-8FF5-1FD72A830845}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE EndGlobalSection GlobalSection(NestedProjects) = preSolution - {B8C2A759-8663-4F6F-9BA4-19595F5E12C1} = {26E8B310-269E-46D4-A612-24601F16065F} {98BE4A81-8AFD-4957-83F7-009D353C6BCB} = {26E8B310-269E-46D4-A612-24601F16065F} {179140D3-97AA-4CB4-8BF6-A0C73CA75701} = {26E8B310-269E-46D4-A612-24601F16065F} {958FF7EC-0226-4E85-A85B-B84EC768197D} = {26E8B310-269E-46D4-A612-24601F16065F} {D9F2ED28-6FA5-40CA-9912-E7A849147AB1} = {26E8B310-269E-46D4-A612-24601F16065F} + {B72DEBFB-9D48-4076-8FF5-1FD72A830845} = {26E8B310-269E-46D4-A612-24601F16065F} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {84F8F8E0-1F73-41E0-BD8D-BB6676E2EE87} diff --git a/Plugins/Login/Login.csproj b/Plugins/Login/Login.csproj index 90f8afe2e..ce7503adb 100644 --- a/Plugins/Login/Login.csproj +++ b/Plugins/Login/Login.csproj @@ -12,6 +12,10 @@ Debug;Release;Prerelease + + TRACE;DEBUG;NETCOREAPP2_0 + + diff --git a/Plugins/Tests/Plugin.cs b/Plugins/Tests/Plugin.cs index bfb9a1526..a6210dd50 100644 --- a/Plugins/Tests/Plugin.cs +++ b/Plugins/Tests/Plugin.cs @@ -1,20 +1,11 @@ #if DEBUG using System; -using System.Collections.Generic; using System.Linq; -using System.Text; using System.Threading.Tasks; -using System.IO; -using SharedLibrary; -using SharedLibrary.Interfaces; -using SharedLibrary.Helpers; -using SharedLibrary.Objects; -using System.Text.RegularExpressions; -using StatsPlugin.Models; -using SharedLibrary.Services; -using SharedLibrary.Database.Models; -using SharedLibrary.Database; +using SharedLibraryCore; +using SharedLibraryCore.Interfaces; +using SharedLibraryCore.Helpers; namespace IW4MAdmin.Plugins { @@ -26,12 +17,9 @@ namespace IW4MAdmin.Plugins public string Author => "RaidMax"; - private DateTime Interval; - - public async Task OnEventAsync(Event E, Server S) + public async Task OnEventAsync(GameEvent E, Server S) { - return; - if (E.Type == Event.GType.Start) + if (E.Type == GameEvent.EventType.Start) { #region PLAYER_HISTORY var rand = new Random(GetHashCode()); @@ -61,361 +49,11 @@ namespace IW4MAdmin.Plugins } } - public async Task OnLoadAsync(IManager manager) - { - // #if DO_IMPORT - var svc = new GenericRepository(); - svc.Insert(new EFServer() - { - Active = true, - Port = 28960, - ServerId = Math.Abs("127.0.0.1:28960".GetHashCode()), - }); - - svc.Insert(new EFServer() - { - Active = true, - Port = 28965, - ServerId = Math.Abs("127.0.0.1:28965".GetHashCode()), - }); - - svc.Insert(new EFServer() - { - Active = true, - Port = 28970, - ServerId = Math.Abs("127.0.0.1:28970".GetHashCode()), - }); - - svc.SaveChanges(); - // #endif - Interval = DateTime.Now; - var clients = new List(); - var oldClients = new Dictionary(); - #region CLIENTS - if (File.Exists("import_clients.csv")) - { - manager.GetLogger().WriteVerbose("Beginning import of existing clients"); - - var lines = File.ReadAllLines("import_clients.csv").Skip(1); - foreach (string line in lines) - { - string[] fields = Regex.Replace(line, "\".*\"", "").Split(','); - fields.All(f => - { - f = f.StripColors().Trim(); - return true; - }); - - if (fields.Length != 11) - { - manager.GetLogger().WriteError("Invalid client import file... aborting import"); - return; - } - - if (fields[1].Substring(0, 5) == "01100" || fields[0] == string.Empty || fields[1] == string.Empty || fields[6] == string.Empty) - continue; - - if (!Regex.Match(fields[6], @"^\d+\.\d+\.\d+.\d+$").Success) - fields[6] = "0"; - - var client = new Player() - { - Name = fields[0], - NetworkId = fields[1].ConvertLong(), - IPAddress = fields[6].ConvertToIP(), - Level = (Player.Permission)Convert.ToInt32(fields[3]), - Connections = Convert.ToInt32(fields[5]), - LastConnection = DateTime.Parse(fields[7]), - }; - - clients.Add(client); - oldClients.Add(Convert.ToInt32(fields[2]), client); - } - clients = clients.Distinct().ToList(); - // #if DO_IMPORT - - /*clients = clients - .GroupBy(c => new { c.Name, c.IPAddress }) - .Select(c => c.FirstOrDefault()) - .ToList();*/ - - //newClients = clients.ToList(); - //newClients.ForEach(c => c.ClientId = 0); - - manager.GetLogger().WriteVerbose($"Read {clients.Count} clients for import"); - - try - { - SharedLibrary.Database.Importer.ImportClients(clients); - } - - catch (Exception e) - { - manager.GetLogger().WriteError("Saving imported clients failed"); - } - // #endif - } -#endregion - // load the entire database lol - var ctx = new DatabaseContext(); - ctx.Configuration.ProxyCreationEnabled = false; - var cls = ctx.Clients.Include("AliasLink.Children").ToList(); //manager.GetClientService().Find(c => c.Active).Result; - ctx.Dispose(); - -#region ALIASES - if (File.Exists("import_aliases.csv")) - { - manager.GetLogger().WriteVerbose("Beginning import of existing aliases"); - - var aliases = new List(); - - var lines = File.ReadAllLines("import_aliases.csv").Skip(1); - foreach (string line in lines) - { - string[] fields = Regex.Replace(line, "\".*\"", "").Split(','); - fields.All(f => - { - f = f.StripColors().Trim(); - return true; - }); - - if (fields.Length != 3) - { - manager.GetLogger().WriteError("Invalid alias import file... aborting import"); - return; - } - try - { - int number = Int32.Parse(fields[0]); - var names = fields[1].Split(';').Where(n => n != String.Empty && n.Length > 2); - - var oldClient = oldClients[number]; - var newClient = cls.FirstOrDefault(c => c.NetworkId == oldClient.NetworkId); - - foreach (string name in names) - { - // this is slow :D - if (newClient.AliasLink.Children.FirstOrDefault(n => n.Name == name) != null) continue; - var alias = new EFAlias() - { - Active = true, - DateAdded = DateTime.UtcNow, - Name = name, - LinkId = newClient.AliasLinkId, - IPAddress = newClient.IPAddress - }; - - aliases.Add(alias); - } - } - catch (KeyNotFoundException) - { - continue; - } - - catch (Exception) - { - manager.GetLogger().WriteVerbose($"Could not import alias with line {line}"); - } - } - - SharedLibrary.Database.Importer.ImportSQLite(aliases); - } -#endregion -#region PENALTIES - if (File.Exists("import_penalties.csv")) - { - var penalties = new List(); - manager.GetLogger().WriteVerbose("Beginning import of existing penalties"); - foreach (string line in File.ReadAllLines("import_penalties.csv").Skip(1)) - { - string comma = Regex.Match(line, "\".*,.*\"").Value.Replace(",", ""); - string[] fields = Regex.Replace(line, "\".*,.*\"", comma).Split(','); - - fields.All(f => - { - f = f.StripColors().Trim(); - return true; - }); - - if (fields.Length != 7) - { - manager.GetLogger().WriteError("Invalid penalty import file... aborting import"); - return; - } - - if (fields[2].Substring(0, 5) == "01100" || fields[2].Contains("0000000")) - continue; - try - { - - var expires = DateTime.Parse(fields[6]); - var when = DateTime.Parse(fields[5]); - - var penaltyType = (Penalty.PenaltyType)Int32.Parse(fields[0]); - if (penaltyType == Penalty.PenaltyType.Ban) - expires = DateTime.MaxValue; - - var penalty = new Penalty() - { - Type = penaltyType, - Expires = expires == DateTime.MinValue ? when : expires, - Punisher = new SharedLibrary.Database.Models.EFClient() { NetworkId = fields[3].ConvertLong() }, - Offender = new SharedLibrary.Database.Models.EFClient() { NetworkId = fields[2].ConvertLong() }, - Offense = fields[1].Replace("\"", "").Trim(), - Active = true, - When = when, - }; - - - penalties.Add(penalty); - } - - catch (Exception e) - { - manager.GetLogger().WriteVerbose($"Could not import penalty with line {line}"); - } - } - //#if DO_IMPORT - SharedLibrary.Database.Importer.ImportPenalties(penalties); - manager.GetLogger().WriteVerbose($"Imported {penalties.Count} penalties"); - //#endif - } -#endregion -#region CHATHISTORY - - if (File.Exists("import_chathistory.csv")) - { - var chatHistory = new List(); - manager.GetLogger().WriteVerbose("Beginning import of existing messages"); - foreach (string line in File.ReadAllLines("import_chathistory.csv").Skip(1)) - { - string comma = Regex.Match(line, "\".*,.*\"").Value.Replace(",", ""); - string[] fields = Regex.Replace(line, "\".*,.*\"", comma).Split(','); - - fields.All(f => - { - f = f.StripColors().Trim(); - return true; - }); - - if (fields.Length != 4) - { - manager.GetLogger().WriteError("Invalid chat history import file... aborting import"); - return; - } - try - { - int cId = Convert.ToInt32(fields[0]); - var linkedClient = oldClients[cId]; - - var newcl = cls.FirstOrDefault(c => c.NetworkId == linkedClient.NetworkId); - if (newcl == null) - newcl = cls.FirstOrDefault(c => c.Name == linkedClient.Name && c.IPAddress == linkedClient.IPAddress); - int newCId = newcl.ClientId; - - var chatMessage = new EFClientMessage() - { - Active = true, - ClientId = newCId, - Message = fields[1], - TimeSent = DateTime.Parse(fields[3]), - ServerId = Math.Abs($"127.0.0.1:{Convert.ToInt32(fields[2]).ToString()}".GetHashCode()) - }; - - chatHistory.Add(chatMessage); - } - - catch (Exception e) - { - manager.GetLogger().WriteVerbose($"Could not import chatmessage with line {line}"); - } - } - manager.GetLogger().WriteVerbose($"Read {chatHistory.Count} messages for import"); - SharedLibrary.Database.Importer.ImportSQLite(chatHistory); - } -#endregion -#region STATS - foreach (string file in Directory.GetFiles(Environment.CurrentDirectory)) - { - if (Regex.Match(file, @"import_stats_[0-9]+.csv").Success) - { - int port = Int32.Parse(Regex.Match(file, "[0-9]{5}").Value); - var stats = new List(); - manager.GetLogger().WriteVerbose("Beginning import of existing client stats"); - - var lines = File.ReadAllLines(file).Skip(1); - foreach (string line in lines) - { - string[] fields = line.Split(','); - - if (fields.Length != 9) - { - manager.GetLogger().WriteError("Invalid client import file... aborting import"); - return; - } - - try - { - if (fields[0].Substring(0, 5) == "01100") - continue; - - long id = fields[0].ConvertLong(); - var client = cls.Single(c => c.NetworkId == id); - - var time = Convert.ToInt32(fields[8]); - double spm = time < 60 ? 0 : Math.Round(Convert.ToInt32(fields[1]) * 100.0 / time, 3); - if (spm > 1000) - spm = 0; - - var st = new EFClientStatistics() - { - Active = true, - ClientId = client.ClientId, - ServerId = Math.Abs($"127.0.0.1:{port}".GetHashCode()), - Kills = Convert.ToInt32(fields[1]), - Deaths = Convert.ToInt32(fields[2]), - SPM = spm, - Skill = 0, - TimePlayed = time * 60 - }; - // client.TotalConnectionTime += time; - stats.Add(st); - stats = stats.AsEnumerable() - .GroupBy(c => new { c.ClientId }) - .Select(c => c.FirstOrDefault()).ToList(); - - var cl = await manager.GetClientService().Get(st.ClientId); - cl.TotalConnectionTime += time * 60; - await manager.GetClientService().Update(cl); - } - catch (Exception e) - { - continue; - } - - } - - - manager.GetLogger().WriteVerbose($"Read {stats.Count} clients stats for import"); - - try - { - SharedLibrary.Database.Importer.ImportSQLite(stats); - } - - catch (Exception e) - { - manager.GetLogger().WriteError("Saving imported stats failed"); - } - } - } -#endregion - } + public Task OnLoadAsync(IManager manager) => Task.CompletedTask; public async Task OnTickAsync(Server S) { - return; + /* if ((DateTime.Now - Interval).TotalSeconds > 1) { var rand = new Random(); @@ -492,13 +130,10 @@ namespace IW4MAdmin.Plugins } } } - + */ } - public async Task OnUnloadAsync() - { - - } + public Task OnUnloadAsync() => Task.CompletedTask; } } #endif \ No newline at end of file diff --git a/Plugins/Tests/Properties/AssemblyInfo.cs b/Plugins/Tests/Properties/AssemblyInfo.cs deleted file mode 100644 index 1d901a309..000000000 --- a/Plugins/Tests/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,36 +0,0 @@ -using System.Reflection; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; - -// General Information about an assembly is controlled through the following -// set of attributes. Change these attribute values to modify the information -// associated with an assembly. -[assembly: AssemblyTitle("Tests")] -[assembly: AssemblyDescription("")] -[assembly: AssemblyConfiguration("")] -[assembly: AssemblyCompany("")] -[assembly: AssemblyProduct("Tests")] -[assembly: AssemblyCopyright("Copyright © 2017")] -[assembly: AssemblyTrademark("")] -[assembly: AssemblyCulture("")] - -// Setting ComVisible to false makes the types in this assembly not visible -// to COM components. If you need to access a type in this assembly from -// COM, set the ComVisible attribute to true on that type. -[assembly: ComVisible(false)] - -// The following GUID is for the ID of the typelib if this project is exposed to COM -[assembly: Guid("b8c2a759-8663-4f6f-9ba4-19595f5e12c1")] - -// Version information for an assembly consists of the following four values: -// -// Major Version -// Minor Version -// Build Number -// Revision -// -// You can specify all the values or you can default the Build and Revision Numbers -// by using the '*' as shown below: -// [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("1.0.0.0")] -[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/Plugins/Tests/Tests.csproj b/Plugins/Tests/Tests.csproj index 3091f3ad7..db6807dd8 100644 --- a/Plugins/Tests/Tests.csproj +++ b/Plugins/Tests/Tests.csproj @@ -1,97 +1,22 @@ - - - + + - Debug - AnyCPU - {B8C2A759-8663-4F6F-9BA4-19595F5E12C1} Library - Properties - Tests - Tests - v4.5 - 512 + netcoreapp2.0 + + - - true - full - false - bin\Debug\ - DEBUG;TRACE - prompt - 4 - - - pdbonly - true - bin\Release\ - TRACE - prompt - 4 - - - true - bin\x86\Debug\ - DEBUG;TRACE - full - x86 - prompt - MinimumRecommendedRules.ruleset - - - bin\x86\Release\ - TRACE - true - pdbonly - x86 - prompt - MinimumRecommendedRules.ruleset - - - bin\Release-Stable\ - TRACE - true - pdbonly - AnyCPU - prompt - MinimumRecommendedRules.ruleset - - - bin\x86\Release-Stable\ - TRACE - true - pdbonly - x86 - prompt - MinimumRecommendedRules.ruleset + + + TRACE;DEBUG;NETCOREAPP2_0 + + + + + - - - - - - - - - + - - - - - - - {d51eeceb-438a-47da-870f-7d7b41bc24d6} - SharedLibrary - - - {4785ab75-66f3-4391-985d-63a5a049a0fa} - StatsPlugin - - - - - if $(ConfigurationName) == Debug (copy /Y "$(TargetDir)$(TargetName).dll" "$(SolutionDir)BUILD\plugins\") - - \ No newline at end of file + + diff --git a/README.md b/README.md index 8aab5216c..22c340e36 100644 --- a/README.md +++ b/README.md @@ -1,111 +1,155 @@ -# IW4MAdmin -### Quick Start Guide -### Version 1.5 + +# IW4MAdmin +### Quick Start Guide +### Version 2.0 _______ +### About +**IW4MAdmin** is an administration tool for [IW4x](https://iw4xcachep26muba.onion.link/), [T6M](https://plutonium.pw/), and most Call of Duty® dedicated servers. It allows complete control of your server; from changing maps, to banning players, **IW4MAdmin** monitors and records activity on your server(s). With plugin support, extending its functionality is a breeze. ### Setup -IW4MAdmin requires minimal configuration to run. There is only one prerequisite. -1. [.NET Framework 4.5](https://www.microsoft.com/en-us/download/details.aspx?id=30653) *or newer* +**IW4MAdmin** requires minimal configuration to run. There is only one prerequisite. +* [.NET Core 2.0 Runtime](https://www.microsoft.com/net/download/dotnet-core/runtime-2.0.5) *or newer* -Extract `IW4MAdmin.zip` -Run `IW4MAdmin.exe` +1. Extract `IW4MAdmin-.zip` +2. Open command prompt or terminal in the extracted folder +3. Run `>dotnet IW4MAdmin.dll` ___ ### Configuration -_If you wish to customize your experience of IW4MAdmin, the following configuration files will allow you to changes core options._ +#### Initial Configuration +When **IW4MAdmin** is launched for the _first time_, you will be prompted to setup your configuration. -`maps.cfg` - * This is the configuration file that links an IW4 map file name to its common/in-game name - * This can be safely modified to add additional SP/DLC maps +`Enable webfront` +* Enables you to monitor and control your server(s) through a web interface [defaults to `http://127.0.0.1:1624`] -`messages.cfg` - * This is the configuration file that broadcasts messages to your server at a set time - * The _first line_ specifies the amount of time between messages (in seconds) - * Every new line is interpreted as a new message - * Color codes are allowed in the messages - * Tokens are denoted by double braces: {{TOKEN}} +`Enable multiple owners` +* Enables more than one client to be promoted to level of `Owner` -`rules.cfg` - * This is the configuration file that sets the server's rules. - * Every new line is interpreted as a new rule - * All rules are _global_ across servers +`Enable stepped privilege hierarchy` +* Allows privileged clients to promote other clients to the level below their current level -`web.cfg` - * This is the configuration file that specifies the web front bindings - * The first line specifies the `IP` or `Hostname` to bind to - * The second line specifies the `port` to bind to +`Enable custom say name` +* Shows a prefix to every message send by **IW4MAdmin** -- `[Admin] message` +* _This feature requires you specify a custom say name_ +`Enable client VPNs` +* Allow clients to use a [VPN](https://en.wikipedia.org/wiki/Virtual_private_network) +* _This feature requires an active api key on [iphub.info](https://iphub.info/)_ + +`Enable discord link` +* Shows a link to your server's discord on the webfront +* _This feature requires an invite link to your discord server_ + +#### Advanced Configuration +If you wish to further customize your experience of **IW4MAdmin**, the following configuration file(s) will allow you to changes core options using any text-editor. + +#### `IW4MAdminSettings.json`-- _this file is created after initial setup_ +* This file uses the [JSON](https://en.wikipedia.org/wiki/JSON#JSON_sample) specification, so please validate it before running **IW4MAdmin** + +`WebfrontBindUrl` +* Specifies the address and port the webfront will listen on. +* The value can be an [IP Address](https://en.wikipedia.org/wiki/IP_address):port or [Domain Name](https://en.wikipedia.org/wiki/Domain_name):port + +`Servers` +* Specifies the list of servers **IW4MAdmin** will monitor +* `IPAddress` + * Specifies the IP Address of the particular server +* `Port` + * Specifies the port of the particular server +* `Password` + * Specifies the `rcon_password` of the particular server +* `AutoMessages` + * Specifies the list of messages that are broadcasted to the particular server +* `Rules` + * Specifies the list of rules that apply to the particular server + +`AutoMessagePeriod` +* Specifies (in seconds) how often messages should be broadcasted to the server(s) + +`AutoMessages` +* Specifies the list of messages that are broadcasted to **all** servers + +`GlobalRules` +* Specifies the list of rules that apply to **all** servers` + +`Maps` +* Specifies the list of maps for each supported game +* `Name` + * Specifies the name of the map as returned by the game +* `Alias` + * Specifies the display name of the map (as seen while loading in) ___ + ### Commands |Name |Alias|Description |Requires Target|Syntax |Required Level| |--------------| -----| --------------------------------------------------------| -----------------| -------------| ----------------| -|disabletrusted|dt|disable trusted player group for the server|False|!dt |Owner| -|enabletrusted|et|enable trusted player group for the server|False|!et |Owner| -|prune|p|demote any admins that have not connected recently (defaults to 30 days)|False|!p \|Owner| +|prune|pa|demote any admins that have not connected recently (defaults to 30 days)|False|!pa \|Owner| |quit|q|quit IW4MAdmin|False|!q |Owner| |rcon|rcon|send rcon command to server|False|!rcon \|Owner| -|reload|rl|reload configuration files|False|!rl |Owner| -|setlevel|sl|set player to specified administration level|True|!sl \ \|Owner| |ban|b|permanently ban a player from the server|True|!b \ \|SeniorAdmin| -|fredisable|frd|disable fast restarting at the end of a map|False|!frd |SeniorAdmin| -|frenable|fre|enable fast restarting at the end of a map|False|!fre |SeniorAdmin| -|unban|ub|unban player by database id|True|!ub \|SeniorAdmin| +|unban|ub|unban player by database id|True|!ub \ \|SeniorAdmin| |find|f|find player in database|False|!f \|Administrator| -|findall|fa|find a player by their aliase(s)|False|!fa \|Administrator| +|killserver|kill|kill the game server|False|!kill |Administrator| |map|m|change to specified map|False|!m \|Administrator| |maprotate|mr|cycle to the next map in rotation|False|!mr |Administrator| -|mask|hide|hide your online presence from online admin list|False|!hide |Administrator| |plugins|p|view all loaded plugins|False|!p |Administrator| |alias|known|get past aliases and ips of a player|True|!known \|Moderator| |baninfo|bi|get information about a ban for a player|True|!bi \|Moderator| |fastrestart|fr|fast restart current map|False|!fr |Moderator| |flag|fp|flag a suspicious player and announce to admins on join|True|!fp \ \|Moderator| |list|l|list active clients|False|!l |Moderator| +|mask|hide|hide your presence as an administrator|False|!hide |Moderator| |reports|reps|get or clear recent reports|False|!reps \|Moderator| |say|s|broadcast message to all players|False|!s \|Moderator| +|setlevel|sl|set player to specified administration level|True|!sl \ \|Moderator| +|setpassword|sp|set your authentication password|False|!sp \|Moderator| |tempban|tb|temporarily ban a player for specified time (defaults to 1 hour)|True|!tb \ \ \|Moderator| |uptime|up|get current application running time|False|!up |Moderator| |usage|us|get current application memory usage|False|!us |Moderator| |kick|k|kick a player by name|True|!k \ \|Trusted| +|login|l|login using password|False|!l \|Trusted| |warn|w|warn player for infringing rules|True|!w \ \|Trusted| |warnclear|wc|remove all warning for a player|True|!wc \|Trusted| |admins|a|list currently connected admins|False|!a |User| |getexternalip|ip|view your external IP address|False|!ip |User| |help|h|list all available commands|False|!h \|User| +|ping|pi|get client's ping|False|!pi \|User| |privatemessage|pm|send message to other player|True|!pm \ \|User| |report|rep|report a player for suspicious behavior|True|!rep \ \|User| |resetstats|rs|reset your stats to factory-new|False|!rs |User| |rules|r|list server rules|False|!r |User| |stats|xlrstats|view your stats|False|!xlrstats \|User| |topstats|ts|view the top 5 players on this server|False|!ts |User| -|vote|v|vote for the next map|False|!v \|User| -|votecancel|vc|cancel your vote for the next map|False|!vc |User| |whoami|who|give information about yourself.|False|!who |User| +_These commands include all shipped plugin commands._ + +--- #### Player Identification All players are identified 4 seperate ways 1. `npID/GUID/XUID` - The ID corresponding to the player's hardware or forum account 2. `IP` - The player's IP Address -3. `Database ID` - The internal reference to a player, generated by IW4MAdmin +3. `Client ID` - The internal reference to a player, generated by **IW4MAdmin** 4. `Name` - The visible player name as it appears in game For most commands players are identified by their `Name` -However, if they are currently offline, or their name contains un-typable characters, their `Database ID` must be used +However, if they are currently offline, or their name contains un-typable characters, their `Client ID` must be used -The `dbID` is specified by prefixing a player's reference number with `@`. -For example, `@123` would reference the player with a `dbID` of 123. -Players can also be referenced by `clientID`, which is simply their slot (0 - 17) +The `Client ID` is specified by prefixing a player's reference number with `@`. +For example, `@123` would reference the player with a `Client ID` of 123. +While in-game, [layers can also be referenced by `Client Number`, which is simply their slot [0 - 17] **All commands that require a `target` look at the `first argument` for a form of player identification** +--- -#### Additional Command Parameters +#### Additional Command Examples `setlevel` - _shortcut_ - `sl` - _Parameter 1_ - Player to modify level of -- _Parameter 2_ - Level to set the player to ```[ User, Trusted, Moderator, Administrator, SeniorAdmin ]``` +- _Parameter 2_ - Level to set the player to ```[ User, Trusted, Moderator, Administrator, SeniorAdmin, Owner ]``` - _Example_ - `!setlevel Player1 SeniorAdmin`, `!sl @123 Moderator` -- **NOTE** - It has been purposefully designed that there should only be **1 Owner** ( owner cannot set another player's level to owner unless the configuration option is enabled during setup) +- **NOTE** - An `owner` cannot set another player's level to `owner` unless the configuration option is enabled during setup `ban` - _Shortcut_ - `b` @@ -126,58 +170,57 @@ Players can also be referenced by `clientID`, which is simply their slot (0 - 17 ___ ### Plugins -#### EventAPI -- This plugin adds a page to the webfront that serves JSON content in the form of server events -- The page is located at 127.0.0.1/api/events -- JSON Object Structure - * **eventCount** - Number of events in the generated response ( 0 or 1 ) - * **Event** - The event object corresponding to generated event ( will be null if eventCount = 0 ) - * _Version_ - The supported version of the Event Object ( IW4MAdmin = 0 ) - * _Type_ - The type of Event Object ( Notification = 0, Status = 1, Alert = 2 ) - * _Message_ - The string contents of the Event Object ( ie chat message text ) - * _Title_ - The string header/title of the Event Object ( optional ) - * _Origin_ - The string origin of the Event Object ( ie player name or sv_hostname ) - * _Target_ - The string target of the Event Object ( ie reported player's name ) - * _ID_ - The int ID of the Event Object ( will be unique unless two events are generated simultaneously ) -- Optional Parameters - * appending a `GET` parameter of `status=1` to your request will generate a list of currently monitored servers - * For example: 127.0.0.1/api/events?status=1 - * The contents of the response will be in the `Message` property of the response -- Each event will be consumed ( eaten by the request, so save the event if you need to use it later ) -- The plugin additionally scans chat messages for phrases that indicate a cheater on the server -- If enough matching phrases are detected, an alert will be generated -- No commands are added by this plugin -- Additional Features will be added in the future + #### Welcome -- This plugin uses geolocation data to welcome a player based on their IP's country -- All privileged users ( Trusted or higher ) recieve a specialized welcome message as well +- This plugin uses geo-location data to welcome a player based on their country of origin +- All privileged users ( Trusted or higher ) receive a specialized welcome message as well +- Welcome messages can be customized in `WelcomePluginSettings.json` #### Stats -- This plugin calculates basic player performance, skill approximation, and kill/death ratio -- Total play-time is stored by this plugin -- After 3 days ( 36 hours ) of total play-time, a user earns the `Trusted` rank ( will be optional in a later release ) +- This plugin calculates basic player performance, skill approximation, and kill/death ratio **Commands added by this plugin** |Name |Alias|Description |Requires Target|Syntax |Required Level| |--------------| -----| --------------------------------------------------------| -----------------| -------------| ----------------| -|disabletrusted|dt|disable trusted player group for the server|False|!dt |Owner| -|enabletrusted|et|enable trusted player group for the server|False|!et |Owner| -|prune|p|demote any admins that have not connected recently (defaults to 30 days)|False|!p \|Owner| |resetstats|rs|reset your stats to factory-new|False|!rs |User| |stats|xlrstats|view your stats|False|!xlrstats \|User| |topstats|ts|view the top 5 players on this server|False|!ts |User| -- To qualify for top stats, a player must meet the following criteria - * `Skill` > 10 - * `Kills` > 150 - * `Play Time` > 1 hour +- To qualify for top stats, a client must have played for at least `1 hour` and connected within the past `30 days`. -- Each server has seperated stats and can be reset by deleting `stats_.rm` +#### Login +- This plugin deters GUID spoofing by requiring privileged users to login with their password before executing commands +- A password must be set using the `setpassword` command before logging in + **Commands added by this plugin** +|Name |Alias|Description |Requires Target|Syntax |Required Level| +|--------------| -----| --------------------------------------------------------| -----------------| -------------| ----------------| +|login|l|login using password|False|!l \|Trusted| + +#### Profanity Determent +- This plugin warns and kicks players for using profanity +- Profane words and warning message can be specified in `ProfanityDetermentSettings.json` ___ +### Webfront +`Home` +* Shows an overview of the monitored server(s) + +`Penalties` +* Shows a chronological ordered list of client penalties (scrolling down loads older penalties) + +`Admins` +* Shows a list of privileged clients + +`Login` +* Allows privileged users to login using their `Client ID` and password set via `setpassword` + +`Profile` +* Shows a client's information and history + +--- + ### Misc #### Database Storage -All unique client information is stored in `clients.rm`. Should you need to reset your database, this file can simply be deleted. -Player aliases and previous ips are stored in `aliases.rm`. +All **IW4MAdmin** information is stored in `Database.db`. Should you need to reset your database, this file can simply be deleted. Additionally, this file should be preserved during updates to retain client information. \ No newline at end of file diff --git a/SharedLibraryCore/File.cs b/SharedLibraryCore/File.cs index 4136a7813..ba275e89d 100644 --- a/SharedLibraryCore/File.cs +++ b/SharedLibraryCore/File.cs @@ -39,7 +39,7 @@ namespace SharedLibraryCore if (fileName != string.Empty) { Name = fileName; - Handle = new StreamReader(new FileStream(fileName, FileMode.Open, FileAccess.Read, FileShare.ReadWrite, 4096, true), Encoding.UTF7); + Handle = new StreamReader(new FileStream(fileName, FileMode.Open, FileAccess.Read, FileShare.ReadWrite, 4096, true), Encoding.UTF8); sze = Handle.BaseStream.Length; } diff --git a/SharedLibraryCore/Utilities.cs b/SharedLibraryCore/Utilities.cs index a2993b0ce..fed971eb0 100644 --- a/SharedLibraryCore/Utilities.cs +++ b/SharedLibraryCore/Utilities.cs @@ -267,6 +267,11 @@ namespace SharedLibraryCore return Game.UKN; } + public static string EscapeMarkdown(this string markdownString) + { + return markdownString.Replace("<", "\\<").Replace(">", "\\>").Replace("|", "\\|"); + } + public static TimeSpan ParseTimespan(this string input) { var expressionMatch = Regex.Match(input, @"[0-9]+.\b");