Music: Favorites takes playlist 0 on the music player's channels only

The Favorites order fix gave Favorites to any channel that asked for
playlist 0. That is what the music player asks for when Favorites is
chosen, but nothing said another channel could not ask for 0 and mean
something else. The channels the music player's own queues pick songs
for are now remembered as they are seen, and only those are given
Favorites for 0; on any other channel the request goes through as it
came. The channel numbers are still the game's: none is written down.

A channel's binding was logged once per change, except for a channel
past the end of the table, which was logged on every call. Those are no
longer logged at all. The binding line now says whether the channel is
one of the music player's.

The hook on the game's candidate check is removed, with its address: it
changed nothing and only logged.
This commit is contained in:
AyeZeeBB committed 2026-10-11 10:59:08 -05:00
1 parent a95c542a72
commit aa08050c8b
4 files changed
+17 -55

No files matched your search

-2
View File
@@ -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<Fingerprint, 7> 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}},
+17 -40
View File
@@ -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<std::uintptr_t> image_base{};
@@ -29,8 +28,10 @@ std::atomic<bool> playback_available{};
MusicSelectNext select_next{};
MusicChannelPlaylistBind bind_channel_playlist{};
std::array<std::atomic<std::uint64_t>, 256> last_logged_channel_bindings{};
MusicCandidateAdmission candidate_admission{};
std::array<std::atomic<std::uint8_t>, 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<std::atomic<bool>, 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<std::uint64_t>::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<std::uintptr_t>(&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<std::uint64_t>(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<std::uint8_t>(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 {
-5
View File
@@ -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;
@@ -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<unsigned char, 32> 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,