1/*
2 Simple DirectMedia Layer
3 Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
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#if defined(HAVE_GETHOSTNAME) && !defined(SDL_PLATFORM_WINDOWS)
24#include <unistd.h>
25#endif
26
27#include "joystick/SDL_joystick_c.h" // For SDL_GetGamepadTypeFromVIDPID()
28
29
30// Common utility functions that aren't in the public API
31
32int SDL_powerof2(int x)
33{
34 int value;
35
36 if (x <= 0) {
37 // Return some sane value - we shouldn't hit this in our use cases
38 return 1;
39 }
40
41 // This trick works for 32-bit values
42 {
43 SDL_COMPILE_TIME_ASSERT(SDL_powerof2, sizeof(x) == sizeof(Uint32));
44 }
45 value = x;
46 value -= 1;
47 value |= value >> 1;
48 value |= value >> 2;
49 value |= value >> 4;
50 value |= value >> 8;
51 value |= value >> 16;
52 value += 1;
53
54 return value;
55}
56
57Uint32 SDL_CalculateGCD(Uint32 a, Uint32 b)
58{
59 if (b == 0) {
60 return a;
61 }
62 return SDL_CalculateGCD(b, (a % b));
63}
64
65// Algorithm adapted with thanks from John Cook's blog post:
66// http://www.johndcook.com/blog/2010/10/20/best-rational-approximation
67void SDL_CalculateFraction(float x, int *numerator, int *denominator)
68{
69 const int N = 1000;
70 int a = 0, b = 1;
71 int c = 1, d = 0;
72
73 while (b <= N && d <= N) {
74 float mediant = (float)(a + c) / (b + d);
75 if (x == mediant) {
76 if (b + d <= N) {
77 *numerator = a + c;
78 *denominator = b + d;
79 } else if (d > b) {
80 *numerator = c;
81 *denominator = d;
82 } else {
83 *numerator = a;
84 *denominator = b;
85 }
86 return;
87 } else if (x > mediant) {
88 a = a + c;
89 b = b + d;
90 } else {
91 c = a + c;
92 d = b + d;
93 }
94 }
95 if (b > N) {
96 *numerator = c;
97 *denominator = d;
98 } else {
99 *numerator = a;
100 *denominator = b;
101 }
102}
103
104bool SDL_startswith(const char *string, const char *prefix)
105{
106 if (SDL_strncmp(string, prefix, SDL_strlen(prefix)) == 0) {
107 return true;
108 }
109 return false;
110}
111
112bool SDL_endswith(const char *string, const char *suffix)
113{
114 size_t string_length = string ? SDL_strlen(string) : 0;
115 size_t suffix_length = suffix ? SDL_strlen(suffix) : 0;
116
117 if (suffix_length > 0 && suffix_length <= string_length) {
118 if (SDL_memcmp(string + string_length - suffix_length, suffix, suffix_length) == 0) {
119 return true;
120 }
121 }
122 return false;
123}
124
125SDL_COMPILE_TIME_ASSERT(sizeof_object_id, sizeof(int) == sizeof(Uint32));
126
127Uint32 SDL_GetNextObjectID(void)
128{
129 static SDL_AtomicInt last_id;
130
131 Uint32 id = (Uint32)SDL_AtomicIncRef(&last_id) + 1;
132 if (id == 0) {
133 id = (Uint32)SDL_AtomicIncRef(&last_id) + 1;
134 }
135 return id;
136}
137
138static SDL_InitState SDL_objects_init;
139static SDL_HashTable *SDL_objects;
140
141static Uint32 SDLCALL SDL_HashObject(void *unused, const void *key)
142{
143 return (Uint32)(uintptr_t)key;
144}
145
146static bool SDL_KeyMatchObject(void *unused, const void *a, const void *b)
147{
148 return (a == b);
149}
150
151void SDL_SetObjectValid(void *object, SDL_ObjectType type, bool valid)
152{
153 SDL_assert(object != NULL);
154
155 if (SDL_ShouldInit(&SDL_objects_init)) {
156 SDL_objects = SDL_CreateHashTable(0, true, SDL_HashObject, SDL_KeyMatchObject, NULL, NULL);
157 const bool initialized = (SDL_objects != NULL);
158 SDL_SetInitialized(&SDL_objects_init, initialized);
159 if (!initialized) {
160 return;
161 }
162 }
163
164 if (valid) {
165 SDL_InsertIntoHashTable(SDL_objects, object, (void *)(uintptr_t)type, true);
166 } else {
167 SDL_RemoveFromHashTable(SDL_objects, object);
168 }
169}
170
171bool SDL_ObjectValid(void *object, SDL_ObjectType type)
172{
173 if (!object) {
174 return false;
175 }
176
177 const void *object_type;
178 if (!SDL_FindInHashTable(SDL_objects, object, &object_type)) {
179 return false;
180 }
181
182 return (((SDL_ObjectType)(uintptr_t)object_type) == type);
183}
184
185typedef struct GetOneObjectData
186{
187 const SDL_ObjectType type;
188 void **objects;
189 const int count;
190 int num_objects;
191} GetOneObjectData;
192
193static bool SDLCALL GetOneObject(void *userdata, const SDL_HashTable *table, const void *object, const void *object_type)
194{
195 GetOneObjectData *data = (GetOneObjectData *) userdata;
196 if ((SDL_ObjectType)(uintptr_t)object_type == data->type) {
197 if (data->num_objects < data->count) {
198 data->objects[data->num_objects] = (void *)object;
199 }
200 ++data->num_objects;
201 }
202 return true; // keep iterating.
203}
204
205
206int SDL_GetObjects(SDL_ObjectType type, void **objects, int count)
207{
208 GetOneObjectData data = { type, objects, count, 0 };
209 SDL_IterateHashTable(SDL_objects, GetOneObject, &data);
210 return data.num_objects;
211}
212
213static bool SDLCALL LogOneLeakedObject(void *userdata, const SDL_HashTable *table, const void *object, const void *object_type)
214{
215 const char *type = "unknown object";
216 switch ((SDL_ObjectType)(uintptr_t)object_type) {
217 #define SDLOBJTYPECASE(typ, name) case SDL_OBJECT_TYPE_##typ: type = name; break
218 SDLOBJTYPECASE(WINDOW, "SDL_Window");
219 SDLOBJTYPECASE(RENDERER, "SDL_Renderer");
220 SDLOBJTYPECASE(TEXTURE, "SDL_Texture");
221 SDLOBJTYPECASE(JOYSTICK, "SDL_Joystick");
222 SDLOBJTYPECASE(GAMEPAD, "SDL_Gamepad");
223 SDLOBJTYPECASE(HAPTIC, "SDL_Haptic");
224 SDLOBJTYPECASE(SENSOR, "SDL_Sensor");
225 SDLOBJTYPECASE(HIDAPI_DEVICE, "hidapi device");
226 SDLOBJTYPECASE(HIDAPI_JOYSTICK, "hidapi joystick");
227 SDLOBJTYPECASE(THREAD, "thread");
228 SDLOBJTYPECASE(TRAY, "SDL_Tray");
229 #undef SDLOBJTYPECASE
230 default: break;
231 }
232 SDL_Log("Leaked %s (%p)", type, object);
233 return true; // keep iterating.
234}
235
236void SDL_SetObjectsInvalid(void)
237{
238 if (SDL_ShouldQuit(&SDL_objects_init)) {
239 // Log any leaked objects
240 SDL_IterateHashTable(SDL_objects, LogOneLeakedObject, NULL);
241 SDL_assert(SDL_HashTableEmpty(SDL_objects));
242 SDL_DestroyHashTable(SDL_objects);
243 SDL_objects = NULL;
244 SDL_SetInitialized(&SDL_objects_init, false);
245 }
246}
247
248static int SDL_URIDecode(const char *src, char *dst, int len)
249{
250 int ri, wi, di;
251 char decode = '\0';
252 if (!src || !dst || len < 0) {
253 return -1;
254 }
255 if (len == 0) {
256 len = (int)SDL_strlen(src);
257 }
258 for (ri = 0, wi = 0, di = 0; ri < len && wi < len; ri += 1) {
259 if (di == 0) {
260 // start decoding
261 if (src[ri] == '%') {
262 decode = '\0';
263 di += 1;
264 continue;
265 }
266 // normal write
267 dst[wi] = src[ri];
268 wi += 1;
269 } else if (di == 1 || di == 2) {
270 char off = '\0';
271 char isa = src[ri] >= 'a' && src[ri] <= 'f';
272 char isA = src[ri] >= 'A' && src[ri] <= 'F';
273 char isn = src[ri] >= '0' && src[ri] <= '9';
274 if (!(isa || isA || isn)) {
275 // not a hexadecimal
276 int sri;
277 for (sri = ri - di; sri <= ri; sri += 1) {
278 dst[wi] = src[sri];
279 wi += 1;
280 }
281 di = 0;
282 continue;
283 }
284 // itsy bitsy magicsy
285 if (isn) {
286 off = 0 - '0';
287 } else if (isa) {
288 off = 10 - 'a';
289 } else if (isA) {
290 off = 10 - 'A';
291 }
292 decode |= (src[ri] + off) << (2 - di) * 4;
293 if (di == 2) {
294 dst[wi] = decode;
295 wi += 1;
296 di = 0;
297 } else {
298 di += 1;
299 }
300 }
301 }
302 dst[wi] = '\0';
303 return wi;
304}
305
306int SDL_URIToLocal(const char *src, char *dst)
307{
308 if (SDL_memcmp(src, "file:/", 6) == 0) {
309 src += 6; // local file?
310 } else if (SDL_strstr(src, ":/") != NULL) {
311 return -1; // wrong scheme
312 }
313
314 bool local = src[0] != '/' || (src[0] != '\0' && src[1] == '/');
315
316 // Check the hostname, if present. RFC 3986 states that the hostname component of a URI is not case-sensitive.
317 if (!local && src[0] == '/' && src[2] != '/') {
318 char *hostname_end = SDL_strchr(src + 1, '/');
319 if (hostname_end) {
320 const size_t src_len = hostname_end - (src + 1);
321 size_t hostname_len;
322
323#if defined(HAVE_GETHOSTNAME) && !defined(SDL_PLATFORM_WINDOWS)
324 char hostname[257];
325 if (gethostname(hostname, 255) == 0) {
326 hostname[256] = '\0';
327 hostname_len = SDL_strlen(hostname);
328 if (hostname_len == src_len && SDL_strncasecmp(src + 1, hostname, src_len) == 0) {
329 src = hostname_end + 1;
330 local = true;
331 }
332 }
333#endif
334
335 if (!local) {
336 static const char *localhost = "localhost";
337 hostname_len = SDL_strlen(localhost);
338 if (hostname_len == src_len && SDL_strncasecmp(src + 1, localhost, src_len) == 0) {
339 src = hostname_end + 1;
340 local = true;
341 }
342 }
343 }
344 }
345
346 if (local) {
347 // Convert URI escape sequences to real characters
348 if (src[0] == '/') {
349 src++;
350 } else {
351 src--;
352 }
353 return SDL_URIDecode(src, dst, 0);
354 }
355 return -1;
356}
357
358// This is a set of per-thread persistent strings that we can return from the SDL API.
359// This is used for short strings that might persist past the lifetime of the object
360// they are related to.
361
362static SDL_TLSID SDL_string_storage;
363
364static void SDL_FreePersistentStrings( void *value )
365{
366 SDL_HashTable *strings = (SDL_HashTable *)value;
367 SDL_DestroyHashTable(strings);
368}
369
370const char *SDL_GetPersistentString(const char *string)
371{
372 if (!string) {
373 return NULL;
374 }
375 if (!*string) {
376 return "";
377 }
378
379 SDL_HashTable *strings = (SDL_HashTable *)SDL_GetTLS(&SDL_string_storage);
380 if (!strings) {
381 strings = SDL_CreateHashTable(0, false, SDL_HashString, SDL_KeyMatchString, SDL_DestroyHashValue, NULL);
382 if (!strings) {
383 return NULL;
384 }
385
386 SDL_SetTLS(&SDL_string_storage, strings, SDL_FreePersistentStrings);
387 }
388
389 const char *result;
390 if (!SDL_FindInHashTable(strings, string, (const void **)&result)) {
391 char *new_string = SDL_strdup(string);
392 if (!new_string) {
393 return NULL;
394 }
395
396 // If the hash table insert fails, at least we can return the string we allocated
397 SDL_InsertIntoHashTable(strings, new_string, new_string, false);
398 result = new_string;
399 }
400 return result;
401}
402
403static int PrefixMatch(const char *a, const char *b)
404{
405 int matchlen = 0;
406 while (*a && *b) {
407 if (SDL_tolower((unsigned char)*a++) == SDL_tolower((unsigned char)*b++)) {
408 ++matchlen;
409 } else {
410 break;
411 }
412 }
413 return matchlen;
414}
415
416char *SDL_CreateDeviceName(Uint16 vendor, Uint16 product, const char *vendor_name, const char *product_name, const char *default_name)
417{
418 static struct
419 {
420 const char *prefix;
421 const char *replacement;
422 } replacements[] = {
423 { "8BitDo Tech Ltd", "8BitDo" },
424 { "ASTRO Gaming", "ASTRO" },
425 { "Bensussen Deutsch & Associates,Inc.(BDA)", "BDA" },
426 { "Guangzhou Chicken Run Network Technology Co., Ltd.", "GameSir" },
427 { "HORI CO.,LTD", "HORI" },
428 { "HORI CO.,LTD.", "HORI" },
429 { "Mad Catz Inc.", "Mad Catz" },
430 { "Nintendo Co., Ltd.", "Nintendo" },
431 { "NVIDIA Corporation ", "" },
432 { "Performance Designed Products", "PDP" },
433 { "QANBA USA, LLC", "Qanba" },
434 { "QANBA USA,LLC", "Qanba" },
435 { "Unknown ", "" },
436 };
437 char *name = NULL;
438 size_t i, len;
439
440 if (!vendor_name) {
441 vendor_name = "";
442 }
443 if (!product_name) {
444 product_name = "";
445 }
446
447 while (*vendor_name == ' ') {
448 ++vendor_name;
449 }
450 while (*product_name == ' ') {
451 ++product_name;
452 }
453
454 if (*vendor_name && *product_name) {
455 len = (SDL_strlen(vendor_name) + 1 + SDL_strlen(product_name) + 1);
456 name = (char *)SDL_malloc(len);
457 if (name) {
458 (void)SDL_snprintf(name, len, "%s %s", vendor_name, product_name);
459 }
460 } else if (*product_name) {
461 name = SDL_strdup(product_name);
462 } else if (vendor || product) {
463 // Couldn't find a controller name, try to give it one based on device type
464 switch (SDL_GetGamepadTypeFromVIDPID(vendor, product, NULL, true)) {
465 case SDL_GAMEPAD_TYPE_XBOX360:
466 name = SDL_strdup("Xbox 360 Controller");
467 break;
468 case SDL_GAMEPAD_TYPE_XBOXONE:
469 name = SDL_strdup("Xbox One Controller");
470 break;
471 case SDL_GAMEPAD_TYPE_PS3:
472 name = SDL_strdup("PS3 Controller");
473 break;
474 case SDL_GAMEPAD_TYPE_PS4:
475 name = SDL_strdup("PS4 Controller");
476 break;
477 case SDL_GAMEPAD_TYPE_PS5:
478 name = SDL_strdup("DualSense Wireless Controller");
479 break;
480 case SDL_GAMEPAD_TYPE_NINTENDO_SWITCH_PRO:
481 name = SDL_strdup("Nintendo Switch Pro Controller");
482 break;
483 default:
484 len = (6 + 1 + 6 + 1);
485 name = (char *)SDL_malloc(len);
486 if (name) {
487 (void)SDL_snprintf(name, len, "0x%.4x/0x%.4x", vendor, product);
488 }
489 break;
490 }
491 } else if (default_name) {
492 name = SDL_strdup(default_name);
493 }
494
495 if (!name) {
496 return NULL;
497 }
498
499 // Trim trailing whitespace
500 for (len = SDL_strlen(name); (len > 0 && name[len - 1] == ' '); --len) {
501 // continue
502 }
503 name[len] = '\0';
504
505 // Compress duplicate spaces
506 for (i = 0; i < (len - 1);) {
507 if (name[i] == ' ' && name[i + 1] == ' ') {
508 SDL_memmove(&name[i], &name[i + 1], (len - i));
509 --len;
510 } else {
511 ++i;
512 }
513 }
514
515 // Perform any manufacturer replacements
516 for (i = 0; i < SDL_arraysize(replacements); ++i) {
517 size_t prefixlen = SDL_strlen(replacements[i].prefix);
518 if (SDL_strncasecmp(name, replacements[i].prefix, prefixlen) == 0) {
519 size_t replacementlen = SDL_strlen(replacements[i].replacement);
520 if (replacementlen <= prefixlen) {
521 SDL_memcpy(name, replacements[i].replacement, replacementlen);
522 SDL_memmove(name + replacementlen, name + prefixlen, (len - prefixlen) + 1);
523 len -= (prefixlen - replacementlen);
524 } else {
525 // FIXME: Need to handle the expand case by reallocating the string
526 }
527 break;
528 }
529 }
530
531 /* Remove duplicate manufacturer or product in the name
532 * e.g. Razer Razer Raiju Tournament Edition Wired
533 */
534 for (i = 1; i < (len - 1); ++i) {
535 int matchlen = PrefixMatch(name, &name[i]);
536 while (matchlen > 0) {
537 if (name[matchlen] == ' ' || name[matchlen] == '-') {
538 SDL_memmove(name, name + matchlen + 1, len - matchlen);
539 break;
540 }
541 --matchlen;
542 }
543 if (matchlen > 0) {
544 // We matched the manufacturer's name and removed it
545 break;
546 }
547 }
548
549 return name;
550}
551