예제 #1
0
        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());
            }
        }