C#实现简易游戏修改器

像偶等之人,虽然平时不太玩游戏,但游戏作为在工作忙碌时的调节也是一个不错东西。像RPG游戏,通常会玩的是剧情,但很多时候碰到一个怎么也打不 过的BOSS,游戏就卡在那边,影响了体会剧情的心情。这个时候大家都会想到修改器,把自己的血量维持住,就无敌了。网上搜了一下,发现个《Quick Memory Editor》,共享软件,有使用次数限制。试用了一下,揣摩了一下原理,然后自己实现了一个简易的版本,拿出来分享一下。程序中缺少输入校验,也没有特 别设计过,只是随性写来。至于为什么要用C#实现,主是基于调用API方便,以及BitConverter这个类,可以很方便的把具体的数值转换成内存中 的字节表示方式,还有是对于C/C++写界面方面不熟。有了BitConverter这个类,就不用考虑具体的类型在内存是是什么表示方式了,一切已经为 你封装好了,非常方便。点击这里下载

修改器的原理非常简单,就一句话:把预期的值写入游戏进程的某段内存中(使用kernel32.dll的WriteProcessMemory函数,见包中的代码)。那么要找到需要修改的值的内存地址,该如何处理呢?这里有个小窍门。

1.我在内存中查找预期值的地址,会得到一组地址,所需要的地址就隐藏的其中。。如图

ge1

我这里正好是角色的血量是522,在游戏进程的内存中搜索522的值,可以搜索到一大片。

2.经过一番拼杀后,血量降为501了,在结果中查找一下,如图

ge2

呵呵,我这里只找了一次,就只剩下一条了,运气不错。

通 常一些人物的属性,基本上都是一些全局的变量,进程起来后地址是不会变的,有些游戏甚至这个地址是编译时决定的,只要记住那个地址,不需要查找就可以修 改。第一次查找后,目标的地址就会包含在这个列表中。第二次,查找时因为血量变了,目标地址的值是预期的,而非目标地址的值可能变成与血量值相等,也可能 变成其他的值,或者是不变,第二次在结果中查询就过滤掉了一大批。如果没找到目标地址,那么再去拼杀一会,再从结果从查找一次,当搜到最后,列表中的数量 不再变化时,目标地址就找到了。有些游戏的目标地址不只一个,这个时候,把这些地址一起修改了就行了。根据这个前提,这种方式不是对每个游戏都适合的,特 别是针对网游,基本上是不起作用的,是否适用,还需试了才知道啊。

3.做一个定时任务,把目标地址修改成需要的值即可(就是暴力修改,每隔一定时间就修改一次,我的程序里以100ms间隔)

ge3

上图的LockedValue就是想要修改成的值。

下面看一下具体的代码是怎么实现的

首先,要查找游戏进程的内存中的数据,需要用到api函数ReadProcessMemory(kernel32.dll中)

[DllImport("kernel32.dll", SetLastError = true)]
public static extern bool ReadProcessMemory(
  IntPtr hProcess,
  IntPtr lpBaseAddress,
  [Out] byte[] lpBuffer,
  int size,
  out int lpNumberOfBytesRead
 );

每次从游戏进程中读取4KB的内存(一页就是4KB),然后从这4K中查找你要找的值,把匹配的内存地址记下来。如下是查找按钮的单击事件,处理了对整个游戏进程内存的搜索过程

