mirror of
https://github.com/goatcorp/Dalamud.git
synced 2025-12-12 18:27:23 +01:00
Magic the magic happen
This commit is contained in:
parent
84769ae5b7
commit
658eedca37
188 changed files with 10329 additions and 3549 deletions
|
|
@ -1,60 +1,89 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup Label="Target">
|
||||
<PlatformTarget>AnyCPU</PlatformTarget>
|
||||
<TargetFramework>net48</TargetFramework>
|
||||
<LangVersion>8.0</LangVersion>
|
||||
<Platforms>AnyCPU;x64</Platforms>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Label="Build">
|
||||
<OutputType>WinExe</OutputType>
|
||||
<OutputPath>$(SolutionDir)bin</OutputPath>
|
||||
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
|
||||
<DebugSymbols>true</DebugSymbols>
|
||||
<DebugType>Portable</DebugType>
|
||||
<NoWarn>IDE1006;CS1701;CS1702</NoWarn>
|
||||
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
||||
<DocumentationFile>$(SolutionDir)\bin\Dalamud.Injector.xml</DocumentationFile>
|
||||
<TargetFramework>net5.0</TargetFramework>
|
||||
<RuntimeIdentifier>win-x64</RuntimeIdentifier>
|
||||
<PlatformTarget>x64</PlatformTarget>
|
||||
<Platforms>x64;AnyCPU</Platforms>
|
||||
<LangVersion>9.0</LangVersion>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Label="Feature">
|
||||
<InjectorVersion>5.2.4.6</InjectorVersion>
|
||||
<Description>XIV Launcher addon injector</Description>
|
||||
<AssemblyVersion>$(InjectorVersion)</AssemblyVersion>
|
||||
<FileVersion>$(InjectorVersion)</FileVersion>
|
||||
<Version>$(InjectorVersion)</Version>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Label="Output">
|
||||
<OutputType>Library</OutputType>
|
||||
<OutputPath>..\bin\$(Configuration)\</OutputPath>
|
||||
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
|
||||
<AppendRuntimeIdentifierToOutputPath>false</AppendRuntimeIdentifierToOutputPath>
|
||||
<GenerateRuntimeConfigurationFiles>true</GenerateRuntimeConfigurationFiles>
|
||||
<ProduceReferenceAssembly>false</ProduceReferenceAssembly>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Label="Documentation">
|
||||
<DocumentationFile></DocumentationFile>
|
||||
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Label="Build">
|
||||
<EnableDynamicLoading>true</EnableDynamicLoading>
|
||||
<DebugSymbols>true</DebugSymbols>
|
||||
<DebugType>portable</DebugType>
|
||||
<Deterministic>true</Deterministic>
|
||||
<Nullable>annotations</Nullable>
|
||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||
<AssemblyVersion>5.2.4.6</AssemblyVersion>
|
||||
<FileVersion>5.2.4.6</FileVersion>
|
||||
<Description>XIVLauncher addon injection</Description>
|
||||
<Version>5.2.4.6</Version>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Label="Configuration">
|
||||
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)'=='Debug'">
|
||||
<DefineConstants>DEBUG;TRACE</DefineConstants>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)'=='Release'">
|
||||
<AppOutputBase>$(MSBuildProjectDirectory)\</AppOutputBase>
|
||||
<PathMap>$(AppOutputBase)=C:\goatsoft\companysecrets\injector\</PathMap>
|
||||
<Deterministic>true</Deterministic>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup>
|
||||
<GeneratePackageOnBuild>false</GeneratePackageOnBuild>
|
||||
<PackageIcon></PackageIcon>
|
||||
<PackageIconUrl />
|
||||
<ApplicationIcon>dalamud.ico</ApplicationIcon>
|
||||
|
||||
<PropertyGroup Label="Warnings">
|
||||
<NoWarn>IDE1006;CS1591;CS1701;CS1702</NoWarn>
|
||||
<!-- IDE1006 - Naming violation -->
|
||||
<!-- CS1591 - Missing XML comment for publicly visible type or member -->
|
||||
<!-- CS1701 - Runtime policy may be needed -->
|
||||
<!-- CS1702 - Runtime policy may be needed -->
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<None Remove="stylecop.json" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<AdditionalFiles Include="stylecop.json" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="EasyHook" Version="2.7.6270" />
|
||||
<PackageReference Include="Newtonsoft.Json" Version="12.0.2" />
|
||||
<PackageReference Include="Iced" Version="1.12.0" />
|
||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.1" />
|
||||
<PackageReference Include="PeNet" Version="2.6.3" />
|
||||
<PackageReference Include="Reloaded.Memory" Version="4.1.1" />
|
||||
<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.File" Version="5.0.0" />
|
||||
<PackageReference Include="StyleCop.Analyzers" Version="1.2.0-beta.333">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\DalamudDebugStub\DalamudDebugStub.vcxproj" />
|
||||
<ProjectReference Include="..\Dalamud\Dalamud.csproj" />
|
||||
<AdditionalFiles Include="..\stylecop.json" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Reference Include="System.Windows.Forms" />
|
||||
<!-- This prevents us from having to include Dalamud itself as a dependency -->
|
||||
<!-- If the files move just update the paths here -->
|
||||
<Compile Include="..\Dalamud\ClientLanguage.cs" />
|
||||
<Compile Include="..\Dalamud\DalamudStartInfo.cs" />
|
||||
<Compile Include="..\Dalamud\Game\GameVersion.cs" />
|
||||
<Compile Include="..\Dalamud\Game\GameVersionConverter.cs" />
|
||||
<Compile Include="..\Dalamud\Interface\Internal\SerilogEventSink.cs" />
|
||||
</ItemGroup>
|
||||
<Target Name="PostBuild" AfterTargets="PostBuildEvent" Condition="'$(Configuration)'=='Release'">
|
||||
<Exec Command="powershell -ExecutionPolicy Unrestricted $(SolutionDir)CreateHashList.ps1 $(OutputPath)" />
|
||||
</Target>
|
||||
</Project>
|
||||
|
|
|
|||
275
Dalamud.Injector/EntryPoint.cs
Normal file
275
Dalamud.Injector/EntryPoint.cs
Normal file
|
|
@ -0,0 +1,275 @@
|
|||
using System;
|
||||
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 Dalamud.Interface.Internal;
|
||||
using Newtonsoft.Json;
|
||||
using Reloaded.Memory.Buffers;
|
||||
using Serilog;
|
||||
using Serilog.Core;
|
||||
using Serilog.Events;
|
||||
|
||||
using static Dalamud.Injector.NativeFunctions;
|
||||
|
||||
namespace Dalamud.Injector
|
||||
{
|
||||
/// <summary>
|
||||
/// Entrypoint to the program.
|
||||
/// </summary>
|
||||
public sealed class EntryPoint
|
||||
{
|
||||
/// <summary>
|
||||
/// A delegate used during initialization of the CLR from Dalamud.Injector.Boot.
|
||||
/// </summary>
|
||||
/// <param name="argc">Count of arguments.</param>
|
||||
/// <param name="argvPtr">char** string arguments.</param>
|
||||
public delegate void MainDelegate(int argc, IntPtr argvPtr);
|
||||
|
||||
/// <summary>
|
||||
/// Start the Dalamud injector.
|
||||
/// </summary>
|
||||
/// <param name="argc">Count of arguments.</param>
|
||||
/// <param name="argvPtr">byte** string arguments.</param>
|
||||
public static void Main(int argc, IntPtr argvPtr)
|
||||
{
|
||||
InitUnhandledException();
|
||||
InitLogging();
|
||||
|
||||
var args = new string[argc];
|
||||
|
||||
unsafe
|
||||
{
|
||||
var argv = (IntPtr*)argvPtr;
|
||||
for (var i = 0; i < argc; i++)
|
||||
{
|
||||
args[i] = Marshal.PtrToStringUni(argv[i]);
|
||||
}
|
||||
}
|
||||
|
||||
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));
|
||||
var startInfo = GetStartInfo(args.ElementAtOrDefault(2), process);
|
||||
|
||||
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()
|
||||
{
|
||||
AppDomain.CurrentDomain.UnhandledException += (sender, eventArgs) =>
|
||||
{
|
||||
if (Log.Logger == null)
|
||||
{
|
||||
Console.WriteLine($"A fatal error has occurred: {eventArgs.ExceptionObject}");
|
||||
}
|
||||
else
|
||||
{
|
||||
var exObj = eventArgs.ExceptionObject;
|
||||
if (exObj is Exception ex)
|
||||
{
|
||||
Log.Error(ex, "A fatal error has occurred.");
|
||||
}
|
||||
else
|
||||
{
|
||||
Log.Error($"A fatal error has occurred: {eventArgs.ExceptionObject}");
|
||||
}
|
||||
}
|
||||
|
||||
#if DEBUG
|
||||
var caption = "Debug Error";
|
||||
var message =
|
||||
$"Couldn't inject.\nMake sure that Dalamud was not injected into your target process " +
|
||||
$"as a release build before and that the target process can be accessed with VM_WRITE permissions.\n\n" +
|
||||
$"{eventArgs.ExceptionObject}";
|
||||
#else
|
||||
var caption = "XIVLauncher Error";
|
||||
var message =
|
||||
"Failed to inject the XIVLauncher in-game addon.\nPlease try restarting your game and your PC.\n" +
|
||||
"If this keeps happening, please report this error.";
|
||||
#endif
|
||||
_ = MessageBoxW(IntPtr.Zero, message, caption, MessageBoxType.IconError | MessageBoxType.Ok);
|
||||
|
||||
Environment.Exit(0);
|
||||
};
|
||||
}
|
||||
|
||||
private static void InitLogging()
|
||||
{
|
||||
var baseDirectory = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
|
||||
|
||||
#if DEBUG
|
||||
var logPath = Path.Combine(baseDirectory, "injector.log");
|
||||
#else
|
||||
var logPath = Path.Combine(baseDirectory, "..", "..", "..", "dalamud.injector.log");
|
||||
#endif
|
||||
|
||||
var levelSwitch = new LoggingLevelSwitch();
|
||||
|
||||
#if DEBUG
|
||||
levelSwitch.MinimumLevel = LogEventLevel.Verbose;
|
||||
#else
|
||||
levelSwitch.MinimumLevel = LogEventLevel.Information;
|
||||
#endif
|
||||
|
||||
Log.Logger = new LoggerConfiguration()
|
||||
.WriteTo.Async(a => a.File(logPath))
|
||||
.WriteTo.Sink(SerilogEventSink.Instance)
|
||||
.MinimumLevel.ControlledBy(levelSwitch)
|
||||
.CreateLogger();
|
||||
}
|
||||
|
||||
private static Process GetProcess(string arg)
|
||||
{
|
||||
Process process;
|
||||
|
||||
var pid = -1;
|
||||
if (arg != default)
|
||||
{
|
||||
pid = int.Parse(arg);
|
||||
}
|
||||
|
||||
switch (pid)
|
||||
{
|
||||
case -1:
|
||||
process = Process.GetProcessesByName("ffxiv_dx11").FirstOrDefault();
|
||||
|
||||
if (process == default)
|
||||
{
|
||||
throw new Exception("Could not find process");
|
||||
}
|
||||
|
||||
break;
|
||||
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:
|
||||
process = Process.GetProcessById(pid);
|
||||
break;
|
||||
}
|
||||
|
||||
return process;
|
||||
}
|
||||
|
||||
private static DalamudStartInfo GetStartInfo(string arg, Process process)
|
||||
{
|
||||
DalamudStartInfo startInfo;
|
||||
|
||||
if (arg != default)
|
||||
{
|
||||
startInfo = JsonConvert.DeserializeObject<DalamudStartInfo>(Encoding.UTF8.GetString(Convert.FromBase64String(arg)));
|
||||
}
|
||||
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"),
|
||||
GameVersion = gameVer,
|
||||
Language = ClientLanguage.English,
|
||||
OptOutMbCollection = false,
|
||||
};
|
||||
|
||||
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" +
|
||||
$" OptOutMbCollection: {startInfo.OptOutMbCollection}");
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
return startInfo;
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
injector.LoadLibrary(nethostPath, out _);
|
||||
injector.LoadLibrary(bootPath, out var bootModule);
|
||||
|
||||
// ======================================================
|
||||
|
||||
var startInfoJson = JsonConvert.SerializeObject(startInfo);
|
||||
var startInfoBytes = Encoding.UTF8.GetBytes(startInfoJson);
|
||||
|
||||
using var startInfoBuffer = new MemoryBufferHelper(process).CreatePrivateMemoryBuffer(startInfoBytes.Length + 0x8);
|
||||
var startInfoAddress = startInfoBuffer.Add(startInfoBytes);
|
||||
|
||||
if (startInfoAddress == IntPtr.Zero)
|
||||
throw new Exception("Unable to allocate start info JSON");
|
||||
|
||||
injector.GetFunctionAddress(bootModule, "Initialize", out var initAddress);
|
||||
injector.CallRemoteFunction(initAddress, startInfoAddress, out var exitCode);
|
||||
|
||||
// ======================================================
|
||||
|
||||
if (exitCode > 0)
|
||||
{
|
||||
Log.Error($"Dalamud.Boot::Initialize returned {exitCode}");
|
||||
return;
|
||||
}
|
||||
|
||||
Log.Information("Done");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -6,14 +6,8 @@
|
|||
using System.Diagnostics.CodeAnalysis;
|
||||
|
||||
// General
|
||||
[assembly: SuppressMessage("StyleCop.CSharp.ReadabilityRules", "SA1118:Parameter should not span multiple lines", Justification = "Preventing long lines", Scope = "namespaceanddescendants", Target = "~N:Dalamud")]
|
||||
[assembly: SuppressMessage("StyleCop.CSharp.ReadabilityRules", "SA1124:Do not use regions", Justification = "I like regions", Scope = "namespaceanddescendants", Target = "~N:Dalamud")]
|
||||
[assembly: SuppressMessage("StyleCop.CSharp.ReadabilityRules", "SA1123:Do not place regions within elements", Justification = "I like regions in elements too", Scope = "namespaceanddescendants", Target = "~N:Dalamud")]
|
||||
[assembly: SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1633:File should have header", Justification = "We don't do those yet")]
|
||||
[assembly: SuppressMessage("StyleCop.CSharp.LayoutRules", "SA1503:Braces should not be omitted", Justification = "This is annoying", Scope = "namespaceanddescendants", Target = "~N:Dalamud")]
|
||||
[assembly: SuppressMessage("StyleCop.CSharp.LayoutRules", "SA1512:Single-line comments should not be followed by blank line", Justification = "I like this better", Scope = "namespaceanddescendants", Target = "~N:Dalamud")]
|
||||
[assembly: SuppressMessage("StyleCop.CSharp.LayoutRules", "SA1515:Single-line comment should be preceded by blank line", Justification = "I like this better", Scope = "namespaceanddescendants", Target = "~N:Dalamud")]
|
||||
[assembly: SuppressMessage("StyleCop.CSharp.ReadabilityRules", "SA1127:Generic type constraints should be on their own line", Justification = "I like this better", Scope = "namespaceanddescendants", Target = "~N:Dalamud")]
|
||||
[assembly: SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1633:File should have header", Justification = "We don't do those yet")]
|
||||
|
||||
// Program.cs
|
||||
[assembly: SuppressMessage("CodeQuality", "IDE0051:Remove unused private members", Justification = "Used during #if DEBUG", Scope = "member", Target = "~M:Dalamud.Injector.Program.NativeInject(System.Diagnostics.Process)")]
|
||||
[assembly: SuppressMessage("Performance", "CA1822:Mark members as static", Justification = "I'll make what I want static", Scope = "namespaceanddescendants", Target = "~N:Dalamud")]
|
||||
|
|
|
|||
303
Dalamud.Injector/Injector.cs
Normal file
303
Dalamud.Injector/Injector.cs
Normal file
|
|
@ -0,0 +1,303 @@
|
|||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
|
||||
using Iced.Intel;
|
||||
using PeNet;
|
||||
using PeNet.Header.Pe;
|
||||
using Reloaded.Memory.Buffers;
|
||||
using Reloaded.Memory.Sources;
|
||||
using Reloaded.Memory.Utilities;
|
||||
|
||||
using static Dalamud.Injector.NativeFunctions;
|
||||
using static Iced.Intel.AssemblerRegisters;
|
||||
|
||||
namespace Dalamud.Injector
|
||||
{
|
||||
/// <summary>
|
||||
/// This class implements injecting into a remote process. It is a highly stripped down version of the
|
||||
/// https://github.com/Reloaded-Project injector/assembler implementation due to issues with Lutris and
|
||||
/// Wine.
|
||||
/// </summary>
|
||||
internal sealed class Injector : IDisposable
|
||||
{
|
||||
private readonly Process targetProcess;
|
||||
private readonly ExternalMemory extMemory;
|
||||
private readonly CircularBuffer circularBuffer;
|
||||
private readonly PrivateMemoryBuffer privateBuffer;
|
||||
|
||||
private IntPtr loadLibraryShellPtr;
|
||||
private IntPtr loadLibraryRetPtr;
|
||||
|
||||
private IntPtr getProcAddressShellPtr;
|
||||
private IntPtr getProcAddressRetPtr;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="Injector"/> class.
|
||||
/// </summary>
|
||||
/// <param name="targetProcess">Process to inject.</param>
|
||||
public Injector(Process targetProcess)
|
||||
{
|
||||
this.targetProcess = targetProcess;
|
||||
|
||||
this.extMemory = new ExternalMemory(targetProcess);
|
||||
this.circularBuffer = new CircularBuffer(4096, this.extMemory);
|
||||
this.privateBuffer = new MemoryBufferHelper(targetProcess).CreatePrivateMemoryBuffer(4096);
|
||||
|
||||
using var kernel32Module = this.GetProcessModule("KERNEL32.DLL");
|
||||
var kernel32PeFile = new PeFile(kernel32Module.FileName);
|
||||
var kernel32Exports = kernel32PeFile.ExportedFunctions;
|
||||
|
||||
this.SetupLoadLibrary(kernel32Module, kernel32Exports);
|
||||
this.SetupGetProcAddress(kernel32Module, kernel32Exports);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Finalizes an instance of the <see cref="Injector"/> class.
|
||||
/// </summary>
|
||||
~Injector() => this.Dispose();
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Dispose()
|
||||
{
|
||||
GC.SuppressFinalize(this);
|
||||
|
||||
this.targetProcess?.Dispose();
|
||||
this.circularBuffer?.Dispose();
|
||||
this.privateBuffer?.Dispose();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Load a module by absolute file path.
|
||||
/// </summary>
|
||||
/// <param name="modulePath">Absolute file path.</param>
|
||||
/// <param name="address">Address to the module.</param>
|
||||
public void LoadLibrary(string modulePath, out IntPtr address)
|
||||
{
|
||||
var lpParameter = this.WriteNullTerminatedUnicodeString(modulePath);
|
||||
|
||||
if (lpParameter == IntPtr.Zero)
|
||||
throw new Exception("Unable to allocate LoadLibraryW parameter");
|
||||
|
||||
var threadHandle = CreateRemoteThread(
|
||||
this.targetProcess.Handle,
|
||||
IntPtr.Zero,
|
||||
UIntPtr.Zero,
|
||||
this.loadLibraryShellPtr,
|
||||
lpParameter,
|
||||
CreateThreadFlags.RunImmediately,
|
||||
out _);
|
||||
|
||||
_ = WaitForSingleObject(threadHandle, uint.MaxValue);
|
||||
|
||||
this.extMemory.Read(this.loadLibraryRetPtr, out address);
|
||||
|
||||
if (address == IntPtr.Zero)
|
||||
throw new Exception($"Error calling LoadLibraryW with {modulePath}");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the address of an exported module function.
|
||||
/// </summary>
|
||||
/// <param name="module">Module address.</param>
|
||||
/// <param name="functionName">Name of the exported method.</param>
|
||||
/// <param name="address">Address to the function.</param>
|
||||
public void GetFunctionAddress(IntPtr module, string functionName, out IntPtr address)
|
||||
{
|
||||
var getProcAddressParams = new GetProcAddressParams(module, this.WriteNullTerminatedASCIIString(functionName));
|
||||
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);
|
||||
|
||||
if (address == IntPtr.Zero)
|
||||
throw new Exception($"Error calling GetProcAddress with {functionName}");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Call a method in a remote process via CreateRemoteThread.
|
||||
/// </summary>
|
||||
/// <param name="methodAddress">Method address.</param>
|
||||
/// <param name="parameterAddress">Parameter address.</param>
|
||||
/// <param name="exitCode">Thread exit code.</param>
|
||||
public void CallRemoteFunction(IntPtr methodAddress, IntPtr parameterAddress, out uint exitCode)
|
||||
{
|
||||
// Create and initialize a thread at our address and parameter address.
|
||||
var threadHandle = CreateRemoteThread(
|
||||
this.targetProcess.Handle,
|
||||
IntPtr.Zero,
|
||||
UIntPtr.Zero,
|
||||
methodAddress,
|
||||
parameterAddress,
|
||||
CreateThreadFlags.RunImmediately,
|
||||
out _);
|
||||
|
||||
_ = WaitForSingleObject(threadHandle, uint.MaxValue);
|
||||
|
||||
GetExitCodeThread(threadHandle, out exitCode);
|
||||
}
|
||||
|
||||
private void SetupLoadLibrary(ProcessModule kernel32Module, ExportFunction[] kernel32Exports)
|
||||
{
|
||||
var offset = this.GetExportedFunctionOffset(kernel32Exports, "LoadLibraryW");
|
||||
var functionAddr = kernel32Module.BaseAddress + (int)offset;
|
||||
var functionPtr = this.privateBuffer.Add(ref functionAddr);
|
||||
|
||||
if (functionPtr == IntPtr.Zero)
|
||||
throw new Exception("Unable to allocate LoadLibraryW function ptr");
|
||||
|
||||
var dummy = 0L;
|
||||
this.loadLibraryRetPtr = this.privateBuffer.Add(ref dummy);
|
||||
|
||||
if (this.loadLibraryRetPtr == IntPtr.Zero)
|
||||
throw new Exception("Unable to allocate LoadLibraryW return value");
|
||||
|
||||
var func = functionPtr.ToInt64();
|
||||
var retVal = this.loadLibraryRetPtr.ToInt64();
|
||||
|
||||
var asm = new Assembler(64);
|
||||
|
||||
asm.sub(rsp, 40); // sub rsp, 40 // Re-align stack to 16 byte boundary + shadow space.
|
||||
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)
|
||||
|
||||
var bytes = this.Assemble(asm);
|
||||
this.loadLibraryShellPtr = this.privateBuffer.Add(bytes);
|
||||
|
||||
if (this.loadLibraryShellPtr == IntPtr.Zero)
|
||||
throw new Exception("Unable to allocate LoadLibraryW shellcode");
|
||||
}
|
||||
|
||||
private void SetupGetProcAddress(ProcessModule kernel32Module, ExportFunction[] kernel32Exports)
|
||||
{
|
||||
var offset = this.GetExportedFunctionOffset(kernel32Exports, "GetProcAddress");
|
||||
var functionAddr = kernel32Module.BaseAddress + (int)offset;
|
||||
var functionPtr = this.privateBuffer.Add(ref functionAddr);
|
||||
|
||||
if (functionPtr == IntPtr.Zero)
|
||||
throw new Exception("Unable to allocate GetProcAddress function ptr");
|
||||
|
||||
var dummy = 0L;
|
||||
this.getProcAddressRetPtr = this.privateBuffer.Add(ref dummy);
|
||||
|
||||
if (this.getProcAddressRetPtr == IntPtr.Zero)
|
||||
throw new Exception("Unable to allocate GetProcAddress return value");
|
||||
|
||||
var func = functionPtr.ToInt64();
|
||||
var retVal = this.getProcAddressRetPtr.ToInt64();
|
||||
|
||||
var asm = new Assembler(64);
|
||||
|
||||
asm.sub(rsp, 40); // sub rsp, 40 // Re-align stack to 16 byte boundary +32 shadow space
|
||||
asm.mov(rdx, __qword_ptr[__qword_ptr[rcx + 8]]); // mov rdx, qword [qword rcx + 8] // lpProcName
|
||||
asm.mov(rcx, __qword_ptr[__qword_ptr[rcx + 0]]); // mov rcx, qword [qword rcx + 0] // hModule
|
||||
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)
|
||||
|
||||
var bytes = this.Assemble(asm);
|
||||
this.getProcAddressShellPtr = this.privateBuffer.Add(bytes);
|
||||
|
||||
if (this.getProcAddressShellPtr == IntPtr.Zero)
|
||||
throw new Exception("Unable to allocate GetProcAddress shellcode");
|
||||
}
|
||||
|
||||
private byte[] Assemble(Assembler assembler)
|
||||
{
|
||||
using var stream = new MemoryStream();
|
||||
assembler.Assemble(new StreamCodeWriter(stream), 0);
|
||||
|
||||
stream.Position = 0;
|
||||
var reader = new StreamCodeReader(stream);
|
||||
|
||||
int next;
|
||||
var bytes = new byte[stream.Length];
|
||||
while ((next = reader.ReadByte()) >= 0)
|
||||
{
|
||||
bytes[stream.Position - 1] = (byte)next;
|
||||
}
|
||||
|
||||
return bytes;
|
||||
}
|
||||
|
||||
private ProcessModule GetProcessModule(string moduleName)
|
||||
{
|
||||
var modules = this.targetProcess.Modules;
|
||||
for (var i = 0; i < modules.Count; i++)
|
||||
{
|
||||
var module = modules[i];
|
||||
if (module.ModuleName.Equals(moduleName, StringComparison.InvariantCultureIgnoreCase))
|
||||
{
|
||||
return module;
|
||||
}
|
||||
}
|
||||
|
||||
throw new Exception($"Failed to find {moduleName} in target process' modules");
|
||||
}
|
||||
|
||||
private uint GetExportedFunctionOffset(ExportFunction[] exportFunctions, string functionName)
|
||||
{
|
||||
var exportFunction = exportFunctions.FirstOrDefault(func => func.Name == functionName);
|
||||
|
||||
if (exportFunction == default)
|
||||
throw new Exception($"Failed to find exported function {functionName} in target module's exports");
|
||||
|
||||
return exportFunction.Address;
|
||||
}
|
||||
|
||||
private IntPtr WriteNullTerminatedASCIIString(string libraryPath)
|
||||
{
|
||||
var libraryNameBytes = Encoding.ASCII.GetBytes(libraryPath + '\0');
|
||||
var value = this.circularBuffer.Add(libraryNameBytes);
|
||||
|
||||
if (value == IntPtr.Zero)
|
||||
throw new Exception("Unable to write ASCII string to buffer");
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
private IntPtr WriteNullTerminatedUnicodeString(string libraryPath)
|
||||
{
|
||||
var libraryNameBytes = Encoding.Unicode.GetBytes(libraryPath + '\0');
|
||||
var value = this.circularBuffer.Add(libraryNameBytes);
|
||||
|
||||
if (value == IntPtr.Zero)
|
||||
throw new Exception("Unable to write Unicode string to buffer");
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
private struct GetProcAddressParams
|
||||
{
|
||||
public GetProcAddressParams(IntPtr hModule, IntPtr lPProcName)
|
||||
{
|
||||
this.HModule = hModule.ToInt64();
|
||||
this.LPProcName = lPProcName.ToInt64();
|
||||
}
|
||||
|
||||
public long HModule { get; set; }
|
||||
|
||||
public long LPProcName { get; set; }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,14 +1,234 @@
|
|||
using System;
|
||||
using System.Runtime.ConstrainedExecution;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Security;
|
||||
|
||||
namespace Dalamud.Injector
|
||||
{
|
||||
/// <summary>
|
||||
/// Native functions.
|
||||
/// Native user32 functions.
|
||||
/// </summary>
|
||||
internal static class NativeFunctions
|
||||
internal static partial class NativeFunctions
|
||||
{
|
||||
/// <summary>
|
||||
/// MB_* from winuser.
|
||||
/// </summary>
|
||||
public enum MessageBoxType : uint
|
||||
{
|
||||
/// <summary>
|
||||
/// The default value for any of the various subtypes.
|
||||
/// </summary>
|
||||
DefaultValue = 0x0,
|
||||
|
||||
// To indicate the buttons displayed in the message box, specify one of the following values.
|
||||
|
||||
/// <summary>
|
||||
/// The message box contains three push buttons: Abort, Retry, and Ignore.
|
||||
/// </summary>
|
||||
AbortRetryIgnore = 0x2,
|
||||
|
||||
/// <summary>
|
||||
/// The message box contains three push buttons: Cancel, Try Again, Continue. Use this message box type instead
|
||||
/// of MB_ABORTRETRYIGNORE.
|
||||
/// </summary>
|
||||
CancelTryContinue = 0x6,
|
||||
|
||||
/// <summary>
|
||||
/// Adds a Help button to the message box. When the user clicks the Help button or presses F1, the system sends
|
||||
/// a WM_HELP message to the owner.
|
||||
/// </summary>
|
||||
Help = 0x4000,
|
||||
|
||||
/// <summary>
|
||||
/// The message box contains one push button: OK. This is the default.
|
||||
/// </summary>
|
||||
Ok = DefaultValue,
|
||||
|
||||
/// <summary>
|
||||
/// The message box contains two push buttons: OK and Cancel.
|
||||
/// </summary>
|
||||
OkCancel = 0x1,
|
||||
|
||||
/// <summary>
|
||||
/// The message box contains two push buttons: Retry and Cancel.
|
||||
/// </summary>
|
||||
RetryCancel = 0x5,
|
||||
|
||||
/// <summary>
|
||||
/// The message box contains two push buttons: Yes and No.
|
||||
/// </summary>
|
||||
YesNo = 0x4,
|
||||
|
||||
/// <summary>
|
||||
/// The message box contains three push buttons: Yes, No, and Cancel.
|
||||
/// </summary>
|
||||
YesNoCancel = 0x3,
|
||||
|
||||
// To display an icon in the message box, specify one of the following values.
|
||||
|
||||
/// <summary>
|
||||
/// An exclamation-point icon appears in the message box.
|
||||
/// </summary>
|
||||
IconExclamation = 0x30,
|
||||
|
||||
/// <summary>
|
||||
/// An exclamation-point icon appears in the message box.
|
||||
/// </summary>
|
||||
IconWarning = IconExclamation,
|
||||
|
||||
/// <summary>
|
||||
/// An icon consisting of a lowercase letter i in a circle appears in the message box.
|
||||
/// </summary>
|
||||
IconInformation = 0x40,
|
||||
|
||||
/// <summary>
|
||||
/// An icon consisting of a lowercase letter i in a circle appears in the message box.
|
||||
/// </summary>
|
||||
IconAsterisk = IconInformation,
|
||||
|
||||
/// <summary>
|
||||
/// A question-mark icon appears in the message box.
|
||||
/// The question-mark message icon is no longer recommended because it does not clearly represent a specific type
|
||||
/// of message and because the phrasing of a message as a question could apply to any message type. In addition,
|
||||
/// users can confuse the message symbol question mark with Help information. Therefore, do not use this question
|
||||
/// mark message symbol in your message boxes. The system continues to support its inclusion only for backward
|
||||
/// compatibility.
|
||||
/// </summary>
|
||||
IconQuestion = 0x20,
|
||||
|
||||
/// <summary>
|
||||
/// A stop-sign icon appears in the message box.
|
||||
/// </summary>
|
||||
IconStop = 0x10,
|
||||
|
||||
/// <summary>
|
||||
/// A stop-sign icon appears in the message box.
|
||||
/// </summary>
|
||||
IconError = IconStop,
|
||||
|
||||
/// <summary>
|
||||
/// A stop-sign icon appears in the message box.
|
||||
/// </summary>
|
||||
IconHand = IconStop,
|
||||
|
||||
// To indicate the default button, specify one of the following values.
|
||||
|
||||
/// <summary>
|
||||
/// The first button is the default button.
|
||||
/// MB_DEFBUTTON1 is the default unless MB_DEFBUTTON2, MB_DEFBUTTON3, or MB_DEFBUTTON4 is specified.
|
||||
/// </summary>
|
||||
DefButton1 = DefaultValue,
|
||||
|
||||
/// <summary>
|
||||
/// The second button is the default button.
|
||||
/// </summary>
|
||||
DefButton2 = 0x100,
|
||||
|
||||
/// <summary>
|
||||
/// The third button is the default button.
|
||||
/// </summary>
|
||||
DefButton3 = 0x200,
|
||||
|
||||
/// <summary>
|
||||
/// The fourth button is the default button.
|
||||
/// </summary>
|
||||
DefButton4 = 0x300,
|
||||
|
||||
// To indicate the modality of the dialog box, specify one of the following values.
|
||||
|
||||
/// <summary>
|
||||
/// The user must respond to the message box before continuing work in the window identified by the hWnd parameter.
|
||||
/// However, the user can move to the windows of other threads and work in those windows. Depending on the hierarchy
|
||||
/// of windows in the application, the user may be able to move to other windows within the thread. All child windows
|
||||
/// of the parent of the message box are automatically disabled, but pop-up windows are not. MB_APPLMODAL is the
|
||||
/// default if neither MB_SYSTEMMODAL nor MB_TASKMODAL is specified.
|
||||
/// </summary>
|
||||
ApplModal = DefaultValue,
|
||||
|
||||
/// <summary>
|
||||
/// Same as MB_APPLMODAL except that the message box has the WS_EX_TOPMOST style.
|
||||
/// Use system-modal message boxes to notify the user of serious, potentially damaging errors that require immediate
|
||||
/// attention (for example, running out of memory). This flag has no effect on the user's ability to interact with
|
||||
/// windows other than those associated with hWnd.
|
||||
/// </summary>
|
||||
SystemModal = 0x1000,
|
||||
|
||||
/// <summary>
|
||||
/// Same as MB_APPLMODAL except that all the top-level windows belonging to the current thread are disabled if the
|
||||
/// hWnd parameter is NULL. Use this flag when the calling application or library does not have a window handle
|
||||
/// available but still needs to prevent input to other windows in the calling thread without suspending other threads.
|
||||
/// </summary>
|
||||
TaskModal = 0x2000,
|
||||
|
||||
// To specify other options, use one or more of the following values.
|
||||
|
||||
/// <summary>
|
||||
/// Same as desktop of the interactive window station. For more information, see Window Stations. If the current
|
||||
/// input desktop is not the default desktop, MessageBox does not return until the user switches to the default
|
||||
/// desktop.
|
||||
/// </summary>
|
||||
DefaultDesktopOnly = 0x20000,
|
||||
|
||||
/// <summary>
|
||||
/// The text is right-justified.
|
||||
/// </summary>
|
||||
Right = 0x80000,
|
||||
|
||||
/// <summary>
|
||||
/// Displays message and caption text using right-to-left reading order on Hebrew and Arabic systems.
|
||||
/// </summary>
|
||||
RtlReading = 0x100000,
|
||||
|
||||
/// <summary>
|
||||
/// The message box becomes the foreground window. Internally, the system calls the SetForegroundWindow function
|
||||
/// for the message box.
|
||||
/// </summary>
|
||||
SetForeground = 0x10000,
|
||||
|
||||
/// <summary>
|
||||
/// The message box is created with the WS_EX_TOPMOST window style.
|
||||
/// </summary>
|
||||
Topmost = 0x40000,
|
||||
|
||||
/// <summary>
|
||||
/// The caller is a service notifying the user of an event. The function displays a message box on the current active
|
||||
/// desktop, even if there is no user logged on to the computer.
|
||||
/// </summary>
|
||||
ServiceNotification = 0x200000,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Displays a modal dialog box that contains a system icon, a set of buttons, and a brief application-specific message,
|
||||
/// such as status or error information. The message box returns an integer value that indicates which button the user
|
||||
/// clicked.
|
||||
/// </summary>
|
||||
/// <param name="hWnd">
|
||||
/// A handle to the owner window of the message box to be created. If this parameter is NULL, the message box has no
|
||||
/// owner window.
|
||||
/// </param>
|
||||
/// <param name="text">
|
||||
/// The message to be displayed. If the string consists of more than one line, you can separate the lines using a carriage
|
||||
/// return and/or linefeed character between each line.
|
||||
/// </param>
|
||||
/// <param name="caption">
|
||||
/// The dialog box title. If this parameter is NULL, the default title is Error.</param>
|
||||
/// <param name="type">
|
||||
/// The contents and behavior of the dialog box. This parameter can be a combination of flags from the following groups
|
||||
/// of flags.
|
||||
/// </param>
|
||||
/// <returns>
|
||||
/// If a message box has a Cancel button, the function returns the IDCANCEL value if either the ESC key is pressed or
|
||||
/// the Cancel button is selected. If the message box has no Cancel button, pressing ESC will no effect - unless an
|
||||
/// MB_OK button is present. If an MB_OK button is displayed and the user presses ESC, the return value will be IDOK.
|
||||
/// If the function fails, the return value is zero.To get extended error information, call GetLastError. If the function
|
||||
/// succeeds, the return value is one of the ID* enum values.
|
||||
/// </returns>
|
||||
[DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
|
||||
public static extern int MessageBoxW(IntPtr hWnd, string text, string caption, MessageBoxType type);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Native kernel32 functions.
|
||||
/// </summary>
|
||||
internal static partial class NativeFunctions
|
||||
{
|
||||
/// <summary>
|
||||
/// MEM_* from memoryapi.
|
||||
|
|
@ -20,14 +240,14 @@ namespace Dalamud.Injector
|
|||
/// To coalesce two adjacent placeholders, specify MEM_RELEASE | MEM_COALESCE_PLACEHOLDERS. When you coalesce
|
||||
/// placeholders, lpAddress and dwSize must exactly match those of the placeholder.
|
||||
/// </summary>
|
||||
CoalescePlaceholders = 0x00000001,
|
||||
CoalescePlaceholders = 0x1,
|
||||
|
||||
/// <summary>
|
||||
/// Frees an allocation back to a placeholder (after you've replaced a placeholder with a private allocation using
|
||||
/// VirtualAlloc2 or Virtual2AllocFromApp). To split a placeholder into two placeholders, specify
|
||||
/// MEM_RELEASE | MEM_PRESERVE_PLACEHOLDER.
|
||||
/// </summary>
|
||||
PreservePlaceholder = 0x00000002,
|
||||
PreservePlaceholder = 0x2,
|
||||
|
||||
/// <summary>
|
||||
/// Allocates memory charges (from the overall size of memory and the paging files on disk) for the specified reserved
|
||||
|
|
@ -88,7 +308,7 @@ namespace Dalamud.Injector
|
|||
/// the specified address range is intact. If the function fails, at least some of the data in the address range
|
||||
/// has been replaced with zeroes. This value cannot be used with any other value. If MEM_RESET_UNDO is called on
|
||||
/// an address range which was not MEM_RESET earlier, the behavior is undefined. When you specify MEM_RESET, the
|
||||
/// VirtualAllocEx function ignores the value of flProtect. However, you must still set flProtect to a valid
|
||||
/// VirtualAllocEx function ignores the value of flProtect. However, you must still set flProtect to a valid
|
||||
/// protection value, such as PAGE_NOACCESS.
|
||||
/// </summary>
|
||||
ResetUndo = 0x1000000,
|
||||
|
|
@ -122,6 +342,28 @@ namespace Dalamud.Injector
|
|||
LargePages = 0x20000000,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Unprefixed flags from CreateRemoteThread.
|
||||
/// </summary>
|
||||
[Flags]
|
||||
public enum CreateThreadFlags
|
||||
{
|
||||
/// <summary>
|
||||
/// The thread runs immediately after creation.
|
||||
/// </summary>
|
||||
RunImmediately = 0x0,
|
||||
|
||||
/// <summary>
|
||||
/// The thread is created in a suspended state, and does not run until the ResumeThread function is called.
|
||||
/// </summary>
|
||||
CreateSuspended = 0x4,
|
||||
|
||||
/// <summary>
|
||||
/// The dwStackSize parameter specifies the initial reserve size of the stack. If this flag is not specified, dwStackSize specifies the commit size.
|
||||
/// </summary>
|
||||
StackSizeParamIsReservation = 0x10000,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// PAGE_* from memoryapi.
|
||||
/// </summary>
|
||||
|
|
@ -198,7 +440,7 @@ namespace Dalamud.Injector
|
|||
/// The default behavior for VirtualProtect protection change to executable is to mark all locations as valid call
|
||||
/// targets for CFG.
|
||||
/// </summary>
|
||||
TargetsNoUpdate = 0x40000000,
|
||||
TargetsNoUpdate = TargetsInvalid,
|
||||
|
||||
/// <summary>
|
||||
/// Pages in the region become guard pages. Any attempt to access a guard page causes the system to raise a
|
||||
|
|
@ -312,23 +554,33 @@ namespace Dalamud.Injector
|
|||
}
|
||||
|
||||
/// <summary>
|
||||
/// Closes an open object handle.
|
||||
/// WAIT_* from synchapi.
|
||||
/// </summary>
|
||||
/// <param name="hObject">
|
||||
/// A valid handle to an open object.
|
||||
/// </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. If the application is running under a debugger, the function will throw an
|
||||
/// exception if it receives either a handle value that is not valid or a pseudo-handle value. This can happen if you
|
||||
/// close a handle twice, or if you call CloseHandle on a handle returned by the FindFirstFile function instead of calling
|
||||
/// the FindClose function.
|
||||
/// </returns>
|
||||
[DllImport("kernel32.dll", SetLastError = true)]
|
||||
[ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)]
|
||||
[SuppressUnmanagedCodeSecurity]
|
||||
[return: MarshalAs(UnmanagedType.Bool)]
|
||||
public static extern bool CloseHandle(IntPtr hObject);
|
||||
public enum WaitResult
|
||||
{
|
||||
/// <summary>
|
||||
/// The specified object is a mutex object that was not released by the thread that owned the mutex object
|
||||
/// before the owning thread terminated.Ownership of the mutex object is granted to the calling thread and
|
||||
/// the mutex state is set to nonsignaled. If the mutex was protecting persistent state information, you
|
||||
/// should check it for consistency.
|
||||
/// </summary>
|
||||
Abandoned = 0x80,
|
||||
|
||||
/// <summary>
|
||||
/// The state of the specified object is signaled.
|
||||
/// </summary>
|
||||
Object0 = 0x0,
|
||||
|
||||
/// <summary>
|
||||
/// The time-out interval elapsed, and the object's state is nonsignaled.
|
||||
/// </summary>
|
||||
Timeout = 0x102,
|
||||
|
||||
/// <summary>
|
||||
/// The function has failed. To get extended error information, call GetLastError.
|
||||
/// </summary>
|
||||
WAIT_FAILED = 0xFFFFFFF,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a thread that runs in the virtual address space of another process. Use the CreateRemoteThreadEx function
|
||||
|
|
@ -336,23 +588,23 @@ namespace Dalamud.Injector
|
|||
/// </summary>
|
||||
/// <param name="hProcess">
|
||||
/// A handle to the process in which the thread is to be created. The handle must have the PROCESS_CREATE_THREAD,
|
||||
/// PROCESS_QUERY_INFORMATION, PROCESS_VM_OPERATION, PROCESS_VM_WRITE, and PROCESS_VM_READ access rights, and may fail
|
||||
/// without these rights on certain platforms. For more information, see Process Security and Access Rights.
|
||||
/// PROCESS_QUERY_INFORMATION, PROCESS_VM_OPERATION, PROCESS_VM_WRITE, and PROCESS_VM_READ access rights, and may fail without
|
||||
/// these rights on certain platforms. For more information, see Process Security and Access Rights.
|
||||
/// </param>
|
||||
/// <param name="lpThreadAttributes">
|
||||
/// A pointer to a SECURITY_ATTRIBUTES structure that specifies a security descriptor for the new thread and determines
|
||||
/// whether child processes can inherit the returned handle. If lpThreadAttributes is NULL, the thread gets a default
|
||||
/// security descriptor and the handle cannot be inherited. The access control lists (ACL) in the default security descriptor
|
||||
/// for a thread come from the primary token of the creator.
|
||||
/// A pointer to a SECURITY_ATTRIBUTES structure that specifies a security descriptor for the new thread and determines whether
|
||||
/// child processes can inherit the returned handle. If lpThreadAttributes is NULL, the thread gets a default security descriptor
|
||||
/// and the handle cannot be inherited. The access control lists (ACL) in the default security descriptor for a thread come from
|
||||
/// the primary token of the creator.
|
||||
/// </param>
|
||||
/// <param name="dwStackSize">
|
||||
/// The initial size of the stack, in bytes. The system rounds this value to the nearest page. If this parameter is
|
||||
/// 0 (zero), the new thread uses the default size for the executable. For more information, see Thread Stack Size.
|
||||
/// The initial size of the stack, in bytes. The system rounds this value to the nearest page. If this parameter is 0 (zero), the
|
||||
/// new thread uses the default size for the executable. For more information, see Thread Stack Size.
|
||||
/// </param>
|
||||
/// <param name="lpStartAddress">
|
||||
/// A pointer to the application-defined function of type LPTHREAD_START_ROUTINE to be executed by the thread and
|
||||
/// represents the starting address of the thread in the remote process. The function must exist in the remote process.
|
||||
/// For more information, see ThreadProc.
|
||||
/// A pointer to the application-defined function of type LPTHREAD_START_ROUTINE to be executed by the thread and represents the
|
||||
/// starting address of the thread in the remote process. The function must exist in the remote process. For more information,
|
||||
/// see ThreadProc.
|
||||
/// </param>
|
||||
/// <param name="lpParameter">
|
||||
/// A pointer to a variable to be passed to the thread function.
|
||||
|
|
@ -361,92 +613,43 @@ namespace Dalamud.Injector
|
|||
/// The flags that control the creation of the thread.
|
||||
/// </param>
|
||||
/// <param name="lpThreadId">
|
||||
/// A pointer to a variable that receives the thread identifier. If this parameter is NULL, the thread identifier is
|
||||
/// not returned.
|
||||
/// A pointer to a variable that receives the thread identifier. If this parameter is NULL, the thread identifier is not returned.
|
||||
/// </param>
|
||||
/// <returns>
|
||||
/// If the function succeeds, the return value is a handle to the new thread. If the function fails, the return value
|
||||
/// is NULL.To get extended error information, call GetLastError. Note that CreateRemoteThread may succeed even if
|
||||
/// lpStartAddress points to data, code, or is not accessible. If the start address is invalid when the thread runs,
|
||||
/// an exception occurs, and the thread terminates. Thread termination due to a invalid start address is handled as
|
||||
/// an error exit for the thread's process. This behavior is similar to the asynchronous nature of CreateProcess, where
|
||||
/// the process is created even if it refers to invalid or missing dynamic-link libraries (DLL).
|
||||
/// If the function succeeds, the return value is a handle to the new thread. If the function fails, the return value is
|
||||
/// NULL.To get extended error information, call GetLastError. Note that CreateRemoteThread may succeed even if lpStartAddress
|
||||
/// points to data, code, or is not accessible. If the start address is invalid when the thread runs, an exception occurs, and
|
||||
/// the thread terminates. Thread termination due to a invalid start address is handled as an error exit for the thread's process.
|
||||
/// This behavior is similar to the asynchronous nature of CreateProcess, where the process is created even if it refers to
|
||||
/// invalid or missing dynamic-link libraries (DLL).
|
||||
/// </returns>
|
||||
[DllImport("kernel32.dll")]
|
||||
[DllImport("kernel32.dll", SetLastError = true)]
|
||||
public static extern IntPtr CreateRemoteThread(
|
||||
IntPtr hProcess,
|
||||
IntPtr lpThreadAttributes,
|
||||
uint dwStackSize,
|
||||
UIntPtr dwStackSize,
|
||||
IntPtr lpStartAddress,
|
||||
IntPtr lpParameter,
|
||||
uint dwCreationFlags,
|
||||
IntPtr lpThreadId);
|
||||
CreateThreadFlags dwCreationFlags,
|
||||
out uint lpThreadId);
|
||||
|
||||
/// <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.
|
||||
/// Retrieves the termination status of the specified thread.
|
||||
/// </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 name="hThread">
|
||||
/// A handle to the thread. The handle must have the THREAD_QUERY_INFORMATION or THREAD_QUERY_LIMITED_INFORMATION
|
||||
/// access right.For more information, see Thread Security and Access Rights.
|
||||
/// </param>
|
||||
/// <param name="lpExitCode">
|
||||
/// A pointer to a variable to receive the thread termination status.
|
||||
/// </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.Auto)]
|
||||
public static extern IntPtr GetModuleHandle(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)]
|
||||
public static extern IntPtr GetProcAddress(IntPtr hModule, string procName);
|
||||
|
||||
/// <summary>
|
||||
/// See https://docs.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-openprocess.
|
||||
/// Opens an existing local process object.
|
||||
/// </summary>
|
||||
/// <param name="processAccess">
|
||||
/// The access to the process object. This access right is checked against the security descriptor for the process.
|
||||
/// This parameter can be one or more of the process access rights. If the caller has enabled the SeDebugPrivilege
|
||||
/// privilege, the requested access is granted regardless of the contents of the security descriptor.
|
||||
/// </param>
|
||||
/// <param name="bInheritHandle">
|
||||
/// If this value is TRUE, processes created by this process will inherit the handle. Otherwise, the processes do
|
||||
/// not inherit this handle.
|
||||
/// </param>
|
||||
/// <param name="processId">
|
||||
/// The identifier of the local process to be opened.
|
||||
/// </param>
|
||||
/// <returns>
|
||||
/// If the function succeeds, the return value is an open handle to the specified process. If the function fails, the
|
||||
/// return value is NULL.To get extended error information, call GetLastError.
|
||||
/// 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>
|
||||
[DllImport("kernel32.dll", SetLastError = true)]
|
||||
public static extern IntPtr OpenProcess(
|
||||
ProcessAccessFlags processAccess,
|
||||
bool bInheritHandle,
|
||||
int processId);
|
||||
[return: MarshalAs(UnmanagedType.Bool)]
|
||||
public static extern bool GetExitCodeThread(IntPtr hThread, out uint lpExitCode);
|
||||
|
||||
/// <summary>
|
||||
/// See https://docs.microsoft.com/en-us/windows/win32/api/memoryapi/nf-memoryapi-virtualallocex.
|
||||
|
|
@ -530,6 +733,27 @@ namespace Dalamud.Injector
|
|||
int dwSize,
|
||||
AllocationType dwFreeType);
|
||||
|
||||
/// <summary>
|
||||
/// Waits until the specified object is in the signaled state or the time-out interval elapses. To enter an alertable wait
|
||||
/// state, use the WaitForSingleObjectEx function.To wait for multiple objects, use WaitForMultipleObjects.
|
||||
/// </summary>
|
||||
/// <param name="hHandle">
|
||||
/// A handle to the object. For a list of the object types whose handles can be specified, see the following Remarks section.
|
||||
/// If this handle is closed while the wait is still pending, the function's behavior is undefined. The handle must have the
|
||||
/// SYNCHRONIZE access right. For more information, see Standard Access Rights.
|
||||
/// </param>
|
||||
/// <param name="dwMilliseconds">
|
||||
/// The time-out interval, in milliseconds. If a nonzero value is specified, the function waits until the object is signaled
|
||||
/// or the interval elapses. If dwMilliseconds is zero, the function does not enter a wait state if the object is not signaled;
|
||||
/// it always returns immediately. If dwMilliseconds is INFINITE, the function will return only when the object is signaled.
|
||||
/// </param>
|
||||
/// <returns>
|
||||
/// If the function succeeds, the return value indicates the event that caused the function to return.
|
||||
/// It can be one of the WaitResult values.
|
||||
/// </returns>
|
||||
[DllImport("kernel32.dll", SetLastError = true)]
|
||||
public static extern uint WaitForSingleObject(IntPtr hHandle, uint dwMilliseconds);
|
||||
|
||||
/// <summary>
|
||||
/// Writes data to an area of memory in a specified process. The entire area to be written to must be accessible or
|
||||
/// the operation fails.
|
||||
|
|
|
|||
|
|
@ -1,208 +0,0 @@
|
|||
using System;
|
||||
using System.ComponentModel;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Windows.Forms;
|
||||
|
||||
using EasyHook;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace Dalamud.Injector
|
||||
{
|
||||
/// <summary>
|
||||
/// Application entrypoint.
|
||||
/// </summary>
|
||||
internal static class Program
|
||||
{
|
||||
private static Process process = null;
|
||||
|
||||
private static void Main(string[] args)
|
||||
{
|
||||
AppDomain.CurrentDomain.UnhandledException += (sender, eventArgs) =>
|
||||
{
|
||||
File.WriteAllText("InjectorException.txt", eventArgs.ExceptionObject.ToString());
|
||||
#if !DEBUG
|
||||
MessageBox.Show("Failed to inject the XIVLauncher in-game addon.\nPlease try restarting your game and your PC.\nIf this keeps happening, please report this error.", "XIVLauncher Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
|
||||
#else
|
||||
MessageBox.Show("Couldn't inject.\nMake sure that Dalamud was not injected into your target process as a release build before and that the target process can be accessed with VM_WRITE permissions.\n\n" + eventArgs.ExceptionObject, "Debug Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
|
||||
#endif
|
||||
Environment.Exit(0);
|
||||
};
|
||||
|
||||
var pid = -1;
|
||||
if (args.Length >= 1)
|
||||
{
|
||||
pid = int.Parse(args[0]);
|
||||
}
|
||||
|
||||
switch (pid)
|
||||
{
|
||||
case -1:
|
||||
process = Process.GetProcessesByName("ffxiv_dx11")[0];
|
||||
break;
|
||||
case -2:
|
||||
process = Process.Start(
|
||||
"C:\\Program Files (x86)\\SquareEnix\\FINAL FANTASY XIV - A Realm Reborn\\game\\ffxiv_dx11.exe",
|
||||
"DEV.TestSID=0 DEV.UseSqPack=1 DEV.DataPathType=1 DEV.LobbyHost01=127.0.0.1 DEV.LobbyPort01=54994 DEV.LobbyHost02=127.0.0.1 DEV.LobbyPort02=54994 DEV.LobbyHost03=127.0.0.1 DEV.LobbyPort03=54994 DEV.LobbyHost04=127.0.0.1 DEV.LobbyPort04=54994 DEV.LobbyHost05=127.0.0.1 DEV.LobbyPort05=54994 DEV.LobbyHost06=127.0.0.1 DEV.LobbyPort06=54994 DEV.LobbyHost07=127.0.0.1 DEV.LobbyPort07=54994 DEV.LobbyHost08=127.0.0.1 DEV.LobbyPort08=54994 SYS.Region=0 language=1 version=1.0.0.0 DEV.MaxEntitledExpansionID=2 DEV.GMServerHost=127.0.0.1 DEV.GameQuitMessageBox=0");
|
||||
Thread.Sleep(1000);
|
||||
break;
|
||||
default:
|
||||
process = Process.GetProcessById(pid);
|
||||
break;
|
||||
}
|
||||
|
||||
DalamudStartInfo startInfo;
|
||||
if (args.Length <= 1)
|
||||
{
|
||||
startInfo = GetDefaultStartInfo();
|
||||
Console.WriteLine("\nA Dalamud start info was not found in the program arguments. One has been generated for you.");
|
||||
Console.WriteLine("\nCopy the following contents into the program arguments:");
|
||||
Console.WriteLine();
|
||||
Console.WriteLine(Convert.ToBase64String(Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(startInfo))));
|
||||
}
|
||||
else
|
||||
{
|
||||
startInfo = JsonConvert.DeserializeObject<DalamudStartInfo>(Encoding.UTF8.GetString(Convert.FromBase64String(args[1])));
|
||||
}
|
||||
|
||||
startInfo.WorkingDirectory = Directory.GetCurrentDirectory();
|
||||
|
||||
// Seems to help with the STATUS_INTERNAL_ERROR condition
|
||||
Thread.Sleep(1000);
|
||||
|
||||
// Thread.Sleep(10000);
|
||||
|
||||
// Inject to process
|
||||
Inject(process, startInfo);
|
||||
|
||||
Thread.Sleep(1000);
|
||||
|
||||
#if DEBUG
|
||||
// Inject exception handler
|
||||
// NativeInject(process);
|
||||
#endif
|
||||
}
|
||||
|
||||
private static void Inject(Process process, DalamudStartInfo info)
|
||||
{
|
||||
Console.WriteLine($"Injecting to {process.Id}");
|
||||
|
||||
// File check
|
||||
var libPath = Path.GetFullPath("Dalamud.dll");
|
||||
if (!File.Exists(libPath))
|
||||
{
|
||||
Console.WriteLine($"Can't find a dll on {libPath}");
|
||||
return;
|
||||
}
|
||||
|
||||
RemoteHooking.Inject(process.Id, InjectionOptions.DoNotRequireStrongName, libPath, libPath, info);
|
||||
|
||||
Console.WriteLine("Injected");
|
||||
}
|
||||
|
||||
private static void NativeInject(Process process)
|
||||
{
|
||||
var libPath = Path.GetFullPath("DalamudDebugStub.dll");
|
||||
|
||||
var pathBytes = Encoding.Unicode.GetBytes(libPath);
|
||||
var len = pathBytes.Length + 1;
|
||||
|
||||
Console.WriteLine($"Injecting {libPath}...");
|
||||
|
||||
var handle = NativeFunctions.OpenProcess(
|
||||
NativeFunctions.ProcessAccessFlags.AllAccess,
|
||||
false,
|
||||
process.Id);
|
||||
|
||||
if (handle == IntPtr.Zero)
|
||||
{
|
||||
throw new Win32Exception(Marshal.GetLastWin32Error(), "Could not OpenProcess");
|
||||
}
|
||||
|
||||
var dllMem = NativeFunctions.VirtualAllocEx(
|
||||
handle,
|
||||
IntPtr.Zero,
|
||||
len,
|
||||
NativeFunctions.AllocationType.Commit,
|
||||
NativeFunctions.MemoryProtection.ReadWrite);
|
||||
|
||||
if (dllMem == IntPtr.Zero)
|
||||
{
|
||||
throw new Win32Exception(Marshal.GetLastWin32Error(), $"Could not alloc memory {Marshal.GetLastWin32Error():X}");
|
||||
}
|
||||
|
||||
Console.WriteLine($"dll path at {dllMem.ToInt64():X}");
|
||||
|
||||
if (!NativeFunctions.WriteProcessMemory(
|
||||
handle,
|
||||
dllMem,
|
||||
pathBytes,
|
||||
len,
|
||||
out var bytesWritten))
|
||||
{
|
||||
throw new Win32Exception(Marshal.GetLastWin32Error(), "Could not write DLL");
|
||||
}
|
||||
|
||||
Console.WriteLine($"Wrote {bytesWritten}");
|
||||
|
||||
var kernel32 = NativeFunctions.GetModuleHandle("Kernel32.dll");
|
||||
var loadLibA = NativeFunctions.GetProcAddress(kernel32, "LoadLibraryW");
|
||||
|
||||
var remoteThread = NativeFunctions.CreateRemoteThread(
|
||||
handle,
|
||||
IntPtr.Zero,
|
||||
0,
|
||||
loadLibA,
|
||||
dllMem,
|
||||
0,
|
||||
IntPtr.Zero);
|
||||
|
||||
if (remoteThread == IntPtr.Zero)
|
||||
{
|
||||
throw new Win32Exception(Marshal.GetLastWin32Error(), $"Could not CreateRemoteThread");
|
||||
}
|
||||
|
||||
/*
|
||||
TODO kill myself
|
||||
VirtualFreeEx(
|
||||
handle,
|
||||
dllMem,
|
||||
0,
|
||||
AllocationType.Release);
|
||||
*/
|
||||
|
||||
NativeFunctions.CloseHandle(remoteThread);
|
||||
NativeFunctions.CloseHandle(handle);
|
||||
}
|
||||
|
||||
private static DalamudStartInfo GetDefaultStartInfo()
|
||||
{
|
||||
var ffxivDir = Path.GetDirectoryName(process.MainModule.FileName);
|
||||
var startInfo = new DalamudStartInfo
|
||||
{
|
||||
WorkingDirectory = null,
|
||||
ConfigurationPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "XIVLauncher", "dalamudConfig.json"),
|
||||
PluginDirectory = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "XIVLauncher", "installedPlugins"),
|
||||
DefaultPluginDirectory = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "XIVLauncher", "devPlugins"),
|
||||
AssetDirectory = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "XIVLauncher", "dalamudAssets"),
|
||||
|
||||
GameVersion = File.ReadAllText(Path.Combine(ffxivDir, "ffxivgame.ver")),
|
||||
Language = ClientLanguage.English,
|
||||
};
|
||||
|
||||
Console.WriteLine("Creating a StartInfo with:\n" +
|
||||
$"ConfigurationPath: {startInfo.ConfigurationPath}\n" +
|
||||
$"PluginDirectory: {startInfo.PluginDirectory}\n" +
|
||||
$"DefaultPluginDirectory: {startInfo.DefaultPluginDirectory}\n" +
|
||||
$"Language: {startInfo.Language}\n" +
|
||||
$"GameVersion: {startInfo.GameVersion}\n" +
|
||||
$"OptOutMbCollection: {startInfo.OptOutMbCollection}\n" +
|
||||
$"AssetDirectory: {startInfo.AssetDirectory}");
|
||||
|
||||
return startInfo;
|
||||
}
|
||||
}
|
||||
}
|
||||
79
Dalamud.Injector/RemotePinnedData.cs
Normal file
79
Dalamud.Injector/RemotePinnedData.cs
Normal file
|
|
@ -0,0 +1,79 @@
|
|||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
|
||||
using static Dalamud.Injector.NativeFunctions;
|
||||
|
||||
namespace Dalamud.Injector
|
||||
{
|
||||
/// <summary>
|
||||
/// Pin an arbitrary string to a remote process.
|
||||
/// </summary>
|
||||
internal class RemotePinnedData : IDisposable
|
||||
{
|
||||
private readonly Process process;
|
||||
private readonly byte[] data;
|
||||
private readonly IntPtr allocAddr;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="RemotePinnedData"/> class.
|
||||
/// </summary>
|
||||
/// <param name="process">Process to write in.</param>
|
||||
/// <param name="data">Data to write.</param>
|
||||
public unsafe RemotePinnedData(Process process, byte[] data)
|
||||
{
|
||||
this.process = process;
|
||||
this.data = data;
|
||||
|
||||
this.allocAddr = VirtualAllocEx(
|
||||
this.process.Handle,
|
||||
IntPtr.Zero,
|
||||
this.data.Length,
|
||||
AllocationType.Commit,
|
||||
MemoryProtection.ReadWrite);
|
||||
|
||||
if (this.allocAddr == IntPtr.Zero || Marshal.GetLastWin32Error() != 0)
|
||||
{
|
||||
throw new Exception("Error allocating memory");
|
||||
}
|
||||
|
||||
var result = WriteProcessMemory(
|
||||
this.process.Handle,
|
||||
this.allocAddr,
|
||||
this.data,
|
||||
this.data.Length,
|
||||
out _);
|
||||
|
||||
if (!result || Marshal.GetLastWin32Error() != 0)
|
||||
{
|
||||
throw new Exception("Error writing memory");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the address of the pinned data.
|
||||
/// </summary>
|
||||
public IntPtr Address => this.allocAddr;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Dispose()
|
||||
{
|
||||
if (this.allocAddr == IntPtr.Zero)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var result = VirtualFreeEx(
|
||||
this.process.Handle,
|
||||
this.allocAddr,
|
||||
0,
|
||||
AllocationType.Release);
|
||||
|
||||
if (!result || Marshal.GetLastWin32Error() != 0)
|
||||
{
|
||||
throw new Exception("Error freeing memory");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 52 KiB |
|
|
@ -1,13 +0,0 @@
|
|||
{
|
||||
"$schema": "https://raw.githubusercontent.com/DotNetAnalyzers/StyleCopAnalyzers/master/StyleCop.Analyzers/StyleCop.Analyzers/Settings/stylecop.schema.json",
|
||||
"settings": {
|
||||
"orderingRules": {
|
||||
"systemUsingDirectivesFirst": true,
|
||||
"usingDirectivesPlacement": "outsideNamespace",
|
||||
"blankLinesBetweenUsingGroups": "require"
|
||||
},
|
||||
"maintainabilityRules": {
|
||||
"topLevelTypes": [ "class", "interface", "struct", "enum" ]
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue