the meats

This commit is contained in:
RaidMax 2018-03-06 01:22:19 -06:00
parent 6fa466fdf8
commit 1adf3ceb3c
56 changed files with 1107 additions and 719 deletions

4
.gitignore vendored
View File

@ -1,6 +1,10 @@
## Ignore Visual Studio temporary files, build results, and ## Ignore Visual Studio temporary files, build results, and
## files generated by popular Visual Studio add-ons. ## files generated by popular Visual Studio add-ons.
Detection.cs
DetectionPenaltyResult.cs
Thresholds.cs
# User-specific files # User-specific files
*.suo *.suo
*.user *.user

View File

@ -1,90 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProjectGuid>{D076ABC9-DDD6-4E30-9584-E45273950902}</ProjectGuid>
<OutputType>Library</OutputType>
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>Database</RootNamespace>
<AssemblyName>Database</AssemblyName>
<TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<OutputPath>bin\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<DebugType>pdbonly</DebugType>
<Optimize>true</Optimize>
<OutputPath>bin\Release\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release-Nightly|AnyCPU'">
<OutputPath>bin\Release-Nightly\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<Optimize>true</Optimize>
<DebugType>pdbonly</DebugType>
<PlatformTarget>AnyCPU</PlatformTarget>
<ErrorReport>prompt</ErrorReport>
<CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release-Stable|AnyCPU'">
<OutputPath>bin\Release-Stable\</OutputPath>
</PropertyGroup>
<ItemGroup>
<Reference Include="EntityFramework, Version=6.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089, processorArchitecture=MSIL">
<HintPath>..\packages\EntityFramework.6.2.0\lib\net45\EntityFramework.dll</HintPath>
</Reference>
<Reference Include="EntityFramework.SqlServerCompact, Version=6.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089, processorArchitecture=MSIL">
<HintPath>..\packages\EntityFramework.SqlServerCompact.6.2.0\lib\net45\EntityFramework.SqlServerCompact.dll</HintPath>
</Reference>
<Reference Include="System" />
<Reference Include="System.ComponentModel.DataAnnotations" />
<Reference Include="System.Core" />
<Reference Include="System.Data.SqlServerCe, Version=4.0.0.0, Culture=neutral, PublicKeyToken=89845dcd8080cc91, processorArchitecture=MSIL">
<HintPath>..\packages\Microsoft.SqlServer.Compact.4.0.8876.1\lib\net40\System.Data.SqlServerCe.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="System.Xml.Linq" />
<Reference Include="System.Data.DataSetExtensions" />
<Reference Include="Microsoft.CSharp" />
<Reference Include="System.Data" />
<Reference Include="System.Net.Http" />
<Reference Include="System.Xml" />
</ItemGroup>
<ItemGroup>
<Compile Include="IW4MAdminDatabaseContext.cs" />
<Compile Include="IW4MAdminDatabase.cs" />
<Compile Include="Models\Alias.cs" />
<Compile Include="Models\Client.cs" />
<Compile Include="Models\Penalty.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\SharedLibrary\SharedLibrary.csproj">
<Project>{d51eeceb-438a-47da-870f-7d7b41bc24d6}</Project>
<Name>SharedLibrary</Name>
</ProjectReference>
</ItemGroup>
<ItemGroup>
<None Include="packages.config" />
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<PropertyGroup>
<PostBuildEvent>
if not exist "$(TargetDir)x86" md "$(TargetDir)x86"
xcopy /s /y "$(SolutionDir)packages\Microsoft.SqlServer.Compact.4.0.8876.1\NativeBinaries\x86\*.*" "$(TargetDir)x86"
if not exist "$(TargetDir)amd64" md "$(TargetDir)amd64"
xcopy /s /y "$(SolutionDir)packages\Microsoft.SqlServer.Compact.4.0.8876.1\NativeBinaries\amd64\*.*" "$(TargetDir)amd64"</PostBuildEvent>
</PropertyGroup>
</Project>

View File

@ -1,38 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Database.Models;
namespace Database
{
public class IW4MAdminDatabase : SharedLibrary.Interfaces.IDatabase
{
private IW4MAdminDatabaseContext _context;
public IW4MAdminDatabase()
{
_context = new IW4MAdminDatabaseContext();
}
public SharedLibrary.Interfaces.IDatabaseContext GetContext() => _context;
public async Task<Client> AddClient(Client newClient)
{
var client = _context.Clients.Add(newClient);
await _context.SaveChangesAsync();
return client;
}
public Client GetClient(int clientID) => _context.Clients.SingleOrDefault(c => c.ClientId == clientID);
public Client GetClient(string networkID) => _context.Clients.SingleOrDefault(c => c.NetworkId == networkID);
public IList<Client> GetOwners() => _context.Clients.Where(c => c.Level == SharedLibrary.Player.Permission.Owner).ToList();
public IList<SharedLibrary.Player> GetPlayers(IList<string> networkIDs) => _context.Clients.Where(c => networkIDs.Contains(c.NetworkId)).Select(c => c.ToPlayer()).ToList();
public IList<Penalty> GetPenalties (int clientID) => _context.Penalties.Where(p => p.OffenderId == clientID).ToList();
public IList<SharedLibrary.Player> GetAdmins() => _context.Clients.Where(c => c.Level > SharedLibrary.Player.Permission.Flagged).Select(c => c.ToPlayer()).ToList();
}
}

View File

@ -1,27 +0,0 @@
using System;
using System.Collections.Generic;
using System.Data.Entity;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Database.Models;
using System.Data.SqlServerCe;
namespace Database
{
public class IW4MAdminDatabaseContext : DbContext, SharedLibrary.Interfaces.IDatabaseContext
{
public DbSet<Client> Clients { get; set; }
public DbSet<Alias> Aliases { get; set; }
public DbSet<Penalty> Penalties { get; set; }
public IW4MAdminDatabaseContext() : base("DefaultConnection") { }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
var instance = System.Data.Entity.SqlServerCompact.SqlCeProviderServices.Instance;
// todo custom load DBSets from plugins
// https://aleemkhan.wordpress.com/2013/02/28/dynamically-adding-dbset-properties-in-dbcontext-for-entity-framework-code-first/
base.OnModelCreating(modelBuilder);
}
}
}

View File

@ -1,18 +0,0 @@
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Database.Models
{
public class Alias
{
[Key]
public int AliasId { get; set; }
public int ClientId { get; set; }
public string Name { get; set; }
public string IP { get; set; }
}
}

View File

@ -1,43 +0,0 @@
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Database.Models
{
public class Client
{
[Key]
public int ClientId { get; set; }
[Required]
public string Name { get; set; }
[Required]
public string NetworkId { get; set; }
[Required]
public SharedLibrary.Player.Permission Level { get; set; }
public int Connections { get; set; }
[Required]
public string IPAddress { get; set; }
[Required]
public DateTime LastConnection { get; set; }
public bool Masked { get; set; }
public SharedLibrary.Player ToPlayer()
{
return new SharedLibrary.Player()
{
Name = Name,
Connections = Connections,
DatabaseID = ClientId,
NetworkID = NetworkId,
Level = Level,
IP = IPAddress,
LastConnection = LastConnection,
Masked = Masked
};
}
}
}

View File

@ -1,23 +0,0 @@
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 Database.Models
{
public class Penalty
{
[Key]
public int PenaltyId { get; set; }
public int OffenderId { get; set; }
public Client Offender { get; set; }
public int PunisherId { get; set; }
public Client Punisher { get; set; }
public DateTime When { get; set; }
public DateTime Expires { get; set; }
public SharedLibrary.Penalty.Type Type { get; set; }
}
}

View File

