//========= Copyright Valve Corporation ============// #include "compat.h" #include "strtools.h" #include "pathtools.h" #if defined(_WIN32) #include #include #include #include #include #include #undef GetEnvironmentVariable #else #include #include #include #include #endif #if defined OSX #include #include #include #define _S_IFDIR S_IFDIR // really from tier0/platform.h which we dont have yet #endif #include #include /** Returns the path (including filename) to the current executable */ std::string Path_GetExecutablePath() { #if defined(_WIN32) wchar_t *pwchPath = new wchar_t[MAX_UNICODE_PATH]; char *pchPath = new char[MAX_UNICODE_PATH_IN_UTF8]; ::GetModuleFileNameW(NULL, pwchPath, MAX_UNICODE_PATH); WideCharToMultiByte(CP_UTF8, 0, pwchPath, -1, pchPath, MAX_UNICODE_PATH_IN_UTF8, NULL, NULL); delete[] pwchPath; std::string sPath = pchPath; delete[] pchPath; return sPath; #elif defined(OSX) char rchPath[1024]; uint32_t nBuff = sizeof(rchPath); bool bSuccess = _NSGetExecutablePath(rchPath, &nBuff) == 0; rchPath[nBuff - 1] = '\0'; if (bSuccess) return rchPath; else return ""; #elif defined LINUX char rchPath[1024]; size_t nBuff = sizeof(rchPath); ssize_t nRead = readlink("/proc/self/exe", rchPath, nBuff - 1); if (nRead != -1) { rchPath[nRead] = 0; return rchPath; } else { return ""; } #else AssertMsg(false, "Implement Plat_GetExecutablePath"); return ""; #endif } /** Returns the path of the current working directory */ std::string Path_GetWorkingDirectory() { std::string sPath; #if defined(_WIN32) wchar_t buf[MAX_UNICODE_PATH]; sPath = UTF16to8(_wgetcwd(buf, MAX_UNICODE_PATH)); #else char buf[1024]; sPath = getcwd(buf, sizeof(buf)); #endif return sPath; } /** Sets the path of the current working directory. Returns true if this was successful. */ bool Path_SetWorkingDirectory(const std::string &sPath) { bool bSuccess; #if defined(_WIN32) std::wstring wsPath = UTF8to16(sPath.c_str()); bSuccess = 0 == _wchdir(wsPath.c_str()); #else bSuccess = 0 == chdir(sPath.c_str()); #endif return bSuccess; } /** Returns the specified path without its filename */ std::string Path_StripFilename(const std::string &sPath, char slash) { if (slash == 0) slash = Path_GetSlash(); std::string::size_type n = sPath.find_last_of(slash); if (n == std::string::npos) return sPath; else return std::string(sPath.begin(), sPath.begin() + n); } /** returns just the filename from the provided full or relative path. */ std::string Path_StripDirectory(const std::string &sPath, char slash) { if (slash == 0) slash = Path_GetSlash(); std::string::size_type n = sPath.find_last_of(slash); if (n == std::string::npos) return sPath; else return std::string(sPath.begin() + n + 1, sPath.end()); } /** returns just the filename with no extension of the provided filename. * If there is a path the path is left intact. */ std::string Path_StripExtension(const std::string &sPath) { for (std::string::const_reverse_iterator i = sPath.rbegin(); i != sPath.rend(); i++) { if (*i == '.') { return std::string(sPath.begin(), i.base() - 1); } // if we find a slash there is no extension if (*i == '\\' || *i == '/') break; } // we didn't find an extension return sPath; } /** returns just extension of the provided filename (if any). */ std::string Path_GetExtension(const std::string &sPath) { for (std::string::const_reverse_iterator i = sPath.rbegin(); i != sPath.rend(); i++) { if (*i == '.') { return std::string(i.base(), sPath.end()); } // if we find a slash there is no extension if (*i == '\\' || *i == '/') break; } // we didn't find an extension return ""; } bool Path_IsAbsolute(const std::string &sPath) { if (sPath.empty()) return false; #if defined(WIN32) if (sPath.size() < 3) // must be c:\x or \\x at least return false; if (sPath[1] == ':') // drive letter plus slash, but must test both slash cases { if (sPath[2] == '\\' || sPath[2] == '/') return true; } else if (sPath[0] == '\\' && sPath[1] == '\\') // UNC path return true; #else if (sPath[0] == '\\' || sPath[0] == '/') // any leading slash return true; #endif return false; } /** Makes an absolute path from a relative path and a base path */ std::string Path_MakeAbsolute(const std::string &sRelativePath, const std::string &sBasePath, char slash) { if (slash == 0) slash = Path_GetSlash(); if (Path_IsAbsolute(sRelativePath)) return sRelativePath; else { if (!Path_IsAbsolute(sBasePath)) return ""; std::string sCompacted = Path_Compact(Path_Join(sBasePath, sRelativePath, slash), slash); if (Path_IsAbsolute(sCompacted)) return sCompacted; else return ""; } } /** Fixes the directory separators for the current platform */ std::string Path_FixSlashes(const std::string &sPath, char slash) { if (slash == 0) slash = Path_GetSlash(); std::string sFixed = sPath; for (std::string::iterator i = sFixed.begin(); i != sFixed.end(); i++) { if (*i == '/' || *i == '\\') *i = slash; } return sFixed; } char Path_GetSlash() { #if defined(_WIN32) return '\\'; #else return '/'; #endif } /** Jams two paths together with the right kind of slash */ std::string Path_Join(const std::string &first, const std::string &second, char slash) { if (slash == 0) slash = Path_GetSlash(); // only insert a slash if we don't already have one std::string::size_type nLen = first.length(); if (!nLen) return second; #if defined(_WIN32) if (first.back() == '\\' || first.back() == '/') nLen--; #else char last_char = first[first.length() - 1]; if (last_char == '\\' || last_char == '/') nLen--; #endif return first.substr(0, nLen) + std::string(1, slash) + second; } std::string Path_Join(const std::string &first, const std::string &second, const std::string &third, char slash) { return Path_Join(Path_Join(first, second, slash), third, slash); } std::string Path_Join(const std::string &first, const std::string &second, const std::string &third, const std::string &fourth, char slash) { return Path_Join(Path_Join(Path_Join(first, second, slash), third, slash), fourth, slash); } std::string Path_Join( const std::string &first, const std::string &second, const std::string &third, const std::string &fourth, const std::string &fifth, char slash) { return Path_Join(Path_Join(Path_Join(Path_Join(first, second, slash), third, slash), fourth, slash), fifth, slash); } std::string Path_RemoveTrailingSlash(const std::string &sRawPath, char slash) { if (slash == 0) slash = Path_GetSlash(); std::string sPath = sRawPath; std::string::size_type nCurrent = sRawPath.length(); if (nCurrent == 0) return sPath; int nLastFound = -1; nCurrent--; while (nCurrent != 0) { if (sRawPath[nCurrent] == slash) { nLastFound = (int)nCurrent; nCurrent--; } else { break; } } if (nLastFound >= 0) { sPath.erase(nLastFound, std::string::npos); } return sPath; } /** Removes redundant /.. elements in the path. Returns an empty path if the * specified path has a broken number of directories for its number of ..s */ std::string Path_Compact(const std::string &sRawPath, char slash) { if (slash == 0) slash = Path_GetSlash(); std::string sPath = Path_FixSlashes(sRawPath, slash); std::string sSlashString(1, slash); // strip out all /./ for (std::string::size_type i = 0; (i + 3) < sPath.length();) { if (sPath[i] == slash && sPath[i + 1] == '.' && sPath[i + 2] == slash) { sPath.replace(i, 3, sSlashString); } else { ++i; } } // get rid of trailing /. but leave the path separator if (sPath.length() > 2) { std::string::size_type len = sPath.length(); if (sPath[len - 1] == '.' && sPath[len - 2] == slash) { // sPath.pop_back(); sPath[len - 1] = 0; // for now, at least } } // get rid of leading ./ if (sPath.length() > 2) { if (sPath[0] == '.' && sPath[1] == slash) { sPath.replace(0, 2, ""); } } // each time we encounter .. back up until we've found the previous directory name // then get rid of both std::string::size_type i = 0; while (i < sPath.length()) { if (i > 0 && sPath.length() - i >= 2 && sPath[i] == '.' && sPath[i + 1] == '.' && (i + 2 == sPath.length() || sPath[i + 2] == slash) && sPath[i - 1] == slash) { // check if we've hit the start of the string and have a bogus path if (i == 1) return ""; // find the separator before i-1 std::string::size_type iDirStart = i - 2; while (iDirStart > 0 && sPath[iDirStart - 1] != slash) --iDirStart; // remove everything from iDirStart to i+2 sPath.replace(iDirStart, (i - iDirStart) + 3, ""); // start over i = 0; } else { ++i; } } return sPath; } /** Returns the path to the current DLL or exe */ std::string Path_GetThisModulePath() { // gets the path of vrclient.dll itself #ifdef WIN32 HMODULE hmodule = NULL; ::GetModuleHandleEx(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS | GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT, reinterpret_cast(Path_GetThisModulePath), &hmodule); wchar_t *pwchPath = new wchar_t[MAX_UNICODE_PATH]; char *pchPath = new char[MAX_UNICODE_PATH_IN_UTF8]; ::GetModuleFileNameW(hmodule, pwchPath, MAX_UNICODE_PATH); WideCharToMultiByte(CP_UTF8, 0, pwchPath, -1, pchPath, MAX_UNICODE_PATH_IN_UTF8, NULL, NULL); delete[] pwchPath; std::string sPath = pchPath; delete[] pchPath; return sPath; #elif defined(OSX) || defined(LINUX) // get the addr of a function in vrclient.so and then ask the dlopen system about it Dl_info info; dladdr((void *)Path_GetThisModulePath, &info); return info.dli_fname; #endif } /** returns true if the specified path exists and is a directory */ bool Path_IsDirectory(const std::string &sPath) { std::string sFixedPath = Path_FixSlashes(sPath); if (sFixedPath.empty()) return false; char cLast = sFixedPath[sFixedPath.length() - 1]; if (cLast == '/' || cLast == '\\') sFixedPath.erase(sFixedPath.end() - 1, sFixedPath.end()); // see if the specified path actually exists. #if defined(POSIX) struct stat buf; if (stat(sFixedPath.c_str(), &buf) == -1) { return false; } #if defined(LINUX) || defined(OSX) return S_ISDIR(buf.st_mode); #else return (buf.st_mode & _S_IFDIR) != 0; #endif #else struct _stat buf; std::wstring wsFixedPath = UTF8to16(sFixedPath.c_str()); if (_wstat(wsFixedPath.c_str(), &buf) == -1) { return false; } return (buf.st_mode & _S_IFDIR) != 0; #endif } /** returns true if the specified path represents an app bundle */ bool Path_IsAppBundle(const std::string &sPath) { #if defined(OSX) NSBundle *bundle = [NSBundle bundleWithPath:[NSString stringWithUTF8String:sPath.c_str()]]; bool bisAppBundle = (nullptr != bundle); [bundle release]; return bisAppBundle; #else return false; #endif } //----------------------------------------------------------------------------- // Purpose: returns true if the the path exists //----------------------------------------------------------------------------- bool Path_Exists(const std::string &sPath) { std::string sFixedPath = Path_FixSlashes(sPath); if (sFixedPath.empty()) return false; #if defined(WIN32) struct _stat buf; std::wstring wsFixedPath = UTF8to16(sFixedPath.c_str()); if (_wstat(wsFixedPath.c_str(), &buf) == -1) { return false; } #else struct stat buf; if (stat(sFixedPath.c_str(), &buf) == -1) { return false; } #endif return true; } //----------------------------------------------------------------------------- // Purpose: helper to find a directory upstream from a given path //----------------------------------------------------------------------------- std::string Path_FindParentDirectoryRecursively(const std::string &strStartDirectory, const std::string &strDirectoryName) { std::string strFoundPath = ""; std::string strCurrentPath = Path_FixSlashes(strStartDirectory); if (strCurrentPath.length() == 0) return ""; bool bExists = Path_Exists(strCurrentPath); std::string strCurrentDirectoryName = Path_StripDirectory(strCurrentPath); if (bExists && stricmp(strCurrentDirectoryName.c_str(), strDirectoryName.c_str()) == 0) return strCurrentPath; while (bExists && strCurrentPath.length() != 0) { strCurrentPath = Path_StripFilename(strCurrentPath); strCurrentDirectoryName = Path_StripDirectory(strCurrentPath); bExists = Path_Exists(strCurrentPath); if (bExists && stricmp(strCurrentDirectoryName.c_str(), strDirectoryName.c_str()) == 0) return strCurrentPath; } return ""; } //----------------------------------------------------------------------------- // Purpose: helper to find a subdirectory upstream from a given path //----------------------------------------------------------------------------- std::string Path_FindParentSubDirectoryRecursively(const std::string &strStartDirectory, const std::string &strDirectoryName) { std::string strFoundPath = ""; std::string strCurrentPath = Path_FixSlashes(strStartDirectory); if (strCurrentPath.length() == 0) return ""; bool bExists = Path_Exists(strCurrentPath); while (bExists && strCurrentPath.length() != 0) { strCurrentPath = Path_StripFilename(strCurrentPath); bExists = Path_Exists(strCurrentPath); if (Path_Exists(Path_Join(strCurrentPath, strDirectoryName))) { strFoundPath = Path_Join(strCurrentPath, strDirectoryName); break; } } return strFoundPath; } //----------------------------------------------------------------------------- // Purpose: reading and writing files in the vortex directory //----------------------------------------------------------------------------- unsigned char *Path_ReadBinaryFile(const std::string &strFilename, int *pSize) { FILE *f; #if defined(POSIX) f = fopen(strFilename.c_str(), "rb"); #else std::wstring wstrFilename = UTF8to16(strFilename.c_str()); // the open operation needs to be sharable, therefore use of _wfsopen instead of _wfopen_s f = _wfsopen(wstrFilename.c_str(), L"rb", _SH_DENYNO); #endif unsigned char *buf = NULL; if (f != NULL) { fseek(f, 0, SEEK_END); int size = ftell(f); fseek(f, 0, SEEK_SET); buf = new unsigned char[size]; if (buf && fread(buf, size, 1, f) == 1) { if (pSize) *pSize = size; } else { delete[] buf; buf = 0; } fclose(f); } return buf; } uint32_t Path_ReadBinaryFile(const std::string &strFilename, unsigned char *pBuffer, uint32_t unSize) { FILE *f; #if defined(POSIX) f = fopen(strFilename.c_str(), "rb"); #else std::wstring wstrFilename = UTF8to16(strFilename.c_str()); errno_t err = _wfopen_s(&f, wstrFilename.c_str(), L"rb"); if (err != 0) { f = NULL; } #endif uint32_t unSizeToReturn = 0; if (f != NULL) { fseek(f, 0, SEEK_END); uint32_t size = (uint32_t)ftell(f); fseek(f, 0, SEEK_SET); if (size > unSize || !pBuffer) { unSizeToReturn = (uint32_t)size; } else { if (fread(pBuffer, size, 1, f) == 1) { unSizeToReturn = (uint32_t)size; } } fclose(f); } return unSizeToReturn; } bool Path_WriteBinaryFile(const std::string &strFilename, unsigned char *pData, unsigned nSize) { FILE *f; #if defined(POSIX) f = fopen(strFilename.c_str(), "wb"); #else std::wstring wstrFilename = UTF8to16(strFilename.c_str()); errno_t err = _wfopen_s(&f, wstrFilename.c_str(), L"wb"); if (err != 0) { f = NULL; } #endif size_t written = 0; if (f != NULL) { written = fwrite(pData, sizeof(unsigned char), nSize, f); fclose(f); } return written = nSize ? true : false; } std::string Path_ReadTextFile(const std::string &strFilename) { // doing it this way seems backwards, but I don't // see an easy way to do this with C/C++ style IO // that isn't worse... int size; unsigned char *buf = Path_ReadBinaryFile(strFilename, &size); if (!buf) return ""; // convert CRLF -> LF size_t outsize = 1; for (int i = 1; i < size; i++) { if (buf[i] == '\n' && buf[i - 1] == '\r') // CRLF buf[outsize - 1] = '\n'; // ->LF else buf[outsize++] = buf[i]; // just copy } std::string ret((char *)buf, outsize); delete[] buf; return ret; } bool Path_WriteStringToTextFile(const std::string &strFilename, const char *pchData) { FILE *f; #if defined(POSIX) f = fopen(strFilename.c_str(), "w"); #else std::wstring wstrFilename = UTF8to16(strFilename.c_str()); errno_t err = _wfopen_s(&f, wstrFilename.c_str(), L"w"); if (err != 0) { f = NULL; } #endif bool ok = false; if (f != NULL) { ok = fputs(pchData, f) >= 0; fclose(f); } return ok; } bool Path_WriteStringToTextFileAtomic(const std::string &strFilename, const char *pchData) { std::string strTmpFilename = strFilename + ".tmp"; if (!Path_WriteStringToTextFile(strTmpFilename, pchData)) return false; // Platform specific atomic file replacement #if defined(_WIN32) std::wstring wsFilename = UTF8to16(strFilename.c_str()); std::wstring wsTmpFilename = UTF8to16(strTmpFilename.c_str()); if (!::ReplaceFileW(wsFilename.c_str(), wsTmpFilename.c_str(), nullptr, 0, 0, 0)) { // if we couldn't ReplaceFile, try a non-atomic write as a fallback if (!Path_WriteStringToTextFile(strFilename, pchData)) return false; } #elif defined(POSIX) if (rename(strTmpFilename.c_str(), strFilename.c_str()) == -1) return false; #else #error Do not know how to write atomic file #endif return true; } #if defined(WIN32) #define FILE_URL_PREFIX "file:///" #else #define FILE_URL_PREFIX "file://" #endif // ---------------------------------------------------------------------------------------------------------------------------- // Purpose: Turns a path to a file on disk into a URL (or just returns the value if it's already a URL) // ---------------------------------------------------------------------------------------------------------------------------- std::string Path_FilePathToUrl(const std::string &sRelativePath, const std::string &sBasePath) { if (!strnicmp(sRelativePath.c_str(), "http://", 7) || !strnicmp(sRelativePath.c_str(), "https://", 8) || !strnicmp(sRelativePath.c_str(), "file://", 7)) { return sRelativePath; } else { std::string sAbsolute = Path_MakeAbsolute(sRelativePath, sBasePath); if (sAbsolute.empty()) return sAbsolute; return std::string(FILE_URL_PREFIX) + sAbsolute; } } // ----------------------------------------------------------------------------------------------------- // Purpose: Strips off file:// off a URL and returns the path. For other kinds of URLs an empty string is returned // ----------------------------------------------------------------------------------------------------- std::string Path_UrlToFilePath(const std::string &sFileUrl) { if (!strnicmp(sFileUrl.c_str(), FILE_URL_PREFIX, strlen(FILE_URL_PREFIX))) { return sFileUrl.c_str() + strlen(FILE_URL_PREFIX); } else { return ""; } } // ----------------------------------------------------------------------------------------------------- // Purpose: Returns the root of the directory the system wants us to store user documents in // ----------------------------------------------------------------------------------------------------- std::string GetUserDocumentsPath() { #if defined(WIN32) WCHAR rwchPath[MAX_PATH]; if (!SUCCEEDED(SHGetFolderPathW(NULL, CSIDL_MYDOCUMENTS | CSIDL_FLAG_CREATE, NULL, 0, rwchPath))) { return ""; } // Convert the path to UTF-8 and store in the output std::string sUserPath = UTF16to8(rwchPath); return sUserPath; #elif defined(OSX) @autoreleasepool { NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES); if ([paths count] == 0) { return ""; } return [[paths objectAtIndex:0] UTF8String]; } #elif defined(LINUX) // @todo: not solved/changed as part of OSX - still not real - just removed old class based steam cut and paste const char *pchHome = getenv("HOME"); if (pchHome == NULL) { return ""; } return pchHome; #endif }