Tags: Lua / WoW AddOn / Windows / Networking (kinda?) / Protocol design
World of Warcraft (WoW) is a massively multiplayer online role playing game (MMORPG), which allows players, much to my delight, to write and install homemade user interface extensions (AddOns).
This functionality is mainly intended to allow developers to provide custom looking user interfaces, or even provide functional little helpers. The problems however arise when we start looking at farming bots, which are used by cheaters to automate tedious parts of the game, giving their users an unfair advantage. Such bots are run outside of the game, emulating human interface devices, and get their input, what the game is doing, from looking at audio levels, pixel values, or even the process memory itself. Without saying, unrestricted access to the games lua api would be bad.
This is why the games developers, Activision Blizzard, have gone out of their way to prevent AddOns from having any means of communication to the outside world. No filesystem access, no bluetooth, no networking, no nothing. Whilst this surely does something against cheaters, it also prevents innocent people like me from doing fun stuff.
Activision Blizzard did either not care about, or did not think of people just serializing information. As a proof of concept, I've created a small AddOn that receives an array of bits as data, which it then relays by blinking a small square on screen in correspondence to the bits values. A client run in an external program can then "listen" to this square to receive information.
So it works. One could say... I'm in 😎. It's just a simplex, but that's good enough. Obviously this is really slow and unstable. So here's what I came up with... Let me show you it's features:
Azernet, as derived from Azeroth, the world WoW takes place in, and networking, has to solve several problems:
- Provide fast data transmission
- Provide reliable data transmission
- Provide a multicast capable interface
So let's see how we're tackling these:
Let's first take a look at how we are transmitting raw bits.
To provide speed, Azernet does not utilize just one square, but many. To squeeze out even more performance, azernet has a defined bit-depth for each color-channel. Meaning, it subdivides the range [0, 255] into segments, each corresponding to a sequence of n, n≤8 bits.
This raises the question, why not just transmit a whole byte per color channel? Because of redundancy. One special effect even just slightly altering the color value would ruin the whole frame. And this happens a lot. We need to give the channel at least some leeway to account for channel noise.
What about this big hunk on the left?
That's the so called 'sync lane'. It contains the clock signal (it flips every time the socket updates), the bit depth used in the channels (small squares), how many channels the socket operates with, and a termination bit that indicates that the current network-frame has finished transmitting. The information about bit debth and amount of channels is important as they are dynamic and can be changed at runtime.
For more detailed information, see the socket specification.
Let's take a look at what's happening on a more abstract level. The data link layer does three important things:
We never ever, no way, want to receive a broken package and interpret it as a valid one. So the data link layer hamming-encodes the package body as hamming8-4. We are not doing any error correction here. We are using all the redundancy to identify broken packages, and dropping them. This way we identify more broken packages than if we were actually using error correction.
Each package header has a rolling identifier, which overflows once it reaches 216. This is important, since packages get sent multiple times by default, with a delay inbetween. It is our only way to re-send broken packages, as the connection is a simplex. There is no way for the client to talk to our in-game server, and say I didn't receive that packace correctly.
To enable multicasting, packages get assigned an application address. The data link layer at the receiving end
just drops packages that are designated for another application. The address
ffff is reserved as broadcast.
Also noteworthy are checksums over the package body and header.
For more detailed information, see the frame specification.
The result is a functional simplex networking interface for AddOns in World of Warcraft. The average payload transmission rate is about 50kbit/s, which is definitely fast enough for plaintext/json. Corrupt frames basically never make it past the data link layer, yet still the vast majority of sent packages arrive. It is important to tweak the InterBitGap (IBG), which is the delay between socket updates, the BitDepth, which is how many bits are transmitted in one color channel at a time, and the DataLaneSubdivisionFactor (DLSF) (how many lanes there are). These vary on the host systems performance. For me, this configuration works fine, with many clients running:
- IBG: 0.1
- DLSF: 3 (-> 512 data lanes)
- BitDepth: 4
It is very important to turn on Vsync, Gsync or Freesync. Every frame rendered, but not displayed on the monitor, is potentially lost data, as it cannot be read by the clients.
Azernet can bee used to, for example, log geopositional data in an external application.
*These two gifs are not actually related and have been recorded at completely different points in time.
I have used Azernet to record a playthrough timelapse of the "Shadowlands" extension pack, logging geopositional information for statistics, for fun.