Beberapa bulan yang lalu, sebuah posting diposting di Reddit yang menjelaskan game yang menggunakan klon Notepad open source untuk menangani semua input dan rendering. Membaca tentang ini, saya pikir akan sangat bagus untuk melihat sesuatu yang serupa bekerja dengan Windows Notepad standar. Kemudian saya memiliki terlalu banyak waktu luang.
Saya akhirnya membuat game Snake dan pelacak sinar kecil yang menggunakan Notepad standar untuk semua tugas input dan rendering, dan di sepanjang jalan saya belajar tentang DLL Injection, API Hooking, dan Memory Scanning. Menjelaskan semua yang telah saya pelajari dalam prosesnya mungkin bacaan yang menarik untuk Anda.
Pertama saya ingin berbicara tentang cara kerja pemindai memori dan bagaimana saya menggunakannya untuk mengubah notepad.exe menjadi target render pada 30+ bingkai per detik. Saya juga akan berbicara tentang pelacak sinar yang saya buat untuk rendering di Notepad.
Mengirim peristiwa penting ke notepad
Saya akan mulai dengan berbicara tentang pengiriman peristiwa penting ke instance Notepad yang sedang berjalan. Ini adalah bagian proyek yang membosankan, jadi saya akan singkat.
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!
Bagian selanjutnya (dan terakhir) dari pencarian saya untuk membuat game real-time di Notepad adalah mencari tahu cara menangani input pengguna. Jika Anda ingin lebih, posting selanjutnya dapat ditemukan di sini !