Raytracing in Notepad.exe mit 30 Bildern pro Sekunde

Vor einigen Monaten wurde auf Reddit ein Beitrag veröffentlicht , in dem ein Spiel beschrieben wurde, bei dem ein Open-Source-Notepad-Klon für alle Eingaben und Renderings verwendet wurde. Als ich darüber las, dachte ich, es wäre großartig zu sehen, wie etwas Ähnliches mit dem Standard-Windows-Editor funktioniert. Dann hatte ich zu viel Freizeit.





Am Ende habe ich ein Snake-Spiel und einen kleinen Ray-Tracer erstellt, die den Standard-Editor für alle Eingabe- und Rendering-Aufgaben verwenden, und dabei etwas über DLL-Injection, API-Hooking und Memory-Scanning gelernt. Es könnte für Sie interessant sein, alles zu beschreiben, was ich dabei gelernt habe.





Zuerst möchte ich darüber sprechen, wie Speicherscanner funktionieren und wie ich sie verwendet habe, um notepad.exe mit mehr als 30 Bildern pro Sekunde in ein Renderziel zu verwandeln. Ich werde auch über einen Ray Tracer sprechen, den ich für das Rendern in Notepad erstellt habe.





Senden wichtiger Ereignisse an den Editor

Ich werde zunächst über das Versenden von Schlüsselereignissen an eine laufende Notepad-Instanz sprechen. Dies war der langweilige Teil des Projekts, daher werde ich mich kurz fassen.





Win32 (, ), , , , , «», , . , Visual Studio Spy++, , .





Spy ++ Notepad
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 und Notepad "haben Freunde gefunden"
CheatEngine ""

, ,   , , . CheatEngine, ( ) . :





  1. UTF-16, , UTF-8.





  2. , CheatEngine (, ?)





  3. . ,





, , .





, , . 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!





Der nächste (und letzte) Teil meiner Suche nach einem Echtzeitspiel in Notepad bestand darin, herauszufinden, wie ich mit Benutzereingaben umgehen soll. Wenn Sie mehr wollen, finden Sie den nächsten Beitrag hier !








All Articles