From 88e033b33f12cf91fc6812befc320fd6fe338f99 Mon Sep 17 00:00:00 2001 From: ReSkate local Date: Mon, 5 Oct 2026 06:51:05 +0100 Subject: [PATCH] Music: skip the Mods shelf when no mod declares a playlist Build the Mods shelf only when an enabled mod declares at least one playlist with songs, read from each mod's reskate-music.json. The shelf must be inserted before the native widgets bind, so it cannot wait for Featured rows to hydrate, and it should not publish an empty shelf when there is nothing to show. With no declared mod playlist the native three shelves are left as they are and a music_mods_shelf_skipped event is logged. --- Extension/Music/local_music_shelf.cpp | 50 ++++++++++++++++++--------- contrib/native-music-ui.md | 6 ++++ 2 files changed, 40 insertions(+), 16 deletions(-) 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.