diff --git a/Engine/Game/Build/20260929/local_music.h b/Engine/Game/Build/20260929/local_music.h index c517d72..9b0dad3 100644 --- a/Engine/Game/Build/20260929/local_music.h +++ b/Engine/Game/Build/20260929/local_music.h @@ -56,8 +56,6 @@ inline constexpr Fingerprint playback_select_next_contract{0x14f8ed0, {0x48,0x89 inline constexpr Fingerprint playback_mode_setter_contract{0x443aa0, {0x44,0x8b,0x91,0xa8,0x00,0x00,0x00,0x4c,0x8b,0x99,0xa0,0x00,0x00,0x00,0x44,0x8b,0xca,0x8b,0xc2,0x33,0xd2,0x49,0xf7,0xf2,0x8b,0xc2,0x49,0x8b,0x04,0xc3,0x48,0x85}}; // Native channel-to-playlist binding entrypoint, used by the generated UI/script thunk. inline constexpr Fingerprint playback_channel_playlist_bind_contract{0x8ca6a0, {0x48,0x89,0x5c,0x24,0x08,0x57,0x48,0x83,0xec,0x20,0x48,0x8b,0xda,0x48,0x8b,0xf9,0xe8,0x2b,0xad,0xb6,0xff,0x48,0x85,0xc0,0x74,0x0e,0x4c,0x8b,0xc3,0x48,0x8b,0xd7}}; -// Candidate-admission callback called by the shared selector; arg4 is its actual channel-map key. -inline constexpr Fingerprint playback_candidate_admission_contract{0x43c460, {0x48,0x89,0x5c,0x24,0x08,0x48,0x89,0x6c,0x24,0x10,0x56,0x57,0x41,0x54,0x41,0x56,0x41,0x57,0x48,0x83,0xec,0x30,0x44,0x8b,0x81,0xd0,0x00,0x00,0x00,0x4c,0x8b,0xfa}}; inline constexpr std::array music_ui_contracts{{ music_ui_initialize_contract, {construct_playlist, {0x48,0x89,0x5c,0x24,0x08,0x57,0x48,0x83,0xec,0x20,0x48,0x8b,0xda,0x48,0x8b,0xf9,0x48,0x8d,0x15,0x21,0x95,0x32,0x04,0xe8,0x94,0x30,0x5e,0x00,0x33,0xc9,0x48,0x8d}}, diff --git a/Extension/Music/local_music_playback.cpp b/Extension/Music/local_music_playback.cpp index d8da033..dbf2b31 100644 --- a/Extension/Music/local_music_playback.cpp +++ b/Extension/Music/local_music_playback.cpp @@ -20,7 +20,6 @@ using namespace dingosdk::game::build::v20260929; using local_music::playback_mode_setter_contract; using local_music::playback_select_next_contract; using local_music::playback_channel_playlist_bind_contract; -using local_music::playback_candidate_admission_contract; using music_playback::CursorPlan; std::atomic image_base{}; @@ -29,8 +28,10 @@ std::atomic playback_available{}; MusicSelectNext select_next{}; MusicChannelPlaylistBind bind_channel_playlist{}; std::array, 256> last_logged_channel_bindings{}; -MusicCandidateAdmission candidate_admission{}; -std::array, 256> logged_candidate_admission_results{}; +// The channels the music player's own queues select for (apply_policy learns them: the game +// supplies the numbers, they are not written down here). Only those are ever given Favorites +// for a playlist ID of zero: on any other channel zero is left to mean what the game means. +std::array, 256> music_channels{}; using SetPlaybackMode = void (__fastcall *)(std::uintptr_t manager, std::uint32_t playlist_id, std::uint32_t mode); SetPlaybackMode set_playback_mode{}; @@ -248,6 +249,7 @@ void apply_policy(std::uintptr_t queue, std::uintptr_t playlist_asset) noexcept log_policy_event_once(1u << 2, "{\"event\":\"music_playback_policy_skipped\",\"reason\":\"queue_channel_unavailable\"}"); return; } + music_channels[channel].store(true, std::memory_order_relaxed); std::uintptr_t manager{}; if (!read_at(base + local_music::asset_manager, manager) || !manager || @@ -402,8 +404,7 @@ bool initialize_music_playback(std::uintptr_t base, bool shuffle) noexcept { last_binding_by_channel = {}; for (auto& binding : last_logged_channel_bindings) binding.store(std::numeric_limits::max(), std::memory_order_relaxed); - for (auto& results : logged_candidate_admission_results) - results.store(0, std::memory_order_relaxed); + for (auto& known : music_channels) known.store(false, std::memory_order_relaxed); logged_policy_events.store(0, std::memory_order_relaxed); playback_available.store(false, std::memory_order_release); return true; @@ -433,7 +434,10 @@ std::uintptr_t __fastcall music_channel_playlist_bind_hook(std::uintptr_t channe read_at(playlist_id_pointer, requested_playlist_id); auto playlist_id = requested_playlist_id; bool favorites_remapped{}; - if (have_binding && requested_playlist_id == 0) { + // Zero is Favorites only where the music player asks: a channel it has selected songs for. + const bool music_channel = have_binding && channel < music_channels.size() && + music_channels[channel].load(std::memory_order_relaxed); + if (music_channel && requested_playlist_id == 0) { const auto base = image_base.load(std::memory_order_acquire); std::uintptr_t manager{}, ui_manager{}; const bool have_favorites = base && @@ -456,19 +460,15 @@ std::uintptr_t __fastcall music_channel_playlist_bind_hook(std::uintptr_t channe ? bind_channel_playlist(channel_pointer, reinterpret_cast(&playlist_id)) : bind_channel_playlist(channel_pointer, playlist_id_pointer); - if (have_binding) { + // Said once for each change of a channel's binding. A channel outside the table cannot be + // told from its last binding, so it is not said at all rather than every time. + if (have_binding && channel < last_logged_channel_bindings.size()) { const auto signature = (static_cast(channel) << 32) | playlist_id; - bool should_log = true; - if (channel < last_logged_channel_bindings.size()) { - const auto previous = last_logged_channel_bindings[channel].exchange( - signature, std::memory_order_relaxed); - should_log = previous != signature; - } - if (should_log) { - char message[224]{}; + if (last_logged_channel_bindings[channel].exchange(signature, std::memory_order_relaxed) != signature) { + char message[256]{}; std::snprintf(message, sizeof(message), - "{\"event\":\"music_channel_playlist_binding\",\"channel\":%u,\"requested_playlist_id\":%u,\"playlist_id\":%u,\"favorites\":%u,\"favorites_remapped\":%s,\"applied\":%s}", - channel, requested_playlist_id, playlist_id, favorites_playlist, + "{\"event\":\"music_channel_playlist_binding\",\"channel\":%u,\"music_channel\":%s,\"requested_playlist_id\":%u,\"playlist_id\":%u,\"favorites\":%u,\"favorites_remapped\":%s,\"applied\":%s}", + channel, music_channel ? "true" : "false", requested_playlist_id, playlist_id, favorites_playlist, favorites_remapped ? "true" : "false", result ? "true" : "false"); dingosdk::logging::event(dingosdk::logging::Channel::music, message); } @@ -476,29 +476,6 @@ std::uintptr_t __fastcall music_channel_playlist_bind_hook(std::uintptr_t channe return result; } -MusicCandidateAdmission& music_candidate_admission_original() noexcept { return candidate_admission; } - -bool __fastcall music_candidate_admission_hook(std::uintptr_t manager, - std::uintptr_t candidate_guid, std::uintptr_t context, std::uint32_t channel_key) { - if (!candidate_admission) return false; - const bool accepted = candidate_admission(manager, candidate_guid, context, channel_key); - const auto result_bit = static_cast(accepted ? 2 : 1); - bool should_log = true; - if (channel_key < logged_candidate_admission_results.size()) { - const auto previous = logged_candidate_admission_results[channel_key].fetch_or( - result_bit, std::memory_order_relaxed); - should_log = (previous & result_bit) == 0; - } - if (should_log) { - char message[128]{}; - std::snprintf(message, sizeof(message), - "{\"event\":\"music_candidate_admission\",\"channel_key\":%u,\"accepted\":%s}", - channel_key, accepted ? "true" : "false"); - dingosdk::logging::event(dingosdk::logging::Channel::music, message); - } - return accepted; -} - bool music_shuffle_enabled() noexcept { return shuffle_enabled.load(std::memory_order_acquire); } void set_music_shuffle_enabled(bool enabled) noexcept { diff --git a/Extension/Music/local_music_playback.h b/Extension/Music/local_music_playback.h index 8c2a559..7aa9dd4 100644 --- a/Extension/Music/local_music_playback.h +++ b/Extension/Music/local_music_playback.h @@ -6,8 +6,6 @@ namespace dingosdk::profile_runtime { using MusicSelectNext = std::int32_t (__fastcall *)(std::uintptr_t queue, std::uintptr_t playlist_asset); using MusicChannelPlaylistBind = std::uintptr_t (__fastcall *)(std::uintptr_t channel_pointer, std::uintptr_t playlist_id_pointer); -using MusicCandidateAdmission = bool (__fastcall *)(std::uintptr_t manager, - std::uintptr_t candidate_guid, std::uintptr_t context, std::uint32_t channel_key); // Validate the supported native selector/setter contracts and seed the lock-free // preference read used by the audio selection hook. @@ -20,9 +18,6 @@ std::int32_t __fastcall music_select_next_hook(std::uintptr_t queue, std::uintpt MusicChannelPlaylistBind& music_channel_playlist_bind_original() noexcept; std::uintptr_t __fastcall music_channel_playlist_bind_hook(std::uintptr_t channel_pointer, std::uintptr_t playlist_id_pointer); -MusicCandidateAdmission& music_candidate_admission_original() noexcept; -bool __fastcall music_candidate_admission_hook(std::uintptr_t manager, - std::uintptr_t candidate_guid, std::uintptr_t context, std::uint32_t channel_key); bool music_shuffle_enabled() noexcept; void set_music_shuffle_enabled(bool enabled) noexcept; diff --git a/Extension/Profile/local_profile_runtime.cpp b/Extension/Profile/local_profile_runtime.cpp index 7061943..bb5ea2b 100644 --- a/Extension/Profile/local_profile_runtime.cpp +++ b/Extension/Profile/local_profile_runtime.cpp @@ -553,14 +553,6 @@ bool initialize_local_profile(std::uintptr_t base, bool authored_offline, &music_channel_playlist_bind_hook, music_channel_playlist_bind_original()); else dingosdk::logging::event(dingosdk::logging::Channel::music, "{\"event\":\"music_channel_playlist_binding_contract_mismatch\"}"); - std::array admission_bytes{}; - if (read(base + addr::local_music::playback_candidate_admission_contract.rva, - admission_bytes) && - admission_bytes == addr::local_music::playback_candidate_admission_contract.bytes) - hook(addr::local_music::playback_candidate_admission_contract, - &music_candidate_admission_hook, music_candidate_admission_original()); - else dingosdk::logging::event(dingosdk::logging::Channel::music, - "{\"event\":\"music_candidate_admission_contract_mismatch\"}"); hook(addr::local_music::playback_select_next_contract, &music_select_next_hook, music_select_next_original()); } else dingosdk::logging::event(dingosdk::logging::Channel::music,