From 5a079a2d114f96d4847d1ee305d5b7c16eeec50e Mon Sep 17 00:00:00 2001 From: 3gg <3gg@shellblade.net> Date: Sat, 27 Dec 2025 12:03:39 -0800 Subject: Initial commit --- contrib/SDL-3.2.8/src/core/windows/SDL_directx.h | 112 ++++++ contrib/SDL-3.2.8/src/core/windows/SDL_gameinput.c | 98 +++++ contrib/SDL-3.2.8/src/core/windows/SDL_gameinput.h | 36 ++ contrib/SDL-3.2.8/src/core/windows/SDL_hid.c | 254 ++++++++++++ contrib/SDL-3.2.8/src/core/windows/SDL_hid.h | 215 ++++++++++ contrib/SDL-3.2.8/src/core/windows/SDL_immdevice.c | 434 +++++++++++++++++++++ contrib/SDL-3.2.8/src/core/windows/SDL_immdevice.h | 45 +++ contrib/SDL-3.2.8/src/core/windows/SDL_windows.c | 375 ++++++++++++++++++ contrib/SDL-3.2.8/src/core/windows/SDL_windows.h | 172 ++++++++ contrib/SDL-3.2.8/src/core/windows/SDL_xinput.c | 140 +++++++ contrib/SDL-3.2.8/src/core/windows/SDL_xinput.h | 276 +++++++++++++ contrib/SDL-3.2.8/src/core/windows/pch.c | 21 + contrib/SDL-3.2.8/src/core/windows/pch_cpp.cpp | 21 + contrib/SDL-3.2.8/src/core/windows/version.rc | 38 ++ 14 files changed, 2237 insertions(+) create mode 100644 contrib/SDL-3.2.8/src/core/windows/SDL_directx.h create mode 100644 contrib/SDL-3.2.8/src/core/windows/SDL_gameinput.c create mode 100644 contrib/SDL-3.2.8/src/core/windows/SDL_gameinput.h create mode 100644 contrib/SDL-3.2.8/src/core/windows/SDL_hid.c create mode 100644 contrib/SDL-3.2.8/src/core/windows/SDL_hid.h create mode 100644 contrib/SDL-3.2.8/src/core/windows/SDL_immdevice.c create mode 100644 contrib/SDL-3.2.8/src/core/windows/SDL_immdevice.h create mode 100644 contrib/SDL-3.2.8/src/core/windows/SDL_windows.c create mode 100644 contrib/SDL-3.2.8/src/core/windows/SDL_windows.h create mode 100644 contrib/SDL-3.2.8/src/core/windows/SDL_xinput.c create mode 100644 contrib/SDL-3.2.8/src/core/windows/SDL_xinput.h create mode 100644 contrib/SDL-3.2.8/src/core/windows/pch.c create mode 100644 contrib/SDL-3.2.8/src/core/windows/pch_cpp.cpp create mode 100644 contrib/SDL-3.2.8/src/core/windows/version.rc (limited to 'contrib/SDL-3.2.8/src/core/windows') diff --git a/contrib/SDL-3.2.8/src/core/windows/SDL_directx.h b/contrib/SDL-3.2.8/src/core/windows/SDL_directx.h new file mode 100644 index 0000000..6101e92 --- /dev/null +++ b/contrib/SDL-3.2.8/src/core/windows/SDL_directx.h @@ -0,0 +1,112 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2025 Sam Lantinga + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. +*/ +#include "SDL_internal.h" + +#ifndef SDL_directx_h_ +#define SDL_directx_h_ + +// Include all of the DirectX 8.0 headers and adds any necessary tweaks + +#include "SDL_windows.h" +#include +#ifndef WIN32 +#define WIN32 +#endif +#undef WINNT + +// Far pointers don't exist in 32-bit code +#ifndef FAR +#define FAR +#endif + +// Error codes not yet included in Win32 API header files +#ifndef MAKE_HRESULT +#define MAKE_HRESULT(sev, fac, code) \ + ((HRESULT)(((unsigned long)(sev) << 31) | ((unsigned long)(fac) << 16) | ((unsigned long)(code)))) +#endif + +#ifndef S_OK +#define S_OK (HRESULT)0x00000000L +#endif + +#ifndef SUCCEEDED +#define SUCCEEDED(x) ((HRESULT)(x) >= 0) +#endif +#ifndef FAILED +#define FAILED(x) ((HRESULT)(x) < 0) +#endif + +#ifndef E_FAIL +#define E_FAIL (HRESULT)0x80000008L +#endif +#ifndef E_NOINTERFACE +#define E_NOINTERFACE (HRESULT)0x80004002L +#endif +#ifndef E_OUTOFMEMORY +#define E_OUTOFMEMORY (HRESULT)0x8007000EL +#endif +#ifndef E_INVALIDARG +#define E_INVALIDARG (HRESULT)0x80070057L +#endif +#ifndef E_NOTIMPL +#define E_NOTIMPL (HRESULT)0x80004001L +#endif +#ifndef REGDB_E_CLASSNOTREG +#define REGDB_E_CLASSNOTREG (HRESULT)0x80040154L +#endif + +// Severity codes +#ifndef SEVERITY_ERROR +#define SEVERITY_ERROR 1 +#endif + +// Error facility codes +#ifndef FACILITY_WIN32 +#define FACILITY_WIN32 7 +#endif + +#ifndef FIELD_OFFSET +#define FIELD_OFFSET(type, field) ((LONG) & (((type *)0)->field)) +#endif + +/* DirectX headers (if it isn't included, I haven't tested it yet) + */ +// We need these defines to mark what version of DirectX API we use +#define DIRECTDRAW_VERSION 0x0700 +#define DIRECTSOUND_VERSION 0x0800 +#define DIRECTINPUT_VERSION 0x0800 // Need version 7 for force feedback. Need version 8 so IDirectInput8_EnumDevices doesn't leak like a sieve... + +#ifdef HAVE_DDRAW_H +#include +#endif +#ifdef HAVE_DSOUND_H +#include +#endif +#ifdef HAVE_DINPUT_H +#include +#else +typedef struct +{ + int unused; +} DIDEVICEINSTANCE; +#endif + +#endif // SDL_directx_h_ diff --git a/contrib/SDL-3.2.8/src/core/windows/SDL_gameinput.c b/contrib/SDL-3.2.8/src/core/windows/SDL_gameinput.c new file mode 100644 index 0000000..9ac5912 --- /dev/null +++ b/contrib/SDL-3.2.8/src/core/windows/SDL_gameinput.c @@ -0,0 +1,98 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2025 Sam Lantinga + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. +*/ +#include "SDL_internal.h" + +#ifdef HAVE_GAMEINPUT_H + +#include "SDL_windows.h" +#include "SDL_gameinput.h" + +#ifdef SDL_PLATFORM_WIN32 +#include +// {11BE2A7E-4254-445A-9C09-FFC40F006918} +DEFINE_GUID(SDL_IID_GameInput, 0x11BE2A7E, 0x4254, 0x445A, 0x9C, 0x09, 0xFF, 0xC4, 0x0F, 0x00, 0x69, 0x18); +#endif + +static SDL_SharedObject *g_hGameInputDLL; +static IGameInput *g_pGameInput; +static int g_nGameInputRefCount; + +bool SDL_InitGameInput(IGameInput **ppGameInput) +{ + if (g_nGameInputRefCount == 0) { + g_hGameInputDLL = SDL_LoadObject("gameinput.dll"); + if (!g_hGameInputDLL) { + return false; + } + + typedef HRESULT (WINAPI *GameInputCreate_t)(IGameInput * *gameInput); + GameInputCreate_t GameInputCreateFunc = (GameInputCreate_t)SDL_LoadFunction(g_hGameInputDLL, "GameInputCreate"); + if (!GameInputCreateFunc) { + SDL_UnloadObject(g_hGameInputDLL); + return false; + } + + IGameInput *pGameInput = NULL; + HRESULT hr = GameInputCreateFunc(&pGameInput); + if (FAILED(hr)) { + SDL_UnloadObject(g_hGameInputDLL); + return WIN_SetErrorFromHRESULT("GameInputCreate failed", hr); + } + +#ifdef SDL_PLATFORM_WIN32 + hr = IGameInput_QueryInterface(pGameInput, &SDL_IID_GameInput, (void **)&g_pGameInput); + IGameInput_Release(pGameInput); + if (FAILED(hr)) { + SDL_UnloadObject(g_hGameInputDLL); + return WIN_SetErrorFromHRESULT("GameInput QueryInterface failed", hr); + } +#else + // Assume that the version we get is compatible with the current SDK + // If that isn't the case, define the correct GUID for SDL_IID_GameInput above + g_pGameInput = pGameInput; +#endif + } + ++g_nGameInputRefCount; + + if (ppGameInput) { + *ppGameInput = g_pGameInput; + } + return true; +} + +void SDL_QuitGameInput(void) +{ + SDL_assert(g_nGameInputRefCount > 0); + + --g_nGameInputRefCount; + if (g_nGameInputRefCount == 0) { + if (g_pGameInput) { + IGameInput_Release(g_pGameInput); + g_pGameInput = NULL; + } + if (g_hGameInputDLL) { + SDL_UnloadObject(g_hGameInputDLL); + g_hGameInputDLL = NULL; + } + } +} + +#endif // HAVE_GAMEINPUT_H diff --git a/contrib/SDL-3.2.8/src/core/windows/SDL_gameinput.h b/contrib/SDL-3.2.8/src/core/windows/SDL_gameinput.h new file mode 100644 index 0000000..0022c0b --- /dev/null +++ b/contrib/SDL-3.2.8/src/core/windows/SDL_gameinput.h @@ -0,0 +1,36 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2025 Sam Lantinga + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. +*/ +#include "SDL_internal.h" + +#ifndef SDL_gameinput_h_ +#define SDL_gameinput_h_ + +#ifdef HAVE_GAMEINPUT_H + +#define COBJMACROS +#include + +extern bool SDL_InitGameInput(IGameInput **ppGameInput); +extern void SDL_QuitGameInput(void); + +#endif // HAVE_GAMEINPUT_H + +#endif // SDL_gameinput_h_ diff --git a/contrib/SDL-3.2.8/src/core/windows/SDL_hid.c b/contrib/SDL-3.2.8/src/core/windows/SDL_hid.c new file mode 100644 index 0000000..87e8735 --- /dev/null +++ b/contrib/SDL-3.2.8/src/core/windows/SDL_hid.c @@ -0,0 +1,254 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2025 Sam Lantinga + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. +*/ +#include "SDL_internal.h" + +#include "SDL_hid.h" + +HidD_GetAttributes_t SDL_HidD_GetAttributes; +HidD_GetString_t SDL_HidD_GetManufacturerString; +HidD_GetString_t SDL_HidD_GetProductString; +HidP_GetCaps_t SDL_HidP_GetCaps; +HidP_GetButtonCaps_t SDL_HidP_GetButtonCaps; +HidP_GetValueCaps_t SDL_HidP_GetValueCaps; +HidP_MaxDataListLength_t SDL_HidP_MaxDataListLength; +HidP_GetData_t SDL_HidP_GetData; + +static HMODULE s_pHIDDLL = 0; +static int s_HIDDLLRefCount = 0; + + +bool WIN_LoadHIDDLL(void) +{ + if (s_pHIDDLL) { + SDL_assert(s_HIDDLLRefCount > 0); + s_HIDDLLRefCount++; + return true; // already loaded + } + + s_pHIDDLL = LoadLibrary(TEXT("hid.dll")); + if (!s_pHIDDLL) { + return false; + } + + SDL_assert(s_HIDDLLRefCount == 0); + s_HIDDLLRefCount = 1; + + SDL_HidD_GetAttributes = (HidD_GetAttributes_t)GetProcAddress(s_pHIDDLL, "HidD_GetAttributes"); + SDL_HidD_GetManufacturerString = (HidD_GetString_t)GetProcAddress(s_pHIDDLL, "HidD_GetManufacturerString"); + SDL_HidD_GetProductString = (HidD_GetString_t)GetProcAddress(s_pHIDDLL, "HidD_GetProductString"); + SDL_HidP_GetCaps = (HidP_GetCaps_t)GetProcAddress(s_pHIDDLL, "HidP_GetCaps"); + SDL_HidP_GetButtonCaps = (HidP_GetButtonCaps_t)GetProcAddress(s_pHIDDLL, "HidP_GetButtonCaps"); + SDL_HidP_GetValueCaps = (HidP_GetValueCaps_t)GetProcAddress(s_pHIDDLL, "HidP_GetValueCaps"); + SDL_HidP_MaxDataListLength = (HidP_MaxDataListLength_t)GetProcAddress(s_pHIDDLL, "HidP_MaxDataListLength"); + SDL_HidP_GetData = (HidP_GetData_t)GetProcAddress(s_pHIDDLL, "HidP_GetData"); + if (!SDL_HidD_GetManufacturerString || !SDL_HidD_GetProductString || + !SDL_HidP_GetCaps || !SDL_HidP_GetButtonCaps || + !SDL_HidP_GetValueCaps || !SDL_HidP_MaxDataListLength || !SDL_HidP_GetData) { + WIN_UnloadHIDDLL(); + return false; + } + + return true; +} + +void WIN_UnloadHIDDLL(void) +{ + if (s_pHIDDLL) { + SDL_assert(s_HIDDLLRefCount > 0); + if (--s_HIDDLLRefCount == 0) { + FreeLibrary(s_pHIDDLL); + s_pHIDDLL = NULL; + } + } else { + SDL_assert(s_HIDDLLRefCount == 0); + } +} + +#if !defined(SDL_PLATFORM_XBOXONE) && !defined(SDL_PLATFORM_XBOXSERIES) + +// CM_Register_Notification definitions + +#define CR_SUCCESS 0 + +DECLARE_HANDLE(HCMNOTIFICATION); +typedef HCMNOTIFICATION *PHCMNOTIFICATION; + +typedef enum _CM_NOTIFY_FILTER_TYPE +{ + CM_NOTIFY_FILTER_TYPE_DEVICEINTERFACE = 0, + CM_NOTIFY_FILTER_TYPE_DEVICEHANDLE, + CM_NOTIFY_FILTER_TYPE_DEVICEINSTANCE, + CM_NOTIFY_FILTER_TYPE_MAX +} CM_NOTIFY_FILTER_TYPE, *PCM_NOTIFY_FILTER_TYPE; + +typedef struct _CM_NOTIFY_FILTER +{ + DWORD cbSize; + DWORD Flags; + CM_NOTIFY_FILTER_TYPE FilterType; + DWORD Reserved; + union + { + struct + { + GUID ClassGuid; + } DeviceInterface; + struct + { + HANDLE hTarget; + } DeviceHandle; + struct + { + WCHAR InstanceId[200]; + } DeviceInstance; + } u; +} CM_NOTIFY_FILTER, *PCM_NOTIFY_FILTER; + +typedef enum _CM_NOTIFY_ACTION +{ + CM_NOTIFY_ACTION_DEVICEINTERFACEARRIVAL = 0, + CM_NOTIFY_ACTION_DEVICEINTERFACEREMOVAL, + CM_NOTIFY_ACTION_DEVICEQUERYREMOVE, + CM_NOTIFY_ACTION_DEVICEQUERYREMOVEFAILED, + CM_NOTIFY_ACTION_DEVICEREMOVEPENDING, + CM_NOTIFY_ACTION_DEVICEREMOVECOMPLETE, + CM_NOTIFY_ACTION_DEVICECUSTOMEVENT, + CM_NOTIFY_ACTION_DEVICEINSTANCEENUMERATED, + CM_NOTIFY_ACTION_DEVICEINSTANCESTARTED, + CM_NOTIFY_ACTION_DEVICEINSTANCEREMOVED, + CM_NOTIFY_ACTION_MAX +} CM_NOTIFY_ACTION, *PCM_NOTIFY_ACTION; + +typedef struct _CM_NOTIFY_EVENT_DATA +{ + CM_NOTIFY_FILTER_TYPE FilterType; + DWORD Reserved; + union + { + struct + { + GUID ClassGuid; + WCHAR SymbolicLink[ANYSIZE_ARRAY]; + } DeviceInterface; + struct + { + GUID EventGuid; + LONG NameOffset; + DWORD DataSize; + BYTE Data[ANYSIZE_ARRAY]; + } DeviceHandle; + struct + { + WCHAR InstanceId[ANYSIZE_ARRAY]; + } DeviceInstance; + } u; +} CM_NOTIFY_EVENT_DATA, *PCM_NOTIFY_EVENT_DATA; + +typedef DWORD (CALLBACK *PCM_NOTIFY_CALLBACK)(HCMNOTIFICATION hNotify, PVOID Context, CM_NOTIFY_ACTION Action, PCM_NOTIFY_EVENT_DATA EventData, DWORD EventDataSize); + +typedef DWORD (WINAPI *CM_Register_NotificationFunc)(PCM_NOTIFY_FILTER pFilter, PVOID pContext, PCM_NOTIFY_CALLBACK pCallback, PHCMNOTIFICATION pNotifyContext); +typedef DWORD (WINAPI *CM_Unregister_NotificationFunc)(HCMNOTIFICATION NotifyContext); + +static GUID GUID_DEVINTERFACE_HID = { 0x4D1E55B2L, 0xF16F, 0x11CF, { 0x88, 0xCB, 0x00, 0x11, 0x11, 0x00, 0x00, 0x30 } }; + +static int s_DeviceNotificationsRequested; +static HMODULE cfgmgr32_lib_handle; +static CM_Register_NotificationFunc CM_Register_Notification; +static CM_Unregister_NotificationFunc CM_Unregister_Notification; +static HCMNOTIFICATION s_DeviceNotificationFuncHandle; +static Uint64 s_LastDeviceNotification = 1; + +static DWORD CALLBACK SDL_DeviceNotificationFunc(HCMNOTIFICATION hNotify, PVOID context, CM_NOTIFY_ACTION action, PCM_NOTIFY_EVENT_DATA eventData, DWORD event_data_size) +{ + if (action == CM_NOTIFY_ACTION_DEVICEINTERFACEARRIVAL || + action == CM_NOTIFY_ACTION_DEVICEINTERFACEREMOVAL) { + s_LastDeviceNotification = SDL_GetTicksNS(); + } + return ERROR_SUCCESS; +} + +void WIN_InitDeviceNotification(void) +{ + ++s_DeviceNotificationsRequested; + if (s_DeviceNotificationsRequested > 1) { + return; + } + + cfgmgr32_lib_handle = LoadLibraryA("cfgmgr32.dll"); + if (cfgmgr32_lib_handle) { + CM_Register_Notification = (CM_Register_NotificationFunc)GetProcAddress(cfgmgr32_lib_handle, "CM_Register_Notification"); + CM_Unregister_Notification = (CM_Unregister_NotificationFunc)GetProcAddress(cfgmgr32_lib_handle, "CM_Unregister_Notification"); + if (CM_Register_Notification && CM_Unregister_Notification) { + CM_NOTIFY_FILTER notify_filter; + + SDL_zero(notify_filter); + notify_filter.cbSize = sizeof(notify_filter); + notify_filter.FilterType = CM_NOTIFY_FILTER_TYPE_DEVICEINTERFACE; + notify_filter.u.DeviceInterface.ClassGuid = GUID_DEVINTERFACE_HID; + if (CM_Register_Notification(¬ify_filter, NULL, SDL_DeviceNotificationFunc, &s_DeviceNotificationFuncHandle) == CR_SUCCESS) { + return; + } + } + } + + // FIXME: Should we log errors? +} + +Uint64 WIN_GetLastDeviceNotification(void) +{ + return s_LastDeviceNotification; +} + +void WIN_QuitDeviceNotification(void) +{ + if (--s_DeviceNotificationsRequested > 0) { + return; + } + // Make sure we have balanced calls to init/quit + SDL_assert(s_DeviceNotificationsRequested == 0); + + if (cfgmgr32_lib_handle) { + if (s_DeviceNotificationFuncHandle && CM_Unregister_Notification) { + CM_Unregister_Notification(s_DeviceNotificationFuncHandle); + s_DeviceNotificationFuncHandle = NULL; + } + + FreeLibrary(cfgmgr32_lib_handle); + cfgmgr32_lib_handle = NULL; + } +} + +#else + +void WIN_InitDeviceNotification(void) +{ +} + +Uint64 WIN_GetLastDeviceNotification( void ) +{ + return 0; +} + +void WIN_QuitDeviceNotification(void) +{ +} + +#endif // !SDL_PLATFORM_XBOXONE && !SDL_PLATFORM_XBOXSERIES diff --git a/contrib/SDL-3.2.8/src/core/windows/SDL_hid.h b/contrib/SDL-3.2.8/src/core/windows/SDL_hid.h new file mode 100644 index 0000000..46c22f2 --- /dev/null +++ b/contrib/SDL-3.2.8/src/core/windows/SDL_hid.h @@ -0,0 +1,215 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2025 Sam Lantinga + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. +*/ +#include "SDL_internal.h" + +#ifndef SDL_hid_h_ +#define SDL_hid_h_ + +#include "SDL_windows.h" + +typedef LONG NTSTATUS; +typedef USHORT USAGE; +typedef struct _HIDP_PREPARSED_DATA *PHIDP_PREPARSED_DATA; + +typedef struct _HIDD_ATTRIBUTES +{ + ULONG Size; + USHORT VendorID; + USHORT ProductID; + USHORT VersionNumber; +} HIDD_ATTRIBUTES, *PHIDD_ATTRIBUTES; + +typedef enum +{ + HidP_Input = 0, + HidP_Output = 1, + HidP_Feature = 2 +} HIDP_REPORT_TYPE; + +typedef struct +{ + USAGE UsagePage; + UCHAR ReportID; + BOOLEAN IsAlias; + USHORT BitField; + USHORT LinkCollection; + USAGE LinkUsage; + USAGE LinkUsagePage; + BOOLEAN IsRange; + BOOLEAN IsStringRange; + BOOLEAN IsDesignatorRange; + BOOLEAN IsAbsolute; + ULONG Reserved[10]; + union + { + struct + { + USAGE UsageMin; + USAGE UsageMax; + USHORT StringMin; + USHORT StringMax; + USHORT DesignatorMin; + USHORT DesignatorMax; + USHORT DataIndexMin; + USHORT DataIndexMax; + } Range; + struct + { + USAGE Usage; + USAGE Reserved1; + USHORT StringIndex; + USHORT Reserved2; + USHORT DesignatorIndex; + USHORT Reserved3; + USHORT DataIndex; + USHORT Reserved4; + } NotRange; + }; +} HIDP_BUTTON_CAPS, *PHIDP_BUTTON_CAPS; + +typedef struct +{ + USAGE UsagePage; + UCHAR ReportID; + BOOLEAN IsAlias; + USHORT BitField; + USHORT LinkCollection; + USAGE LinkUsage; + USAGE LinkUsagePage; + BOOLEAN IsRange; + BOOLEAN IsStringRange; + BOOLEAN IsDesignatorRange; + BOOLEAN IsAbsolute; + BOOLEAN HasNull; + UCHAR Reserved; + USHORT BitSize; + USHORT ReportCount; + USHORT Reserved2[5]; + ULONG UnitsExp; + ULONG Units; + LONG LogicalMin; + LONG LogicalMax; + LONG PhysicalMin; + LONG PhysicalMax; + union + { + struct + { + USAGE UsageMin; + USAGE UsageMax; + USHORT StringMin; + USHORT StringMax; + USHORT DesignatorMin; + USHORT DesignatorMax; + USHORT DataIndexMin; + USHORT DataIndexMax; + } Range; + struct + { + USAGE Usage; + USAGE Reserved1; + USHORT StringIndex; + USHORT Reserved2; + USHORT DesignatorIndex; + USHORT Reserved3; + USHORT DataIndex; + USHORT Reserved4; + } NotRange; + }; +} HIDP_VALUE_CAPS, *PHIDP_VALUE_CAPS; + +typedef struct +{ + USAGE Usage; + USAGE UsagePage; + USHORT InputReportByteLength; + USHORT OutputReportByteLength; + USHORT FeatureReportByteLength; + USHORT Reserved[17]; + USHORT NumberLinkCollectionNodes; + USHORT NumberInputButtonCaps; + USHORT NumberInputValueCaps; + USHORT NumberInputDataIndices; + USHORT NumberOutputButtonCaps; + USHORT NumberOutputValueCaps; + USHORT NumberOutputDataIndices; + USHORT NumberFeatureButtonCaps; + USHORT NumberFeatureValueCaps; + USHORT NumberFeatureDataIndices; +} HIDP_CAPS, *PHIDP_CAPS; + +typedef struct +{ + USHORT DataIndex; + USHORT Reserved; + union + { + ULONG RawValue; + BOOLEAN On; + }; +} HIDP_DATA, *PHIDP_DATA; + +#define HIDP_ERROR_CODES(p1, p2) ((NTSTATUS)(((p1) << 28) | (0x11 << 16) | (p2))) +#define HIDP_STATUS_SUCCESS HIDP_ERROR_CODES(0x0, 0x0000) +#define HIDP_STATUS_NULL HIDP_ERROR_CODES(0x8, 0x0001) +#define HIDP_STATUS_INVALID_PREPARSED_DATA HIDP_ERROR_CODES(0xC, 0x0001) +#define HIDP_STATUS_INVALID_REPORT_TYPE HIDP_ERROR_CODES(0xC, 0x0002) +#define HIDP_STATUS_INVALID_REPORT_LENGTH HIDP_ERROR_CODES(0xC, 0x0003) +#define HIDP_STATUS_USAGE_NOT_FOUND HIDP_ERROR_CODES(0xC, 0x0004) +#define HIDP_STATUS_VALUE_OUT_OF_RANGE HIDP_ERROR_CODES(0xC, 0x0005) +#define HIDP_STATUS_BAD_LOG_PHY_VALUES HIDP_ERROR_CODES(0xC, 0x0006) +#define HIDP_STATUS_BUFFER_TOO_SMALL HIDP_ERROR_CODES(0xC, 0x0007) +#define HIDP_STATUS_INTERNAL_ERROR HIDP_ERROR_CODES(0xC, 0x0008) +#define HIDP_STATUS_I8042_TRANS_UNKNOWN HIDP_ERROR_CODES(0xC, 0x0009) +#define HIDP_STATUS_INCOMPATIBLE_REPORT_ID HIDP_ERROR_CODES(0xC, 0x000A) +#define HIDP_STATUS_NOT_VALUE_ARRAY HIDP_ERROR_CODES(0xC, 0x000B) +#define HIDP_STATUS_IS_VALUE_ARRAY HIDP_ERROR_CODES(0xC, 0x000C) +#define HIDP_STATUS_DATA_INDEX_NOT_FOUND HIDP_ERROR_CODES(0xC, 0x000D) +#define HIDP_STATUS_DATA_INDEX_OUT_OF_RANGE HIDP_ERROR_CODES(0xC, 0x000E) +#define HIDP_STATUS_BUTTON_NOT_PRESSED HIDP_ERROR_CODES(0xC, 0x000F) +#define HIDP_STATUS_REPORT_DOES_NOT_EXIST HIDP_ERROR_CODES(0xC, 0x0010) +#define HIDP_STATUS_NOT_IMPLEMENTED HIDP_ERROR_CODES(0xC, 0x0020) + +extern bool WIN_LoadHIDDLL(void); +extern void WIN_UnloadHIDDLL(void); + +typedef BOOLEAN (WINAPI *HidD_GetAttributes_t)(HANDLE HidDeviceObject, PHIDD_ATTRIBUTES Attributes); +typedef BOOLEAN (WINAPI *HidD_GetString_t)(HANDLE HidDeviceObject, PVOID Buffer, ULONG BufferLength); +typedef NTSTATUS (WINAPI *HidP_GetCaps_t)(PHIDP_PREPARSED_DATA PreparsedData, PHIDP_CAPS Capabilities); +typedef NTSTATUS (WINAPI *HidP_GetButtonCaps_t)(HIDP_REPORT_TYPE ReportType, PHIDP_BUTTON_CAPS ButtonCaps, PUSHORT ButtonCapsLength, PHIDP_PREPARSED_DATA PreparsedData); +typedef NTSTATUS (WINAPI *HidP_GetValueCaps_t)(HIDP_REPORT_TYPE ReportType, PHIDP_VALUE_CAPS ValueCaps, PUSHORT ValueCapsLength, PHIDP_PREPARSED_DATA PreparsedData); +typedef ULONG (WINAPI *HidP_MaxDataListLength_t)(HIDP_REPORT_TYPE ReportType, PHIDP_PREPARSED_DATA PreparsedData); +typedef NTSTATUS (WINAPI *HidP_GetData_t)(HIDP_REPORT_TYPE ReportType, PHIDP_DATA DataList, PULONG DataLength, PHIDP_PREPARSED_DATA PreparsedData, PCHAR Report, ULONG ReportLength); + +extern HidD_GetAttributes_t SDL_HidD_GetAttributes; +extern HidD_GetString_t SDL_HidD_GetManufacturerString; +extern HidD_GetString_t SDL_HidD_GetProductString; +extern HidP_GetCaps_t SDL_HidP_GetCaps; +extern HidP_GetButtonCaps_t SDL_HidP_GetButtonCaps; +extern HidP_GetValueCaps_t SDL_HidP_GetValueCaps; +extern HidP_MaxDataListLength_t SDL_HidP_MaxDataListLength; +extern HidP_GetData_t SDL_HidP_GetData; + +void WIN_InitDeviceNotification(void); +Uint64 WIN_GetLastDeviceNotification(void); +void WIN_QuitDeviceNotification(void); + +#endif // SDL_hid_h_ diff --git a/contrib/SDL-3.2.8/src/core/windows/SDL_immdevice.c b/contrib/SDL-3.2.8/src/core/windows/SDL_immdevice.c new file mode 100644 index 0000000..802a412 --- /dev/null +++ b/contrib/SDL-3.2.8/src/core/windows/SDL_immdevice.c @@ -0,0 +1,434 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2025 Sam Lantinga + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. +*/ +#include "SDL_internal.h" + +#if defined(SDL_PLATFORM_WINDOWS) && defined(HAVE_MMDEVICEAPI_H) + +#include "SDL_windows.h" +#include "SDL_immdevice.h" +#include "../../audio/SDL_sysaudio.h" +#include // For CLSIDFromString + +typedef struct SDL_IMMDevice_HandleData +{ + LPWSTR immdevice_id; + GUID directsound_guid; +} SDL_IMMDevice_HandleData; + +static const ERole SDL_IMMDevice_role = eConsole; // !!! FIXME: should this be eMultimedia? Should be a hint? + +// This is global to the WASAPI target, to handle hotplug and default device lookup. +static IMMDeviceEnumerator *enumerator = NULL; +static SDL_IMMDevice_callbacks immcallbacks; + +// PropVariantInit() is an inline function/macro in PropIdl.h that calls the C runtime's memset() directly. Use ours instead, to avoid dependency. +#ifdef PropVariantInit +#undef PropVariantInit +#endif +#define PropVariantInit(p) SDL_zerop(p) + +// Some GUIDs we need to know without linking to libraries that aren't available before Vista. +/* *INDENT-OFF* */ // clang-format off +static const CLSID SDL_CLSID_MMDeviceEnumerator = { 0xbcde0395, 0xe52f, 0x467c,{ 0x8e, 0x3d, 0xc4, 0x57, 0x92, 0x91, 0x69, 0x2e } }; +static const IID SDL_IID_IMMDeviceEnumerator = { 0xa95664d2, 0x9614, 0x4f35,{ 0xa7, 0x46, 0xde, 0x8d, 0xb6, 0x36, 0x17, 0xe6 } }; +static const IID SDL_IID_IMMNotificationClient = { 0x7991eec9, 0x7e89, 0x4d85,{ 0x83, 0x90, 0x6c, 0x70, 0x3c, 0xec, 0x60, 0xc0 } }; +static const IID SDL_IID_IMMEndpoint = { 0x1be09788, 0x6894, 0x4089,{ 0x85, 0x86, 0x9a, 0x2a, 0x6c, 0x26, 0x5a, 0xc5 } }; +static const PROPERTYKEY SDL_PKEY_Device_FriendlyName = { { 0xa45c254e, 0xdf1c, 0x4efd,{ 0x80, 0x20, 0x67, 0xd1, 0x46, 0xa8, 0x50, 0xe0, } }, 14 }; +static const PROPERTYKEY SDL_PKEY_AudioEngine_DeviceFormat = { { 0xf19f064d, 0x82c, 0x4e27,{ 0xbc, 0x73, 0x68, 0x82, 0xa1, 0xbb, 0x8e, 0x4c, } }, 0 }; +static const PROPERTYKEY SDL_PKEY_AudioEndpoint_GUID = { { 0x1da5d803, 0xd492, 0x4edd,{ 0x8c, 0x23, 0xe0, 0xc0, 0xff, 0xee, 0x7f, 0x0e, } }, 4 }; +/* *INDENT-ON* */ // clang-format on + +static bool FindByDevIDCallback(SDL_AudioDevice *device, void *userdata) +{ + LPCWSTR devid = (LPCWSTR)userdata; + if (devid && device && device->handle) { + const SDL_IMMDevice_HandleData *handle = (const SDL_IMMDevice_HandleData *)device->handle; + if (handle->immdevice_id && SDL_wcscmp(handle->immdevice_id, devid) == 0) { + return true; + } + } + return false; +} + +static SDL_AudioDevice *SDL_IMMDevice_FindByDevID(LPCWSTR devid) +{ + return SDL_FindPhysicalAudioDeviceByCallback(FindByDevIDCallback, (void *) devid); +} + +LPGUID SDL_IMMDevice_GetDirectSoundGUID(SDL_AudioDevice *device) +{ + return (device && device->handle) ? &(((SDL_IMMDevice_HandleData *) device->handle)->directsound_guid) : NULL; +} + +LPCWSTR SDL_IMMDevice_GetDevID(SDL_AudioDevice *device) +{ + return (device && device->handle) ? ((const SDL_IMMDevice_HandleData *) device->handle)->immdevice_id : NULL; +} + +static void GetMMDeviceInfo(IMMDevice *device, char **utf8dev, WAVEFORMATEXTENSIBLE *fmt, GUID *guid) +{ + /* PKEY_Device_FriendlyName gives you "Speakers (SoundBlaster Pro)" which drives me nuts. I'd rather it be + "SoundBlaster Pro (Speakers)" but I guess that's developers vs users. Windows uses the FriendlyName in + its own UIs, like Volume Control, etc. */ + IPropertyStore *props = NULL; + *utf8dev = NULL; + SDL_zerop(fmt); + if (SUCCEEDED(IMMDevice_OpenPropertyStore(device, STGM_READ, &props))) { + PROPVARIANT var; + PropVariantInit(&var); + if (SUCCEEDED(IPropertyStore_GetValue(props, &SDL_PKEY_Device_FriendlyName, &var))) { + *utf8dev = WIN_StringToUTF8W(var.pwszVal); + } + PropVariantClear(&var); + if (SUCCEEDED(IPropertyStore_GetValue(props, &SDL_PKEY_AudioEngine_DeviceFormat, &var))) { + SDL_memcpy(fmt, var.blob.pBlobData, SDL_min(var.blob.cbSize, sizeof(WAVEFORMATEXTENSIBLE))); + } + PropVariantClear(&var); + if (SUCCEEDED(IPropertyStore_GetValue(props, &SDL_PKEY_AudioEndpoint_GUID, &var))) { + (void)CLSIDFromString(var.pwszVal, guid); + } + PropVariantClear(&var); + IPropertyStore_Release(props); + } +} + +void SDL_IMMDevice_FreeDeviceHandle(SDL_AudioDevice *device) +{ + if (device && device->handle) { + SDL_IMMDevice_HandleData *handle = (SDL_IMMDevice_HandleData *) device->handle; + SDL_free(handle->immdevice_id); + SDL_free(handle); + device->handle = NULL; + } +} + +static SDL_AudioDevice *SDL_IMMDevice_Add(const bool recording, const char *devname, WAVEFORMATEXTENSIBLE *fmt, LPCWSTR devid, GUID *dsoundguid) +{ + /* You can have multiple endpoints on a device that are mutually exclusive ("Speakers" vs "Line Out" or whatever). + In a perfect world, things that are unplugged won't be in this collection. The only gotcha is probably for + phones and tablets, where you might have an internal speaker and a headphone jack and expect both to be + available and switch automatically. (!!! FIXME...?) */ + + if (!devname) { + return NULL; + } + + // see if we already have this one first. + SDL_AudioDevice *device = SDL_IMMDevice_FindByDevID(devid); + if (device) { + if (SDL_GetAtomicInt(&device->zombie)) { + // whoa, it came back! This can happen if you unplug and replug USB headphones while we're still keeping the SDL object alive. + // Kill this device's IMMDevice id; the device will go away when the app closes it, or maybe a new default device is chosen + // (possibly this reconnected device), so we just want to make sure IMMDevice doesn't try to find the old device by the existing ID string. + SDL_IMMDevice_HandleData *handle = (SDL_IMMDevice_HandleData *) device->handle; + SDL_free(handle->immdevice_id); + handle->immdevice_id = NULL; + device = NULL; // add a new device, below. + } + } + + if (!device) { + // handle is freed by SDL_IMMDevice_FreeDeviceHandle! + SDL_IMMDevice_HandleData *handle = (SDL_IMMDevice_HandleData *)SDL_malloc(sizeof(SDL_IMMDevice_HandleData)); + if (!handle) { + return NULL; + } + handle->immdevice_id = SDL_wcsdup(devid); + if (!handle->immdevice_id) { + SDL_free(handle); + return NULL; + } + SDL_memcpy(&handle->directsound_guid, dsoundguid, sizeof(GUID)); + + SDL_AudioSpec spec; + SDL_zero(spec); + spec.channels = (Uint8)fmt->Format.nChannels; + spec.freq = fmt->Format.nSamplesPerSec; + spec.format = SDL_WaveFormatExToSDLFormat((WAVEFORMATEX *)fmt); + + device = SDL_AddAudioDevice(recording, devname, &spec, handle); + if (!device) { + SDL_free(handle->immdevice_id); + SDL_free(handle); + } + } + + return device; +} + +/* We need a COM subclass of IMMNotificationClient for hotplug support, which is + easy in C++, but we have to tapdance more to make work in C. + Thanks to this page for coaching on how to make this work: + https://www.codeproject.com/Articles/13601/COM-in-plain-C */ + +typedef struct SDLMMNotificationClient +{ + const IMMNotificationClientVtbl *lpVtbl; + SDL_AtomicInt refcount; +} SDLMMNotificationClient; + +static HRESULT STDMETHODCALLTYPE SDLMMNotificationClient_QueryInterface(IMMNotificationClient *client, REFIID iid, void **ppv) +{ + if ((WIN_IsEqualIID(iid, &IID_IUnknown)) || (WIN_IsEqualIID(iid, &SDL_IID_IMMNotificationClient))) { + *ppv = client; + client->lpVtbl->AddRef(client); + return S_OK; + } + + *ppv = NULL; + return E_NOINTERFACE; +} + +static ULONG STDMETHODCALLTYPE SDLMMNotificationClient_AddRef(IMMNotificationClient *iclient) +{ + SDLMMNotificationClient *client = (SDLMMNotificationClient *)iclient; + return (ULONG)(SDL_AtomicIncRef(&client->refcount) + 1); +} + +static ULONG STDMETHODCALLTYPE SDLMMNotificationClient_Release(IMMNotificationClient *iclient) +{ + // client is a static object; we don't ever free it. + SDLMMNotificationClient *client = (SDLMMNotificationClient *)iclient; + const ULONG rc = SDL_AtomicDecRef(&client->refcount); + if (rc == 0) { + SDL_SetAtomicInt(&client->refcount, 0); // uhh... + return 0; + } + return rc - 1; +} + +// These are the entry points called when WASAPI device endpoints change. +static HRESULT STDMETHODCALLTYPE SDLMMNotificationClient_OnDefaultDeviceChanged(IMMNotificationClient *iclient, EDataFlow flow, ERole role, LPCWSTR pwstrDeviceId) +{ + if (role == SDL_IMMDevice_role) { + immcallbacks.default_audio_device_changed(SDL_IMMDevice_FindByDevID(pwstrDeviceId)); + } + return S_OK; +} + +static HRESULT STDMETHODCALLTYPE SDLMMNotificationClient_OnDeviceAdded(IMMNotificationClient *iclient, LPCWSTR pwstrDeviceId) +{ + /* we ignore this; devices added here then progress to ACTIVE, if appropriate, in + OnDeviceStateChange, making that a better place to deal with device adds. More + importantly: the first time you plug in a USB audio device, this callback will + fire, but when you unplug it, it isn't removed (it's state changes to NOTPRESENT). + Plugging it back in won't fire this callback again. */ + return S_OK; +} + +static HRESULT STDMETHODCALLTYPE SDLMMNotificationClient_OnDeviceRemoved(IMMNotificationClient *iclient, LPCWSTR pwstrDeviceId) +{ + return S_OK; // See notes in OnDeviceAdded handler about why we ignore this. +} + +static HRESULT STDMETHODCALLTYPE SDLMMNotificationClient_OnDeviceStateChanged(IMMNotificationClient *iclient, LPCWSTR pwstrDeviceId, DWORD dwNewState) +{ + IMMDevice *device = NULL; + + if (SUCCEEDED(IMMDeviceEnumerator_GetDevice(enumerator, pwstrDeviceId, &device))) { + IMMEndpoint *endpoint = NULL; + if (SUCCEEDED(IMMDevice_QueryInterface(device, &SDL_IID_IMMEndpoint, (void **)&endpoint))) { + EDataFlow flow; + if (SUCCEEDED(IMMEndpoint_GetDataFlow(endpoint, &flow))) { + const bool recording = (flow == eCapture); + if (dwNewState == DEVICE_STATE_ACTIVE) { + char *utf8dev; + WAVEFORMATEXTENSIBLE fmt; + GUID dsoundguid; + GetMMDeviceInfo(device, &utf8dev, &fmt, &dsoundguid); + if (utf8dev) { + SDL_IMMDevice_Add(recording, utf8dev, &fmt, pwstrDeviceId, &dsoundguid); + SDL_free(utf8dev); + } + } else { + immcallbacks.audio_device_disconnected(SDL_IMMDevice_FindByDevID(pwstrDeviceId)); + } + } + IMMEndpoint_Release(endpoint); + } + IMMDevice_Release(device); + } + + return S_OK; +} + +static HRESULT STDMETHODCALLTYPE SDLMMNotificationClient_OnPropertyValueChanged(IMMNotificationClient *client, LPCWSTR pwstrDeviceId, const PROPERTYKEY key) +{ + return S_OK; // we don't care about these. +} + +static const IMMNotificationClientVtbl notification_client_vtbl = { + SDLMMNotificationClient_QueryInterface, + SDLMMNotificationClient_AddRef, + SDLMMNotificationClient_Release, + SDLMMNotificationClient_OnDeviceStateChanged, + SDLMMNotificationClient_OnDeviceAdded, + SDLMMNotificationClient_OnDeviceRemoved, + SDLMMNotificationClient_OnDefaultDeviceChanged, + SDLMMNotificationClient_OnPropertyValueChanged +}; + +static SDLMMNotificationClient notification_client = { ¬ification_client_vtbl, { 1 } }; + +bool SDL_IMMDevice_Init(const SDL_IMMDevice_callbacks *callbacks) +{ + HRESULT ret; + + // just skip the discussion with COM here. + if (!WIN_IsWindowsVistaOrGreater()) { + return SDL_SetError("IMMDevice support requires Windows Vista or later"); + } + + if (FAILED(WIN_CoInitialize())) { + return SDL_SetError("IMMDevice: CoInitialize() failed"); + } + + ret = CoCreateInstance(&SDL_CLSID_MMDeviceEnumerator, NULL, CLSCTX_INPROC_SERVER, &SDL_IID_IMMDeviceEnumerator, (LPVOID *)&enumerator); + if (FAILED(ret)) { + WIN_CoUninitialize(); + return WIN_SetErrorFromHRESULT("IMMDevice CoCreateInstance(MMDeviceEnumerator)", ret); + } + + if (callbacks) { + SDL_copyp(&immcallbacks, callbacks); + } else { + SDL_zero(immcallbacks); + } + + if (!immcallbacks.audio_device_disconnected) { + immcallbacks.audio_device_disconnected = SDL_AudioDeviceDisconnected; + } + if (!immcallbacks.default_audio_device_changed) { + immcallbacks.default_audio_device_changed = SDL_DefaultAudioDeviceChanged; + } + + return true; +} + +void SDL_IMMDevice_Quit(void) +{ + if (enumerator) { + IMMDeviceEnumerator_UnregisterEndpointNotificationCallback(enumerator, (IMMNotificationClient *)¬ification_client); + IMMDeviceEnumerator_Release(enumerator); + enumerator = NULL; + } + + SDL_zero(immcallbacks); + + WIN_CoUninitialize(); +} + +bool SDL_IMMDevice_Get(SDL_AudioDevice *device, IMMDevice **immdevice, bool recording) +{ + const Uint64 timeout = SDL_GetTicks() + 8000; // intel's audio drivers can fail for up to EIGHT SECONDS after a device is connected or we wake from sleep. + + SDL_assert(device != NULL); + SDL_assert(immdevice != NULL); + + LPCWSTR devid = SDL_IMMDevice_GetDevID(device); + SDL_assert(devid != NULL); + + HRESULT ret; + while ((ret = IMMDeviceEnumerator_GetDevice(enumerator, devid, immdevice)) == E_NOTFOUND) { + const Uint64 now = SDL_GetTicks(); + if (timeout > now) { + const Uint64 ticksleft = timeout - now; + SDL_Delay((Uint32)SDL_min(ticksleft, 300)); // wait awhile and try again. + continue; + } + break; + } + + if (!SUCCEEDED(ret)) { + return WIN_SetErrorFromHRESULT("WASAPI can't find requested audio endpoint", ret); + } + return true; +} + +static void EnumerateEndpointsForFlow(const bool recording, SDL_AudioDevice **default_device) +{ + /* Note that WASAPI separates "adapter devices" from "audio endpoint devices" + ...one adapter device ("SoundBlaster Pro") might have multiple endpoint devices ("Speakers", "Line-Out"). */ + + IMMDeviceCollection *collection = NULL; + if (FAILED(IMMDeviceEnumerator_EnumAudioEndpoints(enumerator, recording ? eCapture : eRender, DEVICE_STATE_ACTIVE, &collection))) { + return; + } + + UINT total = 0; + if (FAILED(IMMDeviceCollection_GetCount(collection, &total))) { + IMMDeviceCollection_Release(collection); + return; + } + + LPWSTR default_devid = NULL; + if (default_device) { + IMMDevice *default_immdevice = NULL; + const EDataFlow dataflow = recording ? eCapture : eRender; + if (SUCCEEDED(IMMDeviceEnumerator_GetDefaultAudioEndpoint(enumerator, dataflow, SDL_IMMDevice_role, &default_immdevice))) { + LPWSTR devid = NULL; + if (SUCCEEDED(IMMDevice_GetId(default_immdevice, &devid))) { + default_devid = SDL_wcsdup(devid); // if this fails, oh well. + CoTaskMemFree(devid); + } + IMMDevice_Release(default_immdevice); + } + } + + for (UINT i = 0; i < total; i++) { + IMMDevice *immdevice = NULL; + if (SUCCEEDED(IMMDeviceCollection_Item(collection, i, &immdevice))) { + LPWSTR devid = NULL; + if (SUCCEEDED(IMMDevice_GetId(immdevice, &devid))) { + char *devname = NULL; + WAVEFORMATEXTENSIBLE fmt; + GUID dsoundguid; + SDL_zero(fmt); + SDL_zero(dsoundguid); + GetMMDeviceInfo(immdevice, &devname, &fmt, &dsoundguid); + if (devname) { + SDL_AudioDevice *sdldevice = SDL_IMMDevice_Add(recording, devname, &fmt, devid, &dsoundguid); + if (default_device && default_devid && SDL_wcscmp(default_devid, devid) == 0) { + *default_device = sdldevice; + } + SDL_free(devname); + } + CoTaskMemFree(devid); + } + IMMDevice_Release(immdevice); + } + } + + SDL_free(default_devid); + + IMMDeviceCollection_Release(collection); +} + +void SDL_IMMDevice_EnumerateEndpoints(SDL_AudioDevice **default_playback, SDL_AudioDevice **default_recording) +{ + EnumerateEndpointsForFlow(false, default_playback); + EnumerateEndpointsForFlow(true, default_recording); + + // if this fails, we just won't get hotplug events. Carry on anyhow. + IMMDeviceEnumerator_RegisterEndpointNotificationCallback(enumerator, (IMMNotificationClient *)¬ification_client); +} + +#endif // defined(SDL_PLATFORM_WINDOWS) && defined(HAVE_MMDEVICEAPI_H) diff --git a/contrib/SDL-3.2.8/src/core/windows/SDL_immdevice.h b/contrib/SDL-3.2.8/src/core/windows/SDL_immdevice.h new file mode 100644 index 0000000..66fdf13 --- /dev/null +++ b/contrib/SDL-3.2.8/src/core/windows/SDL_immdevice.h @@ -0,0 +1,45 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2025 Sam Lantinga + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. +*/ + +#ifndef SDL_IMMDEVICE_H +#define SDL_IMMDEVICE_H + +#define COBJMACROS +#include +#include + +struct SDL_AudioDevice; // defined in src/audio/SDL_sysaudio.h + +typedef struct SDL_IMMDevice_callbacks +{ + void (*audio_device_disconnected)(struct SDL_AudioDevice *device); + void (*default_audio_device_changed)(struct SDL_AudioDevice *new_default_device); +} SDL_IMMDevice_callbacks; + +bool SDL_IMMDevice_Init(const SDL_IMMDevice_callbacks *callbacks); +void SDL_IMMDevice_Quit(void); +bool SDL_IMMDevice_Get(struct SDL_AudioDevice *device, IMMDevice **immdevice, bool recording); +void SDL_IMMDevice_EnumerateEndpoints(struct SDL_AudioDevice **default_playback, struct SDL_AudioDevice **default_recording); +LPGUID SDL_IMMDevice_GetDirectSoundGUID(struct SDL_AudioDevice *device); +LPCWSTR SDL_IMMDevice_GetDevID(struct SDL_AudioDevice *device); +void SDL_IMMDevice_FreeDeviceHandle(struct SDL_AudioDevice *device); + +#endif // SDL_IMMDEVICE_H diff --git a/contrib/SDL-3.2.8/src/core/windows/SDL_windows.c b/contrib/SDL-3.2.8/src/core/windows/SDL_windows.c new file mode 100644 index 0000000..286e8e6 --- /dev/null +++ b/contrib/SDL-3.2.8/src/core/windows/SDL_windows.c @@ -0,0 +1,375 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2025 Sam Lantinga + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. +*/ +#include "SDL_internal.h" + +#if defined(SDL_PLATFORM_WINDOWS) + +#include "SDL_windows.h" + +#include // for CoInitialize/CoUninitialize (Win32 only) +#ifdef HAVE_ROAPI_H +#include // For RoInitialize/RoUninitialize (Win32 only) +#else +typedef enum RO_INIT_TYPE +{ + RO_INIT_SINGLETHREADED = 0, + RO_INIT_MULTITHREADED = 1 +} RO_INIT_TYPE; +#endif + +#ifndef _WIN32_WINNT_VISTA +#define _WIN32_WINNT_VISTA 0x0600 +#endif +#ifndef _WIN32_WINNT_WIN7 +#define _WIN32_WINNT_WIN7 0x0601 +#endif +#ifndef _WIN32_WINNT_WIN8 +#define _WIN32_WINNT_WIN8 0x0602 +#endif + +#ifndef LOAD_LIBRARY_SEARCH_SYSTEM32 +#define LOAD_LIBRARY_SEARCH_SYSTEM32 0x00000800 +#endif + +#ifndef WC_ERR_INVALID_CHARS +#define WC_ERR_INVALID_CHARS 0x00000080 +#endif + +// Sets an error message based on an HRESULT +bool WIN_SetErrorFromHRESULT(const char *prefix, HRESULT hr) +{ + TCHAR buffer[1024]; + char *message; + TCHAR *p = buffer; + DWORD c = FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM, NULL, hr, 0, + buffer, SDL_arraysize(buffer), NULL); + buffer[c] = 0; + // kill CR/LF that FormatMessage() sticks at the end + while (*p) { + if (*p == '\r') { + *p = 0; + break; + } + ++p; + } + message = WIN_StringToUTF8(buffer); + SDL_SetError("%s%s%s", prefix ? prefix : "", prefix ? ": " : "", message); + SDL_free(message); + return false; +} + +// Sets an error message based on GetLastError() +bool WIN_SetError(const char *prefix) +{ + return WIN_SetErrorFromHRESULT(prefix, GetLastError()); +} + +HRESULT +WIN_CoInitialize(void) +{ + /* SDL handles any threading model, so initialize with the default, which + is compatible with OLE and if that doesn't work, try multi-threaded mode. + + If you need multi-threaded mode, call CoInitializeEx() before SDL_Init() + */ +#if defined(SDL_PLATFORM_XBOXONE) || defined(SDL_PLATFORM_XBOXSERIES) + // On Xbox, there's no need to call CoInitializeEx (and it's not implemented) + return S_OK; +#else + HRESULT hr = CoInitializeEx(NULL, COINIT_APARTMENTTHREADED); + if (hr == RPC_E_CHANGED_MODE) { + hr = CoInitializeEx(NULL, COINIT_MULTITHREADED); + } + + // S_FALSE means success, but someone else already initialized. + // You still need to call CoUninitialize in this case! + if (hr == S_FALSE) { + return S_OK; + } + + return hr; +#endif +} + +void WIN_CoUninitialize(void) +{ + CoUninitialize(); +} + +FARPROC WIN_LoadComBaseFunction(const char *name) +{ + static bool s_bLoaded; + static HMODULE s_hComBase; + + if (!s_bLoaded) { + s_hComBase = LoadLibraryEx(TEXT("combase.dll"), NULL, LOAD_LIBRARY_SEARCH_SYSTEM32); + s_bLoaded = true; + } + if (s_hComBase) { + return GetProcAddress(s_hComBase, name); + } else { + return NULL; + } +} + +HRESULT +WIN_RoInitialize(void) +{ + typedef HRESULT(WINAPI * RoInitialize_t)(RO_INIT_TYPE initType); + RoInitialize_t RoInitializeFunc = (RoInitialize_t)WIN_LoadComBaseFunction("RoInitialize"); + if (RoInitializeFunc) { + // RO_INIT_SINGLETHREADED is equivalent to COINIT_APARTMENTTHREADED + HRESULT hr = RoInitializeFunc(RO_INIT_SINGLETHREADED); + if (hr == RPC_E_CHANGED_MODE) { + hr = RoInitializeFunc(RO_INIT_MULTITHREADED); + } + + // S_FALSE means success, but someone else already initialized. + // You still need to call RoUninitialize in this case! + if (hr == S_FALSE) { + return S_OK; + } + + return hr; + } else { + return E_NOINTERFACE; + } +} + +void WIN_RoUninitialize(void) +{ + typedef void(WINAPI * RoUninitialize_t)(void); + RoUninitialize_t RoUninitializeFunc = (RoUninitialize_t)WIN_LoadComBaseFunction("RoUninitialize"); + if (RoUninitializeFunc) { + RoUninitializeFunc(); + } +} + +#if !defined(SDL_PLATFORM_XBOXONE) && !defined(SDL_PLATFORM_XBOXSERIES) +static BOOL IsWindowsVersionOrGreater(WORD wMajorVersion, WORD wMinorVersion, WORD wServicePackMajor) +{ + OSVERSIONINFOEXW osvi; + DWORDLONG const dwlConditionMask = VerSetConditionMask( + VerSetConditionMask( + VerSetConditionMask( + 0, VER_MAJORVERSION, VER_GREATER_EQUAL), + VER_MINORVERSION, VER_GREATER_EQUAL), + VER_SERVICEPACKMAJOR, VER_GREATER_EQUAL); + + SDL_zero(osvi); + osvi.dwOSVersionInfoSize = sizeof(osvi); + osvi.dwMajorVersion = wMajorVersion; + osvi.dwMinorVersion = wMinorVersion; + osvi.wServicePackMajor = wServicePackMajor; + + return VerifyVersionInfoW(&osvi, VER_MAJORVERSION | VER_MINORVERSION | VER_SERVICEPACKMAJOR, dwlConditionMask) != FALSE; +} +#endif + +// apply some static variables so we only call into the Win32 API once per process for each check. +#if defined(SDL_PLATFORM_XBOXONE) || defined(SDL_PLATFORM_XBOXSERIES) + #define CHECKWINVER(notdesktop_platform_result, test) return (notdesktop_platform_result); +#else + #define CHECKWINVER(notdesktop_platform_result, test) \ + static bool checked = false; \ + static BOOL result = FALSE; \ + if (!checked) { \ + result = (test); \ + checked = true; \ + } \ + return result; +#endif + +// this is the oldest thing we run on (and we may lose support for this in SDL3 at any time!), +// so there's no "OrGreater" as that would always be TRUE. The other functions are here to +// ask "can we support a specific feature?" but this function is here to ask "do we need to do +// something different for an OS version we probably should abandon?" :) +BOOL WIN_IsWindowsXP(void) +{ + CHECKWINVER(FALSE, !WIN_IsWindowsVistaOrGreater() && IsWindowsVersionOrGreater(HIBYTE(_WIN32_WINNT_WINXP), LOBYTE(_WIN32_WINNT_WINXP), 0)); +} + +BOOL WIN_IsWindowsVistaOrGreater(void) +{ + CHECKWINVER(TRUE, IsWindowsVersionOrGreater(HIBYTE(_WIN32_WINNT_VISTA), LOBYTE(_WIN32_WINNT_VISTA), 0)); +} + +BOOL WIN_IsWindows7OrGreater(void) +{ + CHECKWINVER(TRUE, IsWindowsVersionOrGreater(HIBYTE(_WIN32_WINNT_WIN7), LOBYTE(_WIN32_WINNT_WIN7), 0)); +} + +BOOL WIN_IsWindows8OrGreater(void) +{ + CHECKWINVER(TRUE, IsWindowsVersionOrGreater(HIBYTE(_WIN32_WINNT_WIN8), LOBYTE(_WIN32_WINNT_WIN8), 0)); +} + +#undef CHECKWINVER + + +/* +WAVExxxCAPS gives you 31 bytes for the device name, and just truncates if it's +longer. However, since WinXP, you can use the WAVExxxCAPS2 structure, which +will give you a name GUID. The full name is in the Windows Registry under +that GUID, located here: HKLM\System\CurrentControlSet\Control\MediaCategories + +Note that drivers can report GUID_NULL for the name GUID, in which case, +Windows makes a best effort to fill in those 31 bytes in the usual place. +This info summarized from MSDN: + +http://web.archive.org/web/20131027093034/http://msdn.microsoft.com/en-us/library/windows/hardware/ff536382(v=vs.85).aspx + +Always look this up in the registry if possible, because the strings are +different! At least on Win10, I see "Yeti Stereo Microphone" in the +Registry, and a unhelpful "Microphone(Yeti Stereo Microph" in winmm. Sigh. + +(Also, DirectSound shouldn't be limited to 32 chars, but its device enum +has the same problem.) + +WASAPI doesn't need this. This is just for DirectSound/WinMM. +*/ +char *WIN_LookupAudioDeviceName(const WCHAR *name, const GUID *guid) +{ +#if defined(SDL_PLATFORM_XBOXONE) || defined(SDL_PLATFORM_XBOXSERIES) + return WIN_StringToUTF8W(name); // No registry access on Xbox, go with what we've got. +#else + static const GUID nullguid = { 0 }; + const unsigned char *ptr; + char keystr[128]; + WCHAR *strw = NULL; + bool rc; + HKEY hkey; + DWORD len = 0; + char *result = NULL; + + if (WIN_IsEqualGUID(guid, &nullguid)) { + return WIN_StringToUTF8(name); // No GUID, go with what we've got. + } + + ptr = (const unsigned char *)guid; + (void)SDL_snprintf(keystr, sizeof(keystr), + "System\\CurrentControlSet\\Control\\MediaCategories\\{%02X%02X%02X%02X-%02X%02X-%02X%02X-%02X%02X-%02X%02X%02X%02X%02X%02X}", + ptr[3], ptr[2], ptr[1], ptr[0], ptr[5], ptr[4], ptr[7], ptr[6], + ptr[8], ptr[9], ptr[10], ptr[11], ptr[12], ptr[13], ptr[14], ptr[15]); + + strw = WIN_UTF8ToString(keystr); + rc = (RegOpenKeyExW(HKEY_LOCAL_MACHINE, strw, 0, KEY_QUERY_VALUE, &hkey) == ERROR_SUCCESS); + SDL_free(strw); + if (!rc) { + return WIN_StringToUTF8(name); // oh well. + } + + rc = (RegQueryValueExW(hkey, L"Name", NULL, NULL, NULL, &len) == ERROR_SUCCESS); + if (!rc) { + RegCloseKey(hkey); + return WIN_StringToUTF8(name); // oh well. + } + + strw = (WCHAR *)SDL_malloc(len + sizeof(WCHAR)); + if (!strw) { + RegCloseKey(hkey); + return WIN_StringToUTF8(name); // oh well. + } + + rc = (RegQueryValueExW(hkey, L"Name", NULL, NULL, (LPBYTE)strw, &len) == ERROR_SUCCESS); + RegCloseKey(hkey); + if (!rc) { + SDL_free(strw); + return WIN_StringToUTF8(name); // oh well. + } + + strw[len / 2] = 0; // make sure it's null-terminated. + + result = WIN_StringToUTF8(strw); + SDL_free(strw); + return result ? result : WIN_StringToUTF8(name); +#endif +} + +BOOL WIN_IsEqualGUID(const GUID *a, const GUID *b) +{ + return (SDL_memcmp(a, b, sizeof(*a)) == 0); +} + +BOOL WIN_IsEqualIID(REFIID a, REFIID b) +{ + return (SDL_memcmp(a, b, sizeof(*a)) == 0); +} + +void WIN_RECTToRect(const RECT *winrect, SDL_Rect *sdlrect) +{ + sdlrect->x = winrect->left; + sdlrect->w = (winrect->right - winrect->left) + 1; + sdlrect->y = winrect->top; + sdlrect->h = (winrect->bottom - winrect->top) + 1; +} + +void WIN_RectToRECT(const SDL_Rect *sdlrect, RECT *winrect) +{ + winrect->left = sdlrect->x; + winrect->right = sdlrect->x + sdlrect->w - 1; + winrect->top = sdlrect->y; + winrect->bottom = sdlrect->y + sdlrect->h - 1; +} + +BOOL WIN_IsRectEmpty(const RECT *rect) +{ + // Calculating this manually because Xbox does not support Win32 IsRectEmpty. + return (rect->right <= rect->left) || (rect->bottom <= rect->top); +} + +// Some GUIDs we need to know without linking to libraries that aren't available before Vista. +/* *INDENT-OFF* */ // clang-format off +static const GUID SDL_KSDATAFORMAT_SUBTYPE_PCM = { 0x00000001, 0x0000, 0x0010,{ 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71 } }; +static const GUID SDL_KSDATAFORMAT_SUBTYPE_IEEE_FLOAT = { 0x00000003, 0x0000, 0x0010,{ 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71 } }; +/* *INDENT-ON* */ // clang-format on + +SDL_AudioFormat SDL_WaveFormatExToSDLFormat(WAVEFORMATEX *waveformat) +{ + if ((waveformat->wFormatTag == WAVE_FORMAT_IEEE_FLOAT) && (waveformat->wBitsPerSample == 32)) { + return SDL_AUDIO_F32; + } else if ((waveformat->wFormatTag == WAVE_FORMAT_PCM) && (waveformat->wBitsPerSample == 16)) { + return SDL_AUDIO_S16; + } else if ((waveformat->wFormatTag == WAVE_FORMAT_PCM) && (waveformat->wBitsPerSample == 32)) { + return SDL_AUDIO_S32; + } else if (waveformat->wFormatTag == WAVE_FORMAT_EXTENSIBLE) { + const WAVEFORMATEXTENSIBLE *ext = (const WAVEFORMATEXTENSIBLE *)waveformat; + if ((SDL_memcmp(&ext->SubFormat, &SDL_KSDATAFORMAT_SUBTYPE_IEEE_FLOAT, sizeof(GUID)) == 0) && (waveformat->wBitsPerSample == 32)) { + return SDL_AUDIO_F32; + } else if ((SDL_memcmp(&ext->SubFormat, &SDL_KSDATAFORMAT_SUBTYPE_PCM, sizeof(GUID)) == 0) && (waveformat->wBitsPerSample == 16)) { + return SDL_AUDIO_S16; + } else if ((SDL_memcmp(&ext->SubFormat, &SDL_KSDATAFORMAT_SUBTYPE_PCM, sizeof(GUID)) == 0) && (waveformat->wBitsPerSample == 32)) { + return SDL_AUDIO_S32; + } + } + return SDL_AUDIO_UNKNOWN; +} + + +int WIN_WideCharToMultiByte(UINT CodePage, DWORD dwFlags, LPCWCH lpWideCharStr, int cchWideChar, LPSTR lpMultiByteStr, int cbMultiByte, LPCCH lpDefaultChar, LPBOOL lpUsedDefaultChar) +{ + if (WIN_IsWindowsXP()) { + dwFlags &= ~WC_ERR_INVALID_CHARS; // not supported before Vista. Without this flag, it will just replace bogus chars with U+FFFD. You're on your own, WinXP. + } + return WideCharToMultiByte(CodePage, dwFlags, lpWideCharStr, cchWideChar, lpMultiByteStr, cbMultiByte, lpDefaultChar, lpUsedDefaultChar); +} + +#endif // defined(SDL_PLATFORM_WINDOWS) diff --git a/contrib/SDL-3.2.8/src/core/windows/SDL_windows.h b/contrib/SDL-3.2.8/src/core/windows/SDL_windows.h new file mode 100644 index 0000000..b781b12 --- /dev/null +++ b/contrib/SDL-3.2.8/src/core/windows/SDL_windows.h @@ -0,0 +1,172 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2025 Sam Lantinga + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. +*/ + +// This is an include file for windows.h with the SDL build settings + +#ifndef _INCLUDED_WINDOWS_H +#define _INCLUDED_WINDOWS_H + +#ifdef SDL_PLATFORM_WIN32 +#ifndef WIN32_LEAN_AND_MEAN +#define WIN32_LEAN_AND_MEAN 1 +#endif +#ifndef STRICT +#define STRICT 1 +#endif +#ifndef UNICODE +#define UNICODE 1 +#endif +#undef WINVER +#undef _WIN32_WINNT +#if defined(SDL_VIDEO_RENDER_D3D12) || defined(HAVE_DXGI1_6_H) +#define _WIN32_WINNT 0xA00 // For D3D12, 0xA00 is required +#elif defined(HAVE_SHELLSCALINGAPI_H) +#define _WIN32_WINNT 0x603 // For DPI support +#else +#define _WIN32_WINNT 0x501 // Need 0x410 for AlphaBlend() and 0x500 for EnumDisplayDevices(), 0x501 for raw input +#endif +#define WINVER _WIN32_WINNT + +#elif defined(SDL_PLATFORM_WINGDK) +#ifndef WIN32_LEAN_AND_MEAN +#define WIN32_LEAN_AND_MEAN 1 +#endif +#ifndef STRICT +#define STRICT 1 +#endif +#ifndef UNICODE +#define UNICODE 1 +#endif +#undef WINVER +#undef _WIN32_WINNT +#define _WIN32_WINNT 0xA00 +#define WINVER _WIN32_WINNT + +#elif defined(SDL_PLATFORM_XBOXONE) || defined(SDL_PLATFORM_XBOXSERIES) +#ifndef WIN32_LEAN_AND_MEAN +#define WIN32_LEAN_AND_MEAN 1 +#endif +#ifndef STRICT +#define STRICT 1 +#endif +#ifndef UNICODE +#define UNICODE 1 +#endif +#undef WINVER +#undef _WIN32_WINNT +#define _WIN32_WINNT 0xA00 +#define WINVER _WIN32_WINNT +#endif + +// See https://github.com/libsdl-org/SDL/pull/7607 +// force_align_arg_pointer attribute requires gcc >= 4.2.x. +#if defined(__clang__) +#define HAVE_FORCE_ALIGN_ARG_POINTER +#elif defined(__GNUC__) && (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 2)) +#define HAVE_FORCE_ALIGN_ARG_POINTER +#endif +#if defined(__GNUC__) && defined(__i386__) && defined(HAVE_FORCE_ALIGN_ARG_POINTER) +#define MINGW32_FORCEALIGN __attribute__((force_align_arg_pointer)) +#else +#define MINGW32_FORCEALIGN +#endif + +#include +#include // for REFIID with broken mingw.org headers +#include + +// Routines to convert from UTF8 to native Windows text +#define WIN_StringToUTF8W(S) SDL_iconv_string("UTF-8", "UTF-16LE", (const char *)(S), (SDL_wcslen(S) + 1) * sizeof(WCHAR)) +#define WIN_UTF8ToStringW(S) (WCHAR *)SDL_iconv_string("UTF-16LE", "UTF-8", (const char *)(S), SDL_strlen(S) + 1) +// !!! FIXME: UTF8ToString() can just be a SDL_strdup() here. +#define WIN_StringToUTF8A(S) SDL_iconv_string("UTF-8", "ASCII", (const char *)(S), (SDL_strlen(S) + 1)) +#define WIN_UTF8ToStringA(S) SDL_iconv_string("ASCII", "UTF-8", (const char *)(S), SDL_strlen(S) + 1) +#if UNICODE +#define WIN_StringToUTF8 WIN_StringToUTF8W +#define WIN_UTF8ToString WIN_UTF8ToStringW +#define SDL_tcslen SDL_wcslen +#define SDL_tcsstr SDL_wcsstr +#else +#define WIN_StringToUTF8 WIN_StringToUTF8A +#define WIN_UTF8ToString WIN_UTF8ToStringA +#define SDL_tcslen SDL_strlen +#define SDL_tcsstr SDL_strstr +#endif + +// Set up for C function definitions, even when using C++ +#ifdef __cplusplus +extern "C" { +#endif + +// Sets an error message based on a given HRESULT +extern bool WIN_SetErrorFromHRESULT(const char *prefix, HRESULT hr); + +// Sets an error message based on GetLastError(). Always returns false. +extern bool WIN_SetError(const char *prefix); + +// Load a function from combase.dll +FARPROC WIN_LoadComBaseFunction(const char *name); + +// Wrap up the oddities of CoInitialize() into a common function. +extern HRESULT WIN_CoInitialize(void); +extern void WIN_CoUninitialize(void); + +// Wrap up the oddities of RoInitialize() into a common function. +extern HRESULT WIN_RoInitialize(void); +extern void WIN_RoUninitialize(void); + +// Returns true if we're running on Windows XP (any service pack). DOES NOT CHECK XP "OR GREATER"! +extern BOOL WIN_IsWindowsXP(void); + +// Returns true if we're running on Windows Vista and newer +extern BOOL WIN_IsWindowsVistaOrGreater(void); + +// Returns true if we're running on Windows 7 and newer +extern BOOL WIN_IsWindows7OrGreater(void); + +// Returns true if we're running on Windows 8 and newer +extern BOOL WIN_IsWindows8OrGreater(void); + +// You need to SDL_free() the result of this call. +extern char *WIN_LookupAudioDeviceName(const WCHAR *name, const GUID *guid); + +// Checks to see if two GUID are the same. +extern BOOL WIN_IsEqualGUID(const GUID *a, const GUID *b); +extern BOOL WIN_IsEqualIID(REFIID a, REFIID b); + +// Convert between SDL_rect and RECT +extern void WIN_RECTToRect(const RECT *winrect, SDL_Rect *sdlrect); +extern void WIN_RectToRECT(const SDL_Rect *sdlrect, RECT *winrect); + +// Returns true if the rect is empty +extern BOOL WIN_IsRectEmpty(const RECT *rect); + +extern SDL_AudioFormat SDL_WaveFormatExToSDLFormat(WAVEFORMATEX *waveformat); + +// WideCharToMultiByte, but with some WinXP management. +extern int WIN_WideCharToMultiByte(UINT CodePage, DWORD dwFlags, LPCWCH lpWideCharStr, int cchWideChar, LPSTR lpMultiByteStr, int cbMultiByte, LPCCH lpDefaultChar, LPBOOL lpUsedDefaultChar); + +// Ends C function definitions when using C++ +#ifdef __cplusplus +} +#endif + +#endif // _INCLUDED_WINDOWS_H diff --git a/contrib/SDL-3.2.8/src/core/windows/SDL_xinput.c b/contrib/SDL-3.2.8/src/core/windows/SDL_xinput.c new file mode 100644 index 0000000..ba5e4c1 --- /dev/null +++ b/contrib/SDL-3.2.8/src/core/windows/SDL_xinput.c @@ -0,0 +1,140 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2025 Sam Lantinga + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. +*/ +#include "SDL_internal.h" + +#include "SDL_xinput.h" + +// Set up for C function definitions, even when using C++ +#ifdef __cplusplus +extern "C" { +#endif + +XInputGetState_t SDL_XInputGetState = NULL; +XInputSetState_t SDL_XInputSetState = NULL; +XInputGetCapabilities_t SDL_XInputGetCapabilities = NULL; +XInputGetCapabilitiesEx_t SDL_XInputGetCapabilitiesEx = NULL; +XInputGetBatteryInformation_t SDL_XInputGetBatteryInformation = NULL; +DWORD SDL_XInputVersion = 0; + +static HMODULE s_pXInputDLL = NULL; +static int s_XInputDLLRefCount = 0; + +#if defined(SDL_PLATFORM_XBOXONE) || defined(SDL_PLATFORM_XBOXSERIES) + +bool WIN_LoadXInputDLL(void) +{ + /* Getting handles to system dlls (via LoadLibrary and its variants) is not + * supported on Xbox, thus, pointers to XInput's functions can't be + * retrieved via GetProcAddress. + * + * When on Xbox, assume that XInput is already loaded, and directly map + * its XInput.h-declared functions to the SDL_XInput* set of function + * pointers. + */ + SDL_XInputGetState = (XInputGetState_t)XInputGetState; + SDL_XInputSetState = (XInputSetState_t)XInputSetState; + SDL_XInputGetCapabilities = (XInputGetCapabilities_t)XInputGetCapabilities; + SDL_XInputGetBatteryInformation = (XInputGetBatteryInformation_t)XInputGetBatteryInformation; + + // XInput 1.4 ships with Windows 8 and 8.1: + SDL_XInputVersion = (1 << 16) | 4; + + return true; +} + +void WIN_UnloadXInputDLL(void) +{ +} + +#else // !(defined(SDL_PLATFORM_XBOXONE) || defined(SDL_PLATFORM_XBOXSERIES)) + +bool WIN_LoadXInputDLL(void) +{ + DWORD version = 0; + + if (s_pXInputDLL) { + SDL_assert(s_XInputDLLRefCount > 0); + s_XInputDLLRefCount++; + return true; // already loaded + } + + /* NOTE: Don't load XinputUap.dll + * This is XInput emulation over Windows.Gaming.Input, and has all the + * limitations of that API (no devices at startup, no background input, etc.) + */ + version = (1 << 16) | 4; + s_pXInputDLL = LoadLibrary(TEXT("XInput1_4.dll")); // 1.4 Ships with Windows 8. + if (!s_pXInputDLL) { + version = (1 << 16) | 3; + s_pXInputDLL = LoadLibrary(TEXT("XInput1_3.dll")); // 1.3 can be installed as a redistributable component. + } + if (!s_pXInputDLL) { + s_pXInputDLL = LoadLibrary(TEXT("bin\\XInput1_3.dll")); + } + if (!s_pXInputDLL) { + // "9.1.0" Ships with Vista and Win7, and is more limited than 1.3+ (e.g. XInputGetStateEx is not available.) + s_pXInputDLL = LoadLibrary(TEXT("XInput9_1_0.dll")); + } + if (!s_pXInputDLL) { + return false; + } + + SDL_assert(s_XInputDLLRefCount == 0); + SDL_XInputVersion = version; + s_XInputDLLRefCount = 1; + + // 100 is the ordinal for _XInputGetStateEx, which returns the same struct as XinputGetState, but with extra data in wButtons for the guide button, we think... + SDL_XInputGetState = (XInputGetState_t)GetProcAddress(s_pXInputDLL, (LPCSTR)100); + if (!SDL_XInputGetState) { + SDL_XInputGetState = (XInputGetState_t)GetProcAddress(s_pXInputDLL, "XInputGetState"); + } + SDL_XInputSetState = (XInputSetState_t)GetProcAddress(s_pXInputDLL, "XInputSetState"); + SDL_XInputGetCapabilities = (XInputGetCapabilities_t)GetProcAddress(s_pXInputDLL, "XInputGetCapabilities"); + // 108 is the ordinal for _XInputGetCapabilitiesEx, which additionally returns VID/PID of the controller. + SDL_XInputGetCapabilitiesEx = (XInputGetCapabilitiesEx_t)GetProcAddress(s_pXInputDLL, (LPCSTR)108); + SDL_XInputGetBatteryInformation = (XInputGetBatteryInformation_t)GetProcAddress(s_pXInputDLL, "XInputGetBatteryInformation"); + if (!SDL_XInputGetState || !SDL_XInputSetState || !SDL_XInputGetCapabilities) { + WIN_UnloadXInputDLL(); + return false; + } + + return true; +} + +void WIN_UnloadXInputDLL(void) +{ + if (s_pXInputDLL) { + SDL_assert(s_XInputDLLRefCount > 0); + if (--s_XInputDLLRefCount == 0) { + FreeLibrary(s_pXInputDLL); + s_pXInputDLL = NULL; + } + } else { + SDL_assert(s_XInputDLLRefCount == 0); + } +} + +#endif + +// Ends C function definitions when using C++ +#ifdef __cplusplus +} +#endif diff --git a/contrib/SDL-3.2.8/src/core/windows/SDL_xinput.h b/contrib/SDL-3.2.8/src/core/windows/SDL_xinput.h new file mode 100644 index 0000000..d499cd5 --- /dev/null +++ b/contrib/SDL-3.2.8/src/core/windows/SDL_xinput.h @@ -0,0 +1,276 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2025 Sam Lantinga + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. +*/ +#include "SDL_internal.h" + +#ifndef SDL_xinput_h_ +#define SDL_xinput_h_ + +#include "SDL_windows.h" + +#ifdef HAVE_XINPUT_H +#if defined(SDL_PLATFORM_XBOXONE) || defined(SDL_PLATFORM_XBOXSERIES) +// Xbox supports an XInput wrapper which is a C++-only header... +#include // Required to compile with recent MSVC... +#include +using namespace XInputOnGameInput; +#else +#include +#endif +#endif // HAVE_XINPUT_H + +#ifndef XUSER_MAX_COUNT +#define XUSER_MAX_COUNT 4 +#endif +#ifndef XUSER_INDEX_ANY +#define XUSER_INDEX_ANY 0x000000FF +#endif +#ifndef XINPUT_CAPS_FFB_SUPPORTED +#define XINPUT_CAPS_FFB_SUPPORTED 0x0001 +#endif +#ifndef XINPUT_CAPS_WIRELESS +#define XINPUT_CAPS_WIRELESS 0x0002 +#endif + +#ifndef XINPUT_DEVSUBTYPE_UNKNOWN +#define XINPUT_DEVSUBTYPE_UNKNOWN 0x00 +#endif +#ifndef XINPUT_DEVSUBTYPE_GAMEPAD +#define XINPUT_DEVSUBTYPE_GAMEPAD 0x01 +#endif +#ifndef XINPUT_DEVSUBTYPE_WHEEL +#define XINPUT_DEVSUBTYPE_WHEEL 0x02 +#endif +#ifndef XINPUT_DEVSUBTYPE_ARCADE_STICK +#define XINPUT_DEVSUBTYPE_ARCADE_STICK 0x03 +#endif +#ifndef XINPUT_DEVSUBTYPE_FLIGHT_STICK +#define XINPUT_DEVSUBTYPE_FLIGHT_STICK 0x04 +#endif +#ifndef XINPUT_DEVSUBTYPE_DANCE_PAD +#define XINPUT_DEVSUBTYPE_DANCE_PAD 0x05 +#endif +#ifndef XINPUT_DEVSUBTYPE_GUITAR +#define XINPUT_DEVSUBTYPE_GUITAR 0x06 +#endif +#ifndef XINPUT_DEVSUBTYPE_GUITAR_ALTERNATE +#define XINPUT_DEVSUBTYPE_GUITAR_ALTERNATE 0x07 +#endif +#ifndef XINPUT_DEVSUBTYPE_DRUM_KIT +#define XINPUT_DEVSUBTYPE_DRUM_KIT 0x08 +#endif +#ifndef XINPUT_DEVSUBTYPE_GUITAR_BASS +#define XINPUT_DEVSUBTYPE_GUITAR_BASS 0x0B +#endif +#ifndef XINPUT_DEVSUBTYPE_ARCADE_PAD +#define XINPUT_DEVSUBTYPE_ARCADE_PAD 0x13 +#endif + +#ifndef XINPUT_FLAG_GAMEPAD +#define XINPUT_FLAG_GAMEPAD 0x01 +#endif + +#ifndef XINPUT_GAMEPAD_DPAD_UP +#define XINPUT_GAMEPAD_DPAD_UP 0x0001 +#endif +#ifndef XINPUT_GAMEPAD_DPAD_DOWN +#define XINPUT_GAMEPAD_DPAD_DOWN 0x0002 +#endif +#ifndef XINPUT_GAMEPAD_DPAD_LEFT +#define XINPUT_GAMEPAD_DPAD_LEFT 0x0004 +#endif +#ifndef XINPUT_GAMEPAD_DPAD_RIGHT +#define XINPUT_GAMEPAD_DPAD_RIGHT 0x0008 +#endif +#ifndef XINPUT_GAMEPAD_START +#define XINPUT_GAMEPAD_START 0x0010 +#endif +#ifndef XINPUT_GAMEPAD_BACK +#define XINPUT_GAMEPAD_BACK 0x0020 +#endif +#ifndef XINPUT_GAMEPAD_LEFT_THUMB +#define XINPUT_GAMEPAD_LEFT_THUMB 0x0040 +#endif +#ifndef XINPUT_GAMEPAD_RIGHT_THUMB +#define XINPUT_GAMEPAD_RIGHT_THUMB 0x0080 +#endif +#ifndef XINPUT_GAMEPAD_LEFT_SHOULDER +#define XINPUT_GAMEPAD_LEFT_SHOULDER 0x0100 +#endif +#ifndef XINPUT_GAMEPAD_RIGHT_SHOULDER +#define XINPUT_GAMEPAD_RIGHT_SHOULDER 0x0200 +#endif +#ifndef XINPUT_GAMEPAD_A +#define XINPUT_GAMEPAD_A 0x1000 +#endif +#ifndef XINPUT_GAMEPAD_B +#define XINPUT_GAMEPAD_B 0x2000 +#endif +#ifndef XINPUT_GAMEPAD_X +#define XINPUT_GAMEPAD_X 0x4000 +#endif +#ifndef XINPUT_GAMEPAD_Y +#define XINPUT_GAMEPAD_Y 0x8000 +#endif + +#ifndef XINPUT_GAMEPAD_GUIDE +#define XINPUT_GAMEPAD_GUIDE 0x0400 +#endif + +#ifndef BATTERY_DEVTYPE_GAMEPAD +#define BATTERY_DEVTYPE_GAMEPAD 0x00 +#endif + +#ifndef BATTERY_TYPE_DISCONNECTED +#define BATTERY_TYPE_DISCONNECTED 0x00 +#endif +#ifndef BATTERY_TYPE_WIRED +#define BATTERY_TYPE_WIRED 0x01 +#endif +#ifndef BATTERY_TYPE_UNKNOWN +#define BATTERY_TYPE_UNKNOWN 0xFF +#endif +#ifndef BATTERY_LEVEL_EMPTY +#define BATTERY_LEVEL_EMPTY 0x00 +#endif +#ifndef BATTERY_LEVEL_LOW +#define BATTERY_LEVEL_LOW 0x01 +#endif +#ifndef BATTERY_LEVEL_MEDIUM +#define BATTERY_LEVEL_MEDIUM 0x02 +#endif +#ifndef BATTERY_LEVEL_FULL +#define BATTERY_LEVEL_FULL 0x03 +#endif + +// Set up for C function definitions, even when using C++ +#ifdef __cplusplus +extern "C" { +#endif + +// typedef's for XInput structs we use + + +// This is the same as XINPUT_BATTERY_INFORMATION, but always defined instead of just if WIN32_WINNT >= _WIN32_WINNT_WIN8 +typedef struct +{ + BYTE BatteryType; + BYTE BatteryLevel; +} XINPUT_BATTERY_INFORMATION_EX; + +#ifndef HAVE_XINPUT_H + +typedef struct +{ + WORD wButtons; + BYTE bLeftTrigger; + BYTE bRightTrigger; + SHORT sThumbLX; + SHORT sThumbLY; + SHORT sThumbRX; + SHORT sThumbRY; +} XINPUT_GAMEPAD; + +typedef struct +{ + DWORD dwPacketNumber; + XINPUT_GAMEPAD Gamepad; +} XINPUT_STATE; + +typedef struct +{ + WORD wLeftMotorSpeed; + WORD wRightMotorSpeed; +} XINPUT_VIBRATION; + +typedef struct +{ + BYTE Type; + BYTE SubType; + WORD Flags; + XINPUT_GAMEPAD Gamepad; + XINPUT_VIBRATION Vibration; +} XINPUT_CAPABILITIES; + +#endif // HAVE_XINPUT_H + +// This struct is not defined in XInput headers. +typedef struct +{ + XINPUT_CAPABILITIES Capabilities; + WORD VendorId; + WORD ProductId; + WORD ProductVersion; + WORD unk1; + DWORD unk2; +} SDL_XINPUT_CAPABILITIES_EX; + +// Forward decl's for XInput API's we load dynamically and use if available +typedef DWORD(WINAPI *XInputGetState_t)( + DWORD dwUserIndex, // [in] Index of the gamer associated with the device + XINPUT_STATE *pState // [out] Receives the current state +); + +typedef DWORD(WINAPI *XInputSetState_t)( + DWORD dwUserIndex, // [in] Index of the gamer associated with the device + XINPUT_VIBRATION *pVibration // [in, out] The vibration information to send to the controller +); + +typedef DWORD(WINAPI *XInputGetCapabilities_t)( + DWORD dwUserIndex, // [in] Index of the gamer associated with the device + DWORD dwFlags, // [in] Input flags that identify the device type + XINPUT_CAPABILITIES *pCapabilities // [out] Receives the capabilities +); + +// Only available in XInput 1.4 that is shipped with Windows 8 and newer. +typedef DWORD(WINAPI *XInputGetCapabilitiesEx_t)( + DWORD dwReserved, // [in] Must be 1 + DWORD dwUserIndex, // [in] Index of the gamer associated with the device + DWORD dwFlags, // [in] Input flags that identify the device type + SDL_XINPUT_CAPABILITIES_EX *pCapabilitiesEx // [out] Receives the capabilities +); + +typedef DWORD(WINAPI *XInputGetBatteryInformation_t)( + DWORD dwUserIndex, + BYTE devType, + XINPUT_BATTERY_INFORMATION_EX *pBatteryInformation); + +extern bool WIN_LoadXInputDLL(void); +extern void WIN_UnloadXInputDLL(void); + +extern XInputGetState_t SDL_XInputGetState; +extern XInputSetState_t SDL_XInputSetState; +extern XInputGetCapabilities_t SDL_XInputGetCapabilities; +extern XInputGetCapabilitiesEx_t SDL_XInputGetCapabilitiesEx; +extern XInputGetBatteryInformation_t SDL_XInputGetBatteryInformation; +extern DWORD SDL_XInputVersion; // ((major << 16) & 0xFF00) | (minor & 0xFF) + +// Ends C function definitions when using C++ +#ifdef __cplusplus +} +#endif + +#define XINPUTGETSTATE SDL_XInputGetState +#define XINPUTSETSTATE SDL_XInputSetState +#define XINPUTGETCAPABILITIES SDL_XInputGetCapabilities +#define XINPUTGETCAPABILITIESEX SDL_XInputGetCapabilitiesEx +#define XINPUTGETBATTERYINFORMATION SDL_XInputGetBatteryInformation + +#endif // SDL_xinput_h_ diff --git a/contrib/SDL-3.2.8/src/core/windows/pch.c b/contrib/SDL-3.2.8/src/core/windows/pch.c new file mode 100644 index 0000000..4b0c6f8 --- /dev/null +++ b/contrib/SDL-3.2.8/src/core/windows/pch.c @@ -0,0 +1,21 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2025 Sam Lantinga + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. +*/ +#include "SDL_internal.h" diff --git a/contrib/SDL-3.2.8/src/core/windows/pch_cpp.cpp b/contrib/SDL-3.2.8/src/core/windows/pch_cpp.cpp new file mode 100644 index 0000000..4b0c6f8 --- /dev/null +++ b/contrib/SDL-3.2.8/src/core/windows/pch_cpp.cpp @@ -0,0 +1,21 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2025 Sam Lantinga + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. +*/ +#include "SDL_internal.h" diff --git a/contrib/SDL-3.2.8/src/core/windows/version.rc b/contrib/SDL-3.2.8/src/core/windows/version.rc new file mode 100644 index 0000000..050f1b8 --- /dev/null +++ b/contrib/SDL-3.2.8/src/core/windows/version.rc @@ -0,0 +1,38 @@ + +#include "winresrc.h" + +LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US + +///////////////////////////////////////////////////////////////////////////// +// +// Version +// + +VS_VERSION_INFO VERSIONINFO + FILEVERSION 3,2,8,0 + PRODUCTVERSION 3,2,8,0 + FILEFLAGSMASK 0x3fL + FILEFLAGS 0x0L + FILEOS 0x40004L + FILETYPE 0x2L + FILESUBTYPE 0x0L +BEGIN + BLOCK "StringFileInfo" + BEGIN + BLOCK "040904b0" + BEGIN + VALUE "CompanyName", "\0" + VALUE "FileDescription", "SDL\0" + VALUE "FileVersion", "3, 2, 8, 0\0" + VALUE "InternalName", "SDL\0" + VALUE "LegalCopyright", "Copyright (C) 2025 Sam Lantinga\0" + VALUE "OriginalFilename", "SDL3.dll\0" + VALUE "ProductName", "Simple DirectMedia Layer\0" + VALUE "ProductVersion", "3, 2, 8, 0\0" + END + END + BLOCK "VarFileInfo" + BEGIN + VALUE "Translation", 0x409, 1200 + END +END -- cgit v1.2.3