前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >正确的「记事本」打开方式:能渲染3D图像,还能玩贪吃蛇

正确的「记事本」打开方式:能渲染3D图像,还能玩贪吃蛇

作者头像
量子位
发布2020-06-01 15:40:48
5380
发布2020-06-01 15:40:48
举报
文章被收录于专栏:量子位量子位
渲染3D图像,一个「记事本」就够了。

最近,GitHub上一名叫“Kyle Halladay”的小哥,便上传了这样一个项目,用记事本来渲染图像。

效果是这样的:

立方体旋转、阴影变化,还挺有内味的。

还有贪吃蛇效果的:

那么,小哥是如何拿记事本,就做到这些效果的呢?

正确的「记事本」打开方式

据小哥介绍,所有的输入和渲染效果,都是在记事本中完成。

在此之前,需要做一些设置工作。

首先,是将键盘事件(Key Event),发送到正在运行的记事本。

这里就要用到 Visual Studio 提供的一个叫 Spy + + 的工具,可以列出组成给定应用程序的所有窗口。

Spy + + 显示了要找的记事本子窗口是“编辑”窗口。

一旦我知道了这一点,就只需要搞清楚 Win32函数调用的正确组合,用来获得该 UI 元素的 HWND,然后将输入发送过去。

得到的 HWND 是这样的:

代码语言:javascript
复制
HWND GetWindowForProcessAndClassName(DWORD pid, const char* className)
{
  HWND curWnd = GetTopWindow(0); //0 arg means to get the window at the top of the Z order
  char classNameBuf[256];

  while (curWnd != NULL){
    DWORD curPid;
    DWORD dwThreadId = GetWindowThreadProcessId(curWnd, &curPid);

    if (curPid == pid){
      GetClassName(curWnd, classNameBuf, 256);
      if (strcmp(className, classNameBuf) == 0) return curWnd;

      HWND childWindow = FindWindowEx(curWnd, NULL, className, NULL);
      if (childWindow != NULL) return childWindow;
    }
    curWnd = GetNextWindow(curWnd, GW_HWNDNEXT);
  }
  return NULL;
}

一旦拿到了正确的控件 HWND,在记事本的编辑控件中绘制一个字符,便是使用 PostMessage 向它发送一个 WM char 事件的问题。

接下来,就是建一个内存扫描器 (Memory Scanner),这里要用到一个叫做 CheatEngine 的工具。

基本算法如下:

代码语言:javascript
复制
FOR EACH block of memory allocated by our target process
   IF that block is committed and read/write enabled
       Scan the contents of that block for our byte pattern
       IF WE FIND IT
           return that address

内存扫描程序需要做的第一件事,就是遍历进程分配的内存。

因为 Windows 上每个64位进程的虚拟内存范围是相同的,所以需要制作一个指向地址0的指针,然后使用 VirtualQueryEx 获取目标程序的虚拟地址信息。

将具有相同内存属性的内容页,组织到 MEMORY basic information 结构中,因此,可能是 VirtualQueryEx 为给定地址返回的结构包含超过1页的信息。

一旦有了第一个 MEMORY basic information 结构,在内存中进行迭代只需要将当前结构的 BaseAddress 和 RegionSize 成员添加到一起,并将新地址提供给 VirtualQueryEx 以获得下一组连续的页面。

代码语言:javascript
复制
char* FindBytePatternInProcessMemory(HANDLE process, const char* pattern, size_t patternLen)
{
  char* basePtr = (char*)0x0;

  MEMORY_BASIC_INFORMATION memInfo;

  while (VirtualQueryEx(process, (void*)basePtr, &memInfo, sizeof(MEMORY_BASIC_INFORMATION)))
  {
    const DWORD mem_commit = 0x1000;
    const DWORD page_readwrite = 0x04;
    if (memInfo.State == mem_commit && memInfo.Protect == page_readwrite)
    {
      // search this memory for our pattern
    }

    basePtr = (char*)memInfo.BaseAddress + memInfo.RegionSize;
  }
}

然后,是在进程内存中,搜索字节模式 (Byte Pattern)的工作,此处需要一个叫做 ReadProcessMemory 的工具。

一旦内存被复制到本地可见的缓冲区,搜索字节模式就很容易了。

代码语言:javascript
复制
char* FindPattern(char* src, size_t srcLen, const char* pattern, size_t patternLen)
{
  char* cur = src;
  size_t curPos = 0;

  while (curPos < srcLen){
    if (memcmp(cur, pattern, patternLen) == 0){
      return cur;
    }

    curPos++;
    cur = &src[curPos];
  }
  return nullptr;
}
代码语言:javascript
复制
char* FindBytePatternInProcessMemory(HANDLE process, const char* pattern, size_t patternLen)
{
  MEMORY_BASIC_INFORMATION memInfo;
  char* basePtr = (char*)0x0;

  while (VirtualQueryEx(process, (void*)basePtr, &memInfo, sizeof(MEMORY_BASIC_INFORMATION))){
    const DWORD mem_commit = 0x1000;
    const DWORD page_readwrite = 0x04;
    if (memInfo.State == mem_commit && memInfo.Protect == page_readwrite){
      char* remoteMemRegionPtr = (char*)memInfo.BaseAddress;
      char* localCopyContents = (char*)malloc(memInfo.RegionSize);

      SIZE_T bytesRead = 0;
      if (ReadProcessMemory(process, memInfo.BaseAddress, localCopyContents, memInfo.RegionSize, &bytesRead)){
        char* match = FindPattern(localCopyContents, memInfo.RegionSize, pattern, patternLen);

        if (match){
          uint64_t diff = (uint64_t)match - (uint64_t)(localCopyContents);
          char* processPtr = remoteMemRegionPtr + diff;
          return processPtr;
        }
      }
      free(localCopyContents);
    }
    basePtr = (char*)memInfo.BaseAddress + memInfo.RegionSize;
  }
}

需要注意的是,记事本将屏幕上的文本缓冲区作为 UTF-16数据存储,因此提供给 FindBytePatternInMemory ()的字节模式也必须是 UTF-16。

更多细节描述,可以参考文末的参考链接。

更多的「记事本」玩法

当然,关于记事本的别样玩法,还有好多。

例如,有拿记事本完成「快排」的可视化。

还有用记事本自制绘图软件的。

那么,你还有更炫酷的「记事本」玩法吗?

欢迎在评论区留言推荐~

参考链接

https://github.com/khalladay/render-with-notepad http://kylehalladay.com/blog/2020/05/20/Rendering-With-Notepad.html https://www.bilibili.com/video/BV1v4411e7Gy?from=search&seid=50634434912662370 https://www.bilibili.com/video/BV1os411u7vD?from=search&seid=11201980142804134991

作者系网易新闻·网易号“各有态度”签约作者

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2020-05-24,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 量子位 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 正确的「记事本」打开方式
  • 更多的「记事本」玩法
相关产品与服务
数据保险箱
数据保险箱(Cloud Data Coffer Service,CDCS)为您提供更高安全系数的企业核心数据存储服务。您可以通过自定义过期天数的方法删除数据,避免误删带来的损害,还可以将数据跨地域存储,防止一些不可抗因素导致的数据丢失。数据保险箱支持通过控制台、API 等多样化方式快速简单接入,实现海量数据的存储管理。您可以使用数据保险箱对文件数据进行上传、下载,最终实现数据的安全存储和提取。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档