shuilong

shuilong的博客

Create overlay in the game interface

Background#

Recently, there was an electron project that needed to display a reminder UI on the game interface while running a 3D game.

electron window alwaysTop#

Electron has an interface to control window always on top.

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

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

Generally, this is useful, but it doesn't work in the game interface, as it causes the current game interface to exit. However, it was later discovered that when the game mode is set to windowed instead of fullscreen, it can be displayed normally on the game interface. The game display modes will be introduced below.

Game Display Modes#

fullscreen

The above image shows the settings interface for CS, where we can set different display modes. What is the difference between fullscreen and windowed?

In windowed mode, this application shares available desktop screen space with all running applications. In fullscreen mode, the window in which the application runs will cover the entire desktop and hide all running applications (including your development environment).

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

In simple terms, if the game is set to windowed mode, other windows can also be displayed. The final screen will show multiple windows combined together, while in fullscreen mode, only the game interface will be displayed, and other application windows will not be shown.

Understanding this distinction, we can naturally know that the electron window cannot be displayed on top, which is normal.

Overwolf#

https://www.overwolf.com/

Overwolf is a development platform for creating in-game applications, which is very suitable for our scenario. The basic architecture is:

  1. Start the Overwolf program locally
  2. Load the Overwolf plugin you developed
  3. When the game starts, the Overwolf plugin can start and display on the game interface

To develop plugins locally, you must first apply and explain the purpose of the plugin. They will review and approve it before you can develop and load debug locally. After several rounds of email communication, it was finally approved.

微信截图_20231021230953

When running, it looks like this, and it can indeed be displayed on top in the game interface.

However, there are actually three issues with this solution.

First, of course, this plugin relies on the Overwolf platform, so it must be installed and configured on the computer first.

Second, our requirement is not to display the prompt as soon as the game starts, but to display it only under certain conditions, which requires a notification plugin mechanism. One way might be to relay messages through a server.

Finally, this platform only supports mainstream games, and the complete support list is here https://www.overwolf.com/supported-games/, which is quite a critical issue.

In summary, this solution cannot be adopted.

Goverlay#

This library seems to meet our needs. Its principle is to first inject a DLL into the game process, and then send the window's rendering content to the DLL, which is responsible for rendering the content in the game window by hooking DirectX interfaces.

微信截图_20231021214205

This actually involves many details, such as

  1. The message communication between inject.exe and goverlay.dll is passed through SendMessage https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-postmessagea
  2. How the DLL is injected
  3. How to hook DirectX

I won't elaborate on this here.

Build Native Addon#

fail

At first, I didn't know how to do it and kept getting errors like the one in the image above.

Later, I started from the official configuration of cmake-js https://github.com/cmake-js/cmake-js#general, gradually added back the project's build configuration, and then directly entered the subdirectory electron-overlay to build, which succeeded.

However, after re-downloading the goverlay repository and trying it, I found that there were no errors, so I took some detours here.

Run Demo#

I ran the demo, started the CS game, followed the steps to inject, but the demo content did not appear in the game interface as expected.

Delay#

First, since CS is set to fullscreen by default, if we focus on the demo application, the game interface will be hidden. When inputting Strike, we find that there is no window titled Strike. The specific reason is unknown, but if we delay the injection, that is, click the inject button and then bring up the CS game interface, and then inject, we can find the CS window.

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);
})

Path#

Then, after being able to inject, we find the following logs in the console:

injecting {"windowId":22155402,"processId":14492,"threadId":50188,"title":"Counter-Strike 2"}

And then nothing.

We need to delve into the relevant code to print out the injection results.

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);
}

We find logs like this:

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
}

This indicates that we need to ensure that injector_helper.x64.exe and n_overlay.x64.dll are present in the electron-overlay directory.

However, the official documentation did not mention this at https://github.com/hiitiger/goverlay#run-demo, but it was mentioned at https://github.com/hiitiger/goverlay#inject-a-specific-game. In any case, it was quite confusing the first time I encountered it.

DLL Signing#

Still not working, and I happened to search for issues related to startup failures and found this issue https://github.com/hiitiger/goverlay/issues/70#issuecomment-982464487. The author replied that the DLL might need to be signed to inject properly. Later, I also found this hint in the later section of the homepage https://github.com/hiitiger/goverlay#note.

Generally, when we execute executable files downloaded from the internet, if they are not signed, the Windows system will prompt that the file is unsafe, which is one of the purposes of signing.

I found the signing process online https://www.digicert.com/kb/code-signing/ev-authenticode-certificates.htm, but the prerequisite is that the certificate needs to be purchased, and the price is quite high, https://order.digicert.com/step1/code_signing. It seems that the threshold is quite high.

I accidentally discovered another approach: creating a code signing certificate myself, manually setting the local computer to trust this certificate, and then signing the DLL with this certificate. For related links, see https://blog.csdn.net/dounick/article/details/105643285.

Trust Mode#

Still not working... I also happened to see in the issues that CS has a safe mode, https://help.steampowered.com/en/faqs/view/09A0-4879-4353-EF95#whitelist, which requires adding parameters to remove safe mode.

-allow_third_party_software

Finally, it ran...

Actual Integration into the Project#

Native Addon#

The first issue is that introducing the native addon throws an error.

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")
            }
    }
}

The reason is that the main process files will also go through webpack packaging now, so it needs to be configured to handle the .node suffix.

injector.exe Pops Up Console#

Clearly, the demo test does not pop up a console window, but after integration, the console window pops up. If the console window pops up, it will inevitably interrupt the current fullscreen game.

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);

After rebuilding, the console window no longer pops up.

Unstable#

I found that hovering the mouse over the overlay content causes the electron application to exit, which is also a serious issue.

Later, I discovered that when copying the goverlay code, I did not include the relevant modules.

It is here https://github.com/hiitiger/goverlay/blob/master/client/src/main/electron/app-entry.ts#L116. I only copied the code but forgot to import BrowserWindow at the top of the file.

The problem is that the electron application exits directly without any error printed to the console, and I added the following code in my code:

process.on("uncaughtException", (err) => {
    console.log("perf", err);
});

So, regarding error handling in the native addon, I still need to look into it later to ensure that any errors are exposed.

Anti-Cheat#

I tried testing with the PUBG game and found a pop-up.

微信截图_20231016191554

BattlEye is an anti-cheat system that protects our games and players from hackers, cheats, and other forms of attacks. Therefore, BattlEye blocked our DLL injection. It is currently unclear whether this restriction can be bypassed, although the pop-up states that it can be ignored if it does not cause issues.

Others#

To understand C++ code, I also specifically learned some C++ knowledge, such as

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

微信截图_20231022011549

Printing logs to files in some places

c++

Drawing some simple architecture diagrams to help understand.

Summary#

微信图片_20231022012314

That's how it is. Other games may need more testing, and it's normal for individual games to have issues.

Loading...
Ownership of this post data is guaranteed by blockchain and smart contracts to the creator alone.