Friday 17 March 2023

Doom Interrupted My Naked Lunch

David Cronenbourg's Naked Lunch is a strange film, based on William S. Burroughs' strange book of the same name. Much of it is depicted from the point of view of someone whose grip on reality is very tenuous, and it has the effect of leaving the viewer unsure of reality too.

I saw it for the first time in about twenty years at the Prince Charles Cinema just off Leicester Square, last Tuesday evening.

What made it more strange this time was that, about half-way through, in a generally quiet shot, I suddenly thought I heard an imp from the original 1993 Doom dying. It seemed to stand out very clearly but, this being a cinema screening, I couldn't rewind the film to see if I'd imagined it.

The game is from 1993 and the film is from 1991. Could the game have lifted from the film, or might they have used the same sound effects?

Here's the shot from Naked Lunch, in which the sound that I noticed can be heard at the end:

Meanwhile the imp death in question is here:

It is a match!

Doom Wiki's list of Origins of Doom Sounds says the imp death sounds are actually recordings of camel vocalisations taken from a Lucasfilm sound library, and Naked Lunch must have used the same one.

It was a strange thing to notice in the cinema.

Saturday 14 January 2023

Configuring DOSBox for Doom, the Hard Way

Background

I've been playing Doom (the original 1993 version, of course) using DOSBox to emulate a PC on my old MacBook Pro.

Doom does allow use of the mouse to turn and to move forwards and backwards. With the mouse it's hard to do either of those things independently, though. Standing on the edge of a precipice above some lava or radioactive slime, it's difficult to aim at the imps on the other side without inadvertently stepping off the edge.

This is particularly evident in places like the Toxin Refinery (E1M3) whose architects' lax attitude to health and safety has resulted in a worrying absence of guard rails:


Introduction

I want to use the mouse to turn but not to move forwards and backwards. (The keyboard is just fine for the latter.)

Doom does not have an option for this. DOSBox, similarly, has only one setting for mouse sensitivity. Someone did commit a change that allows setting the X and Y sensitivities independently - just what I want! - but that change didn't make it into the latest DOSBox release, despite being slightly earlier than that release.

What to do?

Possibilities

I could:

  1. Learn to live with the existing mouse behaviour and accept the occasional lava bath.
  2. Wait an indeterminate amount of time for people who owe me nothing to decide to release a new version of DOSBox, which would probably include the patch that does what I want.
  3. Download the DOSBox source code from SourceForge and try to build my own DOSBox that includes the behaviour I want.
  4. Abandon this nostalgia trip entirely and do something useful.
  5. Work out where the existing DOSBox binary interacts with the OS mouse APIs and patch it to do what I want.

I considered all of these. Building my own DOSBox sounded doable, but I was soon discouraged by having to install autoconf and automake first, then finding I need a SDL library against which to link it. I seemed to be needing to install quite a large amount of sledgehammer to crack this nut and I wondered whether I'd even end up with a working program.

Meanwhile, looking at the DOSBox commit that separates the X and Y sensitivities got me thinking: patching the binary might actually be simpler. There ought to be only one place in the code that needs to deal with the mouse and I would expect it to be marked by a call to an OS function whose name contains the word 'Mouse'.

I have the free version of IDA installed already. It's too tempting not to give this try.

Devising a Patch

Let's see what IDA tells us about the DOSBox executable.

The program only uses to three mouse-related symbols, and only the second of those looks relevant to our purposes:


The symbol itself is just a thunk:


But let's see what refers to that thunk:


There's only one cross reference. That's promising:


This is it:


The only point at which this program asks the operating system about mouse movement is in this block. Encouragingly, the six instructions immediately after that call seem to be doing the following:

  • Increment the value at rbp-68h by the value at rbp-38h;
  • Increment the value at rbp-5Ch by the value at rbp-7Ch.

That looks a lot like it's accumulating the X and Y deltas returned by the CGGetLastMouseDelta() function into absolute X and Y positions.

If so, preventing the second of those addition instructions from happening should stop DOSBox from noticing any movement of the mouse on the Y axis, while still letting movement on the X axis have its normal effect.

The second addition is at byte 0x233fb9 in the file, which corresponds to virtual address 0x100232fb9 when loaded into memory.

Applying the Patch

In fact, now that we know what logical address we are looking for, we can find this same sequence of instructions using plain old objdump:

