#include "json.hpp" #include #include #include #include extern "C" { #include "common_utils.h" typedef struct app_launch_ctx { int structsize; int user_id; int app_opt; uint64_t crash_report; int check_flag; } app_launch_ctx_t; int sceSystemServiceLaunchApp(const char *, char **, app_launch_ctx_t *); #define SCE_LNC_UTIL_ERROR_ALREADY_RUNNING 0x8094000c #define SCE_LNC_UTIL_ERROR_ALREADY_RUNNING_KILL_NEEDED 0x80940010 #define SCE_LNC_UTIL_ERROR_ALREADY_RUNNING_SUSPEND_NEEDED 0x80940011 } #define SHELL_DEBUG 1 #include #include #include #include #include #include #include using json = nlohmann::json; // Game Entry structure definition struct GameEntry { std::string tid; // Title ID std::string title; // Game title std::string version; // Game version std::string path; // Displayed path std::string dir_name; // Directory name std::string icon_path; // Path to icon std::string id; // Button ID }; std::vector games_list; void escapePath(std::string& input) { std::unordered_map escapeSequences = { {"&", "&"}, {"<", "<"}, {">", ">"}, {"\"", """}, {"/", "//"} }; for (const auto& pair : escapeSequences) { size_t pos = 0; while ((pos = input.find(pair.first, pos)) != std::string::npos) { input.replace(pos, pair.first.length(), pair.second); pos += pair.second.length(); // Move past the replaced part } } } void escapeXML(std::string& input) { std::unordered_map escapeSequences = { {"&", "&"}, {"<", "<"}, {">", ">"}, {"\"", """}, }; for (const auto& pair : escapeSequences) { size_t pos = 0; while ((pos = input.find(pair.first, pos)) != std::string::npos) { input.replace(pos, pair.first.length(), pair.second); pos += pair.second.length(); // Move past the replaced part } } } bool getContentInfofromJson(const std::string& file_path, std::string& tid, std::string& title, std::string &ver) { try { std::ifstream input_file(file_path); if (!input_file.is_open()) { etaHEN_log("Failed to open file for reading: %s", file_path.c_str()); return false; } json j; input_file >> j; input_file.close(); if (!j.contains("titleId")) { etaHEN_log("JSON does not contain a required value"); return false; } tid = j["titleId"]; #if SHELL_DEBUG==1 etaHEN_log("getContentInfofromJson Title ID: %s", tid.c_str()); #endif if (j.contains("localizedParameters") && j["localizedParameters"].contains("defaultLanguage")) { std::string defaultLanguage = j["localizedParameters"]["defaultLanguage"]; if (j["localizedParameters"].contains(defaultLanguage) && j["localizedParameters"][defaultLanguage].contains("titleName")) { title = j["localizedParameters"][defaultLanguage]["titleName"]; } } else title = "App Title not found"; if (j.contains("contentVersion")) ver = j["contentVersion"]; } catch (const std::exception& e) { etaHEN_log("Exception: %s", e.what()); return false; } return true; } // Helper function to check if path is from external storage bool isExternalStorage(const std::string &path) { return path.rfind("/mnt/ext") != std::string::npos; } // Helper function to copy files bool copyFile(const std::string &src, const std::string &dst) { std::ifstream source(src, std::ios::binary); if (!source.is_open()) { #if SHELL_DEBUG==1 etaHEN_log("Failed to open source file: %s", src.c_str()); #endif return false; } std::ofstream dest(dst, std::ios::binary); if (!dest.is_open()) { #if SHELL_DEBUG==1 etaHEN_log("Failed to open destination file: %s", dst.c_str()); #endif return false; } dest << source.rdbuf(); source.close(); dest.close(); return true; } // Main function void generate_games_xml(std::string &xml_buffer, bool game_shortcut_activated) { struct dirent *entry; unlink("/data/etaHEN/games_list.xml"); std::vector directories = { "/user/data/etaHEN/games", "/mnt/usb0/etaHEN/games", "/mnt/usb1/etaHEN/games", "/mnt/usb2/etaHEN/games", "/mnt/usb3/etaHEN/games", "/mnt/ext1/etaHEN/games", "/mnt/ext2/etaHEN/games", "/mnt/ext0/etaHEN/games", }; std::string list_id = game_shortcut_activated ? "id_debug_settings" : "id_ps5_backups"; xml_buffer = "\n" "\n" "\n"; xml_buffer += "\n"; // Initialize random number generator std::random_device rd; std::mt19937 gen(rd()); std::uniform_int_distribution dist(1000, 9999); // Create cache directory mkdir("/data/etaHEN/cache", 0777); for (const auto &directory : directories) { DIR *dir = opendir(directory.c_str()); if (!dir) { #if SHELL_DEBUG==1 etaHEN_log("Failed to open directory: %s error %s", directory.c_str(), strerror(errno)); #endif continue; } while ((entry = readdir(dir)) != nullptr) { // Skip . and .. directories if (strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0) continue; std::string game_dir = directory + "/" + entry->d_name; // Check if this is a directory struct stat st; if (stat(game_dir.c_str(), &st) != 0 || !S_ISDIR(st.st_mode)) { #if SHELL_DEBUG==1 etaHEN_log("Skipping non-directory: %s", game_dir.c_str()); #endif continue; } std::string param_path = game_dir + "/sce_sys/param.json"; std::string icon_path = game_dir + "/sce_sys/icon0.png"; // Check if param.json exists if (access(param_path.c_str(), F_OK) != 0) { #if SHELL_DEBUG==1 etaHEN_log("No param.json found in: %s", game_dir.c_str()); #endif continue; } #if SHELL_DEBUG==1 etaHEN_log("Found Game: %s", game_dir.c_str()); #endif // Parse JSON to get title_id, content_id, title, and version std::string title_id, title, ver; if (!getContentInfofromJson(param_path, title_id, title, ver)) { #if SHELL_DEBUG==1 etaHEN_log("Failed to parse param.json in: %s", game_dir.c_str()); #endif continue; } std::string shown_path = game_dir; const std::string prefix = "/user"; if (shown_path.find(prefix) == 0) { shown_path = shown_path.substr(prefix.length()); } shown_path = (game_dir.substr(0, 4) == "/usb") ? "/mnt" + game_dir : shown_path; // Generate a random number for the ID int random_num = dist(gen); // Handle icon path and caching if (isExternalStorage(game_dir)) { // Cache icon from external storage // not /data but /user/data for shellui as it's not accessible from /data std::string cached_icon_path = "/user/data/etaHEN/cache/" + title_id + ".png"; // Copy icon if it doesn't exist if (access(cached_icon_path.c_str(), F_OK) != 0) { if (!copyFile(icon_path, cached_icon_path)) { #if SHELL_DEBUG==1 etaHEN_log("Failed to cache icon from: %s", icon_path.c_str()); #endif cached_icon_path = icon_path; // Fallback to original } else { #if SHELL_DEBUG==1 etaHEN_log("Cached icon: %s", cached_icon_path.c_str()); #endif } } icon_path = cached_icon_path; } else if (icon_path.find("/mnt/usb") == 0) { // Transform /mnt/usb... to /usb... icon_path = icon_path.substr(4); // Remove "/mnt" } // Escape paths for XML escapePath(icon_path); escapeXML(title); escapeXML(shown_path); // Create and populate a GameEntry GameEntry game; game.tid = title_id; game.title = title; game.version = ver; game.path = shown_path; game.dir_name = entry->d_name; game.icon_path = icon_path; game.id = "id_etahen_game_loader_" + title_id + "_" + std::to_string(random_num); // Add to the games list games_list.push_back(game); // Format the button XML std::string button = "