+ }
+ else
+ {
+ *s_unescaped_ptr++ = *s_ptr;
+ }
+
+ s_ptr++;
+ }
+
+ out:
+ *s_unescaped_ptr = '\0';
+
+ return s_unescaped;
+}
+
+boolean strEqual(const char *s1, const char *s2)
+{
+ return (s1 == NULL && s2 == NULL ? TRUE :
+ s1 == NULL && s2 != NULL ? FALSE :
+ s1 != NULL && s2 == NULL ? FALSE :
+ strcmp(s1, s2) == 0);
+}
+
+boolean strEqualN(const char *s1, const char *s2, int n)
+{
+ return (s1 == NULL && s2 == NULL ? TRUE :
+ s1 == NULL && s2 != NULL ? FALSE :
+ s1 != NULL && s2 == NULL ? FALSE :
+ strncmp(s1, s2, n) == 0);
+}
+
+boolean strEqualCase(const char *s1, const char *s2)
+{
+ return (s1 == NULL && s2 == NULL ? TRUE :
+ s1 == NULL && s2 != NULL ? FALSE :
+ s1 != NULL && s2 == NULL ? FALSE :
+ strcasecmp(s1, s2) == 0);
+}
+
+boolean strEqualCaseN(const char *s1, const char *s2, int n)
+{
+ return (s1 == NULL && s2 == NULL ? TRUE :
+ s1 == NULL && s2 != NULL ? FALSE :
+ s1 != NULL && s2 == NULL ? FALSE :
+ strncasecmp(s1, s2, n) == 0);
+}
+
+boolean strPrefix(const char *s, const char *prefix)
+{
+ return (s == NULL && prefix == NULL ? TRUE :
+ s == NULL && prefix != NULL ? FALSE :
+ s != NULL && prefix == NULL ? FALSE :
+ strncmp(s, prefix, strlen(prefix)) == 0);
+}
+
+boolean strSuffix(const char *s, const char *suffix)
+{
+ return (s == NULL && suffix == NULL ? TRUE :
+ s == NULL && suffix != NULL ? FALSE :
+ s != NULL && suffix == NULL ? FALSE :
+ strlen(s) < strlen(suffix) ? FALSE :
+ strcmp(&s[strlen(s) - strlen(suffix)], suffix) == 0);
+}
+
+boolean strPrefixLower(const char *s, const char *prefix)
+{
+ char *s_lower = getStringToLower(s);
+ boolean match = strPrefix(s_lower, prefix);
+
+ free(s_lower);
+
+ return match;
+}
+
+boolean strSuffixLower(const char *s, const char *suffix)
+{
+ char *s_lower = getStringToLower(s);
+ boolean match = strSuffix(s_lower, suffix);
+
+ free(s_lower);
+
+ return match;
+}
+
+boolean isURL(const char *s)
+{
+ while (*s && *s >= 'a' && *s <= 'z')
+ s++;
+
+ return strPrefix(s, "://");
+}
+
+
+// ----------------------------------------------------------------------------
+// command line option handling functions
+// ----------------------------------------------------------------------------
+
+void GetOptions(int argc, char *argv[],
+ void (*print_usage_function)(void),
+ void (*print_version_function)(void))
+{
+ char *base_path = getProgramMainDataPath(argv[0], BASE_PATH);
+ char **argvplus = checked_calloc((argc + 1) * sizeof(char **));
+ char **options_left = &argvplus[1];
+
+ // replace original "argv" with null-terminated array of string pointers
+ while (argc--)
+ argvplus[argc] = argv[argc];
+
+ // initialize global program options
+ options.server_host = NULL;
+ options.server_port = 0;
+
+ options.base_directory = base_path;
+
+ options.level_directory = getPath2(base_path, LEVELS_DIRECTORY);
+ options.graphics_directory = getPath2(base_path, GRAPHICS_DIRECTORY);
+ options.sounds_directory = getPath2(base_path, SOUNDS_DIRECTORY);
+ options.music_directory = getPath2(base_path, MUSIC_DIRECTORY);
+ options.docs_directory = getPath2(base_path, DOCS_DIRECTORY);
+ options.conf_directory = getPath2(base_path, CONF_DIRECTORY);
+
+ options.execute_command = NULL;
+ options.tape_log_filename = NULL;
+ options.special_flags = NULL;
+ options.debug_mode = NULL;
+ options.player_name = NULL;
+ options.identifier = NULL;
+ options.level_nr = NULL;
+
+ options.display_nr = 0;
+
+ options.mytapes = FALSE;
+ options.serveronly = FALSE;
+ options.network = FALSE;
+ options.verbose = FALSE;
+ options.debug = FALSE;
+
+#if 1
+ options.verbose = TRUE;
+#else
+#if !defined(PLATFORM_UNIX)
+ if (*options_left == NULL) // no options given -- enable verbose mode
+ options.verbose = TRUE;
+#endif
+#endif
+
+#if DEBUG
+#if defined(PLATFORM_ANDROID)
+ options.debug = TRUE;
+#endif
+#endif
+
+ while (*options_left)
+ {
+ char option_str[MAX_OPTION_LEN];
+ char *option = options_left[0];
+ char *next_option = options_left[1];
+ char *option_arg = NULL;
+ int option_len = strlen(option);
+
+ if (option_len >= MAX_OPTION_LEN)
+ FailWithHelp("unrecognized option '%s'", option);
+
+ strcpy(option_str, option); // copy argument into buffer
+ option = option_str;
+
+ if (strEqual(option, "--")) // stop scanning arguments
+ break;
+
+ if (strPrefix(option, "--")) // treat '--' like '-'
+ option++;
+
+ option_arg = strchr(option, '=');
+ if (option_arg == NULL) // no '=' in option
+ option_arg = next_option;
+ else
+ {
+ *option_arg++ = '\0'; // cut argument from option
+ if (*option_arg == '\0') // no argument after '='
+ FailWithHelp("option '%s' has invalid argument", option_str);
+ }
+
+ option_len = strlen(option);
+
+ if (strEqual(option, "-"))
+ {
+ FailWithHelp("unrecognized option '%s'", option);
+ }
+ else if (strncmp(option, "-help", option_len) == 0)
+ {
+ print_usage_function();
+
+ exit(0);
+ }
+ else if (strncmp(option, "-basepath", option_len) == 0)
+ {
+ if (option_arg == NULL)
+ FailWithHelp("option '%s' requires an argument", option_str);
+
+ options.base_directory = base_path = getStringCopy(option_arg);
+ if (option_arg == next_option)
+ options_left++;
+
+ // adjust paths for sub-directories in base directory accordingly
+ options.level_directory = getPath2(base_path, LEVELS_DIRECTORY);
+ options.graphics_directory = getPath2(base_path, GRAPHICS_DIRECTORY);
+ options.sounds_directory = getPath2(base_path, SOUNDS_DIRECTORY);
+ options.music_directory = getPath2(base_path, MUSIC_DIRECTORY);
+ options.docs_directory = getPath2(base_path, DOCS_DIRECTORY);
+ options.conf_directory = getPath2(base_path, CONF_DIRECTORY);
+ }
+ else if (strncmp(option, "-levels", option_len) == 0)
+ {
+ if (option_arg == NULL)
+ FailWithHelp("option '%s' requires an argument", option_str);
+
+ options.level_directory = getStringCopy(option_arg);
+ if (option_arg == next_option)
+ options_left++;
+ }
+ else if (strncmp(option, "-graphics", option_len) == 0)
+ {
+ if (option_arg == NULL)
+ FailWithHelp("option '%s' requires an argument", option_str);
+
+ options.graphics_directory = getStringCopy(option_arg);
+ if (option_arg == next_option)
+ options_left++;
+ }
+ else if (strncmp(option, "-sounds", option_len) == 0)
+ {
+ if (option_arg == NULL)
+ FailWithHelp("option '%s' requires an argument", option_str);
+
+ options.sounds_directory = getStringCopy(option_arg);
+ if (option_arg == next_option)
+ options_left++;
+ }
+ else if (strncmp(option, "-music", option_len) == 0)
+ {
+ if (option_arg == NULL)
+ FailWithHelp("option '%s' requires an argument", option_str);
+
+ options.music_directory = getStringCopy(option_arg);
+ if (option_arg == next_option)
+ options_left++;
+ }
+ else if (strncmp(option, "-mytapes", option_len) == 0)
+ {
+ options.mytapes = TRUE;
+ }
+ else if (strncmp(option, "-network", option_len) == 0)
+ {
+ options.network = TRUE;
+ }
+ else if (strncmp(option, "-serveronly", option_len) == 0)
+ {
+ options.serveronly = TRUE;
+ }
+ else if (strncmp(option, "-debug", option_len) == 0)
+ {
+ options.debug = TRUE;
+
+ // optionally, debug output can be limited to a specific debug mode
+ if (option_arg != next_option)
+ options.debug_mode = getStringCopy(option_arg);
+ }
+ else if (strncmp(option, "-player-name", option_len) == 0)
+ {
+ if (option_arg == NULL)
+ FailWithHelp("option '%s' requires an argument", option_str);
+
+ options.player_name = getStringCopy(option_arg);
+ if (option_arg == next_option)
+ options_left++;
+ }
+ else if (strncmp(option, "-identifier", option_len) == 0)
+ {
+ if (option_arg == NULL)
+ FailWithHelp("option '%s' requires an argument", option_str);
+
+ options.identifier = getStringCopy(option_arg);
+ if (option_arg == next_option)
+ options_left++;
+ }
+ else if (strncmp(option, "-level-nr", option_len) == 0)
+ {
+ if (option_arg == NULL)
+ FailWithHelp("option '%s' requires an argument", option_str);
+
+ options.level_nr = getStringCopy(option_arg);
+ if (option_arg == next_option)
+ options_left++;
+ }
+ else if (strncmp(option, "-verbose", option_len) == 0)
+ {
+ options.verbose = TRUE;
+ }
+ else if (strncmp(option, "-version", option_len) == 0 ||
+ strncmp(option, "-V", option_len) == 0)
+ {
+ print_version_function();
+
+ exit(0);
+ }
+ else if (strPrefix(option, "-D"))
+ {
+ options.special_flags = getStringCopy(&option[2]);
+ }
+ else if (strncmp(option, "-execute", option_len) == 0)
+ {
+ if (option_arg == NULL)
+ FailWithHelp("option '%s' requires an argument", option_str);
+
+ options.execute_command = getStringCopy(option_arg);
+ if (option_arg == next_option)
+ options_left++;
+
+ // when doing batch processing, always enable verbose mode (warnings)
+ options.verbose = TRUE;
+ }
+ else if (strncmp(option, "-tape_logfile", option_len) == 0)
+ {
+ if (option_arg == NULL)
+ FailWithHelp("option '%s' requires an argument", option_str);
+
+ options.tape_log_filename = getStringCopy(option_arg);
+ if (option_arg == next_option)
+ options_left++;
+ }
+ else if (strncmp(option, "-display", option_len) == 0)
+ {
+ if (option_arg == NULL)
+ FailWithHelp("option '%s' requires an argument", option_str);
+
+ if (option_arg == next_option)
+ options_left++;
+
+ int display_nr = atoi(option_arg);
+
+#if 1
+ // dirty hack: SDL_GetNumVideoDisplays() seems broken on some systems
+ options.display_nr = display_nr;
+#else
+ options.display_nr =
+ MAX(0, MIN(display_nr, SDL_GetNumVideoDisplays() - 1));
+
+ if (display_nr != options.display_nr)
+ Warn("invalid display %d -- using display %d",
+ display_nr, options.display_nr);
+#endif
+ }
+#if defined(PLATFORM_MAC)
+ else if (strPrefix(option, "-psn"))
+ {
+ // ignore process serial number when launched via GUI on Mac OS X
+ }
+#endif
+ else if (*option == '-')
+ {
+ FailWithHelp("unrecognized option '%s'", option_str);
+ }
+ else if (options.server_host == NULL)
+ {
+ options.server_host = *options_left;
+ }
+ else if (options.server_port == 0)
+ {
+ options.server_port = atoi(*options_left);
+ if (options.server_port < 1024)
+ FailWithHelp("bad port number '%d'", options.server_port);
+ }
+ else
+ FailWithHelp("too many arguments");
+
+ options_left++;
+ }
+}
+
+
+// ----------------------------------------------------------------------------
+// checked memory allocation and freeing functions
+// ----------------------------------------------------------------------------
+
+void *checked_malloc(unsigned int size)
+{
+ void *ptr;
+
+ ptr = malloc(size);
+
+ if (ptr == NULL)
+ Fail("cannot allocate %d bytes -- out of memory", size);
+
+ return ptr;
+}
+
+void *checked_calloc(unsigned int size)
+{
+ void *ptr;
+
+ ptr = calloc(1, size);
+
+ if (ptr == NULL)
+ Fail("cannot allocate %d bytes -- out of memory", size);
+
+ return ptr;
+}
+
+void *checked_realloc(void *ptr, unsigned int size)
+{
+ ptr = realloc(ptr, size);
+
+ if (ptr == NULL)
+ Fail("cannot allocate %d bytes -- out of memory", size);
+
+ return ptr;
+}
+
+void checked_free(void *ptr)
+{
+ if (ptr != NULL) // this check should be done by free() anyway
+ free(ptr);
+}
+
+void clear_mem(void *ptr, unsigned int size)
+{
+#if defined(PLATFORM_WINDOWS)
+ // for unknown reason, memset() sometimes crashes when compiled with MinGW
+ char *cptr = (char *)ptr;
+
+ while (size--)
+ *cptr++ = 0;
+#else
+ memset(ptr, 0, size);
+#endif
+}
+
+
+// ----------------------------------------------------------------------------
+// various helper functions
+// ----------------------------------------------------------------------------
+
+void swap_numbers(int *i1, int *i2)
+{
+ int help = *i1;
+
+ *i1 = *i2;
+ *i2 = help;
+}
+
+void swap_number_pairs(int *x1, int *y1, int *x2, int *y2)
+{
+ int help_x = *x1;
+ int help_y = *y1;
+
+ *x1 = *x2;
+ *x2 = help_x;
+
+ *y1 = *y2;
+ *y2 = help_y;
+}
+
+int get_number_of_bits(int bits)
+{
+ /*
+ Counting bits set, Brian Kernighan's way
+
+ Brian Kernighan's method goes through as many iterations as there are set
+ bits. So if we have a 32-bit word with only the high bit set, then it will
+ only go once through the loop.
+
+ Published in 1988, the C Programming Language 2nd Ed. (by Brian W. Kernighan
+ and Dennis M. Ritchie) mentions this in exercise 2-9.
+ First published by Peter Wegner in CACM 3 (1960), 322.
+ */
+
+ int num_bits = 0;
+
+ while (bits)
+ {
+ bits &= bits - 1; // clear the least significant bit set
+ num_bits++;
+ }
+
+ return num_bits;
+}
+
+/* the "put" variants of the following file access functions check for the file
+ pointer being != NULL and return the number of bytes they have or would have
+ written; this allows for chunk writing functions to first determine the size
+ of the (not yet written) chunk, write the correct chunk size and finally
+ write the chunk itself */
+
+int getFile8BitInteger(File *file)
+{
+ return getByteFromFile(file);
+}
+
+int putFile8BitInteger(FILE *file, int value)
+{
+ if (file != NULL)
+ fputc(value, file);
+
+ return 1;
+}
+
+int getFile16BitInteger(File *file, int byte_order)
+{
+ if (byte_order == BYTE_ORDER_BIG_ENDIAN)
+ return ((getByteFromFile(file) << 8) |
+ (getByteFromFile(file) << 0));
+ else // BYTE_ORDER_LITTLE_ENDIAN
+ return ((getByteFromFile(file) << 0) |
+ (getByteFromFile(file) << 8));
+}
+
+int putFile16BitInteger(FILE *file, int value, int byte_order)
+{
+ if (file != NULL)
+ {
+ if (byte_order == BYTE_ORDER_BIG_ENDIAN)
+ {
+ fputc((value >> 8) & 0xff, file);
+ fputc((value >> 0) & 0xff, file);
+ }
+ else // BYTE_ORDER_LITTLE_ENDIAN
+ {
+ fputc((value >> 0) & 0xff, file);
+ fputc((value >> 8) & 0xff, file);
+ }
+ }
+
+ return 2;
+}
+
+int getFile32BitInteger(File *file, int byte_order)
+{
+ if (byte_order == BYTE_ORDER_BIG_ENDIAN)
+ return ((getByteFromFile(file) << 24) |
+ (getByteFromFile(file) << 16) |
+ (getByteFromFile(file) << 8) |
+ (getByteFromFile(file) << 0));
+ else // BYTE_ORDER_LITTLE_ENDIAN
+ return ((getByteFromFile(file) << 0) |
+ (getByteFromFile(file) << 8) |
+ (getByteFromFile(file) << 16) |
+ (getByteFromFile(file) << 24));
+}
+
+int putFile32BitInteger(FILE *file, int value, int byte_order)
+{
+ if (file != NULL)
+ {
+ if (byte_order == BYTE_ORDER_BIG_ENDIAN)
+ {
+ fputc((value >> 24) & 0xff, file);
+ fputc((value >> 16) & 0xff, file);
+ fputc((value >> 8) & 0xff, file);
+ fputc((value >> 0) & 0xff, file);
+ }
+ else // BYTE_ORDER_LITTLE_ENDIAN
+ {
+ fputc((value >> 0) & 0xff, file);
+ fputc((value >> 8) & 0xff, file);
+ fputc((value >> 16) & 0xff, file);
+ fputc((value >> 24) & 0xff, file);
+ }
+ }
+
+ return 4;
+}
+
+boolean getFileChunk(File *file, char *chunk_name, int *chunk_size,
+ int byte_order)
+{
+ const int chunk_name_length = 4;
+
+ // read chunk name
+ if (getStringFromFile(file, chunk_name, chunk_name_length + 1) == NULL)
+ return FALSE;
+
+ if (chunk_size != NULL)
+ {
+ // read chunk size
+ *chunk_size = getFile32BitInteger(file, byte_order);
+ }
+
+ return (checkEndOfFile(file) ? FALSE : TRUE);
+}
+
+int putFileChunk(FILE *file, char *chunk_name, int chunk_size,
+ int byte_order)
+{
+ int num_bytes = 0;
+
+ // write chunk name
+ if (file != NULL)
+ fputs(chunk_name, file);
+
+ num_bytes += strlen(chunk_name);
+
+ if (chunk_size >= 0)
+ {
+ // write chunk size
+ if (file != NULL)
+ putFile32BitInteger(file, chunk_size, byte_order);
+
+ num_bytes += 4;
+ }
+
+ return num_bytes;
+}
+
+int getFileVersion(File *file)
+{
+ int version_super = getByteFromFile(file);
+ int version_major = getByteFromFile(file);
+ int version_minor = getByteFromFile(file);
+ int version_patch = getByteFromFile(file);
+
+ return VERSION_IDENT(version_super, version_major, version_minor,
+ version_patch);
+}
+
+int putFileVersion(FILE *file, int version)
+{
+ if (file != NULL)
+ {
+ int version_super = VERSION_SUPER(version);
+ int version_major = VERSION_MAJOR(version);
+ int version_minor = VERSION_MINOR(version);
+ int version_patch = VERSION_PATCH(version);
+
+ fputc(version_super, file);
+ fputc(version_major, file);
+ fputc(version_minor, file);
+ fputc(version_patch, file);
+ }
+
+ return 4;
+}
+
+void ReadBytesFromFile(File *file, byte *buffer, unsigned int bytes)
+{
+ int i;
+
+ for (i = 0; i < bytes && !checkEndOfFile(file); i++)
+ buffer[i] = getByteFromFile(file);
+}
+
+void WriteBytesToFile(FILE *file, byte *buffer, unsigned int bytes)
+{
+ int i;
+
+ for (i = 0; i < bytes; i++)
+ fputc(buffer[i], file);
+}
+
+void ReadUnusedBytesFromFile(File *file, unsigned int bytes)
+{
+ while (bytes-- && !checkEndOfFile(file))
+ getByteFromFile(file);
+}
+
+void WriteUnusedBytesToFile(FILE *file, unsigned int bytes)
+{
+ while (bytes--)
+ fputc(0, file);
+}
+
+
+// ----------------------------------------------------------------------------
+// functions to convert between ISO-8859-1 and UTF-8
+// ----------------------------------------------------------------------------
+
+char *getUTF8FromLatin1(char *latin1)
+{
+ int max_utf8_size = 2 * strlen(latin1) + 1;
+ char *utf8 = checked_calloc(max_utf8_size);
+ unsigned char *src = (unsigned char *)latin1;
+ unsigned char *dst = (unsigned char *)utf8;
+
+ while (*src)
+ {
+ if (*src < 128) // pure 7-bit ASCII
+ {
+ *dst++ = *src;
+ }
+ else if (*src >= 160) // non-ASCII characters
+ {
+ *dst++ = 194 + (*src >= 192);
+ *dst++ = 128 + (*src & 63);
+ }
+ else // undefined in ISO-8859-1
+ {
+ *dst++ = '?';
+ }
+
+ src++;
+ }
+
+ // only use the smallest possible string buffer size
+ utf8 = checked_realloc(utf8, strlen(utf8) + 1);
+
+ return utf8;
+}
+
+char *getLatin1FromUTF8(char *utf8)
+{
+ int max_latin1_size = strlen(utf8) + 1;
+ char *latin1 = checked_calloc(max_latin1_size);
+ unsigned char *src = (unsigned char *)utf8;
+ unsigned char *dst = (unsigned char *)latin1;
+
+ while (*src)
+ {
+ if (*src < 128) // pure 7-bit ASCII
+ {
+ *dst++ = *src++;
+ }
+ else if (src[0] == 194 &&
+ src[1] >= 128 && src[1] < 192) // non-ASCII characters
+ {
+ *dst++ = src[1];
+ src += 2;
+ }
+ else if (src[0] == 195 &&
+ src[1] >= 128 && src[1] < 192) // non-ASCII characters
+ {
+ *dst++ = src[1] + 64;
+ src += 2;
+ }
+
+ // all other UTF-8 characters are undefined in ISO-8859-1
+
+ else if (src[0] >= 192 && src[0] < 224 &&
+ src[1] >= 128 && src[1] < 192)
+ {
+ *dst++ = '?';
+ src += 2;
+ }
+ else if (src[0] >= 224 && src[0] < 240 &&
+ src[1] >= 128 && src[1] < 192 &&
+ src[2] >= 128 && src[2] < 192)
+ {
+ *dst++ = '?';
+ src += 3;
+ }
+ else if (src[0] >= 240 && src[0] < 248 &&
+ src[1] >= 128 && src[1] < 192 &&
+ src[2] >= 128 && src[2] < 192 &&
+ src[3] >= 128 && src[3] < 192)
+ {
+ *dst++ = '?';
+ src += 4;
+ }
+ else if (src[0] >= 248 && src[0] < 252 &&
+ src[1] >= 128 && src[1] < 192 &&
+ src[2] >= 128 && src[2] < 192 &&
+ src[3] >= 128 && src[3] < 192 &&
+ src[4] >= 128 && src[4] < 192)
+ {
+ *dst++ = '?';
+ src += 5;
+ }
+ else if (src[0] >= 252 && src[0] < 254 &&
+ src[1] >= 128 && src[1] < 192 &&
+ src[2] >= 128 && src[2] < 192 &&
+ src[3] >= 128 && src[3] < 192 &&
+ src[4] >= 128 && src[4] < 192 &&
+ src[5] >= 128 && src[5] < 192)
+ {
+ *dst++ = '?';
+ src += 6;
+ }
+ else
+ {
+ *dst++ = '?';
+ src++;
+ }
+ }
+
+ // only use the smallest possible string buffer size
+ latin1 = checked_realloc(latin1, strlen(latin1) + 1);
+
+ return latin1;
+}
+
+int getTextEncoding(char *text)
+{
+ unsigned char *src = (unsigned char *)text;
+ int encoding = TEXT_ENCODING_ASCII; // default: assume encoding is ASCII
+
+ while (*src)
+ {
+ if (*src >= 128)
+ encoding = TEXT_ENCODING_UTF_8; // non-ASCII character: assume UTF-8
+
+ if (*src < 128)
+ {
+ src++;
+ }
+ else if (src[0] >= 192 && src[0] < 224 &&
+ src[1] >= 128 && src[1] < 192)
+ {
+ src += 2;
+ }
+ else if (src[0] >= 224 && src[0] < 240 &&
+ src[1] >= 128 && src[1] < 192 &&
+ src[2] >= 128 && src[2] < 192)
+ {
+ src += 3;
+ }
+ else if (src[0] >= 240 && src[0] < 248 &&
+ src[1] >= 128 && src[1] < 192 &&
+ src[2] >= 128 && src[2] < 192 &&
+ src[3] >= 128 && src[3] < 192)
+ {
+ src += 4;
+ }
+ else if (src[0] >= 248 && src[0] < 252 &&
+ src[1] >= 128 && src[1] < 192 &&
+ src[2] >= 128 && src[2] < 192 &&
+ src[3] >= 128 && src[3] < 192 &&
+ src[4] >= 128 && src[4] < 192)
+ {
+ src += 5;
+ }
+ else if (src[0] >= 252 && src[0] < 254 &&
+ src[1] >= 128 && src[1] < 192 &&
+ src[2] >= 128 && src[2] < 192 &&
+ src[3] >= 128 && src[3] < 192 &&
+ src[4] >= 128 && src[4] < 192 &&
+ src[5] >= 128 && src[5] < 192)
+ {
+ src += 6;
+ }
+ else
+ {
+ return TEXT_ENCODING_UNKNOWN; // non-UTF-8 character: unknown encoding
+ }
+ }
+
+ return encoding;
+}
+
+
+// ----------------------------------------------------------------------------
+// functions for JSON handling
+// ----------------------------------------------------------------------------
+
+char *getEscapedJSON(char *s)
+{
+ int max_json_size = 2 * strlen(s) + 1;
+ char *json = checked_calloc(max_json_size);
+ unsigned char *src = (unsigned char *)s;
+ unsigned char *dst = (unsigned char *)json;
+ char *escaped[256] =
+ {
+ ['\b'] = "\\b",
+ ['\f'] = "\\f",
+ ['\n'] = "\\n",
+ ['\r'] = "\\r",
+ ['\t'] = "\\t",
+ ['\"'] = "\\\"",
+ ['\\'] = "\\\\",
+ };
+
+ while (*src)
+ {
+ if (escaped[*src] != NULL)
+ {
+ char *esc = escaped[*src++];
+
+ while (*esc)
+ *dst++ = *esc++;
+ }
+ else
+ {
+ *dst++ = *src++;
+ }
+ }
+
+ // only use the smallest possible string buffer size
+ json = checked_realloc(json, strlen(json) + 1);
+
+ return json;
+}
+
+
+// ----------------------------------------------------------------------------
+// functions to translate key identifiers between different format
+// ----------------------------------------------------------------------------
+
+#define TRANSLATE_KEYSYM_TO_KEYNAME 0
+#define TRANSLATE_KEYSYM_TO_X11KEYNAME 1
+#define TRANSLATE_KEYNAME_TO_KEYSYM 2
+#define TRANSLATE_X11KEYNAME_TO_KEYSYM 3
+
+static void translate_keyname(Key *keysym, char **x11name, char **name, int mode)
+{
+ static struct
+ {
+ Key key;
+ char *x11name;
+ char *name;
+ } translate_key[] =
+ {
+ // return and escape keys
+ { KSYM_Return, "XK_Return", "return" },
+ { KSYM_Escape, "XK_Escape", "escape" },
+
+ // normal cursor keys
+ { KSYM_Left, "XK_Left", "cursor left" },
+ { KSYM_Right, "XK_Right", "cursor right" },
+ { KSYM_Up, "XK_Up", "cursor up" },
+ { KSYM_Down, "XK_Down", "cursor down" },
+
+ // keypad cursor keys
+#ifdef KSYM_KP_Left
+ { KSYM_KP_Left, "XK_KP_Left", "keypad left" },
+ { KSYM_KP_Right, "XK_KP_Right", "keypad right" },
+ { KSYM_KP_Up, "XK_KP_Up", "keypad up" },
+ { KSYM_KP_Down, "XK_KP_Down", "keypad down" },
+#endif
+
+ // other keypad keys
+#ifdef KSYM_KP_Enter
+ { KSYM_KP_Enter, "XK_KP_Enter", "keypad enter" },
+ { KSYM_KP_Add, "XK_KP_Add", "keypad +" },
+ { KSYM_KP_Subtract, "XK_KP_Subtract", "keypad -" },
+ { KSYM_KP_Multiply, "XK_KP_Multiply", "keypad mltply" },
+ { KSYM_KP_Divide, "XK_KP_Divide", "keypad /" },
+ { KSYM_KP_Separator,"XK_KP_Separator", "keypad ," },
+#endif
+
+ // modifier keys
+ { KSYM_Shift_L, "XK_Shift_L", "left shift" },
+ { KSYM_Shift_R, "XK_Shift_R", "right shift" },
+ { KSYM_Control_L, "XK_Control_L", "left control" },
+ { KSYM_Control_R, "XK_Control_R", "right control" },
+ { KSYM_Meta_L, "XK_Meta_L", "left meta" },
+ { KSYM_Meta_R, "XK_Meta_R", "right meta" },
+ { KSYM_Alt_L, "XK_Alt_L", "left alt" },
+ { KSYM_Alt_R, "XK_Alt_R", "right alt" },
+ { KSYM_Mode_switch, "XK_Mode_switch", "mode switch" }, // Alt-R
+ { KSYM_Multi_key, "XK_Multi_key", "multi key" }, // Ctrl-R
+
+ // some special keys
+ { KSYM_BackSpace, "XK_BackSpace", "backspace" },
+ { KSYM_Delete, "XK_Delete", "delete" },
+ { KSYM_Insert, "XK_Insert", "insert" },
+ { KSYM_Tab, "XK_Tab", "tab" },
+ { KSYM_Home, "XK_Home", "home" },
+ { KSYM_End, "XK_End", "end" },
+ { KSYM_Page_Up, "XK_Page_Up", "page up" },
+ { KSYM_Page_Down, "XK_Page_Down", "page down" },
+
+ { KSYM_Select, "XK_Select", "select" },
+ { KSYM_Menu, "XK_Menu", "menu" }, // menu key
+ { KSYM_Back, "XK_Back", "back" }, // back key
+ { KSYM_PlayPause, "XK_PlayPause", "play/pause" },
+#if defined(PLATFORM_ANDROID)
+ { KSYM_Rewind, "XK_Rewind", "rewind" },
+ { KSYM_FastForward, "XK_FastForward", "fast forward" },
+#endif
+
+ // ASCII 0x20 to 0x40 keys (except numbers)
+ { KSYM_space, "XK_space", "space" },
+ { KSYM_exclam, "XK_exclam", "!" },
+ { KSYM_quotedbl, "XK_quotedbl", "\"" },
+ { KSYM_numbersign, "XK_numbersign", "#" },
+ { KSYM_dollar, "XK_dollar", "$" },
+ { KSYM_percent, "XK_percent", "%" },
+ { KSYM_ampersand, "XK_ampersand", "&" },
+ { KSYM_apostrophe, "XK_apostrophe", "'" },
+ { KSYM_parenleft, "XK_parenleft", "(" },
+ { KSYM_parenright, "XK_parenright", ")" },
+ { KSYM_asterisk, "XK_asterisk", "*" },
+ { KSYM_plus, "XK_plus", "+" },
+ { KSYM_comma, "XK_comma", "," },
+ { KSYM_minus, "XK_minus", "-" },
+ { KSYM_period, "XK_period", "." },
+ { KSYM_slash, "XK_slash", "/" },
+ { KSYM_colon, "XK_colon", ":" },
+ { KSYM_semicolon, "XK_semicolon", ";" },
+ { KSYM_less, "XK_less", "<" },
+ { KSYM_equal, "XK_equal", "=" },
+ { KSYM_greater, "XK_greater", ">" },
+ { KSYM_question, "XK_question", "?" },
+ { KSYM_at, "XK_at", "@" },
+
+ // more ASCII keys
+ { KSYM_bracketleft, "XK_bracketleft", "[" },
+ { KSYM_backslash, "XK_backslash", "\\" },
+ { KSYM_bracketright,"XK_bracketright", "]" },
+ { KSYM_asciicircum, "XK_asciicircum", "^" },
+ { KSYM_underscore, "XK_underscore", "_" },
+ { KSYM_grave, "XK_grave", "grave" },
+ { KSYM_quoteleft, "XK_quoteleft", "quote left" },
+ { KSYM_braceleft, "XK_braceleft", "brace left" },
+ { KSYM_bar, "XK_bar", "bar" },
+ { KSYM_braceright, "XK_braceright", "brace right" },
+ { KSYM_asciitilde, "XK_asciitilde", "~" },
+
+ // special (non-ASCII) keys
+ { KSYM_degree, "XK_degree", "degree" },
+ { KSYM_Adiaeresis, "XK_Adiaeresis", "A umlaut" },
+ { KSYM_Odiaeresis, "XK_Odiaeresis", "O umlaut" },
+ { KSYM_Udiaeresis, "XK_Udiaeresis", "U umlaut" },
+ { KSYM_adiaeresis, "XK_adiaeresis", "a umlaut" },
+ { KSYM_odiaeresis, "XK_odiaeresis", "o umlaut" },
+ { KSYM_udiaeresis, "XK_udiaeresis", "u umlaut" },
+ { KSYM_ssharp, "XK_ssharp", "sharp s" },
+
+ // special (non-ASCII) keys (UTF-8, for reverse mapping only)
+ { KSYM_degree, "XK_degree", "\xc2\xb0" },
+ { KSYM_Adiaeresis, "XK_Adiaeresis", "\xc3\x84" },
+ { KSYM_Odiaeresis, "XK_Odiaeresis", "\xc3\x96" },
+ { KSYM_Udiaeresis, "XK_Udiaeresis", "\xc3\x9c" },
+ { KSYM_adiaeresis, "XK_adiaeresis", "\xc3\xa4" },
+ { KSYM_odiaeresis, "XK_odiaeresis", "\xc3\xb6" },
+ { KSYM_udiaeresis, "XK_udiaeresis", "\xc3\xbc" },
+ { KSYM_ssharp, "XK_ssharp", "\xc3\x9f" },
+
+ // other keys (for reverse mapping only)
+ { KSYM_space, "XK_space", " " },
+
+ // keypad keys are not in numerical order in SDL2
+ { KSYM_KP_0, "XK_KP_0", "keypad 0" },
+ { KSYM_KP_1, "XK_KP_1", "keypad 1" },
+ { KSYM_KP_2, "XK_KP_2", "keypad 2" },
+ { KSYM_KP_3, "XK_KP_3", "keypad 3" },
+ { KSYM_KP_4, "XK_KP_4", "keypad 4" },
+ { KSYM_KP_5, "XK_KP_5", "keypad 5" },
+ { KSYM_KP_6, "XK_KP_6", "keypad 6" },
+ { KSYM_KP_7, "XK_KP_7", "keypad 7" },
+ { KSYM_KP_8, "XK_KP_8", "keypad 8" },
+ { KSYM_KP_9, "XK_KP_9", "keypad 9" },
+
+ // end-of-array identifier
+ { 0, NULL, NULL }
+ };
+
+ int i;
+
+ if (mode == TRANSLATE_KEYSYM_TO_KEYNAME)
+ {
+ static char name_buffer[30];
+ Key key = *keysym;
+
+ if (key >= KSYM_A && key <= KSYM_Z)
+ sprintf(name_buffer, "%c", 'A' + (char)(key - KSYM_A));
+ else if (key >= KSYM_a && key <= KSYM_z)
+ sprintf(name_buffer, "%c", 'a' + (char)(key - KSYM_a));
+ else if (key >= KSYM_0 && key <= KSYM_9)
+ sprintf(name_buffer, "%c", '0' + (char)(key - KSYM_0));
+ else if (key >= KSYM_FKEY_FIRST && key <= KSYM_FKEY_LAST)
+ sprintf(name_buffer, "F%d", (int)(key - KSYM_FKEY_FIRST + 1));
+ else if (key == KSYM_UNDEFINED)
+ strcpy(name_buffer, "(undefined)");
+ else
+ {
+ i = 0;
+
+ do
+ {
+ if (key == translate_key[i].key)
+ {
+ strcpy(name_buffer, translate_key[i].name);
+ break;
+ }
+ }
+ while (translate_key[++i].name);
+
+ if (!translate_key[i].name)
+ strcpy(name_buffer, "(unknown)");
+ }
+
+ *name = name_buffer;
+ }
+ else if (mode == TRANSLATE_KEYSYM_TO_X11KEYNAME)
+ {
+ static char name_buffer[30];
+ Key key = *keysym;
+
+ if (key >= KSYM_A && key <= KSYM_Z)
+ sprintf(name_buffer, "XK_%c", 'A' + (char)(key - KSYM_A));
+ else if (key >= KSYM_a && key <= KSYM_z)
+ sprintf(name_buffer, "XK_%c", 'a' + (char)(key - KSYM_a));
+ else if (key >= KSYM_0 && key <= KSYM_9)
+ sprintf(name_buffer, "XK_%c", '0' + (char)(key - KSYM_0));
+ else if (key >= KSYM_FKEY_FIRST && key <= KSYM_FKEY_LAST)
+ sprintf(name_buffer, "XK_F%d", (int)(key - KSYM_FKEY_FIRST + 1));
+ else if (key == KSYM_UNDEFINED)
+ strcpy(name_buffer, "[undefined]");
+ else
+ {
+ i = 0;
+
+ do
+ {
+ if (key == translate_key[i].key)
+ {
+ strcpy(name_buffer, translate_key[i].x11name);
+ break;
+ }
+ }
+ while (translate_key[++i].x11name);
+
+ if (!translate_key[i].x11name)
+ sprintf(name_buffer, "0x%04x", (unsigned int)key);
+ }
+
+ *x11name = name_buffer;
+ }
+ else if (mode == TRANSLATE_KEYNAME_TO_KEYSYM)
+ {
+ Key key = KSYM_UNDEFINED;
+ char *name_ptr = *name;
+
+ if (strlen(*name) == 1)
+ {
+ char c = name_ptr[0];
+
+ if (c >= 'A' && c <= 'Z')
+ key = KSYM_A + (Key)(c - 'A');
+ else if (c >= 'a' && c <= 'z')
+ key = KSYM_a + (Key)(c - 'a');
+ else if (c >= '0' && c <= '9')
+ key = KSYM_0 + (Key)(c - '0');
+ }
+
+ if (key == KSYM_UNDEFINED)
+ {
+ i = 0;
+
+ do
+ {
+ if (strEqual(translate_key[i].name, *name))
+ {
+ key = translate_key[i].key;
+ break;
+ }
+ }
+ while (translate_key[++i].x11name);
+ }
+
+ if (key == KSYM_UNDEFINED)
+ Warn("getKeyFromKeyName(): not completely implemented");
+
+ *keysym = key;
+ }
+ else if (mode == TRANSLATE_X11KEYNAME_TO_KEYSYM)
+ {
+ Key key = KSYM_UNDEFINED;
+ char *name_ptr = *x11name;
+
+ if (strPrefix(name_ptr, "XK_") && strlen(name_ptr) == 4)
+ {
+ char c = name_ptr[3];
+
+ if (c >= 'A' && c <= 'Z')
+ key = KSYM_A + (Key)(c - 'A');
+ else if (c >= 'a' && c <= 'z')
+ key = KSYM_a + (Key)(c - 'a');
+ else if (c >= '0' && c <= '9')
+ key = KSYM_0 + (Key)(c - '0');
+ }
+ else if (strPrefix(name_ptr, "XK_F") && strlen(name_ptr) <= 6)
+ {
+ char c1 = name_ptr[4];
+ char c2 = name_ptr[5];
+ int d = 0;
+
+ if ((c1 >= '0' && c1 <= '9') &&
+ ((c2 >= '0' && c1 <= '9') || c2 == '\0'))
+ d = atoi(&name_ptr[4]);
+
+ if (d >= 1 && d <= KSYM_NUM_FKEYS)
+ key = KSYM_F1 + (Key)(d - 1);
+ }
+ else if (strPrefix(name_ptr, "XK_"))
+ {
+ i = 0;
+
+ do
+ {
+ if (strEqual(name_ptr, translate_key[i].x11name))
+ {
+ key = translate_key[i].key;
+ break;
+ }
+ }
+ while (translate_key[++i].x11name);
+ }
+ else if (strPrefix(name_ptr, "0x"))
+ {
+ unsigned int value = 0;
+
+ name_ptr += 2;
+
+ while (name_ptr)
+ {
+ char c = *name_ptr++;
+ int d = -1;
+
+ if (c >= '0' && c <= '9')
+ d = (int)(c - '0');
+ else if (c >= 'a' && c <= 'f')
+ d = (int)(c - 'a' + 10);
+ else if (c >= 'A' && c <= 'F')
+ d = (int)(c - 'A' + 10);
+
+ if (d == -1)
+ {
+ value = -1;
+ break;
+ }
+
+ value = value * 16 + d;
+ }
+
+ if (value != -1)
+ key = (Key)value;
+ }
+
+ *keysym = key;
+ }
+}
+
+char *getKeyNameFromKey(Key key)
+{
+ char *name;
+
+ translate_keyname(&key, NULL, &name, TRANSLATE_KEYSYM_TO_KEYNAME);
+ return name;
+}
+
+char *getX11KeyNameFromKey(Key key)
+{
+ char *x11name;
+
+ translate_keyname(&key, &x11name, NULL, TRANSLATE_KEYSYM_TO_X11KEYNAME);
+ return x11name;
+}
+
+Key getKeyFromKeyName(char *name)
+{
+ Key key;
+
+ translate_keyname(&key, NULL, &name, TRANSLATE_KEYNAME_TO_KEYSYM);
+ return key;
+}
+
+Key getKeyFromX11KeyName(char *x11name)
+{
+ Key key;
+
+ translate_keyname(&key, &x11name, NULL, TRANSLATE_X11KEYNAME_TO_KEYSYM);
+ return key;
+}
+
+char getCharFromKey(Key key)
+{
+ static struct
+ {
+ Key key;
+ byte key_char;
+ } translate_key_char[] =
+ {
+ // special (non-ASCII) keys (ISO-8859-1)
+ { KSYM_degree, CHAR_BYTE_DEGREE },
+ { KSYM_Adiaeresis, CHAR_BYTE_UMLAUT_A },
+ { KSYM_Odiaeresis, CHAR_BYTE_UMLAUT_O },
+ { KSYM_Udiaeresis, CHAR_BYTE_UMLAUT_U },
+ { KSYM_adiaeresis, CHAR_BYTE_UMLAUT_a },
+ { KSYM_odiaeresis, CHAR_BYTE_UMLAUT_o },
+ { KSYM_udiaeresis, CHAR_BYTE_UMLAUT_u },
+ { KSYM_ssharp, CHAR_BYTE_SHARP_S },
+
+ // end-of-array identifier
+ { 0, 0 }
+ };
+
+ char *keyname = getKeyNameFromKey(key);
+ char c = 0;
+
+ if (strlen(keyname) == 1)
+ c = keyname[0];
+ else if (strEqual(keyname, "space"))
+ c = ' ';
+ else
+ {
+ int i = 0;
+
+ do
+ {
+ if (key == translate_key_char[i].key)
+ {
+ c = translate_key_char[i].key_char;
+
+ break;
+ }
+ }
+ while (translate_key_char[++i].key_char);
+ }
+
+ return c;
+}
+
+char getValidConfigValueChar(char c)
+{
+ if (c == '#' || // used to mark comments
+ c == '\\') // used to mark continued lines
+ c = 0;
+
+ return c;
+}
+
+
+// ----------------------------------------------------------------------------
+// functions to translate string identifiers to integer or boolean value
+// ----------------------------------------------------------------------------
+
+int get_integer_from_string(char *s)
+{
+ // check for the most common case first
+ if (s[0] >= '0' && s[0] <= '9')
+ return atoi(s);
+
+ char *s_lower = getStringToLower(s);
+ int result = -1;
+
+ if (strEqual(s_lower, "false") ||
+ strEqual(s_lower, "no") ||
+ strEqual(s_lower, "off"))
+ result = 0;
+ else if (strEqual(s_lower, "true") ||
+ strEqual(s_lower, "yes") ||
+ strEqual(s_lower, "on"))
+ result = 1;
+ else
+ result = atoi(s);
+
+ free(s_lower);
+
+ return result;
+}
+
+boolean get_boolean_from_string(char *s)
+{
+ char *s_lower = getStringToLower(s);
+ boolean result = FALSE;
+
+ if (strEqual(s_lower, "true") ||
+ strEqual(s_lower, "yes") ||
+ strEqual(s_lower, "on") ||
+ get_integer_from_string(s) == 1)
+ result = TRUE;
+
+ free(s_lower);
+
+ return result;
+}
+
+int get_switch3_from_string(char *s)
+{
+ char *s_lower = getStringToLower(s);
+ int result = FALSE;
+
+ if (strEqual(s_lower, "true") ||
+ strEqual(s_lower, "yes") ||
+ strEqual(s_lower, "on") ||
+ get_integer_from_string(s) == 1)
+ result = TRUE;
+ else if (strEqual(s_lower, "auto"))
+ result = AUTO;
+
+ free(s_lower);
+
+ return result;
+}
+
+int get_player_nr_from_string(char *s)
+{
+ static char *player_text[] =
+ {
+ "player_1",
+ "player_2",
+ "player_3",
+ "player_4",
+
+ NULL
+ };
+
+ char *s_lower = getStringToLower(s);
+ int result = 0;
+ int i;
+
+ for (i = 0; player_text[i] != NULL; i++)
+ if (strEqual(s_lower, player_text[i]))
+ result = i;
+
+ free(s_lower);
+
+ return result;
+}
+
+
+// ----------------------------------------------------------------------------
+// functions for generic lists
+// ----------------------------------------------------------------------------
+
+ListNode *newListNode(void)
+{
+ return checked_calloc(sizeof(ListNode));
+}
+
+void addNodeToList(ListNode **node_first, char *key, void *content)
+{
+ ListNode *node_new = newListNode();
+
+ node_new->key = getStringCopy(key);
+ node_new->content = content;
+ node_new->next = *node_first;
+
+ if (*node_first)
+ (*node_first)->prev = node_new;
+
+ *node_first = node_new;
+}
+
+void deleteNodeFromList(ListNode **node_first, char *key,
+ void (*destructor_function)(void *))
+{
+ if (node_first == NULL || *node_first == NULL)
+ return;
+
+ if (strEqual((*node_first)->key, key))
+ {
+ // after first recursion, (*node_first)->prev->next == *node_first,
+ // so *node_first would be overwritten with (*node_first)->next
+ // => use a copy of *node_first (and later of (*node_first)->next)
+ ListNode *node = *node_first;
+ ListNode *node_next = node->next;
+
+ checked_free(node->key);
+
+ if (destructor_function)
+ destructor_function(node->content);
+
+ if (node->prev)
+ node->prev->next = node->next;
+
+ if (node->next)
+ node->next->prev = node->prev;
+
+ checked_free(node);
+
+ // after removing node, set list pointer to next valid list node
+ // (this is important if the first node of the list was deleted)
+ *node_first = node_next;
+ }
+ else
+ {
+ deleteNodeFromList(&(*node_first)->next, key, destructor_function);
+ }
+}
+
+ListNode *getNodeFromKey(ListNode *node_first, char *key)
+{
+ if (node_first == NULL)
+ return NULL;
+
+ if (strEqual(node_first->key, key))
+ return node_first;
+ else
+ return getNodeFromKey(node_first->next, key);
+}