/* Copyright (C) 2025 etaHEN / LightningMods This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 3, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; see the file COPYING. If not, see . */ #pragma once #include #ifndef IPC_HEADER_H #define IPC_HEADER_H #include "HookedFuncs.hpp" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include enum Cheat_Actions { DOWNLOAD_CHEATS = 0, RELOAD_CHEATS, }; extern bool is_testkit, cheats_shortcut_activate; pid_t find_pid(const char *name, bool needle, bool for_bigapp, bool need_eboot = false); static void shellui_log(const char *fmt, ...) { char buffer[DAEMON_BUFF_MAX]; va_list args; va_start(args, fmt); int len = vsnprintf(buffer, DAEMON_BUFF_MAX, fmt, args); va_end(args); if (len >= 0 && len < DAEMON_BUFF_MAX - 2) { // If vsnprintf succeeded and there's space for \n and null terminator buffer[len] = '\n'; buffer[len + 1] = '\0'; } else { // If the buffer was truncated, ensure it ends with \n and null terminator buffer[DAEMON_BUFF_MAX - 2] = '\n'; buffer[DAEMON_BUFF_MAX - 1] = '\0'; } if (!is_testkit) klog_printf(buffer); else printf("%s", buffer); } static int MainDaemonSocket = -1; static int UtilDaemonSocket = -1; class IPC_Client { private: public: bool util_daemon = false; // Socket Management int OpenConnection(const char *path) { sockaddr_un server; int soc = socket(AF_UNIX, SOCK_STREAM, 0); if (soc == -1) { shellui_log("Failed to create socket"); return -1; } server.sun_family = AF_UNIX; strncpy(server.sun_path, path, sizeof(server.sun_path) - 1); if (connect(soc, (struct sockaddr *)&server, SUN_LEN(&server)) == -1) { close(soc); shellui_log("Failed to connect to socket"); return -1; } return soc; } // IPC Functions bool IPCOpenConnection() { util_daemon ? UtilDaemonSocket = OpenConnection(UTIL_IPC_SOC) : MainDaemonSocket = OpenConnection(CRIT_IPC_SOC); return util_daemon ? UtilDaemonSocket >= 0 : MainDaemonSocket >= 0; } bool IPCOpenIfNotConnected() { if (util_daemon ? UtilDaemonSocket >= 0 : MainDaemonSocket >= 0) { return true; } return IPCOpenConnection(); } int IPCReceiveData(IPCMessage &msg, std::string &ipc_msg) { // erase the old message bzero(msg.msg, sizeof(msg.msg)); int timeout_ms = 10 * 1000; // Set receive timeout struct timeval tv; tv.tv_sec = timeout_ms / 1000; tv.tv_usec = (timeout_ms % 1000) * 1000; int socket_fd = util_daemon ? UtilDaemonSocket : MainDaemonSocket; if (setsockopt(socket_fd, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv)) < 0) { return -1; // Error setting timeout } int ret = recv(socket_fd, reinterpret_cast(&msg), sizeof(msg), MSG_NOSIGNAL); if (ret < 0) { shellui_log("recv failed with: 0X%X", ret); return ret; } shellui_log("Daemon returned: %i", msg.error); if (msg.error != 0) { shellui_log("Daemon returned an error: %i (strerror: %s)", msg.error, strerror(errno)); return msg.error; } if (strlen(msg.msg) <= 2) { shellui_log("Daemon message is empty"); return -1; } nlohmann::json j; // parse the json from the payload buffer try { j = nlohmann::json::parse(msg.msg); } catch ( const std::exception &e) { // so if it can parse it doesnt crash shellui shellui_log("Failed to parse json: %s", e.what()); return -1; } if (!j.contains("var")) { shellui_log("Daemon message does not contain the return obj"); return -1; } ipc_msg = j["var"]; shellui_log("Daemon IPC return obj: %s", ipc_msg.c_str()); return msg.error; } int IPCSendData(const IPCMessage &msg) { int ret = send(util_daemon ? UtilDaemonSocket : MainDaemonSocket, reinterpret_cast(&msg), sizeof(msg), MSG_NOSIGNAL); if (ret < 0) { shellui_log("IPCSendData failed with: %s", strerror(errno)); } shellui_log("IPCSendData sent %i bytes", ret); return ret; } int IPCCloseConnection() { if (util_daemon ? UtilDaemonSocket < 0 : MainDaemonSocket < 0) { return -1; } close(util_daemon ? UtilDaemonSocket : MainDaemonSocket); util_daemon ? UtilDaemonSocket = -1 : MainDaemonSocket = -1; return 0; } bool IPCSendCommand(DaemonCommands cmd, std::string &ipc_msg1, std::string ipc_msg2 = "") { int ret = -1; std::string json; nlohmann::json j; #if SHELL_DEBUG==1 shellui_log("Sending command to daemon: 0x%X", cmd); #endif IPCMessage msg; msg.cmd = cmd; if (cmd == BREW_REMOUNT_FOLDER) { j["mount_src"] = ipc_msg1; j["mount_dest"] = ipc_msg2; json = j.dump(); } else if (ipc_msg2.empty()) { if (cmd == BREW_DAEMON_PID || cmd == BREW_UTIL_DAEMON_PID) { json = "{\"pid\": 0 }"; } else { json = "{\"msg_1\": 0}"; } } else { json = ipc_msg2; } // shellui_log("Json: %s", json.c_str()); if (!IPCOpenIfNotConnected()) { shellui_log("Failed to open connection to daemon"); return false; } IPC_Ret error = IPC_Ret::INVALID; snprintf(msg.msg, sizeof(msg.msg), "%s", json.c_str()); if ((ret = IPCSendData(msg)) < 0) { shellui_log("Failed to send message to daemon"); notify("Failed to send message to daemon"); IPCCloseConnection(); return false; } if(cmd == BREW_KILL_DAEMON) { shellui_log("Daemon kill cmd sent"); return true; } // Get message back from daemon error = (IPC_Ret)IPCReceiveData(msg, ipc_msg1); if (error == IPC_Ret::NO_ERROR) { shellui_log("[ItemzDaemon] Daemon returned NO ERROR"); return true; } else { shellui_log("[ItemzDaemon] Daemon returned an ERROR"); // notify("Daemon returned an ERROR"); return false; } return false; } // Deleted copy constructor and assignment operator to ensure only one // instance IPC_Client &operator=(const IPC_Client &) = delete; // Static method to access the instance static IPC_Client &getInstance(bool is_util_daemon) { static IPC_Client instance; // Lazy-loaded instance, guaranteed to be destroyed instance.util_daemon = is_util_daemon; return instance; } int GetDaemonPid() { std::string ipc_msg; if (!IPCSendCommand(util_daemon ? BREW_UTIL_DAEMON_PID : BREW_DAEMON_PID, ipc_msg)) { shellui_log("Failed to get daemon pid"); return -1; } else { shellui_log("Daemon pid: %s", ipc_msg.c_str()); return atoi(ipc_msg.c_str()); } return -1; } IPC_Ret ToggleSetting(DaemonCommands cmd, bool turn_on) { std::string ipc_msg; std::string json = turn_on ? "{\"toggle\": 1}" : "{\"toggle\": 0}"; if (!IPCSendCommand(cmd, ipc_msg, json)) { shellui_log("Failed to toggle setting 0x%X (%d)", cmd, cmd); return IPC_Ret::OPERATION_FAILED; } shellui_log("Setting 0x%X (%d) toggled", cmd, cmd); return IPC_Ret::NO_ERROR; } IPC_Ret DownloadTheStore() { if (util_daemon) { shellui_log("This IPC command is ONLY in the main daemon"); return IPC_Ret::INVALID; } std::string ipc_msg; if (!IPCSendCommand(BREW_INSTALL_THE_STORE, ipc_msg)) { shellui_log("Failed to BREW_INSTALL_THE_STORE"); return IPC_Ret::OPERATION_FAILED; } return IPC_Ret::NO_ERROR; } IPC_Ret DownloadKstuff() { std::string ipc_msg; if (!IPCSendCommand(BREW_UTIL_DOWNLOAD_KSTUFF, ipc_msg)) { shellui_log("Failed to BREW_UTIL_DOWNLOAD_KSTUFF"); return IPC_Ret::OPERATION_FAILED; } return IPC_Ret::NO_ERROR; } void KillDaemon() { std::string ipc_msg; IPCSendCommand(BREW_KILL_DAEMON, ipc_msg); } void ForceKillPID(int pid) { if(util_daemon) { shellui_log("This IPC command is NOT in the util daemon"); return; } std::string ipc_msg; std::string json = "{\"pid\": " + std::to_string(pid) + "}"; IPCSendCommand(BREW_FORCE_KILL_PID, ipc_msg, json); } // IPC_Ret CopyFile(std::string src, std::string dest) { if (util_daemon) { shellui_log("This IPC command is NOT in the util daemon"); return IPC_Ret::INVALID; } std::string ipc_msg; std::string json = "{\"path\": \"" + src + "\", \"dest\": \"" + dest + "\"}"; if (!IPCSendCommand(BREW_COPY_FILE, ipc_msg, json)) { shellui_log("Failed to copy file"); return IPC_Ret::OPERATION_FAILED; } return IPC_Ret::NO_ERROR; } IPC_Ret LaunchPlugin(std::string plugin_path, std::string tid) { if (!util_daemon) { shellui_log("This IPC command is NOT in the main daemon"); return IPC_Ret::INVALID; } std::string ipc_msg; std::string json = "{\"plugin_path\": \"" + plugin_path + "\", \"title_id\": \"" + tid + "\"}"; if (!IPCSendCommand(BREW_UTIL_LAUNCH_PLUGIN, ipc_msg, json)) { shellui_log("Failed to launch plugin"); return IPC_Ret::OPERATION_FAILED; } return IPC_Ret::NO_ERROR; } bool GameVerFromTid(std::string tid, std::string &out_ver) { if (!util_daemon) { shellui_log("This IPC command is NOT in the main daemon"); return false; } std::string json = "{\"tid\": \"" + tid + "\"}"; if (!IPCSendCommand(BREW_UTIL_GET_GAME_VER, out_ver, json)) { shellui_log("Failed to get game name from tid"); return false; } return true; } bool Remount(const char* src, const char* dest) { if (util_daemon) { shellui_log("This IPC command is NOT in the util daemon"); return false; } // send jailbreak IPC command std::string in = src; if (!IPCSendCommand(BREW_REMOUNT_FOLDER, in, dest)) { shellui_log("Failed to remount %s to %s", src, dest); return false; } return true; } bool GetGameCheats(const std::string &tid, const std::string &ver, std::string &cheats) { if (!util_daemon) { shellui_log("This IPC command is NOT in the main daemon"); return false; } std::string json = R"({"tid": ")" + tid + R"(", "version": ")" + ver + R"("}")"; if (!IPCSendCommand(BREW_UTIL_GET_GAME_CHEAT, cheats, json)) { shellui_log("Failed to get cheats for %s", tid.c_str()); return false; } return true; } bool ToggleGameCheat(int pid, const std::string &tid, int cheat_index, std::string &cheat_enabled) { if (!util_daemon) { shellui_log("This IPC command is NOT in the main daemon"); return false; } std::string ipc_msg; std::string json = R"({"tid": ")" + tid + R"(", "cheat_id" : )" + std::to_string(cheat_index) + R"(, "pid" : )" + std::to_string(pid) + "}"; if (!IPCSendCommand(BREW_UTIL_TOGGLE_CHEAT, cheat_enabled, json)) { shellui_log("Failed to enable cheats for %s", tid.c_str()); return false; } return true; } void SendRestModeAction() { if (!util_daemon) { shellui_log("This IPC command is NOT in the main daemon"); return; } std::string ipc_msg; std::string json; if (!IPCSendCommand(BREW_UTIL_SHELLUI_ON_STANDBY, ipc_msg, json)) { shellui_log("Failed to launch plugin"); } } bool IsTestKit() { if (util_daemon) { shellui_log("This IPC command is NOT in the util daemon"); return false; } std::string ipc_msg; if (!IPCSendCommand(BREW_TESTKIT_CHECK, ipc_msg)) { return false; } return true; } void Reload_Daemon_Settings() { std::string ipc_msg; if (!IPCSendCommand(BREW_RELOAD_SETTINGS, ipc_msg)) { shellui_log("Failed to reload daemon settings"); } else { shellui_log("Daemon settings reloaded successfully"); } } bool Launch_Elfldr() { if (!util_daemon) { shellui_log("This IPC command is NOT in the main daemon"); return false; } std::string ipc_msg; if (!IPCSendCommand(BREW_UTIL_LAUNCH_ELFLDR, ipc_msg)) { return false; } return true; } bool Toggle_ps5debug() { if (util_daemon) { shellui_log("This IPC command is NOT in the util daemon"); return false; } std::string ipc_msg; if (!IPCSendCommand(BREW_TOGGLE_PS5DEBUG, ipc_msg)) { return false; } return true; } bool Cheats_Action(Cheat_Actions act) { DaemonCommands cmd; if (!util_daemon) { shellui_log("This IPC command is NOT in the main daemon"); return false; } switch (act) { case DOWNLOAD_CHEATS: cmd = BREW_UTIL_DOWNLOAD_CHEATS; break; case RELOAD_CHEATS: cmd = BREW_UTIL_RELOAD_CHEATS; break; default: shellui_log("Invalid action"); return false; } std::string ipc_msg; if (!IPCSendCommand(cmd, ipc_msg)) { return false; } return true; } bool ToggleDPI(bool turn_on, bool is_v2) { if (!util_daemon) { shellui_log("This IPC command is NOT in the main daemon"); return false; } std::string ipc_msg; std::string json = "{\"toggle\": " + std::to_string(turn_on) + ", \"is_v2\": " + std::to_string(is_v2) + "}"; if (!IPCSendCommand(BREW_UTIL_TOGGLE_DPI, ipc_msg, json)) { shellui_log("Failed to toggle DPI"); return false; } return true; } }; // namespace IPC #endif // IPC_HEADER_H