mirror of
https://github.com/spice2x/spice2x.github.io.git
synced 2026-08-02 23:00:42 -07:00
61a0220c1a
## Link to GitHub Issue or related Pull Request, if one exists Fixes #782 ## Description of change This addresses an issue only seen on Linux + WINE. Defer retaining WASAPI clients until `Initialize` succeeds. Before this PR, temporary clients created during DirectShow device capability probing were retained as active audio clients, causing Wine/Linux crashes in DDR and popn. Also covers IAudioClient3, dummy, ASIO, WaveOut, and null-device initialization paths, for consistency. ## Testing WIP
186 lines
5.7 KiB
C++
186 lines
5.7 KiB
C++
#include "null_device.h"
|
|
|
|
#include <atomic>
|
|
#include <cstring>
|
|
|
|
#include <audioclient.h>
|
|
|
|
#include "hooks/audio/audio.h"
|
|
#include "hooks/audio/audio_private.h"
|
|
#include "hooks/audio/backends/wasapi/dummy_audio_client.h"
|
|
#include "util/logging.h"
|
|
#include "util/utils.h"
|
|
|
|
#include "null_discard_backend.h"
|
|
|
|
// friendly name reported by the synthetic device. must contain "Realtek" so the
|
|
// gitadora arena device search matches it.
|
|
static const wchar_t NULL_DEVICE_FRIENDLY_NAME[] = L"Realtek High Definition Audio";
|
|
|
|
// arbitrary identifier reported by the synthetic device.
|
|
static const wchar_t NULL_DEVICE_ID[] = L"{spice2x-null-render-device}";
|
|
|
|
// PKEY_Device_FriendlyName, hardcoded to avoid pulling in functiondiscoverykeys_devpkey.h
|
|
static const PROPERTYKEY PKEY_DEVICE_FRIENDLY_NAME_LOCAL = {
|
|
{ 0xa45c254e, 0xdf1c, 0x4efd, { 0x80, 0x20, 0x67, 0xd1, 0x46, 0xa8, 0x50, 0xe0 } },
|
|
14
|
|
};
|
|
|
|
bool null_render_device_enabled() {
|
|
return hooks::audio::INJECT_FAKE_REALTEK_AUDIO;
|
|
}
|
|
|
|
// duplicate a wide string into CoTaskMem so the caller can free it with
|
|
// CoTaskMemFree / PropVariantClear as the COM API contract requires.
|
|
static LPWSTR co_task_wcsdup(const wchar_t *src) {
|
|
const size_t bytes = (wcslen(src) + 1) * sizeof(wchar_t);
|
|
auto *dst = static_cast<LPWSTR>(CoTaskMemAlloc(bytes));
|
|
if (dst != nullptr) {
|
|
memcpy(dst, src, bytes);
|
|
}
|
|
return dst;
|
|
}
|
|
|
|
namespace {
|
|
|
|
// minimal IPropertyStore that only answers PKEY_Device_FriendlyName.
|
|
struct NullPropertyStore : IPropertyStore {
|
|
std::atomic<ULONG> ref_cnt = 1;
|
|
|
|
virtual ~NullPropertyStore() = default;
|
|
|
|
HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, void **ppvObj) override {
|
|
if (ppvObj == nullptr) {
|
|
return E_POINTER;
|
|
}
|
|
if (riid == __uuidof(IUnknown) || riid == __uuidof(IPropertyStore)) {
|
|
this->AddRef();
|
|
*ppvObj = this;
|
|
return S_OK;
|
|
}
|
|
*ppvObj = nullptr;
|
|
return E_NOINTERFACE;
|
|
}
|
|
ULONG STDMETHODCALLTYPE AddRef() override {
|
|
return ++this->ref_cnt;
|
|
}
|
|
ULONG STDMETHODCALLTYPE Release() override {
|
|
const ULONG refs = --this->ref_cnt;
|
|
if (refs == 0) {
|
|
delete this;
|
|
}
|
|
return refs;
|
|
}
|
|
HRESULT STDMETHODCALLTYPE GetCount(DWORD *cProps) override {
|
|
if (cProps == nullptr) {
|
|
return E_POINTER;
|
|
}
|
|
*cProps = 1;
|
|
return S_OK;
|
|
}
|
|
HRESULT STDMETHODCALLTYPE GetAt(DWORD iProp, PROPERTYKEY *pkey) override {
|
|
if (pkey == nullptr) {
|
|
return E_POINTER;
|
|
}
|
|
if (iProp != 0) {
|
|
return E_INVALIDARG;
|
|
}
|
|
*pkey = PKEY_DEVICE_FRIENDLY_NAME_LOCAL;
|
|
return S_OK;
|
|
}
|
|
HRESULT STDMETHODCALLTYPE GetValue(REFPROPERTYKEY key, PROPVARIANT *pv) override {
|
|
if (pv == nullptr) {
|
|
return E_POINTER;
|
|
}
|
|
PropVariantInit(pv);
|
|
if (key.fmtid == PKEY_DEVICE_FRIENDLY_NAME_LOCAL.fmtid
|
|
&& key.pid == PKEY_DEVICE_FRIENDLY_NAME_LOCAL.pid) {
|
|
pv->pwszVal = co_task_wcsdup(NULL_DEVICE_FRIENDLY_NAME);
|
|
if (pv->pwszVal == nullptr) {
|
|
return E_OUTOFMEMORY;
|
|
}
|
|
pv->vt = VT_LPWSTR;
|
|
}
|
|
// unknown keys are returned as VT_EMPTY / S_OK
|
|
return S_OK;
|
|
}
|
|
HRESULT STDMETHODCALLTYPE SetValue(REFPROPERTYKEY, REFPROPVARIANT) override {
|
|
return STG_E_ACCESSDENIED;
|
|
}
|
|
HRESULT STDMETHODCALLTYPE Commit() override {
|
|
return S_OK;
|
|
}
|
|
};
|
|
}
|
|
|
|
#pragma region IUnknown
|
|
HRESULT STDMETHODCALLTYPE NullMMDevice::QueryInterface(REFIID riid, void **ppvObj) {
|
|
if (ppvObj == nullptr) {
|
|
return E_POINTER;
|
|
}
|
|
if (riid == __uuidof(IUnknown) || riid == __uuidof(IMMDevice)) {
|
|
this->AddRef();
|
|
*ppvObj = this;
|
|
return S_OK;
|
|
}
|
|
*ppvObj = nullptr;
|
|
return E_NOINTERFACE;
|
|
}
|
|
ULONG STDMETHODCALLTYPE NullMMDevice::AddRef() {
|
|
return ++this->ref_cnt;
|
|
}
|
|
ULONG STDMETHODCALLTYPE NullMMDevice::Release() {
|
|
const ULONG refs = --this->ref_cnt;
|
|
if (refs == 0) {
|
|
delete this;
|
|
}
|
|
return refs;
|
|
}
|
|
#pragma endregion
|
|
|
|
#pragma region IMMDevice
|
|
HRESULT STDMETHODCALLTYPE NullMMDevice::Activate(
|
|
REFIID iid,
|
|
DWORD,
|
|
PROPVARIANT *,
|
|
void **ppInterface)
|
|
{
|
|
if (ppInterface == nullptr) {
|
|
return E_POINTER;
|
|
}
|
|
*ppInterface = nullptr;
|
|
|
|
log_info("audio::null", "NullMMDevice::Activate {}", guid2s(iid));
|
|
|
|
if (iid == IID_IAudioClient) {
|
|
auto *client = static_cast<IAudioClient *>(new DummyIAudioClient(new NullDiscardBackend()));
|
|
*ppInterface = client;
|
|
|
|
return S_OK;
|
|
}
|
|
|
|
return E_NOINTERFACE;
|
|
}
|
|
HRESULT STDMETHODCALLTYPE NullMMDevice::OpenPropertyStore(DWORD, IPropertyStore **ppProperties) {
|
|
if (ppProperties == nullptr) {
|
|
return E_POINTER;
|
|
}
|
|
*ppProperties = new NullPropertyStore();
|
|
return S_OK;
|
|
}
|
|
HRESULT STDMETHODCALLTYPE NullMMDevice::GetId(LPWSTR *ppstrId) {
|
|
if (ppstrId == nullptr) {
|
|
return E_POINTER;
|
|
}
|
|
*ppstrId = co_task_wcsdup(NULL_DEVICE_ID);
|
|
return *ppstrId != nullptr ? S_OK : E_OUTOFMEMORY;
|
|
}
|
|
HRESULT STDMETHODCALLTYPE NullMMDevice::GetState(DWORD *pdwState) {
|
|
if (pdwState == nullptr) {
|
|
return E_POINTER;
|
|
}
|
|
*pdwState = DEVICE_STATE_ACTIVE;
|
|
return S_OK;
|
|
}
|
|
#pragma endregion
|