// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later #include #include "common/logging/log.h" #include "common/path_util.h" #include "common/scope_exit.h" #ifdef __APPLE__ #include #include #endif #ifndef MAX_PATH #ifdef _WIN32 // This is the maximum number of UTF-16 code units permissible in Windows file paths #define MAX_PATH 260 #else // This is the maximum number of UTF-8 code units permissible in all other OSes' file paths #define MAX_PATH 1024 #endif #endif namespace Common::FS { namespace fs = std::filesystem; #ifdef __APPLE__ static std::filesystem::path GetBundleParentDirectory() { if (CFBundleRef bundle_ref = CFBundleGetMainBundle()) { if (CFURLRef bundle_url_ref = CFBundleCopyBundleURL(bundle_ref)) { SCOPE_EXIT { CFRelease(bundle_url_ref); }; if (CFStringRef bundle_path_ref = CFURLCopyFileSystemPath(bundle_url_ref, kCFURLPOSIXPathStyle)) { SCOPE_EXIT { CFRelease(bundle_path_ref); }; char app_bundle_path[MAXPATHLEN]; if (CFStringGetFileSystemRepresentation(bundle_path_ref, app_bundle_path, sizeof(app_bundle_path))) { std::filesystem::path bundle_path{app_bundle_path}; return bundle_path.parent_path(); } } } } return std::filesystem::current_path(); } #endif static auto UserPaths = [] { #ifdef __APPLE__ std::filesystem::current_path(GetBundleParentDirectory()); #endif std::unordered_map paths; const auto user_dir = std::filesystem::current_path() / PORTABLE_DIR; const auto create_path = [&](PathType shad_path, const fs::path& new_path) { std::filesystem::create_directory(new_path); paths.insert_or_assign(shad_path, new_path); }; create_path(PathType::UserDir, user_dir); create_path(PathType::LogDir, user_dir / LOG_DIR); create_path(PathType::ScreenshotsDir, user_dir / SCREENSHOTS_DIR); create_path(PathType::ShaderDir, user_dir / SHADER_DIR); create_path(PathType::PM4Dir, user_dir / PM4_DIR); create_path(PathType::SaveDataDir, user_dir / SAVEDATA_DIR); create_path(PathType::GameDataDir, user_dir / GAMEDATA_DIR); create_path(PathType::TempDataDir, user_dir / TEMPDATA_DIR); create_path(PathType::SysModuleDir, user_dir / SYSMODULES_DIR); create_path(PathType::DownloadDir, user_dir / DOWNLOAD_DIR); create_path(PathType::CapturesDir, user_dir / CAPTURES_DIR); return paths; }(); bool ValidatePath(const fs::path& path) { if (path.empty()) { LOG_ERROR(Common_Filesystem, "Input path is empty, path={}", PathToUTF8String(path)); return false; } #ifdef _WIN32 if (path.u16string().size() >= MAX_PATH) { LOG_ERROR(Common_Filesystem, "Input path is too long, path={}", PathToUTF8String(path)); return false; } #else if (path.u8string().size() >= MAX_PATH) { LOG_ERROR(Common_Filesystem, "Input path is too long, path={}", PathToUTF8String(path)); return false; } #endif return true; } std::string PathToUTF8String(const std::filesystem::path& path) { const auto u8_string = path.u8string(); return std::string{u8_string.begin(), u8_string.end()}; } const fs::path& GetUserPath(PathType shad_path) { return UserPaths.at(shad_path); } std::string GetUserPathString(PathType shad_path) { return PathToUTF8String(GetUserPath(shad_path)); } void SetUserPath(PathType shad_path, const fs::path& new_path) { if (!std::filesystem::is_directory(new_path)) { LOG_ERROR(Common_Filesystem, "Filesystem object at new_path={} is not a directory", PathToUTF8String(new_path)); return; } UserPaths.insert_or_assign(shad_path, new_path); } } // namespace Common::FS