NFSIISE: Original game bug: Gamepads with more than 15 buttons can't bind buttons

Disclaimer: I don’t know if this is fixed in the project currently, I was only pointed at it when I tweeted about the original game bug with gamepad bindings. It goes as follows:

  1. The game polls gamepad button like so - notice the presence of a for loop with a signed int16_t acting as the “number of buttons” (sub_404360 in my executable):
    if ( *(_WORD *)((char *)&word_512F34 + v17 + 2) )
    {
      v3 = PollJoystickButtons(v22, a1);
      if ( v3 )
      {
        v4 = v20;
        v5 = 1;
        for ( i = 0; i < *(int *)((char *)&word_512F34 + v17) >> 16 && !v1; ++i )
        {
          if ( (v5 & v3) != 0 )
            v1 = (v4 & 0xFFFFFF00) + 2;
          else
            v5 *= 2;
          v4 += 256;
        }
        while ( PollJoystickButtons(v22, v5) )
          sub_483EB0();
      }
  1. This int16 is populated with… a button bitmask populated by calling IDirectInputDevice::GetObjectInfo in a loop, like so (sub_421BD0 in my executable):
      v9 = 48;
      do
      {
        if ( v8 >= 32 )
          break;
        v10 = *(int *)&v25[1] >> 24;
        if ( !pDirectInputJoystick[*(int *)&v25[1] >> 24]->lpVtbl->GetObjectInfo(
                pDirectInputJoystick[*(int *)&v25[1] >> 24],
                &v18,
                v9,
                1) )
        {
          v11 = (1 << v8) | dword_4E7388[23 * v10];
          dword_4E7388[23 * v10] = v11;
          if ( a2 )
            *a2 = v11;
          ++*(_DWORD *)v25;
        }
        ++v9;
        ++v8;
      }
      while ( *(_DWORD *)v25 < v22 );

The problem is - since this int16 representing the button count gets populated with a button bitmask, gamepads reporting 16 or more buttons stash a value FFFF in that count, that then gets interpreted as -1 by the for loop. The result - gamepad buttons cannot be bound at all.

I see two ways to fix it:

  1. If you can modify the original game code - modify the code snippet from 2. so dword_4E7388 becomes a button count and not a button bitmask. This is the approach I’m going to take in my standalone fix.
  2. If you absolutely cannot modify the original game code - spoof DirectInput capabilities and adjust gamepad’s GetObjectInfo wrappers to never return more than 15 buttons. This will ensure that the button bitmask is at most 7FFF and therefore avoid underflow. I kind of recommend against this approach, because the game will still potentially do an equivalent of for ( i = 0; i < 32767; i++ ) every time it checks button presses.

About this issue

  • Original URL
  • State: closed
  • Created 10 months ago
  • Comments: 18 (9 by maintainers)

Most upvoted comments

Merged, but this code is auto-generated 😄

Ah, back then I misunderstood what did you mean by “patching C code”. Either way, it’s all solved now. Thanks!

Thanks for the PR!

Here if possible