cellMusic/Decode: add SelectContents functions

This commit is contained in:
Megamouse 2021-06-19 18:33:43 +02:00
parent 00f6763228
commit cd97d74f0f
11 changed files with 610 additions and 81 deletions

View File

@ -419,6 +419,7 @@ target_sources(rpcs3_emu PRIVATE
RSX/Overlays/overlay_edit_text.cpp RSX/Overlays/overlay_edit_text.cpp
RSX/Overlays/overlay_fonts.cpp RSX/Overlays/overlay_fonts.cpp
RSX/Overlays/overlay_list_view.cpp RSX/Overlays/overlay_list_view.cpp
RSX/Overlays/overlay_media_list_dialog.cpp
RSX/Overlays/overlay_message_dialog.cpp RSX/Overlays/overlay_message_dialog.cpp
RSX/Overlays/overlay_osk.cpp RSX/Overlays/overlay_osk.cpp
RSX/Overlays/overlay_osk_panel.cpp RSX/Overlays/overlay_osk_panel.cpp

View File

@ -6,10 +6,10 @@
#include "Emu/Io/music_handler_base.h" #include "Emu/Io/music_handler_base.h"
#include "Emu/System.h" #include "Emu/System.h"
#include "Emu/VFS.h" #include "Emu/VFS.h"
#include "Emu/RSX/Overlays/overlay_media_list_dialog.h"
#include "cellSearch.h" #include "cellSearch.h"
#include "cellSpurs.h" #include "cellSpurs.h"
#include "cellSysutil.h" #include "cellSysutil.h"
#include "cellMusic.h" #include "cellMusic.h"
LOG_CHANNEL(cellMusic); LOG_CHANNEL(cellMusic);
@ -80,6 +80,45 @@ struct music_state
} }
}; };
error_code cell_music_select_contents()
{
auto& music = g_fxo->get<music_state>();
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<CellMusicSelectionContext> context) error_code cellMusicGetSelectionContext(vm::ptr<CellMusicSelectionContext> context)
{ {
cellMusic.todo("cellMusicGetSelectionContext(context=*0x%x)", context); cellMusic.todo("cellMusicGetSelectionContext(context=*0x%x)", context);
@ -392,7 +431,8 @@ error_code cellMusicSetPlaybackCommand2(s32 command, vm::ptr<void> param)
std::string path; std::string path;
{ {
std::lock_guard lock(music.mtx); 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) switch (command)
@ -447,7 +487,8 @@ error_code cellMusicSetPlaybackCommand(s32 command, vm::ptr<void> param)
std::string path; std::string path;
{ {
std::lock_guard lock(music.mtx); 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) switch (command)
@ -488,36 +529,14 @@ error_code cellMusicSelectContents2()
{ {
cellMusic.todo("cellMusicSelectContents2()"); cellMusic.todo("cellMusicSelectContents2()");
auto& music = g_fxo->get<music_state>(); return cell_music_select_contents();
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;
} }
error_code cellMusicSelectContents(u32 container) error_code cellMusicSelectContents(u32 container)
{ {
cellMusic.todo("cellMusicSelectContents(container=0x%x)", container); cellMusic.todo("cellMusicSelectContents(container=0x%x)", container);
auto& music = g_fxo->get<music_state>(); return cell_music_select_contents();
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;
} }
error_code cellMusicInitialize2(s32 mode, s32 spuPriority, vm::ptr<CellMusic2Callback> func, vm::ptr<void> userData) error_code cellMusicInitialize2(s32 mode, s32 spuPriority, vm::ptr<CellMusic2Callback> func, vm::ptr<void> userData)

View File

@ -145,7 +145,7 @@ struct music_selection_context
u32 content_type{0}; u32 content_type{0};
u32 repeat_mode{0}; u32 repeat_mode{0};
u32 context_option{0}; u32 context_option{0};
std::string path; std::string content_path;
static constexpr u32 max_depth = 2; // root + 1 folder + file static constexpr u32 max_depth = 2; // root + 1 folder + file
@ -161,14 +161,14 @@ struct music_selection_context
pos += sizeof(content_type); pos += sizeof(content_type);
repeat_mode = in.data[pos++]; repeat_mode = in.data[pos++];
context_option = in.data[pos++]; context_option = in.data[pos++];
path = &in.data[pos]; content_path = &in.data[pos];
return true; return true;
} }
CellMusicSelectionContext get() const 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"); fmt::throw_exception("Contents of music_selection_context are too large");
} }
@ -183,14 +183,14 @@ struct music_selection_context
pos += sizeof(content_type); pos += sizeof(content_type);
out.data[pos++] = repeat_mode; out.data[pos++] = repeat_mode;
out.data[pos++] = context_option; 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; return out;
} }
std::string to_string() const 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 // Helper

