/* SPDX-License-Identifier: MIT License Copyright © 2021 Thomas “illwieckz” Debesse Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /* transformpath: transform file path based on keywords. This is not an environment variable parser, this only supports a set of keywords using common environment name syntax when it exists to make the strings easier to read. Supported substitution keywords, Windows: - %HOMEPATH% - %USERPROFILE% - %ProgramFiles% - %ProgramFiles(x86)% - %ProgramW6432% - %APPDATA% - [CSIDL_MYDOCUMENTS] Supported substitution keywords, Linux, FreeBSD, macOS: - ~ - ${HOME} Supported substitution keywords, Linux, FreeBSD, other XDG systems: - ${XDG_CONFIG_HOME} - ${XDG_DATA_HOME} Examples, game engine directories: - Windows: %ProgramFiles%\Unvanquished - Linux: ${XDG_DATA_HOME}/unvanquished/base - macOS: ${HOME}/Games/Unvanquished Examples, game home directories: - Windows: [CSIDL_MYDOCUMENTS]\My Games\Unvanquished - Linux: ${XDG_DATA_HOME}/unvanquished - macOS: ${HOME}/Application Data/Unvanquished */ #include "globaldefs.h" #include "stringio.h" #include #include #if GDEF_OS_WINDOWS #include #include #include #pragma comment(lib, "shell32.lib") #endif // !GDEF_OS_WINDOWS #if GDEF_OS_LINUX || GDEF_OS_BSD || GDEF_OS_MACOS #include #include #include #endif // !GDEF_OS_LINUX && !GDEF_OS_BSD && !GDEF_OS_MACOS #if GDEF_OS_WINDOWS static std::string getUserProfilePath(); #endif // !GDEF_OS_WINDOWS static std::string getUserName() { #if GDEF_OS_WINDOWS std::string path( getenv( "USERNAME" ) ); if ( ! path.empty() ) { return path; } globalErrorStream() << "\%USERNAME\% not found.\n"; return ""; #endif // !GDEF_OS_WINDOWS #if GDEF_OS_LINUX || GDEF_OS_BSD || GDEF_OS_MACOS std::string path( getenv( "USERNAME" ) ); if ( ! path.empty() ) { return path; } globalErrorStream() << "${USERNAME} not found, guessing…\n"; path = std::string( getenv( "LOGNAME" ) ); if ( ! path.empty() ) { return path; } globalErrorStream() << "${LOGNAME} not found, guessing…\n"; path = std::string( getenv( "USER" ) ); if ( ! path.empty() ) { return path; } globalErrorStream() << "${USER} not found.\n"; return ""; #endif // !GDEF_OS_LINUX && !GDEF_OS_BSD && !GDEF_OS_MACOS } static std::string getHomePath() { #if GDEF_OS_WINDOWS std::string path( getenv( "HOMEPATH" ) ); if ( ! path.empty() ) { return path; } globalErrorStream() << "\%HOMEPATH\% not found, guessing…\n"; std::string path1 = getUserProfilePath(); if ( ! path1.empty() ) { return path1; } globalErrorStream() << "\%HOMEPATH\% not found.\n"; return ""; #endif // !GDEF_OS_WINDOWS #if GDEF_OS_LINUX || GDEF_OS_BSD || GDEF_OS_MACOS // Get the path environment variable. std::string path( getenv( "HOME" ) ); // Look up path directory in password database. if( ! path.empty() ) { return path; } globalErrorStream() << "${HOME} not found, guessing…\n"; static char buf[ 4096 ]; struct passwd pw, *pwp; if ( getpwuid_r( getuid(), &pw, buf, sizeof( buf ), &pwp ) == 0 ) { return std::string( pw.pw_dir ); } globalErrorStream() << "${HOME} not found, guessing…\n"; std::string path1( "/home/" ); std::string path2 = getUserName(); if ( ! path2.empty() ) { return path1 + path2; } globalErrorStream() << "${HOME} not found…\n"; return ""; #endif // !GDEF_OS_LINUX && !GDEF_OS_BSD && !GDEF_OS_MACOS } #if GDEF_OS_WINDOWS static std::string getSystemDrive() { std::string path( getenv( "SYSTEMDRIVE" ) ); if ( ! path.empty() ) { return path; } globalErrorStream() << "\%SYSTEMDRIVE\% not found, guessing…\n"; return "C:"; } static std::string getUserProfilePath() { std::string path( getenv( "USERPROFILE" ) ); if ( ! path.empty() ) { return path; } globalErrorStream() << "\%USERPROFILE\% not found, guessing…\n"; std::string path1 = getSystemDrive(); std::string path2 = getUserName(); if ( ! path2.empty() ) { return path1 + "\\Users\\" + path2; } globalErrorStream() << "\%USERPROFILE\% not found.\n"; return ""; } static std::string getProgramFilesPath() { std::string path( getenv( "ProgramFiles" ) ); if ( ! path.empty() ) { return path; } globalErrorStream() << "\%ProgramFiles\% not found, guessing…\n"; std::string path1 = getSystemDrive(); return path1 + "\\Program Files"; } static std::string getProgramFilesX86Path() { std::string path( getenv( "ProgramFiles(x86)" ) ); if ( ! path.empty() ) { return path; } globalErrorStream() << "\%ProgramFiles(x86)\% not found, guessing…\n"; return getProgramFilesPath(); } static std::string getProgramW6432Path() { std::string path( getenv( "ProgramW6432" ) ); if ( ! path.empty() ) { return path; } globalErrorStream() << "\%ProgramW6432\% not found, guessing…\n"; return getProgramFilesPath(); } static std::string getAppDataPath() { std::string path( getenv( "APPDATA" ) ); if ( ! path.empty() ) { return path; } globalErrorStream() << "\%APPDATA\% not found, guessing…\n"; std::string path1 = getUserProfilePath(); if ( ! path1.empty() ) { return path1 + "\\AppData\\Roaming"; } globalErrorStream() << "\%APPDATA\% not found.\n"; return std::string( "" ); } /* TODO: see also qFOLDERID_SavedGames in mainframe.cpp, HomePaths_Realise and other things like that, they look to be game paths, not NetRadiant paths. */ static std::string getMyDocumentsPath() { CHAR path[ MAX_PATH ]; HRESULT result = SHGetFolderPath(NULL, CSIDL_MYDOCUMENTS, NULL, SHGFP_TYPE_CURRENT, path); if ( result == S_OK ) { return std::string( path ); } globalErrorStream() << "[CSIDL_MYDOCUMENTS] not found, guessing…\n"; std::string path1 = getHomePath(); if ( ! path1.empty() ) { path1 += "\\Documents"; return path1; } globalErrorStream() << "[CSIDL_MYDOCUMENTS] not found.\n"; return ""; } #endif // !GDEF_OS_WINDOWS #if GDEF_OS_XDG static std::string getXdgConfigHomePath() { /* FIXME: we may want to rely on g_get_user_config_dir() provided by GLib. */ std::string path ( getenv( "XDG_CONFIG_HOME" ) ); if ( ! path.empty() ) { return path; } // This is not an error. // globalErrorStream() << "${XDG_CONFIG_HOME} not found, guessing…\n"; std::string path1 = getHomePath(); if ( ! path1.empty() ) { return path1 + "/.config"; } globalErrorStream() << "${XDG_CONFIG_HOME} not found.\n"; return ""; } static std::string getXdgDataHomePath() { std::string path ( getenv( "XDG_DATA_HOME" ) ); if ( ! path.empty() ) { return path; } // This is not an error. // globalErrorStream() << "${XDG_DATA_HOME} not found, guessing…\n"; std::string path1 = getHomePath(); if ( ! path1.empty() ) { return path1 + "/.local/share"; } globalErrorStream() << "${XDG_DATA_HOME} not found.\n"; return ""; } #endif // GDEF_OS_XDG struct pathTransformer_t { std::string pattern; std::string ( *function )(); }; static const pathTransformer_t pathTransformers[] = { #if GDEF_OS_WINDOWS { "\%HOMEPATH\%", getHomePath }, { "\%USERPROFILE\%", getUserProfilePath }, { "\%ProgramFiles\%", getProgramFilesPath }, { "\%ProgramFiles(x86)\%", getProgramFilesX86Path }, { "\%ProgramW6432\%", getProgramW6432Path }, { "\%APPDATA\%", getAppDataPath }, { "[CSIDL_MYDOCUMENTS]", getMyDocumentsPath }, #endif // GDEF_OS_WINDOWS #if GDEF_OS_LINUX || GDEF_OS_BSD || GDEF_OS_MACOS { "~", getHomePath }, { "${HOME}", getHomePath }, #endif // GDEF_OS_LINUX || GDEF_OS_BSD || GDEF_OS_MACOS #if GDEF_OS_XDG { "${XDG_CONFIG_HOME}", getXdgConfigHomePath }, { "${XDG_DATA_HOME}", getXdgDataHomePath }, #endif // GDEF_OS_XDG }; /* If no transformation succeeds, the path will be returned untransformed. */ std::string transformPath( std::string transformedPath ) { for ( const pathTransformer_t &pathTransformer : pathTransformers ) { if ( transformedPath.find( pathTransformer.pattern, 0 ) == 0 ) { globalOutputStream() << "Path Transforming: '" << transformedPath.c_str() << "'\n"; std::string path = pathTransformer.function(); if ( ! path.empty() ) { transformedPath.replace( 0, pathTransformer.pattern.length(), path ); globalOutputStream() << "Path Transformed: '" << transformedPath.c_str() << "'\n"; return transformedPath; } break; } } globalErrorStream() << "Path not transformed: '" << transformedPath.c_str() << "'\n"; return transformedPath; }