Tuesday, September 19, 2006

Yaahhr, the Talk Like a Pirate MSN Addin

Seeing as today is Talk Like a Pirate Day (http://www.talklikeapirateday.com/wordpress/), I thought I'd have a look at the relatively new addin api provided for MSN Live Messenger. To head off with the conclusion, I must say I'm not 100% pleased. Obviously there are safety issues to consider, but I still believe that the API has come out somewhat crippled. The receiver will be notified that a plugin is sending the messages, and the sender will literally be spammed with notifications along the lines of "This isn't really you typing you know... You know? Are you sure you know? I'll just add some large black lines and vertical spacings, so you're really sure!".

Anyhoo, I wrote up a quick addin which replaces matched words and sentences from outgoing messages. If a match is found in the outgoing string, a replacement is randomly selected from their pirate counterpart. "Hi" would be "Avast", "Ahoy", "Arrr", or who-knows-what :)

Instructions, downloads and such

The addin can be found in an installation package at http://www.indev.no/Yaaahhr_Install.exe. Running this will enable addins in your Messenger, granted that you have got version 8 installed, and put the plugin and configuration in a folder named "Indev\Yaaahhr" within your "Program Files". Once installed, you can restart MSN, go to the options panel and select the new "Addins" tab. From there you can navigate to the "ThePirateSays.Yaaahhr.dll" found in the before-mentioned folder. Next, go back to the main MSN window and open the drop down box which holds your current status (right next to your display picture). Select "Turn on Yaaahhr", and you should be good to go.

The default pirationary (duh) isn't all that broad. Luckily, it can easily be extended. After installing the addin, you will have a shortcut on your start menu's programs folder, under "Yaaahhr!", called "Yaaahhr Dictionary Configuration". This links to a .xml file, which should pretty much explain itself. Play around with it.

The code

Long story short, just about as short as the actual API documentation (http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dnlive/html/messengeraddin_sdk.asp), here's my code:



using System;
using System.Collections.Generic;
using System.Text.RegularExpressions;
using Microsoft.Messenger;

namespace ThePirateSays
{
public class Yaaahhr : IMessengerAddIn
{
private MessengerClient m_client;
private Random m_random;
private int m_prefixChance;
private string[] m_prefixPhrases;
private List<DictionaryPair> m_dictionaryPairs;

public void Initialize(MessengerClient client)
{
m_client = client;
m_random = new Random();
m_dictionaryPairs = new List<DictionaryPair>();

LoadConfiguration();

m_client.AddInProperties.FriendlyName = "Yaaahhr";
m_client.AddInProperties.Description = "You're a pirate!";
m_client.AddInProperties.Creator = "Einar Otto Stangvik";
m_client.AddInProperties.Url = new Uri("http://www.indev.no");

m_client.OutgoingTextMessage += new EventHandler<OutgoingTextMessageEventArgs>(this.OnOutgoingMessage);
}

private void LoadConfiguration()
{
try
{
ConfigHelper ch = new ConfigHelper("yaaahhr.xml");
ch.LoadPrefixPhrases(out m_prefixChance, out m_prefixPhrases);
ch.LoadDictionary(ref m_dictionaryPairs);
}
catch
{
throw new Exception("Configuration could not be loaded.");
}
}

public void OnOutgoingMessage(object sender, OutgoingTextMessageEventArgs args)
{
string toSend = args.TextMessage;

foreach(DictionaryPair pair in m_dictionaryPairs)
{
toSend = RandomlyReplaceStrings(toSend, pair.matches, pair.replacements);
}

toSend = RandomlyPrefixString(toSend, m_prefixChance, m_prefixPhrases);

if (toSend.CompareTo(args.TextMessage) != 0)
{
args.Cancel = true;
m_client.SendTextMessage(toSend, args.UserTo);
}
}

private string RandomlyPrefixString(string input, int chanceOfPrefix, params string[] prefixes)
{
if (m_random.Next(0, 100) < chanceOfPrefix)
{
return prefixes[m_random.Next(0, prefixes.Length)] + " " + input;
}
return input;
}

private string RandomlyReplaceStrings(string input, string[] matches, params string[] replacements)
{
string match;
string replacement;

match = "(" + String.Join("|", matches) + ")";
replacement = replacements[(replacements.Length > 1) ? m_random.Next(0, replacements.Length) : 0];

return Regex.Replace(input, "(?i)(?<R>^|\\W)" + match + "(?<L>$|\\W)", "${R}" + replacement + "${L}");
}
}
}


The heart of this thing is the OnOutgoingMessage event handler. In the Initialize function, which happens to be the only function enforced by the IMessengerAddIn interface, I add my callback to the list of callbacks which receive notifications for outgoing messages. In this handler, I do some string replacement (this is not at all dictionary optimized), cancel the original message and ship off a new one.

The match and replacement texts are parsed from a the tag-along xml file, mentioned earlier, which is installed to the same folder as the dll. Feel free to change it, reload the plugin and go for another ride.

Sunday, September 17, 2006

Easily capturing application crash dumps

I've recently found myself in need of getting software crash dumps from computers to which I have no direct access. A tool I released some time ago, FlashMute, which allows people to mute flash movies / browsers exclusively, has gotten a couple of reports of making IE not start. Since these are only two reports, out of between 50.000 and 100.000 downloads + magazine co-distributions, I'm fairly certain the problem isn't inside FlashMute itself. It's more likely that the error is caused by an incompatibility issue with another piece of software (or malware) running on the computers in question.

Having to fetch a crash dump from these computers, without the users having to install complex debuggers, I looked to the SetUnhandledExceptionFilter function (see http://msdn.microsoft.com/library/en-us/debug/base/setunhandledexceptionfilter.asp?frame=true). As it turns out, though, the new C Runtime Libraries (CRT) forces the use of Dr. Watson when a crash happens (see http://forums.microsoft.com/MSDN/ShowPost.aspx?PostID=299090&SiteID=1 for a discussion on why) . This is done by resetting whatever custom filter is specified by previous calls to SetUnhandledExceptionFilter, prior to calling through. As a quick resort, I wrote up an application which uses the Microsoft Detours library to override attempts to reset the custom filter. I wrapped it up in a small command line application, which can easily be sent to a customer (or similar troubled being), to catch whatever happens prior to a crash. Upon being notified of an unhandled exception, the application will write a stack and memory dump to file, which can be sent back to you for closer analysis.

You can find the current version of the application at http://www.indev.no/CrashCatcher.rar. Be sure to give me a tell if you come across missing functionality, or similar :) Things to try with the application: "crashcatch --help" and "crashcatch crash.exe". That ought to get you started.