Getting Vortex nxm links to work on Linux using a desktop entry link handler

266 views Asked by At

I'm trying to get Vortex working with this installer from Github. It works great apart from handling Nexus Mods nxm links (the "mod manager download" ones on Nexus). To tackle this, installer's creator included a .sh script which creates a .desktop file in ~/.local/share/applications as well as binds it as default for nxm links. The problem is, the .desktop file in question doesn't work properly.

vortex-downloads-handler.desktop:

[Desktop Entry]
Categories=Game;Network;
Comment[en_US]=NXM Protocol Download Handler
Comment=NXM Protocol Download Handler
Exec=sh -c "CONFIGPATH=$(sqlite3 \\"/home/doggy/.local/share/lutris/pga.db\\" \\"select configpath from games where installer_slug = \\\\\\"vortex-mod-manager-wine\\\\\\" order by id asc limit 1;\\");sed -i \\"s/^  args:.*$/  args: -d $(printf \\"%%s\\\\\\\\n\\" \\"%u\\" | sed \\"s/^'//;s/'$//;s/\\//\\\\\\\\\\\\\\\\\\\\\\\\//g;s/\\\\\\\\&/\\\\\\\\\\&/g\\")/\\" \\"/home/doggy/.config/lutris/games/$CONFIGPATH.yml\\";env LUTRIS_SKIP_INIT=1 lutris lutris:rungameid/$(sqlite3 \\"/home/doggy/.local/share/lutris/pga.db\\" \\"select id from games where installer_slug = \\\\\\"vortex-mod-manager-wine\\\\\\" order by id asc limit 1;\\")"
GenericName[en_US]=Writes the provided nxm url as an argument to Vortex by editing the Lutris game config for Vortex twice before launching.
GenericName=Writes the provided nxm url as an argument to Vortex by editing the Lutris game config for Vortex twice before launching.
Icon=lutris_vortex-mod-manager
MimeType=x-scheme-handler/nxm-protocol;x-scheme-handler/nxm;
Name[en_US]=Vortex
Name=Vortex
NoDisplay=true
Path=/mnt/diskd/Games/vortex-mod-manager
StartupNotify=true
Terminal=false
Type=Application

Firefox output when clicking an nxm link:

Error: in prepare, no such column: vortex-mod-manager-wine
  lect configpath from games where installer_slug = "vortex-mod-manager-wine" or
                                      error here ---^
sed: can't read /home/doggy/.config/lutris/games/.yml: No such file or directory
Error: in prepare, no such column: vortex-mod-manager-wine
  select id from games where installer_slug = "vortex-mod-manager-wine" order by
                                error here ---^
2024-01-03 18:09:12,572: Starting Lutris 0.5.14
2024-01-03 18:09:12,573: Using NVIDIA drivers 545.29.06 for x86_64
2024-01-03 18:09:12,574: GPU: NVIDIA GeForce GTX 1650
2024-01-03 18:09:12,574: GPU: 10DE:1F82 1458:4026 (nvidia drivers)
2024-01-03 18:09:13,886: No game found in library
2024-01-03 18:09:13,886: Shutting down Lutris

What I get is that the bash code in Exec is to blame, as it has a lot of unneeded backward slashes and wrong quotation marks, and fixing "CONFIGPATH= ... 1;\\") and env ... 1;\\")" parts of the script is easy, the sed part confuses me.

1

There are 1 answers

0
John Bollinger On

What I get is that the bash code in Exec is to blame, as it has a lot of unneeded backward slashes and wrong quotation marks,

It looks like one set of wrong quotation marks, maybe. And it's probably missing some backslashes. I don't think it has any extras for what it's trying to escape.

and fixing "CONFIGPATH= ... 1;\\") and env ... 1;\\")" parts of the script is easy,

Are you sure?

the sed part confuses me.

Ok.

In the first place, the value for an Exec key in a desktop file must follow quotation rules that differ from the shell's. That is first and foremost an issue with the syntax of the desktop file, not with the Bash syntax inside. Specifically,

  • commands and arguments in the value of an Exec key may be quoted with double quotes, but only in full. They must be quoted if they contain any of a relatively large set of reserved characters.

  • inside such a quoted argument, certain special characters, including but not limited to " and \, must be escaped with a preceding backslash to be taken literally.

  • additionally, however, there is a separate rule for backslash-escaping characters in the value of any desktop entry key of type string, and this combines with the Exec-specific requirement of escaping backslashes inside a quoted argument.

  • The desktop file spec provides for interpolation of certain data into the command, and codes for this are introduced by a % character. Literal % characters must be doubled.

Performing all that dequoting, plus converting command-separating ; characters to newlines, results in this sequence of commands to be executed via /bin/sh:

CONFIGPATH=$(sqlite3 "/home/doggy/.local/share/lutris/pga.db" "select configpath from games where installer_slug = \"vortex-mod-manager-wine\" order by id asc limit 1;")

sed -i "s/^  args:.*$/  args: -d $(printf "%s\\n" "%u" | sed "s/^'//;s/'$//;s/\//\\\\\\//g;s/\\&/\\\&/g")/" "/home/doggy/.config/lutris/games/$CONFIGPATH.yml"

