Author Topic: Changing Virtual Joystick  (Read 2818 times)

Blade Runner

  • Newbie
  • *
  • Posts: 8
Changing Virtual Joystick
« on: March 20, 2018, 02:58:12 AM »
Hi,

I am currently using VA with Thrustmaster Target and was wondering if there is a hotkey or small program to change VA's joystick to the virtual one in Thrustmaster's Target app and back again when I shut Target down.
Thanks ahead of time.

Pfeil

  • Global Moderator
  • Hero Member
  • *****
  • Posts: 4782
  • RTFM
Re: Changing Virtual Joystick
« Reply #1 on: March 20, 2018, 11:12:25 AM »
There is not, as far as I'm aware.

Because VoiceAttack only allows a single joystick to be mapped per command, you can't really swap them out(otherwise you could have the physical stick as VoiceAttack joystick 1, and the virtual as joystick 2, then swapping between them).


In theory you could swap out the entries in the user.config file while VoiceAttack isn't running(the joystick values are loaded at startup), but you need to be familiar with XML, and(as you want to automate this) have experience with a programming language and its XML library.

Even then, if a VoiceAttack update makes a change to the way joystick data is stored, you'd need to update your application(and have a version check, so it doesn't break the config file).


EDIT 2024: Utility updated to support up to 8 joystick slots (note that the current release version of VoiceAttack supports 4 slots; more slots are supported by this utility for future-proofing)
This utility now requires .NET Framework 4.8 (which you should have anyway, as VoiceAttack also requires it)


Alright, so a few(I lost count) hours later I have this:
Code: [Select]
Run application 'C:\Program Files(x86)\VoiceAttack\JoystickSwapper.exe' -with parameters 'File="C:\Users\User\AppData\Local\VoiceAttack.com\VoiceAttack.exe_Url_aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\0.0.0.1\user.config" JoystickNumber=1 JoystickID=aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa JoystickName="My Joystick" JoystickEnabled=True JoystickEnablePOV=False JoystickPOV1Style=0 JoystickPOV2Style=0 JoystickPOV3Style=0 JoystickPOV4Style=0'
Run application 'C:\Program Files(x86)\VoiceAttack\RestartVA.bat' (hidden)
Close window 'VoiceAttack'

What the application will do is replace the config data for a given joystick with data you provide.


