diff --git a/src/XIVLauncher.Common.Unix/Compatibility/CompatibilityTools.cs b/src/XIVLauncher.Common.Unix/Compatibility/CompatibilityTools.cs index afe43885..10ced48c 100644 --- a/src/XIVLauncher.Common.Unix/Compatibility/CompatibilityTools.cs +++ b/src/XIVLauncher.Common.Unix/Compatibility/CompatibilityTools.cs @@ -20,6 +20,8 @@ public class CompatibilityTools private DirectoryInfo dxvkDirectory; + private DirectoryInfo GamePath; + private StreamWriter logWriter; public bool IsToolReady { get; private set; } @@ -36,7 +38,7 @@ public class CompatibilityTools private Dictionary extraEnvironmentVars; - public CompatibilityTools(WineSettings wineSettings, DxvkSettings dxvkSettings, bool? gamemodeOn, DirectoryInfo toolsFolder, bool isFlatpak, Dictionary extraEnvVars = null) + public CompatibilityTools(WineSettings wineSettings, DxvkSettings dxvkSettings, bool? gamemodeOn, DirectoryInfo toolsFolder, DirectoryInfo gamePath, bool isFlatpak, Dictionary extraEnvVars = null) { this.Settings = wineSettings; this.DxvkSettings = dxvkSettings; @@ -49,6 +51,8 @@ public CompatibilityTools(WineSettings wineSettings, DxvkSettings dxvkSettings, this.wineDirectory = new DirectoryInfo(Path.Combine(toolsFolder.FullName, "wine")); this.dxvkDirectory = new DirectoryInfo(Path.Combine(toolsFolder.FullName, "dxvk")); + this.GamePath = gamePath; + this.logWriter = new StreamWriter(wineSettings.LogFile.FullName); if (!this.wineDirectory.Exists) @@ -73,6 +77,9 @@ public async Task EnsureTool(DirectoryInfo tempPath) if (DxvkSettings.Enabled) await InstallDxvk().ConfigureAwait(false); + if (DxvkSettings.NvapiEnabled) + await InstallNvapi().ConfigureAwait(false); + IsToolReady = true; } @@ -93,7 +100,7 @@ private async Task InstallDxvk() File.Copy(fileName, Path.Combine(system32, Path.GetFileName(fileName)), true); } - // 32-bit files for Directx9. + // 32-bit files for Directx9. Only needed for external programs. var dxvkPath32 = Path.Combine(dxvkDirectory.FullName, DxvkSettings.FolderName, "x32"); var syswow64 = Path.Combine(Settings.Prefix.FullName, "drive_c", "windows", "syswow64"); @@ -105,7 +112,83 @@ private async Task InstallDxvk() { File.Copy(fileName, Path.Combine(syswow64, Path.GetFileName(fileName)), true); } - } + } + } + + private async Task InstallNvapi() + { + var dxvkPath = Path.Combine(dxvkDirectory.FullName, DxvkSettings.NvapiFolderName, "x64"); + if (!Directory.Exists(dxvkPath)) + { + Log.Information($"DXVK Nvapi does not exist, downloading {DxvkSettings.NvapiDownloadUrl}"); + var nvapiFolder = new DirectoryInfo(Path.Combine(dxvkDirectory.FullName, DxvkSettings.NvapiFolderName)); + nvapiFolder.Create(); + await DownloadTool(nvapiFolder, DxvkSettings.NvapiDownloadUrl).ConfigureAwait(false); + } + + var system32 = Path.Combine(Settings.Prefix.FullName, "drive_c", "windows", "system32"); + var files = Directory.GetFiles(dxvkPath); + + foreach (string fileName in files) + { + File.Copy(fileName, Path.Combine(system32, Path.GetFileName(fileName)), true); + } + + // Create symlinks to nvngx.dll and _nvngx.dll in the GamePath/game folder. For some reason it doesn't work if you put them in system32. + // If NvngxOverride is set, assume the files/symlinks are already there. For Nix compatibility, mostly. + if (!string.IsNullOrEmpty(DxvkSettings.NvngxFolder) && Directory.Exists(DxvkSettings.NvngxFolder) && !DxvkSettings.NvngxOverride) + { + string[] targets = { "nvngx.dll", "_nvngx.dll"}; + foreach (var target in targets) + { + var source = new FileInfo(Path.Combine(DxvkSettings.NvngxFolder, target)); + var destination = new FileInfo(Path.Combine(GamePath.FullName, "game", target)); + if (source.Exists) + { + if (!destination.Exists) // No file, create link. + { + destination.CreateAsSymbolicLink(source.FullName); + Log.Verbose($"Making symbolic link at {destination.FullName} to {source.FullName}"); + } + else if (destination.ResolveLinkTarget(false) is null) // File exists, is not a symlink. Delete and create link. + { + destination.Delete(); + destination.CreateAsSymbolicLink(source.FullName); + Log.Verbose($"Replacing file at {destination.FullName} with symbolic link to {source.FullName}"); + } + else if (destination.ResolveLinkTarget(true).FullName != source.FullName) // Link exists, but does not point to source. Replace. + { + destination.Delete(); + destination.CreateAsSymbolicLink(source.FullName); + Log.Verbose($"Symbolic link at {destination.FullName} incorrectly links to {destination.ResolveLinkTarget(true).FullName}. Replacing with link to {source.FullName}"); + } + else + Log.Verbose($"Symbolic link at {destination.FullName} to {source.FullName} is correct."); + } + else + Log.Error($"Missing Nvidia dll! DLSS may not work. {target} not found in {DxvkSettings.NvngxFolder}"); + } + } + + // 32-bit files for Directx9. Only needed for external programs. + var dxvkPath32 = Path.Combine(dxvkDirectory.FullName, DxvkSettings.NvapiFolderName, "x32"); + var syswow64 = Path.Combine(Settings.Prefix.FullName, "drive_c", "windows", "syswow64"); + + if (Directory.Exists(dxvkPath32)) + { + files = Directory.GetFiles(dxvkPath32); + + foreach (string fileName in files) + { + File.Copy(fileName, Path.Combine(syswow64, Path.GetFileName(fileName)), true); + } + } + } + + private void UninstallNvngx() + { + File.Delete(Path.Combine(GamePath.FullName, "game", "nvngx.dll")); + File.Delete(Path.Combine(GamePath.FullName, "game", "_nvngx.dll")); } private async Task DownloadTool(DirectoryInfo installDirectory, string downloadUrl) @@ -201,7 +284,8 @@ private Process RunInPrefix(ProcessStartInfo psi, string workingDirectory, IDict var wineEnvironmentVariables = new Dictionary(); wineEnvironmentVariables.Add("WINEPREFIX", Settings.Prefix.FullName); - wineEnvironmentVariables.Add("WINEDLLOVERRIDES", $"msquic=,mscoree=n,b;d3d9,d3d11,d3d10core,dxgi={(wineD3D ? "b" : "n,b")}"); + wineEnvironmentVariables.Add("WINEDLLOVERRIDES", $"msquic=,mscoree=n,b;d3d9,d3d11,d3d10core,dxgi={(wineD3D ? "b" : "n,b")};{Settings.ExtraWineDLLOverrides}"); + Console.WriteLine("WINEDLLOVERRIDES=\"" + wineEnvironmentVariables["WINEDLLOVERRIDES"] + "\""); if (!string.IsNullOrEmpty(Settings.DebugVars)) { diff --git a/src/XIVLauncher.Common.Unix/Compatibility/DxvkSettings.cs b/src/XIVLauncher.Common.Unix/Compatibility/DxvkSettings.cs index db657bbc..29957ad4 100644 --- a/src/XIVLauncher.Common.Unix/Compatibility/DxvkSettings.cs +++ b/src/XIVLauncher.Common.Unix/Compatibility/DxvkSettings.cs @@ -14,13 +14,30 @@ public class DxvkSettings public string DownloadUrl { get; } + public string NvapiFolderName { get; } + + public string NvapiDownloadUrl { get; } + + public string NvngxFolder { get; } + + public bool NvapiEnabled { get; } + + public bool NvngxOverride { get; } + public Dictionary Environment { get; } - public DxvkSettings(string folder, string url, string storageFolder, bool async, int maxFrameRate, bool dxvkHudEnabled, string dxvkHudString, bool mangoHudEnabled = false, bool mangoHudCustomIsFile = false, string customMangoHud = "", bool enabled = true) + public DxvkSettings(string folder, string url, string storageFolder, bool async, int maxFrameRate, bool dxvkHudEnabled, string dxvkHudString, bool mangoHudEnabled = false, bool mangoHudCustomIsFile = false, string customMangoHud = "", bool enabled = true, string nvapiFolder = "", string nvapiUrl = "", string nvngxFolder = "") { FolderName = folder; DownloadUrl = url; + NvapiFolderName = nvapiFolder; + NvapiDownloadUrl = nvapiUrl; + NvngxFolder = nvngxFolder; Enabled = enabled; + NvngxOverride = !string.IsNullOrEmpty(NvapiFolderName) && string.IsNullOrEmpty(NvngxFolder); + + // Disable Nvapi if the NvapiFolderName is empty, if Dxvk is not enabled, or if the dxvk version is dxvk-1.x or dxvk-async-1.x + NvapiEnabled = (!string.IsNullOrEmpty(NvapiFolderName) && DxvkAllowsNvapi(FolderName) && Enabled); var dxvkConfigPath = new DirectoryInfo(Path.Combine(storageFolder, "compatibilitytool", "dxvk")); Environment = new Dictionary @@ -57,6 +74,11 @@ public DxvkSettings(string folder, string url, string storageFolder, bool async, Environment.Add("MANGOHUD_CONFIG", customMangoHud); } } + + if (NvapiEnabled) + { + Environment.Add("DXVK_ENABLE_NVAPI", "1"); + } } public static bool DxvkHudStringIsValid(string customHud) @@ -74,6 +96,12 @@ public static bool DxvkHudStringIsValid(string customHud) return hudvars.All(hudvar => Regex.IsMatch(hudvar, ALLOWED_WORDS)); } + public static bool DxvkAllowsNvapi(string dxvkVersion) + { + var pattern = @"^dxvk-(async-)?1\.\d{1,2}(\.\d)?$"; + return !Regex.IsMatch(dxvkVersion, pattern); + } + public static bool MangoHudIsInstalled() { var usrLib = Path.Combine("/", "usr", "lib", "mangohud", "libMangoHud.so"); // fedora uses this diff --git a/src/XIVLauncher.Common.Unix/Compatibility/WineSettings.cs b/src/XIVLauncher.Common.Unix/Compatibility/WineSettings.cs index ed8d16df..dddd6134 100644 --- a/src/XIVLauncher.Common.Unix/Compatibility/WineSettings.cs +++ b/src/XIVLauncher.Common.Unix/Compatibility/WineSettings.cs @@ -1,4 +1,7 @@ using System.IO; +using System.Text.RegularExpressions; +using System.Linq; + namespace XIVLauncher.Common.Unix.Compatibility; @@ -16,6 +19,8 @@ public class WineSettings public string DownloadUrl; + public string ExtraWineDLLOverrides; + public string EsyncOn { get; private set; } public string FsyncOn { get; private set; } @@ -26,13 +31,14 @@ public class WineSettings public DirectoryInfo Prefix { get; private set; } - public WineSettings(bool isManaged, string customBinPath, string managedFolder, string managedUrl, DirectoryInfo storageFolder, string debugVars, FileInfo logFile, DirectoryInfo prefix, bool? esyncOn, bool? fsyncOn) + public WineSettings(bool isManaged, string customBinPath, string managedFolder, string managedUrl, string extraDLLOverrides, DirectoryInfo storageFolder, string debugVars, FileInfo logFile, DirectoryInfo prefix, bool? esyncOn, bool? fsyncOn) { // storageFolder is the path to .xlcore folder. managedFolder is the foldername inside the tarball that will be downloaded from managedUrl. IsManaged = isManaged; FolderName = managedFolder; DownloadUrl = managedUrl; BinPath = (isManaged) ? Path.Combine(storageFolder.FullName, "compatibilitytool", "wine", managedFolder, "bin") : customBinPath; + ExtraWineDLLOverrides = WineDLLOverrideIsValid(extraDLLOverrides) ? extraDLLOverrides ?? "" : ""; this.EsyncOn = (esyncOn ?? false) ? "1" : "0"; this.FsyncOn = (fsyncOn ?? false) ? "1" : "0"; @@ -40,4 +46,16 @@ public WineSettings(bool isManaged, string customBinPath, string managedFolder, this.LogFile = logFile; this.Prefix = prefix; } + + public static bool WineDLLOverrideIsValid(string dlls) + { + string[] invalid = { "msquic", "mscoree", "d3d9", "d3d11", "d3d10core", "dxgi" }; + var format = @"^(?:(?:[a-zA-Z0-9_\-\.]+,?)+=(?:n,b|b,n|n|b|d|,|);?)+$"; + + if (string.IsNullOrEmpty(dlls)) return true; + if (invalid.Any(s => dlls.Contains(s))) return false; + if (Regex.IsMatch(dlls, format)) return true; + + return false; + } } \ No newline at end of file