using System.Runtime; using System.Runtime.InteropServices; using System.Text; using SPTarkov.Common.Semver; using SPTarkov.Common.Semver.Implementations; using SPTarkov.DI; using SPTarkov.Server.Core.Loaders; using SPTarkov.Server.Core.Models.Spt.Mod; using SPTarkov.Server.Core.Models.Utils; using SPTarkov.Server.Core.Utils; using SPTarkov.Server.Core.Utils.Logger; using SPTarkov.Server.Logger; using SPTarkov.Server.Modding; namespace SPTarkov.Server; public static class Program { public static async Task Main(string[] args) { Console.OutputEncoding = Encoding.UTF8; // Some users don't know how to create a shortcut... if (!IsRunFromInstallationFolder()) { Console.WriteLine( "You have not created a shortcut properly. Please hold alt when dragging to create a shortcut." ); await Task.Delay(-1); return; } // Initialize the program variables ProgramStatics.Initialize(); // Create web builder and logger var builder = CreateNewHostBuilder(args); var diHandler = new DependencyInjectionHandler(builder.Services); // register SPT components diHandler.AddInjectableTypesFromTypeAssembly(typeof(Program)); diHandler.AddInjectableTypesFromTypeAssembly(typeof(App)); List loadedMods = []; if (ProgramStatics.MODS()) { // Search for mod dlls loadedMods = ModDllLoader.LoadAllMods(); // validate and sort mods, this will also discard any mods that are invalid var sortedLoadedMods = ValidateMods(loadedMods); // update the loadedMods list with our validated sorted mods loadedMods = sortedLoadedMods; diHandler.AddInjectableTypesFromAssemblies( sortedLoadedMods.SelectMany(a => a.Assemblies) ); } diHandler.InjectAll(); builder.Services.AddSingleton(builder); builder.Services.AddSingleton>(loadedMods); var serviceProvider = builder.Services.BuildServiceProvider(); var logger = serviceProvider.GetService().CreateLogger("Server"); // Load bundles for bundle mods if (ProgramStatics.MODS()) { var bundleLoader = serviceProvider.GetService(); foreach (var mod in loadedMods) { if (mod.ModMetadata?.IsBundleMod == true) { // Convert to relative path string relativeModPath = Path.GetRelativePath( Directory.GetCurrentDirectory(), mod.Directory ) .Replace('\\', '/'); bundleLoader.AddBundles(relativeModPath); } } } try { SetConsoleOutputMode(); // Get the Built app and run it var app = serviceProvider.GetService(); if (app != null) { await app.InitializeAsync(); // Run garbage collection now the server is ready to start GCSettings.LargeObjectHeapCompactionMode = GCLargeObjectHeapCompactionMode.CompactOnce; GC.Collect(GC.MaxGeneration, GCCollectionMode.Aggressive, true, true); await app.StartAsync(); } } catch (Exception ex) { Console.WriteLine(ex); logger.LogCritical(ex, "Critical exception, stopping server..."); } finally { serviceProvider.GetService>()?.DumpAndStop(); } } private static WebApplicationBuilder CreateNewHostBuilder(string[]? args = null) { var builder = WebApplication.CreateBuilder(args); builder.Logging.ClearProviders(); builder.Configuration.SetBasePath(Directory.GetCurrentDirectory()); builder.Host.UseSptLogger(); return builder; } private static List ValidateMods(List mods) { if (!ProgramStatics.MODS()) { return []; } // We need the SPT dependencies for the ModValidator, but mods are loaded before the web application // So we create a disposable web application that we will throw away after getting the mods to load var builder = CreateNewHostBuilder(); // register SPT components var diHandler = new DependencyInjectionHandler(builder.Services); diHandler.AddInjectableTypesFromAssembly(typeof(Program).Assembly); diHandler.AddInjectableTypesFromAssembly(typeof(App).Assembly); diHandler.InjectAll(); // register the mod validator components var provider = builder .Services.AddScoped(typeof(ISptLogger), typeof(SptLogger)) .AddScoped(typeof(ISemVer), typeof(SemanticVersioningSemVer)) .AddSingleton() .AddSingleton() .BuildServiceProvider(); var modValidator = provider.GetRequiredService(); return modValidator.ValidateAndSort(mods); } private static void SetConsoleOutputMode() { if (!OperatingSystem.IsWindows()) { return; } const int stdOutputHandle = -11; const uint enableVirtualTerminalProcessing = 0x0004; var handle = GetStdHandle(stdOutputHandle); if (!GetConsoleMode(handle, out var consoleMode)) { throw new Exception("Unable to get console mode"); } consoleMode |= enableVirtualTerminalProcessing; if (!SetConsoleMode(handle, consoleMode)) { throw new Exception("Unable to set console mode"); } } private static bool IsRunFromInstallationFolder() { var dirFiles = Directory.GetFiles(Directory.GetCurrentDirectory()); // This file is guaranteed to exist if ran from the correct location, even if the game does not exist here. return dirFiles.Any(dirFile => dirFile.EndsWith("sptLogger.json") || dirFile.EndsWith("sptLogger.Development.json") ); } [DllImport("kernel32.dll", SetLastError = true)] private static extern IntPtr GetStdHandle(int nStdHandle); [DllImport("kernel32.dll")] private static extern bool GetConsoleMode(IntPtr hConsoleHandle, out uint lpMode); [DllImport("kernel32.dll")] private static extern bool SetConsoleMode(IntPtr hConsoleHandle, uint dwMode); }