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}