shuilong

shuilong的博客

在遊戲介面創建overlay

背景#

最近有個 electron 項目,需要在 3D 遊戲運行時,在遊戲界面上能出現提醒 UI。

electron window alwaysTop#

electron 是有控制窗口置頂的接口的

const payWindow = new BrowserWindow({
    ...
});

payWindow.setAlwaysOnTop(true, "screen-saver")
payWindow.setVisibleOnAllWorkspaces(true);

一般情況下確實是有用的,但在遊戲界面裡不行,會使當前遊戲界面退出。但後來發現,在遊戲模式設置非全屏模式時,是可以正常在遊戲界面上展示的,下面會介紹遊戲顯示模式。

遊戲顯示模式#

fullscreen

上圖是 cs的設置界面,我們可以設置不同的顯示模式,全屏跟窗口有什麼區別呢?

在窗口模式中,此應用程序與所有運行的應用共享可用的桌面屏幕空間。在全屏模式中,應用程序運行於的窗口將覆蓋整個桌面,並隱藏所有運行的應用(包括你的開發環境)。

https://learn.microsoft.com/zh-cn/windows/uwp/graphics-concepts/windowed-vs--full-screen-mode

簡單理解就是,遊戲設置成窗口模式的話,那麼其他窗口也是可以顯示的,最終屏幕上呈現的就是多個窗口合成到一起再顯示,而全屏模式的話,就只會顯示遊戲的界面,其他應用的窗口都不會再顯示了。

理解了這個區別,我們就能自然知道上面 electron 窗口沒法置地顯示,是正常的。

overwolf#

https://www.overwolf.com/

overwolf 是一個用於創建遊戲內應用的開發平台,很適合我們的場景。基本架構是。

  1. 在本地啟動 Overwolf 程序
  2. 加載自己開發的 Overwolf 插件
  3. 遊戲啟動時 Overwolf 插件就能啟動並在遊戲界面上顯示

想要在本地開發插件,還必須要先申請下,說明開發插件的用途,他們審核通過才可以在本地開發和加載調試。經過了好幾輪郵件的溝通,終於批准了。

微信截圖_20231021230953

運行起來是這樣,確實是可以在遊戲界面裡置頂顯示。

但採用這個方案其實有 3 個問題

首先當然是這個插件是依託 Overwolf 平台,所以必須要先在電腦上安裝和配置好。

其次我們的需求不是說遊戲一啟動就顯示提示,而是在某個條件下才需要顯示,這就需要有個通知插件的機制。一種方式可能是通過伺服器中轉,下發消息。

最後是,這個平台只支持主流遊戲,完整的支持列表在這裡 https://www.overwolf.com/supported-games/ ,這個問題挺致命的。

綜上所述,這個方案沒法採用。

goverlay#

DirectX hook and game overlay solution for Electron, Qt and CEF, just like discord/steam game overlay,inject any app to overlay in your game

這個庫看起來很符合我們的需求,它的原理是先注入 dll 到遊戲進程裡,然後傳送窗口的渲染內容給 dll,dll 負責把內容通過 hook directX 的接口在遊戲窗口裡渲染出來。

微信截圖_20231021214205

這裡其實涉及到很多細節,比如

  1. inject.exe 和 goverlay.dll 之間的消息通信是通過 SendMessage https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-postmessagea 傳遞
  2. dll 是怎麼注入的
  3. 怎麼 hook directX

這裡不展開了。

構建 native addon#

fail

一開始不知道怎麼弄的,一直報上圖裡這樣的錯。

後來照著 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.exen_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 遊戲測試,發現會出來個彈窗。

微信截圖_20231016191554

BattlEye 是一個反作弊系統,保護我們的遊戲和玩家免受黑客、作弊和其他形式的攻擊。所以 BattlEye 阻止了我們的 dll 的注入。目前暫不清楚能否繞過這個限制,雖然彈窗裡說如果不引發問題,是可以被忽略的。

其他#

為了讀懂 c++ 的代碼,還專門去學習了點 c++ 的知識,比如

  1. https://www.youtube.com/watch?v=ZzaPdXTrSb8
  2. https://www.youtube.com/watch?v=vLnPwxZdW4Y

微信截圖_20231022011549

在一些地方打印日誌到文件裡

c++

畫一些簡單的架構圖,幫助理解。

總結#

微信圖片_20231022012314

就是這個樣子,其他遊戲可能要多測測,個別遊戲如果有問題也很正常。

載入中......
此文章數據所有權由區塊鏈加密技術和智能合約保障僅歸創作者所有。