Using VRChat in a Virtual Machine

🚧

Advanced Users Only!

This information is generally only useful for advanced users and specifically only those running VRChat in a virtual machine. It will contain language that may not be recognizable or useful for most users.

❗️

Running VRChat in VMs is Unsupported!

Using VRChat via a Virtual Machine is not directly supported, however we have done some research to try to make things easier for those that choose to try anyways.

The presence of this documentation does not mean we support running VRChat in a VM. It is possible that these methods or any others may stop working at any given time. We will try to keep this documentation up to date regardless.

If you discover that this documentation is out of date or needs to be edited, please use the "Suggest Edits" link in the top right.

VRChat's anti-cheat solution, Easy Anti-Cheat (EAC) generally fails when running in a Virtual Machine (VM) environment.

You can get virtualization working alongside EAC in some cases, and we don't mind if you do this.

From what we've seen, these workarounds do not have a performance impact.

libvirt

Use virsh edit VM_NAME to open the VM's XML config in your favorite editor. Then simply add the following line under features > hyperv:

<vendor_id state='on' value='0123756792CD'/>

In the end, your features block should look something like this:

  <features>
    <hyperv mode='custom'>
      <!-- other things here -->
      <vendor_id state='on' value='0123756792CD'/>
    </hyperv>
    <!-- other things here -->
  </features>

Again, this does not affect performance. You can leave other settings, such as the hypervisor, topoext and invtsc cpuid flags or the hyper-v clock (which all do have performance benefits) enabled.

In fact, here's a little trick: Instead of just adding a vendor_id, you can set the Hyper-V mode to passthrough:

<hyperv mode='passthrough'>
  <!-- whatever -->
</hyperv>

This will enable all available hyper-v enlightenments* that are available for your kernel/QEMU version - including a vendor id, meaning EAC will not complain, but also including other features you might not have had enabled before, meaning this may very well improve your performance!

* "enlightenments" is hyper-v's way of saying "paravirtualized extensions", i.e. interfaces that the linux kernel or QEMU provide to windows guests to enhance performance or functionality in virtual environments

After 26 AUG 2022, the above fixes may not be enough to get the game to run still (you should still do them). If you are still facing issues, try manually setting SMBIOS strings for your virtual machine. Theoretically, any valid hardware configuration should work fine, but your best bet would be to pull your own system's baseboard information via dmidecode.

You should be able to extrapolate info about your baseboard from these commands:

dmidecode --type bios
dmidecode --type baseboard
dmidecode --type system

