缘起
之前给我的Ubuntu电脑安装了Steam游戏平台和Proton兼容层,可以通过这个兼容层在Linux系统上运行Windows游戏。但我没怎么玩过游戏,所以一直没有仔细研究,只知道Proton兼容层还挺强大的,大部分游戏运行起来都没有问题,即使是游戏的“系统要求”中只显示支持Windows系统。
后来装了一个Nvidia RTX 4060 Ti显卡,主要是想学习一下CUDA编程和深度学习。由于最近《黑神话:悟空》大热,室友想在我的电脑上玩,所以他就在我的电脑上登录了Steam账号,下载了《黑神话:悟空》,发现运行起来完全没问题。
看室友玩了一段时间后,我也想试试,但又不想来回切换Steam账号。所以我就在Ubuntu上另建了一个系统账号,然后启动并登录Steam,但我发现之前室友在另一个账号上下载的游戏并没有显示在我的账号上。由于这些游戏都非常大,我不想再重新下载一遍,所以我就在想办法在我的账号上运行室友下载的游戏。在此过程中遇到了一个又一个问题,最后终于解决了,这里记录一下,以便遇到同样问题的人参考。
前提
如果你只想一个人在Linux系统上玩Steam游戏,那么只需要安装Steam和Proton即可,那么你只需要参照我之前的文章《Ubuntu上Nvidia显卡设置:游戏、CUDA、深度学习、Docker等》中的步骤安装即可,如果你不想点进去看,我这里简单列一下步骤:
安装Steam
- 下载Steam安装包:
|
|
- 安装Steam:
|
|
- 安装可能缺失的依赖项:
|
|
- 运行Steam:
|
|
- 登录Steam账号
安装Proton
在Steam中,选择可以在Linux上运行的游戏,然后点击Settings
,在Steam Play
选项卡中,勾选Enable Steam Play for supported titles
和Enable Steam Play for all other titles
,然后在Steam Play
下拉菜单中选择一个Proton版本,点击OK
。
等待Proton安装完成后,就可以在Linux上运行Windows游戏了。
安装游戏
如果你是第一次使用Steam,尚未购买任何游戏,你可以选择一些免费的游戏进行测试,例如《Dota 2》、《Counter-Strike: Global Offensive》等。
多用户共享游戏
如果你想多个用户都能玩同一个游戏,而不想重复下载(毕竟一个大型游戏可能有几十GB甚至上百GB),那么可以接着看我是怎么操作的。
Steam运行环境
在创建第二个用户并共享同一份游戏文件之前,我们需要先了解一下Linux下Steam的运行环境以及主要文件的位置。
在按照上文的步骤安装Steam和Proton后,Steam安装到了系统目录下,因此所有用户都可以使用Steam客户端。
在Ubuntu用户user1
启动了Steam客户端后,Steam会在user1
用户的家目录下创建一个.steam
目录,该目录下包含了用户的Steam配置文件、游戏文件等。在.steam
目录下,有一个steam
目录,该目录下包含了用户的Steam客户端程序,还有一个steamapps
目录,该目录下包含了用户下载的游戏文件。
因此,如果我们想复用一个游戏,其本质就是让其他用户也能访问到/home/user1/.steam/steam/steamapps
目录下的游戏文件。
创建第二个用户
-
我们为第二个用户创建一个新的系统账号,将其添加到
sudo
组,并设置密码:1 2 3
sudo adduser user2 sudo usermod -aG sudo user2 sudo passwd user2
-
切换到
user2
用户:点击屏幕右上角的用户图标,选择
Switch Account
,然后选择user2
用户登录。 -
打开并登录Steam:
打开Steam客户端后,Steam会在
user2
用户的家目录下创建一个.steam
目录,但是此时user2
用户的.steam/steam/steamapps
目录下并没有游戏文件。因此,当你点击Library
页面时,你会发现库里看不到之前user1
用户下载的游戏。下面我们就主要来解决这个问题,让
user2
用户也能访问到之前user1
用户下载的游戏文件,并正常运行游戏。
共享游戏文件
-
之前
user1
用户下载的游戏文件位于/home/user1/.steam/steam/steamapps
目录下,但我们想让多个用户共享这些游戏文件,所以我们最好将这些游戏文件移动到一个公共目录下,例如/opt/games/steam
下。1 2
sudo mkdir -p /opt/games/steam sudo mv /home/user1/.steam/steam/steamapps /opt/games/steam
-
为了让
user2
用户能够访问到/opt/games/steam
目录下的游戏文件,我们需要将/opt/games/steam
目录的权限设置为755
:1
sudo chmod 775 /opt/games/steam
然后我们创建一个新的用户组
steam
,将user1
和user2
用户都添加到这个用户组中:1 2 3
sudo groupadd steam sudo usermod -aG steam user1 sudo usermod -aG steam user2
接着,我们将
/opt/games/steam
目录的用户组设置为steam
:1
sudo chown -R user1:steam /opt/games/steam
最后,我们需要为共享文件夹设置
setgid
权限,这样可以保证,任何在共享文件夹中新建的文件和目录,其用户组和之前共享文件夹的用户组相同。即/opt/games/steam
的用户组是steam
,之后在/opt/games/steam
中创建的文件和文件夹的用户组都将是steam
:1
sudo chmod g+s /opt/games/steam
-
为了让
user1
的Steam客户端能够访问到/opt/games/steam
目录下的游戏文件,我们需要在user1
用户的家目录下创建一个软链接:1
ln -s /opt/games/steam/steamapps /home/user1/.steam/steam/steamapps
然后我们在
user1
下,打开Steam客户端,点击Library
页面,你会发现之前下载的游戏文件都在了。如果你没有看到,可以尝试重启Steam客户端或者重启电脑。 -
user2
用户也类似,我们需要在user2
用户的家目录下创建一个软链接:1
ln -s /opt/games/steam/steamapps /home/user2/.steam/steam/steamapps
然后我们在
user2
下,打开Steam客户端(可能需要先重启Steam客户端或者重启电脑),点击库
页面,你应该可以看到之前下载的游戏文件了。然而,如果你点击
开始游戏
按钮,你会发现游戏并没有运行起来,而且很可能没有任何提示。如果你想通过错误日志来查找问题,你可以推出Steam客户端,然后在终端中运行Steam客户端,这样你就可以在终端中看到Steam的输出信息,大概是这样:1
pressure-vessel-wrap[44758]: E: openat(/opt/games/steam/steamapps/common/SteamLinuxRuntime_sniper/sniper_platform_0.20240806.97927/files/.ref): Permission denied
或者是这样:
1 2 3 4 5
pressure-vessel-wrap[4109614]: W: For best results, "/opt/games/steam/steamapps/common/SteamLinuxRuntime_sniper/sniper_platform_0.20240820.99315/files" and "/opt/games/steam/steamapps/common/SteamLinuxRuntime_sniper/var/tmp-7P6EU2/usr" should both be on the same fully-featured Linux filesystem. Adding process 4109770 for gameID 1264970 wineserver: /opt/games/steam/steamapps/compatdata/1264970/pfx is not owned by you wine: using kernel write watches, use_kernel_writewatch 1. wine: '/opt/games/steam/steamapps/compatdata/1264970/pfx' is not owned by you
你也可以在
库
页面中右键点击游戏,然后选择属性
,在已安装文件
选项卡中点击检查完整性
,你会发现不能通过完整性检查。 -
上面这个问题很诡异,因为我们已经将
/opt/games/steam
目录的权限设置为755
,并且将user1
和user2
用户都添加到steam
用户组中,但上面的错误信息表明,user2
用户下的Steam仍然无法访问/opt/games/steam/steamapps/common/SteamLinuxRuntime_sniper/sniper_platform_0.20240806.97927/files/.ref
文件。更加诡异的是,这个文件的大小为0!我也不知道这个文件是干嘛的,但经过一番探索,我在网上找到了一些相关的讨论:- https://ubuntuforums.org/showthread.php?t=2494677
- https://github.com/ValveSoftware/Proton/issues/4820
- https://github.com/ValveSoftware/steam-for-linux/issues/3942
这些讨论中主要提出了两种解决方案:
-
在切换系统账号后,更改
/opt/games/steam
目录的所有权为当前用户:1
sudo chown -R user2:steam /opt/games/steam
这样
user2
用户就可以访问/opt/games/steam
目录下的所有文件了。但显然,这个解决方案是一种治标不治本的方法,因为每次切换系统账号后,都需要更改
/opt/games/steam
目录的所有权,这样显然不太方便。 -
上面的问题的本质是Proton兼容层使用的Wine的问题。由于安全原因,Wine不允许用户访问所有权为其他用户的prefix目录。因此,解决方案就是在启动Steam时,不同的用户使用不同的prefix目录。Wine的prefix目录是Proton兼容层的一个重要概念,它存储了游戏的配置文件、缓存文件等。默认情况下,Proton会将prefix目录存储在
~/.steam/steam/steamapps/compatdata/
目录下,但我们可以通过修改Proton的代码,根据当前用户创建不同的prefix目录。为了实现这个方案,我们需要修改Proton的代码,然后重新编译Proton。其实要修改的代码只有两行,可以参见Proton拉取请求#4861,GitHub上显示这个拉取请求创建于2021年5月,但至今还没有合并,我也不太清楚Proton的开发者为啥一直不合并这个简单但有用的拉取请求。既然官方没有加入这个功能,我们接下来就自己动手实现。
修改并编译Proton
-
首先,我们需要下载Proton的源代码:
1 2
git clone --recurse-submodules https://github.com/ValveSoftware/Proton.git proton cd proton
-
然后,我们需要切换到一个稳定的分支,例如
proton_9.0
。我们还需要更新子模块:1 2
git checkout proton_9.0 git submodule update --init --recursive
-
接着,我们需要修改Proton的代码。为此我们新建一个分支,例如叫做
myfeature-pr4861
:1
git checkout -b myfeature-pr4861
打开Proton项目根目录下的
proton
文件(这是一个Python文件),找到CompatData
类的初始化函数__init__
。需要修改self.base_dir
这一行,并添加一行。修改后的代码如下:1 2 3 4
class CompatData: def __init__(self, compatdata): self.base_dir = compatdata + "/" + str(os.getuid()) + "/" os.makedirs(self.base_dir, exist_ok=True)
我们只需要修改这两行代码就够了,参见Proton拉取请求#4861。
这个思路其实很简单,proton的底层是使用Wine来运行游戏的,而Wine的prefix目录是存储在
~/.steam/steam/steamapps/compatdata/
目录下的。在做出上述修改以前,当我们以user1运行某个游戏时,Proton会将prefix目录存储在~/.steam/steam/steamapps/compatdata/
目录下,其所有权为user1。当我们以user2运行同一个游戏时,Wine会尝试访问~/.steam/steam/steamapps/compatdata/
目录下的prefix目录,但由于其所有权为user1,因此Wine会拒绝访问。上面的修改就是让Proton根据当前用户创建不同的prefix目录,例如
user1
的uid为1000
,user2
的uid为1001
,那么user1运行游戏时,Proton会将prefix目录存储在~/.steam/steam/steamapps/compatdata/1000/
目录下,其所有权为user1;user2运行游戏时,Proton会将prefix目录存储在~/.steam/steam/steamapps/compatdata/1001/
目录下,其所有权为user2。这就解决了上面的所有权冲突问题。 -
接下来我们编译Proton。Proton使用了容器化技术,因此我们需要有一个容器工具,例如Docker或Podman。我的电脑上已经安装了Docker,但Docker的运行一般需要
sudo
权限,这样会导致一系列的权限问题。因此这里推荐使用不需要sudo
权限的Podman。-
首先,我们需要安装Podman:
1
sudo apt install podman
-
然后,我们创建一个编译用的文件夹,可以放在跟Proton项目同级的目录下:
1 2 3
cd .. mkdir build cd build
-
接着,使用Proton项目自带的配置工具配置编译环境:
1
../proton/configure.sh --enable-ccache --build-name=myfeature-9.0-pr4861
-
最后,我们开始编译Proton:
1
make
-
-
编译完成后我们创建Proton的安装文件:
1
make redist
这样我们在
build
目录下就会生成一个redist
目录,里面包含了Proton的安装文件。 -
安装Proton。对于用户自定义的兼容层,我们可以将其放在
~/.steam/root/compatibilitytools.d/
目录下。我们将redist
目录整体复制到~/.steam/root/compatibilitytools.d/
目录下:1
cp -r redist ~/.steam/root/compatibilitytools.d/myfeature-9.0-pr4861
注意,不管在哪个账号下使用Steam,我们都需要把
redist
目录复制到~/.steam/root/compatibilitytools.d/
目录下,并修改目录的所有权为当前用户。例如如果是user_x
用户,我们可以这样:1
sudo chown -R user_x:user_x ~/.steam/root/compatibilitytools.d/myfeature-9.0-pr4861
使用自定义Proton
完成上面的步骤后,Steam就能检测到我们自定义的Proton版本了。
不管在哪个账号下使用Steam,我们只需要在Steam中选择我们自定义的Proton版本即可。打开Steam后,点击左上角的Steam
,然后选择设置
,在侧边栏选项卡中选择兼容性
,在运行其他产品中使用
下拉菜单中选择我们自定义的Proton版本myfeature-9.0-pr4861
,然后点击确定
。
最后,选择一个游戏并点击开始游戏
,会弹出一个对话框,让你选择是否以兼容模式运行游戏,我发现其实不选兼容模式也没问题:
Steam会花一会儿时间编译着色器,之后就能进入游戏界面了!
一个小问题
在玩游戏时,我发现当游戏播放较长时间的过场动画时,由于没有操作,显示器在约30秒后就会显示无输入信号。我怀疑是显卡的电源管理机制导致的,运行xset -dpms
命令可以关闭电源管理,这样可以确保显示信号不会中断。
使用xset q
命令可以查看当前的电源管理设置情况,直接使用xset
命令可以查看xset
命令的帮助信息。