Implement dalamud-platform launch argument (#1452)

* implement dalamud platform launch arg
* implement cross-platform gamePath fallback
* refactor platform detection heuristic
* add cross platform dalamud runtime detection
This commit is contained in:
marzent 2025-03-24 16:42:24 +01:00 committed by GitHub
parent 9815cf1d88
commit 7cac19ce81
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
11 changed files with 299 additions and 101 deletions

View file

@ -109,6 +109,7 @@ void from_json(const nlohmann::json& json, DalamudStartInfo& config) {
config.PluginDirectory = json.value("PluginDirectory", config.PluginDirectory); config.PluginDirectory = json.value("PluginDirectory", config.PluginDirectory);
config.AssetDirectory = json.value("AssetDirectory", config.AssetDirectory); config.AssetDirectory = json.value("AssetDirectory", config.AssetDirectory);
config.Language = json.value("Language", config.Language); config.Language = json.value("Language", config.Language);
config.Platform = json.value("Platform", config.Platform);
config.GameVersion = json.value("GameVersion", config.GameVersion); config.GameVersion = json.value("GameVersion", config.GameVersion);
config.TroubleshootingPackData = json.value("TroubleshootingPackData", std::string{}); config.TroubleshootingPackData = json.value("TroubleshootingPackData", std::string{});
config.DelayInitializeMs = json.value("DelayInitializeMs", config.DelayInitializeMs); config.DelayInitializeMs = json.value("DelayInitializeMs", config.DelayInitializeMs);

View file

@ -47,6 +47,7 @@ struct DalamudStartInfo {
std::string PluginDirectory; std::string PluginDirectory;
std::string AssetDirectory; std::string AssetDirectory;
ClientLanguage Language = ClientLanguage::English; ClientLanguage Language = ClientLanguage::English;
std::string Platform;
std::string GameVersion; std::string GameVersion;
std::string TroubleshootingPackData; std::string TroubleshootingPackData;
int DelayInitializeMs = 0; int DelayInitializeMs = 0;

View file

@ -1,4 +1,5 @@
#include "pch.h" #include "pch.h"
#include "DalamudStartInfo.h"
#include "utils.h" #include "utils.h"
@ -584,6 +585,10 @@ std::vector<std::string> utils::get_env_list(const wchar_t* pcszName) {
return res; return res;
} }
bool utils::is_running_on_wine() {
return g_startInfo.Platform != "WINDOWS";
}
std::filesystem::path utils::get_module_path(HMODULE hModule) { std::filesystem::path utils::get_module_path(HMODULE hModule) {
std::wstring buf(MAX_PATH, L'\0'); std::wstring buf(MAX_PATH, L'\0');
while (true) { while (true) {

View file

@ -1,3 +1,4 @@
using System.Runtime.InteropServices;
using Dalamud.Common.Game; using Dalamud.Common.Game;
using Newtonsoft.Json; using Newtonsoft.Json;
@ -15,7 +16,7 @@ public record DalamudStartInfo
/// </summary> /// </summary>
public DalamudStartInfo() public DalamudStartInfo()
{ {
// ignored this.Platform = OSPlatform.Create("UNKNOWN");
} }
/// <summary> /// <summary>
@ -58,6 +59,12 @@ public record DalamudStartInfo
/// </summary> /// </summary>
public ClientLanguage Language { get; set; } = ClientLanguage.English; public ClientLanguage Language { get; set; } = ClientLanguage.English;
/// <summary>
/// Gets or sets the underlying platform<72>Dalamud runs on.
/// </summary>
[JsonConverter(typeof(OSPlatformConverter))]
public OSPlatform Platform { get; set; }
/// <summary> /// <summary>
/// Gets or sets the current game version code. /// Gets or sets the current game version code.
/// </summary> /// </summary>

View file

@ -0,0 +1,78 @@
using System.Runtime.InteropServices;
using Newtonsoft.Json;
namespace Dalamud.Common;
/// <summary>
/// Converts a <see cref="OSPlatform"/> to and from a string (e.g. <c>"FreeBSD"</c>).
/// </summary>
public sealed class OSPlatformConverter : JsonConverter
{
/// <summary>
/// Writes the JSON representation of the object.
/// </summary>
/// <param name="writer">The <see cref="JsonWriter"/> to write to.</param>
/// <param name="value">The value.</param>
/// <param name="serializer">The calling serializer.</param>
public override void WriteJson(JsonWriter writer, object? value, JsonSerializer serializer)
{
if (value == null)
{
writer.WriteNull();
}
else if (value is OSPlatform)
{
writer.WriteValue(value.ToString());
}
else
{
throw new JsonSerializationException("Expected OSPlatform object value");
}
}
/// <summary>
/// Reads the JSON representation of the object.
/// </summary>
/// <param name="reader">The <see cref="JsonReader"/> to read from.</param>
/// <param name="objectType">Type of the object.</param>
/// <param name="existingValue">The existing property value of the JSON that is being converted.</param>
/// <param name="serializer">The calling serializer.</param>
/// <returns>The object value.</returns>
public override object? ReadJson(JsonReader reader, Type objectType, object? existingValue, JsonSerializer serializer)
{
if (reader.TokenType == JsonToken.Null)
{
return null;
}
else
{
if (reader.TokenType == JsonToken.String)
{
try
{
return OSPlatform.Create((string)reader.Value!);
}
catch (Exception ex)
{
throw new JsonSerializationException($"Error parsing OSPlatform string: {reader.Value}", ex);
}
}
else
{
throw new JsonSerializationException($"Unexpected token or value when parsing OSPlatform. Token: {reader.TokenType}, Value: {reader.Value}");
}
}
}
/// <summary>
/// Determines whether this instance can convert the specified object type.
/// </summary>
/// <param name="objectType">Type of the object.</param>
/// <returns>
/// <c>true</c> if this instance can convert the specified object type; otherwise, <c>false</c>.
/// </returns>
public override bool CanConvert(Type objectType)
{
return objectType == typeof(OSPlatform);
}
}

View file

@ -263,6 +263,35 @@ namespace Dalamud.Injector
} }
} }
private static OSPlatform DetectPlatformHeuristic()
{
var ntdll = NativeFunctions.GetModuleHandleW("ntdll.dll");
var wineServerCallPtr = NativeFunctions.GetProcAddress(ntdll, "wine_server_call");
var wineGetHostVersionPtr = NativeFunctions.GetProcAddress(ntdll, "wine_get_host_version");
var winePlatform = GetWinePlatform(wineGetHostVersionPtr);
var isWine = wineServerCallPtr != nint.Zero;
static unsafe string? GetWinePlatform(nint wineGetHostVersionPtr)
{
if (wineGetHostVersionPtr == nint.Zero) return null;
var methodDelegate = (delegate* unmanaged[Fastcall]<out char*, out char*, void>)wineGetHostVersionPtr;
methodDelegate(out var platformPtr, out var _);
if (platformPtr == null) return null;
return Marshal.PtrToStringAnsi((nint)platformPtr);
}
if (!isWine)
return OSPlatform.Windows;
if (winePlatform == "Darwin")
return OSPlatform.OSX;
return OSPlatform.Linux;
}
private static DalamudStartInfo ExtractAndInitializeStartInfoFromArguments(DalamudStartInfo? startInfo, List<string> args) private static DalamudStartInfo ExtractAndInitializeStartInfoFromArguments(DalamudStartInfo? startInfo, List<string> args)
{ {
int len; int len;
@ -278,6 +307,7 @@ namespace Dalamud.Injector
var logName = startInfo.LogName; var logName = startInfo.LogName;
var logPath = startInfo.LogPath; var logPath = startInfo.LogPath;
var languageStr = startInfo.Language.ToString().ToLowerInvariant(); var languageStr = startInfo.Language.ToString().ToLowerInvariant();
var platformStr = startInfo.Platform.ToString().ToLowerInvariant();
var unhandledExceptionStr = startInfo.UnhandledException.ToString().ToLowerInvariant(); var unhandledExceptionStr = startInfo.UnhandledException.ToString().ToLowerInvariant();
var troubleshootingData = "{\"empty\": true, \"description\": \"No troubleshooting data supplied.\"}"; var troubleshootingData = "{\"empty\": true, \"description\": \"No troubleshooting data supplied.\"}";
@ -307,6 +337,10 @@ namespace Dalamud.Injector
{ {
languageStr = args[i][key.Length..].ToLowerInvariant(); languageStr = args[i][key.Length..].ToLowerInvariant();
} }
else if (args[i].StartsWith(key = "--dalamud-platform="))
{
platformStr = args[i][key.Length..].ToLowerInvariant();
}
else if (args[i].StartsWith(key = "--dalamud-tspack-b64=")) else if (args[i].StartsWith(key = "--dalamud-tspack-b64="))
{ {
troubleshootingData = Encoding.UTF8.GetString(Convert.FromBase64String(args[i][key.Length..])); troubleshootingData = Encoding.UTF8.GetString(Convert.FromBase64String(args[i][key.Length..]));
@ -378,11 +412,35 @@ namespace Dalamud.Injector
throw new CommandLineException($"\"{languageStr}\" is not a valid supported language."); throw new CommandLineException($"\"{languageStr}\" is not a valid supported language.");
} }
OSPlatform platform;
if (platformStr[0..(len = Math.Min(platformStr.Length, (key = "win").Length))] == key[0..len]) // covers both win32 and Windows
{
platform = OSPlatform.Windows;
}
else if (platformStr[0..(len = Math.Min(platformStr.Length, (key = "linux").Length))] == key[0..len])
{
platform = OSPlatform.Linux;
}
else if (platformStr[0..(len = Math.Min(platformStr.Length, (key = "macos").Length))] == key[0..len])
{
platform = OSPlatform.OSX;
}
else if (platformStr[0..(len = Math.Min(platformStr.Length, (key = "osx").Length))] == key[0..len])
{
platform = OSPlatform.OSX;
}
else
{
platform = DetectPlatformHeuristic();
Log.Warning("Heuristically determined host system platform as {platform}", platform);
}
startInfo.WorkingDirectory = workingDirectory; startInfo.WorkingDirectory = workingDirectory;
startInfo.ConfigurationPath = configurationPath; startInfo.ConfigurationPath = configurationPath;
startInfo.PluginDirectory = pluginDirectory; startInfo.PluginDirectory = pluginDirectory;
startInfo.AssetDirectory = assetDirectory; startInfo.AssetDirectory = assetDirectory;
startInfo.Language = clientLanguage; startInfo.Language = clientLanguage;
startInfo.Platform = platform;
startInfo.DelayInitializeMs = delayInitializeMs; startInfo.DelayInitializeMs = delayInitializeMs;
startInfo.GameVersion = null; startInfo.GameVersion = null;
startInfo.TroubleshootingPackData = troubleshootingData; startInfo.TroubleshootingPackData = troubleshootingData;
@ -465,7 +523,7 @@ namespace Dalamud.Injector
} }
Console.WriteLine("Specifying dalamud start info: [--dalamud-working-directory=path] [--dalamud-configuration-path=path]"); Console.WriteLine("Specifying dalamud start info: [--dalamud-working-directory=path] [--dalamud-configuration-path=path]");
Console.WriteLine(" [--dalamud-plugin-directory=path]"); Console.WriteLine(" [--dalamud-plugin-directory=path] [--dalamud-platform=win32|linux|macOS]");
Console.WriteLine(" [--dalamud-asset-directory=path] [--dalamud-delay-initialize=0(ms)]"); Console.WriteLine(" [--dalamud-asset-directory=path] [--dalamud-delay-initialize=0(ms)]");
Console.WriteLine(" [--dalamud-client-language=0-3|j(apanese)|e(nglish)|d|g(erman)|f(rench)]"); Console.WriteLine(" [--dalamud-client-language=0-3|j(apanese)|e(nglish)|d|g(erman)|f(rench)]");
@ -732,15 +790,42 @@ namespace Dalamud.Injector
{ {
try try
{ {
var appDataDir = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData); if (dalamudStartInfo.Platform == OSPlatform.Windows)
var xivlauncherDir = Path.Combine(appDataDir, "XIVLauncher"); {
var launcherConfigPath = Path.Combine(xivlauncherDir, "launcherConfigV3.json"); var appDataDir = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData);
gamePath = Path.Combine(JsonSerializer.CreateDefault().Deserialize<Dictionary<string, string>>(new JsonTextReader(new StringReader(File.ReadAllText(launcherConfigPath))))["GamePath"], "game", "ffxiv_dx11.exe"); var xivlauncherDir = Path.Combine(appDataDir, "XIVLauncher");
Log.Information("Using game installation path configuration from from XIVLauncher: {0}", gamePath); var launcherConfigPath = Path.Combine(xivlauncherDir, "launcherConfigV3.json");
gamePath = Path.Combine(
JsonSerializer.CreateDefault()
.Deserialize<Dictionary<string, string>>(
new JsonTextReader(new StringReader(File.ReadAllText(launcherConfigPath))))["GamePath"],
"game",
"ffxiv_dx11.exe");
Log.Information("Using game installation path configuration from from XIVLauncher: {0}", gamePath);
}
else if (dalamudStartInfo.Platform == OSPlatform.Linux)
{
var homeDir = $"Z:\\home\\{Environment.UserName}";
var xivlauncherDir = Path.Combine(homeDir, ".xlcore");
var launcherConfigPath = Path.Combine(xivlauncherDir, "launcher.ini");
var config = File.ReadAllLines(launcherConfigPath)
.Where(line => line.Contains('='))
.ToDictionary(line => line.Split('=')[0], line => line.Split('=')[1]);
gamePath = Path.Combine("Z:" + config["GamePath"].Replace('/', '\\'), "game", "ffxiv_dx11.exe");
Log.Information("Using game installation path configuration from from XIVLauncher Core: {0}", gamePath);
}
else
{
var homeDir = $"Z:\\Users\\{Environment.UserName}";
var xomlauncherDir = Path.Combine(homeDir, "Library", "Application Support", "XIV on Mac");
// we could try to parse the binary plist file here if we really wanted to...
gamePath = Path.Combine(xomlauncherDir, "ffxiv", "game", "ffxiv_dx11.exe");
Log.Information("Using default game installation path from XOM: {0}", gamePath);
}
} }
catch (Exception) catch (Exception)
{ {
Log.Error("Failed to read launcherConfigV3.json to get the set-up game path, please specify one using -g"); Log.Error("Failed to read launcher config to get the set-up game path, please specify one using -g");
return -1; return -1;
} }
@ -795,20 +880,6 @@ namespace Dalamud.Injector
if (encryptArguments) if (encryptArguments)
{ {
var rawTickCount = (uint)Environment.TickCount; var rawTickCount = (uint)Environment.TickCount;
if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
{
[System.Runtime.InteropServices.DllImport("c")]
#pragma warning disable SA1300
static extern ulong clock_gettime_nsec_np(int clockId);
#pragma warning restore SA1300
const int CLOCK_MONOTONIC_RAW = 4;
var rawTickCountFixed = clock_gettime_nsec_np(CLOCK_MONOTONIC_RAW) / 1000000;
Log.Information("ArgumentBuilder::DeriveKey() fixing up rawTickCount from {0} to {1} on macOS", rawTickCount, rawTickCountFixed);
rawTickCount = (uint)rawTickCountFixed;
}
var ticks = rawTickCount & 0xFFFF_FFFFu; var ticks = rawTickCount & 0xFFFF_FFFFu;
var key = ticks & 0xFFFF_0000u; var key = ticks & 0xFFFF_0000u;
gameArguments.Insert(0, $"T={ticks}"); gameArguments.Insert(0, $"T={ticks}");

View file

@ -1,4 +1,5 @@
using System; using System;
using System.Diagnostics.CodeAnalysis;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
namespace Dalamud.Injector namespace Dalamud.Injector
@ -910,5 +911,46 @@ namespace Dalamud.Injector
uint dwDesiredAccess, uint dwDesiredAccess,
[MarshalAs(UnmanagedType.Bool)] bool bInheritHandle, [MarshalAs(UnmanagedType.Bool)] bool bInheritHandle,
DuplicateOptions dwOptions); DuplicateOptions dwOptions);
/// <summary>
/// See https://docs.microsoft.com/en-us/windows/win32/api/libloaderapi/nf-libloaderapi-getmodulehandlew.
/// Retrieves a module handle for the specified module. The module must have been loaded by the calling process. To
/// avoid the race conditions described in the Remarks section, use the GetModuleHandleEx function.
/// </summary>
/// <param name="lpModuleName">
/// The name of the loaded module (either a .dll or .exe file). If the file name extension is omitted, the default
/// library extension .dll is appended. The file name string can include a trailing point character (.) to indicate
/// that the module name has no extension. The string does not have to specify a path. When specifying a path, be sure
/// to use backslashes (\), not forward slashes (/). The name is compared (case independently) to the names of modules
/// currently mapped into the address space of the calling process. If this parameter is NULL, GetModuleHandle returns
/// a handle to the file used to create the calling process (.exe file). The GetModuleHandle function does not retrieve
/// handles for modules that were loaded using the LOAD_LIBRARY_AS_DATAFILE flag.For more information, see LoadLibraryEx.
/// </param>
/// <returns>
/// If the function succeeds, the return value is a handle to the specified module. If the function fails, the return
/// value is NULL.To get extended error information, call GetLastError.
/// </returns>
[DllImport("kernel32.dll", CharSet = CharSet.Unicode, ExactSpelling = true, SetLastError = true)]
public static extern IntPtr GetModuleHandleW(string lpModuleName);
/// <summary>
/// Retrieves the address of an exported function or variable from the specified dynamic-link library (DLL).
/// </summary>
/// <param name="hModule">
/// A handle to the DLL module that contains the function or variable. The LoadLibrary, LoadLibraryEx, LoadPackagedLibrary,
/// or GetModuleHandle function returns this handle. The GetProcAddress function does not retrieve addresses from modules
/// that were loaded using the LOAD_LIBRARY_AS_DATAFILE flag.For more information, see LoadLibraryEx.
/// </param>
/// <param name="procName">
/// The function or variable name, or the function's ordinal value. If this parameter is an ordinal value, it must be
/// in the low-order word; the high-order word must be zero.
/// </param>
/// <returns>
/// If the function succeeds, the return value is the address of the exported function or variable. If the function
/// fails, the return value is NULL.To get extended error information, call GetLastError.
/// </returns>
[DllImport("kernel32", CharSet = CharSet.Ansi, ExactSpelling = true, SetLastError = true)]
[SuppressMessage("Globalization", "CA2101:Specify marshaling for P/Invoke string arguments", Justification = "Ansi only")]
public static extern IntPtr GetProcAddress(IntPtr hModule, string procName);
} }
} }

