C# 读写内存

本文详细介绍了如何使用MemoryUtils库在Windows平台上读写War3游戏进程中的内存,包括初始化步骤、不同类型数据的读写方法,以及关键函数如GetMemoryAddress和ModuleBaseAddress的使用。适合对游戏内插件或内存调试感兴趣的开发者。

代码

var processName = "War3";
var process = MemoryUtils.GetProcessByProcessName(processName);
if (process == null)
{
    throw new Exception($"没有找到进程:{processName },请先启动后,再使用该功能");
} 

var _memoryUtils = new MemoryUtils(process.Id);

// 读写 ↓

// 计算实际地址 ( 返回值是一个地址,需要自己写代码读不同类型的值)
var address = _memoryUtils.GetMemoryAddress("Game.dll", 0x00BE87A4, 0x30, 0x1F0, 0x8C)

// 如果有 for 循环中动态计算的偏移, 还可以继续操作..
// 举例 : 人物结构体 偏移是 0x480, 循环可以取出 100个人物的数据
//for (int i = 0; i < 100; i++)
//{
//    address += 0x480 * i; 
//    var hp = _memoryUtils.ReadToInt(address + 0x4);
//    var mp = _memoryUtils.ReadToInt(address + 0x8);
//}

// 读取 int
var myValue1 = _memoryUtils.ReadToInt(address)
// 写入 int 4字节 类型
_memoryUtils.WriteInt(address, 99999);


//读取 float
var myValue2 = _memoryUtils.ReadToFloat(address)

//写入 float 单浮点 类型
_memoryUtils.WriteFloat(address, 0.5f);

// 更多类型请查看 MemoryUtils 源码

MemoryUtils.cs

using System;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Text;

namespace MyUtils;

public class MemoryUtils
{
    #region API

    //从指定内存中读取字节集数据
    [DllImport("kernel32.dll", EntryPoint = "ReadProcessMemory")]
    private static extern bool ReadProcessMemory(IntPtr hProcess, IntPtr lpBaseAddress, byte[] lpBuffer, int dwSize, IntPtr lpNumberOfBytesRead);

    //从指定内存中写入字节集数据
    [DllImport("kernel32.dll", EntryPoint = "WriteProcessMemory")]
    private static extern bool WriteProcessMemory(IntPtr hProcess, IntPtr lpBaseAddress, byte[] lpBuffer, int dwSize, IntPtr lpNumberOfBytesWritten);

    //打开一个已存在的进程对象,并返回进程的句柄
    [DllImport("kernel32.dll", EntryPoint = "OpenProcess")]
    private static extern IntPtr OpenProcess(int dwDesiredAccess, bool bInheritHandle, int dwProcessId);

    //关闭一个内核对象。其中包括文件、文件映射、进程、线程、安全和同步对象等。
    [DllImport("kernel32.dll")]
    private static extern void CloseHandle(IntPtr hObject);

    #endregion

    // byte		(字节   1字节)
    // int16    (short 2字节),
    // int32    (int   4字节),
    // int64    (long  8字节)
    // float    (单浮点 Single 4字节
    // double   (双浮点 8字节)
    // string	(字符串)
    // byte[]	(字节数组)

    private IntPtr _handle = IntPtr.Zero;
    private int _pid = 0;
    public MemoryUtils(int pid)
    {
        _pid = pid;
        _handle = OpenProcess(0x1F0FFF, false, pid);
    }

    ~MemoryUtils()
    {
        CloseHandle(_handle);
    }

    #region Read
    public byte[] ReadToBytes(IntPtr address, int size)
    {
        byte[] buffer = new byte[size];
        ReadProcessMemory(_handle, address, buffer, size, IntPtr.Zero);
        return buffer;
    }
    public T ReadObject<T>(IntPtr address) where T : struct
    {
        var buffer = ReadToBytes(address, Marshal.SizeOf(typeof(T)));

        var bufferAddress = Marshal.AllocHGlobal(buffer.Length);

        Marshal.Copy(buffer, 0, bufferAddress, buffer.Length);

        var structure = (T)Marshal.PtrToStructure(bufferAddress, typeof(T));

        Marshal.FreeHGlobal(bufferAddress);

        return structure;
    }

    public char ReadToChar(IntPtr address)
    {
        byte[] buffer = ReadToBytes(address, sizeof(char));
        return BitConverter.ToChar(buffer, 0);
    }
    public short ReadToShort(IntPtr address)
    {
        byte[] buffer = ReadToBytes(address, sizeof(short));
        return BitConverter.ToInt16(buffer, 0);
    }

