// modules using System.Runtime.InteropServices; namespace ProcessWrapper { public interface IProcessWrapper { byte[] ReadProcessMemory(int pid, ulong address, uint length); bool WriteProcessMemory(int pid, ulong address, byte[] data); int getuid(); ulong FindCemuBase(int pid, ulong minSize); } // Mac implementation public class MacProcessWrapper : IProcessWrapper { // vars const int KERN_SUCCESS = 0; const string LIBSYSTEM = "/usr/lib/libSystem.B.dylib"; const int VM_REGION_BASIC_INFO_64 = 9; const int VM_REGION_BASIC_INFO_COUNT_64 = 10; const int VM_PROT_READ = 0x01; // Struct [StructLayout(LayoutKind.Sequential)] public struct vm_region_basic_info_64 { public int protection; public int max_protection; public int inheritance; public int shared; public int reserved; public ulong offset; public int behavior; public short user_wired_count; public short user_tag; } // https://github.com/attilathedud/macos_task_for_pid#overview // Well documenated apis, thanks Apple. [DllImport(LIBSYSTEM, SetLastError = true)] private static extern int task_for_pid(IntPtr task, int pid, out IntPtr targetTask); [DllImport(LIBSYSTEM, SetLastError = true)] private static extern IntPtr mach_task_self(); [DllImport(LIBSYSTEM, SetLastError = true)] private static extern int mach_vm_read(IntPtr target_task, ulong address, ulong size, out IntPtr data, out ulong data_count); [DllImport(LIBSYSTEM, SetLastError = true)] private static extern int mach_vm_write(IntPtr target_task, ulong address, byte[] data, uint dataCnt); [DllImport(LIBSYSTEM, SetLastError = true)] private static extern int vm_deallocate(IntPtr task, IntPtr address, ulong size); [DllImport(LIBSYSTEM, SetLastError = true)] private static extern int mach_vm_region(IntPtr task, ref ulong address, out ulong size, int flavor, IntPtr info, ref uint count, out ulong object_name); [DllImport(LIBSYSTEM, SetLastError = true)] private static extern int geteuid(); public int getuid() { return geteuid(); } public byte[] ReadProcessMemory(int pid, ulong address, uint length) { IntPtr task; IntPtr localTask = mach_task_self(); int result = task_for_pid(localTask, pid, out task); if (result != KERN_SUCCESS) { Console.WriteLine($"task_for_pid failed with code {result}"); return new byte[0]; } IntPtr bufferPtr; ulong size; result = mach_vm_read(task, address, length, out bufferPtr, out size); if (result != KERN_SUCCESS) { //Console.WriteLine($"mach_vm_read failed with code {result}"); return new byte[0]; } byte[] buffer = new byte[size]; Marshal.Copy(bufferPtr, buffer, 0, (int)size); // Clean up vm_deallocate(localTask, bufferPtr, size); return buffer; } public bool WriteProcessMemory(int pid, ulong address, byte[] data) { IntPtr task; IntPtr localTask = mach_task_self(); int result = task_for_pid(localTask, pid, out task); if (result != KERN_SUCCESS) { //throw new Exception($"task_for_pid failed: {result}"); return false; } result = mach_vm_write(task, address, data, (uint)data.Length); if (result != KERN_SUCCESS) { //throw new Exception($"mach_vm_write failed: {result}"); Console.WriteLine($"mach_vm_write failed: {result}"); return false; } else { return true; } // Console.WriteLine($"Wrote {data.Length} bytes to 0x{address:X}"); } public ulong FindCemuBase(int pid, ulong maxSize) { IntPtr task; IntPtr localTask = mach_task_self(); byte?[] patternBytes = new byte?[] { 0x02, 0xD4, 0xE7 }; int result = task_for_pid(localTask, pid, out task); if (result != KERN_SUCCESS) { Console.WriteLine($"task_for_pid failed: {result}"); return 0; } ulong address = 0; while (true) { ulong size; uint count = VM_REGION_BASIC_INFO_COUNT_64; IntPtr infoPtr = Marshal.AllocHGlobal(Marshal.SizeOf()); ulong objectName; result = mach_vm_region(task, ref address, out size, VM_REGION_BASIC_INFO_64, infoPtr, ref count, out objectName ); if (result != KERN_SUCCESS) { Marshal.FreeHGlobal(infoPtr); break; } var regionInfo = Marshal.PtrToStructure(infoPtr); Marshal.FreeHGlobal(infoPtr); bool readable = (regionInfo.protection & VM_PROT_READ) != 0; if (readable && size < maxSize || size == maxSize) { var bytes = ReadProcessMemory(pid, (ulong)(address + 0xE000000), 20); if (bytes != null && bytes.Length > 0) { int patternLen = patternBytes.Length; for (int i = 0, j = 0; i + patternLen <= 20; i++) { if (patternBytes[j] == null || bytes[i] == patternBytes[j]) { j++; } else { j = 0; } if (j >= patternLen) { return address; } } } } address += size; } return 0; } } // Linux implementation public class LinuxProcessWrapper : IProcessWrapper { // IOVec Struct [StructLayout(LayoutKind.Sequential)] private struct IOVec { public UIntPtr Base; public UIntPtr Length; } [DllImport("libc", SetLastError = true)] private static extern long process_vm_readv(int pid, IOVec[] local_iov, ulong liovcnt, IOVec[] remote_iov, ulong riovcnt, ulong flags); [DllImport("libc", SetLastError = true)] private static extern long process_vm_writev(int pid, IOVec[] local_iov, ulong liovcnt, IOVec[] remote_iov, ulong riovcnt, ulong flags); [DllImport("libc", SetLastError = true)] private static extern int geteuid(); public int getuid() { return geteuid(); } public byte[] ReadProcessMemory(int pid, ulong address, uint length) { byte[] buffer = new byte[length]; GCHandle handle = GCHandle.Alloc(buffer, GCHandleType.Pinned); try { var local = new IOVec { Base = new UIntPtr((ulong)handle.AddrOfPinnedObject().ToInt64()), Length = (UIntPtr)length }; var remote = new IOVec { Base = (UIntPtr)address, Length = (UIntPtr)length }; long nread = process_vm_readv(pid, new[] { local }, 1, new[] { remote }, 1, 0); if (nread == -1) { // Console.WriteLine($"Failed to read memory. Error code: {Marshal.GetLastWin32Error()}"); return new byte[0]; } else { // Console.WriteLine($"Read {nread} bytes from process {pid} at 0x{address:X}"); } } finally { handle.Free(); } return buffer; } public bool WriteProcessMemory(int pid, ulong address, byte[] data) { GCHandle handle = GCHandle.Alloc(data, GCHandleType.Pinned); try { var local = new IOVec { Base = new UIntPtr((ulong)handle.AddrOfPinnedObject().ToInt64()), Length = (UIntPtr)data.Length }; var remote = new IOVec { Base = (UIntPtr)address, Length = (UIntPtr)data.Length }; long nwritten = process_vm_writev(pid, new[] { local }, 1, new[] { remote }, 1, 0); if (nwritten == -1) { // Console.WriteLine($"Failed to write memory. Error code: {Marshal.GetLastWin32Error()}"); return false; } else { // Console.WriteLine($"Wrote {nwritten} bytes to process {pid} at 0x{address:X}"); return true; } } finally { handle.Free(); } } public ulong FindCemuBase(int pid, ulong maxSize) { // vars string mapsPath = $"/proc/{pid}/maps"; byte?[] patternBytes = new byte?[] { 0x02, 0xD4, 0xE7 }; string[] mapsLines = File.ReadAllLines(mapsPath); foreach (string line in mapsLines) { var split = line.Split(" "); var addrP = split[0].Split("-"); ulong min = Convert.ToUInt64(addrP[0], 16); ulong max = Convert.ToUInt64(addrP[1], 16); if (!split[1].Contains("r")) continue; var len = (max - min); if (len > maxSize || len == maxSize) { var bytes = ReadProcessMemory(pid, (ulong)(min + 0xE000000), 20); if (bytes != null && bytes.Length > 0) { int patternLen = patternBytes.Length; for (int i = 0, j = 0; i + patternLen <= 20; i++) { if (patternBytes[j] == null || bytes[i] == patternBytes[j]) { j++; } else { j = 0; } if (j >= patternLen) { return min; } } } } } return 0; } } public class WindowsProcessWrapper : IProcessWrapper { // vars const uint PROCESS_VM_READ = 0x0010; const uint PROCESS_VM_WRITE = 0x0020; const uint PROCESS_VM_OPERATION = 0x0008; const uint PROCESS_QUERY_INFORMATION = 0x0400; [StructLayout(LayoutKind.Sequential)] public struct MEMORY_BASIC_INFORMATION { public IntPtr BaseAddress; public IntPtr AllocationBase; public uint AllocationProtect; public IntPtr RegionSize; public uint State; public uint Protect; public uint Type; } [DllImport("kernel32.dll", SetLastError = true)] private static extern IntPtr OpenProcess(uint dwDesiredAccess, bool bInheritHandle, int dwProcessId); [DllImport("kernel32.dll", SetLastError = true)] private static extern bool ReadProcessMemory(IntPtr hProcess, IntPtr lpBaseAddress, [Out] byte[] lpBuffer, int dwSize, out int lpNumberOfBytesRead); [DllImport("kernel32.dll", SetLastError = true)] private static extern bool WriteProcessMemory(IntPtr hProcess, IntPtr lpBaseAddress, byte[] lpBuffer, int dwSize, out int lpNumberOfBytesWritten); [DllImport("kernel32.dll", SetLastError = true)] private static extern int VirtualQueryEx(IntPtr hProcess, IntPtr lpAddress, out MEMORY_BASIC_INFORMATION lpBuffer, uint dwLength); // Windows doesnt need this. public int getuid() { return -1; } public byte[] ReadProcessMemory(int pid, ulong address, uint length) { IntPtr hProcess = OpenProcess(PROCESS_VM_READ | PROCESS_QUERY_INFORMATION, false, pid); if (hProcess == IntPtr.Zero) { return new byte[0]; } byte[] buffer = new byte[length]; if (ReadProcessMemory(hProcess, (IntPtr)address, buffer, (int)length, out int bytesRead) && bytesRead > 0) { if (bytesRead < length) { Array.Resize(ref buffer, bytesRead); } return buffer; } return new byte[0]; } public bool WriteProcessMemory(int pid, ulong address, byte[] data) { IntPtr hProcess = OpenProcess(PROCESS_VM_WRITE | PROCESS_VM_OPERATION, false, pid); if (hProcess == IntPtr.Zero) { return false; } return WriteProcessMemory(hProcess, (IntPtr)address, data, data.Length, out int bytesWritten) && bytesWritten == data.Length; } public ulong FindCemuBase(int pid, ulong maxSize) { byte?[] patternBytes = new byte?[] { 0x02, 0xD4, 0xE7 }; IntPtr address = IntPtr.Zero; MEMORY_BASIC_INFORMATION mbi; IntPtr hProcess = OpenProcess(PROCESS_VM_WRITE | PROCESS_VM_OPERATION, false, pid); if (hProcess == IntPtr.Zero) { return 0; } while (VirtualQueryEx(hProcess, address, out mbi, (uint)Marshal.SizeOf(typeof(MEMORY_BASIC_INFORMATION))) != 0) { bool readable = (mbi.State == 0x1000) && (mbi.Protect != 0x01); ulong regionSize = (ulong)mbi.RegionSize.ToInt64(); if (readable && regionSize > maxSize || regionSize == maxSize) { var bytes = ReadProcessMemory(pid, (ulong)(mbi.BaseAddress + 0xE000000), 20); if (bytes != null && bytes.Length > 0) { int patternLen = patternBytes.Length; for (int i = 0, j = 0; i + patternLen <= 20; i++) { if (patternBytes[j] == null || bytes[i] == patternBytes[j]) { j++; } else { j = 0; } if (j >= patternLen) { return (ulong)mbi.BaseAddress; } } } } address = new IntPtr(mbi.BaseAddress.ToInt64() + mbi.RegionSize.ToInt64()); } return 0; } } }