diff --git a/rpcs3/Emu/CMakeLists.txt b/rpcs3/Emu/CMakeLists.txt index 4748abddd4..159d5f88cc 100644 --- a/rpcs3/Emu/CMakeLists.txt +++ b/rpcs3/Emu/CMakeLists.txt @@ -422,6 +422,7 @@ target_sources(rpcs3_emu PRIVATE RSX/Overlays/overlay_perf_metrics.cpp RSX/Overlays/overlay_progress_bar.cpp RSX/Overlays/overlay_save_dialog.cpp + RSX/Overlays/overlay_user_list_dialog.cpp RSX/Overlays/overlay_utils.cpp RSX/Overlays/overlays.cpp RSX/Overlays/overlay_shader_compile_notification.cpp diff --git a/rpcs3/Emu/Cell/Modules/cellSaveData.cpp b/rpcs3/Emu/Cell/Modules/cellSaveData.cpp index d0247c017b..ddf3f50319 100644 --- a/rpcs3/Emu/Cell/Modules/cellSaveData.cpp +++ b/rpcs3/Emu/Cell/Modules/cellSaveData.cpp @@ -1,6 +1,7 @@ #include "stdafx.h" #include "Emu/System.h" #include "Emu/VFS.h" +#include "Emu/IdManager.h" #include "Emu/localized_string.h" #include "Emu/Cell/lv2/sys_fs.h" #include "Emu/Cell/lv2/sys_sync.h" @@ -97,7 +98,7 @@ vm::gvar g_savedata_context; struct savedata_manager { semaphore<> mutex; - atomic_t enable_overlay; + atomic_t enable_overlay{false}; }; static std::vector get_save_entries(const std::string& base_dir, const std::string& prefix) @@ -931,7 +932,7 @@ static NEVER_INLINE error_code savedata_op(ppu_thread& ppu, u32 operation, u32 v return {CELL_SAVEDATA_ERROR_PARAM, "34"}; } - //TODO: If adding the new data to the save_entries vector + // TODO: If adding the new data to the save_entries vector // to be displayed in the save mangaer UI, it should be focused here break; } diff --git a/rpcs3/Emu/Cell/Modules/cellUserInfo.cpp b/rpcs3/Emu/Cell/Modules/cellUserInfo.cpp index 8f96bc8c35..90757bbb04 100644 --- a/rpcs3/Emu/Cell/Modules/cellUserInfo.cpp +++ b/rpcs3/Emu/Cell/Modules/cellUserInfo.cpp @@ -1,7 +1,9 @@ #include "stdafx.h" #include "Emu/System.h" #include "Emu/VFS.h" +#include "Emu/IdManager.h" #include "Emu/Cell/PPUModule.h" +#include "Emu/RSX/Overlays/overlay_user_list_dialog.h" #include "cellUserInfo.h" @@ -10,6 +12,25 @@ LOG_CHANNEL(cellUserInfo); +struct user_info_manager +{ + atomic_t enable_overlay{false}; + atomic_t dialog_opened{false}; +}; + +std::string get_username(const u32 user_id) +{ + std::string username; + + if (const fs::file file{Emulator::GetHddDir() + fmt::format("home/%08d/localusername", user_id)}) + { + username = file.to_string(); + username.resize(CELL_USERINFO_USERNAME_SIZE); // TODO: investigate + } + + return username; +} + template<> void fmt_class_string::format(std::string& out, u64 arg) { @@ -27,6 +48,21 @@ void fmt_class_string::format(std::string& out, u64 arg) }); } +template<> +void fmt_class_string::format(std::string& out, u64 arg) +{ + format_enum(out, arg, [](auto error) + { + switch (error) + { + STR_CASE(CELL_USERINFO_RET_OK); + STR_CASE(CELL_USERINFO_RET_CANCEL); + } + + return unknown; + }); +} + error_code cellUserInfoGetStat(u32 id, vm::ptr stat) { cellUserInfo.warning("cellUserInfoGetStat(id=%d, stat=*0x%x)", id, stat); @@ -43,7 +79,7 @@ error_code cellUserInfoGetStat(u32 id, vm::ptr stat) id = Emu.GetUsrId(); } - const std::string& path = vfs::get(fmt::format("/dev_hdd0/home/%08d/", id)); + const std::string path = vfs::get(fmt::format("/dev_hdd0/home/%08d/", id)); if (!fs::is_dir(path)) { @@ -70,12 +106,103 @@ error_code cellUserInfoGetStat(u32 id, vm::ptr stat) error_code cellUserInfoSelectUser_ListType(vm::ptr listType, vm::ptr funcSelect, u32 container, vm::ptr userdata) { - cellUserInfo.todo("cellUserInfoSelectUser_ListType(listType=*0x%x, funcSelect=*0x%x, container=0x%x, userdata=*0x%x)", listType, funcSelect, container, userdata); + cellUserInfo.warning("cellUserInfoSelectUser_ListType(listType=*0x%x, funcSelect=*0x%x, container=0x%x, userdata=*0x%x)", listType, funcSelect, container, userdata); + + if (!listType || !funcSelect) // TODO: confirm + { + return CELL_USERINFO_ERROR_PARAM; + } + + if (g_fxo->get().dialog_opened) + { + return CELL_USERINFO_ERROR_BUSY; + } + + std::vector user_ids; + const std::string home_dir = Emulator::GetHddDir() + "home"; + + for (const auto& user_folder : fs::dir(home_dir)) + { + if (!user_folder.is_directory) + { + continue; + } + + // Is the folder name exactly 8 all-numerical characters long? + const u32 user_id = Emulator::CheckUsr(user_folder.name); + + if (user_id == 0) + { + continue; + } + + // Does the localusername file exist? + if (!fs::is_file(home_dir + "/" + user_folder.name + "/localusername")) + { + continue; + } + + // TODO: maybe also restrict this to CELL_USERINFO_USER_MAX + if (listType->type != CELL_USERINFO_LISTTYPE_NOCURRENT || user_id != Emu.GetUsrId()) + { + user_ids.push_back(user_id); + } + } + + if (auto manager = g_fxo->try_get()) + { + if (g_fxo->get().dialog_opened.exchange(true)) + { + return CELL_USERINFO_ERROR_BUSY; + } + + const std::string title = listType->title.get_ptr(); + const u32 focused = listType->focus; + + cellUserInfo.warning("cellUserInfoSelectUser_ListType: opening user_list_dialog with: title='%s', focused=%d", title, focused); + + const bool enable_overlay = g_fxo->get().enable_overlay; + const error_code result = manager->create()->show(title, focused, user_ids, enable_overlay, [funcSelect, userdata](s32 status) + { + s32 callback_result = CELL_USERINFO_RET_CANCEL; + u32 selected_user_id = 0; + std::string selected_username; + + if (status >= 0) + { + callback_result = CELL_USERINFO_RET_OK; + selected_user_id = static_cast(status); + selected_username = get_username(selected_user_id); + } + + cellUserInfo.warning("cellUserInfoSelectUser_ListType: callback_result=%s, selected_user_id=%d, selected_username='%s'", callback_result, selected_user_id, selected_username); + + sysutil_register_cb([=](ppu_thread& ppu) -> s32 + { + vm::var selectUser; + if (status >= 0) + { + selectUser->id = selected_user_id; + strcpy_trunc(selectUser->name, selected_username); + } + funcSelect(ppu, callback_result, selectUser, userdata); + return CELL_OK; + }); + + g_fxo->get().dialog_opened = false; + }); + + return result; + } + + cellUserInfo.error("User selection is only possible when the native user interface is enabled in the settings. The currently active user will be selected as a fallback."); sysutil_register_cb([=](ppu_thread& ppu) -> s32 { vm::var selectUser; - funcSelect(ppu, CELL_OK, selectUser, userdata); + selectUser->id = Emu.GetUsrId(); + strcpy_trunc(selectUser->name, get_username(Emu.GetUsrId())); + funcSelect(ppu, CELL_USERINFO_RET_OK, selectUser, userdata); return CELL_OK; }); @@ -84,12 +211,99 @@ error_code cellUserInfoSelectUser_ListType(vm::ptr listType error_code cellUserInfoSelectUser_SetList(vm::ptr setList, vm::ptr funcSelect, u32 container, vm::ptr userdata) { - cellUserInfo.todo("cellUserInfoSelectUser_SetList(setList=*0x%x, funcSelect=*0x%x, container=0x%x, userdata=*0x%x)", setList, funcSelect, container, userdata); + cellUserInfo.warning("cellUserInfoSelectUser_SetList(setList=*0x%x, funcSelect=*0x%x, container=0x%x, userdata=*0x%x)", setList, funcSelect, container, userdata); + + if (!setList || !funcSelect) // TODO: confirm + { + return CELL_USERINFO_ERROR_PARAM; + } + + if (g_fxo->get().dialog_opened) + { + return CELL_USERINFO_ERROR_BUSY; + } + + std::vector user_ids; + + for (usz i = 0; i < CELL_USERINFO_USER_MAX && i < setList->fixedListNum; i++) + { + if (const u32 id = setList->fixedList->userId[i]) + { + user_ids.push_back(id); + } + } + + if (user_ids.empty()) + { + // TODO: Confirm. Also check if this is possible in cellUserInfoSelectUser_ListType. + cellUserInfo.error("cellUserInfoSelectUser_SetList: callback_result=%s", CELL_USERINFO_ERROR_NOUSER); + + sysutil_register_cb([=](ppu_thread& ppu) -> s32 + { + vm::var selectUser; + funcSelect(ppu, CELL_USERINFO_ERROR_NOUSER, selectUser, userdata); + return CELL_OK; + }); + + return CELL_OK; + } + + // TODO: does this function return an error if any (user_id > 0 && not_found) ? + + if (auto manager = g_fxo->try_get()) + { + if (g_fxo->get().dialog_opened.exchange(true)) + { + return CELL_USERINFO_ERROR_BUSY; + } + + const std::string title = setList->title.get_ptr(); + const u32 focused = setList->focus; + + cellUserInfo.warning("cellUserInfoSelectUser_SetList: opening user_list_dialog with: title='%s', focused=%d", title, focused); + + const bool enable_overlay = g_fxo->get().enable_overlay; + const error_code result = manager->create()->show(title, focused, user_ids, enable_overlay, [funcSelect, userdata](s32 status) + { + s32 callback_result = CELL_USERINFO_RET_CANCEL; + u32 selected_user_id = 0; + std::string selected_username; + + if (status >= 0) + { + callback_result = CELL_USERINFO_RET_OK; + selected_user_id = static_cast(status); + selected_username = get_username(selected_user_id); + } + + cellUserInfo.warning("cellUserInfoSelectUser_SetList: callback_result=%s, selected_user_id=%d, selected_username='%s'", callback_result, selected_user_id, selected_username); + + sysutil_register_cb([=](ppu_thread& ppu) -> s32 + { + vm::var selectUser; + if (status >= 0) + { + selectUser->id = selected_user_id; + strcpy_trunc(selectUser->name, selected_username); + } + funcSelect(ppu, callback_result, selectUser, userdata); + return CELL_OK; + }); + + g_fxo->get().dialog_opened = false; + }); + + return result; + } + + cellUserInfo.error("User selection is only possible when the native user interface is enabled in the settings. The currently active user will be selected as a fallback."); sysutil_register_cb([=](ppu_thread& ppu) -> s32 { vm::var selectUser; - funcSelect(ppu, CELL_OK, selectUser, userdata); + selectUser->id = Emu.GetUsrId(); + strcpy_trunc(selectUser->name, get_username(Emu.GetUsrId())); + funcSelect(ppu, CELL_USERINFO_RET_OK, selectUser, userdata); return CELL_OK; }); @@ -98,12 +312,14 @@ error_code cellUserInfoSelectUser_SetList(vm::ptr setList, void cellUserInfoEnableOverlay(s32 enable) { - cellUserInfo.todo("cellUserInfoEnableOverlay(enable=%d)", enable); + cellUserInfo.notice("cellUserInfoEnableOverlay(enable=%d)", enable); + auto& manager = g_fxo->get(); + manager.enable_overlay = enable != 0; } error_code cellUserInfoGetList(vm::ptr listNum, vm::ptr listBuf, vm::ptr currentUserId) { - cellUserInfo.todo("cellUserInfoGetList(listNum=*0x%x, listBuf=*0x%x, currentUserId=*0x%x)", listNum, listBuf, currentUserId); + cellUserInfo.warning("cellUserInfoGetList(listNum=*0x%x, listBuf=*0x%x, currentUserId=*0x%x)", listNum, listBuf, currentUserId); // If only listNum is NULL, an error will be returned if (!listNum) @@ -114,17 +330,58 @@ error_code cellUserInfoGetList(vm::ptr listNum, vm::ptr user_ids; + + for (const auto& user_folder : fs::dir(home_dir)) + { + if (!user_folder.is_directory) + { + continue; + } + + // Is the folder name exactly 8 all-numerical characters long? + const u32 user_id = Emulator::CheckUsr(user_folder.name); + + if (user_id == 0) + { + continue; + } + + // Does the localusername file exist? + if (!fs::is_file(home_dir + "/" + user_folder.name + "/localusername")) + { + continue; + } + + if (user_ids.size() < CELL_USERINFO_USER_MAX) + { + user_ids.push_back(user_id); + } + else + { + cellUserInfo.warning("cellUserInfoGetList: Cannot add user %s. Too many users.", user_folder.name); + } + } + if (listNum) { - *listNum = 1; + *listNum = static_cast(user_ids.size()); } if (listBuf) { - std::memset(listBuf.get_ptr(), 0, listBuf.size()); - - // We report only one user, so it must be the current user - listBuf->userId[0] = Emu.GetUsrId(); + for (usz i = 0; i < CELL_USERINFO_USER_MAX; i++) + { + if (i < user_ids.size()) + { + listBuf->userId[i] = user_ids[i]; + } + else + { + listBuf->userId[i] = 0; + } + } } if (currentUserId) diff --git a/rpcs3/Emu/Cell/Modules/cellUserInfo.h b/rpcs3/Emu/Cell/Modules/cellUserInfo.h index ff6008916a..46dfef116c 100644 --- a/rpcs3/Emu/Cell/Modules/cellUserInfo.h +++ b/rpcs3/Emu/Cell/Modules/cellUserInfo.h @@ -25,12 +25,23 @@ enum CellUserInfoListType CELL_USERINFO_LISTTYPE_NOCURRENT = 1, }; -enum +enum cell_user_callback_result : u32 +{ + CELL_USERINFO_RET_OK = 0, + CELL_USERINFO_RET_CANCEL = 1, +}; + +enum : u32 { CELL_SYSUTIL_USERID_CURRENT = 0, CELL_SYSUTIL_USERID_MAX = 99999999, }; +enum : u32 +{ + CELL_USERINFO_FOCUS_LISTHEAD = 0xffffffff +}; + // Structs struct CellUserInfoUserStat { diff --git a/rpcs3/Emu/RSX/Overlays/overlay_message_dialog.cpp b/rpcs3/Emu/RSX/Overlays/overlay_message_dialog.cpp index 37059132a9..1b45c64b78 100644 --- a/rpcs3/Emu/RSX/Overlays/overlay_message_dialog.cpp +++ b/rpcs3/Emu/RSX/Overlays/overlay_message_dialog.cpp @@ -133,7 +133,8 @@ namespace rsx // Ignore cancel operation for Ok-only return; } - else if (cancel_only) + + if (cancel_only) { return_code = CELL_MSGDIALOG_BUTTON_ESCAPE; } @@ -220,7 +221,7 @@ namespace rsx { if (interactive) { - if (auto error = run_input_loop()) + if (const auto error = run_input_loop()) { rsx_log.error("Dialog input loop exited with error code=%d", error); return error; @@ -249,7 +250,7 @@ namespace rsx { auto ref = g_fxo->get().get(uid); - if (auto error = run_input_loop()) + if (const auto error = run_input_loop()) { rsx_log.error("Dialog input loop exited with error code=%d", error); } diff --git a/rpcs3/Emu/RSX/Overlays/overlay_save_dialog.cpp b/rpcs3/Emu/RSX/Overlays/overlay_save_dialog.cpp index a3b39def6e..7c9bc1c696 100644 --- a/rpcs3/Emu/RSX/Overlays/overlay_save_dialog.cpp +++ b/rpcs3/Emu/RSX/Overlays/overlay_save_dialog.cpp @@ -96,8 +96,8 @@ namespace rsx m_time_thingy->set_pos(1000, 30); m_time_thingy->set_text(date_time::current_time()); - static_cast(m_description.get())->auto_resize(); - static_cast(m_time_thingy.get())->auto_resize(); + m_description->auto_resize(); + m_time_thingy->auto_resize(); m_dim_background->back_color.a = 0.5f; m_description->back_color.a = 0.f; @@ -109,7 +109,7 @@ namespace rsx void save_dialog::update() { m_time_thingy->set_text(date_time::current_time()); - static_cast(m_time_thingy.get())->auto_resize(); + m_time_thingy->auto_resize(); } void save_dialog::on_button_pressed(pad_button button_press) @@ -138,6 +138,7 @@ namespace rsx break; default: rsx_log.trace("[ui] Button %d pressed", static_cast(button_press)); + break; } } @@ -166,7 +167,7 @@ namespace rsx if (enable_overlay) { - m_dim_background->back_color.a = 1.0f; + m_dim_background->back_color.a = 0.9f; } else { @@ -261,10 +262,10 @@ namespace rsx m_list->select_entry(focused); } - static_cast(m_description.get())->auto_resize(); + m_description->auto_resize(); visible = true; - if (auto err = run_input_loop()) + if (const auto err = run_input_loop()) return err; if (return_code >= 0) @@ -273,7 +274,7 @@ namespace rsx { return return_code - 1; } - else if (static_cast(return_code) == entries.size()) + if (static_cast(return_code) == entries.size()) { return selection_code::new_save; } diff --git a/rpcs3/Emu/RSX/Overlays/overlay_user_list_dialog.cpp b/rpcs3/Emu/RSX/Overlays/overlay_user_list_dialog.cpp new file mode 100644 index 0000000000..c2cb355ceb --- /dev/null +++ b/rpcs3/Emu/RSX/Overlays/overlay_user_list_dialog.cpp @@ -0,0 +1,217 @@ +#include "stdafx.h" +#include "overlay_user_list_dialog.h" +#include "Emu/System.h" +#include "Emu/system_config.h" +#include "Utilities/StrUtil.h" +#include "Utilities/Thread.h" + +namespace rsx +{ + namespace overlays + { + user_list_dialog::user_list_entry::user_list_entry(const std::string& username, const std::string& user_id, const std::string& avatar_path) + { + std::unique_ptr image = std::make_unique(); + image->set_size(160, 110); + image->set_padding(36, 36, 11, 11); // Square image, 88x88 + + if (fs::exists(avatar_path)) + { + icon_data = std::make_unique(avatar_path.c_str()); + static_cast(image.get())->set_raw_image(icon_data.get()); + } + else + { + // Fallback + static_cast(image.get())->set_image_resource(resource_config::standard_image_resource::square); + } + + std::unique_ptr text_stack = std::make_unique(); + std::unique_ptr padding = std::make_unique(); + std::unique_ptr header_text = std::make_unique