diff --git a/Extension/Music/local_music_shelf.cpp b/Extension/Music/local_music_shelf.cpp index 1ffd9563..e731c51e 100644 --- a/Extension/Music/local_music_shelf.cpp +++ b/Extension/Music/local_music_shelf.cpp @@ -1,4 +1,5 @@ #include "Extension/Music/local_music_shelf.h" +#include "Extension/Music/local_music_assets.h" #include "Engine/Core/Log/logging.h" #include "Engine/Game/Abi/native_data.h" #include "Engine/Game/Build/addresses.h" @@ -198,22 +199,39 @@ std::uint64_t music_model_construct_hook(std::uintptr_t manager, std::uint8_t mo try { if (!handle || !type || md::read(md::read(type)) != 0x48455d84U) return handle; - game::ModelWriteLock lock(manager); - md::Context context{local_runtime().base, manager}; - const auto items = context.field(md::Value{handle, type}, 0x67223da7U); - unsigned count{}, stride{}; - const auto bytes = context.array(items, 1024, count, stride); - if (count != 3 || stride != 16) return handle; - constexpr const char* records[] = { - "MusicPlaylistManager_Base_ContentResources/Featured_AnchoredContent_VM", - "MusicPlaylistManager_Base_ContentResources/Liked_AnchoredContent_VM", - "MusicPlaylistManager_Base_ContentResources/NewlyDiscovered_AnchoredContent_VM"}; - for (unsigned item = 0; item < 3; ++item) { - std::uintptr_t item_record{}; - std::memcpy(&item_record, bytes.data() + item * stride, sizeof(item_record)); - // Named record header +0x28 is also used by the native UI dumper. - if (!item_record || md::string(md::read(item_record + 0x28), 256) != records[item]) - return handle; + bool is_music_shelf = false; + { + game::ModelWriteLock lock(manager); + md::Context context{local_runtime().base, manager}; + const auto items = context.field(md::Value{handle, type}, 0x67223da7U); + unsigned count{}, stride{}; + const auto bytes = context.array(items, 1024, count, stride); + if (count != 3 || stride != 16) return handle; + constexpr const char* records[] = { + "MusicPlaylistManager_Base_ContentResources/Featured_AnchoredContent_VM", + "MusicPlaylistManager_Base_ContentResources/Liked_AnchoredContent_VM", + "MusicPlaylistManager_Base_ContentResources/NewlyDiscovered_AnchoredContent_VM"}; + is_music_shelf = true; + for (unsigned item = 0; item < 3; ++item) { + std::uintptr_t item_record{}; + std::memcpy(&item_record, bytes.data() + item * stride, sizeof(item_record)); + // Named record header +0x28 is also used by the native UI dumper. + if (!item_record || md::string(md::read(item_record + 0x28), 256) != records[item]) { + is_music_shelf = false; + break; + } + } + } + if (!is_music_shelf) return handle; + // Only add our shelf when an enabled mod actually declares a playlist with songs. Otherwise + // leave the native three shelves untouched instead of publishing an empty Mods shelf. + bool has_mod_playlists = false; + for (const auto& playlist : mod_music_playlists()) + if (!playlist.songs.empty()) { has_mod_playlists = true; break; } + if (!has_mod_playlists) { + mods_shelf = {}; + dingosdk::logging::event(dingosdk::logging::Channel::music, "{\"event\":\"music_mods_shelf_skipped\"}"); + return handle; } const auto report = music_shelf_append_one(local_runtime().base, manager, handle); dingosdk::logging::event(dingosdk::logging::Channel::music, diff --git a/contrib/native-music-ui.md b/contrib/native-music-ui.md index 8e3ea857..d5a6908c 100644 --- a/contrib/native-music-ui.md +++ b/contrib/native-music-ui.md @@ -198,6 +198,12 @@ The Mods implementation performs these steps under the native model write lock: **Mods, Featured, Liked, Newly Discovered**. 7. Populate the owned tile list from mod playlist rows in Featured. +The shelf is only built when an enabled mod declares a playlist with songs, read from +each mod's `reskate-music.json`. The insertion has to happen before the widgets bind, +so its rows cannot be awaited; the enabled mods' declarations are the signal that does +not depend on row hydration. With none, the native three shelves are left untouched +rather than publishing an empty Mods shelf. + The native template supplies layout and presentation defaults. Creating both models separately supplies independent state. Changing the label on the original Liked tile list would rename the original shelf too.