src/fileio.cpp
branchNewGRF_ports
changeset 6720 35756db7e577
parent 6719 4cc327ad39d5
child 6743 cabfaa4a0295
equal deleted inserted replaced
6719:4cc327ad39d5 6720:35756db7e577
     9 #include "string.h"
     9 #include "string.h"
    10 #include "macros.h"
    10 #include "macros.h"
    11 #include "variables.h"
    11 #include "variables.h"
    12 #include "debug.h"
    12 #include "debug.h"
    13 #include "fios.h"
    13 #include "fios.h"
    14 #ifndef WIN32
    14 #ifdef WIN32
       
    15 #include <windows.h>
       
    16 #else
    15 #include <pwd.h>
    17 #include <pwd.h>
    16 #include <unistd.h>
    18 #include <unistd.h>
    17 #include <sys/stat.h>
    19 #include <sys/stat.h>
    18 #endif
    20 #endif
    19 
    21 
    26 
    28 
    27 struct Fio {
    29 struct Fio {
    28 	byte *buffer, *buffer_end;          ///< position pointer in local buffer and last valid byte of buffer
    30 	byte *buffer, *buffer_end;          ///< position pointer in local buffer and last valid byte of buffer
    29 	uint32 pos;                         ///< current (system) position in file
    31 	uint32 pos;                         ///< current (system) position in file
    30 	FILE *cur_fh;                       ///< current file handle
    32 	FILE *cur_fh;                       ///< current file handle
       
    33 	const char *filename;               ///< current filename
    31 	FILE *handles[MAX_HANDLES];         ///< array of file handles we can have open
    34 	FILE *handles[MAX_HANDLES];         ///< array of file handles we can have open
    32 	byte buffer_start[FIO_BUFFER_SIZE]; ///< local buffer when read from file
    35 	byte buffer_start[FIO_BUFFER_SIZE]; ///< local buffer when read from file
       
    36 	const char *filenames[MAX_HANDLES]; ///< array of filenames we (should) have open
    33 #if defined(LIMITED_FDS)
    37 #if defined(LIMITED_FDS)
    34 	uint open_handles;                  ///< current amount of open handles
    38 	uint open_handles;                  ///< current amount of open handles
    35 	const char *filename[MAX_HANDLES];  ///< array of filenames we (should) have open
       
    36 	uint usage_count[MAX_HANDLES];      ///< count how many times this file has been opened
    39 	uint usage_count[MAX_HANDLES];      ///< count how many times this file has been opened
    37 #endif /* LIMITED_FDS */
    40 #endif /* LIMITED_FDS */
    38 };
    41 };
    39 
    42 
    40 static Fio _fio;
    43 static Fio _fio;
    41 
    44 
    42 /* Get current position in file */
    45 /* Get current position in file */
    43 uint32 FioGetPos()
    46 uint32 FioGetPos()
    44 {
    47 {
    45 	return _fio.pos + (_fio.buffer - _fio.buffer_start) - FIO_BUFFER_SIZE;
    48 	return _fio.pos + (_fio.buffer - _fio.buffer_start) - FIO_BUFFER_SIZE;
       
    49 }
       
    50 
       
    51 const char *FioGetFilename()
       
    52 {
       
    53 	return _fio.filename;
    46 }
    54 }
    47 
    55 
    48 void FioSeekTo(uint32 pos, int mode)
    56 void FioSeekTo(uint32 pos, int mode)
    49 {
    57 {
    50 	if (mode == SEEK_CUR) pos += FioGetPos();
    58 	if (mode == SEEK_CUR) pos += FioGetPos();
    74 	FioRestoreFile(pos >> 24);
    82 	FioRestoreFile(pos >> 24);
    75 #endif /* LIMITED_FDS */
    83 #endif /* LIMITED_FDS */
    76 	f = _fio.handles[pos >> 24];
    84 	f = _fio.handles[pos >> 24];
    77 	assert(f != NULL);
    85 	assert(f != NULL);
    78 	_fio.cur_fh = f;
    86 	_fio.cur_fh = f;
       
    87 	_fio.filename = _fio.filenames[pos >> 24];
    79 	FioSeekTo(GB(pos, 0, 24), SEEK_SET);
    88 	FioSeekTo(GB(pos, 0, 24), SEEK_SET);
    80 }
    89 }
    81 
    90 
    82 byte FioReadByte()
    91 byte FioReadByte()
    83 {
    92 {
   172 	f = FioFOpenFile(filename);
   181 	f = FioFOpenFile(filename);
   173 	if (f == NULL) error("Cannot open file '%s'", filename);
   182 	if (f == NULL) error("Cannot open file '%s'", filename);
   174 
   183 
   175 	FioCloseFile(slot); // if file was opened before, close it
   184 	FioCloseFile(slot); // if file was opened before, close it
   176 	_fio.handles[slot] = f;
   185 	_fio.handles[slot] = f;
   177 #if defined(LIMITED_FDS)
   186 	_fio.filenames[slot] = filename;
   178 	_fio.filename[slot] = filename;
   187 #if defined(LIMITED_FDS)
   179 	_fio.usage_count[slot] = 0;
   188 	_fio.usage_count[slot] = 0;
   180 	_fio.open_handles++;
   189 	_fio.open_handles++;
   181 #endif /* LIMITED_FDS */
   190 #endif /* LIMITED_FDS */
   182 	FioSeekToFile(slot << 24);
   191 	FioSeekToFile(slot << 24);
   183 }
   192 }
   184 
   193 
       
   194 const char *_subdirs[NUM_SUBDIRS] = {
       
   195 	"",
       
   196 	"save" PATHSEP,
       
   197 	"save" PATHSEP "autosave" PATHSEP,
       
   198 	"scenario" PATHSEP,
       
   199 	"scenario" PATHSEP "heightmap" PATHSEP,
       
   200 	"gm" PATHSEP,
       
   201 	"data" PATHSEP,
       
   202 	"lang" PATHSEP
       
   203 };
       
   204 
       
   205 const char *_searchpaths[NUM_SEARCHPATHS];
       
   206 
   185 /**
   207 /**
   186  * Check whether the given file exists
   208  * Check whether the given file exists
   187  * @param filename the file to try for existance
   209  * @param filename the file to try for existance
       
   210  * @param subdir the subdirectory to look in
   188  * @return true if and only if the file can be opened
   211  * @return true if and only if the file can be opened
   189  */
   212  */
   190 bool FioCheckFileExists(const char *filename)
   213 bool FioCheckFileExists(const char *filename, Subdirectory subdir)
   191 {
   214 {
   192 	FILE *f = FioFOpenFile(filename);
   215 	FILE *f = FioFOpenFile(filename, "rb", subdir);
   193 	if (f == NULL) return false;
   216 	if (f == NULL) return false;
   194 
   217 
   195 	fclose(f);
   218 	fclose(f);
   196 	return true;
   219 	return true;
   197 }
   220 }
   198 
   221 
   199 /**
   222 char *FioGetFullPath(char *buf, size_t buflen, Searchpath sp, Subdirectory subdir, const char *filename)
   200  * Opens the file with the given name
   223 {
   201  * @param filename the file to open (in either data_dir or second_data_dir)
   224 	assert(subdir < NUM_SUBDIRS);
   202  * @return the opened file or NULL when it failed.
   225 	assert(sp < NUM_SEARCHPATHS);
   203  */
   226 
   204 FILE *FioFOpenFile(const char *filename)
   227 	snprintf(buf, buflen, "%s%s%s", _searchpaths[sp], _subdirs[subdir], filename);
   205 {
   228 	return buf;
   206 	FILE *f;
   229 }
       
   230 
       
   231 char *FioFindFullPath(char *buf, size_t buflen, Subdirectory subdir, const char *filename)
       
   232 {
       
   233 	Searchpath sp;
       
   234 	assert(subdir < NUM_SUBDIRS);
       
   235 
       
   236 	FOR_ALL_SEARCHPATHS(sp) {
       
   237 		FioGetFullPath(buf, buflen, sp, subdir, filename);
       
   238 		if (FileExists(buf)) break;
       
   239 	}
       
   240 
       
   241 	return buf;
       
   242 }
       
   243 
       
   244 char *FioAppendDirectory(char *buf, size_t buflen, Searchpath sp, Subdirectory subdir)
       
   245 {
       
   246 	assert(subdir < NUM_SUBDIRS);
       
   247 	assert(sp < NUM_SEARCHPATHS);
       
   248 
       
   249 	snprintf(buf, buflen, "%s%s", _searchpaths[sp], _subdirs[subdir]);
       
   250 	return buf;
       
   251 }
       
   252 
       
   253 char *FioGetDirectory(char *buf, size_t buflen, Subdirectory subdir)
       
   254 {
       
   255 	Searchpath sp;
       
   256 
       
   257 	/* Find and return the first valid directory */
       
   258 	FOR_ALL_SEARCHPATHS(sp) {
       
   259 		char *ret = FioAppendDirectory(buf, buflen, sp, subdir);
       
   260 		if (FileExists(buf)) return ret;
       
   261 	}
       
   262 
       
   263 	/* Could not find the directory, fall back to a base path */
       
   264 	ttd_strlcpy(buf, _personal_dir, buflen);
       
   265 
       
   266 	return buf;
       
   267 }
       
   268 
       
   269 FILE *FioFOpenFileSp(const char *filename, const char *mode, Searchpath sp, Subdirectory subdir)
       
   270 {
       
   271 #if defined(WIN32) && defined(UNICODE)
       
   272 	/* fopen is implemented as a define with ellipses for
       
   273 	 * Unicode support (prepend an L). As we are not sending
       
   274 	 * a string, but a variable, it 'renames' the variable,
       
   275 	 * so make that variable to makes it compile happily */
       
   276 	wchar_t Lmode[5];
       
   277 	MultiByteToWideChar(CP_ACP, 0, mode, -1, Lmode, lengthof(Lmode));
       
   278 #endif
       
   279 	FILE *f = NULL;
   207 	char buf[MAX_PATH];
   280 	char buf[MAX_PATH];
   208 
   281 
   209 	if (strrchr(filename, PATHSEPCHAR) == NULL) {
   282 	if (subdir == NO_DIRECTORY) {
   210 		snprintf(buf, lengthof(buf), "%s%s", _paths.data_dir, filename);
   283 		ttd_strlcpy(buf, filename, lengthof(buf));
   211 	} else {
   284 	} else {
   212 		ttd_strlcpy(buf, filename, lengthof(buf));
   285 		snprintf(buf, lengthof(buf), "%s%s%s", _searchpaths[sp], _subdirs[subdir], filename);
   213 	}
   286 	}
   214 
   287 
   215 	f = fopen(buf, "rb");
   288 	f = fopen(buf, mode);
   216 #if !defined(WIN32)
   289 #if !defined(WIN32)
   217 	if (f == NULL) {
   290 	if (f == NULL) {
   218 		strtolower(strrchr(buf, PATHSEPCHAR));
   291 		strtolower(buf + strlen(_searchpaths[sp]) - 1);
   219 		f = fopen(buf, "rb");
   292 		f = fopen(buf, mode);
   220 
   293 	}
   221 #if defined SECOND_DATA_DIR
   294 #endif
   222 		/* tries in the 2nd data directory */
   295 	return f;
   223 		if (f == NULL) {
   296 }
   224 			snprintf(buf, lengthof(buf), "%s%s", _paths.second_data_dir, filename);
   297 
   225 			strtolower(buf + strlen(_paths.second_data_dir) - 1);
   298 /** Opens OpenTTD files somewhere in a personal or global directory */
   226 			f = fopen(buf, "rb");
   299 FILE *FioFOpenFile(const char *filename, const char *mode, Subdirectory subdir)
   227 		}
   300 {
   228 #endif
   301 	FILE *f = NULL;
   229 	}
   302 	Searchpath sp;
   230 #endif
   303 
       
   304 	assert(subdir < NUM_SUBDIRS || subdir == NO_DIRECTORY);
       
   305 
       
   306 	FOR_ALL_SEARCHPATHS(sp) {
       
   307 		f = FioFOpenFileSp(filename, mode, sp, subdir);
       
   308 		if (f != NULL || subdir == NO_DIRECTORY) break;
       
   309 	}
   231 
   310 
   232 	return f;
   311 	return f;
   233 }
   312 }
   234 
   313 
   235 /**
   314 /**
   262 		buf[s]     = PATHSEPCHAR;
   341 		buf[s]     = PATHSEPCHAR;
   263 		buf[s + 1] = '\0';
   342 		buf[s + 1] = '\0';
   264 	}
   343 	}
   265 }
   344 }
   266 
   345 
       
   346 /**
       
   347  * Allocates and files a variable with the full path
       
   348  * based on the given directory.
       
   349  * @param dir the directory to base the path on
       
   350  * @return the malloced full path
       
   351  */
       
   352 char *BuildWithFullPath(const char *dir)
       
   353 {
       
   354 	char *dest = MallocT<char>(MAX_PATH);
       
   355 	ttd_strlcpy(dest, dir, MAX_PATH);
       
   356 
       
   357 	/* Check if absolute or relative path */
       
   358 	const char *s = strchr(dest, PATHSEPCHAR);
       
   359 
       
   360 	/* Add absolute path */
       
   361 	if (s == NULL || dest != s) {
       
   362 		getcwd(dest, MAX_PATH);
       
   363 		AppendPathSeparator(dest, MAX_PATH);
       
   364 		ttd_strlcat(dest, dir, MAX_PATH);
       
   365 	}
       
   366 	AppendPathSeparator(dest, MAX_PATH);
       
   367 
       
   368 	return dest;
       
   369 }
       
   370 
   267 #if defined(WIN32) || defined(WINCE)
   371 #if defined(WIN32) || defined(WINCE)
   268 /**
   372 /**
   269  * Determine the base (personal dir and game data dir) paths
   373  * Determine the base (personal dir and game data dir) paths
   270  * @param exe the path to the executable
   374  * @param exe the path from the current path to the executable
       
   375  * @note defined in the OS related files (os2.cpp, win32.cpp, unix.cpp etc)
   271  */
   376  */
   272 extern void DetermineBasePaths(const char *exe);
   377 extern void DetermineBasePaths(const char *exe);
   273 #else /* defined(WIN32) || defined(WINCE) */
   378 #else /* defined(WIN32) || defined(WINCE) */
   274 
   379 
   275 /**
   380 /**
   302  * Determine the base (personal dir and game data dir) paths
   407  * Determine the base (personal dir and game data dir) paths
   303  * @param exe the path to the executable
   408  * @param exe the path to the executable
   304  */
   409  */
   305 void DetermineBasePaths(const char *exe)
   410 void DetermineBasePaths(const char *exe)
   306 {
   411 {
   307 	/* Change the working directory to enable doubleclicking in UIs */
   412 	char tmp[MAX_PATH];
   308 	ChangeWorkingDirectory(exe);
   413 #if defined(__MORPHOS__) || defined(__AMIGA__) || !defined(WITH_PERSONAL_DIR)
   309 
   414 	_searchpaths[SP_PERSONAL_DIR] = NULL;
   310 	_paths.game_data_dir = MallocT<char>(MAX_PATH);
       
   311 	ttd_strlcpy(_paths.game_data_dir, GAME_DATA_DIR, MAX_PATH);
       
   312 #if defined(SECOND_DATA_DIR)
       
   313 	_paths.second_data_dir = MallocT<char>(MAX_PATH);
       
   314 	ttd_strlcpy(_paths.second_data_dir, SECOND_DATA_DIR, MAX_PATH);
       
   315 #else
   415 #else
   316 	_paths.second_data_dir = NULL;
       
   317 #endif
       
   318 
       
   319 #if defined(USE_HOMEDIR)
       
   320 	const char *homedir = getenv("HOME");
   416 	const char *homedir = getenv("HOME");
   321 
   417 
   322 	if (homedir == NULL) {
   418 	if (homedir == NULL) {
   323 		const struct passwd *pw = getpwuid(getuid());
   419 		const struct passwd *pw = getpwuid(getuid());
   324 		if (pw != NULL) homedir = pw->pw_dir;
   420 		homedir = (pw == NULL) ? "" : pw->pw_dir;
   325 	}
   421 	}
   326 
   422 
   327 	_paths.personal_dir = str_fmt("%s" PATHSEP "%s", homedir, PERSONAL_DIR);
   423 	snprintf(tmp, MAX_PATH, "%s" PATHSEP "%s", homedir, PERSONAL_DIR);
   328 #else /* not defined(USE_HOMEDIR) */
   424 	AppendPathSeparator(tmp, MAX_PATH);
   329 	_paths.personal_dir = MallocT<char>(MAX_PATH);
   425 
   330 	ttd_strlcpy(_paths.personal_dir, PERSONAL_DIR, MAX_PATH);
   426 	_searchpaths[SP_PERSONAL_DIR] = strdup(tmp);
   331 
   427 #endif
   332 	/* check if absolute or relative path */
   428 	_searchpaths[SP_SHARED_DIR] = NULL;
   333 	const char *s = strchr(_paths.personal_dir, PATHSEPCHAR);
   429 
   334 
   430 #if defined(__MORPHOS__) || defined(__AMIGA__)
   335 	/* add absolute path */
   431 	_searchpaths[SP_WORKING_DIR] = NULL;
   336 	if (s == NULL || _paths.personal_dir != s) {
   432 #else
   337 		getcwd(_paths.personal_dir, MAX_PATH);
   433 	getcwd(tmp, MAX_PATH);
   338 		AppendPathSeparator(_paths.personal_dir, MAX_PATH);
   434 	AppendPathSeparator(tmp, MAX_PATH);
   339 		ttd_strlcat(_paths.personal_dir, PERSONAL_DIR, MAX_PATH);
   435 	_searchpaths[SP_WORKING_DIR] = strdup(tmp);
   340 	}
   436 #endif
   341 #endif /* defined(USE_HOMEDIR) */
   437 
   342 
   438 	/* Change the working directory to that one of the executable */
   343 	AppendPathSeparator(_paths.personal_dir,  MAX_PATH);
   439 	ChangeWorkingDirectory((char*)exe);
   344 	AppendPathSeparator(_paths.game_data_dir, MAX_PATH);
   440 	getcwd(tmp, MAX_PATH);
       
   441 	AppendPathSeparator(tmp, MAX_PATH);
       
   442 	_searchpaths[SP_BINARY_DIR] = strdup(tmp);
       
   443 
       
   444 #if defined(__MORPHOS__) || defined(__AMIGA__)
       
   445 	_searchpaths[SP_INSTALLATION_DIR] = NULL;
       
   446 #else
       
   447 	snprintf(tmp, MAX_PATH, "%s", GLOBAL_DATA_DIR);
       
   448 	AppendPathSeparator(tmp, MAX_PATH);
       
   449 	_searchpaths[SP_INSTALLATION_DIR] = strdup(tmp);
       
   450 #endif
       
   451 #ifdef WITH_COCOA
       
   452 extern void cocoaSetApplicationBundleDir();
       
   453 	cocoaSetApplicationBundleDir();
       
   454 #else
       
   455 	_searchpaths[SP_APPLICATION_BUNDLE_DIR] = NULL;
       
   456 #endif
   345 }
   457 }
   346 #endif /* defined(WIN32) || defined(WINCE) */
   458 #endif /* defined(WIN32) || defined(WINCE) */
       
   459 
       
   460 char *_personal_dir;
   347 
   461 
   348 /**
   462 /**
   349  * Acquire the base paths (personal dir and game data dir),
   463  * Acquire the base paths (personal dir and game data dir),
   350  * fill all other paths (save dir, autosave dir etc) and
   464  * fill all other paths (save dir, autosave dir etc) and
   351  * make the save and scenario directories.
   465  * make the save and scenario directories.
   352  * @param exe the path to the executable
   466  * @param exe the path from the current path to the executable
   353  * @todo for save_dir, autosave_dir, scenario_dir and heightmap_dir the
       
   354  *       assumption is that there is no path separator, however for gm_dir
       
   355  *       lang_dir and data_dir that assumption is made.
       
   356  *       This inconsistency should be resolved.
       
   357  */
   467  */
   358 void DeterminePaths(const char *exe)
   468 void DeterminePaths(const char *exe)
   359 {
   469 {
   360 	DetermineBasePaths(exe);
   470 	DetermineBasePaths(exe);
   361 
   471 
   362 	_paths.save_dir      = str_fmt("%ssave" PATHSEP, _paths.personal_dir);
   472 	Searchpath sp;
   363 	_paths.autosave_dir  = str_fmt("%s" PATHSEP "autosave" PATHSEP, _paths.save_dir);
   473 	FOR_ALL_SEARCHPATHS(sp) DEBUG(misc, 4, "%s added as search path", _searchpaths[sp]);
   364 	_paths.scenario_dir  = str_fmt("%sscenario" PATHSEP, _paths.personal_dir);
   474 
   365 	_paths.heightmap_dir = str_fmt("%s" PATHSEP "heightmap" PATHSEP, _paths.scenario_dir);
   475 	if (_config_file != NULL) {
   366 	_paths.gm_dir        = str_fmt("%sgm" PATHSEP, _paths.game_data_dir);
   476 		_personal_dir = strdup(_config_file);
   367 	_paths.data_dir      = str_fmt("%sdata" PATHSEP, _paths.game_data_dir);
   477 		char *end = strrchr(_personal_dir , PATHSEPCHAR);
   368 #if defined(CUSTOM_LANG_DIR)
   478 		if (end == NULL) {
   369 	/* Sets the search path for lng files to the custom one */
   479 			_personal_dir[0] = '\0';
   370 	_paths.lang_dir = MallocT<char>(MAX_PATH);
   480 		} else {
   371 	ttd_strlcpy(_paths.lang_dir, CUSTOM_LANG_DIR, MAX_PATH);
   481 			end[1] = '\0';
   372 	AppendPathSeparator(_paths.lang_dir, MAX_PATH);
   482 		}
   373 #else
   483 	} else {
   374 	_paths.lang_dir = str_fmt("%slang" PATHSEP, _paths.game_data_dir);
   484 		char personal_dir[MAX_PATH];
   375 #endif
   485 		FioFindFullPath(personal_dir, lengthof(personal_dir), BASE_DIR, "openttd.cfg");
   376 
   486 
   377 	if (_config_file == NULL) {
   487 		if (FileExists(personal_dir)) {
   378 		_config_file = str_fmt("%sopenttd.cfg", _paths.personal_dir);
   488 			char *end = strrchr(personal_dir, PATHSEPCHAR);
   379 	}
   489 			if (end != NULL) end[1] = '\0';
   380 
   490 			_personal_dir = strdup(personal_dir);
   381 	_highscore_file = str_fmt("%shs.dat", _paths.personal_dir);
   491 			_config_file = str_fmt("%sopenttd.cfg", _personal_dir);
   382 	_log_file = str_fmt("%sopenttd.log",  _paths.personal_dir);
   492 		} else {
   383 
   493 			static const Searchpath new_openttd_cfg_order[] = {
   384 	/* Make (auto)save and scenario folder */
   494 					SP_PERSONAL_DIR, SP_BINARY_DIR, SP_WORKING_DIR, SP_SHARED_DIR, SP_INSTALLATION_DIR
   385 	FioCreateDirectory(_paths.save_dir);
   495 				};
   386 	FioCreateDirectory(_paths.autosave_dir);
   496 
   387 	FioCreateDirectory(_paths.scenario_dir);
   497 			for (uint i = 0; i < lengthof(new_openttd_cfg_order); i++) {
   388 	FioCreateDirectory(_paths.heightmap_dir);
   498 				if (IsValidSearchPath(new_openttd_cfg_order[i])) {
   389 }
   499 					_personal_dir = strdup(_searchpaths[new_openttd_cfg_order[i]]);
       
   500 					_config_file = str_fmt("%sopenttd.cfg", _personal_dir);
       
   501 					break;
       
   502 				}
       
   503 			}
       
   504 		}
       
   505 	}
       
   506 
       
   507 	DEBUG(misc, 3, "%s found as personal directory", _personal_dir);
       
   508 
       
   509 	_highscore_file = str_fmt("%shs.dat", _personal_dir);
       
   510 	_log_file = str_fmt("%sopenttd.log",  _personal_dir);
       
   511 
       
   512 	char *save_dir     = str_fmt("%s%s", _personal_dir, FioGetSubdirectory(SAVE_DIR));
       
   513 	char *autosave_dir = str_fmt("%s%s", _personal_dir, FioGetSubdirectory(AUTOSAVE_DIR));
       
   514 
       
   515 	/* Make the necessary folders */
       
   516 	FioCreateDirectory(_personal_dir);
       
   517 	FioCreateDirectory(save_dir);
       
   518 	FioCreateDirectory(autosave_dir);
       
   519 
       
   520 	free(save_dir);
       
   521 	free(autosave_dir);
       
   522 }
       
   523 
       
   524 /**
       
   525  * Sanitizes a filename, i.e. removes all illegal characters from it.
       
   526  * @param filename the "\0" terminated filename
       
   527  */
       
   528 void SanitizeFilename(char *filename)
       
   529 {
       
   530 	for (; *filename != '\0'; filename++) {
       
   531 		switch (*filename) {
       
   532 			/* The following characters are not allowed in filenames
       
   533 			 * on at least one of the supported operating systems: */
       
   534 			case ':': case '\\': case '*': case '?': case '/':
       
   535 			case '<': case '>': case '|': case '"':
       
   536 				*filename = '_';
       
   537 				break;
       
   538 		}
       
   539 	}
       
   540 }