#include "Crypto/unpkg.h" #include "Crypto/unself.h" #include "Emu/Audio/Cubeb/CubebBackend.h" #include "Emu/Audio/Null/NullAudioBackend.h" #include "Emu/Cell/PPUAnalyser.h" #include "Emu/Cell/SPURecompiler.h" #include "Emu/IdManager.h" #include "Emu/Io/KeyboardHandler.h" #include "Emu/Io/Null/NullKeyboardHandler.h" #include "Emu/Io/Null/NullMouseHandler.h" #include "Emu/Io/Null/NullPadHandler.h" #include "Emu/Io/Null/null_camera_handler.h" #include "Emu/Io/Null/null_music_handler.h" #include "Emu/Io/pad_config_types.h" #include "Emu/RSX/Null/NullGSRender.h" #include "Emu/RSX/Overlays/overlay_manager.h" #include "Emu/RSX/Overlays/overlay_save_dialog.h" #include "Emu/RSX/Overlays/overlay_trophy_notification.h" #include "Emu/RSX/RSXThread.h" #include "Emu/RSX/VK/VKGSRender.h" #include "Emu/localized_string_id.h" #include "Emu/system_config.h" #include "Emu/system_config_types.h" #include "Emu/system_progress.hpp" #include "Emu/system_utils.hpp" #include "Emu/vfs_config.h" #include "Input/ds3_pad_handler.h" #include "Input/ds4_pad_handler.h" #include "Input/dualsense_pad_handler.h" #include "Input/hid_pad_handler.h" #include "Input/pad_thread.h" #include "Input/virtual_pad_handler.h" #include "Loader/PSF.h" #include "Loader/PUP.h" #include "Loader/TAR.h" #include "cellos/sys_sync.h" #include "dev/block_dev.hpp" #include "dev/iso.hpp" #include "hidapi_libusb.h" #include "libusb.h" #include "rpcs3_version.h" #include "rpcsx/fw/ps3/cellMsgDialog.h" #include "rpcsx/fw/ps3/cellSysutil.h" #include "util/File.h" #include "util/JIT.h" #include "util/StrFmt.h" #include "util/StrUtil.h" #include "util/Thread.h" #include "util/asm.hpp" #include "util/console.h" #include "util/fixed_typemap.hpp" #include "util/logs.hpp" #include "util/serialization.hpp" #include "util/sysinfo.hpp" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wreturn-type-c-linkage" struct AtExit { std::function cb; ~AtExit() { cb(); } }; static bool g_initialized; static std::atomic g_native_window; extern std::string g_android_executable_dir; extern std::string g_android_config_dir; extern std::string g_android_cache_dir; static std::mutex g_virtual_pad_mutex; static std::shared_ptr g_virtual_pad; std::string g_input_config_override; cfg_input_configurations g_cfg_input_configs; LOG_CHANNEL(rpcsx_android, "ANDROID"); struct LogListener : logs::listener { LogListener() { logs::listener::add(this); } void log(u64 stamp, const logs::message &msg, const std::string &prefix, const std::string &text) override { int prio = 0; switch (static_cast(msg)) { case logs::level::always: prio = ANDROID_LOG_INFO; break; case logs::level::fatal: prio = ANDROID_LOG_FATAL; break; case logs::level::error: prio = ANDROID_LOG_ERROR; break; case logs::level::todo: prio = ANDROID_LOG_WARN; break; case logs::level::success: prio = ANDROID_LOG_INFO; break; case logs::level::warning: prio = ANDROID_LOG_WARN; break; case logs::level::notice: prio = ANDROID_LOG_DEBUG; break; case logs::level::trace: prio = ANDROID_LOG_VERBOSE; break; } __android_log_write(prio, "RPCS3", text.c_str()); } } static g_androidLogListener; struct GraphicsFrame : GSFrameBase { mutable ANativeWindow *activeNativeWindow = nullptr; mutable int width = 0; mutable int height = 0; ~GraphicsFrame() { if (activeNativeWindow != nullptr) { ANativeWindow_release(activeNativeWindow); } } ANativeWindow *getNativeWindow() const { ANativeWindow *result; while ((result = g_native_window.load()) == nullptr) [[unlikely]] { if (Emu.IsStopped()) { return activeNativeWindow; } std::this_thread::sleep_for(std::chrono::milliseconds(100)); } if (result != activeNativeWindow) [[unlikely]] { ANativeWindow_acquire(result); if (activeNativeWindow != nullptr) { ANativeWindow_release(activeNativeWindow); } activeNativeWindow = result; width = ANativeWindow_getWidth(result); height = ANativeWindow_getHeight(result); } return result; } void close() override {} void reset() override {} bool shown() override { return true; } void hide() override {} void show() override {} void toggle_fullscreen() override {} void delete_context(draw_context_t ctx) override {} draw_context_t make_context() override { return nullptr; } void set_current(draw_context_t ctx) override {} void flip(draw_context_t ctx, bool skip_frame = false) override {} int client_width() override { return width; } int client_height() override { return height; } f64 client_display_rate() override { return 30.f; } bool has_alpha() override { return ANativeWindow_getFormat(getNativeWindow()) == WINDOW_FORMAT_RGBA_8888; } display_handle_t handle() const override { return getNativeWindow(); } bool can_consume_frame() const override { return false; } void present_frame(std::vector &data, u32 pitch, u32 width, u32 height, bool is_bgra) const override {} void take_screenshot(std::vector &&sshot_data, u32 sshot_width, u32 sshot_height, bool is_bgra) override {} }; void jit_announce(uptr, usz, std::string_view); [[noreturn]] void report_fatal_error(std::string_view _text, bool is_html = false, bool include_help_text = true) { std::string buf; buf = std::string(_text); // Check if thread id is in string if (_text.find("\nThread id = "sv) == umax && !thread_ctrl::is_main()) { // Append thread id if it isn't already, except on main thread fmt::append(buf, "\n\nThread id = %u.", thread_ctrl::get_tid()); } if (!g_tls_serialize_name.empty()) { fmt::append(buf, "\nSerialized Object: %s", g_tls_serialize_name); } const system_state state = Emu.GetStatus(false); if (state == system_state::stopped) { fmt::append(buf, "\nEmulation is stopped"); } else { const std::string &name = Emu.GetTitleAndTitleID(); fmt::append(buf, "\nTitle: \"%s\" (emulation is %s)", name.empty() ? "N/A" : name.data(), state == system_state::stopping ? "stopping" : "running"); } fmt::append(buf, "\nBuild: \"%s\"", rpcs3::get_verbose_version()); fmt::append(buf, "\nDate: \"%s\"", std::chrono::system_clock::now()); __android_log_write(ANDROID_LOG_FATAL, "RPCS3", buf.c_str()); jit_announce(0, 0, ""); utils::trap(); std::abort(); std::terminate(); } void qt_events_aware_op(int repeat_duration_ms, std::function wrapped_op) { /// ????? } static std::string unwrap(JNIEnv *env, jstring string) { auto resultBuffer = env->GetStringUTFChars(string, nullptr); std::string result(resultBuffer); env->ReleaseStringUTFChars(string, resultBuffer); return result; } static jstring wrap(JNIEnv *env, const std::string &string) { return env->NewStringUTF(string.c_str()); } static jstring wrap(JNIEnv *env, const char *string) { return env->NewStringUTF(string); } static std::string fix_dir_path(std::string string) { if (!string.empty() && !string.ends_with('/')) { string += '/'; } return string; } enum class FileType { Unknown, Pup, Pkg, Edat, Rap, Iso, }; static FileType getFileType(const fs::file &file) { file.seek(0); if (PUPHeader pupHeader; file.read(pupHeader)) { if (pupHeader.magic == "SCEUF\0\0\0"_u64) { return FileType::Pup; } } file.seek(0); if (PKGHeader pkgHeader; file.read(pkgHeader)) { if (pkgHeader.pkg_magic == std::bit_cast>("\x7FPKG"_u32)) { return FileType::Pkg; } } file.seek(0); if (NPD_HEADER npdHeader; file.read(npdHeader)) { if (npdHeader.magic == "NPD\0"_u32) { return FileType::Edat; } } if (file.size() == 16) { return FileType::Rap; } if (iso_dev::open(std::make_unique(file))) { return FileType::Iso; } return FileType::Unknown; } #define MAKE_STRING(id, x) [int(localized_string_id::id)] = {x, U##x} static std::pair g_strings[] = { MAKE_STRING(RSX_OVERLAYS_COMPILING_SHADERS, "Compiling shaders"), MAKE_STRING(RSX_OVERLAYS_COMPILING_PPU_MODULES, "Compiling PPU Modules"), MAKE_STRING(RSX_OVERLAYS_MSG_DIALOG_YES, "Yes"), MAKE_STRING(RSX_OVERLAYS_MSG_DIALOG_NO, "No"), MAKE_STRING(RSX_OVERLAYS_MSG_DIALOG_CANCEL, "Back"), MAKE_STRING(RSX_OVERLAYS_MSG_DIALOG_OK, "OK"), MAKE_STRING(RSX_OVERLAYS_SAVE_DIALOG_TITLE, "Save Dialog"), MAKE_STRING(RSX_OVERLAYS_SAVE_DIALOG_DELETE, "Delete Save"), MAKE_STRING(RSX_OVERLAYS_SAVE_DIALOG_LOAD, "Load Save"), MAKE_STRING(RSX_OVERLAYS_SAVE_DIALOG_SAVE, "Save"), MAKE_STRING(RSX_OVERLAYS_OSK_DIALOG_ACCEPT, "Enter"), MAKE_STRING(RSX_OVERLAYS_OSK_DIALOG_CANCEL, "Back"), MAKE_STRING(RSX_OVERLAYS_OSK_DIALOG_SPACE, "Space"), MAKE_STRING(RSX_OVERLAYS_OSK_DIALOG_BACKSPACE, "Backspace"), MAKE_STRING(RSX_OVERLAYS_OSK_DIALOG_SHIFT, "Shift"), MAKE_STRING(RSX_OVERLAYS_OSK_DIALOG_ENTER_TEXT, "[Enter Text]"), MAKE_STRING(RSX_OVERLAYS_OSK_DIALOG_ENTER_PASSWORD, "[Enter Password]"), MAKE_STRING(RSX_OVERLAYS_MEDIA_DIALOG_TITLE, "Select media"), MAKE_STRING(RSX_OVERLAYS_MEDIA_DIALOG_TITLE_PHOTO_IMPORT, "Select photo to import"), MAKE_STRING(RSX_OVERLAYS_MEDIA_DIALOG_EMPTY, "No media found."), MAKE_STRING(RSX_OVERLAYS_LIST_SELECT, "Enter"), MAKE_STRING(RSX_OVERLAYS_LIST_CANCEL, "Back"), MAKE_STRING(RSX_OVERLAYS_LIST_DENY, "Deny"), MAKE_STRING(CELL_OSK_DIALOG_TITLE, "On Screen Keyboard"), MAKE_STRING( CELL_OSK_DIALOG_BUSY, "The Home Menu can't be opened while the On Screen Keyboard is busy!"), MAKE_STRING(CELL_SAVEDATA_CB_BROKEN, "Error - Save data corrupted"), MAKE_STRING(CELL_SAVEDATA_CB_FAILURE, "Error - Failed to save or load"), MAKE_STRING(CELL_SAVEDATA_CB_NO_DATA, "Error - Save data cannot be found"), MAKE_STRING(CELL_SAVEDATA_NO_DATA, "There is no saved data."), MAKE_STRING(CELL_SAVEDATA_NEW_SAVED_DATA_TITLE, "New Saved Data"), MAKE_STRING(CELL_SAVEDATA_NEW_SAVED_DATA_SUB_TITLE, "Select to create a new entry"), MAKE_STRING(CELL_SAVEDATA_SAVE_CONFIRMATION, "Do you want to save this data?"), MAKE_STRING(CELL_SAVEDATA_AUTOSAVE, "Saving..."), MAKE_STRING(CELL_SAVEDATA_AUTOLOAD, "Loading..."), MAKE_STRING( CELL_CROSS_CONTROLLER_FW_MSG, "If your system software version on the PS Vita system is earlier than " "1.80, you must update the system software to the latest version."), MAKE_STRING(CELL_NP_RECVMESSAGE_DIALOG_TITLE, "Select Message"), MAKE_STRING(CELL_NP_RECVMESSAGE_DIALOG_TITLE_INVITE, "Select Invite"), MAKE_STRING(CELL_NP_RECVMESSAGE_DIALOG_TITLE_ADD_FRIEND, "Add Friend"), MAKE_STRING(CELL_NP_RECVMESSAGE_DIALOG_FROM, "From:"), MAKE_STRING(CELL_NP_RECVMESSAGE_DIALOG_SUBJECT, "Subject:"), MAKE_STRING(CELL_NP_SENDMESSAGE_DIALOG_TITLE, "Select Message To Send"), MAKE_STRING(CELL_NP_SENDMESSAGE_DIALOG_TITLE_INVITE, "Send Invite"), MAKE_STRING(CELL_NP_SENDMESSAGE_DIALOG_TITLE_ADD_FRIEND, "Add Friend"), MAKE_STRING(RECORDING_ABORTED, "Recording aborted!"), MAKE_STRING(RPCN_NO_ERROR, "RPCN: No Error"), MAKE_STRING(RPCN_ERROR_INVALID_INPUT, "RPCN: Invalid Input (Wrong Host/Port)"), MAKE_STRING(RPCN_ERROR_WOLFSSL, "RPCN Connection Error: WolfSSL Error"), MAKE_STRING(RPCN_ERROR_RESOLVE, "RPCN Connection Error: Resolve Error"), MAKE_STRING(RPCN_ERROR_CONNECT, "RPCN Connection Error"), MAKE_STRING(RPCN_ERROR_LOGIN_ERROR, "RPCN Login Error: Identification Error"), MAKE_STRING(RPCN_ERROR_ALREADY_LOGGED, "RPCN Login Error: User Already Logged In"), MAKE_STRING(RPCN_ERROR_INVALID_LOGIN, "RPCN Login Error: Invalid Username"), MAKE_STRING(RPCN_ERROR_INVALID_PASSWORD, "RPCN Login Error: Invalid Password"), MAKE_STRING(RPCN_ERROR_INVALID_TOKEN, "RPCN Login Error: Invalid Token"), MAKE_STRING(RPCN_ERROR_INVALID_PROTOCOL_VERSION, "RPCN Misc Error: Protocol Version Error (outdated RPCS3?)"), MAKE_STRING(RPCN_ERROR_UNKNOWN, "RPCN: Unknown Error"), MAKE_STRING(RPCN_SUCCESS_LOGGED_ON, "Successfully logged on RPCN!"), MAKE_STRING(HOME_MENU_TITLE, "Home Menu"), MAKE_STRING(HOME_MENU_EXIT_GAME, "Exit Game"), MAKE_STRING(HOME_MENU_RESUME, "Resume Game"), MAKE_STRING(HOME_MENU_FRIENDS, "Friends"), MAKE_STRING(HOME_MENU_FRIENDS_REQUESTS, "Pending Friend Requests"), MAKE_STRING(HOME_MENU_FRIENDS_BLOCKED, "Blocked Users"), MAKE_STRING(HOME_MENU_FRIENDS_STATUS_ONLINE, "Online"), MAKE_STRING(HOME_MENU_FRIENDS_STATUS_OFFLINE, "Offline"), MAKE_STRING(HOME_MENU_FRIENDS_STATUS_BLOCKED, "Blocked"), MAKE_STRING(HOME_MENU_FRIENDS_REQUEST_SENT, "You sent a friend request"), MAKE_STRING(HOME_MENU_FRIENDS_REQUEST_RECEIVED, "Sent you a friend request"), MAKE_STRING(HOME_MENU_FRIENDS_REJECT_REQUEST, "Reject Request"), MAKE_STRING(HOME_MENU_FRIENDS_NEXT_LIST, "Next list"), MAKE_STRING(HOME_MENU_RESTART, "Restart Game"), MAKE_STRING(HOME_MENU_SETTINGS, "Settings"), MAKE_STRING(HOME_MENU_SETTINGS_SAVE, "Save custom configuration?"), MAKE_STRING(HOME_MENU_SETTINGS_SAVE_BUTTON, "Save"), MAKE_STRING(HOME_MENU_SETTINGS_DISCARD, "Discard the current settings' changes?"), MAKE_STRING(HOME_MENU_SETTINGS_DISCARD_BUTTON, "Discard"), MAKE_STRING(HOME_MENU_SETTINGS_AUDIO, "Audio"), MAKE_STRING(HOME_MENU_SETTINGS_AUDIO_MASTER_VOLUME, "Master Volume"), MAKE_STRING(HOME_MENU_SETTINGS_AUDIO_BACKEND, "Audio Backend"), MAKE_STRING(HOME_MENU_SETTINGS_AUDIO_BUFFERING, "Enable Buffering"), MAKE_STRING(HOME_MENU_SETTINGS_AUDIO_BUFFER_DURATION, "Desired Audio Buffer Duration"), MAKE_STRING(HOME_MENU_SETTINGS_AUDIO_TIME_STRETCHING, "Enable Time Stretching"), MAKE_STRING(HOME_MENU_SETTINGS_AUDIO_TIME_STRETCHING_THRESHOLD, "Time Stretching Threshold"), MAKE_STRING(HOME_MENU_SETTINGS_VIDEO, "Video"), MAKE_STRING(HOME_MENU_SETTINGS_VIDEO_FRAME_LIMIT, "Frame Limit"), MAKE_STRING(HOME_MENU_SETTINGS_VIDEO_ANISOTROPIC_OVERRIDE, "Anisotropic Filter Override"), MAKE_STRING(HOME_MENU_SETTINGS_VIDEO_OUTPUT_SCALING, "Output Scaling"), MAKE_STRING(HOME_MENU_SETTINGS_VIDEO_RCAS_SHARPENING, "FidelityFX CAS Sharpening Intensity"), MAKE_STRING(HOME_MENU_SETTINGS_VIDEO_STRETCH_TO_DISPLAY, "Stretch To Display Area"), MAKE_STRING(HOME_MENU_SETTINGS_INPUT, "Input"), MAKE_STRING(HOME_MENU_SETTINGS_INPUT_BACKGROUND_INPUT, "Background Input Enabled"), MAKE_STRING(HOME_MENU_SETTINGS_INPUT_KEEP_PADS_CONNECTED, "Keep Pads Connected"), MAKE_STRING(HOME_MENU_SETTINGS_INPUT_SHOW_PS_MOVE_CURSOR, "Show PS Move Cursor"), MAKE_STRING(HOME_MENU_SETTINGS_INPUT_CAMERA_FLIP, "Camera Flip"), MAKE_STRING(HOME_MENU_SETTINGS_INPUT_PAD_MODE, "Pad Handler Mode"), MAKE_STRING(HOME_MENU_SETTINGS_INPUT_PAD_SLEEP, "Pad Handler Sleep"), MAKE_STRING(HOME_MENU_SETTINGS_INPUT_FAKE_MOVE_ROTATION_CONE_H, "Fake PS Move Rotation Cone (Horizontal)"), MAKE_STRING(HOME_MENU_SETTINGS_INPUT_FAKE_MOVE_ROTATION_CONE_V, "Fake PS Move Rotation Cone (Vertical)"), MAKE_STRING(HOME_MENU_SETTINGS_ADVANCED, "Advanced"), MAKE_STRING(HOME_MENU_SETTINGS_ADVANCED_PREFERRED_SPU_THREADS, "Preferred SPU Threads"), MAKE_STRING(HOME_MENU_SETTINGS_ADVANCED_MAX_CPU_PREEMPTIONS, "Max Power Saving CPU-Preemptions"), MAKE_STRING(HOME_MENU_SETTINGS_ADVANCED_ACCURATE_RSX_RESERVATION_ACCESS, "Accurate RSX reservation access"), MAKE_STRING(HOME_MENU_SETTINGS_ADVANCED_SLEEP_TIMERS_ACCURACY, "Sleep Timers Accuracy"), MAKE_STRING(HOME_MENU_SETTINGS_ADVANCED_MAX_SPURS_THREADS, "Max SPURS Threads"), MAKE_STRING(HOME_MENU_SETTINGS_ADVANCED_DRIVER_WAKE_UP_DELAY, "Driver Wake-Up Delay"), MAKE_STRING(HOME_MENU_SETTINGS_ADVANCED_VBLANK_FREQUENCY, "VBlank Frequency"), MAKE_STRING(HOME_MENU_SETTINGS_ADVANCED_VBLANK_NTSC, "VBlank NTSC Fixup"), MAKE_STRING(HOME_MENU_SETTINGS_OVERLAYS, "Overlays"), MAKE_STRING(HOME_MENU_SETTINGS_OVERLAYS_SHOW_TROPHY_POPUPS, "Show Trophy Popups"), MAKE_STRING(HOME_MENU_SETTINGS_OVERLAYS_SHOW_RPCN_POPUPS, "Show RPCN Popups"), MAKE_STRING(HOME_MENU_SETTINGS_OVERLAYS_SHOW_SHADER_COMPILATION_HINT, "Show Shader Compilation Hint"), MAKE_STRING(HOME_MENU_SETTINGS_OVERLAYS_SHOW_PPU_COMPILATION_HINT, "Show PPU Compilation Hint"), MAKE_STRING(HOME_MENU_SETTINGS_OVERLAYS_SHOW_AUTO_SAVE_LOAD_HINT, "Show Autosave/Autoload Hint"), MAKE_STRING(HOME_MENU_SETTINGS_OVERLAYS_SHOW_PRESSURE_INTENSITY_TOGGLE_HINT, "Show Pressure Intensity Toggle Hint"), MAKE_STRING(HOME_MENU_SETTINGS_OVERLAYS_SHOW_ANALOG_LIMITER_TOGGLE_HINT, "Show Analog Limiter Toggle Hint"), MAKE_STRING(HOME_MENU_SETTINGS_OVERLAYS_SHOW_MOUSE_AND_KB_TOGGLE_HINT, "Show Mouse And Keyboard Toggle Hint"), MAKE_STRING(HOME_MENU_SETTINGS_PERFORMANCE_OVERLAY, "Performance Overlay"), MAKE_STRING(HOME_MENU_SETTINGS_PERFORMANCE_OVERLAY_ENABLE, "Enable Performance Overlay"), MAKE_STRING(HOME_MENU_SETTINGS_PERFORMANCE_OVERLAY_ENABLE_FRAMERATE_GRAPH, "Enable Framerate Graph"), MAKE_STRING(HOME_MENU_SETTINGS_PERFORMANCE_OVERLAY_ENABLE_FRAMETIME_GRAPH, "Enable Frametime Graph"), MAKE_STRING(HOME_MENU_SETTINGS_PERFORMANCE_OVERLAY_DETAIL_LEVEL, "Detail level"), MAKE_STRING(HOME_MENU_SETTINGS_PERFORMANCE_OVERLAY_FRAMERATE_DETAIL_LEVEL, "Framerate Graph Detail Level"), MAKE_STRING(HOME_MENU_SETTINGS_PERFORMANCE_OVERLAY_FRAMETIME_DETAIL_LEVEL, "Frametime Graph Detail Level"), MAKE_STRING( HOME_MENU_SETTINGS_PERFORMANCE_OVERLAY_FRAMERATE_DATAPOINT_COUNT, "Framerate Datapoints"), MAKE_STRING( HOME_MENU_SETTINGS_PERFORMANCE_OVERLAY_FRAMETIME_DATAPOINT_COUNT, "Frametime Datapoints"), MAKE_STRING(HOME_MENU_SETTINGS_PERFORMANCE_OVERLAY_UPDATE_INTERVAL, "Metrics Update Interval"), MAKE_STRING(HOME_MENU_SETTINGS_PERFORMANCE_OVERLAY_POSITION, "Position"), MAKE_STRING(HOME_MENU_SETTINGS_PERFORMANCE_OVERLAY_CENTER_X, "Center Horizontally"), MAKE_STRING(HOME_MENU_SETTINGS_PERFORMANCE_OVERLAY_CENTER_Y, "Center Vertically"), MAKE_STRING(HOME_MENU_SETTINGS_PERFORMANCE_OVERLAY_MARGIN_X, "Horizontal Margin"), MAKE_STRING(HOME_MENU_SETTINGS_PERFORMANCE_OVERLAY_MARGIN_Y, "Vertical Margin"), MAKE_STRING(HOME_MENU_SETTINGS_PERFORMANCE_OVERLAY_FONT_SIZE, "Font Size"), MAKE_STRING(HOME_MENU_SETTINGS_PERFORMANCE_OVERLAY_OPACITY, "Opacity"), MAKE_STRING(HOME_MENU_SETTINGS_DEBUG, "Debug"), MAKE_STRING(HOME_MENU_SETTINGS_DEBUG_OVERLAY, "Debug Overlay"), MAKE_STRING(HOME_MENU_SETTINGS_DEBUG_INPUT_OVERLAY, "Input Debug Overlay"), MAKE_STRING(HOME_MENU_SETTINGS_DEBUG_DISABLE_VIDEO_OUTPUT, "Disable Video Output"), MAKE_STRING(HOME_MENU_SETTINGS_DEBUG_TEXTURE_LOD_BIAS, "Texture LOD Bias Addend"), MAKE_STRING(HOME_MENU_SCREENSHOT, "Take Screenshot"), MAKE_STRING(HOME_MENU_SAVESTATE, "SaveState"), MAKE_STRING(HOME_MENU_SAVESTATE_SAVE, "Save Emulation State"), MAKE_STRING(HOME_MENU_SAVESTATE_AND_EXIT, "Save Emulation State And Exit"), MAKE_STRING(HOME_MENU_RELOAD_SAVESTATE, "Reload Last Emulation State"), MAKE_STRING(HOME_MENU_RECORDING, "Start/Stop Recording"), MAKE_STRING(HOME_MENU_TROPHIES, "Trophies"), MAKE_STRING(HOME_MENU_TROPHY_HIDDEN_TITLE, "Hidden trophy"), MAKE_STRING(HOME_MENU_TROPHY_HIDDEN_DESCRIPTION, "This trophy is hidden"), MAKE_STRING(HOME_MENU_TROPHY_PLATINUM_RELEVANT, "Platinum relevant"), MAKE_STRING(HOME_MENU_TROPHY_GRADE_BRONZE, "Bronze"), MAKE_STRING(HOME_MENU_TROPHY_GRADE_SILVER, "Silver"), MAKE_STRING(HOME_MENU_TROPHY_GRADE_GOLD, "Gold"), MAKE_STRING(HOME_MENU_TROPHY_GRADE_PLATINUM, "Platinum"), MAKE_STRING(AUDIO_MUTED, "Audio muted"), MAKE_STRING(AUDIO_UNMUTED, "Audio unmuted"), MAKE_STRING(PROGRESS_DIALOG_PROGRESS, "Progress:"), MAKE_STRING(PROGRESS_DIALOG_PROGRESS_ANALYZING, "Progress: analyzing..."), MAKE_STRING(PROGRESS_DIALOG_REMAINING, "remaining"), MAKE_STRING(PROGRESS_DIALOG_DONE, "done"), MAKE_STRING(PROGRESS_DIALOG_FILE, "file"), MAKE_STRING(PROGRESS_DIALOG_MODULE, "module"), MAKE_STRING(PROGRESS_DIALOG_OF, "of"), MAKE_STRING(PROGRESS_DIALOG_PLEASE_WAIT, "Please wait"), MAKE_STRING(PROGRESS_DIALOG_STOPPING_PLEASE_WAIT, "Stopping. Please wait..."), MAKE_STRING(PROGRESS_DIALOG_SAVESTATE_PLEASE_WAIT, "Creating savestate. Please wait..."), MAKE_STRING(PROGRESS_DIALOG_SCANNING_PPU_EXECUTABLE, "Scanning PPU Executable..."), MAKE_STRING(PROGRESS_DIALOG_ANALYZING_PPU_EXECUTABLE, "Analyzing PPU Executable..."), MAKE_STRING(PROGRESS_DIALOG_SCANNING_PPU_MODULES, "Scanning PPU Modules..."), MAKE_STRING(PROGRESS_DIALOG_LOADING_PPU_MODULES, "Loading PPU Modules..."), MAKE_STRING(PROGRESS_DIALOG_COMPILING_PPU_MODULES, "Compiling PPU Modules..."), MAKE_STRING(PROGRESS_DIALOG_LINKING_PPU_MODULES, "Linking PPU Modules..."), MAKE_STRING(PROGRESS_DIALOG_APPLYING_PPU_CODE, "Applying PPU Code..."), MAKE_STRING(PROGRESS_DIALOG_BUILDING_SPU_CACHE, "Building SPU Cache..."), MAKE_STRING(EMULATION_PAUSED_RESUME_WITH_START, "Press and hold the START button to resume"), MAKE_STRING(EMULATION_RESUMING, "Resuming...!"), MAKE_STRING(EMULATION_FROZEN, "The PS3 application has likely crashed, you can close it."), MAKE_STRING( SAVESTATE_FAILED_DUE_TO_SAVEDATA, "SaveState failed: Game saving is in progress, wait until finished."), MAKE_STRING(SAVESTATE_FAILED_DUE_TO_VDEC, "SaveState failed: VDEC-base video/cutscenes are in order, " "wait for them to end or enable libvdec.sprx."), MAKE_STRING(SAVESTATE_FAILED_DUE_TO_MISSING_SPU_SETTING, "SaveState failed: Failed to lock SPU state, enabling " "SPU-Compatible mode may fix it."), MAKE_STRING(SAVESTATE_FAILED_DUE_TO_SPU, "SaveState failed: Failed to lock SPU state, using SPU ASMJIT " "will fix it."), MAKE_STRING(INVALID, "Invalid"), }; enum GameFlags { kGameFlagLocked = 1 << 0, kGameFlagTrial = 1 << 1, }; struct GameInfo { std::string path; std::string name; std::string iconPath; int flags = 0; }; class Progress { JNIEnv *env; jlong progressId; jclass progressRepositoryClass; jmethodID onProgressEventMethodId; public: Progress(JNIEnv *env, jlong progressId) : env(env), progressId(progressId) { progressRepositoryClass = ensure(env->FindClass("net/rpcsx/ProgressRepository")); onProgressEventMethodId = env->GetStaticMethodID( progressRepositoryClass, "onProgressEvent", "(JJJLjava/lang/String;)Z"); } bool report(jlong value, jlong max, const std::string &message = {}) { return env->CallStaticBooleanMethod( progressRepositoryClass, onProgressEventMethodId, progressId, value, max, message.empty() ? nullptr : wrap(env, message)); } void failure(const std::string &message = {}) { report(-1, 0, message); } void success(jlong value, const std::string &message = {}) { value = std::max(value, 1); report(value, value, message); } jlong getProgressId() const { return progressId; } }; static void sendFirmwareInstalled(JNIEnv *env, const std::string &version) { auto fwRepositoryClass = ensure(env->FindClass("net/rpcsx/FirmwareRepository")); auto methodId = ensure(env->GetStaticMethodID( fwRepositoryClass, "onFirmwareInstalled", "(Ljava/lang/String;)V")); env->CallStaticVoidMethod(fwRepositoryClass, methodId, wrap(env, version)); } static void sendFirmwareCompiled(JNIEnv *env, const std::string &version) { auto fwRepositoryClass = ensure(env->FindClass("net/rpcsx/FirmwareRepository")); auto methodId = ensure(env->GetStaticMethodID( fwRepositoryClass, "onFirmwareCompiled", "(Ljava/lang/String;)V")); env->CallStaticVoidMethod(fwRepositoryClass, methodId, wrap(env, version)); } static void sendGameInfo(JNIEnv *env, jlong progressId, std::span infos) { auto gameRepositoryClass = ensure(env->FindClass("net/rpcsx/GameRepository")); auto addMethodId = ensure(env->GetStaticMethodID( gameRepositoryClass, "add", "([Lnet/rpcsx/GameInfo;J)V")); auto gameClass = ensure(env->FindClass("net/rpcsx/GameInfo")); jmethodID gameConstructor = ensure(env->GetMethodID( gameClass, "", "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;I)V")); std::vector objects; objects.reserve(infos.size()); for (const auto &info : infos) { auto path = Emu.GetCallbacks().resolve_path(info.path); if (path.ends_with('/')) { path.resize(path.size() - 1); } objects.push_back(env->NewObject( gameClass, gameConstructor, wrap(env, path), wrap(env, info.name), wrap(env, Emu.GetCallbacks().resolve_path(info.iconPath)), jint(info.flags))); } auto result = env->NewObjectArray(objects.size(), gameClass, nullptr); for (std::size_t i = 0; i < objects.size(); ++i) { env->SetObjectArrayElement(result, i, objects[i]); } env->CallStaticVoidMethod(gameRepositoryClass, addMethodId, result, progressId); } static void sendVshBootable(JNIEnv *env, jlong progressId) { auto dev_flash = g_cfg_vfs.get_dev_flash(); sendGameInfo( env, progressId, {{GameInfo{ .path = dev_flash + "/vsh/module/vsh.self", .name = "VSH", .iconPath = dev_flash + "vsh/resource/explore/icon/icon_home.png", }}}); } static bool tryUnlockGame(const psf::registry &psf) { auto contentId = psf::get_string(psf, "CONTENT_ID"); if (contentId.empty()) { return true; } const auto licenseDir = fmt::format( "%shome/%s/exdata/", rpcs3::utils::get_hdd0_dir(), Emu.GetUsr()); const auto licenseFile = fmt::format("%s%s", licenseDir, contentId); if (std::filesystem::is_regular_file(licenseFile + ".rap")) { return true; } if (std::filesystem::is_regular_file(licenseFile + ".edat")) { return true; } return false; } static void collectGamePaths(std::vector &paths, const std::string &rootDir) { std::error_code ec; std::vector workList; workList.reserve(32); if (!std::filesystem::is_directory(rootDir)) { auto rootPath = std::filesystem::path(rootDir).parent_path(); if (rootPath.filename() == "USRDIR") { rootPath = rootPath.parent_path(); } if (rootPath.filename() == "PS3_GAME") { rootPath = rootPath.parent_path(); } workList.push_back(rootPath); } else { workList.push_back(rootDir); } while (!workList.empty()) { auto dir = std::move(workList.back()); workList.pop_back(); for (auto &entry : std::filesystem::directory_iterator(dir, ec)) { if (entry.is_directory()) { if (entry.path().filename() != "C00") { workList.push_back(entry.path()); } continue; } if (entry.is_regular_file() && entry.path().filename() == "PARAM.SFO") { paths.push_back(entry.path().parent_path().string()); continue; } } } } static std::string locateEbootPath(std::string_view root) { if (std::filesystem::is_regular_file(root)) { return std::string(root); } for (auto suffix : { "/EBOOT.BIN", "/USRDIR/EBOOT.BIN", "/USRDIR/ISO.BIN.EDAT", "/PS3_GAME/USRDIR/EBOOT.BIN", }) { auto tryPath = std::string(root); tryPath += suffix; if (std::filesystem::is_regular_file(tryPath)) { return tryPath; } } return {}; } static std::string locateParamSfoPath(std::string_view root) { if (std::filesystem::is_regular_file(root)) { return std::string(root); } for (auto suffix : { "/PARAM.SFO", "/PS3_GAME/PARAM.SFO", }) { auto tryPath = std::string(root); tryPath += suffix; if (std::filesystem::is_regular_file(tryPath)) { return tryPath; } } return {}; } static std::optional fetchGameInfo(const psf::registry &psf, std::filesystem::path psfRootPath = {}) { auto titleId = std::string(psf::get_string(psf, "TITLE_ID")); auto name = std::string(psf::get_string(psf, "TITLE")); auto bootable = psf::get_integer(psf, "BOOTABLE", 0); auto category = psf::get_string(psf, "CATEGORY"); if (!bootable || titleId.empty()) { return {}; } bool isDiscGame = category == "DG"; std::string path; if (!isDiscGame) { path = rpcs3::utils::get_hdd0_dir() + "game/" + titleId + "/"; } else { if (psfRootPath.empty()) { path = fs::get_config_dir() + "games/" + titleId + "/"; } else { // Locate game root path if (psfRootPath.filename() == "USRDIR") { psfRootPath = psfRootPath.parent_path(); } if (psfRootPath.filename() == "PS3_GAME") { psfRootPath = psfRootPath.parent_path(); } path = psfRootPath; if (!path.ends_with('/')) { path += '/'; } } } auto dataPath = isDiscGame ? path + "PS3_GAME/" : path; auto iconPath = dataPath + "ICON0.PNG"; auto moviePath = dataPath + "ICON1.PAM"; int flags = 0; if (!isDiscGame) { auto ebootPath = locateEbootPath(path); bool isLocked = false; if (!ebootPath.empty()) { if (fs::file eboot{ebootPath}; eboot && eboot.size() >= 4 && eboot.read() == "SCE\0"_u32) { isLocked = !decrypt_self(eboot); } } if (isLocked) { flags |= kGameFlagLocked; rpcsx_android.warning("game %s is locked", path); } auto c00Path = path + "/C00"; bool isTrial = std::filesystem::is_directory(c00Path); if (isTrial) { if (!tryUnlockGame(psf)) { flags |= kGameFlagTrial; rpcsx_android.warning("game %s is trial", path); } else { auto c00IconPath = c00Path + "/ICON0.PNG"; if (std::filesystem::is_regular_file(c00IconPath)) { iconPath = c00IconPath; } auto c00SfoPath = c00Path + "/PARAM.SFO"; if (std::filesystem::is_regular_file(c00IconPath)) { auto c00Sfo = psf::load_object(c00SfoPath); titleId = psf::get_string(c00Sfo, "TITLE_ID", titleId); name = psf::get_string(c00Sfo, "TITLE", name); } } } } return GameInfo{ .path = std::move(path), .name = std::move(name), .iconPath = std::move(iconPath), .flags = flags, }; } static void collectGameInfo(JNIEnv *env, jlong progressId, const std::vector &rootDirs) { std::vector paths; for (auto &&rootDir : rootDirs) { collectGamePaths(paths, rootDir); rpcsx_android.notice("collectGameInfo: processed %s", rootDir); } rpcsx_android.notice("collectGameInfo: found %d paths", paths.size()); Progress progress(env, progressId); progress.report(0, paths.size()); std::vector gameInfos; gameInfos.reserve(10); std::size_t processed = 0; auto submit = [&] { if (gameInfos.empty()) { return; } sendGameInfo(env, progressId, gameInfos); progress.report(processed, paths.size()); gameInfos.clear(); }; for (auto &&path : paths) { processed++; if (!std::filesystem::is_regular_file(path + "/PARAM.SFO")) { continue; } const auto psf = psf::load_object(path + "/PARAM.SFO"); rpcsx_android.notice("collectGameInfo: sfo at %s", path); if (auto gameInfo = fetchGameInfo(psf, path)) { gameInfos.push_back(std::move(*gameInfo)); if (gameInfos.size() >= 10) { submit(); } } } submit(); progress.success(processed); } class MainThreadProcessor { std::mutex mutex; std::condition_variable cv; std::deque, atomic_t *>> queue; public: void push(std::function cb, atomic_t *wakeUp = nullptr) { std::lock_guard lock(mutex); queue.push_back({std::move(cb), wakeUp}); cv.notify_one(); } void push(std::function cb, atomic_t *wakeUp = nullptr) { push([cb = std::move(cb)](JNIEnv *) { cb(); }, wakeUp); } void process(JNIEnv *env) { while (true) { std::function cb; atomic_t *wakeUp = nullptr; { std::unique_lock lock(mutex); if (queue.empty()) { cv.wait(lock); continue; } auto item = std::move(queue.front()); queue.pop_front(); cb = std::move(item.first); wakeUp = item.second; } cb(env); if (wakeUp) { *wakeUp = true; wakeUp->notify_all(); } } } } static g_mainThreadProcessor; static void invokeAsync(std::function cb) { g_mainThreadProcessor.push(std::move(cb)); } static void invokeSync(std::function cb) { atomic_t wakeup{false}; g_mainThreadProcessor.push(std::move(cb), &wakeup); while (wakeup.load() == false) { wakeup.wait(false); } } struct ProgressMessageDialog : MsgDialogBase { jlong progressId; jlong value = 0; jlong max = 0; ProgressMessageDialog(jlong progressId) : progressId(progressId) {} void Create(const std::string &msg, const std::string &title) override { rpcsx_android.warning("ProgressMessageDialog::Create(%s, %s)", msg, title); max = 100; invokeSync([this, &msg](JNIEnv *env) { Progress progress(env, progressId); progress.report(0, 0, msg); }); } jlong getValue() const { return value == max && max != 0 ? value - 1 : value; } void Close(bool success) override { rpcsx_android.warning("ProgressMessageDialog::Close(%s)", success); invokeSync([this](JNIEnv *env) { Progress progress(env, progressId); progress.report(0, 0); }); // Progress progress(env, progressId); // if (success) { // progress.success(0); // } else { // progress.failure(); // } // }); } void SetMsg(const std::string &msg) override { rpcsx_android.warning("ProgressMessageDialog::SetMsg(%s)", msg); invokeSync([this, msg](JNIEnv *env) { Progress(env, progressId).report(getValue(), max, msg); }); } void ProgressBarSetMsg(u32 progressBarIndex, const std::string &msg) override { rpcsx_android.warning("ProgressMessageDialog::ProgressBarSetMsg(%d, %s)", progressBarIndex, msg); if (progressBarIndex != 0) { report_fatal_error("Unexpected progress index in progress dialog"); } invokeSync([this, msg](JNIEnv *env) { Progress(env, progressId).report(getValue(), max, msg); }); } void ProgressBarReset(u32 progressBarIndex) override { rpcsx_android.warning("ProgressMessageDialog::ProgressBarReset(%d)", progressBarIndex); if (progressBarIndex != 0) { report_fatal_error("Unexpected progress index in progress dialog"); } value = 0; invokeSync( [this](JNIEnv *env) { Progress(env, progressId).report(value, max); }); } void ProgressBarInc(u32 progressBarIndex, u32 delta) override { rpcsx_android.warning("ProgressMessageDialog::ProgressBarInc(%d, %d)", progressBarIndex, delta); if (progressBarIndex != 0) { report_fatal_error("Unexpected progress index in progress dialog"); } value += delta; invokeSync([this](JNIEnv *env) { Progress(env, progressId).report(getValue(), max); }); } void ProgressBarSetValue(u32 progressBarIndex, u32 value) override { rpcsx_android.warning("ProgressMessageDialog::ProgressBarSetValue(%d, %d)", progressBarIndex, value); if (progressBarIndex != 0) { report_fatal_error("Unexpected progress index in progress dialog"); } this->value = value; invokeSync([this](JNIEnv *env) { Progress(env, progressId).report(getValue(), max); }); } void ProgressBarSetLimit(u32 index, u32 limit) override { rpcsx_android.warning("ProgressMessageDialog::ProgressBarSetLimit(%d, %d)", index, limit); if (index != 0) { report_fatal_error("Unexpected progress index in progress dialog"); } max = limit; invokeSync([this](JNIEnv *env) { Progress(env, progressId).report(getValue(), max); }); } }; struct UiMessageDialog : MsgDialogBase { // FIXME: implement void Create(const std::string &msg, const std::string &title) override {} void Close(bool success) override {} void SetMsg(const std::string &msg) override {} void ProgressBarSetMsg(u32 progressBarIndex, const std::string &msg) override {} void ProgressBarReset(u32 progressBarIndex) override {} void ProgressBarInc(u32 progressBarIndex, u32 delta) override {} void ProgressBarSetValue(u32 progressBarIndex, u32 value) override {} void ProgressBarSetLimit(u32 index, u32 limit) override {} }; struct MessageDialog : MsgDialogBase { std::unique_ptr impl = nullptr; void Create(const std::string &msg, const std::string &title) override { auto progressId = s_pendingProgressId.load(); rpcsx_android.warning("MessageDialog::Create(%s, %s): source %s, id %d", msg, title, source, progressId); if (progressId != -1) { impl = std::make_unique(progressId); } else { impl = std::make_unique(); } impl->type = type; impl->source = source; impl->Create(msg, title); } void Close(bool success) override { impl->Close(success); } void SetMsg(const std::string &msg) override { impl->SetMsg(msg); } void ProgressBarSetMsg(u32 progressBarIndex, const std::string &msg) override { impl->ProgressBarSetMsg(progressBarIndex, msg); } void ProgressBarReset(u32 progressBarIndex) override { impl->ProgressBarReset(progressBarIndex); } void ProgressBarInc(u32 progressBarIndex, u32 delta) override { impl->ProgressBarInc(progressBarIndex, delta); } void ProgressBarSetValue(u32 progressBarIndex, u32 value) override { impl->ProgressBarSetValue(progressBarIndex, value); } void ProgressBarSetLimit(u32 index, u32 limit) override { impl->ProgressBarSetLimit(index, limit); } static void pushPendingProgressId(jlong id) { jlong value = -1; while (!s_pendingProgressId.compare_exchange_weak(value, id)) { s_pendingProgressId.wait(value); value = -1; } } static bool popPendingProgressId(jlong id) { return s_pendingProgressId.compare_exchange_strong(id, -1); } private: static std::atomic s_pendingProgressId; }; struct OverlaySaveDialog : SaveDialogBase { s32 ShowSaveDataList(const std::string &base_dir, std::vector &save_entries, s32 focused, u32 op, vm::ptr listSet, bool enable_overlay) override { rpcsx_android.notice("ShowSaveDataList(save_entries=%d, focused=%d, " "op=0x%x, listSet=*0x%x, enable_overlay=%d)", save_entries.size(), focused, op, listSet, enable_overlay); bool use_end = sysutil_send_system_cmd(CELL_SYSUTIL_DRAWING_BEGIN, 0) >= 0; auto atExit = AtExit([&] { if (use_end) { sysutil_send_system_cmd(CELL_SYSUTIL_DRAWING_END, 0); } }); if (!use_end) { rpcsx_android.error( "ShowSaveDataList(): Not able to notify DRAWING_BEGIN callback " "because one has already been sent!"); } if (auto manager = g_fxo->try_get()) { rpcsx_android.notice("ShowSaveDataList: Showing native UI dialog"); s32 result = manager->create()->show( base_dir, save_entries, focused, op, listSet, enable_overlay); if (result != rsx::overlays::user_interface::selection_code::error) { rpcsx_android.notice( "ShowSaveDataList: Native UI dialog returned with selection %d", result); return result; } rpcsx_android.error("ShowSaveDataList: Native UI dialog returned error"); } return -2; } }; class OverlayTrophyNotification : public TrophyNotificationBase { public: s32 ShowTrophyNotification( const SceNpTrophyDetails &trophy, const std::vector &trophy_icon_buffer) override { if (auto manager = g_fxo->try_get()) { auto popup = std::make_shared(); return manager->add(popup, false)->show(trophy, trophy_icon_buffer); } return 0; } }; std::atomic MessageDialog::s_pendingProgressId = -1; struct CompilationWorkload { jlong progressId; std::string path; }; extern bool ppu_load_exec(const ppu_exec_object &, bool virtual_load, const std::string &, utils::serial * = nullptr); extern void spu_load_exec(const spu_exec_object &); extern void spu_load_rel_exec(const spu_rel_object &); extern void ppu_precompile(std::vector &dir_queue, std::vector *> *loaded_prx); extern bool ppu_initialize(const ppu_module &, bool check_only = false, u64 file_size = 0); extern void ppu_finalize(const ppu_module &); extern bool ppu_load_rel_exec(const ppu_rel_object &); class CompilationQueue { std::atomic nextWorkTag{0}; std::uint64_t lastProcessedTag = 0; std::mutex queueMutex; std::deque queue; public: void push(CompilationWorkload workload) { { std::lock_guard lock(queueMutex); queue.push_back(std::move(workload)); } nextWorkTag.fetch_add(1); } void push(Progress &progress, std::string path) { progress.report(0, 0); push({ .progressId = progress.getProgressId(), .path = std::move(path), }); } void process(JNIEnv *env) { while (true) { auto nextWorkTagValue = nextWorkTag.load(); if (nextWorkTagValue == lastProcessedTag) { nextWorkTag.wait(lastProcessedTag); } if (nextWorkTagValue == lastProcessedTag || queue.empty()) { continue; } CompilationWorkload workload; { std::lock_guard lock(queueMutex); if (queue.empty()) { continue; } workload = std::move(queue.front()); queue.pop_front(); } impl(env, std::move(workload)); lastProcessedTag++; } } private: void impl(JNIEnv *env, CompilationWorkload workload) { if (workload.path.empty()) { Progress(env, workload.progressId).success(0); return; } rpcsx_android.error("Creating cache initiated, state %d", (int)Emu.GetStatus(false)); while (true) { auto state = Emu.GetStatus(false); if (state == system_state::stopped || state == system_state::ready) { break; } rpcsx_android.error("Creating cache wait, state %d", (int)state); std::this_thread::sleep_for(std::chrono::seconds(1)); } bool is_vsh = workload.path.ends_with("/vsh.self"); Emu.SetState(system_state::running); MessageDialog::pushPendingProgressId(workload.progressId); g_fxo->init>(); g_fxo->init>(); g_fxo->init(false, nullptr); auto rootPath = std::filesystem::path(workload.path); if (is_vsh) { rootPath = g_cfg_vfs.get_dev_flash() + "sys/external/"; } else { if (!std::filesystem::is_directory(rootPath)) { rootPath = rootPath.parent_path(); if (rootPath.filename() == "USRDIR") { rootPath = rootPath.parent_path(); } } } auto &_main = *ensure(g_fxo->try_get>()); if (fs::is_file(workload.path)) { if (!is_vsh) { auto sfoPath = locateParamSfoPath(std::string(rootPath)); if (!sfoPath.empty()) { const auto psf = psf::load_object(sfoPath); rpcsx_android.warning("title id is %s", psf::get_string(psf, "TITLE_ID")); Emu.SetTitleID(std::string(psf::get_string(psf, "TITLE_ID"))); } else { rpcsx_android.warning("param.sfo not found"); } } // Compile binary first rpcsx_android.notice("Trying to load binary: %s", workload.path); fs::file src{workload.path}; src = decrypt_self(src); const ppu_exec_object obj = src; if (obj == elf_error::ok && ppu_load_exec(obj, true, workload.path)) { _main.path = workload.path; } else { rpcsx_android.error("Failed to load binary '%s' (%s)", workload.path, obj.get_error()); } } std::vector dir_queue; dir_queue.push_back(rootPath.string()); for (auto &entry : std::filesystem::recursive_directory_iterator(rootPath)) { if (entry.is_directory()) { dir_queue.push_back(entry.path().string()); } } std::vector *> mod_list; rpcsx_android.error("Going to analyze executable"); // FIXME: split states if (!is_vsh) { if (_main.analyse(0, _main.elf_entry, _main.seg0_code_end, _main.applied_patches, std::vector{})) { Emu.ConfigurePPUCache(); Emu.SetTestMode(); rpcsx_android.error("Going to precompile main PPU module"); ppu_initialize(_main); mod_list.emplace_back(&_main); } } ppu_precompile(dir_queue, mod_list.empty() ? nullptr : &mod_list); rpcsx_android.error("Finalization"); g_fxo->reset(); Emu.SetState(system_state::stopped); MessageDialog::popPendingProgressId(workload.progressId); Progress(env, workload.progressId).success(0); } } static g_compilationQueue; static void setupCallbacks() { Emu.SetCallbacks({ .call_from_main_thread = [](std::function cb, atomic_t *wake_up) { cb(); if (wake_up) { *wake_up = true; } }, .on_run = [](auto...) {}, .on_pause = [](auto...) {}, .on_resume = [](auto...) {}, .on_stop = [](auto...) {}, .on_ready = [](auto...) {}, .on_missing_fw = [](auto...) {}, .on_emulation_stop_no_response = [](auto...) {}, .on_save_state_progress = [](auto...) {}, .enable_disc_eject = [](auto...) {}, .enable_disc_insert = [](auto...) {}, .handle_taskbar_progress = [](auto...) {}, .init_kb_handler = [](auto...) { ensure(g_fxo->init( Emu.DeserialManager())); }, .init_mouse_handler = [](auto...) { ensure(g_fxo->init( Emu.DeserialManager())); }, .init_pad_handler = [](auto...) { ensure(g_fxo->init>(nullptr, nullptr, "")); }, .update_emu_settings = [](auto...) {}, .save_emu_settings = [](auto...) { Emulator::SaveSettings(g_cfg.to_string(), Emu.GetTitleID()); }, .close_gs_frame = [](auto...) {}, .get_gs_frame = [] { return std::make_unique(); }, .get_camera_handler = [](auto...) { return std::make_shared(); }, .get_music_handler = [](auto...) { return std::make_shared(); }, .create_pad_handler = [](pad_handler type, void *thread, void *window) -> std::shared_ptr { switch (type) { case pad_handler::keyboard: case pad_handler::null: break; case pad_handler::ds3: return std::make_shared(); case pad_handler::ds4: return std::make_shared(); case pad_handler::dualsense: return std::make_shared(); case pad_handler::skateboard: // return std::make_shared(); break; case pad_handler::move: // return std::make_shared(); break; #ifdef _WIN32 case pad_handler::xinput: return std::make_shared(); case pad_handler::mm: return std::make_shared(); #endif #ifdef HAVE_SDL3 case pad_handler::sdl: return std::make_shared(); #endif #ifdef HAVE_LIBEVDEV case pad_handler::evdev: return std::make_shared(); #endif case pad_handler::virtual_pad: return std::make_shared(); } return std::make_shared(); }, .init_gs_render = [](utils::serial *ar) { switch (g_cfg.video.renderer.get()) { case video_renderer::null: g_fxo->init>(ar); break; case video_renderer::vulkan: g_fxo->init>(ar); break; default: break; } }, .get_audio = [](auto...) { std::shared_ptr result; switch (g_cfg.audio.renderer.get()) { case audio_renderer::null: result = std::make_shared(); break; case audio_renderer::cubeb: default: result = std::make_shared(); break; } if (!result->Initialized()) { rpcsx_android.error( "Audio renderer %s could not be initialized, using a Null " "renderer instead. Make sure that no other application is " "running that might block audio access (e.g. Netflix).", result->GetName()); result = std::make_shared(); } return result; }, .get_audio_enumerator = [](auto...) { return nullptr; }, .get_msg_dialog = [] { return std::make_shared(); }, .get_osk_dialog = [](auto...) { return nullptr; }, .get_save_dialog = [](auto...) { return std::make_unique(); }, .get_sendmessage_dialog = [](auto...) { return nullptr; }, .get_recvmessage_dialog = [](auto...) { return nullptr; }, .get_trophy_notification_dialog = [](auto...) { return std::make_unique(); }, .get_localized_string = [](localized_string_id id, const char *) -> std::string { if (std::size_t(id) < std::size(g_strings)) { return g_strings[int(id)].first; } return ""; }, .get_localized_u32string = [](localized_string_id id, const char *) -> std::u32string { if (std::size_t(id) < std::size(g_strings)) { return g_strings[int(id)].second; } return U""; }, .get_localized_setting = [](auto...) { return ""; }, .play_sound = [](auto...) {}, .get_image_info = [](auto...) { return false; }, .get_scaled_image = [](auto...) { return false; }, .resolve_path = [](std::string_view arg) { std::error_code ec; auto result = std::filesystem::weakly_canonical( std::filesystem::path(fmt::replace_all(arg, "\\", "/")), ec) .string(); return ec ? std::string(arg) : result; }, .get_font_dirs = [](auto...) { return std::vector(); }, .on_install_pkgs = [](const std::vector &pkgs) { for (const std::string &pkg : pkgs) { if (!rpcs3::utils::install_pkg(pkg)) { rpcsx_android.error("cd install pkgs: failed to install %s", pkg); return false; } } return true; }, .add_breakpoint = [](auto...) {}, .display_sleep_control_supported = [](auto...) { return false; }, .enable_display_sleep = [](auto...) {}, .check_microphone_permissions = [](auto...) {}, }); } static bool initVirtualPad(const std::shared_ptr &pad) { u32 pclass_profile = 0; pad->Init(CELL_PAD_STATUS_CONNECTED, CELL_PAD_CAPABILITY_PS3_CONFORMITY | CELL_PAD_CAPABILITY_PRESS_MODE | CELL_PAD_CAPABILITY_HP_ANALOG_STICK | CELL_PAD_CAPABILITY_ACTUATOR //| CELL_PAD_CAPABILITY_SENSOR_MODE , CELL_PAD_DEV_TYPE_STANDARD, CELL_PAD_PCLASS_TYPE_STANDARD, pclass_profile, 0, 0, 50); pad->m_buttons.emplace_back(CELL_PAD_BTN_OFFSET_DIGITAL1, std::set{}, CELL_PAD_CTRL_UP); pad->m_buttons.emplace_back(CELL_PAD_BTN_OFFSET_DIGITAL1, std::set{}, CELL_PAD_CTRL_DOWN); pad->m_buttons.emplace_back(CELL_PAD_BTN_OFFSET_DIGITAL1, std::set{}, CELL_PAD_CTRL_LEFT); pad->m_buttons.emplace_back(CELL_PAD_BTN_OFFSET_DIGITAL1, std::set{}, CELL_PAD_CTRL_RIGHT); pad->m_buttons.emplace_back(CELL_PAD_BTN_OFFSET_DIGITAL2, std::set{}, CELL_PAD_CTRL_CROSS); pad->m_buttons.emplace_back(CELL_PAD_BTN_OFFSET_DIGITAL2, std::set{}, CELL_PAD_CTRL_SQUARE); pad->m_buttons.emplace_back(CELL_PAD_BTN_OFFSET_DIGITAL2, std::set{}, CELL_PAD_CTRL_CIRCLE); pad->m_buttons.emplace_back(CELL_PAD_BTN_OFFSET_DIGITAL2, std::set{}, CELL_PAD_CTRL_TRIANGLE); pad->m_buttons.emplace_back(CELL_PAD_BTN_OFFSET_DIGITAL2, std::set{}, CELL_PAD_CTRL_L1); pad->m_buttons.emplace_back(CELL_PAD_BTN_OFFSET_DIGITAL2, std::set{}, CELL_PAD_CTRL_L2); pad->m_buttons.emplace_back(CELL_PAD_BTN_OFFSET_DIGITAL1, std::set{}, CELL_PAD_CTRL_L3); pad->m_buttons.emplace_back(CELL_PAD_BTN_OFFSET_DIGITAL2, std::set{}, CELL_PAD_CTRL_R1); pad->m_buttons.emplace_back(CELL_PAD_BTN_OFFSET_DIGITAL2, std::set{}, CELL_PAD_CTRL_R2); pad->m_buttons.emplace_back(CELL_PAD_BTN_OFFSET_DIGITAL1, std::set{}, CELL_PAD_CTRL_R3); pad->m_buttons.emplace_back(CELL_PAD_BTN_OFFSET_DIGITAL1, std::set{}, CELL_PAD_CTRL_START); pad->m_buttons.emplace_back(CELL_PAD_BTN_OFFSET_DIGITAL1, std::set{}, CELL_PAD_CTRL_SELECT); pad->m_buttons.emplace_back(CELL_PAD_BTN_OFFSET_DIGITAL1, std::set{}, CELL_PAD_CTRL_PS); pad->m_sticks[0] = AnalogStick(CELL_PAD_BTN_OFFSET_ANALOG_LEFT_X, {}, {}); pad->m_sticks[1] = AnalogStick(CELL_PAD_BTN_OFFSET_ANALOG_LEFT_Y, {}, {}); pad->m_sticks[2] = AnalogStick(CELL_PAD_BTN_OFFSET_ANALOG_RIGHT_X, {}, {}); pad->m_sticks[3] = AnalogStick(CELL_PAD_BTN_OFFSET_ANALOG_RIGHT_Y, {}, {}); pad->m_sensors[0] = AnalogSensor(CELL_PAD_BTN_OFFSET_SENSOR_X, 0, 0, 0, DEFAULT_MOTION_X); pad->m_sensors[1] = AnalogSensor(CELL_PAD_BTN_OFFSET_SENSOR_Y, 0, 0, 0, DEFAULT_MOTION_Y); pad->m_sensors[2] = AnalogSensor(CELL_PAD_BTN_OFFSET_SENSOR_Z, 0, 0, 0, DEFAULT_MOTION_Z); pad->m_sensors[3] = AnalogSensor(CELL_PAD_BTN_OFFSET_SENSOR_G, 0, 0, 0, DEFAULT_MOTION_G); pad->m_vibrateMotors[0] = VibrateMotor(true, 0); pad->m_vibrateMotors[1] = VibrateMotor(false, 0); if (pad->m_player_id == 0) { std::lock_guard lock(g_virtual_pad_mutex); g_virtual_pad = pad; } return true; } extern "C" bool _rpcsx_overlayPadData(int digital1, int digital2, int leftStickX, int leftStickY, int rightStickX, int rightStickY) { auto pad = [] { std::shared_ptr result; std::lock_guard lock(g_virtual_pad_mutex); result = g_virtual_pad; return result; }(); if (pad == nullptr) { return false; } for (auto &btn : pad->m_buttons) { if (btn.m_offset == CELL_PAD_BTN_OFFSET_DIGITAL1) { btn.m_pressed = (digital1 & btn.m_outKeyCode) != 0; if (btn.m_outKeyCode == CELL_PAD_CTRL_PS && btn.m_pressed) { if (auto padThread = pad::get_pad_thread(true)) { padThread->open_home_menu(); } } } else if (btn.m_offset == CELL_PAD_BTN_OFFSET_DIGITAL2) { btn.m_pressed = (digital2 & btn.m_outKeyCode) != 0; } btn.m_value = btn.m_pressed ? 255 : 0; } pad->m_sticks[0].m_value = leftStickX; pad->m_sticks[1].m_value = leftStickY; pad->m_sticks[2].m_value = rightStickX; pad->m_sticks[3].m_value = rightStickY; return true; } extern "C" bool _rpcsx_initialize(std::string_view rootDir, std::string_view user) { auto rootDirStr = fix_dir_path(std::string(rootDir)); if (g_android_executable_dir != rootDirStr) { g_android_executable_dir = rootDirStr; g_android_config_dir = rootDirStr + "config/"; g_android_cache_dir = rootDirStr + "cache/"; std::filesystem::create_directories(g_android_config_dir); std::error_code ec; // std::filesystem::remove_all(g_android_cache_dir, ec); std::filesystem::create_directories(g_android_cache_dir); } if (g_initialized) { return true; } g_initialized = true; if (int r = libusb_set_option(nullptr, LIBUSB_OPTION_NO_DEVICE_DISCOVERY, nullptr); r != 0) { rpcsx_android.warning( "libusb_set_option(LIBUSB_OPTION_NO_DEVICE_DISCOVERY) -> %d", r); } // Initialize thread pool finalizer // ??? static_cast(named_thread("", [](int) {})); static std::unique_ptr log_file; { // Check free space fs::device_stat stats{}; if (!fs::statfs(fs::get_cache_dir(), stats) || stats.avail_free < 128 * 1024 * 1024) { std::fprintf(stderr, "Not enough free space for logs (%f KB)", stats.avail_free / 1000000.); } // preserve old log file if (std::filesystem::exists(fs::get_log_dir() + "RPCSX.log")) { std::error_code ec; std::filesystem::remove(fs::get_log_dir() + "RPCSX.old.log", ec); std::filesystem::rename(fs::get_log_dir() + "RPCSX.log", fs::get_log_dir() + "RPCSX.old.log", ec); } // Limit log size to ~25% of free space log_file = logs::make_file_listener(fs::get_log_dir() + "RPCSX.log", stats.avail_free / 4); } logs::stored_message ver{rpcsx_android.always()}; ver.text = fmt::format("RPCSX-ps3-android v%s", rx::getVersion().toString()); // Write System information logs::stored_message sys{rpcsx_android.always()}; sys.text = utils::get_system_info(); // Write OS version logs::stored_message os{rpcsx_android.always()}; os.text = utils::get_OS_version_string(); // Write current time logs::stored_message time{rpcsx_android.always()}; time.text = fmt::format("Current Time: %s", std::chrono::system_clock::now()); logs::set_init( {std::move(ver), std::move(sys), std::move(os), std::move(time)}); auto set_rlim = [](int resource, std::uint64_t limit) { rlimit64 rlim{}; if (getrlimit64(resource, &rlim) != 0) { rpcsx_android.error("failed to get rlimit for %d", resource); return; } rlim.rlim_cur = std::min(rlim.rlim_max, limit); rpcsx_android.error("rlimit[%d] = %u (requested %u, max %u)", resource, rlim.rlim_cur, limit, rlim.rlim_max); if (setrlimit64(resource, &rlim) != 0) { rpcsx_android.error("failed to set rlimit for %d", resource); return; } }; set_rlim(RLIMIT_MEMLOCK, RLIM_INFINITY); set_rlim(RLIMIT_NOFILE, RLIM_INFINITY); set_rlim(RLIMIT_STACK, 128 * 1024 * 1024); set_rlim(RLIMIT_AS, RLIM_INFINITY); virtual_pad_handler::set_on_connect_cb(initVirtualPad); setupCallbacks(); Emu.SetHasGui(false); Emu.SetUsr(std::string(user)); Emu.Init(); g_cfg_input.player1.handler.set(pad_handler::virtual_pad); g_cfg_input.player1.device.from_string("Virtual"); g_cfg_input.save("", g_cfg_input_configs.default_config); g_cfg.core.llvm_cpu.from_string("cortex-a34"); Emulator::SaveSettings(g_cfg.to_string(), Emu.GetTitleID()); return true; } extern "C" bool _rpcsx_processCompilationQueue(JNIEnv *env) { g_compilationQueue.process(env); return true; } extern "C" bool _rpcsx_startMainThreadProcessor(JNIEnv *env) { g_mainThreadProcessor.process(env); return true; } extern "C" bool _rpcsx_collectGameInfo(JNIEnv *env, std::string_view rootDir, long progressId) { if (std::filesystem::is_regular_file(g_cfg_vfs.get_dev_flash() + "/vsh/module/vsh.self")) { sendVshBootable(env, progressId); } collectGameInfo(env, progressId, {std::string(rootDir)}); return true; } extern "C" void _rpcsx_shutdown() { Emu.Kill(); } extern "C" int _rpcsx_boot(std::string_view path_) { Emu.SetForceBoot(true); std::string path = std::string(path_); while (path.ends_with('/')) { path.pop_back(); } return static_cast(Emu.BootGame(path, "", false, cfg_mode::global)); } extern "C" int _rpcsx_getState() { return static_cast(Emu.GetStatus(false)); } extern "C" void _rpcsx_kill() { Emu.Kill(); } extern "C" void _rpcsx_resume() { Emu.Resume(); } extern "C" void _rpcsx_openHomeMenu() { if (auto padThread = pad::get_pad_thread(true)) { padThread->open_home_menu(); } } extern "C" std::string _rpcsx_getTitleId() { return Emu.GetTitleID(); } extern "C" bool _rpcsx_surfaceEvent(JNIEnv *env, jobject surface, jint event) { rpcsx_android.warning("surface event %p, %d", surface, event); if (event == 2) { auto prevWindow = g_native_window.exchange(nullptr); if (prevWindow != nullptr) { ANativeWindow_release(prevWindow); } if (auto padThread = pad::get_pad_thread()) { padThread->open_home_menu(); } Emu.Pause(); } else { auto newWindow = ANativeWindow_fromSurface(env, surface); if (newWindow == nullptr) { rpcsx_android.fatal("returned native window is null, surface %p", surface); return false; } auto prevWindow = g_native_window.exchange(newWindow); if (newWindow != prevWindow) { ANativeWindow_acquire(newWindow); if (prevWindow != nullptr) { ANativeWindow_release(prevWindow); } } if (event == 0 && Emu.IsPaused()) { Emu.Resume(); } } return true; } extern "C" bool _rpcsx_usbDeviceEvent(int fd, int vendorId, int productId, int event) { rpcsx_android.warning( "usb device event %d fd: %d, vendorId: %d, productId: %d", event, fd, vendorId, productId); { std::lock_guard lock(g_android_usb_devices_mutex); if (event == 0) { g_android_usb_devices.push_back({ .fd = int(fd), .vendorId = u16(vendorId), .productId = u16(productId), }); } else { auto filter = [fd](auto device) { return device.fd == fd; }; if (auto it = std::ranges::find_if(g_android_usb_devices, filter); it != g_android_usb_devices.end()) { g_android_usb_devices.erase(it); } } } { auto selectedHandler = g_cfg_input.player1.handler.get(); std::string selectedDevice; std::map, std::vector>> handlerToDevices; auto collectDevices = [&](T handler) { handler->Init(); std::vector devices; for (const auto &device : handler->list_connected_devices()) { devices.push_back(device.name); } auto type = handler->m_type; handlerToDevices[type] = std::pair{ std::move(handler), std::move(devices), }; }; collectDevices(std::make_unique()); collectDevices(std::make_unique()); collectDevices(std::make_unique()); if (handlerToDevices[selectedHandler].second.empty()) { selectedHandler = pad_handler::null; } if (!handlerToDevices[pad_handler::dualsense].second.empty()) { selectedHandler = pad_handler::dualsense; } else if (!handlerToDevices[pad_handler::ds4].second.empty()) { selectedHandler = pad_handler::ds4; } else if (!handlerToDevices[pad_handler::ds3].second.empty()) { selectedHandler = pad_handler::ds3; } if (selectedHandler == pad_handler::null) { selectedHandler = pad_handler::virtual_pad; } if (selectedHandler != g_cfg_input.player1.handler.get()) { rpcsx_android.warning("install %s pad handler", selectedHandler); g_cfg_input.player1.handler.set(selectedHandler); if (selectedHandler == pad_handler::null) { g_cfg_input.player1.device.from_default(); } else if (selectedHandler == pad_handler::virtual_pad) { g_cfg_input.player1.handler.set(pad_handler::virtual_pad); g_cfg_input.player1.device.from_string("Virtual"); } else { g_cfg_input.player1.device.from_string( handlerToDevices[selectedHandler].second.front()); handlerToDevices[selectedHandler].first->init_config( &g_cfg_input.player1.config); if (selectedHandler != pad_handler::virtual_pad) { std::lock_guard lock(g_virtual_pad_mutex); g_virtual_pad = nullptr; } } g_cfg_input.save("", g_cfg_input_configs.default_config); if (!Emu.IsStopped()) { pad::reset(Emu.GetTitleID()); } } } return true; } static bool installPup(JNIEnv *env, fs::file &&pup_f, jlong progressId) { Progress progress(env, progressId); pup_object pup(std::move(pup_f)); AtExit atExit{[&] { pup.file().release_handle(); }}; if (static_cast(pup) == pup_error::hash_mismatch) { rpcsx_android.fatal("installFw: invalid PUP"); progress.failure("Selected file is not firmware update file"); return false; } if (static_cast(pup) != pup_error::ok) { rpcsx_android.fatal("installFw: invalid PUP"); progress.failure("Firmware update file is broken"); return false; } fs::file update_files_f = pup.get_file(0x300); const usz update_files_size = update_files_f ? update_files_f.size() : 0; if (!update_files_size) { rpcsx_android.fatal("installFw: invalid PUP"); progress.failure("Firmware update file is broken"); return false; } tar_object update_files(update_files_f); auto update_filenames = update_files.get_filenames(); update_filenames.erase(std::remove_if(update_filenames.begin(), update_filenames.end(), [](const std::string &s) { return !s.starts_with("dev_flash_"); }), update_filenames.end()); if (update_filenames.empty()) { rpcsx_android.fatal("installFw: invalid PUP"); progress.failure("Firmware update file is broken"); return false; } std::string version_string; if (fs::file version = pup.get_file(0x100)) { version_string = version.to_string(); } if (const usz version_pos = version_string.find('\n'); version_pos != std::string::npos) { version_string.erase(version_pos); } if (version_string.empty()) { rpcsx_android.fatal("installFw: invalid PUP"); progress.failure("Firmware update file is broken"); return false; } sendVshBootable(env, progressId); jlong processed = 0; for (const auto &update_filename : update_filenames) { auto update_file_stream = update_files.get_file(update_filename); if (update_file_stream->m_file_handler) { // Forcefully read all the data update_file_stream->m_file_handler->handle_file_op( *update_file_stream, 0, update_file_stream->get_size(umax), nullptr); } fs::file update_file = fs::make_stream(std::move(update_file_stream->data)); SCEDecrypter self_dec(update_file); self_dec.LoadHeaders(); self_dec.LoadMetadata(SCEPKG_ERK, SCEPKG_RIV); self_dec.DecryptData(); auto dev_flash_tar_f = self_dec.MakeFile(); if (dev_flash_tar_f.size() < 3) { rpcsx_android.error( "Firmware installation failed: Firmware could not be decompressed"); progress.failure("Firmware update file could not be decompressed"); return false; } tar_object dev_flash_tar(dev_flash_tar_f[2]); if (!dev_flash_tar.extract()) { rpcsx_android.error("Error while installing firmware: TAR contents are " "invalid. (package=%s)", update_filename); progress.failure(fmt::format("TAR contents are invalid (package=%s)", update_filename)); return false; } if (!progress.report(processed++, update_filenames.size())) { // Installation was cancelled return false; } } sendFirmwareInstalled(env, utils::get_firmware_version()); g_compilationQueue.push(progress, g_cfg_vfs.get_dev_flash() + "/vsh/module/vsh.self"); return true; } static bool installPkg(JNIEnv *env, fs::file &&file, jlong progressId) { Progress progress(env, progressId); std::deque readers; std::deque bootable_paths; readers.emplace_back("dummy.pkg", std::move(file)); AtExit atExit{[&] { for (auto &reader : readers) { reader.file().release_handle(); } }}; package_install_result result = {}; named_thread worker("PKG Installer", [&readers, &result, &bootable_paths] { result = package_reader::extract_data(readers, bootable_paths); return result.error == package_install_result::error_type::no_error; }); for (auto &reader : readers) { if (auto gameInfo = fetchGameInfo(reader.get_psf())) { sendGameInfo(env, progressId, {{*gameInfo}}); } } const jlong maxProgress = 10000; while (true) { std::uint64_t totalProgress = 0; for (auto &reader : readers) { if (result.error != package_install_result::error_type::no_error) { progress.failure("Installation failed"); for (package_reader &reader : readers) { reader.abort_extract(); } return false; } totalProgress += reader.get_progress(maxProgress); } if (totalProgress == maxProgress * readers.size()) { break; } totalProgress /= readers.size(); if (!progress.report(totalProgress, maxProgress)) { for (package_reader &reader : readers) { reader.abort_extract(); } return false; } std::this_thread::sleep_for(std::chrono::seconds(2)); } if (worker()) { auto paths = std::vector(bootable_paths.begin(), bootable_paths.end()); collectGameInfo(env, -1, paths); for (auto &path : paths) { g_compilationQueue.push(progress, std::move(path)); } } return true; } static bool installEdat(JNIEnv *env, fs::file &&file, jlong progressId, std::string_view rootPath = {}) { Progress progress(env, progressId); NPD_HEADER npdHeader; if (!file.read(npdHeader)) { progress.failure("Invalid EDAT file"); return false; } if (!rootPath.empty()) { auto ebootPath = locateEbootPath(rootPath); auto sfoPath = locateParamSfoPath(rootPath); if (sfoPath.empty()) { progress.failure("Game is broken: PARAM.SFO not found"); return false; } auto psf = psf::load_object(sfoPath); auto contentId = psf::get_string(psf, "CONTENT_ID"); if (contentId != npdHeader.content_id) { progress.failure(fmt::format("File cannot be used for this game. EDAT " "content ID missmatch %s vs %s", contentId, npdHeader.content_id)); return false; } } const auto licenseFile = fmt::format("%shome/%s/exdata/%s.edat", rpcs3::utils::get_hdd0_dir(), Emu.GetUsr(), npdHeader.content_id); file.seek(0); std::vector bytes(file.size()); if (!file.read(bytes)) { progress.failure("Failed to read key"); return false; } if (!fs::write_file(licenseFile, fs::open_mode::create + fs::open_mode::trunc, bytes)) { progress.failure(fmt::format("Failed to write EDAT to %s", licenseFile)); return false; } auto root = std::string(rootPath); if (root.empty()) { root = rpcs3::utils::get_hdd0_dir() + "game"; } collectGameInfo(env, progressId, {std::move(root)}); return true; } static bool installRap(JNIEnv *env, fs::file &&file, jlong progressId, std::string_view rootPath) { Progress progress(env, progressId); auto ebootPath = locateEbootPath(rootPath); std::vector bytes; if (!file.read(bytes, 16)) { progress.failure("Failed to read key"); return false; } SelfAdditionalInfo info; decrypt_self(fs::file(ebootPath), nullptr, &info); auto npd = [&]() -> NPD_HEADER * { for (auto &supplemental : info.supplemental_hdr) { if (supplemental.type == 3) { return &supplemental.PS3_npdrm_header.npd; } } return nullptr; }(); if (npd == nullptr) { progress.failure("Failed to fetch NPDRM of SELF"); return false; } const auto licenseFile = fmt::format("%shome/%s/exdata/%s.rap", rpcs3::utils::get_hdd0_dir(), Emu.GetUsr(), npd->content_id); if (!fs::write_file(licenseFile, fs::open_mode::create + fs::open_mode::trunc, bytes)) { progress.failure(fmt::format("Failed to write key to %s", licenseFile)); return false; } if (!decrypt_self(fs::file(ebootPath))) { progress.failure("Provided key is invalid for selected game"); fs::remove_file(licenseFile); return false; } collectGameInfo(env, -1, {std::string(rootPath)}); g_compilationQueue.push(progress, std::move(ebootPath)); return true; } static bool installIso(JNIEnv *env, fs::file &&file, jlong progressId) { auto optIso = iso_dev::open(std::make_unique(file)); Progress progress(env, progressId); if (!optIso) { progress.failure("Failed to read ISO"); return false; } auto iso = std::move(*optIso); auto sfo_raw_file = iso.open("PS3_GAME/PARAM.SFO", fs::read); if (!sfo_raw_file) { progress.failure("Failed to locate PARAM.SFO in ISO"); return false; } fs::file sfo_file; sfo_file.reset(std::move(sfo_raw_file)); auto sfo = psf::load_object(sfo_file, "iso://PS3_GAME/PARAM.SFO"); auto title_id = psf::get_string(sfo, "TITLE_ID"); if (title_id.empty()) { progress.failure("Failed to fetch TITLE_ID from PARAM.SFO in ISO"); return false; } if (auto gameInfo = fetchGameInfo(sfo)) { sendGameInfo(env, progressId, {{*gameInfo}}); } std::filesystem::path destinationPath = fs::get_config_dir() + "games/" + std::string(title_id); std::size_t filesCount = 0; auto roots = [&] { std::vector result; std::vector workList; workList.push_back({}); result.push_back({}); while (!workList.empty()) { auto path = std::move(workList.back()); workList.pop_back(); fs::dir dir; dir.reset(iso.open_dir(path)); for (auto &entry : dir) { if (entry.name == "." || entry.name == "..") { continue; } if (entry.name == "PS3_UPDATE" && path.empty()) { continue; } if (entry.is_directory) { result.push_back(path / entry.name); workList.push_back(path / entry.name); } else { filesCount++; } } } return result; }(); progress.report(0, filesCount); std::size_t processedFiles = 0; std::error_code ec; for (auto &root : roots) { auto rootDestPath = root.empty() ? destinationPath : destinationPath / root; std::filesystem::create_directories(rootDestPath, ec); if (ec) { progress.failure(fmt::format("Failed to create dir %s: %s", rootDestPath.string(), ec.message())); return false; } fs::dir dir; dir.reset(iso.open_dir(root)); for (auto &entry : dir) { if (entry.name == "." || entry.name == "..") { continue; } auto entryDestPath = rootDestPath / entry.name; if (entry.is_directory) { std::filesystem::create_directories(entryDestPath, ec); if (ec) { progress.failure(fmt::format("Failed to create dir %s: %s", entryDestPath.string(), ec.message())); return false; } continue; } auto raw_file = iso.open(root / entry.name, fs::read); if (!raw_file) { progress.failure(fmt::format("Failed to open file in ISO: %s", (root / entry.name).string())); return false; } fs::file file; file.reset(std::move(raw_file)); if (!fs::write_file(entryDestPath, fs::open_mode::create + fs::open_mode::trunc, file.to_vector())) { progress.failure(fmt::format("Failed to write file: %s, dest %s", entryDestPath.string(), destinationPath.string())); return false; } progress.report(processedFiles++, filesCount); } } collectGameInfo(env, -1, {destinationPath}); auto ebootPath = locateEbootPath(destinationPath.string()); g_compilationQueue.push(progress, std::move(ebootPath)); return true; } extern "C" bool _rpcsx_installFw(JNIEnv *env, int fd, long progressId) { return installPup(env, fs::file::from_native_handle(fd), progressId); } extern "C" bool _rpcsx_isInstallableFile(jint fd) { auto file = fs::file::from_native_handle(fd); AtExit atExit{[&] { file.release_handle(); }}; auto type = getFileType(file); file.seek(0); return type != FileType::Unknown && type != FileType::Rap; // FIXME: implement rap preinstallation } extern "C" jstring _rpcsx_getDirInstallPath(JNIEnv *env, jint fd) { auto file = fs::file::from_native_handle(fd); AtExit atExit{[&] { file.release_handle(); }}; auto psf = psf::load_object(file, ""); if (auto gameInfo = fetchGameInfo(psf)) { return wrap(env, gameInfo->path); } return nullptr; } extern "C" bool _rpcsx_install(JNIEnv *env, int fd, long progressId) { auto file = fs::file::from_native_handle(fd); AtExit atExit{[&] { file.release_handle(); }}; auto type = getFileType(file); file.seek(0); switch (type) { case FileType::Unknown: Progress(env, progressId).failure("Unsupported file type"); return false; case FileType::Pup: return installPup(env, std::move(file), progressId); case FileType::Pkg: return installPkg(env, std::move(file), progressId); case FileType::Edat: return installEdat(env, std::move(file), progressId); case FileType::Iso: return installIso(env, std::move(file), progressId); case FileType::Rap: Progress(env, progressId) .failure("RAP file cannot be preinstalled. Use lock button on " "installed game instead"); return false; } return true; } extern "C" bool _rpcsx_installKey(JNIEnv *env, int fd, long progressId, std::string_view gamePath) { auto file = fs::file::from_native_handle(fd); AtExit atExit{[&] { file.release_handle(); }}; auto type = getFileType(file); file.seek(0); if (type == FileType::Rap) { return installRap(env, std::move(file), progressId, gamePath); } if (type == FileType::Edat) { return installEdat(env, std::move(file), progressId, gamePath); } Progress(env, progressId).failure("Unsupported key type"); return false; } extern "C" std::string _rpcsx_systemInfo() { std::string result; fmt::append(result, "%s\n\nLLVM CPU: %s\n\n", utils::get_system_info(), fallback_cpu_detection()); { vk::instance device_enum_context; if (device_enum_context.create("RPCS3")) { device_enum_context.bind(); const std::vector &gpus = device_enum_context.enumerate_devices(); for (const auto &gpu : gpus) { fmt::append(result, "GPU: %s\n\nDriver: %s (v%s)\n\nVulkan: %s", gpu.get_name(), gpu.get_driver_name(), gpu.get_driver_version(), gpu.get_driver_vk_version()); } } } return result; } static cfg::_base *find_cfg_node(cfg::_base *root, std::string_view path) { auto pathList = fmt::split(path, {"@@"}); std::ranges::reverse(pathList); while (!pathList.empty()) { auto elem = pathList.back(); pathList.pop_back(); if (elem.empty()) { continue; } auto root_node = dynamic_cast(root); if (root_node == nullptr) { return nullptr; } cfg::_base *child_node = nullptr; for (auto node : root_node->get_nodes()) { if (node->get_name() == elem) { child_node = node; break; } } if (child_node == nullptr) { return nullptr; } root = child_node; } return root; } extern "C" void _rpcsx_loginUser(std::string_view userId) { Emu.SetUsr(std::string(userId)); } extern "C" std::string _rpcsx_getUser() { return Emu.GetUsr(); } extern "C" std::string _rpcsx_settingsGet(std::string_view path) { auto root = find_cfg_node(&g_cfg, path); if (root == nullptr) { return nullptr; } return root->to_json().dump(4); } extern "C" bool _rpcsx_settingsSet(std::string_view path, std::string_view valueString) { nlohmann::json value; try { value = nlohmann::json::parse(valueString); } catch (...) { rpcsx_android.error("settingsSet: node %s passed with invalid json '%s'", path, valueString); return false; } auto root = find_cfg_node(&g_cfg, path); if (root == nullptr) { rpcsx_android.error("settingsSet: node %s not found", path); return false; } if (!root->from_json(value, !Emu.IsStopped())) { rpcsx_android.error("settingsSet: node %s not accepts value '%s'", path, value.dump()); return false; } Emulator::SaveSettings(g_cfg.to_string(), ""); return true; } extern "C" std::string _rpcsx_getVersion() { return rx::getVersion().toString(); } extern "C" void *_rpcsx_setCustomDriver(void *driverHandle) { auto prevLoader = vk::instance::g_vk_loader; if (prevLoader != nullptr) { vk::symbol_cache::cache_instance().clear(); } vk::instance::g_vk_loader = driverHandle; if (driverHandle != nullptr) { vk::symbol_cache::cache_instance().initialize(); } return prevLoader; } #pragma GCC diagnostic pop