diff --git a/YaeAchievement.csproj b/YaeAchievement.csproj index cf102bd..986a23e 100644 --- a/YaeAchievement.csproj +++ b/YaeAchievement.csproj @@ -1,8 +1,8 @@ - + Exe - net6.0 + net7.0-windows7 enable enable preview @@ -21,8 +21,12 @@ - - + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + @@ -31,6 +35,10 @@ App.Designer.cs + + + + PreserveNewest diff --git a/src/AppCenterSDK/DeviceHelper.cs b/src/AppCenterSDK/DeviceHelper.cs index b6abdca..fa102dd 100644 --- a/src/AppCenterSDK/DeviceHelper.cs +++ b/src/AppCenterSDK/DeviceHelper.cs @@ -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; } diff --git a/src/Export.cs b/src/Export.cs index f615f65..9039de7 100644 --- a/src/Export.cs +++ b/src/Export.cs @@ -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; diff --git a/src/Injector.cs b/src/Injector.cs index 7350275..1c9ccc7 100644 --- a/src/Injector.cs +++ b/src/Injector.cs @@ -1,52 +1,71 @@ -using System.ComponentModel; +using Microsoft.Win32.SafeHandles; +using System.ComponentModel; +using System.Runtime.InteropServices; +using System.Runtime.Versioning; +using Windows.Win32; +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 SafeHandle hProc, out SafeHandle hThread, out uint pid) { + Span 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; - hProc = pi.hProcess; - hThread = pi.hThread; + pid = pi.dwProcessId; + hProc = new SafeFileHandle(pi.hProcess, true); + hThread = new SafeFileHandle(pi.hThread, true); return result; } - + // todo: refactor - public static int LoadLibraryAndInject(IntPtr hProc, string libPath) { - var hKernel = GetModuleHandle("kernel32.dll"); - if (hKernel == IntPtr.Zero) { + public static unsafe int LoadLibraryAndInject(SafeHandle hProc, string libPath) + { + var hKernel = Native.GetModuleHandle("kernel32.dll"); + if (hKernel.IsInvalid) + { return new Win32Exception().PrintMsgAndReturnErrCode("GetModuleHandle fail"); } - var pLoadLibrary = GetProcAddress(hKernel, "LoadLibraryA"); - if (pLoadLibrary == IntPtr.Zero) { + var pLoadLibrary = Native.GetProcAddress(hKernel, "LoadLibraryA"); + if (pLoadLibrary.IsNull) + { 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) { + 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"); } - if (!WriteProcessMemory(hProc, pBase, libPath.ToCharArray(), libPath.Length, out _)) { - return new Win32Exception().PrintMsgAndReturnErrCode("WriteProcessMemory fail"); + fixed (void* lpBuffer = libPath.ToCharArray()) + { + if (!Native.WriteProcessMemory(hProc, pBase, lpBuffer, unchecked((uint)libPath.Length), default)) + { + 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"); + var lpStartAddress = pLoadLibrary.CreateDelegate(); + + using (var hThread = Native.CreateRemoteThread(hProc, default, 0, lpStartAddress, pBase, 0, default)) + { + if (hThread.IsInvalid) + { + 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; + return 0; } - if (WaitForSingleObject(hThread, 2000) == 0) { - VirtualFreeEx(hProc, pBase, 0, AllocationType.Release); - } - return !CloseHandle(hThread) ? new Win32Exception().PrintMsgAndReturnErrCode("CloseHandle fail") : 0; } - + } \ No newline at end of file diff --git a/src/NativeMethods.json b/src/NativeMethods.json new file mode 100644 index 0000000..2e55a35 --- /dev/null +++ b/src/NativeMethods.json @@ -0,0 +1,6 @@ +{ + "$schema": "https://aka.ms/CsWin32.schema.json", + "className": "Native", + "allowMarshaling": true, + "public": false +} \ No newline at end of file diff --git a/src/NativeMethods.txt b/src/NativeMethods.txt new file mode 100644 index 0000000..d3adb67 --- /dev/null +++ b/src/NativeMethods.txt @@ -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 \ No newline at end of file diff --git a/src/Utils.cs b/src/Utils.cs index 6da8766..b0aa879 100644 --- a/src/Utils.cs +++ b/src/Utils.cs @@ -1,11 +1,14 @@ -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.Runtime.Versioning; +using Windows.Win32; +using Windows.Win32.Foundation; +using Windows.Win32.System.Console; using YaeAchievement.AppCenterSDK; using YaeAchievement.res; using YaeAchievement.Win32; @@ -64,17 +67,17 @@ 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)) { + + [SupportedOSPlatform("windows5.0")] + 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); - Marshal.Copy(text.ToCharArray(), 0, hPtr, text.Length); - Native.GlobalUnlock(hPtr); - Native.SetClipboardData(13, hGlobal); - Marshal.FreeHGlobal(hGlobal); - Native.CloseClipboard(); + Span textSpan = text.ToCharArray(); + fixed (char* lpBuffer = textSpan) + { + Native.SetClipboardData(/*CF_UNICODETEXT*/13, (HANDLE)(nint)lpBuffer); + Native.CloseClipboard(); + } } else { throw new Win32Exception(); } @@ -144,9 +147,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 +212,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 onReceive) { if (!CheckGenshinIsLatestVersion(exePath)) { @@ -223,31 +227,37 @@ 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)) { - Environment.Exit(new Win32Exception().PrintMsgAndReturnErrCode("TerminateProcess fail")); + using (hProcess) + { + if (Injector.LoadLibraryAndInject(hProcess, GlobalVars.LibFilePath) != 0) + { + if (!Native.TerminateProcess(hProcess, 0)) + { + Environment.Exit(new Win32Exception().PrintMsgAndReturnErrCode("TerminateProcess fail")); + } + } + Console.WriteLine(App.GameLoading, pid); + proc = Process.GetProcessById(Convert.ToInt32(pid)); + proc.EnableRaisingEvents = true; + proc.Exited += (_, _) => { + if (GlobalVars.UnexpectedExit) + { + proc = null; + Console.WriteLine(App.GameProcessExit); + Environment.Exit(114514); + } + }; + if (Native.ResumeThread(hThread) == 0xFFFFFFFF) + { + var e = new Win32Exception(); + if (!Native.TerminateProcess(hProcess, 0)) + { + new Win32Exception().PrintMsgAndReturnErrCode("TerminateProcess fail"); + } + Environment.Exit(e.PrintMsgAndReturnErrCode("ResumeThread fail")); } } - Console.WriteLine(App.GameLoading, pid); - proc = Process.GetProcessById(Convert.ToInt32(pid)); - proc.EnableRaisingEvents = true; - proc.Exited += (_, _) => { - if (GlobalVars.UnexpectedExit) { - proc = null; - Console.WriteLine(App.GameProcessExit); - Environment.Exit(114514); - } - }; - if (Native.ResumeThread(hThread) == 0xFFFFFFFF) { - var e = new Win32Exception(); - if (!Native.TerminateProcess(hProcess, 0)) { - new Win32Exception().PrintMsgAndReturnErrCode("TerminateProcess fail"); - } - Environment.Exit(e.PrintMsgAndReturnErrCode("ResumeThread fail")); - } - if (!Native.CloseHandle(hProcess)) { - Environment.Exit(new Win32Exception().PrintMsgAndReturnErrCode("CloseHandle fail")); - } + var ts = new ThreadStart(() => { var server = new NamedPipeServerStream(GlobalVars.PipeName); server.WaitForConnection(); diff --git a/src/Win32/AllocationType.cs b/src/Win32/AllocationType.cs deleted file mode 100644 index 17c30d6..0000000 --- a/src/Win32/AllocationType.cs +++ /dev/null @@ -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 -} diff --git a/src/Win32/CreationFlags.cs b/src/Win32/CreationFlags.cs deleted file mode 100644 index b9c7529..0000000 --- a/src/Win32/CreationFlags.cs +++ /dev/null @@ -1,9 +0,0 @@ -namespace YaeAchievement.Win32; - -[Flags] -public enum CreationFlags : uint { - CreateSuspended = 0x00000004, - DetachedProcess = 0x00000008, - CreateNoWindow = 0x08000000, - ExtendedStartupInfoPresent = 0x00080000 -} diff --git a/src/Win32/MemoryProtection.cs b/src/Win32/MemoryProtection.cs deleted file mode 100644 index d6899ec..0000000 --- a/src/Win32/MemoryProtection.cs +++ /dev/null @@ -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 -} diff --git a/src/Win32/Native.cs b/src/Win32/Native.cs deleted file mode 100644 index 975fdd5..0000000 --- a/src/Win32/Native.cs +++ /dev/null @@ -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); - -} diff --git a/src/Win32/ProcessInformation.cs b/src/Win32/ProcessInformation.cs deleted file mode 100644 index 192a5e3..0000000 --- a/src/Win32/ProcessInformation.cs +++ /dev/null @@ -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; -} diff --git a/src/Win32/SecurityAttributes.cs b/src/Win32/SecurityAttributes.cs deleted file mode 100644 index 45526b8..0000000 --- a/src/Win32/SecurityAttributes.cs +++ /dev/null @@ -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; -} diff --git a/src/Win32/StartupInfo.cs b/src/Win32/StartupInfo.cs deleted file mode 100644 index 89c46eb..0000000 --- a/src/Win32/StartupInfo.cs +++ /dev/null @@ -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; -}