Updating Hover! for Modern PCs

Microsoft's Hover! has always been somewhat of a curiosity for me. Bundled with the Windows 95 installer CD, it was created as a tech demo to show off the relatively advanced multimedia capabilities of PCs at the time. While not a particularly good game, I used to play it quite often when I was younger as it was free, and I didn't have access to a large library of games.

A few months ago I got hold of a Roland SoundCanvas SC-55, a MIDI synthesiser module from the '90s that was commonly used with computers of the day. It was supported by many popular games - for example the Descent and DOOM series.

Hover! also has a MIDI soundtrack, and after getting my SC-55 all set up, I was keen to try it out with a game. Despite everything seemingly being set up correctly, Hover! was still playing sound through the Microsoft GS Wavetable Synth rather than my SC-55.

I later discovered that Microsoft had removed the MIDI mapper device in Windows Vista and later. The MIDI mapper is a virtual device that acts as a tunnel to a MIDI synthesizer specified by the user. Traditionally, the MIDI mapper appears as the first device (with an ID of 0) in the system.

Many older pieces of software are hardcoded to use the first device in the system (the mapper), as this means they can avoid building a device selection UI and delegate that responsibility to the OS. However, with the MIDI mapper removed, ID 0 is now occupied by the Microsoft GS Wavetable Synth. This means that all software hardcoded to use device ID 0 will now only play sound through the Microsoft GS Wavetable Synth :(.

Instead of just calling it a day like a sensible person would, I decided to dive in and see if I could patch Hover! to use a different MIDI device ID.

No Source Code? No Problem!

First, I wanted to find the function that is responsible for opening a MIDI device. I have close to zero experience with the Windows API, however a quick browse through MSDN turns up the midiOutOpen and midiStreamOpen functions in winmm.dll.

A quick look in Process Explorer confirms that winmm.dll is loaded by Hover!:

Image of Process Explorer demonstrating that winmm.dll is loaded by Hover! From here, I opened up the Hover! binary in a disassembler (I personally use Hopper since it's reasonably priced) and checked for references in the code to both midiOutOpen and midiStreamOpen functions. Unfortunately, the search turned up no results.

Searching for functions containing 'midiOpen' Usually my next step from here is to do a string search for the function names in question, in case the applications looks the symbol up at runtime:

Searching for strings containing 'midiOpen'

This looks promising! One of the potential functions (midiSteamOpen) has turned up. I can now search for references to the "midiStreamOpen" string.

This gives me a single function at 0x437f00, which I've named Hover_midiInit. The beginning of this function does the following (in a C-style psuedocode to avoid listing all the assembly):

Hover_midiReset() // Function at 0x438260 

if (*0x4c5908 == NULL)
    handle = GetModuleHandleA("winmm.dll")

    if (handle != NULL)
        *0x4c5908 = GetProcAddress(handle, "midiStreamOpen");
        *0x4c590c = GetProcAddress(handle, "midiStreamClose");
        *0x4c5910 = GetProcAddress(handle, "midiStreamProperty");
        *0x4c5914 = GetProcAddress(handle, "midiStreamPosition");
        *0x4c5918 = GetProcAddress(handle, "midiStreamOut");
        *0x4c591c = GetProcAddress(handle, "midiStreamPause");
        *0x4c5920 = GetProcAddress(handle, "midiStreamRestart");
        *0x4c5924 = GetProcAddress(handle, "midiStreamStop");

Oh no, function pointers! I went ahead and gave all those addresses more human-friendly names:

Address Name
0x4c590c midiSteamOpenPointer
0x4c5908 midiSteamClosePointer
0x4c5910 midiSteamPropertyPointer
0x4c5914 midiSteamPositionPointer
0x4c5918 midiStreamOutPointer
0x4c591c midiStreamPausePointer
0x4c5920 midiStreamRestartPointer
0x4c5924 midiStreamStopPointer

Next, I searched for references to midiStreamOpenPointer. Apart from the references in Hover_midiInit, there is only one other in the function at 0x438300, which I've called Hover_midiStart:

00438300         sub        esp, 0xc
00438303         push       ebx
00438304         push       esi
00438305         push       edi
00438306         mov        esi, ecx
00438308         push       ebp
00438309         xor        edi, edi
0043830b         mov        ecx, dword [ds:midiStreamOpenPointer]
00438311         mov        dword [ds:esi+0x18], edi
00438314         mov        eax, 0x1
00438319         mov        dword [ds:esi+0x1c], edi
0043831c         mov        dword [ds:esi+0x20], edi
0043831f         cmp        ecx, edi
00438321         mov        dword [ss:esp+0x1c+var_C], 0xffffffff
00438329         je         0x438343

0043832b         lea        eax, dword [ss:esp+0x1c+var_C]
0043832f         push       0x30000
00438334         push       edi
00438335         lea        edx, dword [ds:esi+0x18]
00438338         push       0x438440
0043833d         push       0x1
0043833f         push       eax
00438340         push       edx
00438341         call       ecx

On x86 Windows, function arguments are pushed the stack in right-to-left order. This means the call is as follows:

*midiStreamOpenPointer(edx, eax, 1, 0x438440, edi, CALLBACK_FUNCTION)

Looking at the definition for midiOutOpen, we can see that the MIDI device ID is passed as the second parameter. In this case, it is the value of the eax register.

At 0x43832b, the eax register is loaded with the value at esp+0x1c+var_C. We can see the value loaded into this address at 0x438321 is 0xffffffff, or -1. Changing this value should change the MIDI device ID that Hover! uses to play music.

I did this by just patching as follows:

  • 0x438425 (file offset 0x37725): 0xffffffff -> 0x01000000 (1, since x86 is little-endian).

As my SC-55 is the only other MIDI device attached to my system apart from the Microsoft GS Wavetable Synth, it always has a device ID of 1.

I save the patched executable, start the game, and success! It's now playing music through my SC-55.

Fixing Bugs

With Hover! now playing music through my SC-55, I now wanted to have a look at fixing a couple of annoying bugs that appear when running the game on modern systems.

The Sound of Silence

On Windows Vista and later, the background music will not start playing until you go to Options->Sounds and dismiss the dialog. The "Music" check box in this dialog is also disabled. Windows Vista also happens to be the version where the MIDI mapper was removed. To me, this sounds like the game is looking for a certain type of MIDI device (the mapper) and failing on later Windows versions.

Doing a search for functions prefixed with "midi", we see the following:

Searching for functions prefixed with 'midi'

The midiOutGetNumDevs and midiOutGetDevCapsA functions both look pretty interesting. Both are referenced by a single function at 0x41c9e0, which appears to be some sort of initialisation routine. I just named it Hover_initialise. The interesting part is at 0x41d1f0:

0041d1f0         call       dword [ds:imp_midiOutGetNumDevs]
0041d1f6         mov        dword [ss:ebp+var_80], eax
0041d1f9         cmp        dword [ss:ebp+var_80], 0x0
0041d1fd         jne        0x41d228

0041d203         mov        eax, dword [ss:ebp+var_E4]
0041d209         mov        dword [ds:eax+0x410], 0x0
0041d213         mov        eax, dword [ss:ebp+var_E4]
0041d219         mov        dword [ds:eax+0x40c], 0x0
0041d223         jmp        0x41d2ea

0041d228         mov        eax, dword [ss:ebp+var_E4]       
0041d22e         mov        dword [ds:eax+0x410], 0x0
0041d238         mov        dword [ss:ebp+var_84], 0x0
0041d242         jmp        0x41d24d

0041d247         inc        dword [ss:ebp+var_84]            

0041d24d         mov        eax, dword [ss:ebp+var_80]       
0041d250         cmp        dword [ss:ebp+var_84], eax
0041d256         jae        0x41d2c2

0041d25c         push       0x34
0041d25e         lea        eax, dword [ss:ebp+var_B8]
0041d264         push       eax
0041d265         mov        eax, dword [ss:ebp+var_84]
0041d26b         push       eax
0041d26c         call       dword [ds:imp_midiOutGetDevCapsA]
0041d272         mov        dword [ss:ebp+var_BC], eax
0041d278         cmp        dword [ss:ebp+var_BC], 0x0
0041d27f         jne        0x41d2bd

0041d285         mov        eax, dword [ss:ebp+var_90]
0041d28b         and        eax, 0xffff
0041d290         cmp        eax, 0x5
0041d293         je         0x41d2ad

0041d299         mov        eax, dword [ss:ebp+var_90]
0041d29f         and        eax, 0xffff
0041d2a4         cmp        eax, 0x4
0041d2a7         jne        0x41d2bd

0041d2ad         mov        eax, dword [ss:ebp+var_E4]       
0041d2b3         mov        dword [ds:eax+0x410], 0x1

0041d2bd         jmp        0x41d247

Or, in psuedocode:

UINT deviceCount = midiOutGetNumDevs()

if (deviceCount != 0)
    *(var_E4 + 0x410) = 0

    for (UINT deviceID = 0; deviceID < deviceCount; deviceID++)
        MIDIOUTCAPS deviceCapabilities;
        MMRESULT error = midiOutGetDevCaps(deviceID, &deviceCapabilities, sizeof(deviceCapabilities))

        if (error == MMSYSERR_NOERROR && deviceCapabilities.wTechnology == MOD_FMSYNTH || deviceCapabilities.wTechnology == MOD_MAPPER)
            *(var_E4 + 0x410) = 1

    if (*(var_E4 + 0x410) == 0)
        *(var_E4 + 0x41c) = 1
    *(var_E4 + 0x410) = 0
    *(var_E4 + 0x41c) = 0

Just as I suspected, the game is looking for MIDI devices of a certain type: FM Synth (eg. OPLx found on Adlib/SoundBlaster cards) or MIDI Mapper. I assume that the values in var_E4 are used by the game later to decide whether to play the music or not.

Now that I've identified what the problem is, it can be fixed. My solution was to simply match all device types, as the device type doesn't seem to be used for anything else. This can be done by changing:

0041d290         cmp        eax, 0x5
0041d293         je         0x41d2ad 


0041d290         cmp        eax, 0x0
0041d293         jae        0x41d2ad

This translates to the following patch:

  • 0x41d292 (file offset 0x1c692): 0x050f84 -> 0x000f87.

With this patch, music now starts as soon as the game begins. The "Music" checkbox is also enabled and can be deselected.

But why did it work when the "Sounds" dialog was opened and closed? Turns out, the game completely skips the device detection routine when closing this dialog, and just calls the Hover_midiStart function, playing music through whatever is the default device.


If your graphics mode supports more than 256 colours, you'll get the following message when starting Hover!:

We have detected that you are not running a 256 color video driver. While HOVER! will still run, you will get the best performance if you run HOVER! with a 256 color video driver.

This seems like a classic case of not accounting for the fact that one day, computers will be able to display more than 256 colours, and doing an "equals 256" rather than a "greater than or equal to 256".

Another look at MSDN reveals that the function that's used to get the number of colours of the current graphics mode is GetDeviceCaps.

Sure enough, there is a reference to this function in the code (in Hover_initialise also). The routine looks like this:

int supportsPalette;
int numberOfColours;

HDC deviceContext = GetDC(NULL); // Get device DC for screen

if ((GetDeviceCaps(deviceContext, RASTERCAPS) & RC_PALETTE) != 0)
    supportsPalette = 1;
    supportsPalette = 0;

if (supportsPalette != 0)
    numberOfColours = GetDeviceCaps(deviceContext, SIZEPALETTE);
    numberOfColours = GetDeviceCaps(deviceContext, NUMCOLORS);

ReleaseDC(NULL, deviceContext);

if (supportsPalette == 0 || numberOfColours != 256)
    // Display alert window.

There are a couple of problems with this routine:

  • It only checks if numberOfColours != 256. In the event that more than 256 colours are supported, the NUMCOLORS field will be set to -1. This is contrary to the MSDN documentation, which specifies that it will be set to 1. However, I have confirmed this is incorrect by verifying the value in a debugger while in 16- and 32-bit colour modes.
  • If the graphics driver does not support paletted device contexts, the alert window is always displayed. This causes the value of the NUMCOLORS field to never be used.

Fortunately, patching to obtain the desired functionality is trivial:

  • 0x41cfa8 (File offset 0x1c3a8): 0xd400 -> 0xd8ff
  • 0x41cfac (File offset 0x1c3ac): 0x0d -> 0x51

This changes the supportsPalette == 0 condition to numberOfColors != -1.

Now, when starting the game in graphics modes with more than 256 colours available, the message is no longer erroneously displayed.

Next Steps

While manually patching the executable works, it's less than ideal:

  • Every time I want to change the MIDI device used for playing music, I have to open up a hex editor and change a few bytes.
  • If the modified executable is shared, there is a risk of the original executable being lost since it's no longer as desirable - this is bad for preservation of the original software.

In my next post I'll be covering how I developed a tool to apply these patches at runtime.

Thanks for reading! (*^ ^*)