正確的「記事本」打開(kāi)方式:能渲染3D圖像,還能玩貪吃蛇
「記事本」玩出新高度
金磊 發(fā)自 凹非寺
量子位 報(bào)道 | 公眾號(hào) QbitAI
渲染3D圖像,一個(gè)「記事本」就夠了。
最近,GitHub上一名叫“Kyle Halladay”的小哥,便上傳了這樣一個(gè)項(xiàng)目,用記事本來(lái)渲染圖像。
效果是這樣的:
立方體旋轉(zhuǎn)、陰影變化,還挺有內(nèi)味的。
還有貪吃蛇效果的:
那么,小哥是如何拿記事本,就做到這些效果的呢?
正確的「記事本」打開(kāi)方式
據(jù)小哥介紹,所有的輸入和渲染效果,都是在記事本中完成。
在此之前,需要做一些設(shè)置工作。
首先,是將鍵盤(pán)事件(Key Event),發(fā)送到正在運(yùn)行的記事本。
這里就要用到 Visual Studio 提供的一個(gè)叫 Spy + + 的工具,可以列出組成給定應(yīng)用程序的所有窗口。
Spy + + 顯示了要找的記事本子窗口是“編輯”窗口。
一旦我知道了這一點(diǎn),就只需要搞清楚 Win32函數(shù)調(diào)用的正確組合,用來(lái)獲得該 UI 元素的 HWND,然后將輸入發(fā)送過(guò)去。
得到的 HWND 是這樣的:
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,在記事本的編輯控件中繪制一個(gè)字符,便是使用 PostMessage 向它發(fā)送一個(gè) WM char 事件的問(wèn)題。
接下來(lái),就是建一個(gè)內(nèi)存掃描器?(Memory Scanner),這里要用到一個(gè)叫做 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
內(nèi)存掃描程序需要做的第一件事,就是遍歷進(jìn)程分配的內(nèi)存。
因?yàn)?Windows 上每個(gè)64位進(jìn)程的虛擬內(nèi)存范圍是相同的,所以需要制作一個(gè)指向地址0的指針,然后使用 VirtualQueryEx 獲取目標(biāo)程序的虛擬地址信息。
將具有相同內(nèi)存屬性的內(nèi)容頁(yè),組織到 MEMORY basic information 結(jié)構(gòu)中,因此,可能是 VirtualQueryEx 為給定地址返回的結(jié)構(gòu)包含超過(guò)1頁(yè)的信息。
一旦有了第一個(gè) MEMORY basic information 結(jié)構(gòu),在內(nèi)存中進(jìn)行迭代只需要將當(dāng)前結(jié)構(gòu)的 BaseAddress 和 RegionSize 成員添加到一起,并將新地址提供給 VirtualQueryEx 以獲得下一組連續(xù)的頁(yè)面。
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;
??}
}
然后,是在進(jìn)程內(nèi)存中,搜索字節(jié)模式?(Byte Pattern)的工作,此處需要一個(gè)叫做 ReadProcessMemory 的工具。
一旦內(nèi)存被復(fù)制到本地可見(jiàn)的緩沖區(qū),搜索字節(jié)模式就很容易了。
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;
}
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;
??}
}
需要注意的是,記事本將屏幕上的文本緩沖區(qū)作為 UTF-16數(shù)據(jù)存儲(chǔ),因此提供給 FindBytePatternInMemory ()的字節(jié)模式也必須是 UTF-16。
更多細(xì)節(jié)描述,可以參考文末的參考鏈接。
更多的「記事本」玩法
當(dāng)然,關(guān)于記事本的別樣玩法,還有好多。
例如,有拿記事本完成「快排」的可視化。
還有用記事本自制繪圖軟件的。
那么,你還有更炫酷的「記事本」玩法嗎?
歡迎在評(píng)論區(qū)留言推薦~
參考鏈接
https://github.com/khalladay/render-with-notepadhttp://kylehalladay.com/blog/2020/05/20/Rendering-With-Notepad.htmlhttps://www.bilibili.com/video/BV1v4411e7Gy?from=search&seid=50634434912662370https://www.bilibili.com/video/BV1os411u7vD?from=search&seid=11201980142804134991
- 商湯林達(dá)華萬(wàn)字長(zhǎng)文回答AGI:4層破壁,3大挑戰(zhàn)2025-08-12
- 商湯多模態(tài)大模型賦能鐵路勘察設(shè)計(jì),讓70年經(jīng)驗(yàn)“活”起來(lái)2025-08-13
- 以“具身智能基座”為核,睿爾曼攜全產(chǎn)品矩陣及新品亮相2025 WRC2025-08-11
- 哇塞,今天北京被機(jī)器人人人人人塞滿(mǎn)了!2025-08-08