gitadora: arena cab volume change prevention (#478)

## Link to GitHub Issue, if one exists
#477 

## Description of change
Arena cab has two sound cards and it uses `WrappedIMMDeviceCollection`
to set the volume to 100% on boot on both, instead of the typical
`GetDefaultAudioEndpoint`.

## Testing
Testing Delta, testing other games as well as they are affected.
This commit is contained in:
bicarus-dev
2025-12-29 03:23:58 -08:00
committed by GitHub
parent 07a0df5fdc
commit 21f469b260
10 changed files with 130 additions and 3 deletions
+2
View File
@@ -477,10 +477,12 @@ set(SOURCE_FILES ${SOURCE_FILES}
hooks/audio/acm.cpp hooks/audio/acm.cpp
hooks/audio/audio.cpp hooks/audio/audio.cpp
hooks/audio/buffer.cpp hooks/audio/buffer.cpp
hooks/audio/mme.cpp
hooks/audio/util.cpp hooks/audio/util.cpp
hooks/audio/backends/dsound/dsound_backend.cpp hooks/audio/backends/dsound/dsound_backend.cpp
hooks/audio/backends/mmdevice/audio_endpoint_volume.cpp hooks/audio/backends/mmdevice/audio_endpoint_volume.cpp
hooks/audio/backends/mmdevice/device.cpp hooks/audio/backends/mmdevice/device.cpp
hooks/audio/backends/mmdevice/device_collection.cpp
hooks/audio/backends/mmdevice/device_enumerator.cpp hooks/audio/backends/mmdevice/device_enumerator.cpp
hooks/audio/backends/wasapi/audio_client.cpp hooks/audio/backends/wasapi/audio_client.cpp
hooks/audio/backends/wasapi/audio_render_client.cpp hooks/audio/backends/wasapi/audio_render_client.cpp
+4
View File
@@ -3,6 +3,7 @@
#include "bi2x_hook.h" #include "bi2x_hook.h"
#include <unordered_map> #include <unordered_map>
#include "cfg/configurator.h" #include "cfg/configurator.h"
#include "hooks/audio/mme.h"
#include "hooks/graphics/graphics.h" #include "hooks/graphics/graphics.h"
#include "util/cpuutils.h" #include "util/cpuutils.h"
#include "util/detour.h" #include "util/detour.h"
@@ -236,6 +237,9 @@ namespace games::gitadora {
// test/service/coin buttons // test/service/coin buttons
bi2x_hook_init(); bi2x_hook_init();
// volume change prevention
hooks::audio::mme::init(avs::game::DLL_INSTANCE);
return; return;
} }
+7
View File
@@ -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')
);
}
} }
@@ -8,6 +8,7 @@
#include "hooks/audio/audio_private.h" #include "hooks/audio/audio_private.h"
#include "hooks/audio/backends/mmdevice/audio_endpoint_volume.h" #include "hooks/audio/backends/mmdevice/audio_endpoint_volume.h"
#include "hooks/audio/backends/wasapi/audio_client.h" #include "hooks/audio/backends/wasapi/audio_client.h"
#include "util/utils.h"
#define PRINT_FAILED_RESULT(name, ret) \ #define PRINT_FAILED_RESULT(name, ret) \
do { \ do { \
@@ -56,6 +57,7 @@ ULONG STDMETHODCALLTYPE WrappedIMMDevice::Release() {
ULONG refs = pReal != nullptr ? pReal->Release() : 0; ULONG refs = pReal != nullptr ? pReal->Release() : 0;
if (refs == 0) { if (refs == 0) {
log_misc("audio::mmdevice", "WrappedIMMDevice::Release");
delete this; delete this;
} }
@@ -69,7 +71,7 @@ HRESULT STDMETHODCALLTYPE WrappedIMMDevice::Activate(
PROPVARIANT *pActivationParams, PROPVARIANT *pActivationParams,
void **ppInterface) void **ppInterface)
{ {
log_misc("audio::mmdevice", "WrappedIMMDevice::Activate"); log_misc("audio::mmdevice", "WrappedIMMDevice::Activate {}", guid2s(iid));
// call original // call original
HRESULT ret = pReal->Activate(iid, dwClsCtx, pActivationParams, ppInterface); HRESULT ret = pReal->Activate(iid, dwClsCtx, pActivationParams, ppInterface);
@@ -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;
}
@@ -0,0 +1,28 @@
#pragma once
#include <initguid.h>
#include <mmdeviceapi.h>
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;
};
@@ -1,4 +1,5 @@
#include "device_enumerator.h" #include "device_enumerator.h"
#include "device_collection.h"
#include "device.h" #include "device.h"
#include "util/utils.h" #include "util/utils.h"
@@ -42,7 +43,11 @@ HRESULT STDMETHODCALLTYPE WrappedIMMDeviceEnumerator::EnumAudioEndpoints(
DWORD dwStateMask, DWORD dwStateMask,
IMMDeviceCollection **ppDevices) 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( HRESULT STDMETHODCALLTYPE WrappedIMMDeviceEnumerator::GetDefaultAudioEndpoint(
+27
View File
@@ -0,0 +1,27 @@
#include "mme.h"
#include <mmeapi.h>
#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");
}
}
}
+7
View File
@@ -0,0 +1,7 @@
#pragma once
#include <windows.h>
namespace hooks::audio::mme {
void init(HINSTANCE module);
}
+2 -1
View File
@@ -3,6 +3,7 @@
#include <vector> #include <vector>
#include "avs/game.h" #include "avs/game.h"
#include "games/gitadora/gitadora.h"
#include "util/detour.h" #include "util/detour.h"
#include "util/utils.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 // gitadora arena model needs this, or else
// the game will keep spamming 0xAA // 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; *lpErrors = 0;
} }