Search code examples
c++winapiraw-input

GetRawInputDeviceInfo with RIDI_DEVICENAME returns invalid memory


In my code I have a call to the GetRawInputDeviceInfo function, passing in the RIDI_DEVICENAME command. It is supposed to fill the 3rd parameter with a string containing the name of the buffer, and return the length of the string.

However, when the function returns, that parameter points to invalid memory (inside the debugger it says 0x234449485c3f5c5c, and can't view the contents of it).

This is the complete code:

#include <windows.h>
#include <stdint.h>

#define assert(x) do{if(!(x))__debugbreak();}while(0)

typedef size_t usize;
typedef uint8_t u8;
typedef unsigned int uint;

struct Raw_Input_Memory {
  usize size;
  usize used;
  u8   *data;
};

#define raw_input_memory_push_array(memory, type, count) (type *)raw_input_memory_push_size(memory, sizeof(type) * count)
#define raw_input_memory_push_struct(memory, type) (type *)raw_input_memory_push_size(memory, sizeof(type))
void *raw_input_memory_push_size(Raw_Input_Memory *memory, usize size) {
  assert(memory->used + size <= memory->size);
  
  void *result = memory->data + memory->used;
  memory->used += size;
  
  return result;
}

int main() {
  HINSTANCE module_instance = GetModuleHandle(NULL);
  
  WNDCLASSA window_class = {};
  window_class.lpfnWndProc = DefWindowProc;
  window_class.hInstance = module_instance;
  window_class.hCursor = LoadCursorA(NULL, IDC_ARROW);
  window_class.lpszClassName = "Toplevel";
  
  ATOM window_class_atom = RegisterClassA(&window_class);
  if (window_class_atom) {
    HWND window = CreateWindowA(window_class.lpszClassName, "Raw Input",
                                WS_VISIBLE | WS_OVERLAPPEDWINDOW,
                                CW_USEDEFAULT, CW_USEDEFAULT,
                                CW_USEDEFAULT, CW_USEDEFAULT,
                                NULL, NULL, module_instance, NULL);
    if (window) {
      Raw_Input_Memory raw_input_memory = {};
      raw_input_memory.size = 1024 * 1024;
      raw_input_memory.used = 0;
      raw_input_memory.data = (u8 *)VirtualAlloc(NULL, raw_input_memory.size,
                                                 MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE);
      
      RAWINPUTDEVICE raw_input_device;
      
      raw_input_device.usUsagePage = 0x01;
      raw_input_device.usUsage = 0x05;
      raw_input_device.dwFlags = 0;
      raw_input_device.hwndTarget = 0;
      
      if (RegisterRawInputDevices(&raw_input_device, 1,
                                  sizeof(RAWINPUTDEVICE))) {
        MSG window_message = {};
        while (GetMessageA(&window_message, NULL, 0, 0) > 0) {
          UINT message_kind = window_message.message;
          WPARAM wparam     = window_message.wParam;
          LPARAM lparam     = window_message.lParam;
          
          switch (message_kind) {
            case WM_QUIT: {
              break;
            } break;
            
            case WM_INPUT: {
              if (raw_input_memory.data) {
                raw_input_memory.used = 0;
                
                UINT input_size;
                if (GetRawInputData((HRAWINPUT)lparam, RID_INPUT, NULL, &input_size,
                                    sizeof(RAWINPUTHEADER)) != -1) {
                  RAWINPUT *input = (RAWINPUT *)raw_input_memory_push_size(&raw_input_memory,
                                                                           input_size);
                  if (GetRawInputData((HRAWINPUT)lparam, RID_INPUT, input,
                                      &input_size, sizeof(RAWINPUTHEADER)) == input_size) {
                    assert(input->header.dwType == RIM_TYPEHID);
                    
                    uint device_name_length = 128;
                    char *device_name = NULL;
                    device_name = raw_input_memory_push_array(&raw_input_memory, char,
                                                              device_name_length);
                    assert(device_name);
                    INT got_device_name = GetRawInputDeviceInfoA(input->header.hDevice,
                                                                 RIDI_DEVICENAME,
                                                                 &device_name,
                                                                 &device_name_length);
                    if (got_device_name) {
                      /* ... */
                    }
                  }
                }
              }
            } break;
            
            default: {
              TranslateMessage(&window_message);
              DispatchMessageA(&window_message);
            }
          }
        }
      }
    }
  }

  return 0;
}

Note that the call doesn't explicitly fail: it doesn't return -1 nor 0, but 83; and GetLastError returns 0.

I've tried both allocating the memory myself (as you can see in the code I posted) and passing in a NULL pointer, and both have the same result. I tried the wide version too, and I still get an 83 as a return value and a pointer to 0x005c003f005c005c, which is still invalid.

What am I doing wrong? Why does the function pass me invalid memory and how do I stop it from doing so? I've seen this answer, and this other answer, and read the docs. I don't know what else to do.

By the way, I'm trying it with a PS4 controller.


Solution

  • pData needs to be a pointer to a buffer, but you're passing the address of that pointer. The call then fills that "buffer" (i.e. the stack), corrupting memory as it's doing its thing. If you look closely at what the pointer value is after the call, you'll see that it contains the first four characters of the device name ("\\?\"). You'll have to pass device_name in place of &device_name.

    The reason the compiler doesn't warn you about this is that GetRawInputDeviceInfoA takes an argument of type LPVOID (i.e. void*) for its pData parameter. Since any pointer can be implicitly converted into a void*, the compiler has no way of knowing that an argument of type char** were any worse than an argument of type char*. There's simply no information available that would warrant a warning.