Skip to content
This repository has been archived by the owner on Feb 16, 2024. It is now read-only.

Commit

Permalink
Merge pull request #5 from marzent/xlcommon2
Browse files Browse the repository at this point in the history
Adding XIVLauncher2.Common
  • Loading branch information
NotNite authored Jan 17, 2023
2 parents 477408f + 38e08c4 commit ccd4725
Show file tree
Hide file tree
Showing 172 changed files with 15,773 additions and 20 deletions.
281 changes: 281 additions & 0 deletions src/XIVLauncher2.Common.Unix/Compatibility/CompatibilityTools.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,281 @@
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 XIVLauncher2.Common.Util;

#if FLATPAK
#warning THIS IS A FLATPAK BUILD!!!
#endif

namespace XIVLauncher2.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/7.10.r3.g560db77d/wine-xiv-staging-fsync-git-arch-7.10.r3.g560db77d.tar.xz";
#elif WINE_XIV_FEDORA_LINUX
private const string WINE_XIV_RELEASE_URL = "https://github.com/goatcorp/wine-xiv-git/releases/download/7.10.r3.g560db77d/wine-xiv-staging-fsync-git-fedora-7.10.r3.g560db77d.tar.xz";
#else
private const string WINE_XIV_RELEASE_URL = "https://github.com/goatcorp/wine-xiv-git/releases/download/7.10.r3.g560db77d/wine-xiv-staging-fsync-git-ubuntu-7.10.r3.g560db77d.tar.xz";
#endif
private const string WINE_XIV_RELEASE_NAME = "wine-xiv-staging-fsync-git-7.10.r3.g560db77d";

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;

public DxvkSettings DxvkSettings { get; private set; }

private readonly bool gamemodeOn;

public CompatibilityTools(WineSettings wineSettings, DxvkSettings dxvkSettings, bool? gamemodeOn, DirectoryInfo toolsFolder)
{
this.Settings = wineSettings;
this.DxvkSettings = dxvkSettings;
this.gamemodeOn = gamemodeOn ?? false;
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();
}
}

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, DxvkSettings).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={(DxvkSettings.Enabled && !wineD3D ? "n" : "b")}");

if (!string.IsNullOrEmpty(Settings.DebugVars))
{
wineEnviromentVariables.Add("WINEDEBUG", Settings.DebugVars);
}

wineEnviromentVariables.Add("XL_WINEONLINUX", "true");
string ldPreload = Environment.GetEnvironmentVariable("LD_PRELOAD") ?? "";

if (gamemodeOn && !ldPreload.Contains("libgamemodeauto.so.0"))
{
ldPreload = ldPreload.Equals("") ? "libgamemodeauto.so.0" : ldPreload + ":libgamemodeauto.so.0";
}

foreach (KeyValuePair<string, string> dxvkVar in DxvkSettings.DxvkVars)
wineEnviromentVariables.Add(dxvkVar.Key, dxvkVar.Value);

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);
}
}
79 changes: 79 additions & 0 deletions src/XIVLauncher2.Common.Unix/Compatibility/Dxvk.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
using System.IO;
using System.Net.Http;
using System.Threading.Tasks;
using Serilog;
using XIVLauncher2.Common.Util;

namespace XIVLauncher2.Common.Unix.Compatibility;

public static class Dxvk
{
public static async Task InstallDxvk(DirectoryInfo prefix, DirectoryInfo installDirectory, DxvkSettings dxvkSettings)
{
var dxvkPath = Path.Combine(installDirectory.FullName, dxvkSettings.FolderName, "x64");

if (!Directory.Exists(dxvkPath))
{
Log.Information("DXVK does not exist, downloading");
await DownloadDxvk(installDirectory, dxvkSettings.DownloadURL).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, 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);
}

public enum DxvkHudType
{
[SettingsDescription("None", "Show nothing")]
None,

[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 used with XIVLauncher.Core 1.0.2. 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 (might break Dalamud, GShade)", "Newest version of DXVK. May be faster, but not stable yet.")]
v2_0,
}
}
Loading

0 comments on commit ccd4725

Please sign in to comment.