octalzeroes

URI-Scheme with PowerShell wrapper

published on 20 Feb 2014, tagged with powershell windows irssi quakelive putty gaming

Introduction

Among my hobbies are games and one of the games that I play is quakelive. If you're not familiar with this game it's FPS game with many different gametypes like Capture The Flag and Team Deathmatch. On the IRC network quakenet there is a channel called #tdmpickup where people come together and sign up for games where teams are randomized based on player stats. But hey, lets get to the point.

A server will be posted by a user in the channel and a bot will take the URL posted and retrieve the ip-adress and password for the server and post it like this:

Preben: /connect 91.198.152.137:27022;password tdm

The classic fashion would be to start up the Launcher to QuakeLive, hit login. Once the game starts up you bring down the console and paste the command above and you would start connecting to the server.

I wanted to try a different approach and make these clickable links that would automatically start up my game and connect to the server.

URI association

So I asked myself, how do I make my own URI-association in windows? A quick search lead me over to stackoverflow. The most upvoted response shows the structure of a registry entry. Mine ended up looking like this:

Windows Registry Editor Version 5.00

[HKEY_CLASSES_ROOT\qlive]
@="URL:QuakeLive Protocol"
"URL Protocol"=""

[HKEY_CLASSES_ROOT\qlive\shell]

[HKEY_CLASSES_ROOT\qlive\shell\open]

[HKEY_CLASSES_ROOT\qlive\shell\open\command]
@="C:\\Windows\\system32\\WindowsPowerShell\\v1.0\\powershell.exe -executionpolicy bypass -file \"x:\\scripts\\qlive-launcher.ps1\" -uri \"%1\""

This can be pasted into a file with the .reg extension and merged into your registry. What's been done here is that the qlive:// URI has been associated with powershell.exe that in turn executes a script which I wrote. PowerShell uses something called execution policy and unless -executionpolicy bypass is passed to powershell.exe it wouldn't let me run my own script from the registry. The next parameter -file takes the full path to the script as an argument. Lastly there's -uri %1 which passes the URI as an argument to the script.

PowerShell

Let's take a look at the PS-script I wrote:

Param
  (
    [Parameter(Mandatory=$false)]
    [ValidatePattern("qlive://[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+:[0-9]{5}/\S+")]
    [string]$uri
  )

$log_file = "$Env:TMP\quakelive_launcher.log"
if (Test-Path $log_file) {
  $path, $arg = Select-String -Path $log_file -CaseSensitive -Pattern "^(?:Engine path|Launch parameters): (.*)" | 
    %{ $_.Matches[0].Groups[1].Value }
} else {
  [System.Reflection.Assembly]::LoadWithPartialName("System.Windows.Forms")
  [System.Windows.Forms.MessageBox]::Show("log missing, use Launcher and try again")
  exit
}
if ($uri) {
  $arg += $uri -replace 'qlive://([^/]*)/(.*)', '+wait 500 +password $2 +connect $1'
}
Start-Process "$path\quakelive.exe" "$arg"

Short and simple. If there's any doubts i'll try to explain briefly what it does:

  • The script takes an optional argument which is a URI in the form of qlive://ip:port/password
  • A string $log_file is created that contains the full path to the log file
  • If the file exists we assign $path and $arg. The value for these variables are obtained from $log_file where we match two lines starting with "Engine Path" and "Launch parameters". The value after the semicolon is then matched and stored in a group that are passed through a pipe. The other side of the pipe uses the % operator (it's just an alias for ForEach-Object) that will take the two matched patterns and output the values on one line each
  • If the file does not exist a .NET object is created that displays an error message in a message box
  • If the -uri parameter was defined we append the +connect and +password parameters to our arguments. I realized that the use of +wait was necessary due to the client not being authenticated as soon as it starts up. If you attempt to connect before the game is done initializing it will just kick you out and not even load the site
  • Now we're ready to launch the process and pass the arguments

Note that this script could also be used to create a shortcut to the game since the parameter is optional. Sometimes it's a good idea to use the launcher to make sure your game is up to date but each time you hit login it authenticates you all over again which is not necessary.

%SystemRoot%\system32\WindowsPowerShell\v1.0\powershell.exe -file "x:\scripts\qlive-launcher.ps1"

Irssi & trigger.pl

The irc-client I've used for over a decade is irssi. This client has a lot of user contributed script with one in particular which I need to finalize my whole concept.

As mentioned earlier the bot would put a legitimate CVAR that I can paste into the client. But I'd rather just click it instead and have it do all the work. Since "/connect" isn't really a valid URI-scheme I'm going to have to rewrite the message slightly. This is done using an excellent script called trigger.pl. After loading the script into irssi I will simply type the following:

/trigger add -publics -masks '*!*@ElPrebsi.users.quakenet.org' -regexp '^/connect ([^;]*);password (\S+)' -replace 'qlive://$1/$2'

This line will match public messages sent by the user with the -mask defined above. The message has to match the regex and will then rewrite it so that it comes out like this:

Preben: qlive://91.198.152.137:27022/tdm

Putty

My problem was that I had a fork of putty already acquainted with URI-schemes but unfortunately it would launch them in my browser. Luckily I came across another fork called putty-url that states the following on its webpage:

Uses ShellExecute, meaning support for arbitrary protocols (e.g. Spotify) with an appropriate regexp

We only need to make sure that it's matched. Bring up the settings for the session and locate the following path:

├+- Session
├+- Terminal
└+- Window
    └── Hyperlinks

Uncheck Use the default regular expression and simply add qlive into the existing pattern below. We want the beginning of the regex to look like below:

((((https?|ftp|qlive)