View File

@ -3,6 +3,7 @@
#include "Emu/Cell/lv2/sys_lwmutex.h" #include "Emu/Cell/lv2/sys_lwmutex.h"
#include "Emu/Cell/lv2/sys_lwcond.h" #include "Emu/Cell/lv2/sys_lwcond.h"
#include "Emu/Cell/lv2/sys_spu.h" #include "Emu/Cell/lv2/sys_spu.h"
#include "Emu/RSX/Overlays/overlay_media_list_dialog.h"
#include "Emu/VFS.h" #include "Emu/VFS.h"
#include "cellMusic.h" #include "cellMusic.h"
#include "cellSearch.h" #include "cellSearch.h"
@ -138,7 +139,10 @@ struct music_decode
readPos = 0; readPos = 0;
// Decode data. The format of the decoded data is 48kHz, float 32bit, 2ch LPCM data interleaved in order from left to right. // 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.set_swap_endianness(true);
decoder.decode(); decoder.decode();
break; break;
@ -167,6 +171,46 @@ struct music_decode2 : music_decode
s32 speed = CELL_MUSIC_DECODE2_SPEED_MAX; s32 speed = CELL_MUSIC_DECODE2_SPEED_MAX;
}; };
template <typename Music_Decode>
error_code cell_music_decode_select_contents()
{
auto& dec = g_fxo->get<Music_Decode>();
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 <typename Music_Decode> template <typename Music_Decode>
error_code cell_music_decode_read(vm::ptr<void> buf, vm::ptr<u32> startTime, u64 reqSize, vm::ptr<u64> readSize, vm::ptr<s32> position) error_code cell_music_decode_read(vm::ptr<void> buf, vm::ptr<u32> startTime, u64 reqSize, vm::ptr<u64> readSize, vm::ptr<s32> position)
{ {
@ -285,19 +329,7 @@ error_code cellMusicDecodeSelectContents()
{ {
cellMusicDecode.todo("cellMusicDecodeSelectContents()"); cellMusicDecode.todo("cellMusicDecodeSelectContents()");
auto& dec = g_fxo->get<music_decode>(); return cell_music_decode_select_contents<music_decode>();
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;
} }
error_code cellMusicDecodeSetDecodeCommand(s32 command) error_code cellMusicDecodeSetDecodeCommand(s32 command)
@ -464,19 +496,7 @@ error_code cellMusicDecodeSelectContents2()
{ {
cellMusicDecode.todo("cellMusicDecodeSelectContents2()"); cellMusicDecode.todo("cellMusicDecodeSelectContents2()");
auto& dec = g_fxo->get<music_decode2>(); return cell_music_decode_select_contents<music_decode2>();
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;
} }
error_code cellMusicDecodeSetDecodeCommand2(s32 command) error_code cellMusicDecodeSetDecodeCommand2(s32 command)

View File

@ -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); cellSearch.notice("cellSearchStartContentSearch(): CellSearchId: 0x%x, Content ID: %08X, Path: \"%s\"", id, hash, item_path);
} }
else // file is already stored and tracked else // file is already stored and tracked
{ // TODO {
// TODO
// Perform checks to see if the identified file has been modified since last checked // Perform checks to see if the identified file has been modified since last checked
// In which case, update the stored content's properties // In which case, update the stored content's properties
// auto content_found = &content_map->at(content_id); // auto content_found = &content_map->at(content_id);
@ -1486,8 +1487,8 @@ error_code cellSearchGetMusicSelectionContext(CellSearchId searchId, vm::cptr<Ce
} }
// Use the found content // Use the found content
context.path = content->second->infoPath.contentPath; 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.path); 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) else if (first_content->type == CELL_SEARCH_CONTENTTYPE_MUSICLIST)
{ {
@ -1497,8 +1498,8 @@ error_code cellSearchGetMusicSelectionContext(CellSearchId searchId, vm::cptr<Ce
else else
{ {
// Select the first track by default // Select the first track by default
context.path = first_content->infoPath.contentPath; 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.path); 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) else if (first_content->type == CELL_SEARCH_CONTENTTYPE_MUSICLIST)
@ -1509,8 +1510,8 @@ error_code cellSearchGetMusicSelectionContext(CellSearchId searchId, vm::cptr<Ce
else else
{ {
// Select the first track by default // Select the first track by default
context.path = first_content->infoPath.contentPath; context.content_path = first_content->infoPath.contentPath;
cellSearch.notice("cellSearchGetMusicSelectionContext(): Assigning first track: Type=0x%x, Path=%s", +first_content->type, context.path); cellSearch.notice("cellSearchGetMusicSelectionContext(): Assigning first track: Type=0x%x, Path=%s", +first_content->type, context.content_path);
} }
context.content_type = first_content->type; context.content_type = first_content->type;
@ -1518,9 +1519,9 @@ error_code cellSearchGetMusicSelectionContext(CellSearchId searchId, vm::cptr<Ce
context.context_option = option; context.context_option = option;
// Resolve hashed paths // Resolve hashed paths
if (auto found = search.content_links.find(context.path); found != search.content_links.end()) if (auto found = search.content_links.find(context.content_path); found != search.content_links.end())
{ {
context.path = found->second; context.content_path = found->second;
} }
*outContext = context.get(); *outContext = context.get();
@ -1539,8 +1540,10 @@ error_code cellSearchGetMusicSelectionContextOfSingleTrack(vm::cptr<CellSearchCo
return CELL_SEARCH_ERROR_PARAM; return CELL_SEARCH_ERROR_PARAM;
} }
auto& search = g_fxo->get<search_info>();
// TODO: find out if this check is correct // TODO: find out if this check is correct
if (error_code error = check_search_state(g_fxo->get<search_info>().state.load(), search_state::in_progress)) if (error_code error = check_search_state(search.state.load(), search_state::in_progress))
{ {
return error; return error;
} }
@ -1560,7 +1563,13 @@ error_code cellSearchGetMusicSelectionContextOfSingleTrack(vm::cptr<CellSearchCo
} }
music_selection_context context; music_selection_context context;
context.path = content_info->infoPath.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(); *outContext = context.get();
@ -1586,7 +1595,7 @@ error_code cellSearchGetContentInfoPath(vm::cptr<CellSearchContentId> contentId,
const u64 id = *reinterpret_cast<const u64*>(contentId->data); const u64 id = *reinterpret_cast<const u64*>(contentId->data);
auto& content_map = g_fxo->get<content_id_map>(); auto& content_map = g_fxo->get<content_id_map>();
auto found = content_map.map.find(id); 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)); 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<CellSearchContentId>
// Search for the content that matches our current selection // Search for the content that matches our current selection
auto& content_map = g_fxo->get<content_id_map>(); auto& content_map = g_fxo->get<content_id_map>();
const u64 hash = std::hash<std::string>()(this->path); const u64 hash = std::hash<std::string>()(content_path);
auto found = content_map.map.find(hash); auto found = content_map.map.find(hash);
if (found != content_map.map.end()) if (found != content_map.map.end())
{ {
@ -1822,8 +1831,11 @@ error_code music_selection_context::find_content_id(vm::ptr<CellSearchContentId>
} }
// Try to find the content manually // Try to find the content manually
const std::string vpath = vfs::get("/dev_hdd0/music/"); auto& search = g_fxo->get<search_info>();
for (auto&& entry : fs::dir(vpath)) 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); entry.name = vfs::unescape(entry.name);
@ -1832,7 +1844,8 @@ error_code music_selection_context::find_content_id(vm::ptr<CellSearchContentId>
continue; 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) if (content_type == CELL_SEARCH_CONTENTTYPE_MUSICLIST)
{ {
@ -1841,7 +1854,7 @@ error_code music_selection_context::find_content_id(vm::ptr<CellSearchContentId>
if (hash == dir_hash) if (hash == dir_hash)
{ {
u32 num_of_items = 0; 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); file.name = vfs::unescape(file.name);
@ -1856,16 +1869,32 @@ error_code music_selection_context::find_content_id(vm::ptr<CellSearchContentId>
// TODO: check for actual content inside the directory // TODO: check for actual content inside the directory
std::shared_ptr<search_content_t> curr_find = std::make_shared<search_content_t>(); std::shared_ptr<search_content_t> curr_find = std::make_shared<search_content_t>();
curr_find->type = CELL_SEARCH_CONTENTTYPE_MUSICLIST; 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; CellSearchMusicListInfo& info = curr_find->data.music_list;
info.listType = CELL_SEARCH_LISTSEARCHTYPE_MUSIC_ALBUM; info.listType = CELL_SEARCH_LISTSEARCHTYPE_MUSIC_ALBUM;
info.numOfItems = num_of_items; info.numOfItems = num_of_items;
info.duration = 0; info.duration = 0;
strcpy_trunc(info.title, entry.name); strcpy_trunc(info.title, entry.name);
strcpy_trunc(info.artistName, "ARTIST NAME"); strcpy_trunc(info.artistName, "ARTIST NAME");
content_map.map.emplace(dir_hash, curr_find); content_map.map.emplace(dir_hash, curr_find);
const u128 content_id_128 = dir_hash; const u128 content_id_128 = dir_hash;
std::memcpy(contents_id->data, &content_id_128, CELL_SEARCH_CONTENT_ID_SIZE); 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); cellSearch.warning("find_content_id: found music list %s (path control: '%s')", to_string(), dir_path);
return CELL_OK; return CELL_OK;
} }
@ -1874,7 +1903,7 @@ error_code music_selection_context::find_content_id(vm::ptr<CellSearchContentId>
} }
// Search the subfolders. We assume all music is located in a depth of 2 (max_depth, root + 1 folder + file). // 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 == "..") if (item.is_directory || item.name == "." || item.name == "..")
{ {
@ -1886,19 +1915,36 @@ error_code music_selection_context::find_content_id(vm::ptr<CellSearchContentId>
if (hash == file_hash) 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) if (!success)
{ {
continue; continue;
} }
std::shared_ptr<search_content_t> curr_find = std::make_shared<search_content_t>(); std::shared_ptr<search_content_t> curr_find = std::make_shared<search_content_t>();
curr_find->type = CELL_SEARCH_CONTENTTYPE_MUSIC; 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); populate_music_info(curr_find->data.music, mi, item);
content_map.map.emplace(file_hash, curr_find); content_map.map.emplace(file_hash, curr_find);
const u128 content_id_128 = file_hash; const u128 content_id_128 = file_hash;
std::memcpy(contents_id->data, &content_id_128, CELL_SEARCH_CONTENT_ID_SIZE); 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); cellSearch.warning("find_content_id: found music track %s (path control: '%s')", to_string(), file_path);
return CELL_OK; return CELL_OK;
} }

View File

@ -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<overlay_element> image = std::make_unique<image_view>();
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_view*>(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_view*>(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<image_info>(entry.info.path.c_str());
static_cast<image_view*>(image.get())->set_raw_image(icon_data.get());
}
else
{
// Fallback
// TODO: use proper icon
static_cast<image_view*>(image.get())->set_image_resource(resource_config::standard_image_resource::new_entry);
}
break;
}
case media_list_dialog::media_type::directory:
{
static_cast<image_view*>(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<overlay_element> text_stack = std::make_unique<vertical_layout>();
std::unique_ptr<overlay_element> padding = std::make_unique<spacer>();
std::unique_ptr<overlay_element> header_text = std::make_unique<label>(title);
std::unique_ptr<overlay_element> subtext = std::make_unique<label>(artist);
padding->set_size(1, 1);
header_text->set_size(800, 40);
header_text->set_font("Arial", 16);
header_text->set_wrap_text(true);
subtext->set_size(800, 0);
subtext->set_font("Arial", 14);
subtext->set_wrap_text(true);
static_cast<label*>(subtext.get())->auto_resize(true);
// Make back color transparent for text
header_text->back_color.a = 0.f;
subtext->back_color.a = 0.f;
static_cast<vertical_layout*>(text_stack.get())->pack_padding = 5;
static_cast<vertical_layout*>(text_stack.get())->add_element(padding);
static_cast<vertical_layout*>(text_stack.get())->add_element(header_text);
static_cast<vertical_layout*>(text_stack.get())->add_element(subtext);
if (text_stack->h > image->h)
{
std::unique_ptr<overlay_element> padding2 = std::make_unique<spacer>();
padding2->set_size(1, 5);
static_cast<vertical_layout*>(text_stack.get())->add_element(padding2);
}
// Pack
pack_padding = 15;
add_element(image);
add_element(text_stack);
}
media_list_dialog::media_list_dialog()
{
m_dim_background = std::make_unique<overlay_element>();
m_dim_background->set_size(1280, 720);
m_dim_background->back_color.a = 0.5f;
m_list = std::make_unique<list_view>(1240, 540);
m_list->set_pos(20, 85);
m_description = std::make_unique<label>();
m_description->set_font("Arial", 20);
m_description->set_pos(20, 37);
m_description->set_text("Select media"); // Fallback. I don't think this will ever be used, so I won't localize it.
m_description->auto_resize();
m_description->back_color.a = 0.f;
return_code = selection_code::canceled;
}
void media_list_dialog::on_button_pressed(pad_button button_press)
{
switch (button_press)
{
case pad_button::cross:
if (m_no_media_text)
break;
return_code = m_list->get_selected_index();
[[fallthrough]];
case pad_button::circle:
close(false, true);
break;
case pad_button::dpad_up:
m_list->select_previous();
break;
case pad_button::dpad_down:
m_list->select_next();
break;
case pad_button::L1:
m_list->select_previous(10);
break;
case pad_button::R1:
m_list->select_next(10);
break;
default:
rsx_log.trace("[ui] Button %d pressed", static_cast<u8>(button_press));
break;
}
}
compiled_resource media_list_dialog::get_compiled()
{
if (!visible)
{
return {};
}
compiled_resource result;
result.add(m_dim_background->get_compiled());
result.add(m_list->get_compiled());
result.add(m_description->get_compiled());
if (m_no_media_text)
result.add(m_no_media_text->get_compiled());
return result;
}
s32 media_list_dialog::show(const media_entry& dir_entry, const std::string& title, u32 /*focused*/, bool enable_overlay)
{
rsx_log.warning("Media dialog: showing entry '%s' ('%s')", dir_entry.name, dir_entry.path);
visible = false;
if (enable_overlay)
{
m_dim_background->back_color.a = 0.9f;
}
else
{
m_dim_background->back_color.a = 0.5f;
}
for (const media_entry& child : dir_entry.children)
{
std::unique_ptr<overlay_element> entry = std::make_unique<media_list_entry>(child);
m_list->add_entry(entry);
}
if (m_list->m_items.empty())
{
m_no_media_text = std::make_unique<label>(get_localized_string(localized_string_id::RSX_OVERLAYS_MEDIA_DIALOG_EMPTY));
m_no_media_text->set_font("Arial", 20);
m_no_media_text->align_text(overlay_element::text_align::center);
m_no_media_text->set_pos(m_list->x, m_list->y + m_list->h / 2);
m_no_media_text->set_size(m_list->w, 30);
m_no_media_text->back_color.a = 0;
m_list->set_cancel_only(true);
}
else
{
// Only select an entry if there are entries available
m_list->select_entry(0);
}
m_description->set_text(title);
m_description->auto_resize();
visible = true;
auto ref = g_fxo->get<display_manager>().get(uid);
if (const auto error = run_input_loop())
{
rsx_log.error("Media dialog input loop exited with error code=%d", error);
return error;
}
if (return_code < 0)
{
rsx_log.warning("Media dialog canceled");
}
else
{
ensure(static_cast<size_t>(return_code) < dir_entry.children.size());
rsx_log.success("Media dialog: selected entry: %d ('%s')", return_code, dir_entry.children[return_code].path);
}
return return_code;
}
struct media_list_dialog_thread
{
static constexpr auto thread_name = "MediaList Thread"sv;
};
void parse_media_recursive(u32 depth, const std::string& media_path, const std::string& name, media_list_dialog::media_type type, media_list_dialog::media_entry& current_entry)
{
if (depth++ > music_selection_context::max_depth)
{
return;
}
if (fs::is_dir(media_path))
{
for (auto&& dir_entry : fs::dir{media_path})
{
dir_entry.name = vfs::unescape(dir_entry.name);
if (dir_entry.name == "." || dir_entry.name == "..")
{
continue;
}
media_list_dialog::media_entry new_entry;
parse_media_recursive(depth, media_path + "/" + dir_entry.name, dir_entry.name, type, new_entry);
if (new_entry.type != media_list_dialog::media_type::invalid)
{
new_entry.parent = &current_entry;
current_entry.children.emplace_back(std::move(new_entry));
}
}
// Only keep directories that contain valid entries
if (current_entry.children.empty())
{
rsx_log.notice("parse_media_recursive: No matches in directory '%s'", media_path);
}
else
{
rsx_log.notice("parse_media_recursive: Found %d matches in directory '%s'", current_entry.children.size(), media_path);
current_entry.type = media_list_dialog::media_type::directory;
}
}
else if (type == media_list_dialog::media_type::photo)
{
// Let's restrict this to png and jpg for now
if (name.ends_with(".png") || name.ends_with(".jpg"))
{
current_entry.type = type;
rsx_log.notice("parse_media_recursive: Found photo '%s'", media_path);
}
}
else
{
// Try to peek into the audio or video file
const s32 av_media_type = type == media_list_dialog::media_type::video ? 0 /*AVMEDIA_TYPE_VIDEO*/ : 1 /*AVMEDIA_TYPE_AUDIO*/;
auto [success, info] = utils::get_media_info(media_path, av_media_type);
if (success)
{
current_entry.type = type;
current_entry.info = std::move(info);
rsx_log.notice("parse_media_recursive: Found media '%s'", media_path);
}
}
if (current_entry.type != media_list_dialog::media_type::invalid)
{
current_entry.path = media_path;
current_entry.name = name;
}
};
error_code show_media_list_dialog(media_list_dialog::media_type type, const std::string& path, const std::string& title, std::function<void(s32 status, utils::media_info info)> on_finished)
{
rsx_log.todo("show_media_list_dialog(type=%d, path='%s', title='%s', on_finished=%d)", static_cast<s32>(type), path, title, !!on_finished);
if (!on_finished)
{
return CELL_CANCEL;
}
g_fxo->get<named_thread<media_list_dialog_thread>>()([=]()
{
media_list_dialog::media_entry root_media_entry;
root_media_entry.type = media_list_dialog::media_type::directory;
if (fs::is_dir(path))
{
parse_media_recursive(0, path, title, type, root_media_entry);
}
else
{
rsx_log.error("Media list: Failed to open path: '%s'", path);
}
media_list_dialog::media_entry* media = &root_media_entry;
s32 result = 0;
while (thread_ctrl::state() != thread_state::aborting && result >= 0 && media && media->type == media_list_dialog::media_type::directory)
{
if (auto manager = g_fxo->try_get<rsx::overlays::display_manager>())
{
rsx_log.notice("Creating media list dialog with entry: '%s' (name='%s', path='%s')", title, media->name, media->path);
result = manager->create<rsx::overlays::media_list_dialog>()->show(*media, title, 0, true);
if (result >= 0)
{
ensure(static_cast<size_t>(result) < media->children.size());
media = &media->children[result];
}
rsx_log.notice("Left media list dialog with entry: '%s' ('%s')", media->name, media->path);
}
else
{
media = nullptr;
result = user_interface::selection_code::canceled;
rsx_log.error("Media selection is only possible when the native user interface is enabled in the settings. The action will be canceled.");
}
}
if (result >= 0 && media && media->type == type)
{
on_finished(CELL_OK, media->info);
}
else
{
on_finished(result, {});
}
});
return CELL_OK;
}
} // namespace overlays
} // namespace RSX

