1
0
mirror of https://github.com/RaidMax/IW4M-Admin.git synced 2025-06-10 07:13:58 -05:00

migrating to .NET Core 2.0

This commit is contained in:
RaidMax
2018-04-08 01:44:42 -05:00
parent 9eaed1d07d
commit e8b6525fea
121 changed files with 534 additions and 1157 deletions

View File

@ -0,0 +1,39 @@
using System;
using System.Linq;
using System.Threading.Tasks;
using SharedLibraryCore.Objects;
namespace SharedLibraryCore
{
public class CommandArgument
{
public string Name { get; set; }
public bool Required { get; set; }
}
public abstract class Command
{
public Command(String commandName, String commandDescription, String commandAlias, Player.Permission requiredPermission, bool requiresTarget, CommandArgument[] param = null)
{
Name = commandName;
Description = commandDescription;
Alias = commandAlias;
Permission = requiredPermission;
RequiresTarget = requiresTarget;
Arguments = param ?? new CommandArgument[0];
}
//Execute the command
abstract public Task ExecuteAsync(Event E);
public String Name { get; private set; }
public String Description { get; private set; }
public String Syntax => $"syntax: !{Alias} {String.Join(" ", Arguments.Select(a => $"<{(a.Required ? "" : "optional ")}{a.Name}>"))}";
public String Alias { get; private set; }
public int RequiredArgumentCount => Arguments.Count(c => c.Required);
public bool RequiresTarget { get; private set; }
public Player.Permission Permission { get; private set; }
public CommandArgument[] Arguments { get; private set; }
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,48 @@
using SharedLibraryCore.Interfaces;
using System;
using System.Collections.Generic;
using System.Linq;
namespace SharedLibraryCore.Configuration
{
public class ApplicationConfiguration : IBaseConfiguration
{
public bool EnableMultipleOwners { get; set; }
public bool EnableSteppedHierarchy { get; set; }
public bool EnableClientVPNs { get; set; }
public bool EnableDiscordLink { get; set; }
public bool EnableCustomSayName { get; set; }
public string CustomSayName { get; set; }
public string DiscordInviteCode { get; set; }
public string IPHubAPIKey { get; set; }
public List<ServerConfiguration> Servers { get; set; }
public int AutoMessagePeriod { get; set; }
public List<string> AutoMessages { get; set; }
public List<string> GlobalRules { get; set; }
public List<MapConfiguration> Maps { get; set; }
public IBaseConfiguration Generate()
{
EnableMultipleOwners = Utilities.PromptBool("Enable multiple owners");
EnableSteppedHierarchy = Utilities.PromptBool("Enable stepped privilege hierarchy");
EnableCustomSayName = Utilities.PromptBool("Enable custom say name");
if (EnableCustomSayName)
CustomSayName = Utilities.PromptString("Enter custom say name");
EnableClientVPNs = Utilities.PromptBool("Enable client VPNS");
if (!EnableClientVPNs)
IPHubAPIKey = Utilities.PromptString("Enter iphub.info api key");
EnableDiscordLink = Utilities.PromptBool("Display discord link on webfront");
if (EnableDiscordLink)
DiscordInviteCode = Utilities.PromptString("Enter discord invite link");
return this;
}
public string Name() => "ApplicationConfiguration";
}
}

View File

@ -0,0 +1,16 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using static SharedLibraryCore.Server;
namespace SharedLibraryCore.Configuration
{
public class MapConfiguration
{
public Game Game { get; set; }
public List<Map> Maps { get; set; }
}
}

View File

@ -0,0 +1,13 @@
using System.Collections.Generic;
namespace SharedLibraryCore.Configuration
{
public class ServerConfiguration
{
public string IPAddress { get; set; }
public short Port { get; set; }
public string Password { get; set; }
public List<string> Rules { get; set; }
public List<string> AutoMessages { get; set; }
}
}

View File

@ -0,0 +1,57 @@
using Microsoft.EntityFrameworkCore;
using SharedLibraryCore.Database.Models;
using System;
using System.Linq;
using System.Threading.Tasks;
namespace SharedLibraryCore.Database
{
public class ContextSeed
{
private DatabaseContext context;
public ContextSeed(DatabaseContext ctx)
{
context = ctx;
}
public async Task Seed()
{
if (context.AliasLinks.Count() == 0)
{
context.AliasLinks.Add(new EFAliasLink()
{
AliasLinkId = 1
});
var currentAlias = new EFAlias()
{
AliasId = 1,
Active = true,
DateAdded = DateTime.UtcNow,
IPAddress = 0,
Name = "IW4MAdmin",
LinkId = 1
};
context.Aliases.Add(currentAlias);
context.Clients.Add(new EFClient()
{
ClientId = 1,
Active = false,
Connections = 0,
FirstConnection = DateTime.UtcNow,
LastConnection = DateTime.UtcNow,
Level = Objects.Player.Permission.Console,
Masked = true,
NetworkId = 0,
AliasLinkId = 1,
CurrentAliasId = 1,
});
await context.SaveChangesAsync();
}
}
}
}

View File

@ -0,0 +1,109 @@
using System;
using Microsoft.EntityFrameworkCore;
using SharedLibraryCore.Database.Models;
using System.Reflection;
using System.IO;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Data.Sqlite;
namespace SharedLibraryCore.Database
{
public class DatabaseContext : DbContext
{
public DbSet<EFClient> Clients { get; set; }
public DbSet<EFAlias> Aliases { get; set; }
public DbSet<EFAliasLink> AliasLinks { get; set; }
public DbSet<EFPenalty> Penalties { get; set; }
public DatabaseContext(DbContextOptions<DatabaseContext> opt) : base(opt) { }
public DatabaseContext()
{
}
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
string currentPath = Path.GetDirectoryName(Assembly.GetExecutingAssembly().GetName().CodeBase);
var connectionStringBuilder = new SqliteConnectionStringBuilder { DataSource = $"{currentPath}{Path.DirectorySeparatorChar}Database.db".Substring(6) };
var connectionString = connectionStringBuilder.ToString();
var connection = new SqliteConnection(connectionString);
optionsBuilder.UseSqlite(connection);
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
// make network id unique
modelBuilder.Entity<EFClient>(entity =>
{
entity.HasIndex(e => e.NetworkId).IsUnique();
});
modelBuilder.Entity<EFPenalty>(entity =>
{
entity.HasOne(p => p.Offender)
.WithMany(c => c.ReceivedPenalties)
.HasForeignKey(c => c.OffenderId)
.OnDelete(DeleteBehavior.Restrict);
entity.HasOne(p => p.Punisher)
.WithMany(p => p.AdministeredPenalties)
.HasForeignKey(c => c.PunisherId)
.OnDelete(DeleteBehavior.Restrict);
});
modelBuilder.Entity<EFAliasLink>(entity =>
{
entity.HasMany(e => e.Children)
.WithOne(a => a.Link)
.HasForeignKey(k => k.LinkId)
.OnDelete(DeleteBehavior.Restrict);
});
// https://aleemkhan.wordpress.com/2013/02/28/dynamically-adding-dbset-properties-in-dbcontext-for-entity-framework-code-first/
#if !DEBUG
foreach (string dllPath in Directory.GetFiles($"{Utilities.OperatingDirectory}Plugins"))
#else
IEnumerable<string> directoryFiles;
try
{
directoryFiles = Directory.GetFiles($@"{Environment.CurrentDirectory}{Path.DirectorySeparatorChar}bin{Path.DirectorySeparatorChar}Debug{Path.DirectorySeparatorChar}netcoreapp2.0{Path.DirectorySeparatorChar}Plugins").Where(f => f.Contains(".dll"));
}
catch (Exception)
{
directoryFiles = Directory.GetFiles($@"{Environment.CurrentDirectory}{Path.DirectorySeparatorChar}Plugins").Where(f => f.Contains(".dll"));
}
foreach (string dllPath in directoryFiles)
#endif
{
Assembly library;
try
{
library = Assembly.LoadFile(dllPath);
}
// not a valid assembly, ie plugin files
catch (Exception)
{
continue;
}
foreach (var type in library.ExportedTypes)
{
if (type.IsClass && type.IsSubclassOf(typeof(SharedEntity)))
{
var method = modelBuilder.GetType().GetMethod("Entity");
method = method.MakeGenericMethod(new Type[] { type });
method.Invoke(modelBuilder, null);
}
}
}
base.OnModelCreating(modelBuilder);
}
}
}

View File

@ -0,0 +1,212 @@
using Microsoft.EntityFrameworkCore;
using SharedLibraryCore.Database.Models;
using SharedLibraryCore.Objects;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace SharedLibraryCore.Database
{
//https://stackoverflow.com/questions/5940225/fastest-way-of-inserting-in-entity-framework
public static class Importer
{
public static void ImportClients(IList<Player> clients)
{
DatabaseContext context = null;
try
{
context = new DatabaseContext();
int count = 0;
foreach (var entityToInsert in clients)
{
++count;
var link = new EFAliasLink() { Active = true };
var alias = new EFAlias()
{
Active = true,
DateAdded = entityToInsert.LastConnection,
IPAddress = entityToInsert.IPAddress,
Link = link,
Name = entityToInsert.Name,
};
var client = new EFClient()
{
Active = true,
AliasLink = link,
Connections = entityToInsert.Connections,
CurrentAlias = alias,
FirstConnection = entityToInsert.LastConnection,
Level = entityToInsert.Level,
LastConnection = entityToInsert.LastConnection,
TotalConnectionTime = entityToInsert.TotalConnectionTime,
Masked = entityToInsert.Masked,
NetworkId = entityToInsert.NetworkId
};
context = AddClient(context, client, count, 1000, true);
}
context.SaveChanges();
}
finally
{
if (context != null)
context.Dispose();
}
}
private static DatabaseContext AddClient(DatabaseContext context, EFClient client, int count, int commitCount, bool recreateContext)
{
context.Clients.Add(client);
if (count % commitCount == 0)
{
try
{
context.SaveChanges();
}
catch (Exception)
{
}
if (recreateContext)
{
context.Dispose();
context = new DatabaseContext();
}
}
return context;
}
public static void ImportPenalties(IList<Penalty> penalties)
{
DatabaseContext context = null;
try
{
context = new DatabaseContext();
int count = 0;
foreach (var entityToInsert in penalties)
{
++count;
var punisher = entityToInsert.Offender.NetworkId == entityToInsert.Punisher.NetworkId ?
context.Clients.SingleOrDefault(c => c.ClientId == 1) :
context.Clients.SingleOrDefault(c => c.NetworkId == entityToInsert.Punisher.NetworkId);
if (punisher == null)
continue;
var offender = context.Clients.Include("AliasLink").SingleOrDefault(c => c.NetworkId == entityToInsert.Offender.NetworkId);
if (offender == null)
continue;
var penalty = new EFPenalty()
{
Active = true,
Expires = entityToInsert.Expires.Year == 9999 ? DateTime.Parse(System.Data.SqlTypes.SqlDateTime.MaxValue.ToString()) : entityToInsert.Expires,
Offender = offender,
Punisher = punisher,
Offense = entityToInsert.Offense,
Type = entityToInsert.Type,
When = entityToInsert.When == DateTime.MinValue ? DateTime.UtcNow : entityToInsert.When,
Link = offender.AliasLink
};
context = AddPenalty(context, penalty, count, 1000, true);
}
context.SaveChanges();
}
finally
{
if (context != null)
context.Dispose();
}
}
private static DatabaseContext AddPenalty(DatabaseContext context, EFPenalty penalty, int count, int commitCount, bool recreateContext)
{
context.Penalties.Add(penalty);
if (count % commitCount == 0)
{
try
{
context.SaveChanges();
}
catch (Exception)
{
}
if (recreateContext)
{
context.Dispose();
context = new DatabaseContext();
}
}
return context;
}
public static void ImportSQLite<T>(IList<T> SQLiteData) where T : class
{
DatabaseContext context = null;
try
{
context = new DatabaseContext();
int count = 0;
foreach (var entityToInsert in SQLiteData)
{
++count;
context = AddSQLite(context, entityToInsert, count, 1000, true);
}
context.SaveChanges();
}
finally
{
if (context != null)
context.Dispose();
}
}
private static DatabaseContext AddSQLite<T>(DatabaseContext context, T entity, int count, int commitCount, bool recreateContext) where T : class
{
context.Set<T>().Add(entity);
if (count % commitCount == 0)
{
try
{
context.SaveChanges();
}
catch (Exception)
{
}
if (recreateContext)
{
context.Dispose();
context = new DatabaseContext();
}
}
return context;
}
}
}

View File

@ -0,0 +1,26 @@
using System;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace SharedLibraryCore.Database.Models
{
public class EFAlias : SharedEntity
{
[Key]
public int AliasId { get; set; }
[Required]
public int LinkId { get; set; }
[ForeignKey("LinkId")]
public virtual EFAliasLink Link { get; set; }
// [Index("IX_IPandName", 0, IsUnique = true)]
//[MaxLength(24)]
[Required]
public string Name { get; set; }
// [Index("IX_IPandName", 1, IsUnique = true)]
// [MaxLength(24)]
[Required]
public int IPAddress { get; set; }
[Required]
public DateTime DateAdded { get; set; }
}
}

View File

@ -0,0 +1,21 @@
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
using System.Linq;
namespace SharedLibraryCore.Database.Models
{
public class EFAliasLink : SharedEntity
{
[Key]
public int AliasLinkId { get; set; }
public virtual ICollection<EFAlias> Children { get; set; }
public virtual ICollection<EFPenalty> ReceivedPenalties { get; set; }
public EFAliasLink()
{
Children = new List<EFAlias>();
ReceivedPenalties = new List<EFPenalty>();
}
}
}

View File

@ -0,0 +1,63 @@
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace SharedLibraryCore.Database.Models
{
public class EFClient : SharedEntity
{
[Key]
public int ClientId { get; set; }
public long NetworkId { get; set; }
[Required]
public int Connections { get; set; }
[Required]
// in seconds
public int TotalConnectionTime { get; set; }
[Required]
public DateTime FirstConnection { get; set; }
[Required]
public DateTime LastConnection { get; set; }
public bool Masked { get; set; }
[Required]
public int AliasLinkId { get; set; }
[ForeignKey("AliasLinkId")]
public virtual EFAliasLink AliasLink { get; set; }
[Required]
public Objects.Player.Permission Level { get; set; }
[Required]
public int CurrentAliasId { get; set; }
[ForeignKey("CurrentAliasId")]
public virtual EFAlias CurrentAlias { get; set; }
public string Password { get; set; }
public string PasswordSalt { get; set; }
[NotMapped]
public virtual string Name
{
get { return CurrentAlias.Name; }
set { }
}
[NotMapped]
public virtual int IPAddress
{
get { return CurrentAlias.IPAddress; }
set { }
}
[NotMapped]
public string IPAddressString => new System.Net.IPAddress(BitConverter.GetBytes(IPAddress)).ToString();
public virtual ICollection<EFPenalty> ReceivedPenalties { get; set; }
public virtual ICollection<EFPenalty> AdministeredPenalties { get; set; }
public EFClient()
{
ReceivedPenalties = new List<EFPenalty>();
AdministeredPenalties = new List<EFPenalty>();
}
}
}

View File

@ -0,0 +1,35 @@
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace SharedLibraryCore.Database.Models
{
public class EFPenalty : SharedEntity
{
[Key]
public int PenaltyId { get; set; }
[Required]
public int LinkId { get; set; }
[ForeignKey("LinkId")]
public virtual EFAliasLink Link { get; set; }
[Required]
public int OffenderId { get; set; }
[ForeignKey("OffenderId")]
public virtual EFClient Offender { get; set; }
[Required]
public int PunisherId { get; set; }
[ForeignKey("PunisherId")]
public virtual EFClient Punisher { get; set; }
[Required]
public DateTime When { get; set; }
[Required]
public DateTime Expires { get; set; }
[Required]
public string Offense { get; set; }
public Objects.Penalty.PenaltyType Type { get; set; }
}
}

View File

@ -0,0 +1,13 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace SharedLibraryCore.Database.Models
{
public class SharedEntity
{
public bool Active { get; set; }
}
}

View File

@ -0,0 +1,11 @@
using System;
namespace SharedLibraryCore.Dtos
{
public class ChatInfo
{
public string Message { get; set; }
public DateTime Time { get; set; }
public string Name { get; set; }
}
}

View File

@ -0,0 +1,14 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace SharedLibraryCore.Dtos
{
public class ClientInfo
{
public string Name { get; set; }
public int ClientId { get; set; }
}
}

View File

@ -0,0 +1,14 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace SharedLibraryCore.Dtos
{
public class CommandResponseInfo
{
public string Response { get; set; }
public int ClientId { get; set; }
}
}

View File

@ -0,0 +1,39 @@
using System;
namespace SharedLibraryCore.Dtos
{
public class EventInfo
{
public EventInfo(EventType Ty, EventVersion V, string M, string T, string O, string Ta)
{
Type = Ty;
Version = V;
Message = System.Web.HttpUtility.HtmlEncode(M);
Title = T;
Origin = System.Web.HttpUtility.HtmlEncode(O);
Target = System.Web.HttpUtility.HtmlEncode(Ta);
ID = Math.Abs(DateTime.Now.GetHashCode());
}
public enum EventType
{
NOTIFICATION,
STATUS,
ALERT,
}
public enum EventVersion
{
IW4MAdmin
}
public EventType Type;
public EventVersion Version;
public string Message;
public string Title;
public string Origin;
public string Target;
public int ID;
}
}

View File

@ -0,0 +1,21 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace SharedLibraryCore.Dtos
{
public class PenaltyInfo : SharedInfo
{
public string OffenderName { get; set; }
public int OffenderId { get; set; }
public string PunisherName { get; set; }
public int PunisherId { get; set; }
public string PunisherLevel { get; set; }
public string Offense { get; set; }
public string Type { get; set; }
public string TimePunished { get; set; }
public string TimeRemaining { get; set; }
}
}

View File

@ -0,0 +1,26 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace SharedLibraryCore.Dtos
{
public class PlayerInfo
{
public string Name { get; set; }
public int ClientId { get; set; }
public string Level { get; set; }
public int LevelInt { get; set; }
public string IPAddress { get; set; }
public long NetworkId { get; set; }
public List<string> Aliases { get; set; }
public List<string> IPs { get; set; }
public int ConnectionCount { get; set; }
public string LastSeen { get; set; }
public string FirstSeen { get; set; }
public string TimePlayed { get; set; }
public bool Authenticated { get; set; }
public List<ProfileMeta> Meta { get; set; }
}
}

View File

@ -0,0 +1,17 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace SharedLibraryCore.Dtos
{
public class ProfileMeta : SharedInfo
{
public DateTime When { get; set; }
public string WhenString => Utilities.GetTimePassed(When, false);
public string Key { get; set; }
public dynamic Value { get; set; }
public virtual string Class => Value.GetType().ToString();
}
}

View File

@ -0,0 +1,23 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace SharedLibraryCore.Dtos
{
public class ServerInfo
{
public string Name { get; set; }
public int Port { get; set; }
public string Map { get; set; }
public string GameType { get; set; }
public int ClientCount { get; set; }
public int MaxClients { get; set; }
public ChatInfo[] ChatHistory { get; set; }
public List<PlayerInfo> Players { get; set; }
public Helpers.PlayerHistory[] PlayerHistory { get; set; }
public int ID { get; set; }
public bool Online { get; set; }
}
}

View File

@ -0,0 +1,8 @@

namespace SharedLibraryCore.Dtos
{
public class SharedInfo
{
public bool Sensitive { get; set; }
}
}

13
SharedLibraryCore/Dvar.cs Normal file
View File

@ -0,0 +1,13 @@
namespace SharedLibraryCore
{
public class DVAR<T>
{
public string Name { get; private set; }
public T Value;
public DVAR(string name)
{
Name = name;
}
}
}

110
SharedLibraryCore/Event.cs Normal file
View File

@ -0,0 +1,110 @@
using System;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using SharedLibraryCore.Objects;
namespace SharedLibraryCore
{
public class Event
{
public enum GType
{
//FROM SERVER
Start,
Stop,
Connect,
Disconnect,
Say,
MapChange,
MapEnd,
//FROM ADMIN
Broadcast,
Tell,
Kick,
Ban,
Remote,
Unknown,
//FROM PLAYER
Report,
Flag,
// FROM GAME
Script,
Kill,
Death,
}
public Event(GType t, string d, Player O, Player T, Server S)
{
Type = t;
Data = d?.Trim();
Origin = O;
Target = T;
Owner = S;
}
public static Event ParseEventString(String[] line, Server SV)
{
#if DEBUG == false
try
#endif
{
string removeTime = Regex.Replace(line[0], @"[0-9]+:[0-9]+\ ", "");
if (removeTime[0] == 'K')
{
StringBuilder Data = new StringBuilder();
if (line.Length > 9)
{
for (int i = 9; i < line.Length; i++)
Data.Append(line[i] + ";");
}
if (!SV.CustomCallback)
return new Event(GType.Script, Data.ToString(), SV.ParseClientFromString(line, 6), SV.ParseClientFromString(line, 2), SV);
}
if (line[0].Substring(line[0].Length - 3).Trim() == "say")
{
Regex rgx = new Regex("[^a-zA-Z0-9 -! -_]");
string message = rgx.Replace(line[4], "");
return new Event(GType.Say, message.StripColors(), SV.ParseClientFromString(line, 2), null, SV) { Message = message };
}
if (removeTime.Contains("ScriptKill"))
{
return new Event(GType.Script, String.Join(";", line), SV.Players.First(p => p != null && p.NetworkId == line[1].ConvertLong()), SV.Players.First(p => p != null && p.NetworkId == line[2].ConvertLong()), SV);
}
if (removeTime.Contains("ExitLevel"))
return new Event(GType.MapEnd, line[0], new Player() { ClientId = 1 }, null, SV);
if (removeTime.Contains("InitGame"))
return new Event(GType.MapChange, line[0], new Player() { ClientId = 1 }, null, SV);
return null;
}
#if DEBUG == false
catch (Exception E)
{
SV.Manager.GetLogger().WriteError("Error requesting event " + E.Message);
return null;
}
#endif
}
public GType Type;
public string Data; // Data is usually the message sent by player
public string Message;
public Player Origin;
public Player Target;
public Server Owner;
public Boolean Remote = false;
}
}

View File

@ -0,0 +1,15 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace SharedLibraryCore.Exceptions
{
public class CommandException : ServerException
{
public CommandException(string msg) : base(msg) { }
// .data contains
// "command_name"
}
}

View File

@ -0,0 +1,13 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace SharedLibraryCore.Exceptions
{
public class DatabaseException : Exception
{
public DatabaseException(string msg) : base(msg) { }
}
}

View File

@ -0,0 +1,13 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace SharedLibraryCore.Exceptions
{
public class DvarException : ServerException
{
public DvarException(string msg) : base(msg) { }
}
}

View File

@ -0,0 +1,13 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace SharedLibraryCore.Exceptions
{
public class NetworkException : ServerException
{
public NetworkException(string msg) : base(msg) { }
}
}

View File

@ -0,0 +1,13 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace SharedLibraryCore.Exceptions
{
public class SerializeException : Exception
{
public SerializeException(string msg) : base(msg) { }
}
}

View File

@ -0,0 +1,13 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace SharedLibraryCore.Exceptions
{
public class ServerException : Exception
{
public ServerException(string msg) : base(msg) { }
}
}

105
SharedLibraryCore/File.cs Normal file
View File

@ -0,0 +1,105 @@
using System;
using System.Collections.Generic;
using System.Text;
using System.IO;
using System.Net;
using System.Net.Http;
namespace SharedLibraryCore
{
public class RemoteFile : IFile
{
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 string[] Tail(int lineCount)
{
// Retrieve();
return FileCache;
}
public override long Length()
{
Retrieve();
return FileCache[0].Length;
}
}
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));
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 String[] Tail(int lineCount)
{
var buffer = new List<string>(lineCount);
string line;
for (int i = 0; i < lineCount; i++)
{
line = Handle.ReadLine();
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 = Handle.ReadLine()))
{
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 String Name;
private StreamReader Handle;
}
}

