背景#
最近有个 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 プラットフォームに依存しているため、最初にコンピュータにインストールして設定する必要があります。
次に、私たちの要求はゲームが起動したときにすぐに通知を表示することではなく、特定の条件下でのみ表示する必要があるため、通知プラグインのメカニズムが必要です。1 つの方法は、サーバーを介してメッセージを配信することかもしれません。
最後に、このプラットフォームは主流のゲームのみをサポートしており、完全なサポートリストはここにあります https://www.overwolf.com/supported-games/ 。この問題は致命的です。
以上の理由から、このソリューションは採用できません。
goverlay#
Github Repo not found
The embedded github repo could not be found…
このライブラリは私たちのニーズに非常に合致しているようです。その原理は、最初に dll をゲームプロセスに注入し、次にウィンドウのレンダリング内容を dll に送信し、dll が DirectX のインターフェースをフックしてゲームウィンドウに内容をレンダリングすることです。
ここには実際に多くの詳細が関与しています。例えば、
- inject.exe と goverlay.dll 間のメッセージ通信は SendMessage を介して行われます https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-postmessagea
- dll はどのように注入されるのか
- DirectX をどのようにフックするのか
ここでは詳しく説明しません。
ネイティブアドオンの構築#
最初はどうすればよいかわからず、上の画像のようなエラーがずっと表示されました。
その後、cmake-js の公式設定を起点にして https://github.com/cmake-js/cmake-js#general 、プロジェクトのビルド設定を少しずつ追加し、直接サブディレクトリ electron-overlay に入ってビルドしたら通過しました。
ただし、goverlay リポジトリを再度ダウンロードして試したところ、エラーは表示されませんでした。とにかく、ここでいくつかの遠回りをしました。
デモを実行#
デモを実行し、csゲームを起動し、手順に従って注入しましたが、期待通りにはいかず、デモの内容はゲーム画面に表示されませんでした。
遅延#
まず、csゲームはデフォルトで全画面であるため、デモアプリにフォーカスを合わせると、ゲーム画面が隠れます。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#
それでもうまくいかず、偶然に issue を見ていると、csにはセキュリティモードがあることが言及されていました https://help.steampowered.com/en/faqs/view/09A0-4879-4353-EF95#whitelist セキュリティモードを解除するにはパラメータを追加する必要があります。
-allow_third_party_software
ようやく実行できました。。
実際のプロジェクトへの統合#
ネイティブアドオン#
最初の問題は、ネイティブアドオンを導入するとエラーが発生することです。
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 がコンソールを表示する#
デモテスト時にはコンソールウィンドウが表示されないのに、統合後にはコンソールウィンドウが表示されます。コンソールウィンドウが表示されると、全画面のゲームが中断されることになります。
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);
再構築すると、コンソールウィンドウは表示されなくなります。
不安定#
オーバーレイの内容にマウスをホバーすると、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);
});
したがって、ネイティブアドオン内のエラーハンドリングについては、後で再確認し、できるだけエラーがあれば表示されるようにする必要があります。
反チート#
PUBG ゲームをテストしてみると、ポップアップが表示されました。
BattlEye は反チートシステムで、ゲームとプレイヤーをハッカーやチート、その他の攻撃から保護します。したがって、BattlEye は私たちの dll の注入を阻止しました。この制限を回避できるかどうかは現時点では不明ですが、ポップアップには問題を引き起こさなければ無視できると書かれています。
その他#
C++ のコードを理解するために、少し C++ の知識を学びました。例えば、
いくつかの場所でファイルにログを出力しました。
簡単なアーキテクチャ図を描いて、理解を助けました。
まとめ#
このような感じです。他のゲームも多くテストする必要があり、特定のゲームに問題があるのは非常に普通のことです。