$ objdump -macho -disassemble -x86-asm-syntax=intel DOSBox | grep -B 5 -A 5 100232fb9
100232fa6:	e8 55 ea 07 00 	call	0x1002b1a00 ## symbol stub for: _CGGetLastMouseDelta
100232fab:	48 8b 45 98 	mov	rax, qword ptr [rbp - 104]
100232faf:	03 45 c8 	add	eax, dword ptr [rbp - 56]
100232fb2:	48 89 45 98 	mov	qword ptr [rbp - 104], rax
100232fb6:	8b 45 a4 	mov	eax, dword ptr [rbp - 92]
100232fb9:	03 45 84 	add	eax, dword ptr [rbp - 124]
100232fbc:	89 45 a4 	mov	dword ptr [rbp - 92], eax
100232fbf:	48 8b 5d 88 	mov	rbx, qword ptr [rbp - 120]
100232fc3:	80 7d c7 00 	cmp	byte ptr [rbp - 57], 0
100232fc7:	0f 84 b8 00 00 00 	je	0x100233085
100232fcd:	e9 6f 01 00 00 	jmp	0x100233141

The addition instruction that we'd like to disable is three bytes long.

Armed with the offset of the instruction within the file and the instruction's length, we can patch the executable, replacing that one three-byte add instruction with three one-byte nop instructions:

$ dd if=<(printf "\x90\x90\x90") of=DOSBox obs=1 seek=0x233fb9 conv=notrunc
0+1 records in
3+0 records out
3 bytes transferred in 0.000063 secs (47483 bytes/sec)

Did that change the right instruction? Disassembling the same address again will show:

$ objdump -macho -disassemble -x86-asm-syntax=intel DOSBox | grep -B 5 -A 5 100232fb9
100232fa6:	e8 55 ea 07 00 	call	0x1002b1a00 ## symbol stub for: _CGGetLastMouseDelta
100232fab:	48 8b 45 98 	mov	rax, qword ptr [rbp - 104]
100232faf:	03 45 c8 	add	eax, dword ptr [rbp - 56]
100232fb2:	48 89 45 98 	mov	qword ptr [rbp - 104], rax
100232fb6:	8b 45 a4 	mov	eax, dword ptr [rbp - 92]
100232fb9:	90 	nop
100232fba:	90 	nop
100232fbb:	90 	nop
100232fbc:	89 45 a4 	mov	dword ptr [rbp - 92], eax
100232fbf:	48 8b 5d 88 	mov	rbx, qword ptr [rbp - 120]
100232fc3:	80 7d c7 00 	cmp	byte ptr [rbp - 57], 0

That's exactly what we wanted. Let's fire it up:

$ ./DOSBox
DOSBox version 0.74-3-3
Copyright 2002-2019 DOSBox Team, published under GNU GPL.
---
Killed: 9

Oh.

This crash happened too quickly, though. It can't be because the newly-patched mouse-related code doesn't make sense: the program didn't even get as far as opening a window, let alone trying to deal with mouse coordinates. Besides, the disassembly looks just fine.

More likely is that the change has invalidated a signature on the executable file. Certainly, other people asking about that error message seem to be dealing with code signatures.

Let's try signing the updated executable:

codesign -s - --force DOSBox

-s - avoids requiring any certificate and instead does 'ad-hoc' signing. --force is needed only because the file already has a signature.

This time, it runs ... and the patch did exactly what it was supposed to do:


Limitations

Obviously an emulator that's been hacked this way won't be any use for playing something like a point-and-click adventure that does need both axes of mouse input. And it's silly to have to switch between emulator binaries just to change this one thing.

A different, neater possibility would be to patch the Doom executable rather than the emulator on which I'm running it. I'm less sure how I'd find the bit of Doom that deals with the mouse, though.

Conclusion

Overall this was probably simpler than building my own DOSBox would have been, and was definitely faster than waiting for a new release. It was also more fun and instructive than either of those.

I've been trying to work out why I found this so satisfying. I think part of it is about the way it relies on the interplay between levels of representation.

One of Hofstadter's main points in Gödel, Escher, Bach is that the meaning of something can't be completely intrinsic to it. Whenever we try to read anything at all, we must first have some expectations about what its structure will be.

Patching a program in this way relies on the fact that the same string of bytes in an executable file can be interpreted both as a program in machine code and as just that same string of bytes, so that changing the bytes in the file will change the meaning of the program. It's like gene editing but for computer programs.

It feels good to put that into practice from time to time.