View File

@ -0,0 +1,62 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
namespace SharedLibraryCore.Helpers
{
public sealed class AsyncStatus
{
DateTime StartTime;
int TimesRun;
int UpdateFrequency;
public double RunAverage { get; private set; }
public object Dependant { get; private set; }
public Task RequestedTask { get; private set; }
public CancellationTokenSource TokenSrc { get; private set; }
public AsyncStatus(object dependant, int frequency)
{
TokenSrc = new CancellationTokenSource();
StartTime = DateTime.Now;
Dependant = dependant;
UpdateFrequency = frequency;
// technically 0 but it's faster than checking for division by 0
TimesRun = 1;
}
public CancellationToken GetToken()
{
return TokenSrc.Token;
}
public double ElapsedMillisecondsTime()
{
return (DateTime.Now - StartTime).TotalMilliseconds;
}
public void Update(Task<bool> T)
{
// reset the token source
TokenSrc.Dispose();
TokenSrc = new CancellationTokenSource();
RequestedTask = T;
// Console.WriteLine($"Starting Task {T.Id} ");
RequestedTask.Start();
if (TimesRun > 25)
TimesRun = 1;
RunAverage = RunAverage + ((DateTime.Now - StartTime).TotalMilliseconds - RunAverage - UpdateFrequency) / TimesRun;
StartTime = DateTime.Now;
}
public void Abort()
{
RequestedTask = null;
}
}
}

