diff --git a/assets/insight_fixed_structure.txt b/assets/insight_fixed_structure.txt index 88ca8ce..12ac12c 100644 --- a/assets/insight_fixed_structure.txt +++ b/assets/insight_fixed_structure.txt @@ -10,4 +10,4 @@ [DIRECTORY] - L2_Facts: ../memory/global_mem.txt (Method: 按 TOPIC 检索索引 -> file_read 对应条目) -- L3_SOPs: ../memory/ (Method: ls 查看文件列表 -> 读取具体 .md/.py) +- L3_SOPs: ../memory/ (Method: os.listdir查看文件列表 -> 读取具体 .md/.py) diff --git a/ga.py b/ga.py index 2db9bd5..f172a05 100644 --- a/ga.py +++ b/ga.py @@ -193,15 +193,15 @@ def file_patch(path: str, old_content: str, new_content: str): with open(path, 'r', encoding='utf-8') as f: full_text = f.read() # 检查唯一性 count = full_text.count(old_content) - if count == 0: return {"status": "error", "msg": "未找到匹配的旧文本块,请检查空格、缩进和换行是否完全一致。"} - if count > 1: return {"status": "error", "msg": f"找到 {count} 处匹配,请提供更长的旧文本块以确保唯一性。"} + if count == 0: return {"status": "error", "msg": "未找到匹配的旧文本块,建议:先用 file_read 确认当前内容,再分小段进行 patch。若多次失败则询问用户,严禁自行使用 overwrite 或代码替换。"} + if count > 1: return {"status": "error", "msg": f"找到 {count} 处匹配,无法确定唯一位置。请提供更长、更具体的旧文本块以确保唯一性。建议:包含上下文行来增强特征,或分小段逐个修改。"} updated_text = full_text.replace(old_content, new_content) with open(path, 'w', encoding='utf-8') as f: f.write(updated_text) return {"status": "success", "msg": "文件局部修改成功"} except Exception as e: return {"status": "error", "msg": str(e)} -def file_read(path, start=1, keyword=None, count=100, show_linenos=True): +def file_read(path, start=1, keyword=None, count=200, show_linenos=True): L_MAX = max(100, 1024000//count); TAG = " ... [TRUNCATED]" try: with open(path, 'r', encoding='utf-8', errors='replace') as f: @@ -474,7 +474,7 @@ def get_global_memory(): prompt += f'But prefer use relative paths (./ = cwd) to locate.\n' prompt += 'MEM_RULE: Insight is the index. Sync Insight whenever Facts change. For details, read Facts.\n' prompt += "EXT: ../memory/ may contain other task-specific memories.\n" - prompt += structure + '\nglobal_mem_insight.txt:\n' + prompt += structure + '\n../memory/global_mem_insight.txt:\n' prompt += insight + "\n" except FileNotFoundError: pass return prompt \ No newline at end of file diff --git a/memory/ljqCtrl.py b/memory/ljqCtrl.py new file mode 100644 index 0000000..d12480f --- /dev/null +++ b/memory/ljqCtrl.py @@ -0,0 +1,107 @@ +# coding=utf-8 +""" +ljqCtrl Quick Reference: +- dpi_scale: float (Physical = Logical / dpi_scale) +- Click(x, y=None): Click logical/physical coordinates +- SetCursorPos(z): Move mouse to logical coordinate z=(x, y) +- Press(cmd, staytime=0): Keyboard shortcuts (e.g. 'ctrl+c') +- FindBlock(fn, wrect=None, threshold=0.8) -> (obj_center, is_found) +- MouseDClick(staytime=0.05), MouseClick(staytime=0.05) +""" + +import os, sys, time, random, math +import win32api, win32con +import numpy as np + +dpi_scale = 1 + +try: + from PIL import ImageGrab, Image, ImageEnhance, ImageFilter, ImageDraw + import cv2 +except: pass + +scr = ImageGrab.grab() +swidth, sheight = scr.size +print('Screen width & height:', swidth, sheight) +cwidth, cheight = map(win32api.GetSystemMetrics, [win32con.SM_CXSCREEN, win32con.SM_CYSCREEN]) +dpi_scale = cwidth / swidth +print('dpi_scale:', dpi_scale) + +time.process_time() + +def MouseDown(): win32api.mouse_event(win32con.MOUSEEVENTF_LEFTDOWN,0,0) +def MouseUp(): win32api.mouse_event(win32con.MOUSEEVENTF_LEFTUP,0,0) + +def MouseClick(staytime=0.05): + MouseDown(); time.sleep(staytime) + MouseUp(); time.sleep(0.05) + +def MouseDClick(staytime=0.05): + MouseDown(); MouseUp() + MouseDown(); MouseUp() + time.sleep(0.05) + +def SetCursorPos(z): + z = tuple(map(lambda v:int(v*dpi_scale), z)) + win32api.SetCursorPos(z) + time.sleep(0.05) + +def Click(x, y=None): + if type(x) is type(tuple()): x, y = int(x[0]), int(x[1]) + SetCursorPos( (x, y) ) + MouseClick() + +def Press(cmd, staytime=0): + cmds = cmd.lower().split('+') + for z in cmds: + win32api.keybd_event(VK_CODE[z], 0, 0, 0) + time.sleep(staytime) + for z in reversed(cmds): + time.sleep(staytime) + win32api.keybd_event(VK_CODE[z], 0, win32con.KEYEVENTF_KEYUP, 0) + +VK_CODE = {'backspace':0x08, 'tab':0x09, 'clear':0x0C, 'enter':0x0D, 'shift':0x10, 'ctrl':0x11, 'alt':0x12, 'pause':0x13, 'caps_lock':0x14, 'esc':0x1B, 'space':0x20, 'page_up':0x21, 'page_down':0x22, 'end':0x23, 'home':0x24, 'left_arrow':0x25, 'up_arrow':0x26, 'right_arrow':0x27, 'down_arrow':0x28, 'select':0x29, 'print':0x2A, 'execute':0x2B, 'print_screen':0x2C, 'ins':0x2D, 'del':0x2E, 'help':0x2F, '0':0x30, '1':0x31, '2':0x32, '3':0x33, '4':0x34, '5':0x35, '6':0x36, '7':0x37, '8':0x38, '9':0x39, 'a':0x41, 'b':0x42, 'c':0x43, 'd':0x44, 'e':0x45, 'f':0x46, 'g':0x47, 'h':0x48, 'i':0x49, 'j':0x4A, 'k':0x4B, 'l':0x4C, 'm':0x4D, 'n':0x4E, 'o':0x4F, 'p':0x50, 'q':0x51, 'r':0x52, 's':0x53, 't':0x54, 'u':0x55, 'v':0x56, 'w':0x57, 'x':0x58, 'y':0x59, 'z':0x5A, 'numpad_0':0x60, 'numpad_1':0x61, 'numpad_2':0x62, 'numpad_3':0x63, 'numpad_4':0x64, 'numpad_5':0x65, 'numpad_6':0x66, 'numpad_7':0x67, 'numpad_8':0x68, 'numpad_9':0x69, 'multiply_key':0x6A, 'add_key':0x6B, 'separator_key':0x6C, 'subtract_key':0x6D, 'decimal_key':0x6E, 'divide_key':0x6F, 'F1':0x70, 'F2':0x71, 'F3':0x72, 'F4':0x73, 'F5':0x74, 'F6':0x75, 'F7':0x76, 'F8':0x77, 'F9':0x78, 'F10':0x79, 'F11':0x7A, 'F12':0x7B, 'F13':0x7C, 'F14':0x7D, 'F15':0x7E, 'F16':0x7F, 'F17':0x80, 'F18':0x81, 'F19':0x82, 'F20':0x83, 'F21':0x84, 'F22':0x85, 'F23':0x86, 'F24':0x87, 'num_lock':0x90, 'scroll_lock':0x91, 'left_shift':0xA0, 'right_shift ':0xA1, 'left_control':0xA2, 'right_control':0xA3, 'left_menu':0xA4, 'right_menu':0xA5, 'browser_back':0xA6, 'browser_forward':0xA7, 'browser_refresh':0xA8, 'browser_stop':0xA9, 'browser_search':0xAA, 'browser_favorites':0xAB, 'browser_start_and_home':0xAC, 'volume_mute':0xAD, 'volume_Down':0xAE, 'volume_up':0xAF, 'next_track':0xB0, 'previous_track':0xB1, 'stop_media':0xB2, 'play/pause_media':0xB3, 'start_mail':0xB4, 'select_media':0xB5, 'start_application_1':0xB6, 'start_application_2':0xB7, 'attn_key':0xF6, 'crsel_key':0xF7, 'exsel_key':0xF8, 'play_key':0xFA, 'zoom_key':0xFB, 'clear_key':0xFE, '+':0xBB, ',':0xBC, '-':0xBD, '.':0xBE, '/':0xBF, '`':0xC0, ';':0xBA, '[':0xDB, '\\':0xDC, ']':0xDD, "'":0xDE, '`':0xC0} +VK_CODE = {k.lower():v for k,v in VK_CODE.items()} + +def imshow(mt, sec=0): + cv2.imshow('cc', mt) + cv2.waitKey(sec) + +def GetWRect(sr): + num = int(sr[-1]) + l, u, r, b = 0, 0, swidth, sheight + if 'left' in sr: r = swidth // num + if 'right' in sr: l = swidth * (num-1) // num + if 'top' in sr: b = sheight // num + if 'bottom' in sr: u = sheight * (num-1) // num + return [l, u, r, b] + +def FindBlock(fn, wrect=None, verbose=0, threshold=0.8): + tic = time.process_time() + if wrect is not None and isinstance(wrect, Image.Image): + scr, wrect = wrect, None + else: + if isinstance(wrect, str): wrect = GetWRect(wrect) + scr = ImageGrab.grab(wrect) + blc = Image.open(fn) if isinstance(fn, str) else fn + T = cv2.cvtColor(np.array(blc), cv2.COLOR_RGB2BGR) + B = cv2.cvtColor(np.array(scr), cv2.COLOR_RGB2BGR) + tsh, tsw = T.shape[:2] + if verbose: print('T.shape:', T.shape, '\t', 'B.shape:', B.shape) + res = cv2.matchTemplate(B, T, cv2.TM_CCOEFF_NORMED) + min_val, max_val, min_loc, max_loc = cv2.minMaxLoc(res) + oj, oi = max_loc + if wrect is None: wrect = [0, 0, scr.size[0], scr.size[1]] + obj = (oj + wrect[0] + tsw//2, oi + wrect[1] + tsh//2) + if verbose: + print(f'Max match: {max_val:.4f} at ({oj}, {oi}) cost: {time.process_time() - tic:.3f}s') + sscr = scr.crop([oj, oi, oj+tsw, oi+tsh]) + sscr.show() + return obj, max_val > threshold + +if __name__ == '__main__': + #time.sleep(3) + #SetCursorPos( (1640, 131) ) + #MouseClick() + #print(FindBlock('z:/z.png', [1638, 214, 5838, 414], verbose=1)) + print('completed %.3f' % time.process_time()) \ No newline at end of file diff --git a/memory/ljqCtrl_sop.md b/memory/ljqCtrl_sop.md new file mode 100644 index 0000000..727c80d --- /dev/null +++ b/memory/ljqCtrl_sop.md @@ -0,0 +1,39 @@ +# ljqCtrl 使用与坐标转换 SOP + +## 0. API 快速参考 (Signatures) +- `ljqCtrl.dpi_scale`: float (缩放系数 = 逻辑宽度 / 物理宽度) +- `ljqCtrl.SetCursorPos(z)`: 移动鼠标到逻辑坐标 z=(x, y) +- `ljqCtrl.Click(x, y=None)`: 模拟点击。支持 `Click((x, y))` 或 `Click(x, y)` +- `ljqCtrl.Press(cmd, staytime=0)`: 模拟按键。如 `Press('ctrl+c')` +- `ljqCtrl.FindBlock(fn, wrect=None, threshold=0.8)`: 找图。返回 `((center_x, center_y), is_found)` +- `ljqCtrl.MouseDClick(staytime=0.05)`: 鼠标双击 + +## 1. 环境载入 +必须先将 `../memory` 加入路径,才能导入工具模块: +```python +import sys, os, pygetwindow as gw +sys.path.append("../memory") +import ljqCtrl +``` + +## 2. 核心:High-DPI 物理坐标换算 +`ljqCtrl` 的 `Click/MoveTo` 接口接收的是**物理像素坐标**。 +当使用 `pygetwindow` 等工具获取窗口位置(逻辑坐标)时,必须除以缩放系数。 + +- **换算公式**:`物理坐标 = 逻辑坐标 / ljqCtrl.dpi_scale` +- **注意**:3840 (4K) 仅为当前开发机示例,实际物理边界由系统环境决定,代码应始终通过 `dpi_scale` 动态计算。 + +## 3. 窗口操作与点击流程 +1. **激活窗口**:使用 `gw.getWindowsWithTitle('标题')` 获取窗口,执行 `restore()` 和 `activate()`。 +2. **坐标计算**: +```python +win = gw.getWindowsWithTitle('微信')[0] +# 计算窗口内某个点的逻辑坐标 (lx, ly) +# 转换为物理坐标并点击 +px, py = lx / ljqCtrl.dpi_scale, ly / ljqCtrl.dpi_scale +ljqCtrl.Click(px, py) +``` + +## 4. 避坑指南 +- **物理验证**:模拟操作前必须确保窗口已通过 `activate()` 置于前台。 +- **偏移量**:所有的相对偏移像素值(如“向右移动 10 像素”)同样需要除以 `dpi_scale`。 \ No newline at end of file diff --git a/memory/mem_scanner.py b/memory/mem_scanner.py index f1e1836..7ca5501 100644 --- a/memory/mem_scanner.py +++ b/memory/mem_scanner.py @@ -91,9 +91,12 @@ def scan_memory(pid, pattern, context_size=256, mode='auto', llm_mode=False): matched_data = inst.instances[0].matched_data base = mbi.BaseAddress if mbi.BaseAddress else 0 if llm_mode: - results.append(format_llm_context(data, offset, base)) + results.append(format_llm_context(data, offset, base, length=context_size)) else: - results.append(f"Addr: {hex(base+offset)}\nHex: {data[max(0,offset-16):offset+32].hex()}") + # Expand context based on context_size to capture full KEY+SALT + start = max(0, offset - context_size) + end = min(len(data), offset + len(matched_data) + context_size) + results.append(f"Addr: {hex(base+offset)}\nHex: {data[start:end].hex()}") # Update address using the region size next_addr = (mbi.BaseAddress if mbi.BaseAddress else 0) + mbi.RegionSize diff --git a/memory/web_setup_sop.md b/memory/web_setup_sop.md new file mode 100644 index 0000000..b0cfa50 --- /dev/null +++ b/memory/web_setup_sop.md @@ -0,0 +1,18 @@ +# Web 工具链初始化执行 SOP (精简版) + +若web_scan和web_execute_js已测试可用,无需进行此sop。 +此sop仅供初始安装时,code_run可用但web工具第一次配置使用。 + +## 目标 +在仅具备系统级权限时,引导用户建立 Web 交互能力。 + +## 阶段一:引导安装 (Action: code_run) +1. **启动导航**:使用 PowerShell 启动 Chrome 并直达测试版页面: + `start chrome "https://chromewebstore.google.com/detail/%E7%AF%A1%E6%94%B9%E7%8C%B4%E6%B5%8B%E8%AF%95%E7%89%88/gcalenpjmijncebpfijmoaglllgpjagf"` + +## 阶段二:脚本分发 (Action: file_read + code_run) +1. **资源提取与写入**:使用 Python 读取 `../assets/ljq_web_driver.user.js` 并通过 `pyperclip` 写入系统剪贴板。 +2. **用户交互指令**:通知用户“脚本已入剪贴板”,请求用户在插件中完成【新建-全选-粘贴-保存】操作。 + +## 阶段三:环境确认 (Action: code_run) +1. **切换驱动**:尝试调用 `web_scan` 或注入 JS 进行心跳检测,确认 `ljq_web_driver.user.js` 是否已在目标域生效。