1/*
2 Simple DirectMedia Layer
3 Copyright (C) 1997-2024 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// Common utility functions that aren't in the public API
28
29int SDL_powerof2(int x)
30{
31 int value;
32
33 if (x <= 0) {
34 // Return some sane value - we shouldn't hit this in our use cases
35 return 1;
36 }
37
38 // This trick works for 32-bit values
39 {
40 SDL_COMPILE_TIME_ASSERT(SDL_powerof2, sizeof(x) == sizeof(Uint32));
41 }
42 value = x;
43 value -= 1;
44 value |= value >> 1;
45 value |= value >> 2;
46 value |= value >> 4;
47 value |= value >> 8;
48 value |= value >> 16;
49 value += 1;
50
51 return value;
52}
53
54Uint32 SDL_CalculateGCD(Uint32 a, Uint32 b)
55{
56 if (b == 0) {
57 return a;
58 }
59 return SDL_CalculateGCD(b, (a % b));
60}
61
62// Algorithm adapted with thanks from John Cook's blog post:
63// http://www.johndcook.com/blog/2010/10/20/best-rational-approximation
64void SDL_CalculateFraction(float x, int *numerator, int *denominator)
65{
66 const int N = 1000;
67 int a = 0, b = 1;
68 int c = 1, d = 0;
69
70 while (b <= N && d <= N) {
71 float mediant = (float)(a + c) / (b + d);
72 if (x == mediant) {
73 if (b + d <= N) {
74 *numerator = a + c;
75 *denominator = b + d;
76 } else if (d > b) {
77 *numerator = c;
78 *denominator = d;
79 } else {
80 *numerator = a;
81 *denominator = b;
82 }
83 return;
84 } else if (x > mediant) {
85 a = a + c;
86 b = b + d;
87 } else {
88 c = a + c;
89 d = b + d;
90 }
91 }
92 if (b > N) {
93 *numerator = c;
94 *denominator = d;
95 } else {
96 *numerator = a;
97 *denominator = b;
98 }
99}
100
101bool SDL_startswith(const char *string, const char *prefix)
102{
103 if (SDL_strncmp(string, prefix, SDL_strlen(prefix)) == 0) {
104 return true;
105 }
106 return false;
107}
108
109bool SDL_endswith(const char *string, const char *suffix)
110{
111 size_t string_length = string ? SDL_strlen(string) : 0;
112 size_t suffix_length = suffix ? SDL_strlen(suffix) : 0;
113
114 if (suffix_length > 0 && suffix_length <= string_length) {
115 if (SDL_memcmp(string + string_length - suffix_length, suffix, suffix_length) == 0) {
116 return true;
117 }
118 }
119 return false;
120}
121
122SDL_COMPILE_TIME_ASSERT(sizeof_object_id, sizeof(int) == sizeof(Uint32));
123
124Uint32 SDL_GetNextObjectID(void)
125{
126 static SDL_AtomicInt last_id;
127
128 Uint32 id = (Uint32)SDL_AtomicIncRef(&last_id) + 1;
129 if (id == 0) {
130 id = (Uint32)SDL_AtomicIncRef(&last_id) + 1;
131 }
132 return id;
133}
134
135static SDL_InitState SDL_objects_init;
136static SDL_HashTable *SDL_objects;
137
138static Uint32 SDL_HashObject(const void *key, void *unused)
139{
140 return (Uint32)(uintptr_t)key;
141}
142
143static bool SDL_KeyMatchObject(const void *a, const void *b, void *unused)
144{
145 return (a == b);
146}
147
148void SDL_SetObjectValid(void *object, SDL_ObjectType type, bool valid)
149{
150 SDL_assert(object != NULL);
151
152 if (valid && SDL_ShouldInit(&SDL_objects_init)) {
153 SDL_objects = SDL_CreateHashTable(NULL, 32, SDL_HashObject, SDL_KeyMatchObject, NULL, true, false);
154 if (!SDL_objects) {
155 SDL_SetInitialized(&SDL_objects_init, false);
156 }
157 SDL_SetInitialized(&SDL_objects_init, true);
158 }
159
160 if (valid) {
161 SDL_InsertIntoHashTable(SDL_objects, object, (void *)(uintptr_t)type);
162 } else {
163 SDL_RemoveFromHashTable(SDL_objects, object);
164 }
165}
166
167bool SDL_ObjectValid(void *object, SDL_ObjectType type)
168{
169 if (!object) {
170 return false;
171 }
172
173 const void *object_type;
174 if (!SDL_FindInHashTable(SDL_objects, object, &object_type)) {
175 return false;
176 }
177
178 return (((SDL_ObjectType)(uintptr_t)object_type) == type);
179}
180
181void SDL_SetObjectsInvalid(void)
182{
183 if (SDL_ShouldQuit(&SDL_objects_init)) {
184 // Log any leaked objects
185 const void *object, *object_type;
186 void *iter = NULL;
187 while (SDL_IterateHashTable(SDL_objects, &object, &object_type, &iter)) {
188 const char *type;
189 switch ((SDL_ObjectType)(uintptr_t)object_type) {
190 case SDL_OBJECT_TYPE_WINDOW:
191 type = "SDL_Window";
192 break;
193 case SDL_OBJECT_TYPE_RENDERER:
194 type = "SDL_Renderer";
195 break;
196 case SDL_OBJECT_TYPE_TEXTURE:
197 type = "SDL_Texture";
198 break;
199 case SDL_OBJECT_TYPE_JOYSTICK:
200 type = "SDL_Joystick";
201 break;
202 case SDL_OBJECT_TYPE_GAMEPAD:
203 type = "SDL_Gamepad";
204 break;
205 case SDL_OBJECT_TYPE_HAPTIC:
206 type = "SDL_Haptic";
207 break;
208 case SDL_OBJECT_TYPE_SENSOR:
209 type = "SDL_Sensor";
210 break;
211 case SDL_OBJECT_TYPE_HIDAPI_DEVICE:
212 type = "hidapi device";
213 break;
214 case SDL_OBJECT_TYPE_HIDAPI_JOYSTICK:
215 type = "hidapi joystick";
216 break;
217 case SDL_OBJECT_TYPE_THREAD:
218 type = "thread";
219 break;
220 default:
221 type = "unknown object";
222 break;
223 }
224 SDL_Log("Leaked %s (%p)\n", type, object);
225 }
226 SDL_assert(SDL_HashTableEmpty(SDL_objects));
227
228 SDL_DestroyHashTable(SDL_objects);
229 SDL_objects = NULL;
230
231 SDL_SetInitialized(&SDL_objects_init, false);
232 }
233}
234
235static int SDL_URIDecode(const char *src, char *dst, int len)
236{
237 int ri, wi, di;
238 char decode = '\0';
239 if (!src || !dst || len < 0) {
240 return -1;
241 }
242 if (len == 0) {
243 len = (int)SDL_strlen(src);
244 }
245 for (ri = 0, wi = 0, di = 0; ri < len && wi < len; ri += 1) {
246 if (di == 0) {
247 // start decoding
248 if (src[ri] == '%') {
249 decode = '\0';
250 di += 1;
251 continue;
252 }
253 // normal write
254 dst[wi] = src[ri];
255 wi += 1;
256 } else if (di == 1 || di == 2) {
257 char off = '\0';
258 char isa = src[ri] >= 'a' && src[ri] <= 'f';
259 char isA = src[ri] >= 'A' && src[ri] <= 'F';
260 char isn = src[ri] >= '0' && src[ri] <= '9';
261 if (!(isa || isA || isn)) {
262 // not a hexadecimal
263 int sri;
264 for (sri = ri - di; sri <= ri; sri += 1) {
265 dst[wi] = src[sri];
266 wi += 1;
267 }
268 di = 0;
269 continue;
270 }
271 // itsy bitsy magicsy
272 if (isn) {
273 off = 0 - '0';
274 } else if (isa) {
275 off = 10 - 'a';
276 } else if (isA) {
277 off = 10 - 'A';
278 }
279 decode |= (src[ri] + off) << (2 - di) * 4;
280 if (di == 2) {
281 dst[wi] = decode;
282 wi += 1;
283 di = 0;
284 } else {
285 di += 1;
286 }
287 }
288 }
289 dst[wi] = '\0';
290 return wi;
291}
292
293int SDL_URIToLocal(const char *src, char *dst)
294{
295 if (SDL_memcmp(src, "file:/", 6) == 0) {
296 src += 6; // local file?
297 } else if (SDL_strstr(src, ":/") != NULL) {
298 return -1; // wrong scheme
299 }
300
301 bool local = src[0] != '/' || (src[0] != '\0' && src[1] == '/');
302
303 // Check the hostname, if present. RFC 3986 states that the hostname component of a URI is not case-sensitive.
304 if (!local && src[0] == '/' && src[2] != '/') {
305 char *hostname_end = SDL_strchr(src + 1, '/');
306 if (hostname_end) {
307 const size_t src_len = hostname_end - (src + 1);
308 size_t hostname_len;
309
310#if defined(HAVE_GETHOSTNAME) && !defined(SDL_PLATFORM_WINDOWS)
311 char hostname[257];
312 if (gethostname(hostname, 255) == 0) {
313 hostname[256] = '\0';
314 hostname_len = SDL_strlen(hostname);
315 if (hostname_len == src_len && SDL_strncasecmp(src + 1, hostname, src_len) == 0) {
316 src = hostname_end + 1;
317 local = true;
318 }
319 }
320#endif
321
322 if (!local) {
323 static const char *localhost = "localhost";
324 hostname_len = SDL_strlen(localhost);
325 if (hostname_len == src_len && SDL_strncasecmp(src + 1, localhost, src_len) == 0) {
326 src = hostname_end + 1;
327 local = true;
328 }
329 }
330 }
331 }
332
333 if (local) {
334 // Convert URI escape sequences to real characters
335 if (src[0] == '/') {
336 src++;
337 } else {
338 src--;
339 }
340 return SDL_URIDecode(src, dst, 0);
341 }
342 return -1;
343}
344
345// This is a set of per-thread persistent strings that we can return from the SDL API.
346// This is used for short strings that might persist past the lifetime of the object
347// they are related to.
348
349static SDL_TLSID SDL_string_storage;
350
351static void SDL_FreePersistentStrings( void *value )
352{
353 SDL_HashTable *strings = (SDL_HashTable *)value;
354 SDL_DestroyHashTable(strings);
355}
356
357const char *SDL_GetPersistentString(const char *string)
358{
359 if (!string) {
360 return NULL;
361 }
362 if (!*string) {
363 return "";
364 }
365
366 SDL_HashTable *strings = (SDL_HashTable *)SDL_GetTLS(&SDL_string_storage);
367 if (!strings) {
368 strings = SDL_CreateHashTable(NULL, 32, SDL_HashString, SDL_KeyMatchString, SDL_NukeFreeValue, false, false);
369 if (!strings) {
370 return NULL;
371 }
372
373 SDL_SetTLS(&SDL_string_storage, strings, SDL_FreePersistentStrings);
374 }
375
376 const char *result;
377 if (!SDL_FindInHashTable(strings, string, (const void **)&result)) {
378 char *new_string = SDL_strdup(string);
379 if (!new_string) {
380 return NULL;
381 }
382
383 // If the hash table insert fails, at least we can return the string we allocated
384 SDL_InsertIntoHashTable(strings, new_string, new_string);
385 result = new_string;
386 }
387 return result;
388}