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

就是这个样子,其他游戏可能要多测测,个别游戏如果有问题也很正常。

加载中...
此文章数据所有权由区块链加密技术和智能合约保障仅归创作者所有。