数か月前、オープンソースのメモ帳クローンを使用してすべての入力とレンダリングを処理するゲームについて説明する投稿がRedditに投稿されました。これについて読んで、私は標準のWindowsメモ帳で同様の動作を見るのは素晴らしいことだと思いました。それから私はあまりにも多くの自由な時間を過ごしました。
最終的に、すべての入力タスクとレンダリングタスクに標準のメモ帳を使用するSnakeゲームと小さなレイトレーサーを作成し、その過程でDLLインジェクション、APIフッキング、メモリスキャンについて学びました。その過程で私が学んだことすべてを説明することは、あなたにとって興味深い読み物かもしれません。
最初に、メモリスキャナーがどのように機能するか、およびそれらを使用してnotepad.exeを毎秒30フレーム以上のレンダリングターゲットに変換する方法について説明します。また、メモ帳でレンダリングするために作成したレイトレーサーについても説明します。
重要なイベントをメモ帳に送信する
まず、実行中のNotepadインスタンスにキーイベントをディスパッチする方法について説明します。これはプロジェクトの退屈な部分だったので、簡単に説明します。
Win32 (, ), , , , , «», , . , Visual Studio Spy++, , .
Spy++ , , , «». , , Win32, 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.
, Spy++, 64- . , Visual Studio 2019 . Visual Studio «spyxx_amd64.exe».
, 10 , , , , 30 . , .
CheatEngine
CheatEngine. , . , // , . .
CheatEngine , . , . , :
, (, 100)
- , (, 92)
, ( 100), , 92
, (, , , )
, , , , . CheatEngine, ( ) . :
UTF-16, , UTF-8.
, 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
~ 40 .
, , — .
64- Windows ( 0x00000000000 0x7FFFFFFFFFFF), 0 VirtualQueryEx .
VirtualQueryEx MEMORY_BASIC_INFORMATION
, , , VirtualQueryEx , . MEMORY_BASIC_INFORMATION
.
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;
}
}
, / .State .Protect. MEMORY_BASIC_INFORMATION
, , , 0x1000 (MEM_COMMIT
) 0x04 (PAGE_READWRITE
).
( , , ). . ReadProcessMemory.
, , . , , . , .
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;
}
FindPattern() , . , FindPattern, , . .
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;
}
}
, , «MemoryScanner» github. ! ( , ymmv, ).
UTF-16
, UTF-16, , FindBytePatternInMemory (), UTF-16. . MemoryScanner github :
//convert input string to UTF16 (hackily)
const size_t patternLen = strlen(argv[2]);
char* pattern = new char[patternLen*2];
for (int i = 0; i < patternLen; ++i){
pattern[i*2] = argv[2][i];
pattern[i*2 + 1] = 0x0;
}
, , WriteProcessMemory . , , , Edit.
, Win32 api InvalidateRect, .
, :
void UpdateText(HINSTANCE process, HWND editWindow, char* notepadTextBuffer, char* replacementTextBuffer, int len)
{
size_t written = 0;
WriteProcessMemory(process, notepadTextBuffer, replacementTextBuffer, len, &written);
RECT r;
GetClientRect(editWindow, &r);
InvalidateRect(editWindow, &r, false);
}
. , , , , , .
:
. MoveWindow , , .
, , ( ) , . MoveWindow , WM_CHAR . , .
, , , WM_CHAR.
, . github , .
void PreallocateTextBuffer(DWORD processId)
{
HWND editWindow = GetWindowForProcessAndClassName(processId, "Edit");
// it takes 131 * 30 chars to fill a 1365x768 window with Consolas (size 11) chars
MoveWindow(instance.topWindow, 100, 100, 1365, 768, true);
size_t charCount = 131 * 30;
size_t utf16BufferSize = charCount * 2;
char* frameBuffer = (char*)malloc(utf16BufferSize);
for (int i = 0; i < charCount; i++){
char v = 0x41 + (rand() % 26);
PostMessage(editWindow, WM_CHAR, v, 0);
frameBuffer[i * 2] = v;
frameBuffer[i * 2 + 1] = 0x00;
}
Sleep(5000); //wait for input messages to finish processing...it's slow.
//Now use the frameBuffer as the unique byte pattern to search for
}
, , , .
. , (Consolas, 11pt), - WM_SETFONT , , . Consolas 11pt , .
, , , . , ScratchAPixel . , .
, . WriteProcessMemory ( , ), , ( * 2 (- UTF16)). , WriteProcessMemory . :
void drawChar(int x, int y, char c); //local buffer
void clearScreen(); // local buffer
void swapBuffersAndRedraw(); // pushes changes and refreshes screen.
, , (131 x 30), , «» . , , , , ascii. , .
. , , . , «» , , .
float aspect = (0.5f * SCREEN_CHARS_WIDE) / float(SCREEN_CHARS_TALL);
, , , . , , , WM_VSCROLL, « » , . , , , , .
2: Boogaloo!
メモ帳でリアルタイムゲームを作成するという私の探求の次の(そして最後の)部分は、ユーザー入力の処理方法を理解することでした。もっと欲しい場合は、次の投稿がここにあります!