View File

@ -0,0 +1,61 @@
#pragma once
#include "overlays.h"
#include "overlay_list_view.hpp"
#include "Emu/Cell/ErrorCodes.h"
#include "util/media_utils.h"
namespace rsx
{
namespace overlays
{
struct media_list_dialog : public user_interface
{
public:
enum class media_type
{
invalid, // For internal use only
directory, // For internal use only
audio,
video,
photo,
};
struct media_entry
{
media_type type = media_type::invalid;
std::string name;
std::string path;
utils::media_info info;
media_entry* parent = nullptr;
std::vector<media_entry> children;
};
media_list_dialog();
void on_button_pressed(pad_button button_press) override;
compiled_resource get_compiled() override;
s32 show(const media_entry& dir_entry, const std::string& title, u32 focused, bool enable_overlay);
private:
struct media_list_entry : horizontal_layout
{
public:
media_list_entry(const media_entry& entry);
private:
std::unique_ptr<image_info> icon_data;
};
std::unique_ptr<overlay_element> m_dim_background;
std::unique_ptr<list_view> m_list;
std::unique_ptr<label> m_description;
std::unique_ptr<label> m_no_media_text;
};
error_code show_media_list_dialog(media_list_dialog::media_type type, const std::string& path, const std::string& title, std::function<void(s32 status, utils::media_info info)> on_finished);
}
}

