Merge pull request #51 from Lightczx/master

Use Microsoft.Windows.CsWin32 to replace manual PInvoke
This commit is contained in:
HolographicHat
2023-02-27 19:45:11 +08:00
committed by GitHub
14 changed files with 142 additions and 277 deletions

View File

@@ -1,8 +1,8 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net6.0</TargetFramework>
<TargetFramework>net7.0-windows7</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<LangVersion>preview</LangVersion>
@@ -21,8 +21,12 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Google.Protobuf" Version="3.21.6" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.1" />
<PackageReference Include="Google.Protobuf" Version="3.22.0" />
<PackageReference Include="Microsoft.Windows.CsWin32" Version="0.2.188-beta">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Newtonsoft.Json" Version="13.0.2" />
</ItemGroup>
<ItemGroup>
@@ -31,6 +35,10 @@
<LastGenOutput>App.Designer.cs</LastGenOutput>
</EmbeddedResource>
<None Remove="res\Updater.exe" />
<None Remove="src\NativeMethods.json" />
<None Remove="src\NativeMethods.txt" />
<AdditionalFiles Include="src\NativeMethods.json" />
<AdditionalFiles Include="src\NativeMethods.txt" />
<EmbeddedResource Include="res\Updater.exe">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</EmbeddedResource>

View File

@@ -1,5 +1,8 @@
using Microsoft.Win32;
using YaeAchievement.Win32;
using Windows.Win32;
using Windows.Win32.Foundation;
using Windows.Win32.Graphics.Gdi;
//using YaeAchievement.Win32;
namespace YaeAchievement.AppCenterSDK;
@@ -21,9 +24,9 @@ public static class DeviceHelper {
}
public static string GetScreenSize() {
var desktop = Native.GetDC(IntPtr.Zero);
var size = $"{Native.GetDeviceCaps(desktop, 118)}x{Native.GetDeviceCaps(desktop, 117)}";
Native.ReleaseDC(IntPtr.Zero, desktop);
var desktop = Native.GetDC(HWND.Null);
var size = $"{Native.GetDeviceCaps(desktop, GET_DEVICE_CAPS_INDEX.DESKTOPHORZRES)}x{Native.GetDeviceCaps(desktop, GET_DEVICE_CAPS_INDEX.DESKTOPVERTRES)}";
Native.ReleaseDC(HWND.Null, desktop);
return size;
}

View File

@@ -1,5 +1,6 @@
using System.Diagnostics;
using System.Net;
using System.Runtime.Versioning;
using System.Text;
using System.Text.Json;
using System.Text.Json.Serialization;

View File

