From b46c6ad54853a6328cce2c7e590218248fc5b037 Mon Sep 17 00:00:00 2001 From: Frame Date: Fri, 6 Mar 2026 16:01:24 +0100 Subject: [PATCH 1/8] add 'Win32NativeMethods' project for CsWin32 usage --- STROOP.Win32/NativeMethods.json | 4 ++++ STROOP.Win32/NativeMethods.txt | 1 + STROOP.Win32/STROOP.Win32.csproj | 16 ++++++++++++++++ 3 files changed, 21 insertions(+) create mode 100644 STROOP.Win32/NativeMethods.json create mode 100644 STROOP.Win32/NativeMethods.txt create mode 100644 STROOP.Win32/STROOP.Win32.csproj diff --git a/STROOP.Win32/NativeMethods.json b/STROOP.Win32/NativeMethods.json new file mode 100644 index 000000000..d1b171f72 --- /dev/null +++ b/STROOP.Win32/NativeMethods.json @@ -0,0 +1,4 @@ +{ + "$schema": "https://aka.ms/CsWin32.schema.json", + "public": true +} diff --git a/STROOP.Win32/NativeMethods.txt b/STROOP.Win32/NativeMethods.txt new file mode 100644 index 000000000..5f282702b --- /dev/null +++ b/STROOP.Win32/NativeMethods.txt @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/STROOP.Win32/STROOP.Win32.csproj b/STROOP.Win32/STROOP.Win32.csproj new file mode 100644 index 000000000..73254d5aa --- /dev/null +++ b/STROOP.Win32/STROOP.Win32.csproj @@ -0,0 +1,16 @@ + + + + net8.0 + enable + enable + STROOP.Win32 + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + From 0d5802543c102a4b06f6735d982f151dccab2a27 Mon Sep 17 00:00:00 2001 From: Frame Date: Fri, 6 Mar 2026 17:58:45 +0100 Subject: [PATCH 2/8] replace PInvokes in 'Kernal32NativeMethods.cs' with CsWin32 usages --- .../GameMemoryAccess/DolphinProcessIO.cs | 14 +- .../GameMemoryAccess/WindowsProcessIO.cs | 47 +++--- STROOP.Core/Kernal32NativeMethods.cs | 142 ++---------------- STROOP.Core/STROOP.Core.csproj | 3 + STROOP.Win32/NativeMethodWrappers.cs | 71 +++++++++ STROOP.Win32/NativeMethods.txt | 16 +- STROOP.sln | 14 ++ 7 files changed, 145 insertions(+), 162 deletions(-) create mode 100644 STROOP.Win32/NativeMethodWrappers.cs diff --git a/STROOP.Core/GameMemoryAccess/DolphinProcessIO.cs b/STROOP.Core/GameMemoryAccess/DolphinProcessIO.cs index 276bd7267..7173f935f 100644 --- a/STROOP.Core/GameMemoryAccess/DolphinProcessIO.cs +++ b/STROOP.Core/GameMemoryAccess/DolphinProcessIO.cs @@ -1,5 +1,8 @@ -using System.Diagnostics; +using STROOP.Win32; +using System.Diagnostics; using System.Runtime.InteropServices; +using Windows.Win32.Foundation; +using Windows.Win32.System.ProcessStatus; using static STROOP.Core.Kernal32NativeMethods; namespace STROOP.Core.GameMemoryAccess; @@ -15,12 +18,11 @@ protected override void CalculateOffset() { MemoryBasicInformation info; IntPtr infoSize = (IntPtr)Marshal.SizeOf(typeof(MemoryBasicInformation)); - uint setInfoSize = (uint)Marshal.SizeOf(typeof(PsapiWorkingSetExInformation)); _baseOffset = (UIntPtr)0; bool mem1Found = false; for (IntPtr p = new IntPtr(); - VQueryEx(_processHandle, p, out info, infoSize) == infoSize; + VirtualQueryEx(_processHandle, p, out info, infoSize) == infoSize; p = (IntPtr)(p.ToInt64() + info.RegionSize.ToInt64())) { if (mem1Found) @@ -43,11 +45,9 @@ protected override void CalculateOffset() // exists and have nothing to do with the emulated memory. Only the right page has valid // working set information so an additional check is required that it is backed by physical // memory. - PsapiWorkingSetExInformation wsInfo; - wsInfo.VirtualAddress = (IntPtr)info.BaseAddress.ToUInt64(); - if (QWorkingSetEx(_processHandle, out wsInfo, setInfoSize)) + if (NativeMethodWrappers.QueryWorkingSetEx((HANDLE)_processHandle, info.BaseAddress, out PSAPI_WORKING_SET_EX_INFORMATION wsInfo)) { - if ((wsInfo.VirtualAttributes & 0x01) != 0) + if ((wsInfo.VirtualAttributes.Flags & 0x01) != 0) { _baseOffset = info.BaseAddress; mem1Found = true; diff --git a/STROOP.Core/GameMemoryAccess/WindowsProcessIO.cs b/STROOP.Core/GameMemoryAccess/WindowsProcessIO.cs index 914437a72..5dc518916 100644 --- a/STROOP.Core/GameMemoryAccess/WindowsProcessIO.cs +++ b/STROOP.Core/GameMemoryAccess/WindowsProcessIO.cs @@ -1,5 +1,9 @@ -using System.ComponentModel; +using STROOP.Win32; +using System.ComponentModel; using System.Diagnostics; +using Windows.Win32; +using Windows.Win32.Foundation; +using Windows.Win32.System.Threading; using static STROOP.Core.Kernal32NativeMethods; namespace STROOP.Core.GameMemoryAccess; @@ -29,16 +33,20 @@ public WindowsProcessRamIO(Process process, Emulator emulator) : base() _process.EnableRaisingEvents = true; - ProcessAccess accessFlags = ProcessAccess.PROCESS_QUERY_LIMITED_INFORMATION | ProcessAccess.SUSPEND_RESUME - | ProcessAccess.VM_OPERATION | ProcessAccess.VM_READ | ProcessAccess.VM_WRITE; - _processHandle = ProcessGetHandleFromId(accessFlags, false, _process.Id); + var accessFlags = PROCESS_ACCESS_RIGHTS.PROCESS_QUERY_LIMITED_INFORMATION + | PROCESS_ACCESS_RIGHTS.PROCESS_SUSPEND_RESUME + | PROCESS_ACCESS_RIGHTS.PROCESS_VM_OPERATION + | PROCESS_ACCESS_RIGHTS.PROCESS_VM_READ + | PROCESS_ACCESS_RIGHTS.PROCESS_VM_WRITE; + + _processHandle = PInvoke.OpenProcess(accessFlags, false, (uint)_process.Id); try { CalculateOffset(); } catch (Exception e) { - CloseProcess(_processHandle); + PInvoke.CloseHandle((HANDLE)_processHandle); throw; } @@ -52,16 +60,10 @@ private void _process_Exited(object sender, EventArgs e) } protected override bool ReadFunc(UIntPtr address, byte[] buffer) - { - int numOfBytes = 0; - return ProcessReadMemory(_processHandle, address, buffer, (IntPtr)buffer.Length, ref numOfBytes); - } + => NativeMethodWrappers.ReadProcessMemory(_processHandle, address, buffer); protected override bool WriteFunc(UIntPtr address, byte[] buffer) - { - int numOfBytes = 0; - return ProcessWriteMemory(_processHandle, address, buffer, (IntPtr)buffer.Length, ref numOfBytes); - } + => NativeMethodWrappers.WriteProcessMemory(_processHandle, address, buffer); public override byte[] ReadAllMemory() { @@ -71,8 +73,8 @@ public override byte[] ReadAllMemory() for (uint address = 0; true; address++) { - bool success = ProcessReadMemory(_processHandle, (UIntPtr)address, buffer, (IntPtr)buffer.Length, ref numBytes); - if (!success) break; + if (!NativeMethodWrappers.ReadProcessMemory(_processHandle, address, buffer)) + break; output.Add(buffer[0]); } @@ -91,30 +93,29 @@ private bool CompareBytes(byte[] a, byte[] b) // see https://msdn.microsoft.com/en-us/library/windows/desktop/ms684139%28v=vs.85%29.aspx public static bool Is64Bit(Process process) => Environment.Is64BitOperatingSystem - && IsWow64Process(process.Handle, out bool isWow64) + && PInvoke.IsWow64Process(process.SafeHandle, out var isWow64) ? !isWow64 : throw new Win32Exception(); protected virtual void CalculateOffset() { // Find CORE_RDRAM export from mupen if present - Win32SymbolInfo symbol = Win32SymbolInfo.Create(); - if (SymInitialize(_process.Handle, null, true)) + if (PInvoke.SymInitialize(_process.SafeHandle, null, true)) { try { - if (SymFromName(_process.Handle, "CORE_RDRAM", ref symbol)) + if (NativeMethodWrappers.GetSymbolAddress(_process.SafeHandle, "CORE_RDRAM", out var address)) { bool is64Bit = Is64Bit(_process); byte[]? buffer = new byte[is64Bit ? 8 : 4]; - ReadAbsolute((UIntPtr)symbol.Address, buffer, EndiannessType.Little); + ReadAbsolute((UIntPtr)address, buffer, EndiannessType.Little); _baseOffset = (UIntPtr)(is64Bit ? BitConverter.ToUInt64(buffer, 0) : (ulong)BitConverter.ToUInt32(buffer, 0)); return; } } finally { - if (!SymCleanup(_process.Handle)) + if (!PInvoke.SymCleanup(_process.SafeHandle)) throw new Win32Exception(); } } @@ -122,7 +123,7 @@ protected virtual void CalculateOffset() { // documentation doesn't say what to do when SymInitialize returns false, so just call this and don't care for its result for good (or bad) measure :shrug: // https://learn.microsoft.com/en-us/windows/win32/api/dbghelp/nf-dbghelp-syminitialize - SymCleanup(_process.Handle); + PInvoke.SymCleanup(_process.SafeHandle); } // Find DLL offset if needed @@ -243,7 +244,7 @@ protected virtual void Dispose(bool disposing) } // Close old process - CloseProcess(_processHandle); + PInvoke.CloseHandle((HANDLE)_processHandle); disposedValue = true; } diff --git a/STROOP.Core/Kernal32NativeMethods.cs b/STROOP.Core/Kernal32NativeMethods.cs index 21d2f7da4..b2e7fb771 100644 --- a/STROOP.Core/Kernal32NativeMethods.cs +++ b/STROOP.Core/Kernal32NativeMethods.cs @@ -1,34 +1,13 @@ using System.Diagnostics; using System.Runtime.InteropServices; +using Windows.Win32; +using Windows.Win32.Foundation; +using Windows.Win32.System.Threading; namespace STROOP.Core; public static class Kernal32NativeMethods { - [Flags] - public enum ThreadAccess : int - { - TERMINATE = 0x0001, - SUSPEND_RESUME = 0x0002, - GET_CONTEXT = 0x0008, - SET_CONTEXT = 0x0010, - SET_INFORMATION = 0x0020, - QUERY_INFORMATION = 0x0040, - SET_THREAD_TOKEN = 0x0080, - IMPERSONATE = 0x0100, - DIRECT_IMPERSONATION = 0x0200, - } - - [Flags] - public enum ProcessAccess : int - { - VM_OPERATION = 0x0008, - VM_READ = 0x0010, - VM_WRITE = 0x0020, - PROCESS_QUERY_LIMITED_INFORMATION = 0x1000, - SUSPEND_RESUME = 0x0800, - } - [Flags] public enum MemoryType : uint { @@ -49,121 +28,26 @@ public struct MemoryBasicInformation public MemoryType Type; } - [StructLayout(LayoutKind.Sequential)] - public struct PsapiWorkingSetExInformation - { - public IntPtr VirtualAddress; - public ulong VirtualAttributes; - } - - /// - /// C# representation of SYMBOL_INFO in dbghelp.h. - /// https://learn.microsoft.com/de-de/windows/win32/api/dbghelp/ns-dbghelp-symbol_info - /// - [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)] - public struct Win32SymbolInfo - { - private const int MaxSymbolLen = 2000; - - public static Win32SymbolInfo Create() - => new Win32SymbolInfo - { - MaxNameLen = MaxSymbolLen, - SizeOfStruct = 88, - }; - - public int SizeOfStruct; - public int TypeIndex; - private readonly ulong Reserved1, Reserved2; - public int Index; - public int Size; - public ulong ModuleBase; - public uint Flags; - public long Value; - public ulong Address; - public uint Register; - public uint Scope; - public uint Tag; - public uint NameLen; - public int MaxNameLen; - - [MarshalAs(UnmanagedType.ByValTStr, SizeConst = MaxSymbolLen)] - public string Name; - } - - #region DLL Import - [DllImport("kernel32.dll")] - private static extern IntPtr OpenThread(ThreadAccess dwDesiredAccess, bool bInheritHandle, uint dwThreadId); - - [DllImport("kernel32.dll")] - private static extern uint SuspendThread(IntPtr hThread); - - [DllImport("kernel32.dll")] - private static extern int ResumeThread(IntPtr hThread); - - [DllImport("kernel32.dll")] - private static extern bool CloseHandle(IntPtr hObject); - - [DllImport("kernel32.dll")] - private static extern IntPtr OpenProcess(ProcessAccess dwDesiredAccess, bool bInheritHandle, int dwProcessId); - - [DllImport("kernel32.dll")] - private static extern bool ReadProcessMemory(IntPtr hProcess, - UIntPtr lpBaseAddress, byte[] lpBuffer, IntPtr dwSize, ref int lpNumberOfBytesRead); - - [DllImport("kernel32.dll", SetLastError = true)] - private static extern bool WriteProcessMemory(IntPtr hProcess, UIntPtr lpBaseAddress, - byte[] lpBuffer, IntPtr dwSize, ref int lpNumberOfBytesWritten); - - [DllImport("kernel32.dll")] - private static extern IntPtr VirtualQueryEx(IntPtr hProcess, IntPtr lpAddress, out MemoryBasicInformation lpBuffer, IntPtr dwLength); - - [DllImport("psapi", SetLastError = true)] - private static extern bool QueryWorkingSetEx(IntPtr hProcess, out PsapiWorkingSetExInformation pv, uint cb); - - [DllImport("dbghelp", SetLastError = true, CharSet = CharSet.Unicode, EntryPoint = "SymInitializeW", ExactSpelling = true)] - public static extern bool SymInitialize(IntPtr hProcess, string searchPath, bool invadeProcess); - - [DllImport("dbghelp", SetLastError = true, CharSet = CharSet.Unicode, EntryPoint = "SymCleanup", ExactSpelling = true)] - public static extern bool SymCleanup(IntPtr hProcess); - - [DllImport("dbghelp", SetLastError = true)] - public static extern bool SymFromName(IntPtr hProcess, string name, ref Win32SymbolInfo win32Symbol); - - [DllImport("kernel32.dll", SetLastError = true, CallingConvention = CallingConvention.Winapi)] - [return: MarshalAs(UnmanagedType.Bool)] - public static extern bool IsWow64Process([In] IntPtr process, [Out] out bool wow64Process); - - #endregion - - public static IntPtr ProcessGetHandleFromId(ProcessAccess dwDesiredAccess, bool bInheritHandle, int dwProcessId) => OpenProcess(dwDesiredAccess, bInheritHandle, dwProcessId); - - public static bool CloseProcess(IntPtr processHandle) => CloseHandle(processHandle); - - public static bool ProcessReadMemory(IntPtr hProcess, - UIntPtr lpBaseAddress, byte[] lpBuffer, IntPtr dwSize, ref int lpNumberOfBytesRead) => ReadProcessMemory(hProcess, lpBaseAddress, lpBuffer, dwSize, ref lpNumberOfBytesRead); - - public static bool ProcessWriteMemory(IntPtr hProcess, UIntPtr lpBaseAddress, - byte[] lpBuffer, IntPtr dwSize, ref int lpNumberOfBytesWritten) => WriteProcessMemory(hProcess, lpBaseAddress, lpBuffer, dwSize, ref lpNumberOfBytesWritten); + public static extern IntPtr VirtualQueryEx(IntPtr hProcess, IntPtr lpAddress, out MemoryBasicInformation lpBuffer, IntPtr dwLength); public static void ResumeProcess(Process process) { // Resume all threads foreach (ProcessThread pT in process.Threads) { - IntPtr pOpenThread = OpenThread(ThreadAccess.SUSPEND_RESUME, false, (uint)pT.Id); + HANDLE pOpenThread = PInvoke.OpenThread(THREAD_ACCESS_RIGHTS.THREAD_SUSPEND_RESUME, false, (uint)pT.Id); if (pOpenThread == IntPtr.Zero) continue; - int suspendCount = 0; + uint suspendCount = 0; do { - suspendCount = ResumeThread(pOpenThread); + suspendCount = PInvoke.ResumeThread(pOpenThread); } while (suspendCount > 0); - CloseHandle(pOpenThread); + PInvoke.CloseHandle(pOpenThread); } } @@ -172,17 +56,13 @@ public static void SuspendProcess(Process process) // Pause all threads foreach (ProcessThread pT in process.Threads) { - IntPtr pOpenThread = OpenThread(ThreadAccess.SUSPEND_RESUME, false, (uint)pT.Id); + HANDLE pOpenThread = PInvoke.OpenThread(THREAD_ACCESS_RIGHTS.THREAD_SUSPEND_RESUME, false, (uint)pT.Id); if (pOpenThread == IntPtr.Zero) continue; - SuspendThread(pOpenThread); - CloseHandle(pOpenThread); + PInvoke.SuspendThread(pOpenThread); + PInvoke.CloseHandle(pOpenThread); } } - - public static IntPtr VQueryEx(IntPtr hProcess, IntPtr lpAddress, out MemoryBasicInformation lpBuffer, IntPtr dwLength) => VirtualQueryEx(hProcess, lpAddress, out lpBuffer, dwLength); - - public static bool QWorkingSetEx(IntPtr hProcess, out PsapiWorkingSetExInformation pv, uint cb) => QueryWorkingSetEx(hProcess, out pv, cb); } diff --git a/STROOP.Core/STROOP.Core.csproj b/STROOP.Core/STROOP.Core.csproj index 229200006..86bbab216 100644 --- a/STROOP.Core/STROOP.Core.csproj +++ b/STROOP.Core/STROOP.Core.csproj @@ -6,4 +6,7 @@ disable + + + diff --git a/STROOP.Win32/NativeMethodWrappers.cs b/STROOP.Win32/NativeMethodWrappers.cs new file mode 100644 index 000000000..b33c004c0 --- /dev/null +++ b/STROOP.Win32/NativeMethodWrappers.cs @@ -0,0 +1,71 @@ +using System.Runtime.InteropServices; +using Windows.Win32; +using Windows.Win32.Foundation; +using Windows.Win32.System.Diagnostics.Debug; +using Windows.Win32.System.ProcessStatus; + +namespace STROOP.Win32; + +public static class NativeMethodWrappers +{ + public static unsafe bool ReadProcessMemory(IntPtr hProcess, UIntPtr lpBaseAddress, byte[] buffer) + { + var gcHandle = GCHandle.Alloc(buffer, GCHandleType.Pinned); + UIntPtr lpNumberOfBytesRead; + try + { + return PInvoke.ReadProcessMemory( + (HANDLE)hProcess, + (void*)lpBaseAddress, + (void*)Marshal.UnsafeAddrOfPinnedArrayElement(buffer, 0), + (uint)buffer.Length, + &lpNumberOfBytesRead + ); + } + finally + { + gcHandle.Free(); + } + } + + public static unsafe bool WriteProcessMemory(IntPtr hProcess, UIntPtr lpBaseAddress, byte[] buffer) + { + UIntPtr numOfBytes = 0; + var gcHandle = GCHandle.Alloc(buffer, GCHandleType.Pinned); + try + { + return PInvoke.WriteProcessMemory( + (HANDLE)hProcess, + (void*)lpBaseAddress, + (void*)Marshal.UnsafeAddrOfPinnedArrayElement(buffer, 0), + (UIntPtr)buffer.Length, + &numOfBytes); + } + finally + { + gcHandle.Free(); + } + } + + public static unsafe bool QueryWorkingSetEx(IntPtr hProcess, UIntPtr lpBaseAddress, out PSAPI_WORKING_SET_EX_INFORMATION wsInfo) + { + PSAPI_WORKING_SET_EX_INFORMATION tmp; + tmp.VirtualAddress = (HANDLE)lpBaseAddress; + uint setInfoSize = (uint)Marshal.SizeOf(typeof(PSAPI_WORKING_SET_EX_INFORMATION)); + var result = PInvoke.QueryWorkingSetEx((HANDLE)hProcess, &tmp, setInfoSize); + wsInfo = tmp; + return result; + } + + public static unsafe bool GetSymbolAddress(SafeHandle hProcess, string name, out ulong address) + { + var symbol = new SYMBOL_INFO + { + MaxNameLen = 2000, + SizeOfStruct = 88, + }; + var result = PInvoke.SymFromName(hProcess, name, &symbol); + address = symbol.Address; + return result; + } +} diff --git a/STROOP.Win32/NativeMethods.txt b/STROOP.Win32/NativeMethods.txt index 5f282702b..d2760c095 100644 --- a/STROOP.Win32/NativeMethods.txt +++ b/STROOP.Win32/NativeMethods.txt @@ -1 +1,15 @@ - \ No newline at end of file +OpenThread +SuspendThread +ResumeThread +CloseHandle +OpenProcess +ReadProcessMemory +WriteProcessMemory +QueryWorkingSetEx +SymInitialize +SymCleanup +SymFromName +IsWow64Process + +PSAPI_WORKING_SET_EX_INFORMATION +MEMORY_BASIC_INFORMATION diff --git a/STROOP.sln b/STROOP.sln index 514915ea7..aafba4587 100644 --- a/STROOP.sln +++ b/STROOP.sln @@ -14,6 +14,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "STROOP.Variables", "STROOP.Variables\STROOP.Variables.csproj", "{D5CB8378-E26B-4808-839B-200083A13E3C}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "STROOP.Win32", "STROOP.Win32\STROOP.Win32.csproj", "{590625A1-EC10-4656-8BD3-65BB7AAE004F}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -60,6 +62,18 @@ Global {D5CB8378-E26B-4808-839B-200083A13E3C}.Release|x64.Build.0 = Release|Any CPU {D5CB8378-E26B-4808-839B-200083A13E3C}.Release|x86.ActiveCfg = Release|Any CPU {D5CB8378-E26B-4808-839B-200083A13E3C}.Release|x86.Build.0 = Release|Any CPU + {590625A1-EC10-4656-8BD3-65BB7AAE004F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {590625A1-EC10-4656-8BD3-65BB7AAE004F}.Debug|Any CPU.Build.0 = Debug|Any CPU + {590625A1-EC10-4656-8BD3-65BB7AAE004F}.Debug|x64.ActiveCfg = Debug|Any CPU + {590625A1-EC10-4656-8BD3-65BB7AAE004F}.Debug|x64.Build.0 = Debug|Any CPU + {590625A1-EC10-4656-8BD3-65BB7AAE004F}.Debug|x86.ActiveCfg = Debug|Any CPU + {590625A1-EC10-4656-8BD3-65BB7AAE004F}.Debug|x86.Build.0 = Debug|Any CPU + {590625A1-EC10-4656-8BD3-65BB7AAE004F}.Release|Any CPU.ActiveCfg = Release|Any CPU + {590625A1-EC10-4656-8BD3-65BB7AAE004F}.Release|Any CPU.Build.0 = Release|Any CPU + {590625A1-EC10-4656-8BD3-65BB7AAE004F}.Release|x64.ActiveCfg = Release|Any CPU + {590625A1-EC10-4656-8BD3-65BB7AAE004F}.Release|x64.Build.0 = Release|Any CPU + {590625A1-EC10-4656-8BD3-65BB7AAE004F}.Release|x86.ActiveCfg = Release|Any CPU + {590625A1-EC10-4656-8BD3-65BB7AAE004F}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE From 6e3516f41ed5533059ba8695443273129a8c3e17 Mon Sep 17 00:00:00 2001 From: Frame Date: Fri, 6 Mar 2026 18:03:44 +0100 Subject: [PATCH 3/8] use CsWin32 for 'SigScanSharp' --- STROOP.Core/GameMemoryAccess/SigScanSharp.cs | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/STROOP.Core/GameMemoryAccess/SigScanSharp.cs b/STROOP.Core/GameMemoryAccess/SigScanSharp.cs index 0db9d8aa0..7c8425176 100644 --- a/STROOP.Core/GameMemoryAccess/SigScanSharp.cs +++ b/STROOP.Core/GameMemoryAccess/SigScanSharp.cs @@ -25,7 +25,7 @@ */ using System.Diagnostics; -using System.Runtime.InteropServices; +using STROOP.Win32; namespace STROOP.Core.GameMemoryAccess; @@ -52,7 +52,7 @@ public bool SelectModule(ProcessModule targetModule) g_dictStringPatterns.Clear(); try { - return Win32.ReadProcessMemory(g_hProcess, g_lpModuleBase, g_arrModuleBuffer, (IntPtr)targetModule.ModuleMemorySize); + return NativeMethodWrappers.ReadProcessMemory(g_hProcess, (UIntPtr)g_lpModuleBase, g_arrModuleBuffer); } catch (AccessViolationException) { @@ -112,10 +112,4 @@ private byte[] ParsePatternString(string szPattern) return patternbytes.ToArray(); } - - private static class Win32 - { - [DllImport("kernel32.dll")] - public static extern bool ReadProcessMemory(IntPtr hProcess, IntPtr lpBaseAddress, byte[] lpBuffer, IntPtr dwSize, IntPtr lpNumberOfBytesRead = default(IntPtr)); - } } From 23c715c536ad975c740ad88a024a08761ba5ea28 Mon Sep 17 00:00:00 2001 From: Frame Date: Fri, 6 Mar 2026 17:16:52 +0100 Subject: [PATCH 4/8] use CsWin32 for 'RichTextBoxEx' --- STROOP.Win32/NativeMethods.txt | 7 ++ STROOP/Controls/RichTextBoxEx.cs | 135 ++++--------------------------- 2 files changed, 24 insertions(+), 118 deletions(-) diff --git a/STROOP.Win32/NativeMethods.txt b/STROOP.Win32/NativeMethods.txt index d2760c095..2dd86192b 100644 --- a/STROOP.Win32/NativeMethods.txt +++ b/STROOP.Win32/NativeMethods.txt @@ -13,3 +13,10 @@ IsWow64Process PSAPI_WORKING_SET_EX_INFORMATION MEMORY_BASIC_INFORMATION + +CHARFORMAT2A +CFE_EFFECTS +CFM_MASK +SCF_* +EM_* +SendMessage diff --git a/STROOP/Controls/RichTextBoxEx.cs b/STROOP/Controls/RichTextBoxEx.cs index c2bbac1aa..2d371c1fd 100644 --- a/STROOP/Controls/RichTextBoxEx.cs +++ b/STROOP/Controls/RichTextBoxEx.cs @@ -3,115 +3,14 @@ using System.Drawing; using System.Windows.Forms; using System.Runtime.InteropServices; +using Windows.Win32; +using Windows.Win32.Foundation; +using Windows.Win32.UI.Controls.RichEdit; namespace STROOP.Controls { public class RichTextBoxEx : RichTextBox { - #region Interop-Defines - - [StructLayout(LayoutKind.Sequential)] - private struct CHARFORMAT2_STRUCT - { - public UInt32 cbSize; - public UInt32 dwMask; - public UInt32 dwEffects; - public Int32 yHeight; - public Int32 yOffset; - public Int32 crTextColor; - public byte bCharSet; - public byte bPitchAndFamily; - - [MarshalAs(UnmanagedType.ByValArray, SizeConst = 32)] - public char[] szFaceName; - - public UInt16 wWeight; - public UInt16 sSpacing; - public int crBackColor; // Color.ToArgb() -> int - public int lcid; - public int dwReserved; - public Int16 sStyle; - public Int16 wKerning; - public byte bUnderlineType; - public byte bAnimation; - public byte bRevAuthor; - public byte bReserved1; - } - - [DllImport("user32.dll", CharSet = CharSet.Auto)] - private static extern IntPtr SendMessage(IntPtr hWnd, int msg, IntPtr wParam, IntPtr lParam); - - private const int WM_USER = 0x0400; - private const int EM_GETCHARFORMAT = WM_USER + 58; - private const int EM_SETCHARFORMAT = WM_USER + 68; - - private const int SCF_SELECTION = 0x0001; - private const int SCF_WORD = 0x0002; - private const int SCF_ALL = 0x0004; - - #region CHARFORMAT2 Flags - - private const UInt32 CFE_BOLD = 0x0001; - private const UInt32 CFE_ITALIC = 0x0002; - private const UInt32 CFE_UNDERLINE = 0x0004; - private const UInt32 CFE_STRIKEOUT = 0x0008; - private const UInt32 CFE_PROTECTED = 0x0010; - private const UInt32 CFE_LINK = 0x0020; - private const UInt32 CFE_AUTOCOLOR = 0x40000000; - private const UInt32 CFE_SUBSCRIPT = 0x00010000; /* Superscript and subscript are */ - private const UInt32 CFE_SUPERSCRIPT = 0x00020000; /* mutually exclusive */ - - private const int CFM_SMALLCAPS = 0x0040; /* (*) */ - private const int CFM_ALLCAPS = 0x0080; /* Displayed by 3.0 */ - private const int CFM_HIDDEN = 0x0100; /* Hidden by 3.0 */ - private const int CFM_OUTLINE = 0x0200; /* (*) */ - private const int CFM_SHADOW = 0x0400; /* (*) */ - private const int CFM_EMBOSS = 0x0800; /* (*) */ - private const int CFM_IMPRINT = 0x1000; /* (*) */ - private const int CFM_DISABLED = 0x2000; - private const int CFM_REVISED = 0x4000; - - private const int CFM_BACKCOLOR = 0x04000000; - private const int CFM_LCID = 0x02000000; - private const int CFM_UNDERLINETYPE = 0x00800000; /* Many displayed by 3.0 */ - private const int CFM_WEIGHT = 0x00400000; - private const int CFM_SPACING = 0x00200000; /* Displayed by 3.0 */ - private const int CFM_KERNING = 0x00100000; /* (*) */ - private const int CFM_STYLE = 0x00080000; /* (*) */ - private const int CFM_ANIMATION = 0x00040000; /* (*) */ - private const int CFM_REVAUTHOR = 0x00008000; - - - private const UInt32 CFM_BOLD = 0x00000001; - private const UInt32 CFM_ITALIC = 0x00000002; - private const UInt32 CFM_UNDERLINE = 0x00000004; - private const UInt32 CFM_STRIKEOUT = 0x00000008; - private const UInt32 CFM_PROTECTED = 0x00000010; - private const UInt32 CFM_LINK = 0x00000020; - private const UInt32 CFM_SIZE = 0x80000000; - private const UInt32 CFM_COLOR = 0x40000000; - private const UInt32 CFM_FACE = 0x20000000; - private const UInt32 CFM_OFFSET = 0x10000000; - private const UInt32 CFM_CHARSET = 0x08000000; - private const UInt32 CFM_SUBSCRIPT = CFE_SUBSCRIPT | CFE_SUPERSCRIPT; - private const UInt32 CFM_SUPERSCRIPT = CFM_SUBSCRIPT; - - private const byte CFU_UNDERLINENONE = 0x00000000; - private const byte CFU_UNDERLINE = 0x00000001; - private const byte CFU_UNDERLINEWORD = 0x00000002; /* (*) displayed as ordinary underline */ - private const byte CFU_UNDERLINEDOUBLE = 0x00000003; /* (*) displayed as ordinary underline */ - private const byte CFU_UNDERLINEDOTTED = 0x00000004; - private const byte CFU_UNDERLINEDASH = 0x00000005; - private const byte CFU_UNDERLINEDASHDOT = 0x00000006; - private const byte CFU_UNDERLINEDASHDOTDOT = 0x00000007; - private const byte CFU_UNDERLINEWAVE = 0x00000008; - private const byte CFU_UNDERLINETHICK = 0x00000009; - private const byte CFU_UNDERLINEHAIRLINE = 0x0000000A; /* (*) displayed as ordinary underline */ - - #endregion - - #endregion - public RichTextBoxEx() { // Otherwise, non-standard links get lost when user starts typing @@ -193,7 +92,7 @@ public void InsertLink(string text, string hyperlink, int position) /// true: set link style, false: clear link style public void SetSelectionLink(bool link) { - SetSelectionStyle(CFM_LINK, link ? CFE_LINK : 0); + SetSelectionStyle(CFM_MASK.CFM_LINK, link ? CFE_EFFECTS.CFE_LINK : 0); } /// @@ -202,39 +101,39 @@ public void SetSelectionLink(bool link) /// 0: link style not set, 1: link style set, -1: mixed public int GetSelectionLink() { - return GetSelectionStyle(CFM_LINK, CFE_LINK); + return GetSelectionStyle(CFM_MASK.CFM_LINK, CFE_EFFECTS.CFE_LINK); } - private void SetSelectionStyle(UInt32 mask, UInt32 effect) + private void SetSelectionStyle(CFM_MASK mask, CFE_EFFECTS effect) { - CHARFORMAT2_STRUCT cf = new CHARFORMAT2_STRUCT(); + CHARFORMATA cf = new CHARFORMATA(); cf.cbSize = (UInt32)Marshal.SizeOf(cf); cf.dwMask = mask; cf.dwEffects = effect; - IntPtr wpar = new IntPtr(SCF_SELECTION); - IntPtr lpar = Marshal.AllocCoTaskMem(Marshal.SizeOf(cf)); + WPARAM wpar = new WPARAM(PInvoke.SCF_SELECTION); + LPARAM lpar = Marshal.AllocCoTaskMem(Marshal.SizeOf(cf)); Marshal.StructureToPtr(cf, lpar, false); - IntPtr res = SendMessage(Handle, EM_SETCHARFORMAT, wpar, lpar); + IntPtr res = PInvoke.SendMessage((HWND)Handle, PInvoke.EM_SETCHARFORMAT, wpar, lpar); Marshal.FreeCoTaskMem(lpar); } - private int GetSelectionStyle(UInt32 mask, UInt32 effect) + private int GetSelectionStyle(CFM_MASK mask, CFE_EFFECTS effect) { - CHARFORMAT2_STRUCT cf = new CHARFORMAT2_STRUCT(); + CHARFORMATA cf = new CHARFORMATA(); cf.cbSize = (UInt32)Marshal.SizeOf(cf); - cf.szFaceName = new char[32]; + cf.szFaceName = new __CHAR_32(); - IntPtr wpar = new IntPtr(SCF_SELECTION); - IntPtr lpar = Marshal.AllocCoTaskMem(Marshal.SizeOf(cf)); + WPARAM wpar = new WPARAM(PInvoke.SCF_SELECTION); + LPARAM lpar = Marshal.AllocCoTaskMem(Marshal.SizeOf(cf)); Marshal.StructureToPtr(cf, lpar, false); - IntPtr res = SendMessage(Handle, EM_GETCHARFORMAT, wpar, lpar); + IntPtr res = PInvoke.SendMessage((HWND)Handle, PInvoke.EM_GETCHARFORMAT, wpar, lpar); - cf = (CHARFORMAT2_STRUCT)Marshal.PtrToStructure(lpar, typeof(CHARFORMAT2_STRUCT)); + cf = (CHARFORMATA)Marshal.PtrToStructure(lpar, typeof(CHARFORMATA)); int state; // dwMask holds the information which properties are consistent throughout the selection: From 0040126af8dd1d9018887022c1f3aa954772b189 Mon Sep 17 00:00:00 2001 From: Frame Date: Fri, 6 Mar 2026 17:22:35 +0100 Subject: [PATCH 5/8] use CsWin32 for 'MouseUtility' --- STROOP.Win32/NativeMethods.txt | 4 ++++ STROOP/Utilities/MouseUtility.cs | 17 +++++------------ 2 files changed, 9 insertions(+), 12 deletions(-) diff --git a/STROOP.Win32/NativeMethods.txt b/STROOP.Win32/NativeMethods.txt index 2dd86192b..7087d42e1 100644 --- a/STROOP.Win32/NativeMethods.txt +++ b/STROOP.Win32/NativeMethods.txt @@ -20,3 +20,7 @@ CFM_MASK SCF_* EM_* SendMessage + +GetAsyncKeyState +GetSystemMetrics +SYSTEM_METRICS_INDEX diff --git a/STROOP/Utilities/MouseUtility.cs b/STROOP/Utilities/MouseUtility.cs index 8fc05baa2..a0cb0924b 100644 --- a/STROOP/Utilities/MouseUtility.cs +++ b/STROOP/Utilities/MouseUtility.cs @@ -1,27 +1,20 @@ -using System.Runtime.InteropServices; -using System.Windows.Forms; +using System.Windows.Forms; +using Windows.Win32; +using Windows.Win32.UI.WindowsAndMessaging; namespace STROOP.Utilities { public static class MouseUtility { - const int SM_SWAPBUTTON = 23; - - [DllImport("user32.dll")] - static extern short GetAsyncKeyState(Keys vKey); - - [DllImport("user32.dll")] - static extern int GetSystemMetrics(int nIndex); - public static bool IsMouseDown(int button) { - bool buttonsSwapped = GetSystemMetrics(SM_SWAPBUTTON) != 0; + bool buttonsSwapped = PInvoke.GetSystemMetrics(SYSTEM_METRICS_INDEX.SM_SWAPBUTTON) != 0; if (buttonsSwapped) if (button == 0) button = 1; else if (button == 1) button = 0; Keys key = button == 2 ? Keys.MButton : (Keys)(button + 1); - return (GetAsyncKeyState(key) & 0x8000) != 0; + return (PInvoke.GetAsyncKeyState((int)key) & 0x8000) != 0; } } } From c04499908b71c380088d1208c935b9ded0d8029b Mon Sep 17 00:00:00 2001 From: Frame Date: Fri, 6 Mar 2026 17:29:16 +0100 Subject: [PATCH 6/8] delete unused 'CarretlessTextBox' --- STROOP/Controls/CarretlessTextBox.cs | 29 ------------------- .../VariablePanelValueEditBox.cs | 2 +- STROOP/STROOP.csproj | 3 -- 3 files changed, 1 insertion(+), 33 deletions(-) delete mode 100644 STROOP/Controls/CarretlessTextBox.cs diff --git a/STROOP/Controls/CarretlessTextBox.cs b/STROOP/Controls/CarretlessTextBox.cs deleted file mode 100644 index 4b5c69fda..000000000 --- a/STROOP/Controls/CarretlessTextBox.cs +++ /dev/null @@ -1,29 +0,0 @@ -using System; -using System.Windows.Forms; -using System.Runtime.InteropServices; - -namespace STROOP -{ - public class CarretlessTextBox : TextBox - { - [DllImport("user32.dll")] - static extern bool HideCaret(IntPtr hWnd); - - [DllImport("user32.dll")] - static extern bool ShowCaret(IntPtr hWnd); - - public CarretlessTextBox() - { - } - - public void HideTheCaret() - { - HideCaret(Handle); - } - - public void ShowTheCaret() - { - ShowCaret(Handle); - } - } -} diff --git a/STROOP/Controls/VariablePanel/VariablePanelValueEditBox.cs b/STROOP/Controls/VariablePanel/VariablePanelValueEditBox.cs index 77b3b3a7c..458d2defd 100644 --- a/STROOP/Controls/VariablePanel/VariablePanelValueEditBox.cs +++ b/STROOP/Controls/VariablePanel/VariablePanelValueEditBox.cs @@ -5,7 +5,7 @@ namespace STROOP.Controls.VariablePanel; -public class VariablePanelValueEditBox : CarretlessTextBox, IValueEditBox +public class VariablePanelValueEditBox : TextBox, IValueEditBox { private readonly EventHandler _handleLostFocus; private bool killed = false; diff --git a/STROOP/STROOP.csproj b/STROOP/STROOP.csproj index fe041d587..76291479a 100644 --- a/STROOP/STROOP.csproj +++ b/STROOP/STROOP.csproj @@ -53,9 +53,6 @@ Component - - Component - Component From 903ab6083c2fa7d07d052555a9191045f993be34 Mon Sep 17 00:00:00 2001 From: Frame Date: Fri, 6 Mar 2026 18:24:39 +0100 Subject: [PATCH 7/8] pull 'VirtualQueryEx' into 'STROOP.Win32' project and document intent --- .../GameMemoryAccess/DolphinProcessIO.cs | 9 +++-- STROOP.Core/Kernal32NativeMethods.cs | 23 ------------- STROOP.Win32/VirtualQueryEx.cs | 34 +++++++++++++++++++ 3 files changed, 38 insertions(+), 28 deletions(-) create mode 100644 STROOP.Win32/VirtualQueryEx.cs diff --git a/STROOP.Core/GameMemoryAccess/DolphinProcessIO.cs b/STROOP.Core/GameMemoryAccess/DolphinProcessIO.cs index 7173f935f..4e9bf04b9 100644 --- a/STROOP.Core/GameMemoryAccess/DolphinProcessIO.cs +++ b/STROOP.Core/GameMemoryAccess/DolphinProcessIO.cs @@ -3,7 +3,6 @@ using System.Runtime.InteropServices; using Windows.Win32.Foundation; using Windows.Win32.System.ProcessStatus; -using static STROOP.Core.Kernal32NativeMethods; namespace STROOP.Core.GameMemoryAccess; @@ -16,13 +15,13 @@ public DolphinProcessIO(Process process, Emulator emulator) protected override void CalculateOffset() { - MemoryBasicInformation info; - IntPtr infoSize = (IntPtr)Marshal.SizeOf(typeof(MemoryBasicInformation)); + VirtualQueryEx.MemoryBasicInformation info; + IntPtr infoSize = (IntPtr)Marshal.SizeOf(typeof(VirtualQueryEx.MemoryBasicInformation)); _baseOffset = (UIntPtr)0; bool mem1Found = false; for (IntPtr p = new IntPtr(); - VirtualQueryEx(_processHandle, p, out info, infoSize) == infoSize; + VirtualQueryEx.Invoke(_processHandle, p, out info, infoSize) == infoSize; p = (IntPtr)(p.ToInt64() + info.RegionSize.ToInt64())) { if (mem1Found) @@ -39,7 +38,7 @@ protected override void CalculateOffset() continue; } - if (info.RegionSize == (IntPtr)0x2000000 && info.Type == MemoryType.MEM_MAPPED) + if (info.RegionSize == (IntPtr)0x2000000 && info.Type == VirtualQueryEx.MemoryType.MEM_MAPPED) { // Here, it's likely the right page, but it can happen that multiple pages with these criteria // exists and have nothing to do with the emulated memory. Only the right page has valid diff --git a/STROOP.Core/Kernal32NativeMethods.cs b/STROOP.Core/Kernal32NativeMethods.cs index b2e7fb771..aea438067 100644 --- a/STROOP.Core/Kernal32NativeMethods.cs +++ b/STROOP.Core/Kernal32NativeMethods.cs @@ -8,29 +8,6 @@ namespace STROOP.Core; public static class Kernal32NativeMethods { - [Flags] - public enum MemoryType : uint - { - MEM_IMAGE = 0x1000000, - MEM_MAPPED = 0x40000, - MEM_PRIVATE = 0x20000, - } - - [StructLayout(LayoutKind.Sequential)] - public struct MemoryBasicInformation - { - public UIntPtr BaseAddress; - public IntPtr AllocationBase; - public uint AllocationProtect; - public IntPtr RegionSize; - public uint State; - public uint Protect; - public MemoryType Type; - } - - [DllImport("kernel32.dll")] - public static extern IntPtr VirtualQueryEx(IntPtr hProcess, IntPtr lpAddress, out MemoryBasicInformation lpBuffer, IntPtr dwLength); - public static void ResumeProcess(Process process) { // Resume all threads diff --git a/STROOP.Win32/VirtualQueryEx.cs b/STROOP.Win32/VirtualQueryEx.cs new file mode 100644 index 000000000..82fb0eb7f --- /dev/null +++ b/STROOP.Win32/VirtualQueryEx.cs @@ -0,0 +1,34 @@ +using System.Runtime.InteropServices; + +namespace STROOP.Win32; + +/// +/// The VirtualQueryEx method cannot be generated via CsWin32 unless the C# project itself is compiled for a specific platform.
+/// Invoking it may thus cause a runtime exception on certain platforms, but as this is only used for the Dolphin IO, I'll accept this for now. +/// See https://github.com/microsoft/CsWin32/issues/722 for details. +///
+public static class VirtualQueryEx +{ + [Flags] + public enum MemoryType : uint + { + MEM_IMAGE = 0x1000000, + MEM_MAPPED = 0x40000, + MEM_PRIVATE = 0x20000, + } + + [StructLayout(LayoutKind.Sequential)] + public struct MemoryBasicInformation + { + public UIntPtr BaseAddress; + public IntPtr AllocationBase; + public uint AllocationProtect; + public IntPtr RegionSize; + public uint State; + public uint Protect; + public MemoryType Type; + } + + [DllImport("kernel32.dll", EntryPoint = "VirtualQueryEx")] + public static extern IntPtr Invoke(IntPtr hProcess, IntPtr lpAddress, out MemoryBasicInformation lpBuffer, IntPtr dwLength); +} From f2ab57ea33da2d60614737627033c00d43b3bf71 Mon Sep 17 00:00:00 2001 From: Frame Date: Fri, 6 Mar 2026 18:27:24 +0100 Subject: [PATCH 8/8] rename 'Kernal32NativeMethods' -> 'ProcessHelper' --- STROOP.Core/GameMemoryAccess/WindowsProcessIO.cs | 2 +- STROOP.Core/{Kernal32NativeMethods.cs => ProcessHelper.cs} | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) rename STROOP.Core/{Kernal32NativeMethods.cs => ProcessHelper.cs} (96%) diff --git a/STROOP.Core/GameMemoryAccess/WindowsProcessIO.cs b/STROOP.Core/GameMemoryAccess/WindowsProcessIO.cs index 5dc518916..f0271e1c6 100644 --- a/STROOP.Core/GameMemoryAccess/WindowsProcessIO.cs +++ b/STROOP.Core/GameMemoryAccess/WindowsProcessIO.cs @@ -4,7 +4,7 @@ using Windows.Win32; using Windows.Win32.Foundation; using Windows.Win32.System.Threading; -using static STROOP.Core.Kernal32NativeMethods; +using static STROOP.Core.ProcessHelper; namespace STROOP.Core.GameMemoryAccess; diff --git a/STROOP.Core/Kernal32NativeMethods.cs b/STROOP.Core/ProcessHelper.cs similarity index 96% rename from STROOP.Core/Kernal32NativeMethods.cs rename to STROOP.Core/ProcessHelper.cs index aea438067..5c64268ef 100644 --- a/STROOP.Core/Kernal32NativeMethods.cs +++ b/STROOP.Core/ProcessHelper.cs @@ -6,7 +6,7 @@ namespace STROOP.Core; -public static class Kernal32NativeMethods +public static class ProcessHelper { public static void ResumeProcess(Process process) {