View File

@ -24,6 +24,8 @@ enum class localized_string_id
RSX_OVERLAYS_OSK_DIALOG_SHIFT, RSX_OVERLAYS_OSK_DIALOG_SHIFT,
RSX_OVERLAYS_OSK_DIALOG_ENTER_TEXT, RSX_OVERLAYS_OSK_DIALOG_ENTER_TEXT,
RSX_OVERLAYS_OSK_DIALOG_ENTER_PASSWORD, RSX_OVERLAYS_OSK_DIALOG_ENTER_PASSWORD,
RSX_OVERLAYS_MEDIA_DIALOG_TITLE,
RSX_OVERLAYS_MEDIA_DIALOG_EMPTY,
RSX_OVERLAYS_LIST_SELECT, RSX_OVERLAYS_LIST_SELECT,
RSX_OVERLAYS_LIST_CANCEL, RSX_OVERLAYS_LIST_CANCEL,

View File

@ -74,6 +74,7 @@
<ClCompile Include="Emu\NP\rpcn_config.cpp" /> <ClCompile Include="Emu\NP\rpcn_config.cpp" />
<ClCompile Include="Emu\RSX\Common\texture_cache.cpp" /> <ClCompile Include="Emu\RSX\Common\texture_cache.cpp" />
<ClCompile Include="Emu\RSX\Overlays\overlay_controls.cpp" /> <ClCompile Include="Emu\RSX\Overlays\overlay_controls.cpp" />
<ClCompile Include="Emu\RSX\Overlays\overlay_media_list_dialog.cpp" />
<ClCompile Include="Emu\RSX\Overlays\overlay_osk_panel.cpp" /> <ClCompile Include="Emu\RSX\Overlays\overlay_osk_panel.cpp" />
<ClCompile Include="Emu\RSX\Overlays\overlay_user_list_dialog.cpp" /> <ClCompile Include="Emu\RSX\Overlays\overlay_user_list_dialog.cpp" />
<ClCompile Include="Emu\RSX\Overlays\overlay_utils.cpp" /> <ClCompile Include="Emu\RSX\Overlays\overlay_utils.cpp" />
@ -480,6 +481,7 @@
<ClInclude Include="Emu\RSX\Common\simple_array.hpp" /> <ClInclude Include="Emu\RSX\Common\simple_array.hpp" />
<ClInclude Include="Emu\RSX\Overlays\overlay_edit_text.hpp" /> <ClInclude Include="Emu\RSX\Overlays\overlay_edit_text.hpp" />
<ClInclude Include="Emu\RSX\Overlays\overlay_list_view.hpp" /> <ClInclude Include="Emu\RSX\Overlays\overlay_list_view.hpp" />
<ClInclude Include="Emu\RSX\Overlays\overlay_media_list_dialog.h" />
<ClInclude Include="Emu\RSX\Overlays\overlay_progress_bar.hpp" /> <ClInclude Include="Emu\RSX\Overlays\overlay_progress_bar.hpp" />
<ClInclude Include="Emu\RSX\Program\GLSLTypes.h" /> <ClInclude Include="Emu\RSX\Program\GLSLTypes.h" />
<ClInclude Include="Emu\RSX\Program\ProgramStateCache.h" /> <ClInclude Include="Emu\RSX\Program\ProgramStateCache.h" />

