main
util.c
1#include <string.h>
2#include <stdlib.h>
3#include <stdio.h>
4#include <ctype.h>
5#include <stdint.h>
6#include <stdarg.h>
7
8#include <sys/types.h>
9#include <sys/stat.h>
10#include <unistd.h>
11#include <errno.h>
12
13#define info_puts(msg) fputs(msg, stdout);
14#define warning_puts(msg) fputs(msg, stderr);
15#define fatal_puts(msg) fputs(msg, stderr);
16
17#define info_printf(msg, args) vprintf(msg, args)
18#define warning_printf(msg, args) vfprintf(stderr, msg, args)
19#define fatal_printf(msg, args) vfprintf(stderr, msg, args)
20#include "blastem.h" //for headless global
21#include "render.h" //for render_errorbox
22#include "util.h"
23
24char * alloc_concat(char const * first, char const * second)
25{
26 int flen = strlen(first);
27 int slen = strlen(second);
28 char * ret = malloc(flen + slen + 1);
29 memcpy(ret, first, flen);
30 memcpy(ret+flen, second, slen+1);
31 return ret;
32}
33
34char * alloc_concat_m(int num_parts, char const ** parts)
35{
36 int total = 0;
37 for (int i = 0; i < num_parts; i++) {
38 total += strlen(parts[i]);
39 }
40 char * ret = malloc(total + 1);
41 *ret = 0;
42 for (int i = 0; i < num_parts; i++) {
43 strcat(ret, parts[i]);
44 }
45 return ret;
46}
47
48typedef struct {
49 uint32_t start;
50 uint32_t end;
51 char *value;
52} var_pos;
53
54char *replace_vars(char *base, tern_node *vars, uint8_t allow_env)
55{
56 uint32_t num_vars = 0;
57 for (char *cur = base; *cur; ++cur)
58 {
59 //TODO: Support escaping $ and allow brace syntax
60 if (*cur == '$') {
61 num_vars++;
62 }
63 }
64 var_pos *positions = calloc(num_vars, sizeof(var_pos));
65 num_vars = 0;
66 uint8_t in_var = 0;
67 uint32_t max_var_len = 0;
68 for (char *cur = base; *cur; ++cur)
69 {
70 if (in_var) {
71 if (!(*cur == '_' || isalnum(*cur))) {
72 positions[num_vars].end = cur-base;
73 if (positions[num_vars].end - positions[num_vars].start > max_var_len) {
74 max_var_len = positions[num_vars].end - positions[num_vars].start;
75 }
76 num_vars++;
77 in_var = 0;
78 }
79 } else if (*cur == '$') {
80 positions[num_vars].start = cur-base+1;
81 in_var = 1;
82 }
83 }
84 if (in_var) {
85 positions[num_vars].end = strlen(base);
86 if (positions[num_vars].end - positions[num_vars].start > max_var_len) {
87 max_var_len = positions[num_vars].end - positions[num_vars].start;
88 }
89 num_vars++;
90 }
91 char *varname = malloc(max_var_len+1);
92 uint32_t total_len = 0;
93 uint32_t cur = 0;
94 for (uint32_t i = 0; i < num_vars; i++)
95 {
96 total_len += (positions[i].start - 1) - cur;
97 cur = positions[i].start;
98 memcpy(varname, base + positions[i].start, positions[i].end-positions[i].start);
99 varname[positions[i].end-positions[i].start] = 0;
100 positions[i].value = tern_find_ptr(vars, varname);
101 if (!positions[i].value && allow_env) {
102 positions[i].value = getenv(varname);
103 }
104 if (positions[i].value) {
105 total_len += strlen(positions[i].value);
106 }
107 }
108 total_len += strlen(base+cur);
109 free(varname);
110 char *output = malloc(total_len+1);
111 cur = 0;
112 char *curout = output;
113 for (uint32_t i = 0; i < num_vars; i++)
114 {
115 if (positions[i].start-1 > cur) {
116 memcpy(curout, base + cur, (positions[i].start-1) - cur);
117 curout += (positions[i].start-1) - cur;
118 }
119 if (positions[i].value) {
120 strcpy(curout, positions[i].value);
121 curout += strlen(curout);
122 }
123 cur = positions[i].end;
124 };
125 if (base[cur]) {
126 strcpy(curout, base+cur);
127 } else {
128 *curout = 0;
129 }
130 free(positions);
131 return output;
132}
133
134void byteswap_rom(int filesize, uint16_t *cart)
135{
136 for(uint16_t *cur = cart; cur - cart < filesize/2; ++cur)
137 {
138 *cur = (*cur >> 8) | (*cur << 8);
139 }
140}
141
142
143long file_size(FILE * f)
144{
145 fseek(f, 0, SEEK_END);
146 long fsize = ftell(f);
147 fseek(f, 0, SEEK_SET);
148 return fsize;
149}
150
151char * strip_ws(char * text)
152{
153 while (*text && (!isprint(*text) || isblank(*text)))
154 {
155 text++;
156 }
157 char * ret = text;
158 text = ret + strlen(ret) - 1;
159 while (text > ret && (!isprint(*text) || isblank(*text)))
160 {
161 *text = 0;
162 text--;
163 }
164 return ret;
165}
166
167char * split_keyval(char * text)
168{
169 while (*text && !isblank(*text))
170 {
171 text++;
172 }
173 if (!*text) {
174 return text;
175 }
176 *text = 0;
177 return text+1;
178}
179
180uint8_t startswith(const char *haystack, const char *prefix)
181{
182 return !strncmp(haystack, prefix, strlen(prefix));
183}
184
185void bin_to_hex(uint8_t *output, uint8_t *input, uint64_t size)
186{
187 while (size)
188 {
189 uint8_t digit = *input >> 4;
190 digit += digit > 9 ? 'a' - 0xa : '0';
191 *(output++) = digit;
192 digit = *(input++) & 0xF;
193 digit += digit > 9 ? 'a' - 0xa : '0';
194 *(output++) = digit;
195 size--;
196 }
197 *(output++) = 0;
198}
199
200char *utf16be_to_utf8(uint8_t *buf, uint32_t max_size)
201{
202 uint8_t *cur = buf;
203 uint32_t converted_size = 0;
204 for (uint32_t i = 0; i < max_size; i++, cur+=2)
205 {
206 uint16_t code = *cur << 16 | cur[1];
207 if (!code) {
208 break;
209 }
210 if (code < 0x80) {
211 converted_size++;
212 } else if (code < 0x800) {
213 converted_size += 2;
214 } else {
215 //TODO: Deal with surrogate pairs
216 converted_size += 3;
217 }
218 }
219 char *out = malloc(converted_size + 1);
220 char *cur_out = out;
221 cur = buf;
222 for (uint32_t i = 0; i < max_size; i++, cur+=2)
223 {
224 uint16_t code = *cur << 16 | cur[1];
225 if (!code) {
226 break;
227 }
228 if (code < 0x80) {
229 *(cur_out++) = code;
230 } else if (code < 0x800) {
231 *(cur_out++) = 0xC0 | code >> 6;
232 *(cur_out++) = 0x80 | (code & 0x3F);
233 } else {
234 //TODO: Deal with surrogate pairs
235 *(cur_out++) = 0xF0 | code >> 12;
236 *(cur_out++) = 0x80 | (code >> 6 & 0x3F);
237 *(cur_out++) = 0x80 | (code & 0x3F);
238 }
239 }
240 *cur_out = 0;
241 return out;
242}
243
244int utf8_codepoint(const char **text)
245{
246 uint8_t initial = **text;
247 (*text)++;
248 if (initial < 0x80) {
249 return initial;
250 }
251 int base = 0;
252 uint8_t extended_bytes = 0;
253 if ((initial & 0xE0) == 0xC0) {
254 base = 0x80;
255 initial &= 0x1F;
256 extended_bytes = 1;
257 } else if ((initial & 0xF0) == 0xE0) {
258 base = 0x800;
259 initial &= 0xF;
260 extended_bytes = 2;
261 } else if ((initial & 0xF8) == 0xF0) {
262 base = 0x10000;
263 initial &= 0x7;
264 extended_bytes = 3;
265 }
266 int value = initial;
267 for (uint8_t i = 0; i < extended_bytes; i++)
268 {
269 if ((**text & 0xC0) != 0x80) {
270 return -1;
271 }
272 value = value << 6;
273 value |= (**text) & 0x3F;
274 (*text)++;
275 }
276 return value + base;
277}
278
279char is_path_sep(char c)
280{
281 return c == '/';
282}
283
284char is_absolute_path(char *path)
285{
286 return is_path_sep(path[0]);
287}
288
289char * basename_no_extension(const char *path)
290{
291 const char *lastdot = NULL;
292 const char *lastslash = NULL;
293 const char *cur;
294 for (cur = path; *cur; cur++)
295 {
296 if (*cur == '.') {
297 lastdot = cur;
298 } else if (is_path_sep(*cur)) {
299 lastslash = cur + 1;
300 }
301 }
302 if (!lastdot) {
303 lastdot = cur;
304 }
305 if (!lastslash) {
306 lastslash = path;
307 }
308 char *barename = malloc(lastdot-lastslash+1);
309 memcpy(barename, lastslash, lastdot-lastslash);
310 barename[lastdot-lastslash] = 0;
311
312 return barename;
313}
314
315char *path_extension(char const *path)
316{
317 char const *lastdot = NULL;
318 char const *lastslash = NULL;
319 char const *cur;
320 for (cur = path; *cur; cur++)
321 {
322 if (*cur == '.') {
323 lastdot = cur;
324 } else if (is_path_sep(*cur)) {
325 lastslash = cur + 1;
326 }
327 }
328 if (!lastdot || (lastslash && lastslash > lastdot)) {
329 //no extension
330 return NULL;
331 }
332 return strdup(lastdot+1);
333}
334
335uint8_t path_matches_extensions(char *path, char **ext_list, uint32_t num_exts)
336{
337 char *ext = path_extension(path);
338 if (!ext) {
339 return 0;
340 }
341 uint32_t extidx;
342 for (extidx = 0; extidx < num_exts; extidx++)
343 {
344 if (!strcasecmp(ext, ext_list[extidx])) {
345 free(ext);
346 return 1;
347 }
348 }
349 free(ext);
350 return 0;
351}
352
353char * path_dirname(const char *path)
354{
355 const char *lastslash = NULL;
356 const char *cur;
357 for (cur = path; *cur; cur++)
358 {
359 if (is_path_sep(*cur)) {
360 lastslash = cur;
361 }
362 }
363 if (!lastslash) {
364 return NULL;
365 }
366 char *dir = malloc(lastslash-path+1);
367 memcpy(dir, path, lastslash-path);
368 dir[lastslash-path] = 0;
369
370 return dir;
371}
372
373uint32_t nearest_pow2(uint32_t val)
374{
375 uint32_t ret = 1;
376 while (ret < val)
377 {
378 ret = ret << 1;
379 }
380 return ret;
381}
382
383static char * exe_str;
384
385void set_exe_str(char * str)
386{
387 exe_str = str;
388}
389
390void fatal_error(char *format, ...)
391{
392 va_list args;
393 va_start(args, format);
394 if (!headless) {
395 //take a guess at the final size
396 int32_t size = strlen(format) * 2;
397 char *buf = malloc(size);
398 int32_t actual = vsnprintf(buf, size, format, args);
399 if (actual >= size || actual < 0) {
400 if (actual < 0) {
401 //seems on windows, vsnprintf is returning -1 when the buffer is too small
402 //since we don't know the proper size, a generous multiplier will hopefully suffice
403 actual = size * 4;
404 } else {
405 actual++;
406 }
407 free(buf);
408 buf = malloc(actual);
409 va_end(args);
410 va_start(args, format);
411 vsnprintf(buf, actual, format, args);
412 }
413 fatal_puts(buf);
414 render_errorbox("Fatal Error", buf);
415 free(buf);
416 } else {
417 fatal_printf(format, args);
418 }
419 va_end(args);
420 exit(1);
421}
422
423void warning(char *format, ...)
424{
425 va_list args;
426 va_start(args, format);
427 if (headless || (isatty(STDERR_FILENO) && isatty(STDIN_FILENO))) {
428 warning_printf(format, args);
429 } else {
430 int32_t size = strlen(format) * 2;
431 char *buf = malloc(size);
432 int32_t actual = vsnprintf(buf, size, format, args);
433 if (actual >= size || actual < 0) {
434 if (actual < 0) {
435 //seems on windows, vsnprintf is returning -1 when the buffer is too small
436 //since we don't know the proper size, a generous multiplier will hopefully suffice
437 actual = size * 4;
438 } else {
439 actual++;
440 }
441 free(buf);
442 buf = malloc(actual);
443 va_end(args);
444 va_start(args, format);
445 vsnprintf(buf, actual, format, args);
446 }
447 warning_puts(buf);
448 render_infobox("BlastEm Info", buf);
449 free(buf);
450 }
451 va_end(args);
452}
453
454static uint8_t output_enabled = 1;
455void info_message(char *format, ...)
456{
457 va_list args;
458 va_start(args, format);
459 if (headless || (isatty(STDOUT_FILENO) && isatty(STDIN_FILENO))) {
460 if (output_enabled) {
461 info_printf(format, args);
462 }
463 } else {
464 int32_t size = strlen(format) * 2;
465 char *buf = malloc(size);
466 int32_t actual = vsnprintf(buf, size, format, args);
467 if (actual >= size || actual < 0) {
468 if (actual < 0) {
469 //seems on windows, vsnprintf is returning -1 when the buffer is too small
470 //since we don't know the proper size, a generous multiplier will hopefully suffice
471 actual = size * 4;
472 } else {
473 actual++;
474 }
475 free(buf);
476 buf = malloc(actual);
477 va_end(args);
478 va_start(args, format);
479 vsnprintf(buf, actual, format, args);
480 }
481 if (output_enabled) {
482 info_puts(buf);
483 }
484 render_infobox("BlastEm Info", buf);
485 free(buf);
486 }
487 va_end(args);
488}
489
490void debug_message(char *format, ...)
491{
492 va_list args;
493 va_start(args, format);
494 if (output_enabled) {
495 info_printf(format, args);
496 }
497}
498
499void disable_stdout_messages(void)
500{
501 output_enabled = 0;
502}
503
504char * get_home_dir()
505{
506 return getenv("HOME");
507}
508
509char * readlink_alloc(char * path)
510{
511 char * linktext = NULL;
512 ssize_t linksize = 512;
513 ssize_t cursize = 0;
514 do {
515 if (linksize > cursize) {
516 cursize = linksize;
517 if (linktext) {
518 free(linktext);
519 }
520 }
521 linktext = malloc(cursize);
522 linksize = readlink(path, linktext, cursize-1);
523 if (linksize == -1) {
524 perror("readlink");
525 free(linktext);
526 return NULL;
527 }
528 } while ((linksize+1) > cursize);
529 linktext[linksize] = 0;
530 return linktext;
531}
532
533char * get_exe_dir()
534{
535 static char * exe_dir;
536 if (!exe_dir) {
537 char * cur;
538 char * linktext = readlink_alloc("/proc/self/exe");
539 if (!linktext) {
540 goto fallback;
541 }
542 int linksize = strlen(linktext);
543 for(cur = linktext + linksize - 1; cur != linktext; cur--)
544 {
545 if (is_path_sep(*cur)) {
546 *cur = 0;
547 break;
548 }
549 }
550 if (cur == linktext) {
551 free(linktext);
552fallback:
553 if (!exe_str) {
554 fputs("/proc/self/exe is not available and set_exe_str was not called!", stderr);
555 }
556 int pathsize = strlen(exe_str);
557 for(cur = exe_str + pathsize - 1; cur != exe_str; cur--)
558 {
559 if (is_path_sep(*cur)) {
560 exe_dir = malloc(cur-exe_str+1);
561 memcpy(exe_dir, exe_str, cur-exe_str);
562 exe_dir[cur-exe_str] = 0;
563 break;
564 }
565 }
566 } else {
567 exe_dir = linktext;
568 }
569 }
570 return exe_dir;
571}
572#include <dirent.h>
573
574dir_entry *get_dir_list(char *path, size_t *numret)
575{
576 DIR *d = opendir(path);
577 if (!d) {
578 if (numret) {
579 *numret = 0;
580 }
581 return NULL;
582 }
583 size_t storage = 64;
584 dir_entry *ret = malloc(sizeof(dir_entry) * storage);
585 size_t pos = 0;
586 struct dirent* entry;
587 while (entry = readdir(d))
588 {
589 if (entry->d_type != DT_REG && entry->d_type != DT_LNK && entry->d_type != DT_DIR) {
590 continue;
591 }
592 if (pos == storage) {
593 storage = storage * 2;
594 ret = realloc(ret, sizeof(dir_entry) * storage);
595 }
596 ret[pos].name = strdup(entry->d_name);
597 ret[pos++].is_dir = entry->d_type == DT_DIR;
598 }
599 if (numret) {
600 *numret = pos;
601 }
602 closedir(d);
603 return ret;
604}
605
606time_t get_modification_time(char *path)
607{
608 struct stat st;
609 if (stat(path, &st)) {
610 return 0;
611 }
612#ifdef __APPLE__
613 return st.st_mtimespec.tv_sec;
614#else
615 //Android's Bionic doesn't support the new style so we'll use the old one instead
616 return st.st_mtime;
617#endif
618}
619
620int ensure_dir_exists(const char *path)
621{
622 struct stat st;
623 if (stat(path, &st)) {
624 if (errno == ENOENT) {
625 char *parent = strdup(path);
626 char *sep = strrchr(parent, '/');
627 if (sep && sep != parent) {
628 *sep = 0;
629 if (!ensure_dir_exists(parent)) {
630 free(parent);
631 return 0;
632 }
633 free(parent);
634 }
635 return mkdir(path, 0777) == 0;
636 } else {
637 char buf[80];
638 strerror_r(errno, buf, sizeof(buf));
639 warning("stat failed with error: %s", buf);
640 return 0;
641 }
642 }
643 return S_ISDIR(st.st_mode);
644}
645
646
647void free_dir_list(dir_entry *list, size_t numentries)
648{
649 for (size_t i = 0; i < numentries; i++)
650 {
651 free(list[i].name);
652 }
653 free(list);
654}
655
656static int sort_dir_alpha(const void *a, const void *b)
657{
658 const dir_entry *da, *db;
659 da = a;
660 db = b;
661 if (da->is_dir != db->is_dir) {
662 return db->is_dir - da->is_dir;
663 }
664 return strcasecmp(((dir_entry *)a)->name, ((dir_entry *)b)->name);
665}
666
667void sort_dir_list(dir_entry *list, size_t num_entries)
668{
669 qsort(list, num_entries, sizeof(dir_entry), sort_dir_alpha);
670}
671
672char *read_bundled_file(char *name, uint32_t *sizeret)
673{
674#ifdef DATA_PATH
675 char *data_dir = DATA_PATH;
676#else
677 char *data_dir = get_exe_dir();
678 if (!data_dir) {
679 if (sizeret) {
680 *sizeret = -1;
681 }
682 return NULL;
683 }
684#endif
685 char const *pieces[] = {data_dir, PATH_SEP, name};
686 char *path = alloc_concat_m(3, pieces);
687 FILE *f = fopen(path, "rb");
688 free(path);
689 if (!f) {
690 if (sizeret) {
691 *sizeret = -1;
692 }
693 return NULL;
694 }
695
696 long fsize = file_size(f);
697 if (sizeret) {
698 *sizeret = fsize;
699 }
700 char *ret;
701 if (fsize) {
702 //reserve an extra byte in case caller wants
703 //to null terminate the data
704 ret = malloc(fsize+1);
705 if (fread(ret, 1, fsize, f) != fsize) {
706 free(ret);
707 ret = NULL;
708 }
709 } else {
710 ret = NULL;
711 }
712 fclose(f);
713 return ret;
714}
715
716
717
718#define CONFIG_PREFIX "/.config"
719#define USERDATA_SUFFIX "/.local/share"
720
721char const *get_config_dir()
722{
723 static char* confdir;
724 if (!confdir) {
725 char const *base = get_home_dir();
726 if (base) {
727 confdir = alloc_concat(base, CONFIG_PREFIX PATH_SEP "blastem");
728 }
729 }
730 return confdir;
731}
732
733char const *get_userdata_dir()
734{
735 static char* savedir;
736 if (!savedir) {
737 char const *base = get_home_dir();
738 if (base) {
739 savedir = alloc_concat(base, USERDATA_SUFFIX);
740 }
741 }
742 return savedir;
743}