diff --git a/CMakeLists.txt b/CMakeLists.txt index ba6cd2d9..63f1ace6 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -198,7 +198,7 @@ elseif(CMAKE_CXX_COMPILER_ID MATCHES "MSVC") add_definitions(-DNOEXCEPT= -DHAS_MSVC_EXCEPTION_BUG) # Needed for Debug information (it's set to "No" by default for some reason) - set(CMAKE_EXE_LINKER_FLAGS_DEBUG "${CMAKE_EXE_LINKER_FLAGS} /DEBUG") + set(CMAKE_EXE_LINKER_FLAGS_DEBUG "${CMAKE_EXE_LINKER_FLAGS} /DEBUG /NODEFAULTLIB:MSVCRTD /NODEFAULTLIB:LIBCMT") set(CMAKE_EXE_LINKER_FLAGS_RELWITHDEBINFO "${CMAKE_EXE_LINKER_FLAGS} /DEBUG") else() message(FATAL_ERROR "Your C++ compiler doesn't seem to be supported.") @@ -230,7 +230,10 @@ option(DEV_BUILD "Enable development build (enables some debugging tools, local # PLEASE DO NOT USE ON UNOFFICIAL BUILDS. Thanks. option(OFFICIAL_COLOBOT_BUILD "Official build (changes crash screen text)" OFF) -# Portable build - load all data from current directory +# Hardcode relative paths instead of absolute paths +option(USE_RELATIVE_PATHS "Generate relative paths from absolute paths" OFF) + +# Portable build - load all data from the base directory option(PORTABLE "Portable build" OFF) # Portable saves - suitable for e.g. putting the whole game on external storage and moving your saves with it @@ -391,12 +394,12 @@ endif() # Installation paths defined before compiling sources if(PORTABLE OR (PLATFORM_WINDOWS AND MXE)) - # We need to use STRING because PATH doesn't accept relative paths - set(COLOBOT_INSTALL_BIN_DIR ./ CACHE STRING "Colobot binary directory") - set(COLOBOT_INSTALL_LIB_DIR ./ CACHE STRING "Colobot libraries directory") - set(COLOBOT_INSTALL_DATA_DIR ./data CACHE STRING "Colobot shared data directory") - set(COLOBOT_INSTALL_I18N_DIR ./lang CACHE STRING "Colobot translations directory") - set(COLOBOT_INSTALL_DOC_DIR ./doc CACHE STRING "Colobot documentation directory") + set(COLOBOT_INSTALL_BIN_DIR ${CMAKE_INSTALL_PREFIX}/ CACHE PATH "Colobot binary directory") + set(COLOBOT_INSTALL_LIB_DIR ${CMAKE_INSTALL_PREFIX}/ CACHE PATH "Colobot libraries directory") + set(COLOBOT_INSTALL_DATA_DIR ${CMAKE_INSTALL_PREFIX}/data CACHE PATH "Colobot shared data directory") + set(COLOBOT_INSTALL_I18N_DIR ${CMAKE_INSTALL_PREFIX}/lang CACHE PATH "Colobot translations directory") + set(COLOBOT_INSTALL_DOC_DIR ${CMAKE_INSTALL_PREFIX}/doc CACHE PATH "Colobot documentation directory") + set(USE_RELATIVE_PATHS ON) elseif(PLATFORM_WINDOWS) set(COLOBOT_INSTALL_BIN_DIR ${CMAKE_INSTALL_PREFIX}/ CACHE PATH "Colobot binary directory") set(COLOBOT_INSTALL_LIB_DIR ${CMAKE_INSTALL_PREFIX}/ CACHE PATH "Colobot libraries directory") @@ -417,6 +420,18 @@ else() set(COLOBOT_INSTALL_DOC_DIR ${CMAKE_INSTALL_PREFIX}/share/doc/colobot CACHE PATH "Colobot documentation directory") endif() +# Generate relative paths from absolute paths +if(USE_RELATIVE_PATHS) + message(STATUS "Generating relative paths") + file(RELATIVE_PATH COLOBOT_DATA_DIR ${COLOBOT_INSTALL_BIN_DIR} ${COLOBOT_INSTALL_DATA_DIR}) + file(RELATIVE_PATH COLOBOT_I18N_DIR ${COLOBOT_INSTALL_BIN_DIR} ${COLOBOT_INSTALL_I18N_DIR}) + + add_definitions(-DUSE_RELATIVE_PATHS) +else() + set(COLOBOT_DATA_DIR ${COLOBOT_INSTALL_DATA_DIR}) + set(COLOBOT_I18N_DIR ${COLOBOT_INSTALL_I18N_DIR}) +endif() + # Subdirectory with sources add_subdirectory(src) diff --git a/Jenkinsfile b/Jenkinsfile index 47dbd3c2..35f66489 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -27,6 +27,7 @@ pipeline { dir('build/windows') { sh ''' # FIXME: without -lsetupapi linking sdl2 fails + rm -rf * /opt/mxe/usr/bin/i686-w64-mingw32.static-cmake \ -DCMAKE_CXX_STANDARD_LIBRARIES="-lkernel32 -luser32 -lgdi32 -lwinspool -lshell32 -lole32 -loleaut32 -luuid -lcomdlg32 -ladvapi32 -lsetupapi" \ -DCMAKE_INSTALL_PREFIX=/install \ @@ -53,6 +54,7 @@ pipeline { sh 'mkdir -p build/linux' dir('build/linux') { sh ''' + rm -rf * cmake \ -DCMAKE_INSTALL_PREFIX=/install -DCMAKE_SKIP_INSTALL_RPATH=ON \ -DCMAKE_BUILD_TYPE=RelWithDebInfo -DDEV_BUILD=1 -DPORTABLE=1 -DTOOLS=1 -DTESTS=1 -DDESKTOP=1 ../.. diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 506046c1..fd2e2c3d 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -65,6 +65,8 @@ elseif(PLATFORM_WINDOWS) find_library(TIFF_LIBRARY NAMES tiff.lib) find_library(LZMA_LIBRARY NAMES lzma.lib) find_library(FREETYPE_LIBRARY NAMES freetype.lib) + find_library(ICONV_LIBRARY NAMES libiconv.lib) + find_library(CHARSET_LIBRARY NAMES libcharset.lib) set(MSVC_LIBS ${LIBINTL_LIBRARY} ${OPENAL_MSVC_LIBS} @@ -73,6 +75,8 @@ elseif(PLATFORM_WINDOWS) ${BZ2_LIBRARY} ${LZMA_LIBRARY} ${FREETYPE_LIBRARY} + ${ICONV_LIBRARY} + ${CHARSET_LIBRARY} winmm.lib dxguid.lib imm32.lib @@ -81,6 +85,7 @@ elseif(PLATFORM_WINDOWS) version.lib wsock32.lib ws2_32.lib + setupapi.lib ) else(${MSVC_STATIC}) set(MSVC_LIBS ${LIBINTL_LIBRARY}) diff --git a/src/app/app.cpp b/src/app/app.cpp index 6f864572..94b6319e 100644 --- a/src/app/app.cpp +++ b/src/app/app.cpp @@ -220,6 +220,30 @@ CSoundInterface* CApplication::GetSound() return m_sound.get(); } +void CApplication::LoadEnvironmentVariables() +{ + auto dataDir = m_systemUtils->GetEnvVar("COLOBOT_DATA_DIR"); + if (!dataDir.empty()) + { + m_pathManager->SetDataPath(dataDir); + GetLogger()->Info("Using data dir (based on environment variable): '%s'\n", dataDir.c_str()); + } + + auto langDir = m_systemUtils->GetEnvVar("COLOBOT_LANG_DIR"); + if (!langDir.empty()) + { + m_pathManager->SetLangPath(langDir); + GetLogger()->Info("Using lang dir (based on environment variable): '%s'\n", langDir.c_str()); + } + + auto saveDir = m_systemUtils->GetEnvVar("COLOBOT_SAVE_DIR"); + if (!saveDir.empty()) + { + m_pathManager->SetSavePath(saveDir); + GetLogger()->Info("Using save dir (based on environment variable): '%s'\n", saveDir.c_str()); + } +} + ParseArgsStatus CApplication::ParseArguments(int argc, char *argv[]) { enum OptionType @@ -286,15 +310,18 @@ ParseArgsStatus CApplication::ParseArguments(int argc, char *argv[]) GetLogger()->Message("\n"); GetLogger()->Message("%s\n", COLOBOT_FULLNAME); GetLogger()->Message("\n"); - GetLogger()->Message("List of available options:\n"); + GetLogger()->Message("List of available options and environment variables:\n"); GetLogger()->Message(" -help this help\n"); GetLogger()->Message(" -debug modes enable debug modes (more info printed in logs; see code for reference of modes)\n"); GetLogger()->Message(" -runscene sceneNNN run given scene on start\n"); GetLogger()->Message(" -scenetest win every mission right after it's loaded\n"); GetLogger()->Message(" -loglevel level set log level to level (one of: trace, debug, info, warn, error, none)\n"); GetLogger()->Message(" -langdir path set custom language directory path\n"); + GetLogger()->Message(" environment variable: COLOBOT_LANG_DIR\n"); GetLogger()->Message(" -datadir path set custom data directory path\n"); + GetLogger()->Message(" environment variable: COLOBOT_DATA_DIR\n"); GetLogger()->Message(" -savedir path set custom save directory path (must be writable)\n"); + GetLogger()->Message(" environment variable: COLOBOT_SAVE_DIR\n"); GetLogger()->Message(" -mod path load datadir mod from given path\n"); GetLogger()->Message(" -resolution WxH set resolution\n"); GetLogger()->Message(" -headless headless mode - disables graphics, sound and user interaction\n"); diff --git a/src/app/app.h b/src/app/app.h index ccae3a5c..0c7f6388 100644 --- a/src/app/app.h +++ b/src/app/app.h @@ -164,7 +164,9 @@ public: CSoundInterface* GetSound(); public: - //! Parses commandline arguments + //! Loads some data from environment variables + void LoadEnvironmentVariables(); + //! Parses commandline arguments (they take priority) ParseArgsStatus ParseArguments(int argc, char *argv[]); //! Initializes the application bool Create(); diff --git a/src/app/main.cpp b/src/app/main.cpp index 46b0b88f..8fdf1653 100644 --- a/src/app/main.cpp +++ b/src/app/main.cpp @@ -172,6 +172,8 @@ int main(int argc, char *argv[]) int code = 0; CApplication app(systemUtils.get()); // single instance of the application + app.LoadEnvironmentVariables(); + ParseArgsStatus status = app.ParseArguments(argc, argv); if (status == PARSE_ARGS_FAIL) { diff --git a/src/app/pathman.cpp b/src/app/pathman.cpp index 60715ac1..954fcc4e 100644 --- a/src/app/pathman.cpp +++ b/src/app/pathman.cpp @@ -41,7 +41,7 @@ CPathManager::CPathManager(CSystemUtils* systemUtils) : m_dataPath(systemUtils->GetDataPath()) , m_langPath(systemUtils->GetLangPath()) , m_savePath(systemUtils->GetSaveDir()) - , m_modAutoloadDir{ m_dataPath + "/mods", m_savePath + "/mods" } + , m_modAutoloadDir{} , m_mods{} { } @@ -130,6 +130,10 @@ void CPathManager::InitPaths() { GetLogger()->Info("Data path: %s\n", m_dataPath.c_str()); GetLogger()->Info("Save path: %s\n", m_savePath.c_str()); + + m_modAutoloadDir.push_back(m_dataPath + "/mods"); + m_modAutoloadDir.push_back(m_savePath + "/mods"); + if (!m_modAutoloadDir.empty()) { GetLogger()->Info("Mod autoload dirs:\n"); diff --git a/src/common/config.h.cmake b/src/common/config.h.cmake index f6d0bcae..556e067e 100644 --- a/src/common/config.h.cmake +++ b/src/common/config.h.cmake @@ -18,5 +18,5 @@ #cmakedefine PORTABLE_SAVES @PORTABLE_SAVES@ -#define COLOBOT_DEFAULT_DATADIR "@COLOBOT_INSTALL_DATA_DIR@" -#define COLOBOT_I18N_DIR "@COLOBOT_INSTALL_I18N_DIR@" +#define COLOBOT_DEFAULT_DATADIR "@COLOBOT_DATA_DIR@" +#define COLOBOT_I18N_DIR "@COLOBOT_I18N_DIR@" diff --git a/src/common/system/system.cpp b/src/common/system/system.cpp index 12dc1d36..d69fbf6c 100644 --- a/src/common/system/system.cpp +++ b/src/common/system/system.cpp @@ -36,6 +36,7 @@ #include <iostream> #include <algorithm> +#include <SDL2/SDL.h> std::unique_ptr<CSystemUtils> CSystemUtils::Create() { @@ -176,17 +177,41 @@ float CSystemUtils::TimeStampDiff(SystemTimeStamp *before, SystemTimeStamp *afte return result; } +std::string CSystemUtils::GetBasePath() +{ + if (m_basePath.empty()) + { + auto* path = SDL_GetBasePath(); + m_basePath = path; + SDL_free(path); + } + return m_basePath; +} + std::string CSystemUtils::GetDataPath() { +#ifdef USE_RELATIVE_PATHS + return GetBasePath() + COLOBOT_DEFAULT_DATADIR; +#else return COLOBOT_DEFAULT_DATADIR; +#endif } std::string CSystemUtils::GetLangPath() { +#ifdef USE_RELATIVE_PATHS + return GetBasePath() + COLOBOT_I18N_DIR; +#else return COLOBOT_I18N_DIR; +#endif } std::string CSystemUtils::GetSaveDir() { - return "./saves"; + return GetBasePath() + "saves"; +} + +std::string CSystemUtils::GetEnvVar(const std::string& name) +{ + return ""; } diff --git a/src/common/system/system.h b/src/common/system/system.h index 36e736c9..95901390 100644 --- a/src/common/system/system.h +++ b/src/common/system/system.h @@ -127,6 +127,9 @@ public: /** The difference is \a after - \a before. */ virtual long long TimeStampExactDiff(SystemTimeStamp *before, SystemTimeStamp *after) = 0; + //! Returns the path where the executable binary is located (ends with the path separator) + virtual std::string GetBasePath(); + //! Returns the data path (containing textures, levels, helpfiles, etc) virtual std::string GetDataPath(); @@ -136,9 +139,13 @@ public: //! Returns the save dir location virtual std::string GetSaveDir(); + //! Returns the environment variable with the given name or an empty string if it does not exist + virtual std::string GetEnvVar(const std::string &name); + //! Sleep for given amount of microseconds virtual void Usleep(int usecs) = 0; private: + std::string m_basePath; std::vector<std::unique_ptr<SystemTimeStamp>> m_timeStamps; }; diff --git a/src/common/system/system_linux.cpp b/src/common/system/system_linux.cpp index 9bfe431a..69551bce 100644 --- a/src/common/system/system_linux.cpp +++ b/src/common/system/system_linux.cpp @@ -102,23 +102,23 @@ std::string CSystemUtilsLinux::GetSaveDir() std::string savegameDir; // Determine savegame dir according to XDG Base Directory Specification - char *envXDG_DATA_HOME = getenv("XDG_DATA_HOME"); - if (envXDG_DATA_HOME == nullptr) + auto envXDG_DATA_HOME = GetEnvVar("XDG_DATA_HOME"); + if (envXDG_DATA_HOME.empty()) { - char *envHOME = getenv("HOME"); - if (envHOME == nullptr) + auto envHOME = GetEnvVar("HOME"); + if (envHOME.empty()) { - GetLogger()->Warn("Unable to find directory for saves - using current directory"); - savegameDir = "./saves"; + GetLogger()->Warn("Unable to find directory for saves - using default directory"); + savegameDir = CSystemUtils::GetSaveDir(); } else { - savegameDir = std::string(envHOME) + "/.local/share/colobot"; + savegameDir = envHOME + "/.local/share/colobot"; } } else { - savegameDir = std::string(envXDG_DATA_HOME) + "/colobot"; + savegameDir = envXDG_DATA_HOME + "/colobot"; } GetLogger()->Trace("Saved game files are going to %s\n", savegameDir.c_str()); @@ -126,6 +126,17 @@ std::string CSystemUtilsLinux::GetSaveDir() #endif } +std::string CSystemUtilsLinux::GetEnvVar(const std::string& name) +{ + char* envVar = getenv(name.c_str()); + if (envVar != nullptr) + { + GetLogger()->Trace("Detected environment variable %s = %s\n", name.c_str(), envVar); + return std::string(envVar); + } + return ""; +} + void CSystemUtilsLinux::Usleep(int usec) { usleep(usec); diff --git a/src/common/system/system_linux.h b/src/common/system/system_linux.h index f1576f31..c4de2c7f 100644 --- a/src/common/system/system_linux.h +++ b/src/common/system/system_linux.h @@ -45,6 +45,8 @@ public: std::string GetSaveDir() override; + std::string GetEnvVar(const std::string& name) override; + void Usleep(int usec) override; private: diff --git a/src/common/system/system_macosx.cpp b/src/common/system/system_macosx.cpp index 9ef7ce1d..8a537e11 100644 --- a/src/common/system/system_macosx.cpp +++ b/src/common/system/system_macosx.cpp @@ -113,6 +113,12 @@ std::string CSystemUtilsMacOSX::GetSaveDir() #endif } +std::string CSystemUtilsMacOSX::GetEnvVar(const std::string& str) +{ + // TODO: I have no Mac + return std::string(); +} + void CSystemUtilsMacOSX::Usleep(int usec) { usleep(usec); diff --git a/src/common/system/system_macosx.h b/src/common/system/system_macosx.h index 5b572ec4..c8ea607a 100644 --- a/src/common/system/system_macosx.h +++ b/src/common/system/system_macosx.h @@ -36,6 +36,8 @@ public: std::string GetLangPath() override; std::string GetSaveDir() override; + std::string GetEnvVar(const std::string& name) override; + void Usleep(int usec) override; private: diff --git a/src/common/system/system_windows.cpp b/src/common/system/system_windows.cpp index a20bfcf1..9a8e6833 100644 --- a/src/common/system/system_windows.cpp +++ b/src/common/system/system_windows.cpp @@ -115,15 +115,15 @@ std::string CSystemUtilsWindows::GetSaveDir() #else std::string savegameDir; - wchar_t* envUSERPROFILE = _wgetenv(L"USERPROFILE"); - if (envUSERPROFILE == nullptr) + auto envUSERPROFILE = GetEnvVar("USERPROFILE"); + if (envUSERPROFILE.empty()) { - GetLogger()->Warn("Unable to find directory for saves - using current directory"); - savegameDir = "./saves"; + GetLogger()->Warn("Unable to find directory for saves - using default directory"); + savegameDir = CSystemUtils::GetSaveDir(); } else { - savegameDir = UTF8_Encode(std::wstring(envUSERPROFILE)) + "\\colobot"; + savegameDir = envUSERPROFILE + "\\colobot"; } GetLogger()->Trace("Saved game files are going to %s\n", savegameDir.c_str()); @@ -131,6 +131,22 @@ std::string CSystemUtilsWindows::GetSaveDir() #endif } +std::string CSystemUtilsWindows::GetEnvVar(const std::string& name) +{ + std::wstring wname(name.begin(), name.end()); + wchar_t* envVar = _wgetenv(wname.c_str()); + if (envVar == nullptr) + { + return ""; + } + else + { + std::string var = UTF8_Encode(std::wstring(envVar)); + GetLogger()->Trace("Detected environment variable %s = %s\n", name.c_str(), var.c_str()); + return var; + } +} + void CSystemUtilsWindows::Usleep(int usec) { LARGE_INTEGER ft; diff --git a/src/common/system/system_windows.h b/src/common/system/system_windows.h index 74f02455..05f3c910 100644 --- a/src/common/system/system_windows.h +++ b/src/common/system/system_windows.h @@ -43,6 +43,8 @@ public: std::string GetSaveDir() override; + std::string GetEnvVar(const std::string& name) override; + void Usleep(int usec) override; public: