js 数据写到本地记事本_正确的「记事本」打开方式:能渲染3D图像,还能玩贪吃蛇...

2fae7288d02ea21fbc2e79287da18c9b.png
金磊 发自 凹非寺
量子位 报道 | 公众号 QbitAI

渲染3D图像,一个「记事本」就够了。

最近,GitHub上一名叫“Kyle Halladay”的小哥,便上传了这样一个项目,用记事本来渲染图像。

效果是这样的:

82ea03ef40a247de0cae3f6044d7149b.png

立方体旋转、阴影变化,还挺有内味的。

还有贪吃蛇效果的:

b756d74ac4f5e2bfa4380291361ec35d.png

那么,小哥是如何拿记事本,就做到这些效果的呢?

正确的「记事本」打开方式

据小哥介绍,所有的输入和渲染效果,都是在记事本中完成。

在此之前,需要做一些设置工作。

首先,是将键盘事件(Key Event),发送到正在运行的记事本。

这里就要用到 Visual Studio 提供的一个叫 Spy + + 的工具,可以列出组成给定应用程序的所有窗口。

7629e85e180f18a3d97c32e4c7b4adc2.png

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 < 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;
  }
}

需要注意的是,记事本将屏幕上的文本缓冲区作为 UTF-16数据存储,因此提供给 FindBytePatternInMemory ()的字节模式也必须是 UTF-16。

更多细节描述,可以参考文末的参考链接。

更多的「记事本」玩法

当然,关于记事本的别样玩法,还有好多。

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

5f770ac4a962a18c708e9887191f447f.png

还有用记事本自制绘图软件的。

b4b38cd1245263df6af88b53a7adca4e.png

那么,你还有更炫酷的「记事本」玩法吗?

欢迎在评论区留言推荐~

参考链接

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

—完—

@量子位 · 追踪AI技术和产品新动态

深有感触的朋友,欢迎赞同、关注、分享三连վ'ᴗ' ի ❤

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
抱歉,我是一个 AI 语言模型,无法在浏览器上直接运行游戏。不过,我可以提供一些Python代码的参考,供你编贪吃蛇游戏: ``` import pygame import random # 游戏窗口的大小 WINDOW_WIDTH = 800 WINDOW_HEIGHT = 600 # 定义颜色 WHITE = (255, 255, 255) RED = (255, 0, 0) GREEN = (0, 255, 0) BLACK = (0, 0, 0) # 蛇的初始位置和大小 SNAKE_SIZE = 20 SNAKE_POS_X = WINDOW_WIDTH / 2 SNAKE_POS_Y = WINDOW_HEIGHT / 2 # 定义方向常量 UP = 0 DOWN = 1 LEFT = 2 RIGHT = 3 # 游戏初始化 pygame.init() pygame.display.set_caption("贪吃蛇游戏") window = pygame.display.set_mode((WINDOW_WIDTH, WINDOW_HEIGHT)) clock = pygame.time.Clock() # 绘制蛇和食物的函数 def draw_snake(snake_list): for pos in snake_list: pygame.draw.rect(window, GREEN, [pos[0], pos[1], SNAKE_SIZE, SNAKE_SIZE]) def draw_food(food_pos): pygame.draw.rect(window, RED, [food_pos[0], food_pos[1], SNAKE_SIZE, SNAKE_SIZE]) # 食物的初始位置 food_pos = [random.randrange(0, WINDOW_WIDTH - SNAKE_SIZE), random.randrange(0, WINDOW_HEIGHT - SNAKE_SIZE)] # 蛇的初始位置和方向 snake_list = [[SNAKE_POS_X, SNAKE_POS_Y]] snake_direction = RIGHT # 游戏主循环 while True: # 处理事件 for event in pygame.event.get(): if event.type == pygame.QUIT: pygame.quit() exit() # 处理按键事件 if event.type == pygame.KEYDOWN: if event.key == pygame.K_UP: snake_direction = UP elif event.key == pygame.K_DOWN: snake_direction = DOWN elif event.key == pygame.K_LEFT: snake_direction = LEFT elif event.key == pygame.K_RIGHT: snake_direction = RIGHT # 移动蛇的头部 if snake_direction == UP: snake_head = [snake_list[0][0], snake_list[0][1] - SNAKE_SIZE] elif snake_direction == DOWN: snake_head = [snake_list[0][0], snake_list[0][1] + SNAKE_SIZE] elif snake_direction == LEFT: snake_head = [snake_list[0][0] - SNAKE_SIZE, snake_list[0][1]] elif snake_direction == RIGHT: snake_head = [snake_list[0][0] + SNAKE_SIZE, snake_list[0][1]] # 将蛇的头部加入列表 snake_list.insert(0, snake_head) # 判断是否吃到了食物 if snake_head == food_pos: food_pos = [random.randrange(0, WINDOW_WIDTH - SNAKE_SIZE), random.randrange(0, WINDOW_HEIGHT - SNAKE_SIZE)] else: snake_list.pop() # 绘制游戏界面 window.fill(BLACK) draw_snake(snake_list) draw_food(food_pos) pygame.display.update() # 控制游戏帧率为30帧 clock.tick(30) ``` 你可以将以上代码复制到记事本中,在本地使用Python运行。如果需要将游戏发布到Web上,可以使用Web框架(如Flask或Django)进行封装。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值