#include "stdafx.h" #include "cellMusic.h" #include "util/yaml.hpp" #include "Emu/VFS.h" #include // This is just a helper and not a real cell entity LOG_CHANNEL(cellMusicSelectionContext); bool music_selection_context::set(const CellMusicSelectionContext& in) { if (memcmp(in.data, magic, sizeof(magic)) != 0) { return false; } u32 pos = sizeof(magic); hash = &in.data[pos]; return load_playlist(); } CellMusicSelectionContext music_selection_context::get() const { if (hash.size() + sizeof(magic) > CELL_MUSIC_SELECTION_CONTEXT_SIZE) { fmt::throw_exception("Contents of music_selection_context are too large"); } CellMusicSelectionContext out{}; u32 pos = 0; std::memset(out.data, 0, CELL_MUSIC_SELECTION_CONTEXT_SIZE); std::memcpy(out.data, magic, sizeof(magic)); pos += sizeof(magic); std::memcpy(&out.data[pos], hash.c_str(), hash.size()); return out; } std::string music_selection_context::to_string() const { std::string str = fmt::format(".magic='%s', .content_type=%d, .repeat_mode=%d, .context_option=%d, .first_track=%d, .tracks=%d, .hash='%s', .playlist:", magic, static_cast(content_type), static_cast(repeat_mode), static_cast(context_option), first_track, playlist.size(), hash); for (usz i = 0; i < playlist.size(); i++) { fmt::append(str, "\n - Track %d: %s", i, ::at32(playlist, i)); } return str; } std::string music_selection_context::get_next_hash() { static u64 hash_counter = 0; return fmt::format("music_selection_context_%d", hash_counter++); } std::string music_selection_context::context_to_hex(const CellMusicSelectionContext& context) { std::string dahex; for (usz i = 0; i < CELL_MUSIC_SELECTION_CONTEXT_SIZE; i++) { fmt::append(dahex, " %.2x", context.data[i]); } return dahex; } std::string music_selection_context::get_yaml_path() const { std::string path = fs::get_cache_dir() + "cache/playlists/"; if (!fs::create_path(path)) { cellMusicSelectionContext.fatal("Failed to create path: %s (%s)", path, fs::g_tls_error); } return path + hash + ".yml"; } void music_selection_context::set_playlist(const std::string& path) { playlist.clear(); const std::string dir_path = "/dev_hdd0/music"; const std::string vfs_dir_path = vfs::get("/dev_hdd0/music"); if (fs::is_dir(path)) { content_type = CELL_SEARCH_CONTENTTYPE_MUSICLIST; for (auto&& dir_entry : fs::dir{path}) { if (dir_entry.name == "." || dir_entry.name == "..") { continue; } playlist.push_back(dir_path + std::string(path + "/" + dir_entry.name).substr(vfs_dir_path.length())); } } else { content_type = CELL_SEARCH_CONTENTTYPE_MUSIC; playlist.push_back(dir_path + path.substr(vfs_dir_path.length())); } } void music_selection_context::create_playlist(const std::string& new_hash) { hash = new_hash; const std::string yaml_path = get_yaml_path(); cellMusicSelectionContext.notice("Saving music playlist file %s", yaml_path); YAML::Emitter out; out << YAML::BeginMap; out << "Version" << target_version; out << "FileType" << target_file_type; out << "ContentType" << content_type; out << "ContextOption" << context_option; out << "RepeatMode" << repeat_mode; out << "FirstTrack" << first_track; out << "Tracks" << YAML::BeginSeq; for (const std::string& track : playlist) { out << track; } out << YAML::EndSeq; out << YAML::EndMap; fs::pending_file file(yaml_path); if (!file.file || (file.file.write(out.c_str(), out.size()), !file.commit())) { cellMusicSelectionContext.error("Failed to create music playlist file %s (error=%s)", yaml_path, fs::g_tls_error); } } bool music_selection_context::load_playlist() { playlist.clear(); const std::string path = get_yaml_path(); cellMusicSelectionContext.notice("Loading music playlist file %s", path); std::string content; { // Load patch file fs::file file{path}; if (!file) { cellMusicSelectionContext.error("Failed to load music playlist file %s: %s", path, fs::g_tls_error); return false; } content = file.to_string(); } auto [root, error] = yaml_load(content); if (!error.empty() || !root) { cellMusicSelectionContext.error("Failed to load music playlist file %s:\n%s", path, error); return false; } std::string err; const std::string version = get_yaml_node_value(root["Version"], err); if (!err.empty()) { cellMusicSelectionContext.error("No Version entry found. Error: '%s' (file: %s)", err, path); return false; } if (version != target_version) { cellMusicSelectionContext.error("Version '%s' does not match music playlist target '%s' (file: %s)", version, target_version, path); return false; } const std::string file_type = get_yaml_node_value(root["FileType"], err); if (!err.empty()) { cellMusicSelectionContext.error("No FileType entry found. Error: '%s' (file: %s)", err, path); return false; } if (file_type != target_file_type) { cellMusicSelectionContext.error("FileType '%s' does not match music playlist target '%s' (file: %s)", file_type, target_file_type, path); return false; } content_type = static_cast(get_yaml_node_value(root["ContentType"], err)); if (!err.empty()) { cellMusicSelectionContext.error("No ContentType entry found. Error: '%s' (file: %s)", err, path); return false; } context_option = static_cast(get_yaml_node_value(root["ContextOption"], err)); if (!err.empty()) { cellMusicSelectionContext.error("No ContextOption entry found. Error: '%s' (file: %s)", err, path); return false; } repeat_mode = static_cast(get_yaml_node_value(root["RepeatMode"], err)); if (!err.empty()) { cellMusicSelectionContext.error("No RepeatMode entry found. Error: '%s' (file: %s)", err, path); return false; } first_track = get_yaml_node_value(root["FirstTrack"], err); if (!err.empty()) { cellMusicSelectionContext.error("No FirstTrack entry found. Error: '%s' (file: %s)", err, path); return false; } const YAML::Node& track_node = root["Tracks"]; if (!track_node || track_node.Type() != YAML::NodeType::Sequence) { cellMusicSelectionContext.error("No Tracks entry found or Tracks is not a Sequence. (file: %s)", path); return false; } for (usz i = 0; i < track_node.size(); i++) { playlist.push_back(track_node[i].Scalar()); } valid = true; return true; } u32 music_selection_context::step_track(bool next) { if (playlist.empty()) { cellMusicSelectionContext.error("No tracks to play..."); current_track = umax; return umax; } switch (repeat_mode) { case CELL_SEARCH_REPEATMODE_NONE: { if (next) { // Try to play the next track. if (++current_track >= playlist.size()) { // We are at the end of the playlist. cellMusicSelectionContext.notice("No more tracks to play in playlist..."); current_track = umax; return umax; } } else { // Try to play the previous track. if (current_track == 0) { // We are at the start of the playlist. cellMusicSelectionContext.notice("No more tracks to play in playlist..."); current_track = umax; return umax; } current_track--; } break; } case CELL_SEARCH_REPEATMODE_REPEAT1: { // Keep decoding the same track. break; } case CELL_SEARCH_REPEATMODE_ALL: { if (next) { // Play the next track. Start with the first track if we reached the end of the playlist. current_track = (current_track + 1) % playlist.size(); } else { // Play the previous track. Start with the last track if we reached the start of the playlist. if (current_track == 0) { current_track = ::narrow(playlist.size() - 1); } else { current_track--; } } break; } case CELL_SEARCH_REPEATMODE_NOREPEAT1: { // We are done. We only wanted to decode a single track. cellMusicSelectionContext.notice("No more tracks to play..."); current_track = umax; return umax; } default: { fmt::throw_exception("Unknown repeat mode %d", static_cast(repeat_mode)); } } if (context_option == CELL_SEARCH_CONTEXTOPTION_SHUFFLE && repeat_mode == CELL_SEARCH_REPEATMODE_ALL && playlist.size() > 1) { if (next ? current_track == 0 : current_track == (playlist.size() - 1)) { // We reached the first or last track again. Let's shuffle! cellMusicSelectionContext.notice("Shuffling playlist..."); auto engine = std::default_random_engine{}; std::shuffle(std::begin(playlist), std::end(playlist), engine); } } return current_track; }