Probably the biggest noticeable feature of Aeon 0.63 is support for emulating joysticks/gamepads through the PC game port. This was a pretty easy device to emulate, but hasn’t really been a priority for me since I basically never use game controllers on my PC. Turns out other people do though… Go figure :)
Anyway, the game port interface is actually pretty interesting in that it provides essentially no abstraction from the hardware. Learning how it worked made me finally understand what the deal was with every game needing calibration to work properly, and those weird dials on analog joysticks that I always had to fiddle with to keep the controls from drifting. If you already know how this works or don’t care, you won’t find this article very interesting, so you should probably stop now. Otherwise, read on…
Using a Joystick in DOS
DOS provides essentially no abstraction on anything except the disk (I guess that’s why it’s the Disk Operating System), so to work with the joystick, games would have to read a byte from the game port. Four of the bits of this byte are used to indicate the status of the first four buttons (or the first two buttons of each joystick if two are attached). The other four bits are used to indicate the position of the first four analog axes (again, or the first two of each joystick if two are attached).
Reading button status is easy enough: just look for whether the appropriate bit is cleared, which indicates that the button is pressed. Getting the position of an axis is another story.
You might wonder how an analog value can be transmitted using a single bit. The answer is: time. A DOS program first writes a value to the joystick port (any value will do), then immediately goes into a loop continuously reading the status byte. The axis bits will all start out at 1, and eventually will all drop to 0. The amount of time it takes for this to happen is proportional to the joystick’s position in that axis. The amount of time this takes is device dependent and can be measured in microseconds, so a game would count the number of iterations it takes for the value to drop to 0, rather than using timers or anything sophisticated like that.
Weird, isn’t it? Well, it turns out this makes a lot of sense.
I mentioned before how DOS provides no abstraction for most hardware devices, so you are left communicating with them directly most of the time. Most devices have some degrees of abstraction themselves, so this isn’t a huge problem, but the game port has essentially none. The classic joystick design that it works with was little more than a couple potentiometers (basically adjustable resistors if you are unfamiliar with circuits). Writing a value to the port causes some capacitors in the device to become charged. The speed this charge is lost depends on how much resistance is in the potentiometers (which is controlled by the joystick position and those crazy dials).
So that’s really all there is to it. A charge is placed on a circuit and the program waits for it to go away.
For me, it was important to understand how programs would use the game port for me to emulate it properly. Fortunately, modern API’s make it a lot easier to read a joystick position, so it was trivial to query DirectInput for this. That means I just had to translate this value into something resembling a decaying voltage.
Fortunately, I knew that this charge decayed so quickly that DOS programs would generally just count iterations in a tight loop that looks something like this:
mov cx, 0 ; initialize cx to 0 for a counter
mov dx, 201h ; set game port 201h to the dx register
out dx,al ; write any value to the port
start: inc cx,1 ; increment cx register by 1
in al,dx ; read current status of the game port
test al,1 ; check whether the joystick 1 x-axis 1 bit is set
jnz start ; go to start if the bit is still set
So at the end of this loop, the CX register will contain the number of iterations it took for the bit to drop to 0. Knowing this, I can “cheat,” and just use a counter myself instead of doing any complicated high-resolution timing. All I have to do is scale the position of the joystick and decrement this value every time a program reads from the game port. When the value is greater than 0, Aeon returns a 1 for that axis; otherwise it returns a 0. This approach has worked perfectly in every game I’ve tried so far, and it was really easy.
Modern game controllers are more sophisticated than this – they are designed to communicate using USB or some other proprietary digital protocol and contain all of the logic needed to convert an analog position into a digital value, so fortunately modern game developers don’t have to be aware of the hardware implementation of how their input devices operate anymore. That’s fine with me. I for one have no desire to go back to the days of calibration and fidgeting with dials.