+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
+}
+
+void *get_memcpy(const void *m, size_t size)
+{
+ void *m_copy;
+
+ if (m == NULL)
+ return NULL;
+
+ m_copy = checked_malloc(size);
+ memcpy(m_copy, m, size);
+
+ return m_copy;
+}
+
+
+// ----------------------------------------------------------------------------
+// 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)