    public int ReadToInt(IntPtr address)
    {
        byte[] buffer = ReadToBytes(address, sizeof(int));

        return BitConverter.ToInt32(buffer, 0);
    }

    public long ReadToLong(IntPtr address)
    {
        byte[] buffer = ReadToBytes(address, sizeof(long));
        return BitConverter.ToInt64(buffer, 0);
    }

    public float ReadToFloat(IntPtr address)
    {
        byte[] buffer = ReadToBytes(address, sizeof(float));
        return BitConverter.ToSingle(buffer, 0);
    }

    public double ReadToDouble(IntPtr address)
    {
        byte[] buffer = ReadToBytes(address, sizeof(double));
        return BitConverter.ToDouble(buffer, 0);
    }

    public string ReadToString(IntPtr address, int stringSize)
    {
        byte[] buffer = ReadToBytes(address, stringSize);
        return BitConverter.ToString(buffer);
    }

    #endregion

    #region Write

    public bool WriteByteArray(IntPtr address, byte[] byteData)
    {
        return WriteProcessMemory(_handle, address, byteData, byteData.Length, IntPtr.Zero);
    }

    public bool WriteChar(IntPtr address, char value)
    {
        return WriteByteArray(address, BitConverter.GetBytes(value));
    }

    public bool WriteShort(IntPtr address, short value)
    {
        return WriteByteArray(address, BitConverter.GetBytes(value));
    }

    public bool WriteInt(IntPtr address, int value)
    {
        return WriteByteArray(address, BitConverter.GetBytes(value));
    }

    public bool WriteLong(IntPtr address, long value)
    {
        return WriteByteArray(address, BitConverter.GetBytes(value));
    }

    public bool WriteFloat(IntPtr address, float value)
    {
        return WriteByteArray(address, BitConverter.GetBytes(value));
    }

    public bool WriteDouble(IntPtr address, double value)
    {
        return WriteByteArray(address, BitConverter.GetBytes(value));
    }

    public bool WriteString(IntPtr address, string value)
    {
        return WriteByteArray(address, Encoding.Default.GetBytes(value));
    }

    #endregion

    #region Utils

    /// <summary>
    /// 根据 进程名 获取 PID
    /// </summary>    
    public static int GetPidByProcessName(string processName)
    {
        return GetProcessByProcessName(processName)?.Id ?? 0;
    }

    /// <summary>
    /// 通过 进程名(不加exe后缀) 获取 进程对象
    /// </summary>      
    public static Process GetProcessByProcessName(string processName)
    {
        var processArr = Process.GetProcessesByName(processName);
        if (processArr.Length > 0)
        {
            return processArr[0];
        }

        return null;
    }

    /// <summary>
    /// 根据窗体标题查找窗口句柄(支持模糊匹配)
    /// </summary>    
    public static IntPtr FindWindow(string title)
    {
        var processArray = Process.GetProcesses();
        foreach (var item in processArray)
        {
            if (item.MainWindowTitle.IndexOf(title) != -1)
            {
                return item.MainWindowHandle;
            }
        }

        return IntPtr.Zero;
    }

    /// <summary>
    /// 获取进程中模块的基址 (例如: Game.dll / War3.exe)
    /// </summary>  
    public IntPtr GetModuleBaseAddress(string moduleName)
    {
        var process = Process.GetProcessById(_pid);
        IntPtr baseAddress = default;

        for (int i = 0; i < process.Modules.Count; i++)
        {
            var item = process.Modules[i];
            if (item.ModuleName == moduleName)
            {
                baseAddress = item.BaseAddress;
                break;
            }
        }

        return baseAddress;
    }

    /// <summary>
    /// 计算地址偏移
    /// </summary>   
    public IntPtr GetMemoryAddress(string moduleName, params IntPtr[] offsetArray)
    {
        if ((offsetArray?.Length ?? 0) == 0)
        {
            throw new Exception("至少需要一个偏移");
        }

        IntPtr addr = IntPtr.Zero;

        // 模块的地址
        var addrVal = GetModuleBaseAddress(moduleName);

        // 计算剩下的多级偏移
        for (int i = 0; i < offsetArray.Length; i++)
        {
            addr = addrVal + offsetArray[i];
            addrVal = (IntPtr)ReadToInt(addr);
        }

        // 最终的地址 (只是一个地址, 需要手动去读里面的值)
        return addr;
    }

    /// <summary>
    /// 通过进程名 获取窗口句柄
    /// </summary>
    /// <param name="processName"></param>
    /// <returns></returns>
    /// <exception cref="Exception"></exception>
    public static IntPtr GetWindowHwndByProcessName(string processName)
    {
        var hwnd = MemoryUtils.GetProcessByProcessName(processName)?.MainWindowHandle ?? IntPtr.Zero;

        if (hwnd == IntPtr.Zero)
        {
            throw new Exception($"没有找到[{processName}]进程..");
        }
        return hwnd;
    }