@ -1,36 +0,0 @@
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
// General Information about an assembly is controlled through the following
// set of attributes. Change these attribute values to modify the information
// associated with an assembly.
[assembly: AssemblyTitle("Database")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("Database")]
[assembly: AssemblyCopyright("Copyright © 2017")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
// Setting ComVisible to false makes the types in this assembly not visible
// to COM components. If you need to access a type in this assembly from
// COM, set the ComVisible attribute to true on that type.
[assembly: ComVisible(false)]
// The following GUID is for the ID of the typelib if this project is exposed to COM
[assembly: Guid("d076abc9-ddd6-4e30-9584-e45273950902")]
// Version information for an assembly consists of the following four values:
//
// Major Version
// Minor Version
// Build Number
// Revision
//
// You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below:
// [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion("1.0.0.0")]
[assembly: AssemblyFileVersion("1.0.0.0")]

View File

@ -1,6 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="EntityFramework" version="6.2.0" targetFramework="net45" />
<package id="EntityFramework.SqlServerCompact" version="6.2.0" targetFramework="net45" />
<package id="Microsoft.SqlServer.Compact" version="4.0.8876.1" targetFramework="net45" />
</packages>

View File

@ -1,7 +1,7 @@
 
Microsoft Visual Studio Solution File, Format Version 12.00 Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 15 # Visual Studio 15
VisualStudioVersion = 15.0.27130.2036 VisualStudioVersion = 15.0.27004.2006
MinimumVisualStudioVersion = 10.0.40219.1 MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StatsPlugin", "Plugins\SimpleStats\StatsPlugin.csproj", "{4785AB75-66F3-4391-985D-63A5A049A0FA}" Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StatsPlugin", "Plugins\SimpleStats\StatsPlugin.csproj", "{4785AB75-66F3-4391-985D-63A5A049A0FA}"
ProjectSection(ProjectDependencies) = postProject ProjectSection(ProjectDependencies) = postProject
@ -320,16 +320,16 @@ Global
{65340D7D-5831-406C-ACAD-B13BA634BDE2}.Release-Nightly|Mixed Platforms.Build.0 = Release|Any CPU {65340D7D-5831-406C-ACAD-B13BA634BDE2}.Release-Nightly|Mixed Platforms.Build.0 = Release|Any CPU
{65340D7D-5831-406C-ACAD-B13BA634BDE2}.Release-Nightly|x64.ActiveCfg = Release|Any CPU {65340D7D-5831-406C-ACAD-B13BA634BDE2}.Release-Nightly|x64.ActiveCfg = Release|Any CPU
{65340D7D-5831-406C-ACAD-B13BA634BDE2}.Release-Nightly|x64.Build.0 = Release|Any CPU {65340D7D-5831-406C-ACAD-B13BA634BDE2}.Release-Nightly|x64.Build.0 = Release|Any CPU
{65340D7D-5831-406C-ACAD-B13BA634BDE2}.Release-Nightly|x86.ActiveCfg = Release|Any CPU {65340D7D-5831-406C-ACAD-B13BA634BDE2}.Release-Nightly|x86.ActiveCfg = Release|x86
{65340D7D-5831-406C-ACAD-B13BA634BDE2}.Release-Nightly|x86.Build.0 = Release|Any CPU {65340D7D-5831-406C-ACAD-B13BA634BDE2}.Release-Nightly|x86.Build.0 = Release|x86
{65340D7D-5831-406C-ACAD-B13BA634BDE2}.Release-Stable|Any CPU.ActiveCfg = Release|Any CPU {65340D7D-5831-406C-ACAD-B13BA634BDE2}.Release-Stable|Any CPU.ActiveCfg = Release|Any CPU
{65340D7D-5831-406C-ACAD-B13BA634BDE2}.Release-Stable|Any CPU.Build.0 = Release|Any CPU {65340D7D-5831-406C-ACAD-B13BA634BDE2}.Release-Stable|Any CPU.Build.0 = Release|Any CPU
{65340D7D-5831-406C-ACAD-B13BA634BDE2}.Release-Stable|Mixed Platforms.ActiveCfg = Release|Any CPU {65340D7D-5831-406C-ACAD-B13BA634BDE2}.Release-Stable|Mixed Platforms.ActiveCfg = Release|Any CPU
{65340D7D-5831-406C-ACAD-B13BA634BDE2}.Release-Stable|Mixed Platforms.Build.0 = Release|Any CPU {65340D7D-5831-406C-ACAD-B13BA634BDE2}.Release-Stable|Mixed Platforms.Build.0 = Release|Any CPU
{65340D7D-5831-406C-ACAD-B13BA634BDE2}.Release-Stable|x64.ActiveCfg = Release|Any CPU {65340D7D-5831-406C-ACAD-B13BA634BDE2}.Release-Stable|x64.ActiveCfg = Release|Any CPU
{65340D7D-5831-406C-ACAD-B13BA634BDE2}.Release-Stable|x64.Build.0 = Release|Any CPU {65340D7D-5831-406C-ACAD-B13BA634BDE2}.Release-Stable|x64.Build.0 = Release|Any CPU
{65340D7D-5831-406C-ACAD-B13BA634BDE2}.Release-Stable|x86.ActiveCfg = Release|Any CPU {65340D7D-5831-406C-ACAD-B13BA634BDE2}.Release-Stable|x86.ActiveCfg = Release|x86
{65340D7D-5831-406C-ACAD-B13BA634BDE2}.Release-Stable|x86.Build.0 = Release|Any CPU {65340D7D-5831-406C-ACAD-B13BA634BDE2}.Release-Stable|x86.Build.0 = Release|x86
EndGlobalSection EndGlobalSection
GlobalSection(SolutionProperties) = preSolution GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE HideSolutionNode = FALSE

View File

@ -1,4 +1,5 @@
using SharedLibrary.Interfaces; using SharedLibrary.Interfaces;
using SharedLibrary.Objects;
using StatsPlugin.Models; using StatsPlugin.Models;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
@ -30,62 +31,276 @@ namespace StatsPlugin.Cheat
/// </summary> /// </summary>
/// <param name="kill">kill performed by the player</param> /// <param name="kill">kill performed by the player</param>
/// <returns>true if detection reached thresholds, false otherwise</returns> /// <returns>true if detection reached thresholds, false otherwise</returns>
public bool ProcessKill(EFClientKill kill) public DetectionPenaltyResult ProcessKill(EFClientKill kill)
{ {
if (kill.DeathType != IW4Info.MeansOfDeath.MOD_PISTOL_BULLET && kill.DeathType != IW4Info.MeansOfDeath.MOD_RIFLE_BULLET) if ((kill.DeathType != IW4Info.MeansOfDeath.MOD_PISTOL_BULLET &&
return false; kill.DeathType != IW4Info.MeansOfDeath.MOD_RIFLE_BULLET) ||
kill.HitLoc == IW4Info.HitLocation.none)
bool thresholdReached = false; return new DetectionPenaltyResult()
{
ClientPenalty = Penalty.PenaltyType.Any,
RatioAmount = 0
};
HitLocationCount[kill.HitLoc]++; HitLocationCount[kill.HitLoc]++;
Kills++; Kills++;
AverageKillTime = (AverageKillTime + (DateTime.UtcNow - LastKill).TotalSeconds) / Kills; AverageKillTime = (AverageKillTime + (DateTime.UtcNow - LastKill).TotalSeconds) / Kills;
if (Kills > Thresholds.LowSampleMinKills) #region SESSION_RATIOS
if (Kills >= Thresholds.LowSampleMinKills)
{ {
double marginOfError = Thresholds.GetMarginOfError(Kills); double marginOfError = Thresholds.GetMarginOfError(Kills);
// determine what the max headshot percentage can be for current number of kills // determine what the max headshot percentage can be for current number of kills
double lerpAmount = Math.Min(1.0, (Kills - Thresholds.LowSampleMinKills) / (double)(Thresholds.HighSampleMinKills - Thresholds.LowSampleMinKills)); double lerpAmount = Math.Min(1.0, (Kills - Thresholds.LowSampleMinKills) / (double)(Thresholds.HighSampleMinKills - Thresholds.LowSampleMinKills));
double maxHeadshotLerpValue = Thresholds.Lerp( Thresholds.HeadshotRatioThresholdLowSample, Thresholds.HeadshotRatioThresholdHighSample, lerpAmount); double maxHeadshotLerpValueForFlag = Thresholds.Lerp(Thresholds.HeadshotRatioThresholdLowSample(2.0), Thresholds.HeadshotRatioThresholdHighSample(2.0), lerpAmount) + marginOfError;
double maxHeadshotLerpValueForBan = Thresholds.Lerp(Thresholds.HeadshotRatioThresholdLowSample(3.0), Thresholds.HeadshotRatioThresholdHighSample(3.0), lerpAmount) + marginOfError;
// determine what the max bone percentage can be for current number of kills // determine what the max bone percentage can be for current number of kills
double maxBoneRatioLerpValue = Thresholds.Lerp(Thresholds.BoneRatioThresholdLowSample, Thresholds.BoneRatioThresholdHighSample, lerpAmount); double maxBoneRatioLerpValueForFlag = Thresholds.Lerp(Thresholds.BoneRatioThresholdLowSample(2.25), Thresholds.BoneRatioThresholdHighSample(2.25), lerpAmount) + marginOfError;
double maxBoneRatioLerpValueForBan = Thresholds.Lerp(Thresholds.BoneRatioThresholdLowSample(3.25), Thresholds.BoneRatioThresholdHighSample(3.25), lerpAmount) + marginOfError;
// calculate headshot ratio // calculate headshot ratio
double headshotRatio = ((HitLocationCount[IW4Info.HitLocation.head] + HitLocationCount[IW4Info.HitLocation.helmet]) / (double)Kills) - marginOfError; double currentHeadshotRatio = ((HitLocationCount[IW4Info.HitLocation.head] + HitLocationCount[IW4Info.HitLocation.helmet]) / (double)Kills);
// calculate maximum bone // calculate maximum bone
double maximumBoneRatio = (HitLocationCount.Values.Select(v => v / (double)Kills).Max()) - marginOfError; double currentMaxBoneRatio = (HitLocationCount.Values.Select(v => v / (double)Kills).Max());
if (headshotRatio > maxHeadshotLerpValue) var bone = HitLocationCount.FirstOrDefault(b => b.Value == HitLocationCount.Values.Max()).Key;
#region HEADSHOT_RATIO
// flag on headshot
if (currentHeadshotRatio > maxHeadshotLerpValueForFlag)
{ {
AboveThresholdCount++; // ban on headshot
Log.WriteDebug("**Maximum Headshot Ratio Reached**"); if (currentHeadshotRatio > maxHeadshotLerpValueForFlag)
Log.WriteDebug($"ClientId: {kill.AttackerId}"); {
Log.WriteDebug($"**Kills: {Kills}"); AboveThresholdCount++;
Log.WriteDebug($"**Ratio {headshotRatio}"); Log.WriteDebug("**Maximum Headshot Ratio Reached For Ban**");
Log.WriteDebug($"**MaxRatio {maxHeadshotLerpValue}"); Log.WriteDebug($"ClientId: {kill.AttackerId}");
var sb = new StringBuilder(); Log.WriteDebug($"**Kills: {Kills}");
foreach (var kvp in HitLocationCount) Log.WriteDebug($"**Ratio {currentHeadshotRatio}");
sb.Append($"HitLocation: {kvp.Key} Count: {kvp.Value}"); Log.WriteDebug($"**MaxRatio {maxHeadshotLerpValueForFlag}");
Log.WriteDebug(sb.ToString()); var sb = new StringBuilder();
Log.WriteDebug($"ThresholdReached: {AboveThresholdCount}"); foreach (var kvp in HitLocationCount)
thresholdReached = true; sb.Append($"HitLocation: {kvp.Key} -> {kvp.Value}\r\n");
Log.WriteDebug(sb.ToString());
Log.WriteDebug($"ThresholdReached: {AboveThresholdCount}");
return new DetectionPenaltyResult()
{
ClientPenalty = Penalty.PenaltyType.Ban,
RatioAmount = currentHeadshotRatio,
Bone = IW4Info.HitLocation.head,
KillCount = Kills
};
}
else
{
AboveThresholdCount++;
Log.WriteDebug("**Maximum Headshot Ratio Reached For Flag**");
Log.WriteDebug($"ClientId: {kill.AttackerId}");
Log.WriteDebug($"**Kills: {Kills}");
Log.WriteDebug($"**Ratio {currentHeadshotRatio}");
Log.WriteDebug($"**MaxRatio {maxHeadshotLerpValueForFlag}");
var sb = new StringBuilder();
foreach (var kvp in HitLocationCount)
sb.Append($"HitLocation: {kvp.Key} -> {kvp.Value}\r\n");
Log.WriteDebug(sb.ToString());
Log.WriteDebug($"ThresholdReached: {AboveThresholdCount}");
return new DetectionPenaltyResult()
{
ClientPenalty = Penalty.PenaltyType.Flag,
RatioAmount = currentHeadshotRatio,
Bone = IW4Info.HitLocation.head,
KillCount = Kills
};
}
} }
#endregion
else if (maximumBoneRatio > maxBoneRatioLerpValue) #region BONE_RATIO
// flag on bone ratio
else if (currentMaxBoneRatio > maxBoneRatioLerpValueForFlag)
{ {
Log.WriteDebug("**Maximum Bone Ratio Reached**"); // ban on bone ratio
Log.WriteDebug($"ClientId: {kill.AttackerId}"); if (currentMaxBoneRatio > maxBoneRatioLerpValueForBan)
Log.WriteDebug($"**Kills: {Kills}"); {
Log.WriteDebug($"**Ratio {maximumBoneRatio}"); Log.WriteDebug("**Maximum Bone Ratio Reached For Ban**");
Log.WriteDebug($"**MaxRatio {maxBoneRatioLerpValue}"); Log.WriteDebug($"ClientId: {kill.AttackerId}");
var sb = new StringBuilder(); Log.WriteDebug($"**Kills: {Kills}");
foreach (var kvp in HitLocationCount) Log.WriteDebug($"**Ratio {currentMaxBoneRatio}");
sb.Append($"HitLocation: {kvp.Key} Count: {kvp.Value}"); Log.WriteDebug($"**MaxRatio {maxBoneRatioLerpValueForBan}");
Log.WriteDebug(sb.ToString()); var sb = new StringBuilder();
thresholdReached = true; foreach (var kvp in HitLocationCount)
sb.Append($"HitLocation: {kvp.Key} -> {kvp.Value}\r\n");
Log.WriteDebug(sb.ToString());
return new DetectionPenaltyResult()
{
ClientPenalty = Penalty.PenaltyType.Ban,
RatioAmount = currentMaxBoneRatio,
Bone = bone,
KillCount = Kills
};
}
else
{
Log.WriteDebug("**Maximum Bone Ratio Reached For Flag**");
Log.WriteDebug($"ClientId: {kill.AttackerId}");
Log.WriteDebug($"**Kills: {Kills}");
Log.WriteDebug($"**Ratio {currentMaxBoneRatio}");
Log.WriteDebug($"**MaxRatio {maxBoneRatioLerpValueForFlag}");
var sb = new StringBuilder();
foreach (var kvp in HitLocationCount)
sb.Append($"HitLocation: {kvp.Key} -> {kvp.Value}\r\n");
Log.WriteDebug(sb.ToString());
return new DetectionPenaltyResult()
{
ClientPenalty = Penalty.PenaltyType.Flag,
RatioAmount = currentMaxBoneRatio,
Bone = bone,
KillCount = Kills
};
}
}
#endregion
}
#region CHEST_ABDOMEN_RATIO_SESSION
int chestKills = HitLocationCount[IW4Info.HitLocation.torso_upper];
if (chestKills >= Thresholds.MediumSampleMinKills)
{
double marginOfError = Thresholds.GetMarginOfError(chestKills);
double lerpAmount = Math.Min(1.0, (chestKills - Thresholds.LowSampleMinKills) / (double)(Thresholds.HighSampleMinKills - Thresholds.LowSampleMinKills));
// determine max acceptable ratio of chest to abdomen kills
double chestAbdomenRatioLerpValueForFlag = Thresholds.Lerp(Thresholds.ChestAbdomenRatioThresholdLowSample(2.25), Thresholds.ChestAbdomenRatioThresholdHighSample(2.25), lerpAmount) + marginOfError;
double chestAbdomenLerpValueForBan = Thresholds.Lerp(Thresholds.ChestAbdomenRatioThresholdLowSample(3.25), Thresholds.ChestAbdomenRatioThresholdHighSample(3.25), lerpAmount) + marginOfError;
double currentChestAbdomenRatio = HitLocationCount[IW4Info.HitLocation.torso_upper] / (double)HitLocationCount[IW4Info.HitLocation.torso_lower];
if (currentChestAbdomenRatio > chestAbdomenRatioLerpValueForFlag)
{
if (currentChestAbdomenRatio > chestAbdomenLerpValueForBan && chestKills >= Thresholds.MediumSampleMinKills + 30)
{
Log.WriteDebug("**Maximum Chest/Abdomen Ratio Reached For Ban**");
Log.WriteDebug($"ClientId: {kill.AttackerId}");
Log.WriteDebug($"**Chest Kills: {chestKills}");
Log.WriteDebug($"**Ratio {currentChestAbdomenRatio}");
Log.WriteDebug($"**MaxRatio {chestAbdomenLerpValueForBan}");
var sb = new StringBuilder();
foreach (var kvp in HitLocationCount)
sb.Append($"HitLocation: {kvp.Key} -> {kvp.Value}\r\n");
Log.WriteDebug(sb.ToString());
// Log.WriteDebug($"ThresholdReached: {AboveThresholdCount}");
return new DetectionPenaltyResult()
{
ClientPenalty = Penalty.PenaltyType.Ban,
RatioAmount = currentChestAbdomenRatio,
Bone = IW4Info.HitLocation.torso_upper,
KillCount = chestKills
};
}
else
{
Log.WriteDebug("**Maximum Chest/Abdomen Ratio Reached For Flag**");
Log.WriteDebug($"ClientId: {kill.AttackerId}");
Log.WriteDebug($"**Chest Kills: {chestKills}");
Log.WriteDebug($"**Ratio {currentChestAbdomenRatio}");
Log.WriteDebug($"**MaxRatio {chestAbdomenRatioLerpValueForFlag}");
var sb = new StringBuilder();
foreach (var kvp in HitLocationCount)
sb.Append($"HitLocation: {kvp.Key} -> {kvp.Value}\r\n");
Log.WriteDebug(sb.ToString());
// Log.WriteDebug($"ThresholdReached: {AboveThresholdCount}");
return new DetectionPenaltyResult()
{
ClientPenalty = Penalty.PenaltyType.Flag,
RatioAmount = currentChestAbdomenRatio,
Bone = IW4Info.HitLocation.torso_upper,
KillCount = chestKills
};
}
}
}
#endregion
#endregion
return new DetectionPenaltyResult()
{
ClientPenalty = Penalty.PenaltyType.Any,
RatioAmount = 0
};
}
public DetectionPenaltyResult ProcessTotalRatio(EFClientStatistics stats)
{
int totalChestKills = stats.HitLocations.Single(c => c.Location == IW4Info.HitLocation.left_arm_upper).HitCount;
if (totalChestKills >= 250)
{
double marginOfError = Thresholds.GetMarginOfError(totalChestKills);
double lerpAmount = Math.Min(1.0, (totalChestKills - Thresholds.LowSampleMinKills) / (double)(Thresholds.HighSampleMinKills - Thresholds.LowSampleMinKills));
// determine max acceptable ratio of chest to abdomen kills
double chestAbdomenRatioLerpValueForFlag = Thresholds.Lerp(Thresholds.ChestAbdomenRatioThresholdLowSample(2.25), Thresholds.ChestAbdomenRatioThresholdHighSample(2.25), lerpAmount) + marginOfError;
double chestAbdomenLerpValueForBan = Thresholds.Lerp(Thresholds.ChestAbdomenRatioThresholdLowSample(3.0), Thresholds.ChestAbdomenRatioThresholdHighSample(3.0), lerpAmount) + marginOfError;
double currentChestAbdomenRatio = HitLocationCount[IW4Info.HitLocation.torso_upper] / (double)HitLocationCount[IW4Info.HitLocation.torso_lower];
if (currentChestAbdomenRatio > chestAbdomenRatioLerpValueForFlag)
{
if (currentChestAbdomenRatio > chestAbdomenLerpValueForBan)
{
Log.WriteDebug("**Maximum Lifetime Chest/Abdomen Ratio Reached For Ban**");
Log.WriteDebug($"ClientId: {stats.ClientId}");
Log.WriteDebug($"**Total Chest Kills: {totalChestKills}");
Log.WriteDebug($"**Ratio {currentChestAbdomenRatio}");
Log.WriteDebug($"**MaxRatio {chestAbdomenLerpValueForBan}");
var sb = new StringBuilder();
foreach (var kvp in HitLocationCount)
sb.Append($"HitLocation: {kvp.Key} -> {kvp.Value}\r\n");
Log.WriteDebug(sb.ToString());
// Log.WriteDebug($"ThresholdReached: {AboveThresholdCount}");
return new DetectionPenaltyResult()
{
ClientPenalty = Penalty.PenaltyType.Ban,
RatioAmount = currentChestAbdomenRatio,
Bone = IW4Info.HitLocation.torso_upper,
KillCount = totalChestKills
};
}
else
{
Log.WriteDebug("**Maximum Lifetime Chest/Abdomen Ratio Reached For Flag**");
Log.WriteDebug($"ClientId: {stats.ClientId}");
Log.WriteDebug($"**Total Chest Kills: {totalChestKills}");
Log.WriteDebug($"**Ratio {currentChestAbdomenRatio}");
Log.WriteDebug($"**MaxRatio {chestAbdomenRatioLerpValueForFlag}");
var sb = new StringBuilder();
foreach (var kvp in HitLocationCount)
sb.Append($"HitLocation: {kvp.Key} -> {kvp.Value}\r\n");
Log.WriteDebug(sb.ToString());
// Log.WriteDebug($"ThresholdReached: {AboveThresholdCount}");
return new DetectionPenaltyResult()
{
ClientPenalty = Penalty.PenaltyType.Flag,
RatioAmount = currentChestAbdomenRatio,
Bone = IW4Info.HitLocation.torso_upper,
KillCount = totalChestKills
};
}
} }
} }
return thresholdReached; return new DetectionPenaltyResult()
{
Bone = IW4Info.HitLocation.none,
ClientPenalty = Penalty.PenaltyType.Any
};
} }
} }
} }

