mirror of
https://github.com/Dingo-Shenanigans/ReSkate.git
synced 2026-10-25 14:14:41 +01:00
263 lines
12 KiB
C++
263 lines
12 KiB
C++
#include "local_music_playback.h"
|
|
|
|
#include "local_music_playback_policy.h"
|
|
#include "Engine/Core/Hooks/hooks.h"
|
|
#include "Engine/Core/Platform/memory.h"
|
|
#include "Engine/Game/Build/20260929/local_music.h"
|
|
#include "Engine/Game/Build/supported_build.h"
|
|
|
|
#include <Windows.h>
|
|
#include <array>
|
|
#include <atomic>
|
|
#include <cstring>
|
|
#include <limits>
|
|
|
|
namespace dingosdk::profile_runtime {
|
|
namespace {
|
|
using namespace dingosdk::game::build::v20260929;
|
|
using local_music::playback_mode_setter_contract;
|
|
using local_music::playback_select_next_contract;
|
|
using music_playback::CursorPlan;
|
|
|
|
std::atomic<std::uintptr_t> image_base{};
|
|
std::atomic<bool> shuffle_enabled{};
|
|
std::atomic<bool> playback_available{};
|
|
MusicSelectNext select_next{};
|
|
using SetPlaybackMode = void (__fastcall *)(std::uintptr_t manager, std::uint32_t playlist_id,
|
|
std::uint32_t mode);
|
|
SetPlaybackMode set_playback_mode{};
|
|
struct LastBinding {
|
|
std::uintptr_t manager{};
|
|
std::uintptr_t queue{};
|
|
std::uintptr_t node{};
|
|
std::uint32_t playlist_id{std::numeric_limits<std::uint32_t>::max()};
|
|
};
|
|
std::array<LastBinding, 256> last_binding_by_channel{}; // Protected by the binding-cache lock.
|
|
SRWLOCK binding_cache_lock = SRWLOCK_INIT;
|
|
|
|
bool read_at(std::uintptr_t address, auto& value) noexcept {
|
|
return dingosdk::memory::peek(address, value);
|
|
}
|
|
|
|
struct CriticalSectionGuard {
|
|
explicit CriticalSectionGuard(std::uintptr_t address) noexcept
|
|
: section(reinterpret_cast<LPCRITICAL_SECTION>(address)) { EnterCriticalSection(section); }
|
|
~CriticalSectionGuard() { LeaveCriticalSection(section); }
|
|
CriticalSectionGuard(const CriticalSectionGuard&) = delete;
|
|
CriticalSectionGuard& operator=(const CriticalSectionGuard&) = delete;
|
|
LPCRITICAL_SECTION section;
|
|
};
|
|
|
|
struct BindingCacheGuard {
|
|
BindingCacheGuard() noexcept { AcquireSRWLockExclusive(&binding_cache_lock); }
|
|
~BindingCacheGuard() { ReleaseSRWLockExclusive(&binding_cache_lock); }
|
|
BindingCacheGuard(const BindingCacheGuard&) = delete;
|
|
BindingCacheGuard& operator=(const BindingCacheGuard&) = delete;
|
|
};
|
|
|
|
bool valid_range(std::uintptr_t value, std::size_t size) noexcept {
|
|
return value >= 0x10000 && value <= dingosdk::memory::highest_user_address - size;
|
|
}
|
|
|
|
bool find_channel_node(std::uintptr_t manager, std::uint32_t channel,
|
|
std::uint32_t& playlist_id) noexcept {
|
|
std::uintptr_t buckets{};
|
|
std::uint32_t capacity{};
|
|
std::uintptr_t sentinel{}, node{};
|
|
if (!read_at(manager + 0xc8, buckets) || !read_at(manager + 0xd0, capacity) ||
|
|
!buckets || !capacity || capacity > 4096 || !valid_range(buckets, (capacity + 1ULL) * 8) ||
|
|
!read_at(buckets + capacity * 8ULL, sentinel) ||
|
|
!read_at(buckets + (channel % capacity) * 8ULL, node)) return false;
|
|
for (std::uint32_t seen = 0; node && node != sentinel && seen < 4096; ++seen) {
|
|
std::uint32_t key{}, id{};
|
|
std::uintptr_t next{};
|
|
if (!valid_range(node, 0x10) || !read_at(node, key) || !read_at(node + 4, id) ||
|
|
!read_at(node + 8, next)) return false;
|
|
if (key == channel) { playlist_id = id; return true; }
|
|
node = next;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
std::uintptr_t find_playlist_node(std::uintptr_t manager, std::uint32_t playlist_id) noexcept {
|
|
std::uintptr_t buckets{};
|
|
std::uint32_t capacity{};
|
|
std::uintptr_t sentinel{}, node{};
|
|
if (!read_at(manager + 0xa0, buckets) || !read_at(manager + 0xa8, capacity) ||
|
|
!buckets || !capacity || capacity > 4096 || !valid_range(buckets, (capacity + 1ULL) * 8) ||
|
|
!read_at(buckets + capacity * 8ULL, sentinel) ||
|
|
!read_at(buckets + (playlist_id % capacity) * 8ULL, node)) return 0;
|
|
for (std::uint32_t seen = 0; node && node != sentinel && seen < 8192; ++seen) {
|
|
std::uint32_t id{};
|
|
std::uintptr_t next{};
|
|
if (!valid_range(node, 0x60) || !read_at(node, id) || !read_at(node + 0x58, next)) return 0;
|
|
if (id == playlist_id) return node;
|
|
node = next;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
bool song_count(std::uintptr_t playlist_asset, std::uint32_t& count) noexcept {
|
|
std::uintptr_t entries{};
|
|
std::uint32_t encoded_count{};
|
|
if (!read_at(playlist_asset + 0x60, entries) || !entries ||
|
|
!read_at(entries - sizeof(encoded_count), encoded_count)) return false;
|
|
count = encoded_count & 0x7fffffffu;
|
|
return count > 0 && count <= 8192 && valid_range(entries, static_cast<std::size_t>(count) * 8);
|
|
}
|
|
|
|
bool member_count(std::uintptr_t node, std::uintptr_t& begin, std::uint32_t& count) noexcept {
|
|
std::uintptr_t end{};
|
|
if (!read_at(node + 0x10, begin) || !read_at(node + 0x18, end) || !begin || end < begin ||
|
|
(end - begin) % 16 || (end - begin) / 16 > 8192) return false;
|
|
count = static_cast<std::uint32_t>((end - begin) / 16);
|
|
return count > 0 && valid_range(begin, static_cast<std::size_t>(count) * 16);
|
|
}
|
|
|
|
bool current_guid(std::uintptr_t queue, std::uintptr_t playlist_asset,
|
|
bool& found, std::array<unsigned char, 16>& guid) noexcept {
|
|
found = false;
|
|
std::int32_t current_index{};
|
|
std::uintptr_t candidates_begin{}, candidates_end{}, entries{};
|
|
std::uint32_t count{};
|
|
if (!read_at(queue + 0x70, current_index)) return false;
|
|
if (current_index < 0) return true;
|
|
if (!read_at(queue + 0x78, candidates_begin) || !read_at(queue + 0x80, candidates_end) ||
|
|
!read_at(playlist_asset + 0x60, entries) || !song_count(playlist_asset, count) ||
|
|
candidates_end < candidates_begin || (candidates_end - candidates_begin) % 4) return false;
|
|
if (static_cast<std::uint64_t>(current_index) >= (candidates_end - candidates_begin) / 4) return false;
|
|
|
|
std::uint32_t asset_index{};
|
|
if (!read_at(candidates_begin + static_cast<std::uintptr_t>(current_index) * 4, asset_index) || asset_index >= count)
|
|
return false;
|
|
std::uintptr_t graph{};
|
|
if (!read_at(entries + static_cast<std::uintptr_t>(asset_index) * 8, graph)) return false;
|
|
graph &= ~std::uintptr_t{4};
|
|
if (!valid_range(graph, 0x20) || graph < 16) return false;
|
|
std::uint32_t flags{};
|
|
if (!read_at(graph + 0x14, flags)) return false;
|
|
if (!(flags & 0x100)) return true;
|
|
if (!dingosdk::memory::peek_bytes(graph - 16, guid.data(), guid.size())) return false;
|
|
found = true;
|
|
return true;
|
|
}
|
|
|
|
bool member_index(std::uintptr_t members, std::uint32_t member_count_value,
|
|
const std::array<unsigned char, 16>& guid, bool has_guid,
|
|
bool& found, std::uint32_t& position) noexcept {
|
|
found = false;
|
|
if (!has_guid) return true;
|
|
for (std::uint32_t i = 0; i < member_count_value; ++i) {
|
|
std::array<unsigned char, 16> candidate{};
|
|
if (!dingosdk::memory::peek_bytes(members + static_cast<std::uintptr_t>(i) * 16,
|
|
candidate.data(), candidate.size())) return false;
|
|
std::uint32_t ignored{};
|
|
if (music_playback::find_member_position(candidate.data(), 1, guid, ignored)) {
|
|
found = true;
|
|
position = i;
|
|
break;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool pending_song_request(std::uintptr_t queue, bool& pending) noexcept {
|
|
std::uintptr_t begin{}, end{};
|
|
pending = false;
|
|
if (!read_at(queue + 0x98, begin) || !read_at(queue + 0xa0, end) || end < begin ||
|
|
(end - begin) % 4 || (end - begin) / 4 > 4096) return false;
|
|
pending = begin != end;
|
|
return true;
|
|
}
|
|
|
|
void apply_policy(std::uintptr_t queue, std::uintptr_t playlist_asset) noexcept {
|
|
const auto base = image_base.load(std::memory_order_acquire);
|
|
if (!base || !queue || !playlist_asset) return;
|
|
std::uint32_t queue_type{}, channel{}, channel_playlist{};
|
|
if (!read_at(playlist_asset + 0x70, queue_type) || queue_type != 4 ||
|
|
!read_at(queue + 0x60, channel) || channel >= last_binding_by_channel.size()) return;
|
|
|
|
// Snapshot the queue-local state under its native lock, then release it before
|
|
// taking the music manager lock. The selector can re-enter this queue lock.
|
|
bool pending{}, has_current_guid{};
|
|
std::array<unsigned char, 16> playing_guid{};
|
|
std::uint32_t asset_songs{};
|
|
{
|
|
CriticalSectionGuard queue_lock(queue + 0x150);
|
|
if (!pending_song_request(queue, pending) || !song_count(playlist_asset, asset_songs) ||
|
|
!current_guid(queue, playlist_asset, has_current_guid, playing_guid)) return;
|
|
}
|
|
|
|
std::uintptr_t manager{};
|
|
if (!read_at(base + local_music::asset_manager, manager) || !manager ||
|
|
!valid_range(manager, 0xd8)) return;
|
|
CriticalSectionGuard manager_lock(manager + 0x20);
|
|
std::uintptr_t current_manager{};
|
|
if (!read_at(base + local_music::asset_manager, current_manager) || current_manager != manager ||
|
|
!find_channel_node(manager, channel, channel_playlist)) return;
|
|
const auto node = find_playlist_node(manager, channel_playlist);
|
|
if (!node) return;
|
|
BindingCacheGuard cache_lock;
|
|
|
|
std::uint32_t current_mode{}, current_cursor{}, member_total{};
|
|
std::uintptr_t members{};
|
|
if (!read_at(node + 0x0c, current_mode) || !read_at(node + 0x30, current_cursor) ||
|
|
!member_count(node, members, member_total)) return;
|
|
|
|
const auto& previous = last_binding_by_channel[channel];
|
|
const bool new_group = previous.manager != manager || previous.queue != queue ||
|
|
previous.node != node || previous.playlist_id != channel_playlist;
|
|
bool current_found{};
|
|
std::uint32_t current_position{};
|
|
if (!member_index(members, member_total, playing_guid, has_current_guid, current_found, current_position)) return;
|
|
|
|
const auto plan = music_playback::plan_cursor(shuffle_enabled.load(std::memory_order_acquire),
|
|
current_mode, current_cursor, new_group, pending, current_found, current_position, member_total);
|
|
if (plan.set_mode) {
|
|
// Clear the rebuild latch only as an input. The native rebuild sets it on
|
|
// success; preserving that output is required for later rebuild semantics.
|
|
*reinterpret_cast<volatile std::uint8_t*>(node + 8) = 0;
|
|
set_playback_mode(manager, channel_playlist, plan.mode);
|
|
}
|
|
if (plan.set_cursor) *reinterpret_cast<volatile std::uint32_t*>(node + 0x30) = plan.cursor;
|
|
last_binding_by_channel[channel] = {manager, queue, node, channel_playlist};
|
|
}
|
|
}
|
|
|
|
bool initialize_music_playback(std::uintptr_t base, bool shuffle) noexcept {
|
|
if (!base || local_music::playback_select_next_contract.rva >= supported_build::game_image_size ||
|
|
local_music::playback_mode_setter_contract.rva >= supported_build::game_image_size) return false;
|
|
std::array<unsigned char, 32> selector{}, setter{};
|
|
if (!dingosdk::memory::read_bytes(base + local_music::playback_select_next_contract.rva,
|
|
selector.data(), selector.size()) || selector != local_music::playback_select_next_contract.bytes ||
|
|
!dingosdk::memory::read_bytes(base + local_music::playback_mode_setter_contract.rva,
|
|
setter.data(), setter.size()) || setter != local_music::playback_mode_setter_contract.bytes) return false;
|
|
image_base.store(base, std::memory_order_release);
|
|
set_playback_mode = reinterpret_cast<SetPlaybackMode>(base + local_music::playback_mode_setter_contract.rva);
|
|
shuffle_enabled.store(shuffle, std::memory_order_release);
|
|
last_binding_by_channel = {};
|
|
playback_available.store(false, std::memory_order_release);
|
|
return true;
|
|
}
|
|
|
|
void activate_music_playback() noexcept { playback_available.store(true, std::memory_order_release); }
|
|
void deactivate_music_playback() noexcept { playback_available.store(false, std::memory_order_release); }
|
|
bool music_playback_available() noexcept { return playback_available.load(std::memory_order_acquire); }
|
|
|
|
MusicSelectNext& music_select_next_original() noexcept { return select_next; }
|
|
|
|
std::int32_t __fastcall music_select_next_hook(std::uintptr_t queue, std::uintptr_t playlist_asset) {
|
|
if (select_next) {
|
|
apply_policy(queue, playlist_asset);
|
|
return select_next(queue, playlist_asset);
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
bool music_shuffle_enabled() noexcept { return shuffle_enabled.load(std::memory_order_acquire); }
|
|
|
|
void set_music_shuffle_enabled(bool enabled) noexcept {
|
|
shuffle_enabled.store(enabled, std::memory_order_release);
|
|
}
|
|
} // namespace dingosdk::profile_runtime
|