1 | /********************************************************************************************** |
2 | * |
3 | * raylib.utils - Some common utility functions |
4 | * |
5 | * CONFIGURATION: |
6 | * |
7 | * #define SUPPORT_TRACELOG |
8 | * Show TraceLog() output messages |
9 | * NOTE: By default LOG_DEBUG traces not shown |
10 | * |
11 | * |
12 | * LICENSE: zlib/libpng |
13 | * |
14 | * Copyright (c) 2014-2020 Ramon Santamaria (@raysan5) |
15 | * |
16 | * This software is provided "as-is", without any express or implied warranty. In no event |
17 | * will the authors be held liable for any damages arising from the use of this software. |
18 | * |
19 | * Permission is granted to anyone to use this software for any purpose, including commercial |
20 | * applications, and to alter it and redistribute it freely, subject to the following restrictions: |
21 | * |
22 | * 1. The origin of this software must not be misrepresented; you must not claim that you |
23 | * wrote the original software. If you use this software in a product, an acknowledgment |
24 | * in the product documentation would be appreciated but is not required. |
25 | * |
26 | * 2. Altered source versions must be plainly marked as such, and must not be misrepresented |
27 | * as being the original software. |
28 | * |
29 | * 3. This notice may not be removed or altered from any source distribution. |
30 | * |
31 | **********************************************************************************************/ |
32 | |
33 | #include "raylib.h" // WARNING: Required for: LogType enum |
34 | |
35 | // Check if config flags have been externally provided on compilation line |
36 | #if !defined(EXTERNAL_CONFIG_FLAGS) |
37 | #include "config.h" // Defines module configuration flags |
38 | #endif |
39 | |
40 | #include "utils.h" |
41 | |
42 | #if defined(PLATFORM_ANDROID) |
43 | #include <errno.h> // Required for: Android error types |
44 | #include <android/log.h> // Required for: Android log system: __android_log_vprint() |
45 | #include <android/asset_manager.h> // Required for: Android assets manager: AAsset, AAssetManager_open(), ... |
46 | #endif |
47 | |
48 | #include <stdlib.h> // Required for: exit() |
49 | #include <stdio.h> // Required for: vprintf() |
50 | #include <stdarg.h> // Required for: va_list, va_start(), va_end() |
51 | #include <string.h> // Required for: strcpy(), strcat() |
52 | |
53 | #define MAX_TRACELOG_BUFFER_SIZE 128 // Max length of one trace-log message |
54 | |
55 | #define MAX_UWP_MESSAGES 512 // Max UWP messages to process |
56 | |
57 | //---------------------------------------------------------------------------------- |
58 | // Global Variables Definition |
59 | //---------------------------------------------------------------------------------- |
60 | |
61 | // Log types messages |
62 | static int logTypeLevel = LOG_INFO; // Minimum log type level |
63 | static int logTypeExit = LOG_ERROR; // Log type that exits |
64 | static TraceLogCallback logCallback = NULL; // Log callback function pointer |
65 | |
66 | #if defined(PLATFORM_ANDROID) |
67 | static AAssetManager *assetManager = NULL; // Android assets manager pointer |
68 | static const char *internalDataPath = NULL; // Android internal data path |
69 | #endif |
70 | |
71 | #if defined(PLATFORM_UWP) |
72 | static int UWPOutMessageId = -1; // Last index of output message |
73 | static UWPMessage *UWPOutMessages[MAX_UWP_MESSAGES]; // Messages out to UWP |
74 | static int UWPInMessageId = -1; // Last index of input message |
75 | static UWPMessage *UWPInMessages[MAX_UWP_MESSAGES]; // Messages in from UWP |
76 | #endif |
77 | |
78 | //---------------------------------------------------------------------------------- |
79 | // Module specific Functions Declaration |
80 | //---------------------------------------------------------------------------------- |
81 | #if defined(PLATFORM_ANDROID) |
82 | FILE *funopen(const void *cookie, int (*readfn)(void *, char *, int), int (*writefn)(void *, const char *, int), |
83 | fpos_t (*seekfn)(void *, fpos_t, int), int (*closefn)(void *)); |
84 | |
85 | static int android_read(void *cookie, char *buf, int size); |
86 | static int android_write(void *cookie, const char *buf, int size); |
87 | static fpos_t android_seek(void *cookie, fpos_t offset, int whence); |
88 | static int android_close(void *cookie); |
89 | #endif |
90 | |
91 | //---------------------------------------------------------------------------------- |
92 | // Module Functions Definition - Utilities |
93 | //---------------------------------------------------------------------------------- |
94 | |
95 | // Set the current threshold (minimum) log level |
96 | void SetTraceLogLevel(int logType) |
97 | { |
98 | logTypeLevel = logType; |
99 | } |
100 | |
101 | // Set the exit threshold (minimum) log level |
102 | void SetTraceLogExit(int logType) |
103 | { |
104 | logTypeExit = logType; |
105 | } |
106 | |
107 | // Set a trace log callback to enable custom logging |
108 | void SetTraceLogCallback(TraceLogCallback callback) |
109 | { |
110 | logCallback = callback; |
111 | } |
112 | |
113 | // Show trace log messages (LOG_INFO, LOG_WARNING, LOG_ERROR, LOG_DEBUG) |
114 | void TraceLog(int logType, const char *text, ...) |
115 | { |
116 | #if defined(SUPPORT_TRACELOG) |
117 | // Message has level below current threshold, don't emit |
118 | if (logType < logTypeLevel) return; |
119 | |
120 | va_list args; |
121 | va_start(args, text); |
122 | |
123 | if (logCallback) |
124 | { |
125 | logCallback(logType, text, args); |
126 | va_end(args); |
127 | return; |
128 | } |
129 | |
130 | #if defined(PLATFORM_ANDROID) |
131 | switch(logType) |
132 | { |
133 | case LOG_TRACE: __android_log_vprint(ANDROID_LOG_VERBOSE, "raylib" , text, args); break; |
134 | case LOG_DEBUG: __android_log_vprint(ANDROID_LOG_DEBUG, "raylib" , text, args); break; |
135 | case LOG_INFO: __android_log_vprint(ANDROID_LOG_INFO, "raylib" , text, args); break; |
136 | case LOG_WARNING: __android_log_vprint(ANDROID_LOG_WARN, "raylib" , text, args); break; |
137 | case LOG_ERROR: __android_log_vprint(ANDROID_LOG_ERROR, "raylib" , text, args); break; |
138 | case LOG_FATAL: __android_log_vprint(ANDROID_LOG_FATAL, "raylib" , text, args); break; |
139 | default: break; |
140 | } |
141 | #else |
142 | char buffer[MAX_TRACELOG_BUFFER_SIZE] = { 0 }; |
143 | |
144 | switch (logType) |
145 | { |
146 | case LOG_TRACE: strcpy(buffer, "TRACE: " ); break; |
147 | case LOG_DEBUG: strcpy(buffer, "DEBUG: " ); break; |
148 | case LOG_INFO: strcpy(buffer, "INFO: " ); break; |
149 | case LOG_WARNING: strcpy(buffer, "WARNING: " ); break; |
150 | case LOG_ERROR: strcpy(buffer, "ERROR: " ); break; |
151 | case LOG_FATAL: strcpy(buffer, "FATAL: " ); break; |
152 | default: break; |
153 | } |
154 | |
155 | strcat(buffer, text); |
156 | strcat(buffer, "\n" ); |
157 | vprintf(buffer, args); |
158 | #endif |
159 | |
160 | va_end(args); |
161 | |
162 | if (logType >= logTypeExit) exit(1); // If exit message, exit program |
163 | |
164 | #endif // SUPPORT_TRACELOG |
165 | } |
166 | |
167 | // Load data from file into a buffer |
168 | unsigned char *LoadFileData(const char *fileName, unsigned int *bytesRead) |
169 | { |
170 | unsigned char *data = NULL; |
171 | *bytesRead = 0; |
172 | |
173 | if (fileName != NULL) |
174 | { |
175 | FILE *file = fopen(fileName, "rb" ); |
176 | |
177 | if (file != NULL) |
178 | { |
179 | // WARNING: On binary streams SEEK_END could not be found, |
180 | // using fseek() and ftell() could not work in some (rare) cases |
181 | fseek(file, 0, SEEK_END); |
182 | int size = ftell(file); |
183 | fseek(file, 0, SEEK_SET); |
184 | |
185 | if (size > 0) |
186 | { |
187 | data = (unsigned char *)RL_MALLOC(sizeof(unsigned char)*size); |
188 | |
189 | // NOTE: fread() returns number of read elements instead of bytes, so we read [1 byte, size elements] |
190 | unsigned int count = fread(data, sizeof(unsigned char), size, file); |
191 | *bytesRead = count; |
192 | |
193 | if (count != size) TRACELOG(LOG_WARNING, "FILEIO: [%s] File partially loaded" , fileName); |
194 | else TRACELOG(LOG_INFO, "FILEIO: [%s] File loaded successfully" , fileName); |
195 | } |
196 | else TRACELOG(LOG_WARNING, "FILEIO: [%s] Failed to read file" , fileName); |
197 | |
198 | fclose(file); |
199 | } |
200 | else TRACELOG(LOG_WARNING, "FILEIO: [%s] Failed to open file" , fileName); |
201 | } |
202 | else TRACELOG(LOG_WARNING, "FILEIO: File name provided is not valid" ); |
203 | |
204 | return data; |
205 | } |
206 | |
207 | // Save data to file from buffer |
208 | void SaveFileData(const char *fileName, void *data, unsigned int bytesToWrite) |
209 | { |
210 | if (fileName != NULL) |
211 | { |
212 | FILE *file = fopen(fileName, "wb" ); |
213 | |
214 | if (file != NULL) |
215 | { |
216 | unsigned int count = fwrite(data, sizeof(unsigned char), bytesToWrite, file); |
217 | |
218 | if (count == 0) TRACELOG(LOG_WARNING, "FILEIO: [%s] Failed to write file" , fileName); |
219 | else if (count != bytesToWrite) TRACELOG(LOG_WARNING, "FILEIO: [%s] File partially written" , fileName); |
220 | else TRACELOG(LOG_INFO, "FILEIO: [%s] File saved successfully" , fileName); |
221 | |
222 | fclose(file); |
223 | } |
224 | else TRACELOG(LOG_WARNING, "FILEIO: [%s] Failed to open file" , fileName); |
225 | } |
226 | else TRACELOG(LOG_WARNING, "FILEIO: File name provided is not valid" ); |
227 | } |
228 | |
229 | // Load text data from file, returns a '\0' terminated string |
230 | // NOTE: text chars array should be freed manually |
231 | char *LoadFileText(const char *fileName) |
232 | { |
233 | char *text = NULL; |
234 | |
235 | if (fileName != NULL) |
236 | { |
237 | FILE *textFile = fopen(fileName, "rt" ); |
238 | |
239 | if (textFile != NULL) |
240 | { |
241 | // WARNING: When reading a file as 'text' file, |
242 | // text mode causes carriage return-linefeed translation... |
243 | // ...but using fseek() should return correct byte-offset |
244 | fseek(textFile, 0, SEEK_END); |
245 | int size = ftell(textFile); |
246 | fseek(textFile, 0, SEEK_SET); |
247 | |
248 | if (size > 0) |
249 | { |
250 | text = (char *)RL_MALLOC(sizeof(char)*(size + 1)); |
251 | int count = fread(text, sizeof(char), size, textFile); |
252 | |
253 | // WARNING: \r\n is converted to \n on reading, so, |
254 | // read bytes count gets reduced by the number of lines |
255 | if (count < size) text = RL_REALLOC(text, count + 1); |
256 | |
257 | // Zero-terminate the string |
258 | text[count] = '\0'; |
259 | |
260 | TRACELOG(LOG_INFO, "FILEIO: [%s] Text file loaded successfully" , fileName); |
261 | } |
262 | else TRACELOG(LOG_WARNING, "FILEIO: [%s] Failed to read text file" , fileName); |
263 | |
264 | fclose(textFile); |
265 | } |
266 | else TRACELOG(LOG_WARNING, "FILEIO: [%s] Failed to open text file" , fileName); |
267 | } |
268 | else TRACELOG(LOG_WARNING, "FILEIO: File name provided is not valid" ); |
269 | |
270 | return text; |
271 | } |
272 | |
273 | // Save text data to file (write), string must be '\0' terminated |
274 | void SaveFileText(const char *fileName, char *text) |
275 | { |
276 | if (fileName != NULL) |
277 | { |
278 | FILE *file = fopen(fileName, "wt" ); |
279 | |
280 | if (file != NULL) |
281 | { |
282 | int count = fprintf(file, "%s" , text); |
283 | |
284 | if (count == 0) TRACELOG(LOG_WARNING, "FILEIO: [%s] Failed to write text file" , fileName); |
285 | else TRACELOG(LOG_INFO, "FILEIO: [%s] Text file saved successfully" , fileName); |
286 | |
287 | fclose(file); |
288 | } |
289 | else TRACELOG(LOG_WARNING, "FILEIO: [%s] Failed to open text file" , fileName); |
290 | } |
291 | else TRACELOG(LOG_WARNING, "FILEIO: File name provided is not valid" ); |
292 | } |
293 | |
294 | #if defined(PLATFORM_ANDROID) |
295 | // Initialize asset manager from android app |
296 | void InitAssetManager(AAssetManager *manager, const char *dataPath) |
297 | { |
298 | assetManager = manager; |
299 | internalDataPath = dataPath; |
300 | } |
301 | |
302 | // Replacement for fopen |
303 | // Ref: https://developer.android.com/ndk/reference/group/asset |
304 | FILE *android_fopen(const char *fileName, const char *mode) |
305 | { |
306 | if (mode[0] == 'w') // TODO: Test! |
307 | { |
308 | // TODO: fopen() is mapped to android_fopen() that only grants read access |
309 | // to assets directory through AAssetManager but we want to also be able to |
310 | // write data when required using the standard stdio FILE access functions |
311 | // Ref: https://stackoverflow.com/questions/11294487/android-writing-saving-files-from-native-code-only |
312 | #undef fopen |
313 | return fopen(TextFormat("%s/%s" , internalDataPath, fileName), mode); |
314 | #define fopen(name, mode) android_fopen(name, mode) |
315 | } |
316 | else |
317 | { |
318 | // NOTE: AAsset provides access to read-only asset |
319 | AAsset *asset = AAssetManager_open(assetManager, fileName, AASSET_MODE_UNKNOWN); |
320 | |
321 | if (asset != NULL) |
322 | { |
323 | // Return pointer to file in the assets |
324 | return funopen(asset, android_read, android_write, android_seek, android_close); |
325 | } |
326 | else |
327 | { |
328 | #undef fopen |
329 | // Just do a regular open if file is not found in the assets |
330 | return fopen(TextFormat("%s/%s" , internalDataPath, fileName), mode); |
331 | #define fopen(name, mode) android_fopen(name, mode) |
332 | } |
333 | } |
334 | } |
335 | #endif // PLATFORM_ANDROID |
336 | |
337 | //---------------------------------------------------------------------------------- |
338 | // Module specific Functions Definition |
339 | //---------------------------------------------------------------------------------- |
340 | #if defined(PLATFORM_ANDROID) |
341 | static int android_read(void *cookie, char *buf, int size) |
342 | { |
343 | return AAsset_read((AAsset *)cookie, buf, size); |
344 | } |
345 | |
346 | static int android_write(void *cookie, const char *buf, int size) |
347 | { |
348 | TRACELOG(LOG_WARNING, "ANDROID: Failed to provide write access to APK" ); |
349 | |
350 | return EACCES; |
351 | } |
352 | |
353 | static fpos_t android_seek(void *cookie, fpos_t offset, int whence) |
354 | { |
355 | return AAsset_seek((AAsset *)cookie, offset, whence); |
356 | } |
357 | |
358 | static int android_close(void *cookie) |
359 | { |
360 | AAsset_close((AAsset *)cookie); |
361 | return 0; |
362 | } |
363 | #endif // PLATFORM_ANDROID |
364 | |
365 | #if defined(PLATFORM_UWP) |
366 | UWPMessage *CreateUWPMessage(void) |
367 | { |
368 | UWPMessage *msg = (UWPMessage *)RL_MALLOC(sizeof(UWPMessage)); |
369 | msg->type = UWP_MSG_NONE; |
370 | Vector2 v0 = { 0, 0 }; |
371 | msg->paramVector0 = v0; |
372 | msg->paramInt0 = 0; |
373 | msg->paramInt1 = 0; |
374 | msg->paramChar0 = 0; |
375 | msg->paramFloat0 = 0; |
376 | msg->paramDouble0 = 0; |
377 | msg->paramBool0 = false; |
378 | return msg; |
379 | } |
380 | |
381 | void DeleteUWPMessage(UWPMessage *msg) |
382 | { |
383 | RL_FREE(msg); |
384 | } |
385 | |
386 | bool UWPHasMessages(void) |
387 | { |
388 | return (UWPOutMessageId > -1); |
389 | } |
390 | |
391 | UWPMessage *UWPGetMessage(void) |
392 | { |
393 | if (UWPHasMessages()) return UWPOutMessages[UWPOutMessageId--]; |
394 | |
395 | return NULL; |
396 | } |
397 | |
398 | void UWPSendMessage(UWPMessage *msg) |
399 | { |
400 | if ((UWPInMessageId + 1) < MAX_UWP_MESSAGES) |
401 | { |
402 | UWPInMessageId++; |
403 | UWPInMessages[UWPInMessageId] = msg; |
404 | } |
405 | else TRACELOG(LOG_WARNING, "UWP: Not enough array space to register new inbound message" ); |
406 | } |
407 | |
408 | void SendMessageToUWP(UWPMessage *msg) |
409 | { |
410 | if ((UWPOutMessageId + 1) < MAX_UWP_MESSAGES) |
411 | { |
412 | UWPOutMessageId++; |
413 | UWPOutMessages[UWPOutMessageId] = msg; |
414 | } |
415 | else TRACELOG(LOG_WARNING, "UWP: Not enough array space to register new outward message" ); |
416 | } |
417 | |
418 | bool HasMessageFromUWP(void) |
419 | { |
420 | return UWPInMessageId > -1; |
421 | } |
422 | |
423 | UWPMessage *GetMessageFromUWP(void) |
424 | { |
425 | if (HasMessageFromUWP()) return UWPInMessages[UWPInMessageId--]; |
426 | |
427 | return NULL; |
428 | } |
429 | #endif // PLATFORM_UWP |
430 | |