View File

@ -0,0 +1,53 @@
using Microsoft.Extensions.Configuration;
using Newtonsoft.Json;
using SharedLibraryCore.Interfaces;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace SharedLibraryCore.Configuration
{
public class BaseConfigurationHandler<T> : IConfigurationHandler<T> where T : IBaseConfiguration
{
string Filename;
IConfigurationRoot ConfigurationRoot { get; set; }
T _configuration;
public BaseConfigurationHandler(string fn)
{
Filename = fn;
Build();
}
public void Build()
{
ConfigurationRoot = new ConfigurationBuilder()
.AddJsonFile($"{AppDomain.CurrentDomain.BaseDirectory}{Filename}.json", true)
.Build();
_configuration = ConfigurationRoot.Get<T>();
if (_configuration == null)
_configuration = default(T);
}
public Task Save()
{
var appConfigJSON = JsonConvert.SerializeObject(_configuration, Formatting.Indented);
return Task.Factory.StartNew(() =>
{
File.WriteAllText($"{AppDomain.CurrentDomain.BaseDirectory}{Filename}.json", appConfigJSON);
});
}
public T Configuration() => _configuration;
public void Set(T config)
{
_configuration = config;
}
}
}

View File

@ -0,0 +1,42 @@
using SimpleCrypto;
namespace SharedLibraryCore.Helpers
{
public class Hashing
{
/// <summary>
/// Generate password hash and salt
/// </summary>
/// <param name="password">plaintext password</param>
/// <returns></returns>
public static string[] Hash(string password, string saltStr = null)
{
string hash;
string salt;
var CryptoSvc = new PBKDF2();
// generate new hash
if (saltStr == null)
{
hash = CryptoSvc.Compute(password);
salt = CryptoSvc.Salt;
return new string[]
{
hash,
salt
};
}
else
{
hash = CryptoSvc.Compute(password, saltStr);
return new string[]
{
hash,
""
};
}
}
}
}

View File

@ -0,0 +1,20 @@
using System;
namespace SharedLibraryCore.Helpers
{
public class MessageToken
{
public string Name { get; private set; }
Func<string> Value;
public MessageToken(string Name, Func<string> Value)
{
this.Name = Name;
this.Value = Value;
}
public override string ToString()
{
return Value().ToString();
}
}
}

View File

@ -0,0 +1,24 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace SharedLibraryCore.Helpers
{
public class ParseEnum<T>
{
public static T Get(string e, Type type)
{
try
{
return (T)Enum.Parse(type, e);
}
catch (Exception)
{
return (T)(Enum.GetValues(type).GetValue(0));
}
}
}
}

View File

@ -0,0 +1,51 @@
using System;
namespace SharedLibraryCore.Helpers
{
public class PlayerHistory
{
// how many minutes between updates
public static readonly int UpdateInterval = 5;
public PlayerHistory(int cNum)
{
DateTime t = DateTime.UtcNow;
When = new DateTime(t.Year, t.Month, t.Day, t.Hour, Math.Min(59, UpdateInterval * (int)Math.Round(t.Minute / (float)UpdateInterval)), 0);
PlayerCount = cNum;
}
#if DEBUG
public PlayerHistory(DateTime t, int cNum)
{
When = new DateTime(t.Year, t.Month, t.Day, t.Hour, Math.Min(59, UpdateInterval * (int)Math.Round(t.Minute / (float)UpdateInterval)), 0);
PlayerCount = cNum;
}
#endif
private DateTime When;
private int PlayerCount;
/// <summary>
/// Used by CanvasJS as a point on the x axis
/// </summary>
public string x
{
get
{
return When.ToString("yyyy-MM-ddTHH:mm:ssZ");
}
}
/// <summary>
/// Used by CanvasJS as a point on the y axis
/// </summary>
public int y
{
get
{
return PlayerCount;
}
}
}
}

View File

@ -0,0 +1,48 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace SharedLibraryCore.Helpers
{
/// <summary>
/// Excuse this monstrosity
/// </summary>
/// <typeparam name="T"></typeparam>
public class ThreadSafe<T>
{
private bool _lock;
private T instance;
public ThreadSafe(T instance)
{
this.instance = instance;
_lock = true;
}
public T Value
{
get
{
// shush
if (_lock)
return Value;
_lock = true;
return instance;
}
set
{
if (_lock)
{
Value = Value;
return;
}
instance = Value;
}
}
}
}

View File

@ -0,0 +1,53 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
namespace SharedLibraryCore.Helpers
{
public class Vector3
{
public float X { get; protected set; }
public float Y { get; protected set; }
public float Z { get; protected set; }
public Vector3(float x, float y, float z)
{
X = x;
Y = y;
Z = z;
}
public override string ToString()
{
return $"({X}, {Y}, {Z})";
}
public static Vector3 Parse(string s)
{
bool valid = Regex.Match(s, @"\(-?[0-9]+.?[0-9]*,\ -?[0-9]+.?[0-9]*,\ -?[0-9]+.?[0-9]*\)").Success;
if (!valid)
throw new FormatException("Vector3 is not in correct format");
string removeParenthesis = s.Substring(1, s.Length - 2);
string[] eachPoint = removeParenthesis.Split(',');
return new Vector3(float.Parse(eachPoint[0]), float.Parse(eachPoint[1]), float.Parse(eachPoint[2]));
}
public static double Distance(Vector3 a, Vector3 b)
{
return Math.Sqrt(Math.Pow(b.X - a.X, 2) + Math.Pow(b.Y - a.Y, 2) + Math.Pow(b.Z - a.Z, 2));
}
public static Vector3 Subtract(Vector3 a, Vector3 b) => new Vector3(b.X - a.X, b.Y - a.Y, b.Z - a.Z);
public double DotProduct(Vector3 a) => (a.X * this.X) + (a.Y * this.Y) + (a.Z * this.Z);
public double Magnitude() => Math.Sqrt((X * X) + (Y * Y) + (Z * Z));
public double AngleBetween(Vector3 a) => Math.Acos(this.DotProduct(a) / (a.Magnitude() * this.Magnitude()));
}
}

View File

@ -0,0 +1,14 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace SharedLibraryCore.Interfaces
{
public interface IBaseConfiguration
{
string Name();
IBaseConfiguration Generate();
}
}

View File

@ -0,0 +1,16 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace SharedLibraryCore.Interfaces
{
public interface IConfigurationHandler<T> where T : IBaseConfiguration
{
Task Save();
void Build();
T Configuration();
void Set(T config);
}
}

View File

@ -0,0 +1,20 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Text;
using System.Threading.Tasks;
namespace SharedLibraryCore.Interfaces
{
public interface IEntityService<T>
{
Task<T> CreateProxy();
Task<T> Create(T entity);
Task<T> Delete(T entity);
Task<T> Update(T entity);
Task<T> Get(int entityID);
Task<T> GetUnique(long entityProperty);
Task<IList<T>> Find(Func<T, bool> expression);
}
}

View File

@ -0,0 +1,17 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace SharedLibraryCore.Interfaces
{
public interface ILogger
{
void WriteVerbose(string msg);
void WriteInfo(string msg);
void WriteDebug(string msg);
void WriteWarning(string msg);
void WriteError(string msg);
}
}

View File

@ -0,0 +1,25 @@
using System.Collections.Generic;
using SharedLibraryCore.Objects;
using SharedLibraryCore.Services;
using System.Threading.Tasks;
using SharedLibraryCore.Configuration;
namespace SharedLibraryCore.Interfaces
{
public interface IManager
{
Task Init();
void Start();
void Stop();
ILogger GetLogger();
IList<Server> GetServers();
IList<Command> GetCommands();
IList<Helpers.MessageToken> GetMessageTokens();
IList<Player> GetActiveClients();
IConfigurationHandler<ApplicationConfiguration> GetApplicationSettings();
ClientService GetClientService();
AliasService GetAliasService();
PenaltyService GetPenaltyService();
IDictionary<int, Player> GetPrivilegedClients();
}
}

View File

@ -0,0 +1,18 @@
using System;
using System.Threading.Tasks;
namespace SharedLibraryCore.Interfaces
{
public interface IPlugin
{
Task OnLoadAsync(IManager manager);
Task OnUnloadAsync();
Task OnEventAsync(Event E, Server S);
Task OnTickAsync(Server S);
//for logging purposes
String Name { get; }
float Version { get; }
String Author { get; }
}
}

View File

@ -0,0 +1,60 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
namespace SharedLibraryCore.Interfaces
{
interface ISerializable<T>
{
void Write();
}
public class Serialize<T> : ISerializable<T>
{
public static T Read(string filename)
{
try
{
string configText = File.ReadAllText(filename);
return Newtonsoft.Json.JsonConvert.DeserializeObject<T>(configText);
}
catch (Exception e)
{
throw new Exceptions.SerializeException($"Could not deserialize file {filename}: {e.Message}");
}
}
public void Write()
{
try
{
string configText = Newtonsoft.Json.JsonConvert.SerializeObject(this);
File.WriteAllText(Filename(), configText);
}
catch (Exception e)
{
throw new Exceptions.SerializeException($"Could not serialize file {Filename()}: {e.Message}");
}
}
public static void Write(string filename, T data)
{
try
{
string configText = Newtonsoft.Json.JsonConvert.SerializeObject(data, Newtonsoft.Json.Formatting.Indented);
File.WriteAllText(filename, configText);
}
catch (Exception e)
{
throw new Exceptions.SerializeException($"Could not serialize file {filename}: {e.Message}");
}
}
public virtual string Filename() { return ToString(); }
}
}

