Atlas - SDL_xinputjoystick.c

Home / ext / SDL2 / src / joystick / windows Lines: 1 | Size: 18424 bytes [Download] [Show on GitHub] [Search similar files] [Raw] [Raw (proxy)]
[FILE BEGIN]
1/* 2 Simple DirectMedia Layer 3 Copyright (C) 1997-2018 Sam Lantinga <[email protected]> 4 5 This software is provided 'as-is', without any express or implied 6 warranty. In no event will the authors be held liable for any damages 7 arising from the use of this software. 8 9 Permission is granted to anyone to use this software for any purpose, 10 including commercial applications, and to alter it and redistribute it 11 freely, subject to the following restrictions: 12 13 1. The origin of this software must not be misrepresented; you must not 14 claim that you wrote the original software. If you use this software 15 in a product, an acknowledgment in the product documentation would be 16 appreciated but is not required. 17 2. Altered source versions must be plainly marked as such, and must not be 18 misrepresented as being the original software. 19 3. This notice may not be removed or altered from any source distribution. 20*/ 21#include "../../SDL_internal.h" 22 23#include "../SDL_sysjoystick.h" 24 25#if SDL_JOYSTICK_XINPUT 26 27#include "SDL_assert.h" 28#include "SDL_hints.h" 29#include "SDL_timer.h" 30#include "SDL_windowsjoystick_c.h" 31#include "SDL_xinputjoystick_c.h" 32#include "../hidapi/SDL_hidapijoystick_c.h" 33 34/* 35 * Internal stuff. 36 */ 37static SDL_bool s_bXInputEnabled = SDL_TRUE; 38static char *s_arrXInputDevicePath[XUSER_MAX_COUNT]; 39 40 41static SDL_bool 42SDL_XInputUseOldJoystickMapping() 43{ 44#ifdef __WINRT__ 45 /* TODO: remove this __WINRT__ block, but only after integrating with UWP/WinRT's HID API */ 46 /* FIXME: Why are Win8/10 different here? -flibit */ 47 return (NTDDI_VERSION < NTDDI_WIN10); 48#else 49 static int s_XInputUseOldJoystickMapping = -1; 50 if (s_XInputUseOldJoystickMapping < 0) { 51 s_XInputUseOldJoystickMapping = SDL_GetHintBoolean(SDL_HINT_XINPUT_USE_OLD_JOYSTICK_MAPPING, SDL_FALSE); 52 } 53 return (s_XInputUseOldJoystickMapping > 0); 54#endif 55} 56 57SDL_bool SDL_XINPUT_Enabled(void) 58{ 59 return s_bXInputEnabled; 60} 61 62int 63SDL_XINPUT_JoystickInit(void) 64{ 65 s_bXInputEnabled = SDL_GetHintBoolean(SDL_HINT_XINPUT_ENABLED, SDL_TRUE); 66 67 if (s_bXInputEnabled && WIN_LoadXInputDLL() < 0) { 68 s_bXInputEnabled = SDL_FALSE; /* oh well. */ 69 } 70 return 0; 71} 72 73static char * 74GetXInputName(const Uint8 userid, BYTE SubType) 75{ 76 char name[32]; 77 78 if (SDL_XInputUseOldJoystickMapping()) { 79 SDL_snprintf(name, sizeof(name), "X360 Controller #%u", 1 + userid); 80 } else { 81 switch (SubType) { 82 case XINPUT_DEVSUBTYPE_GAMEPAD: 83 SDL_snprintf(name, sizeof(name), "XInput Controller #%u", 1 + userid); 84 break; 85 case XINPUT_DEVSUBTYPE_WHEEL: 86 SDL_snprintf(name, sizeof(name), "XInput Wheel #%u", 1 + userid); 87 break; 88 case XINPUT_DEVSUBTYPE_ARCADE_STICK: 89 SDL_snprintf(name, sizeof(name), "XInput ArcadeStick #%u", 1 + userid); 90 break; 91 case XINPUT_DEVSUBTYPE_FLIGHT_STICK: 92 SDL_snprintf(name, sizeof(name), "XInput FlightStick #%u", 1 + userid); 93 break; 94 case XINPUT_DEVSUBTYPE_DANCE_PAD: 95 SDL_snprintf(name, sizeof(name), "XInput DancePad #%u", 1 + userid); 96 break; 97 case XINPUT_DEVSUBTYPE_GUITAR: 98 case XINPUT_DEVSUBTYPE_GUITAR_ALTERNATE: 99 case XINPUT_DEVSUBTYPE_GUITAR_BASS: 100 SDL_snprintf(name, sizeof(name), "XInput Guitar #%u", 1 + userid); 101 break; 102 case XINPUT_DEVSUBTYPE_DRUM_KIT: 103 SDL_snprintf(name, sizeof(name), "XInput DrumKit #%u", 1 + userid); 104 break; 105 case XINPUT_DEVSUBTYPE_ARCADE_PAD: 106 SDL_snprintf(name, sizeof(name), "XInput ArcadePad #%u", 1 + userid); 107 break; 108 default: 109 SDL_snprintf(name, sizeof(name), "XInput Device #%u", 1 + userid); 110 break; 111 } 112 } 113 return SDL_strdup(name); 114} 115 116/* We can't really tell what device is being used for XInput, but we can guess 117 and we'll be correct for the case where only one device is connected. 118 */ 119static void 120GuessXInputDevice(Uint8 userid, Uint16 *pVID, Uint16 *pPID, Uint16 *pVersion) 121{ 122#ifndef __WINRT__ /* TODO: remove this ifndef __WINRT__ block, but only after integrating with UWP/WinRT's HID API */ 123 124 PRAWINPUTDEVICELIST devices = NULL; 125 UINT i, j, device_count = 0; 126 127 if ((GetRawInputDeviceList(NULL, &device_count, sizeof(RAWINPUTDEVICELIST)) == -1) || (!device_count)) { 128 return; /* oh well. */ 129 } 130 131 devices = (PRAWINPUTDEVICELIST)SDL_malloc(sizeof(RAWINPUTDEVICELIST) * device_count); 132 if (devices == NULL) { 133 return; 134 } 135 136 if (GetRawInputDeviceList(devices, &device_count, sizeof(RAWINPUTDEVICELIST)) == -1) { 137 SDL_free(devices); 138 return; /* oh well. */ 139 } 140 141 for (i = 0; i < device_count; i++) { 142 RID_DEVICE_INFO rdi; 143 char devName[128]; 144 UINT rdiSize = sizeof(rdi); 145 UINT nameSize = SDL_arraysize(devName); 146 147 rdi.cbSize = sizeof(rdi); 148 if ((devices[i].dwType == RIM_TYPEHID) && 149 (GetRawInputDeviceInfoA(devices[i].hDevice, RIDI_DEVICEINFO, &rdi, &rdiSize) != ((UINT)-1)) && 150 (GetRawInputDeviceInfoA(devices[i].hDevice, RIDI_DEVICENAME, devName, &nameSize) != ((UINT)-1)) && 151 (SDL_strstr(devName, "IG_") != NULL)) { 152 SDL_bool found = SDL_FALSE; 153 for (j = 0; j < SDL_arraysize(s_arrXInputDevicePath); ++j) { 154 if (j == userid) { 155 continue; 156 } 157 if (!s_arrXInputDevicePath[j]) { 158 continue; 159 } 160 if (SDL_strcmp(devName, s_arrXInputDevicePath[j]) == 0) { 161 found = SDL_TRUE; 162 break; 163 } 164 } 165 if (found) { 166 /* We already have this device in our XInput device list */ 167 continue; 168 } 169 170 /* We don't actually know if this is the right device for this 171 * userid, but we'll record it so we'll at least be consistent 172 * when the raw device list changes. 173 */ 174 *pVID = (Uint16)rdi.hid.dwVendorId; 175 *pPID = (Uint16)rdi.hid.dwProductId; 176 *pVersion = (Uint16)rdi.hid.dwVersionNumber; 177 if (s_arrXInputDevicePath[userid]) { 178 SDL_free(s_arrXInputDevicePath[userid]); 179 } 180 s_arrXInputDevicePath[userid] = SDL_strdup(devName); 181 break; 182 } 183 } 184 SDL_free(devices); 185#endif /* ifndef __WINRT__ */ 186} 187 188static void 189AddXInputDevice(Uint8 userid, BYTE SubType, JoyStick_DeviceData **pContext) 190{ 191 Uint16 vendor = 0; 192 Uint16 product = 0; 193 Uint16 version = 0; 194 JoyStick_DeviceData *pPrevJoystick = NULL; 195 JoyStick_DeviceData *pNewJoystick = *pContext; 196 197 if (SDL_XInputUseOldJoystickMapping() && SubType != XINPUT_DEVSUBTYPE_GAMEPAD) 198 return; 199 200 if (SubType == XINPUT_DEVSUBTYPE_UNKNOWN) 201 return; 202 203 while (pNewJoystick) { 204 if (pNewJoystick->bXInputDevice && (pNewJoystick->XInputUserId == userid) && (pNewJoystick->SubType == SubType)) { 205 /* if we are replacing the front of the list then update it */ 206 if (pNewJoystick == *pContext) { 207 *pContext = pNewJoystick->pNext; 208 } else if (pPrevJoystick) { 209 pPrevJoystick->pNext = pNewJoystick->pNext; 210 } 211 212 pNewJoystick->pNext = SYS_Joystick; 213 SYS_Joystick = pNewJoystick; 214 return; /* already in the list. */ 215 } 216 217 pPrevJoystick = pNewJoystick; 218 pNewJoystick = pNewJoystick->pNext; 219 } 220 221 pNewJoystick = (JoyStick_DeviceData *)SDL_malloc(sizeof(JoyStick_DeviceData)); 222 if (!pNewJoystick) { 223 return; /* better luck next time? */ 224 } 225 SDL_zerop(pNewJoystick); 226 227 pNewJoystick->joystickname = GetXInputName(userid, SubType); 228 if (!pNewJoystick->joystickname) { 229 SDL_free(pNewJoystick); 230 return; /* better luck next time? */ 231 } 232 233 pNewJoystick->bXInputDevice = SDL_TRUE; 234 if (SDL_XInputUseOldJoystickMapping()) { 235 SDL_zero(pNewJoystick->guid); 236 } else { 237 Uint16 *guid16 = (Uint16 *)pNewJoystick->guid.data; 238 239 GuessXInputDevice(userid, &vendor, &product, &version); 240 241 *guid16++ = SDL_SwapLE16(SDL_HARDWARE_BUS_USB); 242 *guid16++ = 0; 243 *guid16++ = SDL_SwapLE16(vendor); 244 *guid16++ = 0; 245 *guid16++ = SDL_SwapLE16(product); 246 *guid16++ = 0; 247 *guid16++ = SDL_SwapLE16(version); 248 *guid16++ = 0; 249 250 /* Note that this is an XInput device and what subtype it is */ 251 pNewJoystick->guid.data[14] = 'x'; 252 pNewJoystick->guid.data[15] = SubType; 253 } 254 pNewJoystick->SubType = SubType; 255 pNewJoystick->XInputUserId = userid; 256 257 if (SDL_ShouldIgnoreJoystick(pNewJoystick->joystickname, pNewJoystick->guid)) { 258 SDL_free(pNewJoystick); 259 return; 260 } 261 262#ifdef SDL_JOYSTICK_HIDAPI 263 if (HIDAPI_IsDevicePresent(vendor, product, version)) { 264 /* The HIDAPI driver is taking care of this device */ 265 SDL_free(pNewJoystick); 266 return; 267 } 268#endif 269 270 WINDOWS_AddJoystickDevice(pNewJoystick); 271} 272 273static void 274DelXInputDevice(Uint8 userid) 275{ 276 if (s_arrXInputDevicePath[userid]) { 277 SDL_free(s_arrXInputDevicePath[userid]); 278 s_arrXInputDevicePath[userid] = NULL; 279 } 280} 281 282void 283SDL_XINPUT_JoystickDetect(JoyStick_DeviceData **pContext) 284{ 285 int iuserid; 286 287 if (!s_bXInputEnabled) { 288 return; 289 } 290 291 /* iterate in reverse, so these are in the final list in ascending numeric order. */ 292 for (iuserid = XUSER_MAX_COUNT - 1; iuserid >= 0; iuserid--) { 293 const Uint8 userid = (Uint8)iuserid; 294 XINPUT_CAPABILITIES capabilities; 295 if (XINPUTGETCAPABILITIES(userid, XINPUT_FLAG_GAMEPAD, &capabilities) == ERROR_SUCCESS) { 296 AddXInputDevice(userid, capabilities.SubType, pContext); 297 } else { 298 DelXInputDevice(userid); 299 } 300 } 301} 302 303int 304SDL_XINPUT_JoystickOpen(SDL_Joystick * joystick, JoyStick_DeviceData *joystickdevice) 305{ 306 const Uint8 userId = joystickdevice->XInputUserId; 307 XINPUT_CAPABILITIES capabilities; 308 XINPUT_VIBRATION state; 309 310 SDL_assert(s_bXInputEnabled); 311 SDL_assert(XINPUTGETCAPABILITIES); 312 SDL_assert(XINPUTSETSTATE); 313 SDL_assert(userId < XUSER_MAX_COUNT); 314 315 joystick->hwdata->bXInputDevice = SDL_TRUE; 316 317 if (XINPUTGETCAPABILITIES(userId, XINPUT_FLAG_GAMEPAD, &capabilities) != ERROR_SUCCESS) { 318 SDL_free(joystick->hwdata); 319 joystick->hwdata = NULL; 320 return SDL_SetError("Failed to obtain XInput device capabilities. Device disconnected?"); 321 } 322 SDL_zero(state); 323 joystick->hwdata->bXInputHaptic = (XINPUTSETSTATE(userId, &state) == ERROR_SUCCESS); 324 joystick->hwdata->userid = userId; 325 326 /* The XInput API has a hard coded button/axis mapping, so we just match it */ 327 if (SDL_XInputUseOldJoystickMapping()) { 328 joystick->naxes = 6; 329 joystick->nbuttons = 15; 330 } else { 331 joystick->naxes = 6; 332 joystick->nbuttons = 11; 333 joystick->nhats = 1; 334 } 335 return 0; 336} 337 338static void 339UpdateXInputJoystickBatteryInformation(SDL_Joystick * joystick, XINPUT_BATTERY_INFORMATION_EX *pBatteryInformation) 340{ 341 if (pBatteryInformation->BatteryType != BATTERY_TYPE_UNKNOWN) { 342 SDL_JoystickPowerLevel ePowerLevel = SDL_JOYSTICK_POWER_UNKNOWN; 343 if (pBatteryInformation->BatteryType == BATTERY_TYPE_WIRED) { 344 ePowerLevel = SDL_JOYSTICK_POWER_WIRED; 345 } else { 346 switch (pBatteryInformation->BatteryLevel) { 347 case BATTERY_LEVEL_EMPTY: 348 ePowerLevel = SDL_JOYSTICK_POWER_EMPTY; 349 break; 350 case BATTERY_LEVEL_LOW: 351 ePowerLevel = SDL_JOYSTICK_POWER_LOW; 352 break; 353 case BATTERY_LEVEL_MEDIUM: 354 ePowerLevel = SDL_JOYSTICK_POWER_MEDIUM; 355 break; 356 default: 357 case BATTERY_LEVEL_FULL: 358 ePowerLevel = SDL_JOYSTICK_POWER_FULL; 359 break; 360 } 361 } 362 363 SDL_PrivateJoystickBatteryLevel(joystick, ePowerLevel); 364 } 365} 366 367static void 368UpdateXInputJoystickState_OLD(SDL_Joystick * joystick, XINPUT_STATE_EX *pXInputState, XINPUT_BATTERY_INFORMATION_EX *pBatteryInformation) 369{ 370 static WORD s_XInputButtons[] = { 371 XINPUT_GAMEPAD_DPAD_UP, XINPUT_GAMEPAD_DPAD_DOWN, XINPUT_GAMEPAD_DPAD_LEFT, XINPUT_GAMEPAD_DPAD_RIGHT, 372 XINPUT_GAMEPAD_START, XINPUT_GAMEPAD_BACK, XINPUT_GAMEPAD_LEFT_THUMB, XINPUT_GAMEPAD_RIGHT_THUMB, 373 XINPUT_GAMEPAD_LEFT_SHOULDER, XINPUT_GAMEPAD_RIGHT_SHOULDER, 374 XINPUT_GAMEPAD_A, XINPUT_GAMEPAD_B, XINPUT_GAMEPAD_X, XINPUT_GAMEPAD_Y, 375 XINPUT_GAMEPAD_GUIDE 376 }; 377 WORD wButtons = pXInputState->Gamepad.wButtons; 378 Uint8 button; 379 380 SDL_PrivateJoystickAxis(joystick, 0, (Sint16)pXInputState->Gamepad.sThumbLX); 381 SDL_PrivateJoystickAxis(joystick, 1, (Sint16)(-SDL_max(-32767, pXInputState->Gamepad.sThumbLY))); 382 SDL_PrivateJoystickAxis(joystick, 2, (Sint16)pXInputState->Gamepad.sThumbRX); 383 SDL_PrivateJoystickAxis(joystick, 3, (Sint16)(-SDL_max(-32767, pXInputState->Gamepad.sThumbRY))); 384 SDL_PrivateJoystickAxis(joystick, 4, (Sint16)(((int)pXInputState->Gamepad.bLeftTrigger * 65535 / 255) - 32768)); 385 SDL_PrivateJoystickAxis(joystick, 5, (Sint16)(((int)pXInputState->Gamepad.bRightTrigger * 65535 / 255) - 32768)); 386 387 for (button = 0; button < SDL_arraysize(s_XInputButtons); ++button) { 388 SDL_PrivateJoystickButton(joystick, button, (wButtons & s_XInputButtons[button]) ? SDL_PRESSED : SDL_RELEASED); 389 } 390 391 UpdateXInputJoystickBatteryInformation(joystick, pBatteryInformation); 392} 393 394static void 395UpdateXInputJoystickState(SDL_Joystick * joystick, XINPUT_STATE_EX *pXInputState, XINPUT_BATTERY_INFORMATION_EX *pBatteryInformation) 396{ 397 static WORD s_XInputButtons[] = { 398 XINPUT_GAMEPAD_A, XINPUT_GAMEPAD_B, XINPUT_GAMEPAD_X, XINPUT_GAMEPAD_Y, 399 XINPUT_GAMEPAD_LEFT_SHOULDER, XINPUT_GAMEPAD_RIGHT_SHOULDER, XINPUT_GAMEPAD_BACK, XINPUT_GAMEPAD_START, 400 XINPUT_GAMEPAD_LEFT_THUMB, XINPUT_GAMEPAD_RIGHT_THUMB, 401 XINPUT_GAMEPAD_GUIDE 402 }; 403 WORD wButtons = pXInputState->Gamepad.wButtons; 404 Uint8 button; 405 Uint8 hat = SDL_HAT_CENTERED; 406 407 SDL_PrivateJoystickAxis(joystick, 0, pXInputState->Gamepad.sThumbLX); 408 SDL_PrivateJoystickAxis(joystick, 1, ~pXInputState->Gamepad.sThumbLY); 409 SDL_PrivateJoystickAxis(joystick, 2, ((int)pXInputState->Gamepad.bLeftTrigger * 257) - 32768); 410 SDL_PrivateJoystickAxis(joystick, 3, pXInputState->Gamepad.sThumbRX); 411 SDL_PrivateJoystickAxis(joystick, 4, ~pXInputState->Gamepad.sThumbRY); 412 SDL_PrivateJoystickAxis(joystick, 5, ((int)pXInputState->Gamepad.bRightTrigger * 257) - 32768); 413 414 for (button = 0; button < SDL_arraysize(s_XInputButtons); ++button) { 415 SDL_PrivateJoystickButton(joystick, button, (wButtons & s_XInputButtons[button]) ? SDL_PRESSED : SDL_RELEASED); 416 } 417 418 if (wButtons & XINPUT_GAMEPAD_DPAD_UP) { 419 hat |= SDL_HAT_UP; 420 } 421 if (wButtons & XINPUT_GAMEPAD_DPAD_DOWN) { 422 hat |= SDL_HAT_DOWN; 423 } 424 if (wButtons & XINPUT_GAMEPAD_DPAD_LEFT) { 425 hat |= SDL_HAT_LEFT; 426 } 427 if (wButtons & XINPUT_GAMEPAD_DPAD_RIGHT) { 428 hat |= SDL_HAT_RIGHT; 429 } 430 SDL_PrivateJoystickHat(joystick, 0, hat); 431 432 UpdateXInputJoystickBatteryInformation(joystick, pBatteryInformation); 433} 434 435int 436SDL_XINPUT_JoystickRumble(SDL_Joystick * joystick, Uint16 low_frequency_rumble, Uint16 high_frequency_rumble, Uint32 duration_ms) 437{ 438 XINPUT_VIBRATION XVibration; 439 440 if (!XINPUTSETSTATE) { 441 return SDL_Unsupported(); 442 } 443 444 XVibration.wLeftMotorSpeed = low_frequency_rumble; 445 XVibration.wRightMotorSpeed = high_frequency_rumble; 446 if (XINPUTSETSTATE(joystick->hwdata->userid, &XVibration) != ERROR_SUCCESS) { 447 return SDL_SetError("XInputSetState() failed"); 448 } 449 450 if ((low_frequency_rumble || high_frequency_rumble) && duration_ms) { 451 joystick->hwdata->rumble_expiration = SDL_GetTicks() + duration_ms; 452 } else { 453 joystick->hwdata->rumble_expiration = 0; 454 } 455 return 0; 456} 457 458void 459SDL_XINPUT_JoystickUpdate(SDL_Joystick * joystick) 460{ 461 HRESULT result; 462 XINPUT_STATE_EX XInputState; 463 XINPUT_BATTERY_INFORMATION_EX XBatteryInformation; 464 465 if (!XINPUTGETSTATE) 466 return; 467 468 result = XINPUTGETSTATE(joystick->hwdata->userid, &XInputState); 469 if (result == ERROR_DEVICE_NOT_CONNECTED) { 470 return; 471 } 472 473 SDL_zero(XBatteryInformation); 474 if (XINPUTGETBATTERYINFORMATION) { 475 result = XINPUTGETBATTERYINFORMATION(joystick->hwdata->userid, BATTERY_DEVTYPE_GAMEPAD, &XBatteryInformation); 476 } 477 478 /* only fire events if the data changed from last time */ 479 if (XInputState.dwPacketNumber && XInputState.dwPacketNumber != joystick->hwdata->dwPacketNumber) { 480 if (SDL_XInputUseOldJoystickMapping()) { 481 UpdateXInputJoystickState_OLD(joystick, &XInputState, &XBatteryInformation); 482 } else { 483 UpdateXInputJoystickState(joystick, &XInputState, &XBatteryInformation); 484 } 485 joystick->hwdata->dwPacketNumber = XInputState.dwPacketNumber; 486 } 487 488 if (joystick->hwdata->rumble_expiration) { 489 Uint32 now = SDL_GetTicks(); 490 if (SDL_TICKS_PASSED(now, joystick->hwdata->rumble_expiration)) { 491 SDL_XINPUT_JoystickRumble(joystick, 0, 0, 0); 492 } 493 } 494} 495 496void 497SDL_XINPUT_JoystickClose(SDL_Joystick * joystick) 498{ 499} 500 501void 502SDL_XINPUT_JoystickQuit(void) 503{ 504 if (s_bXInputEnabled) { 505 WIN_UnloadXInputDLL(); 506 } 507} 508 509#else /* !SDL_JOYSTICK_XINPUT */ 510 511typedef struct JoyStick_DeviceData JoyStick_DeviceData; 512 513SDL_bool SDL_XINPUT_Enabled(void) 514{ 515 return SDL_FALSE; 516} 517 518int 519SDL_XINPUT_JoystickInit(void) 520{ 521 return 0; 522} 523 524void 525SDL_XINPUT_JoystickDetect(JoyStick_DeviceData **pContext) 526{ 527} 528 529int 530SDL_XINPUT_JoystickOpen(SDL_Joystick * joystick, JoyStick_DeviceData *joystickdevice) 531{ 532 return SDL_Unsupported(); 533} 534 535int 536SDL_XINPUT_JoystickRumble(SDL_Joystick * joystick, Uint16 low_frequency_rumble, Uint16 high_frequency_rumble, Uint32 duration_ms) 537{ 538 return SDL_Unsupported(); 539} 540 541void 542SDL_XINPUT_JoystickUpdate(SDL_Joystick * joystick) 543{ 544} 545 546void 547SDL_XINPUT_JoystickClose(SDL_Joystick * joystick) 548{ 549} 550 551void 552SDL_XINPUT_JoystickQuit(void) 553{ 554} 555 556#endif /* SDL_JOYSTICK_XINPUT */ 557 558/* vi: set ts=4 sw=4 expandtab: */ 559
[FILE END]
(C) 2025 0x4248 (C) 2025 4248 Media and 4248 Systems, All part of 0x4248 See LICENCE files for more information. Not all files are by 0x4248 always check Licencing.