To do so, add this to your domain root but preferably use your system's information:

  <sysinfo type="smbios">
    <bios>
      <entry name="vendor">American Megatrends Inc.</entry>
      <entry name="version">F31o</entry>
      <entry name="date">12/03/2020</entry>
    </bios>
    <system>
      <entry name="manufacturer">Gigabyte Technology Co., Ltd.</entry>
      <entry name="product">X570 AORUS ULTRA</entry>
      <entry name="version">x.x</entry>
      <entry name="serial">BASEBOARD SERIAL HERE (or "Default string")</entry>
      <entry name="uuid">BASEBOARD UUID HERE</entry>
      <entry name="sku">BASEBOARD SKU HERE (or "Default string"</entry>
      <entry name="family">X570 MB</entry>
    </system>
  </sysinfo>

For your UUID entry, you can use the VM's generated UUID at the top of your domain XML. The UUID in your domain XML and in your sysinfo will need to match, otherwise libvirt will complain.

Also, add this under your <os> group to make use of those new system parameters:

<os>
    <smbios mode="sysinfo"/>
    <!-- whatever -->
</os>

QEMU

If you're starting your VM using a raw QEMU command line, you just have to add the vendor id to the -cpu flag. For example:

-cpu 'host,migratable=off,hypervisor=on,topoext=on,hv_relaxed,hv_reset,hv_runtime,hv_stimer,hv_synic,hv_time,hv_vapic,hv_vpindex,hv-frequencies,hv-avic,hv-vendor-id=0123456789AB,host-cache-info=on,apic=on,invtsc=on'

There may be other stuff in your -cpu argument already, in which case you just append ,hv-vendor-id=0123756792CD to the end, as shown above. The recommended settings presented above enable more than just Hyper-V entailments, ensuring more advanced methods like CPU L2/L3 cache topology is also passed properly.

You can use hyper-v passthrough mode here too:

-cpu host,migratable=off,hypervisor=on,invtsc=on,hv-time=on,hv-passthrough=on

However, using hv-passthrough is not recommended, as this option activates all Hyper-V entitlments supported by KVM itself (not just ones supported by your hardware).

(note: you do not need any other hv-foo arguments other than (I think?) hv-time in that case, passthrough will take care of the rest)

QEMU also support all SMBIOS types emulation using multiple -smbios type=#,... entries, as described in QEMU documentation. Filling-in all parameters from scratch is cumbersome, but can be easily automated. Using a script to get real values from your own hardware is recommended: https://gist.github.com/kiler129/5d437a37c07ac6eb1cdf0e595e488fd2. This ensures values are corresponding to real hardware, and are not exactly the same for all users. An example command line parameters, as generated by the script, are shown below:

-smbios 'type=0,version=F31o,vendor=American Megatrends International,, LLC.,uefi=on,release=5.17,date=12/03/2020' \
-smbios 'type=1,version=-CF,sku=Default string,product=X570 AORUS ULTRA,manufacturer=Gigabyte Technology Co.,, Ltd.,uuid=d30dbc2a-d9b0-11ed-afa1-0242ac120002,serial=Default string,family=X570 MB' \
-smbios 'type=2,asset=Default string,version=Default string,product=X570 AORUS ULTRA,location=Default string,manufacturer=Gigabyte Technology Co.,, Ltd.,serial=Default string' \
-smbios 'type=3,asset=Default string,version=Default string,sku=Default string,manufacturer=Default string,serial=Default string' \
-smbios 'type=4,asset=Unknown,version=AMD Ryzen 9 5950X 16-Core Processor            ,part=Zen,manufacturer=Advanced Micro Devices,, Inc.,serial=Unknown,sock_pfx=AM4' \
-smbios 'type=11,value=Default string' \
-smbios 'type=17,bank=Bank 0,asset=Not Specified,part=OV_8GR1,manufacturer=OEM_VENDOR,speed=2666,serial=OEM33161,loc_pfx=DIMM 0'

Proxmox/PVE

Make sure your operating system is set to "Windows 7" or higher in the "Options" tab of your VM.

While not strictly related to EAC, running VR games in Proxmox (and any other VM) requires consistent and predictable performance. Proxmox-specific performance tuning tutorial, geared towards gaming can be found on Proxmox forum: https://forum.proxmox.com/threads/hey-proxmox-community-lets-talk-about-resources-isolation.124256/

Technical notes

You may notice that some of this is very similar to the infamous "NVIDIA code 43 fix" from the past. Really the only difference here is that hiding the kvm interface (kvm=off or <kvm><hidden state='on'/></kvm>) is not even required (but also doesn't hurt). If you have previously set up your VM using such a guide (e.g. https://passthroughpo.st/apply-error-43-workaround/), then VRC with EAC should be working out of the box for you.

On a technical level, what the hyper-v vendor id does is setting leaf 0x40000000 of the guest's cpuid information to whatever is provided. The default here is "Microsoft HV", which EAC simply denies. Using hyper-v passthrough mode, this turns into "Linux KVM Hv", which still indicates a VM, but EAC is unfazed by it.

Since this does not require changing the hypervisor flag, the OS kernel in the guest (Windows NT) will still recognize the environment as a VM and apply performance enhancing measures as such. This also means that the Task Manager in the guest will report running in a virtual machine. This does not matter for EAC in testing within a KVM environment.

Very technical notes

Here's a little win c++ program to demonstrate the effect of the changes:

#include <iostream>
#include <intrin.h>

void print_leaf(int leaf)
{
    int res[4];
    __cpuid(res, leaf);

    std::cout << "leaf: 0x" << std::hex << leaf << std::endl;

    for (size_t i = 0; i < 4; i++)
    {
        std::cout << "res" << i << ": 0x" << std::hex << res[i] << " (";
        for (size_t j = 0; j < 4; j++)
        {
            char part = (res[i] >> j * 8) & 0xff;
            std::cout << part;
        }
        std::cout << ")" << std::endl;
    }
}

int main()
{
    // cf:
    // https://docs.microsoft.com/en-us/virtualization/hyper-v-on-windows/tlfs/feature-discovery
    // https://docs.microsoft.com/en-us/cpp/intrinsics/cpuid-cpuidex?view=msvc-170

    std::cout << "manufacturer id:" << std::endl;
    print_leaf(0); // Manufacturer ID

    std::cout << "hyper-v id:" << std::endl;
    print_leaf(0x40000000); // Hypervisor CPUID Leaf Range

    return 0;
}

Output on an example machine using hv-passthrough:

manufacturer id:
leaf: 0x0
res0: 0x10 (►   )
res1: 0x68747541 (Auth)
res2: 0x444d4163 (cAMD)
res3: 0x69746e65 (enti)
hyper-v id:
leaf: 0x40000000
res0: 0x40000005 (♣  @)
res1: 0x756e694c (Linu)
res2: 0x564b2078 (x KV)
res3: 0x7648204d (M Hv)