private void OnManuallyTriggerClusterUpdate(string clusterKey) { var context = _contextManager.GetCluster(clusterKey); if (context == null) { MessageBox.Show($"Could not find cluster instance '{clusterKey}'", "Error", MessageBoxButton.OK, MessageBoxImage.Error); } _contextManager.QueueUpdateClusterManual(context); }
internal async Task Init() { System.Console.WriteLine("ARK Bot"); System.Console.WriteLine("------------------------------------------------------"); System.Console.WriteLine(); log4net.Config.XmlConfigurator.Configure(); //load config and check for errors if (!File.Exists(Constants.ConfigFilePath)) { WriteAndWaitForKey($@"The required file config.json is missing from application directory. Please copy defaultconfig.json, set the correct values for your environment and restart the application."); return; } if (!new WindowsPrincipal(WindowsIdentity.GetCurrent()).IsInRole(WindowsBuiltInRole.Administrator)) { WriteAndWaitForKey($@"This application must be run as administrator in order to function properly."); return; } _config = null; string exceptionMessage = null; try { _config = JsonConvert.DeserializeObject <Config>(File.ReadAllText(Constants.ConfigFilePath)); } catch (Exception ex) { exceptionMessage = ex.Message; } if (_config == null) { WriteAndWaitForKey( $@"The required file config.json is empty or contains errors. Please copy defaultconfig.json, set the correct values for your environment and restart the application.", exceptionMessage); return; } var sb = new StringBuilder(); if (string.IsNullOrWhiteSpace(_config.BotId) || !new Regex(@"^[a-z0-9]+$", RegexOptions.IgnoreCase | RegexOptions.Singleline).IsMatch(_config.BotId)) { sb.AppendLine($@"Error: {nameof(_config.BotId)} is not a valid id."); sb.AppendLine($@"Expected value: {ValidationHelper.GetDescriptionForMember(_config, nameof(_config.BotId))}"); sb.AppendLine(); } if (string.IsNullOrWhiteSpace(_config.BotName)) { sb.AppendLine($@"Error: {nameof(_config.BotName)} is not set."); sb.AppendLine($@"Expected value: {ValidationHelper.GetDescriptionForMember(_config, nameof(_config.BotName))}"); sb.AppendLine(); } if (string.IsNullOrWhiteSpace(_config.BotNamespace) || !Uri.IsWellFormedUriString(_config.BotNamespace, UriKind.Absolute)) { sb.AppendLine($@"Error: {nameof(_config.BotNamespace)} is not set or not a valid url."); sb.AppendLine($@"Expected value: {ValidationHelper.GetDescriptionForMember(_config, nameof(_config.BotNamespace))}"); sb.AppendLine(); } if (string.IsNullOrWhiteSpace(_config.TempFileOutputDirPath) || !Directory.Exists(_config.TempFileOutputDirPath)) { sb.AppendLine($@"Error: {nameof(_config.TempFileOutputDirPath)} is not a valid directory path."); sb.AppendLine($@"Expected value: {ValidationHelper.GetDescriptionForMember(_config, nameof(_config.TempFileOutputDirPath))}"); sb.AppendLine(); } if (string.IsNullOrWhiteSpace(_config.BotToken)) { sb.AppendLine($@"Error: {nameof(_config.BotToken)} is not set."); sb.AppendLine($@"Expected value: {ValidationHelper.GetDescriptionForMember(_config, nameof(_config.BotToken))}"); sb.AppendLine(); } if (string.IsNullOrWhiteSpace(_config.SteamOpenIdRedirectUri)) { sb.AppendLine($@"Error: {nameof(_config.SteamOpenIdRedirectUri)} is not set."); sb.AppendLine($@"Expected value: {ValidationHelper.GetDescriptionForMember(_config, nameof(_config.SteamOpenIdRedirectUri))}"); sb.AppendLine(); } if (string.IsNullOrWhiteSpace(_config.SteamOpenIdRelyingServiceListenPrefix)) { sb.AppendLine($@"Error: {nameof(_config.SteamOpenIdRelyingServiceListenPrefix)} is not set."); sb.AppendLine($@"Expected value: {ValidationHelper.GetDescriptionForMember(_config, nameof(_config.SteamOpenIdRelyingServiceListenPrefix))}"); sb.AppendLine(); } if (string.IsNullOrWhiteSpace(_config.GoogleApiKey)) { sb.AppendLine($@"Error: {nameof(_config.GoogleApiKey)} is not set."); sb.AppendLine($@"Expected value: {ValidationHelper.GetDescriptionForMember(_config, nameof(_config.GoogleApiKey))}"); sb.AppendLine(); } if (string.IsNullOrWhiteSpace(_config.SteamApiKey)) { sb.AppendLine($@"Error: {nameof(_config.SteamApiKey)} is not set."); sb.AppendLine($@"Expected value: {ValidationHelper.GetDescriptionForMember(_config, nameof(_config.SteamApiKey))}"); sb.AppendLine(); } if (_config.BackupsEnabled && (string.IsNullOrWhiteSpace(_config.BackupsDirectoryPath) || !FileHelper.IsValidDirectoryPath(_config.BackupsDirectoryPath))) { sb.AppendLine($@"Error: {nameof(_config.BackupsDirectoryPath)} is not a valid directory path."); sb.AppendLine($@"Expected value: {ValidationHelper.GetDescriptionForMember(_config, nameof(_config.BackupsDirectoryPath))}"); sb.AppendLine(); } if (string.IsNullOrWhiteSpace(_config.WebApiListenPrefix)) { sb.AppendLine($@"Error: {nameof(_config.WebApiListenPrefix)} is not set."); sb.AppendLine($@"Expected value: {ValidationHelper.GetDescriptionForMember(_config, nameof(_config.WebApiListenPrefix))}"); sb.AppendLine(); } if (string.IsNullOrWhiteSpace(_config.WebAppListenPrefix)) { sb.AppendLine($@"Error: {nameof(_config.WebAppListenPrefix)} is not set."); sb.AppendLine($@"Expected value: {ValidationHelper.GetDescriptionForMember(_config, nameof(_config.WebAppListenPrefix))}"); sb.AppendLine(); } if (_config.Ssl?.Enabled == true) { if (string.IsNullOrWhiteSpace(_config.Ssl.Name)) { sb.AppendLine($@"Error: {nameof(_config.Ssl)}.{nameof(_config.Ssl.Name)} is not set."); sb.AppendLine($@"Expected value: {ValidationHelper.GetDescriptionForMember(_config.Ssl, nameof(_config.Ssl.Name))}"); sb.AppendLine(); } if (string.IsNullOrWhiteSpace(_config.Ssl.Password)) { sb.AppendLine($@"Error: {nameof(_config.Ssl)}.{nameof(_config.Ssl.Password)} is not set."); sb.AppendLine($@"Expected value: {ValidationHelper.GetDescriptionForMember(_config.Ssl, nameof(_config.Ssl.Password))}"); sb.AppendLine(); } if (string.IsNullOrWhiteSpace(_config.Ssl.Email)) { sb.AppendLine($@"Error: {nameof(_config.Ssl)}.{nameof(_config.Ssl.Email)} is not set."); sb.AppendLine($@"Expected value: {ValidationHelper.GetDescriptionForMember(_config.Ssl, nameof(_config.Ssl.Email))}"); sb.AppendLine(); } if (!(_config.Ssl.Domains?.Length >= 1) || _config.Ssl.Domains.Any(x => string.IsNullOrWhiteSpace(x))) { sb.AppendLine($@"Error: {nameof(_config.Ssl)}.{nameof(_config.Ssl.Domains)} is not set."); sb.AppendLine($@"Expected value: {ValidationHelper.GetDescriptionForMember(_config.Ssl, nameof(_config.Ssl.Domains))}"); sb.AppendLine(); } if (string.IsNullOrWhiteSpace(_config.Ssl.ChallengeListenPrefix)) { sb.AppendLine($@"Error: {nameof(_config.Ssl)}.{nameof(_config.Ssl.ChallengeListenPrefix)} is not set."); sb.AppendLine($@"Expected value: {ValidationHelper.GetDescriptionForMember(_config.Ssl, nameof(_config.Ssl.ChallengeListenPrefix))}"); sb.AppendLine(); } } var clusterkeys = _config.Clusters?.Select(x => x.Key).ToArray(); var serverkeys = _config.Servers?.Select(x => x.Key).ToArray(); if (serverkeys?.Length > 0 && serverkeys.Length != serverkeys.Distinct(StringComparer.OrdinalIgnoreCase).Count()) { sb.AppendLine($@"Error: {nameof(_config.Servers)} contain non-unique keys."); sb.AppendLine($@"Expected value: {ValidationHelper.GetDescriptionForMember(_config, nameof(_config.Servers))}"); sb.AppendLine(); } if (clusterkeys?.Length > 0 && clusterkeys.Length != clusterkeys.Distinct(StringComparer.OrdinalIgnoreCase).Count()) { sb.AppendLine($@"Error: {nameof(_config.Clusters)} contain non-unique keys."); sb.AppendLine($@"Expected value: {ValidationHelper.GetDescriptionForMember(_config, nameof(_config.Clusters))}"); sb.AppendLine(); } if (_config.Servers?.Length > 0) { foreach (var server in _config.Servers) { if (server.Cluster != null && !clusterkeys.Contains(server.Cluster)) { sb.AppendLine($@"Error: {nameof(_config.Servers)}.{nameof(server.Cluster)} reference missing cluster key ""{server.Cluster}""."); sb.AppendLine($@"Expected value: {ValidationHelper.GetDescriptionForMember(server, nameof(server.Cluster))}"); sb.AppendLine(); } if (string.IsNullOrWhiteSpace(server.SaveFilePath) || !File.Exists(server.SaveFilePath)) { sb.AppendLine($@"Error: {nameof(_config.Servers)}.{nameof(server.SaveFilePath)} is not a valid file path for server instance ""{server.Key}""."); sb.AppendLine($@"Expected value: {ValidationHelper.GetDescriptionForMember(server, nameof(server.SaveFilePath))}"); sb.AppendLine(); } if (string.IsNullOrWhiteSpace(server.Ip)) { sb.AppendLine($@"Error: {nameof(_config.Servers)}.{nameof(server.Ip)} is not set for server instance ""{server.Key}""."); sb.AppendLine($@"Expected value: {ValidationHelper.GetDescriptionForMember(server, nameof(server.Ip))}"); sb.AppendLine(); } if (server.Port <= 0) { sb.AppendLine($@"Error: {nameof(_config.Servers)}.{nameof(server.Port)} is not valid for server instance ""{server.Key}""."); sb.AppendLine($@"Expected value: {ValidationHelper.GetDescriptionForMember(server, nameof(server.Port))}"); sb.AppendLine(); } //if (server.RconPort <= 0) //{ // sb.AppendLine($@"Error: {nameof(config.Servers)}.{nameof(server.RconPort)} is not valid for server instance ""{server.Key}""."); // sb.AppendLine($@"Expected value: {ValidationHelper.GetDescriptionForMember(server, nameof(server.RconPort))}"); // sb.AppendLine(); //} } } if (_config.Clusters?.Length > 0) { foreach (var cluster in _config.Clusters) { if (string.IsNullOrWhiteSpace(cluster.SavePath) || !Directory.Exists(cluster.SavePath)) { sb.AppendLine($@"Error: {nameof(_config.Servers)}.{nameof(cluster.SavePath)} is not a valid directory path for cluster instance ""{cluster.Key}""."); sb.AppendLine($@"Expected value: {ValidationHelper.GetDescriptionForMember(cluster, nameof(cluster.SavePath))}"); sb.AppendLine(); } } } //todo: for now this section is not really needed unless !imprintcheck is used //if (config.ArkMultipliers == null) //{ // sb.AppendLine($@"Error: {nameof(config.ArkMultipliers)} section is missing from config file."); // sb.AppendLine($@"Expected value: {ValidationHelper.GetDescriptionForMember(config, nameof(config.ArkMultipliers))}"); // sb.AppendLine(); //} if (_config.AnonymizeWebApiData) { System.Console.WriteLine("Anonymizing all data in the WebAPI (anonymizeWebApiData=true)" + Environment.NewLine); } if (string.IsNullOrWhiteSpace(_config.MemberRoleName)) { _config.MemberRoleName = "ark"; } //load aliases and check integrity var aliases = ArkSpeciesAliases.Instance; if (aliases == null || !aliases.CheckIntegrity) { sb.AppendLine($@"Error: ""{ArkSpeciesAliases._filepath}"" is missing, contains invalid json or duplicate aliases."); if (aliases != null) { foreach (var duplicateAlias in aliases.Aliases?.SelectMany(x => x).GroupBy(x => x) .Where(g => g.Count() > 1) .Select(g => g.Key)) { sb.AppendLine($@"Duplicate alias: ""{duplicateAlias}"""); } } sb.AppendLine(); } var errors = sb.ToString(); if (errors.Length > 0) { WriteAndWaitForKey(errors); return; } IProgress <string> progress = new Progress <string>(message => { Console.AddLog(message); }); var constants = new ArkBot.Constants(); //if (config.Debug) //{ // //we reset the state so that every run will be the same // if (File.Exists(constants.DatabaseFilePath)) File.Delete(constants.DatabaseFilePath); // if (File.Exists(constants.SavedStateFilePath)) File.Delete(constants.SavedStateFilePath); // //optionally use a saved database state // var databaseStateFilePath = Path.Combine(config.JsonOutputDirPath, "Database.state"); // if (File.Exists(databaseStateFilePath)) File.Copy(databaseStateFilePath, constants.DatabaseFilePath); //} _savedstate = null; try { if (File.Exists(constants.SavedStateFilePath)) { _savedstate = JsonConvert.DeserializeObject <SavedState>(File.ReadAllText(constants.SavedStateFilePath)); _savedstate._Path = constants.SavedStateFilePath; } } catch { /*ignore exceptions */ } _savedstate = _savedstate ?? new SavedState(constants.SavedStateFilePath); //var context = new ArkContext(config, constants, progress); //var playedTimeWatcher = new PlayedTimeWatcher(_config); var options = new SteamOpenIdOptions { ListenPrefixes = new[] { _config.SteamOpenIdRelyingServiceListenPrefix }, RedirectUri = _config.SteamOpenIdRedirectUri, }; var openId = new BarebonesSteamOpenId(options, new Func <bool, ulong, ulong, Task <string> >(async(success, steamId, discordId) => { var razorConfig = new TemplateServiceConfiguration { DisableTempFileLocking = true, CachingProvider = new DefaultCachingProvider(t => { }) }; using (var service = RazorEngineService.Create(razorConfig)) { var html = await FileHelper.ReadAllTextTaskAsync(constants.OpenidresponsetemplatePath); return(service.RunCompile(html, constants.OpenidresponsetemplatePath, null, new { Success = success, botName = _config.BotName, botUrl = _config.BotUrl })); } })); var discord = new DiscordSocketClient(new DiscordSocketConfig { WebSocketProvider = WS4NetProvider.Instance, //required for Win 7 LogLevel = LogSeverity.Warning }); discord.Log += msg => { Console.AddLog(msg.Message); return(Task.CompletedTask); }; var discordCommands = new CommandService(new CommandServiceConfig { }); var anonymizeData = new ArkBotAnonymizeData(); //setup dependency injection var thisAssembly = Assembly.GetExecutingAssembly(); var builder = new ContainerBuilder(); builder.RegisterType <ArkServerContext>().AsSelf(); builder.RegisterInstance(anonymizeData).AsSelf().As <ArkAnonymizeData>(); if (_config.UseCompatibilityChangeWatcher) { builder.RegisterType <ArkSaveFileWatcherTimer>().As <IArkSaveFileWatcher>(); } else { builder.RegisterType <ArkSaveFileWatcher>().As <IArkSaveFileWatcher>(); } builder.RegisterInstance(discord).AsSelf(); builder.RegisterInstance(discordCommands).AsSelf(); builder.RegisterType <AutofacDiscordServiceProvider>().As <IServiceProvider>().SingleInstance(); builder.RegisterType <ArkDiscordBot>(); builder.RegisterType <UrlShortenerService>().As <IUrlShortenerService>().SingleInstance(); builder.RegisterInstance(constants).As <IConstants>(); builder.RegisterInstance(_savedstate).As <ISavedState>(); builder.RegisterInstance(_config as Config).As <IConfig>(); //builder.RegisterInstance(playedTimeWatcher).As<IPlayedTimeWatcher>(); builder.RegisterInstance(openId).As <IBarebonesSteamOpenId>(); builder.RegisterType <EfDatabaseContext>().AsSelf().As <IEfDatabaseContext>() .WithParameter(new TypedParameter(typeof(string), constants.DatabaseConnectionString)); builder.RegisterType <EfDatabaseContextFactory>(); builder.RegisterType <Migrations.Configuration>().PropertiesAutowired(); builder.RegisterType <ArkServerService>().As <IArkServerService>().SingleInstance(); builder.RegisterType <SavegameBackupService>().As <ISavegameBackupService>().SingleInstance(); builder.RegisterType <PlayerLastActiveService>().As <IPlayerLastActiveService>().SingleInstance(); //register vote handlers builder.RegisterType <BanVoteHandler>().As <IVoteHandler <BanVote> >(); builder.RegisterType <UnbanVoteHandler>().As <IVoteHandler <UnbanVote> >(); builder.RegisterType <RestartServerVoteHandler>().As <IVoteHandler <RestartServerVote> >(); builder.RegisterType <UpdateServerVoteHandler>().As <IVoteHandler <UpdateServerVote> >(); builder.RegisterType <DestroyWildDinosVoteHandler>().As <IVoteHandler <DestroyWildDinosVote> >(); builder.RegisterType <SetTimeOfDayVoteHandler>().As <IVoteHandler <SetTimeOfDayVote> >(); builder.RegisterApiControllers(Assembly.GetExecutingAssembly()); builder.RegisterHubs(Assembly.GetExecutingAssembly()); builder.RegisterType <ArkContextManager>().WithParameter(new TypedParameter(typeof(IProgress <string>), progress)).AsSelf().SingleInstance(); builder.RegisterType <VotingManager>().WithParameter(new TypedParameter(typeof(IProgress <string>), progress)).AsSelf().SingleInstance(); builder.RegisterType <DiscordManager>().AsSelf().SingleInstance(); builder.RegisterType <ScheduledTasksManager>().AsSelf().PropertiesAutowired(PropertyWiringOptions.AllowCircularDependencies).SingleInstance(); builder.RegisterType <NotificationManager>().AsSelf().SingleInstance(); builder.RegisterType <AutofacDependencyResolver>().As <IDependencyResolver>().SingleInstance(); builder.RegisterType <WebApiStartup>().AsSelf(); builder.RegisterType <WebApp.WebAppStartup>().AsSelf(); var webapiConfig = new System.Web.Http.HttpConfiguration(); var webappConfig = new System.Web.Http.HttpConfiguration(); builder.RegisterInstance(webapiConfig).Keyed <System.Web.Http.HttpConfiguration>("webapi"); builder.RegisterInstance(webappConfig).Keyed <System.Web.Http.HttpConfiguration>("webapp"); builder.RegisterWebApiFilterProvider(webapiConfig); builder.Register(c => new AccessControlAuthorizationFilter(c.Resolve <IConfig>())) .AsWebApiAuthorizationFilterFor <WebApi.Controllers.BaseApiController>() .InstancePerRequest(); //kernel.Bind(typeof(IHubConnectionContext<dynamic>)).ToMethod(context => // resolver.Resolve<IConnectionManager>().GetHubContext<StockTickerHub>().Clients // ).WhenInjectedInto<IStockTicker>(); Container = builder.Build(); var dir = Path.GetDirectoryName(constants.DatabaseFilePath); if (!Directory.Exists(dir)) { Directory.CreateDirectory(dir); } //update database System.Data.Entity.Database.SetInitializer(new System.Data.Entity.MigrateDatabaseToLatestVersion <EfDatabaseContext, Migrations.Configuration>(true, Container.Resolve <Migrations.Configuration>())); //create database immediately to support direct (non-ef) access in application using (var db = Container.Resolve <IEfDatabaseContext>()) { db.Database.Initialize(false); } _contextManager = Container.Resolve <ArkContextManager>(); //server/cluster contexts if (_config.Clusters?.Length > 0) { foreach (var cluster in _config.Clusters) { var context = new ArkClusterContext(cluster, anonymizeData); _contextManager.AddCluster(context); } } if (_config.Servers?.Length > 0) { var playerLastActiveService = Container.Resolve <IPlayerLastActiveService>(); var backupService = Container.Resolve <ISavegameBackupService>(); foreach (var server in _config.Servers) { var clusterContext = _contextManager.GetCluster(server.Cluster); var context = Container.Resolve <ArkServerContext>( new TypedParameter(typeof(ServerConfigSection), server), new TypedParameter(typeof(ArkClusterContext), clusterContext)); var initTask = context.Initialize(); //fire and forget _contextManager.AddServer(context); } // Initialize managers so that they are ready to handle events such as ArkContextManager.InitializationCompleted-event. var scheduledTasksManager = Container.Resolve <ScheduledTasksManager>(); var votingManager = Container.Resolve <VotingManager>(); var notificationMangager = Container.Resolve <NotificationManager>(); // Trigger manual updates for all servers (initialization) foreach (var context in _contextManager.Servers) { ManuallyUpdateServers.Add(new MenuItemViewModel { Header = context.Config.Key, Command = new DelegateCommand <string>(OnManuallyTriggerServerUpdate), CommandParameter = context.Config.Key }); _contextManager.QueueUpdateServerManual(context); } // Trigger manual updates for all clusters (initialization) foreach (var context in _contextManager.Clusters) { ManuallyUpdateClusters.Add(new MenuItemViewModel { Header = context.Config.Key, Command = new DelegateCommand <string>(OnManuallyTriggerClusterUpdate), CommandParameter = context.Config.Key }); _contextManager.QueueUpdateClusterManual(context); } } //run the discord bot if (_config.DiscordBotEnabled) { _runDiscordBotCts = new CancellationTokenSource(); _runDiscordBotTask = await Task.Factory.StartNew(async() => await RunDiscordBot(), _runDiscordBotCts.Token, TaskCreationOptions.LongRunning, TaskScheduler.Default); } else { Console.AddLog("Discord bot is disabled."); } //load the species stats data await ArkSpeciesStats.Instance.LoadOrUpdate(); //ssl if (_config.Ssl?.Enabled == true) { var path = $"{_config.Ssl.Name}.pfx"; var revoke = false; var renew = false; if (File.Exists(path)) { try { using (var rlt = new X509Certificate2(path, _config.Ssl.Password, X509KeyStorageFlags.Exportable | X509KeyStorageFlags.MachineKeySet | X509KeyStorageFlags.PersistKeySet)) { if (DateTime.Now < rlt.NotBefore || DateTime.Now > rlt.NotAfter.AddDays(-31)) { using (var store = new X509Store(StoreName.My, StoreLocation.LocalMachine)) { store.Open(OpenFlags.ReadWrite); if (store.Certificates.Contains(rlt)) { store.Remove(rlt); } store.Close(); } renew = revoke = true; } } } catch (Exception ex) { Logging.LogException("Failed to remove ssl certificate from store.", ex, this.GetType()); } } else { renew = true; } if (renew) { var success = false; Console.AddLog(@"SSL Certificate request issued..."); try { using (var client = new AcmeClient(WellKnownServers.LetsEncrypt)) { var account = await client.NewRegistraton($"mailto:{_config.Ssl.Email}"); account.Data.Agreement = account.GetTermsOfServiceUri(); account = await client.UpdateRegistration(account); var authz = await client.NewAuthorization(new AuthorizationIdentifier { Type = AuthorizationIdentifierTypes.Dns, Value = _config.Ssl.Domains.First() }); var httpChallengeInfo = authz.Data.Challenges.Where(c => c.Type == ChallengeTypes.Http01).First(); var keyAuthString = client.ComputeKeyAuthorization(httpChallengeInfo); using (var webapp = Microsoft.Owin.Hosting.WebApp.Start(_config.Ssl.ChallengeListenPrefix, (appBuilder) => { var challengePath = new PathString("/.well-known/acme-challenge/"); appBuilder.Use(new Func <AppFunc, AppFunc>((next) => { AppFunc appFunc = async environment => { IOwinContext context = new OwinContext(environment); if (!context.Request.Path.Equals(challengePath)) { await next.Invoke(environment); } context.Response.StatusCode = (int)System.Net.HttpStatusCode.OK; context.Response.ContentType = "application/text"; await context.Response.WriteAsync(keyAuthString); }; return(appFunc); })); })) { var httpChallenge = await client.CompleteChallenge(httpChallengeInfo); authz = await client.GetAuthorization(httpChallenge.Location); while (authz.Data.Status == EntityStatus.Pending) { await Task.Delay(500); authz = await client.GetAuthorization(httpChallenge.Location); } if (authz.Data.Status == EntityStatus.Valid) { if (revoke) { //await client.RevokeCertificate(path); //todo: how to revoke a cert when we do not have the AcmeCertificate-object? if (File.Exists(path)) { File.Delete(path); } } var csr = new CertificationRequestBuilder(); foreach (var domain in _config.Ssl.Domains) { csr.AddName("CN", domain); } var cert = await client.NewCertificate(csr); var pfxBuilder = cert.ToPfx(); var pfx = pfxBuilder.Build(_config.Ssl.Name, _config.Ssl.Password); File.WriteAllBytes(path, pfx); success = true; } } } if (success) { Console.AddLog(@"SSL Certificate request completed!"); } else { Console.AddLog(@"SSL Certificate challenge failed!"); } } catch (Exception ex) { Console.AddLog($@"SSL Certificate request failed! (""{ex.Message}"")"); Logging.LogException("Failed to issue ssl certificate.", ex, this.GetType()); } } if (File.Exists(path)) { var hostname = _config.Ssl.Domains.First(); var attribute = (GuidAttribute)Assembly.GetExecutingAssembly().GetCustomAttributes(typeof(GuidAttribute), true)[0]; var appId = attribute.Value; try { using (var rlt = new X509Certificate2(path, _config.Ssl.Password, X509KeyStorageFlags.Exportable | X509KeyStorageFlags.MachineKeySet | X509KeyStorageFlags.PersistKeySet)) { using (var store = new X509Store(StoreName.My, StoreLocation.LocalMachine)) { store.Open(OpenFlags.ReadWrite); var certs = store.Certificates.Find(X509FindType.FindBySubjectName, _config.Ssl.Domains.First(), false); if (!store.Certificates.Contains(rlt)) { store.Add(rlt); } store.Close(); } if (_config.Ssl.UseCompatibilityNonSNIBindings) { Console.AddLog(@"Binding SSL Certificate to ip/port..."); } else { Console.AddLog(@"Binding SSL Certificate to hostname/port..."); } foreach (var port in _config.Ssl.Ports) { var commands = new[] { _config.Ssl.UseCompatibilityNonSNIBindings ? $"netsh http delete sslcert ipport=0.0.0.0:{port}" : $"netsh http delete sslcert hostnameport={hostname}:{port}", _config.Ssl.UseCompatibilityNonSNIBindings ? $"netsh http add sslcert ipport=0.0.0.0:{port} certhash={rlt.Thumbprint} appid={{{appId}}} certstore=my" : $"netsh http add sslcert hostnameport={hostname}:{port} certhash={rlt.Thumbprint} appid={{{appId}}} certstore=my" }; var exitCode = 0; foreach (var cmd in commands) { await Task.Run(() => { using (var proc = Process.Start(new ProcessStartInfo { FileName = "cmd.exe", Arguments = $"/c {cmd}", Verb = "runas", UseShellExecute = false, WindowStyle = ProcessWindowStyle.Hidden, CreateNoWindow = true })) { proc.WaitForExit(); exitCode = proc.ExitCode; //only add (last cmd) is interesting } }); } if (_config.Ssl.UseCompatibilityNonSNIBindings) { Console.AddLog("[" + (exitCode == 0 ? "Success" : "Failed") + $"] ipport: 0.0.0.0:{port}, thumbprint={rlt.Thumbprint}, appid={{{appId}}}"); } else { Console.AddLog("[" + (exitCode == 0 ? "Success" : "Failed") + $"] hostnameport: {hostname}:{port}, thumbprint={rlt.Thumbprint}, appid={{{appId}}}"); } } } } catch (CryptographicException ex) { Logging.LogException("Failed to open SSL certificate.", ex, this.GetType(), LogLevel.FATAL, ExceptionLevel.Unhandled); WriteAndWaitForKey("Failed to open SSL certificate (wrong password?)"); return; } } } //webapi _webapi = Microsoft.Owin.Hosting.WebApp.Start(_config.WebApiListenPrefix, app => { var startup = Container.Resolve <WebApiStartup>(); startup.Configuration(app, _config, Container, webapiConfig); }); Console.AddLog("Web API started"); //webapp _webapp = Microsoft.Owin.Hosting.WebApp.Start(_config.WebAppListenPrefix, app => { var startup = Container.Resolve <WebApp.WebAppStartup>(); startup.Configuration(app, _config, Container, webappConfig); }); Console.AddLog("Web App started"); if (_config.WebAppRedirectListenPrefix?.Length > 0) { foreach (var redir in _config.WebAppRedirectListenPrefix) { _webappRedirects.Add(Microsoft.Owin.Hosting.WebApp.Start <WebApp.WebAppRedirectStartup>(url: redir)); Console.AddLog("Web App redirect added"); } } }
internal async Task Init() { var logRepository = LogManager.GetRepository(Assembly.GetEntryAssembly()); XmlConfigurator.Configure(logRepository, new FileInfo("App.config")); //"log4net.config" if (!new WindowsPrincipal(WindowsIdentity.GetCurrent()).IsInRole(WindowsBuiltInRole.Administrator)) { WriteAndWaitForKey($@"This application must be run as administrator in order to function properly."); return; } //ArkToolkitDomain.Initialize(); //File.WriteAllText(@"ark.json", JsonConvert.SerializeObject(ArkSavegameToolkitNet.ArkToolkitSettings.Instance, Formatting.Indented)); var arkConfigCustom = false; if (File.Exists(Constants.ArkConfigFilePath)) { try { // use custom settings from ark.json ArkToolkitSettings.Instance.Setup(JsonConvert.DeserializeObject <ArkToolkitSettings>(File.ReadAllText(Constants.ArkConfigFilePath))); arkConfigCustom = true; } catch (Exception ex) { Console.AddLogError($@"Error loading 'ark.config'. Using default config. (""{ex.Message}"")"); Logging.LogException("Error loading 'ark.config'. Using default config.", ex, GetType()); } } if (!arkConfigCustom) { // initialize default settings ArkToolkitDomain.Initialize(); } _config = null; string validationMessage = null; string errorMessage = null; if (File.Exists(Constants.ConfigFilePath)) { try { _config = JsonConvert.DeserializeObject <Config>(File.ReadAllText(Constants.ConfigFilePath)); if (_config != null) { if (_config.Discord == null) { _config.Discord = new DiscordConfigSection(); } _config.SetupDefaults(); } else { errorMessage = "Config.json is empty. Please delete it and restart the application."; } } catch (Exception ex) { validationMessage = ex.Message; } } var hasValidConfig = _config != null; if (_config == null) { //load defaultconfig if (!File.Exists(Constants.DefaultConfigFilePath)) { WriteAndWaitForKey($@"The required file defaultconfig.json is missing from application directory. Please redownload the application."); return; } try { _config = JsonConvert.DeserializeObject <Config>(File.ReadAllText(Constants.DefaultConfigFilePath)); } catch (Exception ex) { } WriteAndWaitForKey( $@"The file config.json is empty or contains errors. Skipping automatic startup...", validationMessage); } Configuration.Config = _config as Config; About.HasValidConfig = hasValidConfig; About.ValidationError = validationMessage; About.ConfigError = errorMessage; if (!hasValidConfig) { About.IsActive = true; return; } else { About.IsVisible = false; Console.IsActive = true; } string errors = ValidateConfig(); if (errors.Length > 0) { WriteAndWaitForKey(errors); return; } IProgress <string> progress = new Progress <string>(message => { Console.AddLog(message); }); var constants = new Shared.Constants(); //if (config.Debug) //{ // //we reset the state so that every run will be the same // if (File.Exists(constants.DatabaseFilePath)) File.Delete(constants.DatabaseFilePath); // if (File.Exists(constants.SavedStateFilePath)) File.Delete(constants.SavedStateFilePath); // //optionally use a saved database state // var databaseStateFilePath = Path.Combine(config.JsonOutputDirPath, "Database.state"); // if (File.Exists(databaseStateFilePath)) File.Copy(databaseStateFilePath, constants.DatabaseFilePath); //} _savedstate = null; try { if (File.Exists(constants.SavedStateFilePath)) { _savedstate = JsonConvert.DeserializeObject <SavedState>(File.ReadAllText(constants.SavedStateFilePath)); _savedstate._Path = constants.SavedStateFilePath; } } catch { /*ignore exceptions */ } _savedstate = _savedstate ?? new SavedState(constants.SavedStateFilePath); var discord = new DiscordSocketClient(new DiscordSocketConfig { LogLevel = _config.Discord.LogLevel }); discord.Log += msg => { Console.AddLog(msg.Message); return(Task.CompletedTask); }; var discordCommands = new CommandService(new CommandServiceConfig { }); var anonymizeData = new ArkBotAnonymizeData(); //setup dependency injection var thisAssembly = Assembly.GetExecutingAssembly(); var builder = new ContainerBuilder(); builder.RegisterType <ArkServerContext>().AsSelf(); builder.RegisterInstance(anonymizeData).AsSelf().As <ArkAnonymizeData>(); if (_config.UseCompatibilityChangeWatcher) { builder.RegisterType <ArkSaveFileWatcherTimer>().As <IArkSaveFileWatcher>(); } else { builder.RegisterType <ArkSaveFileWatcher>().As <IArkSaveFileWatcher>(); } builder.RegisterInstance(discord).AsSelf(); builder.RegisterInstance(discordCommands).AsSelf(); builder.RegisterType <AutofacDiscordServiceProvider>().As <IServiceProvider>().SingleInstance(); builder.RegisterType <ArkDiscordBot>(); builder.RegisterInstance(constants).As <IConstants>(); builder.RegisterInstance(_savedstate).As <ISavedState>(); builder.RegisterInstance(_config as Config).As <IConfig>(); builder.RegisterType <EfDatabaseContext>().AsSelf().As <IEfDatabaseContext>() .WithParameter(new TypedParameter(typeof(string), _config.DatabaseConnectionString)); builder.RegisterType <EfDatabaseContextFactory>(); builder.RegisterType <DatabaseRepo>().As <IDatabaseRepo>().SingleInstance(); builder.RegisterType <ArkServerService>().As <IArkServerService>().SingleInstance(); builder.RegisterType <SavegameBackupService>().As <ISavegameBackupService>().SingleInstance(); builder.RegisterType <PlayerLastActiveService>().As <IPlayerLastActiveService>().SingleInstance(); builder.RegisterType <LogCleanupService>().As <ILogCleanupService>().SingleInstance(); builder.RegisterHubs(Assembly.GetExecutingAssembly()); builder.RegisterType <ArkContextManager>().WithParameter(new TypedParameter(typeof(IProgress <string>), progress)).AsSelf().SingleInstance(); builder.RegisterType <DiscordManager>().AsSelf().SingleInstance(); builder.RegisterType <ScheduledTasksManager>().AsSelf().PropertiesAutowired(PropertyWiringOptions.AllowCircularDependencies).SingleInstance(); builder.RegisterType <NotificationManager>().AsSelf().SingleInstance(); builder.RegisterType <PrometheusManager>().AsSelf().SingleInstance(); builder.RegisterType <AuctionHouseManager>().AsSelf().SingleInstance(); Container = builder.Build(); //update database try { using (var db = Container.Resolve <EfDatabaseContext>()) { db.Database.Migrate(); } } catch (SqlException ex) { Console.AddLogError($@"Error initializing Microsoft SQL Server (""{ex.Message}"")"); Logging.LogException("Error initializing Microsoft SQL Server.", ex, GetType()); return; } _contextManager = Container.Resolve <ArkContextManager>(); //server/cluster contexts if (_config.Clusters?.Count > 0) { foreach (var cluster in _config.Clusters) { var context = new ArkClusterContext(cluster, anonymizeData); _contextManager.AddCluster(context); } } if (_config.Servers?.Count > 0) { var playerLastActiveService = Container.Resolve <IPlayerLastActiveService>(); var backupService = Container.Resolve <ISavegameBackupService>(); var logCleanupService = Container.Resolve <ILogCleanupService>(); foreach (var server in _config.Servers) { var clusterContext = _contextManager.GetCluster(server.ClusterKey); var context = Container.Resolve <ArkServerContext>( new TypedParameter(typeof(ServerConfigSection), server), new TypedParameter(typeof(ArkClusterContext), clusterContext)); var initTask = context.Initialize(); //fire and forget _contextManager.AddServer(context); } // Initialize managers so that they are ready to handle events such as ArkContextManager.InitializationCompleted-event. var scheduledTasksManager = Container.Resolve <ScheduledTasksManager>(); var notificationManager = Container.Resolve <NotificationManager>(); // <GHOST DIVISION> var prometheusManager = Container.Resolve <PrometheusManager>(); var auctionHouseManager = Container.Resolve <AuctionHouseManager>(); // </GHOST DIVISION> // Trigger manual updates for all servers (initialization) foreach (var context in _contextManager.Servers) { ManuallyUpdateServers.Add(new MenuItemViewModel { Header = context.Config.Key, Command = new DelegateCommand <string>(OnManuallyTriggerServerUpdate), CommandParameter = context.Config.Key }); _contextManager.QueueUpdateServerManual(context); } // Trigger manual updates for all clusters (initialization) foreach (var context in _contextManager.Clusters) { ManuallyUpdateClusters.Add(new MenuItemViewModel { Header = context.Config.Key, Command = new DelegateCommand <string>(OnManuallyTriggerClusterUpdate), CommandParameter = context.Config.Key }); _contextManager.QueueUpdateClusterManual(context); } // <GHOST DIVISION> // run prometheus endpoint if (_config.Prometheus.Enabled) { prometheusManager.Start(); } else { Console.AddLog("Prometheus is disabled."); } // run auction house manager if (_config.AuctionHouse.Enabled) { auctionHouseManager.Start(); } else { Console.AddLog("Auction House monitor is disabled."); } // </GHOST DIVISION> } //run the discord bot if (_config.Discord.Enabled) { _runDiscordBotCts = new CancellationTokenSource(); _runDiscordBotTask = await Task.Factory.StartNew(async() => await RunDiscordBot(), _runDiscordBotCts.Token, TaskCreationOptions.LongRunning, TaskScheduler.Default); } else { Console.AddLog("Discord bot is disabled."); } //load the server multipliers data await ArkServerMultipliers.Instance.LoadOrUpdate(); var modIds = _config.Servers?.SelectMany(x => x.ModIds).Distinct().ToArray() ?? new int[] { }; //load the species stats data await ArkSpeciesStats.Instance.LoadOrUpdate(modIds); //load the items data await ArkItems.Instance.LoadOrUpdate(modIds); //ssl if (_config.WebApp.Ssl?.Enabled == true) { var path = $"{_config.WebApp.Ssl.Name}.pfx"; var revoke = false; var renew = false; if (File.Exists(path)) { try { using (var rlt = new X509Certificate2(path, _config.WebApp.Ssl.Password, X509KeyStorageFlags.Exportable | X509KeyStorageFlags.MachineKeySet | X509KeyStorageFlags.PersistKeySet)) { if (DateTime.Now < rlt.NotBefore || DateTime.Now > rlt.NotAfter.AddDays(-31)) { using (var store = new X509Store(StoreName.My, StoreLocation.LocalMachine)) { store.Open(OpenFlags.ReadWrite); if (store.Certificates.Contains(rlt)) { store.Remove(rlt); } store.Close(); } renew = revoke = true; } } } catch (Exception ex) { Logging.LogException("Failed to remove ssl certificate from store.", ex, GetType()); } } else { renew = true; } if (renew) { var success = false; Console.AddLog(@"SSL Certificate request issued..."); AcmeContext acme = null; try { var pathAccountKey = $"{_config.WebApp.Ssl.Name}-account.pem"; if (File.Exists(pathAccountKey)) { var accountKey = KeyFactory.FromPem(File.ReadAllText(pathAccountKey)); acme = new AcmeContext(WellKnownServers.LetsEncryptV2, accountKey); var account = await acme.Account(); } else { acme = new AcmeContext(WellKnownServers.LetsEncryptV2); var account = await acme.NewAccount(_config.WebApp.Ssl.Email, true); var pemKey = acme.AccountKey.ToPem(); File.WriteAllText(pathAccountKey, pemKey); } var order = await acme.NewOrder(_config.WebApp.Ssl.Domains); var authz = (await order.Authorizations()).First(); var httpChallenge = await authz.Http(); var keyAuthz = httpChallenge.KeyAuthz; using (var webapp = Host.CreateDefaultBuilder() .UseServiceProviderFactory(new AutofacChildLifetimeScopeServiceProviderFactory(Container.BeginLifetimeScope("AspNetCore_IsolatedRoot_SSL_Renew"))) .ConfigureWebHostDefaults(webBuilder => { webBuilder .ConfigureLogging(logging => { logging.ClearProviders(); logging.AddDebug(); logging.AddWebApp(); }) .UseUrls(_config.WebApp.Ssl.ChallengeListenPrefix) .Configure((appBuilder) => { appBuilder.UseRouting(); appBuilder.UseEndpoints(endpoints => { endpoints.MapGet($"/.well-known/acme-challenge/{httpChallenge.Token}", context => { context.Response.ContentType = "application/text"; context.Response.StatusCode = StatusCodes.Status200OK; return(context.Response.WriteAsync(keyAuthz)); }); }); }); }).Build()) { (webapp as IHost).Start(); var result = await httpChallenge.Validate(); while (result.Status == Certes.Acme.Resource.ChallengeStatus.Pending || result.Status == Certes.Acme.Resource.ChallengeStatus.Processing) { await Task.Delay(500); result = await httpChallenge.Resource(); } if (result.Status == Certes.Acme.Resource.ChallengeStatus.Valid) { var privateKey = KeyFactory.NewKey(KeyAlgorithm.ES256); var cert = await order.Generate(new CsrInfo { CountryName = new RegionInfo(CultureInfo.CurrentCulture.Name).TwoLetterISORegionName, Locality = "Web", Organization = "ARK Bot", }, privateKey); if (revoke) { //await acme.RevokeCertificate( // File.ReadAllBytes(path), // Certes.Acme.Resource.RevocationReason.Superseded, // KeyFactory.FromPem(File.ReadAllText(pathPrivateKey))); if (File.Exists(path)) { File.Delete(path); } } //File.WriteAllText(pathPrivateKey, privateKey.ToPem()); var pfxBuilder = cert.ToPfx(privateKey); var pfx = pfxBuilder.Build(_config.WebApp.Ssl.Name, _config.WebApp.Ssl.Password); File.WriteAllBytes(path, pfx); success = true; } } if (success) { Console.AddLog(@"SSL Certificate request completed!"); } else { Console.AddLog(@"SSL Certificate challenge failed!"); } } catch (Exception ex) { Console.AddLogError($@"SSL Certificate request failed! (""{ex.Message}"")"); Logging.LogException("Failed to issue ssl certificate.", ex, GetType()); } } // clean up all bindings created by ark bot { var(success, output) = await ProcessHelper.RunCommandLine("netsh http show sslcert", _config); if (success && !string.IsNullOrEmpty(output)) { var r = new Regex($@"(?:(?:\s*IP\:port\s*\:\s*(?<ip>[^\:]+?)\:(?<port>\d+))|(?:\s*Hostname\:port\s*\:\s*(?<hostname>[^\:]+?)\:(?<port>\d+))).*Application ID\s*\:\s*{{{Constants.AppId}}}", RegexOptions.IgnoreCase | RegexOptions.Singleline); var sections = output.Split("\r\n\r\n\r\n", StringSplitOptions.RemoveEmptyEntries); foreach (var section in sections) { var m = r.Match(section); if (!m.Success) { continue; } if (m.Groups["hostname"].Success && m.Groups["port"].Success) { await ProcessHelper.RunCommandLine($"netsh http delete sslcert hostnameport={m.Groups["hostname"].Value}:{m.Groups["port"].Value}", _config); } else if (m.Groups["ip"].Success && m.Groups["port"].Success) { await ProcessHelper.RunCommandLine($"netsh http delete sslcert ipport={m.Groups["ip"].Value}:{m.Groups["port"].Value}", _config); } } } } } // https://docs.microsoft.com/en-us/aspnet/core/fundamentals/host/web-host?view=aspnetcore-3.1 try { _webapp = Host.CreateDefaultBuilder() .UseServiceProviderFactory(new AutofacChildLifetimeScopeServiceProviderFactory(Container.BeginLifetimeScope("AspNetCore_IsolatedRoot"))) .ConfigureWebHostDefaults(webBuilder => { webBuilder.ConfigureKestrel(options => { options.Listen(IPEndPoint.Parse(_config.WebApp.IPEndpoint), listenOptions => { if (_config.WebApp.Ssl.Enabled) { listenOptions.UseHttps($"../{_config.WebApp.Ssl.Name}.pfx", _config.WebApp.Ssl.Password); } }); }) .ConfigureLogging(logging => { logging.ClearProviders(); logging.AddDebug(); logging.AddWebApp(); }) .UseContentRoot(Path.Combine(AppContext.BaseDirectory, "WebApp")) //Directory.GetCurrentDirectory() .UseWebRoot(Path.Combine(AppContext.BaseDirectory, "WebApp")) .UseStartup <WebAppStartup>(); }).Build(); (_webapp as IHost).Start(); Console.AddLog("Web App started"); } catch (IOException ex) when((ex.HResult & 0x0000FFFF) == 5664) //Failed to bind to address <...>: address already in use. { var portInUseBy = (string)null; try { if (IPEndPoint.TryParse(_config.WebApp.IPEndpoint, out var ipEndpoint)) { // power shell // Get-Process -Id (Get-NetTCPConnection -LocalPort 8600).OwningProcess | Format-List * var(success, output) = await ProcessHelper.RunCommandLine($"(Get-Process -Id (Get-NetTCPConnection -LocalPort {ipEndpoint.Port}).OwningProcess).Name", _config, UseCallOperator : false); if (success && !string.IsNullOrEmpty(output)) { portInUseBy = $@"Port {ipEndpoint.Port} in use by: {output}"; } } } catch (Exception) { /* do nothing */ } Console.AddLogError($@"Failed to start web app! (""{ex.Message}"")"); if (!string.IsNullOrWhiteSpace(portInUseBy)) { Console.AddLogError(portInUseBy); } Logging.LogException("Failed to start web app.", ex, GetType()); } }
public async Task Cloud([Remainder] string arguments = null) { var args = CommandHelper.ParseArgs(arguments, new { ClusterKey = "", Backup = 0L, Stash = 0L, Pop = 0L, Delete = 0L, List = 0L, Details = 0L, Restore = 0L, Target1 = "", Target2 = "", Skip = 0 }, x => x.For(y => y.ClusterKey, noPrefix: true) .For(y => y.Target1, noPrefix: true) .For(y => y.Target2, noPrefix: true)); var sb = new StringBuilder(); var r_allowedExt = new Regex(@"^(?!tmpprofile)([a-z0-9])+$", RegexOptions.Singleline | RegexOptions.IgnoreCase); //for details and restore commands, get hashes from target1 and target2 int?backupHash = null, cloudSaveHash = null; if (args.Details > 0 || args.Restore > 0) { try { backupHash = !string.IsNullOrWhiteSpace(args.Target1) ? (int?)Convert.ToInt32(args.Target1, 16) : null; } catch { /*ignore exceptions*/ } } if (args.Restore > 0) { try { cloudSaveHash = !string.IsNullOrWhiteSpace(args.Target2) ? (int?)Convert.ToInt32(args.Target2, 16) : null; } catch { /*ignore exceptions*/ } } // for stash and pop commands, check that target1 is a valid tag if ((args.Stash > 0 || args.Pop > 0) && !r_allowedExt.IsMatch(args.Target1)) { await Context.Channel.SendMessageAsync($"**The supplied tag is not allowed (only a-z, 0-9)!**"); return; } // check that the cluster key is valid ArkClusterContext clusterContext = args.ClusterKey != null?_contextManager.GetCluster(args.ClusterKey) : null; if (clusterContext == null) { await Context.Channel.SendMessageAsync($"**Cloud commands need to be prefixed with a valid cluster key.**"); return; } // check that there are one or more servers in the cluster var serverContexts = _contextManager.GetServersInCluster(clusterContext.Config.Key); if (!(serverContexts?.Length > 0)) { await Context.Channel.SendMessageAsync($"**There are no servers in the cluster.**"); return; } /* --------------------------------------------------------------- * List cloud save backups available for a given player. * --------------------------------------------------------------- */ if (args.List > 0) { var result = GetBackupFiles(clusterContext.Config, serverContexts.Select(x => x.Config.Key).ToArray(), args.List); if (result.Count > 0) { var tbl = OutputCloudBackupListingTable(result, args.Skip); sb.Append(tbl); } else { sb.AppendLine("**Could not find any cloud save backups...**"); } } /* --------------------------------------------------------------- * Stash the current cloud save with a given tag to fetch at a later time. * --------------------------------------------------------------- */ else if (args.Stash > 0) { var result = _savegameBackupService.StashCloudSave(clusterContext.Config, args.Stash, args.Target1); if (result == StashResult.Successfull) { sb.AppendLine($"**Cloud save stashed as '{args.Target1}'!**"); } else if (result == StashResult.SourceMissing) { sb.AppendLine("**There is no cloud save to stash...**"); } else if (result == StashResult.TargetExists) { sb.AppendLine("**The supplied tag is already being used...**"); } else { sb.AppendLine("**Failed to stash cloud save...**"); } } /* --------------------------------------------------------------- * Fetch a cloud save previously stashed with a given tag and set it as the current cloud save. * --------------------------------------------------------------- */ else if (args.Pop > 0) { var result = _savegameBackupService.PopCloudSave(clusterContext.Config, args.Pop, args.Target1); if (result == StashResult.Successfull) { sb.AppendLine($"**Cloud save popped from '{args.Target1}'!**"); } else if (result == StashResult.SourceMissing) { sb.AppendLine("**The supplied tag does not exist...**"); } else if (result == StashResult.TargetExists) { sb.AppendLine("**A cloud save already exists, please delete/stash it before popping...**"); } else { sb.AppendLine("**Failed to pop cloud save...**"); } } /* --------------------------------------------------------------- * Delete the current cloud save for a given player. * --------------------------------------------------------------- */ else if (args.Delete > 0) { var targetPath = Path.Combine(clusterContext.Config.SavePath, $"{args.Delete}"); if (File.Exists(targetPath)) { try { File.Delete(targetPath); sb.AppendLine($"**Cloud save deleted!**"); } catch { sb.AppendLine($"**Failed to delete cloud save...**"); } } else { sb.AppendLine($"**There is no cloud save to delete...**"); } } /* --------------------------------------------------------------- * Create a backup of all cloud save files for a given player (including stashed, .tmpprofile etc.) * --------------------------------------------------------------- */ else if (args.Backup > 0) { var result = _savegameBackupService.CreateClusterBackupForSteamId(clusterContext.Config, args.Backup); if (result != null && result.ArchivePaths?.Length > 0) { sb.AppendLine($"**Cloud save backup successfull!**"); } else { sb.AppendLine("**Failed to backup cloud save...**"); } } /* --------------------------------------------------------------- * Get detailed information for a single backup archive or cloud save file. * --------------------------------------------------------------- */ else if (args.Details > 0 && backupHash.HasValue) { var result = GetBackupFiles(clusterContext.Config, serverContexts.Select(x => x.Config.Key).ToArray(), args.Details, backupHash.Value) .Find(x => x.Path.GetHashCode() == backupHash.Value); if (result == null) { await Context.Channel.SendMessageAsync($"**Failed to find the given backup hash!**"); return; } var data = result.Files.Select(file => { string tmpFilePath = null; ArkCloudInventory cloudInventory = null; try { string filePath = null; if (result is FromServerBackupListEntity) { filePath = result.FullPath; } else { filePath = tmpFilePath = FileHelper.ExtractFileInZipFile(result.FullPath, file); } var cresult = ArkClusterData.LoadSingle(filePath, CancellationToken.None, true, true); cloudInventory = cresult.Success ? cresult.Data : null; } catch { /*ignore exception*/ } finally { if (tmpFilePath != null) { File.Delete(tmpFilePath); } } return(new { FilePath = file, CloudSaveHash = file.GetHashCode(), DinoCount = cloudInventory?.Dinos?.Length, CharactersCount = cloudInventory?.Characters?.Length, ItemsCount = cloudInventory?.Items?.Length }); }).ToArray(); var tableBackupFiles = FixedWidthTableHelper.ToString(data, x => x .For(y => y.FilePath, header: "Cloud Save") .For(y => y.CloudSaveHash, header: "Cloud Save Hash", alignment: 1, format: "X") .For(y => y.DinoCount, header: "Dinos", alignment: 1) .For(y => y.CharactersCount, header: "Characters", alignment: 1) .For(y => y.ItemsCount, header: "Items", alignment: 1)); var tableBackupEntries = OutputCloudBackupListingTable(new[] { result }, 0, 1); sb.Append(tableBackupEntries); sb.Append($"```{tableBackupFiles}```"); } /* --------------------------------------------------------------- * Restore a single cloud save file from a backup archive or cloud save file (some overlap with the less verbose pop command). * --------------------------------------------------------------- */ else if (args.Restore > 0 && backupHash.HasValue && cloudSaveHash.HasValue) { var result = GetBackupFiles(clusterContext.Config, serverContexts.Select(x => x.Config.Key).ToArray(), args.Restore, backupHash.Value) .Find(x => x.Path.GetHashCode() == backupHash.Value); if (result == null) { await Context.Channel.SendMessageAsync($"**Failed to find the given backup hash!**"); return; } var cloudSaveFile = result.Files.FirstOrDefault(x => x.GetHashCode() == cloudSaveHash.Value); if (cloudSaveFile == null) { await Context.Channel.SendMessageAsync($"**Failed to find the given cloud save hash!**"); return; } var targetPath = Path.Combine(clusterContext.Config.SavePath, $"{args.Restore}"); if (File.Exists(targetPath)) { await Context.Channel.SendMessageAsync("**A cloud save already exists, please delete/stash it before restoring...**"); return; } string tmpFilePath = null; try { string filePath = null; if (result is FromServerBackupListEntity) { filePath = result.FullPath; } else { filePath = tmpFilePath = FileHelper.ExtractFileInZipFile(result.FullPath, cloudSaveFile); } File.Copy(filePath, targetPath); sb.AppendLine($"**Cloud save successfully restored!**"); } catch { /*ignore exception*/ sb.AppendLine($"**Failed to restore cloud save...**"); } finally { if (tmpFilePath != null) { File.Delete(tmpFilePath); } } } else { var syntaxHelp = MethodBase.GetCurrentMethod().GetCustomAttribute <SyntaxHelpAttribute>()?.SyntaxHelp; var name = MethodBase.GetCurrentMethod().GetCustomAttribute <CommandAttribute>()?.Text; await Context.Channel.SendMessageAsync(string.Join(Environment.NewLine, new string[] { $"**My logic circuits cannot process this command! I am just a bot after all... :(**", !string.IsNullOrWhiteSpace(syntaxHelp) ? $"Help me by following this syntax: **!{name}** {syntaxHelp}" : null }.Where(x => x != null))); return; } var msg = sb.ToString(); if (!string.IsNullOrWhiteSpace(msg)) { await CommandHelper.SendPartitioned(Context.Channel, sb.ToString()); } }