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.

❗️

VMs are 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.

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

This can be very easily worked around though. Most notably, working around it does 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

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,invtsc=on,hv-time=on,hv-vendor-id=0123756792CD

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.

You can use hyper-v passthrough mode here too:

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

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

Proxmox/PVE

Make sure your operating system is set to "Windows 7" or higher in the "Options" tab of your VM. No other action should be required.

Technical notes

You may notice that 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 blacklists. 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 examaple 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)

Did this page help you?