View File

@ -1027,6 +1027,9 @@
<ClCompile Include="Emu\Audio\audio_resampler.cpp"> <ClCompile Include="Emu\Audio\audio_resampler.cpp">
<Filter>Emu\Audio</Filter> <Filter>Emu\Audio</Filter>
</ClCompile> </ClCompile>
<ClCompile Include="Emu\RSX\Overlays\overlay_media_list_dialog.cpp">
<Filter>Emu\GPU\RSX\Overlays</Filter>
</ClCompile>
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<ClInclude Include="Crypto\aes.h"> <ClInclude Include="Crypto\aes.h">
@ -2047,6 +2050,9 @@
<ClInclude Include="Emu\Io\Null\null_music_handler.h"> <ClInclude Include="Emu\Io\Null\null_music_handler.h">
<Filter>Emu\Io\Null</Filter> <Filter>Emu\Io\Null</Filter>
</ClInclude> </ClInclude>
<ClInclude Include="Emu\RSX\Overlays\overlay_media_list_dialog.h">
<Filter>Emu\GPU\RSX\Overlays</Filter>
</ClInclude>
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<None Include="Emu\RSX\Common\Interpreter\FragmentInterpreter.glsl"> <None Include="Emu\RSX\Common\Interpreter\FragmentInterpreter.glsl">

View File

@ -54,6 +54,8 @@ private:
case localized_string_id::RSX_OVERLAYS_OSK_DIALOG_SHIFT: return tr("Shift", "OSK Dialog"); case localized_string_id::RSX_OVERLAYS_OSK_DIALOG_SHIFT: return tr("Shift", "OSK Dialog");
case localized_string_id::RSX_OVERLAYS_OSK_DIALOG_ENTER_TEXT: return tr("[Enter Text]", "OSK Dialog"); case localized_string_id::RSX_OVERLAYS_OSK_DIALOG_ENTER_TEXT: return tr("[Enter Text]", "OSK Dialog");
case localized_string_id::RSX_OVERLAYS_OSK_DIALOG_ENTER_PASSWORD: return tr("[Enter Password]", "OSK Dialog"); case localized_string_id::RSX_OVERLAYS_OSK_DIALOG_ENTER_PASSWORD: return tr("[Enter Password]", "OSK Dialog");
case localized_string_id::RSX_OVERLAYS_MEDIA_DIALOG_TITLE: return tr("Select media", "Media dialog");
case localized_string_id::RSX_OVERLAYS_MEDIA_DIALOG_EMPTY: return tr("No media found.", "Media dialog");
case localized_string_id::RSX_OVERLAYS_LIST_SELECT: return tr("Enter", "Enter Dialog List"); case localized_string_id::RSX_OVERLAYS_LIST_SELECT: return tr("Enter", "Enter Dialog List");
case localized_string_id::RSX_OVERLAYS_LIST_CANCEL: return tr("Back", "Cancel Dialog List"); case localized_string_id::RSX_OVERLAYS_LIST_CANCEL: return tr("Back", "Cancel Dialog List");
case localized_string_id::CELL_GAME_ERROR_BROKEN_GAMEDATA: return tr("ERROR: Game data is corrupted. The application will continue.", "Game Error"); case localized_string_id::CELL_GAME_ERROR_BROKEN_GAMEDATA: return tr("ERROR: Game data is corrupted. The application will continue.", "Game Error");