I’ve decided to try my luck with using the VRChat SDK on Ubuntu. Because frankly, nobody likes starting a Windows VM just to use some tools that should work on Linux as well. This post is an unstructured info dump with some tips and tricks to get you started.
Preparation
The first step is to obtain UnityHub.
I’m using the AppImage, since that is the least amount of effort.
I simply run ./UnityHub.AppImage --no-sandbox
to get it working.
Sign in like you would normally.
Next, you need a specific ancient version of Unity.
At the time of me writing this, the version VRChat requires wasn’t even offered
in the UI anymore, but the download can be triggered via a unityhub
URL:
./UnityHub.AppImage --no-sandbox unityhub://2019.4.31f1/bd5abf232a62
Under Installs, you’ll want to enable the “Windows Build Support” module for this installation. Create a new project with this Unity version.
Finally, download the VRCSDK3 World unitypackage from VRChat. You can simply install this unitypackage as usual, such as by dragging and dropping the file into the project in Unity.
Open the VRChat SDK control panel and log in. Create your world scene like you would normally.
Building and running
The fun starts when you want to test your world in VRChat. The VRCSDK3 was not made for Linux and definitely has no clue about Steam’s Proton environment. Some things to know:
- The VRChat Steam AppID is 438100
- VRChat keeps its data in
%AppData%/LocalLow/VRChat
- Specifically worlds are normally written to
%AppData%/LocalLow/VRChat/VRChat/Worlds/
- VRCSDK3 will produce build artifacts in a temporary directory using
Application.get_temporaryCachePath()
(which will be/tmp
on Linux) - The Proton path will be something like
~/.steam/steam/steamapps/compatdata/438100/pfx/drive_c/users/steamuser/AppData/LocalLow/VRChat/
Issue 1: VRCSDK3 determines the path to LocalLow by calling
SHGetKnownFolderPath, which is a Windows-specific API.
This call will return null
on Linux.
Luckily, the behavior in
VRCWorldAssetExporter.ExportCurrentSceneResource
is
robust enough and VRCSDK3 falls back to using the temporary path it builds
the world in.
Issue 2: to test the world, VRCSDK3 launches VRChat with a number of
commandline arguments.
One of the commandline arguments specifies the location of the world file on
disk.
Due to issue 1, this path will be something like
/tmp/DefaultCompany/MyWorld/foo.vcrw
which is not accessible in Proton using that name.
Luckily we can translate the world path by turning
file:///%2ftmp%2fDefaultcompany%2f...
into
file:///Z%3a%2ftmp%2fDefaultcompany%2f...
so that VRChat can find the world file in the Proton environment, since the Z: drive maps to the Linux filesystem in Proton.
Since the SDK allows you to specify the path to the VRChat executable
(Settings -> VRChat Client), we can
easily write a script that patches the argument and launches the VRChat binary
through Steam.
An example script that I’ve called launch.exe
:
#!/usr/bin/python3
import sys
import subprocess
for i, arg in enumerate(sys.argv):
# Inject Z: into path for Proton
if arg.startswith('--url'):
sys.argv[i] = sys.argv[i].replace('url=file:///', 'url=file:///Z%3a')
subprocess.Popen(
['steam', '-applaunch', '438100'] + sys.argv[1:],
start_new_session=True,
)
The only caveat is that if the application is already running Steam will not launch a second process, which means a world reload would not be triggered. You can just rejoin the world to reload, however.
Issue 3: publishing worlds doesn’t work out of the box.
The issue seems to be that Unity converts filenames to lowercase during the
BuildPipeline.BuildAssetBundles
call.
This results in VRCSDK3 looking for
scene-StandaloneWindows64-SampleScene.vrcw
while the file is actually scene-standalonewindows64-samplescene.vrcw
on disk.
This obviously isn’t an issue on Windows since it doesn’t use case-sensitive
filesystem, but on Linux this does not work.
You can fix this by making a symlink to the lowercased file:
cd /tmp/DefaultCompany/MyWorld
ln -s scene-standalonewindows64-samplescene.vrcw scene-StandaloneWindows64-SampleScene.vrcw
Or, you can add the following button to the UI to fix the path variable (which makes the “Last Build” buttons actually work):
// Assets/VRCSDK/SDK3/Editor/VRCSdkControlPanelWorldBuilder3.cs
// in: public override void OnGUIScene()
if (GUILayout.Button("[Linux] Fix lastVRCPath")) {
string path = EditorPrefs.GetString("lastVRCPath");
string[] parts = path.Split('/');
if (parts.Length > 0)
parts[parts.Length - 1] = parts[parts.Length - 1].ToLower();
path = string.Join("/", parts);
EditorPrefs.SetString("lastVRCPath", path);
Debug.Log("lastVRCPath: "+ EditorPrefs.GetString("lastVRCPath"));
}
And there you go!