From 7cac19ce814e3e2b7ccec9b3c4eb3fb93babf1bf Mon Sep 17 00:00:00 2001 From: marzent Date: Mon, 24 Mar 2025 16:42:24 +0100 Subject: [PATCH] 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 --- Dalamud.Boot/DalamudStartInfo.cpp | 1 + Dalamud.Boot/DalamudStartInfo.h | 1 + Dalamud.Boot/utils.cpp | 5 + Dalamud.Common/DalamudStartInfo.cs | 9 +- Dalamud.Common/OSPlatformConverter.cs | 78 ++++++++++++ Dalamud.Injector/EntryPoint.cs | 113 ++++++++++++++---- Dalamud.Injector/NativeFunctions.cs | 42 +++++++ .../Internal/EnvironmentConfiguration.cs | 5 - Dalamud/EntryPoint.cs | 4 + Dalamud/Utility/Util.cs | 50 +------- lib/CoreCLR/boot.cpp | 92 +++++++++----- 11 files changed, 299 insertions(+), 101 deletions(-) create mode 100644 Dalamud.Common/OSPlatformConverter.cs diff --git a/Dalamud.Boot/DalamudStartInfo.cpp b/Dalamud.Boot/DalamudStartInfo.cpp index 52f201fea..4aa7d46dd 100644 --- a/Dalamud.Boot/DalamudStartInfo.cpp +++ b/Dalamud.Boot/DalamudStartInfo.cpp @@ -109,6 +109,7 @@ void from_json(const nlohmann::json& json, DalamudStartInfo& config) { config.PluginDirectory = json.value("PluginDirectory", config.PluginDirectory); config.AssetDirectory = json.value("AssetDirectory", config.AssetDirectory); config.Language = json.value("Language", config.Language); + config.Platform = json.value("Platform", config.Platform); config.GameVersion = json.value("GameVersion", config.GameVersion); config.TroubleshootingPackData = json.value("TroubleshootingPackData", std::string{}); config.DelayInitializeMs = json.value("DelayInitializeMs", config.DelayInitializeMs); diff --git a/Dalamud.Boot/DalamudStartInfo.h b/Dalamud.Boot/DalamudStartInfo.h index d5a6a6aec..64450e290 100644 --- a/Dalamud.Boot/DalamudStartInfo.h +++ b/Dalamud.Boot/DalamudStartInfo.h @@ -47,6 +47,7 @@ struct DalamudStartInfo { std::string PluginDirectory; std::string AssetDirectory; ClientLanguage Language = ClientLanguage::English; + std::string Platform; std::string GameVersion; std::string TroubleshootingPackData; int DelayInitializeMs = 0; diff --git a/Dalamud.Boot/utils.cpp b/Dalamud.Boot/utils.cpp index bbe47db82..dbfcf39ee 100644 --- a/Dalamud.Boot/utils.cpp +++ b/Dalamud.Boot/utils.cpp @@ -1,4 +1,5 @@ #include "pch.h" +#include "DalamudStartInfo.h" #include "utils.h" @@ -584,6 +585,10 @@ std::vector utils::get_env_list(const wchar_t* pcszName) { return res; } +bool utils::is_running_on_wine() { + return g_startInfo.Platform != "WINDOWS"; +} + std::filesystem::path utils::get_module_path(HMODULE hModule) { std::wstring buf(MAX_PATH, L'\0'); while (true) { diff --git a/Dalamud.Common/DalamudStartInfo.cs b/Dalamud.Common/DalamudStartInfo.cs index ca81d1281..eb2410cfd 100644 --- a/Dalamud.Common/DalamudStartInfo.cs +++ b/Dalamud.Common/DalamudStartInfo.cs @@ -1,3 +1,4 @@ +using System.Runtime.InteropServices; using Dalamud.Common.Game; using Newtonsoft.Json; @@ -15,7 +16,7 @@ public record DalamudStartInfo /// public DalamudStartInfo() { - // ignored + this.Platform = OSPlatform.Create("UNKNOWN"); } /// @@ -58,6 +59,12 @@ public record DalamudStartInfo /// public ClientLanguage Language { get; set; } = ClientLanguage.English; + /// + /// Gets or sets the underlying platform�Dalamud runs on. + /// + [JsonConverter(typeof(OSPlatformConverter))] + public OSPlatform Platform { get; set; } + /// /// Gets or sets the current game version code. /// diff --git a/Dalamud.Common/OSPlatformConverter.cs b/Dalamud.Common/OSPlatformConverter.cs new file mode 100644 index 000000000..62d2996d4 --- /dev/null +++ b/Dalamud.Common/OSPlatformConverter.cs @@ -0,0 +1,78 @@ +using System.Runtime.InteropServices; +using Newtonsoft.Json; + +namespace Dalamud.Common; + +/// +/// Converts a to and from a string (e.g. "FreeBSD"). +/// +public sealed class OSPlatformConverter : JsonConverter +{ + /// + /// Writes the JSON representation of the object. + /// + /// The to write to. + /// The value. + /// The calling serializer. + 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"); + } + } + + /// + /// Reads the JSON representation of the object. + /// + /// The to read from. + /// Type of the object. + /// The existing property value of the JSON that is being converted. + /// The calling serializer. + /// The object value. + 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}"); + } + } + } + + /// + /// Determines whether this instance can convert the specified object type. + /// + /// Type of the object. + /// + /// true if this instance can convert the specified object type; otherwise, false. + /// + public override bool CanConvert(Type objectType) + { + return objectType == typeof(OSPlatform); + } +} diff --git a/Dalamud.Injector/EntryPoint.cs b/Dalamud.Injector/EntryPoint.cs index 9e7033c4d..0ee2e5507 100644 --- a/Dalamud.Injector/EntryPoint.cs +++ b/Dalamud.Injector/EntryPoint.cs @@ -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])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 args) { int len; @@ -278,6 +307,7 @@ namespace Dalamud.Injector var logName = startInfo.LogName; var logPath = startInfo.LogPath; var languageStr = startInfo.Language.ToString().ToLowerInvariant(); + var platformStr = startInfo.Platform.ToString().ToLowerInvariant(); var unhandledExceptionStr = startInfo.UnhandledException.ToString().ToLowerInvariant(); var troubleshootingData = "{\"empty\": true, \"description\": \"No troubleshooting data supplied.\"}"; @@ -307,6 +337,10 @@ namespace Dalamud.Injector { 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=")) { 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."); } + 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.ConfigurationPath = configurationPath; startInfo.PluginDirectory = pluginDirectory; startInfo.AssetDirectory = assetDirectory; startInfo.Language = clientLanguage; + startInfo.Platform = platform; startInfo.DelayInitializeMs = delayInitializeMs; startInfo.GameVersion = null; 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(" [--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-client-language=0-3|j(apanese)|e(nglish)|d|g(erman)|f(rench)]"); @@ -732,15 +790,42 @@ namespace Dalamud.Injector { try { - var appDataDir = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData); - var xivlauncherDir = Path.Combine(appDataDir, "XIVLauncher"); - var launcherConfigPath = Path.Combine(xivlauncherDir, "launcherConfigV3.json"); - gamePath = Path.Combine(JsonSerializer.CreateDefault().Deserialize>(new JsonTextReader(new StringReader(File.ReadAllText(launcherConfigPath))))["GamePath"], "game", "ffxiv_dx11.exe"); - Log.Information("Using game installation path configuration from from XIVLauncher: {0}", gamePath); + if (dalamudStartInfo.Platform == OSPlatform.Windows) + { + var appDataDir = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData); + var xivlauncherDir = Path.Combine(appDataDir, "XIVLauncher"); + var launcherConfigPath = Path.Combine(xivlauncherDir, "launcherConfigV3.json"); + gamePath = Path.Combine( + JsonSerializer.CreateDefault() + .Deserialize>( + 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) { - 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; } @@ -795,20 +880,6 @@ namespace Dalamud.Injector if (encryptArguments) { 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 key = ticks & 0xFFFF_0000u; gameArguments.Insert(0, $"T={ticks}"); diff --git a/Dalamud.Injector/NativeFunctions.cs b/Dalamud.Injector/NativeFunctions.cs index 2a4654aaf..06add3acc 100644 --- a/Dalamud.Injector/NativeFunctions.cs +++ b/Dalamud.Injector/NativeFunctions.cs @@ -1,4 +1,5 @@ using System; +using System.Diagnostics.CodeAnalysis; using System.Runtime.InteropServices; namespace Dalamud.Injector @@ -910,5 +911,46 @@ namespace Dalamud.Injector uint dwDesiredAccess, [MarshalAs(UnmanagedType.Bool)] bool bInheritHandle, DuplicateOptions dwOptions); + + /// + /// 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. + /// + /// + /// 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. + /// + /// + /// 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. + /// + [DllImport("kernel32.dll", CharSet = CharSet.Unicode, ExactSpelling = true, SetLastError = true)] + public static extern IntPtr GetModuleHandleW(string lpModuleName); + + /// + /// Retrieves the address of an exported function or variable from the specified dynamic-link library (DLL). + /// + /// + /// 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. + /// + /// + /// 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. + /// + /// + /// 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. + /// + [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); } } diff --git a/Dalamud/Configuration/Internal/EnvironmentConfiguration.cs b/Dalamud/Configuration/Internal/EnvironmentConfiguration.cs index 2df9ec5fe..11a8d3567 100644 --- a/Dalamud/Configuration/Internal/EnvironmentConfiguration.cs +++ b/Dalamud/Configuration/Internal/EnvironmentConfiguration.cs @@ -5,11 +5,6 @@ namespace Dalamud.Configuration.Internal; /// internal class EnvironmentConfiguration { - /// - /// Gets a value indicating whether the XL_WINEONLINUX setting has been enabled. - /// - public static bool XlWineOnLinux { get; } = GetEnvironmentVariable("XL_WINEONLINUX"); - /// /// Gets a value indicating whether the DALAMUD_NOT_HAVE_PLUGINS setting has been enabled. /// diff --git a/Dalamud/EntryPoint.cs b/Dalamud/EntryPoint.cs index 89e4e0e5e..ebfc975ff 100644 --- a/Dalamud/EntryPoint.cs +++ b/Dalamud/EntryPoint.cs @@ -181,7 +181,11 @@ public sealed class EntryPoint // Apply common fixes for culture issues 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()) InitSymbolHandler(info); diff --git a/Dalamud/Utility/Util.cs b/Dalamud/Utility/Util.cs index 7724c68e0..2fcc4806a 100644 --- a/Dalamud/Utility/Util.cs +++ b/Dalamud/Utility/Util.cs @@ -19,6 +19,7 @@ using Dalamud.Game.ClientState.Objects.SubKinds; using Dalamud.Game.ClientState.Objects.Types; using Dalamud.Interface.Colors; using Dalamud.Interface.Utility; +using Dalamud.Logging.Internal; using Dalamud.Interface.Utility.Raii; using Dalamud.Support; 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 /// will not return information about the host operating system. /// - /// Returns true if Wine is detected, false otherwise. - public static bool IsWine() - { - 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); - } + /// Returns true if running on Wine, false otherwise. + public static bool IsWine() => Service.Get().StartInfo.Platform != OSPlatform.Windows; /// - /// Gets the best guess for the current host's platform based on the XL_PLATFORM environment variable or - /// heuristics. + /// Gets the current host's platform based on the injector launch arguments or heuristics. /// - /// - /// macOS users running without XL_PLATFORM 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. - /// /// Returns the that Dalamud is currently running on. - public static OSPlatform GetHostPlatform() - { - 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; - } + public static OSPlatform GetHostPlatform() => Service.Get().StartInfo.Platform; /// /// Heuristically determine if the Windows version is higher than Windows 11's first build. diff --git a/lib/CoreCLR/boot.cpp b/lib/CoreCLR/boot.cpp index 723a1ed21..f5e626c3f 100644 --- a/lib/CoreCLR/boot.cpp +++ b/lib/CoreCLR/boot.cpp @@ -3,6 +3,7 @@ #include #include #include +#include #include #include "CoreCLR.h" #include "..\..\Dalamud.Boot\logging.h" @@ -27,6 +28,64 @@ void ConsoleTeardown() std::optional 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( void* calling_module, bool enable_etw, @@ -62,31 +121,12 @@ HRESULT InitializeClrAndGetEntryPoint( SetEnvironmentVariable(L"COMPlus_ETWEnabled", enable_etw ? L"1" : L"0"); - wchar_t* dotnet_path; - wchar_t* _appdata; + wchar_t* dotnet_path = GetRuntimePath(); - std::wstring buffer; - buffer.resize(0); - result = GetEnvironmentVariableW(L"DALAMUD_RUNTIME", &buffer[0], 0); - - if (result) + if (!dotnet_path || !std::filesystem::exists(dotnet_path)) { - buffer.resize(result); // The first pass returns the required length - result = GetEnvironmentVariableW(L"DALAMUD_RUNTIME", &buffer[0], result); - 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()); + logging::E("Error: Unable to find .NET runtime path"); + return 1; } // =========================================================================== // @@ -95,12 +135,6 @@ HRESULT InitializeClrAndGetEntryPoint( logging::I("with config_path: {}", runtimeconfig_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 { sizeof(get_hostfxr_parameters),