Let's go back 5 years ago during the World of Warcraft beta. I was working on Cosmos UI, a projects that aimed to improve the World of Warcraft interface.

As interface modification was not officially supported by Blizzard, we went ahead and directly modify the game files written in Lua and XML. We provided the new edited files for the thousands of people using Cosmos.

Previous Method

Let's take a concrete example. We need to analyze the combat log in order to display a damage chart. The game calls the function ProcessChatMessage with all the required information.

function ProcessChatMessage(message, source, channel) {
  // ... Default Code ...
}

Note: World of Warcraft uses Lua as its programming language but I will use Javascript for this blog post as you, my readers, are most familiar with it. The technique remains exactly the same.

In order to process the combat log, we simply add some code at the beginning of the function in order to execute some action if the channel is COMBAT_LOG_CHANNEL. Nothing really extraordinary.

function ProcessChatMessage(message, source, channel) {
  if (channel == COMBAT_LOG_CHANNEL) {
    // ... Addon Code ...
  }
 
  // ... Default Code ...
}

However this solution is extremely intrusive as you have to change alter the code. Every time Blizzard releases a new version, we had to make a diff of all the files that changed and manually update all our code base. While we were doing this process, all the user of our interface could no longer play as they depended on us. This was an extremely stressful process on both parts.

This issue along with Blizzard servers being unstable helped start the meme Patch Day, No Play.

Hook Solution

I found out a non-intrusive way to alter the behavior of the game. With that technology unlocked, we started to modularize our codebase. Blizzard noticed and when our migration was nearly over, they added the ability to officially use modules (called Addons).

The technique is extremely simple. It only requires the language to have first class function. You store the function you want to alter in a variable (1). Then you override that function with yours (2), that will call the stored one to keep the default behavior (3).

// (1) Backup the function
var defaultProcessChatMessage = ProcessChatMessage;
 
// (2) Override the function
ProcessChatMessage = function(message, source, channel) {
  if (channel == COMBAT_LOG_CHANNEL) {
    // ... Addon Code ...
  }
 
  // (3) Call Default Code
  defaultProcessChatMessage(message, source, channel);
}

This solution gives you a great control as you can add pre and post processing and even skip the old code if you need to. It works for most use-cases unless you have to modify what is inside of the function.

The technique has several important features:

  • The target function does not need to be written in a special form. It does not require to support hooking and does not need to be registered as being hookable somewhere.
  • Multiple hooks can be added independently. For example if another addon called VjeuxAddon wants to manage all the messages that start with VjeuxAddon:, you can just write the following code somewhere in the addon:
    var defaultProcessChatMessage = ProcessChatMessage;
    ProcessChatMessage = ProcessChatMessage(message, source, channel) {
      if (message.match(/^VjeuxAddon:/) {
        VjeuxAddon.processChatMessage(message, source, channel);
      } else {
        defaultProcessChatMessage(message, source, channel);
      }
    }
  • The technique is decentralized. You don't have to have a system that coordinates all the hooks. Each addon sees only his own hooks.
  • No library is required to use this technique.

Bonus Links

See these links for non-intrusive hooking techniques in other languages:

If you liked this article, you might be interested in my Twitter feed as well.
 
 

Related Posts

  • September 17, 2011 WoW Interface Anchor Positioning (10)
    I've always found CSS positioning with both float and position: absolute/relative hard to work with. I want to introduce to you an alternative way borrowed from the World of Warcraft Interface: Anchors. Anchor The concept is extremely simple. You can tell where you want the element […]
  • September 11, 2011 World of Warcraft HTML Tooltip Diff (3)
    MMO-Champion is a World of Warcraft news website. When a new patch is released, we want to show what has changed in the game (Post Example). An english summary of each spell change is hand written, but we want to show the exact tooltip changes. jsHTMLDiff is available on […]
  • August 4, 2009 Project – CosmosUI (3)
    CosmosUI is an open source interface modification of World of Warcraft. Many of the CosmosUI additions were later implemented by Blizzard on the default interface. I had been doing Warcraft III map making for more than a year when World of Warcraft has been leaked. This was really […]
  • December 7, 2011 Automatic Links with Trie (1)
    On MMO-Champion, we often paste World of Warcraft patch notes taken from Blizzard. The main problem is that it's plain text. We want to be able to add links to all the spells, quests, zones ... This way people can mouseover and see the description. It helps figuring out what […]
  • September 24, 2011 Javascript: Cyclic Object Detection (17)
    URLON.stringify() suffer from a problem, when passed an object that contains a cycle, it will never stop. This article shows 3 techniques in order to detect if an object is cyclical. Edit the object: Mark In order to detect a cycle in an object, the method we learn at school is to […]