View File

@ -8,26 +8,30 @@ namespace StatsPlugin.Cheat
{ {
class Thresholds class Thresholds
{ {
private const double Deviations = 3.33; public static double HeadshotRatioThresholdLowSample(double deviations) => HeadshotRatioStandardDeviationLowSample * deviations + HeadshotRatioMean;
public static double HeadshotRatioThresholdHighSample(double deviations) => HeadshotRatioStandardDeviationHighSample * deviations + HeadshotRatioMean;
public const double HeadshotRatioThresholdLowSample = HeadshotRatioStandardDeviationLowSample * Deviations + HeadshotRatioMean;
public const double HeadshotRatioThresholdHighSample = HeadshotRatioStandardDeviationHighSample * Deviations + HeadshotRatioMean;
public const double HeadshotRatioStandardDeviationLowSample = 0.1769994181; public const double HeadshotRatioStandardDeviationLowSample = 0.1769994181;
public const double HeadshotRatioStandardDeviationHighSample = 0.03924263235; public const double HeadshotRatioStandardDeviationHighSample = 0.03924263235;
//public const double HeadshotRatioMean = 0.09587712258;
public const double HeadshotRatioMean = 0.222; public const double HeadshotRatioMean = 0.222;
public const double BoneRatioThresholdLowSample = BoneRatioStandardDeviationLowSample * Deviations + BoneRatioMean; public static double BoneRatioThresholdLowSample(double deviations) => BoneRatioStandardDeviationLowSample * deviations + BoneRatioMean;
public const double BoneRatioThresholdHighSample = BoneRatioStandardDeviationHighSample * Deviations + BoneRatioMean; public static double BoneRatioThresholdHighSample(double deviations) => BoneRatioStandardDeviationHighSample * deviations + BoneRatioMean;
public const double BoneRatioStandardDeviationLowSample = 0.1324612879; public const double BoneRatioStandardDeviationLowSample = 0.1324612879;
public const double BoneRatioStandardDeviationHighSample = 0.0515753935; public const double BoneRatioStandardDeviationHighSample = 0.0515753935;
public const double BoneRatioMean = 0.3982907516; public const double BoneRatioMean = 0.4593110238;
public static double ChestAbdomenRatioThresholdLowSample(double deviations) => ChestAbdomenStandardDeviationLowSample * deviations + ChestAbdomenRatioMean;
public static double ChestAbdomenRatioThresholdHighSample(double deviations) => ChestAbdomenStandardDeviationHighSample * deviations + ChestAbdomenRatioMean;
public const double ChestAbdomenStandardDeviationLowSample = 0.2859234644;
public const double ChestAbdomenStandardDeviationHighSample = 0.2195212861;
public const double ChestAbdomenRatioMean = 0.3925617500;
public const int LowSampleMinKills = 15; public const int LowSampleMinKills = 15;
public const int MediumSampleMinKills = 30;
public const int HighSampleMinKills = 100; public const int HighSampleMinKills = 100;
public const double KillTimeThreshold = 0.2; public const double KillTimeThreshold = 0.2;
public static double GetMarginOfError(int numKills) => 1.645 /(2 * Math.Sqrt(numKills)); public static double GetMarginOfError(int numKills) => 0.98 / Math.Sqrt(numKills);
public static double Lerp(double v1, double v2, double amount) public static double Lerp(double v1, double v2, double amount)
{ {

View File

@ -27,6 +27,9 @@ namespace StatsPlugin.Commands
stats.SPM = 0; stats.SPM = 0;
stats.Skill = 0; stats.Skill = 0;
// reset the cached version
Plugin.Manager.ResetStats(E.Origin.ClientId, E.Owner.GetHashCode());
// fixme: this doesn't work properly when another context exists // fixme: this doesn't work properly when another context exists
await svc.SaveChangesAsync(); await svc.SaveChangesAsync();
await E.Origin.Tell("Your stats have been reset"); await E.Origin.Tell("Your stats have been reset");

View File

@ -2,6 +2,7 @@
using StatsPlugin.Cheat; using StatsPlugin.Cheat;
using StatsPlugin.Models; using StatsPlugin.Models;
using System; using System;
using System.Collections.Concurrent;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Text; using System.Text;
@ -10,15 +11,15 @@ using System.Threading.Tasks;
namespace StatsPlugin.Helpers namespace StatsPlugin.Helpers
{ {
class ServerStats { class ServerStats {
public Dictionary<int, EFClientStatistics> PlayerStats { get; set; } public ConcurrentDictionary<int, EFClientStatistics> PlayerStats { get; set; }
public Dictionary<int, Detection> PlayerDetections { get; set; } public ConcurrentDictionary<int, Detection> PlayerDetections { get; set; }
public EFServerStatistics ServerStatistics { get; private set; } public EFServerStatistics ServerStatistics { get; private set; }
public EFServer Server { get; private set; } public EFServer Server { get; private set; }
public ServerStats(EFServer sv, EFServerStatistics st) public ServerStats(EFServer sv, EFServerStatistics st)
{ {
PlayerStats = new Dictionary<int, EFClientStatistics>(); PlayerStats = new ConcurrentDictionary<int, EFClientStatistics>();
PlayerDetections = new Dictionary<int, Detection>(); PlayerDetections = new ConcurrentDictionary<int, Detection>();
ServerStatistics = st; ServerStatistics = st;
Server = sv; Server = sv;
} }

View File

@ -9,6 +9,7 @@ using SharedLibrary.Interfaces;
using SharedLibrary.Objects; using SharedLibrary.Objects;
using SharedLibrary.Services; using SharedLibrary.Services;
using StatsPlugin.Models; using StatsPlugin.Models;
using SharedLibrary.Commands;
namespace StatsPlugin.Helpers namespace StatsPlugin.Helpers
{ {
@ -75,7 +76,7 @@ namespace StatsPlugin.Helpers
catch (Exception e) catch (Exception e)
{ {
Log.WriteWarning($"Could not add server to ServerStats - {e.Message}"); Log.WriteError($"Could not add server to ServerStats - {e.Message}");
} }
} }
@ -84,9 +85,17 @@ namespace StatsPlugin.Helpers
/// </summary> /// </summary>
/// <param name="pl">Player to add/retrieve stats for</param> /// <param name="pl">Player to add/retrieve stats for</param>
/// <returns>EFClientStatistic of specified player</returns> /// <returns>EFClientStatistic of specified player</returns>
public EFClientStatistics AddPlayer(Player pl) public async Task<EFClientStatistics> AddPlayer(Player pl)
{ {
Log.WriteInfo($"Adding {pl} to stats");
int serverId = pl.CurrentServer.GetHashCode(); int serverId = pl.CurrentServer.GetHashCode();
if (!Servers.ContainsKey(serverId))
{
Log.WriteError($"[Stats::AddPlayer] Server with id {serverId} could not be found");
return null;
}
var playerStats = Servers[serverId].PlayerStats; var playerStats = Servers[serverId].PlayerStats;
var statsSvc = ContextThreads[serverId]; var statsSvc = ContextThreads[serverId];
@ -105,33 +114,50 @@ namespace StatsPlugin.Helpers
ServerId = serverId, ServerId = serverId,
Skill = 0.0, Skill = 0.0,
SPM = 0.0, SPM = 0.0,
HitLocations = Enum.GetValues(typeof(IW4Info.HitLocation)).OfType<IW4Info.HitLocation>().Select(hl => new EFHitLocationCount()
{
Active = true,
HitCount = 0,
Location = hl
})
.ToList()
}; };
clientStats = statsSvc.ClientStatSvc.Insert(clientStats); clientStats = statsSvc.ClientStatSvc.Insert(clientStats);
await statsSvc.ClientStatSvc.SaveChangesAsync();
}
// migration for previous existing stats
else if (clientStats.HitLocations.Count == 0)
{
clientStats.HitLocations = Enum.GetValues(typeof(IW4Info.HitLocation)).OfType<IW4Info.HitLocation>().Select(hl => new EFHitLocationCount()
{
Active = true,
HitCount = 0,
Location = hl
})
.ToList();
await statsSvc.ClientStatSvc.SaveChangesAsync();
} }
// set these on connecting // set these on connecting
clientStats.LastActive = DateTime.UtcNow; clientStats.LastActive = DateTime.UtcNow;
clientStats.LastStatCalculation = DateTime.UtcNow; clientStats.LastStatCalculation = DateTime.UtcNow;
clientStats.SessionScore = pl.Score;
lock (playerStats) if (playerStats.ContainsKey(pl.ClientId))
{ {
if (playerStats.ContainsKey(pl.ClientNumber)) Log.WriteWarning($"Duplicate ClientId in stats {pl.ClientId} vs {playerStats[pl.ClientId].ClientId}");
{ playerStats.TryRemove(pl.ClientId, out EFClientStatistics removedValue);
Log.WriteWarning($"Duplicate clientnumber in stats {pl.ClientId} vs {playerStats[pl.ClientNumber].ClientId}");
playerStats.Remove(pl.ClientNumber);
}
playerStats.Add(pl.ClientNumber, clientStats);
} }
playerStats.TryAdd(pl.ClientId, clientStats);
var detectionStats = Servers[serverId].PlayerDetections; var detectionStats = Servers[serverId].PlayerDetections;
lock (detectionStats)
{
if (detectionStats.ContainsKey(pl.ClientNumber))
detectionStats.Remove(pl.ClientNumber);
detectionStats.Add(pl.ClientNumber, new Cheat.Detection(Log)); if (detectionStats.ContainsKey(pl.ClientId))
} detectionStats.TryRemove(pl.ClientId, out Cheat.Detection removedValue);
detectionStats.TryAdd(pl.ClientId, new Cheat.Detection(Log));
return clientStats; return clientStats;
} }
@ -143,19 +169,27 @@ namespace StatsPlugin.Helpers
/// <returns></returns> /// <returns></returns>
public async Task RemovePlayer(Player pl) public async Task RemovePlayer(Player pl)
{ {
Log.WriteInfo($"Removing {pl} from stats");
int serverId = pl.CurrentServer.GetHashCode(); int serverId = pl.CurrentServer.GetHashCode();
var playerStats = Servers[serverId].PlayerStats; var playerStats = Servers[serverId].PlayerStats;
var detectionStats = Servers[serverId].PlayerDetections; var detectionStats = Servers[serverId].PlayerDetections;
var serverStats = Servers[serverId].ServerStatistics; var serverStats = Servers[serverId].ServerStatistics;
var statsSvc = ContextThreads[serverId]; var statsSvc = ContextThreads[serverId];
if (!playerStats.ContainsKey(pl.ClientId))
{
Log.WriteWarning($"Client disconnecting not in stats {pl}");
return;
}
// get individual client's stats // get individual client's stats
var clientStats = playerStats[pl.ClientNumber]; var clientStats = playerStats[pl.ClientId];
// sync their score
clientStats.SessionScore = pl.Score;
// remove the client from the stats dictionary as they're leaving // remove the client from the stats dictionary as they're leaving
lock (playerStats) playerStats.TryRemove(pl.ClientId, out EFClientStatistics removedValue);
playerStats.Remove(pl.ClientNumber); detectionStats.TryRemove(pl.ClientId, out Cheat.Detection removedValue2);
lock (detectionStats)
detectionStats.Remove(pl.ClientNumber);
// sync their stats before they leave // sync their stats before they leave
UpdateStats(clientStats); UpdateStats(clientStats);
@ -174,16 +208,7 @@ namespace StatsPlugin.Helpers
public async Task AddScriptKill(Player attacker, Player victim, int serverId, string map, string hitLoc, string type, public async Task AddScriptKill(Player attacker, Player victim, int serverId, string map, string hitLoc, string type,
string damage, string weapon, string killOrigin, string deathOrigin) string damage, string weapon, string killOrigin, string deathOrigin)
{ {
await AddStandardKill(attacker, victim);
if (victim == null)
{
Log.WriteError($"[AddScriptKill] Victim is null");
return;
}
var statsSvc = ContextThreads[serverId]; var statsSvc = ContextThreads[serverId];
var playerDetection = Servers[serverId].PlayerDetections[attacker.ClientNumber];
var kill = new EFClientKill() var kill = new EFClientKill()
{ {
@ -200,30 +225,88 @@ namespace StatsPlugin.Helpers
Weapon = ParseEnum<IW4Info.WeaponName>.Get(weapon, typeof(IW4Info.WeaponName)) Weapon = ParseEnum<IW4Info.WeaponName>.Get(weapon, typeof(IW4Info.WeaponName))
}; };
playerDetection.ProcessKill(kill); if (kill.DeathType == IW4Info.MeansOfDeath.MOD_SUICIDE &&
kill.Damage == 10000)
{
// suicide by switching teams so let's not count it against them
return;
}
return; await AddStandardKill(attacker, victim);
statsSvc.KillStatsSvc.Insert(kill); var playerDetection = Servers[serverId].PlayerDetections[attacker.ClientId];
await statsSvc.KillStatsSvc.SaveChangesAsync(); var playerStats = Servers[serverId].PlayerStats[attacker.ClientId];
// increment their hit count
if (kill.DeathType == IW4Info.MeansOfDeath.MOD_PISTOL_BULLET ||
kill.DeathType == IW4Info.MeansOfDeath.MOD_RIFLE_BULLET)
{
playerStats.HitLocations.Single(hl => hl.Location == kill.HitLoc).HitCount += 1;
await statsSvc.ClientStatSvc.SaveChangesAsync();
}
//statsSvc.KillStatsSvc.Insert(kill);
//await statsSvc.KillStatsSvc.SaveChangesAsync();
async Task executePenalty(Cheat.DetectionPenaltyResult penalty)
{
switch (penalty.ClientPenalty)
{
case Penalty.PenaltyType.Ban:
await attacker.Ban("You appear to be cheating", new Player() { ClientId = 1 });
break;
case Penalty.PenaltyType.Flag:
if (attacker.Level != Player.Permission.User)
break;
var flagCmd = new CFlag();
await flagCmd.ExecuteAsync(new Event(Event.GType.Flag, $"{(int)penalty.Bone}-{Math.Round(penalty.RatioAmount, 2).ToString()}@{penalty.KillCount}", new Player()
{
ClientId = 1,
Level = Player.Permission.Console,
ClientNumber = -1,
CurrentServer = attacker.CurrentServer
}, attacker, attacker.CurrentServer));
break;
}
}
await executePenalty(playerDetection.ProcessKill(kill));
await executePenalty(playerDetection.ProcessTotalRatio(playerStats));
} }
public async Task AddStandardKill(Player attacker, Player victim) public async Task AddStandardKill(Player attacker, Player victim)
{ {
int serverId = attacker.CurrentServer.GetHashCode(); int serverId = attacker.CurrentServer.GetHashCode();
var attackerStats = Servers[serverId].PlayerStats[attacker.ClientNumber]; EFClientStatistics attackerStats = null;
try
if (victim == null)
{ {
Log.WriteError($"[AddStandardKill] Victim is null"); attackerStats = Servers[serverId].PlayerStats[attacker.ClientId];
}
catch (KeyNotFoundException)
{
Log.WriteError($"[Stats::AddStandardKill] kill attacker ClientId is invalid {attacker.ClientId}-{attacker}");
return; return;
} }
var victimStats = Servers[serverId].PlayerStats[victim.ClientNumber]; EFClientStatistics victimStats = null;
try
{
victimStats = Servers[serverId].PlayerStats[victim.ClientId];
}
catch (KeyNotFoundException)
{
Log.WriteError($"[Stats::AddStandardKill] kill victim ClientId is invalid {victim.ClientId}-{victim}");
return;
}
// update the total stats // update the total stats
Servers[serverId].ServerStatistics.TotalKills += 1; Servers[serverId].ServerStatistics.TotalKills += 1;
attackerStats.SessionScore = attacker.Score;
victimStats.SessionScore = victim.Score;
// calculate for the clients // calculate for the clients
CalculateKill(attackerStats, victimStats); CalculateKill(attackerStats, victimStats);
@ -292,9 +375,7 @@ namespace StatsPlugin.Helpers
return clientStats; return clientStats;
// calculate the players Score Per Minute for the current session // calculate the players Score Per Minute for the current session
int currentScore = Manager.GetActiveClients() int currentScore = clientStats.SessionScore;
.First(c => c.ClientId == clientStats.ClientId)
.Score;
double killSPM = currentScore / (timeSinceLastCalc * 60.0); double killSPM = currentScore / (timeSinceLastCalc * 60.0);
// calculate how much the KDR should weigh // calculate how much the KDR should weigh
@ -350,6 +431,25 @@ namespace StatsPlugin.Helpers
} }
} }
public void ResetKillstreaks(int serverId)
{
var serverStats = Servers[serverId];
foreach (var stat in serverStats.PlayerStats.Values)
{
stat.KillStreak = 0;
stat.DeathStreak = 0;
}
}
public void ResetStats(int clientId, int serverId)
{
var stats = Servers[serverId].PlayerStats[clientId];
stats.Kills = 0;
stats.Deaths = 0;
stats.SPM = 0;
stats.Skill = 0;
}
public async Task AddMessageAsync(int clientId, int serverId, string message) public async Task AddMessageAsync(int clientId, int serverId, string message)
{ {
// the web users can have no account // the web users can have no account

View File

@ -10,7 +10,6 @@ namespace StatsPlugin.Helpers
{ {
public class ThreadSafeStatsService public class ThreadSafeStatsService
{ {
public GenericRepository<EFClientStatistics> ClientStatSvc { get; private set; } public GenericRepository<EFClientStatistics> ClientStatSvc { get; private set; }
public GenericRepository<EFServer> ServerSvc { get; private set; } public GenericRepository<EFServer> ServerSvc { get; private set; }
public GenericRepository<EFClientKill> KillStatsSvc { get; private set; } public GenericRepository<EFClientKill> KillStatsSvc { get; private set; }

View File

@ -1358,7 +1358,9 @@ namespace StatsPlugin
m40a3_mp, m40a3_mp,
peacekeeper_mp, peacekeeper_mp,
dragunov_mp, dragunov_mp,
cobra_player_minigun_mp cobra_player_minigun_mp,
destructible_car,
sentry_minigun_mp
} }
public enum MapName public enum MapName

View File

@ -24,7 +24,9 @@ namespace StatsPlugin.Models
public int Kills { get; set; } public int Kills { get; set; }
[Required] [Required]
public int Deaths { get; set; } public int Deaths { get; set; }
[Required]
public virtual ICollection<EFHitLocationCount> HitLocations { get; set; }
[NotMapped] [NotMapped]
public double KDR public double KDR
{ {
@ -51,5 +53,7 @@ namespace StatsPlugin.Models
public int LastScore { get; set; } public int LastScore { get; set; }
[NotMapped] [NotMapped]
public DateTime LastActive { get; set; } public DateTime LastActive { get; set; }
[NotMapped]
public int SessionScore { get; set; }
} }
} }

View File

@ -0,0 +1,21 @@
using SharedLibrary.Database.Models;
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 StatsPlugin.Models
{
public class EFHitLocationCount : SharedEntity
{
[Key]
public int HitLocationCountId { get; set; }
[Required]
public IW4Info.HitLocation Location { get; set; }
[Required]
public int HitCount { get; set; }
}
}

View File

@ -22,7 +22,7 @@ namespace StatsPlugin
public string Author => "RaidMax"; public string Author => "RaidMax";
private StatManager Manager; public static StatManager Manager { get; private set; }
private IManager ServerManager; private IManager ServerManager;
public async Task OnEventAsync(Event E, Server S) public async Task OnEventAsync(Event E, Server S)
@ -35,19 +35,20 @@ namespace StatsPlugin
case Event.GType.Stop: case Event.GType.Stop:
break; break;
case Event.GType.Connect: case Event.GType.Connect:
Manager.AddPlayer(E.Origin); await Manager.AddPlayer(E.Origin);
break; break;
case Event.GType.Disconnect: case Event.GType.Disconnect:
await Manager.RemovePlayer(E.Origin); await Manager.RemovePlayer(E.Origin);
break; break;
case Event.GType.Say: case Event.GType.Say:
if (E.Data != string.Empty && E.Data.Trim().Length > 0 && E.Data.Trim()[0] != '!') if (E.Data != string.Empty && E.Data.Trim().Length > 0 && E.Message.Trim()[0] != '!' && E.Origin.ClientId > 1)
await Manager.AddMessageAsync(E.Origin.ClientId, E.Owner.GetHashCode(), E.Data); await Manager.AddMessageAsync(E.Origin.ClientId, E.Owner.GetHashCode(), E.Data);
break; break;
case Event.GType.MapChange: case Event.GType.MapChange:
Manager.ResetKillstreaks(S.GetHashCode());
await Manager.Sync(S);
break; break;
case Event.GType.MapEnd: case Event.GType.MapEnd:
await Manager.Sync(S);
break; break;
case Event.GType.Broadcast: case Event.GType.Broadcast:
break; break;
@ -92,6 +93,25 @@ namespace StatsPlugin
double kdr = Math.Round(kills / (double)deaths, 2); double kdr = Math.Round(kills / (double)deaths, 2);
double skill = Math.Round(clientStats.Sum(c => c.Skill) / clientStats.Count, 2); double skill = Math.Round(clientStats.Sum(c => c.Skill) / clientStats.Count, 2);
double chestRatio = 0;
double abdomenRatio = 0;
double chestAbdomenRatio = 0;
if (clientStats.FirstOrDefault()?.HitLocations.Count > 0)
{
chestRatio = Math.Round(clientStats.Where(c => c.HitLocations.Count > 0).Sum(c =>
c.HitLocations.First(hl => hl.Location == IW4Info.HitLocation.torso_upper).HitCount) /
(double)clientStats.Where(c => c.HitLocations.Count > 0)
.Sum(c => c.HitLocations.Where(hl => hl.Location != IW4Info.HitLocation.none).Sum(f => f.HitCount)), 2);
abdomenRatio = Math.Round(clientStats.Where(c => c.HitLocations.Count > 0).Sum(c =>
c.HitLocations.First(hl => hl.Location == IW4Info.HitLocation.torso_lower).HitCount) /
(double)clientStats.Where(c => c.HitLocations.Count > 0).Sum(c => c.HitLocations.Where(hl => hl.Location != IW4Info.HitLocation.none).Sum(f => f.HitCount)), 2);
chestAbdomenRatio = Math.Round(clientStats.Where(c => c.HitLocations.Count > 0).Sum(cs => cs.HitLocations.First(hl => hl.Location == IW4Info.HitLocation.torso_upper).HitCount) /
(double)clientStats.Where(c => c.HitLocations.Count > 0).Sum(cs => cs.HitLocations.First(hl => hl.Location == IW4Info.HitLocation.torso_lower).HitCount), 2);
}
return new List<ProfileMeta>() return new List<ProfileMeta>()
{ {
new ProfileMeta() new ProfileMeta()
@ -113,6 +133,24 @@ namespace StatsPlugin
{ {
Key = "Skill", Key = "Skill",
Value = skill Value = skill
},
new ProfileMeta()
{
Key = "Chest Ratio",
Value = chestRatio,
Sensitive = true
},
new ProfileMeta()
{
Key = "Abdomen Ratio",
Value = abdomenRatio,
Sensitive = true
},
new ProfileMeta()
{
Key = "Chest To Abdomen Ratio",
Value = chestAbdomenRatio,
Sensitive = true
} }
}; };
} }

View File

@ -119,6 +119,7 @@
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<Compile Include="Cheat\Detection.cs" /> <Compile Include="Cheat\Detection.cs" />
<Compile Include="Cheat\DetectionPenaltyResult.cs" />
<Compile Include="Cheat\Thresholds.cs" /> <Compile Include="Cheat\Thresholds.cs" />
<Compile Include="Commands\ResetStats.cs" /> <Compile Include="Commands\ResetStats.cs" />
<Compile Include="Commands\TopStats.cs" /> <Compile Include="Commands\TopStats.cs" />
@ -131,6 +132,7 @@
<Compile Include="MinimapConfig.cs" /> <Compile Include="MinimapConfig.cs" />
<Compile Include="Models\EFClientKill.cs" /> <Compile Include="Models\EFClientKill.cs" />
<Compile Include="Models\EFClientMessage.cs" /> <Compile Include="Models\EFClientMessage.cs" />
<Compile Include="Models\EFHitLocationCount.cs" />
<Compile Include="Models\EFServer.cs" /> <Compile Include="Models\EFServer.cs" />
<Compile Include="Models\EFClientStatistics.cs" /> <Compile Include="Models\EFClientStatistics.cs" />
<Compile Include="Models\EFServerStatistics.cs" /> <Compile Include="Models\EFServerStatistics.cs" />
@ -146,7 +148,6 @@
<ProjectReference Include="..\..\SharedLibrary\SharedLibrary.csproj"> <ProjectReference Include="..\..\SharedLibrary\SharedLibrary.csproj">
<Project>{d51eeceb-438a-47da-870f-7d7b41bc24d6}</Project> <Project>{d51eeceb-438a-47da-870f-7d7b41bc24d6}</Project>
<Name>SharedLibrary</Name> <Name>SharedLibrary</Name>
<Private>False</Private>
</ProjectReference> </ProjectReference>
</ItemGroup> </ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" /> <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />

View File

@ -126,7 +126,7 @@ namespace Welcome_Plugin
msg = msg.Replace("{{ClientLevel}}", Utilities.ConvertLevelToColor(joining.Level)); msg = msg.Replace("{{ClientLevel}}", Utilities.ConvertLevelToColor(joining.Level));
try try
{ {
CountryLookupProj.CountryLookup CLT = new CountryLookupProj.CountryLookup("Plugins/GeoIP.dat"); CountryLookupProj.CountryLookup CLT = new CountryLookupProj.CountryLookup($"{Utilities.OperatingDirectory}Plugins{System.IO.Path.DirectorySeparatorChar}GeoIP.dat");
msg = msg.Replace("{{ClientLocation}}", CLT.LookupCountryName(joining.IPAddressString)); msg = msg.Replace("{{ClientLocation}}", CLT.LookupCountryName(joining.IPAddressString));
} }

View File

@ -1,2 +1,36 @@
<?xml version="1.0" encoding="utf-8"?> <configuration>
<configuration /> <configSections>
<!-- For more information on Entity Framework configuration, visit http://go.microsoft.com/fwlink/?LinkID=237468 -->
<section name="entityFramework" type="System.Data.Entity.Internal.ConfigFile.EntityFrameworkSection, EntityFramework, Version=6.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" requirePermission="false" />
<!-- For more information on Entity Framework configuration, visit http://go.microsoft.com/fwlink/?LinkID=237468 -->
</configSections>
<startup>
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5.2"/>
</startup>
<runtime>
<assemblyBinding>
<!-- <probing privatePath="lib"/>-->
</assemblyBinding>
</runtime>
<entityFramework>
<defaultConnectionFactory type="System.Data.Entity.Infrastructure.SqlCeConnectionFactory, EntityFramework">
<parameters>
<parameter value="System.Data.SqlServerCe.4.0" />
</parameters>
</defaultConnectionFactory>
<providers>
<provider invariantName="System.Data.SqlServerCe.4.0" type="System.Data.Entity.SqlServerCompact.SqlCeProviderServices, EntityFramework.SqlServerCompact" />
</providers>
</entityFramework>
<system.data>
<DbProviderFactories>
<remove invariant="System.Data.SqlServerCe.4.0" />
<add name="Microsoft SQL Server Compact Data Provider 4.0" invariant="System.Data.SqlServerCe.4.0" description=".NET Framework Data Provider for Microsoft SQL Server Compact" type="System.Data.SqlServerCe.SqlCeProviderFactory, System.Data.SqlServerCe, Version=4.0.0.0, Culture=neutral, PublicKeyToken=89845dcd8080cc91" />
</DbProviderFactories>
</system.data>
<connectionStrings>
<add name="DefaultConnection"
providerName="System.Data.SqlServerCe.4.0"
connectionString="Data Source=|DataDirectory|\Database.sdf"/>
</connectionStrings>
</configuration>

View File

@ -11,6 +11,7 @@ using SharedLibrary.Database;
using System.Data.Entity; using System.Data.Entity;
using SharedLibrary.Database.Models; using SharedLibrary.Database.Models;
using SharedLibrary.Services; using SharedLibrary.Services;
using SharedLibrary.Exceptions;
namespace SharedLibrary.Commands namespace SharedLibrary.Commands
{ {
@ -170,6 +171,9 @@ namespace SharedLibrary.Commands
{ {
String Message = Utilities.RemoveWords(E.Data, 1).Trim(); String Message = Utilities.RemoveWords(E.Data, 1).Trim();
var length = E.Data.Split(' ')[0].ToLower().ParseTimespan(); var length = E.Data.Split(' ')[0].ToLower().ParseTimespan();
if (length.TotalHours >= 1 && length.TotalHours < 2)
Message = E.Data;
if (E.Origin.Level > E.Target.Level) if (E.Origin.Level > E.Target.Level)
{ {
@ -428,8 +432,8 @@ namespace SharedLibrary.Commands
if (newPerm > Player.Permission.Banned) if (newPerm > Player.Permission.Banned)
{ {
var ActiveClient = E.Owner.Manager.GetActiveClients().FirstOrDefault(p => p.NetworkId == E.Target.NetworkId); var ActiveClient = E.Owner.Manager.GetActiveClients()
.FirstOrDefault(p => p.NetworkId == E.Target.NetworkId);
if (ActiveClient != null) if (ActiveClient != null)
{ {
@ -444,7 +448,6 @@ namespace SharedLibrary.Commands
} }
await E.Origin.Tell($"{E.Target.Name} was successfully promoted!"); await E.Origin.Tell($"{E.Target.Name} was successfully promoted!");
} }
else else
@ -485,17 +488,22 @@ namespace SharedLibrary.Commands
public override async Task ExecuteAsync(Event E) public override async Task ExecuteAsync(Event E)
{ {
int numOnline = 0;
for (int i = 0; i < E.Owner.Players.Count; i++) for (int i = 0; i < E.Owner.Players.Count; i++)
{ {
var P = E.Owner.Players[i]; var P = E.Owner.Players[i];
if (P != null && P.Level > Player.Permission.Flagged && !P.Masked) if (P != null && P.Level > Player.Permission.Flagged && !P.Masked)
{ {
numOnline++;
if (E.Message[0] == '@') if (E.Message[0] == '@')
await E.Owner.Broadcast(String.Format("[^3{0}^7] {1}", Utilities.ConvertLevelToColor(P.Level), P.Name)); await E.Owner.Broadcast(String.Format("[^3{0}^7] {1}", Utilities.ConvertLevelToColor(P.Level), P.Name));
else else
await E.Origin.Tell(String.Format("[^3{0}^7] {1}", Utilities.ConvertLevelToColor(P.Level), P.Name)); await E.Origin.Tell(String.Format("[^3{0}^7] {1}", Utilities.ConvertLevelToColor(P.Level), P.Name));
} }
} }
if (numOnline == 0)
await E.Origin.Tell("No visible administrators online");
} }
} }
@ -519,14 +527,14 @@ namespace SharedLibrary.Commands
{ {
if (m.Name.ToLower() == newMap || m.Alias.ToLower() == newMap) if (m.Name.ToLower() == newMap || m.Alias.ToLower() == newMap)
{ {
await E.Owner.Broadcast("Changing to map ^2" + m.Alias); await E.Owner.Broadcast($"Changing to map ^5{m.Alias}");
Task.Delay(5000).Wait(); Task.Delay(5000).Wait();
await E.Owner.LoadMap(m.Name); await E.Owner.LoadMap(m.Name);
return; return;
} }
} }
await E.Owner.Broadcast("Attempting to change to unknown map ^1" + newMap); await E.Owner.Broadcast($"Attempting to change to unknown map ^5{newMap}");
Task.Delay(5000).Wait(); Task.Delay(5000).Wait();
await E.Owner.LoadMap(newMap); await E.Owner.LoadMap(newMap);
} }
@ -927,7 +935,7 @@ namespace SharedLibrary.Commands
} }
}) })
{ } { }
public override async Task ExecuteAsync(Event E) public override async Task ExecuteAsync(Event E)
{ {
int inactiveDays = 30; int inactiveDays = 30;
@ -961,7 +969,89 @@ namespace SharedLibrary.Commands
inactiveUsers.ForEach(c => c.Level = Player.Permission.User); inactiveUsers.ForEach(c => c.Level = Player.Permission.User);
await context.SaveChangesAsync(); await context.SaveChangesAsync();
} }
await E.Origin.Tell($"Pruned {inactiveUsers.Count} inactive privileged users"); await E.Origin.Tell($"Pruned {inactiveUsers.Count} inactive privileged users");
}
}
public class CRestartServer : Command
{
public CRestartServer() : base("restartserver", "restart the server", "restart", Player.Permission.Administrator, false)
{
}
public override async Task ExecuteAsync(Event E)
{
var gameserverProcesses = System.Diagnostics.Process.GetProcessesByName("iw4x");
var currentProcess = gameserverProcesses.FirstOrDefault(g => g.GetCommandLine().Contains($"+set net_port {E.Owner.GetPort()}"));
if (currentProcess == null)
{
await E.Origin.Tell("Could not find running/stalled instance of IW4x");
}
else
{
var commandLine = currentProcess.GetCommandLine();
// attempt to kill it natively
try
{
if (!E.Owner.Throttled)
{
// await E.Owner.ExecuteCommandAsync("quit");
}
}
catch (NetworkException)
{
await E.Origin.Tell("Unable to cleanly shutdown server, forcing");
}
if (!currentProcess.HasExited)
{
try
{
currentProcess.Kill();
}
catch (Exception e)
{
await E.Origin.Tell("Could not kill IW4x process");
E.Owner.Logger.WriteDebug("Unable to kill process");
E.Owner.Logger.WriteDebug($"Exception: {e.Message}");
return;
}
try
{
System.Diagnostics.Process process = new System.Diagnostics.Process();
process.StartInfo.UseShellExecute = false;
#if !DEBUG
process.StartInfo.WorkingDirectory = E.Owner.WorkingDirectory;
#else
process.StartInfo.WorkingDirectory = @"C:\Users\User\Desktop\MW2";
#endif
process.StartInfo.FileName = $"{process.StartInfo.WorkingDirectory}\\iw4x.exe";
process.StartInfo.Arguments = commandLine.Substring(6);
process.StartInfo.UserName = E.Owner.Config.RestartUsername;
var pw = new System.Security.SecureString();
foreach (char c in E.Owner.Config.RestartPassword)
pw.AppendChar(c);
process.StartInfo.Password = pw;
process.Start();
}
catch (Exception e)
{
await E.Origin.Tell("Could not start the IW4x process");
E.Owner.Logger.WriteDebug("Unable to start process");
E.Owner.Logger.WriteDebug($"Exception: {e.Message}");
}
}
}
} }
} }

