mirror of
https://github.com/goatcorp/Dalamud.git
synced 2025-12-12 10:17:22 +01:00
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:
parent
9815cf1d88
commit
7cac19ce81
11 changed files with 299 additions and 101 deletions
|
|
@ -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)
|
||||
{
|
||||
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<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);
|
||||
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<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)
|
||||
{
|
||||
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}");
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
||||
/// <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);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue