每日最新頭條.有趣資訊

正確的“記事本”打開方式:能渲染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 = 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 的工具。

基本算法如下:

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 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 的工具。

一旦記憶體被複製到本地可見的緩衝區,搜索字節模式就很容易了。

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 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。

更多細節描述,可以參考文末的參考鏈接。

更多的「記事本」玩法

當然,關於記事本的別樣玩法,還有好多。

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

還有用記事本自製繪圖軟體的。

那麽,你還有更炫酷的「記事本」玩法嗎?

歡迎在評論區留言推薦~

獲得更多的PTT最新消息
按讚加入粉絲團