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

Commit ccd4725

Browse files
authored
Merge pull request #5 from marzent/xlcommon2
Adding XIVLauncher2.Common
2 parents 477408f + 38e08c4 commit ccd4725

File tree

172 files changed

+15773
-20
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

172 files changed

+15773
-20
lines changed
Lines changed: 281 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,281 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Collections.Specialized;
4+
using System.Diagnostics;
5+
using System.IO;
6+
using System.Linq;
7+
using System.Net.Http;
8+
using System.Threading.Tasks;
9+
using Serilog;
10+
using XIVLauncher2.Common.Util;
11+
12+
#if FLATPAK
13+
#warning THIS IS A FLATPAK BUILD!!!
14+
#endif
15+
16+
namespace XIVLauncher2.Common.Unix.Compatibility;
17+
18+
public class CompatibilityTools
19+
{
20+
private DirectoryInfo toolDirectory;
21+
private DirectoryInfo dxvkDirectory;
22+
23+
private StreamWriter logWriter;
24+
25+
#if WINE_XIV_ARCH_LINUX
26+
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";
27+
#elif WINE_XIV_FEDORA_LINUX
28+
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";
29+
#else
30+
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";
31+
#endif
32+
private const string WINE_XIV_RELEASE_NAME = "wine-xiv-staging-fsync-git-7.10.r3.g560db77d";
33+
34+
public bool IsToolReady { get; private set; }
35+
36+
public WineSettings Settings { get; private set; }
37+
38+
private string WineBinPath => Settings.StartupType == WineStartupType.Managed ?
39+
Path.Combine(toolDirectory.FullName, WINE_XIV_RELEASE_NAME, "bin") :
40+
Settings.CustomBinPath;
41+
private string Wine64Path => Path.Combine(WineBinPath, "wine64");
42+
private string WineServerPath => Path.Combine(WineBinPath, "wineserver");
43+
44+
public bool IsToolDownloaded => File.Exists(Wine64Path) && Settings.Prefix.Exists;
45+
46+
public DxvkSettings DxvkSettings { get; private set; }
47+
48+
private readonly bool gamemodeOn;
49+
50+
public CompatibilityTools(WineSettings wineSettings, DxvkSettings dxvkSettings, bool? gamemodeOn, DirectoryInfo toolsFolder)
51+
{
52+
this.Settings = wineSettings;
53+
this.DxvkSettings = dxvkSettings;
54+
this.gamemodeOn = gamemodeOn ?? false;
55+
this.toolDirectory = new DirectoryInfo(Path.Combine(toolsFolder.FullName, "beta"));
56+
this.dxvkDirectory = new DirectoryInfo(Path.Combine(toolsFolder.FullName, "dxvk"));
57+
58+
this.logWriter = new StreamWriter(wineSettings.LogFile.FullName);
59+
60+
if (wineSettings.StartupType == WineStartupType.Managed)
61+
{
62+
if (!this.toolDirectory.Exists)
63+
this.toolDirectory.Create();
64+
65+
if (!this.dxvkDirectory.Exists)
66+
this.dxvkDirectory.Create();
67+
}
68+
}
69+
70+
public async Task EnsureTool(DirectoryInfo tempPath)
71+
{
72+
if (!File.Exists(Wine64Path))
73+
{
74+
Log.Information("Compatibility tool does not exist, downloading");
75+
await DownloadTool(tempPath).ConfigureAwait(false);
76+
}
77+
78+
EnsurePrefix();
79+
await Dxvk.InstallDxvk(Settings.Prefix, dxvkDirectory, DxvkSettings).ConfigureAwait(false);
80+
81+
IsToolReady = true;
82+
}
83+
84+
private async Task DownloadTool(DirectoryInfo tempPath)
85+
{
86+
using var client = new HttpClient();
87+
var tempFilePath = Path.Combine(tempPath.FullName, $"{Guid.NewGuid()}");
88+
89+
await File.WriteAllBytesAsync(tempFilePath, await client.GetByteArrayAsync(WINE_XIV_RELEASE_URL).ConfigureAwait(false)).ConfigureAwait(false);
90+
91+
PlatformHelpers.Untar(tempFilePath, this.toolDirectory.FullName);
92+
93+
Log.Information("Compatibility tool successfully extracted to {Path}", this.toolDirectory.FullName);
94+
95+
File.Delete(tempFilePath);
96+
}
97+
98+
private void ResetPrefix()
99+
{
100+
Settings.Prefix.Refresh();
101+
102+
if (Settings.Prefix.Exists)
103+
Settings.Prefix.Delete(true);
104+
105+
Settings.Prefix.Create();
106+
EnsurePrefix();
107+
}
108+
109+
public void EnsurePrefix()
110+
{
111+
RunInPrefix("cmd /c dir %userprofile%/Documents > nul").WaitForExit();
112+
}
113+
114+
public Process RunInPrefix(string command, string workingDirectory = "", IDictionary<string, string> environment = null, bool redirectOutput = false, bool writeLog = false, bool wineD3D = false)
115+
{
116+
var psi = new ProcessStartInfo(Wine64Path);
117+
psi.Arguments = command;
118+
119+
Log.Verbose("Running in prefix: {FileName} {Arguments}", psi.FileName, command);
120+
return RunInPrefix(psi, workingDirectory, environment, redirectOutput, writeLog, wineD3D);
121+
}
122+
123+
public Process RunInPrefix(string[] args, string workingDirectory = "", IDictionary<string, string> environment = null, bool redirectOutput = false, bool writeLog = false, bool wineD3D = false)
124+
{
125+
var psi = new ProcessStartInfo(Wine64Path);
126+
foreach (var arg in args)
127+
psi.ArgumentList.Add(arg);
128+
129+
Log.Verbose("Running in prefix: {FileName} {Arguments}", psi.FileName, psi.ArgumentList.Aggregate(string.Empty, (a, b) => a + " " + b));
130+
return RunInPrefix(psi, workingDirectory, environment, redirectOutput, writeLog, wineD3D);
131+
}
132+
133+
private void MergeDictionaries(StringDictionary a, IDictionary<string, string> b)
134+
{
135+
if (b is null)
136+
return;
137+
138+
foreach (var keyValuePair in b)
139+
{
140+
if (a.ContainsKey(keyValuePair.Key))
141+
a[keyValuePair.Key] = keyValuePair.Value;
142+
else
143+
a.Add(keyValuePair.Key, keyValuePair.Value);
144+
}
145+
}
146+
147+
private Process RunInPrefix(ProcessStartInfo psi, string workingDirectory, IDictionary<string, string> environment, bool redirectOutput, bool writeLog, bool wineD3D)
148+
{
149+
psi.RedirectStandardOutput = redirectOutput;
150+
psi.RedirectStandardError = writeLog;
151+
psi.UseShellExecute = false;
152+
psi.WorkingDirectory = workingDirectory;
153+
154+
var wineEnviromentVariables = new Dictionary<string, string>();
155+
wineEnviromentVariables.Add("WINEPREFIX", Settings.Prefix.FullName);
156+
wineEnviromentVariables.Add("WINEDLLOVERRIDES", $"msquic=,mscoree=n,b;d3d9,d3d11,d3d10core,dxgi={(DxvkSettings.Enabled && !wineD3D ? "n" : "b")}");
157+
158+
if (!string.IsNullOrEmpty(Settings.DebugVars))
159+
{
160+
wineEnviromentVariables.Add("WINEDEBUG", Settings.DebugVars);
161+
}
162+
163+
wineEnviromentVariables.Add("XL_WINEONLINUX", "true");
164+
string ldPreload = Environment.GetEnvironmentVariable("LD_PRELOAD") ?? "";
165+
166+
if (gamemodeOn && !ldPreload.Contains("libgamemodeauto.so.0"))
167+
{
168+
ldPreload = ldPreload.Equals("") ? "libgamemodeauto.so.0" : ldPreload + ":libgamemodeauto.so.0";
169+
}
170+
171+
foreach (KeyValuePair<string, string> dxvkVar in DxvkSettings.DxvkVars)
172+
wineEnviromentVariables.Add(dxvkVar.Key, dxvkVar.Value);
173+
174+
wineEnviromentVariables.Add("WINEESYNC", Settings.EsyncOn);
175+
wineEnviromentVariables.Add("WINEFSYNC", Settings.FsyncOn);
176+
177+
wineEnviromentVariables.Add("LD_PRELOAD", ldPreload);
178+
179+
MergeDictionaries(psi.EnvironmentVariables, wineEnviromentVariables);
180+
MergeDictionaries(psi.EnvironmentVariables, environment);
181+
182+
#if FLATPAK_NOTRIGHTNOW
183+
psi.FileName = "flatpak-spawn";
184+
185+
psi.ArgumentList.Insert(0, "--host");
186+
psi.ArgumentList.Insert(1, Wine64Path);
187+
188+
foreach (KeyValuePair<string, string> envVar in wineEnviromentVariables)
189+
{
190+
psi.ArgumentList.Insert(1, $"--env={envVar.Key}={envVar.Value}");
191+
}
192+
193+
if (environment != null)
194+
{
195+
foreach (KeyValuePair<string, string> envVar in environment)
196+
{
197+
psi.ArgumentList.Insert(1, $"--env=\"{envVar.Key}\"=\"{envVar.Value}\"");
198+
}
199+
}
200+
#endif
201+
202+
Process helperProcess = new();
203+
helperProcess.StartInfo = psi;
204+
helperProcess.ErrorDataReceived += new DataReceivedEventHandler((_, errLine) =>
205+
{
206+
if (String.IsNullOrEmpty(errLine.Data))
207+
return;
208+
209+
try
210+
{
211+
logWriter.WriteLine(errLine.Data);
212+
Console.Error.WriteLine(errLine.Data);
213+
}
214+
catch (Exception ex) when (ex is ArgumentOutOfRangeException ||
215+
ex is OverflowException ||
216+
ex is IndexOutOfRangeException)
217+
{
218+
// very long wine log lines get chopped off after a (seemingly) arbitrary limit resulting in strings that are not null terminated
219+
//logWriter.WriteLine("Error writing Wine log line:");
220+
//logWriter.WriteLine(ex.Message);
221+
}
222+
});
223+
224+
helperProcess.Start();
225+
if (writeLog)
226+
helperProcess.BeginErrorReadLine();
227+
228+
return helperProcess;
229+
}
230+
231+
public Int32[] GetProcessIds(string executableName)
232+
{
233+
var wineDbg = RunInPrefix("winedbg --command \"info proc\"", redirectOutput: true);
234+
var output = wineDbg.StandardOutput.ReadToEnd();
235+
var matchingLines = output.Split('\n', StringSplitOptions.RemoveEmptyEntries).Where(l => l.Contains(executableName));
236+
return matchingLines.Select(l => int.Parse(l.Substring(1, 8), System.Globalization.NumberStyles.HexNumber)).ToArray();
237+
}
238+
239+
public Int32 GetProcessId(string executableName)
240+
{
241+
return GetProcessIds(executableName).FirstOrDefault();
242+
}
243+
244+
public Int32 GetUnixProcessId(Int32 winePid)
245+
{
246+
var wineDbg = RunInPrefix("winedbg --command \"info procmap\"", redirectOutput: true);
247+
var output = wineDbg.StandardOutput.ReadToEnd();
248+
if (output.Contains("syntax error\n"))
249+
return 0;
250+
var matchingLines = output.Split('\n', StringSplitOptions.RemoveEmptyEntries).Skip(1).Where(
251+
l => int.Parse(l.Substring(1, 8), System.Globalization.NumberStyles.HexNumber) == winePid);
252+
var unixPids = matchingLines.Select(l => int.Parse(l.Substring(10, 8), System.Globalization.NumberStyles.HexNumber)).ToArray();
253+
return unixPids.FirstOrDefault();
254+
}
255+
256+
public string UnixToWinePath(string unixPath)
257+
{
258+
var launchArguments = new string[] { "winepath", "--windows", unixPath };
259+
var winePath = RunInPrefix(launchArguments, redirectOutput: true);
260+
var output = winePath.StandardOutput.ReadToEnd();
261+
return output.Split('\n', StringSplitOptions.RemoveEmptyEntries).LastOrDefault();
262+
}
263+
264+
public void AddRegistryKey(string key, string value, string data)
265+
{
266+
var args = new string[] { "reg", "add", key, "/v", value, "/d", data, "/f" };
267+
var wineProcess = RunInPrefix(args);
268+
wineProcess.WaitForExit();
269+
}
270+
271+
public void Kill()
272+
{
273+
var psi = new ProcessStartInfo(WineServerPath)
274+
{
275+
Arguments = "-k"
276+
};
277+
psi.EnvironmentVariables.Add("WINEPREFIX", Settings.Prefix.FullName);
278+
279+
Process.Start(psi);
280+
}
281+
}
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
using System.IO;
2+
using System.Net.Http;
3+
using System.Threading.Tasks;
4+
using Serilog;
5+
using XIVLauncher2.Common.Util;
6+
7+
namespace XIVLauncher2.Common.Unix.Compatibility;
8+
9+
public static class Dxvk
10+
{
11+
public static async Task InstallDxvk(DirectoryInfo prefix, DirectoryInfo installDirectory, DxvkSettings dxvkSettings)
12+
{
13+
var dxvkPath = Path.Combine(installDirectory.FullName, dxvkSettings.FolderName, "x64");
14+
15+
if (!Directory.Exists(dxvkPath))
16+
{
17+
Log.Information("DXVK does not exist, downloading");
18+
await DownloadDxvk(installDirectory, dxvkSettings.DownloadURL).ConfigureAwait(false);
19+
}
20+
21+
var system32 = Path.Combine(prefix.FullName, "drive_c", "windows", "system32");
22+
var files = Directory.GetFiles(dxvkPath);
23+
24+
foreach (string fileName in files)
25+
{
26+
File.Copy(fileName, Path.Combine(system32, Path.GetFileName(fileName)), true);
27+
}
28+
}
29+
30+
private static async Task DownloadDxvk(DirectoryInfo installDirectory, string downloadURL)
31+
{
32+
using var client = new HttpClient();
33+
var tempPath = Path.GetTempFileName();
34+
35+
File.WriteAllBytes(tempPath, await client.GetByteArrayAsync(downloadURL));
36+
PlatformHelpers.Untar(tempPath, installDirectory.FullName);
37+
38+
File.Delete(tempPath);
39+
}
40+
41+
public enum DxvkHudType
42+
{
43+
[SettingsDescription("None", "Show nothing")]
44+
None,
45+
46+
[SettingsDescription("FPS", "Only show FPS")]
47+
Fps,
48+
49+
[SettingsDescription("DXVK Hud Custom", "Use a custom DXVK_HUD string")]
50+
Custom,
51+
52+
[SettingsDescription("Full", "Show everything")]
53+
Full,
54+
55+
[SettingsDescription("MangoHud Default", "Uses no config file.")]
56+
MangoHud,
57+
58+
[SettingsDescription("MangoHud Custom", "Specify a custom config file")]
59+
MangoHudCustom,
60+
61+
[SettingsDescription("MangoHud Full", "Show (almost) everything")]
62+
MangoHudFull,
63+
}
64+
65+
public enum DxvkVersion
66+
{
67+
[SettingsDescription("1.10.1", "The version of DXVK used with XIVLauncher.Core 1.0.2. Safe to use.")]
68+
v1_10_1,
69+
70+
[SettingsDescription("1.10.2", "Older version of 1.10 branch of DXVK. Safe to use.")]
71+
v1_10_2,
72+
73+
[SettingsDescription("1.10.3 (default)", "Current version of 1.10 branch of DXVK.")]
74+
v1_10_3,
75+
76+
[SettingsDescription("2.0 (might break Dalamud, GShade)", "Newest version of DXVK. May be faster, but not stable yet.")]
77+
v2_0,
78+
}
79+
}

0 commit comments

Comments
 (0)