SplatTool-public/ProcessWrapper.cs

420 lines
15 KiB
C#
Raw Permalink Normal View History

2026-04-23 17:08:47 +02:00
// 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<vm_region_basic_info_64>());
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<vm_region_basic_info_64>(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;
}
}
}