View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
<noInheritable></noInheritable>
<assemblyIdentity type="win32" name="Microsoft.VC90.CRT" version="9.0.30729.4148" processorArchitecture="x86" publicKeyToken="1fc8b3b9a1e18e3b"></assemblyIdentity>
<file name="msvcr90.dll" hashalg="SHA1" hash="98e8006e0a4542e69f1a3555b927758bd76ca07d"><asmv2:hash xmlns:asmv2="urn:schemas-microsoft-com:asm.v2" xmlns:dsig="http://www.w3.org/2000/09/xmldsig#"><dsig:Transforms><dsig:Transform Algorithm="urn:schemas-microsoft-com:HashTransforms.Identity"></dsig:Transform></dsig:Transforms><dsig:DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1"></dsig:DigestMethod><dsig:DigestValue>+CXED+6HzJlSphyMNOn27ujadC0=</dsig:DigestValue></asmv2:hash></file> <file name="msvcp90.dll" hashalg="SHA1" hash="3aec3be680024a46813dee891a753bd58b3f3b12"><asmv2:hash xmlns:asmv2="urn:schemas-microsoft-com:asm.v2" xmlns:dsig="http://www.w3.org/2000/09/xmldsig#"><dsig:Transforms><dsig:Transform Algorithm="urn:schemas-microsoft-com:HashTransforms.Identity"></dsig:Transform></dsig:Transforms><dsig:DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1"></dsig:DigestMethod><dsig:DigestValue>MyKED+9DyS+1XcMeaC0Zlw2vFZ0=</dsig:DigestValue></asmv2:hash></file> <file name="msvcm90.dll" hashalg="SHA1" hash="0195dd0896d74b62531e4f3c771904a3d996450e"><asmv2:hash xmlns:asmv2="urn:schemas-microsoft-com:asm.v2" xmlns:dsig="http://www.w3.org/2000/09/xmldsig#"><dsig:Transforms><dsig:Transform Algorithm="urn:schemas-microsoft-com:HashTransforms.Identity"></dsig:Transform></dsig:Transforms><dsig:DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1"></dsig:DigestMethod><dsig:DigestValue>EeyDE7og6WoPd2oBhYbMEnpFHhY=</dsig:DigestValue></asmv2:hash></file>
</assembly>

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

13
SharedLibraryCore/Map.cs Normal file
View File

@ -0,0 +1,13 @@
using System;
using System.Reflection;
namespace SharedLibraryCore
{
public class Map
{
public String Name { get; set; }
public String Alias { get; set; }
public override string ToString() => Alias;
}
}

View File

@ -0,0 +1,13 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace SharedLibraryCore.Objects
{
public class Alias : Database.Models.EFAlias
{
}
}

View File

@ -0,0 +1,25 @@
using System;
using SharedLibraryCore;
namespace SharedLibraryCore.Objects
{
public class Penalty : Database.Models.EFPenalty
{
public enum PenaltyType
{
Report,
Warning,
Flag,
Kick,
TempBan,
Ban,
Unban,
Any,
}
public String GetWhenFormatted()
{
return When.ToString("MM/dd/yy HH:mm:ss"); ;
}
}
}

View File

@ -0,0 +1,104 @@
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations.Schema;
using System.Linq;
using System.Threading.Tasks;
namespace SharedLibraryCore.Objects
{
public class Player : Database.Models.EFClient
{
public enum Permission
{
Banned = -1,
User = 0,
Flagged = 1,
Trusted = 2,
Moderator = 3,
Administrator = 4,
SeniorAdmin = 5,
Owner = 6,
Creator = 7,
Console = 8,
}
public Player()
{
ConnectionTime = DateTime.UtcNow;
ClientNumber = -1;
}
public override string ToString()
{
return $"{Name}::{NetworkId}";
}
public String GetLastConnection()
{
return Utilities.GetTimePassed(LastConnection);
}
public async Task Tell(String Message)
{
await CurrentServer.Tell(Message, this);
}
public async Task Kick(String Message, Player Sender)
{
await CurrentServer.Kick(Message, this, Sender);
}
public async Task TempBan(String Message, TimeSpan Length, Player Sender)
{
await CurrentServer.TempBan(Message, Length, this, Sender);
}
public async Task Warn(String Message, Player Sender)
{
await CurrentServer.Warn(Message, this, Sender);
}
public async Task Ban(String Message, Player Sender)
{
await CurrentServer.Ban(Message, this, Sender);
}
[NotMapped]
public int ClientNumber { get; set; }
[NotMapped]
public int Ping { get; set; }
[NotMapped]
public int Warnings { get; set; }
[NotMapped]
public DateTime ConnectionTime { get; set; }
[NotMapped]
public Server CurrentServer { get; set; }
[NotMapped]
public int Score { get; set; }
[NotMapped]
public IList<Dtos.ProfileMeta> Meta { get; set; }
private int _ipaddress;
public override int IPAddress
{
get { return _ipaddress; }
set { _ipaddress = value; }
}
private string _name;
public override string Name
{
get { return _name; }
set { _name = value; }
}
public override bool Equals(object obj)
{
return ((Player)obj).NetworkId == NetworkId;
}
public override int GetHashCode()
{
return NetworkId.GetHashCode();
}
}
}

View File

@ -0,0 +1,22 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace SharedLibraryCore.Objects
{
public class Report
{
public Report(Player T, Player O, String R)
{
Target = T;
Origin = O;
Reason = R;
}
public Player Target { get; private set; }
public Player Origin { get; private set; }
public String Reason { get; private set; }
}
}

View File

@ -0,0 +1,78 @@
using System;
using System.IO;
using System.Collections.Generic;
using System.Reflection;
using SharedLibraryCore.Interfaces;
namespace SharedLibraryCore.Plugins
{
public class PluginImporter
{
public static List<Command> ActiveCommands = new List<Command>();
public static List<IPlugin> ActivePlugins = new List<IPlugin>();
public static bool Load(IManager Manager)
{
string[] dllFileNames = Directory.GetFiles($"{Utilities.OperatingDirectory}Plugins{Path.DirectorySeparatorChar}", "*.dll");
if (dllFileNames.Length == 0)
{
Manager.GetLogger().WriteDebug("No plugins found to load");
return true;
}
ICollection<Assembly> assemblies = new List<Assembly>(dllFileNames.Length);
foreach (string dllFile in dllFileNames)
{
// byte[] rawDLL = File.ReadAllBytes(dllFile);
//Assembly assembly = Assembly.Load(rawDLL);
assemblies.Add(Assembly.LoadFrom(dllFile));
}
int LoadedPlugins = 0;
int LoadedCommands = 0;
foreach (Assembly Plugin in assemblies)
{
if (Plugin != null)
{
Type[] types = Plugin.GetTypes();
foreach (Type assemblyType in types)
{
if (assemblyType.IsClass && assemblyType.BaseType.Name == "Command")
{
Object commandObject = Activator.CreateInstance(assemblyType);
Command newCommand = (Command)commandObject;
ActiveCommands.Add(newCommand);
Manager.GetLogger().WriteDebug("Registered command \"" + newCommand.Name + "\"");
LoadedCommands++;
continue;
}
try
{
if (assemblyType.GetInterface("IPlugin", false) == null)
continue;
Object notifyObject = Activator.CreateInstance(assemblyType);
IPlugin newNotify = (IPlugin)notifyObject;
if (ActivePlugins.Find(x => x.Name == newNotify.Name) == null)
{
ActivePlugins.Add(newNotify);
Manager.GetLogger().WriteDebug($"Loaded plugin \"{ newNotify.Name }\" [{newNotify.Version}]");
LoadedPlugins++;
}
}
catch (Exception E)
{
Manager.GetLogger().WriteWarning($"Could not load plugin {Plugin.Location} - {E.Message}");
}
}
}
}
Manager.GetLogger().WriteInfo($"Loaded {LoadedPlugins} plugins and registered {LoadedCommands} commands.");
return true;
}
}
}

View File

