diff --git a/marketplace/Boot.Chat.dll b/marketplace/Boot.Chat.dll deleted file mode 100644 index 32faf37..0000000 Binary files a/marketplace/Boot.Chat.dll and /dev/null differ diff --git a/marketplace/Boot.Codes.dll b/marketplace/Boot.Codes.dll deleted file mode 100644 index 11a87cd..0000000 Binary files a/marketplace/Boot.Codes.dll and /dev/null differ diff --git a/marketplace/Empostor.Plugin.Chat.dll b/marketplace/Empostor.Plugin.Chat.dll new file mode 100644 index 0000000..35498c1 Binary files /dev/null and b/marketplace/Empostor.Plugin.Chat.dll differ diff --git a/marketplace/Empostor.Plugin.Code.dll b/marketplace/Empostor.Plugin.Code.dll new file mode 100644 index 0000000..0be6c0d Binary files /dev/null and b/marketplace/Empostor.Plugin.Code.dll differ diff --git a/marketplace/Empostor.Plugin.Log.dll b/marketplace/Empostor.Plugin.Log.dll new file mode 100644 index 0000000..de48a85 Binary files /dev/null and b/marketplace/Empostor.Plugin.Log.dll differ diff --git a/marketplace/Empostor.Plugins.FixedCode.dll b/marketplace/Empostor.Plugins.FixedCode.dll index 8494b90..f4daae7 100644 Binary files a/marketplace/Empostor.Plugins.FixedCode.dll and b/marketplace/Empostor.Plugins.FixedCode.dll differ diff --git a/marketplace/Empostor.Plugins.FriendCodeValidator.dll b/marketplace/Empostor.Plugins.FriendCodeValidator.dll index 3871e71..2c275c9 100644 Binary files a/marketplace/Empostor.Plugins.FriendCodeValidator.dll and b/marketplace/Empostor.Plugins.FriendCodeValidator.dll differ diff --git a/marketplace/Empostor.Plugins.PlayerChannel.dll b/marketplace/Empostor.Plugins.PlayerChannel.dll index d94b466..37434d1 100644 Binary files a/marketplace/Empostor.Plugins.PlayerChannel.dll and b/marketplace/Empostor.Plugins.PlayerChannel.dll differ diff --git a/marketplace/Empostor.Plugins.QqVerify.dll b/marketplace/Empostor.Plugins.QqVerify.dll index 6310626..78bf44c 100644 Binary files a/marketplace/Empostor.Plugins.QqVerify.dll and b/marketplace/Empostor.Plugins.QqVerify.dll differ diff --git a/marketplace/Empostor.Plugins.Titles.dll b/marketplace/Empostor.Plugins.Titles.dll index 55d387e..4837c20 100644 Binary files a/marketplace/Empostor.Plugins.Titles.dll and b/marketplace/Empostor.Plugins.Titles.dll differ diff --git a/marketplace/Empostor.Plugins.Welcome1.1.0.dll b/marketplace/Empostor.Plugins.Welcome1.1.0.dll deleted file mode 100644 index 7074f5c..0000000 Binary files a/marketplace/Empostor.Plugins.Welcome1.1.0.dll and /dev/null differ diff --git a/marketplace/Serilog.Sinks.File.dll b/marketplace/Serilog.Sinks.File.dll deleted file mode 100644 index 29dc2fd..0000000 Binary files a/marketplace/Serilog.Sinks.File.dll and /dev/null differ diff --git a/marketplace/plugins.json b/marketplace/plugins.json index 2c7a7c7..33e3c64 100644 --- a/marketplace/plugins.json +++ b/marketplace/plugins.json @@ -1,40 +1,40 @@ [ { - "id": "serilog.sinks.file", - "name": "Log Output", - "description": "Write Serilog events to files in text and JSON formats, optionally rolling on time or size", - "author": "Serilog & miniduikboot", + "id": "cn.hayashiume.log", + "name": "Log to File", + "description": "Writes server logs to rolling files. Requires Serilog.Sinks.File.dll alongside this plugin.", + "author": "Serilog & HayashiUme", "versions": [ { - "version": "5.0.0", + "version": "1.0.0", "empostor_version": "2.0.0", - "download_url": "https://raw.githubusercontent.com/Empostor/Empostor/master/marketplace/Serilog.Sinks.File.dll" + "download_url": "https://raw.githubusercontent.com/Empostor/Empostor/master/marketplace/Empostor.Plugin.Log.dll" } ] }, { - "id": "at.duikbo.chat", - "name": "Output Chat Message", - "description": "Output lobbies chat message", - "author": "miniduikboot", + "id": "cn.hayashiume.chat", + "name": "Chat Manager", + "description": "Logs chat messages and enforces configurable message length limits for players and hosts.", + "author": "miniduikboot & HayashiUme", "versions": [ { - "version": "0.1", + "version": "1.0.0", "empostor_version": "2.0.0", - "download_url": "https://raw.githubusercontent.com/Empostor/Empostor/master/marketplace/Boot.Chat.dll" + "download_url": "https://raw.githubusercontent.com/Empostor/Empostor/master/marketplace/Empostor.Plugin.Chat.dll" } ] }, - { - "id": "at.duikbo.codes", - "name": "Custom GameCode", - "description": "The GameCode will spwan according to the BootCodes File", - "author": "miniduikboot", + { + "id": "cn.hayashiume.code", + "name": "Custom Game Codes", + "description": "Assigns game room codes from word lists placed in the Boot.Codes/ folder (4 or 6 letter codes).", + "author": "miniduikboot & HayashiUme", "versions": [ { - "version": "0.1", + "version": "1.0.0", "empostor_version": "2.0.0", - "download_url": "https://raw.githubusercontent.com/Empostor/Empostor/master/marketplace/Boot.Codes.dll" + "download_url": "https://raw.githubusercontent.com/Empostor/Empostor/master/marketplace/Empostor.Plugin.Code.dll" } ] }, @@ -42,11 +42,11 @@ "id": "cn.hayashiume.welcome", "name": "Welcome Messages", "description": "Sends a localised welcome message when players join a room.", - "author": "BunchHanpiDev & HayashiUme", + "author": "Fangkuai & HayashiUme", "versions": [ { "version": "1.0.0", - "empostor_version": "2.0.0", + "empostor_version": "2.0.0-old", "download_url": "https://raw.githubusercontent.com/Empostor/Empostor/master/marketplace/Empostor.Plugins.Welcome.dll" }, { @@ -82,7 +82,6 @@ } ] }, - { "id": "cn.hayashiume.titles", "name": "Custom Title", @@ -109,7 +108,7 @@ } ] }, - { + { "id": "cn.hayashiume.qqverify", "name": "QQ群验证API", "description": "[仅限中国],搭配 [本插件](https://github.com/HayashiUme/LLOneBot_Empostor_Verify) 使用", diff --git a/src/Empostor.Plugin.Chat/ChatConfig.cs b/src/Empostor.Plugin.Chat/ChatConfig.cs new file mode 100644 index 0000000..cd9e7ca --- /dev/null +++ b/src/Empostor.Plugin.Chat/ChatConfig.cs @@ -0,0 +1,10 @@ +namespace Empostor.Plugin.Chat; + +public sealed class ChatConfig +{ + public int PlayerMaxMessageLength { get; set; } = 300; + + public int HostMaxMessageLength { get; set; } = 1200; + + public string TooLongMessage { get; set; } = "[SERVER] Couldn't send your message, it was too long."; +} diff --git a/src/Empostor.Plugin.Chat/ChatEventListener.cs b/src/Empostor.Plugin.Chat/ChatEventListener.cs new file mode 100644 index 0000000..4594dd4 --- /dev/null +++ b/src/Empostor.Plugin.Chat/ChatEventListener.cs @@ -0,0 +1,20 @@ +using Empostor.Api.Events; +using Empostor.Api.Events.Player; + +namespace Empostor.Plugin.Chat; + +public sealed class ChatEventListener : IEventListener +{ + private readonly ChatService _chatService; + + public ChatEventListener(ChatService chatService) + { + _chatService = chatService; + } + + [EventListener] + public void OnPlayerChat(IPlayerChatEvent e) + { + _chatService.HandleChatMessage(e); + } +} diff --git a/src/Empostor.Plugin.Chat/ChatPlugin.cs b/src/Empostor.Plugin.Chat/ChatPlugin.cs new file mode 100644 index 0000000..a81905b --- /dev/null +++ b/src/Empostor.Plugin.Chat/ChatPlugin.cs @@ -0,0 +1,24 @@ +using System.Threading.Tasks; +using Empostor.Api.Plugins; +using Microsoft.Extensions.Logging; + +namespace Empostor.Plugin.Chat; + +[EmpostorPlugin("cn.hayashiume.chat")] +public sealed class ChatPlugin : PluginBase +{ + private readonly ILogger _logger; + + public ChatPlugin(ILogger logger) + { + _logger = logger; + } + + public override ValueTask EnableAsync() + { + _logger.LogInformation("[Chat] Enabled — limiting message length and logging chat."); + return default; + } + + public override ValueTask DisableAsync() => default; +} diff --git a/src/Empostor.Plugin.Chat/ChatService.cs b/src/Empostor.Plugin.Chat/ChatService.cs new file mode 100644 index 0000000..c31667e --- /dev/null +++ b/src/Empostor.Plugin.Chat/ChatService.cs @@ -0,0 +1,83 @@ +using System; +using System.IO; +using System.Text.Json; +using Empostor.Api.Events.Player; +using Microsoft.Extensions.Logging; + +namespace Empostor.Plugin.Chat; + +public sealed class ChatService +{ + private static readonly JsonSerializerOptions JsonOpts = new() { WriteIndented = true }; + + private readonly ILogger _logger; + private readonly ChatConfig _config; + + public ChatService(ILogger logger) + { + _logger = logger; + _config = LoadConfig(); + } + + public void HandleChatMessage(IPlayerChatEvent e) + { + var playerInfo = e.PlayerControl.PlayerInfo; + var playerName = playerInfo?.PlayerName ?? "?"; + var ownerId = e.PlayerControl.OwnerId; + + if (e.ClientPlayer.Character == e.PlayerControl) + { + _logger.LogInformation( + "[{GameCode}] {PlayerName} ({PlayerId}): {Message}", + e.Game.Code, playerName, ownerId, e.Message); + } + else + { + var senderName = e.ClientPlayer.Character?.PlayerInfo?.PlayerName ?? "?"; + var senderId = e.ClientPlayer.Client.Id; + _logger.LogInformation( + "[{GameCode}] {SenderName} ({SenderId}) on behalf of {PlayerName} ({PlayerId}): {Message}", + e.Game.Code, senderName, senderId, playerName, ownerId, e.Message); + } + + var isHost = e.ClientPlayer.IsHost; + var maxLength = isHost ? _config.HostMaxMessageLength : _config.PlayerMaxMessageLength; + + if (e.Message.Length > maxLength) + { + _logger.LogWarning( + "Cancelling chat message from {PlayerName} ({PlayerType}) of {Length} chars (max: {MaxLength}): too long", + playerName, isHost ? "host" : "player", e.Message.Length, maxLength); + + e.PlayerControl.SendChatToPlayerAsync(_config.TooLongMessage, e.PlayerControl); + e.IsCancelled = true; + } + } + + private static ChatConfig LoadConfig() + { + var path = Path.Combine(Directory.GetCurrentDirectory(), "boot_chat.json"); + if (!File.Exists(path)) + { + var defaults = new ChatConfig(); + var json = JsonSerializer.Serialize(new { Chat = defaults }, JsonOpts); + File.WriteAllText(path, json); + return defaults; + } + + try + { + var json = File.ReadAllText(path); + var doc = JsonDocument.Parse(json); + if (doc.RootElement.TryGetProperty("Chat", out var chatEl)) + { + var cfg = JsonSerializer.Deserialize(chatEl.GetRawText()); + if (cfg != null) + return cfg; + } + } + catch (JsonException) { } + + return new ChatConfig(); + } +} diff --git a/src/Empostor.Plugin.Chat/ChatStartup.cs b/src/Empostor.Plugin.Chat/ChatStartup.cs new file mode 100644 index 0000000..e5a81bc --- /dev/null +++ b/src/Empostor.Plugin.Chat/ChatStartup.cs @@ -0,0 +1,17 @@ +using Empostor.Api.Events; +using Empostor.Api.Plugins; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; + +namespace Empostor.Plugin.Chat; + +public sealed class ChatStartup : IPluginStartup +{ + public void ConfigureHost(IHostBuilder host) { } + + public void ConfigureServices(IServiceCollection services) + { + services.AddSingleton(); + services.AddSingleton(); + } +} diff --git a/src/Empostor.Plugin.Chat/Empostor.Plugin.Chat.csproj b/src/Empostor.Plugin.Chat/Empostor.Plugin.Chat.csproj new file mode 100644 index 0000000..4cd599c --- /dev/null +++ b/src/Empostor.Plugin.Chat/Empostor.Plugin.Chat.csproj @@ -0,0 +1,11 @@ + + + net8.0 + Empostor.Plugin.Chat + enable + + + + + + diff --git a/src/Empostor.Plugin.Chat/packages.lock.json b/src/Empostor.Plugin.Chat/packages.lock.json new file mode 100644 index 0000000..ac11d80 --- /dev/null +++ b/src/Empostor.Plugin.Chat/packages.lock.json @@ -0,0 +1,137 @@ +{ + "version": 1, + "dependencies": { + "net8.0": { + "BouncyCastle.Cryptography": { + "type": "Transitive", + "resolved": "2.6.1", + "contentHash": "vZsG2YILhthgRqO+ZVgRff4ZFKKTl0v7kqaVBLCtRvpREhfBP33pcWrdA3PRYgWuFL1RxiUFvjMUHTdBZlJcoA==" + }, + "Microsoft.AspNetCore.Http.Abstractions": { + "type": "Transitive", + "resolved": "2.2.0", + "contentHash": "Nxs7Z1q3f1STfLYKJSVXCs1iBl+Ya6E8o4Oy1bCxJ/rNI44E/0f6tbsrVqAWfB7jlnJfyaAtIalBVxPKUPQb4Q==", + "dependencies": { + "Microsoft.AspNetCore.Http.Features": "2.2.0", + "System.Text.Encodings.Web": "4.5.0" + } + }, + "Microsoft.AspNetCore.Http.Features": { + "type": "Transitive", + "resolved": "2.2.0", + "contentHash": "ziFz5zH8f33En4dX81LW84I6XrYXKf9jg6aM39cM+LffN9KJahViKZ61dGMSO2gd3e+qe5yBRwsesvyqlZaSMg==", + "dependencies": { + "Microsoft.Extensions.Primitives": "2.2.0" + } + }, + "Microsoft.Extensions.Configuration.Abstractions": { + "type": "Transitive", + "resolved": "8.0.0", + "contentHash": "3lE/iLSutpgX1CC0NOW70FJoGARRHbyKmG7dc0klnUZ9Dd9hS6N/POPWhKhMLCEuNN5nXEY5agmlFtH562vqhQ==", + "dependencies": { + "Microsoft.Extensions.Primitives": "8.0.0" + } + }, + "Microsoft.Extensions.DependencyInjection.Abstractions": { + "type": "Transitive", + "resolved": "9.0.6", + "contentHash": "0Zn6nR/6g+90MxskZyOOMPQvnPnrrGu6bytPwkV+azDcTtCSuQ1+GJUrg8Klmnrjk1i6zMpw2lXijl+tw7Q3kA==" + }, + "Microsoft.Extensions.Diagnostics.Abstractions": { + "type": "Transitive", + "resolved": "8.0.0", + "contentHash": "JHYCQG7HmugNYUhOl368g+NMxYE/N/AiclCYRNlgCY9eVyiBkOHMwK4x60RYMxv9EL3+rmj1mqHvdCiPpC+D4Q==", + "dependencies": { + "Microsoft.Extensions.DependencyInjection.Abstractions": "8.0.0", + "Microsoft.Extensions.Options": "8.0.0", + "System.Diagnostics.DiagnosticSource": "8.0.0" + } + }, + "Microsoft.Extensions.FileProviders.Abstractions": { + "type": "Transitive", + "resolved": "8.0.0", + "contentHash": "ZbaMlhJlpisjuWbvXr4LdAst/1XxH3vZ6A0BsgTphZ2L4PGuxRLz7Jr/S7mkAAnOn78Vu0fKhEgNF5JO3zfjqQ==", + "dependencies": { + "Microsoft.Extensions.Primitives": "8.0.0" + } + }, + "Microsoft.Extensions.Hosting.Abstractions": { + "type": "Transitive", + "resolved": "8.0.0", + "contentHash": "AG7HWwVRdCHlaA++1oKDxLsXIBxmDpMPb3VoyOoAghEWnkUvEAdYQUwnV4jJbAaa/nMYNiEh5ByoLauZBEiovg==", + "dependencies": { + "Microsoft.Extensions.Configuration.Abstractions": "8.0.0", + "Microsoft.Extensions.DependencyInjection.Abstractions": "8.0.0", + "Microsoft.Extensions.Diagnostics.Abstractions": "8.0.0", + "Microsoft.Extensions.FileProviders.Abstractions": "8.0.0", + "Microsoft.Extensions.Logging.Abstractions": "8.0.0" + } + }, + "Microsoft.Extensions.Logging.Abstractions": { + "type": "Transitive", + "resolved": "8.0.0", + "contentHash": "arDBqTgFCyS0EvRV7O3MZturChstm50OJ0y9bDJvAcmEPJm0FFpFyjU/JLYyStNGGey081DvnQYlncNX5SJJGA==", + "dependencies": { + "Microsoft.Extensions.DependencyInjection.Abstractions": "8.0.0" + } + }, + "Microsoft.Extensions.ObjectPool": { + "type": "Transitive", + "resolved": "8.0.1", + "contentHash": "X21b8D6z58EG34OMLH955750mFaEaGs5xLGe9Vyi3059Cgb3sk+p2k/JlQXaieEBRrk+SVlFzDei3Q6PdVzJOA==" + }, + "Microsoft.Extensions.Options": { + "type": "Transitive", + "resolved": "8.0.0", + "contentHash": "JOVOfqpnqlVLUzINQ2fox8evY2SKLYJ3BV8QDe/Jyp21u1T7r45x/R/5QdteURMR5r01GxeJSBBUOCOyaNXA3g==", + "dependencies": { + "Microsoft.Extensions.DependencyInjection.Abstractions": "8.0.0", + "Microsoft.Extensions.Primitives": "8.0.0" + } + }, + "Microsoft.Extensions.Primitives": { + "type": "Transitive", + "resolved": "8.0.0", + "contentHash": "bXJEZrW9ny8vjMF1JV253WeLhpEVzFo1lyaZu1vQ4ZxWUlVvknZ/+ftFgVheLubb4eZPSwwxBeqS1JkCOjxd8g==" + }, + "Serilog": { + "type": "Transitive", + "resolved": "4.3.1-dev-02373", + "contentHash": "f0HNPaleOox5++8zqxAdi9afnUUkoznLOmd0ur/UnLbzl8bIaPyyBGOpbIJmC1kz2vHfkQ0fXh6KXMdlz7bcLQ==" + }, + "System.Diagnostics.DiagnosticSource": { + "type": "Transitive", + "resolved": "8.0.0", + "contentHash": "c9xLpVz6PL9lp/djOWtk5KPDZq3cSYpmXoJQY524EOtuFl5z9ZtsotpsyrDW40U1DRnQSYvcPKEUV0X//u6gkQ==" + }, + "System.Text.Encodings.Web": { + "type": "Transitive", + "resolved": "4.5.0", + "contentHash": "Xg4G4Indi4dqP1iuAiMSwpiWS54ZghzR644OtsRCm/m/lBMG8dUBhLVN7hLm8NNrNTR+iGbshCPTwrvxZPlm4g==" + }, + "empostor.api": { + "type": "Project", + "dependencies": { + "Microsoft.AspNetCore.Http.Abstractions": "[2.2.0, )", + "Microsoft.Extensions.Hosting.Abstractions": "[8.0.0, )", + "Microsoft.Extensions.Logging.Abstractions": "[8.0.0, )", + "NextFast.Hazel": "[1.0.2-dev, )", + "NextFast.Hazel.Abstractions": "[1.0.2-dev, )" + } + }, + "NextFast.Hazel": { + "type": "Project", + "dependencies": { + "BouncyCastle.Cryptography": "[2.6.1, )", + "Microsoft.Extensions.DependencyInjection.Abstractions": "[9.0.6, )", + "Microsoft.Extensions.ObjectPool": "[8.0.1, )", + "NextFast.Hazel.Abstractions": "[1.0.2-dev, )", + "Serilog": "[4.3.1-dev-02373, )" + } + }, + "NextFast.Hazel.Abstractions": { + "type": "Project" + } + } + } +} \ No newline at end of file diff --git a/src/Empostor.Plugin.Code/CodePlugin.cs b/src/Empostor.Plugin.Code/CodePlugin.cs new file mode 100644 index 0000000..2e3e350 --- /dev/null +++ b/src/Empostor.Plugin.Code/CodePlugin.cs @@ -0,0 +1,29 @@ +using System.Threading.Tasks; +using Empostor.Api.Plugins; +using Microsoft.Extensions.Logging; + +namespace Empostor.Plugin.Code; + +[EmpostorPlugin("cn.hayashiume.code")] +public sealed class CodePlugin : PluginBase +{ + private readonly ILogger _logger; + private readonly IGameCodeManager _manager; + + public CodePlugin(ILogger logger, IGameCodeManager manager) + { + _logger = logger; + _manager = manager; + } + + public override ValueTask EnableAsync() + { + var total = _manager.FourCharCodes + _manager.SixCharCodes; + _logger.LogInformation( + "[Code] Loaded {FourCharCodes} 4-char codes and {SixCharCodes} 6-char codes [{Total} total] from {Path}", + _manager.FourCharCodes, _manager.SixCharCodes, total, _manager.Path); + return default; + } + + public override ValueTask DisableAsync() => default; +} diff --git a/src/Empostor.Plugin.Code/CodeStartup.cs b/src/Empostor.Plugin.Code/CodeStartup.cs new file mode 100644 index 0000000..64f83d4 --- /dev/null +++ b/src/Empostor.Plugin.Code/CodeStartup.cs @@ -0,0 +1,18 @@ +using Empostor.Api.Events; +using Empostor.Api.Plugins; +using Empostor.Plugin.Code.Handlers; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; + +namespace Empostor.Plugin.Code; + +public sealed class CodeStartup : IPluginStartup +{ + public void ConfigureHost(IHostBuilder host) { } + + public void ConfigureServices(IServiceCollection services) + { + services.AddSingleton(); + services.AddSingleton(); + } +} diff --git a/src/Empostor.Plugin.Code/Empostor.Plugin.Code.csproj b/src/Empostor.Plugin.Code/Empostor.Plugin.Code.csproj new file mode 100644 index 0000000..073fd93 --- /dev/null +++ b/src/Empostor.Plugin.Code/Empostor.Plugin.Code.csproj @@ -0,0 +1,10 @@ + + + net8.0 + Empostor.Plugin.Code + enable + + + + + diff --git a/src/Empostor.Plugin.Code/Extensions.cs b/src/Empostor.Plugin.Code/Extensions.cs new file mode 100644 index 0000000..e8af884 --- /dev/null +++ b/src/Empostor.Plugin.Code/Extensions.cs @@ -0,0 +1,18 @@ +using System.Collections; + +namespace Empostor.Plugin.Code; + +public static class Extensions +{ + public static void Shuffle(this T list) where T : IList + { + var count = list.Count; + for (var i = 0; i < count - 2; i++) + { + var index = StrongRandom.Next(i, count); + var value = list[i]; + list[i] = list[index]; + list[index] = value; + } + } +} diff --git a/src/Empostor.Plugin.Code/GameCodeManager.cs b/src/Empostor.Plugin.Code/GameCodeManager.cs new file mode 100644 index 0000000..4ea0c81 --- /dev/null +++ b/src/Empostor.Plugin.Code/GameCodeManager.cs @@ -0,0 +1,117 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using Empostor.Api.Games; +using Microsoft.Extensions.Logging; + +namespace Empostor.Plugin.Code; + +public sealed class GameCodeManager : IGameCodeManager +{ + private static readonly HashSet V2Chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ".ToHashSet(); + + private readonly ILogger _logger; + private readonly IGameCodeFactory _codeFactory; + private readonly List _codes; + private readonly HashSet _inUse; + private readonly object _sync = new(); + + public string Path => System.IO.Path.GetFullPath("Boot.Codes"); + + public int SixCharCodes { get; } + + public int FourCharCodes { get; } + + public GameCodeManager(ILogger logger, IGameCodeFactory codeFactory) + { + _logger = logger; + _codeFactory = codeFactory; + + _logger.LogInformation("[Code] Reading files from {Path}", Path); + + var list = Read().ToList(); + if (list.Count == 0) + { + _codes = new List(); + _inUse = new HashSet(); + return; + } + + Extensions.Shuffle(list); + + FourCharCodes = list.Count(c => c.Code.Length == 4); + SixCharCodes = list.Count(c => c.Code.Length == 6); + _codes = list; + _inUse = new HashSet(); + } + + private List Read() + { + var dirInfo = new DirectoryInfo(Path); + if (!dirInfo.Exists) + { + dirInfo.Create(); + _logger.LogWarning("[Code] No valid word list found. Place .txt files in Boot.Codes/ folder."); + return new List(); + } + + var seen = new HashSet(); + + foreach (var fileInfo in dirInfo.GetFiles()) + { + _logger.LogInformation("[Code] Reading \"{Name}\"", fileInfo.Name); + + foreach (var rawLine in File.ReadLines(fileInfo.FullName)) + { + var line = rawLine.Trim(); + if (string.IsNullOrWhiteSpace(line) || line.StartsWith("--")) + continue; + + var codeText = line.Split("--", 2, StringSplitOptions.None)[0].TrimEnd(); + + if (codeText.Length != 6 && codeText.Length != 4) + continue; + + if (!codeText.All(c => V2Chars.Contains(c))) + continue; + + var item = new GameCode(codeText); + if (!item.IsInvalid) + seen.Add(item); + } + } + + if (seen.Count > 0) + _logger.LogInformation("[Code] Finished loading {Count} codes.", seen.Count); + + return seen.ToList(); + } + + public GameCode Get() + { + lock (_sync) + { + if (_codes.Count == 0) + { + _logger.LogWarning("[Code] Ran out of codes — falling back to default code factory."); + return _codeFactory.Create(); + } + + var index = StrongRandom.Next(0, _codes.Count); + var gameCode = _codes[index]; + _codes.RemoveAt(index); + _inUse.Add(gameCode); + return gameCode; + } + } + + public void Release(GameCode code) + { + lock (_sync) + { + if (_inUse.Remove(code)) + _codes.Add(code); + } + } +} diff --git a/src/Empostor.Plugin.Code/Handlers/GameEventListener.cs b/src/Empostor.Plugin.Code/Handlers/GameEventListener.cs new file mode 100644 index 0000000..a4c0b39 --- /dev/null +++ b/src/Empostor.Plugin.Code/Handlers/GameEventListener.cs @@ -0,0 +1,26 @@ +using Empostor.Api.Events; +using Empostor.Api.Games; + +namespace Empostor.Plugin.Code.Handlers; + +public sealed class GameEventListener : IEventListener +{ + private readonly IGameCodeManager _gameCodeManager; + + public GameEventListener(IGameCodeManager gameCodeManager) + { + _gameCodeManager = gameCodeManager; + } + + [EventListener] + public void OnGameCreated(IGameCreationEvent e) + { + e.GameCode = _gameCodeManager.Get(); + } + + [EventListener] + public void OnGameDestroyed(IGameDestroyedEvent e) + { + _gameCodeManager.Release(e.Game.Code); + } +} diff --git a/src/Empostor.Plugin.Code/IGameCodeManager.cs b/src/Empostor.Plugin.Code/IGameCodeManager.cs new file mode 100644 index 0000000..d836b82 --- /dev/null +++ b/src/Empostor.Plugin.Code/IGameCodeManager.cs @@ -0,0 +1,16 @@ +using Empostor.Api.Games; + +namespace Empostor.Plugin.Code; + +public interface IGameCodeManager +{ + int SixCharCodes { get; } + + int FourCharCodes { get; } + + string Path { get; } + + GameCode Get(); + + void Release(GameCode code); +} diff --git a/src/Empostor.Plugin.Code/StrongRandom.cs b/src/Empostor.Plugin.Code/StrongRandom.cs new file mode 100644 index 0000000..69e82bc --- /dev/null +++ b/src/Empostor.Plugin.Code/StrongRandom.cs @@ -0,0 +1,32 @@ +using System; +using System.Security.Cryptography; + +namespace Empostor.Plugin.Code; + +public static class StrongRandom +{ + public static int Next(int minValue, int maxExclusiveValue) + { + if (minValue >= maxExclusiveValue) + throw new ArgumentOutOfRangeException(nameof(minValue), "minValue must be lower than maxExclusiveValue"); + + var range = (long)maxExclusiveValue - minValue; + var limit = (long)((ulong.MaxValue / (ulong)range) * (ulong)range); + + uint value; + do + { + value = GetUint(); + } + while ((ulong)value >= (ulong)limit); + + return (int)(minValue + (long)((ulong)value % (ulong)range)); + } + + private static uint GetUint() + { + var bytes = new byte[4]; + RandomNumberGenerator.Fill(bytes); + return BitConverter.ToUInt32(bytes); + } +} diff --git a/src/Empostor.Plugin.Code/packages.lock.json b/src/Empostor.Plugin.Code/packages.lock.json new file mode 100644 index 0000000..ac11d80 --- /dev/null +++ b/src/Empostor.Plugin.Code/packages.lock.json @@ -0,0 +1,137 @@ +{ + "version": 1, + "dependencies": { + "net8.0": { + "BouncyCastle.Cryptography": { + "type": "Transitive", + "resolved": "2.6.1", + "contentHash": "vZsG2YILhthgRqO+ZVgRff4ZFKKTl0v7kqaVBLCtRvpREhfBP33pcWrdA3PRYgWuFL1RxiUFvjMUHTdBZlJcoA==" + }, + "Microsoft.AspNetCore.Http.Abstractions": { + "type": "Transitive", + "resolved": "2.2.0", + "contentHash": "Nxs7Z1q3f1STfLYKJSVXCs1iBl+Ya6E8o4Oy1bCxJ/rNI44E/0f6tbsrVqAWfB7jlnJfyaAtIalBVxPKUPQb4Q==", + "dependencies": { + "Microsoft.AspNetCore.Http.Features": "2.2.0", + "System.Text.Encodings.Web": "4.5.0" + } + }, + "Microsoft.AspNetCore.Http.Features": { + "type": "Transitive", + "resolved": "2.2.0", + "contentHash": "ziFz5zH8f33En4dX81LW84I6XrYXKf9jg6aM39cM+LffN9KJahViKZ61dGMSO2gd3e+qe5yBRwsesvyqlZaSMg==", + "dependencies": { + "Microsoft.Extensions.Primitives": "2.2.0" + } + }, + "Microsoft.Extensions.Configuration.Abstractions": { + "type": "Transitive", + "resolved": "8.0.0", + "contentHash": "3lE/iLSutpgX1CC0NOW70FJoGARRHbyKmG7dc0klnUZ9Dd9hS6N/POPWhKhMLCEuNN5nXEY5agmlFtH562vqhQ==", + "dependencies": { + "Microsoft.Extensions.Primitives": "8.0.0" + } + }, + "Microsoft.Extensions.DependencyInjection.Abstractions": { + "type": "Transitive", + "resolved": "9.0.6", + "contentHash": "0Zn6nR/6g+90MxskZyOOMPQvnPnrrGu6bytPwkV+azDcTtCSuQ1+GJUrg8Klmnrjk1i6zMpw2lXijl+tw7Q3kA==" + }, + "Microsoft.Extensions.Diagnostics.Abstractions": { + "type": "Transitive", + "resolved": "8.0.0", + "contentHash": "JHYCQG7HmugNYUhOl368g+NMxYE/N/AiclCYRNlgCY9eVyiBkOHMwK4x60RYMxv9EL3+rmj1mqHvdCiPpC+D4Q==", + "dependencies": { + "Microsoft.Extensions.DependencyInjection.Abstractions": "8.0.0", + "Microsoft.Extensions.Options": "8.0.0", + "System.Diagnostics.DiagnosticSource": "8.0.0" + } + }, + "Microsoft.Extensions.FileProviders.Abstractions": { + "type": "Transitive", + "resolved": "8.0.0", + "contentHash": "ZbaMlhJlpisjuWbvXr4LdAst/1XxH3vZ6A0BsgTphZ2L4PGuxRLz7Jr/S7mkAAnOn78Vu0fKhEgNF5JO3zfjqQ==", + "dependencies": { + "Microsoft.Extensions.Primitives": "8.0.0" + } + }, + "Microsoft.Extensions.Hosting.Abstractions": { + "type": "Transitive", + "resolved": "8.0.0", + "contentHash": "AG7HWwVRdCHlaA++1oKDxLsXIBxmDpMPb3VoyOoAghEWnkUvEAdYQUwnV4jJbAaa/nMYNiEh5ByoLauZBEiovg==", + "dependencies": { + "Microsoft.Extensions.Configuration.Abstractions": "8.0.0", + "Microsoft.Extensions.DependencyInjection.Abstractions": "8.0.0", + "Microsoft.Extensions.Diagnostics.Abstractions": "8.0.0", + "Microsoft.Extensions.FileProviders.Abstractions": "8.0.0", + "Microsoft.Extensions.Logging.Abstractions": "8.0.0" + } + }, + "Microsoft.Extensions.Logging.Abstractions": { + "type": "Transitive", + "resolved": "8.0.0", + "contentHash": "arDBqTgFCyS0EvRV7O3MZturChstm50OJ0y9bDJvAcmEPJm0FFpFyjU/JLYyStNGGey081DvnQYlncNX5SJJGA==", + "dependencies": { + "Microsoft.Extensions.DependencyInjection.Abstractions": "8.0.0" + } + }, + "Microsoft.Extensions.ObjectPool": { + "type": "Transitive", + "resolved": "8.0.1", + "contentHash": "X21b8D6z58EG34OMLH955750mFaEaGs5xLGe9Vyi3059Cgb3sk+p2k/JlQXaieEBRrk+SVlFzDei3Q6PdVzJOA==" + }, + "Microsoft.Extensions.Options": { + "type": "Transitive", + "resolved": "8.0.0", + "contentHash": "JOVOfqpnqlVLUzINQ2fox8evY2SKLYJ3BV8QDe/Jyp21u1T7r45x/R/5QdteURMR5r01GxeJSBBUOCOyaNXA3g==", + "dependencies": { + "Microsoft.Extensions.DependencyInjection.Abstractions": "8.0.0", + "Microsoft.Extensions.Primitives": "8.0.0" + } + }, + "Microsoft.Extensions.Primitives": { + "type": "Transitive", + "resolved": "8.0.0", + "contentHash": "bXJEZrW9ny8vjMF1JV253WeLhpEVzFo1lyaZu1vQ4ZxWUlVvknZ/+ftFgVheLubb4eZPSwwxBeqS1JkCOjxd8g==" + }, + "Serilog": { + "type": "Transitive", + "resolved": "4.3.1-dev-02373", + "contentHash": "f0HNPaleOox5++8zqxAdi9afnUUkoznLOmd0ur/UnLbzl8bIaPyyBGOpbIJmC1kz2vHfkQ0fXh6KXMdlz7bcLQ==" + }, + "System.Diagnostics.DiagnosticSource": { + "type": "Transitive", + "resolved": "8.0.0", + "contentHash": "c9xLpVz6PL9lp/djOWtk5KPDZq3cSYpmXoJQY524EOtuFl5z9ZtsotpsyrDW40U1DRnQSYvcPKEUV0X//u6gkQ==" + }, + "System.Text.Encodings.Web": { + "type": "Transitive", + "resolved": "4.5.0", + "contentHash": "Xg4G4Indi4dqP1iuAiMSwpiWS54ZghzR644OtsRCm/m/lBMG8dUBhLVN7hLm8NNrNTR+iGbshCPTwrvxZPlm4g==" + }, + "empostor.api": { + "type": "Project", + "dependencies": { + "Microsoft.AspNetCore.Http.Abstractions": "[2.2.0, )", + "Microsoft.Extensions.Hosting.Abstractions": "[8.0.0, )", + "Microsoft.Extensions.Logging.Abstractions": "[8.0.0, )", + "NextFast.Hazel": "[1.0.2-dev, )", + "NextFast.Hazel.Abstractions": "[1.0.2-dev, )" + } + }, + "NextFast.Hazel": { + "type": "Project", + "dependencies": { + "BouncyCastle.Cryptography": "[2.6.1, )", + "Microsoft.Extensions.DependencyInjection.Abstractions": "[9.0.6, )", + "Microsoft.Extensions.ObjectPool": "[8.0.1, )", + "NextFast.Hazel.Abstractions": "[1.0.2-dev, )", + "Serilog": "[4.3.1-dev-02373, )" + } + }, + "NextFast.Hazel.Abstractions": { + "type": "Project" + } + } + } +} \ No newline at end of file diff --git a/src/Empostor.sln b/src/Empostor.sln index a797142..8edf6c4 100644 --- a/src/Empostor.sln +++ b/src/Empostor.sln @@ -1,3 +1,4 @@ + Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio Version 18 VisualStudioVersion = 18.5.11709.299 @@ -24,67 +25,208 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Empostor.Plugins.FixedCode" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Empostor.Plugins.FriendCodeValidator", "Empostor.Plugins.FriendCodeValidator\Empostor.Plugins.FriendCodeValidator.csproj", "{70CD7AC9-1DE3-CD23-602A-EFD504B91450}" EndProject - Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Empostor.Plugins.Titles", "Empostor.Plugins.Titles\Empostor.Plugins.Titles.csproj", "{3038597E-E1A1-2958-1CF7-C4203A85F424}" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Empostor.Plugins.QqVerify", "Empostor.Plugins.QqVerify\Empostor.Plugins.QqVerify.csproj", "{B8A5D7C3-1F4E-4A2B-9C6D-8E3F5A7B1D2E}" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Empostor.Plugins.PlayerChannel", "Empostor.Plugins.PlayerChannel\Empostor.Plugins.PlayerChannel.csproj", "{D4B6C8A2-5F3E-4C1D-9B7A-6E2F8A4C1D3F}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Empostor.Plugin.Log", "Empostor.Plugin.Log\Empostor.Plugin.Log.csproj", "{4A60E512-B425-4331-8E4E-E7CAC2B2EF36}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Empostor.Plugin.Chat", "Empostor.Plugin.Chat\Empostor.Plugin.Chat.csproj", "{9D1FD146-E7D9-4AFF-9B8E-25E6021A69D7}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Empostor.Plugin.Code", "Empostor.Plugin.Code\Empostor.Plugin.Code.csproj", "{ADC4F301-51C0-45DB-B97A-BE3844DFD19B}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU + Debug|x64 = Debug|x64 + Debug|x86 = Debug|x86 Release|Any CPU = Release|Any CPU + Release|x64 = Release|x64 + Release|x86 = Release|x86 EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution {1B0390AF-A4F3-4FE4-B093-708B0135C0B3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {1B0390AF-A4F3-4FE4-B093-708B0135C0B3}.Debug|Any CPU.Build.0 = Debug|Any CPU + {1B0390AF-A4F3-4FE4-B093-708B0135C0B3}.Debug|x64.ActiveCfg = Debug|Any CPU + {1B0390AF-A4F3-4FE4-B093-708B0135C0B3}.Debug|x64.Build.0 = Debug|Any CPU + {1B0390AF-A4F3-4FE4-B093-708B0135C0B3}.Debug|x86.ActiveCfg = Debug|Any CPU + {1B0390AF-A4F3-4FE4-B093-708B0135C0B3}.Debug|x86.Build.0 = Debug|Any CPU {1B0390AF-A4F3-4FE4-B093-708B0135C0B3}.Release|Any CPU.ActiveCfg = Release|Any CPU {1B0390AF-A4F3-4FE4-B093-708B0135C0B3}.Release|Any CPU.Build.0 = Release|Any CPU + {1B0390AF-A4F3-4FE4-B093-708B0135C0B3}.Release|x64.ActiveCfg = Release|Any CPU + {1B0390AF-A4F3-4FE4-B093-708B0135C0B3}.Release|x64.Build.0 = Release|Any CPU + {1B0390AF-A4F3-4FE4-B093-708B0135C0B3}.Release|x86.ActiveCfg = Release|Any CPU + {1B0390AF-A4F3-4FE4-B093-708B0135C0B3}.Release|x86.Build.0 = Release|Any CPU {E096A7D7-D693-4A13-A526-38CC574D84F8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {E096A7D7-D693-4A13-A526-38CC574D84F8}.Debug|Any CPU.Build.0 = Debug|Any CPU + {E096A7D7-D693-4A13-A526-38CC574D84F8}.Debug|x64.ActiveCfg = Debug|Any CPU + {E096A7D7-D693-4A13-A526-38CC574D84F8}.Debug|x64.Build.0 = Debug|Any CPU + {E096A7D7-D693-4A13-A526-38CC574D84F8}.Debug|x86.ActiveCfg = Debug|Any CPU + {E096A7D7-D693-4A13-A526-38CC574D84F8}.Debug|x86.Build.0 = Debug|Any CPU {E096A7D7-D693-4A13-A526-38CC574D84F8}.Release|Any CPU.ActiveCfg = Release|Any CPU {E096A7D7-D693-4A13-A526-38CC574D84F8}.Release|Any CPU.Build.0 = Release|Any CPU + {E096A7D7-D693-4A13-A526-38CC574D84F8}.Release|x64.ActiveCfg = Release|Any CPU + {E096A7D7-D693-4A13-A526-38CC574D84F8}.Release|x64.Build.0 = Release|Any CPU + {E096A7D7-D693-4A13-A526-38CC574D84F8}.Release|x86.ActiveCfg = Release|Any CPU + {E096A7D7-D693-4A13-A526-38CC574D84F8}.Release|x86.Build.0 = Release|Any CPU {8B6C3AC5-E2B2-49F8-854D-22DFB4AB3D31}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {8B6C3AC5-E2B2-49F8-854D-22DFB4AB3D31}.Debug|Any CPU.Build.0 = Debug|Any CPU + {8B6C3AC5-E2B2-49F8-854D-22DFB4AB3D31}.Debug|x64.ActiveCfg = Debug|Any CPU + {8B6C3AC5-E2B2-49F8-854D-22DFB4AB3D31}.Debug|x64.Build.0 = Debug|Any CPU + {8B6C3AC5-E2B2-49F8-854D-22DFB4AB3D31}.Debug|x86.ActiveCfg = Debug|Any CPU + {8B6C3AC5-E2B2-49F8-854D-22DFB4AB3D31}.Debug|x86.Build.0 = Debug|Any CPU {8B6C3AC5-E2B2-49F8-854D-22DFB4AB3D31}.Release|Any CPU.ActiveCfg = Release|Any CPU {8B6C3AC5-E2B2-49F8-854D-22DFB4AB3D31}.Release|Any CPU.Build.0 = Release|Any CPU + {8B6C3AC5-E2B2-49F8-854D-22DFB4AB3D31}.Release|x64.ActiveCfg = Release|Any CPU + {8B6C3AC5-E2B2-49F8-854D-22DFB4AB3D31}.Release|x64.Build.0 = Release|Any CPU + {8B6C3AC5-E2B2-49F8-854D-22DFB4AB3D31}.Release|x86.ActiveCfg = Release|Any CPU + {8B6C3AC5-E2B2-49F8-854D-22DFB4AB3D31}.Release|x86.Build.0 = Release|Any CPU {70F97BB7-12A1-4C7A-B2A5-B962D14B813E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {70F97BB7-12A1-4C7A-B2A5-B962D14B813E}.Debug|Any CPU.Build.0 = Debug|Any CPU + {70F97BB7-12A1-4C7A-B2A5-B962D14B813E}.Debug|x64.ActiveCfg = Debug|Any CPU + {70F97BB7-12A1-4C7A-B2A5-B962D14B813E}.Debug|x64.Build.0 = Debug|Any CPU + {70F97BB7-12A1-4C7A-B2A5-B962D14B813E}.Debug|x86.ActiveCfg = Debug|Any CPU + {70F97BB7-12A1-4C7A-B2A5-B962D14B813E}.Debug|x86.Build.0 = Debug|Any CPU {70F97BB7-12A1-4C7A-B2A5-B962D14B813E}.Release|Any CPU.ActiveCfg = Release|Any CPU {70F97BB7-12A1-4C7A-B2A5-B962D14B813E}.Release|Any CPU.Build.0 = Release|Any CPU + {70F97BB7-12A1-4C7A-B2A5-B962D14B813E}.Release|x64.ActiveCfg = Release|Any CPU + {70F97BB7-12A1-4C7A-B2A5-B962D14B813E}.Release|x64.Build.0 = Release|Any CPU + {70F97BB7-12A1-4C7A-B2A5-B962D14B813E}.Release|x86.ActiveCfg = Release|Any CPU + {70F97BB7-12A1-4C7A-B2A5-B962D14B813E}.Release|x86.Build.0 = Release|Any CPU {AA000002-0000-0000-0000-000000000002}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {AA000002-0000-0000-0000-000000000002}.Debug|Any CPU.Build.0 = Debug|Any CPU + {AA000002-0000-0000-0000-000000000002}.Debug|x64.ActiveCfg = Debug|Any CPU + {AA000002-0000-0000-0000-000000000002}.Debug|x64.Build.0 = Debug|Any CPU + {AA000002-0000-0000-0000-000000000002}.Debug|x86.ActiveCfg = Debug|Any CPU + {AA000002-0000-0000-0000-000000000002}.Debug|x86.Build.0 = Debug|Any CPU {AA000002-0000-0000-0000-000000000002}.Release|Any CPU.ActiveCfg = Release|Any CPU {AA000002-0000-0000-0000-000000000002}.Release|Any CPU.Build.0 = Release|Any CPU + {AA000002-0000-0000-0000-000000000002}.Release|x64.ActiveCfg = Release|Any CPU + {AA000002-0000-0000-0000-000000000002}.Release|x64.Build.0 = Release|Any CPU + {AA000002-0000-0000-0000-000000000002}.Release|x86.ActiveCfg = Release|Any CPU + {AA000002-0000-0000-0000-000000000002}.Release|x86.Build.0 = Release|Any CPU {07B43A32-0E0C-589F-4796-C15DE906326F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {07B43A32-0E0C-589F-4796-C15DE906326F}.Debug|Any CPU.Build.0 = Debug|Any CPU + {07B43A32-0E0C-589F-4796-C15DE906326F}.Debug|x64.ActiveCfg = Debug|Any CPU + {07B43A32-0E0C-589F-4796-C15DE906326F}.Debug|x64.Build.0 = Debug|Any CPU + {07B43A32-0E0C-589F-4796-C15DE906326F}.Debug|x86.ActiveCfg = Debug|Any CPU + {07B43A32-0E0C-589F-4796-C15DE906326F}.Debug|x86.Build.0 = Debug|Any CPU {07B43A32-0E0C-589F-4796-C15DE906326F}.Release|Any CPU.ActiveCfg = Release|Any CPU {07B43A32-0E0C-589F-4796-C15DE906326F}.Release|Any CPU.Build.0 = Release|Any CPU + {07B43A32-0E0C-589F-4796-C15DE906326F}.Release|x64.ActiveCfg = Release|Any CPU + {07B43A32-0E0C-589F-4796-C15DE906326F}.Release|x64.Build.0 = Release|Any CPU + {07B43A32-0E0C-589F-4796-C15DE906326F}.Release|x86.ActiveCfg = Release|Any CPU + {07B43A32-0E0C-589F-4796-C15DE906326F}.Release|x86.Build.0 = Release|Any CPU {5F7A70A7-1FE9-0B48-FFC1-C0F88BE0547A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {5F7A70A7-1FE9-0B48-FFC1-C0F88BE0547A}.Debug|Any CPU.Build.0 = Debug|Any CPU + {5F7A70A7-1FE9-0B48-FFC1-C0F88BE0547A}.Debug|x64.ActiveCfg = Debug|Any CPU + {5F7A70A7-1FE9-0B48-FFC1-C0F88BE0547A}.Debug|x64.Build.0 = Debug|Any CPU + {5F7A70A7-1FE9-0B48-FFC1-C0F88BE0547A}.Debug|x86.ActiveCfg = Debug|Any CPU + {5F7A70A7-1FE9-0B48-FFC1-C0F88BE0547A}.Debug|x86.Build.0 = Debug|Any CPU {5F7A70A7-1FE9-0B48-FFC1-C0F88BE0547A}.Release|Any CPU.ActiveCfg = Release|Any CPU {5F7A70A7-1FE9-0B48-FFC1-C0F88BE0547A}.Release|Any CPU.Build.0 = Release|Any CPU + {5F7A70A7-1FE9-0B48-FFC1-C0F88BE0547A}.Release|x64.ActiveCfg = Release|Any CPU + {5F7A70A7-1FE9-0B48-FFC1-C0F88BE0547A}.Release|x64.Build.0 = Release|Any CPU + {5F7A70A7-1FE9-0B48-FFC1-C0F88BE0547A}.Release|x86.ActiveCfg = Release|Any CPU + {5F7A70A7-1FE9-0B48-FFC1-C0F88BE0547A}.Release|x86.Build.0 = Release|Any CPU {C583BD2A-1468-99CA-4E3D-CBDA4D2EDB9F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {C583BD2A-1468-99CA-4E3D-CBDA4D2EDB9F}.Debug|Any CPU.Build.0 = Debug|Any CPU + {C583BD2A-1468-99CA-4E3D-CBDA4D2EDB9F}.Debug|x64.ActiveCfg = Debug|Any CPU + {C583BD2A-1468-99CA-4E3D-CBDA4D2EDB9F}.Debug|x64.Build.0 = Debug|Any CPU + {C583BD2A-1468-99CA-4E3D-CBDA4D2EDB9F}.Debug|x86.ActiveCfg = Debug|Any CPU + {C583BD2A-1468-99CA-4E3D-CBDA4D2EDB9F}.Debug|x86.Build.0 = Debug|Any CPU {C583BD2A-1468-99CA-4E3D-CBDA4D2EDB9F}.Release|Any CPU.ActiveCfg = Release|Any CPU {C583BD2A-1468-99CA-4E3D-CBDA4D2EDB9F}.Release|Any CPU.Build.0 = Release|Any CPU + {C583BD2A-1468-99CA-4E3D-CBDA4D2EDB9F}.Release|x64.ActiveCfg = Release|Any CPU + {C583BD2A-1468-99CA-4E3D-CBDA4D2EDB9F}.Release|x64.Build.0 = Release|Any CPU + {C583BD2A-1468-99CA-4E3D-CBDA4D2EDB9F}.Release|x86.ActiveCfg = Release|Any CPU + {C583BD2A-1468-99CA-4E3D-CBDA4D2EDB9F}.Release|x86.Build.0 = Release|Any CPU {70CD7AC9-1DE3-CD23-602A-EFD504B91450}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {70CD7AC9-1DE3-CD23-602A-EFD504B91450}.Debug|Any CPU.Build.0 = Debug|Any CPU + {70CD7AC9-1DE3-CD23-602A-EFD504B91450}.Debug|x64.ActiveCfg = Debug|Any CPU + {70CD7AC9-1DE3-CD23-602A-EFD504B91450}.Debug|x64.Build.0 = Debug|Any CPU + {70CD7AC9-1DE3-CD23-602A-EFD504B91450}.Debug|x86.ActiveCfg = Debug|Any CPU + {70CD7AC9-1DE3-CD23-602A-EFD504B91450}.Debug|x86.Build.0 = Debug|Any CPU {70CD7AC9-1DE3-CD23-602A-EFD504B91450}.Release|Any CPU.ActiveCfg = Release|Any CPU {70CD7AC9-1DE3-CD23-602A-EFD504B91450}.Release|Any CPU.Build.0 = Release|Any CPU + {70CD7AC9-1DE3-CD23-602A-EFD504B91450}.Release|x64.ActiveCfg = Release|Any CPU + {70CD7AC9-1DE3-CD23-602A-EFD504B91450}.Release|x64.Build.0 = Release|Any CPU + {70CD7AC9-1DE3-CD23-602A-EFD504B91450}.Release|x86.ActiveCfg = Release|Any CPU + {70CD7AC9-1DE3-CD23-602A-EFD504B91450}.Release|x86.Build.0 = Release|Any CPU {3038597E-E1A1-2958-1CF7-C4203A85F424}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {3038597E-E1A1-2958-1CF7-C4203A85F424}.Debug|Any CPU.Build.0 = Debug|Any CPU + {3038597E-E1A1-2958-1CF7-C4203A85F424}.Debug|x64.ActiveCfg = Debug|Any CPU + {3038597E-E1A1-2958-1CF7-C4203A85F424}.Debug|x64.Build.0 = Debug|Any CPU + {3038597E-E1A1-2958-1CF7-C4203A85F424}.Debug|x86.ActiveCfg = Debug|Any CPU + {3038597E-E1A1-2958-1CF7-C4203A85F424}.Debug|x86.Build.0 = Debug|Any CPU {3038597E-E1A1-2958-1CF7-C4203A85F424}.Release|Any CPU.ActiveCfg = Release|Any CPU {3038597E-E1A1-2958-1CF7-C4203A85F424}.Release|Any CPU.Build.0 = Release|Any CPU + {3038597E-E1A1-2958-1CF7-C4203A85F424}.Release|x64.ActiveCfg = Release|Any CPU + {3038597E-E1A1-2958-1CF7-C4203A85F424}.Release|x64.Build.0 = Release|Any CPU + {3038597E-E1A1-2958-1CF7-C4203A85F424}.Release|x86.ActiveCfg = Release|Any CPU + {3038597E-E1A1-2958-1CF7-C4203A85F424}.Release|x86.Build.0 = Release|Any CPU {B8A5D7C3-1F4E-4A2B-9C6D-8E3F5A7B1D2E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {B8A5D7C3-1F4E-4A2B-9C6D-8E3F5A7B1D2E}.Debug|Any CPU.Build.0 = Debug|Any CPU + {B8A5D7C3-1F4E-4A2B-9C6D-8E3F5A7B1D2E}.Debug|x64.ActiveCfg = Debug|Any CPU + {B8A5D7C3-1F4E-4A2B-9C6D-8E3F5A7B1D2E}.Debug|x64.Build.0 = Debug|Any CPU + {B8A5D7C3-1F4E-4A2B-9C6D-8E3F5A7B1D2E}.Debug|x86.ActiveCfg = Debug|Any CPU + {B8A5D7C3-1F4E-4A2B-9C6D-8E3F5A7B1D2E}.Debug|x86.Build.0 = Debug|Any CPU {B8A5D7C3-1F4E-4A2B-9C6D-8E3F5A7B1D2E}.Release|Any CPU.ActiveCfg = Release|Any CPU {B8A5D7C3-1F4E-4A2B-9C6D-8E3F5A7B1D2E}.Release|Any CPU.Build.0 = Release|Any CPU + {B8A5D7C3-1F4E-4A2B-9C6D-8E3F5A7B1D2E}.Release|x64.ActiveCfg = Release|Any CPU + {B8A5D7C3-1F4E-4A2B-9C6D-8E3F5A7B1D2E}.Release|x64.Build.0 = Release|Any CPU + {B8A5D7C3-1F4E-4A2B-9C6D-8E3F5A7B1D2E}.Release|x86.ActiveCfg = Release|Any CPU + {B8A5D7C3-1F4E-4A2B-9C6D-8E3F5A7B1D2E}.Release|x86.Build.0 = Release|Any CPU {D4B6C8A2-5F3E-4C1D-9B7A-6E2F8A4C1D3F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {D4B6C8A2-5F3E-4C1D-9B7A-6E2F8A4C1D3F}.Debug|Any CPU.Build.0 = Debug|Any CPU + {D4B6C8A2-5F3E-4C1D-9B7A-6E2F8A4C1D3F}.Debug|x64.ActiveCfg = Debug|Any CPU + {D4B6C8A2-5F3E-4C1D-9B7A-6E2F8A4C1D3F}.Debug|x64.Build.0 = Debug|Any CPU + {D4B6C8A2-5F3E-4C1D-9B7A-6E2F8A4C1D3F}.Debug|x86.ActiveCfg = Debug|Any CPU + {D4B6C8A2-5F3E-4C1D-9B7A-6E2F8A4C1D3F}.Debug|x86.Build.0 = Debug|Any CPU {D4B6C8A2-5F3E-4C1D-9B7A-6E2F8A4C1D3F}.Release|Any CPU.ActiveCfg = Release|Any CPU {D4B6C8A2-5F3E-4C1D-9B7A-6E2F8A4C1D3F}.Release|Any CPU.Build.0 = Release|Any CPU + {D4B6C8A2-5F3E-4C1D-9B7A-6E2F8A4C1D3F}.Release|x64.ActiveCfg = Release|Any CPU + {D4B6C8A2-5F3E-4C1D-9B7A-6E2F8A4C1D3F}.Release|x64.Build.0 = Release|Any CPU + {D4B6C8A2-5F3E-4C1D-9B7A-6E2F8A4C1D3F}.Release|x86.ActiveCfg = Release|Any CPU + {D4B6C8A2-5F3E-4C1D-9B7A-6E2F8A4C1D3F}.Release|x86.Build.0 = Release|Any CPU + {4A60E512-B425-4331-8E4E-E7CAC2B2EF36}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {4A60E512-B425-4331-8E4E-E7CAC2B2EF36}.Debug|Any CPU.Build.0 = Debug|Any CPU + {4A60E512-B425-4331-8E4E-E7CAC2B2EF36}.Debug|x64.ActiveCfg = Debug|Any CPU + {4A60E512-B425-4331-8E4E-E7CAC2B2EF36}.Debug|x64.Build.0 = Debug|Any CPU + {4A60E512-B425-4331-8E4E-E7CAC2B2EF36}.Debug|x86.ActiveCfg = Debug|Any CPU + {4A60E512-B425-4331-8E4E-E7CAC2B2EF36}.Debug|x86.Build.0 = Debug|Any CPU + {4A60E512-B425-4331-8E4E-E7CAC2B2EF36}.Release|Any CPU.ActiveCfg = Release|Any CPU + {4A60E512-B425-4331-8E4E-E7CAC2B2EF36}.Release|Any CPU.Build.0 = Release|Any CPU + {4A60E512-B425-4331-8E4E-E7CAC2B2EF36}.Release|x64.ActiveCfg = Release|Any CPU + {4A60E512-B425-4331-8E4E-E7CAC2B2EF36}.Release|x64.Build.0 = Release|Any CPU + {4A60E512-B425-4331-8E4E-E7CAC2B2EF36}.Release|x86.ActiveCfg = Release|Any CPU + {4A60E512-B425-4331-8E4E-E7CAC2B2EF36}.Release|x86.Build.0 = Release|Any CPU + {9D1FD146-E7D9-4AFF-9B8E-25E6021A69D7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {9D1FD146-E7D9-4AFF-9B8E-25E6021A69D7}.Debug|Any CPU.Build.0 = Debug|Any CPU + {9D1FD146-E7D9-4AFF-9B8E-25E6021A69D7}.Debug|x64.ActiveCfg = Debug|Any CPU + {9D1FD146-E7D9-4AFF-9B8E-25E6021A69D7}.Debug|x64.Build.0 = Debug|Any CPU + {9D1FD146-E7D9-4AFF-9B8E-25E6021A69D7}.Debug|x86.ActiveCfg = Debug|Any CPU + {9D1FD146-E7D9-4AFF-9B8E-25E6021A69D7}.Debug|x86.Build.0 = Debug|Any CPU + {9D1FD146-E7D9-4AFF-9B8E-25E6021A69D7}.Release|Any CPU.ActiveCfg = Release|Any CPU + {9D1FD146-E7D9-4AFF-9B8E-25E6021A69D7}.Release|Any CPU.Build.0 = Release|Any CPU + {9D1FD146-E7D9-4AFF-9B8E-25E6021A69D7}.Release|x64.ActiveCfg = Release|Any CPU + {9D1FD146-E7D9-4AFF-9B8E-25E6021A69D7}.Release|x64.Build.0 = Release|Any CPU + {9D1FD146-E7D9-4AFF-9B8E-25E6021A69D7}.Release|x86.ActiveCfg = Release|Any CPU + {9D1FD146-E7D9-4AFF-9B8E-25E6021A69D7}.Release|x86.Build.0 = Release|Any CPU + {ADC4F301-51C0-45DB-B97A-BE3844DFD19B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {ADC4F301-51C0-45DB-B97A-BE3844DFD19B}.Debug|Any CPU.Build.0 = Debug|Any CPU + {ADC4F301-51C0-45DB-B97A-BE3844DFD19B}.Debug|x64.ActiveCfg = Debug|Any CPU + {ADC4F301-51C0-45DB-B97A-BE3844DFD19B}.Debug|x64.Build.0 = Debug|Any CPU + {ADC4F301-51C0-45DB-B97A-BE3844DFD19B}.Debug|x86.ActiveCfg = Debug|Any CPU + {ADC4F301-51C0-45DB-B97A-BE3844DFD19B}.Debug|x86.Build.0 = Debug|Any CPU + {ADC4F301-51C0-45DB-B97A-BE3844DFD19B}.Release|Any CPU.ActiveCfg = Release|Any CPU + {ADC4F301-51C0-45DB-B97A-BE3844DFD19B}.Release|Any CPU.Build.0 = Release|Any CPU + {ADC4F301-51C0-45DB-B97A-BE3844DFD19B}.Release|x64.ActiveCfg = Release|Any CPU + {ADC4F301-51C0-45DB-B97A-BE3844DFD19B}.Release|x64.Build.0 = Release|Any CPU + {ADC4F301-51C0-45DB-B97A-BE3844DFD19B}.Release|x86.ActiveCfg = Release|Any CPU + {ADC4F301-51C0-45DB-B97A-BE3844DFD19B}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -99,6 +241,9 @@ Global {3038597E-E1A1-2958-1CF7-C4203A85F424} = {E0009DCB-32A6-45C4-A06C-7F62D4091A29} {B8A5D7C3-1F4E-4A2B-9C6D-8E3F5A7B1D2E} = {E0009DCB-32A6-45C4-A06C-7F62D4091A29} {D4B6C8A2-5F3E-4C1D-9B7A-6E2F8A4C1D3F} = {E0009DCB-32A6-45C4-A06C-7F62D4091A29} + {4A60E512-B425-4331-8E4E-E7CAC2B2EF36} = {E0009DCB-32A6-45C4-A06C-7F62D4091A29} + {9D1FD146-E7D9-4AFF-9B8E-25E6021A69D7} = {E0009DCB-32A6-45C4-A06C-7F62D4091A29} + {ADC4F301-51C0-45DB-B97A-BE3844DFD19B} = {E0009DCB-32A6-45C4-A06C-7F62D4091A29} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {DAF5625C-7E46-4DCC-AF2C-11D20D965454}