+ if (strchr(basename, '-') != NULL)
+ *strchr(basename, '-') = '\0';
+ }
+
+ return basename;
+}
+
+char *getBasePath(char *filename)
+{
+ char *basepath = getStringCopy(filename);
+ char *last_separator = getLastPathSeparatorPtr(basepath);
+
+ // if no separator was found, use current directory
+ if (last_separator == NULL)
+ {
+ free(basepath);
+
+ return getStringCopy(".");
+ }
+
+ // separator found: strip basename
+ *last_separator = '\0';
+
+ return basepath;
+}
+
+
+// ----------------------------------------------------------------------------
+// various string functions
+// ----------------------------------------------------------------------------
+
+char *getStringCat2WithSeparator(const char *s1,
+ const char *s2,
+ const char *sep)
+{
+ if (s1 == NULL || s2 == NULL || sep == NULL)
+ return NULL;
+
+ char *complete_string = checked_malloc(strlen(s1) + strlen(sep) +
+ strlen(s2) + 1);
+
+ sprintf(complete_string, "%s%s%s", s1, sep, s2);
+
+ return complete_string;
+}
+
+char *getStringCat3WithSeparator(const char *s1,
+ const char *s2,
+ const char *s3,
+ const char *sep)
+{
+ if (s1 == NULL || s2 == NULL || s3 == NULL || sep == NULL)
+ return NULL;
+
+ char *complete_string = checked_malloc(strlen(s1) + strlen(sep) +
+ strlen(s2) + strlen(sep) +
+ strlen(s3) + 1);
+
+ sprintf(complete_string, "%s%s%s%s%s", s1, sep, s2, sep, s3);
+
+ return complete_string;
+}
+
+char *getStringCat2(const char *s1, const char *s2)
+{
+ return getStringCat2WithSeparator(s1, s2, "");
+}
+
+char *getStringCat3(const char *s1, const char *s2, const char *s3)
+{
+ return getStringCat3WithSeparator(s1, s2, s3, "");
+}
+
+char *getPath2(const char *path1, const char *path2)
+{
+#if defined(PLATFORM_ANDROID)
+ // workaround for reading from assets directory -- skip "." subdirs in path
+ if (strEqual(path1, "."))
+ return getStringCopy(path2);
+ else if (strEqual(path2, "."))
+ return getStringCopy(path1);
+#endif
+
+ return getStringCat2WithSeparator(path1, path2, STRING_PATH_SEPARATOR);
+}
+
+char *getPath3(const char *path1, const char *path2, const char *path3)
+{
+#if defined(PLATFORM_ANDROID)
+ // workaround for reading from assets directory -- skip "." subdirs in path
+ if (strEqual(path1, "."))
+ return getStringCat2WithSeparator(path2, path3, STRING_PATH_SEPARATOR);
+ else if (strEqual(path2, "."))
+ return getStringCat2WithSeparator(path1, path3, STRING_PATH_SEPARATOR);
+ else if (strEqual(path3, "."))
+ return getStringCat2WithSeparator(path1, path2, STRING_PATH_SEPARATOR);
+#endif
+
+ return getStringCat3WithSeparator(path1, path2, path3, STRING_PATH_SEPARATOR);
+}
+
+static char *getPngOrPcxIfNotExists(char *filename)
+{
+ // switch from PNG to PCX file and vice versa, if file does not exist
+ // (backwards compatibility with PCX files used in previous versions)
+
+ if (!fileExists(filename) && strSuffix(filename, ".png"))
+ strcpy(&filename[strlen(filename) - 3], "pcx");
+ else if (!fileExists(filename) && strSuffix(filename, ".pcx"))
+ strcpy(&filename[strlen(filename) - 3], "png");
+
+ return filename;
+}
+
+char *getImg2(const char *path1, const char *path2)
+{
+ return getPngOrPcxIfNotExists(getPath2(path1, path2));
+}
+
+char *getImg3(const char *path1, const char *path2, const char *path3)
+{
+ return getPngOrPcxIfNotExists(getPath3(path1, path2, path3));
+}
+
+char *getStringCopy(const char *s)
+{
+ char *s_copy;
+
+ if (s == NULL)
+ return NULL;
+
+ s_copy = checked_malloc(strlen(s) + 1);
+ strcpy(s_copy, s);
+
+ return s_copy;
+}
+
+char *getStringCopyN(const char *s, int n)
+{
+ char *s_copy;
+ int s_len = MAX(0, n);
+
+ if (s == NULL)
+ return NULL;
+
+ s_copy = checked_malloc(s_len + 1);
+ strncpy(s_copy, s, s_len);
+ s_copy[s_len] = '\0';
+
+ return s_copy;
+}
+
+char *getStringCopyNStatic(const char *s, int n)
+{
+ static char *s_copy = NULL;
+
+ checked_free(s_copy);
+
+ s_copy = getStringCopyN(s, n);
+
+ return s_copy;
+}
+
+char *getStringToLower(const char *s)
+{
+ char *s_copy = checked_malloc(strlen(s) + 1);
+ char *s_ptr = s_copy;
+
+ while (*s)
+ *s_ptr++ = tolower(*s++);
+ *s_ptr = '\0';
+
+ return s_copy;
+}
+
+void setString(char **old_value, const char *new_value)
+{
+ checked_free(*old_value);
+
+ *old_value = getStringCopy(new_value);
+}
+
+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
+// ----------------------------------------------------------------------------