@ -0,0 +1,296 @@
using SharedLibraryCore.Exceptions;
using SharedLibraryCore.Interfaces;
using System;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
namespace SharedLibraryCore.RCon
{
class ConnectionState
{
public Socket Client { get; private set; }
public int BufferSize { get; private set; }
public byte[] Buffer { get; private set; }
private readonly StringBuilder sb;
public StringBuilder ResponseString
{
get => sb;
}
public ConnectionState(Socket cl)
{
BufferSize = 8192;
Buffer = new byte[BufferSize];
Client = cl;
sb = new StringBuilder();
}
}
public class Connection
{
IPEndPoint Endpoint;
string RConPassword;
Socket ServerConnection;
ILogger Log;
int FailedSends;
int FailedReceives;
DateTime LastQuery;
string response;
ManualResetEvent OnConnected;
ManualResetEvent OnSent;
ManualResetEvent OnReceived;
public Connection(string ipAddress, int port, string password, ILogger log)
{
Endpoint = new IPEndPoint(IPAddress.Parse(ipAddress), port);
RConPassword = password;
Log = log;
OnConnected = new ManualResetEvent(false);
OnSent = new ManualResetEvent(false);
OnReceived = new ManualResetEvent(false);
try
{
ServerConnection = new Socket(Endpoint.AddressFamily, SocketType.Dgram, ProtocolType.Udp);
ServerConnection.BeginConnect(Endpoint, new AsyncCallback(OnConnectedCallback), ServerConnection);
if (!OnConnected.WaitOne(StaticHelpers.SocketTimeout))
throw new SocketException((int)SocketError.TimedOut);
FailedSends = 0;
}
catch (SocketException e)
{
throw new NetworkException(e.Message);
}
}
~Connection()
{
ServerConnection.Shutdown(SocketShutdown.Both);
ServerConnection.Close();
ServerConnection.Dispose();
}
private void OnConnectedCallback(IAsyncResult ar)
{
var serverSocket = (Socket)ar.AsyncState;
try
{
serverSocket.EndConnect(ar);
#if DEBUG
Log.WriteDebug($"Successfully initialized socket to {serverSocket.RemoteEndPoint}");
#endif
OnConnected.Set();
}
catch (SocketException e)
{
throw new NetworkException($"Could not initialize socket for RCon - {e.Message}");
}
}
private void OnSentCallback(IAsyncResult ar)
{
Socket serverConnection = (Socket)ar.AsyncState;
try
{
int sentByteNum = serverConnection.EndSend(ar);
#if DEBUG
Log.WriteDebug($"Sent {sentByteNum} bytes to {ServerConnection.RemoteEndPoint}");
#endif
OnSent.Set();
}
catch (SocketException)
{
}
}
private void OnReceivedCallback(IAsyncResult ar)
{
var connectionState = (ConnectionState)ar.AsyncState;
var serverConnection = connectionState.Client;
try
{
int bytesRead = serverConnection.EndReceive(ar);
if (bytesRead > 0)
{
#if DEBUG
Log.WriteDebug($"Received {bytesRead} bytes from {ServerConnection.RemoteEndPoint}");
#endif
connectionState.ResponseString.Append(Encoding.UTF7.GetString(connectionState.Buffer, 0, bytesRead).TrimEnd('\0'));
if (serverConnection.Available > 0)
{
ServerConnection.BeginReceive(connectionState.Buffer, 0, connectionState.Buffer.Length, 0,
new AsyncCallback(OnReceivedCallback), connectionState);
}
else
{
response = connectionState.ResponseString.ToString();
OnReceived.Set();
}
}
else
{
response = connectionState.ResponseString.ToString();
OnReceived.Set();
}
}
catch (SocketException)
{
}
}
public async Task<string[]> SendQueryAsync(StaticHelpers.QueryType type, string parameters = "")
{
// will this really prevent flooding?
if ((DateTime.Now - LastQuery).TotalMilliseconds < 150)
{
await Task.Delay(150);
}
LastQuery = DateTime.Now;
OnSent.Reset();
OnReceived.Reset();
string queryString = "";
switch (type)
{
case StaticHelpers.QueryType.DVAR:
case StaticHelpers.QueryType.COMMAND:
queryString = $"ÿÿÿÿrcon {RConPassword} {parameters}";
break;
case StaticHelpers.QueryType.GET_STATUS:
queryString = "ÿÿÿÿgetstatus";
break;
}
byte[] payload = queryString.Select(Convert.ToByte).ToArray();
retrySend:
try
{
ServerConnection.BeginSend(payload, 0, payload.Length, 0, new AsyncCallback(OnSentCallback), ServerConnection);
bool success = await Task.FromResult(OnSent.WaitOne(StaticHelpers.SocketTimeout));
if (!success)
{
FailedSends++;
#if DEBUG
Log.WriteDebug($"{FailedSends} failed sends to {ServerConnection.RemoteEndPoint.ToString()}");
#endif
if (FailedSends < 4)
goto retrySend;
else if (FailedSends == 4)
Log.WriteError($"Failed to send data to {ServerConnection.RemoteEndPoint}");
}
else
{
if (FailedSends >= 4)
{
Log.WriteVerbose($"Resumed send RCon connection with {ServerConnection.RemoteEndPoint}");
FailedSends = 0;
}
}
}
catch (SocketException e)
{
// this result is normal if the server is not listening
if (e.NativeErrorCode != (int)SocketError.ConnectionReset &&
e.NativeErrorCode != (int)SocketError.TimedOut)
throw new NetworkException($"Unexpected error while sending data to server - {e.Message}");
}
var connectionState = new ConnectionState(ServerConnection);
retryReceive:
try
{
ServerConnection.BeginReceive(connectionState.Buffer, 0, connectionState.Buffer.Length, 0,
new AsyncCallback(OnReceivedCallback), connectionState);
bool success = await Task.FromResult(OnReceived.WaitOne(StaticHelpers.SocketTimeout));
if (!success)
{
FailedReceives++;
#if DEBUG
Log.WriteDebug($"{FailedReceives} failed receives from {ServerConnection.RemoteEndPoint.ToString()}");
#endif
if (FailedReceives < 4)
goto retrySend;
else if (FailedReceives == 4)
{
Log.WriteError($"Failed to receive data from {ServerConnection.RemoteEndPoint} after {FailedReceives} tries");
}
if (FailedReceives >= 4)
{
throw new NetworkException($"Could not receive data from the {ServerConnection.RemoteEndPoint}");
}
}
else
{
if (FailedReceives >= 4)
{
Log.WriteVerbose($"Resumed receive RCon connection from {ServerConnection.RemoteEndPoint}");
FailedReceives = 0;
}
}
}
catch (SocketException e)
{
// this result is normal if the server is not listening
if (e.NativeErrorCode != (int)SocketError.ConnectionReset &&
e.NativeErrorCode != (int)SocketError.TimedOut)
throw new NetworkException($"Unexpected error while receiving data from server - {e.Message}");
else if (FailedReceives < 4)
{
goto retryReceive;
}
else if (FailedReceives == 4)
{
Log.WriteError($"Failed to receive data from {ServerConnection.RemoteEndPoint} after {FailedReceives} tries");
}
if (FailedReceives >= 4)
{
throw new NetworkException(e.Message);
}
}
string queryResponse = response;
if (queryResponse.Contains("Invalid password"))
throw new NetworkException("RCON password is invalid");
if (queryResponse.ToString().Contains("rcon_password"))
throw new NetworkException("RCON password has not been set");
string[] splitResponse = queryResponse.Split(new char[]
{
StaticHelpers.SeperatorChar
}, StringSplitOptions.RemoveEmptyEntries);
return splitResponse;
}
}
}

View File

@ -0,0 +1,18 @@
using System;
namespace SharedLibraryCore.RCon
{
public static class StaticHelpers
{
public enum QueryType
{
GET_STATUS,
GET_INFO,
DVAR,
COMMAND,
}
public static char SeperatorChar = (char)int.Parse("0a", System.Globalization.NumberStyles.AllowHexSpecifier);
public static readonly TimeSpan SocketTimeout = new TimeSpan(0, 0, 10);
}
}

334
SharedLibraryCore/Server.cs Normal file
View File

@ -0,0 +1,334 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using SharedLibraryCore.Helpers;
using SharedLibraryCore.Objects;
using SharedLibraryCore.Dtos;
using SharedLibraryCore.Configuration;
namespace SharedLibraryCore
{
public abstract class Server
{
public enum Game
{
UKN,
IW3,
IW4,
IW5,
T4,
T5,
T5M,
}
public Server(Interfaces.IManager mgr, ServerConfiguration config)
{
Password = config.Password;
IP = config.IPAddress;
Port = config.Port;
Manager = mgr;
Logger = Manager.GetLogger();
ServerConfig = config;
RemoteConnection = new RCon.Connection(IP, Port, Password, Logger);
Players = new List<Player>(new Player[18]);
Reports = new List<Report>();
PlayerHistory = new Queue<PlayerHistory>();
ChatHistory = new List<ChatInfo>();
NextMessage = 0;
CustomSayEnabled = Manager.GetApplicationSettings().Configuration().EnableCustomSayName;
CustomSayName = Manager.GetApplicationSettings().Configuration().CustomSayName;
InitializeTokens();
InitializeAutoMessages();
}
//Returns current server IP set by `net_ip` -- *STRING*
public String GetIP()
{
return IP;
}
//Returns current server port set by `net_port` -- *INT*
public int GetPort()
{
return Port;
}
//Returns list of all current players
public List<Player> GetPlayersAsList()
{
return Players.FindAll(x => x != null);
}
/// <summary>
/// Add a player to the server's player list
/// </summary>
/// <param name="P">Player pulled from memory reading</param>
/// <returns>True if player added sucessfully, false otherwise</returns>
abstract public Task<bool> AddPlayer(Player P);
/// <summary>
/// Remove player by client number
/// </summary>
/// <param name="cNum">Client ID of player to be removed</param>
/// <returns>true if removal succeded, false otherwise</returns>
abstract public Task RemovePlayer(int cNum);
/// <summary>
/// Get the player from the server's list by line from game long
/// </summary>
/// <param name="L">Game log line containing event</param>
/// <param name="cIDPos">Position in the line where the cliet ID is written</param>
/// <returns>Matching player if found</returns>
abstract public Player ParseClientFromString(String[] L, int cIDPos);
/// <summary>
/// Get a player by name
/// </summary>
/// <param name="pName">Player name to search for</param>
/// <returns>Matching player if found</returns>
public List<Player> GetClientByName(String pName)
{
string[] QuoteSplit = pName.Split('"');
bool literal = false;
if (QuoteSplit.Length > 1)
{
pName = QuoteSplit[1];
literal = true;
}
if (literal)
return Players.Where(p => p != null && p.Name.ToLower().Equals(pName.ToLower())).ToList();
return Players.Where(p => p != null && p.Name.ToLower().Contains(pName.ToLower())).ToList();
}
/// <summary>
/// Process requested command correlating to an event
/// </summary>
/// <param name="E">Event parameter</param>
/// <param name="C">Command requested from the event</param>
/// <returns></returns>
abstract public Task<Command> ValidateCommand(Event E);
virtual public Task<bool> ProcessUpdatesAsync(CancellationToken cts)
{
return null;
}
/// <summary>
/// Process any server event
/// </summary>
/// <param name="E">Event</param>
/// <returns>True on sucess</returns>
abstract protected Task ProcessEvent(Event E);
abstract public Task ExecuteEvent(Event E);
/// <summary>
/// Send a message to all players
/// </summary>
/// <param name="Message">Message to be sent to all players</param>
public async Task Broadcast(String Message)
{
string sayCommand = (GameName == Game.IW4) ? "sayraw" : "say";
#if !DEBUG
await this.ExecuteCommandAsync($"{sayCommand} {(CustomSayEnabled ? CustomSayName : "")} {Message}");
#else
Logger.WriteVerbose(Message.StripColors());
await Utilities.CompletedTask;
#endif
}
/// <summary>
/// Send a message to a particular players
/// </summary>
/// <param name="Message">Message to send</param>
/// <param name="Target">Player to send message to</param>
public async Task Tell(String Message, Player Target)
{
string tellCommand = (GameName == Game.IW4) ? "tellraw" : "tell";
#if !DEBUG
if (Target.ClientNumber > -1 && Message.Length > 0 && Target.Level != Player.Permission.Console)
await this.ExecuteCommandAsync($"{tellCommand} {Target.ClientNumber} {(CustomSayEnabled ? CustomSayName : "")} {Message}^7");
#else
Logger.WriteVerbose($"{Target.ClientNumber}->{Message.StripColors()}");
await Utilities.CompletedTask;
#endif
if (Target.Level == Player.Permission.Console)
{
Console.ForegroundColor = ConsoleColor.Cyan;
Console.WriteLine(Utilities.StripColors(Message));
Console.ForegroundColor = ConsoleColor.Gray;
}
if (CommandResult.Count > 15)
CommandResult.RemoveAt(0);
CommandResult.Add(new CommandResponseInfo()
{
Response = Utilities.StripColors(Message),
ClientId = Target.ClientId
});
}
/// <summary>
/// Send a message to all admins on the server
/// </summary>
/// <param name="message">Message to send out</param>
public async Task ToAdmins(String message)
{
foreach (Player P in Players)
{
if (P == null)
continue;
if (P.Level > Player.Permission.Flagged)
await P.Tell(message);
}
}
/// <summary>
/// Kick a player from the server
/// </summary>
/// <param name="Reason">Reason for kicking</param>
/// <param name="Target">Player to kick</param>
abstract public Task Kick(String Reason, Player Target, Player Origin);
/// <summary>
/// Temporarily ban a player ( default 1 hour ) from the server
/// </summary>
/// <param name="Reason">Reason for banning the player</param>
/// <param name="Target">The player to ban</param>
abstract public Task TempBan(String Reason, TimeSpan length, Player Target, Player Origin);
/// <summary>
/// Perm ban a player from the server
/// </summary>
/// <param name="Reason">The reason for the ban</param>
/// <param name="Target">The person to ban</param>
/// <param name="Origin">The person who banned the target</param>
abstract public Task Ban(String Reason, Player Target, Player Origin);
abstract public Task Warn(String Reason, Player Target, Player Origin);
/// <summary>
/// Unban a player by npID / GUID
/// </summary>
/// <param name="npID">npID of the player</param>
/// <param name="Target">I don't remember what this is for</param>
/// <returns></returns>
abstract public Task Unban(string reason, Player Target, Player Origin);
/// <summary>
/// Change the current searver map
/// </summary>
/// <param name="mapName">Non-localized map name</param>
public async Task LoadMap(string mapName)
{
await this.ExecuteCommandAsync($"map {mapName}");
}
public async Task LoadMap(Map newMap)
{
await this.ExecuteCommandAsync($"map {newMap.Name}");
}
/// <summary>
/// Initalize the macro variables
/// </summary>
abstract public void InitializeTokens();
/// <summary>
/// Read the map configuration
/// </summary>
protected void InitializeMaps()
{
Maps = new List<Map>();
var gameMaps = Manager.GetApplicationSettings().Configuration().Maps.FirstOrDefault(m => m.Game == GameName);
if (gameMaps != null)
Maps.AddRange(gameMaps.Maps);
}
/// <summary>
/// Initialize the messages to be broadcasted
/// </summary>
protected void InitializeAutoMessages()
{
BroadcastMessages = new List<String>();
if(ServerConfig.AutoMessages != null)
BroadcastMessages.AddRange(ServerConfig.AutoMessages);
BroadcastMessages.AddRange(Manager.GetApplicationSettings().Configuration().AutoMessages);
}
public override string ToString()
{
return $"{IP}_{Port}";
}
protected async Task<bool> ScriptLoaded()
{
try
{
return (await this.GetDvarAsync<string>("sv_customcallbacks")).Value == "1";
}
catch (Exceptions.DvarException)
{
return false;
}
}
// Objects
public Interfaces.IManager Manager { get; protected set; }
public Interfaces.ILogger Logger { get; private set; }
public ServerConfiguration ServerConfig { get; private set; }
public List<Map> Maps { get; protected set; }
public List<Report> Reports { get; set; }
public List<ChatInfo> ChatHistory { get; protected set; }
public Queue<PlayerHistory> PlayerHistory { get; private set; }
public Game GameName { get; protected set; }
// Info
public string Hostname { get; protected set; }
public string Website { get; protected set; }
public string Gametype { get; protected set; }
public Map CurrentMap { get; protected set; }
public int ClientNum
{
get
{
return Players.Where(p => p != null).Count();
}
}
public int MaxClients { get; protected set; }
public List<Player> Players { get; protected set; }
public string Password { get; private set; }
public bool Throttled { get; protected set; }
public bool CustomCallback { get; protected set; }
public string WorkingDirectory { get; protected set; }
public RCon.Connection RemoteConnection { get; protected set; }
// Internal
protected string IP;
protected int Port;
protected string FSGame;
protected int NextMessage;
protected int ConnectionErrors;
protected List<string> BroadcastMessages;
protected TimeSpan LastMessage;
protected IFile LogFile;
protected DateTime LastPoll;
// only here for performance
private bool CustomSayEnabled;
private string CustomSayName;
//Remote
public IList<CommandResponseInfo> CommandResult = new List<CommandResponseInfo>();
}
}

