mirror of
https://github.com/goatcorp/Dalamud.git
synced 2025-12-12 18:27:23 +01:00
Merge branch 'net5'
This commit is contained in:
commit
1395e3a555
94 changed files with 2455 additions and 783 deletions
|
|
@ -67,6 +67,7 @@
|
|||
<PackageReference Include="Reloaded.Memory.Buffers" Version="1.3.5" />
|
||||
<PackageReference Include="Serilog" Version="2.10.0" />
|
||||
<PackageReference Include="Serilog.Sinks.Async" Version="1.5.0" />
|
||||
<PackageReference Include="Serilog.Sinks.Console" Version="4.0.1" />
|
||||
<PackageReference Include="Serilog.Sinks.File" Version="5.0.0" />
|
||||
<PackageReference Include="StyleCop.Analyzers" Version="1.2.0-beta.333">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
|
|
|
|||
|
|
@ -1,11 +1,11 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
|
||||
using Dalamud.Game;
|
||||
using Newtonsoft.Json;
|
||||
|
|
@ -37,45 +37,78 @@ namespace Dalamud.Injector
|
|||
/// <param name="argvPtr">byte** string arguments.</param>
|
||||
public static void Main(int argc, IntPtr argvPtr)
|
||||
{
|
||||
InitUnhandledException();
|
||||
InitLogging();
|
||||
|
||||
var args = new string[argc];
|
||||
Init();
|
||||
|
||||
List<string> args = new(argc);
|
||||
unsafe
|
||||
{
|
||||
var argv = (IntPtr*)argvPtr;
|
||||
for (var i = 0; i < argc; i++)
|
||||
args.Add(Marshal.PtrToStringUni(argv[i]));
|
||||
}
|
||||
|
||||
if (args.Count >= 2 && args[1].ToLowerInvariant() == "launch-test")
|
||||
{
|
||||
Environment.Exit(ProcessLaunchTestCommand(args));
|
||||
return;
|
||||
}
|
||||
|
||||
DalamudStartInfo startInfo = null;
|
||||
if (args.Count == 1)
|
||||
{
|
||||
// No command defaults to inject
|
||||
args.Add("inject");
|
||||
args.Add("--all");
|
||||
args.Add("--warn");
|
||||
}
|
||||
else if (int.TryParse(args[1], out var _))
|
||||
{
|
||||
// Assume that PID has been passed.
|
||||
args.Insert(1, "inject");
|
||||
|
||||
// If originally second parameter exists, then assume that it's a base64 encoded start info.
|
||||
// Dalamud.Injector.exe inject [pid] [base64]
|
||||
if (args.Count == 4)
|
||||
{
|
||||
args[i] = Marshal.PtrToStringUni(argv[i]);
|
||||
startInfo = JsonConvert.DeserializeObject<DalamudStartInfo>(Encoding.UTF8.GetString(Convert.FromBase64String(args[3])));
|
||||
args.RemoveAt(3);
|
||||
}
|
||||
}
|
||||
|
||||
startInfo = ExtractAndInitializeStartInfoFromArguments(startInfo, args);
|
||||
|
||||
var mainCommand = args[1].ToLowerInvariant();
|
||||
if (mainCommand.Length > 0 && mainCommand.Length <= 6 && "inject"[..mainCommand.Length] == mainCommand)
|
||||
{
|
||||
Environment.Exit(ProcessInjectCommand(args, startInfo));
|
||||
}
|
||||
else if (mainCommand.Length > 0 && mainCommand.Length <= 6 && "launch"[..mainCommand.Length] == mainCommand)
|
||||
{
|
||||
Environment.Exit(ProcessLaunchCommand(args, startInfo));
|
||||
}
|
||||
else if (mainCommand.Length > 0 && mainCommand.Length <= 4 && "help"[..mainCommand.Length] == mainCommand)
|
||||
{
|
||||
Environment.Exit(ProcessHelpCommand(args, args.Count >= 3 ? args[2] : null));
|
||||
}
|
||||
else
|
||||
{
|
||||
Console.WriteLine("Invalid command: {0}", mainCommand);
|
||||
ProcessHelpCommand(args);
|
||||
Environment.Exit(-1);
|
||||
}
|
||||
}
|
||||
|
||||
private static void Init()
|
||||
{
|
||||
InitUnhandledException();
|
||||
InitLogging();
|
||||
|
||||
var cwd = new FileInfo(Assembly.GetExecutingAssembly().Location).Directory;
|
||||
if (cwd.FullName != Directory.GetCurrentDirectory())
|
||||
{
|
||||
Log.Debug($"Changing cwd to {cwd}");
|
||||
Directory.SetCurrentDirectory(cwd.FullName);
|
||||
}
|
||||
|
||||
var process = GetProcess(args.ElementAtOrDefault(1));
|
||||
if (process == null)
|
||||
{
|
||||
Log.Error("Could not open process");
|
||||
return;
|
||||
}
|
||||
|
||||
var startInfo = GetStartInfo(args.ElementAtOrDefault(2), process);
|
||||
|
||||
// TODO: XL does not set this!!! we need to keep this line here for now, otherwise we crash in the Dalamud entrypoint
|
||||
startInfo.WorkingDirectory = Directory.GetCurrentDirectory();
|
||||
|
||||
// This seems to help with the STATUS_INTERNAL_ERROR condition
|
||||
Thread.Sleep(1000);
|
||||
|
||||
Inject(process, startInfo);
|
||||
|
||||
Thread.Sleep(1000);
|
||||
}
|
||||
|
||||
private static void InitUnhandledException()
|
||||
|
|
@ -138,6 +171,7 @@ namespace Dalamud.Injector
|
|||
CullLogFile(logPath, 1 * 1024 * 1024);
|
||||
|
||||
Log.Logger = new LoggerConfiguration()
|
||||
.WriteTo.Console(standardErrorFromLevel: LogEventLevel.Verbose)
|
||||
.WriteTo.Async(a => a.File(logPath))
|
||||
.MinimumLevel.ControlledBy(levelSwitch)
|
||||
.CreateLogger();
|
||||
|
|
@ -189,133 +223,423 @@ namespace Dalamud.Injector
|
|||
}
|
||||
}
|
||||
|
||||
private static Process? GetProcess(string? arg)
|
||||
private static DalamudStartInfo ExtractAndInitializeStartInfoFromArguments(DalamudStartInfo? startInfo, List<string> args)
|
||||
{
|
||||
Process process = null;
|
||||
if (startInfo == null)
|
||||
startInfo = new();
|
||||
|
||||
var pid = -1;
|
||||
if (arg != default)
|
||||
var workingDirectory = startInfo.WorkingDirectory;
|
||||
var configurationPath = startInfo.ConfigurationPath;
|
||||
var pluginDirectory = startInfo.PluginDirectory;
|
||||
var defaultPluginDirectory = startInfo.DefaultPluginDirectory;
|
||||
var assetDirectory = startInfo.AssetDirectory;
|
||||
var delayInitializeMs = startInfo.DelayInitializeMs;
|
||||
|
||||
for (var i = 2; i < args.Count; i++)
|
||||
{
|
||||
pid = int.Parse(arg);
|
||||
string key;
|
||||
if (args[i].StartsWith(key = "--dalamud-working-directory="))
|
||||
workingDirectory = args[i][key.Length..];
|
||||
else if (args[i].StartsWith(key = "--dalamud-configuration-path="))
|
||||
configurationPath = args[i][key.Length..];
|
||||
else if (args[i].StartsWith(key = "--dalamud-plugin-directory="))
|
||||
pluginDirectory = args[i][key.Length..];
|
||||
else if (args[i].StartsWith(key = "--dalamud-dev-plugin-directory="))
|
||||
defaultPluginDirectory = args[i][key.Length..];
|
||||
else if (args[i].StartsWith(key = "--dalamud-asset-directory="))
|
||||
assetDirectory = args[i][key.Length..];
|
||||
else if (args[i].StartsWith(key = "--dalamud-delay-initialize="))
|
||||
delayInitializeMs = int.Parse(args[i][key.Length..]);
|
||||
else
|
||||
continue;
|
||||
|
||||
args.RemoveAt(i);
|
||||
i--;
|
||||
}
|
||||
|
||||
switch (pid)
|
||||
var appDataDir = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData);
|
||||
var xivlauncherDir = Path.Combine(appDataDir, "XIVLauncher");
|
||||
|
||||
workingDirectory ??= Directory.GetCurrentDirectory();
|
||||
configurationPath ??= Path.Combine(xivlauncherDir, "dalamudConfig.json");
|
||||
pluginDirectory ??= Path.Combine(xivlauncherDir, "installedPlugins");
|
||||
defaultPluginDirectory ??= Path.Combine(xivlauncherDir, "devPlugins");
|
||||
assetDirectory ??= Path.Combine(xivlauncherDir, "dalamudAssets", "dev");
|
||||
|
||||
return new()
|
||||
{
|
||||
case -1:
|
||||
process = Process.GetProcessesByName("ffxiv_dx11").FirstOrDefault();
|
||||
WorkingDirectory = workingDirectory,
|
||||
ConfigurationPath = configurationPath,
|
||||
PluginDirectory = pluginDirectory,
|
||||
DefaultPluginDirectory = defaultPluginDirectory,
|
||||
AssetDirectory = assetDirectory,
|
||||
Language = ClientLanguage.English,
|
||||
GameVersion = null,
|
||||
DelayInitializeMs = delayInitializeMs,
|
||||
};
|
||||
}
|
||||
|
||||
if (process == default)
|
||||
{
|
||||
throw new Exception("Could not find process");
|
||||
}
|
||||
private static int ProcessHelpCommand(List<string> args, string? particularCommand = default)
|
||||
{
|
||||
var exeName = Path.GetFileName(args[0]);
|
||||
|
||||
#if !DEBUG
|
||||
var result = MessageBoxW(IntPtr.Zero, $"Take care: you are manually injecting Dalamud into FFXIV({process.Id}).\n\nIf you are doing this to use plugins before they are officially whitelisted on patch days, things may go wrong and you may get into trouble.\nWe discourage you from doing this and you won't be warned again in-game.", "Dalamud", MessageBoxType.IconWarning | MessageBoxType.OkCancel);
|
||||
var exeSpaces = string.Empty;
|
||||
for (var i = exeName.Length; i > 0; i--)
|
||||
exeSpaces += " ";
|
||||
|
||||
// IDCANCEL
|
||||
if (result == 2)
|
||||
{
|
||||
Log.Information("User cancelled injection");
|
||||
return null;
|
||||
}
|
||||
#endif
|
||||
if (particularCommand is null or "help")
|
||||
Console.WriteLine("{0} help [command]", exeName);
|
||||
|
||||
break;
|
||||
if (particularCommand is null or "inject")
|
||||
Console.WriteLine("{0} inject [-h/--help] [-a/--all] [--warn] [pid1] [pid2] [pid3] ...", exeName);
|
||||
|
||||
case -2:
|
||||
var exePath = "C:\\Program Files (x86)\\SquareEnix\\FINAL FANTASY XIV - A Realm Reborn\\game\\ffxiv_dx11.exe";
|
||||
var exeArgs = new StringBuilder()
|
||||
.Append("DEV.TestSID=0 DEV.UseSqPack=1 DEV.DataPathType=1 ")
|
||||
.Append("DEV.LobbyHost01=127.0.0.1 DEV.LobbyPort01=54994 ")
|
||||
.Append("DEV.LobbyHost02=127.0.0.1 DEV.LobbyPort02=54994 ")
|
||||
.Append("DEV.LobbyHost03=127.0.0.1 DEV.LobbyPort03=54994 ")
|
||||
.Append("DEV.LobbyHost04=127.0.0.1 DEV.LobbyPort04=54994 ")
|
||||
.Append("DEV.LobbyHost05=127.0.0.1 DEV.LobbyPort05=54994 ")
|
||||
.Append("DEV.LobbyHost06=127.0.0.1 DEV.LobbyPort06=54994 ")
|
||||
.Append("DEV.LobbyHost07=127.0.0.1 DEV.LobbyPort07=54994 ")
|
||||
.Append("DEV.LobbyHost08=127.0.0.1 DEV.LobbyPort08=54994 ")
|
||||
.Append("SYS.Region=0 language=1 version=1.0.0.0 ")
|
||||
.Append("DEV.MaxEntitledExpansionID=2 DEV.GMServerHost=127.0.0.1 DEV.GameQuitMessageBox=0").ToString();
|
||||
process = Process.Start(exePath, exeArgs);
|
||||
Thread.Sleep(1000);
|
||||
break;
|
||||
default:
|
||||
if (particularCommand is null or "launch")
|
||||
{
|
||||
Console.WriteLine("{0} launch [-h/--help] [-f/--fake-arguments]", exeName);
|
||||
Console.WriteLine("{0} [-g path/to/ffxiv_dx11.exe] [--game=path/to/ffxiv_dx11.exe]", exeSpaces);
|
||||
Console.WriteLine("{0} [-m entrypoint|inject] [--mode=entrypoint|inject]", exeSpaces);
|
||||
Console.WriteLine("{0} [--handle-owner=inherited-handle-value]", exeSpaces);
|
||||
Console.WriteLine("{0} [-- game_arg1=value1 game_arg2=value2 ...]", exeSpaces);
|
||||
}
|
||||
|
||||
Console.WriteLine("Specifying dalamud start info: [--dalamud-working-directory path] [--dalamud-configuration-path path]");
|
||||
Console.WriteLine(" [--dalamud-plugin-directory path] [--dalamud-dev-plugin-directory path]");
|
||||
Console.WriteLine(" [--dalamud-asset-directory path] [--dalamud-delay-initialize 0(ms)]");
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
private static int ProcessInjectCommand(List<string> args, DalamudStartInfo dalamudStartInfo)
|
||||
{
|
||||
List<Process> processes = new();
|
||||
|
||||
var targetProcessSpecified = false;
|
||||
var warnManualInjection = false;
|
||||
var showHelp = args.Count <= 2;
|
||||
|
||||
for (var i = 2; i < args.Count; i++)
|
||||
{
|
||||
if (int.TryParse(args[i], out int pid))
|
||||
{
|
||||
targetProcessSpecified = true;
|
||||
try
|
||||
{
|
||||
process = Process.GetProcessById(pid);
|
||||
processes.Add(Process.GetProcessById(pid));
|
||||
}
|
||||
catch (ArgumentException)
|
||||
{
|
||||
Log.Error("Could not find process with PID: {Pid}", pid);
|
||||
}
|
||||
|
||||
break;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (args[i] == "-h" || args[i] == "--help")
|
||||
{
|
||||
showHelp = true;
|
||||
}
|
||||
else if (args[i] == "-a" || args[i] == "--all")
|
||||
{
|
||||
targetProcessSpecified = true;
|
||||
processes.AddRange(Process.GetProcessesByName("ffxiv_dx11"));
|
||||
}
|
||||
else if (args[i] == "--warn")
|
||||
{
|
||||
warnManualInjection = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
Log.Error("\"{0}\" is not a valid argument.", args[i]);
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
return process;
|
||||
if (showHelp)
|
||||
{
|
||||
ProcessHelpCommand(args, "inject");
|
||||
return args.Count <= 2 ? -1 : 0;
|
||||
}
|
||||
|
||||
if (!targetProcessSpecified)
|
||||
{
|
||||
Log.Error("No target process has been specified.");
|
||||
return -1;
|
||||
}
|
||||
else if (!processes.Any())
|
||||
{
|
||||
Log.Error("No suitable target process has been found.");
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (warnManualInjection)
|
||||
{
|
||||
var result = MessageBoxW(IntPtr.Zero, $"Take care: you are manually injecting Dalamud into FFXIV({string.Join(", ", processes.Select(x => $"{x.Id}"))}).\n\nIf you are doing this to use plugins before they are officially whitelisted on patch days, things may go wrong and you may get into trouble.\nWe discourage you from doing this and you won't be warned again in-game.", "Dalamud", MessageBoxType.IconWarning | MessageBoxType.OkCancel);
|
||||
|
||||
// IDCANCEL
|
||||
if (result == 2)
|
||||
{
|
||||
Log.Information("User cancelled injection");
|
||||
return -2;
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var process in processes)
|
||||
Inject(process, AdjustStartInfo(dalamudStartInfo, process.MainModule.FileName));
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
private static DalamudStartInfo GetStartInfo(string? arg, Process process)
|
||||
private static int ProcessLaunchCommand(List<string> args, DalamudStartInfo dalamudStartInfo)
|
||||
{
|
||||
DalamudStartInfo startInfo;
|
||||
string? gamePath = null;
|
||||
List<string> gameArguments = new();
|
||||
string? mode = null;
|
||||
var useFakeArguments = false;
|
||||
var showHelp = args.Count <= 2;
|
||||
var handleOwner = IntPtr.Zero;
|
||||
|
||||
if (arg != default)
|
||||
var parsingGameArgument = false;
|
||||
for (var i = 2; i < args.Count; i++)
|
||||
{
|
||||
startInfo = JsonConvert.DeserializeObject<DalamudStartInfo>(Encoding.UTF8.GetString(Convert.FromBase64String(arg)));
|
||||
if (parsingGameArgument)
|
||||
{
|
||||
gameArguments.Add(args[i]);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (args[i] == "-h" || args[i] == "--help")
|
||||
{
|
||||
showHelp = true;
|
||||
}
|
||||
else if (args[i] == "-f" || args[i] == "--fake-arguments")
|
||||
{
|
||||
useFakeArguments = true;
|
||||
}
|
||||
else if (args[i] == "-g")
|
||||
{
|
||||
gamePath = args[++i];
|
||||
}
|
||||
else if (args[i].StartsWith("--game="))
|
||||
{
|
||||
gamePath = args[i].Split('=', 2)[1];
|
||||
}
|
||||
else if (args[i] == "-m")
|
||||
{
|
||||
mode = args[++i];
|
||||
}
|
||||
else if (args[i].StartsWith("--mode="))
|
||||
{
|
||||
gamePath = args[i].Split('=', 2)[1];
|
||||
}
|
||||
else if (args[i].StartsWith("--handle-owner="))
|
||||
{
|
||||
handleOwner = IntPtr.Parse(args[i].Split('=', 2)[1]);
|
||||
}
|
||||
else if (args[i] == "--")
|
||||
{
|
||||
parsingGameArgument = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
Log.Error("No such command found: {0}", args[i]);
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
if (showHelp)
|
||||
{
|
||||
ProcessHelpCommand(args, "launch");
|
||||
return args.Count <= 2 ? -1 : 0;
|
||||
}
|
||||
|
||||
mode = mode == null ? "entrypoint" : mode.ToLowerInvariant();
|
||||
if (mode.Length > 0 && mode.Length <= 10 && "entrypoint"[0..mode.Length] == mode)
|
||||
{
|
||||
mode = "entrypoint";
|
||||
}
|
||||
else if (mode.Length > 0 && mode.Length <= 6 && "inject"[0..mode.Length] == mode)
|
||||
{
|
||||
mode = "inject";
|
||||
}
|
||||
else
|
||||
{
|
||||
var ffxivDir = Path.GetDirectoryName(process.MainModule.FileName);
|
||||
var appDataDir = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData);
|
||||
var xivlauncherDir = Path.Combine(appDataDir, "XIVLauncher");
|
||||
|
||||
var gameVerStr = File.ReadAllText(Path.Combine(ffxivDir, "ffxivgame.ver"));
|
||||
var gameVer = GameVersion.Parse(gameVerStr);
|
||||
|
||||
startInfo = new DalamudStartInfo
|
||||
{
|
||||
WorkingDirectory = null,
|
||||
ConfigurationPath = Path.Combine(xivlauncherDir, "dalamudConfig.json"),
|
||||
PluginDirectory = Path.Combine(xivlauncherDir, "installedPlugins"),
|
||||
DefaultPluginDirectory = Path.Combine(xivlauncherDir, "devPlugins"),
|
||||
AssetDirectory = Path.Combine(xivlauncherDir, "dalamudAssets", "dev"),
|
||||
GameVersion = gameVer,
|
||||
Language = ClientLanguage.English,
|
||||
};
|
||||
|
||||
Log.Debug(
|
||||
"Creating a new StartInfo with:\n" +
|
||||
$" WorkingDirectory: {startInfo.WorkingDirectory}\n" +
|
||||
$" ConfigurationPath: {startInfo.ConfigurationPath}\n" +
|
||||
$" PluginDirectory: {startInfo.PluginDirectory}\n" +
|
||||
$" DefaultPluginDirectory: {startInfo.DefaultPluginDirectory}\n" +
|
||||
$" AssetDirectory: {startInfo.AssetDirectory}\n" +
|
||||
$" GameVersion: {startInfo.GameVersion}\n" +
|
||||
$" Language: {startInfo.Language}\n");
|
||||
|
||||
Log.Information("A Dalamud start info was not found in the program arguments. One has been generated for you.");
|
||||
Log.Information("Copy the following contents into the program arguments:");
|
||||
|
||||
var startInfoJson = Convert.ToBase64String(Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(startInfo)));
|
||||
Log.Information(startInfoJson);
|
||||
Log.Error("Invalid mode: {0}", mode);
|
||||
return -1;
|
||||
}
|
||||
|
||||
return startInfo;
|
||||
if (gamePath == null)
|
||||
{
|
||||
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);
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
gamePath = @"C:\Program Files (x86)\SquareEnix\FINAL FANTASY XIV - A Realm Reborn\game\ffxiv_dx11.exe";
|
||||
Log.Warning("Failed to read launcherConfigV3.json. Using default game installation path: {0}", gamePath);
|
||||
}
|
||||
|
||||
if (!File.Exists(gamePath))
|
||||
{
|
||||
Log.Error("File not found: {0}", gamePath);
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
if (useFakeArguments)
|
||||
{
|
||||
var gameVersion = File.ReadAllText(Path.Combine(Directory.GetParent(gamePath).FullName, "ffxivgame.ver"));
|
||||
var sqpackPath = Path.Combine(Directory.GetParent(gamePath).FullName, "sqpack");
|
||||
var maxEntitledExpansionId = 0;
|
||||
while (File.Exists(Path.Combine(sqpackPath, $"ex{maxEntitledExpansionId + 1}", $"ex{maxEntitledExpansionId + 1}.ver")))
|
||||
maxEntitledExpansionId++;
|
||||
|
||||
gameArguments.InsertRange(0, new string[]
|
||||
{
|
||||
"DEV.TestSID=0",
|
||||
"DEV.UseSqPack=1",
|
||||
"DEV.DataPathType=1",
|
||||
"DEV.LobbyHost01=127.0.0.1",
|
||||
"DEV.LobbyPort01=54994",
|
||||
"DEV.LobbyHost02=127.0.0.2",
|
||||
"DEV.LobbyPort02=54994",
|
||||
"DEV.LobbyHost03=127.0.0.3",
|
||||
"DEV.LobbyPort03=54994",
|
||||
"DEV.LobbyHost04=127.0.0.4",
|
||||
"DEV.LobbyPort04=54994",
|
||||
"DEV.LobbyHost05=127.0.0.5",
|
||||
"DEV.LobbyPort05=54994",
|
||||
"DEV.LobbyHost06=127.0.0.6",
|
||||
"DEV.LobbyPort06=54994",
|
||||
"DEV.LobbyHost07=127.0.0.7",
|
||||
"DEV.LobbyPort07=54994",
|
||||
"DEV.LobbyHost08=127.0.0.8",
|
||||
"DEV.LobbyPort08=54994",
|
||||
"DEV.LobbyHost09=127.0.0.9",
|
||||
"DEV.LobbyPort09=54994",
|
||||
"SYS.Region=0",
|
||||
"language=1",
|
||||
$"ver={gameVersion}",
|
||||
$"DEV.MaxEntitledExpansionID={maxEntitledExpansionId}",
|
||||
"DEV.GMServerHost=127.0.0.100",
|
||||
"DEV.GameQuitMessageBox=0",
|
||||
});
|
||||
}
|
||||
|
||||
var gameArgumentString = string.Join(" ", gameArguments.Select(x => EncodeParameterArgument(x)));
|
||||
var process = NativeAclFix.LaunchGame(Path.GetDirectoryName(gamePath), gamePath, gameArgumentString, (Process p) =>
|
||||
{
|
||||
if (mode == "entrypoint")
|
||||
{
|
||||
var startInfo = AdjustStartInfo(dalamudStartInfo, gamePath);
|
||||
Log.Information("Using start info: {0}", JsonConvert.SerializeObject(startInfo));
|
||||
if (RewriteRemoteEntryPointW(p.Handle, gamePath, JsonConvert.SerializeObject(startInfo)) != 0)
|
||||
{
|
||||
Log.Error("[HOOKS] RewriteRemoteEntryPointW failed");
|
||||
throw new Exception("RewriteRemoteEntryPointW failed");
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
if (mode == "inject")
|
||||
{
|
||||
var startInfo = AdjustStartInfo(dalamudStartInfo, gamePath);
|
||||
Log.Information("Using start info: {0}", JsonConvert.SerializeObject(startInfo));
|
||||
Inject(process, startInfo);
|
||||
}
|
||||
|
||||
var processHandleForOwner = IntPtr.Zero;
|
||||
if (handleOwner != IntPtr.Zero)
|
||||
{
|
||||
if (!DuplicateHandle(Process.GetCurrentProcess().Handle, process.Handle, handleOwner, out processHandleForOwner, 0, false, DuplicateOptions.SameAccess))
|
||||
Log.Warning("Failed to call DuplicateHandle: Win32 error code {0}", Marshal.GetLastWin32Error());
|
||||
}
|
||||
|
||||
Console.WriteLine($"{{\"pid\": {process.Id}, \"handle\": {processHandleForOwner}}}");
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
private static Process GetInheritableCurrentProcessHandle()
|
||||
{
|
||||
if (!DuplicateHandle(Process.GetCurrentProcess().Handle, Process.GetCurrentProcess().Handle, Process.GetCurrentProcess().Handle, out var inheritableCurrentProcessHandle, 0, true, DuplicateOptions.SameAccess))
|
||||
{
|
||||
Log.Error("Failed to call DuplicateHandle: Win32 error code {0}", Marshal.GetLastWin32Error());
|
||||
return null;
|
||||
}
|
||||
|
||||
return new ExistingProcess(inheritableCurrentProcessHandle);
|
||||
}
|
||||
|
||||
private static int ProcessLaunchTestCommand(List<string> args)
|
||||
{
|
||||
Console.WriteLine("Testing launch command.");
|
||||
args[0] = Process.GetCurrentProcess().MainModule.FileName;
|
||||
args[1] = "launch";
|
||||
|
||||
var inheritableCurrentProcess = GetInheritableCurrentProcessHandle(); // so that it closes the handle when it's done
|
||||
args.Insert(2, $"--handle-owner={inheritableCurrentProcess.Handle}");
|
||||
|
||||
for (var i = 0; i < args.Count; i++)
|
||||
Console.WriteLine("Argument {0}: {1}", i, args[i]);
|
||||
|
||||
Process helperProcess = new();
|
||||
helperProcess.StartInfo.FileName = args[0];
|
||||
for (var i = 1; i < args.Count; i++)
|
||||
helperProcess.StartInfo.ArgumentList.Add(args[i]);
|
||||
helperProcess.StartInfo.RedirectStandardOutput = true;
|
||||
helperProcess.StartInfo.RedirectStandardError = true;
|
||||
helperProcess.StartInfo.UseShellExecute = false;
|
||||
helperProcess.ErrorDataReceived += new DataReceivedEventHandler((sendingProcess, errLine) => Console.WriteLine($"stderr: \"{errLine.Data}\""));
|
||||
helperProcess.Start();
|
||||
helperProcess.BeginErrorReadLine();
|
||||
helperProcess.WaitForExit();
|
||||
if (helperProcess.ExitCode != 0)
|
||||
return -1;
|
||||
|
||||
var result = JsonSerializer.CreateDefault().Deserialize<Dictionary<string, int>>(new JsonTextReader(helperProcess.StandardOutput));
|
||||
var pid = result["pid"];
|
||||
var handle = (IntPtr)result["handle"];
|
||||
var resultProcess = new ExistingProcess(handle);
|
||||
Console.WriteLine("PID: {0}, Handle: {1}", pid, handle);
|
||||
Console.WriteLine("Press Enter to force quit");
|
||||
Console.ReadLine();
|
||||
resultProcess.Kill();
|
||||
return 0;
|
||||
}
|
||||
|
||||
private static DalamudStartInfo AdjustStartInfo(DalamudStartInfo startInfo, string gamePath)
|
||||
{
|
||||
var ffxivDir = Path.GetDirectoryName(gamePath);
|
||||
var gameVerStr = File.ReadAllText(Path.Combine(ffxivDir, "ffxivgame.ver"));
|
||||
var gameVer = GameVersion.Parse(gameVerStr);
|
||||
|
||||
return new()
|
||||
{
|
||||
WorkingDirectory = startInfo.WorkingDirectory,
|
||||
ConfigurationPath = startInfo.ConfigurationPath,
|
||||
PluginDirectory = startInfo.PluginDirectory,
|
||||
DefaultPluginDirectory = startInfo.DefaultPluginDirectory,
|
||||
AssetDirectory = startInfo.AssetDirectory,
|
||||
Language = ClientLanguage.English,
|
||||
GameVersion = gameVer,
|
||||
DelayInitializeMs = startInfo.DelayInitializeMs,
|
||||
};
|
||||
}
|
||||
|
||||
private static void Inject(Process process, DalamudStartInfo startInfo)
|
||||
{
|
||||
var nethostName = "nethost.dll";
|
||||
var bootName = "Dalamud.Boot.dll";
|
||||
|
||||
var nethostPath = Path.GetFullPath(nethostName);
|
||||
var bootPath = Path.GetFullPath(bootName);
|
||||
|
||||
// ======================================================
|
||||
|
||||
using var injector = new Injector(process);
|
||||
using var injector = new Injector(process, false);
|
||||
|
||||
injector.LoadLibrary(nethostPath, out _);
|
||||
injector.LoadLibrary(bootPath, out var bootModule);
|
||||
|
||||
// ======================================================
|
||||
|
|
@ -342,5 +666,73 @@ namespace Dalamud.Injector
|
|||
|
||||
Log.Information("Done");
|
||||
}
|
||||
|
||||
[DllImport("Dalamud.Boot.dll")]
|
||||
private static extern int RewriteRemoteEntryPointW(IntPtr hProcess, [MarshalAs(UnmanagedType.LPWStr)] string gamePath, [MarshalAs(UnmanagedType.LPWStr)] string loadInfoJson);
|
||||
|
||||
/// <summary>
|
||||
/// This routine appends the given argument to a command line such that
|
||||
/// CommandLineToArgvW will return the argument string unchanged. Arguments
|
||||
/// in a command line should be separated by spaces; this function does
|
||||
/// not add these spaces.
|
||||
///
|
||||
/// Taken from https://stackoverflow.com/questions/5510343/escape-command-line-arguments-in-c-sharp
|
||||
/// and https://blogs.msdn.microsoft.com/twistylittlepassagesallalike/2011/04/23/everyone-quotes-command-line-arguments-the-wrong-way/.
|
||||
/// </summary>
|
||||
/// <param name="argument">Supplies the argument to encode.</param>
|
||||
/// <param name="force">
|
||||
/// Supplies an indication of whether we should quote the argument even if it
|
||||
/// does not contain any characters that would ordinarily require quoting.
|
||||
/// </param>
|
||||
private static string EncodeParameterArgument(string argument, bool force = false)
|
||||
{
|
||||
if (argument == null) throw new ArgumentNullException(nameof(argument));
|
||||
|
||||
// Unless we're told otherwise, don't quote unless we actually
|
||||
// need to do so --- hopefully avoid problems if programs won't
|
||||
// parse quotes properly
|
||||
if (force == false
|
||||
&& argument.Length > 0
|
||||
&& argument.IndexOfAny(" \t\n\v\"".ToCharArray()) == -1)
|
||||
{
|
||||
return argument;
|
||||
}
|
||||
|
||||
var quoted = new StringBuilder();
|
||||
quoted.Append('"');
|
||||
|
||||
var numberBackslashes = 0;
|
||||
|
||||
foreach (var chr in argument)
|
||||
{
|
||||
switch (chr)
|
||||
{
|
||||
case '\\':
|
||||
numberBackslashes++;
|
||||
continue;
|
||||
case '"':
|
||||
// Escape all backslashes and the following
|
||||
// double quotation mark.
|
||||
quoted.Append('\\', (numberBackslashes * 2) + 1);
|
||||
quoted.Append(chr);
|
||||
break;
|
||||
default:
|
||||
// Backslashes aren't special here.
|
||||
quoted.Append('\\', numberBackslashes);
|
||||
quoted.Append(chr);
|
||||
break;
|
||||
}
|
||||
|
||||
numberBackslashes = 0;
|
||||
}
|
||||
|
||||
// Escape all backslashes, but let the terminating
|
||||
// double quotation mark we add below be interpreted
|
||||
// as a metacharacter.
|
||||
quoted.Append('\\', numberBackslashes * 2);
|
||||
quoted.Append('"');
|
||||
|
||||
return quoted.ToString();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
34
Dalamud.Injector/ExistingProcess.cs
Normal file
34
Dalamud.Injector/ExistingProcess.cs
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Reflection;
|
||||
|
||||
using Microsoft.Win32.SafeHandles;
|
||||
|
||||
namespace Dalamud.Injector;
|
||||
|
||||
/// <summary>
|
||||
/// Class representing an already held process handle.
|
||||
/// </summary>
|
||||
internal class ExistingProcess : Process
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ExistingProcess"/> class.
|
||||
/// </summary>
|
||||
/// <param name="handle">The existing held process handle.</param>
|
||||
public ExistingProcess(IntPtr handle)
|
||||
{
|
||||
this.SetHandle(handle);
|
||||
}
|
||||
|
||||
private void SetHandle(IntPtr handle)
|
||||
{
|
||||
var baseType = this.GetType().BaseType;
|
||||
if (baseType == null)
|
||||
return;
|
||||
|
||||
var setProcessHandleMethod = baseType.GetMethod(
|
||||
"SetProcessHandle",
|
||||
BindingFlags.NonPublic | BindingFlags.Instance);
|
||||
setProcessHandleMethod?.Invoke(this, new object[] { new SafeProcessHandle(handle, true) });
|
||||
}
|
||||
}
|
||||
|
|
@ -1,4 +1,5 @@
|
|||
using System;
|
||||
using System.ComponentModel;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
|
|
@ -26,6 +27,7 @@ namespace Dalamud.Injector
|
|||
internal sealed class Injector : IDisposable
|
||||
{
|
||||
private readonly Process targetProcess;
|
||||
private readonly bool disposeTargetProcess;
|
||||
private readonly ExternalMemory extMemory;
|
||||
private readonly CircularBuffer circularBuffer;
|
||||
private readonly PrivateMemoryBuffer memoryBuffer;
|
||||
|
|
@ -40,9 +42,11 @@ namespace Dalamud.Injector
|
|||
/// Initializes a new instance of the <see cref="Injector"/> class.
|
||||
/// </summary>
|
||||
/// <param name="targetProcess">Process to inject.</param>
|
||||
public Injector(Process targetProcess)
|
||||
/// <param name="disposeTargetProcess">Dispose given process on disposing self.</param>
|
||||
public Injector(Process targetProcess, bool disposeTargetProcess = true)
|
||||
{
|
||||
this.targetProcess = targetProcess;
|
||||
this.disposeTargetProcess = disposeTargetProcess;
|
||||
|
||||
this.extMemory = new ExternalMemory(targetProcess);
|
||||
this.circularBuffer = new CircularBuffer(4096, this.extMemory);
|
||||
|
|
@ -66,7 +70,8 @@ namespace Dalamud.Injector
|
|||
{
|
||||
GC.SuppressFinalize(this);
|
||||
|
||||
this.targetProcess?.Dispose();
|
||||
if (this.disposeTargetProcess)
|
||||
this.targetProcess?.Dispose();
|
||||
this.circularBuffer?.Dispose();
|
||||
this.memoryBuffer?.Dispose();
|
||||
}
|
||||
|
|
@ -83,23 +88,10 @@ namespace Dalamud.Injector
|
|||
if (lpParameter == IntPtr.Zero)
|
||||
throw new Exception("Unable to allocate LoadLibraryW parameter");
|
||||
|
||||
Log.Verbose($"CreateRemoteThread: call 0x{this.loadLibraryShellPtr.ToInt64():X} with 0x{lpParameter.ToInt64():X}");
|
||||
|
||||
var threadHandle = CreateRemoteThread(
|
||||
this.targetProcess.Handle,
|
||||
IntPtr.Zero,
|
||||
UIntPtr.Zero,
|
||||
this.loadLibraryShellPtr,
|
||||
lpParameter,
|
||||
CreateThreadFlags.RunImmediately,
|
||||
out _);
|
||||
|
||||
_ = WaitForSingleObject(threadHandle, uint.MaxValue);
|
||||
|
||||
this.CallRemoteFunction(this.loadLibraryShellPtr, lpParameter, out var err);
|
||||
address = this.extMemory.Read<IntPtr>(this.loadLibraryRetPtr);
|
||||
|
||||
if (address == IntPtr.Zero)
|
||||
throw new Exception($"Error calling LoadLibraryW with {modulePath}");
|
||||
throw new Exception($"LoadLibraryW(\"{modulePath}\") failure: {new Win32Exception((int)err).Message} ({err})");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -113,25 +105,13 @@ namespace Dalamud.Injector
|
|||
var functionNamePtr = this.WriteNullTerminatedASCIIString(functionName);
|
||||
var getProcAddressParams = new GetProcAddressParams(module, functionNamePtr);
|
||||
var lpParameter = this.circularBuffer.Add(ref getProcAddressParams);
|
||||
|
||||
if (lpParameter == IntPtr.Zero)
|
||||
throw new Exception("Unable to allocate GetProcAddress parameter ptr");
|
||||
|
||||
var threadHandle = CreateRemoteThread(
|
||||
this.targetProcess.Handle,
|
||||
IntPtr.Zero,
|
||||
UIntPtr.Zero,
|
||||
this.getProcAddressShellPtr,
|
||||
lpParameter,
|
||||
CreateThreadFlags.RunImmediately,
|
||||
out _);
|
||||
|
||||
_ = WaitForSingleObject(threadHandle, uint.MaxValue);
|
||||
|
||||
this.extMemory.Read(this.getProcAddressRetPtr, out address);
|
||||
|
||||
this.CallRemoteFunction(this.getProcAddressShellPtr, lpParameter, out var err);
|
||||
address = this.extMemory.Read<IntPtr>(this.getProcAddressRetPtr);
|
||||
if (address == IntPtr.Zero)
|
||||
throw new Exception($"Error calling GetProcAddress with {functionName}");
|
||||
throw new Exception($"GetProcAddress(0x{module:X}, \"{functionName}\") failure: {new Win32Exception((int)err).Message} ({err})");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -152,6 +132,9 @@ namespace Dalamud.Injector
|
|||
CreateThreadFlags.RunImmediately,
|
||||
out _);
|
||||
|
||||
if (threadHandle == IntPtr.Zero)
|
||||
throw new Exception($"CreateRemoteThread failure: {Marshal.GetLastWin32Error()}");
|
||||
|
||||
_ = WaitForSingleObject(threadHandle, uint.MaxValue);
|
||||
|
||||
GetExitCodeThread(threadHandle, out exitCode);
|
||||
|
|
@ -161,8 +144,10 @@ namespace Dalamud.Injector
|
|||
|
||||
private void SetupLoadLibrary(ProcessModule kernel32Module, ExportFunction[] kernel32Exports)
|
||||
{
|
||||
var offset = this.GetExportedFunctionOffset(kernel32Exports, "LoadLibraryW");
|
||||
var functionAddr = kernel32Module.BaseAddress + (int)offset;
|
||||
var getLastErrorAddr = kernel32Module.BaseAddress + (int)this.GetExportedFunctionOffset(kernel32Exports, "GetLastError");
|
||||
Log.Verbose($"GetLastError: 0x{getLastErrorAddr.ToInt64():X}");
|
||||
|
||||
var functionAddr = kernel32Module.BaseAddress + (int)this.GetExportedFunctionOffset(kernel32Exports, "LoadLibraryW");
|
||||
Log.Verbose($"LoadLibraryW: 0x{functionAddr.ToInt64():X}");
|
||||
|
||||
var functionPtr = this.memoryBuffer.Add(ref functionAddr);
|
||||
|
|
@ -187,7 +172,9 @@ namespace Dalamud.Injector
|
|||
asm.call(__qword_ptr[__qword_ptr[func]]); // call qword [qword func] // CreateRemoteThread lpParameter with string already in ECX.
|
||||
asm.mov(__qword_ptr[__qword_ptr[retVal]], rax); // mov qword [qword retVal], rax //
|
||||
asm.add(rsp, 40); // add rsp, 40 // Re-align stack to 16 byte boundary + shadow space.
|
||||
asm.ret(); // ret // Restore stack ptr. (Callee cleanup)
|
||||
asm.mov(rax, (ulong)getLastErrorAddr); // mov rax, pfnGetLastError // Change return address to GetLastError.
|
||||
asm.push(rax); // push rax //
|
||||
asm.ret(); // ret // Jump to GetLastError.
|
||||
|
||||
var bytes = this.Assemble(asm);
|
||||
this.loadLibraryShellPtr = this.memoryBuffer.Add(bytes);
|
||||
|
|
@ -212,6 +199,9 @@ namespace Dalamud.Injector
|
|||
|
||||
private void SetupGetProcAddress(ProcessModule kernel32Module, ExportFunction[] kernel32Exports)
|
||||
{
|
||||
var getLastErrorAddr = kernel32Module.BaseAddress + (int)this.GetExportedFunctionOffset(kernel32Exports, "GetLastError");
|
||||
Log.Verbose($"GetLastError: 0x{getLastErrorAddr.ToInt64():X}");
|
||||
|
||||
var offset = this.GetExportedFunctionOffset(kernel32Exports, "GetProcAddress");
|
||||
var functionAddr = kernel32Module.BaseAddress + (int)offset;
|
||||
Log.Verbose($"GetProcAddress: 0x{functionAddr.ToInt64():X}");
|
||||
|
|
@ -240,7 +230,9 @@ namespace Dalamud.Injector
|
|||
asm.call(__qword_ptr[__qword_ptr[func]]); // call qword [qword func] //
|
||||
asm.mov(__qword_ptr[__qword_ptr[retVal]], rax); // mov qword [qword retVal] //
|
||||
asm.add(rsp, 40); // add rsp, 40 // Re-align stack to 16 byte boundary + shadow space.
|
||||
asm.ret(); // ret // Restore stack ptr. (Callee cleanup)
|
||||
asm.mov(rax, (ulong)getLastErrorAddr); // mov rax, pfnGetLastError // Change return address to GetLastError.
|
||||
asm.push(rax); // push rax //
|
||||
asm.ret(); // ret // Jump to GetLastError.
|
||||
|
||||
var bytes = this.Assemble(asm);
|
||||
this.getProcAddressShellPtr = this.memoryBuffer.Add(bytes);
|
||||
|
|
|
|||
563
Dalamud.Injector/NativeAclFix.cs
Normal file
563
Dalamud.Injector/NativeAclFix.cs
Normal file
|
|
@ -0,0 +1,563 @@
|
|||
using System;
|
||||
using System.ComponentModel;
|
||||
using System.Diagnostics;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Threading;
|
||||
|
||||
using Serilog;
|
||||
|
||||
// ReSharper disable InconsistentNaming
|
||||
|
||||
namespace Dalamud.Injector
|
||||
{
|
||||
/// <summary>
|
||||
/// Class responsible for stripping ACL protections from processes.
|
||||
/// </summary>
|
||||
public static class NativeAclFix
|
||||
{
|
||||
/// <summary>
|
||||
/// Start a process without ACL protections.
|
||||
/// </summary>
|
||||
/// <param name="workingDir">The working directory.</param>
|
||||
/// <param name="exePath">The path to the executable file.</param>
|
||||
/// <param name="arguments">Arguments to pass to the executable file.</param>
|
||||
/// <param name="beforeResume">Action to execute before the process is started.</param>
|
||||
/// <returns>The started process.</returns>
|
||||
/// <exception cref="Win32Exception">Thrown when a win32 error occurs.</exception>
|
||||
/// <exception cref="GameExitedException">Thrown when the process did not start correctly.</exception>
|
||||
public static Process LaunchGame(string workingDir, string exePath, string arguments, Action<Process> beforeResume)
|
||||
{
|
||||
Process process = null;
|
||||
|
||||
var userName = Environment.UserName;
|
||||
|
||||
var pExplicitAccess = default(PInvoke.EXPLICIT_ACCESS);
|
||||
PInvoke.BuildExplicitAccessWithName(
|
||||
ref pExplicitAccess,
|
||||
userName,
|
||||
PInvoke.STANDARD_RIGHTS_ALL | PInvoke.SPECIFIC_RIGHTS_ALL & ~PInvoke.PROCESS_VM_WRITE,
|
||||
PInvoke.GRANT_ACCESS,
|
||||
0);
|
||||
|
||||
if (PInvoke.SetEntriesInAcl(1, ref pExplicitAccess, IntPtr.Zero, out var newAcl) != 0)
|
||||
{
|
||||
throw new Win32Exception(Marshal.GetLastWin32Error());
|
||||
}
|
||||
|
||||
if (!PInvoke.InitializeSecurityDescriptor(out var secDesc, PInvoke.SECURITY_DESCRIPTOR_REVISION))
|
||||
{
|
||||
throw new Win32Exception(Marshal.GetLastWin32Error());
|
||||
}
|
||||
|
||||
if (!PInvoke.SetSecurityDescriptorDacl(ref secDesc, true, newAcl, false))
|
||||
{
|
||||
throw new Win32Exception(Marshal.GetLastWin32Error());
|
||||
}
|
||||
|
||||
var psecDesc = Marshal.AllocHGlobal(Marshal.SizeOf<PInvoke.SECURITY_DESCRIPTOR>());
|
||||
Marshal.StructureToPtr(secDesc, psecDesc, true);
|
||||
|
||||
var lpProcessInformation = default(PInvoke.PROCESS_INFORMATION);
|
||||
try
|
||||
{
|
||||
var lpProcessAttributes = new PInvoke.SECURITY_ATTRIBUTES
|
||||
{
|
||||
nLength = Marshal.SizeOf<PInvoke.SECURITY_ATTRIBUTES>(),
|
||||
lpSecurityDescriptor = psecDesc,
|
||||
bInheritHandle = false,
|
||||
};
|
||||
|
||||
var lpStartupInfo = new PInvoke.STARTUPINFO
|
||||
{
|
||||
cb = Marshal.SizeOf<PInvoke.STARTUPINFO>(),
|
||||
};
|
||||
|
||||
var compatLayerPrev = Environment.GetEnvironmentVariable("__COMPAT_LAYER");
|
||||
|
||||
Environment.SetEnvironmentVariable("__COMPAT_LAYER", "RunAsInvoker");
|
||||
try
|
||||
{
|
||||
if (!PInvoke.CreateProcess(
|
||||
null,
|
||||
$"\"{exePath}\" {arguments}",
|
||||
ref lpProcessAttributes,
|
||||
IntPtr.Zero,
|
||||
false,
|
||||
PInvoke.CREATE_SUSPENDED,
|
||||
IntPtr.Zero,
|
||||
workingDir,
|
||||
ref lpStartupInfo,
|
||||
out lpProcessInformation))
|
||||
{
|
||||
throw new Win32Exception(Marshal.GetLastWin32Error());
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
Environment.SetEnvironmentVariable("__COMPAT_LAYER", compatLayerPrev);
|
||||
}
|
||||
|
||||
DisableSeDebug(lpProcessInformation.hProcess);
|
||||
|
||||
process = new ExistingProcess(lpProcessInformation.hProcess);
|
||||
|
||||
beforeResume?.Invoke(process);
|
||||
|
||||
PInvoke.ResumeThread(lpProcessInformation.hThread);
|
||||
|
||||
// Ensure that the game main window is prepared
|
||||
try
|
||||
{
|
||||
do
|
||||
{
|
||||
process.WaitForInputIdle();
|
||||
|
||||
Thread.Sleep(100);
|
||||
}
|
||||
while (TryFindGameWindow(process) == IntPtr.Zero);
|
||||
}
|
||||
catch (InvalidOperationException)
|
||||
{
|
||||
throw new GameExitedException();
|
||||
}
|
||||
|
||||
if (PInvoke.GetSecurityInfo(
|
||||
PInvoke.GetCurrentProcess(),
|
||||
PInvoke.SE_OBJECT_TYPE.SE_KERNEL_OBJECT,
|
||||
PInvoke.SECURITY_INFORMATION.DACL_SECURITY_INFORMATION,
|
||||
IntPtr.Zero,
|
||||
IntPtr.Zero,
|
||||
out var pACL,
|
||||
IntPtr.Zero,
|
||||
IntPtr.Zero) != 0)
|
||||
{
|
||||
throw new Win32Exception(Marshal.GetLastWin32Error());
|
||||
}
|
||||
|
||||
if (PInvoke.SetSecurityInfo(
|
||||
lpProcessInformation.hProcess,
|
||||
PInvoke.SE_OBJECT_TYPE.SE_KERNEL_OBJECT,
|
||||
PInvoke.SECURITY_INFORMATION.DACL_SECURITY_INFORMATION | PInvoke.SECURITY_INFORMATION.UNPROTECTED_DACL_SECURITY_INFORMATION,
|
||||
IntPtr.Zero,
|
||||
IntPtr.Zero,
|
||||
pACL,
|
||||
IntPtr.Zero) != 0)
|
||||
{
|
||||
throw new Win32Exception(Marshal.GetLastWin32Error());
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error(ex, "[NativeAclFix] Uncaught error during initialization, trying to kill process");
|
||||
|
||||
try
|
||||
{
|
||||
process?.Kill();
|
||||
}
|
||||
catch (Exception killEx)
|
||||
{
|
||||
Log.Error(killEx, "[NativeAclFix] Could not kill process");
|
||||
}
|
||||
|
||||
throw;
|
||||
}
|
||||
finally
|
||||
{
|
||||
Marshal.FreeHGlobal(psecDesc);
|
||||
PInvoke.CloseHandle(lpProcessInformation.hThread);
|
||||
}
|
||||
|
||||
return process;
|
||||
}
|
||||
|
||||
private static void DisableSeDebug(IntPtr processHandle)
|
||||
{
|
||||
if (!PInvoke.OpenProcessToken(processHandle, PInvoke.TOKEN_QUERY | PInvoke.TOKEN_ADJUST_PRIVILEGES, out var tokenHandle))
|
||||
{
|
||||
throw new Win32Exception(Marshal.GetLastWin32Error());
|
||||
}
|
||||
|
||||
var luidDebugPrivilege = default(PInvoke.LUID);
|
||||
if (!PInvoke.LookupPrivilegeValue(null, "SeDebugPrivilege", ref luidDebugPrivilege))
|
||||
{
|
||||
throw new Win32Exception(Marshal.GetLastWin32Error());
|
||||
}
|
||||
|
||||
var requiredPrivileges = new PInvoke.PRIVILEGE_SET
|
||||
{
|
||||
PrivilegeCount = 1,
|
||||
Control = PInvoke.PRIVILEGE_SET_ALL_NECESSARY,
|
||||
Privilege = new PInvoke.LUID_AND_ATTRIBUTES[1],
|
||||
};
|
||||
|
||||
requiredPrivileges.Privilege[0].Luid = luidDebugPrivilege;
|
||||
requiredPrivileges.Privilege[0].Attributes = PInvoke.SE_PRIVILEGE_ENABLED;
|
||||
|
||||
if (!PInvoke.PrivilegeCheck(tokenHandle, ref requiredPrivileges, out bool bResult))
|
||||
{
|
||||
throw new Win32Exception(Marshal.GetLastWin32Error());
|
||||
}
|
||||
|
||||
// SeDebugPrivilege is enabled; try disabling it
|
||||
if (bResult)
|
||||
{
|
||||
var tokenPrivileges = new PInvoke.TOKEN_PRIVILEGES
|
||||
{
|
||||
PrivilegeCount = 1,
|
||||
Privileges = new PInvoke.LUID_AND_ATTRIBUTES[1],
|
||||
};
|
||||
|
||||
tokenPrivileges.Privileges[0].Luid = luidDebugPrivilege;
|
||||
tokenPrivileges.Privileges[0].Attributes = PInvoke.SE_PRIVILEGE_REMOVED;
|
||||
|
||||
if (!PInvoke.AdjustTokenPrivileges(tokenHandle, false, ref tokenPrivileges, 0, IntPtr.Zero, 0))
|
||||
{
|
||||
throw new Win32Exception(Marshal.GetLastWin32Error());
|
||||
}
|
||||
}
|
||||
|
||||
PInvoke.CloseHandle(tokenHandle);
|
||||
}
|
||||
|
||||
private static IntPtr TryFindGameWindow(Process process)
|
||||
{
|
||||
IntPtr hwnd = IntPtr.Zero;
|
||||
while ((hwnd = PInvoke.FindWindowEx(IntPtr.Zero, hwnd, "FFXIVGAME", IntPtr.Zero)) != IntPtr.Zero)
|
||||
{
|
||||
PInvoke.GetWindowThreadProcessId(hwnd, out uint pid);
|
||||
|
||||
if (pid == process.Id && PInvoke.IsWindowVisible(hwnd))
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return hwnd;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Exception thrown when the process has exited before a window could be found.
|
||||
/// </summary>
|
||||
public class GameExitedException : Exception
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="GameExitedException"/> class.
|
||||
/// </summary>
|
||||
public GameExitedException()
|
||||
: base("Game exited prematurely.")
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
// Definitions taken from PInvoke.net (with some changes)
|
||||
[SuppressMessage("StyleCop.CSharp.NamingRules", "SA1307:Accessible fields should begin with upper-case letter", Justification = "WINAPI conventions")]
|
||||
[SuppressMessage("StyleCop.CSharp.ReadabilityRules", "SA1121:Use built-in type alias", Justification = "WINAPI conventions")]
|
||||
[SuppressMessage("StyleCop.CSharp.MaintainabilityRules", "SA1400:Access modifier should be declared", Justification = "WINAPI conventions")]
|
||||
[SuppressMessage("StyleCop.CSharp.NamingRules", "SA1306:Field names should begin with lower-case letter", Justification = "WINAPI conventions")]
|
||||
[SuppressMessage("StyleCop.CSharp.NamingRules", "SA1310:Field names should not contain underscore", Justification = "WINAPI conventions")]
|
||||
[SuppressMessage("StyleCop.CSharp.ReadabilityRules", "SA1124:Do not use regions", Justification = "WINAPI conventions")]
|
||||
private static class PInvoke
|
||||
{
|
||||
#region Constants
|
||||
public const UInt32 STANDARD_RIGHTS_ALL = 0x001F0000;
|
||||
public const UInt32 SPECIFIC_RIGHTS_ALL = 0x0000FFFF;
|
||||
public const UInt32 PROCESS_VM_WRITE = 0x0020;
|
||||
|
||||
public const UInt32 GRANT_ACCESS = 1;
|
||||
|
||||
public const UInt32 SECURITY_DESCRIPTOR_REVISION = 1;
|
||||
|
||||
public const UInt32 CREATE_SUSPENDED = 0x00000004;
|
||||
|
||||
public const UInt32 TOKEN_QUERY = 0x0008;
|
||||
public const UInt32 TOKEN_ADJUST_PRIVILEGES = 0x0020;
|
||||
|
||||
public const UInt32 PRIVILEGE_SET_ALL_NECESSARY = 1;
|
||||
|
||||
public const UInt32 SE_PRIVILEGE_ENABLED = 0x00000002;
|
||||
public const UInt32 SE_PRIVILEGE_REMOVED = 0x00000004;
|
||||
|
||||
public enum MULTIPLE_TRUSTEE_OPERATION
|
||||
{
|
||||
NO_MULTIPLE_TRUSTEE,
|
||||
TRUSTEE_IS_IMPERSONATE,
|
||||
}
|
||||
|
||||
public enum TRUSTEE_FORM
|
||||
{
|
||||
TRUSTEE_IS_SID,
|
||||
TRUSTEE_IS_NAME,
|
||||
TRUSTEE_BAD_FORM,
|
||||
TRUSTEE_IS_OBJECTS_AND_SID,
|
||||
TRUSTEE_IS_OBJECTS_AND_NAME,
|
||||
}
|
||||
|
||||
public enum TRUSTEE_TYPE
|
||||
{
|
||||
TRUSTEE_IS_UNKNOWN,
|
||||
TRUSTEE_IS_USER,
|
||||
TRUSTEE_IS_GROUP,
|
||||
TRUSTEE_IS_DOMAIN,
|
||||
TRUSTEE_IS_ALIAS,
|
||||
TRUSTEE_IS_WELL_KNOWN_GROUP,
|
||||
TRUSTEE_IS_DELETED,
|
||||
TRUSTEE_IS_INVALID,
|
||||
TRUSTEE_IS_COMPUTER,
|
||||
}
|
||||
|
||||
public enum SE_OBJECT_TYPE
|
||||
{
|
||||
SE_UNKNOWN_OBJECT_TYPE,
|
||||
SE_FILE_OBJECT,
|
||||
SE_SERVICE,
|
||||
SE_PRINTER,
|
||||
SE_REGISTRY_KEY,
|
||||
SE_LMSHARE,
|
||||
SE_KERNEL_OBJECT,
|
||||
SE_WINDOW_OBJECT,
|
||||
SE_DS_OBJECT,
|
||||
SE_DS_OBJECT_ALL,
|
||||
SE_PROVIDER_DEFINED_OBJECT,
|
||||
SE_WMIGUID_OBJECT,
|
||||
SE_REGISTRY_WOW64_32KEY,
|
||||
}
|
||||
|
||||
[Flags]
|
||||
public enum SECURITY_INFORMATION
|
||||
{
|
||||
OWNER_SECURITY_INFORMATION = 1,
|
||||
GROUP_SECURITY_INFORMATION = 2,
|
||||
DACL_SECURITY_INFORMATION = 4,
|
||||
SACL_SECURITY_INFORMATION = 8,
|
||||
UNPROTECTED_SACL_SECURITY_INFORMATION = 0x10000000,
|
||||
UNPROTECTED_DACL_SECURITY_INFORMATION = 0x20000000,
|
||||
PROTECTED_SACL_SECURITY_INFORMATION = 0x40000000,
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Methods
|
||||
|
||||
[DllImport("advapi32.dll", SetLastError = true, CharSet = CharSet.Auto)]
|
||||
public static extern void BuildExplicitAccessWithName(
|
||||
ref EXPLICIT_ACCESS pExplicitAccess,
|
||||
string pTrusteeName,
|
||||
uint accessPermissions,
|
||||
uint accessMode,
|
||||
uint inheritance);
|
||||
|
||||
[DllImport("advapi32.dll", SetLastError = true, CharSet = CharSet.Auto)]
|
||||
public static extern int SetEntriesInAcl(
|
||||
int cCountOfExplicitEntries,
|
||||
ref EXPLICIT_ACCESS pListOfExplicitEntries,
|
||||
IntPtr oldAcl,
|
||||
out IntPtr newAcl);
|
||||
|
||||
[DllImport("advapi32.dll", SetLastError = true)]
|
||||
public static extern bool InitializeSecurityDescriptor(
|
||||
out SECURITY_DESCRIPTOR pSecurityDescriptor,
|
||||
uint dwRevision);
|
||||
|
||||
[DllImport("advapi32.dll", SetLastError = true)]
|
||||
public static extern bool SetSecurityDescriptorDacl(
|
||||
ref SECURITY_DESCRIPTOR pSecurityDescriptor,
|
||||
bool bDaclPresent,
|
||||
IntPtr pDacl,
|
||||
bool bDaclDefaulted);
|
||||
|
||||
[DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Auto)]
|
||||
public static extern bool CreateProcess(
|
||||
string lpApplicationName,
|
||||
string lpCommandLine,
|
||||
ref SECURITY_ATTRIBUTES lpProcessAttributes,
|
||||
IntPtr lpThreadAttributes,
|
||||
bool bInheritHandles,
|
||||
UInt32 dwCreationFlags,
|
||||
IntPtr lpEnvironment,
|
||||
string lpCurrentDirectory,
|
||||
[In] ref STARTUPINFO lpStartupInfo,
|
||||
out PROCESS_INFORMATION lpProcessInformation);
|
||||
|
||||
[DllImport("kernel32.dll", SetLastError = true)]
|
||||
public static extern bool CloseHandle(IntPtr hObject);
|
||||
|
||||
[DllImport("kernel32.dll", SetLastError = true)]
|
||||
public static extern uint ResumeThread(IntPtr hThread);
|
||||
|
||||
[DllImport("advapi32.dll", SetLastError = true)]
|
||||
public static extern bool OpenProcessToken(
|
||||
IntPtr processHandle,
|
||||
UInt32 desiredAccess,
|
||||
out IntPtr tokenHandle);
|
||||
|
||||
[DllImport("advapi32.dll", SetLastError = true)]
|
||||
public static extern bool LookupPrivilegeValue(string lpSystemName, string lpName, ref LUID lpLuid);
|
||||
|
||||
[DllImport("advapi32.dll", SetLastError = true)]
|
||||
public static extern bool PrivilegeCheck(
|
||||
IntPtr clientToken,
|
||||
ref PRIVILEGE_SET requiredPrivileges,
|
||||
out bool pfResult);
|
||||
|
||||
[DllImport("advapi32.dll", SetLastError = true)]
|
||||
public static extern bool AdjustTokenPrivileges(
|
||||
IntPtr tokenHandle,
|
||||
bool disableAllPrivileges,
|
||||
ref TOKEN_PRIVILEGES newState,
|
||||
UInt32 bufferLengthInBytes,
|
||||
IntPtr previousState,
|
||||
UInt32 returnLengthInBytes);
|
||||
|
||||
[DllImport("advapi32.dll", SetLastError = true)]
|
||||
public static extern uint GetSecurityInfo(
|
||||
IntPtr handle,
|
||||
SE_OBJECT_TYPE objectType,
|
||||
SECURITY_INFORMATION securityInfo,
|
||||
IntPtr pSidOwner,
|
||||
IntPtr pSidGroup,
|
||||
out IntPtr pDacl,
|
||||
IntPtr pSacl,
|
||||
IntPtr pSecurityDescriptor);
|
||||
|
||||
[DllImport("advapi32.dll", SetLastError = true)]
|
||||
public static extern uint SetSecurityInfo(
|
||||
IntPtr handle,
|
||||
SE_OBJECT_TYPE objectType,
|
||||
SECURITY_INFORMATION securityInfo,
|
||||
IntPtr psidOwner,
|
||||
IntPtr psidGroup,
|
||||
IntPtr pDacl,
|
||||
IntPtr pSacl);
|
||||
|
||||
[DllImport("kernel32.dll", SetLastError = true)]
|
||||
public static extern IntPtr GetCurrentProcess();
|
||||
|
||||
[DllImport("user32.dll", SetLastError = true)]
|
||||
public static extern IntPtr FindWindowEx(IntPtr parentHandle, IntPtr hWndChildAfter, string className, IntPtr windowTitle);
|
||||
|
||||
[DllImport("user32.dll", SetLastError = true)]
|
||||
public static extern uint GetWindowThreadProcessId(IntPtr hWnd, out uint lpdwProcessId);
|
||||
|
||||
[DllImport("user32.dll")]
|
||||
[return: MarshalAs(UnmanagedType.Bool)]
|
||||
public static extern bool IsWindowVisible(IntPtr hWnd);
|
||||
|
||||
#endregion
|
||||
|
||||
#region Structures
|
||||
|
||||
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto, Pack = 0)]
|
||||
public struct TRUSTEE : IDisposable
|
||||
{
|
||||
public IntPtr pMultipleTrustee;
|
||||
public MULTIPLE_TRUSTEE_OPERATION MultipleTrusteeOperation;
|
||||
public TRUSTEE_FORM TrusteeForm;
|
||||
public TRUSTEE_TYPE TrusteeType;
|
||||
private IntPtr ptstrName;
|
||||
|
||||
public string Name => Marshal.PtrToStringAuto(this.ptstrName) ?? string.Empty;
|
||||
|
||||
#pragma warning disable CA1416
|
||||
|
||||
void IDisposable.Dispose()
|
||||
{
|
||||
if (this.ptstrName != IntPtr.Zero) Marshal.Release(this.ptstrName);
|
||||
}
|
||||
|
||||
#pragma warning restore CA1416
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto, Pack = 0)]
|
||||
public struct EXPLICIT_ACCESS
|
||||
{
|
||||
uint grfAccessPermissions;
|
||||
uint grfAccessMode;
|
||||
uint grfInheritance;
|
||||
TRUSTEE Trustee;
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
public struct SECURITY_DESCRIPTOR
|
||||
{
|
||||
public byte Revision;
|
||||
public byte Sbz1;
|
||||
public UInt16 Control;
|
||||
public IntPtr Owner;
|
||||
public IntPtr Group;
|
||||
public IntPtr Sacl;
|
||||
public IntPtr Dacl;
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
|
||||
public struct STARTUPINFO
|
||||
{
|
||||
public Int32 cb;
|
||||
public string lpReserved;
|
||||
public string lpDesktop;
|
||||
public string lpTitle;
|
||||
public Int32 dwX;
|
||||
public Int32 dwY;
|
||||
public Int32 dwXSize;
|
||||
public Int32 dwYSize;
|
||||
public Int32 dwXCountChars;
|
||||
public Int32 dwYCountChars;
|
||||
public Int32 dwFillAttribute;
|
||||
public Int32 dwFlags;
|
||||
public Int16 wShowWindow;
|
||||
public Int16 cbReserved2;
|
||||
public IntPtr lpReserved2;
|
||||
public IntPtr hStdInput;
|
||||
public IntPtr hStdOutput;
|
||||
public IntPtr hStdError;
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
public struct PROCESS_INFORMATION
|
||||
{
|
||||
public IntPtr hProcess;
|
||||
public IntPtr hThread;
|
||||
public int dwProcessId;
|
||||
public UInt32 dwThreadId;
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
public struct SECURITY_ATTRIBUTES
|
||||
{
|
||||
public int nLength;
|
||||
public IntPtr lpSecurityDescriptor;
|
||||
public bool bInheritHandle;
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
public struct LUID
|
||||
{
|
||||
public UInt32 LowPart;
|
||||
public Int32 HighPart;
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
public struct PRIVILEGE_SET
|
||||
{
|
||||
public UInt32 PrivilegeCount;
|
||||
public UInt32 Control;
|
||||
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 1)]
|
||||
public LUID_AND_ATTRIBUTES[] Privilege;
|
||||
}
|
||||
|
||||
public struct LUID_AND_ATTRIBUTES
|
||||
{
|
||||
public LUID Luid;
|
||||
public UInt32 Attributes;
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
public struct TOKEN_PRIVILEGES
|
||||
{
|
||||
public UInt32 PrivilegeCount;
|
||||
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 1)]
|
||||
public LUID_AND_ATTRIBUTES[] Privileges;
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -364,6 +364,23 @@ namespace Dalamud.Injector
|
|||
StackSizeParamIsReservation = 0x10000,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// DUPLICATE_* values for DuplicateHandle's dwDesiredAccess.
|
||||
/// </summary>
|
||||
[Flags]
|
||||
public enum DuplicateOptions : uint
|
||||
{
|
||||
/// <summary>
|
||||
/// Closes the source handle. This occurs regardless of any error status returned.
|
||||
/// </summary>
|
||||
CloseSource = 0x00000001,
|
||||
|
||||
/// <summary>
|
||||
/// Ignores the dwDesiredAccess parameter. The duplicate handle has the same access as the source handle.
|
||||
/// </summary>
|
||||
SameAccess = 0x00000002,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// PAGE_* from memoryapi.
|
||||
/// </summary>
|
||||
|
|
@ -833,5 +850,65 @@ namespace Dalamud.Injector
|
|||
byte[] lpBuffer,
|
||||
int dwSize,
|
||||
out IntPtr lpNumberOfBytesWritten);
|
||||
|
||||
/// <summary>
|
||||
/// Duplicates an object handle.
|
||||
/// </summary>
|
||||
/// <param name="hSourceProcessHandle">
|
||||
/// A handle to the process with the handle to be duplicated.
|
||||
///
|
||||
/// The handle must have the PROCESS_DUP_HANDLE access right.
|
||||
/// </param>
|
||||
/// <param name="hSourceHandle">
|
||||
/// The handle to be duplicated. This is an open object handle that is valid in the context of the source process.
|
||||
/// For a list of objects whose handles can be duplicated, see the following Remarks section.
|
||||
/// </param>
|
||||
/// <param name="hTargetProcessHandle">
|
||||
/// A handle to the process that is to receive the duplicated handle.
|
||||
///
|
||||
/// The handle must have the PROCESS_DUP_HANDLE access right.
|
||||
/// </param>
|
||||
/// <param name="lpTargetHandle">
|
||||
/// A pointer to a variable that receives the duplicate handle. This handle value is valid in the context of the target process.
|
||||
///
|
||||
/// If hSourceHandle is a pseudo handle returned by GetCurrentProcess or GetCurrentThread, DuplicateHandle converts it to a real handle to a process or thread, respectively.
|
||||
///
|
||||
/// If lpTargetHandle is NULL, the function duplicates the handle, but does not return the duplicate handle value to the caller. This behavior exists only for backward compatibility with previous versions of this function. You should not use this feature, as you will lose system resources until the target process terminates.
|
||||
///
|
||||
/// This parameter is ignored if hTargetProcessHandle is NULL.
|
||||
/// </param>
|
||||
/// <param name="dwDesiredAccess">
|
||||
/// The access requested for the new handle. For the flags that can be specified for each object type, see the following Remarks section.
|
||||
///
|
||||
/// This parameter is ignored if the dwOptions parameter specifies the DUPLICATE_SAME_ACCESS flag. Otherwise, the flags that can be specified depend on the type of object whose handle is to be duplicated.
|
||||
///
|
||||
/// This parameter is ignored if hTargetProcessHandle is NULL.
|
||||
/// </param>
|
||||
/// <param name="bInheritHandle">
|
||||
/// A variable that indicates whether the handle is inheritable. If TRUE, the duplicate handle can be inherited by new processes created by the target process. If FALSE, the new handle cannot be inherited.
|
||||
///
|
||||
/// This parameter is ignored if hTargetProcessHandle is NULL.
|
||||
/// </param>
|
||||
/// <param name="dwOptions">
|
||||
/// Optional actions.
|
||||
/// </param>
|
||||
/// <returns>
|
||||
/// If the function succeeds, the return value is nonzero.
|
||||
///
|
||||
/// If the function fails, the return value is zero. To get extended error information, call GetLastError.
|
||||
/// </returns>
|
||||
/// <remarks>
|
||||
/// See https://docs.microsoft.com/en-us/windows/win32/api/handleapi/nf-handleapi-duplicatehandle.
|
||||
/// </remarks>
|
||||
[DllImport("kernel32.dll", SetLastError = true)]
|
||||
[return: MarshalAs(UnmanagedType.Bool)]
|
||||
public static extern bool DuplicateHandle(
|
||||
IntPtr hSourceProcessHandle,
|
||||
IntPtr hSourceHandle,
|
||||
IntPtr hTargetProcessHandle,
|
||||
out IntPtr lpTargetHandle,
|
||||
uint dwDesiredAccess,
|
||||
[MarshalAs(UnmanagedType.Bool)] bool bInheritHandle,
|
||||
DuplicateOptions dwOptions);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue