src/fileio.cpp
branchgamebalance
changeset 9913 e79cd19772dd
parent 9912 1ac8aac92385
--- a/src/fileio.cpp	Wed Jun 13 12:05:56 2007 +0000
+++ b/src/fileio.cpp	Tue Jun 19 07:21:01 2007 +0000
@@ -11,7 +11,9 @@
 #include "variables.h"
 #include "debug.h"
 #include "fios.h"
-#ifndef WIN32
+#ifdef WIN32
+#include <windows.h>
+#else
 #include <pwd.h>
 #include <unistd.h>
 #include <sys/stat.h>
@@ -28,11 +30,12 @@
 	byte *buffer, *buffer_end;          ///< position pointer in local buffer and last valid byte of buffer
 	uint32 pos;                         ///< current (system) position in file
 	FILE *cur_fh;                       ///< current file handle
+	const char *filename;               ///< current filename
 	FILE *handles[MAX_HANDLES];         ///< array of file handles we can have open
 	byte buffer_start[FIO_BUFFER_SIZE]; ///< local buffer when read from file
+	const char *filenames[MAX_HANDLES]; ///< array of filenames we (should) have open
 #if defined(LIMITED_FDS)
 	uint open_handles;                  ///< current amount of open handles
-	const char *filename[MAX_HANDLES];  ///< array of filenames we (should) have open
 	uint usage_count[MAX_HANDLES];      ///< count how many times this file has been opened
 #endif /* LIMITED_FDS */
 };
@@ -45,6 +48,11 @@
 	return _fio.pos + (_fio.buffer - _fio.buffer_start) - FIO_BUFFER_SIZE;
 }
 
+const char *FioGetFilename()
+{
+	return _fio.filename;
+}
+
 void FioSeekTo(uint32 pos, int mode)
 {
 	if (mode == SEEK_CUR) pos += FioGetPos();
@@ -76,6 +84,7 @@
 	f = _fio.handles[pos >> 24];
 	assert(f != NULL);
 	_fio.cur_fh = f;
+	_fio.filename = _fio.filenames[pos >> 24];
 	FioSeekTo(GB(pos, 0, 24), SEEK_SET);
 }
 
@@ -174,60 +183,130 @@
 
 	FioCloseFile(slot); // if file was opened before, close it
 	_fio.handles[slot] = f;
+	_fio.filenames[slot] = filename;
 #if defined(LIMITED_FDS)
-	_fio.filename[slot] = filename;
 	_fio.usage_count[slot] = 0;
 	_fio.open_handles++;
 #endif /* LIMITED_FDS */
 	FioSeekToFile(slot << 24);
 }
 
+const char *_subdirs[NUM_SUBDIRS] = {
+	"",
+	"save" PATHSEP,
+	"save" PATHSEP "autosave" PATHSEP,
+	"scenario" PATHSEP,
+	"scenario" PATHSEP "heightmap" PATHSEP,
+	"gm" PATHSEP,
+	"data" PATHSEP,
+	"lang" PATHSEP
+};
+
+const char *_searchpaths[NUM_SEARCHPATHS];
+
 /**
  * Check whether the given file exists
  * @param filename the file to try for existance
+ * @param subdir the subdirectory to look in
  * @return true if and only if the file can be opened
  */
-bool FioCheckFileExists(const char *filename)
+bool FioCheckFileExists(const char *filename, Subdirectory subdir)
 {
-	FILE *f = FioFOpenFile(filename);
+	FILE *f = FioFOpenFile(filename, "rb", subdir);
 	if (f == NULL) return false;
 
 	fclose(f);
 	return true;
 }
 