View File

@ -0,0 +1,103 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Linq.Expressions;
using SharedLibraryCore.Interfaces;
using SharedLibraryCore.Database.Models;
using SharedLibraryCore.Database;
using Microsoft.EntityFrameworkCore;
namespace SharedLibraryCore.Services
{
public class AliasService : IEntityService<EFAlias>
{
public async Task<EFAlias> Create(EFAlias entity)
{
throw await Task.FromResult(new Exception());
/*using (var context = new DatabaseContext())
{
var alias = new EFAlias()
{
Active = true,
DateAdded = DateTime.UtcNow,
IPAddress = entity.IPAddress,
Name = entity.Name
};
entity.Link = await context.AliasLinks
.FirstAsync(a => a.AliasLinkId == entity.Link.AliasLinkId);
context.Aliases.Add(entity);
await context.SaveChangesAsync();
return entity;
}*/
}
public Task<EFAlias> CreateProxy()
{
return null;
}
public async Task<EFAlias> Delete(EFAlias entity)
{
using (var context = new DatabaseContext())
{
var alias = context.Aliases
.Single(e => e.AliasId == entity.AliasId);
alias.Active = false;
await context.SaveChangesAsync();
return entity;
}
}
public async Task<IList<EFAlias>> Find(Func<EFAlias, bool> expression)
{
return await Task.Run(() =>
{
using (var context = new DatabaseContext())
return context.Aliases
.AsNoTracking()
.Include(a => a.Link.Children)
.Where(expression)
.ToList();
});
}
public async Task<EFAlias> Get(int entityID)
{
using (var context = new DatabaseContext())
return await context.Aliases
.AsNoTracking()
.SingleOrDefaultAsync(e => e.AliasId == entityID);
}
public Task<EFAlias> GetUnique(long entityProperty)
{
throw new NotImplementedException();
}
public async Task<EFAlias> Update(EFAlias entity)
{
throw await Task.FromResult(new Exception());
/*using (var context = new DatabaseContext())
{
entity = context.Aliases.Attach(entity);
context.Entry(entity).State = EntityState.Modified;
await context.SaveChangesAsync();
return entity;
}*/
}
public async Task<EFAliasLink> CreateLink(EFAliasLink link)
{
using (var context = new DatabaseContext())
{
context.AliasLinks.Add(link);
await context.SaveChangesAsync();
return link;
}
}
}
}

View File

@ -0,0 +1,316 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using SharedLibraryCore.Database;
using SharedLibraryCore.Database.Models;
using System.Linq.Expressions;
using SharedLibraryCore.Objects;
using Microsoft.EntityFrameworkCore;
namespace SharedLibraryCore.Services
{
public class ClientService : Interfaces.IEntityService<EFClient>
{
public async Task<EFClient> Create(EFClient entity)
{
using (var context = new DatabaseContext())
{
bool hasExistingAlias = false;
// get all aliases by IP
var aliases = await context.Aliases
.Include(a => a.Link)
.Where(a => a.IPAddress == entity.IPAddress)
.ToListAsync();
// see if they have a matching IP + Name but new NetworkId
var existingAlias = aliases.FirstOrDefault(a => a.Name == entity.Name);
// if existing alias matches link them
EFAliasLink aliasLink = existingAlias?.Link;
// if no exact matches find the first IP that matches
aliasLink = aliasLink ?? aliases.FirstOrDefault()?.Link;
// if no exact or IP matches, create new link
aliasLink = aliasLink ?? new EFAliasLink()
{
Active = true,
};
// this has to be set here because we can't evalute it properly later
hasExistingAlias = existingAlias != null;
// if no existing alias create new alias
existingAlias = existingAlias ?? new EFAlias()
{
Active = true,
DateAdded = DateTime.UtcNow,
IPAddress = entity.IPAddress,
Link = aliasLink,
Name = entity.Name,
};
var client = new EFClient()
{
Active = true,
// set the level to the level of the existing client if they have the same IP + Name but new NetworkId
// fixme: issues?
Level = hasExistingAlias ?
context.Clients.First(c => c.AliasLinkId == existingAlias.LinkId).Level :
Player.Permission.User,
FirstConnection = DateTime.UtcNow,
Connections = 1,
LastConnection = DateTime.UtcNow,
Masked = false,
NetworkId = entity.NetworkId,
AliasLink = aliasLink,
CurrentAlias = existingAlias,
};
context.Clients.Add(client);
await context.SaveChangesAsync();
return client;
}
}
public async Task<EFClient> Delete(EFClient entity)
{
using (var context = new DatabaseContext())
{
var client = context.Clients
.Single(e => e.ClientId == entity.ClientId);
entity.Active = false;
context.Entry(entity).State = EntityState.Modified;
await context.SaveChangesAsync();
return entity;
}
}
public async Task<IList<EFClient>> Find(Func<EFClient, bool> e)
{
return await Task.Run(() =>
{
using (var context = new DatabaseContext())
{
return context.Clients
.AsNoTracking()
.Include(c => c.CurrentAlias)
.Include(c => c.AliasLink.Children)
.Where(e).ToList();
}
});
}
public async Task<EFClient> Get(int entityID)
{
using (var context = new DatabaseContext())
{
return await new DatabaseContext().Clients
.AsNoTracking()
.Include(c => c.CurrentAlias)
.Include(c => c.AliasLink.Children)
.SingleOrDefaultAsync(e => e.ClientId == entityID);
}
}
public async Task<EFClient> GetUnique(long entityAttribute)
{
using (var context = new DatabaseContext())
{
return await context.Clients
.AsNoTracking()
.Include(c => c.CurrentAlias)
.Include(c => c.AliasLink.Children)
.SingleOrDefaultAsync(c => c.NetworkId == (long)entityAttribute);
}
}
public async Task<EFClient> Update(EFClient entity)
{
using (var context = new DatabaseContext())
{
// grab the context version of the entity
var client = context.Clients
.Include(c => c.AliasLink)
.Include(c => c.CurrentAlias)
.Single(e => e.ClientId == entity.ClientId);
// if their level has been changed
if (entity.Level != client.Level)
{
// get all clients that use the same aliasId
var matchingClients = await context.Clients
.Where(c => c.CurrentAliasId == client.CurrentAliasId)
.ToListAsync();
// update all related clients level
matchingClients.ForEach(c => c.Level = (client.Level == Objects.Player.Permission.Banned) ?
client.Level : entity.Level);
}
// their alias has been updated and not yet saved
if (entity.CurrentAlias.AliasId == 0)
{
client.CurrentAlias = new EFAlias()
{
Active = true,
DateAdded = DateTime.UtcNow,
IPAddress = entity.CurrentAlias.IPAddress,
Name = entity.CurrentAlias.Name,
Link = client.AliasLink
};
}
else
{
client.CurrentAliasId = entity.CurrentAliasId;
}
// set remaining non-navigation properties that may have been updated
client.Level = entity.Level;
client.LastConnection = entity.LastConnection;
client.Connections = entity.Connections;
client.FirstConnection = entity.FirstConnection;
client.Masked = entity.Masked;
client.TotalConnectionTime = entity.TotalConnectionTime;
client.Password = entity.Password;
client.PasswordSalt = entity.PasswordSalt;
// update in database
await context.SaveChangesAsync();
// this is set so future updates don't trigger a new alias add
if (entity.CurrentAlias.AliasId == 0)
entity.CurrentAlias.AliasId = client.CurrentAlias.AliasId;
return client;
}
}
#region ServiceSpecific
public async Task<IList<EFClient>> GetOwners()
{
using (var context = new DatabaseContext())
return await context.Clients
.Where(c => c.Level == Objects.Player.Permission.Owner)
.ToListAsync();
}
public async Task<bool> IsAuthenticated(int clientIP)
{
using (var context = new DatabaseContext())
{
var iqMatching = from alias in context.Aliases
where alias.IPAddress == clientIP
join client in context.Clients
on alias.LinkId equals client.AliasLinkId
where client.Level > Player.Permission.Trusted
select client;
return (await iqMatching.CountAsync()) > 0;
}
}
public async Task<IList<EFClient>> GetPrivilegedClients()
{
using (var context = new DatabaseContext())
{
return await new DatabaseContext().Clients
.AsNoTracking()
.Include(c => c.CurrentAlias)
.Where(c => c.Level >= Player.Permission.Trusted)
.ToListAsync();
}
}
public async Task<IList<EFClient>> GetClientByName(string name)
{
using (var context = new DatabaseContext())
{
var iqClients = (from alias in context.Aliases
.AsNoTracking()
where alias.Name
.Contains(name)
join link in context.AliasLinks
on alias.LinkId equals link.AliasLinkId
join client in context.Clients
.AsNoTracking()
on alias.LinkId equals client.AliasLinkId
select client)
.Distinct()
.Include(c => c.CurrentAlias)
.Include(c => c.AliasLink.Children);
return await iqClients.ToListAsync();
}
}
public async Task<IList<EFClient>> GetClientByIP(int ipAddress)
{
using (var context = new DatabaseContext())
{
var iqClients = (from alias in context.Aliases
.AsNoTracking()
where alias.IPAddress == ipAddress
join link in context.AliasLinks
on alias.LinkId equals link.AliasLinkId
join client in context.Clients
.AsNoTracking()
on alias.LinkId equals client.AliasLinkId
select client)
.Distinct()
.Include(c => c.CurrentAlias)
.Include(c => c.AliasLink.Children);
return await iqClients.ToListAsync();
}
}
public async Task<IList<EFClient>> GetRecentClients(int offset, int count)
{
using (var context = new DatabaseContext())
return await context.Clients
.AsNoTracking()
.Include(c => c.CurrentAlias)
.Include(p => p.AliasLink)
.OrderByDescending(p => p.ClientId)
.Skip(offset)
.Take(count)
.ToListAsync();
}
public async Task<IList<EFClient>> PruneInactivePrivilegedClients(int inactiveDays)
{
using (var context = new DatabaseContext())
{
var inactive = await context.Clients.Where(c => c.Level > Objects.Player.Permission.Flagged)
.AsNoTracking()
.Where(c => (DateTime.UtcNow - c.LastConnection).TotalDays >= inactiveDays)
.ToListAsync();
inactive.ForEach(c => c.Level = Objects.Player.Permission.User);
await context.SaveChangesAsync();
return inactive;
}
}
public async Task<int> GetTotalClientsAsync()
{
using (var context = new DatabaseContext())
return await context.Clients
.CountAsync();
}
public Task<EFClient> CreateProxy()
{
throw new NotImplementedException();
}
public async Task<int> GetTotalPlayTime()
{
using (var context = new DatabaseContext())
return await context.Clients.SumAsync(c => c.TotalConnectionTime);
}
#endregion
}
}

