A tool for deriving PKG packet encryption keys for ps4 written in c++
| 1 | // SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project |
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later |
| 3 | |
| 4 | #include <unordered_map> |
| 5 | #include "common/logging/log.h" |
| 6 | #include "common/path_util.h" |
| 7 | #include "common/scope_exit.h" |
| 8 | |
| 9 | #ifdef __APPLE__ |
| 10 | #include <CoreFoundation/CFBundle.h> |
| 11 | #include <dlfcn.h> |
| 12 | #include <sys/param.h> |
| 13 | #endif |
| 14 | |
| 15 | #ifndef MAX_PATH |
| 16 | #ifdef _WIN32 |
| 17 | // This is the maximum number of UTF-16 code units permissible in Windows file paths |
| 18 | #define MAX_PATH 260 |
| 19 | #else |
| 20 | // This is the maximum number of UTF-8 code units permissible in all other OSes' file paths |
| 21 | #define MAX_PATH 1024 |
| 22 | #endif |
| 23 | #endif |
| 24 | |
| 25 | #ifdef ENABLE_QT_GUI |
| 26 | #include <QString> |
| 27 | #endif |
| 28 | |
| 29 | namespace Common::FS { |
| 30 | |
| 31 | namespace fs = std::filesystem; |
| 32 | |
| 33 | #ifdef __APPLE__ |
| 34 | using IsTranslocatedURLFunc = Boolean (*)(CFURLRef path, bool* isTranslocated, |
| 35 | CFErrorRef* __nullable error); |
| 36 | using CreateOriginalPathForURLFunc = CFURLRef __nullable (*)(CFURLRef translocatedPath, |
| 37 | CFErrorRef* __nullable error); |
| 38 | |
| 39 | static CFURLRef UntranslocateBundlePath(const CFURLRef bundle_path) { |
| 40 | if (void* security_handle = |
| 41 | dlopen("/System/Library/Frameworks/Security.framework/Security", RTLD_LAZY)) { |
| 42 | SCOPE_EXIT { |
| 43 | dlclose(security_handle); |
| 44 | }; |
| 45 | |
| 46 | const auto IsTranslocatedURL = reinterpret_cast<IsTranslocatedURLFunc>( |
| 47 | dlsym(security_handle, "SecTranslocateIsTranslocatedURL")); |
| 48 | const auto CreateOriginalPathForURL = reinterpret_cast<CreateOriginalPathForURLFunc>( |
| 49 | dlsym(security_handle, "SecTranslocateCreateOriginalPathForURL")); |
| 50 | |
| 51 | bool is_translocated = false; |
| 52 | if (IsTranslocatedURL && CreateOriginalPathForURL && |
| 53 | IsTranslocatedURL(bundle_path, &is_translocated, nullptr) && is_translocated) { |
| 54 | return CreateOriginalPathForURL(bundle_path, nullptr); |
| 55 | } |
| 56 | } |
| 57 | return nullptr; |
| 58 | } |
| 59 | |
| 60 | static std::filesystem::path GetBundleParentDirectory() { |
| 61 | if (CFBundleRef bundle_ref = CFBundleGetMainBundle()) { |
| 62 | if (CFURLRef bundle_url_ref = CFBundleCopyBundleURL(bundle_ref)) { |
| 63 | SCOPE_EXIT { |
| 64 | CFRelease(bundle_url_ref); |
| 65 | }; |
| 66 | |
| 67 | CFURLRef untranslocated_url_ref = UntranslocateBundlePath(bundle_url_ref); |
| 68 | SCOPE_EXIT { |
| 69 | if (untranslocated_url_ref) { |
| 70 | CFRelease(untranslocated_url_ref); |
| 71 | } |
| 72 | }; |
| 73 | |
| 74 | char app_bundle_path[MAXPATHLEN]; |
| 75 | if (CFURLGetFileSystemRepresentation( |
| 76 | untranslocated_url_ref ? untranslocated_url_ref : bundle_url_ref, true, |
| 77 | reinterpret_cast<u8*>(app_bundle_path), sizeof(app_bundle_path))) { |
| 78 | std::filesystem::path bundle_path{app_bundle_path}; |
| 79 | return bundle_path.parent_path(); |
| 80 | } |
| 81 | } |
| 82 | } |
| 83 | return std::filesystem::current_path(); |
| 84 | } |
| 85 | #endif |
| 86 | |
| 87 | static auto UserPaths = [] { |
| 88 | #ifdef __APPLE__ |
| 89 | // Set the current path to the directory containing the app bundle. |
| 90 | std::filesystem::current_path(GetBundleParentDirectory()); |
| 91 | #endif |
| 92 | |
| 93 | // Try the portable user directory first. |
| 94 | auto user_dir = std::filesystem::current_path() / PORTABLE_DIR; |
| 95 | if (!std::filesystem::exists(user_dir)) { |
| 96 | // If it doesn't exist, use the standard path for the platform instead. |
| 97 | // NOTE: On Windows we currently just create the portable directory instead. |
| 98 | #ifdef __APPLE__ |
| 99 | user_dir = |
| 100 | std::filesystem::path(getenv("HOME")) / "Library" / "Application Support" / "shadPS4"; |
| 101 | #elif defined(__linux__) |
| 102 | const char* xdg_data_home = getenv("XDG_DATA_HOME"); |
| 103 | if (xdg_data_home != nullptr && strlen(xdg_data_home) > 0) { |
| 104 | user_dir = std::filesystem::path(xdg_data_home) / "shadPS4"; |
| 105 | } else { |
| 106 | user_dir = std::filesystem::path(getenv("HOME")) / ".local" / "share" / "shadPS4"; |
| 107 | } |
| 108 | #endif |
| 109 | } |
| 110 | |
| 111 | std::unordered_map<PathType, fs::path> paths; |
| 112 | |
| 113 | const auto create_path = [&](PathType shad_path, const fs::path& new_path) { |
| 114 | // Non creare directory all'avvio: memorizziamo solo i percorsi. |
| 115 | // Le directory verranno create on-demand dove strettamente necessario. |
| 116 | paths.insert_or_assign(shad_path, new_path); |
| 117 | }; |
| 118 | |
| 119 | create_path(PathType::UserDir, user_dir); |
| 120 | create_path(PathType::LogDir, user_dir / LOG_DIR); |
| 121 | create_path(PathType::ScreenshotsDir, user_dir / SCREENSHOTS_DIR); |
| 122 | create_path(PathType::ShaderDir, user_dir / SHADER_DIR); |
| 123 | create_path(PathType::SaveDataDir, user_dir / SAVEDATA_DIR); |
| 124 | create_path(PathType::GameDataDir, user_dir / GAMEDATA_DIR); |
| 125 | create_path(PathType::TempDataDir, user_dir / TEMPDATA_DIR); |
| 126 | create_path(PathType::SysModuleDir, user_dir / SYSMODULES_DIR); |
| 127 | create_path(PathType::DownloadDir, user_dir / DOWNLOAD_DIR); |
| 128 | create_path(PathType::CapturesDir, user_dir / CAPTURES_DIR); |
| 129 | create_path(PathType::CheatsDir, user_dir / CHEATS_DIR); |
| 130 | create_path(PathType::PatchesDir, user_dir / PATCHES_DIR); |
| 131 | create_path(PathType::MetaDataDir, user_dir / METADATA_DIR); |
| 132 | |
| 133 | return paths; |
| 134 | }(); |
| 135 | |
| 136 | bool ValidatePath(const fs::path& path) { |
| 137 | if (path.empty()) { |
| 138 | LOG_ERROR(Common_Filesystem, "Input path is empty, path={}", PathToUTF8String(path)); |
| 139 | return false; |
| 140 | } |
| 141 | |
| 142 | #ifdef _WIN32 |
| 143 | if (path.u16string().size() >= MAX_PATH) { |
| 144 | LOG_ERROR(Common_Filesystem, "Input path is too long, path={}", PathToUTF8String(path)); |
| 145 | return false; |
| 146 | } |
| 147 | #else |
| 148 | if (path.u8string().size() >= MAX_PATH) { |
| 149 | LOG_ERROR(Common_Filesystem, "Input path is too long, path={}", PathToUTF8String(path)); |
| 150 | return false; |
| 151 | } |
| 152 | #endif |
| 153 | |
| 154 | return true; |
| 155 | } |
| 156 | |
| 157 | std::string PathToUTF8String(const std::filesystem::path& path) { |
| 158 | const auto u8_string = path.u8string(); |
| 159 | return std::string{u8_string.begin(), u8_string.end()}; |
| 160 | } |
| 161 | |
| 162 | const fs::path& GetUserPath(PathType shad_path) { |
| 163 | return UserPaths.at(shad_path); |
| 164 | } |
| 165 | |
| 166 | std::string GetUserPathString(PathType shad_path) { |
| 167 | return PathToUTF8String(GetUserPath(shad_path)); |
| 168 | } |
| 169 | |
| 170 | void SetUserPath(PathType shad_path, const fs::path& new_path) { |
| 171 | if (!std::filesystem::is_directory(new_path)) { |
| 172 | LOG_ERROR(Common_Filesystem, "Filesystem object at new_path={} is not a directory", |
| 173 | PathToUTF8String(new_path)); |
| 174 | return; |
| 175 | } |
| 176 | |
| 177 | UserPaths.insert_or_assign(shad_path, new_path); |
| 178 | } |
| 179 | |
| 180 | #ifdef ENABLE_QT_GUI |
| 181 | void PathToQString(QString& result, const std::filesystem::path& path) { |
| 182 | #ifdef _WIN32 |
| 183 | result = QString::fromStdWString(path.wstring()); |
| 184 | #else |
| 185 | result = QString::fromStdString(path.string()); |
| 186 | #endif |
| 187 | } |
| 188 | |
| 189 | std::filesystem::path PathFromQString(const QString& path) { |
| 190 | #ifdef _WIN32 |
| 191 | return std::filesystem::path(path.toStdWString()); |
| 192 | #else |
| 193 | return std::filesystem::path(path.toStdString()); |
| 194 | #endif |
| 195 | } |
| 196 | #endif |
| 197 | |
| 198 | } // namespace Common::FS |