Skip to content

Commit

Permalink
Split up hooks into separate cpp files
Browse files Browse the repository at this point in the history
  • Loading branch information
michael-r-elp committed Jul 22, 2024
1 parent 15f8f33 commit c5e2144
Show file tree
Hide file tree
Showing 5 changed files with 364 additions and 231 deletions.
107 changes: 107 additions & 0 deletions include/hooking.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
#pragma once

#include "beatsaber-hook/shared/utils/hooking.hpp"
#include "beatsaber-hook/shared/utils/logging.hpp"
#include <vector>

namespace QuestSounds {
class Hooking {
private:
inline static std::vector<void (*)()> installFuncs;

public:
static void AddInstallFunc(void (*installFunc)()) {
installFuncs.push_back(installFunc);
}

static inline void InstallHooks() {
for (auto& func : installFuncs) func();
}
};

template<auto mPtr>
concept has_metadata = requires() {
{ ::il2cpp_utils::il2cpp_type_check::MetadataGetter<mPtr>::methodInfo() } -> std::same_as<MethodInfo const*>;
};

template<auto mPtr>
requires(has_metadata<mPtr>)
using Metadata = ::il2cpp_utils::il2cpp_type_check::MetadataGetter<mPtr>;

/// @brief checks whether the function is match hookable, which requires the function to be at least 5 (5 * 4 = 20 bytes) instructions and not have an address of 0 (abstract/virtual funcs)
template<auto mPtr>
concept match_hookable = has_metadata<mPtr> && Metadata<mPtr>::size >= (0x5 * sizeof(int32_t)) && Metadata<mPtr>::addrs != 0x0;
}

#define AUTO_INSTALL_PATCH(name_) \
struct Auto_Patch_##name_ { \
Auto_Patch_##name_() { \
::QuestSounds::Hooking::AddInstallFunc(Patch_##name_); \
} \
}; \
static Auto_Patch_##name_ Auto_Patch_Instance_##name_

#define HOOK_AUTO_INSTALL_ORIG(name_) \
struct Auto_Hook_##name_ { \
static void Auto_Hook_##name_##_Install() { \
static constexpr auto logger = Paper::ConstLoggerContext(MOD_ID "_Install_" #name_); \
::Hooking::InstallOrigHook<Hook_##name_>(logger); \
} \
Auto_Hook_##name_() { ::QuestSounds::Hooking::AddInstallFunc(Auto_Hook_##name_##_Install); } \
}; \
static Auto_Hook_##name_ Auto_Hook_Instance_##name_

#define HOOK_AUTO_INSTALL(name_) \
struct Auto_Hook_##name_ { \
static void Auto_Hook_##name_##_Install() { \
static constexpr auto logger = Paper::ConstLoggerContext(MOD_ID "_Install_" #name_); \
::Hooking::InstallHook<Hook_##name_>(logger); \
} \
Auto_Hook_##name_() { ::QuestSounds::Hooking::AddInstallFunc(Auto_Hook_##name_##_Install); } \
}; \
static Auto_Hook_##name_ Auto_Hook_Instance_##name_

