diff --git a/src/spice2x/CMakeLists.txt b/src/spice2x/CMakeLists.txt index aaa4c7d..787b058 100644 --- a/src/spice2x/CMakeLists.txt +++ b/src/spice2x/CMakeLists.txt @@ -477,10 +477,12 @@ set(SOURCE_FILES ${SOURCE_FILES} hooks/audio/acm.cpp hooks/audio/audio.cpp hooks/audio/buffer.cpp + hooks/audio/mme.cpp hooks/audio/util.cpp hooks/audio/backends/dsound/dsound_backend.cpp hooks/audio/backends/mmdevice/audio_endpoint_volume.cpp hooks/audio/backends/mmdevice/device.cpp + hooks/audio/backends/mmdevice/device_collection.cpp hooks/audio/backends/mmdevice/device_enumerator.cpp hooks/audio/backends/wasapi/audio_client.cpp hooks/audio/backends/wasapi/audio_render_client.cpp diff --git a/src/spice2x/games/gitadora/gitadora.cpp b/src/spice2x/games/gitadora/gitadora.cpp index 2419a5c..9cb982b 100644 --- a/src/spice2x/games/gitadora/gitadora.cpp +++ b/src/spice2x/games/gitadora/gitadora.cpp @@ -3,6 +3,7 @@ #include "bi2x_hook.h" #include #include "cfg/configurator.h" +#include "hooks/audio/mme.h" #include "hooks/graphics/graphics.h" #include "util/cpuutils.h" #include "util/detour.h" @@ -236,6 +237,9 @@ namespace games::gitadora { // test/service/coin buttons bi2x_hook_init(); + + // volume change prevention + hooks::audio::mme::init(avs::game::DLL_INSTANCE); return; } diff --git a/src/spice2x/games/gitadora/gitadora.h b/src/spice2x/games/gitadora/gitadora.h index b80a014..f0fb68a 100644 --- a/src/spice2x/games/gitadora/gitadora.h +++ b/src/spice2x/games/gitadora/gitadora.h @@ -31,4 +31,11 @@ namespace games::gitadora { ); } + static inline bool is_arena_model() { + return ( + avs::game::is_model("M32") && + (avs::game::SPEC[0] == 'C' || avs::game::SPEC[0] == 'D') + ); + } + } diff --git a/src/spice2x/hooks/audio/backends/mmdevice/device.cpp b/src/spice2x/hooks/audio/backends/mmdevice/device.cpp index cbf014e..de757bf 100644 --- a/src/spice2x/hooks/audio/backends/mmdevice/device.cpp +++ b/src/spice2x/hooks/audio/backends/mmdevice/device.cpp @@ -8,6 +8,7 @@ #include "hooks/audio/audio_private.h" #include "hooks/audio/backends/mmdevice/audio_endpoint_volume.h" #include "hooks/audio/backends/wasapi/audio_client.h" +#include "util/utils.h" #define PRINT_FAILED_RESULT(name, ret) \ do { \ @@ -56,6 +57,7 @@ ULONG STDMETHODCALLTYPE WrappedIMMDevice::Release() { ULONG refs = pReal != nullptr ? pReal->Release() : 0; if (refs == 0) { + log_misc("audio::mmdevice", "WrappedIMMDevice::Release"); delete this; } @@ -69,7 +71,7 @@ HRESULT STDMETHODCALLTYPE WrappedIMMDevice::Activate( PROPVARIANT *pActivationParams, void **ppInterface) { - log_misc("audio::mmdevice", "WrappedIMMDevice::Activate"); + log_misc("audio::mmdevice", "WrappedIMMDevice::Activate {}", guid2s(iid)); // call original HRESULT ret = pReal->Activate(iid, dwClsCtx, pActivationParams, ppInterface); diff --git a/src/spice2x/hooks/audio/backends/mmdevice/device_collection.cpp b/src/spice2x/hooks/audio/backends/mmdevice/device_collection.cpp new file mode 100644 index 0000000..0b86aed --- /dev/null +++ b/src/spice2x/hooks/audio/backends/mmdevice/device_collection.cpp @@ -0,0 +1,44 @@ +#include "device_collection.h" +#include "device.h" +#include "util/utils.h" +#include "util/logging.h" + +HRESULT STDMETHODCALLTYPE WrappedIMMDeviceCollection::QueryInterface(REFIID riid, void **ppvObj) { + if (ppvObj == nullptr) { + return E_POINTER; + } + if (riid == __uuidof(IMMDeviceCollection)) { + this->AddRef(); + *ppvObj = this; + return S_OK; + } + return pReal->QueryInterface(riid, ppvObj); +} + +ULONG STDMETHODCALLTYPE WrappedIMMDeviceCollection::AddRef() { + return pReal->AddRef(); +} + +ULONG STDMETHODCALLTYPE WrappedIMMDeviceCollection::Release() { + // get reference count of underlying interface + ULONG refs = pReal != nullptr ? pReal->Release() : 0; + if (refs == 0) { + delete this; + } + return refs; +} + +HRESULT STDMETHODCALLTYPE WrappedIMMDeviceCollection::GetCount(UINT *pcDevices) { + return pReal->GetCount(pcDevices); +} + +HRESULT STDMETHODCALLTYPE WrappedIMMDeviceCollection::Item(UINT nDevice, IMMDevice **ppDevice) { + log_info("audio", "WrappedIMMDeviceCollection::Item[{}]", nDevice); + + // call original + const auto hr = pReal->Item(nDevice, ppDevice); + + // wrap interface + *ppDevice = new WrappedIMMDevice(*ppDevice); + return hr; +} \ No newline at end of file diff --git a/src/spice2x/hooks/audio/backends/mmdevice/device_collection.h b/src/spice2x/hooks/audio/backends/mmdevice/device_collection.h new file mode 100644 index 0000000..ffdbe10 --- /dev/null +++ b/src/spice2x/hooks/audio/backends/mmdevice/device_collection.h @@ -0,0 +1,28 @@ +#pragma once + +#include +#include + +struct WrappedIMMDeviceCollection : IMMDeviceCollection { + explicit WrappedIMMDeviceCollection(IMMDeviceCollection *orig) : pReal(orig) { + } + + WrappedIMMDeviceCollection(const WrappedIMMDeviceCollection &) = delete; + WrappedIMMDeviceCollection &operator=(const WrappedIMMDeviceCollection &) = delete; + + virtual ~WrappedIMMDeviceCollection() = default; + +#pragma region IUnknown + virtual HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, void **ppvObj) override; + virtual ULONG STDMETHODCALLTYPE AddRef() override; + virtual ULONG STDMETHODCALLTYPE Release() override; +#pragma endregion + +#pragma region IMMDeviceCollection + virtual HRESULT STDMETHODCALLTYPE GetCount(UINT *pcDevices) override; + virtual HRESULT STDMETHODCALLTYPE Item(UINT nDevice, IMMDevice **ppDevice) override; +#pragma endregion + +private: + IMMDeviceCollection *const pReal; +}; diff --git a/src/spice2x/hooks/audio/backends/mmdevice/device_enumerator.cpp b/src/spice2x/hooks/audio/backends/mmdevice/device_enumerator.cpp index acfe324..1384a7c 100644 --- a/src/spice2x/hooks/audio/backends/mmdevice/device_enumerator.cpp +++ b/src/spice2x/hooks/audio/backends/mmdevice/device_enumerator.cpp @@ -1,4 +1,5 @@ #include "device_enumerator.h" +#include "device_collection.h" #include "device.h" #include "util/utils.h" @@ -42,7 +43,11 @@ HRESULT STDMETHODCALLTYPE WrappedIMMDeviceEnumerator::EnumAudioEndpoints( DWORD dwStateMask, IMMDeviceCollection **ppDevices) { - return pReal->EnumAudioEndpoints(dataFlow, dwStateMask, ppDevices); + const auto hr = pReal->EnumAudioEndpoints(dataFlow, dwStateMask, ppDevices); + if (SUCCEEDED(hr) && (ppDevices != nullptr) && (*ppDevices != nullptr)) { + *ppDevices = new WrappedIMMDeviceCollection(*ppDevices); + } + return hr; } HRESULT STDMETHODCALLTYPE WrappedIMMDeviceEnumerator::GetDefaultAudioEndpoint( diff --git a/src/spice2x/hooks/audio/mme.cpp b/src/spice2x/hooks/audio/mme.cpp new file mode 100644 index 0000000..7066995 --- /dev/null +++ b/src/spice2x/hooks/audio/mme.cpp @@ -0,0 +1,27 @@ +#include "mme.h" +#include + +#include "util/detour.h" +#include "util/logging.h" + +namespace hooks::audio::mme { + + static decltype(mixerSetControlDetails) *mixerSetControlDetails_orig = nullptr; + + MMRESULT + WINAPI + mixerSetControlDetails_hook( + HMIXEROBJ hmxobj, LPMIXERCONTROLDETAILS pmxcd, DWORD fdwDetails) { + log_misc("audio::mme", "mixerSetControlDetails_hook called; ignoring volume change"); + return MMSYSERR_NOERROR; + } + + void init(HINSTANCE module) { + mixerSetControlDetails_orig = + detour::iat_try("mixerSetControlDetails", mixerSetControlDetails_hook, module); + + if (mixerSetControlDetails_orig != nullptr) { + log_misc("audio::mme", "mixerSetControlDetails hooked"); + } + } +} diff --git a/src/spice2x/hooks/audio/mme.h b/src/spice2x/hooks/audio/mme.h new file mode 100644 index 0000000..002ab15 --- /dev/null +++ b/src/spice2x/hooks/audio/mme.h @@ -0,0 +1,7 @@ +#pragma once + +#include + +namespace hooks::audio::mme { + void init(HINSTANCE module); +} diff --git a/src/spice2x/hooks/devicehook.cpp b/src/spice2x/hooks/devicehook.cpp index 5dc072b..9f47b0a 100644 --- a/src/spice2x/hooks/devicehook.cpp +++ b/src/spice2x/hooks/devicehook.cpp @@ -3,6 +3,7 @@ #include #include "avs/game.h" +#include "games/gitadora/gitadora.h" #include "util/detour.h" #include "util/utils.h" @@ -401,7 +402,7 @@ static BOOL WINAPI ClearCommError_hook(HANDLE hFile, LPDWORD lpErrors, LPCOMSTAT // gitadora arena model needs this, or else // the game will keep spamming 0xAA - if (avs::game::is_model("M32") && (avs::game::SPEC[0] == 'C' || avs::game::SPEC[0] == 'D') && lpErrors) { + if (games::gitadora::is_arena_model() && lpErrors) { *lpErrors = 0; }