using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; using System.Net.Http; using System.Runtime.InteropServices; using System.Threading; using System.Threading.Tasks; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Wabbajack.Common; using Wabbajack.Compiler; using Wabbajack.Configuration; using Wabbajack.Downloaders; using Wabbajack.Downloaders.GameFile; using Wabbajack.Downloaders.ModDB; using Wabbajack.Downloaders.VerificationCache; using Wabbajack.DTOs; using Wabbajack.DTOs.Interventions; using Wabbajack.DTOs.JsonConverters; using Wabbajack.DTOs.Logins; using Wabbajack.Hashing.PHash; using Wabbajack.Installer; using Wabbajack.Networking.BethesdaNet; using Wabbajack.Networking.Discord; using Wabbajack.Networking.Http; using Wabbajack.Networking.Http.Interfaces; using Wabbajack.Networking.NexusApi; using Wabbajack.Networking.Steam; using Wabbajack.Networking.WabbajackClientApi; using Wabbajack.Paths; using Wabbajack.Paths.IO; using Wabbajack.RateLimiter; using Wabbajack.Services.OSIntegrated.Services; using Wabbajack.Services.OSIntegrated.TokenProviders; using Wabbajack.VFS; using Wabbajack.VFS.Interfaces; using Client = Wabbajack.Networking.WabbajackClientApi.Client; namespace Wabbajack.Services.OSIntegrated; public static class ServiceExtensions { /// /// Adds variants of services that integrate into global OS services. These are not testing /// variants or services that require Environment variables. These are the "full fat" services. /// /// public static IServiceCollection AddOSIntegrated(this IServiceCollection service, Action? cfn = null) { // Register app-wide cancellation token source to allow clean termination service.AddSingleton(new CancellationTokenSource()); service.AddTransient(typeof(CancellationToken), s => s.GetRequiredService().Token); var options = new OSIntegratedOptions(); cfn?.Invoke(options); var tempBase = KnownFolders.EntryPoint.Combine("temp"); service.AddTransient(s => new TemporaryFileManager(tempBase.Combine(Environment.ProcessId + "_" + Guid.NewGuid()))); Task.Run(() => CleanAllTempData(tempBase)); service.AddSingleton(s => options.UseLocalCache ? new FileHashCache(s.GetService()!.CreateFile().Path, s.GetService>()!) : new FileHashCache(KnownFolders.AppDataLocal.Combine("Wabbajack", "GlobalHashCache2.sqlite"), s.GetService>()!)); service.AddSingleton(s => { var diskCache = options.UseLocalCache ? new VFSDiskCache(s.GetService()!.CreateFile().Path) : new VFSDiskCache(KnownFolders.WabbajackAppLocal.Combine("GlobalVFSCache5.sqlite")); var cesiCache = new CesiVFSCache(s.GetRequiredService>(), s.GetRequiredService()); return new FallthroughVFSCache(new IVfsCache[] {diskCache}); }); service.AddSingleton(s => options.UseLocalCache ? new BinaryPatchCache(s.GetRequiredService>(), s.GetService()!.CreateFolder().Path) : new BinaryPatchCache(s.GetRequiredService>(),KnownFolders.WabbajackAppLocal.Combine("PatchCache"))); service.AddSingleton(s => { var dtos = s.GetRequiredService(); return options.UseLocalCache ? new VerificationCache(s.GetRequiredService>(), s.GetService()!.CreateFile().Path, TimeSpan.FromDays(1), dtos) : new VerificationCache(s.GetRequiredService>(), KnownFolders.WabbajackAppLocal.Combine("VerificationCacheV3.sqlite"), TimeSpan.FromDays(1), dtos); }); service.AddSingleton(new ParallelOptions {MaxDegreeOfParallelism = Environment.ProcessorCount}); Func> GetResourceSettings(IServiceProvider provider, string name) { return async () => { var s = await provider.GetService()!.GetSettings(name); return ((int) s.MaxTasks, s.MaxThroughput); }; } // Settings service.AddSingleton(s => new Configuration { EncryptedDataLocation = KnownFolders.WabbajackAppLocal.Combine("encrypted"), ModListsDownloadLocation = KnownFolders.EntryPoint.Combine("downloaded_mod_lists"), SavedSettingsLocation = KnownFolders.WabbajackAppLocal.Combine("saved_settings"), LogLocation = KnownFolders.LauncherAwarePath.Combine("logs"), ImageCacheLocation = KnownFolders.WabbajackAppLocal.Combine("image_cache") }); service.AddSingleton(); service.AddSingleton(); service.AddSingleton(s => GetAppSettings(s, MainSettings.SettingsFileName)); // Resources service.AddAllSingleton>(s => new Resource("Downloads", GetResourceSettings(s, "Downloads"), s.GetRequiredService())); service.AddAllSingleton>(s => new Resource("Web Requests", GetResourceSettings(s, "Web Requests"), s.GetRequiredService())); service.AddAllSingleton>(s => new Resource("VFS", GetResourceSettings(s, "VFS"), s.GetRequiredService())); service.AddAllSingleton>(s => new Resource("File Hashing", GetResourceSettings(s, "File Hashing"), s.GetRequiredService())); service.AddAllSingleton>(s => new Resource("Wabbajack Client", GetResourceSettings(s, "Wabbajack Client"), s.GetRequiredService())); service.AddAllSingleton>(s => new Resource("File Extractor", GetResourceSettings(s, "File Extractor"), s.GetRequiredService())); service.AddAllSingleton>(s => new Resource("Compiler", GetResourceSettings(s, "Compiler"), s.GetRequiredService())); service.AddAllSingleton>(s => new Resource("Installer", GetResourceSettings(s, "Installer"), s.GetRequiredService())); service.AddAllSingleton>(s => new Resource("User Intervention", 1, token: s.GetRequiredService())); service.AddSingleton(); service.AddScoped(); service.AddSingleton(); service.AddSingleton(); // Networking service.AddSingleton(); service.AddAllSingleton(); service.AddSteam(); service.AddSingleton(); service.AddSingleton(); service.AddBethesdaNet(); // Token Providers service.AddAllSingleton, EncryptedJsonTokenProvider, NexusApiTokenProvider>(); service.AddAllSingleton, EncryptedJsonTokenProvider, MegaTokenProvider>(); service.AddAllSingleton, EncryptedJsonTokenProvider, BethesdaNetTokenProvider>(); service .AddAllSingleton, EncryptedJsonTokenProvider, LoversLabTokenProvider>(); service .AddAllSingleton, EncryptedJsonTokenProvider, VectorPlexusTokenProvider>(); service .AddAllSingleton, EncryptedJsonTokenProvider, SteamTokenProvider>(); service.AddAllSingleton, WabbajackApiTokenProvider>(); service .AddAllSingleton>, EncryptedJsonTokenProvider>, DiscordTokenProvider>(); service.AddAllSingleton(); service.AddDownloadDispatcher(); if (options.UseStubbedGameFolders) service.AddAllSingleton(); else service.AddAllSingleton(); // ImageLoader if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) service.AddSingleton(); else service.AddSingleton(); // Installer/Compiler Configuration service.AddScoped(); service.AddScoped(); service.AddScoped(); service.AddScoped(); service.AddSingleton(); // Application Info var version = $"{ThisAssembly.Git.SemVer.Major}.{ThisAssembly.Git.SemVer.Major}.{ThisAssembly.Git.SemVer.Patch}{ThisAssembly.Git.SemVer.DashLabel}"; service.AddSingleton(s => new ApplicationInfo { ApplicationSlug = "Wabbajack", ApplicationName = Environment.ProcessPath?.ToAbsolutePath().FileName.ToString() ?? "Wabbajack", ApplicationSha = ThisAssembly.Git.Sha, Platform = RuntimeInformation.ProcessArchitecture.ToString(), OperatingSystemDescription = RuntimeInformation.OSDescription, RuntimeIdentifier = RuntimeInformation.RuntimeIdentifier, OSVersion = Environment.OSVersion.VersionString, Version = version }); return service; } public static MainSettings GetAppSettings(IServiceProvider provider, string name) { var settingsManager = provider.GetRequiredService(); var settings = Task.Run(() => settingsManager.Load(name)).Result; if (settings.Upgrade()) { settingsManager.Save(MainSettings.SettingsFileName, settings).FireAndForget(); } return settings; } private static void CleanAllTempData(AbsolutePath path) { // Get directories first and cache them, this freezes the directories were looking at // so any new ones don't show up in the middle of our deletes. var dirs = path.EnumerateDirectories().ToList(); var processIds = Process.GetProcesses().Select(p => p.Id).ToHashSet(); foreach (var dir in dirs) { var name = dir.FileName.ToString().Split("_"); if (!int.TryParse(name[0], out var processId)) continue; if (processIds.Contains(processId)) continue; try { dir.DeleteDirectory(); } catch (Exception) { // ignored } } } public class OSIntegratedOptions { public bool UseLocalCache { get; set; } = false; public bool UseStubbedGameFolders { get; set; } = false; } }