diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 967def1..55d9536 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -18,7 +18,7 @@ Build: script: - dotnet restore - dotnet test tests - - dotnet publish --version-suffix "$VERSION" -r linux-x64 -c Release -o ./app ./src/Bot/ + - dotnet publish --version-suffix "$VERSION" -r linux-x64 -c Release -p:PublishSingleFile=true -p:DebugType=embedded --no-self-contained -o ./app ./src/Startup/ Package: stage: docker diff --git a/Geekbot.net.sln b/Geekbot.net.sln index 78033a3..f33d887 100644 --- a/Geekbot.net.sln +++ b/Geekbot.net.sln @@ -15,6 +15,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Commands", "src\Commands\Co EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Interactions", "src\Interactions\Interactions.csproj", "{FF6859D9-C539-4910-BE1E-9ECFED2F46FA}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Startup", "src\Startup\Startup.csproj", "{A691B018-4B19-4A7A-A0F6-DBB17641254F}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -45,6 +47,10 @@ Global {FF6859D9-C539-4910-BE1E-9ECFED2F46FA}.Debug|Any CPU.Build.0 = Debug|Any CPU {FF6859D9-C539-4910-BE1E-9ECFED2F46FA}.Release|Any CPU.ActiveCfg = Release|Any CPU {FF6859D9-C539-4910-BE1E-9ECFED2F46FA}.Release|Any CPU.Build.0 = Release|Any CPU + {A691B018-4B19-4A7A-A0F6-DBB17641254F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {A691B018-4B19-4A7A-A0F6-DBB17641254F}.Debug|Any CPU.Build.0 = Debug|Any CPU + {A691B018-4B19-4A7A-A0F6-DBB17641254F}.Release|Any CPU.ActiveCfg = Release|Any CPU + {A691B018-4B19-4A7A-A0F6-DBB17641254F}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/src/Bot/Bot.csproj b/src/Bot/Bot.csproj index 7337f79..aab0802 100644 --- a/src/Bot/Bot.csproj +++ b/src/Bot/Bot.csproj @@ -1,26 +1,21 @@ - Exe net6.0 - win10-x64;linux-x64;linux-musl-x64 - false - derp.ico $(VersionSuffix) Geekbot.Bot - Geekbot + Geekbot.Bot $(VersionSuffix) 0.0.0-DEV - Pizza and Coffee Studios - Pizza and Coffee Studios - A Discord bot - https://github.com/pizzaandcoffee/Geekbot.net NU1701 - git - https://geekbot.pizzaandcoffee.rocks + enable + True + Library - - true + + + x64 + @@ -36,12 +31,7 @@ + - - - - - Ship.resx - diff --git a/src/Bot/BotStartup.cs b/src/Bot/BotStartup.cs new file mode 100644 index 0000000..4933742 --- /dev/null +++ b/src/Bot/BotStartup.cs @@ -0,0 +1,117 @@ +using System.Reflection; +using Discord; +using Discord.Commands; +using Discord.WebSocket; +using Geekbot.Bot.Handlers; +using Geekbot.Core; +using Geekbot.Core.Database; +using Geekbot.Core.GlobalSettings; +using Geekbot.Core.GuildSettingsManager; +using Geekbot.Core.Logger; +using Geekbot.Core.ReactionListener; +using Geekbot.Core.UserRepository; +using Microsoft.Extensions.DependencyInjection; + +namespace Geekbot.Bot; + +public class BotStartup +{ + private readonly IServiceCollection _serviceCollection; + private readonly GeekbotLogger _logger; + private readonly RunParameters _runParameters; + private readonly IGlobalSettings _globalSettings; + private DiscordSocketClient _client; + + public BotStartup(IServiceCollection serviceCollection, GeekbotLogger logger, RunParameters runParameters, IGlobalSettings globalSettings) + { + _serviceCollection = serviceCollection; + _logger = logger; + _runParameters = runParameters; + _globalSettings = globalSettings; + } + + public async Task Start() + { + _logger.Information(LogSource.Geekbot, "Connecting to Discord"); + SetupDiscordClient(); + await Login(); + await _client.SetGameAsync(_globalSettings.GetKey("Game")); + _logger.Information(LogSource.Geekbot, $"Now Connected as {_client.CurrentUser.Username} to {_client.Guilds.Count} Servers"); + + _logger.Information(LogSource.Geekbot, "Registering Gateway Handlers"); + await RegisterHandlers(); + + _logger.Information(LogSource.Geekbot, "Done and ready for use"); + await Task.Delay(-1); + } + + private void SetupDiscordClient() + { + _client = new DiscordSocketClient(new DiscordSocketConfig + { + LogLevel = LogSeverity.Verbose, + MessageCacheSize = 1000, + ExclusiveBulkDelete = true + }); + + var discordLogger = new DiscordLogger(_logger); + _client.Log += discordLogger.Log; + } + + private async Task Login() + { + try + { + var token = await GetToken(); + await _client.LoginAsync(TokenType.Bot, token); + await _client.StartAsync(); + while (!_client.ConnectionState.Equals(ConnectionState.Connected)) await Task.Delay(25); + } + catch (Exception e) + { + _logger.Error(LogSource.Geekbot, "Could not connect to Discord", e); + Environment.Exit(GeekbotExitCode.CouldNotLogin.GetHashCode()); + } + } + + private async Task GetToken() + { + var token = _runParameters.Token ?? _globalSettings.GetKey("DiscordToken"); + if (string.IsNullOrEmpty(token)) + { + Console.Write("Your bot Token: "); + var newToken = Console.ReadLine(); + await _globalSettings.SetKey("DiscordToken", newToken); + await _globalSettings.SetKey("Game", "Ping Pong"); + token = newToken; + } + + return token; + } + + private async Task RegisterHandlers() + { + var applicationInfo = await _client.GetApplicationInfoAsync(); + + _serviceCollection.AddSingleton(_client); + var serviceProvider = _serviceCollection.BuildServiceProvider(); + + var commands = new CommandService(); + await commands.AddModulesAsync(Assembly.GetAssembly(typeof(BotStartup)), serviceProvider); + + var commandHandler = new CommandHandler(serviceProvider.GetService(), _client, _logger, serviceProvider, commands, applicationInfo, serviceProvider.GetService()); + var userHandler = new UserHandler(serviceProvider.GetService(), _logger, serviceProvider.GetService(), _client); + var reactionHandler = new ReactionHandler(serviceProvider.GetService()); + var statsHandler = new StatsHandler(_logger, serviceProvider.GetService()); + var messageDeletedHandler = new MessageDeletedHandler(serviceProvider.GetService(), _logger, _client); + + _client.MessageReceived += commandHandler.RunCommand; + _client.MessageDeleted += messageDeletedHandler.HandleMessageDeleted; + _client.UserJoined += userHandler.Joined; + _client.UserUpdated += userHandler.Updated; + _client.UserLeft += userHandler.Left; + _client.ReactionAdded += reactionHandler.Added; + _client.ReactionRemoved += reactionHandler.Removed; + if (!_runParameters.InMemory) _client.MessageReceived += statsHandler.UpdateStats; + } +} \ No newline at end of file diff --git a/src/Bot/Program.cs b/src/Bot/Program.cs deleted file mode 100644 index ce91527..0000000 --- a/src/Bot/Program.cs +++ /dev/null @@ -1,247 +0,0 @@ -using System; -using System.Reflection; -using System.Text; -using System.Threading.Tasks; -using CommandLine; -using Discord; -using Discord.Commands; -using Discord.WebSocket; -using Geekbot.Bot.Handlers; -using Geekbot.Core; -using Geekbot.Core.Converters; -using Geekbot.Core.Database; -using Geekbot.Core.DiceParser; -using Geekbot.Core.ErrorHandling; -using Geekbot.Core.GlobalSettings; -using Geekbot.Core.GuildSettingsManager; -using Geekbot.Core.Highscores; -using Geekbot.Core.KvInMemoryStore; -using Geekbot.Core.Levels; -using Geekbot.Core.Logger; -using Geekbot.Core.Media; -using Geekbot.Core.RandomNumberGenerator; -using Geekbot.Core.ReactionListener; -using Geekbot.Core.UserRepository; -using Geekbot.Core.WikipediaClient; -using Geekbot.Web; -using Microsoft.EntityFrameworkCore; -using Microsoft.Extensions.DependencyInjection; -using Sentry; -using Constants = Geekbot.Core.Constants; - -namespace Geekbot.Bot -{ - internal class Program - { - private DiscordSocketClient _client; - private CommandService _commands; - private DatabaseInitializer _databaseInitializer; - private IGlobalSettings _globalSettings; - private IServiceProvider _servicesProvider; - private GeekbotLogger _logger; - private IUserRepository _userRepository; - private RunParameters _runParameters; - private IReactionListener _reactionListener; - private IGuildSettingsManager _guildSettingsManager; - - private static async Task Main(string[] args) - { - RunParameters runParameters = null; - Parser.Default.ParseArguments(args) - .WithParsed(e => runParameters = e) - .WithNotParsed(_ => Environment.Exit(GeekbotExitCode.InvalidArguments.GetHashCode())); - - var logo = new StringBuilder(); - logo.AppendLine(@" ____ _____ _____ _ ______ ___ _____"); - logo.AppendLine(@" / ___| ____| ____| |/ / __ ) / _ \\_ _|"); - logo.AppendLine(@"| | _| _| | _| | ' /| _ \| | | || |"); - logo.AppendLine(@"| |_| | |___| |___| . \| |_) | |_| || |"); - logo.AppendLine(@" \____|_____|_____|_|\_\____/ \___/ |_|"); - logo.AppendLine($"Version {Constants.BotVersion()} ".PadRight(41, '=')); - Console.WriteLine(logo.ToString()); - var logger = new GeekbotLogger(runParameters); - logger.Information(LogSource.Geekbot, "Starting..."); - try - { - await new Program().Start(runParameters, logger); - } - catch (Exception e) - { - logger.Error(LogSource.Geekbot, "RIP", e); - } - } - - private async Task Start(RunParameters runParameters, GeekbotLogger logger) - { - _logger = logger; - _runParameters = runParameters; - - logger.Information(LogSource.Geekbot, "Connecting to Database"); - var database = ConnectToDatabase(); - _globalSettings = new GlobalSettings(database); - - if (!runParameters.DisableGateway) - { - logger.Information(LogSource.Geekbot, "Connecting to Discord"); - SetupDiscordClient(); - await Login(); - _logger.Information(LogSource.Geekbot, $"Now Connected as {_client.CurrentUser.Username} to {_client.Guilds.Count} Servers"); - await _client.SetGameAsync(_globalSettings.GetKey("Game")); - } - - RegisterSentry(); - - _logger.Information(LogSource.Geekbot, "Loading Dependencies and Handlers"); - RegisterDependencies(); - if (!runParameters.DisableGateway) await RegisterHandlers(); - - if (runParameters.DisableApi) - { - _logger.Information(LogSource.Geekbot, "Done and ready for use"); - await Task.Delay(-1); - } - else - { - _logger.Information(LogSource.Api, "Starting Web API"); - StartWebApi(); - } - } - - private async Task Login() - { - try - { - var token = await GetToken(); - await _client.LoginAsync(TokenType.Bot, token); - await _client.StartAsync(); - while (!_client.ConnectionState.Equals(ConnectionState.Connected)) await Task.Delay(25); - } - catch (Exception e) - { - _logger.Error(LogSource.Geekbot, "Could not connect to Discord", e); - Environment.Exit(GeekbotExitCode.CouldNotLogin.GetHashCode()); - } - } - - private DatabaseContext ConnectToDatabase() - { - _databaseInitializer = new DatabaseInitializer(_runParameters, _logger); - var database = _databaseInitializer.Initialize(); - database.Database.EnsureCreated(); - if(!_runParameters.InMemory) database.Database.Migrate(); - - return database; - } - - private async Task GetToken() - { - var token = _runParameters.Token ?? _globalSettings.GetKey("DiscordToken"); - if (string.IsNullOrEmpty(token)) - { - Console.Write("Your bot Token: "); - var newToken = Console.ReadLine(); - await _globalSettings.SetKey("DiscordToken", newToken); - await _globalSettings.SetKey("Game", "Ping Pong"); - token = newToken; - } - - return token; - } - - private void SetupDiscordClient() - { - _client = new DiscordSocketClient(new DiscordSocketConfig - { - LogLevel = LogSeverity.Verbose, - MessageCacheSize = 1000, - ExclusiveBulkDelete = true - }); - - var discordLogger = new DiscordLogger(_logger); - _client.Log += discordLogger.Log; - } - - private void RegisterDependencies() - { - var services = new ServiceCollection(); - - _userRepository = new UserRepository(_databaseInitializer.Initialize(), _logger); - _reactionListener = new ReactionListener(_databaseInitializer.Initialize()); - _guildSettingsManager = new GuildSettingsManager(_databaseInitializer.Initialize()); - var fortunes = new FortunesProvider(_logger); - var levelCalc = new LevelCalc(); - var emojiConverter = new EmojiConverter(); - var mtgManaConverter = new MtgManaConverter(); - var wikipediaClient = new WikipediaClient(); - var randomNumberGenerator = new RandomNumberGenerator(); - var mediaProvider = new MediaProvider(_logger, randomNumberGenerator); - var kvMemoryStore = new KvInInMemoryStore(); - var errorHandler = new ErrorHandler(_logger, _runParameters, () => Geekbot.Core.Localization.Internal.SomethingWentWrong); - var diceParser = new DiceParser(randomNumberGenerator); - - services.AddSingleton(_userRepository); - services.AddSingleton(_logger); - services.AddSingleton(levelCalc); - services.AddSingleton(emojiConverter); - services.AddSingleton(fortunes); - services.AddSingleton(mediaProvider); - services.AddSingleton(mtgManaConverter); - services.AddSingleton(wikipediaClient); - services.AddSingleton(randomNumberGenerator); - services.AddSingleton(kvMemoryStore); - services.AddSingleton(_globalSettings); - services.AddSingleton(errorHandler); - services.AddSingleton(diceParser); - services.AddSingleton(_reactionListener); - services.AddSingleton(_guildSettingsManager); - services.AddTransient(e => new HighscoreManager(_databaseInitializer.Initialize(), _userRepository)); - services.AddTransient(e => _databaseInitializer.Initialize()); - if (!_runParameters.DisableGateway) services.AddSingleton(_client); - - _servicesProvider = services.BuildServiceProvider(); - } - - private async Task RegisterHandlers() - { - var applicationInfo = await _client.GetApplicationInfoAsync(); - - _commands = new CommandService(); - await _commands.AddModulesAsync(Assembly.GetEntryAssembly(), _servicesProvider); - - var commandHandler = new CommandHandler(_databaseInitializer.Initialize(), _client, _logger, _servicesProvider, _commands, applicationInfo, _guildSettingsManager); - var userHandler = new UserHandler(_userRepository, _logger, _databaseInitializer.Initialize(), _client); - var reactionHandler = new ReactionHandler(_reactionListener); - var statsHandler = new StatsHandler(_logger, _databaseInitializer.Initialize()); - var messageDeletedHandler = new MessageDeletedHandler(_databaseInitializer.Initialize(), _logger, _client); - - _client.MessageReceived += commandHandler.RunCommand; - _client.MessageDeleted += messageDeletedHandler.HandleMessageDeleted; - _client.UserJoined += userHandler.Joined; - _client.UserUpdated += userHandler.Updated; - _client.UserLeft += userHandler.Left; - _client.ReactionAdded += reactionHandler.Added; - _client.ReactionRemoved += reactionHandler.Removed; - if (!_runParameters.InMemory) _client.MessageReceived += statsHandler.UpdateStats; - } - - private void StartWebApi() - { - var highscoreManager = new HighscoreManager(_databaseInitializer.Initialize(), _userRepository); - WebApiStartup.StartWebApi(_servicesProvider, _logger, _runParameters, _commands, _databaseInitializer.Initialize(), _globalSettings, highscoreManager, _guildSettingsManager); - } - - private void RegisterSentry() - { - var sentryDsn = _runParameters.SentryEndpoint; - if (string.IsNullOrEmpty(sentryDsn)) return; - SentrySdk.Init(o => - { - o.Dsn = sentryDsn; - o.Release = Constants.BotVersion(); - o.Environment = "Production"; - o.TracesSampleRate = 1.0; - }); - _logger.Information(LogSource.Geekbot, $"Command Errors will be logged to Sentry: {sentryDsn}"); - } - } -} \ No newline at end of file diff --git a/src/Commands/Commands.csproj b/src/Commands/Commands.csproj index 787388c..3f7b12b 100644 --- a/src/Commands/Commands.csproj +++ b/src/Commands/Commands.csproj @@ -2,7 +2,6 @@ net6.0 - win10-x64;linux-x64;linux-musl-x64 $(VersionSuffix) $(VersionSuffix) 0.0.0-DEV @@ -12,6 +11,12 @@ CS8618 enable enable + True + Library + + + + x64 diff --git a/src/Core/Core.csproj b/src/Core/Core.csproj index c6d0a59..cba7069 100644 --- a/src/Core/Core.csproj +++ b/src/Core/Core.csproj @@ -2,13 +2,18 @@ net6.0 - win10-x64;linux-x64;linux-musl-x64 $(VersionSuffix) $(VersionSuffix) 0.0.0-DEV Geekbot.Core Geekbot.Core NU1701 + True + Library + + + + x64 diff --git a/src/Interactions/Interactions.csproj b/src/Interactions/Interactions.csproj index 2b993d0..bbc0b76 100644 --- a/src/Interactions/Interactions.csproj +++ b/src/Interactions/Interactions.csproj @@ -12,6 +12,12 @@ CS8618 enable enable + True + Library + + + + x64 diff --git a/src/Startup/Program.cs b/src/Startup/Program.cs new file mode 100644 index 0000000..e356335 --- /dev/null +++ b/src/Startup/Program.cs @@ -0,0 +1,135 @@ +using System.Text; +using CommandLine; +using Geekbot.Bot; +using Geekbot.Core; +using Geekbot.Core.Converters; +using Geekbot.Core.Database; +using Geekbot.Core.DiceParser; +using Geekbot.Core.ErrorHandling; +using Geekbot.Core.GlobalSettings; +using Geekbot.Core.GuildSettingsManager; +using Geekbot.Core.Highscores; +using Geekbot.Core.KvInMemoryStore; +using Geekbot.Core.Levels; +using Geekbot.Core.Logger; +using Geekbot.Core.Media; +using Geekbot.Core.RandomNumberGenerator; +using Geekbot.Core.ReactionListener; +using Geekbot.Core.UserRepository; +using Geekbot.Core.WikipediaClient; +using Geekbot.Web; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.DependencyInjection; +using Sentry; +using Constants = Geekbot.Core.Constants; + +// +// Parse Parameters +// +RunParameters runParameters = null!; +Parser.Default.ParseArguments(args) + .WithParsed(e => runParameters = e) + .WithNotParsed(_ => Environment.Exit(GeekbotExitCode.InvalidArguments.GetHashCode())); + +// +// Print Logo +// +var logo = new StringBuilder(); +logo.AppendLine(@" ____ _____ _____ _ ______ ___ _____"); +logo.AppendLine(@" / ___| ____| ____| |/ / __ ) / _ \\_ _|"); +logo.AppendLine(@"| | _| _| | _| | ' /| _ \| | | || |"); +logo.AppendLine(@"| |_| | |___| |___| . \| |_) | |_| || |"); +logo.AppendLine(@" \____|_____|_____|_|\_\____/ \___/ |_|"); +logo.AppendLine($"Version {Constants.BotVersion()} ".PadRight(41, '=')); +Console.WriteLine(logo.ToString()); + +// +// Init Logger +// +var logger = new GeekbotLogger(runParameters); +logger.Information(LogSource.Geekbot, "Starting..."); + +// +// Connect to Database +// +logger.Information(LogSource.Geekbot, "Connecting to Database"); +var databaseInitializer = new DatabaseInitializer(runParameters, logger); +var database = databaseInitializer.Initialize(); +database.Database.EnsureCreated(); +if(!runParameters.InMemory) database.Database.Migrate(); +var globalSettings = new GlobalSettings(database); + +// +// Register Services +// +logger.Information(LogSource.Geekbot, "Registering Services"); +RegisterSentry(); +var serviceProvider = RegisterServices(); + +// +// Start Gateway Bot +// +if (!runParameters.DisableGateway) +{ + new BotStartup(serviceProvider, logger, runParameters, globalSettings).Start(); +} + +// +// Start WebApi +// +if (!runParameters.DisableApi) +{ + WebApiStartup.StartWebApi(serviceProvider.BuildServiceProvider(), logger, runParameters, databaseInitializer.Initialize(), globalSettings); +} + +ServiceCollection RegisterServices() +{ + var services = new ServiceCollection(); + var userRepository = new UserRepository(databaseInitializer.Initialize(), logger); + var reactionListener = new ReactionListener(databaseInitializer.Initialize()); + var guildSettingsManager = new GuildSettingsManager(databaseInitializer.Initialize()); + var fortunes = new FortunesProvider(logger); + var levelCalc = new LevelCalc(); + var emojiConverter = new EmojiConverter(); + var mtgManaConverter = new MtgManaConverter(); + var wikipediaClient = new WikipediaClient(); + var randomNumberGenerator = new RandomNumberGenerator(); + var mediaProvider = new MediaProvider(logger, randomNumberGenerator); + var kvMemoryStore = new KvInInMemoryStore(); + var errorHandler = new ErrorHandler(logger, runParameters, () => Geekbot.Core.Localization.Internal.SomethingWentWrong); + var diceParser = new DiceParser(randomNumberGenerator); + + services.AddSingleton(userRepository); + services.AddSingleton(logger); + services.AddSingleton(levelCalc); + services.AddSingleton(emojiConverter); + services.AddSingleton(fortunes); + services.AddSingleton(mediaProvider); + services.AddSingleton(mtgManaConverter); + services.AddSingleton(wikipediaClient); + services.AddSingleton(randomNumberGenerator); + services.AddSingleton(kvMemoryStore); + services.AddSingleton(globalSettings); + services.AddSingleton(errorHandler); + services.AddSingleton(diceParser); + services.AddSingleton(reactionListener); + services.AddSingleton(guildSettingsManager); + services.AddTransient(e => new HighscoreManager(databaseInitializer.Initialize(), userRepository)); + services.AddTransient(e => databaseInitializer.Initialize()); + + return services; +} + +void RegisterSentry() +{ + var sentryDsn = runParameters.SentryEndpoint; + if (string.IsNullOrEmpty(sentryDsn)) return; + SentrySdk.Init(o => + { + o.Dsn = sentryDsn; + o.Release = Constants.BotVersion(); + o.Environment = "Production"; + o.TracesSampleRate = 1.0; + }); + logger.Information(LogSource.Geekbot, $"Command Errors will be logged to Sentry: {sentryDsn}"); +} \ No newline at end of file diff --git a/src/Startup/Startup.csproj b/src/Startup/Startup.csproj new file mode 100644 index 0000000..d38ef67 --- /dev/null +++ b/src/Startup/Startup.csproj @@ -0,0 +1,45 @@ + + + + derp.ico + Pizza and Coffee Studios + Pizza and Coffee Studios + A Discord bot + https://github.com/pizzaandcoffee/Geekbot.net + git + https://geekbot.pizzaandcoffee.rocks + + + + Exe + false + embedded + net6.0 + $(VersionSuffix) + $(VersionSuffix) + 0.0.0-DEV + Geekbot.Startup + Geekbot + NU1701 + CS8618 + enable + enable + True + + + + true + x64 + + + + x64 + + + + + + + + + diff --git a/src/Bot/derp.ico b/src/Startup/derp.ico similarity index 100% rename from src/Bot/derp.ico rename to src/Startup/derp.ico diff --git a/src/Web/Web.csproj b/src/Web/Web.csproj index 0f2cd54..a251901 100644 --- a/src/Web/Web.csproj +++ b/src/Web/Web.csproj @@ -2,8 +2,6 @@ net6.0 - win10-x64;linux-x64;linux-musl-x64 - false $(VersionSuffix) $(VersionSuffix) 0.0.0-DEV @@ -13,6 +11,12 @@ CS8618 enable enable + True + Library + + + + x64