mirror of
https://github.com/RPCSX/rpcsx.git
synced 2026-01-11 19:20:09 +01:00
2644 lines
84 KiB
C++
2644 lines
84 KiB
C++
#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 <Emu/Io/pad_config.h>
|
|
#include <Emu/RSX/GSFrameBase.h>
|
|
#include <Emu/System.h>
|
|
#include <nlohmann/json.hpp>
|
|
#include <rpcsx/fw/ps3/cellSaveData.h>
|
|
#include <rpcsx/fw/ps3/sceNpTrophy.h>
|
|
#include <rx/Version.hpp>
|
|
|
|
#include <algorithm>
|
|
#include <android/log.h>
|
|
#include <android/native_window.h>
|
|
#include <android/native_window_jni.h>
|
|
#include <atomic>
|
|
#include <chrono>
|
|
#include <condition_variable>
|
|
#include <deque>
|
|
#include <filesystem>
|
|
#include <functional>
|
|
#include <iterator>
|
|
#include <jni.h>
|
|
#include <optional>
|
|
#include <span>
|
|
#include <string>
|
|
#include <sys/resource.h>
|
|
#include <thread>
|
|
#include <vector>
|
|
|
|
#pragma GCC diagnostic push
|
|
#pragma GCC diagnostic ignored "-Wreturn-type-c-linkage"
|
|
|
|
struct AtExit {
|
|
std::function<void()> cb;
|
|
~AtExit() { cb(); }
|
|
};
|
|
|
|
static bool g_initialized;
|
|
static std::atomic<ANativeWindow *> 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<Pad> 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<logs::level>(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<u8> &data, u32 pitch, u32 width, u32 height,
|
|
bool is_bgra) const override {}
|
|
void take_screenshot(std::vector<u8> &&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<bool()> 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<le_t<u32>>("\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_view_block_dev>(file))) {
|
|
return FileType::Iso;
|
|
}
|
|
|
|
return FileType::Unknown;
|
|
}
|
|
|
|
#define MAKE_STRING(id, x) [int(localized_string_id::id)] = {x, U##x}
|
|
|
|
static std::pair<std::string, std::u32string> 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<jlong>(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<const GameInfo> 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, "<init>",
|
|
"(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;I)V"));
|
|
|
|
std::vector<jobject> 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<std::string> &paths,
|
|
const std::string &rootDir) {
|
|
std::error_code ec;
|
|
std::vector<std::filesystem::path> 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<GameInfo>
|
|
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<u32>() == "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<std::string> &rootDirs) {
|
|
std::vector<std::string> 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<GameInfo> 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<std::pair<std::function<void(JNIEnv *)>, atomic_t<u32> *>> queue;
|
|
|
|
public:
|
|
void push(std::function<void(JNIEnv *)> cb, atomic_t<u32> *wakeUp = nullptr) {
|
|
std::lock_guard lock(mutex);
|
|
queue.push_back({std::move(cb), wakeUp});
|
|
cv.notify_one();
|
|
}
|
|
|
|
void push(std::function<void()> cb, atomic_t<u32> *wakeUp = nullptr) {
|
|
push([cb = std::move(cb)](JNIEnv *) { cb(); }, wakeUp);
|
|
}
|
|
|
|
void process(JNIEnv *env) {
|
|
while (true) {
|
|
std::function<void(JNIEnv *)> cb;
|
|
atomic_t<u32> *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<void(JNIEnv *)> cb) {
|
|
g_mainThreadProcessor.push(std::move(cb));
|
|
}
|
|
|
|
static void invokeSync(std::function<void(JNIEnv *)> cb) {
|
|
atomic_t<u32> 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<MsgDialogBase> 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<ProgressMessageDialog>(progressId);
|
|
} else {
|
|
impl = std::make_unique<UiMessageDialog>();
|
|
}
|
|
|
|
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<jlong> s_pendingProgressId;
|
|
};
|
|
|
|
struct OverlaySaveDialog : SaveDialogBase {
|
|
s32 ShowSaveDataList(const std::string &base_dir,
|
|
std::vector<SaveDataEntry> &save_entries, s32 focused,
|
|
u32 op, vm::ptr<CellSaveDataListSet> 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<rsx::overlays::display_manager>()) {
|
|
rpcsx_android.notice("ShowSaveDataList: Showing native UI dialog");
|
|
|
|
s32 result = manager->create<rsx::overlays::save_dialog>()->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<uchar> &trophy_icon_buffer) override {
|
|
if (auto manager = g_fxo->try_get<rsx::overlays::display_manager>()) {
|
|
auto popup = std::make_shared<rsx::overlays::trophy_notification>();
|
|
return manager->add(popup, false)->show(trophy, trophy_icon_buffer);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
};
|
|
|
|
std::atomic<jlong> 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<std::string> &dir_queue,
|
|
std::vector<ppu_module<lv2_obj> *> *loaded_prx);
|
|
extern bool ppu_initialize(const ppu_module<lv2_obj> &, bool check_only = false,
|
|
u64 file_size = 0);
|
|
extern void ppu_finalize(const ppu_module<lv2_obj> &);
|
|
extern bool ppu_load_rel_exec(const ppu_rel_object &);
|
|
|
|
class CompilationQueue {
|
|
std::atomic<std::uint64_t> nextWorkTag{0};
|
|
std::uint64_t lastProcessedTag = 0;
|
|
std::mutex queueMutex;
|
|
std::deque<CompilationWorkload> 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<named_thread<progress_dialog_server>>();
|
|
g_fxo->init<main_ppu_module<lv2_obj>>();
|
|
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<main_ppu_module<lv2_obj>>());
|
|
|
|
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<std::string> 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<ppu_module<lv2_obj> *> 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<u32>{})) {
|
|
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<void()> cb, atomic_t<u32> *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<KeyboardHandlerBase, NullKeyboardHandler>(
|
|
Emu.DeserialManager()));
|
|
},
|
|
.init_mouse_handler =
|
|
[](auto...) {
|
|
ensure(g_fxo->init<MouseHandlerBase, NullMouseHandler>(
|
|
Emu.DeserialManager()));
|
|
},
|
|
.init_pad_handler =
|
|
[](auto...) {
|
|
ensure(g_fxo->init<named_thread<pad_thread>>(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<GraphicsFrame>(); },
|
|
.get_camera_handler =
|
|
[](auto...) { return std::make_shared<null_camera_handler>(); },
|
|
.get_music_handler =
|
|
[](auto...) { return std::make_shared<null_music_handler>(); },
|
|
.create_pad_handler = [](pad_handler type, void *thread, void *window)
|
|
-> std::shared_ptr<PadHandlerBase> {
|
|
switch (type) {
|
|
case pad_handler::keyboard:
|
|
case pad_handler::null:
|
|
break;
|
|
case pad_handler::ds3:
|
|
return std::make_shared<ds3_pad_handler>();
|
|
case pad_handler::ds4:
|
|
return std::make_shared<ds4_pad_handler>();
|
|
case pad_handler::dualsense:
|
|
return std::make_shared<dualsense_pad_handler>();
|
|
case pad_handler::skateboard:
|
|
// return std::make_shared<skateboard_pad_handler>();
|
|
break;
|
|
case pad_handler::move:
|
|
// return std::make_shared<ps_move_handler>();
|
|
break;
|
|
#ifdef _WIN32
|
|
case pad_handler::xinput:
|
|
return std::make_shared<xinput_pad_handler>();
|
|
case pad_handler::mm:
|
|
return std::make_shared<mm_joystick_handler>();
|
|
#endif
|
|
#ifdef HAVE_SDL3
|
|
case pad_handler::sdl:
|
|
return std::make_shared<sdl_pad_handler>();
|
|
#endif
|
|
#ifdef HAVE_LIBEVDEV
|
|
case pad_handler::evdev:
|
|
return std::make_shared<evdev_joystick_handler>();
|
|
#endif
|
|
case pad_handler::virtual_pad:
|
|
return std::make_shared<virtual_pad_handler>();
|
|
}
|
|
|
|
return std::make_shared<NullPadHandler>();
|
|
},
|
|
.init_gs_render =
|
|
[](utils::serial *ar) {
|
|
switch (g_cfg.video.renderer.get()) {
|
|
case video_renderer::null:
|
|
g_fxo->init<rsx::thread, named_thread<NullGSRender>>(ar);
|
|
break;
|
|
case video_renderer::vulkan:
|
|
g_fxo->init<rsx::thread, named_thread<VKGSRender>>(ar);
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
},
|
|
.get_audio =
|
|
[](auto...) {
|
|
std::shared_ptr<AudioBackend> result;
|
|
|
|
switch (g_cfg.audio.renderer.get()) {
|
|
case audio_renderer::null:
|
|
result = std::make_shared<NullAudioBackend>();
|
|
break;
|
|
|
|
case audio_renderer::cubeb:
|
|
default:
|
|
result = std::make_shared<CubebBackend>();
|
|
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<NullAudioBackend>();
|
|
}
|
|
return result;
|
|
},
|
|
.get_audio_enumerator = [](auto...) { return nullptr; },
|
|
.get_msg_dialog = [] { return std::make_shared<MessageDialog>(); },
|
|
.get_osk_dialog = [](auto...) { return nullptr; },
|
|
.get_save_dialog =
|
|
[](auto...) { return std::make_unique<OverlaySaveDialog>(); },
|
|
.get_sendmessage_dialog = [](auto...) { return nullptr; },
|
|
.get_recvmessage_dialog = [](auto...) { return nullptr; },
|
|
.get_trophy_notification_dialog =
|
|
[](auto...) { return std::make_unique<OverlayTrophyNotification>(); },
|
|
.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<std::string>(); },
|
|
.on_install_pkgs =
|
|
[](const std::vector<std::string> &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> &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<u32>{},
|
|
CELL_PAD_CTRL_UP);
|
|
pad->m_buttons.emplace_back(CELL_PAD_BTN_OFFSET_DIGITAL1, std::set<u32>{},
|
|
CELL_PAD_CTRL_DOWN);
|
|
pad->m_buttons.emplace_back(CELL_PAD_BTN_OFFSET_DIGITAL1, std::set<u32>{},
|
|
CELL_PAD_CTRL_LEFT);
|
|
pad->m_buttons.emplace_back(CELL_PAD_BTN_OFFSET_DIGITAL1, std::set<u32>{},
|
|
CELL_PAD_CTRL_RIGHT);
|
|
pad->m_buttons.emplace_back(CELL_PAD_BTN_OFFSET_DIGITAL2, std::set<u32>{},
|
|
CELL_PAD_CTRL_CROSS);
|
|
pad->m_buttons.emplace_back(CELL_PAD_BTN_OFFSET_DIGITAL2, std::set<u32>{},
|
|
CELL_PAD_CTRL_SQUARE);
|
|
pad->m_buttons.emplace_back(CELL_PAD_BTN_OFFSET_DIGITAL2, std::set<u32>{},
|
|
CELL_PAD_CTRL_CIRCLE);
|
|
pad->m_buttons.emplace_back(CELL_PAD_BTN_OFFSET_DIGITAL2, std::set<u32>{},
|
|
CELL_PAD_CTRL_TRIANGLE);
|
|
pad->m_buttons.emplace_back(CELL_PAD_BTN_OFFSET_DIGITAL2, std::set<u32>{},
|
|
CELL_PAD_CTRL_L1);
|
|
pad->m_buttons.emplace_back(CELL_PAD_BTN_OFFSET_DIGITAL2, std::set<u32>{},
|
|
CELL_PAD_CTRL_L2);
|
|
pad->m_buttons.emplace_back(CELL_PAD_BTN_OFFSET_DIGITAL1, std::set<u32>{},
|
|
CELL_PAD_CTRL_L3);
|
|
pad->m_buttons.emplace_back(CELL_PAD_BTN_OFFSET_DIGITAL2, std::set<u32>{},
|
|
CELL_PAD_CTRL_R1);
|
|
pad->m_buttons.emplace_back(CELL_PAD_BTN_OFFSET_DIGITAL2, std::set<u32>{},
|
|
CELL_PAD_CTRL_R2);
|
|
pad->m_buttons.emplace_back(CELL_PAD_BTN_OFFSET_DIGITAL1, std::set<u32>{},
|
|
CELL_PAD_CTRL_R3);
|
|
pad->m_buttons.emplace_back(CELL_PAD_BTN_OFFSET_DIGITAL1, std::set<u32>{},
|
|
CELL_PAD_CTRL_START);
|
|
pad->m_buttons.emplace_back(CELL_PAD_BTN_OFFSET_DIGITAL1, std::set<u32>{},
|
|
CELL_PAD_CTRL_SELECT);
|
|
pad->m_buttons.emplace_back(CELL_PAD_BTN_OFFSET_DIGITAL1, std::set<u32>{},
|
|
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<Pad> 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<void>(named_thread("", [](int) {}));
|
|
|
|
static std::unique_ptr<logs::listener> 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<std::size_t>(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<int>(Emu.BootGame(path, "", false, cfg_mode::global));
|
|
}
|
|
|
|
extern "C" int _rpcsx_getState() {
|
|
return static_cast<int>(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<pad_handler, std::pair<std::unique_ptr<PadHandlerBase>,
|
|
std::vector<std::string>>>
|
|
handlerToDevices;
|
|
|
|
auto collectDevices = [&]<typename T>(T handler) {
|
|
handler->Init();
|
|
|
|
std::vector<std::string> 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<dualsense_pad_handler>());
|
|
collectDevices(std::make_unique<ds4_pad_handler>());
|
|
collectDevices(std::make_unique<ds3_pad_handler>());
|
|
|
|
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_error>(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_error>(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<package_reader> readers;
|
|
std::deque<std::string> 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<std::uint8_t> 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<std::uint8_t> 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_view_block_dev>(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<std::filesystem::path> result;
|
|
std::vector<std::filesystem::path> 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<std::uint8_t>())) {
|
|
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<vk::physical_device> &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<cfg::node *>(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
|