Playing D2R on the OneXPlayer 2 Pro with Arch Linux
D2R has already been out for four years. I used to play it on the Nintendo Switch, but recently I noticed a discount on the PC version, so I bought a copy for PC. Thanks to Wine, Valve, and the Steam Deck, playing games on Linux has become much easier. There are many articles about how to play D2R on a Steam Deck. However, since I’m using KWin with Wayland, I had a few issues to overcome.
Battle.net
Unlike Diablo IV, we can't run D2R directly through Steam. We need to launch Battle.net first. There are two issues here.
- we can't add non-steam game in Big-Picture mode.
- `Battle.net` has recently updated its server certificate, which is now signed by a new root certificate. To communicate with the server properly, we should use Proton 10 or later. Otherwise, running `Battle.net` will show BLZBNTBNA00000005 error.
There are two important points to note:
- If the
Battle.netinstaller is not run with Proton 10 or later, switching to Proton 10 afterward will not resolve the issue — a complete installation is required. - Before installing D2R, you should change the installation path in the
Battle.netsettings to a location outside the Steam folder. So that we don't need to install D2R again if we remove and install `Battle.net` again.
Windowed of Battle.net
Battle.net may be run in windowed mode, a titlebar and frame is added by KWin. It will make the Battle.net running in a wrong resolution, and the play button won't be shown. We can add a KWin Window Rule to force No titlebar and frame and the minimum size of the window.
Controller
D2R works out of the box with mouse and keyboard. But if you want to play it with a controller, you should enable Steam Input. After the press any button screen is passed, D2R can be controlled by a controller. We can touch the screen, switch the controller to mouse mode, or map a button of the controller to send a input event using keyd(There is no extra key in OneXPlayer 2 Pro controller. The keyboard mode switch key is hardware controlled, and the system can't read that key event.) to pass the press any button screen. It looks like D2R will show press A button screen instead of press any button screen if we don't attach any mouse. We can use a controller to pass that screen.
Enable Steam Input
Steam Input uses uinput to handle the event from controllers. By default, the user running steam doesn't have enough permission. We need to install game-devices-udev` to change the permission of the device.
In Arch Linux, there is an AUR package, install it by running: paru -S game-devices-udev, change paru to the aur helper you are using.
Cursor in Inventory UI with Controller
In the inventory ui, the cursor might be not moved correctly. The position of the cursor is moved, but the cursor surface is not. We can run D2R with gamescope to solve this problem.
- Install
gamescope. Because my steam is installed byflatpak, so I should installgamescopebyflatpaktoo:flatpak --user install org.freedesktop.Platform.VulkanLayer.gamescope, choose the same version oforg.freedesktop.Platformneeded by steam. - Add the
binfolder of gamescope toPATHof steam inflatpaksettings, thebinfolder is/usr/lib/extensions/vulkan/gamescope/bin. - Set the launch option
# Ghost editor replace -- with –
gamescope --hide-cursor-delay -1 -- %command%
The cursor surface might not be shown at first, we can move the mouse to make it appear.
Gamescope Error Popups
Launching D2R, there are two gamescope error popup windows: CreateSwapchainKHR and QueuePresentKHR. We can just click OK to pass them. Don't click Cancel, otherwise, D2R will display a black screen. When I disable WSI layer with When WSI layer is disabled, the cursor in inventory ui becomes laggy.ENABLE_GAMESCOPE_WSI=0, those popups are gone. The launch option becomes: ENABLE_GAMESCOPE_WSI=0 gamescope --hide-cursor-delay -1 – %command%.
Unsolved
Steam Freeze
This is an issue of steam. If steam lost-focus(may) or the screen is locked(must), steam will freeze. the game might lose control or be frozen. I'm not sure whether disabling Steam Input will prevent Steam freeze from affecting the game. I'm also not looking into the lost-focus issue — it seems to be related to the KWin compositor's behavior during the game, such as when pressing Alt+Tab.
What a heartbeat moment — playing D2R Hardcore mode and experiencing an unexpected freeze!
Running D2R without Opening Steam
I think I can run D2R directly to avoid the steam freeze problem. I used pstree -plas $PID to find out the parent process running Battle.net, and used cat /proc/$PID/environ | tr '\0' '\n' to print all its environment variables. I kept almost all variables starting with STEAM. After that, I can run D2R with Proton directly.
Controller Mapping
I found that the controller works out of box opening outside of steam. It looks like steam disables my controller using a SDL environment variable. I am a Nintendo controller guy. Using Xbox controller layout is bit inconvenient for me. Thanks for SDL, I can use a Nintendo controller layout. Here is my controller mapping edited via AntiMicroX:
SDL_GAMECONTROLLERCONFIG="030081b85e0400008e020000080100001118654,Atari Xbox 360 Game Controller,platform:Linux,a:b1,b:b0,x:b3,y:b2,back:b6,start:b7,guide:b8,leftshoulder:b4,rightshoulder:b5,leftstick:b9,rightstick:b10,leftx:a0,lefty:a1,rightx:a3,righty:a4,lefttrigger:a2,righttrigger:a5,dpup:h0.1,dpleft:h0.8,dpdown:h0.4,dpright:h0.2,"
Gamescope
To my surprise, if I run D2R with gamescope nothing will show but the Battle.net is actual running. Comparing the processes between steam-run and direct-run, I found the proton process executed by gamescope will create a child process and exit in direct-run. So, gamescope thinks there is no application running. I finally wrote a script to get the flatpak instance id with (--instance-id-fd) and monitor that instance id with (flatpak ps) to keep the process running.
Then comes the second surprise. Unlike steam-run, all windows (Battle.net launcher and D2R) are running inside a window. Actually, the direct-run behavior is what I would expect based on my understanding. It is what a nested compositor should do. But the scale of the cursor is wrong and I must manually set the resolution. I really want the steam-run behavior. Comparing the environment variables, I found Display is set to :0 in steam-run and Display is set to :1. So, all windows of steam-run is drawing directly by Kwin. But why does the cursor in the inventory ui affects?
During the search, I found that gamescope actually doesn't work with the builtin Proton. I should install com.valvesoftware.Steam.CompatibilityTool.Proton-GE. After all windows of steam-run will run inside the window of gamescope. And those error popups disappear. But the cursor in the inventory ui doesn't move. After adding `--force-grab-cursor`, the cursor moves but it twinkles.
Scripts
Here are my scripts:
- run-steam-battlenet
#! /usr/bin/env bash
set_envs() {
set -a
STEAM_BASE_FOLDER=$HOME/.var/app/com.valvesoftware.Steam/.local/share/Steam
STEAM_CLIENT_CONFIG_FILE=$HOME/.var/app/com.valvesoftware.Steam/.local/share/steam.cfg
STEAM_COMPAT_APP_ID=0
STEAM_COMPAT_CLIENT_INSTALL_PATH=$HOME/.var/app/com.valvesoftware.Steam/.local/share/Steam
STEAM_COMPAT_DATA_PATH=$HOME/.var/app/com.valvesoftware.Steam/.local/share/Steam/steamapps/compatdata/$EX_STEAM_GAME_ID
STEAM_COMPAT_LIBRARY_PATHS=$HOME/.var/app/com.valvesoftware.Steam/.local/share/Steam/steamapps
STEAM_COMPAT_MEDIA_PATH=$HOME/.var/app/com.valvesoftware.Steam/.local/share/Steam/steamapps/shadercache/$EX_STEAM_GAME_ID/fozmediav1
STEAM_COMPAT_MOUNTS="$HOME/.var/app/com.valvesoftware.Steam/.local/share/Steam/steamapps/common/Proton - Experimental":$HOME/.var/app/com.valvesoftware.Steam/.local/share/Steam/steamapps/common/SteamLinuxRuntime_sniper
STEAM_COMPAT_PROTON=1
STEAM_COMPAT_TOOL_PATHS="$HOME/.var/app/com.valvesoftware.Steam/.local/share/Steam/steamapps/common/Proton - Experimental":$HOME/.var/app/com.valvesoftware.Steam/.local/share/Steam/steamapps/common/SteamLinuxRuntime_sniper
STEAM_COMPAT_TRANSCODED_MEDIA_PATH=$HOME/.var/app/com.valvesoftware.Steam/.local/share/Steam/steamapps/shadercache/$EX_STEAM_GAME_ID
STEAM_EXTRA_COMPAT_TOOLS_PATHS=/app/share/steam/compatibilitytools.d:/app/utils/share/steam/compatibilitytools.d
STEAM_FOSSILIZE_DUMP_PATH=$HOME/.var/app/com.valvesoftware.Steam/.local/share/Steam/steamapps/shadercache/$EX_STEAM_GAME_ID/fozpipelinesv6/steamapprun_pipeline_cache
STEAM_RUNTIME_LIBRARY_PATH=$HOME/.var/app/com.valvesoftware.Steam/.local/share/Steam/ubuntu12_32/steam-runtime/pinned_libs_32:$HOME/.var/app/com.valvesoftware.Steam/.local/share/Steam/ubuntu12_32/steam-runtime/pinned_libs_64:/app/lib/i386-linux-gnu/GL/default/lib:/app/lib32:/app/lib/i386-linux-gnu:/lib64:/app/lib:/usr/lib/x86_64-linux-gnu/GL/default/lib:/usr/lib/x86_64-linux-gnu/openh264/extra:/usr/lib/x86_64-linux-gnu:$HOME/.var/app/com.valvesoftware.Steam/.local/share/Steam/ubuntu12_32/steam-runtime/lib/i386-linux-gnu:$HOME/.var/app/com.valvesoftware.Steam/.local/share/Steam/ubuntu12_32/steam-runtime/usr/lib/i386-linux-gnu:$HOME/.var/app/com.valvesoftware.Steam/.local/share/Steam/ubuntu12_32/steam-runtime/lib/x86_64-linux-gnu:$HOME/.var/app/com.valvesoftware.Steam/.local/share/Steam/ubuntu12_32/steam-runtime/usr/lib/x86_64-linux-gnu:$HOME/.var/app/com.valvesoftware.Steam/.local/share/Steam/ubuntu12_32/steam-runtime/lib:$HOME/.var/app/com.valvesoftware.Steam/.local/share/Steam/ubuntu12_32/steam-runtime/usr/lib
STEAM_ZENITY=/usr/bin/zenity
DISPLAY=${DISPLAY_BEFORE_GAMESCOPE:-$DISPLAY}
set +a
}
set_controller_mapping() {
set -a
SDL_GAMECONTROLLERCONFIG="030081b85e0400008e020000080100001118654,Atari Xbox 360 Game Controller,platform:Linux,a:b1,b:b0,x:b3,y:b2,back:b6,start:b7,guide:b8,leftshoulder:b4,rightshoulder:b5,leftstick:b9,rightstick:b10,leftx:a0,lefty:a1,rightx:a3,righty:a4,lefttrigger:a2,righttrigger:a5,dpup:h0.1,dpleft:h0.8,dpdown:h0.4,dpright:h0.2,"
set +a
}
start_bottle_net() {
# Create a temp file for storing the instance id
tmpfile_instance_id=$(mktemp /tmp/run-steam-battlenet-instance-id.XXXXXX)
echo "Steam instance id will be stored at $tmpfile_instance_id"
exec 3>"$tmpfile_instance_id"
/usr/bin/flatpak run --instance-id-fd=3 --branch=stable --arch=x86_64 --command="$HOME/.var/app/com.valvesoftware.Steam/.local/share/Steam/steamapps/common/Proton - Experimental/proton" com.valvesoftware.Steam waitforexitandrun "$HOME/.var/app/com.valvesoftware.Steam/.local/share/Steam/steamapps/compatdata/$EX_STEAM_GAME_ID/pfx/drive_c/Program Files (x86)/Battle.net/Battle.net Launcher.exe"
exec 3>&-
set +e
read instance_id < "$tmpfile_instance_id"
echo "Running as Battle.net as Steam Instance[$instance_id]"
while flatpak ps --columns=instance | grep -qx "$instance_id"; do
sleep 1
done
}
delete_file() {
if [ -n "$1" ]
then
if [ -f "$1" ]
then
rm "$1"
fi
fi
}
clean_up() {
delete_file "$tmpfile_instance_id"
}
EX_STEAM_GAME_ID=3494142050
set -e
tmpfile_instance_id=""
trap 'clean_up' EXIT
set_envs
set_controller_mapping
start_bottle_net
- run-steam-battlenet-with-gamescope
#! /usr/bin/env bash
export DISPLAY_BEFORE_GAMESCOPE="$DISPLAY"
/usr/bin/gamescope --hide-cursor-delay=-1 -- "$(dirname $0)/run-steam-battlenet"