diff --git a/src/XIVLauncher.Common.Unix/Compatibility/CompatibilityTools.cs b/src/XIVLauncher.Common.Unix/Compatibility/CompatibilityTools.cs index 72923a4e..3e1f1aa6 100644 --- a/src/XIVLauncher.Common.Unix/Compatibility/CompatibilityTools.cs +++ b/src/XIVLauncher.Common.Unix/Compatibility/CompatibilityTools.cs @@ -43,17 +43,15 @@ public class CompatibilityTools public bool IsToolDownloaded => File.Exists(Wine64Path) && Settings.Prefix.Exists; - private readonly Dxvk.DxvkHudType hudType; + public DxvkSettings DxvkSettings { get; private set; } + private readonly bool gamemodeOn; - private readonly string dxvkAsyncOn; - public CompatibilityTools(WineSettings wineSettings, Dxvk.DxvkHudType hudType, bool? gamemodeOn, bool? dxvkAsyncOn, DirectoryInfo toolsFolder) + public CompatibilityTools(WineSettings wineSettings, DxvkSettings dxvkSettings, bool? gamemodeOn, DirectoryInfo toolsFolder) { this.Settings = wineSettings; - this.hudType = hudType; + this.DxvkSettings = dxvkSettings; this.gamemodeOn = gamemodeOn ?? false; - this.dxvkAsyncOn = (dxvkAsyncOn ?? false) ? "1" : "0"; - this.toolDirectory = new DirectoryInfo(Path.Combine(toolsFolder.FullName, "beta")); this.dxvkDirectory = new DirectoryInfo(Path.Combine(toolsFolder.FullName, "dxvk")); @@ -67,9 +65,6 @@ public CompatibilityTools(WineSettings wineSettings, Dxvk.DxvkHudType hudType, b if (!this.dxvkDirectory.Exists) this.dxvkDirectory.Create(); } - - if (!wineSettings.Prefix.Exists) - wineSettings.Prefix.Create(); } public async Task EnsureTool(DirectoryInfo tempPath) @@ -81,7 +76,7 @@ public async Task EnsureTool(DirectoryInfo tempPath) } EnsurePrefix(); - await Dxvk.InstallDxvk(Settings.Prefix, dxvkDirectory).ConfigureAwait(false); + await Dxvk.InstallDxvk(Settings.Prefix, dxvkDirectory, DxvkSettings).ConfigureAwait(false); IsToolReady = true; } @@ -158,7 +153,7 @@ private Process RunInPrefix(ProcessStartInfo psi, string workingDirectory, IDict var wineEnviromentVariables = new Dictionary(); wineEnviromentVariables.Add("WINEPREFIX", Settings.Prefix.FullName); - wineEnviromentVariables.Add("WINEDLLOVERRIDES", $"msquic=,mscoree=n,b;d3d9,d3d11,d3d10core,dxgi={(wineD3D ? "b" : "n")}"); + wineEnviromentVariables.Add("WINEDLLOVERRIDES", $"msquic=,mscoree=n,b;d3d9,d3d11,d3d10core,dxgi={(DxvkSettings.Enabled && !wineD3D ? "n" : "b")}"); if (!string.IsNullOrEmpty(Settings.DebugVars)) { @@ -168,21 +163,14 @@ private Process RunInPrefix(ProcessStartInfo psi, string workingDirectory, IDict wineEnviromentVariables.Add("XL_WINEONLINUX", "true"); string ldPreload = Environment.GetEnvironmentVariable("LD_PRELOAD") ?? ""; - string dxvkHud = hudType switch - { - Dxvk.DxvkHudType.None => "0", - Dxvk.DxvkHudType.Fps => "fps", - Dxvk.DxvkHudType.Full => "full", - _ => throw new ArgumentOutOfRangeException() - }; - - if (this.gamemodeOn == true && !ldPreload.Contains("libgamemodeauto.so.0")) + if (gamemodeOn && !ldPreload.Contains("libgamemodeauto.so.0")) { ldPreload = ldPreload.Equals("") ? "libgamemodeauto.so.0" : ldPreload + ":libgamemodeauto.so.0"; } - wineEnviromentVariables.Add("DXVK_HUD", dxvkHud); - wineEnviromentVariables.Add("DXVK_ASYNC", dxvkAsyncOn); + foreach (var dxvkVar in DxvkSettings.DxvkVars) + wineEnviromentVariables.Add(dxvkVar.Key, dxvkVar.Value); + wineEnviromentVariables.Add("WINEESYNC", Settings.EsyncOn); wineEnviromentVariables.Add("WINEFSYNC", Settings.FsyncOn); @@ -290,4 +278,4 @@ public void Kill() Process.Start(psi); } -} \ No newline at end of file +} diff --git a/src/XIVLauncher.Common.Unix/Compatibility/Dxvk.cs b/src/XIVLauncher.Common.Unix/Compatibility/Dxvk.cs index 50fa98d9..95d7ba74 100644 --- a/src/XIVLauncher.Common.Unix/Compatibility/Dxvk.cs +++ b/src/XIVLauncher.Common.Unix/Compatibility/Dxvk.cs @@ -8,17 +8,14 @@ namespace XIVLauncher.Common.Unix.Compatibility; public static class Dxvk { - private const string DXVK_DOWNLOAD = "https://github.com/Sporif/dxvk-async/releases/download/1.10.1/dxvk-async-1.10.1.tar.gz"; - private const string DXVK_NAME = "dxvk-async-1.10.1"; - - public static async Task InstallDxvk(DirectoryInfo prefix, DirectoryInfo installDirectory) + public static async Task InstallDxvk(DirectoryInfo prefix, DirectoryInfo installDirectory, DxvkSettings dxvkSettings) { - var dxvkPath = Path.Combine(installDirectory.FullName, DXVK_NAME, "x64"); + var dxvkPath = Path.Combine(installDirectory.FullName, dxvkSettings.FolderName, "x64"); if (!Directory.Exists(dxvkPath)) { Log.Information("DXVK does not exist, downloading"); - await DownloadDxvk(installDirectory).ConfigureAwait(false); + await DownloadDxvk(installDirectory, dxvkSettings.DownloadURL).ConfigureAwait(false); } var system32 = Path.Combine(prefix.FullName, "drive_c", "windows", "system32"); @@ -30,12 +27,12 @@ public static async Task InstallDxvk(DirectoryInfo prefix, DirectoryInfo install } } - private static async Task DownloadDxvk(DirectoryInfo installDirectory) + private static async Task DownloadDxvk(DirectoryInfo installDirectory, string downloadURL) { using var client = new HttpClient(); var tempPath = Path.GetTempFileName(); - File.WriteAllBytes(tempPath, await client.GetByteArrayAsync(DXVK_DOWNLOAD)); + File.WriteAllBytes(tempPath, await client.GetByteArrayAsync(downloadURL)); PlatformHelpers.Untar(tempPath, installDirectory.FullName); File.Delete(tempPath); @@ -49,7 +46,40 @@ public enum DxvkHudType [SettingsDescription("FPS", "Only show FPS")] Fps, + [SettingsDescription("DXVK Hud Custom", "Use a custom DXVK_HUD string")] + Custom, + [SettingsDescription("Full", "Show everything")] Full, + + [SettingsDescription("MangoHud Default", "Uses no config file.")] + MangoHud, + + [SettingsDescription("MangoHud Custom", "Specify a custom config file")] + MangoHudCustom, + + [SettingsDescription("MangoHud Full", "Show (almost) everything")] + MangoHudFull, + } + + public enum DxvkVersion + { + [SettingsDescription("1.10.1", "The version of DXVK originally used with XIVLauncher.Core 1.0.3. Safe to use.")] + v1_10_1, + + [SettingsDescription("1.10.2", "Older version of 1.10 branch of DXVK. Safe to use.")] + v1_10_2, + + [SettingsDescription("1.10.3 (default)", "Current version of 1.10 branch of DXVK.")] + v1_10_3, + + [SettingsDescription("2.0", "Newer version of DXVK. Last version with Async patch")] + v2_0, + + [SettingsDescription("2.1 (No Async)", "Newer version of DXVK, using graphics pipeline library. No Async patch.")] + v2_1, + + [SettingsDescription("2.2 (No Async)", "Newest version of DXVK, using graphics pipeline library. No Async patch.")] + v2_2, } -} \ No newline at end of file +} diff --git a/src/XIVLauncher.Common.Unix/Compatibility/DxvkSettings.cs b/src/XIVLauncher.Common.Unix/Compatibility/DxvkSettings.cs new file mode 100644 index 00000000..58929042 --- /dev/null +++ b/src/XIVLauncher.Common.Unix/Compatibility/DxvkSettings.cs @@ -0,0 +1,142 @@ +#nullable enable +using System; +using System.IO; +using System.Collections.Generic; +using System.Linq; +using System.Text.RegularExpressions; + +namespace XIVLauncher.Common.Unix.Compatibility; + +public class DxvkSettings +{ + public bool Enabled { get; } + + public string DownloadURL { get; } + + public string FolderName { get; } + + public Dictionary DxvkVars { get; } + + public Dxvk.DxvkHudType DxvkHud { get; } + + public Dxvk.DxvkVersion DxvkVersion { get; } + + private const string ALLOWED_CHARS = "^[0-9a-zA-Z,=.]+$"; + + private const string ALLOWED_WORDS = "^(?:devinfo|fps|frametimes|submissions|drawcalls|pipelines|descriptors|memory|gpuload|version|api|cs|compiler|samplers|scale=(?:[0-9])*(?:.(?:[0-9])+)?)$"; + + public DxvkSettings(Dxvk.DxvkHudType hud, DirectoryInfo corePath, Dxvk.DxvkVersion version, bool enabled = true, string? dxvkHudCustom = null, FileInfo? mangoHudConfig = null, bool async = true, + int maxFrameRate = 0) + { + Enabled = enabled; + DxvkHud = hud; + var dxvkConfigPath = new DirectoryInfo(Path.Combine(corePath.FullName, "compatibilitytool", "dxvk")); + if (!dxvkConfigPath.Exists) + dxvkConfigPath.Create(); + DxvkVars = new Dictionary + { + { "DXVK_LOG_PATH", Path.Combine(corePath.FullName, "logs") }, + { "DXVK_CONFIG_FILE", Path.Combine(dxvkConfigPath.FullName, "dxvk.conf") }, + { "DXVK_FRAME_RATE", (maxFrameRate).ToString() } + }; + DxvkVersion = version; + var release = DxvkVersion switch + { + Dxvk.DxvkVersion.v1_10_1 => "1.10.1", + Dxvk.DxvkVersion.v1_10_2 => "1.10.2", + Dxvk.DxvkVersion.v1_10_3 => "1.10.3", + Dxvk.DxvkVersion.v2_0 => "2.0", + Dxvk.DxvkVersion.v2_1 => "2.1", + Dxvk.DxvkVersion.v2_2 => "2.2", + _ => throw new ArgumentOutOfRangeException(), + }; + if (new[] {"1.10.1", "1.10.2", "1.10.3", "2.0"}.Contains(release)) + { + DownloadURL = $"https://github.com/Sporif/dxvk-async/releases/download/{release}/dxvk-async-{release}.tar.gz"; + FolderName = $"dxvk-async-{release}"; + DxvkVars.Add("DXVK_ASYNC", async ? "1" : "0"); + } + else + { + DownloadURL = $"https://github.com/doitsujin/dxvk/releases/download/v{release}/dxvk-{release}.tar.gz"; + FolderName = $"dxvk-{release}"; + } + + var dxvkCachePath = new DirectoryInfo(Path.Combine(dxvkConfigPath.FullName, "cache")); + if (!dxvkCachePath.Exists) dxvkCachePath.Create(); + this.DxvkVars.Add("DXVK_STATE_CACHE_PATH", Path.Combine(dxvkCachePath.FullName, release + (async ? "-async" : ""))); + + switch(this.DxvkHud) + { + case Dxvk.DxvkHudType.Fps: + DxvkVars.Add("DXVK_HUD","fps"); + DxvkVars.Add("MANGOHUD","0"); + break; + + case Dxvk.DxvkHudType.Custom: + if (!CheckDxvkHudString(dxvkHudCustom)) + dxvkHudCustom = "fps,frametimes,gpuload,version"; + DxvkVars.Add("DXVK_HUD", dxvkHudCustom!); + DxvkVars.Add("MANGOHUD","0"); + break; + + case Dxvk.DxvkHudType.Full: + DxvkVars.Add("DXVK_HUD","full"); + DxvkVars.Add("MANGOHUD","0"); + break; + + case Dxvk.DxvkHudType.MangoHud: + DxvkVars.Add("DXVK_HUD","0"); + DxvkVars.Add("MANGOHUD","1"); + DxvkVars.Add("MANGOHUD_CONFIG", ""); + break; + + case Dxvk.DxvkHudType.MangoHudCustom: + DxvkVars.Add("DXVK_HUD","0"); + DxvkVars.Add("MANGOHUD","1"); + + if (mangoHudConfig is null) + { + var home = Environment.GetFolderPath(Environment.SpecialFolder.UserProfile); + var conf1 = Path.Combine(corePath.FullName, "MangoHud.conf"); + var conf2 = Path.Combine(home, ".config", "MangoHud", "wine-ffxiv_dx11.conf"); + var conf3 = Path.Combine(home, ".config", "MangoHud", "MangoHud.conf"); + if (File.Exists(conf1)) + mangoHudConfig = new FileInfo(conf1); + else if (File.Exists(conf2)) + mangoHudConfig = new FileInfo(conf2); + else if (File.Exists(conf3)) + mangoHudConfig = new FileInfo(conf3); + } + + if (mangoHudConfig != null && mangoHudConfig.Exists) + DxvkVars.Add("MANGOHUD_CONFIGFILE", mangoHudConfig.FullName); + else + DxvkVars.Add("MANGOHUD_CONFIG", ""); + break; + + case Dxvk.DxvkHudType.MangoHudFull: + DxvkVars.Add("DXVK_HUD","0"); + DxvkVars.Add("MANGOHUD","1"); + DxvkVars.Add("MANGOHUD_CONFIG","full"); + break; + + case Dxvk.DxvkHudType.None: + break; + + default: + throw new ArgumentOutOfRangeException(); + } + } + + public static bool CheckDxvkHudString(string? customHud) + { + if (string.IsNullOrWhiteSpace(customHud)) return false; + if (customHud == "1") return true; + if (!Regex.IsMatch(customHud,ALLOWED_CHARS)) return false; + + string[] hudvars = customHud.Split(","); + + return hudvars.All(hudvar => Regex.IsMatch(hudvar, ALLOWED_WORDS)); + } +}