diff --git a/rpcs3/Emu/CMakeLists.txt b/rpcs3/Emu/CMakeLists.txt index 8251d6efb7..1a27efa1f9 100644 --- a/rpcs3/Emu/CMakeLists.txt +++ b/rpcs3/Emu/CMakeLists.txt @@ -419,6 +419,7 @@ target_sources(rpcs3_emu PRIVATE RSX/Overlays/overlay_edit_text.cpp RSX/Overlays/overlay_fonts.cpp RSX/Overlays/overlay_list_view.cpp + RSX/Overlays/overlay_media_list_dialog.cpp RSX/Overlays/overlay_message_dialog.cpp RSX/Overlays/overlay_osk.cpp RSX/Overlays/overlay_osk_panel.cpp diff --git a/rpcs3/Emu/Cell/Modules/cellMusic.cpp b/rpcs3/Emu/Cell/Modules/cellMusic.cpp index e35895c6e4..4291f97fa1 100644 --- a/rpcs3/Emu/Cell/Modules/cellMusic.cpp +++ b/rpcs3/Emu/Cell/Modules/cellMusic.cpp @@ -6,10 +6,10 @@ #include "Emu/Io/music_handler_base.h" #include "Emu/System.h" #include "Emu/VFS.h" +#include "Emu/RSX/Overlays/overlay_media_list_dialog.h" #include "cellSearch.h" #include "cellSpurs.h" #include "cellSysutil.h" - #include "cellMusic.h" LOG_CHANNEL(cellMusic); @@ -80,6 +80,45 @@ struct music_state } }; +error_code cell_music_select_contents() +{ + auto& music = g_fxo->get(); + + if (!music.func) + return CELL_MUSIC_ERROR_GENERIC; + + const std::string dir_path = "/dev_hdd0/music"; + const std::string vfs_dir_path = vfs::get("/dev_hdd0/music"); + const std::string title = get_localized_string(localized_string_id::RSX_OVERLAYS_MEDIA_DIALOG_EMPTY); + + error_code error = rsx::overlays::show_media_list_dialog(rsx::overlays::media_list_dialog::media_type::audio, vfs_dir_path, title, + [&music, dir_path, vfs_dir_path](s32 status, utils::media_info info) + { + sysutil_register_cb([&music, dir_path, vfs_dir_path, info, status](ppu_thread& ppu) -> s32 + { + const u32 result = status >= 0 ? u32{CELL_OK} : u32{CELL_MUSIC_CANCELED}; + if (result == CELL_OK) + { + music_selection_context context; + context.content_path = info.path; + context.content_path = dir_path + info.path.substr(vfs_dir_path.length()); // We need the non-vfs path here + context.content_type = fs::is_dir(info.path) ? CELL_SEARCH_CONTENTTYPE_MUSICLIST : CELL_SEARCH_CONTENTTYPE_MUSIC; + // TODO: context.repeat_mode = CELL_SEARCH_REPEATMODE_NONE; + // TODO: context.context_option = CELL_SEARCH_CONTEXTOPTION_NONE; + music.current_selection_context = context; + cellMusic.success("Media list dialog: selected entry '%s'", context.content_path); + } + else + { + cellMusic.warning("Media list dialog was canceled"); + } + music.func(ppu, CELL_MUSIC_EVENT_SELECT_CONTENTS_RESULT, vm::addr_t(result), music.userData); + return CELL_OK; + }); + }); + return error; +} + error_code cellMusicGetSelectionContext(vm::ptr context) { cellMusic.todo("cellMusicGetSelectionContext(context=*0x%x)", context); @@ -392,7 +431,8 @@ error_code cellMusicSetPlaybackCommand2(s32 command, vm::ptr param) std::string path; { std::lock_guard lock(music.mtx); - path = vfs::get(music.current_selection_context.path); + path = vfs::get(music.current_selection_context.content_path); + cellMusic.notice("cellMusicSetPlaybackCommand2: current vfs path: '%s' (unresolved='%s')", path, music.current_selection_context.content_path); } switch (command) @@ -447,7 +487,8 @@ error_code cellMusicSetPlaybackCommand(s32 command, vm::ptr param) std::string path; { std::lock_guard lock(music.mtx); - path = vfs::get(music.current_selection_context.path); + path = vfs::get(music.current_selection_context.content_path); + cellMusic.notice("cellMusicSetPlaybackCommand: current vfs path: '%s' (unresolved='%s')", path, music.current_selection_context.content_path); } switch (command) @@ -488,36 +529,14 @@ error_code cellMusicSelectContents2() { cellMusic.todo("cellMusicSelectContents2()"); - auto& music = g_fxo->get(); - - if (!music.func) - return CELL_MUSIC2_ERROR_GENERIC; - - sysutil_register_cb([=, &music](ppu_thread& ppu) -> s32 - { - music.func(ppu, CELL_MUSIC2_EVENT_SELECT_CONTENTS_RESULT, vm::addr_t(CELL_OK), music.userData); - return CELL_OK; - }); - - return CELL_OK; + return cell_music_select_contents(); } error_code cellMusicSelectContents(u32 container) { cellMusic.todo("cellMusicSelectContents(container=0x%x)", container); - auto& music = g_fxo->get(); - - if (!music.func) - return CELL_MUSIC_ERROR_GENERIC; - - sysutil_register_cb([=, &music](ppu_thread& ppu) -> s32 - { - music.func(ppu, CELL_MUSIC_EVENT_SELECT_CONTENTS_RESULT, vm::addr_t(CELL_OK), music.userData); - return CELL_OK; - }); - - return CELL_OK; + return cell_music_select_contents(); } error_code cellMusicInitialize2(s32 mode, s32 spuPriority, vm::ptr func, vm::ptr userData) diff --git a/rpcs3/Emu/Cell/Modules/cellMusic.h b/rpcs3/Emu/Cell/Modules/cellMusic.h index cc2a09cb2b..577ff2fa3f 100644 --- a/rpcs3/Emu/Cell/Modules/cellMusic.h +++ b/rpcs3/Emu/Cell/Modules/cellMusic.h @@ -145,7 +145,7 @@ struct music_selection_context u32 content_type{0}; u32 repeat_mode{0}; u32 context_option{0}; - std::string path; + std::string content_path; static constexpr u32 max_depth = 2; // root + 1 folder + file @@ -161,14 +161,14 @@ struct music_selection_context pos += sizeof(content_type); repeat_mode = in.data[pos++]; context_option = in.data[pos++]; - path = &in.data[pos]; + content_path = &in.data[pos]; return true; } CellMusicSelectionContext get() const { - if (path.size() + 2 + sizeof(content_type) + sizeof(magic) > CELL_MUSIC_SELECTION_CONTEXT_SIZE) + if (content_path.size() + 2 + sizeof(content_type) + sizeof(magic) > CELL_MUSIC_SELECTION_CONTEXT_SIZE) { fmt::throw_exception("Contents of music_selection_context are too large"); } @@ -183,14 +183,14 @@ struct music_selection_context pos += sizeof(content_type); out.data[pos++] = repeat_mode; out.data[pos++] = context_option; - std::memcpy(&out.data[pos], path.c_str(), path.size()); + std::memcpy(&out.data[pos], content_path.c_str(), content_path.size()); return out; } std::string to_string() const { - return fmt::format("{ .magic='%s', .content_type=%d, .repeat_mode=%d, .context_option=%d, .path='%s' }", magic, content_type, repeat_mode, context_option, path); + return fmt::format("{ .magic='%s', .content_type=%d, .repeat_mode=%d, .context_option=%d, .path='%s' }", magic, content_type, repeat_mode, context_option, content_path); } // Helper diff --git a/rpcs3/Emu/Cell/Modules/cellMusicDecode.cpp b/rpcs3/Emu/Cell/Modules/cellMusicDecode.cpp index ff09d02e02..ff391b97e2 100644 --- a/rpcs3/Emu/Cell/Modules/cellMusicDecode.cpp +++ b/rpcs3/Emu/Cell/Modules/cellMusicDecode.cpp @@ -3,6 +3,7 @@ #include "Emu/Cell/lv2/sys_lwmutex.h" #include "Emu/Cell/lv2/sys_lwcond.h" #include "Emu/Cell/lv2/sys_spu.h" +#include "Emu/RSX/Overlays/overlay_media_list_dialog.h" #include "Emu/VFS.h" #include "cellMusic.h" #include "cellSearch.h" @@ -138,7 +139,10 @@ struct music_decode readPos = 0; // Decode data. The format of the decoded data is 48kHz, float 32bit, 2ch LPCM data interleaved in order from left to right. - decoder.set_path(vfs::get(current_selection_context.path)); + const std::string path = vfs::get(current_selection_context.content_path); + cellMusicDecode.notice("set_decode_command(START): Setting vfs path: '%s' (unresolved='%s')", path, current_selection_context.content_path); + + decoder.set_path(path); decoder.set_swap_endianness(true); decoder.decode(); break; @@ -167,6 +171,46 @@ struct music_decode2 : music_decode s32 speed = CELL_MUSIC_DECODE2_SPEED_MAX; }; +template +error_code cell_music_decode_select_contents() +{ + auto& dec = g_fxo->get(); + + if (!dec.func) + return CELL_MUSIC_DECODE_ERROR_GENERIC; + + const std::string dir_path = "/dev_hdd0/music"; + const std::string vfs_dir_path = vfs::get("/dev_hdd0/music"); + const std::string title = get_localized_string(localized_string_id::RSX_OVERLAYS_MEDIA_DIALOG_EMPTY); + + error_code error = rsx::overlays::show_media_list_dialog(rsx::overlays::media_list_dialog::media_type::audio, vfs_dir_path, title, + [&dec, dir_path, vfs_dir_path](s32 status, utils::media_info info) + { + sysutil_register_cb([&dec, dir_path, vfs_dir_path, info, status](ppu_thread& ppu) -> s32 + { + std::lock_guard lock(dec.mutex); + const u32 result = status >= 0 ? u32{CELL_OK} : u32{CELL_MUSIC_DECODE_CANCELED}; + if (result == CELL_OK) + { + music_selection_context context; + context.content_path = dir_path + info.path.substr(vfs_dir_path.length()); // We need the non-vfs path here + context.content_type = fs::is_dir(info.path) ? CELL_SEARCH_CONTENTTYPE_MUSICLIST : CELL_SEARCH_CONTENTTYPE_MUSIC; + // TODO: context.repeat_mode = CELL_SEARCH_REPEATMODE_NONE; + // TODO: context.context_option = CELL_SEARCH_CONTEXTOPTION_NONE; + dec.current_selection_context = context; + cellMusicDecode.success("Media list dialog: selected entry '%s'", context.content_path); + } + else + { + cellMusicDecode.warning("Media list dialog was canceled"); + } + dec.func(ppu, CELL_MUSIC_DECODE_EVENT_SELECT_CONTENTS_RESULT, vm::addr_t(result), dec.userData); + return CELL_OK; + }); + }); + return error; +} + template error_code cell_music_decode_read(vm::ptr buf, vm::ptr startTime, u64 reqSize, vm::ptr readSize, vm::ptr position) { @@ -285,19 +329,7 @@ error_code cellMusicDecodeSelectContents() { cellMusicDecode.todo("cellMusicDecodeSelectContents()"); - auto& dec = g_fxo->get(); - std::lock_guard lock(dec.mutex); - - if (!dec.func) - return CELL_MUSIC_DECODE_ERROR_GENERIC; - - sysutil_register_cb([=, &dec](ppu_thread& ppu) -> s32 - { - dec.func(ppu, CELL_MUSIC_DECODE_EVENT_SELECT_CONTENTS_RESULT, vm::addr_t(CELL_OK), dec.userData); - return CELL_OK; - }); - - return CELL_OK; + return cell_music_decode_select_contents(); } error_code cellMusicDecodeSetDecodeCommand(s32 command) @@ -464,19 +496,7 @@ error_code cellMusicDecodeSelectContents2() { cellMusicDecode.todo("cellMusicDecodeSelectContents2()"); - auto& dec = g_fxo->get(); - std::lock_guard lock(dec.mutex); - - if (!dec.func) - return CELL_MUSIC_DECODE_ERROR_GENERIC; - - sysutil_register_cb([=, &dec](ppu_thread& ppu) -> s32 - { - dec.func(ppu, CELL_MUSIC_DECODE_EVENT_SELECT_CONTENTS_RESULT, vm::addr_t(CELL_OK), dec.userData); - return CELL_OK; - }); - - return CELL_OK; + return cell_music_decode_select_contents(); } error_code cellMusicDecodeSetDecodeCommand2(s32 command) diff --git a/rpcs3/Emu/Cell/Modules/cellSearch.cpp b/rpcs3/Emu/Cell/Modules/cellSearch.cpp index b85cb7076a..e4186cee59 100644 --- a/rpcs3/Emu/Cell/Modules/cellSearch.cpp +++ b/rpcs3/Emu/Cell/Modules/cellSearch.cpp @@ -1045,7 +1045,8 @@ error_code cellSearchStartContentSearch(CellSearchContentSearchType type, CellSe cellSearch.notice("cellSearchStartContentSearch(): CellSearchId: 0x%x, Content ID: %08X, Path: \"%s\"", id, hash, item_path); } else // file is already stored and tracked - { // TODO + { + // TODO // Perform checks to see if the identified file has been modified since last checked // In which case, update the stored content's properties // auto content_found = &content_map->at(content_id); @@ -1486,8 +1487,8 @@ error_code cellSearchGetMusicSelectionContext(CellSearchId searchId, vm::cptrsecond->infoPath.contentPath; - cellSearch.notice("cellSearchGetMusicSelectionContext(): Hash=%08X, Assigning found track: Type=0x%x, Path=%s", content_hash, +content->second->type, context.path); + context.content_path = content->second->infoPath.contentPath; + cellSearch.notice("cellSearchGetMusicSelectionContext(): Hash=%08X, Assigning found track: Type=0x%x, Path=%s", content_hash, +content->second->type, context.content_path); } else if (first_content->type == CELL_SEARCH_CONTENTTYPE_MUSICLIST) { @@ -1497,8 +1498,8 @@ error_code cellSearchGetMusicSelectionContext(CellSearchId searchId, vm::cptrinfoPath.contentPath; - cellSearch.notice("cellSearchGetMusicSelectionContext(): Hash=%08X, Assigning first track: Type=0x%x, Path=%s", content_hash, +first_content->type, context.path); + context.content_path = first_content->infoPath.contentPath; + cellSearch.notice("cellSearchGetMusicSelectionContext(): Hash=%08X, Assigning first track: Type=0x%x, Path=%s", content_hash, +first_content->type, context.content_path); } } else if (first_content->type == CELL_SEARCH_CONTENTTYPE_MUSICLIST) @@ -1509,8 +1510,8 @@ error_code cellSearchGetMusicSelectionContext(CellSearchId searchId, vm::cptrinfoPath.contentPath; - cellSearch.notice("cellSearchGetMusicSelectionContext(): Assigning first track: Type=0x%x, Path=%s", +first_content->type, context.path); + context.content_path = first_content->infoPath.contentPath; + cellSearch.notice("cellSearchGetMusicSelectionContext(): Assigning first track: Type=0x%x, Path=%s", +first_content->type, context.content_path); } context.content_type = first_content->type; @@ -1518,9 +1519,9 @@ error_code cellSearchGetMusicSelectionContext(CellSearchId searchId, vm::cptrsecond; + context.content_path = found->second; } *outContext = context.get(); @@ -1539,8 +1540,10 @@ error_code cellSearchGetMusicSelectionContextOfSingleTrack(vm::cptrget(); + // TODO: find out if this check is correct - if (error_code error = check_search_state(g_fxo->get().state.load(), search_state::in_progress)) + if (error_code error = check_search_state(search.state.load(), search_state::in_progress)) { return error; } @@ -1560,7 +1563,13 @@ error_code cellSearchGetMusicSelectionContextOfSingleTrack(vm::cptrinfoPath.contentPath; + context.content_path = content_info->infoPath.contentPath; + + // Resolve hashed paths + if (auto found = search.content_links.find(context.content_path); found != search.content_links.end()) + { + context.content_path = found->second; + } *outContext = context.get(); @@ -1586,7 +1595,7 @@ error_code cellSearchGetContentInfoPath(vm::cptr contentId, const u64 id = *reinterpret_cast(contentId->data); auto& content_map = g_fxo->get(); auto found = content_map.map.find(id); - if(found != content_map.map.end()) + if (found != content_map.map.end()) { std::memcpy(infoPath.get_ptr(), &found->second->infoPath, sizeof(found->second->infoPath)); } @@ -1810,7 +1819,7 @@ error_code music_selection_context::find_content_id(vm::ptr // Search for the content that matches our current selection auto& content_map = g_fxo->get(); - const u64 hash = std::hash()(this->path); + const u64 hash = std::hash()(content_path); auto found = content_map.map.find(hash); if (found != content_map.map.end()) { @@ -1822,8 +1831,11 @@ error_code music_selection_context::find_content_id(vm::ptr } // Try to find the content manually - const std::string vpath = vfs::get("/dev_hdd0/music/"); - for (auto&& entry : fs::dir(vpath)) + auto& search = g_fxo->get(); + const std::string music_dir = "/dev_hdd0/music/"; + const std::string vfs_music_dir = vfs::get(music_dir); + + for (auto&& entry : fs::dir(vfs_music_dir)) { entry.name = vfs::unescape(entry.name); @@ -1832,7 +1844,8 @@ error_code music_selection_context::find_content_id(vm::ptr continue; } - std::string dir_path = vpath + entry.name; + const std::string dir_path = music_dir + entry.name; + const std::string vfs_dir_path = vfs_music_dir + entry.name; if (content_type == CELL_SEARCH_CONTENTTYPE_MUSICLIST) { @@ -1841,7 +1854,7 @@ error_code music_selection_context::find_content_id(vm::ptr if (hash == dir_hash) { u32 num_of_items = 0; - for (auto&& file : fs::dir(dir_path)) + for (auto&& file : fs::dir(vfs_dir_path)) { file.name = vfs::unescape(file.name); @@ -1856,16 +1869,32 @@ error_code music_selection_context::find_content_id(vm::ptr // TODO: check for actual content inside the directory std::shared_ptr curr_find = std::make_shared(); curr_find->type = CELL_SEARCH_CONTENTTYPE_MUSICLIST; - strcpy_trunc(curr_find->infoPath.contentPath, entry.name); // only save dir name due to 63 character limit + + if (dir_path.length() > CELL_SEARCH_PATH_LEN_MAX) + { + // Create mapping which will be resolved to an actual hard link in VFS by cellSearchPrepareFile + std::string link = "/.tmp/" + std::to_string(hash) + entry.name; + strcpy_trunc(curr_find->infoPath.contentPath, link); + + std::lock_guard lock(search.links_mutex); + search.content_links.emplace(std::move(link), dir_path); + } + else + { + strcpy_trunc(curr_find->infoPath.contentPath, dir_path); + } + CellSearchMusicListInfo& info = curr_find->data.music_list; info.listType = CELL_SEARCH_LISTSEARCHTYPE_MUSIC_ALBUM; info.numOfItems = num_of_items; info.duration = 0; strcpy_trunc(info.title, entry.name); strcpy_trunc(info.artistName, "ARTIST NAME"); + content_map.map.emplace(dir_hash, curr_find); const u128 content_id_128 = dir_hash; std::memcpy(contents_id->data, &content_id_128, CELL_SEARCH_CONTENT_ID_SIZE); + cellSearch.warning("find_content_id: found music list %s (path control: '%s')", to_string(), dir_path); return CELL_OK; } @@ -1874,7 +1903,7 @@ error_code music_selection_context::find_content_id(vm::ptr } // Search the subfolders. We assume all music is located in a depth of 2 (max_depth, root + 1 folder + file). - for (auto&& item : fs::dir(dir_path)) + for (auto&& item : fs::dir(vfs_dir_path)) { if (item.is_directory || item.name == "." || item.name == "..") { @@ -1886,19 +1915,36 @@ error_code music_selection_context::find_content_id(vm::ptr if (hash == file_hash) { - const auto [success, mi] = utils::get_media_info(path, 1); // AVMEDIA_TYPE_AUDIO + const auto [success, mi] = utils::get_media_info(vfs_dir_path + "/" + item.name, 1); // AVMEDIA_TYPE_AUDIO if (!success) { continue; } + std::shared_ptr curr_find = std::make_shared(); curr_find->type = CELL_SEARCH_CONTENTTYPE_MUSIC; - const std::string short_path = entry.name + "/" + item.name; // only save dir name and item name due to 63 character limit - strcpy_trunc(curr_find->infoPath.contentPath, short_path); + + if (file_path.length() > CELL_SEARCH_PATH_LEN_MAX) + { + // Create mapping which will be resolved to an actual hard link in VFS by cellSearchPrepareFile + const size_t ext_offset = item.name.find_last_of('.'); + std::string link = "/.tmp/" + std::to_string(hash) + item.name.substr(ext_offset); + strcpy_trunc(curr_find->infoPath.contentPath, link); + + std::lock_guard lock(search.links_mutex); + search.content_links.emplace(std::move(link), file_path); + } + else + { + strcpy_trunc(curr_find->infoPath.contentPath, file_path); + } + populate_music_info(curr_find->data.music, mi, item); + content_map.map.emplace(file_hash, curr_find); const u128 content_id_128 = file_hash; std::memcpy(contents_id->data, &content_id_128, CELL_SEARCH_CONTENT_ID_SIZE); + cellSearch.warning("find_content_id: found music track %s (path control: '%s')", to_string(), file_path); return CELL_OK; } diff --git a/rpcs3/Emu/RSX/Overlays/overlay_media_list_dialog.cpp b/rpcs3/Emu/RSX/Overlays/overlay_media_list_dialog.cpp new file mode 100644 index 0000000000..b6ab023c16 --- /dev/null +++ b/rpcs3/Emu/RSX/Overlays/overlay_media_list_dialog.cpp @@ -0,0 +1,370 @@ +#include "stdafx.h" +#include "overlay_media_list_dialog.h" +#include "Utilities/StrUtil.h" +#include "Utilities/Thread.h" +#include "overlays.h" +#include "Emu/VFS.h" +#include "Emu/Cell/Modules/cellMusic.h" + +namespace rsx +{ + namespace overlays + { + media_list_dialog::media_list_entry::media_list_entry(const media_list_dialog::media_entry& entry) + { + std::unique_ptr image = std::make_unique(); + image->set_size(160, 110); + image->set_padding(36, 36, 11, 11); // Square image, 88x88 + + switch (entry.type) + { + case media_list_dialog::media_type::audio: + { + // TODO: use thumbnail or proper icon + static_cast(image.get())->set_image_resource(resource_config::standard_image_resource::new_entry); + break; + } + case media_list_dialog::media_type::video: + { + // TODO: use thumbnail or proper icon + static_cast(image.get())->set_image_resource(resource_config::standard_image_resource::new_entry); + break; + } + case media_list_dialog::media_type::photo: + { + if (fs::exists(entry.info.path)) + { + icon_data = std::make_unique(entry.info.path.c_str()); + static_cast(image.get())->set_raw_image(icon_data.get()); + } + else + { + // Fallback + // TODO: use proper icon + static_cast(image.get())->set_image_resource(resource_config::standard_image_resource::new_entry); + } + break; + } + case media_list_dialog::media_type::directory: + { + static_cast(image.get())->set_image_resource(resource_config::standard_image_resource::save); + break; + } + case media_list_dialog::media_type::invalid: + fmt::throw_exception("Unexpected media type"); + } + + char title[384]{}; // CELL_SEARCH_TITLE_LEN_MAX + char artist[384]{}; // CELL_SEARCH_TITLE_LEN_MAX + + if (entry.type == media_type::directory) + { + strcpy_trunc(title, entry.name); + } + else + { + utils::parse_metadata(title, entry.info, "title", entry.name.substr(0, entry.name.find_last_of('.')), 384); // CELL_SEARCH_TITLE_LEN_MAX + utils::parse_metadata(artist, entry.info, "artist", "Unknown Artist", 384); // CELL_SEARCH_TITLE_LEN_MAX + } + + std::unique_ptr text_stack = std::make_unique(); + std::unique_ptr padding = std::make_unique(); + std::unique_ptr header_text = std::make_unique