/// <summary>
/// 查找按键的单击事件
/// </summary>
private void btnSearch_Click(object sender, EventArgs e)
{
    List<Result> resultList = new List<Result>();
    String value = txtValue.Text.Trim();
    Type type = (Type)cmbDateType.SelectedValue;
    int size = 4 * 1024;//4KB,每次读取4K数据
    //从4MB开始读,0-4M是无法读取的,为了防止整数举出,都少乘了个4
    for (int i = 4 * 1024; i < 2 * 1024 * 1024; i += 4)
    {
        byte[] buffer = new byte[size];//从目标进程中读取的数据是保存在这个数组中
        int sizeOfRead;
        //这里的i要乘1024才是真正的地址, 这里调用Api读取内存
        bool isOk = MemoryUtils.ReadMemoryBlock(this.GetAttacthedProcessHandle(), (IntPtr)(i * 1024), buffer, size, out sizeOfRead);

        //对取出来的这4k内存与期待值进行匹配,返回这个数组中所有匹配的数据的起始偏移量
        IList<int> pos = MemoryUtils.SearchResult(buffer, MemoryUtils.GetBytes(value, type), type);

        if (pos != null && pos.Count > 0)
        {
            foreach (int el in pos)
            {
                //i * 1024 + el这个是转化成真正的虚拟地址
                resultList.Add(new Result((IntPtr)(i * 1024 + el), txtValue.Text.Trim(), cmbDateType.SelectedValue.ToString()));
            }
        }<br>        if (!isOk)
        {
            break;//如果读取失败,则退出,通常意味着读到最后了
        }
    }
    dgvResult.DataSource = resultList;
}
 

由于进程的句柄用了一次后就可能会变化,所以,我每次从游戏进程读数据时,都是重新取了一下句柄。最后会把查找的结果放入DatagridView展示。

这里对在这4KB内的搜索进行了优化,主要是考虑到通常的程序都做了内存对齐(如对内存对齐不了解,Google哈),搜索的代码如下:

/// <summary>
     /// 从buffer中查找input,buffer为从进程中地读取的数据
     /// 本方法考虑到一般32位系统是4或8对齐的,为了查找的效率与兼容性,
     /// 以假设内存是4对齐来进行查找
     /// </summary>
     /// <param name="buffer">从进程中读取的数据</param>
     /// <param name="input">转化成字节数组后的期待值</param>
     /// <param name="type">期待值的类型</param>
     /// <returns></returns>
     public static IList<int> SearchResult(byte[] buffer, byte[] input, Type type)
     {
         //默认每次取4字节与期待值进行比较
         int offset = 4;
         if (type.IsPrimitive)
         {
             //如果期待值的长度小于4,则用期待值的长度,这里缺少校验
             if (input.Length < 4)
             {
                 offset = input.Length;
             }<br>      }
         IList<int> results = new List<int>();
         int i = 0;
         while (i < buffer.Length)
         {
             bool fit = true;
             for (int j = i; j < i + input.Length; j++)
             {
                 //当j超过长度时,为防止数组越界
                 if (j >= buffer.Length)
                 {
                     fit = false;
                     break;
                 }
                 fit = fit && (buffer[j] == input[j - i]);
                 //只要有一个字节没有匹配,没这一块内存没有匹配
                 if (!fit)
                 {
                     break;
                 }
             }
             if (fit)
             {
                 results.Add(i);
             }<br>        i += offset;//如果把offset改成1,则会有最好的兼容性
         }
         return results;
     }
 

接着是从结果中查询,只需要比对第一次查询出来的地址对应的值即可。没有复杂的地方,代码如下

/// <summary>
    /// 在结果中查找的单击事件
    /// </summary>
    private void btnSearchInResult_Click(object sender, EventArgs e)
    {
        List<Result> resultList = (List<Result>)dgvResult.DataSource;
        List<Result> newResultList = new List<Result>();
        String value = txtValue.Text.Trim();
        Type type = (Type)cmbDateType.SelectedValue;

        byte[] inputBytes = MemoryUtils.GetBytes(value, type);
        int inputSize = inputBytes.Length;
        //对上一次查询出来的结果再次与新的期待值进行匹配
        foreach (Result result in resultList)
        {
            byte[] buffer = new byte[inputSize];
            int sizeOfRead = 0;
            //这里调用Api读取内存
            bool isOk = MemoryUtils.ReadMemoryBlock(this.GetAttacthedProcessHandle(), result.GetAddress(), buffer, inputSize, out sizeOfRead);
            if (isOk)
            {
                bool isFit = true;
                for (int i = 0; i < inputSize; i++)
                {
                    isFit = isFit && (buffer[i] == inputBytes[i]);
                    if (!isFit)
                    {
                        break;
                    }
                }
                if (isFit)
                {
                    newResultList.Add(new Result(result.GetAddress(), value, result.Type));
                }
            }
        }<br>      dgvResult.DataSource = newResultList;<br>    }
 

从结果中查询结果后,再次在这个DatagridView中展示。

最后,就是把要修改的值添加到任务列表中,通过Timer来跑任务写到目标内存中。我定义了一下Task类,内部实现了写内存的方法:

/// <summary>
  /// 锁定任务
  /// </summary>
  class Task
  {

     private IntPtr addr;//起始地址
      private string lockedValue;//要锁定的值
      private Type type;//数据类型

      public Task(IntPtr addr, String lockedValue, Type type)
      {
          this.addr = addr;
          this.lockedValue = lockedValue;
          this.type = type;
      }
      public string Address
      {
          get
          {
              return addr.ToString("X");
          }
      }
      public string LockedValue
      {
          get
          {
              return lockedValue;
          }
      }
      public string DataType
      {
          get
          {
              return type.FullName;
          }
      }

      /// <summary>
      /// 写内存
      /// </summary>
      /// <param name="processHandle">目标进程句柄</param>
      public void WriteMemory(IntPtr processHandle)
      {
          byte[] input = MemoryUtils.GetBytes(this.lockedValue, this.type);
          int size = input.Length;
          int sizeOfWrite;
          MemoryUtils.WriteMemory(processHandle, this.addr, input, size, out sizeOfWrite);
      }
  }
 

经测试,在我自己的机器上伊苏1(复刻版)测试通过。

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
好的,我可以给你提供一个基于C#简易购物车实现的代码示例,使用cookie来存储购物车信息。以下是示例代码: ```csharp // 添加商品到购物车 protected void AddToCart_Click(object sender, EventArgs e) { string productId = ProductId.Text; string productName = ProductName.Text; decimal price = Decimal.Parse(Price.Text); int quantity = Int32.Parse(Quantity.Text); // 创建一个新cookie对象 HttpCookie cookie = new HttpCookie("shoppingCart"); // 检查购物车是否已经存在 if (Request.Cookies["shoppingCart"] != null) { cookie = Request.Cookies["shoppingCart"]; } // 将商品信息添加到cookie string item = productId + "|" + productName + "|" + price + "|" + quantity; cookie.Values.Add(productId, item); // 设置cookie的过期时间 cookie.Expires = DateTime.Now.AddDays(7); // 将cookie保存到客户端 Response.Cookies.Add(cookie); } // 显示购物车内容 protected void ShowCart_Click(object sender, EventArgs e) { // 从cookie获取购物车信息 HttpCookie cookie = Request.Cookies["shoppingCart"]; // 检查购物车是否为空 if (cookie != null && cookie.Values.Count > 0) { // 遍历cookie的商品信息并显示在页面上 foreach (string productId in cookie.Values.AllKeys) { string[] item = cookie.Values[productId].Split('|'); string productName = item[1]; decimal price = Decimal.Parse(item[2]); int quantity = Int32.Parse(item[3]); Response.Write("<p>" + productName + " - " + price.ToString("c") + " x " + quantity + "</p>"); } } else { Response.Write("<p>购物车为空。</p>"); } } ``` 以上代码实现了向购物车添加商品和显示购物车内容的功能。当用户点击“添加到购物车”按钮时,代码会创建一个名为“shoppingCart”的cookie对象,将商品信息添加到cookie,并将cookie保存到客户端。当用户点击“显示购物车”按钮时,代码会从cookie获取购物车信息,并将每个商品的名称、价格和数量显示在页面上。 请注意,这只是一个简单的示例代码,实际的购物车实现可能需要更复杂的逻辑来处理商品数量、价格计算等问题。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值