env LUTRIS_SKIP_INIT=1 lutris lutris:rungameid/$(sqlite3 "/home/doggy/.local/share/lutris/pga.db" "select id from games where installer_slug = \"vortex-mod-manager-wine\" order by id asc limit 1;")

Note that per the desktop file spec, the %u in there will be converted to a URL designating the file to be opened by the (overall) command.

The first and third errors

Two of the errors reported are the same:

Error: in prepare, no such column: vortex-mod-manager-wine
  lect configpath from games where installer_slug = "vortex-mod-manager-wine" or
                                      error here ---^

These are coming from sqlite3 in the identical command substitutions that make up the bulk of the first and third commands, and they pertain to this SQL where clause:

where installer_slug = "vortex-mod-manager-wine"

(which is a result of another level of de-quoting by the shell). In most SQL dialects, literal strings must be delimited by single quotes. In many SQL dialects, double quotes have a lexical-only quoting significance. Both of those apply to SQLite. The net effect is that the where clause is interpreted as trying to filter for rows in which the value of the installer_slug column is the same as the value of the vortex-mod-manager-wine column. The issue is that the latter does not actually name an existing column.

More likely than not, the "vortex-mod-manager-wine" was meant to be a literal string, which would be spelled 'vortex-mod-manager-wine'. That, you can fix by just switching those quotes.

But it is also possible that the code is correct and the DB wrong. That might happen, for instance, if the desktop file has fallen out of sync with the software. If that's the case then you should seek assistance from the project developers.

The second error

This ...

sed: can't read /home/doggy/.config/lutris/games/.yml: No such file or directory

... is from sed, yes, but it's not about the given sed expression. It's just about the file name. This is expressed in the dequoted command as "/home/doggy/.config/lutris/games/$CONFIGPATH.yml". The error message shows that $CONFIGPATH is expanding to nothing, and that should not be surprising because that variable is (supposed to be) set by the previous command, which is erroring out. Thus, this particular error is just fallout from the one discussed above.

Additional

The sed command is just processing the provided URL, as expressed in shell-compatible format via printf. It removes a leading and trailing single quote, and it appears intended to convert forward slashes (/) to backslashes (\), but I think it is under- (not over-)quoted for that. It also appears to be trying to do something with ampersand (&) characters -- maybe backslash-escaping them -- but this, too, seems underquoted.

Possible correction

Supposing that the SQL problem is wrong quotation marks, and that I have correctly deduced the full intent of the sed expression, the sequence of commands that seem to be wanted is

CONFIGPATH=$(sqlite3 "/home/doggy/.local/share/lutris/pga.db" "select configpath from games where installer_slug = 'vortex-mod-manager-wine' order by id asc limit 1;")
sed -i "s/^  args:.*$/  args: -d $(printf "%s\n" "file:///some/file" | sed "s/^'//;s/'$//;s/\//\\\\//g;s/\&/\\&/g")/" "/home/doggy/.config/lutris/games/$CONFIGPATH.yml"
env LUTRIS_SKIP_INIT=1 lutris lutris:rungameid/$(sqlite3 "/home/doggy/.local/share/lutris/pga.db" "select id from games where installer_slug = 'vortex-mod-manager-wine' order by id asc limit 1;")

Joing those with semicolons and quoting them as required for an argument in the value of an Exec key gets us:

"CONFIGPATH=\$(sqlite3 \"/home/doggy/.local/share/lutris/pga.db\" \"select configpath from games where installer_slug = 'vortex-mod-manager-wine' order by id asc limit 1;\");sed -i \"s/^  args:.*\$/  args: -d \$(printf \"%s\\n\" \"file:///some/file\" | sed \"s/^'//;s/'\$//;s/\\//\\\\\\\\//g;s/\\&/\\\\&/g\")/\" \"/home/doggy/.config/lutris/games/\$CONFIGPATH.yml\";env LUTRIS_SKIP_INIT=1 lutris lutris:rungameid/\$(sqlite3 \"/home/doggy/.local/share/lutris/pga.db\" \"select id from games where installer_slug = 'vortex-mod-manager-wine' order by id asc limit 1;\")"

Putting the Exec=sh -c in front of that, escaping backslashes one more time, and doubling the literal % gets us

Exec=sh -c "CONFIGPATH=\\$(sqlite3 \\"/home/doggy/.local/share/lutris/pga.db\\" \\"select configpath from games where installer_slug = 'vortex-mod-manager-wine' order by id asc limit 1;\\");sed -i \\"s/^  args:.*\\$/  args: -d \\$(printf \\"%%s\\\\n\\" \\"file:///some/file\\" | sed \\"s/^'//;s/'\\$//;s/\\\\//\\\\\\\\\\\\\\\\//g;s/\\\\&/\\\\\\\\&/g\\")/\\" \\"/home/doggy/.config/lutris/games/\\$CONFIGPATH.yml\\";env LUTRIS_SKIP_INIT=1 lutris lutris:rungameid/\\$(sqlite3 \\"/home/doggy/.local/share/lutris/pga.db\\" \\"select id from games where installer_slug = 'vortex-mod-manager-wine' order by id asc limit 1;\\")"

, which is my best guess as to what was intended.