From c0ee1b433f7d4b8a8a476b67f47e87220151352d Mon Sep 17 00:00:00 2001 From: marzent Date: Tue, 26 Sep 2023 12:53:35 +0200 Subject: [PATCH 1/4] implement dalamud platform launch arg --- Dalamud.Boot/DalamudStartInfo.cpp | 1 + Dalamud.Boot/DalamudStartInfo.h | 1 + Dalamud.Boot/utils.cpp | 16 +--- Dalamud.Injector/Dalamud.Injector.csproj | 1 + Dalamud.Injector/EntryPoint.cs | 61 +++++++++++++- Dalamud.Injector/NativeFunctions.cs | 42 ++++++++++ .../Internal/EnvironmentConfiguration.cs | 5 -- Dalamud/DalamudStartInfo.cs | 11 ++- Dalamud/EntryPoint.cs | 2 +- Dalamud/OSPlatformConverter.cs | 80 +++++++++++++++++++ Dalamud/Utility/Util.cs | 50 +----------- 11 files changed, 201 insertions(+), 69 deletions(-) create mode 100644 Dalamud/OSPlatformConverter.cs diff --git a/Dalamud.Boot/DalamudStartInfo.cpp b/Dalamud.Boot/DalamudStartInfo.cpp index 15faf82ada..de713757f2 100644 --- a/Dalamud.Boot/DalamudStartInfo.cpp +++ b/Dalamud.Boot/DalamudStartInfo.cpp @@ -78,6 +78,7 @@ void from_json(const nlohmann::json& json, DalamudStartInfo& config) { config.DefaultPluginDirectory = json.value("DefaultPluginDirectory", config.DefaultPluginDirectory); 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.DelayInitializeMs = json.value("DelayInitializeMs", config.DelayInitializeMs); config.TroubleshootingPackData = json.value("TroubleshootingPackData", std::string{}); diff --git a/Dalamud.Boot/DalamudStartInfo.h b/Dalamud.Boot/DalamudStartInfo.h index 66109abf7f..d2a787ff46 100644 --- a/Dalamud.Boot/DalamudStartInfo.h +++ b/Dalamud.Boot/DalamudStartInfo.h @@ -32,6 +32,7 @@ struct DalamudStartInfo { std::string DefaultPluginDirectory; std::string AssetDirectory; ClientLanguage Language = ClientLanguage::English; + std::string Platform; std::string GameVersion; int DelayInitializeMs = 0; std::string TroubleshootingPackData; diff --git a/Dalamud.Boot/utils.cpp b/Dalamud.Boot/utils.cpp index b457950454..b6aea08250 100644 --- a/Dalamud.Boot/utils.cpp +++ b/Dalamud.Boot/utils.cpp @@ -1,4 +1,5 @@ #include "pch.h" +#include "DalamudStartInfo.h" #include "utils.h" @@ -579,20 +580,7 @@ std::vector utils::get_env_list(const wchar_t* pcszName) { } bool utils::is_running_on_wine() { - if (get_env(L"XL_WINEONLINUX")) - return true; - HMODULE hntdll = GetModuleHandleW(L"ntdll.dll"); - if (!hntdll) - return true; - if (GetProcAddress(hntdll, "wine_get_version")) - return true; - if (GetProcAddress(hntdll, "wine_get_host_version")) - return true; - if (GetProcAddress(hntdll, "wine_server_call")) - return true; - if (GetProcAddress(hntdll, "wine_unix_to_nt_file_name")) - return true; - return false; + return g_startInfo.Platform != "WINDOWS"; } std::filesystem::path utils::get_module_path(HMODULE hModule) { diff --git a/Dalamud.Injector/Dalamud.Injector.csproj b/Dalamud.Injector/Dalamud.Injector.csproj index ea9e4f0a38..da56e68f9b 100644 --- a/Dalamud.Injector/Dalamud.Injector.csproj +++ b/Dalamud.Injector/Dalamud.Injector.csproj @@ -86,6 +86,7 @@ + diff --git a/Dalamud.Injector/EntryPoint.cs b/Dalamud.Injector/EntryPoint.cs index a352480625..68124a98a6 100644 --- a/Dalamud.Injector/EntryPoint.cs +++ b/Dalamud.Injector/EntryPoint.cs @@ -263,6 +263,7 @@ private static DalamudStartInfo ExtractAndInitializeStartInfoFromArguments(Dalam var logName = startInfo.LogName; var logPath = startInfo.LogPath; var languageStr = startInfo.Language.ToString().ToLowerInvariant(); + var platformStr = startInfo.Platform.ToString().ToLowerInvariant(); var troubleshootingData = "{\"empty\": true, \"description\": \"No troubleshooting data supplied.\"}"; for (var i = 2; i < args.Count; i++) @@ -291,6 +292,10 @@ private static DalamudStartInfo ExtractAndInitializeStartInfoFromArguments(Dalam { 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..])); @@ -358,11 +363,65 @@ private static DalamudStartInfo ExtractAndInitializeStartInfoFromArguments(Dalam 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 + { + 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) + platform = OSPlatform.Windows; + else if (winePlatform == "Linux") + platform = OSPlatform.Linux; + else if (winePlatform == "Darwin") + platform = OSPlatform.OSX; + else if (winePlatform is not null) // probably custom Linux kernel or BSD + platform = OSPlatform.Linux; + else if (bool.Parse(Environment.GetEnvironmentVariable("XL_WINEONLINUX") ?? "false")) + platform = OSPlatform.Linux; + else // this means we have wine with exports hidden, so not a mac license, proabably Linux + platform = OSPlatform.Linux; + + 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; @@ -426,7 +485,7 @@ private static int ProcessHelpCommand(List args, string? particularComma } 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)]"); diff --git a/Dalamud.Injector/NativeFunctions.cs b/Dalamud.Injector/NativeFunctions.cs index 2a4654aafe..06add3acc8 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 @@ public static extern bool DuplicateHandle( 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 a251da7631..6b95b75fd4 100644 --- a/Dalamud/Configuration/Internal/EnvironmentConfiguration.cs +++ b/Dalamud/Configuration/Internal/EnvironmentConfiguration.cs @@ -7,11 +7,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/DalamudStartInfo.cs b/Dalamud/DalamudStartInfo.cs index 63a61c97e4..04d42d5f49 100644 --- a/Dalamud/DalamudStartInfo.cs +++ b/Dalamud/DalamudStartInfo.cs @@ -1,6 +1,6 @@ using System; using System.Collections.Generic; - +using System.Runtime.InteropServices; using Dalamud.Game; using Newtonsoft.Json; @@ -17,7 +17,7 @@ public record DalamudStartInfo : IServiceType /// public DalamudStartInfo() { - // ignored + this.Platform = OSPlatform.Create("UNKNOWN"); } /// @@ -33,6 +33,7 @@ public DalamudStartInfo(DalamudStartInfo other) this.PluginDirectory = other.PluginDirectory; this.AssetDirectory = other.AssetDirectory; this.Language = other.Language; + this.Platform = other.Platform; this.GameVersion = other.GameVersion; this.DelayInitializeMs = other.DelayInitializeMs; this.TroubleshootingPackData = other.TroubleshootingPackData; @@ -87,6 +88,12 @@ public DalamudStartInfo(DalamudStartInfo other) /// 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/EntryPoint.cs b/Dalamud/EntryPoint.cs index 7ad794e42e..d9df14e5da 100644 --- a/Dalamud/EntryPoint.cs +++ b/Dalamud/EntryPoint.cs @@ -166,7 +166,7 @@ private static void RunThread(DalamudStartInfo info, IntPtr mainThreadContinueEv // 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; - if (!Util.IsWine()) + if (info.Platform == OSPlatform.Windows) // Currently VEH is not fully functional on WINE InitSymbolHandler(info); var dalamud = new Dalamud(info, configuration, mainThreadContinueEvent); diff --git a/Dalamud/OSPlatformConverter.cs b/Dalamud/OSPlatformConverter.cs new file mode 100644 index 0000000000..9186281ca9 --- /dev/null +++ b/Dalamud/OSPlatformConverter.cs @@ -0,0 +1,80 @@ +using System; +using System.Runtime.InteropServices; + +using Newtonsoft.Json; + +namespace Dalamud; + +/// +/// 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/Utility/Util.cs b/Dalamud/Utility/Util.cs index 8ca87b6914..76ec2ccd2d 100644 --- a/Dalamud/Utility/Util.cs +++ b/Dalamud/Utility/Util.cs @@ -17,7 +17,6 @@ using Dalamud.Interface.Colors; using Dalamud.Interface.Utility; using Dalamud.Logging.Internal; -using Dalamud.Memory; using ImGuiNET; using Lumina.Excel.GeneratedSheets; using Serilog; @@ -491,55 +490,14 @@ public static string DecompressString(byte[] bytes) /// 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().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().Platform; /// /// Heuristically determine if the Windows version is higher than Windows 11's first build. From df680586fbac4ca6977f6be1c76dad9b21791a1e Mon Sep 17 00:00:00 2001 From: marzent Date: Tue, 26 Sep 2023 19:32:54 +0200 Subject: [PATCH 2/4] implement cross-platform gamePath fallback --- Dalamud.Injector/EntryPoint.cs | 32 ++++++++++++++++++++++++++++++-- 1 file changed, 30 insertions(+), 2 deletions(-) diff --git a/Dalamud.Injector/EntryPoint.cs b/Dalamud.Injector/EntryPoint.cs index 68124a98a6..e53305d12c 100644 --- a/Dalamud.Injector/EntryPoint.cs +++ b/Dalamud.Injector/EntryPoint.cs @@ -751,15 +751,43 @@ private static int ProcessLaunchCommand(List args, DalamudStartInfo dala { try { + 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"); + 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 = "Z:" + config["GamePath"].Replace('/', '\\'); + Environment.SetEnvironmentVariable("MY_VARIABLE", "SomeValue"); + 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"); + Log.Information("Using default game installation path from 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; } From 3875f199cec55eb93542aafda5d771bd0b300743 Mon Sep 17 00:00:00 2001 From: marzent Date: Tue, 26 Sep 2023 19:34:06 +0200 Subject: [PATCH 3/4] refactor platform detection heuristic --- Dalamud.Injector/EntryPoint.cs | 86 ++++++++++++++-------------------- 1 file changed, 35 insertions(+), 51 deletions(-) diff --git a/Dalamud.Injector/EntryPoint.cs b/Dalamud.Injector/EntryPoint.cs index e53305d12c..500621312d 100644 --- a/Dalamud.Injector/EntryPoint.cs +++ b/Dalamud.Injector/EntryPoint.cs @@ -248,6 +248,35 @@ private static void CullLogFile(string logPath, int cullingFileSize) } } + 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; @@ -382,37 +411,7 @@ private static DalamudStartInfo ExtractAndInitializeStartInfoFromArguments(Dalam } else { - 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) - platform = OSPlatform.Windows; - else if (winePlatform == "Linux") - platform = OSPlatform.Linux; - else if (winePlatform == "Darwin") - platform = OSPlatform.OSX; - else if (winePlatform is not null) // probably custom Linux kernel or BSD - platform = OSPlatform.Linux; - else if (bool.Parse(Environment.GetEnvironmentVariable("XL_WINEONLINUX") ?? "false")) - platform = OSPlatform.Linux; - else // this means we have wine with exports hidden, so not a mac license, proabably Linux - platform = OSPlatform.Linux; - + platform = DetectPlatformHeuristic(); Log.Warning("Heuristically determined host system platform as {platform}", platform); } @@ -753,17 +752,17 @@ private static int ProcessLaunchCommand(List args, DalamudStartInfo dala { 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"); + 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); - } + Log.Information("Using game installation path configuration from from XIVLauncher: {0}", gamePath); + } else if (dalamudStartInfo.Platform == OSPlatform.Linux) { var homeDir = $"Z:\\home\\{Environment.UserName}"; @@ -773,7 +772,6 @@ private static int ProcessLaunchCommand(List args, DalamudStartInfo dala .Where(line => line.Contains('=')) .ToDictionary(line => line.Split('=')[0], line => line.Split('=')[1]); gamePath = "Z:" + config["GamePath"].Replace('/', '\\'); - Environment.SetEnvironmentVariable("MY_VARIABLE", "SomeValue"); Log.Information("Using game installation path configuration from from XIVLauncher Core: {0}", gamePath); } else @@ -842,20 +840,6 @@ private static int ProcessLaunchCommand(List args, DalamudStartInfo dala 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}"); From 26b961d3f66e5f8abe98e7204d9f3940c5d13342 Mon Sep 17 00:00:00 2001 From: marzent Date: Tue, 26 Sep 2023 22:35:24 +0200 Subject: [PATCH 4/4] add cross platform dalamud runtime detection --- Dalamud.Injector/EntryPoint.cs | 6 +-- lib/CoreCLR/boot.cpp | 93 +++++++++++++++++++++++----------- 2 files changed, 67 insertions(+), 32 deletions(-) diff --git a/Dalamud.Injector/EntryPoint.cs b/Dalamud.Injector/EntryPoint.cs index 500621312d..c08fdd6043 100644 --- a/Dalamud.Injector/EntryPoint.cs +++ b/Dalamud.Injector/EntryPoint.cs @@ -771,7 +771,7 @@ private static int ProcessLaunchCommand(List args, DalamudStartInfo dala var config = File.ReadAllLines(launcherConfigPath) .Where(line => line.Contains('=')) .ToDictionary(line => line.Split('=')[0], line => line.Split('=')[1]); - gamePath = "Z:" + config["GamePath"].Replace('/', '\\'); + 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 @@ -779,8 +779,8 @@ private static int ProcessLaunchCommand(List args, DalamudStartInfo dala 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"); - Log.Information("Using default game installation path from from XOM: {0}", gamePath); + gamePath = Path.Combine(xomlauncherDir, "ffxiv", "game", "ffxiv_dx11.exe"); + Log.Information("Using default game installation path from XOM: {0}", gamePath); } } catch (Exception) diff --git a/lib/CoreCLR/boot.cpp b/lib/CoreCLR/boot.cpp index e3db99c4f9..36b338620c 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,65 @@ 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()); + printf("%ws", runtime_path); + 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; +} + int InitializeClrAndGetEntryPoint( void* calling_module, bool enable_etw, @@ -56,31 +116,12 @@ int 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 result; - } - - 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; } // =========================================================================== // @@ -89,12 +130,6 @@ int 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 1; - } - get_hostfxr_parameters init_parameters { sizeof(get_hostfxr_parameters),