View File

@ -0,0 +1,146 @@
using Microsoft.EntityFrameworkCore;
using SharedLibraryCore.Database;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Text;
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 dynamic _context;
private DbSet<TEntity> _dbSet;
protected DbContext Context
{
get
{
if (_context == null)
{
_context = new DatabaseContext();
}
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;
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

@ -0,0 +1,25 @@
using SharedLibraryCore.Dtos;
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
namespace SharedLibraryCore.Services
{
public class MetaService
{
private static List<Func<int, Task<List<ProfileMeta>>>> MetaActions = new List<Func<int, Task<List<ProfileMeta>>>>();
public static void AddMeta(Func<int, Task<List<ProfileMeta>>> metaAction)
{
MetaActions.Add(metaAction);
}
public static async Task<List<ProfileMeta>> GetMeta(int clientId)
{
var meta = new List<ProfileMeta>();
foreach (var action in MetaActions)
meta.AddRange(await action(clientId));
return meta;
}
}
}

View File

@ -0,0 +1,239 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using SharedLibraryCore.Database;
using SharedLibraryCore.Database.Models;
using System.Linq.Expressions;
using SharedLibraryCore.Dtos;
using Microsoft.EntityFrameworkCore;
namespace SharedLibraryCore.Services
{
public class PenaltyService : Interfaces.IEntityService<EFPenalty>
{
public async Task<EFPenalty> Create(EFPenalty entity)
{
using (var context = new DatabaseContext())
{
entity.Offender = context.Clients.Single(e => e.ClientId == entity.Offender.ClientId);
entity.Punisher = context.Clients.Single(e => e.ClientId == entity.Punisher.ClientId);
entity.Link = context.AliasLinks.Single(l => l.AliasLinkId == entity.Link.AliasLinkId);
if (entity.Expires == DateTime.MaxValue)
entity.Expires = DateTime.Parse(System.Data.SqlTypes.SqlDateTime.MaxValue.ToString());
// make bans propogate to all aliases
if (entity.Type == Objects.Penalty.PenaltyType.Ban)
{
await context.Clients
.Where(c => c.AliasLinkId == entity.Link.AliasLinkId)
.ForEachAsync(c => c.Level = Objects.Player.Permission.Banned);
}
// make flags propogate to all aliases
else if (entity.Type == Objects.Penalty.PenaltyType.Flag)
{
await context.Clients
.Where(c => c.AliasLinkId == entity.Link.AliasLinkId)
.ForEachAsync(c => c.Level = Objects.Player.Permission.Flagged);
}
context.Penalties.Add(entity);
await context.SaveChangesAsync();
return entity;
}
}
public Task<EFPenalty> CreateProxy()
{
throw new NotImplementedException();
}
public Task<EFPenalty> Delete(EFPenalty entity)
{
throw new NotImplementedException();
}
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)
{
throw new NotImplementedException();
}
public Task<EFPenalty> GetUnique(long entityProperty)
{
throw new NotImplementedException();
}
public Task<EFPenalty> Update(EFPenalty entity)
{
throw new NotImplementedException();
}
public async Task<IList<EFPenalty>> GetRecentPenalties(int count, int offset)
{
using (var context = new DatabaseContext())
return await context.Penalties
.AsNoTracking()
.Include(p => p.Offender.CurrentAlias)
.Include(p => p.Punisher.CurrentAlias)
.Where(p => p.Active)
.OrderByDescending(p => p.When)
.Skip(offset)
.Take(count)
.ToListAsync();
}
public async Task<IList<EFPenalty>> GetClientPenaltiesAsync(int clientId)
{
using (var context = new DatabaseContext())
return await context.Penalties
.AsNoTracking()
.Where(p => p.OffenderId == clientId)
.Where(p => p.Active)
.Include(p => p.Offender.CurrentAlias)
.Include(p => p.Punisher.CurrentAlias)
.ToListAsync();
}
/// <summary>
/// Get a read-only copy of client penalties
/// </summary>
/// <param name="clientI"></param>
/// <param name="victim">Retreive penalties for clients receiving penalties, other wise given</param>
/// <returns></returns>
public async Task<List<ProfileMeta>> ReadGetClientPenaltiesAsync(int clientId, bool victim = true)
{
using (var context = new DatabaseContext())
{
/*context.Configuration.LazyLoadingEnabled = false;
context.Configuration.ProxyCreationEnabled = false;
context.Configuration.AutoDetectChangesEnabled = false;*/
if (victim)
{
var iqPenalties = from penalty in context.Penalties.AsNoTracking()
where penalty.OffenderId == clientId
join victimClient in context.Clients.AsNoTracking()
on penalty.OffenderId equals victimClient.ClientId
join victimAlias in context.Aliases
on victimClient.CurrentAliasId equals victimAlias.AliasId
join punisherClient in context.Clients
on penalty.PunisherId equals punisherClient.ClientId
join punisherAlias in context.Aliases
on punisherClient.CurrentAliasId equals punisherAlias.AliasId
//orderby penalty.When descending
select new ProfileMeta()
{
Key = "Event.Penalty",
Value = new PenaltyInfo
{
OffenderName = victimAlias.Name,
OffenderId = victimClient.ClientId,
PunisherName = punisherAlias.Name,
PunisherId = penalty.PunisherId,
Offense = penalty.Offense,
Type = penalty.Type.ToString()
},
When = penalty.When,
Sensitive = penalty.Type == Objects.Penalty.PenaltyType.Flag
};
// fixme: is this good and fast?
return await iqPenalties.ToListAsync();
}
else
{
var iqPenalties = from penalty in context.Penalties.AsNoTracking()
where penalty.PunisherId == clientId
join victimClient in context.Clients.AsNoTracking()
on penalty.OffenderId equals victimClient.ClientId
join victimAlias in context.Aliases
on victimClient.CurrentAliasId equals victimAlias.AliasId
join punisherClient in context.Clients
on penalty.PunisherId equals punisherClient.ClientId
join punisherAlias in context.Aliases
on punisherClient.CurrentAliasId equals punisherAlias.AliasId
//orderby penalty.When descending
select new ProfileMeta()
{
Key = "Event.Penalty",
Value = new PenaltyInfo
{
OffenderName = victimAlias.Name,
OffenderId = victimClient.ClientId,
PunisherName = punisherAlias.Name,
PunisherId = penalty.PunisherId,
Offense = penalty.Offense,
Type = penalty.Type.ToString()
},
When = penalty.When
};
// fixme: is this good and fast?
return await iqPenalties.ToListAsync();
}
}
}
public async Task<List<EFPenalty>> GetActivePenaltiesAsync(int aliasId)
{
using (var context = new DatabaseContext())
{
var iqPenalties = from link in context.AliasLinks
where link.AliasLinkId == aliasId
join penalty in context.Penalties
on link.AliasLinkId equals penalty.LinkId
where penalty.Active
select penalty;
return await iqPenalties.ToListAsync();
}
}
public async Task RemoveActivePenalties(int aliasLinkId)
{
using (var context = new DatabaseContext())
{
var now = DateTime.UtcNow;
var penalties = await context.Penalties
.Include(p => p.Link.Children)
.Where(p => p.LinkId == aliasLinkId)
.Where(p => p.Expires > now)
.ToListAsync();
penalties.ForEach(async p =>
{
p.Active = false;
// reset the player levels
if (p.Type == Objects.Penalty.PenaltyType.Ban)
await context.Clients
.Where(c => c.AliasLinkId == p.LinkId)
.ForEachAsync(c => c.Level = Objects.Player.Permission.User);
});
await context.SaveChangesAsync();
}
}
}
}

View File

@ -0,0 +1,46 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Library</OutputType>
<TargetFramework>netcoreapp2.0</TargetFramework>
<ApplicationIcon />
<StartupObject />
<PackageId>RaidMax.IW4MAdmin.SharedLibraryCore</PackageId>
<Version>2.0.0</Version>
<Authors>RaidMax</Authors>
<Company>Forever None</Company>
</PropertyGroup>
<ItemGroup>
<None Remove="LibSQLCe\x86\Microsoft.VC90.CRT\msvcr90.dll" />
<None Remove="LibSQLCe\x86\Microsoft.VC90.CRT\README_ENU.txt" />
<None Remove="LibSQLCe\x86\sqlceca40.dll" />
<None Remove="LibSQLCe\x86\sqlcecompact40.dll" />
<None Remove="LibSQLCe\x86\sqlceer40EN.dll" />
<None Remove="LibSQLCe\x86\sqlceme40.dll" />
<None Remove="LibSQLCe\x86\sqlceqp40.dll" />
<None Remove="LibSQLCe\x86\sqlcese40.dll" />
</ItemGroup>
<ItemGroup>
<Content Include="LibSQLCe\x86\Microsoft.VC90.CRT\msvcr90.dll" />
<Content Include="LibSQLCe\x86\Microsoft.VC90.CRT\README_ENU.txt" />
<Content Include="LibSQLCe\x86\sqlceca40.dll" />
<Content Include="LibSQLCe\x86\sqlcecompact40.dll" />
<Content Include="LibSQLCe\x86\sqlceer40EN.dll" />
<Content Include="LibSQLCe\x86\sqlceme40.dll" />
<Content Include="LibSQLCe\x86\sqlceqp40.dll" />
<Content Include="LibSQLCe\x86\sqlcese40.dll" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="2.0.2" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="2.0.2" />
<PackageReference Include="Microsoft.Extensions.Configuration" Version="2.0.1" />
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="2.0.1" />
<PackageReference Include="Microsoft.Extensions.Options.ConfigurationExtensions" Version="2.0.1" />
<PackageReference Include="Newtonsoft.Json" Version="11.0.2" />
<PackageReference Include="SimpleCrypto.NetCore" Version="1.0.0" />
</ItemGroup>
</Project>

View File

@ -0,0 +1,439 @@
using System;
using System.Threading;
using System.Text;
using System.Text.RegularExpressions;
using System.Linq;
using System.Collections.Generic;
using SharedLibraryCore.Objects;
using static SharedLibraryCore.Server;
using System.Reflection;
using System.IO;
using System.Diagnostics;
using System.Threading.Tasks;
using static SharedLibraryCore.RCon.StaticHelpers;
using System.Runtime.InteropServices;
namespace SharedLibraryCore
{
public static class Utilities
{
public static string OperatingDirectory = Path.GetDirectoryName(Assembly.GetEntryAssembly().Location) + Path.DirectorySeparatorChar;
public static readonly Task CompletedTask = Task.FromResult(false);
//Get string with specified number of spaces -- really only for visual output
public static String GetSpaces(int Num)
{
String SpaceString = String.Empty;
while (Num > 0)
{
SpaceString += ' ';
Num--;
}
return SpaceString;
}
//Remove words from a space delimited string
public static String RemoveWords(this string str, int num)
{
if (str == null || str.Length == 0)
return "";
String newStr = String.Empty;
String[] tmp = str.Split(' ');
for (int i = 0; i < tmp.Length; i++)
{
if (i >= num)
newStr += tmp[i] + ' ';
}
return newStr;
}
public static List<Player> PlayersFromStatus(string[] Status)
{
List<Player> StatusPlayers = new List<Player>();
foreach (String S in Status)
{
String responseLine = S.Trim();
if (Regex.Matches(responseLine, @"\d+$", RegexOptions.IgnoreCase).Count > 0 && responseLine.Length > 72) // its a client line!
{
String[] playerInfo = responseLine.Split(new char[] { ' ' }, StringSplitOptions.RemoveEmptyEntries);
int cID = -1;
int Ping = -1;
Int32.TryParse(playerInfo[2], out Ping);
String cName = Encoding.UTF8.GetString(Encoding.Convert(Encoding.UTF7, Encoding.UTF8, Encoding.UTF7.GetBytes(StripColors(responseLine.Substring(46, 18)).Trim())));
long npID = Regex.Match(responseLine, @"([a-z]|[0-9]){16}", RegexOptions.IgnoreCase).Value.ConvertLong();
int.TryParse(playerInfo[0], out cID);
var regex = Regex.Match(responseLine, @"\d+\.\d+\.\d+.\d+\:\d{1,5}");
#if DEBUG
Ping = 1;
#endif
int cIP = regex.Value.Split(':')[0].ConvertToIP();
regex = Regex.Match(responseLine, @"[0-9]{1,2}\s+[0-9]+\s+");
int score = Int32.Parse(regex.Value.Split(new char[] { ' ' }, StringSplitOptions.RemoveEmptyEntries)[1]);
Player P = new Player() { Name = cName, NetworkId = npID, ClientNumber = cID, IPAddress = cIP, Ping = Ping, Score = score };
StatusPlayers.Add(P);
}
}
return StatusPlayers;
}
public static Player.Permission MatchPermission(String str)
{
String lookingFor = str.ToLower();
for (Player.Permission Perm = Player.Permission.User; Perm < Player.Permission.Console; Perm++)
if (lookingFor.Contains(Perm.ToString().ToLower()))
return Perm;
return Player.Permission.Banned;
}
/// <summary>
/// Remove all IW Engine color codes
/// </summary>
/// <param name="str">String containing color codes</param>
/// <returns></returns>
public static String StripColors(this string str)
{
if (str == null)
return "";
str = Regex.Replace(str, @"(\^+((?![a-z]|[A-Z]).){0,1})+", "");
string str2 = Regex.Match(str, @"(^\/+.*$)|(^.*\/+$)")
.Value
.Replace("/", " /");
return str2.Length > 0 ? str2 : str;
}
/// <summary>
/// Get the IW Engine color code corresponding to an admin level
/// </summary>
/// <param name="level">Specified player level</param>
/// <returns></returns>
public static String ConvertLevelToColor(Player.Permission level)
{
switch (level)
{
case Player.Permission.Banned:
return "^1" + Player.Permission.Banned;
case Player.Permission.Flagged:
return "^9" + Player.Permission.Flagged;
case Player.Permission.Owner:
return "^5" + Player.Permission.Owner;
case Player.Permission.User:
return "^2" + Player.Permission.User;
case Player.Permission.Trusted:
return "^3" + Player.Permission.Trusted;
default:
return "^6" + level;
}
}
public static String ProcessMessageToken(IList<Helpers.MessageToken> tokens, String str)
{
MatchCollection RegexMatches = Regex.Matches(str, @"\{\{[A-Z]+\}\}", RegexOptions.IgnoreCase);
foreach (Match M in RegexMatches)
{
String Match = M.Value;
String Identifier = M.Value.Substring(2, M.Length - 4);
var found = tokens.FirstOrDefault(t => t.Name.ToLower() == Identifier.ToLower());
if (found != null)
str = str.Replace(Match, found.ToString());
}
return str;
}
public static bool IsBroadcastCommand(this string str)
{
return str[0] == '@';
}
/// <summary>
/// Get the full gametype name
/// </summary>
/// <param name="input">Shorthand gametype reported from server</param>
/// <returns></returns>
public static String GetLocalizedGametype(String input)
{
switch (input)
{
case "dm":
return "Deathmatch";
case "war":
return "Team Deathmatch";
case "koth":
return "Headquarters";
case "ctf":
return "Capture The Flag";
case "dd":
return "Demolition";
case "dom":
return "Domination";
case "sab":
return "Sabotage";
case "sd":
return "Search & Destroy";
case "vip":
return "Very Important Person";
case "gtnw":
return "Global Thermonuclear War";
case "oitc":
return "One In The Chamber";
case "arena":
return "Arena";
case "dzone":
return "Drop Zone";
case "gg":
return "Gun Game";
case "snipe":
return "Sniping";
case "ss":
return "Sharp Shooter";
case "m40a3":
return "M40A3";
case "fo":
return "Face Off";
case "dmc":
return "Deathmatch Classic";
case "killcon":
return "Kill Confirmed";
case "oneflag":
return "One Flag CTF";
default:
return input;
}
}
public static long ConvertLong(this string str)
{
try
{
return Int64.Parse(str, System.Globalization.NumberStyles.HexNumber);
}
catch (FormatException)
{
return -1;
}
}
public static int ConvertToIP(this string str)
{
try
{
return BitConverter.ToInt32(System.Net.IPAddress.Parse(str).GetAddressBytes(), 0);
}
catch (FormatException)
{
return 0;
}
}
public static string ConvertIPtoString(this int ip)
{
return new System.Net.IPAddress(BitConverter.GetBytes(ip)).ToString();
}
public static String GetTimePassed(DateTime start)
{
return GetTimePassed(start, true);
}
public static String GetTimePassed(DateTime start, bool includeAgo)
{
TimeSpan Elapsed = DateTime.UtcNow - start;
string ago = includeAgo ? " ago" : "";
if (Elapsed.TotalSeconds < 30 && includeAgo)
return "just now";
if (Elapsed.TotalMinutes < 120)
{
if (Elapsed.TotalMinutes < 1.5)
return $"1 minute{ago}";
return Math.Round(Elapsed.TotalMinutes, 0) + $" minutes{ago}";
}
if (Elapsed.TotalHours <= 24)
{
if (Elapsed.TotalHours < 1.5)
return $"1 hour{ago}";
return Math.Round(Elapsed.TotalHours, 0) + $" hours{ago}";
}
if (Elapsed.TotalDays <= 365)
{
if (Elapsed.TotalDays < 1.5)
return $"1 day{ago}";
return Math.Round(Elapsed.TotalDays, 0) + $" days{ago}";
}
else
return $"a very long time{ago}";
}
public static Game GetGame(string gameName)
{
if (gameName.Contains("IW4"))
return Game.IW4;
if (gameName.Contains("CoD4"))
return Game.IW3;
if (gameName.Contains("WaW"))
return Game.T4;
if (gameName.Contains("COD_T5_S"))
return Game.T5;
if (gameName.Contains("T5M"))
return Game.T5M;
if (gameName.Contains("IW5"))
return Game.IW5;
return Game.UKN;
}
public static TimeSpan ParseTimespan(this string input)
{
var expressionMatch = Regex.Match(input, @"[0-9]+.\b");
if (!expressionMatch.Success) // fallback to default tempban length of 1 hour
return new TimeSpan(1, 0, 0);
char lengthDenote = expressionMatch.Value[expressionMatch.Value.Length - 1];
int length = Int32.Parse(expressionMatch.Value.Substring(0, expressionMatch.Value.Length - 1));
switch (lengthDenote)
{
case 'm':
return new TimeSpan(0, length, 0);
case 'h':
return new TimeSpan(length, 0, 0);
case 'd':
return new TimeSpan(length, 0, 0, 0);
case 'w':
return new TimeSpan(length * 7, 0, 0, 0);
case 'y':
return new TimeSpan(length * 365, 0, 0, 0);
default:
return new TimeSpan(1, 0, 0);
}
}
public static string TimeSpanText(this TimeSpan span)
{
if (span.TotalMinutes < 60)
return $"{span.Minutes} minute(s)";
else if (span.Hours >= 1 && span.TotalHours < 24)
return $"{span.Hours} hour(s)";
else if (span.TotalDays >= 1 && span.TotalDays < 7)
return $"{span.Days} day(s)";
else if (span.TotalDays >= 7 && span.TotalDays < 365)
return $"{Math.Ceiling(span.Days / 7.0)} week(s)";
else if (span.TotalDays >= 365 && span.TotalDays < 36500)
return $"{Math.Ceiling(span.Days / 365.0)} year(s)";
else if (span.TotalDays >= 36500)
return "Forever";
return "1 hour";
}
public static Player AsPlayer(this Database.Models.EFClient client)
{
return client == null ? null : new Player()
{
Active = client.Active,
AliasLink = client.AliasLink,
AliasLinkId = client.AliasLinkId,
ClientId = client.ClientId,
ClientNumber = -1,
FirstConnection = client.FirstConnection,
Connections = client.Connections,
NetworkId = client.NetworkId,
TotalConnectionTime = client.TotalConnectionTime,
Masked = client.Masked,
Name = client.CurrentAlias.Name,
IPAddress = client.CurrentAlias.IPAddress,
Level = client.Level,
LastConnection = DateTime.UtcNow,
CurrentAlias = client.CurrentAlias,
CurrentAliasId = client.CurrentAlias.AliasId
};
}
public static bool IsPrivileged(this Player p) => p.Level > Player.Permission.User;
public static bool PromptBool(string question)
{
Console.Write($"{question}? [y/n]: ");
return (Console.ReadLine().ToLower().FirstOrDefault() as char?) == 'y';
}
public static string PromptString(string question)
{
Console.Write($"{question}: ");
string response;
do
{
response = Console.ReadLine();
} while (string.IsNullOrWhiteSpace(response));
return response;
}
public static async Task<DVAR<T>> GetDvarAsync<T>(this Server server, string dvarName)
{
string[] LineSplit = await server.RemoteConnection.SendQueryAsync(QueryType.DVAR, dvarName);
if (LineSplit.Length != 3)
{
var e = new Exceptions.DvarException($"DVAR \"{dvarName}\" does not exist");
e.Data["dvar_name"] = dvarName;
throw e;
}
string[] ValueSplit = LineSplit[1].Split(new char[] { '"' }, StringSplitOptions.RemoveEmptyEntries);
if (ValueSplit.Length != 5)
{
var e = new Exceptions.DvarException($"DVAR \"{dvarName}\" does not exist");
e.Data["dvar_name"] = dvarName;
throw e;
}
string DvarName = Regex.Replace(ValueSplit[0], @"\^[0-9]", "");
string DvarCurrentValue = Regex.Replace(ValueSplit[2], @"\^[0-9]", "");
string DvarDefaultValue = Regex.Replace(ValueSplit[4], @"\^[0-9]", "");
return new DVAR<T>(DvarName) { Value = (T)Convert.ChangeType(DvarCurrentValue, typeof(T)) };
}
public static async Task SetDvarAsync(this Server server, string dvarName, object dvarValue)
{
await server.RemoteConnection.SendQueryAsync(QueryType.DVAR, $"set {dvarName} {dvarValue}");
}
public static async Task<string[]> ExecuteCommandAsync(this Server server, string commandName)
{
return (await server.RemoteConnection.SendQueryAsync(QueryType.COMMAND, commandName)).Skip(1).ToArray();
}
public static async Task<List<Player>> GetStatusAsync(this Server server)
{
#if DEBUG && DEBUG_PLAYERS
string[] response = await Task.Run(() => System.IO.File.ReadAllLines("players.txt"));
#else
string[] response = await server.RemoteConnection.SendQueryAsync(QueryType.DVAR, "status");
#endif
return Utilities.PlayersFromStatus(response);
}
public static bool IsRunningOnMono() => Type.GetType("Mono.Runtime") != null;
}
}