@@ -1,52 +1,77 @@
using System.ComponentModel;
using Microsoft.Win32.SafeHandles;
using System.ComponentModel;
using System.Runtime.InteropServices;
using System.Runtime.Versioning;
using Windows.Win32;
using Windows.Win32.Foundation;
using Windows.Win32.System.Memory;
using Windows.Win32.System.Threading;
using YaeAchievement.Win32;
using static YaeAchievement.Win32.Native;
namespace YaeAchievement;
namespace YaeAchievement;
public static class Injector {
public static unsafe bool CreateProcess(string path, out IntPtr hProc, out IntPtr hThread, out uint pid) {
var si = new StartupInfo();
SecurityAttributes* attr = null;
public static unsafe bool CreateProcess(string path, out HANDLE hProc, out HANDLE hThread, out uint pid) {
Span<char> cmdLines = stackalloc char[1]; // "\0"
var si = new STARTUPINFOW() { cb = unchecked((uint)sizeof(STARTUPINFOW)) };
var dir = Path.GetDirectoryName(path)!;
var result = Native.CreateProcess(
path, null, ref *attr, ref *attr, false,
CreationFlags.CreateSuspended, IntPtr.Zero, dir, ref si, out var pi
path, ref cmdLines, default, default, false,
PROCESS_CREATION_FLAGS.CREATE_SUSPENDED, default, dir, in si, out var pi
);
pid = pi.dwProcessID;
pid = pi.dwProcessId;
hProc = pi.hProcess;
hThread = pi.hThread;
return result;
}
// todo: refactor
public static int LoadLibraryAndInject(IntPtr hProc, string libPath) {
var hKernel = GetModuleHandle("kernel32.dll");
if (hKernel == IntPtr.Zero) {
return new Win32Exception().PrintMsgAndReturnErrCode("GetModuleHandle fail");
public static unsafe int LoadLibraryAndInject(HANDLE hProc, ReadOnlySpan<byte> libPath)
{
fixed (char* lpModelName = "kernel32.dll")
{
HINSTANCE hKernel = Native.GetModuleHandle(lpModelName);
if (hKernel.IsNull)
{
return new Win32Exception().PrintMsgAndReturnErrCode("GetModuleHandle fail");
}
fixed(byte* lpProcName = "LoadLibraryA"u8)
{
var pLoadLibrary = Native.GetProcAddress(hKernel, (PCSTR)lpProcName);
if (pLoadLibrary.IsNull)
{
return new Win32Exception().PrintMsgAndReturnErrCode("GetProcAddress fail");
}
var pBase = Native.VirtualAllocEx(hProc, default, unchecked((uint)libPath.Length + 1), VIRTUAL_ALLOCATION_TYPE.MEM_RESERVE | VIRTUAL_ALLOCATION_TYPE.MEM_COMMIT, PAGE_PROTECTION_FLAGS.PAGE_READWRITE);
if ((nint)pBase == 0)
{
return new Win32Exception().PrintMsgAndReturnErrCode("VirtualAllocEx fail");
}
fixed (void* lpBuffer = libPath)
{
if (!Native.WriteProcessMemory(hProc, pBase, lpBuffer, unchecked((uint)libPath.Length), default))
{
return new Win32Exception().PrintMsgAndReturnErrCode("WriteProcessMemory fail");
}
}
var lpStartAddress = pLoadLibrary.CreateDelegate<LPTHREAD_START_ROUTINE>();
var hThread = Native.CreateRemoteThread(hProc, default, 0, lpStartAddress, pBase, 0, default);
if (hThread.IsNull)
{
var e = new Win32Exception();
Native.VirtualFreeEx(hProc, pBase, 0, VIRTUAL_FREE_TYPE.MEM_RELEASE);
return e.PrintMsgAndReturnErrCode("CreateRemoteThread fail");
}
if (Native.WaitForSingleObject(hThread, 2000) == 0)
{
Native.VirtualFreeEx(hProc, pBase, 0, VIRTUAL_FREE_TYPE.MEM_RELEASE);
}
return !Native.CloseHandle(hThread) ? new Win32Exception().PrintMsgAndReturnErrCode("CloseHandle fail") : 0;
}
}
var pLoadLibrary = GetProcAddress(hKernel, "LoadLibraryA");
if (pLoadLibrary == IntPtr.Zero) {
return new Win32Exception().PrintMsgAndReturnErrCode("GetProcAddress fail");
}
var pBase = VirtualAllocEx(hProc, IntPtr.Zero, libPath.Length + 1, AllocationType.Reserve | AllocationType.Commit, MemoryProtection.ReadWrite);
if (pBase == IntPtr.Zero) {
return new Win32Exception().PrintMsgAndReturnErrCode("VirtualAllocEx fail");
}
if (!WriteProcessMemory(hProc, pBase, libPath.ToCharArray(), libPath.Length, out _)) {
return new Win32Exception().PrintMsgAndReturnErrCode("WriteProcessMemory fail");
}
var hThread = CreateRemoteThread(hProc, IntPtr.Zero, 0, pLoadLibrary, pBase, 0, out _);
if (hThread == IntPtr.Zero) {
var e = new Win32Exception();
VirtualFreeEx(hProc, pBase, 0, AllocationType.Release);
return e.PrintMsgAndReturnErrCode("CreateRemoteThread fail");
}
if (WaitForSingleObject(hThread, 2000) == 0) {
VirtualFreeEx(hProc, pBase, 0, AllocationType.Release);
}
return !CloseHandle(hThread) ? new Win32Exception().PrintMsgAndReturnErrCode("CloseHandle fail") : 0;
}
}

6
src/NativeMethods.json Normal file
View File

@@ -0,0 +1,6 @@
{
"$schema": "https://aka.ms/CsWin32.schema.json",
"className": "Native",
"allowMarshaling": true,
"public": true
}

21
src/NativeMethods.txt Normal file
View File

@@ -0,0 +1,21 @@
CreateProcess
GetModuleHandle
GetProcAddress
VirtualAllocEx
WriteProcessMemory
CreateRemoteThread
VirtualFreeEx
WaitForSingleObject
OpenClipboard
EmptyClipboard
GlobalLock
SetClipboardData
GlobalUnlock
CloseClipboard
GetStdHandle
GetConsoleMode
SetConsoleMode
TerminateProcess
ResumeThread
GetDeviceCaps
GetDC

View File

@@ -1,11 +1,15 @@
using System.ComponentModel;
using Microsoft.Win32;
using System.ComponentModel;
using System.Diagnostics;
using System.IO.Pipes;
using System.Net;
using System.Net.Http.Headers;
using System.Net.Sockets;
using System.Runtime.InteropServices;
using Microsoft.Win32;
using System.Text;
using Windows.Win32;
using Windows.Win32.Foundation;
using Windows.Win32.System.Console;
using YaeAchievement.AppCenterSDK;
using YaeAchievement.res;
using YaeAchievement.Win32;
@@ -64,18 +68,21 @@ public static class Utils {
public static bool ToBooleanOrFalse(string? value) {
return value != null && bool.TryParse(value, out var result) && result;
}
public static void CopyToClipboard(string text) {
if (Native.OpenClipboard(IntPtr.Zero)) {
public static unsafe void CopyToClipboard(string text) {
if (Native.OpenClipboard(HWND.Null))
{
Native.EmptyClipboard();
var hGlobal = Marshal.AllocHGlobal((text.Length + 1) * 2);
var hPtr = Native.GlobalLock(hGlobal);
HANDLE hGlobal = (HANDLE)Marshal.AllocHGlobal((text.Length + 1) * 2);
IntPtr hPtr = (IntPtr)Native.GlobalLock(hGlobal);
Marshal.Copy(text.ToCharArray(), 0, hPtr, text.Length);
Native.GlobalUnlock(hPtr);
Native.SetClipboardData(13, hGlobal);
Marshal.FreeHGlobal(hGlobal);
Native.CloseClipboard();
} else {
}
else
{
throw new Win32Exception();
}
}
@@ -144,9 +151,10 @@ public static class Utils {
}
// ReSharper disable once UnusedMethodReturnValue.Global
public static bool TryDisableQuickEdit() {
var handle = Native.GetStdHandle();
return Native.GetConsoleMode(handle, out var mode) && Native.SetConsoleMode(handle, mode&~64);
public static unsafe bool TryDisableQuickEdit() {
var handle = Native.GetStdHandle(STD_HANDLE.STD_INPUT_HANDLE);
CONSOLE_MODE mode = default;
return Native.GetConsoleMode(handle, &mode) && Native.SetConsoleMode(handle, mode & ~CONSOLE_MODE.ENABLE_QUICK_EDIT_MODE);
}
public static void CheckGenshinIsRunning() {
@@ -208,7 +216,7 @@ public static class Utils {
return File.Exists(path) && (hash == _updateInfo.CurrentCNGameHash || hash == _updateInfo.CurrentOSGameHash);
#endif
}
// ReSharper disable once UnusedMethodReturnValue.Global
public static Thread StartAndWaitResult(string exePath, Func<string, bool> onReceive) {
if (!CheckGenshinIsLatestVersion(exePath)) {
@@ -223,8 +231,10 @@ public static class Utils {
if (!Injector.CreateProcess(exePath, out var hProcess, out var hThread, out var pid)) {
Environment.Exit(new Win32Exception().PrintMsgAndReturnErrCode("ICreateProcess fail"));
}
if (Injector.LoadLibraryAndInject(hProcess, GlobalVars.LibFilePath) != 0) {
if (!Native.TerminateProcess(hProcess, 0)) {
if (Injector.LoadLibraryAndInject(hProcess,Encoding.UTF8.GetBytes(GlobalVars.LibFilePath)) != 0)
{
if (!Native.TerminateProcess(hProcess, 0))
{
Environment.Exit(new Win32Exception().PrintMsgAndReturnErrCode("TerminateProcess fail"));
}
}
@@ -232,22 +242,27 @@ public static class Utils {
proc = Process.GetProcessById(Convert.ToInt32(pid));
proc.EnableRaisingEvents = true;
proc.Exited += (_, _) => {
if (GlobalVars.UnexpectedExit) {
if (GlobalVars.UnexpectedExit)
{
proc = null;
Console.WriteLine(App.GameProcessExit);
Environment.Exit(114514);
}
};
if (Native.ResumeThread(hThread) == 0xFFFFFFFF) {
if (Native.ResumeThread(hThread) == 0xFFFFFFFF)
{
var e = new Win32Exception();
if (!Native.TerminateProcess(hProcess, 0)) {
if (!Native.TerminateProcess(hProcess, 0))
{
new Win32Exception().PrintMsgAndReturnErrCode("TerminateProcess fail");
}
Environment.Exit(e.PrintMsgAndReturnErrCode("ResumeThread fail"));
}
if (!Native.CloseHandle(hProcess)) {
if (!Native.CloseHandle(hProcess))
{
Environment.Exit(new Win32Exception().PrintMsgAndReturnErrCode("CloseHandle fail"));
}
var ts = new ThreadStart(() => {
var server = new NamedPipeServerStream(GlobalVars.PipeName);
server.WaitForConnection();

View File

@@ -1,16 +0,0 @@
namespace YaeAchievement.Win32;
[Flags]
public enum AllocationType : uint {
Commit = 0x00001000,
Reserve = 0x00002000,
Reset = 0x00080000,
TopDown = 0x00100000,
WriteWatch = 0x00200000,
Physical = 0x00400000,
Rotate = 0x00800000,
ResetUndo = 0x01000000,
LargePages = 0x20000000,
Decommit = 0x00004000,
Release = 0x00008000
}

View File

@@ -1,9 +0,0 @@
namespace YaeAchievement.Win32;
[Flags]
public enum CreationFlags : uint {
CreateSuspended = 0x00000004,
DetachedProcess = 0x00000008,
CreateNoWindow = 0x08000000,
ExtendedStartupInfoPresent = 0x00080000
}

View File

@@ -1,16 +0,0 @@
namespace YaeAchievement.Win32;
[Flags]
public enum MemoryProtection : uint {
Execute = 0x10,
ExecuteRead = 0x20,
ExecuteReadWrite = 0x40,
ExecuteWriteCopy = 0x80,
NoAccess = 0x01,
ReadOnly = 0x02,
ReadWrite = 0x04,
WriteCopy = 0x08,
GuardModifierFlag = 0x100,
NoCacheModifierFlag = 0x200,
WriteCombineModifierFlag = 0x400
}

View File

@@ -1,119 +0,0 @@
using System.Runtime.InteropServices;
using System.Security;
namespace YaeAchievement.Win32;
#pragma warning disable CA1401, CA2101
public static class Native {
[DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
public static extern bool CreateProcess(
string lpApplicationName,
string? lpCommandLine,
ref SecurityAttributes lpProcessAttributes,
ref SecurityAttributes lpThreadAttributes,
bool bInheritHandles,
CreationFlags dwCreationFlags,
IntPtr lpEnvironment,
string? lpCurrentDirectory,
[In] ref StartupInfo lpStartupInfo,
out ProcessInformation lpProcessInformation
);
[return: MarshalAs(UnmanagedType.Bool)]
[DllImport("kernel32.dll", SetLastError = true)]
public static extern bool TerminateProcess(IntPtr hProcess, uint uExitCode);
[DllImport("kernel32.dll")]
public static extern bool WriteProcessMemory(
IntPtr hProcess,
IntPtr lpBaseAddress,
char[] lpBuffer,
int nSize,
out IntPtr lpNumberOfBytesWritten
);
[DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
public static extern IntPtr GetModuleHandle(string lpModuleName);
[DllImport("kernel32.dll", CharSet = CharSet.Ansi, SetLastError = true)]
public static extern IntPtr GetProcAddress(IntPtr hModule, string lpProcName);
[DllImport("kernel32.dll", SetLastError = true, ExactSpelling = true)]
public static extern IntPtr VirtualAllocEx(
IntPtr hProcess,
IntPtr lpAddress,
int dwSize,
AllocationType flAllocationType,
MemoryProtection flProtect
);
[DllImport("kernel32.dll", SetLastError = true, ExactSpelling = true)]
public static extern bool VirtualFreeEx(IntPtr hProcess, IntPtr lpAddress, int dwSize, AllocationType dwFreeType);
[DllImport("kernel32.dll", SetLastError = true)]
public static extern uint ResumeThread(IntPtr hThread);
[SuppressUnmanagedCodeSecurity]
[return: MarshalAs(UnmanagedType.Bool)]
[DllImport("kernel32.dll", SetLastError = true)]
public static extern bool CloseHandle(IntPtr hObject);
[DllImport("kernel32.dll", SetLastError = true)]
public static extern IntPtr CreateRemoteThread(
IntPtr hProcess,
IntPtr lpThreadAttributes,
int dwStackSize,
IntPtr lpStartAddress,
IntPtr lpParameter,
uint dwCreationFlags,
out IntPtr lpThreadId
);
// ReSharper disable once InconsistentNaming
private const int STD_INPUT_HANDLE = -10;
[DllImport("kernel32.dll", SetLastError = true)]
public static extern IntPtr GetStdHandle(int nStdHandle = STD_INPUT_HANDLE);
[DllImport("kernel32.dll", SetLastError = true)]
public static extern bool GetConsoleMode(IntPtr handle, out int lpMode);
[DllImport("kernel32.dll", SetLastError = true)]
public static extern bool SetConsoleMode(IntPtr handle, int ioMode);
[DllImport("kernel32.dll", SetLastError = true)]
public static extern IntPtr GlobalLock(IntPtr mem);
[return: MarshalAs(UnmanagedType.Bool)]
[DllImport("kernel32.dll", SetLastError = true)]
public static extern bool GlobalUnlock(IntPtr mem);
[DllImport("user32.dll", SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool OpenClipboard(IntPtr owner);
[return: MarshalAs(UnmanagedType.Bool)]
[DllImport("user32.dll", SetLastError = true)]
public static extern bool CloseClipboard();
[DllImport("user32.dll", SetLastError = true)]
public static extern IntPtr SetClipboardData(uint uFormat, IntPtr data);
[DllImport("user32.dll", SetLastError = true)]
public static extern bool EmptyClipboard();
[DllImport("gdi32.dll", SetLastError = true)]
public static extern int GetDeviceCaps(IntPtr hdc, int nIndex);
[DllImport("user32.dll", SetLastError = true)]
public static extern IntPtr GetDC(IntPtr hWnd);
[DllImport("user32.dll", SetLastError = true)]
public static extern bool ReleaseDC(IntPtr hWnd, IntPtr hdc);
[DllImport("kernel32.dll", SetLastError = true)]
public static extern uint WaitForSingleObject(IntPtr handle, ulong dwMilliseconds);
}

View File

@@ -1,14 +0,0 @@
using System.Runtime.InteropServices;
// ReSharper disable MemberCanBePrivate.Global
// ReSharper disable FieldCanBeMadeReadOnly.Global
namespace YaeAchievement.Win32;
[StructLayout(LayoutKind.Sequential)]
public struct ProcessInformation {
public IntPtr hProcess;
public IntPtr hThread;
public uint dwProcessID;
public uint dwThreadID;
}

View File

@@ -1,12 +0,0 @@
using System.Runtime.InteropServices;
// ReSharper disable MemberCanBePrivate.Global
// ReSharper disable FieldCanBeMadeReadOnly.Global
namespace YaeAchievement.Win32;
[StructLayout(LayoutKind.Sequential)]
public struct SecurityAttributes {
public int nLength;
public IntPtr lpSecurityDescriptor;
}

View File

@@ -1,28 +0,0 @@
using System.Runtime.InteropServices;
// ReSharper disable MemberCanBePrivate.Global
// ReSharper disable FieldCanBeMadeReadOnly.Global
namespace YaeAchievement.Win32;
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
public struct StartupInfo {
public uint cb;
public string lpReserved;
public string lpDesktop;
public string lpTitle;
public uint dwX;
public uint dwY;
public uint dwXSize;
public uint dwYSize;
public uint dwXCountChars;
public uint dwYCountChars;
public uint dwFillAttribute;
public uint dwFlags;
public ushort wShowWindow;
public ushort cbReserved2;
public IntPtr lpReserved2;
public IntPtr hStdInput;
public IntPtr hStdOutput;
public IntPtr hStdError;
}