工作原理
text
test script (pwsh) Minecraft.Windows.exe + BeDD.dll
────────────────── ─────────────────────────────────────────────
Send-BeDDKey E ──JSON line──▶ \\.\pipe\BeDD
│ queue Event{WM_KEYDOWN 'E'}; PostMessage(hwnd, WM_LHI)
▼
subclassed window procedure (game thread)
WM_LHI → apply virtual key/cursor state
→ CallWindowProc(game's WndProc, WM_KEYDOWN, ...)
real WM_KEY*/WM_MOUSE*/WM_INPUT/WM_POINTER* → swallowed
WM_ACTIVATEAPP(0)/WM_KILLFOCUS/WM_ACTIVATE(inactive) → swallowed
◀──{"ok":true}── after the game thread dispatched it- 鍵盤、文字和指針位置走遊戲自己的窗口過程。 模塊子類化遊戲窗口。按鍵變成
WM_KEYDOWN/WM_KEYUP,文字變成WM_CHAR,指針變成WM_MOUSEMOVE加一個虛擬光標(被掛鉤的GetCursorPos返回的位置),在遊戲線程上按順序分發。 遊戲把它們當作真實輸入處理(MainGameCore::WndProc_PC::onKeyboardAction→HIDControllerGameCoreDesktop::onKeyDown), 所以聊天、鍵位映射和界面懸停都能用。遊戲線程處理完輸入後才回復,所以腳本的結果是確定的。 - 鼠標按鈕、滾輪和視角走虛擬 GameInput 鼠標。 基岩版用 GDK 的 GameInput API(
GameInputRedist.dll,接口 v2) 直接從設備讀取鼠標:WM_LBUTTONDOWN之類的窗口消息會被忽略;沒有模塊的話,你真實的鼠標仍會在後臺遊戲裡點擊、轉視角。 模塊掛鉤IGameInputReading::GetMouseState(以及GetKeyCount/GetKeyState):遊戲讀到的是虛擬按鈕、 相對位移(視角)、滾輪和絕對位置。設備報告輸入之前,GameInput 沒有該鼠標的讀數,所以在沒人碰過的電腦上, 遊戲的GetCurrentReading會從模塊拿到該設備的一個虛擬讀數。 - 遊戲從不搶前臺,也不會失去焦點。 失去焦點的消息被吞掉,所以遊戲不會暫停或鬆開按鍵。
Start-BeDDGame.ps1用SW_SHOWNOACTIVATE啟動 exe;被掛鉤的ShowWindow/SetWindowPos/SetActiveWindow/BringWindowToTop/SetForegroundWindow從不激活遊戲窗口,窗口一直保持在 Z 序最底部。user32 鉤子在load()裡就裝好了:遊戲在enable()之前就創建並顯示窗口。 模塊發送的"獲得焦點"消息會跳過默認的WM_ACTIVATE處理(掛鉤DefWindowProcW),否則它會設置焦點從而激活窗口: 從用戶前臺應用(終端、agent)啟動的遊戲被允許搶前臺。 - 你的真實輸入永遠到不了遊戲。 真實的客戶區輸入消息被吞掉,遊戲用來讀取或鎖定設備的 user32 調用看到的是虛擬狀態:
ClipCursor(空操作:你的光標從不被鎖住)、SetCursorPos(只移動虛擬光標)、GetCursorPos/GetPhysicalCursorPos、GetAsyncKeyState、GetKeyState、GetKeyboardState、GetForegroundWindow/GetActiveWindow/GetFocus(報告遊戲窗口)、GetRawInputData/GetRawInputBuffer(真實的原始輸入被丟棄)。 - 截圖用
PrintWindow(PW_RENDERFULLCONTENT),窗口被擋住時也能用。 - 管理窗口(
client/BeDDManager.ps1,用Add-Type編譯的 C# WinForms,每個登錄會話一個)通過 DWM 縮略圖顯示每個遊戲, 遊戲窗口在屏幕外時縮略圖仍是實時的;遊戲只顯示在這裡。Start-BeDDGame.ps1通過 WMI(在conhost --headless裡調用Win32_Process.Create) 啟動它,所以它不屬於調用者的進程樹或作業,啟動它的 agent 或測試框架退出後它仍然存在。模塊在load()裡連接它, 沒有管理窗口時就啟動一個(配置managerScript:pwsh -File <它> -Ensure);90 秒內沒有管理窗口連上、或者徹底失去連接的遊戲會自行關閉。 遊戲總是把窗口創建為屏幕外的工具窗口(掛鉤CreateWindowExW:遊戲在enable()之前不處理消息,所以在那之前無法從它的線程外修改窗口), 並一直留在那裡(SetWindowPos的移動被丟棄,一個監視器會把它放回去)。管理窗口把每個已連接的遊戲放進它自己的 kill-on-close 作業(連接之後才放,啟動過程不受影響;一分鐘內沒連接的遊戲之後也會加入):關閉或結束管理窗口就會關閉這些遊戲。
環境變量 BEDD_RETURN_FOCUS(一個窗口句柄,由 Start-BeDDGame.ps1 和墊片的 Start-Game 設置)告訴模塊:遊戲啟動時若仍激活了自己,
把前臺還給哪個窗口。