Now, before you actually try any of this, make sure you have backups of your VoiceAttack files(the contents of "%localappdata%\VoiceAttack.com" and "%appdata%/VoiceAttack/" at least, so you have your working config file and profile database.

I take no responsibility if this application or anything else affects your installation of VoiceAttack, your computer, your life, or the universe as we know it.


I ran it through VirusTotal as a matter of course, and it comes back clean, as it should.
EDIT 2024: 8-joystick version scanned and linked


The setup would be like so:

Find your VoiceAttack config file, it will be in a subfolder of "%localappdata%\VoiceAttack.com". You can easily copy the path by holding Shift, then right clicking the config file and clicking "Copy as path" in the context menu.


Now run the application with the "File" parameter, E.G.
Code: [Select]
File="C:\Users\User\AppData\Local\VoiceAttack.com\VoiceAttack.exe_Url_aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\0.0.0.1\user.config"Note that the double quotes are important here. If you used "Copy as path" they'll automatically be included, so make sure you only have one set of them.

This will make the application read your config file and output the current data for joystick 1, for convenience it will copy this data to the clipboard so you can paste it.


Now setup the VoiceAttack command as shown above, and paste the arguments into the "With these parameters" field of the Run an Application" dialog.


The batch file(RestartVA.bat) contains the following:
Code: [Select]
:wait
tasklist|find /i "VoiceAttack"
IF "%ERRORLEVEL%"=="1" (
start "" "C:\Program Files(x86)\VoiceAttack\VoiceAttack.exe"
GOTO exit
)
GOTO wait
:exit
You need this file to restart VoiceAttack, because the date from the config file is only loaded at startup(and an application cannot restart itself).


When you run the command, the application will swap out the data you specified and VoiceAttack will restart with the new data in place.

However, because this only modifies the config file and joystick button assignments are stored within the profiles themselves(which makes those assignments device-independent, in theory), this cannot change those assignments.

I'm hoping the Target virtual joystick either has the same assignments or can be programmed to that effect, because otherwise this application is likely useless to you(And just about anyone else).


If you want to modify joystick 2 instead, you can supply the "JoystickNumber" argument together with the "File" argument.
E.G.
Code: [Select]
'File="C:\Users\User\AppData\Local\VoiceAttack.com\VoiceAttack.exe_Url_aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\0.0.0.1\user.config" JoystickNumber=2
As long as only the "File" and "JoystickNumber" arguments are supplied, the application will read data and copy it to the clipboard, if any other arguments are supplied, data will be written.

You don't have to supply all arguments if you don't want to, E.G.
Code: [Select]
File="C:\Users\User\AppData\Local\VoiceAttack.com\VoiceAttack.exe_Url_aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\0.0.0.1\user.config" JoystickID=aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaab JoystickName="My other Joystick"Is perfectly valid.

"File" is always mandatory, as the application needs to know where to read and write data from, but argument order is not important.

E.G. you could use
Code: [Select]
JoystickName="My other Joystick" JoystickID=aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaab File="C:\Users\User\AppData\Local\VoiceAttack.com\VoiceAttack.exe_Url_aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\0.0.0.1\user.config"




Here's the source code of the application(In C#) if anyone's curious(Or crazy enough to try and update/improve the thing at some point):

EDIT 2024: Source code updated to 8-joystick version
Code: [Select]
using System;
using System.Xml;
using System.Windows.Forms;

namespace Config_Swapper
{
class Program
{
[STAThread]
public static int Main(string[] args)
{
        // Test if input arguments were supplied:
        if (args.Length == 0)
        {
            Console.WriteLine("Error: No input arguments supplied");
            Console.Write("Press any key to exit . . . ");
            Console.ReadKey(true);
            return 1;
        }
       
        string file = "";
       
        int joystickNumber = 1;

        string joystickID = "";
        string joystickName = "";
        string joystickEnabled = "";

        string joystickEnablePOV = "";
        string joystickPOV1Style = "";
        string joystickPOV2Style = "";
        string joystickPOV3Style = "";
        string joystickPOV4Style = "";
       
        bool dumpParameters = false;
       
        foreach (string s in args)
        {
        if (!s.Contains("="))
        {
        Console.WriteLine("Error: \"" + s + "\" must supply a value separated by \"=\"");
            Console.Write("Press any key to exit . . . ");
            Console.ReadKey(true);
            return 1;
        }
        string[] arg = s.Split('=');
        if (string.IsNullOrEmpty(arg[1]))
        {
        Console.WriteLine("Error: No value supplied for \"" + arg[0] + "\"");
            Console.Write("Press any key to exit . . . ");
            Console.ReadKey(true);
            return 1;
        }
        switch (arg[0])
        {
        case "File":
        file = arg[1];
        break;
        case "JoystickNumber":
        if (arg[1] != "1" && arg[1] != "2" && arg[1] != "3" && arg[1] != "4" && arg[1] != "5" && arg[1] != "6" && arg[1] != "7" && arg[1] != "8")
        {
Console.WriteLine("Error: JoystickNumber value must be 1, 2, 3, 4, 5, 6, 7, or 8");
Console.Write("Press any key to exit . . . ");
Console.ReadKey(true);
return 1;
        }
        joystickNumber = Convert.ToInt32(arg[1]);
        break;
       
        case "JoystickID":
        joystickID = arg[1];
        break;
        case "JoystickName":
        joystickName = arg[1];
        break;
        case "JoystickEnabled":
        if (arg[1] == "True" || arg[1] == "1")
        {
        joystickEnabled = "True";
        }
        else if (arg[1] == "False" || arg[1] == "0")
        {
        joystickEnabled = "False";
        }
        else
        {
        Console.WriteLine("Error: JoystickEnabled value must be True or False(Case sensitive)");
Console.Write("Press any key to exit . . . ");
Console.ReadKey(true);
return 1;
        }
        break;
       
        case "JoystickEnablePOV":
        if (arg[1] == "True" || arg[1] == "1")
        {
        joystickEnablePOV = "True";
        }
        else if (arg[1] == "False" || arg[1] == "0")
        {
        joystickEnablePOV = "False";
        }
        else
        {
        Console.WriteLine("Error: JoystickEnablePOV value must be True or False(Case sensitive)");
Console.Write("Press any key to exit . . . ");
Console.ReadKey(true);
return 1;
        }
        break;
        case "JoystickPOV1Style":
    if (arg[1] != "0" && arg[1] != "1" && arg[1] != "2" && arg[1] != "3" && arg[1] != "4" && arg[1] != "8")
        {
Console.WriteLine("Error: JoystickPOV1Style value must be 0, 1, 2, 3, 4 or 8");
Console.Write("Press any key to exit . . . ");
Console.ReadKey(true);
return 1;
        }
        joystickPOV1Style = arg[1];
        break;
    case "JoystickPOV2Style":
    if (arg[1] != "0" && arg[1] != "1" && arg[1] != "2" && arg[1] != "3" && arg[1] != "4" && arg[1] != "8")
        {
Console.WriteLine("Error: JoystickPOV2Style value must be 0, 1, 2, 3, 4 or 8");
Console.Write("Press any key to exit . . . ");
Console.ReadKey(true);
return 1;
        }
        joystickPOV2Style = arg[1];
        break;
    case "JoystickPOV3Style":
    if (arg[1] != "0" && arg[1] != "1" && arg[1] != "2" && arg[1] != "3" && arg[1] != "4" && arg[1] != "8")
        {
Console.WriteLine("Error: JoystickPOV3Style value must be 0, 1, 2, 3, 4 or 8");
Console.Write("Press any key to exit . . . ");
Console.ReadKey(true);
return 1;
        }
        joystickPOV3Style = arg[1];
        break;
        case "JoystickPOV4Style":
    if (arg[1] != "0" && arg[1] != "1" && arg[1] != "2" && arg[1] != "3" && arg[1] != "4" && arg[1] != "8")
        {
Console.WriteLine("Error: JoystickPOV4Style value must be 0, 1, 2, 3, 4 or 8");
Console.Write("Press any key to exit . . . ");
Console.ReadKey(true);
return 1;
        }
        joystickPOV4Style = arg[1];
        break;
       
        default:
        Console.WriteLine("Error: invalid argument \"" + arg[0] + "\"");
            Console.Write("Press any key to exit . . . ");
            Console.ReadKey(true);
        return 1;
        }
        }
       
        if (file == "")
        {
        Console.WriteLine("Error: File has not been specified, this is the only mandatory argument(the application must know where to look for data in order to function!)");
            Console.Write("Press any key to exit . . . ");
            Console.ReadKey(true);
    return 1;
        }
       
        if (joystickID == "" && joystickName == "" && joystickEnabled == "" && joystickEnablePOV == "" && joystickPOV1Style == "" && joystickPOV2Style == "" && joystickPOV3Style == "" && joystickPOV4Style == "")
        {
        dumpParameters = true;
        }

        // instantiate XmlDocument and load XML from file
        XmlDocument doc = new XmlDocument();
        doc.Load(@file);
       
        // get a list of nodes
        XmlNodeList aNodes = doc.SelectNodes("/configuration/userSettings/VoiceAttack.Properties.Settings/setting");

        // loop through all "setting" nodes
        foreach (XmlNode aNode in aNodes)
        {
           // grab the "name" attribute
           XmlAttribute idAttribute = aNode.Attributes["name"];

           // check if that attribute even exists...
           if (idAttribute != null)
           {
              // if yes - read its current value
              string currentValue = idAttribute.Value;

              if (!string.IsNullOrEmpty(currentValue))
              {
                //Basic info
                if (idAttribute.Value == "Joystick" + joystickNumber + "Name")
                {
                if (joystickName != "")
                {
                aNode.InnerXml = "<value>" + joystickName + "</value>";
                }
                else
                {
                joystickName = aNode.InnerText;
                }
                    //Console.WriteLine("Joystick" + joystickNumber + "Name: " + aNode.InnerText);
                }
                else if(idAttribute.Value == "Joystick" + joystickNumber + "Id")
                {
if (joystickID != "")
                {
                aNode.InnerXml = "<value>" + joystickID + "</value>";
                }
                else
                {
                joystickID = aNode.InnerText;
                }
                    //Console.WriteLine("Joystick" + joystickNumber + "ID: " + aNode.InnerText);
                }
                else if(idAttribute.Value == "Joystick" + joystickNumber + "Enabled")
                {
if (joystickEnabled != "")
                {
                aNode.InnerXml = "<value>" + joystickEnabled + "</value>";
                }
                else
                {
                joystickEnabled = aNode.InnerText;
                }
                    //Console.WriteLine("Joystick" + joystickNumber + "Enabled: " + aNode.InnerText);
                }

                //POV Hats
                else if(idAttribute.Value == "Joystick" + joystickNumber + "EnablePOV")
                {
                if (joystickEnablePOV != "")
                {
                aNode.InnerXml = "<value>" + joystickEnablePOV + "</value>";
                }
                else
                {
                joystickEnablePOV = aNode.InnerText;
                }
                    //Console.WriteLine("Joystick" + joystickNumber + "EnablePOV: " + aNode.InnerText);
                }
                else if(idAttribute.Value == "Joystick" + joystickNumber + "POV1Style")
                {
                if (joystickPOV1Style != "")
                {
                aNode.InnerXml = "<value>" + joystickPOV1Style + "</value>";
                }
                else
                {
                joystickPOV1Style = aNode.InnerText;
                }
                    //Console.WriteLine("Joystick" + joystickNumber + "POV1Style: " + aNode.InnerText);
                }
                else if(idAttribute.Value == "Joystick" + joystickNumber + "POV2Style")
                {
                if (joystickPOV2Style != "")
                {
                aNode.InnerXml = "<value>" + joystickPOV2Style + "</value>";
                }
                else
                {
                joystickPOV2Style = aNode.InnerText;
                }
                    //Console.WriteLine("Joystick" + joystickNumber + "POV2Style: " + aNode.InnerText);
                }
                else if(idAttribute.Value == "Joystick" + joystickNumber + "POV3Style")
                {
                if (joystickPOV3Style != "")
                {
                aNode.InnerXml = "<value>" + joystickPOV3Style + "</value>";
                }
                else
                {
                joystickPOV3Style = aNode.InnerText;
                }
                    //Console.WriteLine("Joystick" + joystickNumber + "POV3Style: " + aNode.InnerText);
                }
                else if(idAttribute.Value == "Joystick" + joystickNumber + "POV4Style")
                {
                if (joystickPOV4Style != "")
                {
                aNode.InnerXml = "<value>" + joystickPOV4Style + "</value>";
                }
                else
                {
                joystickPOV4Style = aNode.InnerText;
                }
                    //Console.WriteLine("Joystick" + joystickNumber + "POV4Style: " + aNode.InnerText);
                }
              }
              else
                {
                    Console.WriteLine("Error: Value is empty");
                    Console.Write("Press any key to exit . . . ");
            Console.ReadKey(true);
        return 1;
                }
           }
           else
            {
                Console.WriteLine("Error: No such attribute");
                Console.Write("Press any key to exit . . . ");
            Console.ReadKey(true);
        return 1;
            }
        }
       
        if (dumpParameters)
        {
        string output = "File=\"" + file + "\" JoystickNumber=" + joystickNumber + " JoystickID=" + joystickID + " JoystickName=\"" + joystickName + "\" JoystickEnabled=" + joystickEnabled + " JoystickEnablePOV=" + joystickEnablePOV + " JoystickPOV1Style=" + joystickPOV1Style + " JoystickPOV2Style=" + joystickPOV2Style + " JoystickPOV3Style=" + joystickPOV3Style + " JoystickPOV4Style=" + joystickPOV4Style;
        Console.WriteLine("Arguments for joystick " + joystickNumber + ":");
        Console.WriteLine(output);
        Console.WriteLine("This has been copied to the clipboard(you can use Ctrl-V to paste it when creating a new shortcut)");
        Clipboard.SetText(output);
        Console.Write("Press any key to exit . . . ");
Console.ReadKey(true);
        }
        else
        {
        doc.Save(@file);
        Console.WriteLine("Values have been written to " + file);
        }
return 0;
}
}
}

I don't advise using anything you see in there; It's slapped together with a StackExchange example(I'd say quick and dirty, but while the latter applies it took too much time for the former), it's not flexible at all(It could have been more of a universal config manipulation thing, but that way more work to implement), it probably doesn't use any industry-standard best practices(If it does, that's by accident, I assure you), there was no attempt at optimizing performance(though it's so "simple" that no modern system should care about that), there's plenty of repeated code, there's some error checking though probably not enough(is there ever?), and it's just a novice effort(from a novice programmer/code-together-slapperer).

Also, the forum seems to chew up some of the indentation, so yeah.
« Last Edit: August 29, 2024, 02:01:24 AM by Pfeil »

Blade Runner

  • Newbie
  • *
  • Posts: 8
Re: Changing Virtual Joystick
« Reply #2 on: March 21, 2018, 07:10:09 PM »
Hi Pfeil,

Thanks for putting in so much work on this and it really is much appreciated

I guess I was after something that gets voice attack to refresh the joystick options in general settings automatically and didn't realise how complex what i was asking for is.  The biggest problem i have besides not being very good at programming is that I'm using thrustmaster target to combine two joysticks. As you pointed out  if they don't share the same assignments then it wont work. 
Anyway once again many thanks for your help. 

Gary

  • Administrator
  • Hero Member
  • *****
  • Posts: 2832
Re: Changing Virtual Joystick
« Reply #3 on: March 21, 2018, 08:39:58 PM »
Pfeil is mostly machine.