-/**
- * Opens the file with the given name
- * @param filename the file to open (in either data_dir or second_data_dir)
- * @return the opened file or NULL when it failed.
- */
-FILE *FioFOpenFile(const char *filename)
+char *FioGetFullPath(char *buf, size_t buflen, Searchpath sp, Subdirectory subdir, const char *filename)
 {
-	FILE *f;
-	char buf[MAX_PATH];
+	assert(subdir < NUM_SUBDIRS);
+	assert(sp < NUM_SEARCHPATHS);
 
-	if (filename[0] == PATHSEPCHAR || filename[1] == ':') {
-		ttd_strlcpy(buf, filename, lengthof(buf));
-	} else {
-		snprintf(buf, lengthof(buf), "%s%s", _paths.data_dir, filename);
+	snprintf(buf, buflen, "%s%s%s", _searchpaths[sp], _subdirs[subdir], filename);
+	return buf;
+}
+
+char *FioFindFullPath(char *buf, size_t buflen, Subdirectory subdir, const char *filename)
+{
+	Searchpath sp;
+	assert(subdir < NUM_SUBDIRS);
+
+	FOR_ALL_SEARCHPATHS(sp) {
+		FioGetFullPath(buf, buflen, sp, subdir, filename);
+		if (FileExists(buf)) break;
 	}
 
-	f = fopen(buf, "rb");
+	return buf;
+}
+
+char *FioAppendDirectory(char *buf, size_t buflen, Searchpath sp, Subdirectory subdir)
+{
+	assert(subdir < NUM_SUBDIRS);
+	assert(sp < NUM_SEARCHPATHS);
+
+	snprintf(buf, buflen, "%s%s", _searchpaths[sp], _subdirs[subdir]);
+	return buf;
+}
+
+char *FioGetDirectory(char *buf, size_t buflen, Subdirectory subdir)
+{
+	Searchpath sp;
+
+	/* Find and return the first valid directory */
+	FOR_ALL_SEARCHPATHS(sp) {
+		char *ret = FioAppendDirectory(buf, buflen, sp, subdir);
+		if (FileExists(buf)) return ret;
+	}
+
+	/* Could not find the directory, fall back to a base path */
+	ttd_strlcpy(buf, _personal_dir, buflen);
+
+	return buf;
+}
+
+FILE *FioFOpenFileSp(const char *filename, const char *mode, Searchpath sp, Subdirectory subdir)
+{
+#if defined(WIN32) && defined(UNICODE)
+	/* fopen is implemented as a define with ellipses for
+	 * Unicode support (prepend an L). As we are not sending
+	 * a string, but a variable, it 'renames' the variable,
+	 * so make that variable to makes it compile happily */
+	wchar_t Lmode[5];
+	MultiByteToWideChar(CP_ACP, 0, mode, -1, Lmode, lengthof(Lmode));
+#endif
+	FILE *f = NULL;
+	char buf[MAX_PATH];
+
+	if (subdir == NO_DIRECTORY) {
+		ttd_strlcpy(buf, filename, lengthof(buf));
+	} else {
+		snprintf(buf, lengthof(buf), "%s%s%s", _searchpaths[sp], _subdirs[subdir], filename);
+	}
+
+	f = fopen(buf, mode);
 #if !defined(WIN32)
 	if (f == NULL) {
-		strtolower(strrchr(buf, PATHSEPCHAR));
-		f = fopen(buf, "rb");
-
-#if defined SECOND_DATA_DIR
-		/* tries in the 2nd data directory */
-		if (f == NULL) {
-			snprintf(buf, lengthof(buf), "%s%s", _paths.second_data_dir, filename);
-			strtolower(buf + strlen(_paths.second_data_dir) - 1);
-			f = fopen(buf, "rb");
-		}
-#endif
+		strtolower(buf + strlen(_searchpaths[sp]) - 1);
+		f = fopen(buf, mode);
 	}
 #endif
+	return f;
+}
+
+/** Opens OpenTTD files somewhere in a personal or global directory */
+FILE *FioFOpenFile(const char *filename, const char *mode, Subdirectory subdir)
+{
+	FILE *f = NULL;
+	Searchpath sp;
+
+	assert(subdir < NUM_SUBDIRS || subdir == NO_DIRECTORY);
+
+	FOR_ALL_SEARCHPATHS(sp) {
+		f = FioFOpenFileSp(filename, mode, sp, subdir);
+		if (f != NULL || subdir == NO_DIRECTORY) break;
+	}
 
 	return f;
 }
@@ -292,7 +371,8 @@
 #if defined(WIN32) || defined(WINCE)
 /**
  * Determine the base (personal dir and game data dir) paths
- * @param exe the path to the executable
+ * @param exe the path from the current path to the executable
+ * @note defined in the OS related files (os2.cpp, win32.cpp, unix.cpp etc)
  */
 extern void DetermineBasePaths(const char *exe);
 #else /* defined(WIN32) || defined(WINCE) */
@@ -329,68 +409,123 @@
  */
 void DetermineBasePaths(const char *exe)
 {
-	/* Change the working directory to enable doubleclicking in UIs */
-	ChangeWorkingDirectory(exe);
-
-	_paths.game_data_dir = BuildWithFullPath(GAME_DATA_DIR);
-#if defined(SECOND_DATA_DIR)
-	_paths.second_data_dir = BuildWithFullPath(SECOND_DATA_DIR);
-#else
-	_paths.second_data_dir = NULL;
-#endif
-
-#if defined(USE_HOMEDIR)
+	char tmp[MAX_PATH];
+#ifdef WITH_PERSONAL_DIR
 	const char *homedir = getenv("HOME");
 
 	if (homedir == NULL) {
 		const struct passwd *pw = getpwuid(getuid());
-		if (pw != NULL) homedir = pw->pw_dir;
+		homedir = (pw == NULL) ? "" : pw->pw_dir;
 	}
 
-	_paths.personal_dir = str_fmt("%s" PATHSEP "%s", homedir, PERSONAL_DIR);
-	AppendPathSeparator(_paths.personal_dir, MAX_PATH);
-#else /* not defined(USE_HOMEDIR) */
-	_paths.personal_dir = BuildWithFullPath(PERSONAL_DIR);
-#endif /* defined(USE_HOMEDIR) */
+	snprintf(tmp, MAX_PATH, "%s" PATHSEP "%s", homedir, PERSONAL_DIR);
+	AppendPathSeparator(tmp, MAX_PATH);
+
+	_searchpaths[SP_PERSONAL_DIR] = strdup(tmp);
+#else
+	_searchpaths[SP_PERSONAL_DIR] = NULL;
+#endif
+	_searchpaths[SP_SHARED_DIR] = NULL;
+
+	getcwd(tmp, MAX_PATH);
+	AppendPathSeparator(tmp, MAX_PATH);
+	_searchpaths[SP_WORKING_DIR] = strdup(tmp);
+
+	/* Change the working directory to that one of the executable */
+	ChangeWorkingDirectory((char*)exe);
+	getcwd(tmp, MAX_PATH);
+	AppendPathSeparator(tmp, MAX_PATH);
+	_searchpaths[SP_BINARY_DIR] = strdup(tmp);
+
+	snprintf(tmp, MAX_PATH, "%s", GLOBAL_DATA_DIR);
+	AppendPathSeparator(tmp, MAX_PATH);
+	_searchpaths[SP_INSTALLATION_DIR] = strdup(tmp);
+#ifdef WITH_COCOA
+extern void cocoaSetApplicationBundleDir();
+	cocoaSetApplicationBundleDir();
+#else
+	_searchpaths[SP_APPLICATION_BUNDLE_DIR] = NULL;
+#endif
 }
 #endif /* defined(WIN32) || defined(WINCE) */
 
+char *_personal_dir;
+
 /**
  * Acquire the base paths (personal dir and game data dir),
  * fill all other paths (save dir, autosave dir etc) and
  * make the save and scenario directories.
- * @param exe the path to the executable
- * @todo for save_dir, autosave_dir, scenario_dir and heightmap_dir the
- *       assumption is that there is no path separator, however for gm_dir
- *       lang_dir and data_dir that assumption is made.
- *       This inconsistency should be resolved.
+ * @param exe the path from the current path to the executable
  */
 void DeterminePaths(const char *exe)
 {
 	DetermineBasePaths(exe);
 
-	_paths.save_dir      = str_fmt("%ssave" PATHSEP, _paths.personal_dir);
-	_paths.autosave_dir  = str_fmt("%s" PATHSEP "autosave" PATHSEP, _paths.save_dir);
-	_paths.scenario_dir  = str_fmt("%sscenario" PATHSEP, _paths.personal_dir);
-	_paths.heightmap_dir = str_fmt("%s" PATHSEP "heightmap" PATHSEP, _paths.scenario_dir);
-	_paths.gm_dir        = str_fmt("%sgm" PATHSEP, _paths.game_data_dir);
-	_paths.data_dir      = str_fmt("%sdata" PATHSEP, _paths.game_data_dir);
-#if defined(CUSTOM_LANG_DIR)
-	_paths.lang_dir = BuildWithFullPath(CUSTOM_LANG_DIR);
-#else
-	_paths.lang_dir = str_fmt("%slang" PATHSEP, _paths.game_data_dir);
-#endif
+	Searchpath sp;
+	FOR_ALL_SEARCHPATHS(sp) DEBUG(misc, 4, "%s added as search path", _searchpaths[sp]);
 
-	if (_config_file == NULL) {
-		_config_file = str_fmt("%sopenttd.cfg", _paths.personal_dir);
+	if (_config_file != NULL) {
+		_personal_dir = strdup(_config_file);
+		char *end = strrchr(_personal_dir , PATHSEPCHAR);
+		if (end == NULL) {
+			_personal_dir[0] = '\0';
+		} else {
+			end[1] = '\0';
+		}
+	} else {
+		char personal_dir[MAX_PATH];
+		FioFindFullPath(personal_dir, lengthof(personal_dir), BASE_DIR, "openttd.cfg");
+
+		if (FileExists(personal_dir)) {
+			char *end = strrchr(personal_dir, PATHSEPCHAR);
+			if (end != NULL) end[1] = '\0';
+			_personal_dir = strdup(personal_dir);
+			_config_file = str_fmt("%sopenttd.cfg", _personal_dir);
+		} else {
+			static const Searchpath new_openttd_cfg_order[] = {
+					SP_PERSONAL_DIR, SP_BINARY_DIR, SP_WORKING_DIR, SP_SHARED_DIR, SP_INSTALLATION_DIR
+				};
+
+			for (uint i = 0; i < lengthof(new_openttd_cfg_order); i++) {
+				if (IsValidSearchPath(new_openttd_cfg_order[i])) {
+					_personal_dir = strdup(_searchpaths[new_openttd_cfg_order[i]]);
+					_config_file = str_fmt("%sopenttd.cfg", _personal_dir);
+					break;
+				}
+			}
+		}
 	}
 
-	_highscore_file = str_fmt("%shs.dat", _paths.personal_dir);
-	_log_file = str_fmt("%sopenttd.log",  _paths.personal_dir);
+	DEBUG(misc, 3, "%s found as personal directory", _personal_dir);
 
-	/* Make (auto)save and scenario folder */
-	FioCreateDirectory(_paths.save_dir);
-	FioCreateDirectory(_paths.autosave_dir);
-	FioCreateDirectory(_paths.scenario_dir);
-	FioCreateDirectory(_paths.heightmap_dir);
+	_highscore_file = str_fmt("%shs.dat", _personal_dir);
+	_log_file = str_fmt("%sopenttd.log",  _personal_dir);
+
+	char *save_dir     = str_fmt("%s%s", _personal_dir, FioGetSubdirectory(SAVE_DIR));
+	char *autosave_dir = str_fmt("%s%s", _personal_dir, FioGetSubdirectory(AUTOSAVE_DIR));
+
+	/* Make the necessary folders */
+	FioCreateDirectory(_personal_dir);
+	FioCreateDirectory(save_dir);
+	FioCreateDirectory(autosave_dir);
+
+	free(save_dir);
+	free(autosave_dir);
 }
+
+/**
+ * Sanitizes a filename, i.e. removes all illegal characters from it.
+ * @param filename the "\0" terminated filename
+ */
+void SanitizeFilename(char *filename)
+{
+	for (; *filename != '\0'; filename++) {
+		switch (*filename) {
+			/* The following characters are not allowed in filenames
+			 * on at least one of the supported operating systems: */
+			case ':': case '\\': case '*': case '?': case '/':
+				*filename = '_';
+				break;
+		}
+	}
+}