From 87762a9b17ee2a81a860987b0334ed466ca19a1a Mon Sep 17 00:00:00 2001 From: Megamouse Date: Tue, 21 Jun 2022 22:13:22 +0200 Subject: [PATCH] cellGame: implement disc change callbacks --- rpcs3/Emu/CMakeLists.txt | 1 + rpcs3/Emu/Cell/Modules/cellGame.cpp | 172 +++++++++++++++++- rpcs3/Emu/Cell/Modules/cellGame.h | 29 ++- rpcs3/Emu/System.cpp | 268 +++++++++++++++++++++------- rpcs3/Emu/System.h | 8 + rpcs3/Emu/VFS.cpp | 77 +++++++- rpcs3/Emu/VFS.h | 3 + rpcs3/Loader/disc.cpp | 149 ++++++++++++++++ rpcs3/Loader/disc.h | 15 ++ rpcs3/emucore.vcxproj | 2 + rpcs3/emucore.vcxproj.filters | 6 + rpcs3/headless_application.cpp | 3 + rpcs3/rpcs3qt/gui_application.cpp | 17 ++ rpcs3/rpcs3qt/gui_application.h | 2 + rpcs3/rpcs3qt/gui_settings.h | 1 + rpcs3/rpcs3qt/main_window.cpp | 38 ++++ rpcs3/rpcs3qt/main_window.h | 2 + rpcs3/rpcs3qt/main_window.ui | 19 ++ rpcs3/rpcs3qt/qt_camera_handler.cpp | 2 +- 19 files changed, 728 insertions(+), 86 deletions(-) create mode 100644 rpcs3/Loader/disc.cpp create mode 100644 rpcs3/Loader/disc.h diff --git a/rpcs3/Emu/CMakeLists.txt b/rpcs3/Emu/CMakeLists.txt index 02af5463ee..7b424a45dc 100644 --- a/rpcs3/Emu/CMakeLists.txt +++ b/rpcs3/Emu/CMakeLists.txt @@ -105,6 +105,7 @@ target_sources(rpcs3_emu PRIVATE # Loader target_sources(rpcs3_emu PRIVATE + ../Loader/disc.cpp ../Loader/ELF.cpp ../Loader/mself.cpp ../Loader/PSF.cpp diff --git a/rpcs3/Emu/Cell/Modules/cellGame.cpp b/rpcs3/Emu/Cell/Modules/cellGame.cpp index 1fbe7d4210..e082d170be 100644 --- a/rpcs3/Emu/Cell/Modules/cellGame.cpp +++ b/rpcs3/Emu/Cell/Modules/cellGame.cpp @@ -199,6 +199,162 @@ void fmt_class_string::format(std::string& out, }); } +template<> +void fmt_class_string::format(std::string& out, u64 arg) +{ + format_enum(out, arg, [](auto error) + { + switch (error) + { + STR_CASE(disc_change_manager::eject_state::inserted); + STR_CASE(disc_change_manager::eject_state::ejected); + STR_CASE(disc_change_manager::eject_state::busy); + } + + return unknown; + }); +} + + +disc_change_manager::disc_change_manager() +{ + Emu.GetCallbacks().enable_disc_eject(false); + Emu.GetCallbacks().enable_disc_insert(false); +} + +disc_change_manager::~disc_change_manager() +{ + Emu.GetCallbacks().enable_disc_eject(false); + Emu.GetCallbacks().enable_disc_insert(false); +} + +error_code disc_change_manager::register_callbacks(vm::ptr func_eject, vm::ptr func_insert) +{ + if (!func_eject || !func_insert) + { + return CELL_GAME_ERROR_PARAM; + } + + std::lock_guard lock(mtx); + + eject_callback = func_eject; + insert_callback = func_insert; + + Emu.GetCallbacks().enable_disc_eject(true); + Emu.GetCallbacks().enable_disc_insert(false); + + return CELL_OK; +} + +error_code disc_change_manager::unregister_callbacks() +{ + const auto unregister = [this]() -> void + { + eject_callback = vm::null; + insert_callback = vm::null; + + Emu.GetCallbacks().enable_disc_eject(false); + Emu.GetCallbacks().enable_disc_insert(false); + }; + + if (is_inserting) + { + // NOTE: The insert_callback is known to call cellGameUnregisterDiscChangeCallback. + // So we keep it out of the mutex lock until it proves to be an issue. + unregister(); + } + else + { + std::lock_guard lock(mtx); + unregister(); + } + + return CELL_OK; +} + +void disc_change_manager::eject_disc() +{ + cellGame.notice("Ejecting disc..."); + + std::lock_guard lock(mtx); + + if (state != eject_state::inserted) + { + cellGame.fatal("Can not eject disc in the current state. (state=%s)", state.load()); + return; + } + + state = eject_state::busy; + Emu.GetCallbacks().enable_disc_eject(false); + + ensure(eject_callback); + + sysutil_register_cb([](ppu_thread& cb_ppu) -> s32 + { + auto& dcm = g_fxo->get(); + std::lock_guard lock(dcm.mtx); + + cellGame.notice("Executing eject_callback..."); + dcm.eject_callback(cb_ppu); + + ensure(vfs::unmount("/dev_bdvd")); + ensure(vfs::unmount("/dev_ps2disc")); + dcm.state = eject_state::ejected; + + Emu.GetCallbacks().enable_disc_insert(true); + + return CELL_OK; + }); +} + +void disc_change_manager::insert_disc(u32 disc_type, std::string title_id) +{ + cellGame.notice("Inserting disc..."); + + std::lock_guard lock(mtx); + + if (state != eject_state::ejected) + { + cellGame.fatal("Can not insert disc in the current state. (state=%s)", state.load()); + return; + } + + state = eject_state::busy; + Emu.GetCallbacks().enable_disc_insert(false); + + ensure(insert_callback); + + is_inserting = true; + + sysutil_register_cb([disc_type, title_id = std::move(title_id)](ppu_thread& cb_ppu) -> s32 + { + auto& dcm = g_fxo->get(); + std::lock_guard lock(dcm.mtx); + + if (disc_type == CELL_GAME_DISCTYPE_PS3) + { + vm::var _title_id = vm::make_str(title_id); + cellGame.notice("Executing insert_callback for title '%s' with disc_type %d...", _title_id.get_ptr(), disc_type); + dcm.insert_callback(cb_ppu, disc_type, _title_id); + } + else + { + cellGame.notice("Executing insert_callback with disc_type %d...", disc_type); + dcm.insert_callback(cb_ppu, disc_type, vm::null); + } + + dcm.state = eject_state::inserted; + + // Re-enable disc ejection only if the callback is still registered + Emu.GetCallbacks().enable_disc_eject(!!dcm.eject_callback); + + dcm.is_inserting = false; + + return CELL_OK; + }); +} + + error_code cellHddGameCheck(ppu_thread& ppu, u32 version, vm::cptr dirName, u32 errDialog, vm::ptr funcStat, u32 container) { cellGame.warning("cellHddGameCheck(version=%d, dirName=%s, errDialog=%d, funcStat=*0x%x, container=%d)", version, dirName, errDialog, funcStat, container); @@ -1566,30 +1722,30 @@ error_code cellDiscGameGetBootDiscInfo(vm::ptr getP error_code cellDiscGameRegisterDiscChangeCallback(vm::ptr funcEject, vm::ptr funcInsert) { - cellGame.todo("cellDiscGameRegisterDiscChangeCallback(funcEject=*0x%x, funcInsert=*0x%x)", funcEject, funcInsert); + cellGame.warning("cellDiscGameRegisterDiscChangeCallback(funcEject=*0x%x, funcInsert=*0x%x)", funcEject, funcInsert); - return CELL_OK; + return g_fxo->get().register_callbacks(funcEject, funcInsert); } error_code cellDiscGameUnregisterDiscChangeCallback() { - cellGame.todo("cellDiscGameUnregisterDiscChangeCallback()"); + cellGame.warning("cellDiscGameUnregisterDiscChangeCallback()"); - return CELL_OK; + return g_fxo->get().unregister_callbacks(); } error_code cellGameRegisterDiscChangeCallback(vm::ptr funcEject, vm::ptr funcInsert) { - cellGame.todo("cellGameRegisterDiscChangeCallback(funcEject=*0x%x, funcInsert=*0x%x)", funcEject, funcInsert); + cellGame.warning("cellGameRegisterDiscChangeCallback(funcEject=*0x%x, funcInsert=*0x%x)", funcEject, funcInsert); - return CELL_OK; + return g_fxo->get().register_callbacks(funcEject, funcInsert); } error_code cellGameUnregisterDiscChangeCallback() { - cellGame.todo("cellGameUnregisterDiscChangeCallback()"); + cellGame.warning("cellGameUnregisterDiscChangeCallback()"); - return CELL_OK; + return g_fxo->get().unregister_callbacks(); } void cellSysutil_GameData_init() diff --git a/rpcs3/Emu/Cell/Modules/cellGame.h b/rpcs3/Emu/Cell/Modules/cellGame.h index 7140f1e8d9..5be2085c58 100644 --- a/rpcs3/Emu/Cell/Modules/cellGame.h +++ b/rpcs3/Emu/Cell/Modules/cellGame.h @@ -318,5 +318,30 @@ typedef void(CellHddGameStatCallback)(vm::ptr cbResult, vm: typedef s32(CellGameThemeInstallCallback)(u32 fileOffset, u32 readSize, vm::ptr buf); typedef void(CellGameDiscEjectCallback)(); typedef void(CellGameDiscInsertCallback)(u32 discType, vm::ptr titleId); -typedef void(CellDiscGameDiscEjectCallback)(); -typedef void(CellDiscGameDiscInsertCallback)(u32 discType, vm::ptr titleId); +using CellDiscGameDiscEjectCallback = CellGameDiscEjectCallback; +using CellDiscGameDiscInsertCallback = CellGameDiscInsertCallback; + +struct disc_change_manager +{ + disc_change_manager(); + virtual ~disc_change_manager(); + + std::mutex mtx; + atomic_t is_inserting = false; + vm::ptr eject_callback = vm::null; + vm::ptr insert_callback = vm::null; + + enum class eject_state + { + inserted, + ejected, + busy + }; + atomic_t state = eject_state::inserted; + + error_code register_callbacks(vm::ptr func_eject, vm::ptr func_insert); + error_code unregister_callbacks(); + + void eject_disc(); + void insert_disc(u32 disc_type, std::string title_id); +}; diff --git a/rpcs3/Emu/System.cpp b/rpcs3/Emu/System.cpp index 6f256f6988..654d856097 100644 --- a/rpcs3/Emu/System.cpp +++ b/rpcs3/Emu/System.cpp @@ -28,6 +28,7 @@ #include "Loader/PSF.h" #include "Loader/ELF.h" +#include "Loader/disc.h" #include "Utilities/StrUtil.h" @@ -543,6 +544,35 @@ bool Emulator::BootRsxCapture(const std::string& path) return true; } +game_boot_result Emulator::GetElfPathFromDir(std::string& elf_path, const std::string& path) +{ + if (!fs::is_dir(path)) + { + return game_boot_result::invalid_file_or_folder; + } + + static const char* boot_list[] = + { + "/EBOOT.BIN", + "/USRDIR/EBOOT.BIN", + "/USRDIR/ISO.BIN.EDAT", + "/PS3_GAME/USRDIR/EBOOT.BIN", + }; + + for (std::string elf : boot_list) + { + elf = path + elf; + + if (fs::is_file(elf)) + { + elf_path = elf; + return game_boot_result::no_errors; + } + } + + return game_boot_result::invalid_file_or_folder; +} + game_boot_result Emulator::BootGame(const std::string& path, const std::string& title_id, bool direct, bool add_only, cfg_mode config_mode, const std::string& config_path) { if (!fs::exists(path)) @@ -563,24 +593,12 @@ game_boot_result Emulator::BootGame(const std::string& path, const std::string& game_boot_result result = game_boot_result::nothing_to_boot; - static const char* boot_list[] = + std::string elf; + if (const game_boot_result res = GetElfPathFromDir(elf, path); res == game_boot_result::no_errors) { - "/EBOOT.BIN", - "/USRDIR/EBOOT.BIN", - "/USRDIR/ISO.BIN.EDAT", - "/PS3_GAME/USRDIR/EBOOT.BIN", - }; - - for (std::string elf : boot_list) - { - elf = path + elf; - - if (fs::is_file(elf)) - { - m_path = elf; - result = Load(title_id, add_only); - break; - } + ensure(!elf.empty()); + m_path = elf; + result = Load(title_id, add_only); } if (add_only) @@ -1012,62 +1030,26 @@ game_boot_result Emulator::Load(const std::string& title_id, bool add_only, bool // Mount /dev_bdvd/ if necessary if (bdvd_dir.empty() && disc.empty()) { - // Find disc directory by searching a valid PS3_DISC.SFB closest to root directory - std::string sfb_dir, main_dir; - std::string_view main_dir_name; + std::string sfb_dir; + GetBdvdDir(bdvd_dir, sfb_dir, m_game_dir, elf_dir); - for (std::string search_dir = elf_dir;;) + if (!sfb_dir.empty() && from_hdd0_game) { - std::string parent_dir = fs::get_parent_dir(search_dir); + // Booting disc game from wrong location + sys_log.error("Disc game %s found at invalid location /dev_hdd0/game/", m_title_id); - if (parent_dir.size() == search_dir.size()) + const std::string dst_dir = hdd0_disc + sfb_dir.substr(hdd0_game.size()); + + // Move and retry from correct location + if (fs::create_path(fs::get_parent_dir(dst_dir)) && fs::rename(sfb_dir, dst_dir, false)) { - // Keep looking until root directory is reached - break; + sys_log.success("Disc game %s moved to special location /dev_hdd0/disc/", m_title_id); + m_path = hdd0_disc + m_path.substr(hdd0_game.size()); + return Load(m_title_id, add_only); } - if (fs::file sfb_file{parent_dir + "/PS3_DISC.SFB", fs::read + fs::isfile}; sfb_file && sfb_file.size() >= 4 && sfb_file.read() == ".SFB"_u32) - { - main_dir_name = std::string_view{search_dir}.substr(search_dir.find_last_of(fs::delim) + 1); - - if (main_dir_name == "PS3_GAME" || std::regex_match(main_dir_name.begin(), main_dir_name.end(), std::regex("^PS3_GM[[:digit:]]{2}$"))) - { - // Remember valid disc directory - main_dir = search_dir; - sfb_dir = parent_dir; - main_dir_name = std::string_view{main_dir}.substr(main_dir.find_last_of(fs::delim) + 1); - } - } - - search_dir = std::move(parent_dir); - } - - if (!sfb_dir.empty()) - { - { - if (from_hdd0_game) - { - // Booting disc game from wrong location - sys_log.error("Disc game %s found at invalid location /dev_hdd0/game/", m_title_id); - - const std::string dst_dir = hdd0_disc + sfb_dir.substr(hdd0_game.size()); - - // Move and retry from correct location - if (fs::create_path(fs::get_parent_dir(dst_dir)) && fs::rename(sfb_dir, dst_dir, false)) - { - sys_log.success("Disc game %s moved to special location /dev_hdd0/disc/", m_title_id); - return m_path = hdd0_disc + m_path.substr(hdd0_game.size()), Load(m_title_id, add_only); - } - - sys_log.error("Failed to move disc game %s to /dev_hdd0/disc/ (%s)", m_title_id, fs::g_tls_error); - return game_boot_result::wrong_disc_location; - } - - bdvd_dir = sfb_dir + "/"; - - // Set game dir - m_game_dir = std::string{main_dir_name}; - } + sys_log.error("Failed to move disc game %s to /dev_hdd0/disc/ (%s)", m_title_id, fs::g_tls_error); + return game_boot_result::wrong_disc_location; } } @@ -1315,7 +1297,8 @@ game_boot_result Emulator::Load(const std::string& title_id, bool add_only, bool { // Booting game update sys_log.success("Updates found at /dev_hdd0/game/%s/", m_title_id); - return m_path = hdd0_boot, Load(m_title_id, false, true); + m_path = hdd0_boot; + return Load(m_title_id, false, true); } if (!disc_psf_obj.empty()) @@ -2257,4 +2240,153 @@ const std::string Emulator::GetSfoDir(bool prefer_disc_sfo) const return m_sfo_dir; } +void Emulator::GetBdvdDir(std::string& bdvd_dir, std::string& sfb_dir, std::string& game_dir, const std::string& elf_dir) +{ + // Find disc directory by searching a valid PS3_DISC.SFB closest to root directory + std::string main_dir; + std::string_view main_dir_name; + + for (std::string search_dir = elf_dir;;) + { + std::string parent_dir = fs::get_parent_dir(search_dir); + + if (parent_dir.size() == search_dir.size()) + { + // Keep looking until root directory is reached + break; + } + + if (fs::file sfb_file{parent_dir + "/PS3_DISC.SFB", fs::read + fs::isfile}; sfb_file && sfb_file.size() >= 4 && sfb_file.read() == ".SFB"_u32) + { + main_dir_name = std::string_view{search_dir}.substr(search_dir.find_last_of(fs::delim) + 1); + + if (main_dir_name == "PS3_GAME" || std::regex_match(main_dir_name.begin(), main_dir_name.end(), std::regex("^PS3_GM[[:digit:]]{2}$"))) + { + // Remember valid disc directory + main_dir = search_dir; + sfb_dir = parent_dir; + main_dir_name = std::string_view{main_dir}.substr(main_dir.find_last_of(fs::delim) + 1); + } + } + + search_dir = std::move(parent_dir); + } + + if (!sfb_dir.empty()) + { + bdvd_dir = sfb_dir + "/"; + game_dir = std::string{main_dir_name}; + } +} + +void Emulator::EjectDisc() +{ + if (!Emu.IsRunning()) + { + sys_log.error("Can not eject disc if the Emulator is not running!"); + return; + } + + if (vfs::get("/dev_bdvd").empty()) + { + sys_log.error("Can not eject disc if dev_bdvd is not mounted!"); + return; + } + + sys_log.notice("Ejecting disc..."); + + m_sfo_dir.clear(); + + if (g_fxo->is_init()) + { + g_fxo->get().eject_disc(); + } +} + +game_boot_result Emulator::InsertDisc(const std::string& path) +{ + if (!Emu.IsRunning()) + { + sys_log.error("Can not insert disc if the Emulator is not running!"); + return game_boot_result::generic_error; + } + + sys_log.notice("Inserting disc... (path='%s')", path); + + const std::string hdd0_game = vfs::get("/dev_hdd0/game/"); + const bool from_hdd0_game = IsPathInsideDir(path, hdd0_game); + + if (from_hdd0_game) + { + sys_log.error("Inserting disc failed: Can not mount discs from '/dev_hdd0/game/'. (path='%s')", path); + return game_boot_result::wrong_disc_location; + } + + std::string disc_root; + std::string ps3_game_dir; + const disc::disc_type disc_type = disc::get_disc_type(path, disc_root, ps3_game_dir); + + if (disc_type == disc::disc_type::invalid) + { + sys_log.error("Inserting disc failed: not a disc (path='%s')", path); + return game_boot_result::wrong_disc_location; + } + + ensure(!disc_root.empty()); + + u32 type = CELL_GAME_DISCTYPE_OTHER; + std::string title_id; + + if (disc_type == disc::disc_type::ps3) + { + type = CELL_GAME_DISCTYPE_PS3; + + // Double check PARAM.SFO + const std::string sfo_dir = rpcs3::utils::get_sfo_dir_from_game_path(disc_root); + const psf::registry _psf = psf::load_object(fs::file(sfo_dir + "/PARAM.SFO")); + + if (_psf.empty()) + { + sys_log.error("Inserting disc failed: Corrupted PARAM.SFO found! (path='%s/PARAM.SFO')", sfo_dir); + return game_boot_result::invalid_file_or_folder; + } + + title_id = std::string(psf::get_string(_psf, "TITLE_ID")); + + if (title_id.empty()) + { + sys_log.error("Inserting disc failed: Corrupted PARAM.SFO found! TITLE_ID empty (path='%s/PARAM.SFO')", sfo_dir); + return game_boot_result::invalid_file_or_folder; + } + + m_sfo_dir = sfo_dir; + m_game_dir = ps3_game_dir; + + sys_log.notice("New sfo dir: %s", m_sfo_dir); + sys_log.notice("New game dir: %s", m_game_dir); + + ensure(vfs::mount("/dev_bdvd", disc_root)); + ensure(vfs::mount("/dev_bdvd/PS3_GAME", disc_root + m_game_dir + "/")); + } + else if (disc_type == disc::disc_type::ps2) + { + type = CELL_GAME_DISCTYPE_PS2; + + ensure(vfs::mount("/dev_ps2disc", disc_root)); + } + else + { + // TODO: find out where other discs are mounted + sys_log.todo("Mounting non-ps2/ps3 disc in dev_bdvd. Is this correct? (path='%s')", disc_root); + ensure(vfs::mount("/dev_bdvd", disc_root)); + } + + if (g_fxo->is_init()) + { + g_fxo->get().insert_disc(type, std::move(title_id)); + } + + return game_boot_result::no_errors; +} + Emulator Emu; diff --git a/rpcs3/Emu/System.h b/rpcs3/Emu/System.h index 802bb4684a..7856dd39f5 100644 --- a/rpcs3/Emu/System.h +++ b/rpcs3/Emu/System.h @@ -63,6 +63,8 @@ struct EmuCallbacks std::function on_stop; std::function on_ready; std::function on_missing_fw; + std::function enable_disc_eject; + std::function enable_disc_insert; std::function)> try_to_quit; // (force_quit, on_exit) Try to close RPCS3 std::function handle_taskbar_progress; // (type, value) type: 0 for reset, 1 for increment, 2 for set_limit, 3 for set_value std::function init_kb_handler; @@ -296,6 +298,12 @@ public: // Check if path is inside the specified directory bool IsPathInsideDir(std::string_view path, std::string_view dir) const; + + void EjectDisc(); + game_boot_result InsertDisc(const std::string& path); + + static game_boot_result GetElfPathFromDir(std::string& elf_path, const std::string& path); + static void GetBdvdDir(std::string& bdvd_dir, std::string& sfb_dir, std::string& game_dir, const std::string& elf_dir); }; extern Emulator Emu; diff --git a/rpcs3/Emu/VFS.cpp b/rpcs3/Emu/VFS.cpp index 6ca8b2072a..916d49136f 100644 --- a/rpcs3/Emu/VFS.cpp +++ b/rpcs3/Emu/VFS.cpp @@ -35,6 +35,13 @@ struct vfs_manager bool vfs::mount(std::string_view vpath, std::string_view path) { + if (vpath.empty()) + { + // Empty relative path, should set relative path base; unsupported + vfs_log.error("Cannot mount empty path to \"%s\"", path); + return false; + } + // Workaround g_fxo->need(); @@ -44,13 +51,6 @@ bool vfs::mount(std::string_view vpath, std::string_view path) std::lock_guard lock(table.mutex); - if (vpath.empty()) - { - // Empty relative path, should set relative path base; unsupported - vfs_log.error("Cannot mount empty path to \"%s\"", path); - return false; - } - const std::string_view vpath_backup = vpath; for (std::vector list{&table.root};;) @@ -116,6 +116,69 @@ bool vfs::mount(std::string_view vpath, std::string_view path) } } +bool vfs::unmount(std::string_view vpath) +{ + if (vpath.empty()) + { + vfs_log.error("Cannot unmount empty path"); + return false; + } + + const std::vector entry_list = fmt::split(vpath, {"/"}); + + if (entry_list.empty()) + { + vfs_log.error("Cannot unmount path: '%s'", vpath); + return false; + } + + vfs_log.notice("About to unmount '%s'", vpath); + + // Workaround + g_fxo->need(); + + auto& table = g_fxo->get(); + + std::lock_guard lock(table.mutex); + + // Search entry recursively and remove it (including all children) + std::function unmount_children; + unmount_children = [&entry_list, &unmount_children](vfs_directory& dir, usz depth) -> void + { + if (depth >= entry_list.size()) + { + return; + } + + // Get the current name based on the depth + const std::string& name = entry_list.at(depth); + + // Go through all children of this node + for (auto it = dir.dirs.begin(); it != dir.dirs.end();) + { + // Find the matching node + if (it->first == name) + { + // Remove the matching node if we reached the maximum depth + if (depth + 1 == entry_list.size()) + { + vfs_log.notice("Unmounting '%s' = '%s'", it->first, it->second.path); + it = dir.dirs.erase(it); + continue; + } + + // Otherwise continue searching in the next level of depth + unmount_children(it->second, depth + 1); + } + + ++it; + } + }; + unmount_children(table.root, 0); + + return true; +} + std::string vfs::get(std::string_view vpath, std::vector* out_dir, std::string* out_path) { auto& table = g_fxo->get(); diff --git a/rpcs3/Emu/VFS.h b/rpcs3/Emu/VFS.h index 809472ab84..5753ad69b2 100644 --- a/rpcs3/Emu/VFS.h +++ b/rpcs3/Emu/VFS.h @@ -11,6 +11,9 @@ namespace vfs // Mount VFS device bool mount(std::string_view vpath, std::string_view path); + // Unmount VFS device + bool unmount(std::string_view vpath); + // Convert VFS path to fs path, optionally listing directories mounted in it std::string get(std::string_view vpath, std::vector* out_dir = nullptr, std::string* out_path = nullptr); diff --git a/rpcs3/Loader/disc.cpp b/rpcs3/Loader/disc.cpp new file mode 100644 index 0000000000..de79eb5038 --- /dev/null +++ b/rpcs3/Loader/disc.cpp @@ -0,0 +1,149 @@ +#include "stdafx.h" +#include "disc.h" +#include "PSF.h" +#include "util/logs.hpp" +#include "Utilities/StrUtil.h" +#include "Emu/System.h" +#include "Emu/system_utils.hpp" + +LOG_CHANNEL(disc_log, "DISC"); + +namespace disc +{ + disc_type get_disc_type(std::string path, std::string& disc_root, std::string& ps3_game_dir) + { + disc_type type = disc_type::unknown; + + disc_root.clear(); + ps3_game_dir.clear(); + + if (path.empty()) + { + disc_log.error("Can not determine disc type. Path is empty."); + return disc_type::invalid; + } + + if (!fs::is_dir(path)) + { + disc_log.error("Can not determine disc type. Path not a directory: '%s'", path); + return disc_type::invalid; + } + + // Check for PS3 game first. + std::string elf_path; + if (const game_boot_result result = Emulator::GetElfPathFromDir(elf_path, path); + result == game_boot_result::no_errors) + { + // Every error past this point is considered a corrupt disc. + + std::string sfb_dir; + const std::string elf_dir = fs::get_parent_dir(elf_path); + + Emulator::GetBdvdDir(disc_root, sfb_dir, ps3_game_dir, elf_dir); + + if (!fs::is_dir(disc_root) || !fs::is_dir(sfb_dir) || ps3_game_dir.empty()) + { + disc_log.error("Not a PS3 disc: invalid folder (bdvd_dir='%s', sfb_dir='%s', game_dir='%s')", disc_root, sfb_dir, ps3_game_dir); + return disc_type::invalid; + } + + // Load PARAM.SFO + const std::string sfo_dir = rpcs3::utils::get_sfo_dir_from_game_path(elf_dir + "/../", ""); + const psf::registry _psf = psf::load_object(fs::file(sfo_dir + "/PARAM.SFO")); + + if (_psf.empty()) + { + disc_log.error("Not a PS3 disc: Corrupted PARAM.SFO found! (path='%s/PARAM.SFO')", sfo_dir); + return disc_type::invalid; + } + + const std::string cat = std::string(psf::get_string(_psf, "CATEGORY")); + + if (cat != "DG") + { + disc_log.error("Not a PS3 disc: Wrong category '%s'.", cat); + return disc_type::invalid; + } + + return disc_type::ps3; + } + + disc_log.notice("Not a PS3 disc: Elf not found. Looking for SYSTEM.CNF... (path='%s')", path); + + std::vector lines; + + // Try to find SYSTEM.CNF + for (std::string search_dir = path;;) + { + if (fs::file file(search_dir + "/SYSTEM.CNF"); file) + { + disc_root = search_dir + "/"; + lines = fmt::split(file.to_string(), {"\n"}); + break; + } + + std::string parent_dir = fs::get_parent_dir(search_dir); + + if (parent_dir.size() == search_dir.size()) + { + // Keep looking until root directory is reached + disc_log.error("SYSTEM.CNF not found in path: '%s'", path); + return disc_type::invalid; + } + + search_dir = std::move(parent_dir); + } + + for (usz i = 0; i < lines.size(); i++) + { + const std::string& line = lines[i]; + const usz pos = line.find('='); + + if (pos == umax) + { + continue; + } + + const std::string key = fmt::trim(line.substr(0, pos)); + std::string value; + + if (pos != (line.size() - 1)) + { + value = fmt::trim(line.substr(pos + 1)); + } + + if (value.empty() && i != (lines.size() - 1) && line.size() != 1) + { + // Some games have a character on the last line of the file, don't print the error in those cases. + disc_log.warning("Unusual or malformed entry in SYSTEM.CNF ignored: %s", line); + continue; + } + + if (key == "BOOT2") + { + disc_log.notice("SYSTEM.CNF - Detected PS2 Disc = %s", value); + type = disc_type::ps2; + } + else if (key == "BOOT") + { + disc_log.notice("SYSTEM.CNF - Detected PSX/PSone Disc = %s", value); + type = disc_type::ps1; + } + else if (key == "VMODE") + { + disc_log.notice("SYSTEM.CNF - Disc region type = %s", value); + } + else if (key == "VER") + { + disc_log.notice("SYSTEM.CNF - Software version = %s", value); + } + } + + if (type == disc_type::unknown) + { + disc_log.error("SYSTEM.CNF - Disc is not a PSX/PSone or PS2 game!"); + } + + return type; + } +} diff --git a/rpcs3/Loader/disc.h b/rpcs3/Loader/disc.h new file mode 100644 index 0000000000..c31d345b71 --- /dev/null +++ b/rpcs3/Loader/disc.h @@ -0,0 +1,15 @@ +#pragma once + +namespace disc +{ + enum class disc_type + { + invalid, + unknown, + ps1, + ps2, + ps3 + }; + + disc_type get_disc_type(std::string path, std::string& disc_root, std::string& ps3_game_dir); +} diff --git a/rpcs3/emucore.vcxproj b/rpcs3/emucore.vcxproj index d5a7238134..c92b2e3a58 100644 --- a/rpcs3/emucore.vcxproj +++ b/rpcs3/emucore.vcxproj @@ -107,6 +107,7 @@ + NotUsing @@ -538,6 +539,7 @@ + diff --git a/rpcs3/emucore.vcxproj.filters b/rpcs3/emucore.vcxproj.filters index d113d6b9e3..2b164a04d8 100644 --- a/rpcs3/emucore.vcxproj.filters +++ b/rpcs3/emucore.vcxproj.filters @@ -1078,6 +1078,9 @@ Emu + + Loader + @@ -2143,6 +2146,9 @@ Emu + + Loader + diff --git a/rpcs3/headless_application.cpp b/rpcs3/headless_application.cpp index 25bd1eb755..e32c983256 100644 --- a/rpcs3/headless_application.cpp +++ b/rpcs3/headless_application.cpp @@ -138,6 +138,9 @@ void headless_application::InitializeCallbacks() callbacks.on_stop = []() {}; callbacks.on_ready = []() {}; + callbacks.enable_disc_eject = [](bool) {}; + callbacks.enable_disc_insert = [](bool) {}; + callbacks.on_missing_fw = []() { return false; }; callbacks.handle_taskbar_progress = [](s32, s32) {}; diff --git a/rpcs3/rpcs3qt/gui_application.cpp b/rpcs3/rpcs3qt/gui_application.cpp index fb016f1b8b..1e4c503b5d 100644 --- a/rpcs3/rpcs3qt/gui_application.cpp +++ b/rpcs3/rpcs3qt/gui_application.cpp @@ -246,6 +246,8 @@ void gui_application::InitializeConnects() connect(this, &gui_application::OnEmulatorPause, m_main_window, &main_window::OnEmuPause); connect(this, &gui_application::OnEmulatorResume, m_main_window, &main_window::OnEmuResume); connect(this, &gui_application::OnEmulatorReady, m_main_window, &main_window::OnEmuReady); + connect(this, &gui_application::OnEnableDiscEject, m_main_window, &main_window::OnEnableDiscEject); + connect(this, &gui_application::OnEnableDiscInsert, m_main_window, &main_window::OnEnableDiscInsert); } #ifdef WITH_DISCORD_RPC @@ -414,6 +416,21 @@ void gui_application::InitializeCallbacks() callbacks.on_stop = [this]() { OnEmulatorStop(); }; callbacks.on_ready = [this]() { OnEmulatorReady(); }; + callbacks.enable_disc_eject = [this](bool enabled) + { + Emu.CallFromMainThread([this, enabled]() + { + OnEnableDiscEject(enabled); + }); + }; + callbacks.enable_disc_insert = [this](bool enabled) + { + Emu.CallFromMainThread([this, enabled]() + { + OnEnableDiscInsert(enabled); + }); + }; + callbacks.on_missing_fw = [this]() { if (!m_main_window) return false; diff --git a/rpcs3/rpcs3qt/gui_application.h b/rpcs3/rpcs3qt/gui_application.h index 6aafed0c39..7d9fe42575 100644 --- a/rpcs3/rpcs3qt/gui_application.h +++ b/rpcs3/rpcs3qt/gui_application.h @@ -92,6 +92,8 @@ Q_SIGNALS: void OnEmulatorResume(bool start_playtime); void OnEmulatorStop(); void OnEmulatorReady(); + void OnEnableDiscEject(bool enabled); + void OnEnableDiscInsert(bool enabled); void RequestCallFromMainThread(const std::function& func); diff --git a/rpcs3/rpcs3qt/gui_settings.h b/rpcs3/rpcs3qt/gui_settings.h index 00c980a2a4..59d9680f62 100644 --- a/rpcs3/rpcs3qt/gui_settings.h +++ b/rpcs3/rpcs3qt/gui_settings.h @@ -140,6 +140,7 @@ namespace gui const gui_save fd_log_viewer = gui_save(main_window, "lastExplorePathLOG", ""); const gui_save fd_ext_mself = gui_save(main_window, "lastExplorePathExMSELF", ""); const gui_save fd_ext_tar = gui_save(main_window, "lastExplorePathExTAR", ""); + const gui_save fd_insert_disc = gui_save(main_window, "lastExplorePathDISC", ""); const gui_save mw_debugger = gui_save(main_window, "debuggerVisible", false); const gui_save mw_logger = gui_save(main_window, "loggerVisible", true); diff --git a/rpcs3/rpcs3qt/main_window.cpp b/rpcs3/rpcs3qt/main_window.cpp index 8ba250e739..5c732669b3 100644 --- a/rpcs3/rpcs3qt/main_window.cpp +++ b/rpcs3/rpcs3qt/main_window.cpp @@ -1692,6 +1692,16 @@ void main_window::EnableMenus(bool enabled) const ui->actionCreate_RSX_Capture->setEnabled(enabled); } +void main_window::OnEnableDiscEject(bool enabled) const +{ + ui->ejectDiscAct->setEnabled(enabled); +} + +void main_window::OnEnableDiscInsert(bool enabled) const +{ + ui->insertDiscAct->setEnabled(enabled); +} + void main_window::BootRecentAction(const QAction* act) { if (Emu.IsRunning()) @@ -2053,6 +2063,34 @@ void main_window::CreateConnects() Emu.Restart(); }); + connect(ui->ejectDiscAct, &QAction::triggered, this, []() + { + gui_log.notice("User triggered eject disc action in menu bar"); + Emu.EjectDisc(); + }); + connect(ui->insertDiscAct, &QAction::triggered, this, [this]() + { + gui_log.notice("User triggered insert disc action in menu bar"); + + const QString path_last_game = m_gui_settings->GetValue(gui::fd_insert_disc).toString(); + const QString dir_path = QFileDialog::getExistingDirectory(this, tr("Select Disc Game Folder"), path_last_game, QFileDialog::ShowDirsOnly | QFileDialog::DontResolveSymlinks); + + if (dir_path.isEmpty()) + { + return; + } + + const game_boot_result result = Emu.InsertDisc(dir_path.toStdString()); + + if (result != game_boot_result::no_errors) + { + QMessageBox::warning(this, tr("Failed to insert disc"), tr("Make sure that the emulation is running and that the selected path belongs to a valid disc game.")); + return; + } + + m_gui_settings->SetValue(gui::fd_insert_disc, QFileInfo(dir_path).path()); + }); + connect(ui->sysSendOpenMenuAct, &QAction::triggered, this, [this]() { if (Emu.IsStopped()) return; diff --git a/rpcs3/rpcs3qt/main_window.h b/rpcs3/rpcs3qt/main_window.h index 182e071c46..6d8bd5eb73 100644 --- a/rpcs3/rpcs3qt/main_window.h +++ b/rpcs3/rpcs3qt/main_window.h @@ -101,6 +101,8 @@ public Q_SLOTS: void OnEmuResume() const; void OnEmuPause() const; void OnEmuReady() const; + void OnEnableDiscEject(bool enabled) const; + void OnEnableDiscInsert(bool enabled) const; void RepaintGui(); void RetranslateUI(const QStringList& language_codes, const QString& language); diff --git a/rpcs3/rpcs3qt/main_window.ui b/rpcs3/rpcs3qt/main_window.ui index 54f0271a2f..14b25a5a8f 100644 --- a/rpcs3/rpcs3qt/main_window.ui +++ b/rpcs3/rpcs3qt/main_window.ui @@ -222,6 +222,9 @@ + + + @@ -1199,6 +1202,22 @@ Cameras + + + false + + + Eject Disc + + + + + false + + + Insert Disc + + diff --git a/rpcs3/rpcs3qt/qt_camera_handler.cpp b/rpcs3/rpcs3qt/qt_camera_handler.cpp index 64d8f93d24..b57044da1e 100644 --- a/rpcs3/rpcs3qt/qt_camera_handler.cpp +++ b/rpcs3/rpcs3qt/qt_camera_handler.cpp @@ -100,7 +100,7 @@ void qt_camera_handler::open_camera() if (const std::string camera_id = g_cfg.io.camera_id.to_string(); m_camera_id != camera_id) { - camera_log.notice("Switching camera from %s to %s", camera_id, m_camera_id); + camera_log.notice("Switching camera from %s to %s", m_camera_id, camera_id); camera_log.notice("Unloading old camera..."); if (m_camera) m_camera->unload(); m_camera_id = camera_id;