View file

@ -5,11 +5,6 @@ namespace Dalamud.Configuration.Internal;
/// </summary> /// </summary>
internal class EnvironmentConfiguration internal class EnvironmentConfiguration
{ {
/// <summary>
/// Gets a value indicating whether the XL_WINEONLINUX setting has been enabled.
/// </summary>
public static bool XlWineOnLinux { get; } = GetEnvironmentVariable("XL_WINEONLINUX");
/// <summary> /// <summary>
/// Gets a value indicating whether the DALAMUD_NOT_HAVE_PLUGINS setting has been enabled. /// Gets a value indicating whether the DALAMUD_NOT_HAVE_PLUGINS setting has been enabled.
/// </summary> /// </summary>

View file

@ -182,6 +182,10 @@ public sealed class EntryPoint
// Apply common fixes for culture issues // Apply common fixes for culture issues
CultureFixes.Apply(); CultureFixes.Apply();
// This is due to GitHub not supporting TLS 1.0, so we enable all TLS versions globally
ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls11 | SecurityProtocolType.Tls12 | SecurityProtocolType.Tls;
// Currently VEH is not fully functional on WINE
if (!Util.IsWine()) if (!Util.IsWine())
InitSymbolHandler(info); InitSymbolHandler(info);

View file

@ -19,6 +19,7 @@ using Dalamud.Game.ClientState.Objects.SubKinds;
using Dalamud.Game.ClientState.Objects.Types; using Dalamud.Game.ClientState.Objects.Types;
using Dalamud.Interface.Colors; using Dalamud.Interface.Colors;
using Dalamud.Interface.Utility; using Dalamud.Interface.Utility;
using Dalamud.Logging.Internal;
using Dalamud.Interface.Utility.Raii; using Dalamud.Interface.Utility.Raii;
using Dalamud.Support; using Dalamud.Support;
using ImGuiNET; using ImGuiNET;
@ -500,55 +501,14 @@ public static class Util
/// Determine if Dalamud is currently running within a Wine context (e.g. either on macOS or Linux). This method /// Determine if Dalamud is currently running within a Wine context (e.g. either on macOS or Linux). This method
/// will not return information about the host operating system. /// will not return information about the host operating system.
/// </summary> /// </summary>
/// <returns>Returns true if Wine is detected, false otherwise.</returns> /// <returns>Returns true if running on Wine, false otherwise.</returns>
public static bool IsWine() public static bool IsWine() => Service<Dalamud>.Get().StartInfo.Platform != OSPlatform.Windows;
{
if (EnvironmentConfiguration.XlWineOnLinux) return true;
if (Environment.GetEnvironmentVariable("XL_PLATFORM") is not null and not "Windows") return true;
var ntdll = NativeFunctions.GetModuleHandleW("ntdll.dll");
// Test to see if any Wine specific exports exist. If they do, then we are running on Wine.
// The exports "wine_get_version", "wine_get_build_id", and "wine_get_host_version" will tend to be hidden
// by most Linux users (else FFXIV will want a macOS license), so we will additionally check some lesser-known
// exports as well.
return AnyProcExists(
ntdll,
"wine_get_version",
"wine_get_build_id",
"wine_get_host_version",
"wine_server_call",
"wine_unix_to_nt_file_name");
bool AnyProcExists(nint handle, params string[] procs) =>
procs.Any(p => NativeFunctions.GetProcAddress(handle, p) != nint.Zero);
}
/// <summary> /// <summary>
/// Gets the best guess for the current host's platform based on the <c>XL_PLATFORM</c> environment variable or /// Gets the current host's platform based on the injector launch arguments or heuristics.
/// heuristics.
/// </summary> /// </summary>
/// <remarks>
/// macOS users running without <c>XL_PLATFORM</c> being set will be reported as Linux users. Due to the way our
/// Wines work, there isn't a great (consistent) way to split the two apart if we're not told.
/// </remarks>
/// <returns>Returns the <see cref="OSPlatform"/> that Dalamud is currently running on.</returns> /// <returns>Returns the <see cref="OSPlatform"/> that Dalamud is currently running on.</returns>
public static OSPlatform GetHostPlatform() public static OSPlatform GetHostPlatform() => Service<Dalamud>.Get().StartInfo.Platform;
{
switch (Environment.GetEnvironmentVariable("XL_PLATFORM"))
{
case "Windows": return OSPlatform.Windows;
case "MacOS": return OSPlatform.OSX;
case "Linux": return OSPlatform.Linux;
}
// n.b. we had some fancy code here to check if the Wine host version returned "Darwin" but apparently
// *all* our Wines report Darwin if exports aren't hidden. As such, it is effectively impossible (without some
// (very cursed and inaccurate heuristics) to determine if we're on macOS or Linux unless we're explicitly told
// by our launcher. See commit a7aacb15e4603a367e2f980578271a9a639d8852 for the old check.
return IsWine() ? OSPlatform.Linux : OSPlatform.Windows;
}
/// <summary> /// <summary>
/// Heuristically determine if the Windows version is higher than Windows 11's first build. /// Heuristically determine if the Windows version is higher than Windows 11's first build.

View file

@ -3,6 +3,7 @@
#include <cstdio> #include <cstdio>
#include <filesystem> #include <filesystem>
#include <Windows.h> #include <Windows.h>
#include <Lmcons.h>
#include <Shlobj.h> #include <Shlobj.h>
#include "CoreCLR.h" #include "CoreCLR.h"
#include "..\..\Dalamud.Boot\logging.h" #include "..\..\Dalamud.Boot\logging.h"
@ -27,6 +28,64 @@ void ConsoleTeardown()
std::optional<CoreCLR> g_clr; std::optional<CoreCLR> g_clr;
static wchar_t* GetRuntimePath()
{
int result;
std::wstring buffer;
wchar_t* runtime_path;
wchar_t* _appdata;
DWORD username_len = UNLEN + 1;
wchar_t username[UNLEN + 1];
buffer.resize(0);
result = GetEnvironmentVariableW(L"DALAMUD_RUNTIME", &buffer[0], 0);
if (result)
{
buffer.resize(result); // The first pass returns the required length
result = GetEnvironmentVariableW(L"DALAMUD_RUNTIME", &buffer[0], result);
return _wcsdup(buffer.c_str());
}
// Detect Windows first
result = SHGetKnownFolderPath(FOLDERID_RoamingAppData, KF_FLAG_DEFAULT, nullptr, &_appdata);
if (result != 0)
{
logging::E("Unable to get RoamingAppData path (err={})", result);
return NULL;
}
std::filesystem::path fs_app_data(_appdata);
runtime_path = _wcsdup(fs_app_data.append("XIVLauncher").append("runtime").c_str());
if (std::filesystem::exists(runtime_path))
return runtime_path;
free(runtime_path);
// Next XLCore on Linux
result = GetUserNameW(username, &username_len);
if (result != 0)
{
logging::E("Unable to get user name (err={})", result);
return NULL;
}
std::filesystem::path homeDir = L"Z:\\home\\" + std::wstring(username);
runtime_path = _wcsdup(homeDir.append(".xlcore").append("runtime").c_str());
if (std::filesystem::exists(runtime_path))
return runtime_path;
free(runtime_path);
// Finally XOM
homeDir = L"Z:\\Users\\" + std::wstring(username);
runtime_path = _wcsdup(homeDir.append("Library").append("Application Suppor").append("XIV on Mac").append("runtime").c_str());
if (std::filesystem::exists(runtime_path))
return runtime_path;
free(runtime_path);
return NULL;
}
HRESULT InitializeClrAndGetEntryPoint( HRESULT InitializeClrAndGetEntryPoint(
void* calling_module, void* calling_module,
bool enable_etw, bool enable_etw,
@ -62,31 +121,12 @@ HRESULT InitializeClrAndGetEntryPoint(
SetEnvironmentVariable(L"COMPlus_ETWEnabled", enable_etw ? L"1" : L"0"); SetEnvironmentVariable(L"COMPlus_ETWEnabled", enable_etw ? L"1" : L"0");
wchar_t* dotnet_path; wchar_t* dotnet_path = GetRuntimePath();
wchar_t* _appdata;
std::wstring buffer; if (!dotnet_path || !std::filesystem::exists(dotnet_path))
buffer.resize(0);
result = GetEnvironmentVariableW(L"DALAMUD_RUNTIME", &buffer[0], 0);
if (result)
{ {
buffer.resize(result); // The first pass returns the required length logging::E("Error: Unable to find .NET runtime path");
result = GetEnvironmentVariableW(L"DALAMUD_RUNTIME", &buffer[0], result); return 1;
dotnet_path = _wcsdup(buffer.c_str());
}
else
{
result = SHGetKnownFolderPath(FOLDERID_RoamingAppData, KF_FLAG_DEFAULT, nullptr, &_appdata);
if (result != 0)
{
logging::E("Unable to get RoamingAppData path (err={})", result);
return HRESULT_FROM_WIN32(ERROR_PATH_NOT_FOUND);
}
std::filesystem::path fs_app_data(_appdata);
dotnet_path = _wcsdup(fs_app_data.append("XIVLauncher").append("runtime").c_str());
} }
// =========================================================================== // // =========================================================================== //
@ -95,12 +135,6 @@ HRESULT InitializeClrAndGetEntryPoint(
logging::I("with config_path: {}", runtimeconfig_path); logging::I("with config_path: {}", runtimeconfig_path);
logging::I("with module_path: {}", module_path); logging::I("with module_path: {}", module_path);
if (!std::filesystem::exists(dotnet_path))
{
logging::E("Error: Unable to find .NET runtime path");
return HRESULT_FROM_WIN32(ERROR_PATH_NOT_FOUND);
}
get_hostfxr_parameters init_parameters get_hostfxr_parameters init_parameters
{ {
sizeof(get_hostfxr_parameters), sizeof(get_hostfxr_parameters),