Friday, August 6, 2021

Making a virtual multi-tap using ViGEm/ViGEm.NET

Update 1/24/2022: Updated the download, includes the required but missing file this time, also fixed some bugs which prevents the addition of new controllers.

    So my brother has been playing a game on PCSX2 and its a multiplayer game and he asked me if I could make a program which acts as basically a virtual controller that mimics the actions of the physical controller. Now receiving controller inputs I was familiar with through DirectInput and XInput but simulating/emulating a controller is something I haven't a clue about but I know we have lots of talented people so I did some research. Now I am mainly a C# developer these days so I specifically researched on that front. Sure enough there is a driver for simulating/emulating a controller called ViGEm and even a .net wrapper called ViGEm.NET. With this in hand I did some tests and after all test passing, I proceeded to ask my brother the specifications he would like for the program and off I went. With the driver and wrapper it was easy functionality-wise but most of the work went into handling the mappings and conversions to a DualShock4. Weirdly enough the XBox360 controller emulation didn't work. I went into the discord channel for ViGEm and asked around and they couldn't figure out why either but that was fine, dualshock4 will do.

So I made this:
https://mega.nz/file/7FhWADTZ#mC76AbApajA6TSFJ0mIo3Zvq2k6yEhzJmhX3Epg6Phk

I'll number each step I mention here as a comment in the source here:
https://github.com/TizzyT566/VirtualMultitap

0) This step isn't going to be in the source code but you have to install the ViGEm.NET package which can be found here:
https://www.nuget.org/packages/Nefarius.ViGEm.Client/
Another thing is we have to specify useLegacyV2RuntimeActivationPolicy="true" in the App.config file. Finally you have to have the driver installed which can be obtained here:
https://github.com/ViGEm/ViGEmBus

1) I want to ask the user how many virtual controllers they want, since my brother only asked for up to 4 players thats what I did.

2) Now that I know how many controllers the user wants, we can leave that info for later, we'll come back to it, the first controller related step is to get user inputs for a single controller which will control the virtual controllers. We can do that via Manager.GetDevices(DeviceClass.GameControl, EnumDevicesFlags.AttachedOnly). We specify that we only want to get GameControllers that are currently attached. Each device has a device id which is represented by a guid. We can populate a list of these guids to query these controllers later.

3) Lets actually make some virtual controllers now. We first create an instance of the ViGEmClient so we can use it to make the controllers. The number we stored in step 1 will be used to create 2 arrays. One array to store the actual virtual controllers and a second to represent active ones. We want to be able to specify which of the 4 or even all of them to be active on the fly.

4) Now getting back to that list of device guids. We need to be able to single out a master controller for the virtual controllers.

4.1) We are only given the device guids so we cant really tell which device is which unless you memorized the guids. Even then because windows is wonky handing out these ids you'll never be able to distinguish any 2 controllers reliably. So there must be a method to detect which controller you want instead of selecting from a list. In this case I made it so that if the user selects 0, it would go into a sort of detection mode where the user physically presses a button on the controller they want to act as the master controller. To do that we create an array of devices and populate it with aquired devices using the guids from the list we created earlier. We have to make sure to se the data that comes back from the device be joystick data. Then we loop through the device array over and over again until one of the controllers DPad or buttons are pressed, and we make note of that device, setting it to a variable. To clean up we iterate over the device array one last time to Unacquire all the controllers we arent using.

4.2) If the user selects an option other than 0, the first thing we need to do is decrement the selected option by 1 since 0 was taking the place for Detecting a controller in step 4.1. We then initialize a Device using the selected guid. We set the dataformat and acquire it and make note of it just like in step 4.1.

5) Now that we have the master controller established we need to deal with the mappings. I decided to store the mappings in the Documents\Virtual Multitap folder. If the folder doesn't exist yet, we create it. We construct the mapping's full file path using the guid. The JoyStickState structure doesn't correlate directly with how the DualShock4 is mapped in ViGEm so I just associated one mapping to another using indexes. We loop over and over again for each input for a DualShock4 controller, mapping all buttons, sliders, axises, and the viewpoint (aka DPad). Finally I save the mapping with each dualshock4 input on a separate line in Document\Virtual Multitap\<guid>.

6) We now load the mapping we just made or load one if one already exists for that device guid.

7) The last thing to do in our routine is to kick off a loop to listen for keyboard presses and events on the physical controller and simulate them on the virtual controller. I don't use a regular loop, instead I use a SpinWait(). This allows the loop to process relatively fast while not hogging a bunch of CPU.