#define MAKE_AUTO_HOOK_MATCH(name_, mPtr, retval, ...) \
struct Hook_##name_ \
{ \
using funcType = retval (*)(__VA_ARGS__); \
static_assert(QuestSounds::match_hookable<mPtr>); \
static_assert(std::is_same_v<funcType, ::Hooking::InternalMethodCheck<decltype(mPtr)>::funcType>, "Hook method signature does not match!"); \
constexpr static const char* name() { return #name_; } \
static const MethodInfo* getInfo() { return ::il2cpp_utils::il2cpp_type_check::MetadataGetter<mPtr>::methodInfo(); } \
static funcType* trampoline() { return &name_; } \
static inline retval (*name_)(__VA_ARGS__) = nullptr; \
static funcType hook() { return &::Hooking::HookCatchWrapper<&hook_##name_, funcType>::wrapper; } \
static retval hook_##name_(__VA_ARGS__); \
}; \
HOOK_AUTO_INSTALL(name_); \
retval Hook_##name_::hook_##name_(__VA_ARGS__)

#define MAKE_AUTO_HOOK_FIND_VERBOSE(name_, infoGet, retval, ...) \
struct Hook_##name_ { \
constexpr static const char* name() { return #name_; } \
static const MethodInfo* getInfo() { return infoGet; } \
using funcType = retval (*)(__VA_ARGS__); \
static funcType* trampoline() { return &name_; } \
static inline retval (*name_)(__VA_ARGS__) = nullptr; \
static funcType hook() { return &::Hooking::HookCatchWrapper<&hook_##name_, funcType>::wrapper; } \
static retval hook_##name_(__VA_ARGS__); \
}; \
HOOK_AUTO_INSTALL_ORIG(name_); \
retval Hook_##name_::hook_##name_(__VA_ARGS__)

#define MAKE_AUTO_HOOK_ORIG_MATCH(name_, mPtr, retval, ...) \
struct Hook_##name_ \
{ \
using funcType = retval (*)(__VA_ARGS__); \
static_assert(QuestSounds::match_hookable<mPtr>); \
static_assert(std::is_same_v<funcType, ::Hooking::InternalMethodCheck<decltype(mPtr)>::funcType>, "Hook method signature does not match!"); \
constexpr static const char* name() { return #name_; } \
static const MethodInfo* getInfo() { return ::il2cpp_utils::il2cpp_type_check::MetadataGetter<mPtr>::methodInfo(); } \
static funcType* trampoline() { return &name_; } \
static inline retval (*name_)(__VA_ARGS__) = nullptr; \
static funcType hook() { return &::Hooking::HookCatchWrapper<&hook_##name_, funcType>::wrapper; } \
static retval hook_##name_(__VA_ARGS__); \
}; \
HOOK_AUTO_INSTALL_ORIG(name_); \
retval Hook_##name_::hook_##name_(__VA_ARGS__)
93 changes: 93 additions & 0 deletions src/Hooks/BackgroundMusicHooks.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
#include "hooking.hpp"
#include "logging.hpp"
#include "Config.hpp"
using namespace QuestSounds;

// TODO: I'd like to get rid of this header
#include "AudioClips.hpp"
using namespace QuestSounds::AudioClips;

#include "GlobalNamespace/SongPreviewPlayer.hpp"
#include "GlobalNamespace/GameServerLobbyFlowCoordinator.hpp"
#include "GlobalNamespace/MultiplayerModeSelectionFlowCoordinator.hpp"
using namespace GlobalNamespace;


MAKE_AUTO_HOOK_MATCH(SongPreviewPlayer_OnEnable, &SongPreviewPlayer::OnEnable, void, SongPreviewPlayer* self) {
getLogger().info("is it true: %i", menuMusicLoader.loaded);

if (!menuMusicLoader.OriginalAudioSource) {
menuMusicLoader.set_OriginalClip(self->_defaultAudioClip);
}

if (menuMusicLoader.loaded && Config.Sounds.MenuMusic.Active)
{
getLogger().debug("Overriding MenuMusic Audio");
UnityEngine::AudioClip* audioClip = menuMusicLoader.getClip();
if (audioClip != nullptr)
self->_defaultAudioClip = audioClip;
}
else {
getLogger().debug("Loading MenuMusic Audio normally: isLoaded='{}' isActive='{}'", menuMusicLoader.loaded, Config.Sounds.MenuMusic.Active);
self->_defaultAudioClip = menuMusicLoader.get_OriginalClip();
}
SongPreviewPlayer_OnEnable(self);
}

MAKE_AUTO_HOOK_MATCH(GameServerLobbyFlowCoordinator_DidActivate, &GameServerLobbyFlowCoordinator::DidActivate, void, GameServerLobbyFlowCoordinator* self, bool firstActivation, bool addedToHierarchy, bool screenSystemEnabling)
{
getLogger().debug("GameServerLobbyFlowCoordinator_DidActivate");
if (!lobbyAmbienceLoader.OriginalAudioSource) lobbyAmbienceLoader.set_OriginalClip(self->_ambienceAudioClip);

if (lobbyAmbienceLoader.loaded && Config.Sounds.LobbyMusic.Active)
{
getLogger().debug("Overwriting LobbyAmbience Audio");
self->_ambienceAudioClip = lobbyAmbienceLoader.getClip();
}
else {
self->_ambienceAudioClip = lobbyAmbienceLoader.get_OriginalClip();
}
GameServerLobbyFlowCoordinator_DidActivate(self, firstActivation, addedToHierarchy, screenSystemEnabling);
}

MAKE_AUTO_HOOK_MATCH(GameServerLobbyFlowCoordinator_DidDeactivate, &GameServerLobbyFlowCoordinator::DidDeactivate, void, GameServerLobbyFlowCoordinator* self, bool removedFromHierarchy, bool screenSystemDisabling)
{
getLogger().debug("GameServerLobbyFlowCoordinator_DidActivate");
if (menuMusicLoader.loaded && Config.Sounds.MenuMusic.Active && removedFromHierarchy)
{
getLogger().debug("Switching LobbyMusic to MenuMusic Audio");
self->_ambienceAudioClip = menuMusicLoader.getClip();
}
else {
self->_ambienceAudioClip = menuMusicLoader.get_OriginalClip();
}
GameServerLobbyFlowCoordinator_DidDeactivate(self, removedFromHierarchy, screenSystemDisabling);
}

MAKE_AUTO_HOOK_MATCH(MultiplayerModeSelectionFlowCoordinator_DidActivate, &MultiplayerModeSelectionFlowCoordinator::DidActivate, void, MultiplayerModeSelectionFlowCoordinator* self, bool firstActivation, bool addedToHierarchy, bool screenSystemEnabling)
{
getLogger().debug("MultiplayerModeSelectionFlowCoordinator_DidActivate");
if (menuMusicLoader.loaded && Config.Sounds.MenuMusic.Active)
{
getLogger().debug("Switching LobbyMusic to MenuMusic Audio");
self->_ambienceAudioClip = menuMusicLoader.getClip();
}
else {
self->_ambienceAudioClip = menuMusicLoader.get_OriginalClip();
}
MultiplayerModeSelectionFlowCoordinator_DidActivate(self, firstActivation, addedToHierarchy, screenSystemEnabling); // This has to be ran last, otherwise it will not work correctly
}

MAKE_AUTO_HOOK_MATCH(MultiplayerModeSelectionFlowCoordinator_DidDeactivate, &MultiplayerModeSelectionFlowCoordinator::DidDeactivate, void, MultiplayerModeSelectionFlowCoordinator* self, bool removedFromHierarchy, bool screenSystemDisabling)
{
getLogger().debug("MultiplayerModeSelectionFlowCoordinator_DidDeactivate");
if (menuMusicLoader.loaded && Config.Sounds.MenuMusic.Active && removedFromHierarchy)
{
self->_ambienceAudioClip = menuMusicLoader.getClip();
}
else {
self->_ambienceAudioClip = menuMusicLoader.get_OriginalClip();
}

MultiplayerModeSelectionFlowCoordinator_DidDeactivate(self, removedFromHierarchy, screenSystemDisabling);
}
77 changes: 77 additions & 0 deletions src/Hooks/LevelResultSoundHooks.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
#include "hooking.hpp"
#include "logging.hpp"
#include "Config.hpp"
using namespace QuestSounds;

// TODO: I'd like to get rid of this header
#include "AudioClips.hpp"
using namespace QuestSounds::AudioClips;

#include "GlobalNamespace/ResultsViewController.hpp"
#include "GlobalNamespace/LevelCompletionResults.hpp"
#include "GlobalNamespace/SongPreviewPlayer.hpp"
#include "GlobalNamespace/FireworkItemController.hpp"
using namespace GlobalNamespace;

#pragma region LevelResultSound
MAKE_AUTO_HOOK_MATCH(ResultsViewController_DidActivate, &ResultsViewController::DidActivate, void, ResultsViewController* self, bool firstActivation, bool addedToHierarchy, bool screenSystemEnabling) {
if (firstActivation && addedToHierarchy && !levelClearedLoader.OriginalAudioSource) {
levelClearedLoader.set_OriginalClip(self->_levelClearedAudioClip);
}
if (self->_levelCompletionResults->levelEndStateType == LevelCompletionResults::LevelEndStateType::Failed) {
self->_songPreviewPlayer->StopAllCoroutines();
if (levelFailedLoader.loaded && addedToHierarchy && Config.Sounds.LevelFailed.Active) {
UnityEngine::AudioClip* FailedSound = levelFailedLoader.getClip();
float length = FailedSound->get_length();
getLogger().debug("Duration of LevelFailed Sound: {}", length);
if (length > 7.0f) {
getLogger().info("Long LevelFailedSound");
self->_songPreviewPlayer->CrossfadeTo(FailedSound, -4.0f, 0.0f, length, nullptr);
}
else {
getLogger().info("Short LevelFailedSound");
self->_songPreviewPlayer->FadeOut(0.1f);
self->_songPreviewPlayer->_fadeSpeed = self->_songPreviewPlayer->_fadeInSpeed;
getLogger().debug("volume: {}", self->_songPreviewPlayer->_volume);
getLogger().debug("AmbientVolume: {}", self->_songPreviewPlayer->_ambientVolumeScale);
getLogger().debug("Set Volume: {}", self->_songPreviewPlayer->_volume * self->_songPreviewPlayer->_ambientVolumeScale);

levelFailedLoader.audioSource->set_volume(self->_songPreviewPlayer->_volume * self->_songPreviewPlayer->_ambientVolumeScale);
self->_songPreviewPlayer->StartCoroutine(self->_songPreviewPlayer->CrossFadeAfterDelayCoroutine(length - 1.2f));
levelFailedLoader.audioSource->Play();
}
}
}
else {
if (levelClearedLoader.loaded && addedToHierarchy && Config.Sounds.LevelCleared.Active)
{
UnityEngine::AudioClip* audioClip = levelClearedLoader.getClip();
self->_levelClearedAudioClip = audioClip;
}
else {
self->_levelClearedAudioClip = levelClearedLoader.get_OriginalClip();
}
}
ResultsViewController_DidActivate(self, firstActivation, addedToHierarchy, screenSystemEnabling);
}

MAKE_AUTO_HOOK_MATCH(ResultsViewController_RestartButtonPressed, &ResultsViewController::RestartButtonPressed, void, ResultsViewController* self) {
if (levelFailedLoader.loaded && levelFailedLoader.audioSource->get_isPlaying()) {
levelFailedLoader.audioSource->Stop();
}
ResultsViewController_RestartButtonPressed(self);
}
#pragma endregion

#pragma region FireworkSound
// Replacing the function here, as replacing the AudioClips proves to be difficult
MAKE_AUTO_HOOK_MATCH(FireworkItemController_PlayExplosionSound, &FireworkItemController::PlayExplosionSound, void, FireworkItemController* self) {
if (fireworkSoundLoader.loaded && Config.Sounds.Firework.Active) {
self->_audioSource->set_clip(fireworkSoundLoader.getClip());
float pitch = 1.2f + (((float)rand()) / (float)RAND_MAX) * (0.8f - 1.2f);
self->_audioSource->set_pitch(pitch);
self->_audioSource->Play();
}
else FireworkItemController_PlayExplosionSound(self);
}
#pragma endregion
Loading

0 comments on commit c5e2144

Please sign in to comment.