Initial backportFunction()

I've finally finished my first draft at a function that will replace an existing non-configurable JS API function in the global scope with a custom function I supply.

It achieves the following goals I'd set myself:

  • The result must be completely transparent to JS scripts - other than including a backport library there should be no other code changes or noticeable effects.
  • The original functions must still be easily available
  • It should be easy to determine if a function has been backported
  • It should be trivially easy to enable backporting on JS API functions, which are all read-only and non-configurable.
Here's the result so far:
function backportFunction(sFuncName,fNew) {
  // store funcs where eval can get at them
  eval("backportFunction.fOld = "+sFuncName);
  backportFunction.fNew = fNew;
  // make sure it's not already backported
  if (!!backportFunction.fOld.backport) return;
  // construct the backport
  var backport = "function "+sFuncName+"(){return "+sFuncName+".run.apply(this,arguments);} "+sFuncName+".backport = backportFunction.fOld; "+sFuncName+".run = backportFunction.fNew;";
  // now do the eval
  eval.call(null,backport);
  // clear up our mess
  delete backportFunction.fOld;
  delete backportFunction.fNew;
}

That took a surprisingly long time to build – almost 3 hours due to difficulties testing and debugging in Warzone's JS environment, not to mention some interesting side-effects caused by the way JS API functions are created.

Anyway, now let's say I want to customise the JS API's enumDroid() function. It's read-only, non-configurable, and to this day I still have no idea how to determine where its actually stored despite doing lots of sniffing around the WZ JS environment to find out just what exactly is available to us.

I'll just use something simple in this example – I'll keep the same functionality as the original enumDroid() but I'll send a "running" message to the console whenever enumDroid() is called:

backportFunction("enumDroid",function() {
  console("running");
  return enumDroid["backport"]();
});

After making that call, the global enumDroid() function is now under my control. Whenever it's called, it transparently calls the new function I've passed in. As you can see, enumDroid() now has a new property ".backport" that's a reference to the original enumDroid() function from the JS API - so I can still get at the original whenever I want to.

In my script I just use enumDroid() as usual, my little hack is completely transparent. Every time enumDroid() is called, I get a "running" message in the console window to show it's working. Awesome win!

My next task is to think of a neat way to modularise all the things I want to apply to enumDroid(). For example, there's a bunch of properties that would randomly appear, some that were buggy and some that were missing depending on the Warzone version the script is running in. I want to find a way of dynamically adding "hacks" that fix bugs, backport features or extend what the JS API functions do.

The various enum functions are a good place to start as they all work in roughly the same way - you call them with some parameters and they return a list of objects. I've already got working prototypes for backporting enumBlips() to 3.1 Beta 1, and adding things like the .canHitAir and .canHitLand properties to droid objects in enumDroid() and enumStructure(), plus loads of other useful bits and pieces.

Anyway, it's 5:30am and I've got a day full of meetings tomorrow so I need to head to bed.