From 347c28b71ac754aee20f8eeeae1df1a3b9a49d10 Mon Sep 17 00:00:00 2001 From: rubidium Date: Sun, 17 Jun 2007 15:48:57 +0000 Subject: [PATCH] (svn r10182) -Codechange: rewrite most part of the file loading/searching to be more flexible. -Codechange: add support for personal directories on Windows. -Fix [FS#153, FS#193, FS#502, FS#816, FS#854]: fix issues related to fixed names, fixed places of files/directories and application bundles. --- Makefile.in | 6 - config.lib | 92 +++++-------- projects/openttd.vcproj | 4 +- projects/openttd.vcproj.in | 4 +- projects/openttd_vs80.vcproj | 8 +- projects/openttd_vs80.vcproj.in | 8 +- src/console_cmds.cpp | 13 +- src/fileio.cpp | 223 ++++++++++++++++++++++---------- src/fileio.h | 72 ++++++++++- src/fios.cpp | 8 +- src/misc_gui.cpp | 12 +- src/music_gui.cpp | 23 ++-- src/network/network_client.cpp | 11 +- src/network/network_server.cpp | 8 +- src/newgrf_config.cpp | 10 +- src/openttd.cpp | 56 ++++---- src/saveload.cpp | 8 +- src/saveload.h | 4 +- src/screenshot.cpp | 24 ++-- src/stdafx.h | 6 +- src/strings.cpp | 10 +- src/variables.h | 15 --- src/video/cocoa_v.mm | 17 +++ src/video/dedicated_v.cpp | 5 +- src/win32.cpp | 54 ++++++-- 25 files changed, 427 insertions(+), 274 deletions(-) diff --git a/Makefile.in b/Makefile.in index 95c08f1f1a..dc145c9d20 100644 --- a/Makefile.in +++ b/Makefile.in @@ -228,11 +228,6 @@ bundle_dmg: bundle $(Q)mkdir -p "$(BUNDLES_DIR)" $(Q)hdiutil create -ov -format UDZO -srcfolder "$(BUNDLE_DIR)" "$(BUNDLES_DIR)/$(BUNDLE_NAME).dmg" -# TODO: ENABLE_INSTALL should be removed when the search path patch has been applied -ifeq ($(ENABLE_INSTALL), 0) -install: - @echo '[INSTALL] Cannot install. Not compiled with installation paths' -else ifdef OSXAPP install: @echo '[INSTALL] Cannot install the OSX Application Bundle' @@ -257,4 +252,3 @@ else $(Q)cp -R "$(BUNDLE_DIR)/scenario" "$(INSTALL_DATA_DIR)" endif # INSTALL_PERSONAL_DIR endif # OSXAPP -endif # ENABLE_INSTALL diff --git a/config.lib b/config.lib index c07b776033..9b115a93dc 100644 --- a/config.lib +++ b/config.lib @@ -28,11 +28,8 @@ set_default() { binary_dir="games" data_dir="share/games/openttd" icon_dir="share/pixmaps" - personal_dir="" - custom_lang_dir="" - second_data_dir="" + personal_dir="1" install_dir="/" - enable_install="0" enable_debug="0" enable_profiling="0" enable_dedicated="0" @@ -63,7 +60,7 @@ set_default() { with_fontconfig="1" with_psp_config="1" - save_params_array="build host cc_build cc_host cxx_build cxx_host windres strip awk lipo os revision endian config_log prefix_dir binary_dir data_dir icon_dir personal_dir install_dir custom_lang_dir second_data_dir enable_install enable_debug enable_profiling enable_dedicated enable_network enable_static enable_translator enable_assert enable_strip with_distcc with_ccache with_osx_sysroot enable_universal enable_osx_g5 enable_unicode with_application_bundle with_sdl with_cocoa with_zlib with_png with_makedepend with_direct_music with_sort with_iconv with_midi with_midi_arg with_libtimidity with_freetype with_fontconfig with_psp_config CC CXX CFLAGS LDFLAGS" + save_params_array="build host cc_build cc_host cxx_build cxx_host windres strip awk lipo os revision endian config_log prefix_dir binary_dir data_dir icon_dir personal_dir install_dir enable_debug enable_profiling enable_dedicated enable_network enable_static enable_translator enable_assert enable_strip with_distcc with_ccache with_osx_sysroot enable_universal enable_osx_g5 enable_unicode with_application_bundle with_sdl with_cocoa with_zlib with_png with_makedepend with_direct_music with_sort with_iconv with_midi with_midi_arg with_libtimidity with_freetype with_fontconfig with_psp_config CC CXX CFLAGS LDFLAGS" } detect_params() { @@ -131,20 +128,11 @@ detect_params() { --personal-dir) prevp_p="personal-dir";; --personal-dir=*) personal_dir="$optarg";; + --without-personal-dir) personal_dir="";; --install-dir) prevp_p="install-dir";; --install-dir=*) install_dir="$optarg";; -# TODO: The next few cases will be removed when the search path patch is applied - --custom-lang-dir) prevp_p="custom-lang-dir";; - --custom-lang-dir=*) custom_lang_dir="$optarg";; - - --second-data-dir) prevp_p="second-data-dir";; - --second-data-dir=*) second_data_dir="$optarg";; - - --enable-install) enable_install="1";; - --enable-install=*) enable_install="$optarg";; -# TODO: End of to be removed cases --enable-debug) enable_debug="1";; @@ -623,21 +611,6 @@ check_params() { if [ "$os" = "OSX" ] && [ "$with_application_bundle" = "1" ]; then OSXAPP="OpenTTD.app" - -# TODO: remove next few lines of code when the search path patch has been applied - if [ -n "$custom_lang_dir" ] && [ "$custom_lang_dir" != "${OSXAPP}/Contents/Resources/lang/" ]; then - log 1 "configure: error: --custom-lang-dir and --with-application-bundle are not compatible - exit 1 - fi - - if [ -n "$custom_lang_dir" ] && [ "$second_data_dir" != "${OSXAPP}/Contents/Resources/data/" ]; then - log 1 "configure: error: --second-data-dir and --with-application-bundle are not compatible - exit 1 - fi - - custom_lang_dir="${OSXAPP}/Contents/Resources/lang/" - second_data_dir="${OSXAPP}/Contents/Resources/data/" -# TODO: remove till here else OSXAPP="" fi @@ -720,6 +693,30 @@ check_params() { sleep 5 fi fi + + if [ "$personal_dir" == "1" ]; then + if [ "$os" = "MINGW" ] || [ "$os" = "CYGWIN" ] || [ "$os" = "WINCE" ]; then + personal_dir="OpenTTD" + elif [ "$os" = "OSX" ]; then + personal_dir="Documents/OpenTTD" + else + personal_dir=".openttd" + fi + fi + + if [ -n "$personal_dir" ] + then + log 1 "personal home directory... $personal_dir" + else + log 1 "personal home directory... none" + fi + + if [ -n "$install_dir" ] + then + log 1 "installation directory... $install_dir" + else + log 1 "installation directory... none" + fi } make_cflags_and_ldflags() { @@ -1018,29 +1015,11 @@ make_cflags_and_ldflags() { LDFLAGS="$LDFLAGS -Wl,-syslibroot,/Developer/SDKs/MacOSX$with_osx_sysroot.sdk" fi -# TODO: remove next few lines of code when the search path patch has been applied - if [ -n "$second_data_dir" ]; then - CFLAGS="$CFLAGS -DSECOND_DATA_DIR=\\\\\"$second_data_dir\\\\\"" + if [ -n "$personal_dir" ]; then + CFLAGS="$CFLAGS -DWITH_PERSONAL_DIR -DPERSONAL_DIR=\\\\\"$personal_dir\\\\\"" fi - if [ -n "$custom_lang_dir" ]; then - CFLAGS="$CFLAGS -DCUSTOM_LANG_DIR=\\\\\"$custom_lang_dir\\\\\"" - fi -# TODO: remove till here - - if [ "$enable_install" = "1" ]; then - if [ -n "$personal_dir" ]; then - CFLAGS="$CFLAGS -DUSE_HOMEDIR=1 -DPERSONAL_DIR=\\\\\"$personal_dir/\\\\\"" - fi - - if [ -n "$data_dir" ]; then - CFLAGS="$CFLAGS -DGAME_DATA_DIR=\\\\\"$prefix_dir/$data_dir/\\\\\"" - fi - - if [ -n "$icon_dir" ]; then - CFLAGS="$CFLAGS -DICON_DIR=\\\\\"$prefix_dir/$icon_dir/\\\\\"" - fi - fi + CFLAGS="$CFLAGS -DGLOBAL_DATA_DIR=\\\\\"$prefix_dir/$data_dir\\\\\"" log 1 "using CFLAGS... $CFLAGS $CC_CFLAGS" log 1 "using LDFLAGS... $LIBS $LDFLAGS" @@ -1974,7 +1953,6 @@ make_sed() { s#!!REVISION!!#$revision#g; s#!!AWK!!#$awk#g; s#!!GCC295!!#$gcc295#g; - s#!!ENABLE_INSTALL!!#$enable_install#g; s#!!DISTCC!!#$distcc#g; " } @@ -2090,18 +2068,10 @@ showhelp() { echo " [share/games/openttd]" echo " --icon-dir=dir location of icons. Will be prefixed" echo " with the prefix-dir [share/pixmaps]" - echo " --personal-dir=dir location of the personal directory []" + echo " --personal-dir=dir location of the personal directory [.openttd]" echo " --install-dir=dir specifies the root to install to." echo " Useful to install into jails [/]" echo "" -# TODO: The Following 3 tags will be removed when the 'search path patch' is applied - echo " --second-data-dir=dir specifies a second directory for the" - echo " data files" - echo " --custom-lang-dir=dir specifies a custom directory for the" - echo " language files" - echo " --enable-install make a binary that uses the specified" - echo " data-dir and icon-dir" - echo "" echo "Features and packages:" echo " --enable-debug[=LVL] enable debug-mode (LVL=[0123], 0 is release)" echo " --enable-profiling enables profiling" diff --git a/projects/openttd.vcproj b/projects/openttd.vcproj index 76d8c475c6..899464bf8c 100644 --- a/projects/openttd.vcproj +++ b/projects/openttd.vcproj @@ -30,7 +30,7 @@ OmitFramePointers="TRUE" OptimizeForProcessor="1" AdditionalIncludeDirectories="..\objs\langs" - PreprocessorDefinitions="WIN32;NDEBUG;_CONSOLE;WIN32_EXCEPTION_TRACKER;WIN32_ENABLE_DIRECTMUSIC_SUPPORT;WITH_ZLIB;WITH_PNG;WITH_FREETYPE;ENABLE_NETWORK" + PreprocessorDefinitions="WIN32;NDEBUG;_CONSOLE;WIN32_EXCEPTION_TRACKER;WIN32_ENABLE_DIRECTMUSIC_SUPPORT;WITH_ZLIB;WITH_PNG;WITH_FREETYPE;ENABLE_NETWORK;WITH_PERSONAL_DIR;PERSONAL_DIR=\"OpenTTD\"" StringPooling="TRUE" ExceptionHandling="TRUE" RuntimeLibrary="0" @@ -103,7 +103,7 @@ Name="VCCLCompilerTool" Optimization="0" AdditionalIncludeDirectories="..\objs\langs" - PreprocessorDefinitions="WIN32;_DEBUG;_CONSOLE;WIN32_ENABLE_DIRECTMUSIC_SUPPORT;WITH_ZLIB;WITH_PNG;WITH_FREETYPE;ENABLE_NETWORK" + PreprocessorDefinitions="WIN32;_DEBUG;_CONSOLE;WIN32_ENABLE_DIRECTMUSIC_SUPPORT;WITH_ZLIB;WITH_PNG;WITH_FREETYPE;ENABLE_NETWORK;WITH_PERSONAL_DIR;PERSONAL_DIR=\"OpenTTD\"" BasicRuntimeChecks="3" RuntimeLibrary="1" UsePrecompiledHeader="0" diff --git a/projects/openttd.vcproj.in b/projects/openttd.vcproj.in index 08a938d1cd..0bf01c070e 100644 --- a/projects/openttd.vcproj.in +++ b/projects/openttd.vcproj.in @@ -30,7 +30,7 @@ OmitFramePointers="TRUE" OptimizeForProcessor="1" AdditionalIncludeDirectories="..\objs\langs" - PreprocessorDefinitions="WIN32;NDEBUG;_CONSOLE;WIN32_EXCEPTION_TRACKER;WIN32_ENABLE_DIRECTMUSIC_SUPPORT;WITH_ZLIB;WITH_PNG;WITH_FREETYPE;ENABLE_NETWORK" + PreprocessorDefinitions="WIN32;NDEBUG;_CONSOLE;WIN32_EXCEPTION_TRACKER;WIN32_ENABLE_DIRECTMUSIC_SUPPORT;WITH_ZLIB;WITH_PNG;WITH_FREETYPE;ENABLE_NETWORK;WITH_PERSONAL_DIR;PERSONAL_DIR=\"OpenTTD\"" StringPooling="TRUE" ExceptionHandling="TRUE" RuntimeLibrary="0" @@ -103,7 +103,7 @@ Name="VCCLCompilerTool" Optimization="0" AdditionalIncludeDirectories="..\objs\langs" - PreprocessorDefinitions="WIN32;_DEBUG;_CONSOLE;WIN32_ENABLE_DIRECTMUSIC_SUPPORT;WITH_ZLIB;WITH_PNG;WITH_FREETYPE;ENABLE_NETWORK" + PreprocessorDefinitions="WIN32;_DEBUG;_CONSOLE;WIN32_ENABLE_DIRECTMUSIC_SUPPORT;WITH_ZLIB;WITH_PNG;WITH_FREETYPE;ENABLE_NETWORK;WITH_PERSONAL_DIR;PERSONAL_DIR=\"OpenTTD\"" BasicRuntimeChecks="3" RuntimeLibrary="1" UsePrecompiledHeader="0" diff --git a/projects/openttd_vs80.vcproj b/projects/openttd_vs80.vcproj index 397f3e6edd..93f7d12415 100644 --- a/projects/openttd_vs80.vcproj +++ b/projects/openttd_vs80.vcproj @@ -59,7 +59,7 @@ FavorSizeOrSpeed="2" OmitFramePointers="true" AdditionalIncludeDirectories="..\objs\langs" - PreprocessorDefinitions="WIN32;NDEBUG;_CONSOLE;WIN32_EXCEPTION_TRACKER;WIN32_ENABLE_DIRECTMUSIC_SUPPORT;WITH_ZLIB;WITH_PNG;WITH_FREETYPE;ENABLE_NETWORK" + PreprocessorDefinitions="WIN32;NDEBUG;_CONSOLE;WIN32_EXCEPTION_TRACKER;WIN32_ENABLE_DIRECTMUSIC_SUPPORT;WITH_ZLIB;WITH_PNG;WITH_FREETYPE;ENABLE_NETWORK;WITH_PERSONAL_DIR;PERSONAL_DIR=\"OpenTTD\"" StringPooling="true" ExceptionHandling="1" RuntimeLibrary="0" @@ -171,7 +171,7 @@ FavorSizeOrSpeed="2" OmitFramePointers="true" AdditionalIncludeDirectories="..\objs\langs" - PreprocessorDefinitions="WIN32;NDEBUG;_CONSOLE;WIN32_EXCEPTION_TRACKER;WIN32_ENABLE_DIRECTMUSIC_SUPPORT;WITH_ZLIB;WITH_PNG;WITH_FREETYPE;ENABLE_NETWORK" + PreprocessorDefinitions="WIN32;NDEBUG;_CONSOLE;WIN32_EXCEPTION_TRACKER;WIN32_ENABLE_DIRECTMUSIC_SUPPORT;WITH_ZLIB;WITH_PNG;WITH_FREETYPE;ENABLE_NETWORK;WITH_PERSONAL_DIR;PERSONAL_DIR=\"OpenTTD\"" StringPooling="true" ExceptionHandling="1" RuntimeLibrary="0" @@ -277,7 +277,7 @@ Name="VCCLCompilerTool" Optimization="0" AdditionalIncludeDirectories="..\objs\langs" - PreprocessorDefinitions="WIN32;_DEBUG;_CONSOLE;WIN32_ENABLE_DIRECTMUSIC_SUPPORT;WITH_ZLIB;WITH_PNG;WITH_FREETYPE;ENABLE_NETWORK" + PreprocessorDefinitions="WIN32;_DEBUG;_CONSOLE;WIN32_ENABLE_DIRECTMUSIC_SUPPORT;WITH_ZLIB;WITH_PNG;WITH_FREETYPE;ENABLE_NETWORK;WITH_PERSONAL_DIR;PERSONAL_DIR=\"OpenTTD\"" BasicRuntimeChecks="3" RuntimeLibrary="1" UsePrecompiledHeader="0" @@ -373,7 +373,7 @@ Name="VCCLCompilerTool" Optimization="0" AdditionalIncludeDirectories="..\objs\langs" - PreprocessorDefinitions="WIN32;_DEBUG;_CONSOLE;WIN32_ENABLE_DIRECTMUSIC_SUPPORT;WITH_ZLIB;WITH_PNG;WITH_FREETYPE;ENABLE_NETWORK" + PreprocessorDefinitions="WIN32;_DEBUG;_CONSOLE;WIN32_ENABLE_DIRECTMUSIC_SUPPORT;WITH_ZLIB;WITH_PNG;WITH_FREETYPE;ENABLE_NETWORK;WITH_PERSONAL_DIR;PERSONAL_DIR=\"OpenTTD\"" BasicRuntimeChecks="3" RuntimeLibrary="1" UsePrecompiledHeader="0" diff --git a/projects/openttd_vs80.vcproj.in b/projects/openttd_vs80.vcproj.in index 59e0b0438b..b5ae42e968 100644 --- a/projects/openttd_vs80.vcproj.in +++ b/projects/openttd_vs80.vcproj.in @@ -59,7 +59,7 @@ FavorSizeOrSpeed="2" OmitFramePointers="true" AdditionalIncludeDirectories="..\objs\langs" - PreprocessorDefinitions="WIN32;NDEBUG;_CONSOLE;WIN32_EXCEPTION_TRACKER;WIN32_ENABLE_DIRECTMUSIC_SUPPORT;WITH_ZLIB;WITH_PNG;WITH_FREETYPE;ENABLE_NETWORK" + PreprocessorDefinitions="WIN32;NDEBUG;_CONSOLE;WIN32_EXCEPTION_TRACKER;WIN32_ENABLE_DIRECTMUSIC_SUPPORT;WITH_ZLIB;WITH_PNG;WITH_FREETYPE;ENABLE_NETWORK;WITH_PERSONAL_DIR;PERSONAL_DIR=\"OpenTTD\"" StringPooling="true" ExceptionHandling="1" RuntimeLibrary="0" @@ -171,7 +171,7 @@ FavorSizeOrSpeed="2" OmitFramePointers="true" AdditionalIncludeDirectories="..\objs\langs" - PreprocessorDefinitions="WIN32;NDEBUG;_CONSOLE;WIN32_EXCEPTION_TRACKER;WIN32_ENABLE_DIRECTMUSIC_SUPPORT;WITH_ZLIB;WITH_PNG;WITH_FREETYPE;ENABLE_NETWORK" + PreprocessorDefinitions="WIN32;NDEBUG;_CONSOLE;WIN32_EXCEPTION_TRACKER;WIN32_ENABLE_DIRECTMUSIC_SUPPORT;WITH_ZLIB;WITH_PNG;WITH_FREETYPE;ENABLE_NETWORK;WITH_PERSONAL_DIR;PERSONAL_DIR=\"OpenTTD\"" StringPooling="true" ExceptionHandling="1" RuntimeLibrary="0" @@ -277,7 +277,7 @@ Name="VCCLCompilerTool" Optimization="0" AdditionalIncludeDirectories="..\objs\langs" - PreprocessorDefinitions="WIN32;_DEBUG;_CONSOLE;WIN32_ENABLE_DIRECTMUSIC_SUPPORT;WITH_ZLIB;WITH_PNG;WITH_FREETYPE;ENABLE_NETWORK" + PreprocessorDefinitions="WIN32;_DEBUG;_CONSOLE;WIN32_ENABLE_DIRECTMUSIC_SUPPORT;WITH_ZLIB;WITH_PNG;WITH_FREETYPE;ENABLE_NETWORK;WITH_PERSONAL_DIR;PERSONAL_DIR=\"OpenTTD\"" BasicRuntimeChecks="3" RuntimeLibrary="1" UsePrecompiledHeader="0" @@ -373,7 +373,7 @@ Name="VCCLCompilerTool" Optimization="0" AdditionalIncludeDirectories="..\objs\langs" - PreprocessorDefinitions="WIN32;_DEBUG;_CONSOLE;WIN32_ENABLE_DIRECTMUSIC_SUPPORT;WITH_ZLIB;WITH_PNG;WITH_FREETYPE;ENABLE_NETWORK" + PreprocessorDefinitions="WIN32;_DEBUG;_CONSOLE;WIN32_ENABLE_DIRECTMUSIC_SUPPORT;WITH_ZLIB;WITH_PNG;WITH_FREETYPE;ENABLE_NETWORK;WITH_PERSONAL_DIR;PERSONAL_DIR=\"OpenTTD\"" BasicRuntimeChecks="3" RuntimeLibrary="1" UsePrecompiledHeader="0" diff --git a/src/console_cmds.cpp b/src/console_cmds.cpp index 0fe85c86dd..5f3f320b62 100644 --- a/src/console_cmds.cpp +++ b/src/console_cmds.cpp @@ -19,6 +19,7 @@ #include "command.h" #include "settings.h" #include "fios.h" +#include "fileio.h" #include "vehicle.h" #include "station.h" #include "strings.h" @@ -173,7 +174,6 @@ DEF_CONSOLE_CMD(ConScrollToTile) return false; } -extern bool SafeSaveOrLoad(const char *filename, int mode, int newgm); extern void BuildFileList(); extern void SetFiosType(const byte fiostype); @@ -186,16 +186,15 @@ DEF_CONSOLE_CMD(ConSave) } if (argc == 2) { - char buf[200]; - - snprintf(buf, lengthof(buf), "%s%s%s.sav", _paths.save_dir, PATHSEP, argv[1]); + char *filename = str_fmt("%s.sav", argv[1]); IConsolePrint(_icolour_def, "Saving map..."); - if (SaveOrLoad(buf, SL_SAVE) != SL_OK) { - IConsolePrint(_icolour_err, "SaveMap failed"); + if (SaveOrLoad(filename, SL_SAVE, SAVE_DIR) != SL_OK) { + IConsolePrint(_icolour_err, "Saving map failed"); } else { - IConsolePrintF(_icolour_def, "Map sucessfully saved to %s", buf); + IConsolePrintF(_icolour_def, "Map sucessfully saved to %s", filename); } + free(filename); return true; } diff --git a/src/fileio.cpp b/src/fileio.cpp index f8173ab866..e79f549840 100644 --- a/src/fileio.cpp +++ b/src/fileio.cpp @@ -11,7 +11,9 @@ #include "variables.h" #include "debug.h" #include "fios.h" -#ifndef WIN32 +#ifdef WIN32 +#include +#else #include #include #include @@ -189,52 +191,122 @@ void FioOpenFile(int slot, const char *filename) 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; + assert(subdir < NUM_SUBDIRS); + assert(sp < NUM_SEARCHPATHS); + + snprintf(buf, buflen, "%s%s" PATHSEP "%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; + } + + 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 (filename[0] == PATHSEPCHAR || filename[1] == ':') { + if (subdir == BASE_DIR) { ttd_strlcpy(buf, filename, lengthof(buf)); } else { - snprintf(buf, lengthof(buf), "%s%s", _paths.data_dir, filename); + snprintf(buf, lengthof(buf), "%s%s%s", _searchpaths[sp], _subdirs[subdir], filename); } - f = fopen(buf, "rb"); + 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); + + FOR_ALL_SEARCHPATHS(sp) { + f = FioFOpenFileSp(filename, mode, sp, subdir); + if (f != NULL || subdir == 0) break; + } return f; } @@ -299,7 +371,8 @@ char *BuildWithFullPath(const char *dir) #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) */ @@ -336,70 +409,86 @@ void ChangeWorkingDirectory(const char *exe) */ 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); + /* Search for the first search path, as that will be the closest to + * the personal directory. */ + FOR_ALL_SEARCHPATHS(sp) { + _personal_dir = strdup(_searchpaths[sp]); + DEBUG(misc, 3, "%s found as personal directory", _searchpaths[sp]); + break; } - _highscore_file = str_fmt("%shs.dat", _paths.personal_dir); - _log_file = str_fmt("%sopenttd.log", _paths.personal_dir); + if (_config_file == NULL) { + _config_file = str_fmt("%sopenttd.cfg", _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); } /** diff --git a/src/fileio.h b/src/fileio.h index 97a62249f1..40d7a95b7a 100644 --- a/src/fileio.h +++ b/src/fileio.h @@ -5,6 +5,8 @@ #ifndef FILEIO_H #define FILEIO_H +#include "helpers.hpp" + void FioSeekTo(uint32 pos, int mode); void FioSeekToFile(uint32 pos); uint32 FioGetPos(); @@ -16,13 +18,77 @@ void FioCloseAll(); void FioOpenFile(int slot, const char *filename); void FioReadBlock(void *ptr, uint size); void FioSkipBytes(int n); - -FILE *FioFOpenFile(const char *filename); -bool FioCheckFileExists(const char *filename); void FioCreateDirectory(const char *filename); +/** + * The different kinds of subdirectories OpenTTD uses + */ +enum Subdirectory { + BASE_DIR, ///< Base directory for all subdirectories + SAVE_DIR, ///< Base directory for all savegames + AUTOSAVE_DIR, ///< Subdirectory of save for autosaves + SCENARIO_DIR, ///< Base directory for all scenarios + HEIGHTMAP_DIR, ///< Subdirectory of scenario for heightmaps + GM_DIR, ///< Subdirectory for all music + DATA_DIR, ///< Subdirectory for all data (GRFs, sample.cat, intro game) + LANG_DIR, ///< Subdirectory for all translation files + NUM_SUBDIRS, ///< Number of subdirectories +}; + +/** + * Types of searchpaths OpenTTD might use + */ +enum Searchpath { + SP_PERSONAL_DIR, ///< Search in the personal directory + SP_SHARED_DIR, ///< Search in the shared directory, like 'Shared Files' under Windows + SP_WORKING_DIR, ///< Search in the working directory + SP_BINARY_DIR, ///< Search in the directory where the binary resides + SP_INSTALLATION_DIR, ///< Search in the installation directory + SP_APPLICATION_BUNDLE_DIR, ///< Search within the application bundle + NUM_SEARCHPATHS +}; + +DECLARE_POSTFIX_INCREMENT(Searchpath); + +/** + * The searchpaths OpenTTD could search through. + * At least one of the slots has to be filled with a path. + * NULL paths tell that there is no such path for the + * current operating system. + */ +extern const char *_searchpaths[NUM_SEARCHPATHS]; + +/** + * Checks whether the given search path is a valid search path + * @param sp the search path to check + * @return true if the search path is valid + */ +static inline bool IsValidSearchPath(Searchpath sp) +{ + return sp < NUM_SEARCHPATHS && _searchpaths[sp] != NULL; +} + +/** Iterator for all the search paths */ +#define FOR_ALL_SEARCHPATHS(sp) for (sp = SP_PERSONAL_DIR; sp < NUM_SEARCHPATHS; sp++) if (IsValidSearchPath(sp)) + +FILE *FioFOpenFile(const char *filename, const char *mode = "rb", Subdirectory subdir = DATA_DIR); +bool FioCheckFileExists(const char *filename, Subdirectory subdir = DATA_DIR); +char *FioGetFullPath(char *buf, size_t buflen, Searchpath sp, Subdirectory subdir, const char *filename); +char *FioFindFullPath(char *buf, size_t buflen, Subdirectory subdir, const char *filename); +char *FioAppendDirectory(char *buf, size_t buflen, Searchpath sp, Subdirectory subdir); +char *FioGetDirectory(char *buf, size_t buflen, Subdirectory subdir); + +static inline const char *FioGetSubdirectory(Subdirectory subdir) +{ + extern const char *_subdirs[NUM_SUBDIRS]; + assert(subdir < NUM_SUBDIRS); + return _subdirs[subdir]; +} + void SanitizeFilename(char *filename); void AppendPathSeparator(char *buf, size_t buflen); void DeterminePaths(const char *exe); +extern char *_personal_dir; ///< custom directory for personal settings, saves, newgrf, etc. + #endif /* FILEIO_H */ diff --git a/src/fios.cpp b/src/fios.cpp index cdde6e3cdf..bec0475c24 100644 --- a/src/fios.cpp +++ b/src/fios.cpp @@ -14,6 +14,7 @@ #include "helpers.hpp" #include "table/strings.h" #include "fios.h" +#include "fileio.h" #include #include @@ -334,7 +335,7 @@ FiosItem *FiosGetSavegameList(int mode) if (_fios_save_path == NULL) { _fios_save_path = MallocT(MAX_PATH); - ttd_strlcpy(_fios_save_path, _paths.save_dir, MAX_PATH); + FioGetDirectory(_fios_save_path, MAX_PATH, SAVE_DIR); } _fios_path = _fios_save_path; @@ -380,9 +381,10 @@ FiosItem *FiosGetScenarioList(int mode) { static char *_fios_scn_path = NULL; + /* Copy the default path on first run or on 'New Game' */ if (_fios_scn_path == NULL) { _fios_scn_path = MallocT(MAX_PATH); - ttd_strlcpy(_fios_scn_path, _paths.scenario_dir, MAX_PATH); + FioGetDirectory(_fios_scn_path, MAX_PATH, SCENARIO_DIR); } _fios_path = _fios_scn_path; @@ -413,7 +415,7 @@ FiosItem *FiosGetHeightmapList(int mode) if (_fios_hmap_path == NULL) { _fios_hmap_path = MallocT(MAX_PATH); - strcpy(_fios_hmap_path, _paths.heightmap_dir); + FioGetDirectory(_fios_hmap_path, MAX_PATH, HEIGHTMAP_DIR); } _fios_path = _fios_hmap_path; diff --git a/src/misc_gui.cpp b/src/misc_gui.cpp index 276542cf99..f9bde39bb8 100644 --- a/src/misc_gui.cpp +++ b/src/misc_gui.cpp @@ -37,6 +37,7 @@ #include "player_face.h" #include "fileio.h" +#include "fileio.h" #include "fios.h" /* Variables to display file lists */ FiosItem *_fios_list; @@ -1407,28 +1408,27 @@ static void SaveLoadDlgWndProc(Window *w, WindowEvent *e) static FiosItem o_dir; switch (e->event) { - case WE_CREATE: { // Set up OPENTTD button + case WE_CREATE: // Set up OPENTTD button o_dir.type = FIOS_TYPE_DIRECT; switch (_saveload_mode) { case SLD_SAVE_GAME: case SLD_LOAD_GAME: - ttd_strlcpy(&o_dir.name[0], _paths.save_dir, sizeof(o_dir.name)); + FioGetDirectory(o_dir.name, lengthof(o_dir.name), SAVE_DIR); break; case SLD_SAVE_SCENARIO: case SLD_LOAD_SCENARIO: - ttd_strlcpy(&o_dir.name[0], _paths.scenario_dir, sizeof(o_dir.name)); + FioGetDirectory(o_dir.name, lengthof(o_dir.name), SCENARIO_DIR); break; case SLD_LOAD_HEIGHTMAP: - ttd_strlcpy(&o_dir.name[0], _paths.heightmap_dir, sizeof(o_dir.name)); + FioGetDirectory(o_dir.name, lengthof(o_dir.name), HEIGHTMAP_DIR); break; default: - ttd_strlcpy(&o_dir.name[0], _paths.personal_dir, sizeof(o_dir.name)); + ttd_strlcpy(o_dir.name, _personal_dir, lengthof(o_dir.name)); } break; - } case WE_PAINT: { int pos; diff --git a/src/music_gui.cpp b/src/music_gui.cpp index 8566a9aa23..0d932cb268 100644 --- a/src/music_gui.cpp +++ b/src/music_gui.cpp @@ -7,6 +7,7 @@ #include "table/strings.h" #include "table/sprites.h" #include "functions.h" +#include "fileio.h" #include "window.h" #include "gfx.h" #include "sound.h" @@ -90,9 +91,9 @@ static void MusicVolumeChanged(byte new_vol) static void DoPlaySong() { - char filename[256]; - snprintf(filename, sizeof(filename), "%s%s", - _paths.gm_dir, origin_songs_specs[_music_wnd_cursong - 1].filename); + char filename[MAX_PATH]; + FioFindFullPath(filename, lengthof(filename), GM_DIR, + origin_songs_specs[_music_wnd_cursong - 1].filename); _music_driver->play_song(filename); } @@ -105,20 +106,14 @@ static void SelectSongToPlay() { uint i = 0; uint j = 0; - char filename[256]; memset(_cur_playlist, 0, sizeof(_cur_playlist)); do { - if (_playlists[msf.playlist][i] != 0) { // Don't evaluate playlist terminator - snprintf(filename, sizeof(filename), "%s%s", - _paths.gm_dir, origin_songs_specs[(_playlists[msf.playlist][i]) - 1].filename); - - /* we are now checking for the existence of that file prior - * to add it to the list of available songs */ - if (FileExists(filename)) { - _cur_playlist[j] = _playlists[msf.playlist][i]; - j++; - } + /* We are now checking for the existence of that file prior + * to add it to the list of available songs */ + if (FioCheckFileExists(origin_songs_specs[_playlists[msf.playlist][i]].filename, GM_DIR)) { + _cur_playlist[j] = _playlists[msf.playlist][i]; + j++; } } while (_playlists[msf.playlist][i++] != 0 && i < lengthof(_cur_playlist) - 1); diff --git a/src/network/network_client.cpp b/src/network/network_client.cpp index 1bfaa04026..d7865beef1 100644 --- a/src/network/network_client.cpp +++ b/src/network/network_client.cpp @@ -21,6 +21,7 @@ #include "../variables.h" #include "../ai/ai.h" #include "../helpers.hpp" +#include "../fileio.h" // This file handles all the client-commands @@ -269,7 +270,7 @@ DEF_CLIENT_SEND_COMMAND_PARAM(PACKET_CLIENT_RCON)(const char *pass, const char * // DEF_CLIENT_RECEIVE_COMMAND has parameter: Packet *p // ********** -extern bool SafeSaveOrLoad(const char *filename, int mode, int newgm); +extern bool SafeSaveOrLoad(const char *filename, int mode, int newgm, Subdirectory subdir); DEF_CLIENT_RECEIVE_COMMAND(PACKET_SERVER_FULL) { @@ -489,7 +490,6 @@ DEF_CLIENT_RECEIVE_COMMAND(PACKET_SERVER_WAIT) DEF_CLIENT_RECEIVE_COMMAND(PACKET_SERVER_MAP) { - static char filename[256]; static FILE *file_pointer; byte maptype; @@ -500,10 +500,7 @@ DEF_CLIENT_RECEIVE_COMMAND(PACKET_SERVER_MAP) // First packet, init some stuff if (maptype == MAP_PACKET_START) { - // The name for the temp-map - snprintf(filename, lengthof(filename), "%s%snetwork_client.tmp", _paths.autosave_dir, PATHSEP); - - file_pointer = fopen(filename, "wb"); + file_pointer = FioFOpenFile("network_client.tmp", "wb", AUTOSAVE_DIR);; if (file_pointer == NULL) { _switch_mode_errorstr = STR_NETWORK_ERR_SAVEGAMEERROR; return NETWORK_RECV_STATUS_SAVEGAME; @@ -545,7 +542,7 @@ DEF_CLIENT_RECEIVE_COMMAND(PACKET_SERVER_MAP) InvalidateWindow(WC_NETWORK_STATUS_WINDOW, 0); /* The map is done downloading, load it */ - if (!SafeSaveOrLoad(filename, SL_LOAD, GM_NORMAL)) { + if (!SafeSaveOrLoad("network_client.tmp", SL_LOAD, GM_NORMAL, AUTOSAVE_DIR)) { DeleteWindowById(WC_NETWORK_STATUS_WINDOW, 0); _switch_mode_errorstr = STR_NETWORK_ERR_SAVEGAMEERROR; return NETWORK_RECV_STATUS_SAVEGAME; diff --git a/src/network/network_server.cpp b/src/network/network_server.cpp index 736fe64a10..635148b626 100644 --- a/src/network/network_server.cpp +++ b/src/network/network_server.cpp @@ -24,6 +24,7 @@ #include "../variables.h" #include "../genworld.h" #include "../helpers.hpp" +#include "../fileio.h" // This file handles all the server-commands @@ -307,14 +308,13 @@ DEF_SERVER_SEND_COMMAND(PACKET_SERVER_MAP) } if (cs->status == STATUS_AUTH) { - char filename[256]; + const char *filename = "network_server.tmp"; Packet *p; // Make a dump of the current game - snprintf(filename, lengthof(filename), "%s%snetwork_server.tmp", _paths.autosave_dir, PATHSEP); - if (SaveOrLoad(filename, SL_SAVE) != SL_OK) error("network savedump failed"); + if (SaveOrLoad(filename, SL_SAVE, AUTOSAVE_DIR) != SL_OK) error("network savedump failed"); - file_pointer = fopen(filename, "rb"); + file_pointer = FioFOpenFile(filename, "rb", AUTOSAVE_DIR); fseek(file_pointer, 0, SEEK_END); if (ftell(file_pointer) == 0) error("network savedump failed - zero sized savegame?"); diff --git a/src/newgrf_config.cpp b/src/newgrf_config.cpp index 6d6709c6bb..74ec3b837f 100644 --- a/src/newgrf_config.cpp +++ b/src/newgrf_config.cpp @@ -363,14 +363,16 @@ static uint ScanPath(const char *path, int basepath_length) /* Scan for all NewGRFs */ void ScanNewGRFFiles() { - uint num; + Searchpath sp; + char path[MAX_PATH]; + uint num = 0; ClearGRFConfigList(&_all_grfs); DEBUG(grf, 1, "Scanning for NewGRFs"); - num = ScanPath(_paths.data_dir, strlen(_paths.data_dir)); - if (_paths.second_data_dir != NULL) { - num += ScanPath(_paths.second_data_dir, strlen(_paths.second_data_dir)); + FOR_ALL_SEARCHPATHS(sp) { + FioAppendDirectory(path, MAX_PATH, sp, DATA_DIR); + num += ScanPath(path, strlen(path)); } DEBUG(grf, 1, "Scan complete, found %d files", num); } diff --git a/src/openttd.cpp b/src/openttd.cpp index 22302146b3..0ccb850c68 100644 --- a/src/openttd.cpp +++ b/src/openttd.cpp @@ -312,8 +312,6 @@ static void UnInitializeGame() static void LoadIntroGame() { - char filename[256]; - _game_mode = GM_MENU; /* Clear transparency options */ @@ -326,14 +324,8 @@ static void LoadIntroGame() ResetWindowSystem(); SetupColorsAndInitialWindow(); - /* Generate a world. */ - snprintf(filename, lengthof(filename), "%sopntitle.dat", _paths.data_dir); -#if defined SECOND_DATA_DIR - if (SaveOrLoad(filename, SL_LOAD) != SL_OK) { - snprintf(filename, lengthof(filename), "%sopntitle.dat", _paths.second_data_dir); - } -#endif - if (SaveOrLoad(filename, SL_LOAD) != SL_OK) { + /* Load the default opening screen savegame */ + if (SaveOrLoad("opntitle.dat", SL_LOAD, DATA_DIR) != SL_OK) { GenerateWorld(GW_EMPTY, 64, 64); // if failed loading, make empty world. WaitTillGeneratedWorld(); } @@ -760,7 +752,7 @@ static void StartScenario() ResetGRFConfig(true); /* Load game */ - if (SaveOrLoad(_file_to_saveload.name, _file_to_saveload.mode) != SL_OK) { + if (SaveOrLoad(_file_to_saveload.name, _file_to_saveload.mode, SCENARIO_DIR) != SL_OK) { LoadIntroGame(); ShowErrorMessage(INVALID_STRING_ID, STR_4009_GAME_LOAD_FAILED, 0, 0); } @@ -782,12 +774,20 @@ static void StartScenario() MarkWholeScreenDirty(); } -bool SafeSaveOrLoad(const char *filename, int mode, int newgm) +/** Load the specified savegame but on error do different things. + * If loading fails due to corrupt savegame, bad version, etc. go back to + * a previous correct state. In the menu for example load the intro game again. + * @param filename file to be loaded + * @param mode mode of loading, either SL_LOAD or SL_OLD_LOAD + * @param newgm switch to this mode of loading fails due to some unknown error + * @param subdir default directory to look for filename, set to 0 if not needed */ +bool SafeSaveOrLoad(const char *filename, int mode, int newgm, Subdirectory subdir) { byte ogm = _game_mode; _game_mode = newgm; - switch (SaveOrLoad(filename, mode)) { + assert(mode == SL_LOAD || mode == SL_OLD_LOAD); + switch (SaveOrLoad(filename, mode, subdir)) { case SL_OK: return true; case SL_REINIT: @@ -860,7 +860,7 @@ void SwitchMode(int new_mode) _opt_ptr = &_opt; ResetGRFConfig(true); - if (!SafeSaveOrLoad(_file_to_saveload.name, _file_to_saveload.mode, GM_NORMAL)) { + if (!SafeSaveOrLoad(_file_to_saveload.name, _file_to_saveload.mode, GM_NORMAL, BASE_DIR)) { LoadIntroGame(); ShowErrorMessage(INVALID_STRING_ID, STR_4009_GAME_LOAD_FAILED, 0, 0); } else { @@ -894,7 +894,7 @@ void SwitchMode(int new_mode) break; case SM_LOAD_SCENARIO: { /* Load scenario from scenario editor */ - if (SafeSaveOrLoad(_file_to_saveload.name, _file_to_saveload.mode, GM_EDITOR)) { + if (SafeSaveOrLoad(_file_to_saveload.name, _file_to_saveload.mode, GM_EDITOR, BASE_DIR)) { _opt_ptr = &_opt; SetLocalPlayer(OWNER_NONE); @@ -910,7 +910,7 @@ void SwitchMode(int new_mode) break; case SM_SAVE: /* Save game */ - if (SaveOrLoad(_file_to_saveload.name, SL_SAVE) != SL_OK) { + if (SaveOrLoad(_file_to_saveload.name, SL_SAVE, BASE_DIR) != SL_OK) { ShowErrorMessage(INVALID_STRING_ID, STR_4007_GAME_SAVE_FAILED, 0, 0); } else { DeleteWindowById(WC_SAVELOAD, 0); @@ -967,9 +967,11 @@ void StateGameLoop() } } +/** Create an autosave. The default name is "autosave#.sav". However with + * the patch setting 'keep_all_autosave' the name defaults to company-name + date */ static void DoAutosave() { - char buf[200]; + char buf[MAX_PATH]; #if defined(PSP) /* Autosaving in networking is too time expensive for the PSP */ @@ -979,27 +981,21 @@ static void DoAutosave() if (_patches.keep_all_autosave && _local_player != PLAYER_SPECTATOR) { const Player *p = GetPlayer(_local_player); - char* s = buf; - - s += snprintf(buf, lengthof(buf), "%s%s", _paths.autosave_dir, PATHSEP); SetDParam(0, p->name_1); SetDParam(1, p->name_2); SetDParam(2, _date); - s = GetString(s, STR_4004, lastof(buf)); - strecpy(s, ".sav", lastof(buf)); - } else { // generate a savegame name and number according to _patches.max_num_autosaves - snprintf(buf, lengthof(buf), "%s%sautosave%d.sav", _paths.autosave_dir, PATHSEP, _autosave_ctr); + GetString(buf, STR_4004, lastof(buf)); + ttd_strlcpy(buf, ".sav", sizeof(buf)); + } else { + /* generate a savegame name and number according to _patches.max_num_autosaves */ + snprintf(buf, sizeof(buf), "autosave%d.sav", _autosave_ctr); - _autosave_ctr++; - if (_autosave_ctr >= _patches.max_num_autosaves) { - /* we reached the limit for numbers of autosaves. We will start over */ - _autosave_ctr = 0; - } + if (++_autosave_ctr >= _patches.max_num_autosaves) _autosave_ctr = 0; } DEBUG(sl, 2, "Autosaving to '%s'", buf); - if (SaveOrLoad(buf, SL_SAVE) != SL_OK) + if (SaveOrLoad(buf, SL_SAVE, AUTOSAVE_DIR) != SL_OK) ShowErrorMessage(INVALID_STRING_ID, STR_AUTOSAVE_FAILED, 0, 0); } diff --git a/src/saveload.cpp b/src/saveload.cpp index 420e5e98a1..4b1f12bf88 100644 --- a/src/saveload.cpp +++ b/src/saveload.cpp @@ -1561,7 +1561,7 @@ void WaitTillSaved() * @param mode Save or load. Load can also be a TTD(Patch) game. Use SL_LOAD, SL_OLD_LOAD or SL_SAVE * @return Return the results of the action. SL_OK, SL_ERROR or SL_REINIT ("unload" the game) */ -SaveOrLoadResult SaveOrLoad(const char *filename, int mode) +SaveOrLoadResult SaveOrLoad(const char *filename, int mode, Subdirectory sb) { uint32 hdr[2]; const SaveLoadFormat *fmt; @@ -1583,7 +1583,7 @@ SaveOrLoadResult SaveOrLoad(const char *filename, int mode) return SL_OK; } - _sl.fh = (mode == SL_SAVE) ? fopen(filename, "wb") : fopen(filename, "rb"); + _sl.fh = (mode == SL_SAVE) ? FioFOpenFile(filename, "wb", sb) : FioFOpenFile(filename, "rb", sb); if (_sl.fh == NULL) { DEBUG(sl, 0, "Cannot open savegame '%s' for saving/loading.", filename); return SL_ERROR; @@ -1721,9 +1721,7 @@ SaveOrLoadResult SaveOrLoad(const char *filename, int mode) /** Do a save when exiting the game (patch option) _patches.autosave_on_exit */ void DoExitSave() { - char buf[200]; - snprintf(buf, sizeof(buf), "%s%sexit.sav", _paths.autosave_dir, PATHSEP); - SaveOrLoad(buf, SL_SAVE); + SaveOrLoad("exit.sav", SL_SAVE, AUTOSAVE_DIR); } #if 0 diff --git a/src/saveload.h b/src/saveload.h index 78d560217f..9a212a8728 100644 --- a/src/saveload.h +++ b/src/saveload.h @@ -5,6 +5,8 @@ #ifndef SAVELOAD_H #define SAVELOAD_H +#include "fileio.h" + #ifdef SIZE_MAX #undef SIZE_MAX #endif @@ -26,7 +28,7 @@ enum SaveOrLoadMode { SL_BMP = 4, }; -SaveOrLoadResult SaveOrLoad(const char *filename, int mode); +SaveOrLoadResult SaveOrLoad(const char *filename, int mode, Subdirectory sb); void WaitTillSaved(); void DoExitSave(); diff --git a/src/screenshot.cpp b/src/screenshot.cpp index 9beac53d5a..78235553de 100644 --- a/src/screenshot.cpp +++ b/src/screenshot.cpp @@ -8,11 +8,13 @@ #include "table/strings.h" #include "gfx.h" #include "hal.h" +#include "fileio.h" #include "viewport.h" #include "player.h" #include "screenshot.h" #include "variables.h" #include "date.h" +#include "string.h" #include "helpers.hpp" #include "blitter/blitter.hpp" #include "fileio.h" @@ -521,12 +523,12 @@ static void LargeWorldCallback(void *userdata, void *buf, uint y, uint pitch, ui static char *MakeScreenshotName(const char *ext) { - static char filename[256]; - char *base; + static char filename[MAX_PATH]; int serial; + size_t len; if (_game_mode == GM_EDITOR || _game_mode == GM_MENU || _local_player == PLAYER_SPECTATOR) { - sprintf(_screenshot_name, "screenshot"); + ttd_strlcpy(_screenshot_name, "screenshot", lengthof(_screenshot_name)); } else { const Player* p = GetPlayer(_local_player); SetDParam(0, p->name_1); @@ -535,16 +537,16 @@ static char *MakeScreenshotName(const char *ext) GetString(_screenshot_name, STR_4004, lastof(_screenshot_name)); } + /* Add extension to screenshot file */ SanitizeFilename(_screenshot_name); - base = strchr(_screenshot_name, 0); - base[0] = '.'; strcpy(base + 1, ext); + len = strlen(_screenshot_name); + snprintf(&_screenshot_name[len], lengthof(_screenshot_name) - len, ".%s", ext); - serial = 0; - for (;;) { - snprintf(filename, sizeof(filename), "%s%s", _paths.personal_dir, _screenshot_name); - if (!FileExists(filename)) - break; - sprintf(base, " #%d.%s", ++serial, ext); + for (serial = 1;; serial++) { + snprintf(filename, lengthof(filename), "%s%s", _personal_dir, _screenshot_name); + if (!FileExists(filename)) break; + /* If file exists try another one with same name, but just with a higher index */ + snprintf(&_screenshot_name[len], lengthof(_screenshot_name) - len, "#%d.%s", serial, ext); } return filename; diff --git a/src/stdafx.h b/src/stdafx.h index a25789ecad..bcd2ddbe01 100644 --- a/src/stdafx.h +++ b/src/stdafx.h @@ -286,11 +286,7 @@ typedef unsigned char byte; # define TO_BE32X(x) BSWAP32(x) #endif /* TTD_BIG_ENDIAN */ -#if !defined(GAME_DATA_DIR) -# define GAME_DATA_DIR "" -#endif - -#if !defined(PERSONAL_DIR) +#if !defined(WITH_PERSONAL_DIR) # define PERSONAL_DIR "" #endif diff --git a/src/strings.cpp b/src/strings.cpp index 603371029f..f434bf90f3 100644 --- a/src/strings.cpp +++ b/src/strings.cpp @@ -23,6 +23,7 @@ #include "music.h" #include "date.h" #include "industry.h" +#include "fileio.h" #include "helpers.hpp" #include "cargotype.h" #include "group.h" @@ -1295,8 +1296,15 @@ static int GetLanguageList(Language *langs, int start, int max, const char *path */ void InitializeLanguagePacks() { + Searchpath sp; Language files[MAX_LANG]; - uint language_count = GetLanguageList(files, 0, lengthof(files), _paths.lang_dir); + uint language_count = 0; + + FOR_ALL_SEARCHPATHS(sp) { + char path[MAX_PATH]; + FioAppendDirectory(path, lengthof(path), sp, LANG_DIR); + language_count += GetLanguageList(files, language_count, lengthof(files), path); + } if (language_count == 0) error("No available language packs (invalid versions?)"); /* Sort the language names alphabetically */ diff --git a/src/variables.h b/src/variables.h index c8a3cd23eb..a43d9d1f39 100644 --- a/src/variables.h +++ b/src/variables.h @@ -265,21 +265,6 @@ struct Cheats { VARDEF Cheats _cheats; -struct Paths { - char *personal_dir; // includes cfg file and save folder - char *game_data_dir; // includes data, gm, lang - char *data_dir; - char *gm_dir; - char *lang_dir; - char *save_dir; - char *autosave_dir; - char *scenario_dir; - char *heightmap_dir; - char *second_data_dir; -}; - -VARDEF Paths _paths; - /* NOSAVE: Used in palette animations only, not really important. */ VARDEF int _timer_counter; diff --git a/src/video/cocoa_v.mm b/src/video/cocoa_v.mm index bc43589126..950aa2b282 100644 --- a/src/video/cocoa_v.mm +++ b/src/video/cocoa_v.mm @@ -74,6 +74,7 @@ extern "C" void HideMenuBar(); #include "cocoa_keys.h" #include "../blitter/blitter.hpp" #include "../renderer/renderer.hpp" +#include "../fileio.h" #undef Point #undef Rect @@ -2059,6 +2060,22 @@ void CocoaDialog(const char* title, const char* message, const char* buttonLabel _cocoa_video_dialog = false; } +/* This is needed since OS X application bundles do not have a + * current directory and the data files are 'somewhere' in the bundle */ +void cocoaSetApplicationBundleDir() +{ + char tmp[MAXPATHLEN]; + CFURLRef url = CFBundleCopyResourcesDirectoryURL(CFBundleGetMainBundle()); + if (CFURLGetFileSystemRepresentation(url, true, (unsigned char*)tmp, MAXPATHLEN)) { + AppendPathSeparator(tmp, lengthof(tmp)); + _searchpaths[SP_APPLICATION_BUNDLE_DIR] = strdup(tmp); + } else { + _searchpaths[SP_APPLICATION_BUNDLE_DIR] = NULL; + } + + CFRelease(url); +} + /* These are called from main() to prevent a _NSAutoreleaseNoPool error when * exiting before the cocoa video driver has been loaded */ diff --git a/src/video/dedicated_v.cpp b/src/video/dedicated_v.cpp index 4aa6b9f20b..596e554976 100644 --- a/src/video/dedicated_v.cpp +++ b/src/video/dedicated_v.cpp @@ -13,6 +13,7 @@ #include "../console.h" #include "../variables.h" #include "../genworld.h" +#include "../fileio.h" #include "../blitter/blitter.hpp" #include "dedicated_v.h" @@ -115,7 +116,7 @@ static void CloseWindowsConsoleThread() static void *_dedicated_video_mem; -extern bool SafeSaveOrLoad(const char *filename, int mode, int newgm); +extern bool SafeSaveOrLoad(const char *filename, int mode, int newgm, Subdirectory subdir); extern void SwitchMode(int new_mode); @@ -260,7 +261,7 @@ static void DedicatedVideoMainLoop() _switch_mode = SM_NONE; /* First we need to test if the savegame can be loaded, else we will end up playing the * intro game... */ - if (!SafeSaveOrLoad(_file_to_saveload.name, _file_to_saveload.mode, GM_NORMAL)) { + if (!SafeSaveOrLoad(_file_to_saveload.name, _file_to_saveload.mode, GM_NORMAL, BASE_DIR)) { /* Loading failed, pop out.. */ DEBUG(net, 0, "Loading requested map failed, aborting"); _networking = false; diff --git a/src/win32.cpp b/src/win32.cpp index fc119ce8ed..5dc753670b 100644 --- a/src/win32.cpp +++ b/src/win32.cpp @@ -23,11 +23,13 @@ #include "variables.h" #include "win32.h" #include "fios.h" // opendir/readdir/closedir +#include "fileio.h" #include #include #include #include #include +#include static bool _has_console; @@ -224,7 +226,7 @@ static const TCHAR _save_succeeded[] = static bool EmergencySave() { - SaveOrLoad("crash.sav", SL_SAVE); + SaveOrLoad("crash.sav", SL_SAVE, BASE_DIR); return true; } @@ -958,22 +960,54 @@ char *getcwd(char *buf, size_t size) return buf; } -extern char *BuildWithFullPath(const char *dir); void DetermineBasePaths(const char *exe) { - _paths.personal_dir = MallocT(MAX_PATH); - getcwd(_paths.personal_dir, MAX_PATH); + char tmp[MAX_PATH]; + TCHAR path[MAX_PATH]; +#ifdef WITH_PERSONAL_DIR + SHGetFolderPath(NULL, CSIDL_PERSONAL, NULL, SHGFP_TYPE_CURRENT, path); + strncpy(tmp, WIDE_TO_MB_BUFFER(path, tmp, lengthof(tmp)), lengthof(tmp)); + AppendPathSeparator(tmp, MAX_PATH); + ttd_strlcat(tmp, PERSONAL_DIR, MAX_PATH); + AppendPathSeparator(tmp, MAX_PATH); + _searchpaths[SP_PERSONAL_DIR] = strdup(tmp); - _paths.game_data_dir = BuildWithFullPath(GAME_DATA_DIR); -#if defined(SECOND_DATA_DIR) - _paths.second_data_dir = BuildWithFullPath(SECOND_DATA_DIR); + SHGetFolderPath(NULL, CSIDL_COMMON_DOCUMENTS, NULL, SHGFP_TYPE_CURRENT, path); + strncpy(tmp, WIDE_TO_MB_BUFFER(path, tmp, lengthof(tmp)), lengthof(tmp)); + AppendPathSeparator(tmp, MAX_PATH); + ttd_strlcat(tmp, PERSONAL_DIR, MAX_PATH); + AppendPathSeparator(tmp, MAX_PATH); + _searchpaths[SP_SHARED_DIR] = strdup(tmp); #else - _paths.second_data_dir = NULL; + _searchpaths[SP_PERSONAL_DIR] = NULL; + _searchpaths[SP_SHARED_DIR] = NULL; #endif - _paths.personal_dir[0] = toupper(_paths.personal_dir[0]); - AppendPathSeparator(_paths.personal_dir, MAX_PATH); + /* Get the path to working directory of OpenTTD */ + getcwd(tmp, lengthof(tmp)); + AppendPathSeparator(tmp, MAX_PATH); + _searchpaths[SP_WORKING_DIR] = strdup(tmp); + + if (!GetModuleFileName(NULL, path, lengthof(path))) { + DEBUG(misc, 0, "GetModuleFileName failed (%d)\n", GetLastError()); + _searchpaths[SP_BINARY_DIR] = NULL; + } else { + TCHAR exec_dir[MAX_PATH]; + _tcsncpy(path, MB_TO_WIDE_BUFFER(exe, path, lengthof(path)), lengthof(path)); + if (!GetFullPathName(path, lengthof(exec_dir), exec_dir, NULL)) { + DEBUG(misc, 0, "GetFullPathName failed (%d)\n", GetLastError()); + _searchpaths[SP_BINARY_DIR] = NULL; + } else { + strncpy(tmp, WIDE_TO_MB_BUFFER(exec_dir, tmp, lengthof(tmp)), lengthof(tmp)); + char *s = strrchr(tmp, PATHSEPCHAR); + *(s + 1) = '\0'; + _searchpaths[SP_BINARY_DIR] = strdup(tmp); + } + } + + _searchpaths[SP_INSTALLATION_DIR] = NULL; + _searchpaths[SP_APPLICATION_BUNDLE_DIR] = NULL; } /**