快好知 kuaihz

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

金磊 发自 凹非寺量子位 报道 | 公众号 QbitAI

渲染3D图像,一个「记事本」就够了。

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

效果是这样的:

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

还有贪吃蛇效果的:

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

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

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

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

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

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

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

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

得到的 HWND 是这样的:

HWND GetWindowForProcessAndClassName(DWORD pid, const char* className){  HWND curWnd = indow(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 的工具。

基本算法如下:

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 以获得下一组连续的页面。

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 it = 0x1000;    const DWORD page_readwrite = 0x04;    if (memInfo.State == it && memInfo.Protect == page_readwrite)    {      // search this memory for our pattern    }    basePtr = (char*)memInfo.BaseAddress + memInfo.RegionSize;  }}

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

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

char* FindPattern(char* src, size_t srcLen, const char* pattern, size_t patternLen){  char* cur = src;  size_t curPos = 0;  while (curPos  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 it = 0x1000;    const DWORD page_readwrite = 0x04;    if (memInfo.State == it && 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。

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

更多的「记事本」玩法

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

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

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

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

欢迎在评论区留言推荐~

参考链接

khalladay/render-with-notepadblog/2020/05/20/Rendering-With-Notepad.html:

— 完 —

量子位 QbitAI · 头条号签约

关注我们,第一时间获知前沿科技动态

本站资源来自互联网,仅供学习,如有侵权,请通知删除,敬请谅解!
搜索建议:渲染  渲染词条  图像  图像词条  记事本  记事本词条  正确  正确词条  打开  打开词条  
开店经营

 怎样经营好汽车4S店

树立以服务为中心的经营理念,建立以服务为中心的企业经营管理模式。要从企业的经营理念、企业文化、服务意识、服务态度、服务专业水平、专业技术等多方面对员工进行培训,...(展开)