Debugging Wild Metal Country

From WineHQ Wiki
Jump to: navigation, search

Wild Metal Country is a game from Rockstar Games which is one of their freely (as in beer) downloadable classic games.

When initially run, it seems to work fine, but after entering your name at the prompt, it fails with this error message (after spewing a lot of DDraw errors):

err:seh:setup_exception stack overflow 252 bytes in thread 0009 eip 00500143 esp 405a0f04 stack 0x405a0000-0x406a0000

What that means is that basically the game entered into an exception loop (i.e. it had an exception whose exception handler had an exception whose exception handler had ...) up to the exhaustion of the exception stack.

What interests us here is why the game had the first exception. For that, run Wine with the following command line:

WINEDEBUG=+seh,+relay,+tid,+ddraw,+dinput,+dsound wine WinEnv

As we know that it is a DirectX game (from the displayed error messages), a simple +relay trace is not enough as it won't show COM method calls. So we add to the list of the usual debugging channels (relay, seh and tid) the most common ones from DirectX.

After searching for seh in the resulting log, one arrives to this:

0009:Ret  ntdll.RtlNtStatusToDosError() retval=00000103 ret=40934283
0009:Ret  advapi32.RegEnumKeyExA() retval=00000103 ret=40a69f1e
0009:Ret  dplayx.DirectPlayEnumerateA() retval=00000000 ret=00465f13
0009:trace:seh:EXC_RtlRaiseException code=c0000005 flags=0 addr=0x466003
0009:trace:seh:EXC_RtlRaiseException  info[0]=00000000

Hmmm, so it seems that the game does not like at all what we return to it during this DirectPlay enumeration call. So let's refine our command line to this:

WINEDEBUG=+dplay,+dplayx wine WinEnv.exe

This gives us this:

trace:dplay:DirectPlayEnumerateA : lpEnumCallback=0x465d60 lpContext=0x5adad2
err:seh:setup_exception stack overflow 252 bytes in thread 0009 eip 00500143 esp 405a0f04 stack 0x405a0000-0x406a0000

As this looks pretty sparse a debug output, look at the code of the DirectPlayEnumerateA function in the dplayx DLL (dlls/dplayx). And one can see this code:

  for( dwIndex=0;
       RegEnumKeyExA( hkResult, dwIndex, subKeyName, &sizeOfSubKeyName,
                      NULL, NULL, NULL, &filetime ) != ERROR_NO_MORE_ITEMS;
       ++dwIndex, sizeOfSubKeyName=50 )
  {
...
    TRACE(" this time through: %s\n", subKeyName );
...
    if( !lpEnumCallback( &serviceProviderGUID , subKeyName,
                         majVersionNum, minVersionNum, lpContext ) )
    {
      WARN("lpEnumCallback returning FALSE\n" );
      break;
    }

So we should have had a TRACE line for each service provider that would have been enumerated to the game. As we do not see any, the conclusion is that we never did call the game-provided enumeration function.

As applications are notoriously bad at handling unexpected scenarios, the current hypothesis explaining the crash would be that it does expect to have at least one provider present.

To confirm this (in a quick and dirty way), the following test program has been compiled and executed on Windows:

#include <dplay.h>
#include <dplay8.h>

BOOL FAR PASCAL EnumDPCallback(LPGUID lpguidSP, LPSTR lpSPName, DWORD dwMajorVersion, DWORD dwMinorVersion, LPVOID lpContext)
{
  printf("{%08lx-%04x-%04x-%02x%02x-%02x%02x%02x%02x%02x%02x} %s %d %d\n",
         lpguidSP->Data1, lpguidSP->Data2, lpguidSP->Data3,
         lpguidSP->Data4[0], lpguidSP->Data4[1],
         lpguidSP->Data4[2], lpguidSP->Data4[3],
         lpguidSP->Data4[4], lpguidSP->Data4[5],
         lpguidSP->Data4[6], lpguidSP->Data4[7],
         lpSPName,
         dwMajorVersion,
         dwMinorVersion);
  return TRUE;
}

int main(int argc, char *argv[])
{
  DirectPlayEnumerate(&EnumDPCallback, NULL);
}

Which gives the following result:

{0f1d6860-88d9-11cf-9c4e-00a0c905425e} Serial Connection For DirectPlay 6 0
{44eaa760-cb68-11cf-9c4e-00a0c905425e} Modem Connection For DirectPlay 6 0
{685bc400-9d2c-11cf-a9cd-00aa006886e3} IPX Connection For DirectPlay 6 0
{36e95ee0-8577-11cf-960c-0080c7534e82} Internet TCP/IP Connection For DirectPlay 6 0

So we just add the following code to the enumeration function in Wine:

GUID hack_guid = { 0x36E95EE0, 0x8577, 0x11CF, { 0x96, 0x0c, 0x00, 0x80, 0xC7, 0x53, 0x4E, 0x82 } } ;

HRESULT WINAPI DirectPlayEnumerateA( LPDPENUMDPCALLBACKA lpEnumCallback,
                                     LPVOID lpContext )
{
...
  lpEnumCallback( &hack_guid, "Internet TCP/IP Connection For DirectPlay", 6, 0, lpContext );
...
}

And rebuild and install the dplayx DLL.

As we re-run the game, surprise, it starts fine now and seems playable (with some graphical corruptions though).

So here we did the first step of all debugging: we found the reason of the crash and confirmed the way to fix it with a small hack (it's often useful to proceed this way to prevent coding thousands of lines of code to finally find out that they do not fix the problem at all - i.e. start small with a hack and then code the proper way being sure of the problem).

Note that I just sent a possible 'real' fix for this issue: basically, at Wine install / upgrade time, the set of keys needed to enumerate all service providers will be installed via the 'wine.inf' file.