In this episode I discover some a seriously WTF aspect of DOS game programming. Namely how I need to get a millisecond timer.
I'm still pretty new to the realm of serious DOS game programming. I did a lot of QBasic as a kid, but moved to Windows 95 progamming around the time I started doing anything serious with C and C++. So here's some stuff that I'm pretty sure any DOS game dev will already be well versed in, but was a bit of a surprise to me.
There's a piece of hardware on the IBM PCs, the Intel 8253/8254 Programmable Interval Timer chip, which is responsible for firing off an interrupt at about 18.2hz (by default).
In DOS, you can replace the DOS timer interrupt handler with your own handler.
The 8253/8254 actually is capable of sending signals at about 1.19318mhz. The way it gets to an output of 18.2hz is by dividing that frequency by 65535 (1193180hz / 65535 ≈ 18.2hz). That 65535 value is stored in a two-byte register called the divisor. That divisor value is fairly trivial to change. If you lower it, the interrupt handler function will be called much faster.
So just to summarize so far... You can replace the DOS timer interrupt function with your own function, and you can change the rate at which that interrupt handler is called.
So you write a function, use 'setvect(0x08, your_function_here)' (under Watcom C/C++) to set the timer interrupt to it, and sure enough, your code function gets executed 18.2 times per second. You change the clock divisor, and it calls at an even higher rate.
Then you exit your application and find out that the system time still says the time that you started your application. Time did not pass while your program ran, apparently.
Turns out, that original DOS timer interrupt that you replaced was responsible for updating the system clock. So it's pretty easy to just getvect() to get a function pointer for the original DOS timer interrupt and call that and the end of your own, right? Sure enough, if you do this, the system clock once again gets updated.
Errr... sort of.
See, now the system clock is way too far ahead of whatever time it really is after running your program. The reason it's so far ahead, is because you called the DOS timer interrupt handler too fast. The original DOS timer interrupt handler will update the system clock forward by 1/18.2 seconds every time you call it. It works great as long as you call it at a rate of 18.2hz, but now you're calling it every millisecond (1000hz) or so. (Milliseconds because that's the hard-coded divisor I went with.)
The final trick here is to keep calling the original DOS timer interrupt, but at the original 18.2hz rate. This means probably keeping a counter going inside the timer interrupt and calling the original handler only once the counter passes a certain threshold. It's a dumb, simple, solution.
More information here.
Here's the timer code for the DOS game as it stands right now.