金磊发自凹非寺量子位报道公众号QbitAI 渲染3D图像,一个记事本就够了。 最近,GitHub上一名叫KyleHalladay的小哥,便上传了这样一个项目,用记事本来渲染图像。 效果是这样的: 立方体旋转、阴影变化,还挺有内味的。 还有贪吃蛇效果的: 那么,小哥是如何拿记事本,就做到这些效果的呢? 正确的记事本打开方式 据小哥介绍,所有的输入和渲染效果,都是在记事本中完成。 在此之前,需要做一些设置工作。 首先,是将键盘事件(KeyEvent),发送到正在运行的记事本。 这里就要用到VisualStudio提供的一个叫Spy的工具,可以列出组成给定应用程序的所有窗口。 Spy显示了要找的记事本子窗口是编辑窗口。 一旦我知道了这一点,就只需要搞清楚Win32函数调用的正确组合,用来获得该UI元素的HWND,然后将输入发送过去。 得到的HWND是这样的: HWNDGetWindowForProcessAndClassName(DWORDpid,constcharclassName){HWNDcurWndindow(0);0argmeanstogetthewindowatthetopoftheZordercharclassNameBuf〔256〕;while(curWnd!NULL){DWORDcurPDWORDdwThreadIdGetWindowThreadProcessId(curWnd,curPid);if(curPidpid){GetClassName(curWnd,classNameBuf,256);if(strcmp(className,classNameBuf)0)returncurWHWNDchildWindowFindWindowEx(curWnd,NULL,className,NULL);if(childWindow!NULL)returnchildW}curWndGetNextWindow(curWnd,GWHWNDNEXT);}returnNULL;} 一旦拿到了正确的控件HWND,在记事本的编辑控件中绘制一个字符,便是使用PostMessage向它发送一个WMchar事件的问题。 接下来,就是建一个内存扫描器(MemoryScanner),这里要用到一个叫做CheatEngine的工具。 基本算法如下: FOREACHblockofmemoryallocatedbyourtargetprocessIFthatblockiscommittedandreadwriteenabledScanthecontentsofthatblockforourbytepatternIFWEFINDITreturnthataddress 内存扫描程序需要做的第一件事,就是遍历进程分配的内存。 因为Windows上每个64位进程的虚拟内存范围是相同的,所以需要制作一个指向地址0的指针,然后使用VirtualQueryEx获取目标程序的虚拟地址信息。 将具有相同内存属性的内容页,组织到MEMORYbasicinformation结构中,因此,可能是VirtualQueryEx为给定地址返回的结构包含超过1页的信息。 一旦有了第一个MEMORYbasicinformation结构,在内存中进行迭代只需要将当前结构的BaseAddress和RegionSize成员添加到一起,并将新地址提供给VirtualQueryEx以获得下一组连续的页面。 charFindBytePatternInProcessMemory(HANDLEprocess,constcharpattern,sizetpatternLen){charbasePtr(char)0x0;MEMORYBASICINFORMATIONmemIwhile(VirtualQueryEx(process,(void)basePtr,memInfo,sizeof(MEMORYBASICINFORMATION))){constDWORDit0x1000;constDWORDpagereadwrite0x04;if(memInfo。SmemInfo。Protectpagereadwrite){searchthismemoryforourpattern}basePtr(char)memInfo。BaseAddressmemInfo。RegionS}} 然后,是在进程内存中,搜索字节模式(BytePattern)的工作,此处需要一个叫做ReadProcessMemory的工具。 一旦内存被复制到本地可见的缓冲区,搜索字节模式就很容易了。 charFindPattern(charsrc,sizetsrcLen,constcharpattern,sizetpatternLen){sizetcurPos0;while(curPoscharFindBytePatternInProcessMemory(HANDLEprocess,constcharpattern,sizetpatternLen){MEMORYBASICINFORMATIONmemIcharbasePtr(char)0x0;while(VirtualQueryEx(process,(void)basePtr,memInfo,sizeof(MEMORYBASICINFORMATION))){constDWORDit0x1000;constDWORDpagereadwrite0x04;if(memInfo。SmemInfo。Protectpagereadwrite){charremoteMemRegionPtr(char)memInfo。BaseAcharlocalCopyContents(char)malloc(memInfo。RegionSize);SIZETbytesRead0;if(ReadProcessMemory(process,memInfo。BaseAddress,localCopyContents,memInfo。RegionSize,bytesRead)){charmatchFindPattern(localCopyContents,memInfo。RegionSize,pattern,patternLen);if(match){uint64tdiff(uint64t)match(uint64t)(localCopyContents);charprocessPtrremoteMemRegionPreturnprocessP}}free(localCopyContents);}basePtr(char)memInfo。BaseAddressmemInfo。RegionS}} 需要注意的是,记事本将屏幕上的文本缓冲区作为UTF16数据存储,因此提供给FindBytePatternInMemory()的字节模式也必须是UTF16。 更多细节描述,可以参考文末的参考链接。 更多的记事本玩法 当然,关于记事本的别样玩法,还有好多。 例如,有拿记事本完成快排的可视化。 还有用记事本自制绘图软件的。 那么,你还有更炫酷的记事本玩法吗? 欢迎在评论区留言推荐 参考链接 khalladayrenderwithnotepadblog20200520RenderingWithNotepad。html: 完 量子位QbitAI头条号签约 关注我们,第一时间获知前沿科技动态