-
Notifications
You must be signed in to change notification settings - Fork 42
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #188 from rankynbass/move-xl-common-unix
Move XIVLauncher.Common.Unix to XIVLauncher.Core
- Loading branch information
Showing
14 changed files
with
757 additions
and
2 deletions.
There are no files selected for viewing
293 changes: 293 additions & 0 deletions
293
src/XIVLauncher.Common.Unix/Compatibility/CompatibilityTools.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,293 @@ | ||
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 XIVLauncher.Common.Util; | ||
|
||
#if FLATPAK | ||
#warning THIS IS A FLATPAK BUILD!!! | ||
#endif | ||
|
||
namespace XIVLauncher.Common.Unix.Compatibility; | ||
|
||
public class CompatibilityTools | ||
{ | ||
private DirectoryInfo toolDirectory; | ||
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 bool IsToolDownloaded => File.Exists(Wine64Path) && Settings.Prefix.Exists; | ||
|
||
private readonly Dxvk.DxvkHudType hudType; | ||
private readonly bool gamemodeOn; | ||
private readonly string dxvkAsyncOn; | ||
|
||
public CompatibilityTools(WineSettings wineSettings, Dxvk.DxvkHudType hudType, bool? gamemodeOn, bool? dxvkAsyncOn, DirectoryInfo toolsFolder) | ||
{ | ||
this.Settings = wineSettings; | ||
this.hudType = hudType; | ||
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")); | ||
|
||
this.logWriter = new StreamWriter(wineSettings.LogFile.FullName); | ||
|
||
if (wineSettings.StartupType == WineStartupType.Managed) | ||
{ | ||
if (!this.toolDirectory.Exists) | ||
this.toolDirectory.Create(); | ||
|
||
if (!this.dxvkDirectory.Exists) | ||
this.dxvkDirectory.Create(); | ||
} | ||
|
||
if (!wineSettings.Prefix.Exists) | ||
wineSettings.Prefix.Create(); | ||
} | ||
|
||
public async Task EnsureTool(DirectoryInfo tempPath) | ||
{ | ||
if (!File.Exists(Wine64Path)) | ||
{ | ||
Log.Information("Compatibility tool does not exist, downloading"); | ||
await DownloadTool(tempPath).ConfigureAwait(false); | ||
} | ||
|
||
EnsurePrefix(); | ||
await Dxvk.InstallDxvk(Settings.Prefix, dxvkDirectory).ConfigureAwait(false); | ||
|
||
IsToolReady = true; | ||
} | ||
|
||
private async Task DownloadTool(DirectoryInfo tempPath) | ||
{ | ||
using var client = new HttpClient(); | ||
var tempFilePath = Path.Combine(tempPath.FullName, $"{Guid.NewGuid()}"); | ||
|
||
await File.WriteAllBytesAsync(tempFilePath, await client.GetByteArrayAsync(WINE_XIV_RELEASE_URL).ConfigureAwait(false)).ConfigureAwait(false); | ||
|
||
PlatformHelpers.Untar(tempFilePath, this.toolDirectory.FullName); | ||
|
||
Log.Information("Compatibility tool successfully extracted to {Path}", this.toolDirectory.FullName); | ||
|
||
File.Delete(tempFilePath); | ||
} | ||
|
||
private void ResetPrefix() | ||
{ | ||
Settings.Prefix.Refresh(); | ||
|
||
if (Settings.Prefix.Exists) | ||
Settings.Prefix.Delete(true); | ||
|
||
Settings.Prefix.Create(); | ||
EnsurePrefix(); | ||
} | ||
|
||
public void EnsurePrefix() | ||
{ | ||
RunInPrefix("cmd /c dir %userprofile%/Documents > nul").WaitForExit(); | ||
} | ||
|
||
public Process RunInPrefix(string command, string workingDirectory = "", IDictionary<string, string> environment = null, bool redirectOutput = false, bool writeLog = false, bool wineD3D = false) | ||
{ | ||
var psi = new ProcessStartInfo(Wine64Path); | ||
psi.Arguments = command; | ||
|
||
Log.Verbose("Running in prefix: {FileName} {Arguments}", psi.FileName, command); | ||
return RunInPrefix(psi, workingDirectory, environment, redirectOutput, writeLog, wineD3D); | ||
} | ||
|
||
public Process RunInPrefix(string[] args, string workingDirectory = "", IDictionary<string, string> environment = null, bool redirectOutput = false, bool writeLog = false, bool wineD3D = false) | ||
{ | ||
var psi = new ProcessStartInfo(Wine64Path); | ||
foreach (var arg in args) | ||
psi.ArgumentList.Add(arg); | ||
|
||
Log.Verbose("Running in prefix: {FileName} {Arguments}", psi.FileName, psi.ArgumentList.Aggregate(string.Empty, (a, b) => a + " " + b)); | ||
return RunInPrefix(psi, workingDirectory, environment, redirectOutput, writeLog, wineD3D); | ||
} | ||
|
||
private void MergeDictionaries(StringDictionary a, IDictionary<string, string> b) | ||
{ | ||
if (b is null) | ||
return; | ||
|
||
foreach (var keyValuePair in b) | ||
{ | ||
if (a.ContainsKey(keyValuePair.Key)) | ||
a[keyValuePair.Key] = keyValuePair.Value; | ||
else | ||
a.Add(keyValuePair.Key, keyValuePair.Value); | ||
} | ||
} | ||
|
||
private Process RunInPrefix(ProcessStartInfo psi, string workingDirectory, IDictionary<string, string> environment, bool redirectOutput, bool writeLog, bool wineD3D) | ||
{ | ||
psi.RedirectStandardOutput = redirectOutput; | ||
psi.RedirectStandardError = writeLog; | ||
psi.UseShellExecute = false; | ||
psi.WorkingDirectory = workingDirectory; | ||
|
||
var wineEnviromentVariables = new Dictionary<string, string>(); | ||
wineEnviromentVariables.Add("WINEPREFIX", Settings.Prefix.FullName); | ||
wineEnviromentVariables.Add("WINEDLLOVERRIDES", $"msquic=,mscoree=n,b;d3d9,d3d11,d3d10core,dxgi={(wineD3D ? "b" : "n")}"); | ||
|
||
if (!string.IsNullOrEmpty(Settings.DebugVars)) | ||
{ | ||
wineEnviromentVariables.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"; | ||
} | ||
|
||
wineEnviromentVariables.Add("DXVK_HUD", dxvkHud); | ||
wineEnviromentVariables.Add("DXVK_ASYNC", dxvkAsyncOn); | ||
wineEnviromentVariables.Add("WINEESYNC", Settings.EsyncOn); | ||
wineEnviromentVariables.Add("WINEFSYNC", Settings.FsyncOn); | ||
|
||
wineEnviromentVariables.Add("LD_PRELOAD", ldPreload); | ||
|
||
MergeDictionaries(psi.EnvironmentVariables, wineEnviromentVariables); | ||
MergeDictionaries(psi.EnvironmentVariables, environment); | ||
|
||
#if FLATPAK_NOTRIGHTNOW | ||
psi.FileName = "flatpak-spawn"; | ||
|
||
psi.ArgumentList.Insert(0, "--host"); | ||
psi.ArgumentList.Insert(1, Wine64Path); | ||
|
||
foreach (KeyValuePair<string, string> envVar in wineEnviromentVariables) | ||
{ | ||
psi.ArgumentList.Insert(1, $"--env={envVar.Key}={envVar.Value}"); | ||
} | ||
|
||
if (environment != null) | ||
{ | ||
foreach (KeyValuePair<string, string> envVar in environment) | ||
{ | ||
psi.ArgumentList.Insert(1, $"--env=\"{envVar.Key}\"=\"{envVar.Value}\""); | ||
} | ||
} | ||
#endif | ||
|
||
Process helperProcess = new(); | ||
helperProcess.StartInfo = psi; | ||
helperProcess.ErrorDataReceived += new DataReceivedEventHandler((_, errLine) => | ||
{ | ||
if (String.IsNullOrEmpty(errLine.Data)) | ||
return; | ||
|
||
try | ||
{ | ||
logWriter.WriteLine(errLine.Data); | ||
Console.Error.WriteLine(errLine.Data); | ||
} | ||
catch (Exception ex) when (ex is ArgumentOutOfRangeException || | ||
ex is OverflowException || | ||
ex is IndexOutOfRangeException) | ||
{ | ||
// very long wine log lines get chopped off after a (seemingly) arbitrary limit resulting in strings that are not null terminated | ||
//logWriter.WriteLine("Error writing Wine log line:"); | ||
//logWriter.WriteLine(ex.Message); | ||
} | ||
}); | ||
|
||
helperProcess.Start(); | ||
if (writeLog) | ||
helperProcess.BeginErrorReadLine(); | ||
|
||
return helperProcess; | ||
} | ||
|
||
public Int32[] GetProcessIds(string executableName) | ||
{ | ||
var wineDbg = RunInPrefix("winedbg --command \"info proc\"", redirectOutput: true); | ||
var output = wineDbg.StandardOutput.ReadToEnd(); | ||
var matchingLines = output.Split('\n', StringSplitOptions.RemoveEmptyEntries).Where(l => l.Contains(executableName)); | ||
return matchingLines.Select(l => int.Parse(l.Substring(1, 8), System.Globalization.NumberStyles.HexNumber)).ToArray(); | ||
} | ||
|
||
public Int32 GetProcessId(string executableName) | ||
{ | ||
return GetProcessIds(executableName).FirstOrDefault(); | ||
} | ||
|
||
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; | ||
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(); | ||
} | ||
|
||
public string UnixToWinePath(string unixPath) | ||
{ | ||
var launchArguments = new string[] { "winepath", "--windows", unixPath }; | ||
var winePath = RunInPrefix(launchArguments, redirectOutput: true); | ||
var output = winePath.StandardOutput.ReadToEnd(); | ||
return output.Split('\n', StringSplitOptions.RemoveEmptyEntries).LastOrDefault(); | ||
} | ||
|
||
public void AddRegistryKey(string key, string value, string data) | ||
{ | ||
var args = new string[] { "reg", "add", key, "/v", value, "/d", data, "/f" }; | ||
var wineProcess = RunInPrefix(args); | ||
wineProcess.WaitForExit(); | ||
} | ||
|
||
public void Kill() | ||
{ | ||
var psi = new ProcessStartInfo(WineServerPath) | ||
{ | ||
Arguments = "-k" | ||
}; | ||
psi.EnvironmentVariables.Add("WINEPREFIX", Settings.Prefix.FullName); | ||
|
||
Process.Start(psi); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,55 @@ | ||
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, | ||
} | ||
} |
28 changes: 28 additions & 0 deletions
28
src/XIVLauncher.Common.Unix/Compatibility/GameFixes/GameFix.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
using System.IO; | ||
|
||
namespace XIVLauncher.Common.Unix.Compatibility.GameFixes; | ||
|
||
public abstract class GameFix | ||
{ | ||
public GameFix(DirectoryInfo gameDirectory, DirectoryInfo configDirectory, DirectoryInfo winePrefixDirectory, DirectoryInfo tempDirectory) | ||
{ | ||
GameDir = gameDirectory; | ||
ConfigDir = configDirectory; | ||
WinePrefixDir = winePrefixDirectory; | ||
TempDir = tempDirectory; | ||
} | ||
|
||
public abstract string LoadingTitle { get; } | ||
|
||
public GameFixApply.UpdateProgressDelegate UpdateProgress; | ||
|
||
public DirectoryInfo WinePrefixDir { get; private set; } | ||
|
||
public DirectoryInfo ConfigDir { get; private set; } | ||
|
||
public DirectoryInfo GameDir { get; private set; } | ||
|
||
public DirectoryInfo TempDir { get; private set; } | ||
|
||
public abstract void Apply(); | ||
} |
Oops, something went wrong.