背景#
最近有個 electron 項目,需要在 3D 遊戲運行時,在遊戲界面上能出現提醒 UI。
electron window alwaysTop#
electron 是有控制窗口置頂的接口的
const payWindow = new BrowserWindow({
...
});
payWindow.setAlwaysOnTop(true, "screen-saver")
payWindow.setVisibleOnAllWorkspaces(true);
一般情況下確實是有用的,但在遊戲界面裡不行,會使當前遊戲界面退出。但後來發現,在遊戲模式設置非全屏模式時,是可以正常在遊戲界面上展示的,下面會介紹遊戲顯示模式。
遊戲顯示模式#
上圖是 cs的設置界面,我們可以設置不同的顯示模式,全屏跟窗口有什麼區別呢?
在窗口模式中,此應用程序與所有運行的應用共享可用的桌面屏幕空間。在全屏模式中,應用程序運行於的窗口將覆蓋整個桌面,並隱藏所有運行的應用(包括你的開發環境)。
https://learn.microsoft.com/zh-cn/windows/uwp/graphics-concepts/windowed-vs--full-screen-mode
簡單理解就是,遊戲設置成窗口模式的話,那麼其他窗口也是可以顯示的,最終屏幕上呈現的就是多個窗口合成到一起再顯示,而全屏模式的話,就只會顯示遊戲的界面,其他應用的窗口都不會再顯示了。
理解了這個區別,我們就能自然知道上面 electron 窗口沒法置地顯示,是正常的。
overwolf#
overwolf 是一個用於創建遊戲內應用的開發平台,很適合我們的場景。基本架構是。
- 在本地啟動 Overwolf 程序
- 加載自己開發的 Overwolf 插件
- 遊戲啟動時 Overwolf 插件就能啟動並在遊戲界面上顯示
想要在本地開發插件,還必須要先申請下,說明開發插件的用途,他們審核通過才可以在本地開發和加載調試。經過了好幾輪郵件的溝通,終於批准了。
運行起來是這樣,確實是可以在遊戲界面裡置頂顯示。
但採用這個方案其實有 3 個問題
首先當然是這個插件是依託 Overwolf 平台,所以必須要先在電腦上安裝和配置好。
其次我們的需求不是說遊戲一啟動就顯示提示,而是在某個條件下才需要顯示,這就需要有個通知插件的機制。一種方式可能是通過伺服器中轉,下發消息。
最後是,這個平台只支持主流遊戲,完整的支持列表在這裡 https://www.overwolf.com/supported-games/ ,這個問題挺致命的。
綜上所述,這個方案沒法採用。
goverlay#
Github Repo not found
The embedded github repo could not be found…
這個庫看起來很符合我們的需求,它的原理是先注入 dll 到遊戲進程裡,然後傳送窗口的渲染內容給 dll,dll 負責把內容通過 hook directX 的接口在遊戲窗口裡渲染出來。
這裡其實涉及到很多細節,比如
- inject.exe 和 goverlay.dll 之間的消息通信是通過 SendMessage https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-postmessagea 傳遞
- dll 是怎麼注入的
- 怎麼 hook directX
這裡不展開了。
構建 native addon#
一開始不知道怎麼弄的,一直報上圖裡這樣的錯。
後來照著 cmake-js 官方的配置為起點 https://github.com/cmake-js/cmake-js#general ,再一點點加回項目的構建配置,然後直接進入子目錄 electron-overlay 去構建才通過。
不過再重新下載了 goverlay 倉庫試了下,發現就沒有報錯了,反正這裡走了些彎路。
跑 demo#
跑了下 demo,啟動 cs遊戲,跟著步驟注入,並沒有如預期那樣,demo 的內容沒有在遊戲界面裡呈現。
延遲#
首先是,由於 cs遊戲默認是全屏,如果我們聚焦到 demo 應用裡,這時遊戲界面是會隱藏的,輸入 Strike,我們會發現找不到窗口標題是 Strike 的窗口。具體原因未知,但如果我們延遲注入,也就是點擊注入按鈕,然後把 cs的遊戲界面喚出來,這時再注入,這時就能找到 cs的窗口。
https://github.com/hiitiger/goverlay/blob/master/client/src/renderer/main.ts#L12
// before
injectButton.addEventListener("click", () => {
const title = titleInput.value
ipcRenderer.send("inject", title)
})
// after
injectButton.addEventListener("click", () => {
const title = titleInput.value
setTimeout(() => {
ipcRenderer.send("inject", title)
}, 5000);
})
路徑#
然後能注入了,會在控制台發現下面這樣的日誌
injecting {"windowId":22155402,"processId":14492,"threadId":50188,"title":"Counter-Strike 2"}
然後沒有了。
我們需要深入到相關代碼,把注入結果打印出來。
https://github.com/hiitiger/goverlay/blob/master/client/src/main/electron/app-entry.ts#L516
// before
if (window.title.indexOf(arg) !== -1) {
console.log(`--------------------\n injecting ${JSON.stringify(window)}`);
this.Overlay.injectProcess(window);
}
// after
if (window.title.indexOf(arg) !== -1) {
console.log(`--------------------\n injecting ${JSON.stringify(window)}`);
const result = this.Overlay.injectProcess(window);
console.log(result);
}
發現這樣的日誌
injecting {"windowId":22155402,"processId":14492,"threadId":50188,"title":"Counter-Strike 2"}
{
injectHelper: '\\\\?\\C:\\Users\\yun77\\waibao\\goverlay-master\\electron-overlay\\injector_helper.x64.exe',
injectDll: '\\\\?\\C:\\Users\\yun77\\waibao\\goverlay-master\\electron-overlay\\n_overlay.x64.dll',
injectSucceed: false
}
說明需要確保 electron-overlay 目錄下有injector_helper.x64.exe
和n_overlay.x64.dll
但官方文檔裡在 https://github.com/hiitiger/goverlay#run-demo 這裡沒提,不過在https://github.com/hiitiger/goverlay#inject-a-specific-game 提了,反正第一次接觸時感覺挺困惑的。
DLL 簽名#
還是不行,然後也是偶然搜那種啟動不起來的 issue,發現了這樣的 issue https://github.com/hiitiger/goverlay/issues/70#issuecomment-982464487 作者回覆了說要可能對 dll 做簽名才能正常注入,後來在首頁比較靠後的區域也發現了這樣的提示 https://github.com/hiitiger/goverlay#note
一般我們從網上下載的可執行文件執行的話,如果沒有簽名,windows 系統會提示說文件不安全,這是簽名的其中一個用處。
網上找了下簽名相關的流程 https://www.digicert.com/kb/code-signing/ev-authenticode-certificates.htm ,前提是證書是需要購買的,而且價格不菲,https://order.digicert.com/step1/code_signing 。 看起來門檻還是蠻高的。
偶然發現還有種思路是,自己創建代碼簽名證書,手動設置本地電腦信任這個證書,再用這個證書給 dll 簽名。相關鏈接可以參見 https://blog.csdn.net/dounick/article/details/105643285。
trust mode#
還是不行。。也是偶爾在翻看 issues 時,有提到 cs有安全模式,https://help.steampowered.com/en/faqs/view/09A0-4879-4353-EF95#whitelist 需要加參數才可以去掉安全模式。
-allow_third_party_software
終於跑起來了。。
實際集成到項目#
native addon#
第一個問題就是引入 native addon 會報錯。
pluginOptions: {
'style-resources-loader': {
preProcessor: 'less',
patterns: [path.join(__dirname, "./src/assets/less/index.less")]
},
electronBuilder:{
chainWebpackMainProcess(config) {
config.module
.rule("node")
.test(/\.node$/)
.use("native-ext-loader")
.loader("native-ext-loader")
}
}
}
原因是現在主進程的文件現在也會經過 webpack 打包,所以需要配置下對.node 後綴的處理。
injector.exe 會彈出控制台#
明明 demo 測試時不會彈出控制台窗口,但集成之後會彈出控制台窗口。如果彈出控制台窗口,勢必會導致當前全屏的遊戲會被打斷。
https://github.com/hiitiger/goverlay/blob/master/electron-overlay/src/utils/win-utils.h#L105
// before
BOOL ret = CreateProcessW(path.c_str(), (LPWSTR)(cmdLine.c_str()), NULL, NULL, FALSE, 0, NULL, dir.c_str(), &StartupInfo, &ProcInfo);
// after
BOOL ret = CreateProcessW(path.c_str(), (LPWSTR)(cmdLine.c_str()), NULL, NULL, FALSE, CREATE_NO_WINDOW, NULL, dir.c_str(), &StartupInfo, &ProcInfo);
重新構建下,就不再彈出控制台窗口了。
不穩定#
發現鼠標 hover 到 overlay 內容會導致 electron 應用退出,這也是個嚴重的問題。
後來發現原來是拷貝 goverlay 代碼時沒有把相關的模塊也引入進來。
就是這裡 https://github.com/hiitiger/goverlay/blob/master/client/src/main/electron/app-entry.ts#L116 ,只拷貝了代碼,但忘了把BrowserWindow
在文件頂部引入。。
問題是,electron 應用就直接退出了,完全沒有一點錯誤打印到控制台,而且我在代碼裡是加了如下的代碼的
process.on("uncaughtException", (err) => {
console.log("perf", err);
});
所以針對 native addon 裡面的錯誤處理後面還是得再看下,盡量能讓有報錯就暴露。
反作弊#
嘗試使用 PUBG 遊戲測試,發現會出來個彈窗。
BattlEye 是一個反作弊系統,保護我們的遊戲和玩家免受黑客、作弊和其他形式的攻擊。所以 BattlEye 阻止了我們的 dll 的注入。目前暫不清楚能否繞過這個限制,雖然彈窗裡說如果不引發問題,是可以被忽略的。
其他#
為了讀懂 c++ 的代碼,還專門去學習了點 c++ 的知識,比如
在一些地方打印日誌到文件裡
畫一些簡單的架構圖,幫助理解。
總結#
就是這個樣子,其他遊戲可能要多測測,個別遊戲如果有問題也很正常。