Improve exe matching on Linux (#1567)

Co-authored-by: Alexandru Macocian <amacocian@microsoft.com>
This commit is contained in:
2026-06-30 12:58:49 +02:00
parent 0a0ec5cefc
commit fabb98f74e
7 changed files with 225 additions and 130 deletions
+4
View File
@@ -25,6 +25,10 @@ internal static partial class NativeMethods
[LibraryImport("kernel32.dll")]
public static partial nint OpenProcess(ProcessAccessFlags dwDesiredAccess, [MarshalAs(UnmanagedType.Bool)] bool bInheritHandle, uint dwProcessID);
[LibraryImport("kernel32.dll", EntryPoint = "QueryFullProcessImageNameW", SetLastError = true, StringMarshalling = StringMarshalling.Utf16)]
[return: MarshalAs(UnmanagedType.Bool)]
public static partial bool QueryFullProcessImageName(nint hProcess, uint dwFlags, [Out] char[] lpExeName, ref uint lpdwSize);
[LibraryImport("kernel32.dll", EntryPoint = "GetModuleHandleW", StringMarshalling = StringMarshalling.Utf16)]
public static partial nint GetModuleHandle(string lpModuleName);
+78
View File
@@ -0,0 +1,78 @@
using Daybreak.Shared.Models;
using System.Diagnostics;
namespace Daybreak.Injector;
/// <summary>
/// Resolves a Wine-internal process id from a Guild Wars executable path.
///
/// On Linux multiple Guild Wars instances (each in its own install directory) run under
/// the same Wine prefix. Matching by executable name alone cannot tell them apart, so we
/// enumerate the running processes and compare each one's full image path — which the
/// caller already knows from the Linux side — to find the unique owning Wine process.
///
/// This runs inside Wine, where <see cref="Process.Id"/> is the Wine pid, so the matched
/// id is exactly what the stub/winapi injectors expect.
/// </summary>
public static class ProcessResolver
{
private const string GuildWarsProcessName = "Gw";
public static InjectorResponses.ResolveResult Resolve(string executablePath, out int processId)
{
processId = 0;
var target = NormalizePath(executablePath);
foreach (var process in Process.GetProcessesByName(GuildWarsProcessName))
{
try
{
if (TryGetImagePath(process.Id) is { } imagePath &&
string.Equals(NormalizePath(imagePath), target, StringComparison.Ordinal))
{
processId = process.Id;
return InjectorResponses.ResolveResult.Success;
}
}
finally
{
process.Dispose();
}
}
return InjectorResponses.ResolveResult.ProcessNotFound;
}
private static string? TryGetImagePath(int processId)
{
var handle = NativeMethods.OpenProcess(
NativeMethods.ProcessAccessFlags.QueryLimitedInformation, false, (uint)processId);
if (handle is 0)
{
return null;
}
try
{
var buffer = new char[1024];
var size = (uint)buffer.Length;
if (!NativeMethods.QueryFullProcessImageName(handle, 0, buffer, ref size) || size is 0)
{
return null;
}
return new string(buffer, 0, (int)size);
}
finally
{
NativeMethods.CloseHandle(handle);
}
}
/// <summary>
/// Normalizes a Windows/Wine path for comparison: unifies separators, trims a trailing
/// null/whitespace, and lower-cases (Windows paths are case-insensitive).
/// </summary>
private static string NormalizePath(string path) =>
path.Trim().TrimEnd('\0').Replace('/', '\\').ToLowerInvariant();
}
+38
View File
@@ -12,11 +12,13 @@ static void PrintUsage()
Console.WriteLine("- stub");
Console.WriteLine("- launch");
Console.WriteLine("- resume");
Console.WriteLine("- resolve");
Console.WriteLine("Examples:");
Console.WriteLine("1) Daybreak.Injector winapi 1234 C:\\path\\to\\dll.dll");
Console.WriteLine("2) Daybreak.Injector stub 1234 entryPoint C:\\path\\to\\dll.dll");
Console.WriteLine("3) Daybreak.Injector launch true C:\\path\\to\\dll.dll arg1 arg2 arg3");
Console.WriteLine("4) Daybreak.Injector resume 1234");
Console.WriteLine("5) Daybreak.Injector resolve \"Z:\\path\\to\\Gw.exe\"");
Console.WriteLine("======================================================");
}
@@ -173,6 +175,24 @@ static bool TryParseThreadResumeArgs(
return true;
}
static bool TryParseResolveArgs(
string[] args,
[NotNullWhen(true)] out string? executablePath,
out InjectorResponses.ResolveResult exitCode)
{
executablePath = default;
if (args.Length < 2 || string.IsNullOrWhiteSpace(args[1]))
{
PrintUsage();
exitCode = InjectorResponses.ResolveResult.InvalidArgs;
return false;
}
executablePath = args[1];
exitCode = InjectorResponses.ResolveResult.Success;
return true;
}
if (args.Length < 1)
{
PrintUsage();
@@ -244,6 +264,24 @@ switch (mode)
Console.WriteLine($"ExitCode: {result}");
return result;
}
case "resolve":
{
if (!TryParseResolveArgs(args, out var executablePath, out var parseResult))
{
Console.WriteLine($"ExitCode: {(int)parseResult}");
return (int)parseResult;
}
Console.WriteLine($"Resolving Wine PID for {executablePath}");
var result = ProcessResolver.Resolve(executablePath, out var processId);
if (result is InjectorResponses.ResolveResult.Success)
{
Console.WriteLine($"ProcessId: {processId}");
}
Console.WriteLine($"ExitCode: {(int)result}");
return (int)result;
}
default:
PrintUsage();
Console.WriteLine($"ExitCode: {(int)InjectorResponses.GenericResults.InvalidMode}");