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

金磊发自凹非寺
量子位报道|公众号QbitAI
渲染3D图像 , 一个「记事本」就够了 。
正确的“记事本”打开方式:能渲染3D图像,还能玩贪吃蛇
文章图片
最近 , GitHub上一名叫“KyleHalladay”的小哥 , 便上传了这样一个项目 , 用记事本来渲染图像 。
效果是这样的:
正确的“记事本”打开方式:能渲染3D图像,还能玩贪吃蛇
文章图片
立方体旋转、阴影变化 , 还挺有内味的 。
还有贪吃蛇效果的:
正确的“记事本”打开方式:能渲染3D图像,还能玩贪吃蛇
文章图片
那么 , 小哥是如何拿记事本 , 就做到这些效果的呢?
正确的「记事本」打开方式
据小哥介绍 , 所有的输入和渲染效果 , 都是在记事本中完成 。
在此之前 , 需要做一些设置工作 。
首先 , 是将键盘事件(KeyEvent) , 发送到正在运行的记事本 。
这里就要用到VisualStudio提供的一个叫Spy++的工具 , 可以列出组成给定应用程序的所有窗口 。
正确的“记事本”打开方式:能渲染3D图像,还能玩贪吃蛇
文章图片
Spy++显示了要找的记事本子窗口是“编辑”窗口 。
一旦我知道了这一点 , 就只需要搞清楚Win32函数调用的正确组合 , 用来获得该UI元素的HWND , 然后将输入发送过去 。
得到的HWND是这样的:
HWNDGetWindowForProcessAndClassName(DWORDpid,constchar*className){HWNDcurWnd=GetTopWindow(0);//0argmeanstogetthewindowatthetopoftheZordercharclassNameBuf[256];while(curWnd!=NULL){DWORDcurPid;DWORDdwThreadId=GetWindowThreadProcessId(curWnd,&curPid);if(curPid==pid){GetClassName(curWnd,classNameBuf,256);if(strcmp(className,classNameBuf)==0)returncurWnd;HWNDchildWindow=FindWindowEx(curWnd,NULL,className,NULL);if(childWindow!=NULL)returnchildWindow;}curWnd=GetNextWindow(curWnd,GW_HWNDNEXT);}returnNULL;}
一旦拿到了正确的控件HWND , 在记事本的编辑控件中绘制一个字符 , 便是使用PostMessage向它发送一个WMchar事件的问题 。
接下来 , 就是建一个内存扫描器(MemoryScanner) , 这里要用到一个叫做CheatEngine的工具 。
基本算法如下:
FOREACHblockofmemoryallocatedbyourtargetprocessIFthatblockiscommittedandread/writeenabledScanthecontentsofthatblockforourbytepatternIFWEFINDITreturnthataddress
内存扫描程序需要做的第一件事 , 就是遍历进程分配的内存 。
因为Windows上每个64位进程的虚拟内存范围是相同的 , 所以需要制作一个指向地址0的指针 , 然后使用VirtualQueryEx获取目标程序的虚拟地址信息 。
将具有相同内存属性的内容页 , 组织到MEMORYbasicinformation结构中 , 因此 , 可能是VirtualQueryEx为给定地址返回的结构包含超过1页的信息 。
一旦有了第一个MEMORYbasicinformation结构 , 在内存中进行迭代只需要将当前结构的BaseAddress和RegionSize成员添加到一起 , 并将新地址提供给VirtualQueryEx以获得下一组连续的页面 。
char*FindBytePatternInProcessMemory(HANDLEprocess,constchar*pattern,size_tpatternLen){char*basePtr=(char*)0x0;MEMORY_BASIC_INFORMATIONmemInfo;while(VirtualQueryEx(process,(void*)basePtr,&memInfo,sizeof(MEMORY_BASIC_INFORMATION))){constDWORDmem_commit=0x1000;constDWORDpage_readwrite=0x04;if(memInfo.State==mem_commit&&memInfo.Protect==page_readwrite){//searchthismemoryforourpattern}basePtr=(char*)memInfo.BaseAddress+memInfo.RegionSize;}}
然后 , 是在进程内存中 , 搜索字节模式(BytePattern)的工作 , 此处需要一个叫做ReadProcessMemory的工具 。
一旦内存被复制到本地可见的缓冲区 , 搜索字节模式就很容易了 。
char*FindPattern(char* src,size_t srcLen,constchar*pattern,size_tpatternLen){char*cur= src;size_tcurPos=0;while(curPos
char*FindBytePatternInProcessMemory(HANDLEprocess,constchar*pattern,size_tpatternLen){MEMORY_BASIC_INFORMATIONmemInfo;char*basePtr=(char*)0x0;while(VirtualQueryEx(process,(void*)basePtr,&memInfo,sizeof(MEMORY_BASIC_INFORMATION))){constDWORDmem_commit=0x1000;constDWORDpage_readwrite=0x04;if(memInfo.State==mem_commit&&memInfo.Protect==page_readwrite){char*remoteMemRegionPtr=(char*)memInfo.BaseAddress;char*localCopyContents=(char*)malloc(memInfo.RegionSize);SIZE_TbytesRead=0;if(ReadProcessMemory(process,memInfo.BaseAddress,localCopyContents,memInfo.RegionSize,&bytesRead)){char*match=FindPattern(localCopyContents,memInfo.RegionSize,pattern,patternLen);if(match){uint64_tdiff=(uint64_t)match-(uint64_t)(localCopyContents);char*processPtr=remoteMemRegionPtr+diff;returnprocessPtr;}}free(localCopyContents);}basePtr=(char*)memInfo.BaseAddress+memInfo.RegionSize;}}
需要注意的是 , 记事本将屏幕上的文本缓冲区作为UTF-16数据存储 , 因此提供给FindBytePatternInMemory()的字节模式也必须是UTF-16 。
更多细节描述 , 可以参考文末的参考链接 。
更多的「记事本」玩法
当然 , 关于记事本的别样玩法 , 还有好多 。
例如 , 有拿记事本完成「快排」的可视化 。
正确的“记事本”打开方式:能渲染3D图像,还能玩贪吃蛇
文章图片
还有用记事本自制绘图软件的 。
正确的“记事本”打开方式:能渲染3D图像,还能玩贪吃蛇
文章图片
那么 , 你还有更炫酷的「记事本」玩法吗?
【正确的“记事本”打开方式:能渲染3D图像,还能玩贪吃蛇】欢迎在评论区留言推荐~


    推荐阅读