X-Git-Url: https://git.artsoft.org/?p=rocksndiamonds.git;a=blobdiff_plain;f=src%2Flibgame%2Fmisc.c;h=93a62e07d1a55ad5bf4c2ca2627491b4ff83effb;hp=41b68190559a73a7eae920ee3e1a472000197120;hb=730deaee1a38f75d749a21cddbb562c2b339cc73;hpb=db0cf963a41d958dc11ee1d3cfb2b1f88cba7f76 diff --git a/src/libgame/misc.c b/src/libgame/misc.c index 41b68190..93a62e07 100644 --- a/src/libgame/misc.c +++ b/src/libgame/misc.c @@ -1,23 +1,23 @@ -/*********************************************************** -* Artsoft Retro-Game Library * -*----------------------------------------------------------* -* (c) 1994-2006 Artsoft Entertainment * -* Holger Schemel * -* Detmolder Strasse 189 * -* 33604 Bielefeld * -* Germany * -* e-mail: info@artsoft.org * -*----------------------------------------------------------* -* misc.c * -***********************************************************/ +// ============================================================================ +// Artsoft Retro-Game Library +// ---------------------------------------------------------------------------- +// (c) 1995-2014 by Artsoft Entertainment +// Holger Schemel +// info@artsoft.org +// http://www.artsoft.org/ +// ---------------------------------------------------------------------------- +// misc.c +// ============================================================================ #include #include #include +#include #include #include #include #include +#include #include "platform.h" @@ -38,38 +38,150 @@ /* ========================================================================= */ /* ------------------------------------------------------------------------- */ -/* platform independent wrappers for printf() et al. (newline aware) */ +/* logging functions */ /* ------------------------------------------------------------------------- */ -static void vfprintf_newline(FILE *stream, char *format, va_list ap) +#define DUPLICATE_LOG_OUT_TO_STDOUT TRUE +#define DUPLICATE_LOG_ERR_TO_STDERR TRUE + + +#if defined(PLATFORM_ANDROID) +static int android_log_prio = ANDROID_LOG_INFO; +static char *android_log_buffer = NULL; + +static void append_to_android_log_buffer(char *format, va_list ap) { - char *newline = STRING_NEWLINE; + char text_new[MAX_OUTPUT_LINESIZE]; + + // print text to temporary string + vsnprintf(text_new, MAX_OUTPUT_LINESIZE, format, ap); + + if (android_log_buffer == NULL) + { + android_log_buffer = getStringCopy(text_new); + } + else + { + char *android_log_buffer_old = android_log_buffer; - vfprintf(stream, format, ap); + // append new text to existing text + android_log_buffer = getStringCat2(android_log_buffer, text_new); - fprintf(stream, "%s", newline); + checked_free(android_log_buffer_old); + } } -static void fprintf_newline(FILE *stream, char *format, ...) +static void vprintf_log_nonewline(char *format, va_list ap) { - if (format) + // add log output to buffer until text with newline is printed + append_to_android_log_buffer(format, ap); +} + +static void vprintf_log(char *format, va_list ap) +{ + // add log output to buffer + append_to_android_log_buffer(format, ap); + + // __android_log_vprint(android_log_prio, program.program_title, format, ap); + __android_log_write(android_log_prio, program.program_title, + android_log_buffer); + + checked_free(android_log_buffer); + android_log_buffer = NULL; +} + +#else + +static void vprintf_log_nonewline(char *format, va_list ap) +{ + FILE *file = program.log_file[LOG_ERR_ID]; + +#if DUPLICATE_LOG_ERR_TO_STDERR + if (file != program.log_file_default[LOG_ERR_ID]) { - va_list ap; + va_list ap2; + va_copy(ap2, ap); - va_start(ap, format); - vfprintf_newline(stream, format, ap); - va_end(ap); + vfprintf(program.log_file_default[LOG_ERR_ID], format, ap2); + + va_end(ap2); + } +#endif + + vfprintf(file, format, ap); +} + +static void vprintf_log(char *format, va_list ap) +{ + FILE *file = program.log_file[LOG_ERR_ID]; + char *newline = STRING_NEWLINE; + +#if DUPLICATE_LOG_ERR_TO_STDERR + if (file != program.log_file_default[LOG_ERR_ID]) + { + va_list ap2; + va_copy(ap2, ap); + + vfprintf(program.log_file_default[LOG_ERR_ID], format, ap2); + fprintf(program.log_file_default[LOG_ERR_ID], "%s", newline); + + va_end(ap2); } +#endif + + vfprintf(file, format, ap); + fprintf(file, "%s", newline); +} +#endif + +static void printf_log_nonewline(char *format, ...) +{ + va_list ap; + + va_start(ap, format); + vprintf_log_nonewline(format, ap); + va_end(ap); } -void fprintf_line(FILE *stream, char *line_chars, int line_length) +static void printf_log(char *format, ...) +{ + va_list ap; + + va_start(ap, format); + vprintf_log(format, ap); + va_end(ap); +} + +static void printf_log_line(char *line_chars, int line_length) +{ + int i; + + for (i = 0; i < line_length; i++) + printf_log_nonewline("%s", line_chars); + + printf_log(""); +} + + +/* ------------------------------------------------------------------------- */ +/* platform independent wrappers for printf() et al. */ +/* ------------------------------------------------------------------------- */ + +void fprintf_line(FILE *file, char *line_chars, int line_length) { int i; for (i = 0; i < line_length; i++) - fprintf(stream, "%s", line_chars); + fprintf(file, "%s", line_chars); + + fprintf(file, "\n"); +} - fprintf_newline(stream, ""); +void fprintf_line_with_prefix(FILE *file, char *prefix, char *line_chars, + int line_length) +{ + fprintf(file, "%s", prefix); + fprintf_line(file, line_chars, line_length); } void printf_line(char *line_chars, int line_length) @@ -79,8 +191,51 @@ void printf_line(char *line_chars, int line_length) void printf_line_with_prefix(char *prefix, char *line_chars, int line_length) { - fprintf(stdout, "%s", prefix); - fprintf_line(stdout, line_chars, line_length); + fprintf_line_with_prefix(stdout, prefix, line_chars, line_length); +} + +static void vPrint(char *format, va_list ap) +{ + FILE *file = program.log_file[LOG_OUT_ID]; + +#if DUPLICATE_LOG_OUT_TO_STDOUT + if (file != program.log_file_default[LOG_OUT_ID]) + { + va_list ap2; + va_copy(ap2, ap); + + vfprintf(program.log_file_default[LOG_OUT_ID], format, ap2); + + va_end(ap2); + } +#endif + + vfprintf(file, format, ap); +} + +void Print(char *format, ...) +{ + va_list ap; + + va_start(ap, format); + vPrint(format, ap); + va_end(ap); +} + +void PrintLine(char *line_chars, int line_length) +{ + int i; + + for (i = 0; i < line_length; i++) + Print(line_chars); + + Print("\n"); +} + +void PrintLineWithPrefix(char *prefix, char *line_chars, int line_length) +{ + Print(prefix); + PrintLine(line_chars, line_length); } @@ -106,7 +261,7 @@ char *int2str(int number, int size) if (size > 20) size = 20; - if (size) + if (size > 0) { sprintf(s, " %09d", number); return &s[strlen(s) - size]; @@ -156,99 +311,62 @@ int log_2(unsigned int x) return e; } - -/* ------------------------------------------------------------------------- */ -/* counter functions */ -/* ------------------------------------------------------------------------- */ - -#if defined(PLATFORM_MSDOS) -volatile unsigned long counter = 0; - -void increment_counter() +boolean getTokenValueFromString(char *string, char **token, char **value) { - counter++; + return getTokenValueFromSetupLine(string, token, value); } -END_OF_FUNCTION(increment_counter); -#endif +/* ------------------------------------------------------------------------- */ +/* counter functions */ +/* ------------------------------------------------------------------------- */ /* maximal allowed length of a command line option */ #define MAX_OPTION_LEN 256 -#ifdef TARGET_SDL -static unsigned long mainCounter(int mode) +static unsigned int getCurrentMS() { - static unsigned long base_ms = 0; - unsigned long current_ms; - unsigned long counter_ms; - - current_ms = SDL_GetTicks(); - - /* reset base time in case of counter initializing or wrap-around */ - if (mode == INIT_COUNTER || current_ms < base_ms) - base_ms = current_ms; - - counter_ms = current_ms - base_ms; - - return counter_ms; /* return milliseconds since last init */ + return SDL_GetTicks(); } -#else /* !TARGET_SDL */ - -#if defined(PLATFORM_UNIX) -static unsigned long mainCounter(int mode) +static unsigned int mainCounter(int mode) { - static struct timeval base_time = { 0, 0 }; - struct timeval current_time; - unsigned long counter_ms; + static unsigned int base_ms = 0; + unsigned int current_ms; - gettimeofday(¤t_time, NULL); - - /* reset base time in case of counter initializing or wrap-around */ - if (mode == INIT_COUNTER || current_time.tv_sec < base_time.tv_sec) - base_time = current_time; + /* get current system milliseconds */ + current_ms = getCurrentMS(); - counter_ms = (current_time.tv_sec - base_time.tv_sec) * 1000 - + (current_time.tv_usec - base_time.tv_usec) / 1000; + /* reset base timestamp in case of counter reset or wrap-around */ + if (mode == INIT_COUNTER || current_ms < base_ms) + base_ms = current_ms; - return counter_ms; /* return milliseconds since last init */ + /* return milliseconds since last counter reset */ + return current_ms - base_ms; } -#endif /* PLATFORM_UNIX */ -#endif /* !TARGET_SDL */ void InitCounter() /* set counter back to zero */ { -#if !defined(PLATFORM_MSDOS) mainCounter(INIT_COUNTER); -#else - LOCK_VARIABLE(counter); - LOCK_FUNCTION(increment_counter); - install_int_ex(increment_counter, BPS_TO_TIMER(100)); -#endif } -unsigned long Counter() /* get milliseconds since last call of InitCounter() */ +unsigned int Counter() /* get milliseconds since last call of InitCounter() */ { -#if !defined(PLATFORM_MSDOS) return mainCounter(READ_COUNTER); -#else - return (counter * 10); -#endif } -static void sleep_milliseconds(unsigned long milliseconds_delay) +static void sleep_milliseconds(unsigned int milliseconds_delay) { boolean do_busy_waiting = (milliseconds_delay < 5 ? TRUE : FALSE); if (do_busy_waiting) { /* we want to wait only a few ms -- if we assume that we have a - kernel timer resolution of 10 ms, we would wait far to long; + kernel timer resolution of 10 ms, we would wait far too long; therefore it's better to do a short interval of busy waiting to get our sleeping time more accurate */ - unsigned long base_counter = Counter(), actual_counter = Counter(); + unsigned int base_counter = Counter(), actual_counter = Counter(); while (actual_counter < base_counter + milliseconds_delay && actual_counter >= base_counter) @@ -256,31 +374,19 @@ static void sleep_milliseconds(unsigned long milliseconds_delay) } else { -#if defined(TARGET_SDL) SDL_Delay(milliseconds_delay); -#elif defined(TARGET_ALLEGRO) - rest(milliseconds_delay); -#else - struct timeval delay; - - delay.tv_sec = milliseconds_delay / 1000; - delay.tv_usec = 1000 * (milliseconds_delay % 1000); - - if (select(0, NULL, NULL, NULL, &delay) != 0) - Error(ERR_WARN, "sleep_milliseconds(): select() failed"); -#endif } } -void Delay(unsigned long delay) /* Sleep specified number of milliseconds */ +void Delay(unsigned int delay) /* Sleep specified number of milliseconds */ { sleep_milliseconds(delay); } -boolean FrameReached(unsigned long *frame_counter_var, - unsigned long frame_delay) +boolean FrameReached(unsigned int *frame_counter_var, + unsigned int frame_delay) { - unsigned long actual_frame_counter = FrameCounter; + unsigned int actual_frame_counter = FrameCounter; if (actual_frame_counter >= *frame_counter_var && actual_frame_counter < *frame_counter_var + frame_delay) @@ -291,10 +397,10 @@ boolean FrameReached(unsigned long *frame_counter_var, return TRUE; } -boolean DelayReached(unsigned long *counter_var, - unsigned long delay) +boolean DelayReached(unsigned int *counter_var, + unsigned int delay) { - unsigned long actual_counter = Counter(); + unsigned int actual_counter = Counter(); if (actual_counter >= *counter_var && actual_counter < *counter_var + delay) @@ -305,9 +411,10 @@ boolean DelayReached(unsigned long *counter_var, return TRUE; } -void WaitUntilDelayReached(unsigned long *counter_var, unsigned long delay) +int WaitUntilDelayReached(unsigned int *counter_var, unsigned int delay) { - unsigned long actual_counter; + unsigned int actual_counter; + int skip_frames = 0; while (1) { @@ -320,7 +427,55 @@ void WaitUntilDelayReached(unsigned long *counter_var, unsigned long delay) break; } + if (*counter_var != 0 && + delay != 0 && + actual_counter >= *counter_var + delay) + { + int lag = actual_counter - (*counter_var + delay); + int delay2 = (delay + 1) / 2; + + if (lag >= delay2) + skip_frames = (lag + delay2) / delay; + } + *counter_var = actual_counter; + + return skip_frames; +} + +void SkipUntilDelayReached(unsigned int *counter_var, unsigned int delay, + int *loop_var, int last_loop_value) +{ + int skip_frames = WaitUntilDelayReached(counter_var, delay); + +#if 0 +#if DEBUG + printf("::: %d: %d ms", *loop_var, delay); + if (skip_frames) + printf(" -> SKIP %d FRAME(S) [%d ms]", skip_frames, skip_frames * delay); + printf("\n"); +#endif +#endif + + if (skip_frames == 0) + return; + + // when skipping frames, make sure to never skip the last frame, as + // this may be needed for animations to reach a defined end state; + // furthermore, we assume that this function is called at the end + // of a "for" loop, which continues by incrementing the loop variable + // by one before checking the loop condition again; therefore we have + // to check against the last loop value minus one here + + last_loop_value--; + + if (*loop_var < last_loop_value) // never skip the last frame + { + *loop_var += skip_frames; + + if (*loop_var > last_loop_value) // never skip the last frame + *loop_var = last_loop_value; + } } @@ -328,18 +483,27 @@ void WaitUntilDelayReached(unsigned long *counter_var, unsigned long delay) /* random generator functions */ /* ------------------------------------------------------------------------- */ -unsigned int init_random_number(int nr, long seed) +unsigned int init_random_number(int nr, int seed) { if (seed == NEW_RANDOMIZE) { -#if defined(TARGET_SDL) - seed = (long)SDL_GetTicks(); -#else + /* default random seed */ + seed = (int)time(NULL); // seconds since the epoch + +#if !defined(PLATFORM_WIN32) + /* add some more randomness */ struct timeval current_time; gettimeofday(¤t_time, NULL); - seed = (long)current_time.tv_usec; + + seed += (int)current_time.tv_usec; // microseconds since the epoch #endif + + /* add some more randomness */ + seed += (int)SDL_GetTicks(); // milliseconds since SDL init + + /* add some more randomness */ + seed += GetSimpleRandom(1000000); } srandom_linux_libc(nr, (unsigned int) seed); @@ -357,7 +521,7 @@ unsigned int get_random_number(int nr, int max) /* system info functions */ /* ------------------------------------------------------------------------- */ -#if !defined(PLATFORM_MSDOS) +#if !defined(PLATFORM_ANDROID) static char *get_corrected_real_name(char *real_name) { char *real_name_new = checked_malloc(MAX_USERNAME_LEN + 1); @@ -365,16 +529,16 @@ static char *get_corrected_real_name(char *real_name) char *to_ptr = real_name_new; /* copy the name string, but not more than MAX_USERNAME_LEN characters */ - while (*from_ptr && (long)(to_ptr - real_name_new) < MAX_USERNAME_LEN - 1) + while (*from_ptr && (int)(to_ptr - real_name_new) < MAX_USERNAME_LEN - 1) { /* the name field read from "passwd" file may also contain additional user information, separated by commas, which will be removed here */ if (*from_ptr == ',') break; - /* the user's real name may contain 'ß' characters (german sharp s), + /* the user's real name may contain 'german sharp s' characters, which have no equivalent in upper case letters (used by our fonts) */ - if (*from_ptr == 'ß') + if (*from_ptr == CHAR_BYTE_SHARP_S) { from_ptr++; *to_ptr++ = 's'; @@ -433,7 +597,7 @@ char *getRealName() else real_name = ANONYMOUS_NAME; } -#elif defined(PLATFORM_UNIX) +#elif defined(PLATFORM_UNIX) && !defined(PLATFORM_ANDROID) if (real_name == NULL) { struct passwd *pwd; @@ -450,6 +614,16 @@ char *getRealName() return real_name; } +time_t getFileTimestampEpochSeconds(char *filename) +{ + struct stat file_status; + + if (stat(filename, &file_status) != 0) /* cannot stat file */ + return 0; + + return file_status.st_mtime; +} + /* ------------------------------------------------------------------------- */ /* path manipulation functions */ @@ -480,6 +654,23 @@ char *getBaseName(char *filename) return getStringCopy(getBaseNamePtr(filename)); } +char *getBaseNameNoSuffix(char *filename) +{ + char *basename = getStringCopy(getBaseNamePtr(filename)); + + // remove trailing suffix (separated by dot or hyphen) + if (basename[0] != '.' && basename[0] != '-') + { + if (strchr(basename, '.') != NULL) + *strchr(basename, '.') = '\0'; + + if (strchr(basename, '-') != NULL) + *strchr(basename, '-') = '\0'; + } + + return basename; +} + char *getBasePath(char *filename) { char *basepath = getStringCopy(filename); @@ -493,6 +684,36 @@ char *getBasePath(char *filename) return basepath; } +static char *getProgramMainDataPath() +{ + char *main_data_path = getStringCopy(program.command_basepath); + +#if defined(PLATFORM_MACOSX) + static char *main_data_binary_subdir = NULL; + + if (main_data_binary_subdir == NULL) + { + main_data_binary_subdir = checked_malloc(strlen(program.program_title) + 1 + + strlen("app") + 1 + + strlen(MAC_APP_BINARY_SUBDIR) + 1); + + sprintf(main_data_binary_subdir, "%s.app/%s", + program.program_title, MAC_APP_BINARY_SUBDIR); + } + + // cut relative path to Mac OS X application binary directory from path + if (strSuffix(main_data_path, main_data_binary_subdir)) + main_data_path[strlen(main_data_path) - + strlen(main_data_binary_subdir)] = '\0'; + + // cut trailing path separator from path (but not if path is root directory) + if (strSuffix(main_data_path, "/") && !strEqual(main_data_path, "/")) + main_data_path[strlen(main_data_path) - 1] = '\0'; +#endif + + return main_data_path; +} + /* ------------------------------------------------------------------------- */ /* various string functions */ @@ -500,6 +721,9 @@ char *getBasePath(char *filename) char *getStringCat2WithSeparator(char *s1, char *s2, char *sep) { + if (s1 == NULL || s2 == NULL || sep == NULL) + return NULL; + char *complete_string = checked_malloc(strlen(s1) + strlen(sep) + strlen(s2) + 1); @@ -510,6 +734,9 @@ char *getStringCat2WithSeparator(char *s1, char *s2, char *sep) char *getStringCat3WithSeparator(char *s1, char *s2, char *s3, 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); @@ -531,15 +758,75 @@ char *getStringCat3(char *s1, char *s2, char *s3) char *getPath2(char *path1, 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(char *path1, char *path2, 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); } -char *getStringCopy(char *s) +char *getImg2(char *path1, char *path2) +{ + char *filename = getPath2(path1, path2); + + if (!fileExists(filename) && strSuffix(path2, ".png")) + { + // backward compatibility: if PNG file not found, check for PCX file + char *path2pcx = getStringCopy(path2); + + strcpy(&path2pcx[strlen(path2pcx) - 3], "pcx"); + + free(filename); + + filename = getPath2(path1, path2pcx); + + free(path2pcx); + } + + return filename; +} + +char *getImg3(char *path1, char *path2, char *path3) +{ + char *filename = getPath3(path1, path2, path3); + + if (!fileExists(filename) && strSuffix(path3, ".png")) + { + // backward compatibility: if PNG file not found, check for PCX file + char *path3pcx = getStringCopy(path3); + + strcpy(&path3pcx[strlen(path3pcx) - 3], "pcx"); + + free(filename); + + filename = getPath3(path1, path2, path3pcx); + + free(path3pcx); + } + + return filename; +} + +char *getStringCopy(const char *s) { char *s_copy; @@ -552,7 +839,33 @@ char *getStringCopy(char *s) return s_copy; } -char *getStringToLower(char *s) +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; @@ -579,34 +892,84 @@ boolean strEqual(char *s1, char *s2) strcmp(s1, s2) == 0); } +boolean strEqualN(char *s1, 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 strPrefix(char *s, 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(char *s, char *suffix) +{ + return (s == NULL && suffix == NULL ? TRUE : + s == NULL && suffix != NULL ? FALSE : + s != NULL && suffix == NULL ? FALSE : + strlen(s) < strlen(suffix) ? FALSE : + strncmp(&s[strlen(s) - strlen(suffix)], suffix, strlen(suffix)) == 0); +} + +boolean strPrefixLower(char *s, char *prefix) +{ + char *s_lower = getStringToLower(s); + boolean match = strPrefix(s_lower, prefix); + + free(s_lower); + + return match; +} + +boolean strSuffixLower(char *s, char *suffix) +{ + char *s_lower = getStringToLower(s); + boolean match = strSuffix(s_lower, suffix); + + free(s_lower); + + return match; +} + /* ------------------------------------------------------------------------- */ /* command line option handling functions */ /* ------------------------------------------------------------------------- */ -void GetOptions(char *argv[], void (*print_usage_function)(void)) +void GetOptions(int argc, char *argv[], + void (*print_usage_function)(void), + void (*print_version_function)(void)) { char *ro_base_path = RO_BASE_PATH; char *rw_base_path = RW_BASE_PATH; - char **options_left = &argv[1]; + 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]; -#if !defined(PLATFORM_MACOSX) /* if the program is configured to start from current directory (default), - determine program package directory (KDE/Konqueror does not do this by - itself and fails otherwise); on Mac OS X, the program binary is stored - in an application package directory -- do not try to use this directory - as the program data directory (Mac OS X handles this correctly anyway) */ + determine program package directory from program binary (some versions + of KDE/Konqueror and Mac OS X (especially "Mavericks") apparently do not + set the current working directory to the program package directory) */ if (strEqual(ro_base_path, ".")) - ro_base_path = program.command_basepath; + ro_base_path = getProgramMainDataPath(); if (strEqual(rw_base_path, ".")) - rw_base_path = program.command_basepath; -#endif + rw_base_path = getProgramMainDataPath(); /* initialize global program options */ options.display_name = NULL; options.server_host = NULL; options.server_port = 0; + options.ro_base_directory = ro_base_path; options.rw_base_directory = rw_base_path; options.level_directory = getPath2(ro_base_path, LEVELS_DIRECTORY); @@ -614,15 +977,22 @@ void GetOptions(char *argv[], void (*print_usage_function)(void)) options.sounds_directory = getPath2(ro_base_path, SOUNDS_DIRECTORY); options.music_directory = getPath2(ro_base_path, MUSIC_DIRECTORY); options.docs_directory = getPath2(ro_base_path, DOCS_DIRECTORY); + options.execute_command = NULL; + options.special_flags = NULL; + 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 while (*options_left) @@ -642,7 +1012,7 @@ void GetOptions(char *argv[], void (*print_usage_function)(void)) if (strEqual(option, "--")) /* stop scanning arguments */ break; - if (strncmp(option, "--", 2) == 0) /* treat '--' like '-' */ + if (strPrefix(option, "--")) /* treat '--' like '-' */ option++; option_arg = strchr(option, '='); @@ -658,7 +1028,9 @@ void GetOptions(char *argv[], void (*print_usage_function)(void)) option_len = strlen(option); if (strEqual(option, "-")) + { Error(ERR_EXIT_HELP, "unrecognized option '%s'", option); + } else if (strncmp(option, "-help", option_len) == 0) { print_usage_function(); @@ -736,13 +1108,24 @@ void GetOptions(char *argv[], void (*print_usage_function)(void)) { options.serveronly = TRUE; } + else if (strncmp(option, "-debug", option_len) == 0) + { + options.debug = TRUE; + } else if (strncmp(option, "-verbose", option_len) == 0) { options.verbose = TRUE; } - else if (strncmp(option, "-debug", option_len) == 0) + else if (strncmp(option, "-version", option_len) == 0 || + strncmp(option, "-V", option_len) == 0) { - options.debug = TRUE; + print_version_function(); + + exit(0); + } + else if (strPrefix(option, "-D")) + { + options.special_flags = getStringCopy(&option[2]); } else if (strncmp(option, "-execute", option_len) == 0) { @@ -756,6 +1139,12 @@ void GetOptions(char *argv[], void (*print_usage_function)(void)) /* when doing batch processing, always enable verbose mode (warnings) */ options.verbose = TRUE; } +#if defined(PLATFORM_MACOSX) + else if (strPrefix(option, "-psn")) + { + /* ignore process serial number when launched via GUI on Mac OS X */ + } +#endif else if (*option == '-') { Error(ERR_EXIT_HELP, "unrecognized option '%s'", option_str); @@ -782,15 +1171,17 @@ void GetOptions(char *argv[], void (*print_usage_function)(void)) /* error handling functions */ /* ------------------------------------------------------------------------- */ +#define MAX_INTERNAL_ERROR_SIZE 1024 + /* used by SetError() and GetError() to store internal error messages */ -static char internal_error[1024]; /* this is bad */ +static char internal_error[MAX_INTERNAL_ERROR_SIZE]; void SetError(char *format, ...) { va_list ap; va_start(ap, format); - vsprintf(internal_error, format, ap); + vsnprintf(internal_error, MAX_INTERNAL_ERROR_SIZE, format, ap); va_end(ap); } @@ -804,6 +1195,17 @@ void Error(int mode, char *format, ...) static boolean last_line_was_separator = FALSE; char *process_name = ""; + if (program.log_file[LOG_ERR_ID] == NULL) + return; + +#if defined(PLATFORM_ANDROID) + android_log_prio = (mode & ERR_DEBUG ? ANDROID_LOG_DEBUG : + mode & ERR_INFO ? ANDROID_LOG_INFO : + mode & ERR_WARN ? ANDROID_LOG_WARN : + mode & ERR_EXIT ? ANDROID_LOG_FATAL : + ANDROID_LOG_UNKNOWN); +#endif + /* display warnings only when running in verbose mode */ if (mode & ERR_WARN && !options.verbose) return; @@ -811,7 +1213,7 @@ void Error(int mode, char *format, ...) if (mode == ERR_INFO_LINE) { if (!last_line_was_separator) - fprintf_line(program.error_file, format, 79); + printf_log_line(format, 79); last_line_was_separator = TRUE; @@ -829,27 +1231,36 @@ void Error(int mode, char *format, ...) if (format) { - va_list ap; - - fprintf(program.error_file, "%s%s: ", program.command_basename, - process_name); +#if !defined(PLATFORM_ANDROID) + printf_log_nonewline("%s%s: ", program.command_basename, process_name); +#endif if (mode & ERR_WARN) - fprintf(program.error_file, "warning: "); + printf_log_nonewline("warning: "); + + if (mode & ERR_EXIT) + printf_log_nonewline("fatal error: "); + + va_list ap; va_start(ap, format); - vfprintf_newline(program.error_file, format, ap); + vprintf_log(format, ap); va_end(ap); + + if ((mode & ERR_EXIT) && !(mode & ERR_FROM_SERVER)) + { + va_start(ap, format); + program.exit_message_function(format, ap); + va_end(ap); + } } if (mode & ERR_HELP) - fprintf_newline(program.error_file, - "%s: Try option '--help' for more information.", - program.command_basename); + printf_log("%s: Try option '--help' for more information.", + program.command_basename); if (mode & ERR_EXIT) - fprintf_newline(program.error_file, "%s%s: aborting", - program.command_basename, process_name); + printf_log("%s%s: aborting", program.command_basename, process_name); if (mode & ERR_EXIT) { @@ -865,7 +1276,7 @@ void Error(int mode, char *format, ...) /* checked memory allocation and freeing functions */ /* ------------------------------------------------------------------------- */ -void *checked_malloc(unsigned long size) +void *checked_malloc(unsigned int size) { void *ptr; @@ -877,7 +1288,7 @@ void *checked_malloc(unsigned long size) return ptr; } -void *checked_calloc(unsigned long size) +void *checked_calloc(unsigned int size) { void *ptr; @@ -889,7 +1300,7 @@ void *checked_calloc(unsigned long size) return ptr; } -void *checked_realloc(void *ptr, unsigned long size) +void *checked_realloc(void *ptr, unsigned int size) { ptr = realloc(ptr, size); @@ -905,12 +1316,25 @@ void checked_free(void *ptr) free(ptr); } +void clear_mem(void *ptr, unsigned int size) +{ +#if defined(PLATFORM_WIN32) + /* 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 */ /* ------------------------------------------------------------------------- */ -inline void swap_numbers(int *i1, int *i2) +void swap_numbers(int *i1, int *i2) { int help = *i1; @@ -918,7 +1342,7 @@ inline void swap_numbers(int *i1, int *i2) *i2 = help; } -inline void swap_number_pairs(int *x1, int *y1, int *x2, int *y2) +void swap_number_pairs(int *x1, int *y1, int *x2, int *y2) { int help_x = *x1; int help_y = *y1; @@ -936,9 +1360,9 @@ inline void swap_number_pairs(int *x1, int *y1, int *x2, int *y2) of the (not yet written) chunk, write the correct chunk size and finally write the chunk itself */ -int getFile8BitInteger(FILE *file) +int getFile8BitInteger(File *file) { - return fgetc(file); + return getByteFromFile(file); } int putFile8BitInteger(FILE *file, int value) @@ -949,14 +1373,14 @@ int putFile8BitInteger(FILE *file, int value) return 1; } -int getFile16BitInteger(FILE *file, int byte_order) +int getFile16BitInteger(File *file, int byte_order) { if (byte_order == BYTE_ORDER_BIG_ENDIAN) - return ((fgetc(file) << 8) | - (fgetc(file) << 0)); + return ((getByteFromFile(file) << 8) | + (getByteFromFile(file) << 0)); else /* BYTE_ORDER_LITTLE_ENDIAN */ - return ((fgetc(file) << 0) | - (fgetc(file) << 8)); + return ((getByteFromFile(file) << 0) | + (getByteFromFile(file) << 8)); } int putFile16BitInteger(FILE *file, int value, int byte_order) @@ -978,18 +1402,18 @@ int putFile16BitInteger(FILE *file, int value, int byte_order) return 2; } -int getFile32BitInteger(FILE *file, int byte_order) +int getFile32BitInteger(File *file, int byte_order) { if (byte_order == BYTE_ORDER_BIG_ENDIAN) - return ((fgetc(file) << 24) | - (fgetc(file) << 16) | - (fgetc(file) << 8) | - (fgetc(file) << 0)); + return ((getByteFromFile(file) << 24) | + (getByteFromFile(file) << 16) | + (getByteFromFile(file) << 8) | + (getByteFromFile(file) << 0)); else /* BYTE_ORDER_LITTLE_ENDIAN */ - return ((fgetc(file) << 0) | - (fgetc(file) << 8) | - (fgetc(file) << 16) | - (fgetc(file) << 24)); + return ((getByteFromFile(file) << 0) | + (getByteFromFile(file) << 8) | + (getByteFromFile(file) << 16) | + (getByteFromFile(file) << 24)); } int putFile32BitInteger(FILE *file, int value, int byte_order) @@ -1015,13 +1439,14 @@ int putFile32BitInteger(FILE *file, int value, int byte_order) return 4; } -boolean getFileChunk(FILE *file, char *chunk_name, int *chunk_size, +boolean getFileChunk(File *file, char *chunk_name, int *chunk_size, int byte_order) { const int chunk_name_length = 4; /* read chunk name */ - fgets(chunk_name, chunk_name_length + 1, file); + if (getStringFromFile(file, chunk_name, chunk_name_length + 1) == NULL) + return FALSE; if (chunk_size != NULL) { @@ -1029,7 +1454,7 @@ boolean getFileChunk(FILE *file, char *chunk_name, int *chunk_size, *chunk_size = getFile32BitInteger(file, byte_order); } - return (feof(file) || ferror(file) ? FALSE : TRUE); + return (checkEndOfFile(file) ? FALSE : TRUE); } int putFileChunk(FILE *file, char *chunk_name, int chunk_size, @@ -1055,12 +1480,12 @@ int putFileChunk(FILE *file, char *chunk_name, int chunk_size, return num_bytes; } -int getFileVersion(FILE *file) +int getFileVersion(File *file) { - int version_major = fgetc(file); - int version_minor = fgetc(file); - int version_patch = fgetc(file); - int version_build = fgetc(file); + int version_major = getByteFromFile(file); + int version_minor = getByteFromFile(file); + int version_patch = getByteFromFile(file); + int version_build = getByteFromFile(file); return VERSION_IDENT(version_major, version_minor, version_patch, version_build); @@ -1084,15 +1509,15 @@ int putFileVersion(FILE *file, int version) return 4; } -void ReadBytesFromFile(FILE *file, byte *buffer, unsigned long bytes) +void ReadBytesFromFile(File *file, byte *buffer, unsigned int bytes) { int i; - for(i = 0; i < bytes && !feof(file); i++) - buffer[i] = fgetc(file); + for (i = 0; i < bytes && !checkEndOfFile(file); i++) + buffer[i] = getByteFromFile(file); } -void WriteBytesToFile(FILE *file, byte *buffer, unsigned long bytes) +void WriteBytesToFile(FILE *file, byte *buffer, unsigned int bytes) { int i; @@ -1100,13 +1525,13 @@ void WriteBytesToFile(FILE *file, byte *buffer, unsigned long bytes) fputc(buffer[i], file); } -void ReadUnusedBytesFromFile(FILE *file, unsigned long bytes) +void ReadUnusedBytesFromFile(File *file, unsigned int bytes) { - while (bytes-- && !feof(file)) - fgetc(file); + while (bytes-- && !checkEndOfFile(file)) + getByteFromFile(file); } -void WriteUnusedBytesToFile(FILE *file, unsigned long bytes) +void WriteUnusedBytesToFile(FILE *file, unsigned int bytes) { while (bytes--) fputc(0, file); @@ -1164,8 +1589,10 @@ void translate_keyname(Key *keysym, char **x11name, char **name, int mode) { KSYM_Meta_R, "XK_Meta_R", "right meta" }, { KSYM_Alt_L, "XK_Alt_L", "left alt" }, { KSYM_Alt_R, "XK_Alt_R", "right alt" }, +#if !defined(TARGET_SDL2) { KSYM_Super_L, "XK_Super_L", "left super" }, /* Win-L */ { KSYM_Super_R, "XK_Super_R", "right super" }, /* Win-R */ +#endif { KSYM_Mode_switch, "XK_Mode_switch", "mode switch" }, /* Alt-R */ { KSYM_Multi_key, "XK_Multi_key", "multi key" }, /* Ctrl-R */ @@ -1178,7 +1605,11 @@ void translate_keyname(Key *keysym, char **x11name, char **name, int mode) { KSYM_End, "XK_End", "end" }, { KSYM_Page_Up, "XK_Page_Up", "page up" }, { KSYM_Page_Down, "XK_Page_Down", "page down" }, - { KSYM_Menu, "XK_Menu", "menu" }, /* Win-Menu */ + +#if defined(TARGET_SDL2) + { KSYM_Menu, "XK_Menu", "menu" }, /* menu key */ + { KSYM_Back, "XK_Back", "back" }, /* back key */ +#endif /* ASCII 0x20 to 0x40 keys (except numbers) */ { KSYM_space, "XK_space", "space" }, @@ -1219,15 +1650,44 @@ void translate_keyname(Key *keysym, char **x11name, char **name, int mode) { KSYM_asciitilde, "XK_asciitilde", "~" }, /* special (non-ASCII) keys */ - { KSYM_degree, "XK_degree", "°" }, - { KSYM_Adiaeresis, "XK_Adiaeresis", "Ä" }, - { KSYM_Odiaeresis, "XK_Odiaeresis", "Ö" }, - { KSYM_Udiaeresis, "XK_Udiaeresis", "Ü" }, - { KSYM_adiaeresis, "XK_adiaeresis", "ä" }, - { KSYM_odiaeresis, "XK_odiaeresis", "ö" }, - { KSYM_udiaeresis, "XK_udiaeresis", "ü" }, + { 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" }, +#if defined(TARGET_SDL2) + /* 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", " " }, +#endif + +#if defined(TARGET_SDL2) + /* 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" }, +#endif + /* end-of-array identifier */ { 0, NULL, NULL } }; @@ -1245,8 +1705,10 @@ void translate_keyname(Key *keysym, char **x11name, char **name, int mode) 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)); +#if !defined(TARGET_SDL2) else if (key >= KSYM_KP_0 && key <= KSYM_KP_9) sprintf(name_buffer, "keypad %c", '0' + (char)(key - KSYM_KP_0)); +#endif 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) @@ -1282,8 +1744,10 @@ void translate_keyname(Key *keysym, char **x11name, char **name, int mode) 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)); +#if !defined(TARGET_SDL2) else if (key >= KSYM_KP_0 && key <= KSYM_KP_9) sprintf(name_buffer, "XK_KP_%c", '0' + (char)(key - KSYM_KP_0)); +#endif 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) @@ -1303,7 +1767,7 @@ void translate_keyname(Key *keysym, char **x11name, char **name, int mode) while (translate_key[++i].x11name); if (!translate_key[i].x11name) - sprintf(name_buffer, "0x%04lx", (unsigned long)key); + sprintf(name_buffer, "0x%04x", (unsigned int)key); } *x11name = name_buffer; @@ -1311,17 +1775,34 @@ void translate_keyname(Key *keysym, char **x11name, char **name, int mode) else if (mode == TRANSLATE_KEYNAME_TO_KEYSYM) { Key key = KSYM_UNDEFINED; + char *name_ptr = *name; - i = 0; - do + 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) { - if (strEqual(translate_key[i].name, *name)) + i = 0; + + do { - key = translate_key[i].key; - break; + if (strEqual(translate_key[i].name, *name)) + { + key = translate_key[i].key; + break; + } } + while (translate_key[++i].x11name); } - while (translate_key[++i].x11name); if (key == KSYM_UNDEFINED) Error(ERR_WARN, "getKeyFromKeyName(): not completely implemented"); @@ -1333,7 +1814,7 @@ void translate_keyname(Key *keysym, char **x11name, char **name, int mode) Key key = KSYM_UNDEFINED; char *name_ptr = *x11name; - if (strncmp(name_ptr, "XK_", 3) == 0 && strlen(name_ptr) == 4) + if (strPrefix(name_ptr, "XK_") && strlen(name_ptr) == 4) { char c = name_ptr[3]; @@ -1344,14 +1825,16 @@ void translate_keyname(Key *keysym, char **x11name, char **name, int mode) else if (c >= '0' && c <= '9') key = KSYM_0 + (Key)(c - '0'); } - else if (strncmp(name_ptr, "XK_KP_", 6) == 0 && strlen(name_ptr) == 7) +#if !defined(TARGET_SDL2) + else if (strPrefix(name_ptr, "XK_KP_") && strlen(name_ptr) == 7) { char c = name_ptr[6]; if (c >= '0' && c <= '9') key = KSYM_KP_0 + (Key)(c - '0'); } - else if (strncmp(name_ptr, "XK_F", 4) == 0 && strlen(name_ptr) <= 6) +#endif + else if (strPrefix(name_ptr, "XK_F") && strlen(name_ptr) <= 6) { char c1 = name_ptr[4]; char c2 = name_ptr[5]; @@ -1364,7 +1847,7 @@ void translate_keyname(Key *keysym, char **x11name, char **name, int mode) if (d >= 1 && d <= KSYM_NUM_FKEYS) key = KSYM_F1 + (Key)(d - 1); } - else if (strncmp(name_ptr, "XK_", 3) == 0) + else if (strPrefix(name_ptr, "XK_")) { i = 0; @@ -1378,9 +1861,9 @@ void translate_keyname(Key *keysym, char **x11name, char **name, int mode) } while (translate_key[++i].x11name); } - else if (strncmp(name_ptr, "0x", 2) == 0) + else if (strPrefix(name_ptr, "0x")) { - unsigned long value = 0; + unsigned int value = 0; name_ptr += 2; @@ -1447,6 +1930,26 @@ Key getKeyFromX11KeyName(char *x11name) 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; @@ -1454,6 +1957,21 @@ char getCharFromKey(Key key) 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; } @@ -1504,113 +2022,481 @@ int get_integer_from_string(char *s) if (result == -1) { - if (strEqual(s_lower, "false")) + if (strEqual(s_lower, "false") || + strEqual(s_lower, "no") || + strEqual(s_lower, "off")) result = 0; - else if (strEqual(s_lower, "true")) + else if (strEqual(s_lower, "true") || + strEqual(s_lower, "yes") || + strEqual(s_lower, "on")) result = 1; else result = atoi(s); } - free(s_lower); + 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; +} + + +/* ------------------------------------------------------------------------- */ +/* functions for generic lists */ +/* ------------------------------------------------------------------------- */ + +ListNode *newListNode() +{ + 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)) + { + checked_free((*node_first)->key); + + if (destructor_function) + destructor_function((*node_first)->content); + + if ((*node_first)->next) + (*node_first)->next->prev = (*node_first)->prev; + + checked_free(*node_first); + + *node_first = (*node_first)->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); +} + +int getNumNodes(ListNode *node_first) +{ + return (node_first ? 1 + getNumNodes(node_first->next) : 0); +} + +void dumpList(ListNode *node_first) +{ + ListNode *node = node_first; + + while (node) + { + printf("['%s' (%d)]\n", node->key, + ((struct ListNodeInfo *)node->content)->num_references); + node = node->next; + } + + printf("[%d nodes]\n", getNumNodes(node_first)); +} + + +/* ------------------------------------------------------------------------- */ +/* functions for file handling */ +/* ------------------------------------------------------------------------- */ + +File *openFile(char *filename, char *mode) +{ + File *file = checked_calloc(sizeof(File)); + + file->file = fopen(filename, mode); + + if (file->file != NULL) + { + file->filename = getStringCopy(filename); + + return file; + } + +#if defined(PLATFORM_ANDROID) + file->asset_file = SDL_RWFromFile(filename, mode); + + if (file->asset_file != NULL) + { + file->file_is_asset = TRUE; + file->filename = getStringCopy(filename); + + return file; + } +#endif + + checked_free(file); + + return NULL; +} + +int closeFile(File *file) +{ + if (file == NULL) + return -1; + + int result = 0; + +#if defined(PLATFORM_ANDROID) + if (file->asset_file) + result = SDL_RWclose(file->asset_file); +#endif + + if (file->file) + result = fclose(file->file); + + checked_free(file->filename); + checked_free(file); + + return result; +} + +int checkEndOfFile(File *file) +{ +#if defined(PLATFORM_ANDROID) + if (file->file_is_asset) + return file->end_of_file; +#endif + + return feof(file->file); +} + +size_t readFile(File *file, void *buffer, size_t item_size, size_t num_items) +{ +#if defined(PLATFORM_ANDROID) + if (file->file_is_asset) + { + if (file->end_of_file) + return 0; + + size_t num_items_read = + SDL_RWread(file->asset_file, buffer, item_size, num_items); + + if (num_items_read < num_items) + file->end_of_file = TRUE; + + return num_items_read; + } +#endif + + return fread(buffer, item_size, num_items, file->file); +} + +int seekFile(File *file, long offset, int whence) +{ +#if defined(PLATFORM_ANDROID) + if (file->file_is_asset) + { + int sdl_whence = (whence == SEEK_SET ? RW_SEEK_SET : + whence == SEEK_CUR ? RW_SEEK_CUR : + whence == SEEK_END ? RW_SEEK_END : 0); + + return (SDL_RWseek(file->asset_file, offset, sdl_whence) == -1 ? -1 : 0); + } +#endif + + return fseek(file->file, offset, whence); +} + +int getByteFromFile(File *file) +{ +#if defined(PLATFORM_ANDROID) + if (file->file_is_asset) + { + if (file->end_of_file) + return EOF; + + byte c; + size_t num_bytes_read = SDL_RWread(file->asset_file, &c, 1, 1); + + if (num_bytes_read < 1) + file->end_of_file = TRUE; + + return (file->end_of_file ? EOF : (int)c); + } +#endif + + return fgetc(file->file); +} + +char *getStringFromFile(File *file, char *line, int size) +{ +#if defined(PLATFORM_ANDROID) + if (file->file_is_asset) + { + if (file->end_of_file) + return NULL; + + char *line_ptr = line; + int num_bytes_read = 0; + + while (num_bytes_read < size - 1 && + SDL_RWread(file->asset_file, line_ptr, 1, 1) == 1 && + *line_ptr++ != '\n') + num_bytes_read++; + + *line_ptr = '\0'; + + if (strlen(line) == 0) + { + file->end_of_file = TRUE; + + return NULL; + } + + return line; + } +#endif + + return fgets(line, size, file->file); +} + + +/* ------------------------------------------------------------------------- */ +/* functions for directory handling */ +/* ------------------------------------------------------------------------- */ + +Directory *openDirectory(char *dir_name) +{ + Directory *dir = checked_calloc(sizeof(Directory)); + + dir->dir = opendir(dir_name); + + if (dir->dir != NULL) + { + dir->filename = getStringCopy(dir_name); + + return dir; + } + +#if defined(PLATFORM_ANDROID) + char *asset_toc_filename = getPath2(dir_name, ASSET_TOC_BASENAME); + + dir->asset_toc_file = SDL_RWFromFile(asset_toc_filename, MODE_READ); + + checked_free(asset_toc_filename); + + if (dir->asset_toc_file != NULL) + { + dir->directory_is_asset = TRUE; + dir->filename = getStringCopy(dir_name); + + return dir; + } +#endif + + checked_free(dir); + + return NULL; +} + +int closeDirectory(Directory *dir) +{ + if (dir == NULL) + return -1; + + int result = 0; + +#if defined(PLATFORM_ANDROID) + if (dir->asset_toc_file) + result = SDL_RWclose(dir->asset_toc_file); +#endif + + if (dir->dir) + result = closedir(dir->dir); + + if (dir->dir_entry) + freeDirectoryEntry(dir->dir_entry); + + checked_free(dir->filename); + checked_free(dir); + + return result; +} + +DirectoryEntry *readDirectory(Directory *dir) +{ + if (dir->dir_entry) + freeDirectoryEntry(dir->dir_entry); + + dir->dir_entry = NULL; + +#if defined(PLATFORM_ANDROID) + if (dir->directory_is_asset) + { + char line[MAX_LINE_LEN]; + char *line_ptr = line; + int num_bytes_read = 0; + + while (num_bytes_read < MAX_LINE_LEN - 1 && + SDL_RWread(dir->asset_toc_file, line_ptr, 1, 1) == 1 && + *line_ptr != '\n') + { + line_ptr++; + num_bytes_read++; + } + + *line_ptr = '\0'; - return result; -} + if (strlen(line) == 0) + return NULL; -boolean get_boolean_from_string(char *s) -{ - char *s_lower = getStringToLower(s); - boolean result = FALSE; + dir->dir_entry = checked_calloc(sizeof(DirectoryEntry)); - if (strEqual(s_lower, "true") || - strEqual(s_lower, "yes") || - strEqual(s_lower, "on") || - get_integer_from_string(s) == 1) - result = TRUE; + dir->dir_entry->is_directory = FALSE; + if (line[strlen(line) - 1] == '/') + { + dir->dir_entry->is_directory = TRUE; - free(s_lower); + line[strlen(line) - 1] = '\0'; + } - return result; -} + dir->dir_entry->basename = getStringCopy(line); + dir->dir_entry->filename = getPath2(dir->filename, line); + return dir->dir_entry; + } +#endif -/* ------------------------------------------------------------------------- */ -/* functions for generic lists */ -/* ------------------------------------------------------------------------- */ + struct dirent *dir_entry = readdir(dir->dir); -ListNode *newListNode() -{ - return checked_calloc(sizeof(ListNode)); -} + if (dir_entry == NULL) + return NULL; -void addNodeToList(ListNode **node_first, char *key, void *content) -{ - ListNode *node_new = newListNode(); + dir->dir_entry = checked_calloc(sizeof(DirectoryEntry)); - node_new->key = getStringCopy(key); - node_new->content = content; - node_new->next = *node_first; - *node_first = node_new; + dir->dir_entry->basename = getStringCopy(dir_entry->d_name); + dir->dir_entry->filename = getPath2(dir->filename, dir_entry->d_name); + + struct stat file_status; + + dir->dir_entry->is_directory = + (stat(dir->dir_entry->filename, &file_status) == 0 && + S_ISDIR(file_status.st_mode)); + + return dir->dir_entry; } -void deleteNodeFromList(ListNode **node_first, char *key, - void (*destructor_function)(void *)) +void freeDirectoryEntry(DirectoryEntry *dir_entry) { - if (node_first == NULL || *node_first == NULL) + if (dir_entry == NULL) return; - if (strEqual((*node_first)->key, key)) - { - checked_free((*node_first)->key); - if (destructor_function) - destructor_function((*node_first)->content); - *node_first = (*node_first)->next; - } - else - deleteNodeFromList(&(*node_first)->next, key, destructor_function); + checked_free(dir_entry->basename); + checked_free(dir_entry->filename); + checked_free(dir_entry); } -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); -} +/* ------------------------------------------------------------------------- */ +/* functions for checking files and filenames */ +/* ------------------------------------------------------------------------- */ -int getNumNodes(ListNode *node_first) +boolean directoryExists(char *dir_name) { - return (node_first ? 1 + getNumNodes(node_first->next) : 0); -} + if (dir_name == NULL) + return FALSE; -void dumpList(ListNode *node_first) -{ - ListNode *node = node_first; + struct stat file_status; + boolean success = (stat(dir_name, &file_status) == 0 && + S_ISDIR(file_status.st_mode)); - while (node) +#if defined(PLATFORM_ANDROID) + if (!success) { - printf("['%s' (%d)]\n", node->key, - ((struct ListNodeInfo *)node->content)->num_references); - node = node->next; - } + // this might be an asset directory; check by trying to open toc file + char *asset_toc_filename = getPath2(dir_name, ASSET_TOC_BASENAME); + SDL_RWops *file = SDL_RWFromFile(asset_toc_filename, MODE_READ); - printf("[%d nodes]\n", getNumNodes(node_first)); -} + checked_free(asset_toc_filename); + success = (file != NULL); -/* ------------------------------------------------------------------------- */ -/* functions for checking files and filenames */ -/* ------------------------------------------------------------------------- */ + if (success) + SDL_RWclose(file); + } +#endif + + return success; +} boolean fileExists(char *filename) { if (filename == NULL) return FALSE; - return (access(filename, F_OK) == 0); + boolean success = (access(filename, F_OK) == 0); + +#if defined(PLATFORM_ANDROID) + if (!success) + { + // this might be an asset file; check by trying to open it + SDL_RWops *file = SDL_RWFromFile(filename, MODE_READ); + + success = (file != NULL); + + if (success) + SDL_RWclose(file); + } +#endif + + return success; } boolean fileHasPrefix(char *basename, char *prefix) @@ -1657,48 +2543,37 @@ boolean fileHasSuffix(char *basename, char *suffix) return FALSE; } -boolean FileIsGraphic(char *filename) +static boolean FileCouldBeArtwork(char *filename) { char *basename = getBaseNamePtr(filename); - return fileHasSuffix(basename, "pcx"); + return (!strEqual(basename, ".") && + !strEqual(basename, "..") && + !fileHasSuffix(basename, "txt") && + !fileHasSuffix(basename, "conf") && + !directoryExists(filename)); } -boolean FileIsSound(char *filename) +boolean FileIsGraphic(char *filename) { - char *basename = getBaseNamePtr(filename); + return FileCouldBeArtwork(filename); +} - return fileHasSuffix(basename, "wav"); +boolean FileIsSound(char *filename) +{ + return FileCouldBeArtwork(filename); } boolean FileIsMusic(char *filename) { - char *basename = getBaseNamePtr(filename); - - if (FileIsSound(basename)) - return TRUE; - -#if defined(TARGET_SDL) - if (fileHasPrefix(basename, "mod") || - fileHasSuffix(basename, "mod") || - fileHasSuffix(basename, "s3m") || - fileHasSuffix(basename, "it") || - fileHasSuffix(basename, "xm") || - fileHasSuffix(basename, "midi") || - fileHasSuffix(basename, "mid") || - fileHasSuffix(basename, "mp3") || - fileHasSuffix(basename, "ogg")) - return TRUE; -#endif - - return FALSE; + return FileCouldBeArtwork(filename); } -boolean FileIsArtworkType(char *basename, int type) +boolean FileIsArtworkType(char *filename, int type) { - if ((type == TREE_TYPE_GRAPHICS_DIR && FileIsGraphic(basename)) || - (type == TREE_TYPE_SOUNDS_DIR && FileIsSound(basename)) || - (type == TREE_TYPE_MUSIC_DIR && FileIsMusic(basename))) + if ((type == TREE_TYPE_GRAPHICS_DIR && FileIsGraphic(filename)) || + (type == TREE_TYPE_SOUNDS_DIR && FileIsSound(filename)) || + (type == TREE_TYPE_MUSIC_DIR && FileIsMusic(filename))) return TRUE; return FALSE; @@ -1782,7 +2657,15 @@ int get_parameter_value(char *value_raw, char *suffix, int type) { result = (strEqual(value, "left") ? ALIGN_LEFT : strEqual(value, "right") ? ALIGN_RIGHT : - strEqual(value, "center") ? ALIGN_CENTER : ALIGN_DEFAULT); + strEqual(value, "center") ? ALIGN_CENTER : + strEqual(value, "middle") ? ALIGN_CENTER : ALIGN_DEFAULT); + } + else if (strEqual(suffix, ".valign")) + { + result = (strEqual(value, "top") ? VALIGN_TOP : + strEqual(value, "bottom") ? VALIGN_BOTTOM : + strEqual(value, "middle") ? VALIGN_MIDDLE : + strEqual(value, "center") ? VALIGN_MIDDLE : VALIGN_DEFAULT); } else if (strEqual(suffix, ".anim_mode")) { @@ -1798,8 +2681,6 @@ int get_parameter_value(char *value_raw, char *suffix, int type) string_has_parameter(value, "horizontal") ? ANIM_HORIZONTAL : string_has_parameter(value, "vertical") ? ANIM_VERTICAL : string_has_parameter(value, "centered") ? ANIM_CENTERED : - string_has_parameter(value, "fade") ? ANIM_FADE : - string_has_parameter(value, "crossfade") ? ANIM_CROSSFADE : ANIM_DEFAULT); if (string_has_parameter(value, "reverse")) @@ -1811,6 +2692,32 @@ int get_parameter_value(char *value_raw, char *suffix, int type) if (string_has_parameter(value, "static_panel")) result |= ANIM_STATIC_PANEL; } + else if (strEqual(suffix, ".class")) + { + result = get_hash_from_key(value); + } + else if (strEqual(suffix, ".style")) + { + result = STYLE_DEFAULT; + + if (string_has_parameter(value, "accurate_borders")) + result |= STYLE_ACCURATE_BORDERS; + + if (string_has_parameter(value, "inner_corners")) + result |= STYLE_INNER_CORNERS; + } + else if (strEqual(suffix, ".fade_mode")) + { + result = (string_has_parameter(value, "none") ? FADE_MODE_NONE : + string_has_parameter(value, "fade") ? FADE_MODE_FADE : + string_has_parameter(value, "crossfade") ? FADE_MODE_CROSSFADE : + string_has_parameter(value, "melt") ? FADE_MODE_MELT : + FADE_MODE_DEFAULT); + } + else if (strPrefix(suffix, ".font")) /* (may also be ".font_xyz") */ + { + result = gfx.get_font_from_token_function(value); + } else /* generic parameter of type integer or boolean */ { result = (strEqual(value, ARG_UNDEFINED) ? ARG_UNDEFINED_VALUE : @@ -1824,20 +2731,6 @@ int get_parameter_value(char *value_raw, char *suffix, int type) return result; } -int get_auto_parameter_value(char *token, char *value_raw) -{ - char *suffix; - - if (token == NULL || value_raw == NULL) - return ARG_UNDEFINED_VALUE; - - suffix = strrchr(token, '.'); - if (suffix == NULL) - suffix = token; - - return get_parameter_value(value_raw, suffix, TYPE_INTEGER); -} - struct ScreenModeInfo *get_screen_mode_from_string(char *screen_mode_string) { static struct ScreenModeInfo screen_mode; @@ -1922,14 +2815,15 @@ struct FileInfo *getFileListFromConfigList(struct ConfigInfo *config_list, file_list[i].redefined = FALSE; file_list[i].fallback_to_default = FALSE; + file_list[i].default_is_cloned = FALSE; } } list_pos = 0; + for (i = 0; config_list[i].token != NULL; i++) { int len_config_token = strlen(config_list[i].token); - int len_config_value = strlen(config_list[i].value); boolean is_file_entry = TRUE; for (j = 0; suffix_list[j].token != NULL; j++) @@ -1944,6 +2838,7 @@ struct FileInfo *getFileListFromConfigList(struct ConfigInfo *config_list, config_list[i].value); is_file_entry = FALSE; + break; } } @@ -1961,23 +2856,12 @@ struct FileInfo *getFileListFromConfigList(struct ConfigInfo *config_list, if (list_pos >= num_file_list_entries) break; - /* simple sanity check if this is really a file definition */ - if (!strEqual(&config_list[i].value[len_config_value - 4], ".pcx") && - !strEqual(&config_list[i].value[len_config_value - 4], ".wav") && - !strEqual(config_list[i].value, UNDEFINED_FILENAME)) - { - Error(ERR_INFO, "Configuration directive '%s' -> '%s':", - config_list[i].token, config_list[i].value); - Error(ERR_EXIT, "This seems to be no valid definition -- please fix"); - } - file_list[list_pos].token = config_list[i].token; file_list[list_pos].default_filename = config_list[i].value; - -#if 0 - printf("::: '%s' => '%s'\n", config_list[i].token, config_list[i].value); -#endif } + + if (strSuffix(config_list[i].token, ".clone_from")) + file_list[list_pos].default_is_cloned = TRUE; } num_file_list_entries_found = list_pos + 1; @@ -1992,10 +2876,6 @@ struct FileInfo *getFileListFromConfigList(struct ConfigInfo *config_list, Error(ERR_EXIT, "please fix"); } -#if 0 - printf("::: ---------- DONE ----------\n"); -#endif - return file_list; } @@ -2086,6 +2966,7 @@ static void add_dynamic_file_list_entry(struct FileInfo **list, new_list_entry->redefined = FALSE; new_list_entry->fallback_to_default = FALSE; + new_list_entry->default_is_cloned = FALSE; read_token_parameters(extra_file_hash, suffix_list, new_list_entry); } @@ -2136,10 +3017,6 @@ static void LoadArtworkConfigFromFilename(struct ArtworkListInfo *artwork_info, if (filename == NULL) return; -#if 0 - printf("LoadArtworkConfigFromFilename '%s' ...\n", filename); -#endif - if ((setup_file_hash = loadSetupFileHash(filename)) == NULL) return; @@ -2222,10 +3099,6 @@ static void LoadArtworkConfigFromFilename(struct ArtworkListInfo *artwork_info, boolean base_prefix_found = FALSE; boolean parameter_suffix_found = FALSE; -#if 0 - printf("::: examining '%s' -> '%s'\n", token, HASH_ITERATION_VALUE(itr)); -#endif - /* skip all parameter definitions (handled by read_token_parameters()) */ for (i = 0; i < num_suffix_list_entries && !parameter_suffix_found; i++) { @@ -2278,11 +3151,6 @@ static void LoadArtworkConfigFromFilename(struct ArtworkListInfo *artwork_info, continue; } -#if 0 - if (IS_PARENT_PROCESS()) - printf("---> examining token '%s': search 1st suffix ...\n", token); -#endif - /* ---------- step 1: search for matching first suffix ---------- */ start_pos += len_base_prefix; @@ -2321,11 +3189,6 @@ static void LoadArtworkConfigFromFilename(struct ArtworkListInfo *artwork_info, if (exact_match) break; -#if 0 - if (IS_PARENT_PROCESS()) - printf("---> examining token '%s': search 2nd suffix ...\n", token); -#endif - /* ---------- step 2: search for matching second suffix ---------- */ for (k = 0; k < num_ext2_suffixes && !ext2_suffix_found; k++) @@ -2363,11 +3226,6 @@ static void LoadArtworkConfigFromFilename(struct ArtworkListInfo *artwork_info, if (exact_match) break; -#if 0 - if (IS_PARENT_PROCESS()) - printf("---> examining token '%s': search 3rd suffix ...\n",token); -#endif - /* ---------- step 3: search for matching third suffix ---------- */ for (l = 0; l < num_ext3_suffixes && !ext3_suffix_found; l++) @@ -2485,17 +3343,6 @@ static void LoadArtworkConfigFromFilename(struct ArtworkListInfo *artwork_info, freeSetupFileHash(extra_file_hash); freeSetupFileHash(empty_file_hash); - -#if 0 - for (i = 0; i < num_file_list_entries; i++) - { - printf("'%s' ", file_list[i].token); - if (file_list[i].filename) - printf("-> '%s'\n", file_list[i].filename); - else - printf("-> UNDEFINED [-> '%s']\n", file_list[i].default_filename); - } -#endif } void LoadArtworkConfig(struct ArtworkListInfo *artwork_info) @@ -2547,7 +3394,7 @@ void LoadArtworkConfig(struct ArtworkListInfo *artwork_info) artwork_info->num_property_mapping_entries = 0; } - if (!SETUP_OVERRIDE_ARTWORK(setup, artwork_info->type)) + if (!GFX_OVERRIDE_ARTWORK(artwork_info->type)) { /* first look for special artwork configured in level series config */ filename_base = getCustomArtworkLevelConfigFilename(artwork_info->type); @@ -2598,6 +3445,22 @@ static void replaceArtworkListEntry(struct ArtworkListInfo *artwork_info, basename = file_list_entry->default_filename; + /* fail for cloned default artwork that has no default filename defined */ + if (file_list_entry->default_is_cloned && + strEqual(basename, UNDEFINED_FILENAME)) + { + int error_mode = ERR_WARN; + + /* we can get away without sounds and music, but not without graphics */ + if (*listnode == NULL && artwork_info->type == ARTWORK_TYPE_GRAPHICS) + error_mode = ERR_EXIT; + + Error(error_mode, "token '%s' was cloned and has no default filename", + file_list_entry->token); + + return; + } + /* dynamic artwork has no default filename / skip empty default artwork */ if (basename == NULL || strEqual(basename, UNDEFINED_FILENAME)) return; @@ -2629,23 +3492,15 @@ static void replaceArtworkListEntry(struct ArtworkListInfo *artwork_info, This usually means that this artwork does not exist in this artwork set and a fallback to the existing artwork is done. */ -#if 0 - printf("[artwork '%s' already exists (same list entry)]\n", filename); -#endif - return; } /* delete existing artwork file entry */ deleteArtworkListEntry(artwork_info, listnode); - /* check if the new artwork file already exists in the list of artworks */ + /* check if the new artwork file already exists in the list of artwork */ if ((node = getNodeFromKey(artwork_info->content_list, filename)) != NULL) { -#if 0 - printf("[artwork '%s' already exists (other list entry)]\n", filename); -#endif - *listnode = (struct ListNodeInfo *)node->content; (*listnode)->num_references++; @@ -2657,10 +3512,7 @@ static void replaceArtworkListEntry(struct ArtworkListInfo *artwork_info, if ((*listnode = artwork_info->load_artwork(filename)) != NULL) { -#if 0 - printf("[adding new artwork '%s']\n", filename); -#endif - + /* add new artwork file entry to the list of artwork files */ (*listnode)->num_references = 1; addNodeToList(&artwork_info->content_list, (*listnode)->source_filename, *listnode); @@ -2674,6 +3526,7 @@ static void replaceArtworkListEntry(struct ArtworkListInfo *artwork_info, error_mode = ERR_EXIT; Error(error_mode, "cannot load artwork file '%s'", basename); + return; } } @@ -2682,13 +3535,10 @@ static void LoadCustomArtwork(struct ArtworkListInfo *artwork_info, struct ListNodeInfo **listnode, struct FileInfo *file_list_entry) { -#if 0 - printf("GOT CUSTOM ARTWORK FILE '%s'\n", filename); -#endif - if (strEqual(file_list_entry->filename, UNDEFINED_FILENAME)) { deleteArtworkListEntry(artwork_info, listnode); + return; } @@ -2704,6 +3554,8 @@ void ReloadCustomArtworkList(struct ArtworkListInfo *artwork_info) artwork_info->num_dynamic_file_list_entries; int i; + print_timestamp_init("ReloadCustomArtworkList"); + for (i = 0; i < num_file_list_entries; i++) LoadCustomArtwork(artwork_info, &artwork_info->artwork_list[i], &file_list[i]); @@ -2712,6 +3564,8 @@ void ReloadCustomArtworkList(struct ArtworkListInfo *artwork_info) LoadCustomArtwork(artwork_info, &artwork_info->dynamic_artwork_list[i], &dynamic_file_list[i]); + print_timestamp_done("ReloadCustomArtworkList"); + #if 0 dumpList(artwork_info->content_list); #endif @@ -2753,37 +3607,53 @@ void FreeCustomArtworkLists(struct ArtworkListInfo *artwork_info) /* (now also added for Windows, to create files in user data directory) */ /* ------------------------------------------------------------------------- */ -char *getErrorFilename(char *basename) +char *getLogFilename(char *basename) { return getPath2(getUserGameDataDir(), basename); } -void openErrorFile() +void OpenLogFiles() { + int i; + InitUserDataDirectory(); - if ((program.error_file = fopen(program.error_filename, MODE_WRITE)) == NULL) - fprintf_newline(stderr, "ERROR: cannot open file '%s' for writing!", - program.error_filename); + for (i = 0; i < NUM_LOGS; i++) + { + if ((program.log_file[i] = fopen(program.log_filename[i], MODE_WRITE)) + == NULL) + { + program.log_file[i] = program.log_file_default[i]; // reset to default + + Error(ERR_WARN, "cannot open file '%s' for writing: %s", + program.log_filename[i], strerror(errno)); + } + + /* output should be unbuffered so it is not truncated in a crash */ + setbuf(program.log_file[i], NULL); + } } -void closeErrorFile() +void CloseLogFiles() { - if (program.error_file != stderr) /* do not close stream 'stderr' */ - fclose(program.error_file); + int i; + + for (i = 0; i < NUM_LOGS; i++) + if (program.log_file[i] != program.log_file_default[i]) + fclose(program.log_file[i]); } -void dumpErrorFile() +void DumpLogFile(int nr) { - FILE *error_file = fopen(program.error_filename, MODE_READ); + FILE *log_file = fopen(program.log_filename[nr], MODE_READ); - if (error_file != NULL) - { - while (!feof(error_file)) - fputc(fgetc(error_file), stderr); + if (log_file == NULL) + return; - fclose(error_file); - } + while (!feof(log_file)) + fputc(fgetc(log_file), stdout); + + fclose(log_file); } void NotifyUserAboutErrorFile() @@ -2792,7 +3662,8 @@ void NotifyUserAboutErrorFile() char *title_text = getStringCat2(program.program_title, " Error Message"); char *error_text = getStringCat2("The program was aborted due to an error; " "for details, see the following error file:" - STRING_NEWLINE, program.error_filename); + STRING_NEWLINE, + program.log_filename[LOG_ERR_ID]); MessageBox(NULL, error_text, title_text, MB_OK); #endif @@ -2805,22 +3676,80 @@ void NotifyUserAboutErrorFile() #if DEBUG -#define DEBUG_NUM_TIMESTAMPS 3 +#define DEBUG_PRINT_INIT_TIMESTAMPS FALSE +#define DEBUG_PRINT_INIT_TIMESTAMPS_DEPTH 10 + +#define DEBUG_NUM_TIMESTAMPS 10 +#define DEBUG_TIME_IN_MICROSECONDS 0 + +#if DEBUG_TIME_IN_MICROSECONDS +static double Counter_Microseconds() +{ + static struct timeval base_time = { 0, 0 }; + struct timeval current_time; + double counter; + + gettimeofday(¤t_time, NULL); + + /* reset base time in case of wrap-around */ + if (current_time.tv_sec < base_time.tv_sec) + base_time = current_time; + + counter = + ((double)(current_time.tv_sec - base_time.tv_sec)) * 1000000 + + ((double)(current_time.tv_usec - base_time.tv_usec)); + + return counter; /* return microseconds since last init */ +} +#endif + +char *debug_print_timestamp_get_padding(int padding_size) +{ + static char *padding = NULL; + int max_padding_size = 100; + + if (padding == NULL) + { + padding = checked_calloc(max_padding_size + 1); + memset(padding, ' ', max_padding_size); + } + + return &padding[MAX(0, max_padding_size - padding_size)]; +} void debug_print_timestamp(int counter_nr, char *message) { - static long counter[DEBUG_NUM_TIMESTAMPS][2]; + int indent_size = 8; + int padding_size = 40; + float timestamp_interval; - if (counter_nr >= DEBUG_NUM_TIMESTAMPS) + if (counter_nr < 0) + Error(ERR_EXIT, "debugging: invalid negative counter"); + else if (counter_nr >= DEBUG_NUM_TIMESTAMPS) Error(ERR_EXIT, "debugging: increase DEBUG_NUM_TIMESTAMPS in misc.c"); - counter[counter_nr][0] = Counter(); +#if DEBUG_TIME_IN_MICROSECONDS + static double counter[DEBUG_NUM_TIMESTAMPS][2]; + char *unit = "ms"; - if (message) - printf("%s %.3f seconds\n", message, - (float)(counter[counter_nr][0] - counter[counter_nr][1]) / 1000); + counter[counter_nr][0] = Counter_Microseconds(); +#else + static int counter[DEBUG_NUM_TIMESTAMPS][2]; + char *unit = "s"; + counter[counter_nr][0] = Counter(); +#endif + + timestamp_interval = counter[counter_nr][0] - counter[counter_nr][1]; counter[counter_nr][1] = counter[counter_nr][0]; + + if (message) + Error(ERR_DEBUG, "%s%s%s %.3f %s", + debug_print_timestamp_get_padding(counter_nr * indent_size), + message, + debug_print_timestamp_get_padding(padding_size - strlen(message)), + timestamp_interval / 1000, + unit); } void debug_print_parent_only(char *format, ...) @@ -2839,4 +3768,69 @@ void debug_print_parent_only(char *format, ...) printf("\n"); } } + +#endif /* DEBUG */ + +void print_timestamp_ext(char *message, char *mode) +{ +#if DEBUG_PRINT_INIT_TIMESTAMPS + static char *debug_message = NULL; + static char *last_message = NULL; + static int counter_nr = 0; + int max_depth = DEBUG_PRINT_INIT_TIMESTAMPS_DEPTH; + + checked_free(debug_message); + debug_message = getStringCat3(mode, " ", message); + + if (strEqual(mode, "INIT")) + { + debug_print_timestamp(counter_nr, NULL); + + if (counter_nr + 1 < max_depth) + debug_print_timestamp(counter_nr, debug_message); + + counter_nr++; + + debug_print_timestamp(counter_nr, NULL); + } + else if (strEqual(mode, "DONE")) + { + counter_nr--; + + if (counter_nr + 1 < max_depth || + (counter_nr == 0 && max_depth == 1)) + { + last_message = message; + + if (counter_nr == 0 && max_depth == 1) + { + checked_free(debug_message); + debug_message = getStringCat3("TIME", " ", message); + } + + debug_print_timestamp(counter_nr, debug_message); + } + } + else if (!strEqual(mode, "TIME") || + !strEqual(message, last_message)) + { + if (counter_nr < max_depth) + debug_print_timestamp(counter_nr, debug_message); + } #endif +} + +void print_timestamp_init(char *message) +{ + print_timestamp_ext(message, "INIT"); +} + +void print_timestamp_time(char *message) +{ + print_timestamp_ext(message, "TIME"); +} + +void print_timestamp_done(char *message) +{ + print_timestamp_ext(message, "DONE"); +}