diff --git a/source/code/main.cpp b/source/code/main.cpp index 8dade9a..6fac2d7 100644 --- a/source/code/main.cpp +++ b/source/code/main.cpp @@ -17,7 +17,7 @@ You should have received a copy of the GNU General Public License along with this program. If not, see http://www.gnu.org/licenses/ Source information and contacts persons can be found at -http://blockattack.net +http://www.blockattack.net =========================================================================== */ @@ -1651,7 +1651,7 @@ int main(int argc, char* argv[]) { cout << savepath << endl; return 0; } - + OsCreateSaveFolder(); PhysFsSetSearchPath(search_paths, savepath); //Os create folders must be after the paramters because they can change the home folder PhysFsCreateFolders(); diff --git a/source/code/os.cpp b/source/code/os.cpp index bd4de29..25e7b88 100644 --- a/source/code/os.cpp +++ b/source/code/os.cpp @@ -24,41 +24,11 @@ http://blockattack.net #include "os.hpp" #include #include +#include "sago/platform_folders.h" -#ifdef __unix__ -#include -#include -#include +static sago::PlatformFolders pf; -/** - * Retrives the effective user's home dir. - * If the user is running as root we ignore the HOME environment. It works badly with sudo. - * Writing to $HOME as root implies security concerns that a multiplatform program cannot be assumed to handle. - * @return The home directory. HOME environment is respected for non-root users if it exists. - */ -static std::string getHome() { - std::string res; - int uid = getuid(); - const char* homeEnv = getenv("HOME"); - if ( uid != 0 && homeEnv) { - //We only acknowlegde HOME if not root. - res = homeEnv; - return res; - } - struct passwd* pw = getpwuid(uid); - if (!pw) { - throw std::runtime_error("Unable to get passwd struct."); - } - const char* tempRes = pw->pw_dir; - if (!tempRes) { - throw std::runtime_error("User has no home directory"); - } - res = tempRes; - return res; -} -#endif - /* *Files will be saved in: * HOME/.gamesaves/"+GAMENAME (unix) @@ -66,31 +36,6 @@ static std::string getHome() { */ #define GAMENAME "blockattack" -using namespace std; - -#ifdef _WIN32 -//Returns path to "my Documents" in windows: -string getMyDocumentsPath() { - char pszPath[MAX_PATH]; - //if (SUCCEEDED(SHGetSpecialFolderPath(nullptr, pszPath, CSIDL_PERSONAL, FALSE))) { - if (SUCCEEDED(SHGetFolderPathA(nullptr, CSIDL_PERSONAL, nullptr, 0, pszPath))) { - // pszPath is now the path that you want -#if DEBUG - cout << "MyDocuments Located: " << pszPath << endl; -#endif - string theResult= pszPath; - return theResult; - } - else { - cout << "Warning: My Documents not found!" << endl; - string theResult =""; - return theResult; - } -} - - -#endif - static std::string overrideSavePath = ""; /** @@ -103,27 +48,23 @@ std::string getPathToSaveFiles() { if (overrideSavePath.length() > 0) { return overrideSavePath; } -#ifdef __unix__ - return (std::string)getenv("HOME")+(std::string)"/.gamesaves/"+GAMENAME; -#elif _WIN32 - return getMyDocumentsPath()+(string)"/My Games/"+GAMENAME; -#else - return "."; -#endif + return pf.getSaveGamesFolder1()+"/"+GAMENAME; } void setPathToSaveFiles(const std::string& path) { overrideSavePath = path; } -std::string getPuzzleSetSavePath() { - std::string ret; +void OsCreateSaveFolder() { #if defined(__unix__) - ret = getHome()+"/.gamesaves/blockattack/puzzle.levels.save"; + std::string cmd = "mkdir -p "+getPathToSaveFiles()+"/"; + int retcode = system(cmd.c_str()); + if (retcode != 0) { + std::cerr << "Failed to create: " << getPathToSaveFiles()+"/" << std::endl; + } #elif defined(_WIN32) - ret = getMyDocumentsPath()+"/My Games/blockattack/puzzle.levels.save"; -#else - ret = "puzzle.levels.save"; + //Now for Windows NT/2k/xp/2k3 etc. + std::string tempA = getPathToSaveFiles(); + CreateDirectory(tempA.c_str(),nullptr); #endif - return ret; } diff --git a/source/code/os.hpp b/source/code/os.hpp index 7f6c478..ab99a33 100644 --- a/source/code/os.hpp +++ b/source/code/os.hpp @@ -34,4 +34,4 @@ std::string getMyDocumentsPath(); void setPathToSaveFiles(const std::string& path); -std::string getPuzzleSetSavePath(); \ No newline at end of file +void OsCreateSaveFolder(); diff --git a/source/code/sago/platform_folders.cpp b/source/code/sago/platform_folders.cpp new file mode 100644 index 0000000..4aa637d --- /dev/null +++ b/source/code/sago/platform_folders.cpp @@ -0,0 +1,350 @@ +/* + Its is under the MIT license, to encourage reuse by cut-and-paste. + + The original files are hosted here: https://github.com/sago007/PlatformFolders + + Copyright (c) 2015 Poul Sander + + 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. +*/ + +#include "platform_folders.h" +#include +#include +#include +#include +#include + +#if defined(_WIN32) +#include +#include + +#define strtok_r strtok_s + +static std::string GetWindowsFolder(int folderId, const char* errorMsg) { + char szPath[MAX_PATH]; + szPath[0] = 0; + if ( !SUCCEEDED( SHGetFolderPathA( NULL, folderId, NULL, 0, szPath ) ) ) + { + throw std::runtime_error(errorMsg); + } + return szPath; +} + +static std::string GetAppData() { + return GetWindowsFolder(CSIDL_APPDATA, "RoamingAppData could not be found"); +} + +static std::string GetAppDataCommon() { + return GetWindowsFolder(CSIDL_COMMON_APPDATA, "Common appdata could not be found"); +} + +static std::string GetAppDataLocal() { + return GetWindowsFolder(CSIDL_LOCAL_APPDATA, "LocalAppData could not be found"); +} +#elif defined(__APPLE__) +#include + +static std::string GetMacFolder(OSType folderType, const char* errorMsg) { + std::string ret; + FSRef ref; + char path[PATH_MAX]; + OSStatus err = FSFindFolder( kUserDomain, folderType, kCreateFolder, &ref ); + if (err != noErr) { + throw std::runtime_error(errorMsg); + } + FSRefMakePath( &ref, (UInt8*)&path, PATH_MAX ); + ret = path; + return ret; +} + +#else +#include +#include +#include +#include +#include +//Typically Linux. For easy reading the comments will just say Linux but should work with most *nixes + +static void throwOnRelative(const char* envName, const char* envValue) { + if (envValue[0] != '/') { + char buffer[200]; + snprintf(buffer, sizeof(buffer), "Environment \"%s\" does not start with an '/'. XDG specifies that the value must be absolute. The current value is: \"%s\"", envName, envValue); + throw std::runtime_error(buffer); + } +} + +/** + * Retrives the effective user's home dir. + * If the user is running as root we ignore the HOME environment. It works badly with sudo. + * Writing to $HOME as root implies security concerns that a multiplatform program cannot be assumed to handle. + * @return The home directory. HOME environment is respected for non-root users if it exists. + */ +static std::string getHome() { + std::string res; + int uid = getuid(); + const char* homeEnv = getenv("HOME"); + if ( uid != 0 && homeEnv) { + //We only acknowlegde HOME if not root. + res = homeEnv; + return res; + } + struct passwd *pw = getpwuid(uid); + if (!pw) { + throw std::runtime_error("Unable to get passwd struct."); + } + const char* tempRes = pw->pw_dir; + if (!tempRes) { + throw std::runtime_error("User has no home directory"); + } + res = tempRes; + return res; +} + +static std::string getLinuxFolderDefault(const char* envName, const char* defaultRelativePath) { + std::string res; + const char* tempRes = getenv(envName); + if (tempRes) { + throwOnRelative(envName, tempRes); + res = tempRes; + return res; + } + res = getHome() + "/" + defaultRelativePath; + return res; +} + +static void appendExtraFoldersTokenizer(const char* envName, const char* envValue, std::vector& folders) { + std::vector buffer(envValue, envValue + strlen(envValue)); + char *saveptr; + const char* p = strtok_r (buffer.data(), ":", &saveptr); + while (p != NULL) { + if (p[0] == '/') { + folders.push_back(p); + } + else { + //Unless the system is wrongly configured this should never happen... But of course some systems will be incorectly configured. + //The XDG documentation indicates that the folder should be ignored but that the program should continue. + std::cerr << "Skipping path \"" << p << "\" in \"" << envName << "\" because it does not start with a \"/\"" << std::endl; + } + p = strtok_r (NULL, ":", &saveptr); + } +} + +static void appendExtraFolders(const char* envName, const char* defaultValue, std::vector& folders) { + const char* envValue = getenv(envName); + if (!envValue) { + envValue = defaultValue; + } + appendExtraFoldersTokenizer(envName, envValue, folders); +} + +#endif + + +namespace sago { + +std::string getDataHome() { +#if defined(_WIN32) + return GetAppData(); +#elif defined(__APPLE__) + return GetMacFolder(kApplicationSupportFolderType, "Failed to find the Application Support Folder"); +#else + return getLinuxFolderDefault("XDG_DATA_HOME", ".local/share"); +#endif +} + +std::string getConfigHome() { +#if defined(_WIN32) + return GetAppData(); +#elif defined(__APPLE__) + return GetMacFolder(kApplicationSupportFolderType, "Failed to find the Application Support Folder"); +#else + return getLinuxFolderDefault("XDG_CONFIG_HOME", ".config"); +#endif +} + +std::string getCacheDir() { +#if defined(_WIN32) + return GetAppDataLocal(); +#elif defined(__APPLE__) + return GetMacFolder(kCachedDataFolderType, "Failed to find the Application Support Folder"); +#else + return getLinuxFolderDefault("XDG_CONFIG_HOME", ".cache"); +#endif +} + +void appendAdditionalDataDirectories(std::vector& homes) { +#if defined(_WIN32) + homes.push_back(GetAppDataCommon()); +#elif defined(__APPLE__) +#else + appendExtraFolders("XDG_DATA_DIRS", "/usr/local/share/:/usr/share/", homes); +#endif +} + +void appendAdditionalConfigDirectories(std::vector& homes) { +#if defined(_WIN32) + homes.push_back(GetAppDataCommon()); +#elif defined(__APPLE__) +#else + appendExtraFolders("XDG_CONFIG_DIRS", "/etc/xdg", homes); +#endif +} + +#if defined(_WIN32) +#elif defined(__APPLE__) +#else +struct PlatformFolders::PlatformFoldersData { + std::map folders; +}; + +static void PlatformFoldersAddFromFile(const std::string& filename, std::map& folders) { + std::ifstream infile(filename.c_str()); + std::string line; + while (std::getline(infile, line)) { + if (line.length() == 0 || line.at(0) == '#') { + continue; + } + std::size_t splitPos = line.find("="); + std::string key = line.substr(0, splitPos); + std::string value = line.substr(splitPos+2, line.length()-splitPos-3); + folders[key] = value; + //std::cout << key << " : " << value << std::endl; + } +} + +static void PlatformFoldersFillData(std::map& folders) { + folders["XDG_DOCUMENTS_DIR"] = "$HOME/Documents"; + folders["XDG_DESKTOP_DIR"] = "$HOME/Desktop"; + folders["XDG_DOWNLOAD_DIR"] = "$HOME/Downloads"; + folders["XDG_MUSIC_DIR"] = "$HOME/Music"; + folders["XDG_PICTURES_DIR"] = "$HOME/Pictures"; + folders["XDG_PUBLICSHARE_DIR"] = "$HOME/Public"; + folders["XDG_TEMPLATES_DIR"] = "$HOME/.Templates"; + folders["XDG_VIDEOS_DIR"] = "$HOME/Videos"; + PlatformFoldersAddFromFile( getConfigHome()+"/user-dirs.dirs", folders); + for (std::map::iterator itr = folders.begin() ; itr != folders.end() ; itr ++ ) { + std::string& value = itr->second; + if (value.compare(0, 5, "$HOME") == 0) { + value = getHome() + value.substr(5, std::string::npos); + } + } +} +#endif + +PlatformFolders::PlatformFolders() { +#if defined(_WIN32) +#elif defined(__APPLE__) +#else + this->data = new PlatformFolders::PlatformFoldersData(); + try { + PlatformFoldersFillData(data->folders); + } catch (...) { + delete this->data; + throw; + } +#endif +} + +PlatformFolders::~PlatformFolders() { +#if defined(_WIN32) +#elif defined(__APPLE__) +#else + delete this->data; +#endif +} + +std::string PlatformFolders::getDocumentsFolder() const { +#if defined(_WIN32) + return GetWindowsFolder(CSIDL_PERSONAL, "Failed to find My Documents folder"); +#elif defined(__APPLE__) + return GetMacFolder(kDocumentsFolderType, "Failed to find Documents Folder"); +#else + return data->folders["XDG_DOCUMENTS_DIR"]; +#endif +} + +std::string PlatformFolders::getDesktopFolder() const { +#if defined(_WIN32) + return GetWindowsFolder(CSIDL_DESKTOP, "Failed to find Desktop folder"); +#elif defined(__APPLE__) + return GetMacFolder(kDesktopFolderType, "Failed to find Desktop folder"); +#else + return data->folders["XDG_DESKTOP_DIR"]; +#endif +} + +std::string PlatformFolders::getPicturesFolder() const { +#if defined(_WIN32) + return GetWindowsFolder(CSIDL_MYPICTURES, "Failed to find My Pictures folder"); +#elif defined(__APPLE__) + return GetMacFolder(kPictureDocumentsFolderType, "Failed to find Picture folder"); +#else + return data->folders["XDG_PICTURES_DIR"]; +#endif +} + +std::string PlatformFolders::getDownloadFolder1() const { +#if defined(_WIN32) + //Pre Vista. Files was downloaded to the desktop + return GetWindowsFolder(CSIDL_DESKTOP, "Failed to find My Downloads (Desktop) folder"); +#elif defined(__APPLE__) + return GetMacFolder(kDownloadsFolderType, "Failed to find Download folder"); +#else + return data->folders["XDG_DOWNLOAD_DIR"]; +#endif +} + +std::string PlatformFolders::getMusicFolder() const { +#if defined(_WIN32) + return GetWindowsFolder(CSIDL_MYMUSIC, "Failed to find My Music folder"); +#elif defined(__APPLE__) + return GetMacFolder(kMusicDocumentsFolderType, "Failed to find Music folder"); +#else + return data->folders["XDG_MUSIC_DIR"]; +#endif +} + +std::string PlatformFolders::getVideoFolder() const { +#if defined(_WIN32) + return GetWindowsFolder(CSIDL_MYVIDEO, "Failed to find My Video folder"); +#elif defined(__APPLE__) + return GetMacFolder(kMovieDocumentsFolderType, "Failed to find Movie folder"); +#else + return data->folders["XDG_VIDEOS_DIR"]; +#endif +} + +std::string PlatformFolders::getSaveGamesFolder1() const { +#if defined(_WIN32) + //A dedicated Save Games folder was not introduced until Vista. For XP and older save games are most often saved in a normal folder named "My Games". + //Data that should not be user accessible should be placed under GetDataHome() instead + return GetWindowsFolder(CSIDL_PERSONAL, "Failed to find My Documents folder")+"\\My Games"; +#elif defined(__APPLE__) + return GetMacFolder(kApplicationSupportFolderType, "Failed to find Application Support Folder"); +#else + return getDataHome(); +#endif +} + + + +} //namespace sago diff --git a/source/code/sago/platform_folders.h b/source/code/sago/platform_folders.h new file mode 100644 index 0000000..fddee5c --- /dev/null +++ b/source/code/sago/platform_folders.h @@ -0,0 +1,172 @@ +/* + Its is under the MIT license, to encourage reuse by cut-and-paste. + + The original files are hosted here: https://github.com/sago007/PlatformFolders + + Copyright (c) 2015 Poul Sander + + 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. +*/ + +#ifndef SAGO_PLATFORM_FOLDERS_H +#define SAGO_PLATFORM_FOLDERS_H + +#include +#include + +/** + * The namespace I use for common function. Nothing special about it. + */ +namespace sago { + +/** + * Retrives the base folder for storring data files. + * You must add the program name yourself like this: + * @code{.cpp} + * string data_home = getDataHome()+"/My Program Name/"; + * @endcode + * On Windows this defaults to %APPDATA% (Roaming profile) + * On Linux this defaults to ~/.local/share but can be configured + * @return The base folder for storring program data. + */ +std::string getDataHome(); +/** + * Retrives the base folder for storring config files. + * You must add the program name yourself like this: + * @code{.cpp} + * string data_home = getConfigHome()+"/My Program Name/"; + * @endcode + * On Windows this defaults to %APPDATA% (Roaming profile) + * On Linux this defaults to ~/.config but can be configured + * @return The base folder for storring config data. + */ +std::string getConfigHome(); +/** + * Retrives the base folder for storring cache files. + * You must add the program name yourself like this: + * @code{.cpp} + * string data_home = getCacheDir()+"/My Program Name/"; + * @endcode + * On Windows this defaults to %APPDATALOCAL% + * On Linux this defaults to ~/.cache but can be configured + * @return The base folder for storring data that do not need to be backed up. + */ +std::string getCacheDir(); +/** + * This will append extra folders that your program should be looking for data files in. + * This does not normally include the path returned by GetDataHome(). + * If you want all the folders you should do something like: + * @code{.cpp} + * vector folders; + * folders.push_back(getDataHome()); + * appendAdditionalDataDirectories(folders); + * for (string s& : folders) { + * s+="/My Program Name/"; + * } + * @endcode + * You must apply "/My Program Name/" to all the strings. + * The string at the lowest index has the highest priority. + * @param homes A vector that extra folders will be appended to. + */ +void appendAdditionalDataDirectories(std::vector& homes); +/** + * This will append extra folders that your program should be looking for config files in. + * This does not normally include the path returned by GetConfigHome(). + * If you want all the folders you should do something like: + * @code{.cpp} + * vector folders; + * folders.push_back(getConfigHome()); + * appendAdditionalConfigDirectories(folders); + * for (string s& : folders) { + * s+="/My Program Name/"; + * } + * @endcode + * You must apply "/My Program Name/" to all the strings. + * The string at the lowest index has the highest priority. + * @param homes A vector that extra folders will be appended to. + */ +void appendAdditionalConfigDirectories(std::vector& homes); + +/** + * This class contains methods for finding the system depended special folders. + * For Windows these folders are either by convention or given by CSIDL. + * For Linux XDG convention is used. + * The Linux version has very little error checking and assumes that the config is correct + */ +class PlatformFolders { +public: + PlatformFolders(); + ~PlatformFolders(); + /** + * The folder that represents the desktop. + * Normally you should try not to use this folder. + * @return Absolute path to the user's desktop + */ + std::string getDesktopFolder() const; + /** + * The folder to store user documents to + * @return Absolute path to the "Documents" folder + */ + std::string getDocumentsFolder() const; + /** + * The folder for storring the user's pictures. + * @return Absolute path to the "Picture" folder + */ + std::string getPicturesFolder() const; + /** + * The folder where files are downloaded. + * @note Windows: This version is XP compatible and returns the Desktop. Vista and later has a dedicated folder. + * @return Absolute path to the folder where files are downloaded to. + */ + std::string getDownloadFolder1() const; + /** + * The folder where music is stored + * @return Absolute path to the music folder + */ + std::string getMusicFolder() const; + /** + * The folder where video is stored + * @return Absolute path to the video folder + */ + std::string getVideoFolder() const; + /** + * The base folder for storring saved games. + * You must add the program name to it like this: + * @code{.cpp} + * PlatformFolders pf; + * string saved_games_folder = pf.getSaveGamesFolder1()+"/My Program Name/"; + * @endcode + * @note Windows: This is an XP compatible version and returns the path to "My Games" in Documents. Vista and later has an official folder. + * @note Linux: XDF does not define a folder for saved games. This will just return the same as GetDataHome() + * @return The folder base folder for storring save games. + */ + std::string getSaveGamesFolder1() const; +private: + PlatformFolders(const PlatformFolders&); + PlatformFolders& operator=(const PlatformFolders&); + struct PlatformFoldersData; + mutable PlatformFoldersData *data; +}; + +} //namespace sago + +#endif /* PLATFORM_FOLDERS_H */ +