mirror of
https://github.com/gwdevhub/Daybreak.git
synced 2026-07-15 15:19:57 +00:00
Improve exe matching on Linux (#1567)
Co-authored-by: Alexandru Macocian <amacocian@microsoft.com>
This commit is contained in:
@@ -25,6 +25,10 @@ internal static partial class NativeMethods
|
|||||||
[LibraryImport("kernel32.dll")]
|
[LibraryImport("kernel32.dll")]
|
||||||
public static partial nint OpenProcess(ProcessAccessFlags dwDesiredAccess, [MarshalAs(UnmanagedType.Bool)] bool bInheritHandle, uint dwProcessID);
|
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)]
|
[LibraryImport("kernel32.dll", EntryPoint = "GetModuleHandleW", StringMarshalling = StringMarshalling.Utf16)]
|
||||||
public static partial nint GetModuleHandle(string lpModuleName);
|
public static partial nint GetModuleHandle(string lpModuleName);
|
||||||
|
|
||||||
|
|||||||
@@ -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();
|
||||||
|
}
|
||||||
@@ -12,11 +12,13 @@ static void PrintUsage()
|
|||||||
Console.WriteLine("- stub");
|
Console.WriteLine("- stub");
|
||||||
Console.WriteLine("- launch");
|
Console.WriteLine("- launch");
|
||||||
Console.WriteLine("- resume");
|
Console.WriteLine("- resume");
|
||||||
|
Console.WriteLine("- resolve");
|
||||||
Console.WriteLine("Examples:");
|
Console.WriteLine("Examples:");
|
||||||
Console.WriteLine("1) Daybreak.Injector winapi 1234 C:\\path\\to\\dll.dll");
|
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("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("3) Daybreak.Injector launch true C:\\path\\to\\dll.dll arg1 arg2 arg3");
|
||||||
Console.WriteLine("4) Daybreak.Injector resume 1234");
|
Console.WriteLine("4) Daybreak.Injector resume 1234");
|
||||||
|
Console.WriteLine("5) Daybreak.Injector resolve \"Z:\\path\\to\\Gw.exe\"");
|
||||||
Console.WriteLine("======================================================");
|
Console.WriteLine("======================================================");
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -173,6 +175,24 @@ static bool TryParseThreadResumeArgs(
|
|||||||
return true;
|
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)
|
if (args.Length < 1)
|
||||||
{
|
{
|
||||||
PrintUsage();
|
PrintUsage();
|
||||||
@@ -244,6 +264,24 @@ switch (mode)
|
|||||||
Console.WriteLine($"ExitCode: {result}");
|
Console.WriteLine($"ExitCode: {result}");
|
||||||
return 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:
|
default:
|
||||||
PrintUsage();
|
PrintUsage();
|
||||||
Console.WriteLine($"ExitCode: {(int)InjectorResponses.GenericResults.InvalidMode}");
|
Console.WriteLine($"ExitCode: {(int)InjectorResponses.GenericResults.InvalidMode}");
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ public class DaybreakInjector(
|
|||||||
) : IDaybreakInjector
|
) : IDaybreakInjector
|
||||||
{
|
{
|
||||||
private const string InjectorRelativePath = "Injector/Daybreak.Injector.exe";
|
private const string InjectorRelativePath = "Injector/Daybreak.Injector.exe";
|
||||||
|
private const string GuildWarsExecutableName = "Gw.exe";
|
||||||
|
|
||||||
private readonly ILogger<DaybreakInjector> logger = logger;
|
private readonly ILogger<DaybreakInjector> logger = logger;
|
||||||
private readonly IWinePrefixManager winePrefixManager = winePrefixManager;
|
private readonly IWinePrefixManager winePrefixManager = winePrefixManager;
|
||||||
@@ -57,8 +58,9 @@ public class DaybreakInjector(
|
|||||||
return InjectorResponses.InjectResult.InvalidInjector;
|
return InjectorResponses.InjectResult.InvalidInjector;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Convert Linux PID to Wine PID for the injector
|
// Resolve the Wine PID by matching the process's full image path (handles
|
||||||
var winePid = this.winePidMapper.LinuxPidToWinePid(processId);
|
// multiple concurrent Guild Wars instances).
|
||||||
|
var winePid = await this.ResolveWinePid(processId, cancellationToken);
|
||||||
if (winePid is null)
|
if (winePid is null)
|
||||||
{
|
{
|
||||||
scopedLogger.LogError("No Wine PID mapping found for Linux PID {ProcessId}", processId);
|
scopedLogger.LogError("No Wine PID mapping found for Linux PID {ProcessId}", processId);
|
||||||
@@ -99,8 +101,9 @@ public class DaybreakInjector(
|
|||||||
return InjectorResponses.InjectResult.InvalidInjector;
|
return InjectorResponses.InjectResult.InvalidInjector;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Convert Linux PID to Wine PID for the injector
|
// Resolve the Wine PID by matching the process's full image path (handles
|
||||||
var winePid = this.winePidMapper.LinuxPidToWinePid(processId);
|
// multiple concurrent Guild Wars instances).
|
||||||
|
var winePid = await this.ResolveWinePid(processId, cancellationToken);
|
||||||
if (winePid is null)
|
if (winePid is null)
|
||||||
{
|
{
|
||||||
scopedLogger.LogError("No Wine PID mapping found for Linux PID {ProcessId}", processId);
|
scopedLogger.LogError("No Wine PID mapping found for Linux PID {ProcessId}", processId);
|
||||||
@@ -290,4 +293,88 @@ public class DaybreakInjector(
|
|||||||
|
|
||||||
return defaultExitCode;
|
return defaultExitCode;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Resolves the Wine PID for a Linux Guild Wars process by asking the injector (which runs
|
||||||
|
/// inside Wine) to match on the process's full image path. This disambiguates multiple
|
||||||
|
/// concurrent Guild Wars instances, which a name-only lookup cannot.
|
||||||
|
/// </summary>
|
||||||
|
private async Task<int?> ResolveWinePid(int linuxPid, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
var scopedLogger = this.logger.CreateScopedLogger();
|
||||||
|
|
||||||
|
var wineExecutablePath = TryGetWineExecutablePath(linuxPid);
|
||||||
|
if (wineExecutablePath is null)
|
||||||
|
{
|
||||||
|
scopedLogger.LogError("Could not read Wine executable path for Linux PID {ProcessId}", linuxPid);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
var (output, _, _) = await this.LaunchInjector(
|
||||||
|
["resolve", $"\"{wineExecutablePath}\""],
|
||||||
|
cancellationToken,
|
||||||
|
completionChecker: (line, _) => line.StartsWith("ExitCode: ")
|
||||||
|
);
|
||||||
|
|
||||||
|
var winePid = ParseLabeledIntFromOutput(output, "ProcessId: ");
|
||||||
|
if (winePid is null)
|
||||||
|
{
|
||||||
|
scopedLogger.LogWarning(
|
||||||
|
"Injector could not resolve Wine PID for {ExecutablePath} (Linux PID {ProcessId})",
|
||||||
|
wineExecutablePath,
|
||||||
|
linuxPid
|
||||||
|
);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
scopedLogger.LogDebug(
|
||||||
|
"Resolved Linux PID {ProcessId} -> Wine PID {WinePid} via {ExecutablePath}",
|
||||||
|
linuxPid,
|
||||||
|
winePid.Value,
|
||||||
|
wineExecutablePath
|
||||||
|
);
|
||||||
|
return winePid;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Reads the Wine-form executable path (e.g. "Z:\home\...\Gw.exe") from the process's
|
||||||
|
/// command line, used to uniquely identify it among concurrent Guild Wars instances.
|
||||||
|
/// </summary>
|
||||||
|
private static string? TryGetWineExecutablePath(int linuxPid)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var cmdline = File.ReadAllText($"/proc/{linuxPid}/cmdline");
|
||||||
|
var segments = cmdline.Split('\0', StringSplitOptions.RemoveEmptyEntries);
|
||||||
|
return segments.FirstOrDefault(s =>
|
||||||
|
s.EndsWith(GuildWarsExecutableName, StringComparison.OrdinalIgnoreCase));
|
||||||
|
}
|
||||||
|
catch (IOException)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
catch (UnauthorizedAccessException)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int? ParseLabeledIntFromOutput(string? output, string label)
|
||||||
|
{
|
||||||
|
if (output is null)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var line in output.Split(Environment.NewLine, StringSplitOptions.RemoveEmptyEntries))
|
||||||
|
{
|
||||||
|
if (line.StartsWith(label) &&
|
||||||
|
int.TryParse(line[label.Length..], out var value))
|
||||||
|
{
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
namespace Daybreak.Linux.Services.Wine;
|
namespace Daybreak.Linux.Services.Wine;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Stateless translator between Wine-internal PIDs and Linux system PIDs.
|
/// Translates Wine-internal PIDs to Linux system PIDs by scanning /proc.
|
||||||
/// Uses /proc scanning and winedbg to resolve mappings on demand.
|
/// The reverse direction (Linux → Wine PID) is handled by the injector, which runs
|
||||||
|
/// inside Wine and can disambiguate concurrent instances by full image path.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public interface IWinePidMapper
|
public interface IWinePidMapper
|
||||||
{
|
{
|
||||||
@@ -18,13 +19,4 @@ public interface IWinePidMapper
|
|||||||
/// unambiguous matching) or a bare file name (e.g. "Gw.exe") as a best-effort fallback.</param>
|
/// unambiguous matching) or a bare file name (e.g. "Gw.exe") as a best-effort fallback.</param>
|
||||||
/// <returns>The Linux PID, or null if not found.</returns>
|
/// <returns>The Linux PID, or null if not found.</returns>
|
||||||
int? WinePidToLinuxPid(int winePid, string executable);
|
int? WinePidToLinuxPid(int winePid, string executable);
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Converts a Linux system PID back to a Wine-internal PID.
|
|
||||||
/// Reads the process's cmdline to determine the executable, then queries
|
|
||||||
/// winedbg to find the corresponding Wine PID.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="linuxPid">The Linux system PID.</param>
|
|
||||||
/// <returns>The Wine PID, or null if not found.</returns>
|
|
||||||
int? LinuxPidToWinePid(int linuxPid);
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,19 +1,16 @@
|
|||||||
using System.Diagnostics;
|
|
||||||
using System.Globalization;
|
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
|
|
||||||
namespace Daybreak.Linux.Services.Wine;
|
namespace Daybreak.Linux.Services.Wine;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Stateless translator between Wine-internal PIDs and Linux system PIDs.
|
/// Translates Wine-internal PIDs to Linux system PIDs by scanning /proc and matching the
|
||||||
/// Uses /proc scanning for Wine→Linux and winedbg for Linux→Wine.
|
/// full executable path. The reverse direction (Linux → Wine PID) lives in the injector,
|
||||||
|
/// which runs inside Wine and can disambiguate concurrent instances by image path.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public sealed class WinePidMapper(
|
public sealed class WinePidMapper(
|
||||||
IWinePrefixManager winePrefixManager,
|
|
||||||
ILogger<WinePidMapper> logger
|
ILogger<WinePidMapper> logger
|
||||||
) : IWinePidMapper
|
) : IWinePidMapper
|
||||||
{
|
{
|
||||||
private readonly IWinePrefixManager winePrefixManager = winePrefixManager;
|
|
||||||
private readonly ILogger<WinePidMapper> logger = logger;
|
private readonly ILogger<WinePidMapper> logger = logger;
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
@@ -113,113 +110,4 @@ public sealed class WinePidMapper(
|
|||||||
|
|
||||||
return winePath.Replace('\\', '/');
|
return winePath.Replace('\\', '/');
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
public int? LinuxPidToWinePid(int linuxPid)
|
|
||||||
{
|
|
||||||
// Step 1: Read the executable name from /proc/<pid>/cmdline
|
|
||||||
string? executableName;
|
|
||||||
try
|
|
||||||
{
|
|
||||||
var cmdline = File.ReadAllText($"/proc/{linuxPid}/cmdline");
|
|
||||||
// cmdline is null-separated; find the segment containing .exe
|
|
||||||
var segments = cmdline.Split('\0', StringSplitOptions.RemoveEmptyEntries);
|
|
||||||
var exeSegment = segments.FirstOrDefault(s => s.EndsWith(".exe", StringComparison.OrdinalIgnoreCase))
|
|
||||||
?? segments.FirstOrDefault(s => s.Contains(".exe", StringComparison.OrdinalIgnoreCase));
|
|
||||||
|
|
||||||
if (exeSegment is null)
|
|
||||||
{
|
|
||||||
this.logger.LogWarning("Could not determine executable name for Linux PID {LinuxPid}", linuxPid);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Extract just the filename (e.g. "Gw.exe" from "Z:\mnt\...\Gw.exe")
|
|
||||||
executableName = exeSegment.Split('\\').Last().Split('/').Last();
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
this.logger.LogWarning(ex, "Could not read cmdline for Linux PID {LinuxPid}", linuxPid);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Step 2: Query winedbg for the Wine PID of that executable
|
|
||||||
return this.QueryWineDbgForPid(executableName);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Runs <c>winedbg --command "info proc"</c> in our prefix and parses the output
|
|
||||||
/// to find the Wine PID for the given executable name.
|
|
||||||
/// Output format: " 0000021c 1 'Gw.exe'"
|
|
||||||
/// </summary>
|
|
||||||
private int? QueryWineDbgForPid(string executableName)
|
|
||||||
{
|
|
||||||
var prefixPath = this.winePrefixManager.GetWinePrefixPath();
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
var startInfo = new ProcessStartInfo
|
|
||||||
{
|
|
||||||
FileName = "winedbg",
|
|
||||||
Arguments = "--command \"info proc\"",
|
|
||||||
RedirectStandardOutput = true,
|
|
||||||
RedirectStandardError = true,
|
|
||||||
UseShellExecute = false,
|
|
||||||
CreateNoWindow = true,
|
|
||||||
};
|
|
||||||
|
|
||||||
startInfo.Environment["WINEPREFIX"] = prefixPath;
|
|
||||||
|
|
||||||
using var process = new Process { StartInfo = startInfo };
|
|
||||||
process.Start();
|
|
||||||
|
|
||||||
var output = process.StandardOutput.ReadToEnd();
|
|
||||||
process.WaitForExit(TimeSpan.FromSeconds(5));
|
|
||||||
|
|
||||||
// Parse lines like:
|
|
||||||
// 0000021c 1 'Gw.exe'
|
|
||||||
// 00000038 12 \_ 'services.exe'
|
|
||||||
foreach (var line in output.Split('\n', StringSplitOptions.RemoveEmptyEntries))
|
|
||||||
{
|
|
||||||
var trimmed = line.Trim();
|
|
||||||
|
|
||||||
// Skip header line
|
|
||||||
if (trimmed.StartsWith("pid", StringComparison.OrdinalIgnoreCase))
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if this line contains our executable
|
|
||||||
if (!trimmed.Contains(executableName, StringComparison.OrdinalIgnoreCase))
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Strip tree prefixes like "\_ " or "= "
|
|
||||||
var cleaned = trimmed.TrimStart('=', ' ');
|
|
||||||
if (cleaned.StartsWith("\\_"))
|
|
||||||
{
|
|
||||||
cleaned = cleaned[2..].TrimStart();
|
|
||||||
}
|
|
||||||
|
|
||||||
// First token is the hex PID
|
|
||||||
var hexPid = cleaned.Split(' ', StringSplitOptions.RemoveEmptyEntries).FirstOrDefault();
|
|
||||||
if (hexPid is not null && int.TryParse(hexPid, NumberStyles.HexNumber, CultureInfo.InvariantCulture, out var winePid))
|
|
||||||
{
|
|
||||||
this.logger.LogDebug(
|
|
||||||
"Resolved Linux PID -> Wine PID {WinePid} (0x{WinePidHex}) for {ExeName}",
|
|
||||||
winePid, hexPid, executableName
|
|
||||||
);
|
|
||||||
|
|
||||||
return winePid;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
this.logger.LogError(ex, "Failed to query winedbg for {ExeName}", executableName);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.logger.LogWarning("Could not find Wine PID for {ExeName} via winedbg", executableName);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -52,4 +52,12 @@ public static class InjectorResponses
|
|||||||
InvalidInjector = -3,
|
InvalidInjector = -3,
|
||||||
InvalidThreadHandle = -400,
|
InvalidThreadHandle = -400,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public enum ResolveResult
|
||||||
|
{
|
||||||
|
Success = 0,
|
||||||
|
InvalidArgs = -1,
|
||||||
|
InvalidInjector = -3,
|
||||||
|
ProcessNotFound = -501,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user