View File

@ -10,6 +10,7 @@ using System.Data.Entity.ModelConfiguration.Conventions;
using System.Reflection; using System.Reflection;
using System.Data.Entity.Infrastructure; using System.Data.Entity.Infrastructure;
using System.Data.Entity.SqlServerCompact; using System.Data.Entity.SqlServerCompact;
using System.IO;
namespace SharedLibrary.Database namespace SharedLibrary.Database
{ {
@ -23,7 +24,8 @@ namespace SharedLibrary.Database
public DatabaseContext() : base("DefaultConnection") public DatabaseContext() : base("DefaultConnection")
{ {
System.Data.Entity.Database.SetInitializer(new Initializer()); System.Data.Entity.Database.SetInitializer(new MigrateDatabaseToLatestVersion<DatabaseContext, Migrations.Configuration>());
//Database.CreateIfNotExists();
Configuration.LazyLoadingEnabled = true; Configuration.LazyLoadingEnabled = true;
} }
@ -50,7 +52,12 @@ namespace SharedLibrary.Database
modelBuilder.Conventions.Remove<OneToManyCascadeDeleteConvention>(); modelBuilder.Conventions.Remove<OneToManyCascadeDeleteConvention>();
// https://aleemkhan.wordpress.com/2013/02/28/dynamically-adding-dbset-properties-in-dbcontext-for-entity-framework-code-first/ // https://aleemkhan.wordpress.com/2013/02/28/dynamically-adding-dbset-properties-in-dbcontext-for-entity-framework-code-first/
//string dir = Path.GetDirectoryName(Assembly.GetEntryAssembly().Location) + Path.DirectorySeparatorChar;
#if !DEBUG
foreach (string dllPath in System.IO.Directory.GetFiles($"{Utilities.OperatingDirectory}Plugins")) foreach (string dllPath in System.IO.Directory.GetFiles($"{Utilities.OperatingDirectory}Plugins"))
#else
foreach (string dllPath in System.IO.Directory.GetFiles(@"C:\Users\User\Desktop\stuff\IW4M-Admin\IW4M-Admin\WebfrontCore\bin\x86\Debug\Plugins"))
#endif
{ {
Assembly library; Assembly library;
try try
@ -64,7 +71,7 @@ namespace SharedLibrary.Database
continue; continue;
} }
foreach(var type in library.ExportedTypes) foreach (var type in library.ExportedTypes)
{ {
if (type.IsClass && type.IsSubclassOf(typeof(SharedEntity))) if (type.IsClass && type.IsSubclassOf(typeof(SharedEntity)))
{ {

View File

@ -1,42 +0,0 @@
using System;
using System.Collections.Generic;
using System.Data.Entity;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace SharedLibrary.Database
{
public class Initializer : DropCreateDatabaseIfModelChanges<DatabaseContext>
{
protected override void Seed(DatabaseContext context)
{
var aliasLink = new Models.EFAliasLink();
var currentAlias = new Models.EFAlias()
{
Active = true,
DateAdded = DateTime.UtcNow,
IPAddress = 0,
Name = "IW4MAdmin",
Link = aliasLink
};
context.Clients.Add(new Models.EFClient()
{
Active = false,
Connections = 0,
FirstConnection = DateTime.UtcNow,
LastConnection = DateTime.UtcNow,
Level = Objects.Player.Permission.Console,
Masked = true,
NetworkId = 0,
AliasLink = aliasLink,
CurrentAlias = currentAlias
});
base.Seed(context);
}
}
}

View File

@ -0,0 +1,26 @@
using SharedLibrary.Interfaces;
using System;
using System.Data.SqlServerCe;
namespace SharedLibrary.Database
{
public class Repair
{
public static void Run(ILogger log)
{
SqlCeEngine engine = new SqlCeEngine(@"Data Source=|DataDirectory|\Database.sdf");
if (false == engine.Verify())
{
log.WriteWarning("Database is corrupted.");
try
{
engine.Repair(null, RepairOption.DeleteCorruptedRows);
}
catch (SqlCeException ex)
{
log.WriteError(ex.Message);
}
}
}
}
}

View File

@ -6,7 +6,7 @@ using System.Threading.Tasks;
namespace SharedLibrary.Dtos namespace SharedLibrary.Dtos
{ {
public class PenaltyInfo public class PenaltyInfo : SharedInfo
{ {
public string OffenderName { get; set; } public string OffenderName { get; set; }
public int OffenderId { get; set; } public int OffenderId { get; set; }

View File

@ -7,9 +7,10 @@ using System.Threading.Tasks;
namespace SharedLibrary.Dtos namespace SharedLibrary.Dtos
{ {
public class ProfileMeta public class ProfileMeta : SharedInfo
{ {
public DateTime When { get; set; } public DateTime When { get; set; }
public bool Sensitive { get; set; }
public string WhenString => Utilities.GetTimePassed(When, false); public string WhenString => Utilities.GetTimePassed(When, false);
public string Key { get; set; } public string Key { get; set; }
public dynamic Value { get; set; } public dynamic Value { get; set; }

View File

@ -0,0 +1,8 @@

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

View File

@ -2,12 +2,13 @@
using SharedLibrary.Objects; using SharedLibrary.Objects;
using SharedLibrary.Database.Models; using SharedLibrary.Database.Models;
using SharedLibrary.Services; using SharedLibrary.Services;
using System.Threading.Tasks;
namespace SharedLibrary.Interfaces namespace SharedLibrary.Interfaces
{ {
public interface IManager public interface IManager
{ {
void Init(); Task Init();
void Start(); void Start();
void Stop(); void Stop();
ILogger GetLogger(); ILogger GetLogger();
@ -15,7 +16,7 @@ namespace SharedLibrary.Interfaces
IList<Command> GetCommands(); IList<Command> GetCommands();
IList<Helpers.MessageToken> GetMessageTokens(); IList<Helpers.MessageToken> GetMessageTokens();
IList<Player> GetActiveClients(); IList<Player> GetActiveClients();
ClientService GetClientService(); ClientService GetClientService();
AliasService GetAliasService(); AliasService GetAliasService();
PenaltyService GetPenaltyService(); PenaltyService GetPenaltyService();
} }

View File

@ -0,0 +1,29 @@
// <auto-generated />
namespace SharedLibrary.Migrations
{
using System.CodeDom.Compiler;
using System.Data.Entity.Migrations;
using System.Data.Entity.Migrations.Infrastructure;
using System.Resources;
[GeneratedCode("EntityFramework.Migrations", "6.2.0-61023")]
public sealed partial class Intial : IMigrationMetadata
{
private readonly ResourceManager Resources = new ResourceManager(typeof(Intial));
string IMigrationMetadata.Id
{
get { return "201803030146021_Intial"; }
}
string IMigrationMetadata.Source
{
get { return null; }
}
string IMigrationMetadata.Target
{
get { return Resources.GetString("Target"); }
}
}
}

View File

@ -0,0 +1,16 @@
namespace SharedLibrary.Migrations
{
using System;
using System.Data.Entity.Migrations;
public partial class Intial : DbMigration
{
public override void Up()
{
}
public override void Down()
{
}
}
}

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,52 @@
namespace SharedLibrary.Migrations
{
using System;
using System.Data.Entity;
using System.Data.Entity.Migrations;
using System.Linq;
internal sealed class Configuration : DbMigrationsConfiguration<SharedLibrary.Database.DatabaseContext>
{
public Configuration()
{
AutomaticMigrationsEnabled = true;
AutomaticMigrationDataLossAllowed = true;
}
protected override void Seed(SharedLibrary.Database.DatabaseContext context)
{
context.AliasLinks.AddOrUpdate(new SharedLibrary.Database.Models.EFAliasLink()
{
AliasLinkId = 1
});
var currentAlias = new SharedLibrary.Database.Models.EFAlias()
{
AliasId = 1,
Active = true,
DateAdded = DateTime.UtcNow,
IPAddress = 0,
Name = "IW4MAdmin",
LinkId = 1
};
context.Aliases.AddOrUpdate(currentAlias);
context.Clients.AddOrUpdate(new SharedLibrary.Database.Models.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,
});
base.Seed(context);
}
}
}

View File

@ -66,7 +66,7 @@ namespace SharedLibrary.Network
do do
{ {
ReceiveBuffer = ServerOOBConnection.Receive(ref Endpoint); ReceiveBuffer = ServerOOBConnection.Receive(ref Endpoint);
QueryResponseString.Append(Encoding.ASCII.GetString(ReceiveBuffer).TrimEnd('\0')); QueryResponseString.Append(Encoding.UTF7.GetString(ReceiveBuffer).TrimEnd('\0'));
} while (ServerOOBConnection.Available > 0 && ServerOOBConnection.Client.Connected); } while (ServerOOBConnection.Available > 0 && ServerOOBConnection.Client.Connected);
if (QueryResponseString.ToString().Contains("Invalid password")) if (QueryResponseString.ToString().Contains("Invalid password"))

View File

@ -385,6 +385,7 @@ namespace SharedLibrary
public string Password { get; private set; } public string Password { get; private set; }
public bool Throttled { get; protected set; } public bool Throttled { get; protected set; }
public bool CustomCallback { get; protected set; } public bool CustomCallback { get; protected set; }
public string WorkingDirectory { get; protected set; }
// Internal // Internal
protected string IP; protected string IP;

View File

@ -10,6 +10,8 @@ namespace SharedLibrary
public string FtpPrefix; public string FtpPrefix;
public bool AllowMultipleOwners; public bool AllowMultipleOwners;
public bool AllowTrustedRank; public bool AllowTrustedRank;
public string RestartUsername;
public string RestartPassword;
public override string Filename() public override string Filename()
{ {

View File

@ -168,6 +168,11 @@ namespace SharedLibrary.Services
}; };
} }
else
{
client.CurrentAliasId = entity.CurrentAliasId;
}
// set remaining non-navigation properties that may have been updated // set remaining non-navigation properties that may have been updated
client.Level = entity.Level; client.Level = entity.Level;
client.LastConnection = entity.LastConnection; client.LastConnection = entity.LastConnection;

View File

@ -15,6 +15,21 @@
<TargetFrameworkProfile /> <TargetFrameworkProfile />
<NuGetPackageImportStamp> <NuGetPackageImportStamp>
</NuGetPackageImportStamp> </NuGetPackageImportStamp>
<PublishUrl>publish\</PublishUrl>
<Install>true</Install>
<InstallFrom>Disk</InstallFrom>
<UpdateEnabled>false</UpdateEnabled>
<UpdateMode>Foreground</UpdateMode>
<UpdateInterval>7</UpdateInterval>
<UpdateIntervalUnits>Days</UpdateIntervalUnits>
<UpdatePeriodically>false</UpdatePeriodically>
<UpdateRequired>false</UpdateRequired>
<MapFileExtensions>true</MapFileExtensions>
<ApplicationRevision>0</ApplicationRevision>
<ApplicationVersion>1.0.0.%2a</ApplicationVersion>
<IsWebBootstrapper>false</IsWebBootstrapper>
<UseApplicationTrust>false</UseApplicationTrust>
<BootstrapperEnabled>true</BootstrapperEnabled>
</PropertyGroup> </PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' "> <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<PlatformTarget>AnyCPU</PlatformTarget> <PlatformTarget>AnyCPU</PlatformTarget>
@ -118,22 +133,10 @@
<CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet> <CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<Reference Include="EntityFramework, Version=6.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089, processorArchitecture=MSIL">
<HintPath>..\packages\EntityFramework.6.2.0\lib\net45\EntityFramework.dll</HintPath>
</Reference>
<Reference Include="EntityFramework.SqlServer, Version=6.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089, processorArchitecture=MSIL">
<HintPath>..\packages\EntityFramework.6.2.0\lib\net45\EntityFramework.SqlServer.dll</HintPath>
</Reference>
<Reference Include="EntityFramework.SqlServerCompact, Version=6.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089, processorArchitecture=MSIL">
<HintPath>..\packages\EntityFramework.SqlServerCompact.6.2.0\lib\net45\EntityFramework.SqlServerCompact.dll</HintPath>
</Reference>
<Reference Include="System" /> <Reference Include="System" />
<Reference Include="System.ComponentModel.DataAnnotations" /> <Reference Include="System.ComponentModel.DataAnnotations" />
<Reference Include="System.Core" /> <Reference Include="System.Core" />
<Reference Include="System.Data.SqlServerCe, Version=4.0.0.0, Culture=neutral, PublicKeyToken=89845dcd8080cc91, processorArchitecture=MSIL"> <Reference Include="System.Management" />
<HintPath>..\packages\Microsoft.SqlServer.Compact.4.0.8876.1\lib\net40\System.Data.SqlServerCe.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="System.Net.Http" /> <Reference Include="System.Net.Http" />
<Reference Include="System.Web" /> <Reference Include="System.Web" />
<Reference Include="System.Xml.Linq" /> <Reference Include="System.Xml.Linq" />
@ -144,19 +147,20 @@
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<Compile Include="Database\Importer.cs" /> <Compile Include="Database\Importer.cs" />
<Compile Include="Database\Initializer.cs" />
<Compile Include="Database\DatabaseContext.cs" /> <Compile Include="Database\DatabaseContext.cs" />
<Compile Include="Database\Models\EFAlias.cs" /> <Compile Include="Database\Models\EFAlias.cs" />
<Compile Include="Database\Models\EFAliasLink.cs" /> <Compile Include="Database\Models\EFAliasLink.cs" />
<Compile Include="Database\Models\EFClient.cs" /> <Compile Include="Database\Models\EFClient.cs" />
<Compile Include="Database\Models\EFPenalty.cs" /> <Compile Include="Database\Models\EFPenalty.cs" />
<Compile Include="Database\Models\SharedEntity.cs" /> <Compile Include="Database\Models\SharedEntity.cs" />
<Compile Include="Database\Repair.cs" />
<Compile Include="Dtos\CommandResponseInfo.cs" /> <Compile Include="Dtos\CommandResponseInfo.cs" />
<Compile Include="Dtos\PlayerInfo.cs" /> <Compile Include="Dtos\PlayerInfo.cs" />
<Compile Include="Dtos\ClientInfo.cs" /> <Compile Include="Dtos\ClientInfo.cs" />
<Compile Include="Dtos\ProfileMeta.cs" /> <Compile Include="Dtos\ProfileMeta.cs" />
<Compile Include="Dtos\PenaltyInfo.cs" /> <Compile Include="Dtos\PenaltyInfo.cs" />
<Compile Include="Dtos\ServerInfo.cs" /> <Compile Include="Dtos\ServerInfo.cs" />
<Compile Include="Dtos\SharedInfo.cs" />
<Compile Include="Exceptions\DatabaseException.cs" /> <Compile Include="Exceptions\DatabaseException.cs" />
<Compile Include="Helpers\AsyncStatus.cs" /> <Compile Include="Helpers\AsyncStatus.cs" />
<Compile Include="Commands\NativeCommands.cs" /> <Compile Include="Commands\NativeCommands.cs" />
@ -173,6 +177,11 @@
<Compile Include="Interfaces\IManager.cs" /> <Compile Include="Interfaces\IManager.cs" />
<Compile Include="Interfaces\ISerializable.cs" /> <Compile Include="Interfaces\ISerializable.cs" />
<Compile Include="Helpers\MessageToken.cs" /> <Compile Include="Helpers\MessageToken.cs" />
<Compile Include="Migrations\201803030146021_Intial.cs" />
<Compile Include="Migrations\201803030146021_Intial.Designer.cs">
<DependentUpon>201803030146021_Intial.cs</DependentUpon>
</Compile>
<Compile Include="Migrations\Configuration.cs" />
<Compile Include="Objects\Alias.cs" /> <Compile Include="Objects\Alias.cs" />
<Compile Include="Objects\Penalty.cs" /> <Compile Include="Objects\Penalty.cs" />
<Compile Include="Command.cs" /> <Compile Include="Command.cs" />
@ -197,13 +206,37 @@
<Compile Include="Utilities.cs" /> <Compile Include="Utilities.cs" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<Content Include="app.config" />
<None Include="packages.config" /> <None Include="packages.config" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="EntityFramework">
<Version>6.2.0</Version>
</PackageReference>
<PackageReference Include="EntityFramework.SqlServerCompact">
<Version>6.2.0</Version>
</PackageReference>
<PackageReference Include="Newtonsoft.Json"> <PackageReference Include="Newtonsoft.Json">
<Version>11.0.1</Version> <Version>11.0.1</Version>
</PackageReference> </PackageReference>
</ItemGroup> </ItemGroup>
<ItemGroup>
<BootstrapperPackage Include=".NETFramework,Version=v4.5.2">
<Visible>False</Visible>
<ProductName>Microsoft .NET Framework 4.5.2 %28x86 and x64%29</ProductName>
<Install>true</Install>
</BootstrapperPackage>
<BootstrapperPackage Include="Microsoft.Net.Framework.3.5.SP1">
<Visible>False</Visible>
<ProductName>.NET Framework 3.5 SP1</ProductName>
<Install>false</Install>
</BootstrapperPackage>
</ItemGroup>
<ItemGroup>
<EmbeddedResource Include="Migrations\201803030146021_Intial.resx">
<DependentUpon>201803030146021_Intial.cs</DependentUpon>
</EmbeddedResource>
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" /> <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<PropertyGroup> <PropertyGroup>
<PostBuildEvent>copy /Y "$(TargetDir)$(TargetName).dll" "$(SolutionDir)BUILD\lib" <PostBuildEvent>copy /Y "$(TargetDir)$(TargetName).dll" "$(SolutionDir)BUILD\lib"
@ -218,9 +251,7 @@ if not exist "$(TargetDir)amd64" md "$(TargetDir)amd64"
xcopy /Y /I /E "$(TargetDir)*" "$(SolutionDir)BUILD\Lib"</PostBuildEvent> xcopy /Y /I /E "$(TargetDir)*" "$(SolutionDir)BUILD\Lib"</PostBuildEvent>
</PropertyGroup> </PropertyGroup>
<PropertyGroup> <PropertyGroup>
<PreBuildEvent>if exist "$(SolutionDir)BUILD\Plugins" rmdir /Q /S "$(SolutionDir)BUILD\Plugins" <PreBuildEvent>
mkdir "$(SolutionDir)BUILD\Plugins"
if not exist "$(SolutionDir)BUILD" mkdir "$(SolutionDir)BUILD" if not exist "$(SolutionDir)BUILD" mkdir "$(SolutionDir)BUILD"
if not exist "$(SolutionDir)BUILD\Lib" mkdir "$(SolutionDir)BUILD\Lib" if not exist "$(SolutionDir)BUILD\Lib" mkdir "$(SolutionDir)BUILD\Lib"
if not exist "$(SolutionDir)BUILD\userraw\scripts" mkdir "$(SolutionDir)BUILD\userraw\scripts"</PreBuildEvent> if not exist "$(SolutionDir)BUILD\userraw\scripts" mkdir "$(SolutionDir)BUILD\userraw\scripts"</PreBuildEvent>

View File

@ -4,11 +4,13 @@ using System.Text;
using System.Text.RegularExpressions; using System.Text.RegularExpressions;
using System.Linq; using System.Linq;
using System.Collections.Generic; using System.Collections.Generic;
using System.Management;
using SharedLibrary.Objects; using SharedLibrary.Objects;
using static SharedLibrary.Server; using static SharedLibrary.Server;
using System.Reflection; using System.Reflection;
using System.IO; using System.IO;
using System.Diagnostics;
namespace SharedLibrary namespace SharedLibrary
{ {
@ -61,14 +63,14 @@ namespace SharedLibrary
int cID = -1; int cID = -1;
int Ping = -1; int Ping = -1;
Int32.TryParse(playerInfo[2], out Ping); Int32.TryParse(playerInfo[2], out Ping);
String cName = Utilities.StripColors(responseLine.Substring(46, 18)).Trim(); 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(); long npID = Regex.Match(responseLine, @"([a-z]|[0-9]){16}", RegexOptions.IgnoreCase).Value.ConvertLong();
int.TryParse(playerInfo[0], out cID); int.TryParse(playerInfo[0], out cID);
var regex = Regex.Match(responseLine, @"\d+\.\d+\.\d+.\d+\:\d{1,5}"); var regex = Regex.Match(responseLine, @"\d+\.\d+\.\d+.\d+\:\d{1,5}");
int cIP = regex.Value.Split(':')[0].ConvertToIP(); int cIP = regex.Value.Split(':')[0].ConvertToIP();
regex = Regex.Match(responseLine, @"[0-9]{1,2}\s+[0-9]+\s+"); regex = Regex.Match(responseLine, @"[0-9]{1,2}\s+[0-9]+\s+");
int score = Int32.Parse(regex.Value.Split(new char[] { ' ' }, StringSplitOptions.RemoveEmptyEntries)[1]); 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}; Player P = new Player() { Name = cName, NetworkId = npID, ClientNumber = cID, IPAddress = cIP, Ping = Ping, Score = score };
StatusPlayers.Add(P); StatusPlayers.Add(P);
} }
} }
@ -96,8 +98,11 @@ namespace SharedLibrary
{ {
if (str == null) if (str == null)
return ""; return "";
return Regex.Replace(str, @"(\^+((?![a-z]|[A-Z]).){0,1})+", "") str = Regex.Replace(str, @"(\^+((?![a-z]|[A-Z]).){0,1})+", "");
string str2 = Regex.Match(str, @"(^\/+.*$)|(^.*\/+$)")
.Value
.Replace("/", " /"); .Replace("/", " /");
return str2.Length > 0 ? str2 : str;
} }
/// <summary> /// <summary>
@ -334,11 +339,6 @@ namespace SharedLibrary
return "1 hour"; return "1 hour";
} }
public static string EscapeMarkdown(this string markdownString)
{
return markdownString.Replace("<", "\\<").Replace(">", "\\>").Replace("|", "\\|");
}
public static Player AsPlayer(this Database.Models.EFClient client) public static Player AsPlayer(this Database.Models.EFClient client)
{ {
return client == null ? null : new Player() return client == null ? null : new Player()
@ -361,5 +361,35 @@ namespace SharedLibrary
CurrentAliasId = client.CurrentAlias.AliasId CurrentAliasId = client.CurrentAlias.AliasId
}; };
} }
/*https://stackoverflow.com/questions/2633628/can-i-get-command-line-arguments-of-other-processes-from-net-c*/
// Define an extension method for type System.Process that returns the command
// line via WMI.
public static string GetCommandLine(this Process process)
{
string cmdLine = null;
using (var searcher = new ManagementObjectSearcher(
$"SELECT CommandLine FROM Win32_Process WHERE ProcessId = {process.Id}"))
{
// By definition, the query returns at most 1 match, because the process
// is looked up by ID (which is unique by definition).
var matchEnum = searcher.Get().GetEnumerator();
if (matchEnum.MoveNext()) // Move to the 1st item.
{
cmdLine = matchEnum.Current["CommandLine"]?.ToString();
}
}
if (cmdLine == null)
{
// Not having found a command line implies 1 of 2 exceptions, which the
// WMI query masked:
// An "Access denied" exception due to lack of privileges.
// A "Cannot process request because the process (<pid>) has exited."
// exception due to the process having terminated.
// We provoke the same exception again simply by accessing process.MainModule.
var dummy = process.MainModule; // Provoke exception.
}
return cmdLine;
}
} }
} }

View File

@ -1,191 +0,0 @@
using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Linq;
using System.Text;
using Kayak.Http;
using Kayak;
namespace IW4MAdmin
{
class Scheduler : ISchedulerDelegate
{
public void OnException(IScheduler scheduler, Exception e)
{
// it looks like there's a library error in
// Kayak.Http.HttpServerTransactionDelegate.OnError
if ((uint)e.HResult == 0x80004003 || (uint)e.InnerException?.HResult == 0x80004003)
return;
ApplicationManager.GetInstance().Logger.WriteWarning("Web service has encountered an error - " + e.Message);
ApplicationManager.GetInstance().Logger.WriteDebug($"Stack Trace: {e.StackTrace}");
if (e.InnerException != null)
{
ApplicationManager.GetInstance().Logger.WriteDebug($"Inner Exception: {e.InnerException.Message}");
ApplicationManager.GetInstance().Logger.WriteDebug($"Inner Stack Trace: {e.InnerException.StackTrace}");
}
}
public void OnStop(IScheduler scheduler)
{
ApplicationManager.GetInstance().Logger.WriteInfo("Web service has been stopped...");
}
}
class Request : IHttpRequestDelegate
{
public void OnRequest(HttpRequestHead request, IDataProducer requestBody, IHttpResponseDelegate response)
{
#if DEBUG
var logger = ApplicationManager.GetInstance().GetLogger();
logger.WriteDebug($"HTTP request {request.Path}");
logger.WriteDebug($"QueryString: {request.QueryString}");
logger.WriteDebug($"IP: {request.IPAddress}");
#endif
NameValueCollection querySet = new NameValueCollection();
if (request.QueryString != null)
querySet = System.Web.HttpUtility.ParseQueryString(request.QueryString);
querySet.Set("IP", request.IPAddress);
try
{
request.Path = String.IsNullOrEmpty(request.Path) ? "/" : request.Path;
SharedLibrary.HttpResponse requestedPage = WebService.GetPage(request.Path, querySet, request.Headers);
bool binaryContent = requestedPage.BinaryContent != null;
if (requestedPage.content != null && requestedPage.content.GetType() != typeof(string))
#if !DEBUG
requestedPage.content = Newtonsoft.Json.JsonConvert.SerializeObject(requestedPage.content);
#else
requestedPage.content = Newtonsoft.Json.JsonConvert.SerializeObject(requestedPage.content, Newtonsoft.Json.Formatting.Indented);
#endif
string maxAge = requestedPage.contentType == "application/json" ? "0" : "21600";
var headers = new HttpResponseHead()
{
Status = "200 OK",
Headers = new Dictionary<string, string>()
{
{ "Content-Type", requestedPage.contentType },
{ "Content-Length", binaryContent ? requestedPage.BinaryContent.Length.ToString() : requestedPage.content.ToString().Length.ToString() },
{ "Access-Control-Allow-Origin", "*" },
{ "Cache-Control", $"public,max-age={maxAge}"}
}
};
foreach (var key in requestedPage.additionalHeaders.Keys)
headers.Headers.Add(key, requestedPage.additionalHeaders[key]);
if (!binaryContent)
response.OnResponse(headers, new BufferedProducer((string)requestedPage.content));
else
response.OnResponse(headers, new BufferedProducer(requestedPage.BinaryContent));
}
catch (Exception e)
{
if (e.GetType() == typeof(FormatException))
{
ApplicationManager.GetInstance().Logger.WriteWarning("Request parameter data format was incorrect");
ApplicationManager.GetInstance().Logger.WriteDebug($"Request Path {request.Path}");
ApplicationManager.GetInstance().Logger.WriteDebug($"Request Query String {request.QueryString}");
response.OnResponse(new HttpResponseHead()
{
Status = "400 Bad Request",
Headers = new Dictionary<string, string>()
{
{ "Content-Type", "text/html" },
{ "Content-Length", "0"},
}
}, new BufferedProducer(""));
}
else
{
ApplicationManager.GetInstance().Logger.WriteError($"Webfront error during request");
ApplicationManager.GetInstance().Logger.WriteDebug($"Message: {e.Message}");
ApplicationManager.GetInstance().Logger.WriteDebug($"Stack Trace: {e.StackTrace}");
response.OnResponse(new HttpResponseHead()
{
Status = "500 Internal Server Error",
Headers = new Dictionary<string, string>()
{
{ "Content-Type", "text/html" },
{ "Content-Length", "0"},
}
}, new BufferedProducer(""));
}
}
}
}
class BufferedProducer : IDataProducer
{
ArraySegment<byte> data;
public BufferedProducer(string data) : this(data, Encoding.ASCII) { }
public BufferedProducer(string data, Encoding encoding) : this(encoding.GetBytes(data)) { }
public BufferedProducer(byte[] data) : this(new ArraySegment<byte>(data)) { }
public BufferedProducer(ArraySegment<byte> data)
{
this.data = data;
}
public IDisposable Connect(IDataConsumer channel)
{
try
{
channel?.OnData(data, null);
channel?.OnEnd();
}
catch (Exception)
{
}
return null;
}
}
class BufferedConsumer : IDataConsumer
{
List<ArraySegment<byte>> buffer = new List<ArraySegment<byte>>();
Action<string> resultCallback;
Action<Exception> errorCallback;
public BufferedConsumer(Action<string> resultCallback, Action<Exception> errorCallback)
{
this.resultCallback = resultCallback;
this.errorCallback = errorCallback;
}
public bool OnData(ArraySegment<byte> data, Action continuation)
{
// this should hopefully clean the non ascii characters out.
buffer?.Add(new ArraySegment<byte>(Encoding.ASCII.GetBytes(Encoding.ASCII.GetString(data.ToArray()))));
return false;
}
public void OnError(Exception error)
{
// errorCallback?.Invoke(error);
}
public void OnEnd()
{
var str = buffer
.Select(b => Encoding.ASCII.GetString(b.Array, b.Offset, b.Count))
.Aggregate((result, next) => result + next);
resultCallback(str);
}
}
}

View File

@ -6,11 +6,7 @@ using System.Threading.Tasks;
using System.IO; using System.IO;
using SharedLibrary.Objects; using SharedLibrary.Objects;
using System.Reflection; using System.Reflection;
using System.Linq;
#if DEBUG
using SharedLibrary.Database;
#endif
namespace IW4MAdmin namespace IW4MAdmin
{ {
@ -20,7 +16,7 @@ namespace IW4MAdmin
public static extern bool AllocConsole(); public static extern bool AllocConsole();
static public double Version { get; private set; } static public double Version { get; private set; }
static public ApplicationManager ServerManager = ApplicationManager.GetInstance(); static public ApplicationManager ServerManager = ApplicationManager.GetInstance();
public static string OperatingDirectory = Path.GetDirectoryName(Assembly.GetEntryAssembly().Location) + Path.DirectorySeparatorChar; public static string OperatingDirectory = Path.GetDirectoryName(Assembly.GetEntryAssembly().Location) + Path.DirectorySeparatorChar;
public static void Start() public static void Start()
{ {
@ -29,7 +25,6 @@ namespace IW4MAdmin
Version = 1.6; Version = 1.6;
//double.TryParse(CheckUpdate(), out double latestVersion);
Console.WriteLine("====================================================="); Console.WriteLine("=====================================================");
Console.WriteLine(" IW4M ADMIN"); Console.WriteLine(" IW4M ADMIN");
Console.WriteLine(" by RaidMax "); Console.WriteLine(" by RaidMax ");
@ -40,13 +35,16 @@ namespace IW4MAdmin
{ {
CheckDirectories(); CheckDirectories();
ServerManager = ApplicationManager.GetInstance(); Task.Run(async () =>
ServerManager.Init(); {
ServerManager = ApplicationManager.GetInstance();
SharedLibrary.Database.Repair.Run(ServerManager.Logger);
await ServerManager.Init();
ServerManager.Start();
});
Task.Run(() => Task.Run(() =>
{ {
ServerManager.Start();
String userInput; String userInput;
Player Origin = ServerManager.GetClientService().Get(1).Result.AsPlayer(); Player Origin = ServerManager.GetClientService().Get(1).Result.AsPlayer();
@ -66,6 +64,8 @@ namespace IW4MAdmin
Console.Write('>'); Console.Write('>');
} while (ServerManager.Running); } while (ServerManager.Running);
Console.WriteLine("Shutdown complete");
}); });
} }

View File

@ -11,8 +11,6 @@ using SharedLibrary.Commands;
using SharedLibrary.Helpers; using SharedLibrary.Helpers;
using SharedLibrary.Exceptions; using SharedLibrary.Exceptions;
using SharedLibrary.Objects; using SharedLibrary.Objects;
using SharedLibrary.Database;
using SharedLibrary.Database.Models;
using SharedLibrary.Services; using SharedLibrary.Services;
namespace IW4MAdmin namespace IW4MAdmin
@ -21,6 +19,7 @@ namespace IW4MAdmin
{ {
private List<Server> _servers; private List<Server> _servers;
public List<Server> Servers => _servers.OrderByDescending(s => s.ClientNum).ToList(); public List<Server> Servers => _servers.OrderByDescending(s => s.ClientNum).ToList();
public List<int> AdministratorIPs { get; set; }
public ILogger Logger { get; private set; } public ILogger Logger { get; private set; }
public bool Running { get; private set; } public bool Running { get; private set; }
@ -34,7 +33,7 @@ namespace IW4MAdmin
#if FTP_LOG #if FTP_LOG
const int UPDATE_FREQUENCY = 700; const int UPDATE_FREQUENCY = 700;
#else #else
const int UPDATE_FREQUENCY = 300; const int UPDATE_FREQUENCY = 750;
#endif #endif
private ApplicationManager() private ApplicationManager()
@ -47,6 +46,7 @@ namespace IW4MAdmin
ClientSvc = new ClientService(); ClientSvc = new ClientService();
AliasSvc = new AliasService(); AliasSvc = new AliasService();
PenaltySvc = new PenaltyService(); PenaltySvc = new PenaltyService();
AdministratorIPs = new List<int>();
} }
public IList<Server> GetServers() public IList<Server> GetServers()
@ -64,12 +64,12 @@ namespace IW4MAdmin
return Instance ?? (Instance = new ApplicationManager()); return Instance ?? (Instance = new ApplicationManager());
} }
public void Init() public async Task Init()
{ {
#region WEBSERVICE #region DATABASE
// SharedLibrary.WebService.Init(); AdministratorIPs = (await ClientSvc.Find(c => c.Level > Player.Permission.Trusted))
//WebSvc = new WebService(); .Select(c => c.IPAddress)
//WebSvc.StartScheduler(); .ToList();
#endregion #endregion
#region PLUGINS #region PLUGINS
@ -79,7 +79,7 @@ namespace IW4MAdmin
{ {
try try
{ {
Plugin.OnLoadAsync(this); await Plugin.OnLoadAsync(this);
} }
catch (Exception e) catch (Exception e)
@ -174,6 +174,7 @@ namespace IW4MAdmin
Commands.Add(new CIP()); Commands.Add(new CIP());
Commands.Add(new CMask()); Commands.Add(new CMask());
Commands.Add(new CPruneAdmins()); Commands.Add(new CPruneAdmins());
Commands.Add(new CRestartServer());
foreach (Command C in SharedLibrary.Plugins.PluginImporter.ActiveCommands) foreach (Command C in SharedLibrary.Plugins.PluginImporter.ActiveCommands)
Commands.Add(C); Commands.Add(C);

View File

@ -10,6 +10,8 @@ using SharedLibrary.Network;
using SharedLibrary.Interfaces; using SharedLibrary.Interfaces;
using SharedLibrary.Objects; using SharedLibrary.Objects;
using System.Text.RegularExpressions; using System.Text.RegularExpressions;
using SharedLibrary.Services;
using SharedLibrary.Database.Models;
namespace IW4MAdmin namespace IW4MAdmin
{ {
@ -99,7 +101,7 @@ namespace IW4MAdmin
await Manager.GetClientService().Update(client); await Manager.GetClientService().Update(client);
} }
else if (existingAlias.Name == polledPlayer.Name) else if (existingAlias.Name == polledPlayer.Name)
{ {
client.CurrentAlias = existingAlias; client.CurrentAlias = existingAlias;
client.CurrentAliasId = existingAlias.AliasId; client.CurrentAliasId = existingAlias.AliasId;
@ -154,12 +156,13 @@ namespace IW4MAdmin
if (cNum >= 0 && Players[cNum] != null) if (cNum >= 0 && Players[cNum] != null)
{ {
Player Leaving = Players[cNum]; Player Leaving = Players[cNum];
Logger.WriteInfo($"Client {Leaving} disconnecting...");
await ExecuteEvent(new Event(Event.GType.Disconnect, "", Leaving, null, this));
Leaving.TotalConnectionTime += (int)(DateTime.UtcNow - Leaving.ConnectionTime).TotalSeconds; Leaving.TotalConnectionTime += (int)(DateTime.UtcNow - Leaving.ConnectionTime).TotalSeconds;
Leaving.LastConnection = DateTime.UtcNow; Leaving.LastConnection = DateTime.UtcNow;
await Manager.GetClientService().Update(Leaving); await Manager.GetClientService().Update(Leaving);
Logger.WriteInfo($"Client {Leaving} disconnecting...");
await ExecuteEvent(new Event(Event.GType.Disconnect, "", Leaving, null, this));
Players[cNum] = null; Players[cNum] = null;
} }
} }
@ -357,7 +360,11 @@ namespace IW4MAdmin
async Task<int> PollPlayersAsync() async Task<int> PollPlayersAsync()
{ {
var now = DateTime.Now;
var CurrentPlayers = await this.GetStatusAsync(); var CurrentPlayers = await this.GetStatusAsync();
#if DEBUG
Logger.WriteInfo($"Polling players took {(DateTime.Now - now).TotalMilliseconds}ms");
#endif
for (int i = 0; i < Players.Count; i++) for (int i = 0; i < Players.Count; i++)
{ {
@ -385,9 +392,9 @@ namespace IW4MAdmin
override public async Task<bool> ProcessUpdatesAsync(CancellationToken cts) override public async Task<bool> ProcessUpdatesAsync(CancellationToken cts)
{ {
this.cts = cts; this.cts = cts;
#if DEBUG == false //#if DEBUG == false
try try
#endif //#endif
{ {
// first start // first start
if (firstRun) if (firstRun)
@ -504,7 +511,7 @@ namespace IW4MAdmin
} }
return true; return true;
} }
#if DEBUG == false //#if !DEBUG
catch (SharedLibrary.Exceptions.NetworkException) catch (SharedLibrary.Exceptions.NetworkException)
{ {
Logger.WriteError($"Could not communicate with {IP}:{Port}"); Logger.WriteError($"Could not communicate with {IP}:{Port}");
@ -518,7 +525,7 @@ namespace IW4MAdmin
Logger.WriteDebug("Error Trace: " + E.StackTrace); Logger.WriteDebug("Error Trace: " + E.StackTrace);
return false; return false;
} }
#endif //#endif
} }
public async Task Initialize() public async Task Initialize()
@ -537,6 +544,7 @@ namespace IW4MAdmin
await this.GetDvarAsync<int>("sv_maxclients"); await this.GetDvarAsync<int>("sv_maxclients");
var gametype = await this.GetDvarAsync<string>("g_gametype"); var gametype = await this.GetDvarAsync<string>("g_gametype");
var basepath = await this.GetDvarAsync<string>("fs_basepath"); var basepath = await this.GetDvarAsync<string>("fs_basepath");
WorkingDirectory = basepath.Value;
var game = await this.GetDvarAsync<string>("fs_game"); var game = await this.GetDvarAsync<string>("fs_game");
var logfile = await this.GetDvarAsync<string>("g_log"); var logfile = await this.GetDvarAsync<string>("g_log");
var logsync = await this.GetDvarAsync<int>("g_logsync"); var logsync = await this.GetDvarAsync<int>("g_logsync");
@ -705,6 +713,11 @@ namespace IW4MAdmin
string mapname = this.GetDvarAsync<string>("mapname").Result.Value; string mapname = this.GetDvarAsync<string>("mapname").Result.Value;
CurrentMap = Maps.Find(m => m.Name == mapname) ?? new Map(mapname, mapname); CurrentMap = Maps.Find(m => m.Name == mapname) ?? new Map(mapname, mapname);
// todo: make this more efficient
((ApplicationManager)(Manager)).AdministratorIPs = (await new GenericRepository<EFClient>().FindAsync(c => c.Level > Player.Permission.Trusted))
.Select(c => c.IPAddress)
.ToList();
} }
if (E.Type == Event.GType.MapEnd) if (E.Type == Event.GType.MapEnd)

View File

@ -0,0 +1,25 @@
using IW4MAdmin;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Filters;
using SharedLibrary;
using SharedLibrary.Interfaces;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace WebfrontCore.Controllers
{
public class BaseController : Controller
{
protected ApplicationManager Manager;
protected bool Authorized { get; private set; }
public override void OnActionExecuting(ActionExecutingContext context)
{
Manager = IW4MAdmin.Program.ServerManager;
Authorized = Manager.AdministratorIPs.Contains(context.HttpContext.Connection.RemoteIpAddress.ToString().ConvertToIP());
base.OnActionExecuting(context);
}
}
}

View File

@ -9,11 +9,11 @@ using System.Threading.Tasks;
namespace WebfrontCore.Controllers namespace WebfrontCore.Controllers
{ {
public class ClientController : Controller public class ClientController : BaseController
{ {
public async Task<IActionResult> ProfileAsync(int id) public async Task<IActionResult> ProfileAsync(int id)
{ {
var client = await IW4MAdmin.ApplicationManager.GetInstance().GetClientService().Get(id); var client = await Manager.GetClientService().Get(id);
var clientDto = new PlayerInfo() var clientDto = new PlayerInfo()
{ {
Name = client.Name, Name = client.Name,
@ -38,11 +38,15 @@ namespace WebfrontCore.Controllers
.OrderBy(i => i) .OrderBy(i => i)
.ToList(), .ToList(),
}; };
var meta = await MetaService.GetMeta(client.ClientId);
clientDto.Meta.AddRange(await MetaService.GetMeta(client.ClientId)); clientDto.Meta.AddRange(Authorized ? meta : meta.Where(m => !m.Sensitive));
clientDto.Meta.AddRange(await IW4MAdmin.ApplicationManager.GetInstance().GetPenaltyService().ReadGetClientPenaltiesAsync(client.ClientId)); clientDto.Meta.AddRange(await Manager.GetPenaltyService()
clientDto.Meta.AddRange(await IW4MAdmin.ApplicationManager.GetInstance().GetPenaltyService().ReadGetClientPenaltiesAsync(client.ClientId, false)); .ReadGetClientPenaltiesAsync(client.ClientId));
clientDto.Meta = clientDto.Meta.OrderByDescending(m => m.When).ToList(); clientDto.Meta.AddRange(await Manager.GetPenaltyService()
.ReadGetClientPenaltiesAsync(client.ClientId, false));
clientDto.Meta = clientDto.Meta
.OrderByDescending(m => m.When)
.ToList();
ViewBag.Title = clientDto.Name; ViewBag.Title = clientDto.Name;
@ -51,7 +55,7 @@ namespace WebfrontCore.Controllers
public async Task<IActionResult> PrivilegedAsync() public async Task<IActionResult> PrivilegedAsync()
{ {
var admins = (await IW4MAdmin.ApplicationManager.GetInstance().GetClientService().GetPrivilegedClients()) var admins = (await Manager.GetClientService().GetPrivilegedClients())
.Where(a => a.Active) .Where(a => a.Active)
.OrderByDescending(a => a.Level); .OrderByDescending(a => a.Level);
var adminsDict = new Dictionary<SharedLibrary.Objects.Player.Permission, IList<ClientInfo>>(); var adminsDict = new Dictionary<SharedLibrary.Objects.Player.Permission, IList<ClientInfo>>();
@ -74,7 +78,7 @@ namespace WebfrontCore.Controllers
public async Task<IActionResult> FindAsync(string clientName) public async Task<IActionResult> FindAsync(string clientName)
{ {
var clients = (await IW4MAdmin.ApplicationManager.GetInstance().GetClientService().GetClientByName(clientName)) var clients = (await Manager.GetClientService().GetClientByName(clientName))
.OrderByDescending(c => c.LastConnection); .OrderByDescending(c => c.LastConnection);
var clientsDto = clients.Select(c => new PlayerInfo() var clientsDto = clients.Select(c => new PlayerInfo()
{ {

View File

@ -9,11 +9,11 @@ using System.Threading.Tasks;
namespace WebfrontCore.Controllers namespace WebfrontCore.Controllers
{ {
public class ConsoleController : Controller public class ConsoleController : BaseController
{ {
public IActionResult Index() public IActionResult Index()
{ {
var activeServers = IW4MAdmin.ApplicationManager.GetInstance().Servers.Select(s => new ServerInfo() var activeServers = Manager.Servers.Select(s => new ServerInfo()
{ {
Name = s.Hostname, Name = s.Hostname,
ID = s.GetHashCode(), ID = s.GetHashCode(),
@ -38,10 +38,10 @@ namespace WebfrontCore.Controllers
IPAddress = intIP IPAddress = intIP
}; };
#else #else
var origin = (await IW4MAdmin.ApplicationManager.GetInstance().GetClientService().GetUnique(0)).AsPlayer(); var origin = (await Manager.GetClientService().GetUnique(0)).AsPlayer();
#endif #endif
var server = IW4MAdmin.ApplicationManager.GetInstance().Servers.First(s => s.GetHashCode() == serverId); var server = Manager.Servers.First(s => s.GetHashCode() == serverId);
origin.CurrentServer = server; origin.CurrentServer = server;
var remoteEvent = new Event(Event.GType.Say, command, origin, null, server); var remoteEvent = new Event(Event.GType.Say, command, origin, null, server);

View File

@ -8,7 +8,7 @@ using SharedLibrary.Dtos;
namespace WebfrontCore.Controllers namespace WebfrontCore.Controllers
{ {
public class HomeController : Controller public class HomeController : BaseController
{ {
public IActionResult Index() public IActionResult Index()
{ {

View File

@ -9,9 +9,9 @@ using WebfrontCore.ViewComponents;
namespace WebfrontCore.Controllers namespace WebfrontCore.Controllers
{ {
public class PenaltyController : Controller public class PenaltyController : BaseController
{ {
public IActionResult List() public IActionResult List()
{ {
ViewBag.Title = "Penalty List"; ViewBag.Title = "Penalty List";
return View(); return View();

View File

@ -7,15 +7,14 @@ using System.Threading.Tasks;
namespace WebfrontCore.Controllers namespace WebfrontCore.Controllers
{ {
public class ServerController : Controller public class ServerController : BaseController
{ {
[HttpGet] [HttpGet]
[ResponseCache(NoStore = true, Duration = 0)] [ResponseCache(NoStore = true, Duration = 0)]
public IActionResult ClientActivity(int id) public IActionResult ClientActivity(int id)
{ {
var s = IW4MAdmin.Program.ServerManager.GetServers().FirstOrDefault(s2 => s2.GetHashCode() == id); var s = Manager.GetServers().FirstOrDefault(s2 => s2.GetHashCode() == id);
if (s == null) if (s == null)
return View("Error", "Invalid server!"); return View("Error", "Invalid server!");

View File

@ -12,6 +12,12 @@ namespace WebfrontCore.ViewComponents
{ {
public async Task<IViewComponentResult> InvokeAsync(int offset) public async Task<IViewComponentResult> InvokeAsync(int offset)
{ {
int ip = HttpContext.Connection.RemoteIpAddress
.ToString().ConvertToIP();
bool authed = IW4MAdmin.ApplicationManager.GetInstance()
.AdministratorIPs.Contains(ip);
var penalties = await IW4MAdmin.ApplicationManager.GetInstance().GetPenaltyService().GetRecentPenalties(15, offset); var penalties = await IW4MAdmin.ApplicationManager.GetInstance().GetPenaltyService().GetRecentPenalties(15, offset);
var penaltiesDto = penalties.Select(p => new PenaltyInfo() var penaltiesDto = penalties.Select(p => new PenaltyInfo()
{ {
@ -23,8 +29,11 @@ namespace WebfrontCore.ViewComponents
Offense = p.Offense, Offense = p.Offense,
Type = p.Type.ToString(), Type = p.Type.ToString(),
TimePunished = Utilities.GetTimePassed(p.When, false), TimePunished = Utilities.GetTimePassed(p.When, false),
TimeRemaining = DateTime.UtcNow > p.Expires ? "" : Utilities.TimeSpanText(p.Expires - DateTime.UtcNow) TimeRemaining = DateTime.UtcNow > p.Expires ? "" : Utilities.TimeSpanText(p.Expires - DateTime.UtcNow),
}).ToList(); Sensitive = p.Type == SharedLibrary.Objects.Penalty.PenaltyType.Flag
});
penaltiesDto = authed ? penaltiesDto.ToList() : penaltiesDto.Where(p => !p.Sensitive).ToList();
return View("_List", penaltiesDto); return View("_List", penaltiesDto);
} }

View File

@ -8,7 +8,10 @@
</div> </div>
<div id="profile_info" class="text-center text-sm-left pr-3 pl-3"> <div id="profile_info" class="text-center text-sm-left pr-3 pl-3">
<div id="profile_name"> <div id="profile_name">
<h1><span class="client-name mr-4">@Model.Name<span id="profile_aliases_btn" class="oi oi-caret-bottom pl-2"></span></span></h1> @{
string displayAliasButton = Model.Aliases.Count > 0 ? "" : "display: none;";
}
<h1><span class="client-name mr-4">@Model.Name<span id="profile_aliases_btn" class="oi oi-caret-bottom pl-2 @displayAliasButton"></span></span></h1>
<div id="profile_aliases" class="pr-0 pr-sm-4 pb-2 mb-2 text-muted"> <div id="profile_aliases" class="pr-0 pr-sm-4 pb-2 mb-2 text-muted">
@{ @{
foreach (string alias in Model.Aliases) foreach (string alias in Model.Aliases)

View File

@ -8,19 +8,22 @@
<PackageId>WebfrontCore</PackageId> <PackageId>WebfrontCore</PackageId>
<Platforms>AnyCPU;x86</Platforms> <Platforms>AnyCPU;x86</Platforms>
<ApplicationIcon>wwwroot\favicon.ico</ApplicationIcon> <ApplicationIcon>wwwroot\favicon.ico</ApplicationIcon>
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
</PropertyGroup> </PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'"> <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
<PlatformTarget>x86</PlatformTarget> <PlatformTarget>x86</PlatformTarget>
</PropertyGroup> </PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x86'">
<OutputPath>bin\x86\Debug\</OutputPath>
</PropertyGroup>
<ItemGroup> <ItemGroup>
<Compile Remove="Application\Kayak.cs" /> <Compile Remove="Application\Kayak.cs" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<None Include="App.config" />
<None Include="Application\Kayak.cs" />
<None Update="wwwroot\**\*"> <None Update="wwwroot\**\*">
<CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory> <CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory>
</None> </None>
@ -60,12 +63,6 @@
</ProjectReference> </ProjectReference>
</ItemGroup> </ItemGroup>
<ItemGroup>
<Content Update="app.config">
<CopyToOutputDirectory>Never</CopyToOutputDirectory>
</Content>
</ItemGroup>
<Target Name="PostBuild" AfterTargets="PostBuildEvent"> <Target Name="PostBuild" AfterTargets="PostBuildEvent">
<Exec Command="xcopy /Y &quot;$(SolutionDir)BUILD\Plugins&quot; &quot;$(TargetDir)Plugins\&quot;&#xD;&#xA;xcopy /Y /I /E &quot;$(SolutionDir)BUILD\Lib&quot; &quot;$(TargetDir)&quot; &#xD;&#xA;&#xD;&#xA;xcopy /Y /I /E &quot;$(ProjectDir)Application\Config&quot; &quot;$(TargetDir)Config&quot;" /> <Exec Command="xcopy /Y &quot;$(SolutionDir)BUILD\Plugins&quot; &quot;$(TargetDir)Plugins\&quot;&#xD;&#xA;xcopy /Y /I /E &quot;$(SolutionDir)BUILD\Lib&quot; &quot;$(TargetDir)&quot; &#xD;&#xA;&#xD;&#xA;xcopy /Y /I /E &quot;$(ProjectDir)Application\Config&quot; &quot;$(TargetDir)Config&quot;" />
</Target> </Target>

View File

@ -31,6 +31,6 @@
<connectionStrings> <connectionStrings>
<add name="DefaultConnection" <add name="DefaultConnection"
providerName="System.Data.SqlServerCe.4.0" providerName="System.Data.SqlServerCe.4.0"
connectionString="Data Source=|DataDirectory|\Database\Database.sdf"/> connectionString="Data Source=|DataDirectory|\Database.sdf"/>
</connectionStrings> </connectionStrings>
</configuration> </configuration>

View File

@ -2,9 +2,9 @@
"Logging": { "Logging": {
"IncludeScopes": false, "IncludeScopes": false,
"LogLevel": { "LogLevel": {
"Default": "Debug", "Default": "Trace",
"System": "Information", "System": "Information",
"Microsoft": "Information" "Microsoft": "None"
} }
} }
} }