diff --git a/src/XIVLauncher.Common.Unix/Compatibility/CompatibilityTools.cs b/src/XIVLauncher.Common.Unix/Compatibility/CompatibilityTools.cs index 8216526f..afe43885 100644 --- a/src/XIVLauncher.Common.Unix/Compatibility/CompatibilityTools.cs +++ b/src/XIVLauncher.Common.Unix/Compatibility/CompatibilityTools.cs @@ -1,13 +1,12 @@ using System; using System.Collections.Generic; -using System.Collections.Specialized; using System.Diagnostics; using System.IO; using System.Linq; -using System.Net.Http; using System.Threading.Tasks; -using Serilog; +using System.Net.Http; using XIVLauncher.Common.Util; +using Serilog; #if FLATPAK #warning THIS IS A FLATPAK BUILD!!! @@ -17,56 +16,46 @@ namespace XIVLauncher.Common.Unix.Compatibility; public class CompatibilityTools { - private DirectoryInfo toolDirectory; + private DirectoryInfo wineDirectory; + private DirectoryInfo dxvkDirectory; private StreamWriter logWriter; -#if WINE_XIV_ARCH_LINUX - private const string WINE_XIV_RELEASE_URL = "https://github.com/goatcorp/wine-xiv-git/releases/download/8.5.r4.g4211bac7/wine-xiv-staging-fsync-git-arch-8.5.r4.g4211bac7.tar.xz"; -#elif WINE_XIV_FEDORA_LINUX - private const string WINE_XIV_RELEASE_URL = "https://github.com/goatcorp/wine-xiv-git/releases/download/8.5.r4.g4211bac7/wine-xiv-staging-fsync-git-fedora-8.5.r4.g4211bac7.tar.xz"; -#else - private const string WINE_XIV_RELEASE_URL = "https://github.com/goatcorp/wine-xiv-git/releases/download/8.5.r4.g4211bac7/wine-xiv-staging-fsync-git-ubuntu-8.5.r4.g4211bac7.tar.xz"; -#endif - private const string WINE_XIV_RELEASE_NAME = "wine-xiv-staging-fsync-git-8.5.r4.g4211bac7"; - public bool IsToolReady { get; private set; } public WineSettings Settings { get; private set; } - private string WineBinPath => Settings.StartupType == WineStartupType.Managed ? - Path.Combine(toolDirectory.FullName, WINE_XIV_RELEASE_NAME, "bin") : - Settings.CustomBinPath; - private string Wine64Path => Path.Combine(WineBinPath, "wine64"); - private string WineServerPath => Path.Combine(WineBinPath, "wineserver"); + public DxvkSettings DxvkSettings { get; private set; } - public bool IsToolDownloaded => File.Exists(Wine64Path) && Settings.Prefix.Exists; + public bool IsToolDownloaded => File.Exists(Settings.WinePath) && Settings.Prefix.Exists; - private readonly Dxvk.DxvkHudType hudType; + public bool IsFlatpak { get; } + private readonly bool gamemodeOn; - private readonly string dxvkAsyncOn; - public CompatibilityTools(WineSettings wineSettings, Dxvk.DxvkHudType hudType, bool? gamemodeOn, bool? dxvkAsyncOn, DirectoryInfo toolsFolder) + private Dictionary extraEnvironmentVars; + + public CompatibilityTools(WineSettings wineSettings, DxvkSettings dxvkSettings, bool? gamemodeOn, DirectoryInfo toolsFolder, bool isFlatpak, Dictionary extraEnvVars = null) { 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")); + // These are currently unused. Here for future use. + this.IsFlatpak = isFlatpak; + this.extraEnvironmentVars = extraEnvVars ?? new Dictionary(); + + this.wineDirectory = new DirectoryInfo(Path.Combine(toolsFolder.FullName, "wine")); this.dxvkDirectory = new DirectoryInfo(Path.Combine(toolsFolder.FullName, "dxvk")); this.logWriter = new StreamWriter(wineSettings.LogFile.FullName); - if (wineSettings.StartupType == WineStartupType.Managed) - { - if (!this.toolDirectory.Exists) - this.toolDirectory.Create(); + if (!this.wineDirectory.Exists) + this.wineDirectory.Create(); - if (!this.dxvkDirectory.Exists) - this.dxvkDirectory.Create(); - } + if (!this.dxvkDirectory.Exists) + this.dxvkDirectory.Create(); if (!wineSettings.Prefix.Exists) wineSettings.Prefix.Create(); @@ -74,30 +63,60 @@ public CompatibilityTools(WineSettings wineSettings, Dxvk.DxvkHudType hudType, b public async Task EnsureTool(DirectoryInfo tempPath) { - if (!File.Exists(Wine64Path)) + if (!File.Exists(Settings.WinePath)) { - Log.Information("Compatibility tool does not exist, downloading"); - await DownloadTool(tempPath).ConfigureAwait(false); + Log.Information($"Compatibility tool does not exist, downloading {Settings.DownloadUrl}"); + await DownloadTool(wineDirectory, Settings.DownloadUrl).ConfigureAwait(false); } - EnsurePrefix(); - await Dxvk.InstallDxvk(Settings.Prefix, dxvkDirectory).ConfigureAwait(false); + + if (DxvkSettings.Enabled) + await InstallDxvk().ConfigureAwait(false); IsToolReady = true; } - private async Task DownloadTool(DirectoryInfo tempPath) + private async Task InstallDxvk() { - using var client = new HttpClient(); - var tempFilePath = Path.Combine(tempPath.FullName, $"{Guid.NewGuid()}"); + var dxvkPath = Path.Combine(dxvkDirectory.FullName, DxvkSettings.FolderName, "x64"); + if (!Directory.Exists(dxvkPath)) + { + Log.Information($"DXVK does not exist, downloading {DxvkSettings.DownloadUrl}"); + await DownloadTool(dxvkDirectory, DxvkSettings.DownloadUrl).ConfigureAwait(false); + } - await File.WriteAllBytesAsync(tempFilePath, await client.GetByteArrayAsync(WINE_XIV_RELEASE_URL).ConfigureAwait(false)).ConfigureAwait(false); + var system32 = Path.Combine(Settings.Prefix.FullName, "drive_c", "windows", "system32"); + var files = Directory.GetFiles(dxvkPath); - PlatformHelpers.Untar(tempFilePath, this.toolDirectory.FullName); + foreach (string fileName in files) + { + File.Copy(fileName, Path.Combine(system32, Path.GetFileName(fileName)), true); + } - Log.Information("Compatibility tool successfully extracted to {Path}", this.toolDirectory.FullName); + // 32-bit files for Directx9. + var dxvkPath32 = Path.Combine(dxvkDirectory.FullName, DxvkSettings.FolderName, "x32"); + var syswow64 = Path.Combine(Settings.Prefix.FullName, "drive_c", "windows", "syswow64"); - File.Delete(tempFilePath); + if (Directory.Exists(dxvkPath32)) + { + files = Directory.GetFiles(dxvkPath32); + + foreach (string fileName in files) + { + File.Copy(fileName, Path.Combine(syswow64, Path.GetFileName(fileName)), true); + } + } + } + + private async Task DownloadTool(DirectoryInfo installDirectory, string downloadUrl) + { + using var client = new HttpClient(); + var tempPath = Path.GetTempFileName(); + + File.WriteAllBytes(tempPath, await client.GetByteArrayAsync(downloadUrl)); + PlatformHelpers.Untar(tempPath, installDirectory.FullName); + + File.Delete(tempPath); } private void ResetPrefix() @@ -118,7 +137,7 @@ public void EnsurePrefix() public Process RunInPrefix(string command, string workingDirectory = "", IDictionary environment = null, bool redirectOutput = false, bool writeLog = false, bool wineD3D = false) { - var psi = new ProcessStartInfo(Wine64Path); + var psi = new ProcessStartInfo(Settings.WinePath); psi.Arguments = command; Log.Verbose("Running in prefix: {FileName} {Arguments}", psi.FileName, command); @@ -127,7 +146,7 @@ public Process RunInPrefix(string command, string workingDirectory = "", IDictio public Process RunInPrefix(string[] args, string workingDirectory = "", IDictionary environment = null, bool redirectOutput = false, bool writeLog = false, bool wineD3D = false) { - var psi = new ProcessStartInfo(Wine64Path); + var psi = new ProcessStartInfo(Settings.WinePath); foreach (var arg in args) psi.ArgumentList.Add(arg); @@ -135,7 +154,7 @@ public Process RunInPrefix(string[] args, string workingDirectory = "", IDiction return RunInPrefix(psi, workingDirectory, environment, redirectOutput, writeLog, wineD3D); } - private void MergeDictionaries(StringDictionary a, IDictionary b) + private void MergeDictionaries(IDictionary a, IDictionary b) { if (b is null) return; @@ -143,12 +162,34 @@ private void MergeDictionaries(StringDictionary a, IDictionary b foreach (var keyValuePair in b) { if (a.ContainsKey(keyValuePair.Key)) - a[keyValuePair.Key] = keyValuePair.Value; + { + if (keyValuePair.Key == "LD_PRELOAD") + a[keyValuePair.Key] = MergeLDPreload(a[keyValuePair.Key], keyValuePair.Value); + else + a[keyValuePair.Key] = keyValuePair.Value; + } else a.Add(keyValuePair.Key, keyValuePair.Value); } } + private string MergeLDPreload(string a, string b) + { + a ??= ""; + b ??= ""; + return (a.Trim(':') + ":" + b.Trim(':')).Trim(':'); + } + + public void AddEnvironmentVar(string key, string value) + { + extraEnvironmentVars.Add(key, value); + } + + public void AddEnvironmentVars(IDictionary env) + { + MergeDictionaries(extraEnvironmentVars, env); + } + private Process RunInPrefix(ProcessStartInfo psi, string workingDirectory, IDictionary environment, bool redirectOutput, bool writeLog, bool wineD3D) { psi.RedirectStandardOutput = redirectOutput; @@ -156,48 +197,38 @@ private Process RunInPrefix(ProcessStartInfo psi, string workingDirectory, IDict psi.UseShellExecute = false; psi.WorkingDirectory = workingDirectory; - var wineEnviromentVariables = new Dictionary(); - wineEnviromentVariables.Add("WINEPREFIX", Settings.Prefix.FullName); - wineEnviromentVariables.Add("WINEDLLOVERRIDES", $"msquic=,mscoree=n,b;d3d9,d3d11,d3d10core,dxgi={(wineD3D ? "b" : "n")}"); + wineD3D = !DxvkSettings.Enabled || wineD3D; + + 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")}"); if (!string.IsNullOrEmpty(Settings.DebugVars)) { - wineEnviromentVariables.Add("WINEDEBUG", Settings.DebugVars); + wineEnvironmentVariables.Add("WINEDEBUG", Settings.DebugVars); } - 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")) - { - ldPreload = ldPreload.Equals("") ? "libgamemodeauto.so.0" : ldPreload + ":libgamemodeauto.so.0"; - } + wineEnvironmentVariables.Add("XL_WINEONLINUX", "true"); - wineEnviromentVariables.Add("DXVK_HUD", dxvkHud); - wineEnviromentVariables.Add("DXVK_ASYNC", dxvkAsyncOn); - wineEnviromentVariables.Add("WINEESYNC", Settings.EsyncOn); - wineEnviromentVariables.Add("WINEFSYNC", Settings.FsyncOn); + if (this.gamemodeOn) + wineEnvironmentVariables.Add("LD_PRELOAD", MergeLDPreload("libgamemodeauto.so.0" , Environment.GetEnvironmentVariable("LD_PRELOAD"))); - wineEnviromentVariables.Add("LD_PRELOAD", ldPreload); + foreach (var dxvkVar in DxvkSettings.Environment) + wineEnvironmentVariables.Add(dxvkVar.Key, dxvkVar.Value); + wineEnvironmentVariables.Add("WINEESYNC", Settings.EsyncOn); + wineEnvironmentVariables.Add("WINEFSYNC", Settings.FsyncOn); - MergeDictionaries(psi.EnvironmentVariables, wineEnviromentVariables); - MergeDictionaries(psi.EnvironmentVariables, environment); + MergeDictionaries(psi.Environment, wineEnvironmentVariables); + MergeDictionaries(psi.Environment, extraEnvironmentVars); // Allow extraEnvironmentVars to override what we set here. + MergeDictionaries(psi.Environment, environment); #if FLATPAK_NOTRIGHTNOW psi.FileName = "flatpak-spawn"; psi.ArgumentList.Insert(0, "--host"); - psi.ArgumentList.Insert(1, Wine64Path); + psi.ArgumentList.Insert(1, Settings.WinePath); - foreach (KeyValuePair envVar in wineEnviromentVariables) + foreach (KeyValuePair envVar in wineEnvironmentVariables) { psi.ArgumentList.Insert(1, $"--env={envVar.Key}={envVar.Value}"); } @@ -257,14 +288,54 @@ public Int32 GetUnixProcessId(Int32 winePid) { var wineDbg = RunInPrefix("winedbg --command \"info procmap\"", redirectOutput: true); var output = wineDbg.StandardOutput.ReadToEnd(); - if (output.Contains("syntax error\n")) - return 0; + if (output.Contains("syntax error\n") || output.Contains("Exception c0000005")) // Proton8 wine changed the error message + { + var processName = GetProcessName(winePid); + return GetUnixProcessIdByName(processName); + } var matchingLines = output.Split('\n', StringSplitOptions.RemoveEmptyEntries).Skip(1).Where( l => int.Parse(l.Substring(1, 8), System.Globalization.NumberStyles.HexNumber) == winePid); var unixPids = matchingLines.Select(l => int.Parse(l.Substring(10, 8), System.Globalization.NumberStyles.HexNumber)).ToArray(); return unixPids.FirstOrDefault(); } + private string GetProcessName(Int32 winePid) + { + var wineDbg = RunInPrefix("winedbg --command \"info proc\"", redirectOutput: true); + var output = wineDbg.StandardOutput.ReadToEnd(); + var matchingLines = output.Split('\n', StringSplitOptions.RemoveEmptyEntries).Skip(1).Where( + l => int.Parse(l.Substring(1, 8), System.Globalization.NumberStyles.HexNumber) == winePid); + var processNames = matchingLines.Select(l => l.Substring(20).Trim('\'')).ToArray(); + return processNames.FirstOrDefault(); + } + + private Int32 GetUnixProcessIdByName(string executableName) + { + int closest = 0; + int early = 0; + var currentProcess = Process.GetCurrentProcess(); // Gets XIVLauncher.Core's process + bool nonunique = false; + foreach (var process in Process.GetProcessesByName(executableName)) + { + if (process.Id < currentProcess.Id) + { + early = process.Id; + continue; // Process was launched before XIVLauncher.Core + } + // Assume that the closest PID to XIVLauncher.Core's is the correct one. But log an error if more than one is found. + if ((closest - currentProcess.Id) > (process.Id - currentProcess.Id) || closest == 0) + { + if (closest != 0) nonunique = true; + closest = process.Id; + } + if (nonunique) Log.Error($"More than one {executableName} found! Selecting the most likely match with process id {closest}."); + } + // Deal with rare edge-case where pid rollover causes the ffxiv pid to be lower than XLCore's. + if (closest == 0 && early != 0) closest = early; + if (closest != 0) Log.Verbose($"Process for {executableName} found using fallback method: {closest}. XLCore pid: {currentProcess.Id}"); + return closest; + } + public string UnixToWinePath(string unixPath) { var launchArguments = new string[] { "winepath", "--windows", unixPath }; @@ -282,7 +353,7 @@ public void AddRegistryKey(string key, string value, string data) public void Kill() { - var psi = new ProcessStartInfo(WineServerPath) + var psi = new ProcessStartInfo(Settings.WineServerPath) { Arguments = "-k" }; diff --git a/src/XIVLauncher.Common.Unix/Compatibility/Dxvk.cs b/src/XIVLauncher.Common.Unix/Compatibility/Dxvk.cs deleted file mode 100644 index 62e3a303..00000000 --- a/src/XIVLauncher.Common.Unix/Compatibility/Dxvk.cs +++ /dev/null @@ -1,55 +0,0 @@ -using System.IO; -using System.Net.Http; -using System.Threading.Tasks; -using Serilog; -using XIVLauncher.Common.Util; - -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) - { - var dxvkPath = Path.Combine(installDirectory.FullName, DXVK_NAME, "x64"); - - if (!Directory.Exists(dxvkPath)) - { - Log.Information("DXVK does not exist, downloading"); - await DownloadDxvk(installDirectory).ConfigureAwait(false); - } - - var system32 = Path.Combine(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); - } - } - - private static async Task DownloadDxvk(DirectoryInfo installDirectory) - { - using var client = new HttpClient(); - var tempPath = PlatformHelpers.GetTempFileName(); - - File.WriteAllBytes(tempPath, await client.GetByteArrayAsync(DXVK_DOWNLOAD)); - PlatformHelpers.Untar(tempPath, installDirectory.FullName); - - File.Delete(tempPath); - } - - public enum DxvkHudType - { - [SettingsDescription("None", "Show nothing")] - None, - - [SettingsDescription("FPS", "Only show FPS")] - Fps, - - [SettingsDescription("Full", "Show everything")] - Full, - } -} \ 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..db657bbc --- /dev/null +++ b/src/XIVLauncher.Common.Unix/Compatibility/DxvkSettings.cs @@ -0,0 +1,87 @@ +using System.IO; +using System.Collections.Generic; +using System.Text.RegularExpressions; +using System.Linq; +using Serilog; + +namespace XIVLauncher.Common.Unix.Compatibility; + +public class DxvkSettings +{ + public bool Enabled { get; } + + public string FolderName { get; } + + public string DownloadUrl { 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) + { + FolderName = folder; + DownloadUrl = url; + Enabled = enabled; + + var dxvkConfigPath = new DirectoryInfo(Path.Combine(storageFolder, "compatibilitytool", "dxvk")); + Environment = new Dictionary + { + { "DXVK_LOG_PATH", Path.Combine(storageFolder, "logs") }, + { "DXVK_CONFIG_FILE", Path.Combine(dxvkConfigPath.FullName, "dxvk.conf") }, + }; + + if (maxFrameRate != 0) + Environment.Add("DXVK_FRAME_RATE", (maxFrameRate).ToString()); + + if (async) + Environment.Add("DXVK_ASYNC", "1"); + + var dxvkCachePath = new DirectoryInfo(Path.Combine(dxvkConfigPath.FullName, "cache")); + if (!dxvkCachePath.Exists) dxvkCachePath.Create(); + Environment.Add("DXVK_STATE_CACHE_PATH", Path.Combine(dxvkCachePath.FullName, folder)); + + if (dxvkHudEnabled) + Environment.Add("DXVK_HUD", DxvkHudStringIsValid(dxvkHudString) ? dxvkHudString : "1"); + + if (mangoHudEnabled && MangoHudIsInstalled()) + { + Environment.Add("MANGOHUD", "1"); + if (mangoHudCustomIsFile) + { + if (File.Exists(customMangoHud)) + Environment.Add("MANGOHUD_CONFIGFILE", customMangoHud); + else + Environment.Add("MANGOHUD_CONFIG", ""); + } + else + { + Environment.Add("MANGOHUD_CONFIG", customMangoHud); + } + } + } + + public static bool DxvkHudStringIsValid(string customHud) + { + var ALLOWED_CHARS = "^[0-9a-zA-Z,=.]+$"; + var ALLOWED_WORDS = "^(?:devinfo|fps|frametimes|submissions|drawcalls|pipelines|descriptors|memory|gpuload|version|api|cs|compiler|samplers|scale=(?:[0-9])*(?:.(?:[0-9])+)?)$"; + + if (string.IsNullOrWhiteSpace(customHud)) return false; + if (customHud == "full") return true; + 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)); + } + + public static bool MangoHudIsInstalled() + { + var usrLib = Path.Combine("/", "usr", "lib", "mangohud", "libMangoHud.so"); // fedora uses this + var usrLib64 = Path.Combine("/", "usr", "lib64", "mangohud", "libMangoHud.so"); // arch and openSUSE use this + var flatpak = Path.Combine("/", "usr", "lib", "extensions", "vulkan", "MangoHud", "lib", "x86_64-linux-gnu", "libMangoHud.so"); + var debuntu = Path.Combine("/", "usr", "lib", "x86_64-linux-gnu", "mangohud", "libMangoHud.so"); + if (File.Exists(usrLib64) || File.Exists(usrLib) || File.Exists(flatpak) || File.Exists(debuntu)) + return true; + return false; + } +} \ No newline at end of file diff --git a/src/XIVLauncher.Common.Unix/Compatibility/WineSettings.cs b/src/XIVLauncher.Common.Unix/Compatibility/WineSettings.cs index 9b122f7a..ed8d16df 100644 --- a/src/XIVLauncher.Common.Unix/Compatibility/WineSettings.cs +++ b/src/XIVLauncher.Common.Unix/Compatibility/WineSettings.cs @@ -2,32 +2,38 @@ namespace XIVLauncher.Common.Unix.Compatibility; -public enum WineStartupType +public class WineSettings { - [SettingsDescription("Managed by XIVLauncher", "The game installation and wine setup is managed by XIVLauncher - you can leave it up to us.")] - Managed, + public bool IsManaged { get; private set; } - [SettingsDescription("Custom", "Point XIVLauncher to a custom location containing wine binaries to run the game with.")] - Custom, -} + public string WineServerPath => Path.Combine(BinPath, "wineserver"); -public class WineSettings -{ - public WineStartupType StartupType { get; private set; } - public string CustomBinPath { get; private set; } + public string WinePath => File.Exists(Path.Combine(BinPath, "wine64")) ? Path.Combine(BinPath, "wine64") : Path.Combine(BinPath, "wine"); + + private string BinPath; + + public string FolderName; + + public string DownloadUrl; public string EsyncOn { get; private set; } + public string FsyncOn { get; private set; } public string DebugVars { get; private set; } + public FileInfo LogFile { get; private set; } public DirectoryInfo Prefix { get; private set; } - public WineSettings(WineStartupType? startupType, string customBinPath, string debugVars, FileInfo logFile, DirectoryInfo prefix, bool? esyncOn, bool? fsyncOn) + public WineSettings(bool isManaged, string customBinPath, string managedFolder, string managedUrl, DirectoryInfo storageFolder, string debugVars, FileInfo logFile, DirectoryInfo prefix, bool? esyncOn, bool? fsyncOn) { - this.StartupType = startupType ?? WineStartupType.Custom; - this.CustomBinPath = customBinPath; + // 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; + this.EsyncOn = (esyncOn ?? false) ? "1" : "0"; this.FsyncOn = (fsyncOn ?? false) ? "1" : "0"; this.DebugVars = debugVars; diff --git a/src/XIVLauncher.Common.Unix/UnixDalamudRunner.cs b/src/XIVLauncher.Common.Unix/UnixDalamudRunner.cs index ef1b8545..b2fde02c 100644 --- a/src/XIVLauncher.Common.Unix/UnixDalamudRunner.cs +++ b/src/XIVLauncher.Common.Unix/UnixDalamudRunner.cs @@ -74,12 +74,28 @@ public UnixDalamudRunner(CompatibilityTools compatibility, DirectoryInfo dotnetR launchArguments.Add(gameArgs); var dalamudProcess = compatibility.RunInPrefix(string.Join(" ", launchArguments), environment: environment, redirectOutput: true, writeLog: true); - var output = dalamudProcess.StandardOutput.ReadLine(); - if (output == null) - throw new DalamudRunnerException("An internal Dalamud error has occured"); + DalamudConsoleOutput dalamudConsoleOutput = null; + int invalidJsonCount = 0; - Console.WriteLine(output); + // Keep checking for valid json output, but only 5 times. If it's still erroring out at that point, give up. + while (dalamudConsoleOutput == null && invalidJsonCount < 5) + { + var output = dalamudProcess.StandardOutput.ReadLine(); + if (output == null) + throw new DalamudRunnerException("An internal Dalamud error has occured"); + Console.WriteLine(output); + + try + { + dalamudConsoleOutput = JsonConvert.DeserializeObject(output); + } + catch (Exception ex) + { + Log.Warning(ex, $"Couldn't parse Dalamud output: {output}"); + } + invalidJsonCount++; + } new Thread(() => { @@ -89,17 +105,15 @@ public UnixDalamudRunner(CompatibilityTools compatibility, DirectoryInfo dotnetR if (output != null) Console.WriteLine(output); } - }).Start(); try { - var dalamudConsoleOutput = JsonConvert.DeserializeObject(output); var unixPid = compatibility.GetUnixProcessId(dalamudConsoleOutput.Pid); if (unixPid == 0) { - Log.Error("Could not retrive Unix process ID, this feature currently requires a patched wine version"); + Log.Error("Could not retrieve Unix process ID"); return null; } @@ -107,9 +121,9 @@ public UnixDalamudRunner(CompatibilityTools compatibility, DirectoryInfo dotnetR Log.Verbose($"Got game process handle {gameProcess.Handle} with Unix pid {gameProcess.Id} and Wine pid {dalamudConsoleOutput.Pid}"); return gameProcess; } - catch (JsonReaderException ex) + catch (Exception ex) { - Log.Error(ex, $"Couldn't parse Dalamud output: {output}"); + Log.Error(ex, $"Could not retrieve game Process information"); return null; } }