    #endregion

    #region 进制转换

    /// <summary>
    /// 16进制(0x41)转为10进制(65)
    /// </summary>   
    public static int ConvertFrom16To10(string value)
    {
        return Convert.ToInt32(value, 16);
    }

    /// <summary>
    /// 16进制(0x41)转为10进制(65)
    /// </summary>    
    public static int ConvertFrom16To10(IntPtr value)
    {
        return Convert.ToInt32(Convert.ToString(value, 10));
    }

    /// <summary>
    /// 10进制(65)转为16进制(0x41)
    /// </summary>   
    public static string ConvertFrom10To16(IntPtr value)
    {
        //x4 0补齐4位
        //x8 0补齐8位
        return value.ToString("x");
    }

    /// <summary>
    /// 16/10进制转为2进制
    /// </summary>     
    public static string ConvertFrom16Or10To2(IntPtr value)
    {
        return Convert.ToString(value, 2);
    }

    /// <summary>
    /// 2进制(1010)到10进制(2)
    /// </summary>    
    public static int ConvertFrom2To10(string value)
    {
        return Convert.ToInt32(value, 2);
    }

    #endregion

}

例子

https://github.com/xxxxue/war3-72bian

C#中实现读写内存地址有多种方法,以下为你介绍不同的实现方式: ### 使用内存映射文件 可使用内存映射文件在C#中实现进程间通信(IPC),允许多个进程共享内存内容,实现数据交换。需.NET Framework 4.5或更高版本以及Visual Studio或其他兼容的IDE。可编写两个独立的C#控制台应用程序,分别用于写入和读取共享内存中的数据 [^1]。 ### 使用Memory<T> 使用`Memory<T>`能够更高效地进行内存读写、提高性能、减少GC压力,可更高效地进行内存数据的读写操作 [^3]。 ### 使用Win32 API函数 可以使用Win32 API函数来打开目标进程并获取进程的句柄,然后使用`WriteProcessMemory`函数将一个整数值写入到指定的内存地址,最后输出写入成功的提示,并关闭进程句柄 [^4]。 ### 代码示例 以下是一个使用Win32 API进行简内存读写的示例代码: ```csharp using System; using System.Diagnostics; using System.Runtime.InteropServices; class MemoryAccess { [DllImport("kernel32.dll")] public static extern IntPtr OpenProcess(int dwDesiredAccess, bool bInheritHandle, int dwProcessId); [DllImport("kernel32.dll")] public static extern bool ReadProcessMemory(IntPtr hProcess, IntPtr lpBaseAddress, byte[] lpBuffer, int dwSize, out int lpNumberOfBytesRead); [DllImport("kernel32.dll")] public static extern bool WriteProcessMemory(IntPtr hProcess, IntPtr lpBaseAddress, byte[] lpBuffer, int dwSize, out int lpNumberOfBytesWritten); [DllImport("kernel32.dll")] public static extern bool CloseHandle(IntPtr hObject); const int PROCESS_ALL_ACCESS = 0x1F0FFF; public static void Main() { // 获取目标进程的ID,这里假设目标进程为记事本 Process targetProcess = Process.GetProcessesByName("notepad")[0]; int processId = targetProcess.Id; // 打开目标进程 IntPtr processHandle = OpenProcess(PROCESS_ALL_ACCESS, false, processId); if (processHandle != IntPtr.Zero) { // 假设要读写内存地址 IntPtr memoryAddress = (IntPtr)0x00151F38; // 写入数据 byte[] dataToWrite = BitConverter.GetBytes(9999); int bytesWritten; bool writeSuccess = WriteProcessMemory(processHandle, memoryAddress, dataToWrite, dataToWrite.Length, out bytesWritten); if (writeSuccess) { Console.WriteLine("内存写入成功"); } else { Console.WriteLine("内存写入失败"); } // 读取数据 byte[] buffer = new byte[4]; int bytesRead; bool readSuccess = ReadProcessMemory(processHandle, memoryAddress, buffer, buffer.Length, out bytesRead); if (readSuccess) { int readValue = BitConverter.ToInt32(buffer, 0); Console.WriteLine($"内存读取数据: {readValue}"); } else { Console.WriteLine("内存读取失败"); } // 关闭进程句柄 CloseHandle(processHandle); } else { Console.WriteLine("无法打开目标进程"); } } } ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值