Doom2 Longtics Hack

The Problem

When recording demos, doom2.exe only records 4 bytes of data per tic. One for forward/backward movement, one for sidestepping, one for turning, and one for actions (firing, door opening, etc). The angle data in Doom is normally stored in 2 bytes — this is what is transmitted in network games — for demos, presumably to reduce the space used, the low byte is thrown away.

This reduces the resolution of the player turning data. This is very noticable in play: the player's turning steps through about 1.5 degrees at a time, instead of turning smoothly. Not only has Doom halved the available data, but because it keeps the high byte, the player can still turn 180 degrees in an instant, but loses all the accuracy of small turns. So they made the strange choice to continue to allow fast turning, and to make accurate small turns, needed for aiming, impossible.

Clearly modern computers are not short of disk space, so it becomes desirable to regain the greater accuracy in demos which is already available in network games and normal play. This is easy for the ports, but plenty of players still want to use doom2.exe. And since we don't have the source code, compilers and libraries to build a new doom2.exe, we have to work on the old one.

Any hack has to be minimal: there has to be no incompatibility with the original EXE, and we mustn't affect the gameplay other than allowing better demos. The people still using doom2.exe are purists :-)

The Hack

Okay, G_WriteDemoTiccmd is at offset 0x00638e4 in doom2.exe v1.9. Don't ask how I know this — let's just say it took a lot of work. We want to record one extra byte, so we insert an extra inc of the pointer, modify the old store instruction to store the low byte, and add a store instruction for the high byte. Fortunately the existing code is quite long-winded, as it has to round the angleturn to the nearest value when it truncates it — we are replacing it by simpler code in fact.

63912: 43             inc    %ebx
63913: 88 03 90 90    mov %al,(%ebx); nop; nop
Extra pointer advance.
Store low byte of angleturn.
Existing pointer advance left in place.
63918: 90 90 90       nop; nop; nop
6391b: 88 23          mov    %ah,(%ebx)
Padding where old rounding/truncation was.
Store high byte of angleturn.
63928: 83 eb 04       sub    $0x4,%ebx
Finally, we have to move the pointer back one more than before ready for the G_ReadDemoTiccmd.

Now for the reading end. G_ReadDemoTiccmd is at 0x0063894. The code is much simpler here; we just have to read an extra byte and advance the pointer, instead of inserting a 0 byte.

638bb:	8a 33		mov    (%ebx),%dh
638bd:	43		inc    %ebx

But this part of the hack doesn't work — G_ReadDemoTiccmd does not get called. The code has been inlined by the Watcom compiler in the calling functions. So we have also to patch G_WriteDemoTiccmd (which calls the read function in order to read back in what it wrote, to ensure that the game being played and recorded will match the playback), and G_Ticker. These are at 0x00638e4 and 0x0062464 respectively. The corresponding changes are:

63965:  8a 23           mov    (%ebx),%ah
63967:  43              inc    %ebx


62653:  8a 22           mov    (%edx),%ah
62655:  42              inc    %edx

Finally, to change the demo version number, we change the version number check in G_DoPlayDemo at 0x0063ae4:

63b17:       80 fa 6f                cmp    $0x6f,%dl

and the version number recorded in G_BeginRecording at 0x0063a44:

63a4b:       c6 02 6f               movb   $0x6f,(%edx)


Colin Phipps. Written 2004/09/23.