A while ago, I avidly played a MMORPG called Asheron’s Call, from Turbine Games. Indeed, so great a fan was I that I collaborated with a group of others in the creation and maintenance of what is still one of the most advanced “third party” systems for any game. Consisting of custom network, GUI, input and management APIs, it integrated so perfectly into the AC client that it’s indistinguishable from the ingame system. It became very popular, and is called Decal.
Recently, AC released a new expansion pack, “Throne of Destiny”, in which Turbine significantly reworked the client. Well, our wonderful integration is no longer quite so perfect. In fact, it’s entirely broken. So, along with much of the same crew as before, we return to stick that Decal back onto AC, that it might continue to provide useful tools without (we hope) allowing a great deal of evil.
As I’ve just returned from a fortnight of moor walking, I figured I’d jump in with something I’m reasonably familiar with – the actual display of the GUI components. As this article is targeted at people who might not be programmers, I’ll quickly go over the concepts you’ll need to understand. Experienced folk can ignore this bit, and I ask their forgiveness for my (over) simplifications.
When you play Asheron’s Call, you’re sitting in front of a computer. This might sound obvious, but at the end of the day, computers are incredibly stupid. All they can do it go through a huge list of instructions, stepping from one to the next, adding a number, moving it around in memory, and little more. As you can imagine, with such limited capacity, it takes a lot of these instructions to get anything done. Millions, in fact. Hundreds of millions.
As humans, we find that very hard to deal with. After all, when I want to write down “Hello” onto a piece of paper, I don’t create a huge list of instructions telling me how to move my hand one millimetre at a time – I just do it! So, we make it easier to program our computers by writing languages that help us express what we want to do more clearly.
A popular method of doing this is via “object oriented languages”. These languages let us talk to the computer in terms we can more easily deal with. Rather than a big list of instructions, we can say “Take this “Hello” object and “Draw” it onto this “Paper” object”. The people who programmed the “Paper” object have already dealt with the drawing instructions for us, making our job much clearer, so we can get things done!
In Decal, we deal with a lot of objects. There are objects representing plugins, controls on the screen, events from the game and many others. When we’ve finished creating all these objects, we “compile” our objects back into these huge lists of instructions that we can send to you, the player.
We also deal with some objects we didn’t write, some that are provided by Turbine. This is the task of finding the fabled “memlocs”. Turbine wrote lots of objects of their own, and compiled them into Asheron’s Call. We can’t see them, as all we have are the big instruction lists. When finding the “memory locations”, we go through these lists, and work out which chunks of it belong to which object. We can then note them down and use them just like Turbine did.
Right, back to the problem. When the new client was released, one of the major changes was the graphics update. To facilitate this, a change was made, from DirectX 6 (a set of objects that deal with graphics and multimedia) to DirectX 9. Our original code worked with DirectX 6, and it doesn’t much like Direct X 9.
So, what’s first? Well, lets explain how Decal gets from our hearts to your screen in the first place. When you run Decal, it sets up “hooks” into your system that sit and watching what you run. They don’t do anything until they see AC start up, when they suddenly spring to life, and step in. “Excuse me, AC”, they say, “I’ll just stop you for a second”. Before the poor game knows what’s happened, BAM, it’s over! We’ve been inside it, and looked around. We find the objects that it’s creating (in this case, the graphics objects), and we replace them with some of our own.
Our fake objects are so perfect that when AC wakes up, it doesn’t know what’s happened. It carries merrily on, using our new objects when it would have used its own. Except that the new objects are secretly reporting everything to us, so we know what’s happening. When the graphics object reports to us that it’s drawing your screen, we wait until it’s done, and again step in at the last moment. It’s in this period that we can draw onto your screen. This is the code that was broken when they upgraded the graphics engine.
So, how do we fix this? Well, the first thing we need is some new fake objects, ones that’ll work with DirectX 9. They’re easy to create, and after that’s done (more by Hazridi before I came back), we end up with an AC that we can draw on again. To prove these new objects work, we’ll draw a test.
Great, we can draw inside AC again. Next up, we need to start putting in the original graphics. We’ll hook up our old GUI objects with our new drawing object, and we get…
Hmmm. That text looks suspicious. What’s going on here!? Well, after poking around, it turns out that our new fake objects are not quite so indistinguishable from the real ones anymore. When we sneak in and try and draw, the objects AC created are fighting back! DirectX 9 does not allow a lot of what DirectX 6 does, and as such, our old techniques are not quite so effective. So, let’s sit back, take stock, and examine the options.
In our corner, we have a set of graphics designed for drawing onto an object that no longer exists. In fact, all plugins are likely to be using a similar system to us. That means we can’t just change our code and walk on – all the plugins will break! We need to find a solution that will keep everyone happy. After some thought, three options come to mind…
- We could stop using the objects, and do everything ourselves
- We could try and update half our code so that it integrates better with the new system. Perhaps rather than drawing right onto the screen, we could create a 3D model and draw onto that?
- We could blackmail the original objects into letting us do what we want
As with all such choices, there are no right or wrong answers. All our options are equally plausible, so we’ll have to try them all.
[Sound of keyboard]
OK! After option 1, we have a problem. By bypassing the way that we’re “meant” to work, we have massively slowed the system down. You see, if we refuse to use the objects we’re given, we have to manually look at all the images and decide what to paint, and where. But your system doesn’t like this – it hates people looking at images that have already been created. As such, everything slows down to a crawl (in fact, only 1 frame per second). So, option 1 in a failure, and 100 lines of code vanish into the recycle bin.
Option 2! This one sounds fun, it has 3D in the title! After clattering away for a while, option 2 does work, but has a similar problem to option 1. In converting our old style image to a new style 3D object, we have to do a lot of work every frame. Decal redraws itself all the time (as do plugin HUDS), so we have to constantly convert between what we already can do, and what we need for the new client. End result, 1 frame per second again. Sigh. 150 lines down the tube.
So, option 3 it is. Now, being honest, most of the performance problems from options 1 and 2 have been in preserving what we call the “colour key”. When Decal draws onto your screen, it keeps a special colour as “transparent”, so then when plugins appear on your screen, this colour disappears and you can see the game underneath. Old versions of the DirectX did this for us, but the ability is largely gone from DirectX 9. Options 1 and 2 ended up having to create big copies of the screen, and draw onto them, them put them back onto the game again, obeying this colour key.
But, if we manage to persuade AC that we can draw right onto it again, we’ve solved most of our problems – we don’t have to deal with big copies! After some fiddling, it turns out that the back buffer (the bit that it actually drawn onto by AC every frame) is almost right for what we want. So, we use our fake objects to force AC to create a back buffer that’s right for us. It’s still good enough for AC not to notice, and it lets us work our magic. Hence, our end result.
Those of you with good vision will notice some lurid turquoise in that image. That’s the colour key inside the plugin windows – our option 3 doesn’t quite solve that. So, tune in another day, and you might find part deux, whereby hideous colours are banished forever!
Oh, and for those of you interested, the end solution was 2 lines. Yes, 0.7% of the code written for this project. So, when reading programs, remember – for every line of code you see, there are at least 10 dead lines lying somewhere by the roadside. Salute these dead lines, and remember, they died in the cause of good software.
Adam Wright (As Asriel)
PS – There are several other people working on bringing Decal back to you. In truth, what I’ve done here is a minor part of a much larger operation.
PPS – A study in software creation will resume soon!
Disclaimer – This is “code in progress” and may not reflect actual code delivered to the end product. So if it still takes another month to finish, don’t blame us.