Initial re-upload of spice2x-24-08-24
This commit is contained in:
92
hooks/audio/audio.cpp
Normal file
92
hooks/audio/audio.cpp
Normal file
@@ -0,0 +1,92 @@
|
||||
#include "audio.h"
|
||||
|
||||
#include <vector>
|
||||
|
||||
#include <windows.h>
|
||||
#include <initguid.h>
|
||||
#include <audioclient.h>
|
||||
#include <mmdeviceapi.h>
|
||||
|
||||
#include "hooks/audio/backends/mmdevice/device_enumerator.h"
|
||||
#include "util/detour.h"
|
||||
#include "util/logging.h"
|
||||
#include "util/memutils.h"
|
||||
|
||||
#include "audio_private.h"
|
||||
|
||||
#ifdef _MSC_VER
|
||||
DEFINE_GUID(CLSID_MMDeviceEnumerator,
|
||||
0xBCDE0395, 0xE52F, 0x467C,
|
||||
0x8E, 0x3D, 0xC4, 0x57, 0x92, 0x91, 0x69, 0x2E);
|
||||
#endif
|
||||
|
||||
// function pointers
|
||||
static decltype(CoCreateInstance) *CoCreateInstance_orig = nullptr;
|
||||
|
||||
namespace hooks::audio {
|
||||
|
||||
// public globals
|
||||
bool ENABLED = true;
|
||||
bool VOLUME_HOOK_ENABLED = true;
|
||||
bool USE_DUMMY = false;
|
||||
WAVEFORMATEXTENSIBLE FORMAT {};
|
||||
std::optional<Backend> BACKEND = std::nullopt;
|
||||
size_t ASIO_DRIVER_ID = 0;
|
||||
bool ASIO_FORCE_UNLOAD_ON_STOP = false;
|
||||
|
||||
// private globals
|
||||
IAudioClient *CLIENT = nullptr;
|
||||
std::mutex INITIALIZE_LOCK; // for asio
|
||||
}
|
||||
|
||||
static HRESULT STDAPICALLTYPE CoCreateInstance_hook(
|
||||
REFCLSID rclsid,
|
||||
LPUNKNOWN pUnkOuter,
|
||||
DWORD dwClsContext,
|
||||
REFIID riid,
|
||||
LPVOID *ppv)
|
||||
{
|
||||
// call original
|
||||
HRESULT ret = CoCreateInstance_orig(rclsid, pUnkOuter, dwClsContext, riid, ppv);
|
||||
if (FAILED(ret)) {
|
||||
if (IsEqualCLSID(rclsid, CLSID_MMDeviceEnumerator)) {
|
||||
log_warning("audio", "CoCreateInstance failed, hr={}", FMT_HRESULT(ret));
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
// check if this is the audio device enumerator
|
||||
if (IsEqualCLSID(rclsid, CLSID_MMDeviceEnumerator)) {
|
||||
|
||||
// wrap object
|
||||
auto mmde = reinterpret_cast<IMMDeviceEnumerator **>(ppv);
|
||||
*mmde = new WrappedIMMDeviceEnumerator(*mmde);
|
||||
}
|
||||
|
||||
// return original result
|
||||
return ret;
|
||||
}
|
||||
|
||||
namespace hooks::audio {
|
||||
void init() {
|
||||
if (!ENABLED) {
|
||||
return;
|
||||
}
|
||||
|
||||
log_info("audio", "initializing");
|
||||
init_low_latency();
|
||||
|
||||
// general hooks
|
||||
CoCreateInstance_orig = detour::iat_try("CoCreateInstance", CoCreateInstance_hook);
|
||||
}
|
||||
|
||||
void stop() {
|
||||
log_info("audio", "stopping");
|
||||
if (CLIENT) {
|
||||
CLIENT->Stop();
|
||||
CLIENT->Release();
|
||||
CLIENT = nullptr;
|
||||
}
|
||||
stop_low_latency();
|
||||
}
|
||||
}
|
||||
39
hooks/audio/audio.h
Normal file
39
hooks/audio/audio.h
Normal file
@@ -0,0 +1,39 @@
|
||||
#pragma once
|
||||
|
||||
#include <optional>
|
||||
|
||||
#include <windows.h>
|
||||
#include <mmreg.h>
|
||||
#ifndef _MSC_VER
|
||||
#include <ks.h>
|
||||
#include <ksmedia.h>
|
||||
#endif
|
||||
|
||||
namespace hooks::audio {
|
||||
enum class Backend {
|
||||
Asio,
|
||||
WaveOut,
|
||||
};
|
||||
|
||||
extern bool ENABLED;
|
||||
extern bool VOLUME_HOOK_ENABLED;
|
||||
extern bool USE_DUMMY;
|
||||
extern WAVEFORMATEXTENSIBLE FORMAT;
|
||||
extern std::optional<Backend> BACKEND;
|
||||
extern size_t ASIO_DRIVER_ID;
|
||||
extern bool ASIO_FORCE_UNLOAD_ON_STOP;
|
||||
extern bool LOW_LATENCY_SHARED_WASAPI;
|
||||
|
||||
void init();
|
||||
void stop();
|
||||
|
||||
inline std::optional<Backend> name_to_backend(const char *value) {
|
||||
if (_stricmp(value, "asio") == 0) {
|
||||
return Backend::Asio;
|
||||
} else if (_stricmp(value, "waveout") == 0) {
|
||||
return Backend::WaveOut;
|
||||
}
|
||||
|
||||
return std::nullopt;
|
||||
}
|
||||
}
|
||||
17
hooks/audio/audio_private.h
Normal file
17
hooks/audio/audio_private.h
Normal file
@@ -0,0 +1,17 @@
|
||||
#pragma once
|
||||
|
||||
#include <mutex>
|
||||
|
||||
#include <windows.h>
|
||||
#include <audioclient.h>
|
||||
#include "hooks/audio/backends/wasapi/low_latency_client.h"
|
||||
|
||||
constexpr bool AUDIO_LOG_HRESULT = true;
|
||||
|
||||
namespace hooks::audio {
|
||||
|
||||
extern IAudioClient *CLIENT;
|
||||
extern std::mutex INITIALIZE_LOCK;
|
||||
extern bool VOLUME_HOOK_ENABLED;
|
||||
extern LowLatencyAudioClient *LOW_LATENCY_CLIENT;
|
||||
}
|
||||
240
hooks/audio/backends/dsound/dsound_backend.cpp
Normal file
240
hooks/audio/backends/dsound/dsound_backend.cpp
Normal file
@@ -0,0 +1,240 @@
|
||||
#include "dsound_backend.h"
|
||||
|
||||
#include <dsound.h>
|
||||
#include "util/detour.h"
|
||||
#include "util/libutils.h"
|
||||
#include "util/logging.h"
|
||||
|
||||
static void *DIRECT_SOUND_CREATE8_ADR = nullptr;
|
||||
static char DIRECT_SOUND_CREATE8_CONTENTS[16];
|
||||
static decltype(DirectSoundCreate8) *DirectSoundCreate8_orig = nullptr;
|
||||
|
||||
static HRESULT WINAPI DirectSoundCreate8_hook(LPCGUID lpGUID, LPDIRECTSOUND8 *ppDS8, LPUNKNOWN pUnkOuter) {
|
||||
log_misc("audio::dsound", "DirectSoundCreate8 hook hit");
|
||||
|
||||
// remove hook
|
||||
if (DIRECT_SOUND_CREATE8_ADR) {
|
||||
detour::inline_restore(DIRECT_SOUND_CREATE8_ADR, DIRECT_SOUND_CREATE8_CONTENTS);
|
||||
|
||||
if (DirectSoundCreate8_orig == nullptr) {
|
||||
DirectSoundCreate8_orig = reinterpret_cast<decltype(DirectSoundCreate8) *>(DIRECT_SOUND_CREATE8_ADR);
|
||||
}
|
||||
}
|
||||
|
||||
HRESULT result = DirectSoundCreate8_orig(lpGUID, ppDS8, pUnkOuter);
|
||||
if (result != DS_OK) {
|
||||
log_warning("audio::dsound", "failed to create DirectSound interface");
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
*ppDS8 = new WrappedIDirectSound8(*ppDS8);
|
||||
|
||||
// add hook
|
||||
if (DIRECT_SOUND_CREATE8_ADR) {
|
||||
detour::inline_noprotect((void *) DirectSoundCreate8_hook, DIRECT_SOUND_CREATE8_ADR);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
#pragma region WrappedIDirectSound8
|
||||
__declspec (nothrow) HRESULT WINAPI WrappedIDirectSound8::QueryInterface(const IID &riid, void **ppvObject) {
|
||||
return pReal->QueryInterface(riid, ppvObject);
|
||||
}
|
||||
|
||||
__declspec (nothrow) ULONG WINAPI WrappedIDirectSound8::AddRef() {
|
||||
return pReal->AddRef();
|
||||
}
|
||||
|
||||
__declspec (nothrow) ULONG WINAPI WrappedIDirectSound8::Release() {
|
||||
return pReal->Release();
|
||||
}
|
||||
|
||||
__declspec (nothrow) HRESULT WINAPI WrappedIDirectSound8::CreateSoundBuffer(
|
||||
LPCDSBUFFERDESC lpcDSBufferDesc,
|
||||
LPLPDIRECTSOUNDBUFFER lplpDirectSoundBuffer,
|
||||
IUnknown *pUnkOuter)
|
||||
{
|
||||
HRESULT result = pReal->CreateSoundBuffer(lpcDSBufferDesc, lplpDirectSoundBuffer, pUnkOuter);
|
||||
if (result != DS_OK) {
|
||||
log_warning("audio::dsound", "failed to create sound buffer");
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
*lplpDirectSoundBuffer = new WrappedIDirectSoundBuffer(*lplpDirectSoundBuffer);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
__declspec (nothrow) HRESULT WINAPI WrappedIDirectSound8::GetCaps(LPDSCAPS lpDSCaps) {
|
||||
return pReal->GetCaps(lpDSCaps);
|
||||
}
|
||||
|
||||
__declspec (nothrow) HRESULT WINAPI WrappedIDirectSound8::DuplicateSoundBuffer(
|
||||
LPDIRECTSOUNDBUFFER lpDsbOriginal,
|
||||
LPLPDIRECTSOUNDBUFFER lplpDsbDuplicate)
|
||||
{
|
||||
return pReal->DuplicateSoundBuffer(lpDsbOriginal, lplpDsbDuplicate);
|
||||
}
|
||||
|
||||
__declspec (nothrow) HRESULT WINAPI WrappedIDirectSound8::SetCooperativeLevel(HWND hwnd, DWORD dwLevel) {
|
||||
return pReal->SetCooperativeLevel(hwnd, dwLevel);
|
||||
}
|
||||
|
||||
__declspec (nothrow) HRESULT WINAPI WrappedIDirectSound8::Compact() {
|
||||
return pReal->Compact();
|
||||
}
|
||||
|
||||
__declspec (nothrow) HRESULT WINAPI WrappedIDirectSound8::GetSpeakerConfig(LPDWORD lpdwSpeakerConfig) {
|
||||
return pReal->GetSpeakerConfig(lpdwSpeakerConfig);
|
||||
}
|
||||
|
||||
__declspec (nothrow) HRESULT WINAPI WrappedIDirectSound8::SetSpeakerConfig(DWORD dwSpeakerConfig) {
|
||||
return pReal->SetSpeakerConfig(dwSpeakerConfig);
|
||||
}
|
||||
|
||||
__declspec (nothrow) HRESULT WINAPI WrappedIDirectSound8::Initialize(LPCGUID lpcGuid) {
|
||||
return pReal->Initialize(lpcGuid);
|
||||
}
|
||||
|
||||
__declspec (nothrow) HRESULT WINAPI WrappedIDirectSound8::VerifyCertification(LPDWORD pdwCertified) {
|
||||
return pReal->VerifyCertification(pdwCertified);
|
||||
}
|
||||
#pragma endregion
|
||||
|
||||
#pragma region WrappedIDirectSoundBuffer
|
||||
__declspec (nothrow) HRESULT WINAPI WrappedIDirectSoundBuffer::QueryInterface(const IID &riid, void **ppvObject) {
|
||||
return pReal->QueryInterface(riid, ppvObject);
|
||||
}
|
||||
|
||||
__declspec (nothrow) ULONG WINAPI WrappedIDirectSoundBuffer::AddRef() {
|
||||
return pReal->AddRef();
|
||||
}
|
||||
|
||||
__declspec (nothrow) ULONG WINAPI WrappedIDirectSoundBuffer::Release() {
|
||||
return pReal->Release();
|
||||
}
|
||||
|
||||
__declspec (nothrow) HRESULT WINAPI WrappedIDirectSoundBuffer::GetCaps(LPDSBCAPS lpDSBufferCaps) {
|
||||
return pReal->GetCaps(lpDSBufferCaps);
|
||||
}
|
||||
|
||||
__declspec (nothrow) HRESULT WINAPI WrappedIDirectSoundBuffer::GetCurrentPosition(
|
||||
LPDWORD lpdwCurrentPlayCursor,
|
||||
LPDWORD lpdwCurrentWriteCursor)
|
||||
{
|
||||
return pReal->GetCurrentPosition(lpdwCurrentPlayCursor, lpdwCurrentWriteCursor);
|
||||
}
|
||||
|
||||
__declspec (nothrow) HRESULT WINAPI WrappedIDirectSoundBuffer::GetFormat(
|
||||
LPWAVEFORMATEX lpwfxFormat,
|
||||
DWORD dwSizeAllocated,
|
||||
LPDWORD lpdwSizeWritten)
|
||||
{
|
||||
return pReal->GetFormat(lpwfxFormat, dwSizeAllocated, lpdwSizeWritten);
|
||||
}
|
||||
|
||||
__declspec (nothrow) HRESULT WINAPI WrappedIDirectSoundBuffer::GetVolume(LPLONG lplVolume) {
|
||||
return pReal->GetVolume(lplVolume);
|
||||
}
|
||||
|
||||
__declspec (nothrow) HRESULT WINAPI WrappedIDirectSoundBuffer::GetPan(LPLONG lplpan) {
|
||||
return pReal->GetPan(lplpan);
|
||||
}
|
||||
|
||||
__declspec (nothrow) HRESULT WINAPI WrappedIDirectSoundBuffer::GetFrequency(LPDWORD lpdwFrequency) {
|
||||
return pReal->GetFrequency(lpdwFrequency);
|
||||
}
|
||||
|
||||
__declspec (nothrow) HRESULT WINAPI WrappedIDirectSoundBuffer::GetStatus(LPDWORD lpdwStatus) {
|
||||
return pReal->GetStatus(lpdwStatus);
|
||||
}
|
||||
|
||||
__declspec (nothrow) HRESULT WINAPI WrappedIDirectSoundBuffer::Initialize(
|
||||
LPDIRECTSOUND lpDirectSound,
|
||||
LPCDSBUFFERDESC lpcDSBufferDesc)
|
||||
{
|
||||
return pReal->Initialize(lpDirectSound, lpcDSBufferDesc);
|
||||
}
|
||||
|
||||
__declspec (nothrow) HRESULT WINAPI WrappedIDirectSoundBuffer::Lock(
|
||||
DWORD dwOffset,
|
||||
DWORD dwBytes,
|
||||
LPVOID *ppvAudioPtr1,
|
||||
LPDWORD pdwAudioBytes1,
|
||||
LPVOID *ppvAudioPtr2,
|
||||
LPDWORD pdwAudioBytes2,
|
||||
DWORD dwFlags)
|
||||
{
|
||||
return pReal->Lock(dwOffset, dwBytes, ppvAudioPtr1, pdwAudioBytes1, ppvAudioPtr2, pdwAudioBytes2, dwFlags);
|
||||
}
|
||||
|
||||
__declspec (nothrow) HRESULT WINAPI WrappedIDirectSoundBuffer::Play(
|
||||
DWORD dwReserved1,
|
||||
DWORD dwReserved2,
|
||||
DWORD dwFlags)
|
||||
{
|
||||
return pReal->Play(dwReserved1, dwReserved2, dwFlags);
|
||||
}
|
||||
|
||||
__declspec (nothrow) HRESULT WINAPI WrappedIDirectSoundBuffer::SetCurrentPosition(DWORD dwNewPosition) {
|
||||
return pReal->SetCurrentPosition(dwNewPosition);
|
||||
}
|
||||
|
||||
__declspec (nothrow) HRESULT WINAPI WrappedIDirectSoundBuffer::SetFormat(LPCWAVEFORMATEX lpcfxFormat) {
|
||||
HRESULT result = pReal->SetFormat(lpcfxFormat);
|
||||
// for KBR, MBR
|
||||
if (result == DSERR_ALLOCATED) {
|
||||
log_info("audio::dsound", "WrappedIDirectSoundBuffer::SetFormat returned DSERR_ALLOCATED");
|
||||
return DS_OK;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
__declspec (nothrow) HRESULT WINAPI WrappedIDirectSoundBuffer::SetVolume(LONG lVolume) {
|
||||
return pReal->SetVolume(lVolume);
|
||||
}
|
||||
|
||||
__declspec (nothrow) HRESULT WINAPI WrappedIDirectSoundBuffer::SetPan(LONG lPan) {
|
||||
return pReal->SetPan(lPan);
|
||||
}
|
||||
|
||||
__declspec (nothrow) HRESULT WINAPI WrappedIDirectSoundBuffer::SetFrequency(DWORD dwFrequency) {
|
||||
return pReal->SetFrequency(dwFrequency);
|
||||
}
|
||||
|
||||
__declspec (nothrow) HRESULT WINAPI WrappedIDirectSoundBuffer::Stop() {
|
||||
return pReal->Stop();
|
||||
}
|
||||
|
||||
__declspec (nothrow) HRESULT WINAPI WrappedIDirectSoundBuffer::Unlock(
|
||||
LPVOID pvAudioPtr1,
|
||||
DWORD dwAudioBytes1,
|
||||
LPVOID pvAudioPtr2,
|
||||
DWORD dwAudioPtr2)
|
||||
{
|
||||
return pReal->Unlock(pvAudioPtr1, dwAudioBytes1, pvAudioPtr2, dwAudioPtr2);
|
||||
}
|
||||
|
||||
__declspec (nothrow) HRESULT WINAPI WrappedIDirectSoundBuffer::Restore() {
|
||||
return pReal->Restore();
|
||||
}
|
||||
#pragma endregion
|
||||
|
||||
void audio_dsound_init() {
|
||||
log_info("audio::dsound", "initializing");
|
||||
|
||||
// dsound inline hooks
|
||||
HMODULE dsound = libutils::try_module("dsound.dll");
|
||||
if (!dsound) {
|
||||
log_info("audio::dsound", "skipping inline hooks");
|
||||
} else {
|
||||
DIRECT_SOUND_CREATE8_ADR = (void *) libutils::get_proc(dsound, "DirectSoundCreate8");
|
||||
detour::inline_preserve(reinterpret_cast<void *>(DirectSoundCreate8_hook), DIRECT_SOUND_CREATE8_ADR, DIRECT_SOUND_CREATE8_CONTENTS);
|
||||
}
|
||||
|
||||
DirectSoundCreate8_orig = detour::iat_try("DirectSoundCreate8", DirectSoundCreate8_hook);
|
||||
}
|
||||
76
hooks/audio/backends/dsound/dsound_backend.h
Normal file
76
hooks/audio/backends/dsound/dsound_backend.h
Normal file
@@ -0,0 +1,76 @@
|
||||
#pragma once
|
||||
|
||||
#include <windows.h>
|
||||
#include <mmsystem.h>
|
||||
#include <dsound.h>
|
||||
|
||||
void audio_dsound_init();
|
||||
|
||||
struct WrappedIDirectSound8 : IDirectSound8 {
|
||||
explicit WrappedIDirectSound8(IDirectSound8 *orig) : pReal(orig) {}
|
||||
|
||||
WrappedIDirectSound8(const WrappedIDirectSound8 &) = delete;
|
||||
WrappedIDirectSound8 &operator=(const WrappedIDirectSound8 &) = delete;
|
||||
|
||||
virtual ~WrappedIDirectSound8() = default;
|
||||
|
||||
#pragma region IUnknown
|
||||
__declspec (nothrow) HRESULT WINAPI QueryInterface(REFIID riid, void** ppvObject) override;
|
||||
__declspec (nothrow) ULONG WINAPI AddRef() override;
|
||||
__declspec (nothrow) ULONG WINAPI Release() override;
|
||||
#pragma endregion
|
||||
|
||||
#pragma region IDirectSound8 methods
|
||||
__declspec (nothrow) HRESULT WINAPI CreateSoundBuffer(LPCDSBUFFERDESC lpcDSBufferDesc, LPLPDIRECTSOUNDBUFFER lplpDirectSoundBuffer, IUnknown *pUnkOuter) override;
|
||||
__declspec (nothrow) HRESULT WINAPI GetCaps(LPDSCAPS lpDSCaps) override;
|
||||
__declspec (nothrow) HRESULT WINAPI DuplicateSoundBuffer(LPDIRECTSOUNDBUFFER lpDsbOriginal, LPLPDIRECTSOUNDBUFFER lplpDsbDuplicate) override;
|
||||
__declspec (nothrow) HRESULT WINAPI SetCooperativeLevel(HWND hwnd, DWORD dwLevel) override;
|
||||
__declspec (nothrow) HRESULT WINAPI Compact() override;
|
||||
__declspec (nothrow) HRESULT WINAPI GetSpeakerConfig(LPDWORD lpdwSpeakerConfig) override;
|
||||
__declspec (nothrow) HRESULT WINAPI SetSpeakerConfig(DWORD dwSpeakerConfig) override;
|
||||
__declspec (nothrow) HRESULT WINAPI Initialize(LPCGUID lpcGuid) override;
|
||||
__declspec (nothrow) HRESULT WINAPI VerifyCertification(LPDWORD pdwCertified) override;
|
||||
#pragma endregion
|
||||
|
||||
private:
|
||||
IDirectSound8 *pReal;
|
||||
};
|
||||
|
||||
struct WrappedIDirectSoundBuffer : IDirectSoundBuffer {
|
||||
explicit WrappedIDirectSoundBuffer(IDirectSoundBuffer *orig) : pReal(orig) {}
|
||||
|
||||
WrappedIDirectSoundBuffer(const WrappedIDirectSound8 &) = delete;
|
||||
WrappedIDirectSoundBuffer &operator=(const WrappedIDirectSoundBuffer &) = delete;
|
||||
|
||||
virtual ~WrappedIDirectSoundBuffer() = default;
|
||||
|
||||
#pragma region IUnknown
|
||||
__declspec (nothrow) HRESULT WINAPI QueryInterface(REFIID riid, void** ppvObject) override;
|
||||
__declspec (nothrow) ULONG WINAPI AddRef() override;
|
||||
__declspec (nothrow) ULONG WINAPI Release() override;
|
||||
#pragma endregion
|
||||
|
||||
#pragma region IDirectSoundBuffer methods
|
||||
__declspec (nothrow) HRESULT WINAPI GetCaps(LPDSBCAPS lpDSBufferCaps) override;
|
||||
__declspec (nothrow) HRESULT WINAPI GetCurrentPosition(LPDWORD lpdwCurrentPlayCursor, LPDWORD lpdwCurrentWriteCursor) override;
|
||||
__declspec (nothrow) HRESULT WINAPI GetFormat(LPWAVEFORMATEX lpwfxFormat, DWORD dwSizeAllocated, LPDWORD lpdwSizeWritten) override;
|
||||
__declspec (nothrow) HRESULT WINAPI GetVolume(LPLONG lplVolume) override;
|
||||
__declspec (nothrow) HRESULT WINAPI GetPan(LPLONG lplpan) override;
|
||||
__declspec (nothrow) HRESULT WINAPI GetFrequency(LPDWORD lpdwFrequency) override;
|
||||
__declspec (nothrow) HRESULT WINAPI GetStatus(LPDWORD lpdwStatus) override;
|
||||
__declspec (nothrow) HRESULT WINAPI Initialize(LPDIRECTSOUND lpDirectSound, LPCDSBUFFERDESC lpcDSBufferDesc) override;
|
||||
__declspec (nothrow) HRESULT WINAPI Lock(DWORD dwOffset, DWORD dwBytes, LPVOID *ppvAudioPtr1, LPDWORD pdwAudioBytes1, LPVOID *ppvAudioPtr2, LPDWORD pdwAudioBytes2, DWORD dwFlags) override;
|
||||
__declspec (nothrow) HRESULT WINAPI Play(DWORD dwReserved1, DWORD dwReserved2, DWORD dwFlags) override;
|
||||
__declspec (nothrow) HRESULT WINAPI SetCurrentPosition(DWORD dwNewPosition) override;
|
||||
__declspec (nothrow) HRESULT WINAPI SetFormat(LPCWAVEFORMATEX lpcfxFormat) override;
|
||||
__declspec (nothrow) HRESULT WINAPI SetVolume(LONG lVolume) override;
|
||||
__declspec (nothrow) HRESULT WINAPI SetPan(LONG lPan) override;
|
||||
__declspec (nothrow) HRESULT WINAPI SetFrequency(DWORD dwFrequency) override;
|
||||
__declspec (nothrow) HRESULT WINAPI Stop() override;
|
||||
__declspec (nothrow) HRESULT WINAPI Unlock(LPVOID pvAudioPtr1, DWORD dwAudioBytes1, LPVOID pvAudioPtr2, DWORD dwAudioPtr2) override;
|
||||
__declspec (nothrow) HRESULT WINAPI Restore() override;
|
||||
#pragma endregion
|
||||
|
||||
private:
|
||||
IDirectSoundBuffer *pReal;
|
||||
};
|
||||
112
hooks/audio/backends/mmdevice/audio_endpoint_volume.cpp
Normal file
112
hooks/audio/backends/mmdevice/audio_endpoint_volume.cpp
Normal file
@@ -0,0 +1,112 @@
|
||||
#include "audio_endpoint_volume.h"
|
||||
#include "util/logging.h"
|
||||
|
||||
HRESULT STDMETHODCALLTYPE WrappedIAudioEndpointVolume::QueryInterface(REFIID riid, void **ppvObj) {
|
||||
if (ppvObj == nullptr) {
|
||||
return E_POINTER;
|
||||
}
|
||||
|
||||
if (riid == __uuidof(IAudioEndpointVolume)) {
|
||||
this->AddRef();
|
||||
*ppvObj = this;
|
||||
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
return pReal->QueryInterface(riid, ppvObj);
|
||||
}
|
||||
|
||||
ULONG STDMETHODCALLTYPE WrappedIAudioEndpointVolume::AddRef() {
|
||||
return pReal->AddRef();
|
||||
}
|
||||
|
||||
ULONG STDMETHODCALLTYPE WrappedIAudioEndpointVolume::Release() {
|
||||
|
||||
// get reference count of underlying interface
|
||||
ULONG refs = pReal != nullptr ? pReal->Release() : 0;
|
||||
|
||||
if (refs == 0) {
|
||||
delete this;
|
||||
}
|
||||
|
||||
return refs;
|
||||
}
|
||||
|
||||
HRESULT STDMETHODCALLTYPE WrappedIAudioEndpointVolume::SetMasterVolumeLevelScalar(float fLevel, LPCGUID pguidEventContext) {
|
||||
log_misc("audio", "WrappedIAudioEndpointVolume::SetMasterVolumeLevelScalar called; ignoring volume change");
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
HRESULT STDMETHODCALLTYPE WrappedIAudioEndpointVolume::RegisterControlChangeNotify(IAudioEndpointVolumeCallback *pNotify) {
|
||||
return pReal->RegisterControlChangeNotify(pNotify);
|
||||
}
|
||||
|
||||
HRESULT STDMETHODCALLTYPE WrappedIAudioEndpointVolume::UnregisterControlChangeNotify(IAudioEndpointVolumeCallback *pNotify) {
|
||||
return pReal->UnregisterControlChangeNotify(pNotify);
|
||||
}
|
||||
|
||||
HRESULT STDMETHODCALLTYPE WrappedIAudioEndpointVolume::GetChannelCount(uint32_t *pnChannelCount) {
|
||||
return pReal->GetChannelCount(pnChannelCount);
|
||||
}
|
||||
|
||||
HRESULT STDMETHODCALLTYPE WrappedIAudioEndpointVolume::SetMasterVolumeLevel(float fLevelDB, LPCGUID pguidEventContext) {
|
||||
log_misc("audio", "WrappedIAudioEndpointVolume::SetMasterVolumeLevel called; ignoring volume change");
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
HRESULT STDMETHODCALLTYPE WrappedIAudioEndpointVolume::GetMasterVolumeLevel(float *fLevelDB) {
|
||||
return pReal->GetMasterVolumeLevel(fLevelDB);
|
||||
}
|
||||
|
||||
HRESULT STDMETHODCALLTYPE WrappedIAudioEndpointVolume::GetMasterVolumeLevelScalar(float *fLevel) {
|
||||
return pReal->GetMasterVolumeLevelScalar(fLevel);
|
||||
}
|
||||
|
||||
HRESULT STDMETHODCALLTYPE WrappedIAudioEndpointVolume::SetChannelVolumeLevel(uint32_t nChannel, float fLevelDB, LPCGUID pguidEventContext) {
|
||||
log_misc("audio", "WrappedIAudioEndpointVolume::SetChannelVolumeLevel called; ignoring volume change");
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
HRESULT STDMETHODCALLTYPE WrappedIAudioEndpointVolume::SetChannelVolumeLevelScalar(uint32_t nChannel, float fLevel, LPCGUID pguidEventContext) {
|
||||
log_misc("audio", "WrappedIAudioEndpointVolume::SetChannelVolumeLevelScalar called; ignoring volume change");
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
HRESULT STDMETHODCALLTYPE WrappedIAudioEndpointVolume::GetChannelVolumeLevel(uint32_t nChannel, float *fLevelDB) {
|
||||
return pReal->GetChannelVolumeLevel(nChannel, fLevelDB);
|
||||
}
|
||||
|
||||
HRESULT STDMETHODCALLTYPE WrappedIAudioEndpointVolume::GetChannelVolumeLevelScalar(uint32_t nChannel, float *fLevel) {
|
||||
return pReal->GetChannelVolumeLevelScalar(nChannel, fLevel);
|
||||
}
|
||||
|
||||
HRESULT STDMETHODCALLTYPE WrappedIAudioEndpointVolume::SetMute(WINBOOL bMute, LPCGUID pguidEventContext) {
|
||||
log_misc("audio", "WrappedIAudioEndpointVolume::SetMute called; ignoring volume change");
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
HRESULT STDMETHODCALLTYPE WrappedIAudioEndpointVolume::GetMute(WINBOOL *bMute) {
|
||||
return pReal->GetMute(bMute);
|
||||
}
|
||||
|
||||
HRESULT STDMETHODCALLTYPE WrappedIAudioEndpointVolume::GetVolumeStepInfo(uint32_t *pnStep, uint32_t *pnStepCount) {
|
||||
return pReal->GetVolumeStepInfo(pnStep, pnStepCount);
|
||||
}
|
||||
|
||||
HRESULT STDMETHODCALLTYPE WrappedIAudioEndpointVolume::VolumeStepUp(LPCGUID pguidEventContext) {
|
||||
log_misc("audio", "WrappedIAudioEndpointVolume::VolumeStepUp called; ignoring volume change");
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
HRESULT STDMETHODCALLTYPE WrappedIAudioEndpointVolume::VolumeStepDown(LPCGUID pguidEventContext) {
|
||||
log_misc("audio", "WrappedIAudioEndpointVolume::VolumeStepDown called; ignoring volume change");
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
HRESULT STDMETHODCALLTYPE WrappedIAudioEndpointVolume::QueryHardwareSupport(DWORD *pdwHardwareSupportMask) {
|
||||
return pReal->QueryHardwareSupport(pdwHardwareSupportMask);
|
||||
}
|
||||
|
||||
HRESULT STDMETHODCALLTYPE WrappedIAudioEndpointVolume::GetVolumeRange(float *pflVolumeMindB, float *pflVolumeMaxdB, float *pflVolumeIncrementdB) {
|
||||
return pReal->GetVolumeRange(pflVolumeMindB, pflVolumeMaxdB, pflVolumeIncrementdB);
|
||||
}
|
||||
43
hooks/audio/backends/mmdevice/audio_endpoint_volume.h
Normal file
43
hooks/audio/backends/mmdevice/audio_endpoint_volume.h
Normal file
@@ -0,0 +1,43 @@
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
#include <endpointvolume.h>
|
||||
|
||||
struct WrappedIAudioEndpointVolume : IAudioEndpointVolume {
|
||||
explicit WrappedIAudioEndpointVolume(IAudioEndpointVolume *orig) : pReal(orig) {}
|
||||
|
||||
WrappedIAudioEndpointVolume(const WrappedIAudioEndpointVolume &) = delete;
|
||||
WrappedIAudioEndpointVolume &operator=(const WrappedIAudioEndpointVolume &) = delete;
|
||||
|
||||
virtual ~WrappedIAudioEndpointVolume() = default;
|
||||
|
||||
#pragma region IUnknown
|
||||
HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, void **ppvObj) override;
|
||||
ULONG STDMETHODCALLTYPE AddRef() override;
|
||||
ULONG STDMETHODCALLTYPE Release() override;
|
||||
#pragma endregion
|
||||
|
||||
#pragma region IAudioEndpointVolume
|
||||
HRESULT STDMETHODCALLTYPE RegisterControlChangeNotify(IAudioEndpointVolumeCallback *pNotify) override;
|
||||
HRESULT STDMETHODCALLTYPE UnregisterControlChangeNotify(IAudioEndpointVolumeCallback *pNotify) override;
|
||||
HRESULT STDMETHODCALLTYPE GetChannelCount(uint32_t *pnChannelCount) override;
|
||||
HRESULT STDMETHODCALLTYPE SetMasterVolumeLevel(float fLevelDB, LPCGUID pguidEventContext) override;
|
||||
HRESULT STDMETHODCALLTYPE SetMasterVolumeLevelScalar(float fLevel, LPCGUID pguidEventContext) override;
|
||||
HRESULT STDMETHODCALLTYPE GetMasterVolumeLevel(float *fLevelDB) override;
|
||||
HRESULT STDMETHODCALLTYPE GetMasterVolumeLevelScalar(float *fLevel) override;
|
||||
HRESULT STDMETHODCALLTYPE SetChannelVolumeLevel(uint32_t nChannel, float fLevelDB, LPCGUID pguidEventContext) override;
|
||||
HRESULT STDMETHODCALLTYPE SetChannelVolumeLevelScalar(uint32_t nChannel, float fLevel, LPCGUID pguidEventContext) override;
|
||||
HRESULT STDMETHODCALLTYPE GetChannelVolumeLevel(uint32_t nChannel, float *fLevelDB) override;
|
||||
HRESULT STDMETHODCALLTYPE GetChannelVolumeLevelScalar(uint32_t nChannel, float *fLevel) override;
|
||||
HRESULT STDMETHODCALLTYPE SetMute(WINBOOL bMute, LPCGUID pguidEventContext) override;
|
||||
HRESULT STDMETHODCALLTYPE GetMute(WINBOOL *bMute) override;
|
||||
HRESULT STDMETHODCALLTYPE GetVolumeStepInfo(uint32_t *pnStep, uint32_t *pnStepCount) override;
|
||||
HRESULT STDMETHODCALLTYPE VolumeStepUp(LPCGUID pguidEventContext) override;
|
||||
HRESULT STDMETHODCALLTYPE VolumeStepDown(LPCGUID pguidEventContext) override;
|
||||
HRESULT STDMETHODCALLTYPE QueryHardwareSupport(DWORD *pdwHardwareSupportMask) override;
|
||||
HRESULT STDMETHODCALLTYPE GetVolumeRange(float *pflVolumeMindB, float *pflVolumeMaxdB, float *pflVolumeIncrementdB) override;
|
||||
#pragma endregion
|
||||
|
||||
private:
|
||||
IAudioEndpointVolume *const pReal;
|
||||
};
|
||||
128
hooks/audio/backends/mmdevice/device.cpp
Normal file
128
hooks/audio/backends/mmdevice/device.cpp
Normal file
@@ -0,0 +1,128 @@
|
||||
#include "device.h"
|
||||
|
||||
#include <mutex>
|
||||
|
||||
#include <audioclient.h>
|
||||
#include <endpointvolume.h>
|
||||
|
||||
#include "hooks/audio/audio_private.h"
|
||||
#include "hooks/audio/backends/mmdevice/audio_endpoint_volume.h"
|
||||
#include "hooks/audio/backends/wasapi/audio_client.h"
|
||||
|
||||
#define PRINT_FAILED_RESULT(name, ret) \
|
||||
do { \
|
||||
if (AUDIO_LOG_HRESULT) { \
|
||||
log_warning("audio::mmdevice", "{} failed, hr={}", name, FMT_HRESULT(ret)); \
|
||||
} \
|
||||
} while (0)
|
||||
|
||||
#define CHECK_RESULT(x) \
|
||||
do { \
|
||||
HRESULT __ret = (x); \
|
||||
if (FAILED(__ret)) { \
|
||||
PRINT_FAILED_RESULT(__FUNCTION__, __ret); \
|
||||
} \
|
||||
return __ret; \
|
||||
} while (0)
|
||||
|
||||
#ifdef _MSC_VER
|
||||
DEFINE_GUID(IID_IMMDevice,
|
||||
0xd666063f, 0x1587, 0x4e43,
|
||||
0x81, 0xf1, 0xb9, 0x48, 0xe8, 0x07, 0x36, 0x3f);
|
||||
#endif
|
||||
|
||||
HRESULT STDMETHODCALLTYPE WrappedIMMDevice::QueryInterface(REFIID riid, void **ppvObj) {
|
||||
if (ppvObj == nullptr) {
|
||||
return E_POINTER;
|
||||
}
|
||||
|
||||
if (riid == IID_WrappedIMMDevice ||
|
||||
riid == IID_IMMDevice)
|
||||
{
|
||||
this->AddRef();
|
||||
*ppvObj = this;
|
||||
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
return pReal->QueryInterface(riid, ppvObj);
|
||||
}
|
||||
ULONG STDMETHODCALLTYPE WrappedIMMDevice::AddRef() {
|
||||
return pReal->AddRef();
|
||||
}
|
||||
ULONG STDMETHODCALLTYPE WrappedIMMDevice::Release() {
|
||||
|
||||
// get reference count of underlying interface
|
||||
ULONG refs = pReal != nullptr ? pReal->Release() : 0;
|
||||
|
||||
if (refs == 0) {
|
||||
delete this;
|
||||
}
|
||||
|
||||
return refs;
|
||||
}
|
||||
|
||||
// IMMDevice
|
||||
HRESULT STDMETHODCALLTYPE WrappedIMMDevice::Activate(
|
||||
REFIID iid,
|
||||
DWORD dwClsCtx,
|
||||
PROPVARIANT *pActivationParams,
|
||||
void **ppInterface)
|
||||
{
|
||||
log_misc("audio::mmdevice", "WrappedIMMDevice::Activate");
|
||||
|
||||
// call original
|
||||
HRESULT ret = pReal->Activate(iid, dwClsCtx, pActivationParams, ppInterface);
|
||||
|
||||
// check for failure
|
||||
if (FAILED(ret)) {
|
||||
PRINT_FAILED_RESULT("IMMDevice::Activate", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
if (iid == IID_IAudioClient) {
|
||||
|
||||
// prevent initialization recursion when using some ASIO backends that proxy to DirectSound, WASAPI, or WDM
|
||||
// like ASIO4All or FlexASIO
|
||||
if (!hooks::audio::INITIALIZE_LOCK.try_lock()) {
|
||||
log_warning("audio::mmdevice", "ignoring wrap request while backend is initializing, possible recursion");
|
||||
return ret;
|
||||
}
|
||||
std::lock_guard initialize_guard(hooks::audio::INITIALIZE_LOCK, std::adopt_lock);
|
||||
|
||||
auto client = reinterpret_cast<IAudioClient *>(*ppInterface);
|
||||
|
||||
// release old audio client if initialized
|
||||
if (hooks::audio::CLIENT) {
|
||||
hooks::audio::CLIENT->Release();
|
||||
}
|
||||
|
||||
/*
|
||||
ret = wrap_audio_client(pReal, dwClsCtx, pActivationParams, &client);
|
||||
if (FAILED(ret)) {
|
||||
return ret;
|
||||
}
|
||||
*/
|
||||
client = wrap_audio_client(client);
|
||||
|
||||
*ppInterface = client;
|
||||
|
||||
// persist the audio client
|
||||
hooks::audio::CLIENT = client;
|
||||
hooks::audio::CLIENT->AddRef();
|
||||
|
||||
} else if (iid == __uuidof(IAudioEndpointVolume) && hooks::audio::VOLUME_HOOK_ENABLED) {
|
||||
*ppInterface = new WrappedIAudioEndpointVolume(reinterpret_cast<IAudioEndpointVolume *>(*ppInterface));
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
HRESULT STDMETHODCALLTYPE WrappedIMMDevice::OpenPropertyStore(DWORD stgmAccess, IPropertyStore **ppProperties) {
|
||||
CHECK_RESULT(pReal->OpenPropertyStore(stgmAccess, ppProperties));
|
||||
}
|
||||
HRESULT STDMETHODCALLTYPE WrappedIMMDevice::GetId(LPWSTR *ppstrId) {
|
||||
CHECK_RESULT(pReal->GetId(ppstrId));
|
||||
}
|
||||
HRESULT STDMETHODCALLTYPE WrappedIMMDevice::GetState(DWORD *pdwState) {
|
||||
CHECK_RESULT(pReal->GetState(pdwState));
|
||||
}
|
||||
36
hooks/audio/backends/mmdevice/device.h
Normal file
36
hooks/audio/backends/mmdevice/device.h
Normal file
@@ -0,0 +1,36 @@
|
||||
#pragma once
|
||||
|
||||
#include <initguid.h>
|
||||
#include <mmdeviceapi.h>
|
||||
|
||||
#include "util/logging.h"
|
||||
|
||||
// {7CC2A363-D96F-4BE2-B6CF-2A44AADA424B}
|
||||
static const GUID IID_WrappedIMMDevice = {
|
||||
0x7cc2a363, 0xd96f, 0x4be2, { 0xb6, 0xcf, 0x2a, 0x44, 0xaa, 0xda, 0x42, 0x4b }
|
||||
};
|
||||
|
||||
struct WrappedIMMDevice : IMMDevice {
|
||||
explicit WrappedIMMDevice(IMMDevice *orig) : pReal(orig) {
|
||||
}
|
||||
|
||||
WrappedIMMDevice(const WrappedIMMDevice &) = delete;
|
||||
WrappedIMMDevice &operator=(const WrappedIMMDevice &) = delete;
|
||||
|
||||
virtual ~WrappedIMMDevice() = default;
|
||||
|
||||
#pragma region IUnknown
|
||||
HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, void **ppvObj) override;
|
||||
ULONG STDMETHODCALLTYPE AddRef() override;
|
||||
ULONG STDMETHODCALLTYPE Release() override;
|
||||
#pragma endregion
|
||||
|
||||
#pragma region IMMDevice
|
||||
HRESULT STDMETHODCALLTYPE Activate(REFIID iid, DWORD dwClsCtx, PROPVARIANT *pActivationParams, void **ppInterface) override;
|
||||
HRESULT STDMETHODCALLTYPE OpenPropertyStore(DWORD stgmAccess, IPropertyStore **ppProperties) override;
|
||||
HRESULT STDMETHODCALLTYPE GetId(LPWSTR *ppstrId) override;
|
||||
HRESULT STDMETHODCALLTYPE GetState(DWORD *pdwState) override;
|
||||
#pragma endregion
|
||||
|
||||
IMMDevice *const pReal;
|
||||
};
|
||||
82
hooks/audio/backends/mmdevice/device_enumerator.cpp
Normal file
82
hooks/audio/backends/mmdevice/device_enumerator.cpp
Normal file
@@ -0,0 +1,82 @@
|
||||
#include "device_enumerator.h"
|
||||
#include "device.h"
|
||||
|
||||
#ifdef _MSC_VER
|
||||
DEFINE_GUID(IID_IMMDeviceEnumerator,
|
||||
0xa95664d2, 0x9614, 0x4f35,
|
||||
0xa7, 0x46, 0xde, 0x8d, 0xb6, 0x36, 0x17, 0xe6);
|
||||
#endif
|
||||
|
||||
HRESULT STDMETHODCALLTYPE WrappedIMMDeviceEnumerator::QueryInterface(REFIID riid, void **ppvObj) {
|
||||
if (ppvObj == nullptr) {
|
||||
return E_POINTER;
|
||||
}
|
||||
|
||||
if (riid == IID_IMMDeviceEnumerator) {
|
||||
this->AddRef();
|
||||
*ppvObj = this;
|
||||
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
return pReal->QueryInterface(riid, ppvObj);
|
||||
}
|
||||
ULONG STDMETHODCALLTYPE WrappedIMMDeviceEnumerator::AddRef() {
|
||||
return pReal->AddRef();
|
||||
}
|
||||
ULONG STDMETHODCALLTYPE WrappedIMMDeviceEnumerator::Release() {
|
||||
|
||||
// get reference count of underlying interface
|
||||
ULONG refs = pReal != nullptr ? pReal->Release() : 0;
|
||||
|
||||
if (refs == 0) {
|
||||
delete this;
|
||||
}
|
||||
|
||||
return refs;
|
||||
}
|
||||
|
||||
HRESULT STDMETHODCALLTYPE WrappedIMMDeviceEnumerator::EnumAudioEndpoints(
|
||||
EDataFlow dataFlow,
|
||||
DWORD dwStateMask,
|
||||
IMMDeviceCollection **ppDevices)
|
||||
{
|
||||
return pReal->EnumAudioEndpoints(dataFlow, dwStateMask, ppDevices);
|
||||
}
|
||||
|
||||
HRESULT STDMETHODCALLTYPE WrappedIMMDeviceEnumerator::GetDefaultAudioEndpoint(
|
||||
EDataFlow dataFlow,
|
||||
ERole role,
|
||||
IMMDevice **ppEndpoint)
|
||||
{
|
||||
// call orignal
|
||||
HRESULT ret = this->pReal->GetDefaultAudioEndpoint(dataFlow, role, ppEndpoint);
|
||||
|
||||
// check for failure
|
||||
if (FAILED(ret)) {
|
||||
log_warning("audio", "IMMDeviceEnumerator::GetDefaultAudioEndpoint failed, hr={}", FMT_HRESULT(ret));
|
||||
return ret;
|
||||
}
|
||||
|
||||
// wrap interface
|
||||
*ppEndpoint = new WrappedIMMDevice(*ppEndpoint);
|
||||
|
||||
// return original result
|
||||
return ret;
|
||||
}
|
||||
HRESULT STDMETHODCALLTYPE WrappedIMMDeviceEnumerator::GetDevice(
|
||||
LPCWSTR pwstrId,
|
||||
IMMDevice **ppDevice)
|
||||
{
|
||||
return pReal->GetDevice(pwstrId, ppDevice);
|
||||
}
|
||||
HRESULT STDMETHODCALLTYPE WrappedIMMDeviceEnumerator::RegisterEndpointNotificationCallback(
|
||||
IMMNotificationClient *pClient)
|
||||
{
|
||||
return pReal->RegisterEndpointNotificationCallback(pClient);
|
||||
}
|
||||
HRESULT STDMETHODCALLTYPE WrappedIMMDeviceEnumerator::UnregisterEndpointNotificationCallback(
|
||||
IMMNotificationClient *pClient)
|
||||
{
|
||||
return pReal->UnregisterEndpointNotificationCallback(pClient);
|
||||
}
|
||||
33
hooks/audio/backends/mmdevice/device_enumerator.h
Normal file
33
hooks/audio/backends/mmdevice/device_enumerator.h
Normal file
@@ -0,0 +1,33 @@
|
||||
#pragma once
|
||||
|
||||
#include <initguid.h>
|
||||
#include <mmdeviceapi.h>
|
||||
|
||||
#include "util/logging.h"
|
||||
|
||||
struct WrappedIMMDeviceEnumerator : IMMDeviceEnumerator {
|
||||
explicit WrappedIMMDeviceEnumerator(IMMDeviceEnumerator *orig) : pReal(orig) {
|
||||
}
|
||||
|
||||
WrappedIMMDeviceEnumerator(const WrappedIMMDeviceEnumerator &) = delete;
|
||||
WrappedIMMDeviceEnumerator &operator=(const WrappedIMMDeviceEnumerator &) = delete;
|
||||
|
||||
virtual ~WrappedIMMDeviceEnumerator() = 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 IMMDeviceEnumerator
|
||||
virtual HRESULT STDMETHODCALLTYPE EnumAudioEndpoints(EDataFlow dataFlow, DWORD dwStateMask, IMMDeviceCollection **ppDevices) override;
|
||||
virtual HRESULT STDMETHODCALLTYPE GetDefaultAudioEndpoint(EDataFlow dataFlow, ERole role, IMMDevice **ppEndpoint) override;
|
||||
virtual HRESULT STDMETHODCALLTYPE GetDevice(LPCWSTR pwstrId, IMMDevice **ppDevice) override;
|
||||
virtual HRESULT STDMETHODCALLTYPE RegisterEndpointNotificationCallback(IMMNotificationClient *pClient) override;
|
||||
virtual HRESULT STDMETHODCALLTYPE UnregisterEndpointNotificationCallback(IMMNotificationClient *pClient) override;
|
||||
#pragma endregion
|
||||
|
||||
private:
|
||||
IMMDeviceEnumerator *const pReal;
|
||||
};
|
||||
484
hooks/audio/backends/wasapi/audio_client.cpp
Normal file
484
hooks/audio/backends/wasapi/audio_client.cpp
Normal file
@@ -0,0 +1,484 @@
|
||||
#include "audio_client.h"
|
||||
|
||||
#include <ks.h>
|
||||
#include <ksmedia.h>
|
||||
|
||||
#include "avs/game.h"
|
||||
#include "hooks/audio/audio.h"
|
||||
#include "hooks/audio/util.h"
|
||||
#include "hooks/audio/backends/wasapi/util.h"
|
||||
#include "hooks/audio/implementations/asio.h"
|
||||
#include "hooks/audio/implementations/wave_out.h"
|
||||
//#include "util/co_task_mem_ptr.h"
|
||||
|
||||
#include "defs.h"
|
||||
#include "dummy_audio_client.h"
|
||||
#include "wasapi_private.h"
|
||||
|
||||
#if 0
|
||||
#define WRAP_DEBUG log_misc("audio::wasapi", "{}::{}", CLASS_NAME, __func__)
|
||||
#define WRAP_DEBUG_FMT(format, ...) log_misc("audio::wasapi", format, __VA_ARGS__)
|
||||
#else
|
||||
#define WRAP_DEBUG do {} while (0)
|
||||
#define WRAP_DEBUG_FMT(format, ...) do {} while (0)
|
||||
#endif
|
||||
|
||||
#if 1
|
||||
#define WRAP_VERBOSE log_misc("audio::wasapi", "{}::{}", CLASS_NAME, __func__)
|
||||
#else
|
||||
#define WRAP_VERBOSE do {} while (0)
|
||||
#endif
|
||||
|
||||
const char CLASS_NAME[] = "WrappedIAudioClient";
|
||||
|
||||
static void fix_rec_format(WAVEFORMATEX *pFormat) {
|
||||
log_misc("audio::wasapi", "changing format to 2ch 16-bit");
|
||||
|
||||
pFormat->nChannels = 2;
|
||||
pFormat->wBitsPerSample = 16;
|
||||
pFormat->nBlockAlign = pFormat->nChannels * (pFormat->wBitsPerSample / 8);
|
||||
pFormat->nAvgBytesPerSec = pFormat->nSamplesPerSec * pFormat->nBlockAlign;
|
||||
}
|
||||
|
||||
// TODO(felix): is it appropriate to automatically switch to shared mode? should we do a
|
||||
// `MessageBox` to notify the user?
|
||||
/*
|
||||
static bool check_for_exclusive_access(IAudioClient *client) {
|
||||
static bool checked_once = false;
|
||||
static bool previous_check_result = false;
|
||||
|
||||
CoTaskMemPtr<WAVEFORMATEX> mix_format;
|
||||
REFERENCE_TIME requested_duration = 0;
|
||||
|
||||
if (checked_once) {
|
||||
return previous_check_result;
|
||||
}
|
||||
if (audio::BACKEND.has_value()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// scope function so it has access to the local static variables
|
||||
auto set_result = [](bool result) {
|
||||
checked_once = true;
|
||||
previous_check_result = result;
|
||||
|
||||
return result;
|
||||
};
|
||||
|
||||
HRESULT ret = client->GetMixFormat(mix_format.ppv());
|
||||
if (FAILED(ret)) {
|
||||
PRINT_FAILED_RESULT("IAudioClient::GetMixFormat", ret);
|
||||
return set_result(false);
|
||||
}
|
||||
|
||||
log_info("audio::wasapi", "Mix Format:");
|
||||
print_format(mix_format.data());
|
||||
|
||||
ret = client->IsFormatSupported(AUDCLNT_SHAREMODE_EXCLUSIVE, mix_format.data(), nullptr);
|
||||
if (ret == AUDCLNT_E_UNSUPPORTED_FORMAT) {
|
||||
auto mix_format_ex = reinterpret_cast<WAVEFORMATEXTENSIBLE *>(mix_format.data());
|
||||
|
||||
log_warning("audio::wasapi", "device does not natively support the mix format, converting to PCM");
|
||||
|
||||
if (mix_format->wFormatTag == WAVE_FORMAT_EXTENSIBLE &&
|
||||
IsEqualGUID(GUID_KSDATAFORMAT_SUBTYPE_IEEE_FLOAT, mix_format_ex->SubFormat))
|
||||
{
|
||||
mix_format_ex->Format.wBitsPerSample = 16;
|
||||
mix_format_ex->Format.nBlockAlign = mix_format_ex->Format.nChannels * (mix_format_ex->Format.wBitsPerSample / 8);
|
||||
mix_format_ex->Format.nAvgBytesPerSec = mix_format_ex->Format.nSamplesPerSec * mix_format_ex->Format.nBlockAlign;
|
||||
mix_format_ex->Samples.wValidBitsPerSample = 16;
|
||||
mix_format_ex->SubFormat = GUID_KSDATAFORMAT_SUBTYPE_PCM;
|
||||
} else if (mix_format->wFormatTag == WAVE_FORMAT_IEEE_FLOAT) {
|
||||
mix_format->wBitsPerSample = 16;
|
||||
mix_format->nBlockAlign = mix_format->nChannels * (mix_format->wBitsPerSample / 8);
|
||||
mix_format->nAvgBytesPerSec = mix_format->nSamplesPerSec * mix_format->nBlockAlign;
|
||||
mix_format->wFormatTag = WAVE_FORMAT_PCM;
|
||||
} else {
|
||||
log_warning("audio::wasapi", "mix format is not a floating point format");
|
||||
return set_result(false);
|
||||
}
|
||||
|
||||
ret = client->IsFormatSupported(AUDCLNT_SHAREMODE_EXCLUSIVE, mix_format.data(), nullptr);
|
||||
if (FAILED(ret)) {
|
||||
log_warning("audio::wasapi", "mix format is not supported");
|
||||
return set_result(false);
|
||||
}
|
||||
}
|
||||
|
||||
ret = client->GetDevicePeriod(nullptr, &requested_duration);
|
||||
if (FAILED(ret)) {
|
||||
PRINT_FAILED_RESULT("IAudioClient::GetDevicePeriod", ret);
|
||||
return false;
|
||||
}
|
||||
|
||||
ret = client->Initialize(
|
||||
AUDCLNT_SHAREMODE_EXCLUSIVE,
|
||||
AUDCLNT_STREAMFLAGS_EVENTCALLBACK,
|
||||
requested_duration,
|
||||
requested_duration,
|
||||
mix_format.data(),
|
||||
nullptr);
|
||||
|
||||
if (ret == AUDCLNT_E_BUFFER_SIZE_NOT_ALIGNED || SUCCEEDED(ret)) {
|
||||
log_info("audio::wasapi", "exclusive mode is available, disabling backend");
|
||||
return set_result(true);
|
||||
} else {
|
||||
log_warning("audio::wasapi", "exclusive mode is not available, enabling backend, hr={}", FMT_HRESULT(ret));
|
||||
}
|
||||
|
||||
return set_result(false);
|
||||
}
|
||||
|
||||
HRESULT wrap_audio_client(
|
||||
IMMDevice *device,
|
||||
DWORD cls_ctx,
|
||||
PROPVARIANT *activation_params,
|
||||
IAudioClient **audio_client)
|
||||
{
|
||||
auto exclusive_available = check_for_exclusive_access(*audio_client);
|
||||
(*audio_client)->Stop();
|
||||
(*audio_client)->Reset();
|
||||
(*audio_client)->Release();
|
||||
*audio_client = nullptr;
|
||||
|
||||
SAFE_CALL("IMMDevice", "Activate", device->Activate(
|
||||
IID_IAudioClient,
|
||||
cls_ctx,
|
||||
activation_params,
|
||||
reinterpret_cast<void **>(audio_client)));
|
||||
*/
|
||||
IAudioClient *wrap_audio_client(IAudioClient *audio_client) {
|
||||
AudioBackend *backend = nullptr;
|
||||
bool requires_dummy = false;
|
||||
|
||||
if (hooks::audio::BACKEND.has_value()) {
|
||||
switch (hooks::audio::BACKEND.value()) {
|
||||
case hooks::audio::Backend::Asio:
|
||||
backend = new AsioBackend();
|
||||
requires_dummy = true;
|
||||
break;
|
||||
case hooks::audio::Backend::WaveOut:
|
||||
backend = new WaveOutBackend();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
//} else if (!exclusive_available) {
|
||||
// backend = new WaveOutBackend();
|
||||
//}
|
||||
|
||||
IAudioClient *new_client;
|
||||
|
||||
if (hooks::audio::USE_DUMMY || requires_dummy) {
|
||||
|
||||
// release the old context since it is not used by the dummy context
|
||||
audio_client->Release();
|
||||
|
||||
new_client = new DummyIAudioClient(backend);
|
||||
} else {
|
||||
new_client = new WrappedIAudioClient(audio_client, backend);
|
||||
}
|
||||
|
||||
return new_client;
|
||||
}
|
||||
|
||||
// IUnknown
|
||||
HRESULT STDMETHODCALLTYPE WrappedIAudioClient::QueryInterface(REFIID riid, void **ppvObj) {
|
||||
if (ppvObj == nullptr) {
|
||||
return E_POINTER;
|
||||
}
|
||||
|
||||
if (riid == IID_WrappedIAudioClient ||
|
||||
riid == IID_IAudioClient)
|
||||
{
|
||||
this->AddRef();
|
||||
*ppvObj = this;
|
||||
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
return pReal->QueryInterface(riid, ppvObj);
|
||||
}
|
||||
ULONG STDMETHODCALLTYPE WrappedIAudioClient::AddRef() {
|
||||
return pReal->AddRef();
|
||||
}
|
||||
ULONG STDMETHODCALLTYPE WrappedIAudioClient::Release() {
|
||||
|
||||
// get reference count of underlying interface
|
||||
ULONG refs = pReal != nullptr ? pReal->Release() : 0;
|
||||
|
||||
if (refs == 0) {
|
||||
delete this;
|
||||
}
|
||||
|
||||
return refs;
|
||||
}
|
||||
|
||||
// IAudioClient
|
||||
HRESULT STDMETHODCALLTYPE WrappedIAudioClient::Initialize(
|
||||
AUDCLNT_SHAREMODE ShareMode,
|
||||
DWORD StreamFlags,
|
||||
REFERENCE_TIME hnsBufferDuration,
|
||||
REFERENCE_TIME hnsPeriodicity,
|
||||
const WAVEFORMATEX *pFormat,
|
||||
LPCGUID AudioSessionGuid)
|
||||
{
|
||||
WRAP_DEBUG;
|
||||
|
||||
if (!pFormat) {
|
||||
return E_POINTER;
|
||||
}
|
||||
|
||||
// check if format needs to be fixed
|
||||
if (pFormat->nChannels > 2 && avs::game::is_model("REC")) {
|
||||
fix_rec_format(const_cast<WAVEFORMATEX *>(pFormat));
|
||||
}
|
||||
|
||||
// verbose output
|
||||
log_info("audio::wasapi", "IAudioClient::Initialize hook hit");
|
||||
log_info("audio::wasapi", "... ShareMode : {}", share_mode_str(ShareMode));
|
||||
log_info("audio::wasapi", "... StreamFlags : {}", stream_flags_str(StreamFlags));
|
||||
log_info("audio::wasapi", "... hnsBufferDuration : {}", hnsBufferDuration);
|
||||
log_info("audio::wasapi", "... hnsPeriodicity : {}", hnsPeriodicity);
|
||||
print_format(pFormat);
|
||||
|
||||
if (this->backend) {
|
||||
SAFE_CALL("AudioBackend", "on_initialize", this->backend->on_initialize(
|
||||
&ShareMode,
|
||||
&StreamFlags,
|
||||
&hnsBufferDuration,
|
||||
&hnsPeriodicity,
|
||||
pFormat,
|
||||
AudioSessionGuid));
|
||||
|
||||
log_info("audio::wasapi", "AudioBackend::on_initialize call finished");
|
||||
log_info("audio::wasapi", "... ShareMode : {}", share_mode_str(ShareMode));
|
||||
log_info("audio::wasapi", "... StreamFlags : {}", stream_flags_str(StreamFlags));
|
||||
log_info("audio::wasapi", "... hnsBufferDuration : {}", hnsBufferDuration);
|
||||
log_info("audio::wasapi", "... hnsPeriodicity : {}", hnsPeriodicity);
|
||||
print_format(pFormat);
|
||||
}
|
||||
|
||||
// check for exclusive mode
|
||||
if (ShareMode == AUDCLNT_SHAREMODE_EXCLUSIVE) {
|
||||
this->exclusive_mode = true;
|
||||
this->frame_size = pFormat->nChannels * (pFormat->wBitsPerSample / 8);
|
||||
}
|
||||
|
||||
// call next
|
||||
HRESULT ret = pReal->Initialize(
|
||||
ShareMode,
|
||||
StreamFlags,
|
||||
hnsBufferDuration,
|
||||
hnsPeriodicity,
|
||||
pFormat,
|
||||
AudioSessionGuid);
|
||||
|
||||
// check for failure
|
||||
if (FAILED(ret)) {
|
||||
PRINT_FAILED_RESULT("IAudioClient", "Initialize", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
log_info("audio::wasapi", "IAudioClient::Initialize success, hr={}", FMT_HRESULT(ret));
|
||||
|
||||
/*
|
||||
if (ShareMode == AUDCLNT_SHAREMODE_SHARED) {
|
||||
IAudioClockAdjustment *clock = nullptr;
|
||||
|
||||
SAFE_CALL("IAudioClient", "GetService", pReal->GetService(
|
||||
IID_IAudioClockAdjustment,
|
||||
reinterpret_cast<void **>(&clock)));
|
||||
|
||||
SAFE_CALL("IAudioClockAdjustment", "SetSampleRate", clock->SetSampleRate(
|
||||
static_cast<float>(pFormat->nSamplesPerSec)));
|
||||
}
|
||||
*/
|
||||
|
||||
copy_wave_format(&hooks::audio::FORMAT, pFormat);
|
||||
|
||||
return ret;
|
||||
}
|
||||
HRESULT STDMETHODCALLTYPE WrappedIAudioClient::GetBufferSize(UINT32 *pNumBufferFrames) {
|
||||
static std::once_flag printed;
|
||||
std::call_once(printed, []() {
|
||||
log_misc("audio::wasapi", "WrappedIAudioClient::GetBufferSize");
|
||||
});
|
||||
|
||||
if (this->backend) {
|
||||
uint32_t buffer_frames = 0;
|
||||
|
||||
SAFE_CALL("AudioBackend", "on_get_buffer_size", this->backend->on_get_buffer_size(&buffer_frames));
|
||||
|
||||
if (buffer_frames > 0) {
|
||||
*pNumBufferFrames = buffer_frames;
|
||||
|
||||
return S_OK;
|
||||
}
|
||||
}
|
||||
|
||||
CHECK_RESULT(pReal->GetBufferSize(pNumBufferFrames));
|
||||
}
|
||||
HRESULT STDMETHODCALLTYPE WrappedIAudioClient::GetStreamLatency(REFERENCE_TIME *phnsLatency) {
|
||||
static std::once_flag printed;
|
||||
std::call_once(printed, []() {
|
||||
log_misc("audio::wasapi", "WrappedIAudioClient::GetStreamLatency");
|
||||
});
|
||||
|
||||
if (this->backend) {
|
||||
REFERENCE_TIME latency = 0;
|
||||
|
||||
SAFE_CALL("AudioBackend", "on_get_stream_latency", this->backend->on_get_stream_latency(
|
||||
&latency));
|
||||
|
||||
if (latency > 0) {
|
||||
*phnsLatency = latency;
|
||||
|
||||
return S_OK;
|
||||
}
|
||||
}
|
||||
|
||||
CHECK_RESULT(pReal->GetStreamLatency(phnsLatency));
|
||||
}
|
||||
HRESULT STDMETHODCALLTYPE WrappedIAudioClient::GetCurrentPadding(UINT32 *pNumPaddingFrames) {
|
||||
static std::once_flag printed;
|
||||
std::call_once(printed, []() {
|
||||
log_misc("audio::wasapi", "WrappedIAudioClient::GetCurrentPadding");
|
||||
});
|
||||
|
||||
if (pNumPaddingFrames && this->backend) {
|
||||
std::optional<uint32_t> padding_frames;
|
||||
|
||||
SAFE_CALL("AudioBackend", "on_get_current_padding",this->backend->on_get_current_padding(
|
||||
padding_frames));
|
||||
|
||||
if (padding_frames.has_value()) {
|
||||
*pNumPaddingFrames = padding_frames.value();
|
||||
|
||||
return S_OK;
|
||||
}
|
||||
}
|
||||
|
||||
CHECK_RESULT(pReal->GetCurrentPadding(pNumPaddingFrames));
|
||||
}
|
||||
HRESULT STDMETHODCALLTYPE WrappedIAudioClient::IsFormatSupported(
|
||||
AUDCLNT_SHAREMODE ShareMode,
|
||||
const WAVEFORMATEX *pFormat,
|
||||
WAVEFORMATEX **ppClosestMatch)
|
||||
{
|
||||
WRAP_VERBOSE;
|
||||
|
||||
if (!pFormat) {
|
||||
return E_POINTER;
|
||||
}
|
||||
|
||||
// check if format needs to be fixed
|
||||
if (avs::game::is_model("REC") && pFormat->nChannels > 2) {
|
||||
fix_rec_format(const_cast<WAVEFORMATEX *>(pFormat));
|
||||
}
|
||||
|
||||
if (this->backend) {
|
||||
HRESULT ret = this->backend->on_is_format_supported(&ShareMode, pFormat, ppClosestMatch);
|
||||
|
||||
if (SUCCEEDED(ret)) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
// return errors other than unsupported format
|
||||
if (ret != AUDCLNT_E_UNSUPPORTED_FORMAT) {
|
||||
SAFE_CALL("AudioBackend", "on_is_format_supported", ret);
|
||||
}
|
||||
}
|
||||
|
||||
CHECK_RESULT(pReal->IsFormatSupported(ShareMode, pFormat, ppClosestMatch));
|
||||
}
|
||||
HRESULT STDMETHODCALLTYPE WrappedIAudioClient::GetMixFormat(WAVEFORMATEX **ppDeviceFormat) {
|
||||
WRAP_VERBOSE;
|
||||
|
||||
if (!ppDeviceFormat) {
|
||||
return E_POINTER;
|
||||
}
|
||||
|
||||
if (this->backend) {
|
||||
HRESULT ret = this->backend->on_get_mix_format(ppDeviceFormat);
|
||||
|
||||
if (SUCCEEDED(ret)) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
// return errors other than E_NOTIMPL
|
||||
if (ret != E_NOTIMPL) {
|
||||
SAFE_CALL("AudioBackend", "on_get_mix_format", ret);
|
||||
}
|
||||
}
|
||||
|
||||
CHECK_RESULT(pReal->GetMixFormat(ppDeviceFormat));
|
||||
}
|
||||
HRESULT STDMETHODCALLTYPE WrappedIAudioClient::GetDevicePeriod(
|
||||
REFERENCE_TIME *phnsDefaultDevicePeriod,
|
||||
REFERENCE_TIME *phnsMinimumDevicePeriod)
|
||||
{
|
||||
static std::once_flag printed;
|
||||
std::call_once(printed, []() {
|
||||
log_misc("audio::wasapi", "WrappedIAudioClient::GetDevicePeriod");
|
||||
});
|
||||
|
||||
HRESULT ret = pReal->GetDevicePeriod(phnsDefaultDevicePeriod, phnsMinimumDevicePeriod);
|
||||
|
||||
if (SUCCEEDED(ret) && this->backend) {
|
||||
SAFE_CALL("AudioBackend", "on_get_device_period", this->backend->on_get_device_period(
|
||||
phnsDefaultDevicePeriod,
|
||||
phnsMinimumDevicePeriod));
|
||||
}
|
||||
|
||||
CHECK_RESULT(ret);
|
||||
}
|
||||
HRESULT STDMETHODCALLTYPE WrappedIAudioClient::Start() {
|
||||
WRAP_VERBOSE;
|
||||
|
||||
HRESULT ret = pReal->Start();
|
||||
|
||||
if (SUCCEEDED(ret) && this->backend) {
|
||||
SAFE_CALL("AudioBackend", "on_start", this->backend->on_start());
|
||||
}
|
||||
|
||||
CHECK_RESULT(ret);
|
||||
}
|
||||
HRESULT STDMETHODCALLTYPE WrappedIAudioClient::Stop() {
|
||||
WRAP_VERBOSE;
|
||||
|
||||
HRESULT ret = pReal->Stop();
|
||||
|
||||
if (SUCCEEDED(ret) && this->backend) {
|
||||
SAFE_CALL("AudioBackend", "on_stop", this->backend->on_stop());
|
||||
}
|
||||
|
||||
CHECK_RESULT(ret);
|
||||
}
|
||||
HRESULT STDMETHODCALLTYPE WrappedIAudioClient::Reset() {
|
||||
WRAP_VERBOSE;
|
||||
CHECK_RESULT(pReal->Reset());
|
||||
}
|
||||
HRESULT STDMETHODCALLTYPE WrappedIAudioClient::SetEventHandle(HANDLE eventHandle) {
|
||||
WRAP_VERBOSE;
|
||||
|
||||
if (this->backend) {
|
||||
SAFE_CALL("AudioBackend", "on_set_event_handle", this->backend->on_set_event_handle(&eventHandle));
|
||||
}
|
||||
|
||||
CHECK_RESULT(pReal->SetEventHandle(eventHandle));
|
||||
}
|
||||
HRESULT STDMETHODCALLTYPE WrappedIAudioClient::GetService(REFIID riid, void **ppv) {
|
||||
WRAP_DEBUG_FMT("WrappedIAudioClient::GetService({})", guid2s(riid));
|
||||
|
||||
HRESULT ret = pReal->GetService(riid, ppv);
|
||||
|
||||
if (SUCCEEDED(ret) && ppv && *ppv && riid == IID_IAudioRenderClient) {
|
||||
auto render_client = reinterpret_cast<IAudioRenderClient *>(*ppv);
|
||||
|
||||
*ppv = new WrappedIAudioRenderClient(this, render_client);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
55
hooks/audio/backends/wasapi/audio_client.h
Normal file
55
hooks/audio/backends/wasapi/audio_client.h
Normal file
@@ -0,0 +1,55 @@
|
||||
#pragma once
|
||||
|
||||
#include <initguid.h>
|
||||
#include <audioclient.h>
|
||||
#include <mmdeviceapi.h>
|
||||
|
||||
#include "hooks/audio/implementations/backend.h"
|
||||
#include "hooks/audio/audio_private.h"
|
||||
#include "util/logging.h"
|
||||
|
||||
#include "audio_render_client.h"
|
||||
|
||||
// {1FBC8530-AF3E-4128-B418-115DE72F76B6}
|
||||
static const GUID IID_WrappedIAudioClient = {
|
||||
0x1fbc8530, 0xaf3e, 0x4128, { 0xb4, 0x18, 0x11, 0x5d, 0xe7, 0x2f, 0x76, 0xb6 }
|
||||
};
|
||||
|
||||
IAudioClient *wrap_audio_client(IAudioClient *client);
|
||||
|
||||
struct WrappedIAudioClient : IAudioClient {
|
||||
explicit WrappedIAudioClient(IAudioClient *orig, AudioBackend *backend) : pReal(orig), backend(backend) {
|
||||
}
|
||||
|
||||
WrappedIAudioClient(const WrappedIAudioClient &) = delete;
|
||||
WrappedIAudioClient &operator=(const WrappedIAudioClient &) = delete;
|
||||
|
||||
virtual ~WrappedIAudioClient() = default;
|
||||
|
||||
#pragma region IUnknown
|
||||
HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, void **ppvObj) override;
|
||||
ULONG STDMETHODCALLTYPE AddRef() override;
|
||||
ULONG STDMETHODCALLTYPE Release() override;
|
||||
#pragma endregion
|
||||
|
||||
#pragma region IAudioClient
|
||||
HRESULT STDMETHODCALLTYPE Initialize(AUDCLNT_SHAREMODE ShareMode, DWORD StreamFlags, REFERENCE_TIME hnsBufferDuration, REFERENCE_TIME hnsPeriodicity, const WAVEFORMATEX *pFormat, LPCGUID AudioSessionGuid) override;
|
||||
HRESULT STDMETHODCALLTYPE GetBufferSize(UINT32 *pNumBufferFrames) override;
|
||||
HRESULT STDMETHODCALLTYPE GetStreamLatency(REFERENCE_TIME *phnsLatency) override;
|
||||
HRESULT STDMETHODCALLTYPE GetCurrentPadding(UINT32 *pNumPaddingFrames) override;
|
||||
HRESULT STDMETHODCALLTYPE IsFormatSupported(AUDCLNT_SHAREMODE ShareMode, const WAVEFORMATEX *pFormat, WAVEFORMATEX **ppClosestMatch) override;
|
||||
HRESULT STDMETHODCALLTYPE GetMixFormat(WAVEFORMATEX **ppDeviceFormat) override;
|
||||
HRESULT STDMETHODCALLTYPE GetDevicePeriod(REFERENCE_TIME *phnsDefaultDevicePeriod, REFERENCE_TIME *phnsMinimumDevicePeriod) override;
|
||||
HRESULT STDMETHODCALLTYPE Start() override;
|
||||
HRESULT STDMETHODCALLTYPE Stop() override;
|
||||
HRESULT STDMETHODCALLTYPE Reset() override;
|
||||
HRESULT STDMETHODCALLTYPE SetEventHandle(HANDLE eventHandle) override;
|
||||
HRESULT STDMETHODCALLTYPE GetService(REFIID riid, void **ppv) override;
|
||||
#pragma endregion
|
||||
|
||||
IAudioClient *const pReal;
|
||||
AudioBackend *const backend;
|
||||
|
||||
bool exclusive_mode = false;
|
||||
int frame_size = 0;
|
||||
};
|
||||
88
hooks/audio/backends/wasapi/audio_render_client.cpp
Normal file
88
hooks/audio/backends/wasapi/audio_render_client.cpp
Normal file
@@ -0,0 +1,88 @@
|
||||
#include "audio_render_client.h"
|
||||
|
||||
#include "audio_client.h"
|
||||
#include "wasapi_private.h"
|
||||
|
||||
const char CLASS_NAME[] = "WrappedIAudioRenderClient";
|
||||
|
||||
HRESULT STDMETHODCALLTYPE WrappedIAudioRenderClient::QueryInterface(REFIID riid, void **ppvObj) {
|
||||
if (ppvObj == nullptr) {
|
||||
return E_POINTER;
|
||||
}
|
||||
|
||||
if (riid == IID_WrappedIAudioRenderClient ||
|
||||
riid == IID_IAudioRenderClient)
|
||||
{
|
||||
this->AddRef();
|
||||
*ppvObj = this;
|
||||
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
return pReal->QueryInterface(riid, ppvObj);
|
||||
}
|
||||
ULONG STDMETHODCALLTYPE WrappedIAudioRenderClient::AddRef() {
|
||||
return pReal->AddRef();
|
||||
}
|
||||
ULONG STDMETHODCALLTYPE WrappedIAudioRenderClient::Release() {
|
||||
|
||||
// get reference count of underlying interface
|
||||
ULONG refs = pReal != nullptr ? pReal->Release() : 0;
|
||||
|
||||
if (refs == 0) {
|
||||
delete this;
|
||||
}
|
||||
|
||||
return refs;
|
||||
}
|
||||
|
||||
// IAudioRenderClient
|
||||
HRESULT STDMETHODCALLTYPE WrappedIAudioRenderClient::GetBuffer(UINT32 NumFramesRequested, BYTE **ppData) {
|
||||
static std::once_flag printed;
|
||||
std::call_once(printed, []() {
|
||||
log_misc("audio::wasapi", "WrappedIAudioRenderClient::GetBuffer");
|
||||
});
|
||||
|
||||
if (this->client->backend) {
|
||||
SAFE_CALL("AudioBackend", "on_get_buffer", this->client->backend->on_get_buffer(
|
||||
NumFramesRequested,
|
||||
ppData));
|
||||
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
// call original
|
||||
HRESULT ret = pReal->GetBuffer(NumFramesRequested, ppData);
|
||||
|
||||
// store buffer reference
|
||||
if (SUCCEEDED(ret)) {
|
||||
this->audio_buffer = *ppData;
|
||||
}
|
||||
|
||||
CHECK_RESULT(ret);
|
||||
}
|
||||
HRESULT STDMETHODCALLTYPE WrappedIAudioRenderClient::ReleaseBuffer(UINT32 NumFramesWritten, DWORD dwFlags) {
|
||||
static std::once_flag printed;
|
||||
std::call_once(printed, []() {
|
||||
log_misc("audio::wasapi", "WrappedIAudioRenderClient::ReleaseBuffer");
|
||||
});
|
||||
|
||||
if (this->client->backend) {
|
||||
SAFE_CALL("AudioBackend", "on_release_buffer", this->client->backend->on_release_buffer(
|
||||
NumFramesWritten,
|
||||
dwFlags));
|
||||
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
// fix for audio pop effect
|
||||
if (this->buffers_to_mute > 0 && this->client->frame_size > 0) {
|
||||
|
||||
// zero out = mute
|
||||
memset(this->audio_buffer, 0, NumFramesWritten * this->client->frame_size);
|
||||
|
||||
this->buffers_to_mute--;
|
||||
}
|
||||
|
||||
CHECK_RESULT(pReal->ReleaseBuffer(NumFramesWritten, dwFlags));
|
||||
}
|
||||
38
hooks/audio/backends/wasapi/audio_render_client.h
Normal file
38
hooks/audio/backends/wasapi/audio_render_client.h
Normal file
@@ -0,0 +1,38 @@
|
||||
#pragma once
|
||||
|
||||
#include <initguid.h>
|
||||
#include <audioclient.h>
|
||||
|
||||
struct WrappedIAudioClient;
|
||||
|
||||
// {1CB6ABEE-1181-4FF7-8449-1CA18C2109E3}
|
||||
static const GUID IID_WrappedIAudioRenderClient = {
|
||||
0x1cb6abee, 0x1181, 0x4ff7, { 0x84, 0x49, 0x1c, 0xa1, 0x8c, 0x21, 0x09, 0xe3 }
|
||||
};
|
||||
|
||||
struct WrappedIAudioRenderClient : IAudioRenderClient {
|
||||
explicit WrappedIAudioRenderClient(WrappedIAudioClient *client, IAudioRenderClient *orig) : pReal(orig), client(client) {
|
||||
}
|
||||
|
||||
WrappedIAudioRenderClient(const WrappedIAudioRenderClient &) = delete;
|
||||
WrappedIAudioRenderClient &operator=(const WrappedIAudioRenderClient &) = delete;
|
||||
|
||||
virtual ~WrappedIAudioRenderClient() = default;
|
||||
|
||||
#pragma region IUnknown
|
||||
HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, void **ppvObj) override;
|
||||
ULONG STDMETHODCALLTYPE AddRef() override;
|
||||
ULONG STDMETHODCALLTYPE Release() override;
|
||||
#pragma endregion
|
||||
|
||||
#pragma region IAudioClient
|
||||
HRESULT STDMETHODCALLTYPE GetBuffer(UINT32 NumFramesRequested, BYTE **ppData) override;
|
||||
HRESULT STDMETHODCALLTYPE ReleaseBuffer(UINT32 NumFramesWritten, DWORD dwFlags) override;
|
||||
#pragma endregion
|
||||
|
||||
IAudioRenderClient *const pReal;
|
||||
WrappedIAudioClient *const client;
|
||||
|
||||
int buffers_to_mute = 16;
|
||||
BYTE *audio_buffer = nullptr;
|
||||
};
|
||||
51
hooks/audio/backends/wasapi/defs.h
Normal file
51
hooks/audio/backends/wasapi/defs.h
Normal file
@@ -0,0 +1,51 @@
|
||||
#pragma once
|
||||
|
||||
#include <initguid.h>
|
||||
|
||||
// missing from MinGW
|
||||
#ifndef AUDCLNT_STREAMFLAGS_SRC_DEFAULT_QUALITY
|
||||
#define AUDCLNT_STREAMFLAGS_SRC_DEFAULT_QUALITY 0x08000000
|
||||
#endif
|
||||
#ifndef AUDCLNT_STREAMFLAGS_AUTOCONVERTPCM
|
||||
#define AUDCLNT_STREAMFLAGS_AUTOCONVERTPCM 0x80000000
|
||||
#endif
|
||||
|
||||
// defined starting with Windows 10, version 1803
|
||||
// https://docs.microsoft.com/en-us/windows/win32/coreaudio/audclnt-streamflags-xxx-constants
|
||||
#ifndef AUDCLNT_STREAMFLAGS_PREVENT_LOOPBACK_CAPTURE
|
||||
#define AUDCLNT_STREAMFLAGS_PREVENT_LOOPBACK_CAPTURE 0x01000000
|
||||
#endif
|
||||
|
||||
DEFINE_GUID(GUID_KSDATAFORMAT_SUBTYPE_PCM,
|
||||
0x00000001, 0x0000, 0x0010,
|
||||
0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71);
|
||||
|
||||
DEFINE_GUID(GUID_KSDATAFORMAT_SUBTYPE_IEEE_FLOAT,
|
||||
0x00000003, 0x0000, 0x0010,
|
||||
0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71);
|
||||
|
||||
#ifdef _MSC_VER
|
||||
DEFINE_GUID(IID_IAudioClient,
|
||||
0x1cb9ad4c, 0xdbfa, 0x4c32,
|
||||
0xb1, 0x78, 0xc2, 0xf5, 0x68, 0xa7, 0x03, 0xb2);
|
||||
|
||||
DEFINE_GUID(IID_IAudioClock,
|
||||
0xcd63314f, 0x3fba, 0x4a1b,
|
||||
0x81, 0x2c, 0xef, 0x96, 0x35, 0x87, 0x28, 0xe7);
|
||||
|
||||
DEFINE_GUID(IID_IAudioClock2,
|
||||
0x6f49ff73, 0x6727, 0x49ac,
|
||||
0xa0, 0x08, 0xd9, 0x8c, 0xf5, 0xe7, 0x00, 0x48);
|
||||
|
||||
DEFINE_GUID(IID_IAudioClockAdjustment,
|
||||
0xf6e4c0a0, 0x46d9, 0x4fb8,
|
||||
0xbe, 0x21, 0x57, 0xa3, 0xef, 0x2b, 0x62, 0x6c);
|
||||
|
||||
DEFINE_GUID(IID_IAudioRenderClient,
|
||||
0xf294acfc, 0x3146, 0x4483,
|
||||
0xa7, 0xbf, 0xad, 0xdc, 0xa7, 0xc2, 0x60, 0xe2);
|
||||
|
||||
DEFINE_GUID(IID_IAudioSessionControl,
|
||||
0xf4b1a599, 0x7266, 0x4319,
|
||||
0xa8, 0xca, 0xe7, 0x0a, 0xcb, 0x11, 0xe8, 0xcd);
|
||||
#endif
|
||||
216
hooks/audio/backends/wasapi/dummy_audio_client.cpp
Normal file
216
hooks/audio/backends/wasapi/dummy_audio_client.cpp
Normal file
@@ -0,0 +1,216 @@
|
||||
#include "dummy_audio_client.h"
|
||||
|
||||
#include "hooks/audio/audio.h"
|
||||
#include "hooks/audio/util.h"
|
||||
|
||||
#include "defs.h"
|
||||
#include "dummy_audio_clock.h"
|
||||
#include "dummy_audio_render_client.h"
|
||||
#include "dummy_audio_session_control.h"
|
||||
#include "util.h"
|
||||
#include "wasapi_private.h"
|
||||
|
||||
#if 0
|
||||
#define WRAP_DEBUG log_misc("audio::wasapi", "{}::{}", CLASS_NAME, __func__)
|
||||
#define WRAP_DEBUG_FMT(format, ...) log_misc("audio::wasapi", format, __VA_ARGS__)
|
||||
#else
|
||||
#define WRAP_DEBUG do {} while (0)
|
||||
#define WRAP_DEBUG_FMT(format, ...) do {} while (0)
|
||||
#endif
|
||||
|
||||
#if 1
|
||||
#define WRAP_VERBOSE log_misc("audio::wasapi", "{}::{}", CLASS_NAME, __func__)
|
||||
#else
|
||||
#define WRAP_VERBOSE do {} while (0)
|
||||
#endif
|
||||
|
||||
const char CLASS_NAME[] = "DummyIAudioClient";
|
||||
|
||||
// IUnknown
|
||||
HRESULT STDMETHODCALLTYPE DummyIAudioClient::QueryInterface(REFIID riid, void **ppvObj) {
|
||||
if (ppvObj == nullptr) {
|
||||
return E_POINTER;
|
||||
}
|
||||
|
||||
if (riid == IID_DummyIAudioClient ||
|
||||
riid == IID_IAudioClient)
|
||||
{
|
||||
this->AddRef();
|
||||
*ppvObj = this;
|
||||
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
return E_NOINTERFACE;
|
||||
}
|
||||
ULONG STDMETHODCALLTYPE DummyIAudioClient::AddRef() {
|
||||
return ++this->ref_cnt;
|
||||
}
|
||||
ULONG STDMETHODCALLTYPE DummyIAudioClient::Release() {
|
||||
ULONG refs = --this->ref_cnt;
|
||||
|
||||
if (refs == 0) {
|
||||
delete this;
|
||||
}
|
||||
|
||||
return refs;
|
||||
}
|
||||
|
||||
// IAudioClient
|
||||
HRESULT STDMETHODCALLTYPE DummyIAudioClient::Initialize(
|
||||
AUDCLNT_SHAREMODE ShareMode,
|
||||
DWORD StreamFlags,
|
||||
REFERENCE_TIME hnsBufferDuration,
|
||||
REFERENCE_TIME hnsPeriodicity,
|
||||
const WAVEFORMATEX *pFormat,
|
||||
LPCGUID AudioSessionGuid)
|
||||
{
|
||||
WRAP_DEBUG;
|
||||
|
||||
if (!pFormat) {
|
||||
return E_POINTER;
|
||||
}
|
||||
|
||||
// verbose output
|
||||
log_info("audio::wasapi", "IAudioClient::Initialize hook hit");
|
||||
log_info("audio::wasapi", "... ShareMode : {}", share_mode_str(ShareMode));
|
||||
log_info("audio::wasapi", "... StreamFlags : {}", stream_flags_str(StreamFlags));
|
||||
log_info("audio::wasapi", "... hnsBufferDuration : {}", hnsBufferDuration);
|
||||
log_info("audio::wasapi", "... hnsPeriodicity : {}", hnsPeriodicity);
|
||||
print_format(pFormat);
|
||||
|
||||
CHECK_RESULT(this->backend->on_initialize(
|
||||
&ShareMode,
|
||||
&StreamFlags,
|
||||
&hnsBufferDuration,
|
||||
&hnsPeriodicity,
|
||||
pFormat,
|
||||
AudioSessionGuid));
|
||||
}
|
||||
HRESULT STDMETHODCALLTYPE DummyIAudioClient::GetBufferSize(UINT32 *pNumBufferFrames) {
|
||||
static std::once_flag printed;
|
||||
std::call_once(printed, []() {
|
||||
log_misc("audio::wasapi", "DummyIAudioClient::GetBufferSize");
|
||||
});
|
||||
|
||||
CHECK_RESULT(this->backend->on_get_buffer_size(pNumBufferFrames));
|
||||
}
|
||||
HRESULT STDMETHODCALLTYPE DummyIAudioClient::GetStreamLatency(REFERENCE_TIME *phnsLatency) {
|
||||
static std::once_flag printed;
|
||||
std::call_once(printed, []() {
|
||||
log_misc("audio::wasapi", "DummyIAudioClient::GetStreamLatency");
|
||||
});
|
||||
|
||||
CHECK_RESULT(this->backend->on_get_stream_latency(phnsLatency));
|
||||
}
|
||||
HRESULT STDMETHODCALLTYPE DummyIAudioClient::GetCurrentPadding(UINT32 *pNumPaddingFrames) {
|
||||
static std::once_flag printed;
|
||||
std::call_once(printed, []() {
|
||||
log_misc("audio::wasapi", "DummyIAudioClient::GetCurrentPadding");
|
||||
});
|
||||
|
||||
if (!pNumPaddingFrames) {
|
||||
return E_POINTER;
|
||||
}
|
||||
|
||||
std::optional<uint32_t> padding_frames;
|
||||
|
||||
HRESULT ret = this->backend->on_get_current_padding(padding_frames);
|
||||
|
||||
if (SUCCEEDED(ret)) {
|
||||
*pNumPaddingFrames = padding_frames.value_or(0);
|
||||
}
|
||||
|
||||
CHECK_RESULT(ret);
|
||||
}
|
||||
HRESULT STDMETHODCALLTYPE DummyIAudioClient::IsFormatSupported(
|
||||
AUDCLNT_SHAREMODE ShareMode,
|
||||
const WAVEFORMATEX *pFormat,
|
||||
WAVEFORMATEX **ppClosestMatch)
|
||||
{
|
||||
WRAP_VERBOSE;
|
||||
|
||||
if (!pFormat) {
|
||||
return E_POINTER;
|
||||
}
|
||||
|
||||
CHECK_RESULT(this->backend->on_is_format_supported(&ShareMode, pFormat, ppClosestMatch));
|
||||
}
|
||||
HRESULT STDMETHODCALLTYPE DummyIAudioClient::GetMixFormat(WAVEFORMATEX **ppDeviceFormat) {
|
||||
WRAP_VERBOSE;
|
||||
|
||||
if (!ppDeviceFormat) {
|
||||
return E_POINTER;
|
||||
}
|
||||
|
||||
CHECK_RESULT(this->backend->on_get_mix_format(ppDeviceFormat));
|
||||
}
|
||||
HRESULT STDMETHODCALLTYPE DummyIAudioClient::GetDevicePeriod(
|
||||
REFERENCE_TIME *phnsDefaultDevicePeriod,
|
||||
REFERENCE_TIME *phnsMinimumDevicePeriod)
|
||||
{
|
||||
static std::once_flag printed;
|
||||
std::call_once(printed, []() {
|
||||
log_misc("audio::wasapi", "DummyIAudioClient::GetDevicePeriod");
|
||||
});
|
||||
|
||||
CHECK_RESULT(this->backend->on_get_device_period(
|
||||
phnsDefaultDevicePeriod,
|
||||
phnsMinimumDevicePeriod));
|
||||
}
|
||||
HRESULT STDMETHODCALLTYPE DummyIAudioClient::Start() {
|
||||
WRAP_VERBOSE;
|
||||
|
||||
HRESULT ret = this->backend->on_start();
|
||||
|
||||
if (SUCCEEDED(ret)) {
|
||||
for (auto &handler : this->session_notification_handlers) {
|
||||
handler->OnStateChanged(AudioSessionStateActive);
|
||||
}
|
||||
}
|
||||
|
||||
CHECK_RESULT(ret);
|
||||
}
|
||||
HRESULT STDMETHODCALLTYPE DummyIAudioClient::Stop() {
|
||||
WRAP_VERBOSE;
|
||||
|
||||
HRESULT ret = this->backend->on_stop();
|
||||
|
||||
if (SUCCEEDED(ret)) {
|
||||
for (auto &handler : this->session_notification_handlers) {
|
||||
handler->OnStateChanged(AudioSessionStateInactive);
|
||||
}
|
||||
}
|
||||
|
||||
CHECK_RESULT(ret);
|
||||
}
|
||||
HRESULT STDMETHODCALLTYPE DummyIAudioClient::Reset() {
|
||||
WRAP_VERBOSE;
|
||||
|
||||
return S_OK;
|
||||
}
|
||||
HRESULT STDMETHODCALLTYPE DummyIAudioClient::SetEventHandle(HANDLE eventHandle) {
|
||||
WRAP_VERBOSE;
|
||||
CHECK_RESULT(this->backend->on_set_event_handle(&eventHandle));
|
||||
}
|
||||
HRESULT STDMETHODCALLTYPE DummyIAudioClient::GetService(REFIID riid, void **ppv) {
|
||||
WRAP_DEBUG_FMT("DummyIAudioClient::GetService({})", guid2s(riid));
|
||||
|
||||
if (ppv) {
|
||||
if (riid == IID_IAudioRenderClient) {
|
||||
*ppv = new DummyIAudioRenderClient(this);
|
||||
|
||||
return S_OK;
|
||||
} else if (riid == IID_IAudioSessionControl) {
|
||||
*ppv = new DummyIAudioSessionControl(this);
|
||||
|
||||
return S_OK;
|
||||
} else if (riid == IID_IAudioClock) {
|
||||
*ppv = new DummyIAudioClock(this->backend);
|
||||
|
||||
return S_OK;
|
||||
}
|
||||
}
|
||||
|
||||
CHECK_RESULT(E_NOINTERFACE);
|
||||
}
|
||||
63
hooks/audio/backends/wasapi/dummy_audio_client.h
Normal file
63
hooks/audio/backends/wasapi/dummy_audio_client.h
Normal file
@@ -0,0 +1,63 @@
|
||||
#pragma once
|
||||
|
||||
#include <atomic>
|
||||
#include <vector>
|
||||
|
||||
#include <initguid.h>
|
||||
#include <audioclient.h>
|
||||
#include <audiopolicy.h>
|
||||
#include <mmdeviceapi.h>
|
||||
|
||||
#include "hooks/audio/implementations/backend.h"
|
||||
#include "hooks/audio/implementations/asio.h"
|
||||
#include "hooks/audio/audio_private.h"
|
||||
#include "util/logging.h"
|
||||
|
||||
// {F0842A04-0F8E-4F5C-B3FF-0ED24C589BDA}
|
||||
static const GUID IID_DummyIAudioClient = {
|
||||
0xf0842a04, 0x0f8e, 0x4f5c, { 0xb3, 0xff, 0x0e, 0xd2, 0x4c, 0x58, 0x9b, 0xda }
|
||||
};
|
||||
|
||||
struct DummyIAudioClient : IAudioClient {
|
||||
explicit DummyIAudioClient(AudioBackend *backend) : backend(backend) {
|
||||
if (!this->backend) {
|
||||
log_fatal("audio::wasapi", "DummyIAudioClient: no backend initialized");
|
||||
}
|
||||
}
|
||||
|
||||
DummyIAudioClient(const DummyIAudioClient &) = delete;
|
||||
DummyIAudioClient &operator=(const DummyIAudioClient &) = delete;
|
||||
|
||||
virtual ~DummyIAudioClient() {
|
||||
log_misc("audio::wasapi", "~DummyIAudioClient");
|
||||
|
||||
delete this->backend;
|
||||
}
|
||||
|
||||
#pragma region IUnknown
|
||||
HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, void **ppvObj) override;
|
||||
ULONG STDMETHODCALLTYPE AddRef() override;
|
||||
ULONG STDMETHODCALLTYPE Release() override;
|
||||
#pragma endregion
|
||||
|
||||
#pragma region IAudioClient
|
||||
HRESULT STDMETHODCALLTYPE Initialize(AUDCLNT_SHAREMODE ShareMode, DWORD StreamFlags, REFERENCE_TIME hnsBufferDuration, REFERENCE_TIME hnsPeriodicity, const WAVEFORMATEX *pFormat, LPCGUID AudioSessionGuid) override;
|
||||
HRESULT STDMETHODCALLTYPE GetBufferSize(UINT32 *pNumBufferFrames) override;
|
||||
HRESULT STDMETHODCALLTYPE GetStreamLatency(REFERENCE_TIME *phnsLatency) override;
|
||||
HRESULT STDMETHODCALLTYPE GetCurrentPadding(UINT32 *pNumPaddingFrames) override;
|
||||
HRESULT STDMETHODCALLTYPE IsFormatSupported(AUDCLNT_SHAREMODE ShareMode, const WAVEFORMATEX *pFormat, WAVEFORMATEX **ppClosestMatch) override;
|
||||
HRESULT STDMETHODCALLTYPE GetMixFormat(WAVEFORMATEX **ppDeviceFormat) override;
|
||||
HRESULT STDMETHODCALLTYPE GetDevicePeriod(REFERENCE_TIME *phnsDefaultDevicePeriod, REFERENCE_TIME *phnsMinimumDevicePeriod) override;
|
||||
HRESULT STDMETHODCALLTYPE Start() override;
|
||||
HRESULT STDMETHODCALLTYPE Stop() override;
|
||||
HRESULT STDMETHODCALLTYPE Reset() override;
|
||||
HRESULT STDMETHODCALLTYPE SetEventHandle(HANDLE eventHandle) override;
|
||||
HRESULT STDMETHODCALLTYPE GetService(REFIID riid, void **ppv) override;
|
||||
#pragma endregion
|
||||
|
||||
std::atomic<ULONG> ref_cnt = 1;
|
||||
|
||||
AudioBackend *const backend;
|
||||
|
||||
std::vector<IAudioSessionEvents *> session_notification_handlers;
|
||||
};
|
||||
62
hooks/audio/backends/wasapi/dummy_audio_clock.cpp
Normal file
62
hooks/audio/backends/wasapi/dummy_audio_clock.cpp
Normal file
@@ -0,0 +1,62 @@
|
||||
#include "dummy_audio_clock.h"
|
||||
|
||||
#include "hooks/audio/backends/wasapi/dummy_audio_client.h"
|
||||
|
||||
#include "wasapi_private.h"
|
||||
|
||||
const char CLASS_NAME[] = "DummyIAudioClock";
|
||||
|
||||
// IUnknown
|
||||
HRESULT STDMETHODCALLTYPE DummyIAudioClock::QueryInterface(REFIID riid, void **ppvObj) {
|
||||
if (ppvObj == nullptr) {
|
||||
return E_POINTER;
|
||||
}
|
||||
|
||||
if (riid == IID_DummyIAudioClock ||
|
||||
riid == IID_IAudioClock)
|
||||
{
|
||||
this->AddRef();
|
||||
*ppvObj = this;
|
||||
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
return E_NOINTERFACE;
|
||||
}
|
||||
ULONG STDMETHODCALLTYPE DummyIAudioClock::AddRef() {
|
||||
return ++this->ref_cnt;
|
||||
}
|
||||
ULONG STDMETHODCALLTYPE DummyIAudioClock::Release() {
|
||||
ULONG refs = --this->ref_cnt;
|
||||
|
||||
if (refs == 0) {
|
||||
delete this;
|
||||
}
|
||||
|
||||
return refs;
|
||||
}
|
||||
|
||||
// IAudioClock
|
||||
HRESULT STDMETHODCALLTYPE DummyIAudioClock::GetFrequency(UINT64 *pu64Frequency) {
|
||||
static std::once_flag printed;
|
||||
std::call_once(printed, []() {
|
||||
log_misc("audio::wasapi", "DummyIAudioClock::GetFrequency");
|
||||
});
|
||||
|
||||
if (!pu64Frequency) {
|
||||
return E_POINTER;
|
||||
}
|
||||
|
||||
*pu64Frequency = static_cast<UINT64>(this->backend->format().Format.nSamplesPerSec);
|
||||
|
||||
return S_OK;
|
||||
}
|
||||
HRESULT STDMETHODCALLTYPE DummyIAudioClock::GetPosition(
|
||||
UINT64 *pu64Position,
|
||||
UINT64 *pu64QPCPosition)
|
||||
{
|
||||
CHECK_RESULT(E_NOTIMPL);
|
||||
}
|
||||
HRESULT STDMETHODCALLTYPE DummyIAudioClock::GetCharacteristics(DWORD *pdwCharacteristics) {
|
||||
CHECK_RESULT(E_NOTIMPL);
|
||||
}
|
||||
39
hooks/audio/backends/wasapi/dummy_audio_clock.h
Normal file
39
hooks/audio/backends/wasapi/dummy_audio_clock.h
Normal file
@@ -0,0 +1,39 @@
|
||||
#pragma once
|
||||
|
||||
#include <atomic>
|
||||
|
||||
#include <initguid.h>
|
||||
#include <audiopolicy.h>
|
||||
|
||||
struct AudioBackend;
|
||||
|
||||
// {8AE52B4A-ACC4-420C-9169-BA8AF07A251F}
|
||||
static const GUID IID_DummyIAudioClock = {
|
||||
0x8ae52b4a, 0xacc4, 0x420c, { 0x91, 0x69, 0xba, 0x8a, 0xf0, 0x7a, 0x25, 0x1f }
|
||||
};
|
||||
|
||||
struct DummyIAudioClock : IAudioClock {
|
||||
explicit DummyIAudioClock(AudioBackend *backend) : backend(backend) {
|
||||
}
|
||||
|
||||
DummyIAudioClock(const DummyIAudioClock &) = delete;
|
||||
DummyIAudioClock &operator=(const DummyIAudioClock &) = delete;
|
||||
|
||||
virtual ~DummyIAudioClock() = default;
|
||||
|
||||
#pragma region IUnknown
|
||||
HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, void **ppvObj) override;
|
||||
ULONG STDMETHODCALLTYPE AddRef() override;
|
||||
ULONG STDMETHODCALLTYPE Release() override;
|
||||
#pragma endregion
|
||||
|
||||
#pragma region IAudioClock
|
||||
HRESULT STDMETHODCALLTYPE GetFrequency(UINT64 *pu64Frequency) override;
|
||||
HRESULT STDMETHODCALLTYPE GetPosition(UINT64 *pu64Position, UINT64 *pu64QPCPosition) override;
|
||||
HRESULT STDMETHODCALLTYPE GetCharacteristics(DWORD *pdwCharacteristics) override;
|
||||
#pragma endregion
|
||||
|
||||
AudioBackend *const backend;
|
||||
|
||||
std::atomic<ULONG> ref_cnt = 1;
|
||||
};
|
||||
57
hooks/audio/backends/wasapi/dummy_audio_render_client.cpp
Normal file
57
hooks/audio/backends/wasapi/dummy_audio_render_client.cpp
Normal file
@@ -0,0 +1,57 @@
|
||||
#include "dummy_audio_render_client.h"
|
||||
|
||||
#include "dummy_audio_client.h"
|
||||
#include "wasapi_private.h"
|
||||
|
||||
const char CLASS_NAME[] = "DummyIAudioRenderClient";
|
||||
|
||||
HRESULT STDMETHODCALLTYPE DummyIAudioRenderClient::QueryInterface(REFIID riid, void **ppvObj) {
|
||||
if (ppvObj == nullptr) {
|
||||
return E_POINTER;
|
||||
}
|
||||
|
||||
if (riid == IID_DummyIAudioRenderClient ||
|
||||
riid == IID_IAudioRenderClient)
|
||||
{
|
||||
this->AddRef();
|
||||
*ppvObj = this;
|
||||
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
return E_NOINTERFACE;
|
||||
}
|
||||
ULONG STDMETHODCALLTYPE DummyIAudioRenderClient::AddRef() {
|
||||
return ++this->ref_cnt;
|
||||
}
|
||||
ULONG STDMETHODCALLTYPE DummyIAudioRenderClient::Release() {
|
||||
ULONG refs = --this->ref_cnt;
|
||||
|
||||
if (refs == 0) {
|
||||
delete this;
|
||||
}
|
||||
|
||||
return refs;
|
||||
}
|
||||
|
||||
// IAudioRenderClient
|
||||
HRESULT STDMETHODCALLTYPE DummyIAudioRenderClient::GetBuffer(UINT32 NumFramesRequested, BYTE **ppData) {
|
||||
static std::once_flag printed;
|
||||
std::call_once(printed, []() {
|
||||
log_misc("audio::wasapi", "DummyIAudioRenderClient::GetBuffer");
|
||||
});
|
||||
|
||||
CHECK_RESULT(this->client->backend->on_get_buffer(
|
||||
NumFramesRequested,
|
||||
ppData));
|
||||
}
|
||||
HRESULT STDMETHODCALLTYPE DummyIAudioRenderClient::ReleaseBuffer(UINT32 NumFramesWritten, DWORD dwFlags) {
|
||||
static std::once_flag printed;
|
||||
std::call_once(printed, []() {
|
||||
log_misc("audio::wasapi", "DummyIAudioRenderClient::ReleaseBuffer");
|
||||
});
|
||||
|
||||
CHECK_RESULT(this->client->backend->on_release_buffer(
|
||||
NumFramesWritten,
|
||||
dwFlags));
|
||||
}
|
||||
38
hooks/audio/backends/wasapi/dummy_audio_render_client.h
Normal file
38
hooks/audio/backends/wasapi/dummy_audio_render_client.h
Normal file
@@ -0,0 +1,38 @@
|
||||
#pragma once
|
||||
|
||||
#include <atomic>
|
||||
|
||||
#include <initguid.h>
|
||||
#include <audioclient.h>
|
||||
|
||||
struct DummyIAudioClient;
|
||||
|
||||
// {453BF965-DDA4-4234-8846-F02BA5E874B7}
|
||||
static const GUID IID_DummyIAudioRenderClient = {
|
||||
0x453bf965, 0xdda4, 0x4234, { 0x88, 0x46, 0xf0, 0x2b, 0xa5, 0xe8, 0x74, 0xb7 }
|
||||
};
|
||||
|
||||
struct DummyIAudioRenderClient : IAudioRenderClient {
|
||||
explicit DummyIAudioRenderClient(DummyIAudioClient *client) : client(client) {
|
||||
}
|
||||
|
||||
DummyIAudioRenderClient(const DummyIAudioRenderClient &) = delete;
|
||||
DummyIAudioRenderClient &operator=(const DummyIAudioRenderClient &) = delete;
|
||||
|
||||
virtual ~DummyIAudioRenderClient() = default;
|
||||
|
||||
#pragma region IUnknown
|
||||
HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, void **ppvObj) override;
|
||||
ULONG STDMETHODCALLTYPE AddRef() override;
|
||||
ULONG STDMETHODCALLTYPE Release() override;
|
||||
#pragma endregion
|
||||
|
||||
#pragma region IAudioClient
|
||||
HRESULT STDMETHODCALLTYPE GetBuffer(UINT32 NumFramesRequested, BYTE **ppData) override;
|
||||
HRESULT STDMETHODCALLTYPE ReleaseBuffer(UINT32 NumFramesWritten, DWORD dwFlags) override;
|
||||
#pragma endregion
|
||||
|
||||
std::atomic<ULONG> ref_cnt = 1;
|
||||
|
||||
DummyIAudioClient *const client;
|
||||
};
|
||||
176
hooks/audio/backends/wasapi/dummy_audio_session_control.cpp
Normal file
176
hooks/audio/backends/wasapi/dummy_audio_session_control.cpp
Normal file
@@ -0,0 +1,176 @@
|
||||
#include "dummy_audio_session_control.h"
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
#include "dummy_audio_client.h"
|
||||
#include "wasapi_private.h"
|
||||
|
||||
#if 1
|
||||
#define WRAP_DEBUG log_misc("audio::wasapi", "{}::{}", CLASS_NAME, __func__)
|
||||
#else
|
||||
#define WRAP_DEBUG do {} while (0)
|
||||
#endif
|
||||
|
||||
const char CLASS_NAME[] = "DummyIAudioSessionControl";
|
||||
|
||||
// IUnknown
|
||||
HRESULT STDMETHODCALLTYPE DummyIAudioSessionControl::QueryInterface(REFIID riid, void **ppvObj) {
|
||||
if (ppvObj == nullptr) {
|
||||
return E_POINTER;
|
||||
}
|
||||
|
||||
if (riid == IID_DummyIAudioSessionControl ||
|
||||
riid == IID_IAudioSessionControl)
|
||||
{
|
||||
this->AddRef();
|
||||
*ppvObj = this;
|
||||
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
return E_NOINTERFACE;
|
||||
}
|
||||
ULONG STDMETHODCALLTYPE DummyIAudioSessionControl::AddRef() {
|
||||
return ++this->ref_cnt;
|
||||
}
|
||||
ULONG STDMETHODCALLTYPE DummyIAudioSessionControl::Release() {
|
||||
ULONG refs = --this->ref_cnt;
|
||||
|
||||
if (refs == 0) {
|
||||
delete this;
|
||||
}
|
||||
|
||||
return refs;
|
||||
}
|
||||
|
||||
// IAudioSessionControl
|
||||
HRESULT STDMETHODCALLTYPE DummyIAudioSessionControl::GetState(AudioSessionState *pRetVal) {
|
||||
CHECK_RESULT(E_NOTIMPL);
|
||||
}
|
||||
HRESULT STDMETHODCALLTYPE DummyIAudioSessionControl::GetDisplayName(LPWSTR *pRetVal) {
|
||||
WRAP_DEBUG;
|
||||
|
||||
if (!pRetVal) {
|
||||
return E_POINTER;
|
||||
}
|
||||
|
||||
auto length = this->display_name.length();
|
||||
auto value = reinterpret_cast<LPWSTR>(CoTaskMemAlloc(length + 1));
|
||||
|
||||
if (!value) {
|
||||
CHECK_RESULT(E_OUTOFMEMORY);
|
||||
}
|
||||
|
||||
memcpy(value, this->display_name.c_str(), length);
|
||||
value[length] = L'\0';
|
||||
|
||||
*pRetVal = value;
|
||||
|
||||
return S_OK;
|
||||
}
|
||||
HRESULT STDMETHODCALLTYPE DummyIAudioSessionControl::SetDisplayName(
|
||||
LPCWSTR Value,
|
||||
LPCGUID EventContext)
|
||||
{
|
||||
WRAP_DEBUG;
|
||||
|
||||
if (!Value) {
|
||||
return E_POINTER;
|
||||
}
|
||||
|
||||
this->display_name = std::wstring(Value);
|
||||
|
||||
return S_OK;
|
||||
}
|
||||
HRESULT STDMETHODCALLTYPE DummyIAudioSessionControl::GetIconPath(LPWSTR *pRetVal) {
|
||||
WRAP_DEBUG;
|
||||
|
||||
if (!pRetVal) {
|
||||
return E_POINTER;
|
||||
}
|
||||
|
||||
auto length = this->icon_path.length();
|
||||
auto value = reinterpret_cast<LPWSTR>(CoTaskMemAlloc(length + 1));
|
||||
|
||||
if (!value) {
|
||||
CHECK_RESULT(E_OUTOFMEMORY);
|
||||
}
|
||||
|
||||
memcpy(value, this->icon_path.c_str(), length);
|
||||
value[length] = L'\0';
|
||||
|
||||
*pRetVal = value;
|
||||
|
||||
return S_OK;
|
||||
}
|
||||
HRESULT STDMETHODCALLTYPE DummyIAudioSessionControl::SetIconPath(
|
||||
LPCWSTR Value,
|
||||
LPCGUID EventContext)
|
||||
{
|
||||
WRAP_DEBUG;
|
||||
|
||||
if (!Value) {
|
||||
return E_POINTER;
|
||||
}
|
||||
|
||||
this->icon_path = std::wstring(Value);
|
||||
|
||||
return S_OK;
|
||||
}
|
||||
HRESULT STDMETHODCALLTYPE DummyIAudioSessionControl::GetGroupingParam(GUID *pRetVal) {
|
||||
WRAP_DEBUG;
|
||||
|
||||
if (!pRetVal) {
|
||||
return E_POINTER;
|
||||
}
|
||||
|
||||
memcpy(pRetVal, &this->grouping_param, sizeof(this->grouping_param));
|
||||
|
||||
return S_OK;
|
||||
}
|
||||
HRESULT STDMETHODCALLTYPE DummyIAudioSessionControl::SetGroupingParam(
|
||||
LPCGUID Override,
|
||||
LPCGUID EventContext)
|
||||
{
|
||||
WRAP_DEBUG;
|
||||
|
||||
if (!Override) {
|
||||
return E_POINTER;
|
||||
}
|
||||
|
||||
memcpy(&this->grouping_param, Override, sizeof(this->grouping_param));
|
||||
|
||||
return S_OK;
|
||||
}
|
||||
HRESULT STDMETHODCALLTYPE DummyIAudioSessionControl::RegisterAudioSessionNotification(
|
||||
IAudioSessionEvents *NewNotifications)
|
||||
{
|
||||
WRAP_DEBUG;
|
||||
|
||||
if (!NewNotifications) {
|
||||
return E_POINTER;
|
||||
}
|
||||
|
||||
this->client->session_notification_handlers.emplace_back(NewNotifications);
|
||||
|
||||
return S_OK;
|
||||
}
|
||||
HRESULT STDMETHODCALLTYPE DummyIAudioSessionControl::UnregisterAudioSessionNotification(
|
||||
IAudioSessionEvents *NewNotifications)
|
||||
{
|
||||
WRAP_DEBUG;
|
||||
|
||||
if (!NewNotifications) {
|
||||
return E_POINTER;
|
||||
}
|
||||
|
||||
this->client->session_notification_handlers.erase(
|
||||
std::remove(
|
||||
this->client->session_notification_handlers.begin(),
|
||||
this->client->session_notification_handlers.end(),
|
||||
NewNotifications
|
||||
),
|
||||
this->client->session_notification_handlers.end());
|
||||
|
||||
return S_OK;
|
||||
}
|
||||
50
hooks/audio/backends/wasapi/dummy_audio_session_control.h
Normal file
50
hooks/audio/backends/wasapi/dummy_audio_session_control.h
Normal file
@@ -0,0 +1,50 @@
|
||||
#pragma once
|
||||
|
||||
#include <atomic>
|
||||
#include <string>
|
||||
|
||||
#include <initguid.h>
|
||||
#include <audiopolicy.h>
|
||||
|
||||
struct DummyIAudioClient;
|
||||
|
||||
// {5412A875-C82F-451F-B29A-0E18DB1CDFA2}
|
||||
static const GUID IID_DummyIAudioSessionControl = {
|
||||
0x5412a875, 0xc82f, 0x451f, { 0xb2, 0x9a, 0x0e, 0x18, 0xdb, 0x1c, 0xdf, 0xa2 }
|
||||
};
|
||||
|
||||
struct DummyIAudioSessionControl : IAudioSessionControl {
|
||||
explicit DummyIAudioSessionControl(DummyIAudioClient *client) : client(client) {
|
||||
}
|
||||
|
||||
DummyIAudioSessionControl(const DummyIAudioSessionControl &) = delete;
|
||||
DummyIAudioSessionControl &operator=(const DummyIAudioSessionControl &) = delete;
|
||||
|
||||
virtual ~DummyIAudioSessionControl() = default;
|
||||
|
||||
#pragma region IUnknown
|
||||
HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, void **ppvObj) override;
|
||||
ULONG STDMETHODCALLTYPE AddRef() override;
|
||||
ULONG STDMETHODCALLTYPE Release() override;
|
||||
#pragma endregion
|
||||
|
||||
#pragma region IAudioSessionControl
|
||||
HRESULT STDMETHODCALLTYPE GetState(AudioSessionState *pRetVal) override;
|
||||
HRESULT STDMETHODCALLTYPE GetDisplayName(LPWSTR *pRetVal) override;
|
||||
HRESULT STDMETHODCALLTYPE SetDisplayName(LPCWSTR Value, LPCGUID EventContext) override;
|
||||
HRESULT STDMETHODCALLTYPE GetIconPath(LPWSTR *pRetVal) override;
|
||||
HRESULT STDMETHODCALLTYPE SetIconPath(LPCWSTR Value, LPCGUID EventContext) override;
|
||||
HRESULT STDMETHODCALLTYPE GetGroupingParam(GUID *pRetVal) override;
|
||||
HRESULT STDMETHODCALLTYPE SetGroupingParam(LPCGUID Override, LPCGUID EventContext) override;
|
||||
HRESULT STDMETHODCALLTYPE RegisterAudioSessionNotification(IAudioSessionEvents *NewNotifications) override;
|
||||
HRESULT STDMETHODCALLTYPE UnregisterAudioSessionNotification(IAudioSessionEvents *NewNotifications) override;
|
||||
#pragma endregion
|
||||
|
||||
DummyIAudioClient *const client;
|
||||
|
||||
std::atomic<ULONG> ref_cnt = 1;
|
||||
|
||||
std::wstring display_name = L"Dummy Audio Device";
|
||||
std::wstring icon_path = L"";
|
||||
GUID grouping_param = GUID_NULL;
|
||||
};
|
||||
153
hooks/audio/backends/wasapi/low_latency_client.cpp
Normal file
153
hooks/audio/backends/wasapi/low_latency_client.cpp
Normal file
@@ -0,0 +1,153 @@
|
||||
#include "low_latency_client.h"
|
||||
#include "util/logging.h"
|
||||
|
||||
#ifdef _MSC_VER
|
||||
DEFINE_GUID(CLSID_MMDeviceEnumerator,
|
||||
0xBCDE0395, 0xE52F, 0x467C,
|
||||
0x8E, 0x3D, 0xC4, 0x57, 0x92, 0x91, 0x69, 0x2E);
|
||||
#endif
|
||||
|
||||
#define PRINT_FAILED_RESULT(name, ret) \
|
||||
log_warning("audio::lowlatency", "{} failed, hr={}", name, FMT_HRESULT(ret))
|
||||
|
||||
namespace hooks::audio {
|
||||
bool LOW_LATENCY_SHARED_WASAPI = false;
|
||||
static bool COM_INITIALIZED = false;
|
||||
static LowLatencyAudioClient *LOW_LATENCY_CLIENT = nullptr;
|
||||
|
||||
void init_low_latency() {
|
||||
if (!LOW_LATENCY_SHARED_WASAPI) {
|
||||
return;
|
||||
}
|
||||
log_info("audio::lowlatency", "initializing");
|
||||
|
||||
HRESULT hr;
|
||||
|
||||
// initialize COM
|
||||
COM_INITIALIZED = true;
|
||||
hr = CoInitialize(NULL);
|
||||
if (FAILED(hr)) {
|
||||
PRINT_FAILED_RESULT("CoInitialize", hr);
|
||||
return;
|
||||
}
|
||||
|
||||
// initialize device enumerator
|
||||
IMMDeviceEnumerator* enumerator;
|
||||
hr = CoCreateInstance(
|
||||
CLSID_MMDeviceEnumerator,
|
||||
NULL,
|
||||
CLSCTX_ALL,
|
||||
IID_IMMDeviceEnumerator,
|
||||
reinterpret_cast<void**>(&enumerator));
|
||||
if (FAILED(hr)) {
|
||||
PRINT_FAILED_RESULT("CoCreateInstance(CLSID_MMDeviceEnumerator)", hr);
|
||||
return;
|
||||
}
|
||||
|
||||
// get default audio endpoint from enumerator
|
||||
IMMDevice* device;
|
||||
hr = enumerator->GetDefaultAudioEndpoint(eRender, eConsole, &device);
|
||||
if (FAILED(hr)) {
|
||||
PRINT_FAILED_RESULT("GetDefaultAudioEndpoint", hr);
|
||||
return;
|
||||
}
|
||||
enumerator->Release();
|
||||
|
||||
// start the client using the default audio endpoint
|
||||
LOW_LATENCY_CLIENT = hooks::audio::LowLatencyAudioClient::Create(device);
|
||||
log_info("audio::lowlatency", "initialized");
|
||||
}
|
||||
|
||||
void stop_low_latency() {
|
||||
if (!LOW_LATENCY_SHARED_WASAPI) {
|
||||
return;
|
||||
}
|
||||
log_info("audio::lowlatency", "stopping");
|
||||
if (LOW_LATENCY_CLIENT) {
|
||||
delete LOW_LATENCY_CLIENT;
|
||||
LOW_LATENCY_CLIENT = nullptr;
|
||||
}
|
||||
if (COM_INITIALIZED) {
|
||||
COM_INITIALIZED = false;
|
||||
CoUninitialize();
|
||||
}
|
||||
log_info("audio::lowlatency", "stopped");
|
||||
}
|
||||
}
|
||||
|
||||
using namespace hooks::audio;
|
||||
|
||||
LowLatencyAudioClient::LowLatencyAudioClient(IAudioClient3* audioClient) : audioClient(audioClient) {}
|
||||
|
||||
LowLatencyAudioClient::~LowLatencyAudioClient() {
|
||||
if (this->audioClient) {
|
||||
HRESULT ret;
|
||||
ret = this->audioClient->Stop();
|
||||
if (FAILED(ret)) {
|
||||
PRINT_FAILED_RESULT("IAudioClient3::Stop", ret);
|
||||
}
|
||||
this->audioClient->Release();
|
||||
this->audioClient = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
LowLatencyAudioClient *LowLatencyAudioClient::Create(IMMDevice *device) {
|
||||
HRESULT ret;
|
||||
UINT32 minPeriod;
|
||||
UINT32 defaultPeriod;
|
||||
UINT32 fundamentalPeriod;
|
||||
UINT32 maxPeriod;
|
||||
PWAVEFORMATEX pFormat;
|
||||
IAudioClient3* audioClient;
|
||||
|
||||
ret = device->Activate(__uuidof(IAudioClient3), CLSCTX_ALL, NULL, reinterpret_cast<void**>(&audioClient));
|
||||
device->Release();
|
||||
if (FAILED(ret)) {
|
||||
PRINT_FAILED_RESULT("IMMDevice::Activate(IID_IAudioClient3...)", ret);
|
||||
log_warning("audio::lowlatency", "note that only Windows 10 and above supports IAudioClient3");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
ret = audioClient->GetMixFormat(&pFormat);
|
||||
if (FAILED(ret)) {
|
||||
PRINT_FAILED_RESULT("IAudioClient3::GetMixFormat", ret);
|
||||
audioClient->Release();
|
||||
audioClient = nullptr;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
ret = audioClient->GetSharedModeEnginePeriod(pFormat, &defaultPeriod, &fundamentalPeriod, &minPeriod, &maxPeriod);
|
||||
if (FAILED(ret)) {
|
||||
PRINT_FAILED_RESULT("IAudioClient3::GetSharedModeEnginePeriod", ret);
|
||||
audioClient->Release();
|
||||
audioClient = nullptr;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
ret = audioClient->InitializeSharedAudioStream(0, minPeriod, pFormat, NULL);
|
||||
if (FAILED(ret)) {
|
||||
PRINT_FAILED_RESULT("IAudioClient3::InitializeSharedAudioStream", ret);
|
||||
audioClient->Release();
|
||||
audioClient = nullptr;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
ret = audioClient->Start();
|
||||
if (FAILED(ret)) {
|
||||
PRINT_FAILED_RESULT("IAudioClient3::Start", ret);
|
||||
audioClient->Release();
|
||||
audioClient = nullptr;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
log_info("audio::lowlatency", "low latency shared mode audio client initialized successfully.");
|
||||
log_info("audio::lowlatency", "this is NOT used to output sound...");
|
||||
log_info("audio::lowlatency", "but rather to reduce buffer sizes when the game requests an audio client at a later point");
|
||||
log_info("audio::lowlatency", "has no effect if the game uses exclusive WASAPI or ASIO!");
|
||||
log_info("audio::lowlatency", "... sample rate : {} Hz", pFormat->nSamplesPerSec);
|
||||
log_info("audio::lowlatency", "... min buffer size : {} samples ({} ms)", minPeriod, 1000.0f * minPeriod / pFormat->nSamplesPerSec);
|
||||
log_info("audio::lowlatency", "... max buffer size : {} samples ({} ms)", maxPeriod, 1000.0f * maxPeriod / pFormat->nSamplesPerSec);
|
||||
log_info("audio::lowlatency", "... default buffer size : {} samples ({} ms)", defaultPeriod, 1000.0f * defaultPeriod / pFormat->nSamplesPerSec);
|
||||
log_info("audio::lowlatency", "... Windows will use minimum buffer size (instead of default) for shared mode audio clients from now on");
|
||||
return new LowLatencyAudioClient(audioClient);
|
||||
}
|
||||
22
hooks/audio/backends/wasapi/low_latency_client.h
Normal file
22
hooks/audio/backends/wasapi/low_latency_client.h
Normal file
@@ -0,0 +1,22 @@
|
||||
#pragma once
|
||||
|
||||
#include <initguid.h>
|
||||
#include <audioclient.h>
|
||||
#include <mmdeviceapi.h>
|
||||
|
||||
#include "hooks/audio/audio.h"
|
||||
|
||||
namespace hooks::audio {
|
||||
void init_low_latency();
|
||||
void stop_low_latency();
|
||||
|
||||
class LowLatencyAudioClient {
|
||||
public:
|
||||
static LowLatencyAudioClient *Create(IMMDevice *device);
|
||||
~LowLatencyAudioClient();
|
||||
|
||||
private:
|
||||
LowLatencyAudioClient(IAudioClient3* audioClient);
|
||||
IAudioClient3* audioClient;
|
||||
};
|
||||
}
|
||||
20
hooks/audio/backends/wasapi/util.cpp
Normal file
20
hooks/audio/backends/wasapi/util.cpp
Normal file
@@ -0,0 +1,20 @@
|
||||
#include "util.h"
|
||||
|
||||
#include <audioclient.h>
|
||||
|
||||
#include "util/flags_helper.h"
|
||||
|
||||
#include "defs.h"
|
||||
|
||||
std::string stream_flags_str(DWORD flags) {
|
||||
FLAGS_START(flags);
|
||||
FLAG(flags, AUDCLNT_STREAMFLAGS_CROSSPROCESS);
|
||||
FLAG(flags, AUDCLNT_STREAMFLAGS_LOOPBACK);
|
||||
FLAG(flags, AUDCLNT_STREAMFLAGS_EVENTCALLBACK);
|
||||
FLAG(flags, AUDCLNT_STREAMFLAGS_NOPERSIST);
|
||||
FLAG(flags, AUDCLNT_STREAMFLAGS_RATEADJUST);
|
||||
FLAG(flags, AUDCLNT_STREAMFLAGS_PREVENT_LOOPBACK_CAPTURE);
|
||||
FLAG(flags, AUDCLNT_STREAMFLAGS_AUTOCONVERTPCM);
|
||||
FLAG(flags, AUDCLNT_STREAMFLAGS_SRC_DEFAULT_QUALITY);
|
||||
FLAGS_END(flags);
|
||||
}
|
||||
8
hooks/audio/backends/wasapi/util.h
Normal file
8
hooks/audio/backends/wasapi/util.h
Normal file
@@ -0,0 +1,8 @@
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
|
||||
#include <windows.h>
|
||||
#include <mmreg.h>
|
||||
|
||||
std::string stream_flags_str(DWORD flags);
|
||||
29
hooks/audio/backends/wasapi/wasapi_private.h
Normal file
29
hooks/audio/backends/wasapi/wasapi_private.h
Normal file
@@ -0,0 +1,29 @@
|
||||
#pragma once
|
||||
|
||||
#include <windows.h>
|
||||
|
||||
#include "hooks/audio/audio_private.h"
|
||||
#include "util/logging.h"
|
||||
|
||||
#define PRINT_FAILED_RESULT(class_name, func_name, ret) \
|
||||
if (AUDIO_LOG_HRESULT) { \
|
||||
log_warning("audio::wasapi", "{}::{} failed, hr={}", class_name, func_name, FMT_HRESULT(ret)); \
|
||||
}
|
||||
|
||||
#define SAFE_CALL(class_name, func_name, x) \
|
||||
do { \
|
||||
HRESULT __hr = (x); \
|
||||
if (FAILED(__hr)) { \
|
||||
PRINT_FAILED_RESULT(class_name, func_name, __hr); \
|
||||
return __hr; \
|
||||
} \
|
||||
} while (0)
|
||||
|
||||
#define CHECK_RESULT(x) \
|
||||
do { \
|
||||
HRESULT __ret = (x); \
|
||||
if (FAILED(__ret)) { \
|
||||
PRINT_FAILED_RESULT(CLASS_NAME, __func__, __ret); \
|
||||
} \
|
||||
return __ret; \
|
||||
} while (0)
|
||||
99
hooks/audio/buffer.cpp
Normal file
99
hooks/audio/buffer.cpp
Normal file
@@ -0,0 +1,99 @@
|
||||
#include "buffer.h"
|
||||
|
||||
void convert_sample_type(
|
||||
const size_t channels,
|
||||
uint8_t *buffer,
|
||||
const size_t source_size,
|
||||
std::vector<double> &temp_buffer,
|
||||
const SampleType source_type,
|
||||
const SampleType dest_type)
|
||||
{
|
||||
// fast case: same type
|
||||
if (source_type == dest_type) {
|
||||
return;
|
||||
}
|
||||
|
||||
const size_t source_sample_size = sample_type_size(source_type);
|
||||
|
||||
// number of samples *per channel*
|
||||
const size_t num_samples = source_size / source_sample_size / channels;
|
||||
|
||||
// calculate the required size for the temporary buffer
|
||||
// samples are converted to doubles and then converted to the target sample type
|
||||
const size_t temp_size = num_samples * channels;
|
||||
|
||||
// resize temporary buffer if needed
|
||||
if (temp_buffer.size() < temp_size) {
|
||||
temp_buffer.resize(temp_size);
|
||||
}
|
||||
|
||||
#define SAMPLE_LOOP(VAR) for (size_t VAR = 0; VAR < num_samples * channels; VAR++)
|
||||
|
||||
// converts to double in temporary buffer
|
||||
#define CONVERT_LOOP(TY) \
|
||||
do { \
|
||||
const auto source = reinterpret_cast<TY *>(buffer); \
|
||||
SAMPLE_LOOP(i) { \
|
||||
temp_buffer[i] = convert_number_to_double<TY>(source[i]); \
|
||||
} \
|
||||
} while (0)
|
||||
|
||||
// converts double back to desired format
|
||||
#define STORE_LOOP(TY) \
|
||||
do { \
|
||||
const auto dest = reinterpret_cast<TY *>(buffer); \
|
||||
SAMPLE_LOOP(i) { \
|
||||
dest[i] = convert_double_to_number<TY>(temp_buffer[i]); \
|
||||
} \
|
||||
} while (0)
|
||||
|
||||
// converts double to float
|
||||
#define STORE_LOOP_FLOAT() \
|
||||
do { \
|
||||
const auto dest = reinterpret_cast<float *>(buffer); \
|
||||
SAMPLE_LOOP(i) { \
|
||||
dest[i] = static_cast<float>(temp_buffer[i]); \
|
||||
} \
|
||||
} while (0)
|
||||
|
||||
if (source_type == SampleType::SINT_16) {
|
||||
CONVERT_LOOP(int16_t);
|
||||
} else if (source_type == SampleType::SINT_24) {
|
||||
const auto source = reinterpret_cast<int24_t *>(buffer);
|
||||
|
||||
SAMPLE_LOOP(i) {
|
||||
temp_buffer[i] = convert_number_to_double<int32_t, int24_t>(source[i].as_int());
|
||||
}
|
||||
} else if (source_type == SampleType::SINT_32) {
|
||||
CONVERT_LOOP(int32_t);
|
||||
} else if (source_type == SampleType::FLOAT_32) {
|
||||
const auto source = reinterpret_cast<float *>(buffer);
|
||||
|
||||
SAMPLE_LOOP(i) {
|
||||
temp_buffer[i] = source[i];
|
||||
}
|
||||
} else if (source_type == SampleType::FLOAT_64) {
|
||||
memcpy(temp_buffer.data(), buffer, temp_size);
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
|
||||
if (dest_type == SampleType::SINT_16) {
|
||||
STORE_LOOP(int16_t);
|
||||
} else if (dest_type == SampleType::SINT_24) {
|
||||
STORE_LOOP(int24_t);
|
||||
} else if (dest_type == SampleType::SINT_32) {
|
||||
STORE_LOOP(int32_t);
|
||||
} else if (dest_type == SampleType::FLOAT_32) {
|
||||
STORE_LOOP_FLOAT();
|
||||
} else if (dest_type == SampleType::FLOAT_64) {
|
||||
memcpy(buffer, temp_buffer.data(), temp_size);
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
|
||||
#undef STORE_LOOP_FLOAT
|
||||
#undef STORE_LOOP
|
||||
#undef CONVERT_LOOP
|
||||
#undef SAMPLE_LOOP
|
||||
}
|
||||
162
hooks/audio/buffer.h
Normal file
162
hooks/audio/buffer.h
Normal file
@@ -0,0 +1,162 @@
|
||||
#pragma once
|
||||
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
#include <vector>
|
||||
#include <limits>
|
||||
|
||||
#ifdef min
|
||||
#undef min
|
||||
#endif
|
||||
#ifdef max
|
||||
#undef max
|
||||
#endif
|
||||
|
||||
enum class SampleType {
|
||||
UNSUPPORTED = 0,
|
||||
SINT_16,
|
||||
SINT_24,
|
||||
SINT_32,
|
||||
FLOAT_32,
|
||||
FLOAT_64,
|
||||
};
|
||||
|
||||
static constexpr const char *sample_type_str(SampleType sample_type) {
|
||||
switch (sample_type) {
|
||||
case SampleType::UNSUPPORTED:
|
||||
return "UNSUPPORTED";
|
||||
case SampleType::SINT_16:
|
||||
return "SINT_16";
|
||||
case SampleType::SINT_24:
|
||||
return "SINT_24";
|
||||
case SampleType::SINT_32:
|
||||
return "SINT_32";
|
||||
case SampleType::FLOAT_32:
|
||||
return "FLOAT_32";
|
||||
case SampleType::FLOAT_64:
|
||||
return "FLOAT_64";
|
||||
default:
|
||||
return "Unknown";
|
||||
}
|
||||
}
|
||||
|
||||
struct int24_t {
|
||||
uint8_t c3[3] {};
|
||||
|
||||
constexpr int24_t() = default;
|
||||
|
||||
constexpr int24_t(const short &i) noexcept {
|
||||
*this = static_cast<int>(i);
|
||||
}
|
||||
constexpr int24_t(const int &i) noexcept {
|
||||
*this = i;
|
||||
}
|
||||
constexpr int24_t(const long &l) noexcept {
|
||||
*this = static_cast<int>(l);
|
||||
}
|
||||
constexpr int24_t(const float &f) noexcept {
|
||||
*this = static_cast<int>(f);
|
||||
}
|
||||
constexpr int24_t(const double &d) noexcept {
|
||||
*this = static_cast<int>(d);
|
||||
}
|
||||
|
||||
constexpr int24_t &operator=(const int &i) noexcept {
|
||||
c3[0] = (i & 0x0000ffu);
|
||||
c3[1] = (i & 0x00ff00u) >> 8;
|
||||
c3[2] = (i & 0xff0000u) >> 16;
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
constexpr int32_t as_int() noexcept {
|
||||
uint32_t i = c3[0] | (c3[1] << 8) | (c3[2] << 16);
|
||||
if (i & 0x800000) {
|
||||
i |= ~0xffffffu;
|
||||
}
|
||||
return static_cast<int32_t>(i);
|
||||
}
|
||||
};
|
||||
|
||||
template<>
|
||||
class std::numeric_limits<int24_t> {
|
||||
public:
|
||||
static constexpr int32_t(min)() noexcept {
|
||||
return -0x800000;
|
||||
}
|
||||
static constexpr int32_t(max)() noexcept {
|
||||
return 0x7fffff;
|
||||
}
|
||||
};
|
||||
|
||||
template<typename T>
|
||||
class conversion_limits {
|
||||
public:
|
||||
static constexpr double absolute_max_value() noexcept {
|
||||
|
||||
// 1.0 is added here because the minimum value is `abs(min)` because of two's complement
|
||||
return static_cast<double>(std::numeric_limits<T>::max()) + 1.0;
|
||||
}
|
||||
};
|
||||
|
||||
static_assert(std::numeric_limits<int16_t>::max() == 32767);
|
||||
static_assert(std::numeric_limits<int24_t>::max() == 8388607);
|
||||
static_assert(std::numeric_limits<int32_t>::max() == 2147483647);
|
||||
static_assert(std::numeric_limits<int64_t>::max() == 9223372036854775807LL);
|
||||
static_assert(conversion_limits<int16_t>::absolute_max_value() == 32768.0);
|
||||
static_assert(conversion_limits<int24_t>::absolute_max_value() == 8388608.0);
|
||||
static_assert(conversion_limits<int32_t>::absolute_max_value() == 2147483648.0);
|
||||
static_assert(conversion_limits<int64_t>::absolute_max_value() == 9223372036854775808.0);
|
||||
|
||||
template<typename T, typename U = T>
|
||||
constexpr double convert_number_to_double(T num) {
|
||||
return static_cast<double>(num) / conversion_limits<U>::absolute_max_value();
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
constexpr T convert_double_to_number(double num) {
|
||||
constexpr auto ABSOLUTE_MAX_VALUE = conversion_limits<T>::absolute_max_value();
|
||||
constexpr auto MAX_VALUE = static_cast<long>(std::numeric_limits<T>::max());
|
||||
|
||||
return static_cast<T>(std::min(std::lround(num * ABSOLUTE_MAX_VALUE), MAX_VALUE));
|
||||
}
|
||||
|
||||
// ...before Felix makes this mistake again, make sure 24-bit ints are converted with the correct range
|
||||
static_assert(convert_number_to_double<int32_t, int24_t>(8388607) == (8388607.0 / 8388608.0));
|
||||
|
||||
static constexpr size_t sample_type_size(SampleType sample_type) {
|
||||
switch (sample_type) {
|
||||
case SampleType::UNSUPPORTED:
|
||||
return 0;
|
||||
case SampleType::SINT_16:
|
||||
return sizeof(int16_t);
|
||||
case SampleType::SINT_24:
|
||||
return sizeof(int24_t);
|
||||
case SampleType::SINT_32:
|
||||
return sizeof(int32_t);
|
||||
case SampleType::FLOAT_32:
|
||||
return sizeof(float);
|
||||
case SampleType::FLOAT_64:
|
||||
return sizeof(double);
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
static constexpr size_t required_buffer_size(
|
||||
const size_t num_frames,
|
||||
const size_t channels,
|
||||
const SampleType dest_sample_type)
|
||||
{
|
||||
return num_frames * channels * sample_type_size(dest_sample_type);
|
||||
}
|
||||
|
||||
void convert_sample_type(
|
||||
const size_t channels,
|
||||
uint8_t *buffer,
|
||||
const size_t source_size,
|
||||
std::vector<double> &temp_buffer,
|
||||
const SampleType source_type,
|
||||
const SampleType dest_type);
|
||||
1068
hooks/audio/implementations/asio.cpp
Normal file
1068
hooks/audio/implementations/asio.cpp
Normal file
File diff suppressed because it is too large
Load Diff
153
hooks/audio/implementations/asio.h
Normal file
153
hooks/audio/implementations/asio.h
Normal file
@@ -0,0 +1,153 @@
|
||||
#pragma once
|
||||
|
||||
#include <atomic>
|
||||
#include <condition_variable>
|
||||
#include <functional>
|
||||
#include <mutex>
|
||||
#include <optional>
|
||||
#include <thread>
|
||||
|
||||
#include "external/asio/asio.h"
|
||||
#include "external/asio/iasiodrv.h"
|
||||
#include "external/readerwriterqueue/readerwriterqueue.h"
|
||||
#include "hooks/audio/audio_private.h"
|
||||
#include "hooks/audio/buffer.h"
|
||||
|
||||
#include "backend.h"
|
||||
|
||||
struct AsioBackend;
|
||||
|
||||
extern AsioBackend *ASIO_BACKEND;
|
||||
|
||||
struct BufferEntry {
|
||||
BYTE *buffer;
|
||||
size_t length;
|
||||
size_t read;
|
||||
};
|
||||
|
||||
struct AsioInstanceInfo {
|
||||
long inputs = 0;
|
||||
long outputs = 0;
|
||||
long buffer_min_size = 0;
|
||||
long buffer_max_size = 0;
|
||||
long buffer_preferred_size = 0;
|
||||
long buffer_granularity = 0;
|
||||
long input_latency = 0;
|
||||
long output_latency = 0;
|
||||
};
|
||||
|
||||
struct AsioBackend final : AudioBackend {
|
||||
public:
|
||||
explicit AsioBackend();
|
||||
|
||||
~AsioBackend() final;
|
||||
|
||||
const WAVEFORMATEXTENSIBLE &format() const noexcept override;
|
||||
|
||||
HRESULT on_initialize(
|
||||
AUDCLNT_SHAREMODE *ShareMode,
|
||||
DWORD *StreamFlags,
|
||||
REFERENCE_TIME *hnsBufferDuration,
|
||||
REFERENCE_TIME *hnsPeriodicity,
|
||||
const WAVEFORMATEX *pFormat,
|
||||
LPCGUID AudioSessionGuid) noexcept override;
|
||||
|
||||
HRESULT on_get_buffer_size(uint32_t *buffer_frames) noexcept override;
|
||||
HRESULT on_get_stream_latency(REFERENCE_TIME *latency) noexcept override;
|
||||
HRESULT on_get_current_padding(std::optional<uint32_t> &padding_frames) noexcept override;
|
||||
|
||||
HRESULT on_is_format_supported(
|
||||
AUDCLNT_SHAREMODE *ShareMode,
|
||||
const WAVEFORMATEX *pFormat,
|
||||
WAVEFORMATEX **ppClosestMatch) noexcept override;
|
||||
|
||||
HRESULT on_get_mix_format(WAVEFORMATEX **pp_device_format) noexcept override;
|
||||
|
||||
HRESULT on_get_device_period(
|
||||
REFERENCE_TIME *default_device_period,
|
||||
REFERENCE_TIME *minimum_device_period) noexcept override;
|
||||
|
||||
HRESULT on_start() noexcept override;
|
||||
HRESULT on_stop() noexcept override;
|
||||
HRESULT on_set_event_handle(HANDLE *event_handle) noexcept override;
|
||||
|
||||
HRESULT on_get_buffer(uint32_t num_frames_requested, BYTE **pp_data) noexcept override;
|
||||
HRESULT on_release_buffer(uint32_t num_frames_written, DWORD dwFlags) noexcept override;
|
||||
|
||||
// for overlay
|
||||
inline const AsioDriverInfo &driver_info() const noexcept {
|
||||
return this->driver_info_;
|
||||
}
|
||||
inline const std::vector<AsioChannelInfo> &channel_info() const noexcept {
|
||||
return this->asio_channel_info_;
|
||||
}
|
||||
inline const AsioInstanceInfo &asio_info() const noexcept {
|
||||
return this->asio_info_;
|
||||
}
|
||||
void open_control_panel();
|
||||
|
||||
std::atomic<uint32_t> queued_frames = 0;
|
||||
std::atomic<size_t> queued_bytes = 0;
|
||||
|
||||
private:
|
||||
using AsioFunction = std::function<AsioError()>;
|
||||
|
||||
enum class AsioThreadState {
|
||||
Closed,
|
||||
Failed,
|
||||
Running,
|
||||
ShuttingDown,
|
||||
};
|
||||
struct AsioThreadMessage {
|
||||
AsioFunction fn;
|
||||
bool result_needed;
|
||||
};
|
||||
|
||||
void set_thread_state(AsioThreadState state);
|
||||
bool load_driver();
|
||||
bool update_driver_info();
|
||||
bool update_latency();
|
||||
bool set_initial_format(WAVEFORMATEXTENSIBLE &target);
|
||||
bool init();
|
||||
bool unload_driver();
|
||||
void reset();
|
||||
AsioError run_on_asio_thread(AsioFunction fn, bool result_needed = true);
|
||||
|
||||
// ASIO callbacks
|
||||
static void buffer_switch(long double_buffer_index, AsioBool direct_process);
|
||||
static void sample_rate_did_change(AsioSampleRate sample_rate);
|
||||
static long asio_message(long selector, long value, void *message, double *opt);
|
||||
|
||||
// helper methods
|
||||
static bool is_supported_subformat(const WAVEFORMATEXTENSIBLE &format_ex) noexcept;
|
||||
REFERENCE_TIME compute_ref_time() const;
|
||||
REFERENCE_TIME compute_latency_ref_time() const;
|
||||
|
||||
std::thread asio_thread;
|
||||
std::atomic_bool asio_thread_initialized = false;
|
||||
std::mutex asio_thread_state_lock;
|
||||
// TODO: use `std::atomic<T>::wait` when stabilized in MSVC
|
||||
std::condition_variable asio_thread_state_cv;
|
||||
std::atomic<AsioThreadState> asio_thread_state = AsioThreadState::Closed;
|
||||
moodycamel::BlockingReaderWriterQueue<AsioThreadMessage> asio_msg_queue_func;
|
||||
moodycamel::BlockingReaderWriterQueue<AsioError> asio_msg_queue_result;
|
||||
|
||||
moodycamel::ReaderWriterQueue<BufferEntry> queue;
|
||||
std::optional<HANDLE> relay_handle = std::nullopt;
|
||||
|
||||
IAsio *asio_driver = nullptr;
|
||||
AsioCallbacks asio_callbacks {};
|
||||
AsioDriverInfo driver_info_ {};
|
||||
AsioInstanceInfo asio_info_;
|
||||
std::vector<AsioChannelInfo> asio_channel_info_;
|
||||
std::vector<AsioBufferInfo> asio_buffers;
|
||||
SampleType asio_sample_type = SampleType::UNSUPPORTED;
|
||||
|
||||
std::atomic_bool started = false;
|
||||
WAVEFORMATEXTENSIBLE format_ {};
|
||||
WAVEFORMATEXTENSIBLE last_checked_format {};
|
||||
|
||||
//std::vector<BYTE> last_sound_buffer;
|
||||
std::vector<double> conversion_sound_buffer;
|
||||
BYTE *active_sound_buffer = nullptr;
|
||||
};
|
||||
55
hooks/audio/implementations/backend.h
Normal file
55
hooks/audio/implementations/backend.h
Normal file
@@ -0,0 +1,55 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include <optional>
|
||||
|
||||
#include <windows.h>
|
||||
#include <audioclient.h>
|
||||
#include <ksmedia.h>
|
||||
|
||||
struct WrappedIAudioClient;
|
||||
|
||||
struct AudioBackend {
|
||||
public:
|
||||
virtual ~AudioBackend() = default;
|
||||
|
||||
[[nodiscard]] virtual const WAVEFORMATEXTENSIBLE &format() const noexcept = 0;
|
||||
|
||||
#pragma region IAudioClient
|
||||
virtual HRESULT on_initialize(
|
||||
AUDCLNT_SHAREMODE *ShareMode,
|
||||
DWORD *StreamFlags,
|
||||
REFERENCE_TIME *hnsBufferDuration,
|
||||
REFERENCE_TIME *hnsPeriodicity,
|
||||
const WAVEFORMATEX *pFormat,
|
||||
LPCGUID AudioSessionGuid) = 0;
|
||||
|
||||
virtual HRESULT on_get_buffer_size(uint32_t *buffer_frames) = 0;
|
||||
|
||||
virtual HRESULT on_get_stream_latency(REFERENCE_TIME *latency) = 0;
|
||||
|
||||
virtual HRESULT on_get_current_padding(std::optional<uint32_t> &padding_frames) = 0;
|
||||
|
||||
virtual HRESULT on_is_format_supported(
|
||||
AUDCLNT_SHAREMODE *ShareMode,
|
||||
const WAVEFORMATEX *pFormat,
|
||||
WAVEFORMATEX **ppClosestMatch) = 0;
|
||||
|
||||
virtual HRESULT on_get_mix_format(WAVEFORMATEX **pp_device_format) = 0;
|
||||
|
||||
virtual HRESULT on_get_device_period(
|
||||
REFERENCE_TIME *default_device_period,
|
||||
REFERENCE_TIME *minimum_device_period) = 0;
|
||||
|
||||
virtual HRESULT on_start() = 0;
|
||||
|
||||
virtual HRESULT on_stop() = 0;
|
||||
|
||||
virtual HRESULT on_set_event_handle(HANDLE *event_handle) = 0;
|
||||
#pragma endregion
|
||||
|
||||
#pragma region IAudioRenderClient
|
||||
virtual HRESULT on_get_buffer(uint32_t num_frames_requested, BYTE **ppData) = 0;
|
||||
virtual HRESULT on_release_buffer(uint32_t num_frames_written, DWORD dwFlags) = 0;
|
||||
#pragma endregion
|
||||
};
|
||||
224
hooks/audio/implementations/wave_out.cpp
Normal file
224
hooks/audio/implementations/wave_out.cpp
Normal file
@@ -0,0 +1,224 @@
|
||||
#include "wave_out.h"
|
||||
|
||||
#include "hooks/audio/audio.h"
|
||||
#include "hooks/audio/backends/wasapi/audio_client.h"
|
||||
#include "hooks/audio/backends/wasapi/defs.h"
|
||||
|
||||
static REFERENCE_TIME WASAPI_TARGET_REFTIME = TARGET_REFTIME;
|
||||
|
||||
HRESULT WaveOutBackend::init(uint32_t buffer_size) {
|
||||
auto &format = hooks::audio::FORMAT.Format;
|
||||
format.wFormatTag = WAVE_FORMAT_PCM;
|
||||
|
||||
log_info("audio::wave_out", "initializing waveOut backend with {} channels, {} Hz, {}-bit",
|
||||
format.nChannels,
|
||||
format.nSamplesPerSec,
|
||||
format.wBitsPerSample);
|
||||
log_info("audio::wave_out", "... nBlockAlign : {} bytes", format.nBlockAlign);
|
||||
log_info("audio::wave_out", "... nAvgBytesPerSec : {} bytes", format.nAvgBytesPerSec);
|
||||
log_info("audio::wave_out", "... buffer reftime : {} ms", WASAPI_TARGET_REFTIME / 10000.f);
|
||||
log_info("audio::wave_out", "... buffer count : {} buffers", _countof(this->hdrs));
|
||||
|
||||
MMRESULT ret = waveOutOpen(
|
||||
&this->handle,
|
||||
WAVE_MAPPER,
|
||||
reinterpret_cast<const WAVEFORMATEX *>(&hooks::audio::FORMAT.Format),
|
||||
reinterpret_cast<DWORD_PTR>(this->dispatcher_event),
|
||||
reinterpret_cast<DWORD_PTR>(nullptr),
|
||||
CALLBACK_EVENT);
|
||||
|
||||
if (ret != MMSYSERR_NOERROR) {
|
||||
log_warning("audio::wave_out", "failed to initialize waveOut backend, hr={:#08x}",
|
||||
static_cast<unsigned>(ret));
|
||||
|
||||
return static_cast<HRESULT>(ret);
|
||||
}
|
||||
|
||||
// initialize buffers
|
||||
for (auto &hdr : this->hdrs) {
|
||||
memset(&hdr, 0, sizeof(hdr));
|
||||
hdr.lpData = new char[buffer_size] {};
|
||||
hdr.dwBufferLength = buffer_size;
|
||||
hdr.dwBytesRecorded = 0;
|
||||
hdr.dwUser = 0;
|
||||
hdr.dwFlags = 0;
|
||||
hdr.dwLoops = 0;
|
||||
hdr.lpNext = nullptr;
|
||||
ret = waveOutPrepareHeader(this->handle, &hdr, sizeof(hdr));
|
||||
|
||||
if (ret != MMSYSERR_NOERROR) {
|
||||
log_warning("audio::wave_out", "failed to prepare waveOut header, hr=0x{:08x}",
|
||||
static_cast<unsigned>(ret));
|
||||
|
||||
return static_cast<HRESULT>(ret);
|
||||
}
|
||||
|
||||
ret = waveOutWrite(this->handle, &hdr, sizeof(hdr));
|
||||
|
||||
if (ret != MMSYSERR_NOERROR) {
|
||||
log_warning("audio::wave_out", "failed to write waveOut header, hr=0x{:08x}",
|
||||
static_cast<unsigned>(ret));
|
||||
|
||||
return static_cast<HRESULT>(ret);
|
||||
}
|
||||
}
|
||||
|
||||
// mark as initialized
|
||||
this->initialized = true;
|
||||
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
const WAVEFORMATEXTENSIBLE &WaveOutBackend::format() const noexcept {
|
||||
return hooks::audio::FORMAT;
|
||||
}
|
||||
|
||||
HRESULT WaveOutBackend::on_initialize(
|
||||
AUDCLNT_SHAREMODE *ShareMode,
|
||||
DWORD *StreamFlags,
|
||||
REFERENCE_TIME *hnsBufferDuration,
|
||||
REFERENCE_TIME *hnsPeriodicity,
|
||||
const WAVEFORMATEX *pFormat,
|
||||
LPCGUID AudioSessionGuid) noexcept
|
||||
{
|
||||
*ShareMode = AUDCLNT_SHAREMODE_SHARED;
|
||||
*StreamFlags = AUDCLNT_STREAMFLAGS_EVENTCALLBACK |
|
||||
AUDCLNT_STREAMFLAGS_RATEADJUST |
|
||||
AUDCLNT_STREAMFLAGS_AUTOCONVERTPCM |
|
||||
AUDCLNT_STREAMFLAGS_SRC_DEFAULT_QUALITY;
|
||||
*hnsBufferDuration = WASAPI_TARGET_REFTIME;
|
||||
*hnsPeriodicity = WASAPI_TARGET_REFTIME;
|
||||
|
||||
// this backend only supports stereo audio
|
||||
if (pFormat->nChannels > 2) {
|
||||
return AUDCLNT_E_UNSUPPORTED_FORMAT;
|
||||
}
|
||||
|
||||
return S_OK;
|
||||
}
|
||||
HRESULT WaveOutBackend::on_get_buffer_size(uint32_t *buffer_frames) noexcept {
|
||||
*buffer_frames = _countof(this->hdrs);
|
||||
|
||||
return S_OK;
|
||||
}
|
||||
HRESULT WaveOutBackend::on_get_stream_latency(REFERENCE_TIME *latency) noexcept {
|
||||
*latency = WASAPI_TARGET_REFTIME;
|
||||
|
||||
return S_OK;
|
||||
}
|
||||
HRESULT WaveOutBackend::on_get_current_padding(std::optional<uint32_t> &padding_frames) noexcept {
|
||||
size_t queued_bytes = 0;
|
||||
|
||||
for (auto &hdr : this->hdrs) {
|
||||
if (hdr.dwFlags & WHDR_DONE) {
|
||||
queued_bytes += static_cast<unsigned>(hdr.dwBufferLength);
|
||||
}
|
||||
}
|
||||
|
||||
auto frames = static_cast<uint32_t>(queued_bytes / hooks::audio::FORMAT.Format.nBlockAlign);
|
||||
//log_info("audio::wave_out", "queued_bytes = {}, frames = {}", queued_bytes, frames);
|
||||
|
||||
padding_frames = frames;
|
||||
|
||||
return S_OK;
|
||||
}
|
||||
HRESULT WaveOutBackend::on_is_format_supported(
|
||||
AUDCLNT_SHAREMODE *ShareMode,
|
||||
const WAVEFORMATEX *pFormat,
|
||||
WAVEFORMATEX **ppClosestMatch) noexcept
|
||||
{
|
||||
// always support 44.1 kHz, stereo, 16-bits per channel with custom backends
|
||||
if (*ShareMode == AUDCLNT_SHAREMODE_EXCLUSIVE &&
|
||||
pFormat->nChannels == 2 &&
|
||||
pFormat->nSamplesPerSec == 44100 &&
|
||||
pFormat->wBitsPerSample == 16)
|
||||
{
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
return AUDCLNT_E_UNSUPPORTED_FORMAT;
|
||||
}
|
||||
HRESULT WaveOutBackend::on_get_mix_format(WAVEFORMATEX **pp_device_format) noexcept {
|
||||
return E_NOTIMPL;
|
||||
}
|
||||
HRESULT WaveOutBackend::on_get_device_period(
|
||||
REFERENCE_TIME *default_device_period,
|
||||
REFERENCE_TIME *minimum_device_period)
|
||||
{
|
||||
*default_device_period = WASAPI_TARGET_REFTIME;
|
||||
*minimum_device_period = WASAPI_TARGET_REFTIME;
|
||||
|
||||
return S_OK;
|
||||
}
|
||||
HRESULT WaveOutBackend::on_start() noexcept {
|
||||
return S_OK;
|
||||
}
|
||||
HRESULT WaveOutBackend::on_stop() noexcept {
|
||||
return S_OK;
|
||||
}
|
||||
HRESULT WaveOutBackend::on_set_event_handle(HANDLE *event_handle) {
|
||||
this->relay_event = *event_handle;
|
||||
this->dispatcher_event = CreateEvent(nullptr, true, false, nullptr);
|
||||
|
||||
*event_handle = this->dispatcher_event;
|
||||
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
HRESULT WaveOutBackend::on_get_buffer(uint32_t num_frames_requested, BYTE **ppData) {
|
||||
auto buffer_size = hooks::audio::FORMAT.Format.nBlockAlign * num_frames_requested;
|
||||
|
||||
if (!this->initialized) {
|
||||
this->init(buffer_size);
|
||||
}
|
||||
|
||||
// wait for a free slot
|
||||
WaitForSingleObject(this->dispatcher_event, INFINITE);
|
||||
|
||||
// allocate temporary sound buffer
|
||||
this->active_sound_buffer = reinterpret_cast<BYTE *>(CoTaskMemAlloc(buffer_size));
|
||||
|
||||
// hand the buffer to the callee
|
||||
*ppData = this->active_sound_buffer;
|
||||
|
||||
return S_OK;
|
||||
}
|
||||
HRESULT WaveOutBackend::on_release_buffer(uint32_t num_frames_written, DWORD dwFlags) {
|
||||
bool written = false;
|
||||
|
||||
// reset the dispatcher event
|
||||
ResetEvent(this->dispatcher_event);
|
||||
|
||||
while (!written) {
|
||||
for (WAVEHDR &hdr : this->hdrs) {
|
||||
if (hdr.dwFlags & WHDR_DONE) {
|
||||
memcpy(hdr.lpData, this->active_sound_buffer, hdr.dwBufferLength);
|
||||
|
||||
// write the data to the device now
|
||||
MMRESULT ret = waveOutWrite(this->handle, &hdr, sizeof(hdr));
|
||||
|
||||
if (ret != MMSYSERR_NOERROR) {
|
||||
log_warning("audio::wave_out", "failed to write waveOut data, hr={:#08x}",
|
||||
static_cast<unsigned>(ret));
|
||||
}
|
||||
|
||||
written = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// avoid pegging the CPU
|
||||
if (!written) {
|
||||
Sleep(1);
|
||||
}
|
||||
}
|
||||
|
||||
// free temporary sound buffer
|
||||
CoTaskMemFree(this->active_sound_buffer);
|
||||
this->active_sound_buffer = nullptr;
|
||||
|
||||
// trigger game audio callback
|
||||
SetEvent(this->relay_event);
|
||||
|
||||
return S_OK;
|
||||
}
|
||||
58
hooks/audio/implementations/wave_out.h
Normal file
58
hooks/audio/implementations/wave_out.h
Normal file
@@ -0,0 +1,58 @@
|
||||
#pragma once
|
||||
|
||||
#include <mmdeviceapi.h>
|
||||
#include <mmsystem.h>
|
||||
|
||||
#include "backend.h"
|
||||
|
||||
#define WASAPI_BUFFER_COUNT 3
|
||||
#define TARGET_REFTIME (100000) // 10 ms
|
||||
|
||||
struct WaveOutBackend final : AudioBackend {
|
||||
public:
|
||||
~WaveOutBackend() final = default;
|
||||
|
||||
HRESULT init(uint32_t buffer_size);
|
||||
|
||||
const WAVEFORMATEXTENSIBLE &format() const noexcept override;
|
||||
|
||||
HRESULT on_initialize(
|
||||
AUDCLNT_SHAREMODE *ShareMode,
|
||||
DWORD *StreamFlags,
|
||||
REFERENCE_TIME *hnsBufferDuration,
|
||||
REFERENCE_TIME *hnsPeriodicity,
|
||||
const WAVEFORMATEX *pFormat,
|
||||
LPCGUID AudioSessionGuid) noexcept override;
|
||||
|
||||
HRESULT on_get_buffer_size(uint32_t *buffer_frames) noexcept override;
|
||||
HRESULT on_get_stream_latency(REFERENCE_TIME *latency) noexcept override;
|
||||
HRESULT on_get_current_padding(std::optional<uint32_t> &padding_frames) noexcept override;
|
||||
|
||||
HRESULT on_is_format_supported(
|
||||
AUDCLNT_SHAREMODE *ShareMode,
|
||||
const WAVEFORMATEX *pFormat,
|
||||
WAVEFORMATEX **ppClosestMatch) noexcept override;
|
||||
|
||||
HRESULT on_get_mix_format(WAVEFORMATEX **pp_device_format) noexcept override;
|
||||
|
||||
HRESULT on_get_device_period(
|
||||
REFERENCE_TIME *default_device_period,
|
||||
REFERENCE_TIME *minimum_device_period) override;
|
||||
|
||||
HRESULT on_start() noexcept override;
|
||||
HRESULT on_stop() noexcept override;
|
||||
HRESULT on_set_event_handle(HANDLE *event_handle) override;
|
||||
|
||||
HRESULT on_get_buffer(uint32_t num_frames_requested, BYTE **ppData) override;
|
||||
HRESULT on_release_buffer(uint32_t num_frames_written, DWORD dwFlags) override;
|
||||
|
||||
private:
|
||||
WrappedIAudioClient *client;
|
||||
|
||||
bool initialized = false;
|
||||
HANDLE relay_event = nullptr;
|
||||
HANDLE dispatcher_event = nullptr;
|
||||
HWAVEOUT handle = nullptr;
|
||||
WAVEHDR hdrs[WASAPI_BUFFER_COUNT] {};
|
||||
BYTE *active_sound_buffer = nullptr;
|
||||
};
|
||||
80
hooks/audio/util.cpp
Normal file
80
hooks/audio/util.cpp
Normal file
@@ -0,0 +1,80 @@
|
||||
#include "util.h"
|
||||
|
||||
#include <ks.h>
|
||||
#include <ksmedia.h>
|
||||
|
||||
#include "util/flags_helper.h"
|
||||
#include "util/logging.h"
|
||||
#include "util/utils.h"
|
||||
|
||||
std::string channel_mask_str(DWORD channel_mask) {
|
||||
FLAGS_START(channel_mask);
|
||||
FLAG(channel_mask, SPEAKER_FRONT_LEFT);
|
||||
FLAG(channel_mask, SPEAKER_FRONT_RIGHT);
|
||||
FLAG(channel_mask, SPEAKER_FRONT_CENTER);
|
||||
FLAG(channel_mask, SPEAKER_LOW_FREQUENCY);
|
||||
FLAG(channel_mask, SPEAKER_BACK_LEFT);
|
||||
FLAG(channel_mask, SPEAKER_BACK_RIGHT);
|
||||
FLAG(channel_mask, SPEAKER_FRONT_LEFT_OF_CENTER);
|
||||
FLAG(channel_mask, SPEAKER_FRONT_RIGHT_OF_CENTER);
|
||||
FLAG(channel_mask, SPEAKER_BACK_CENTER);
|
||||
FLAG(channel_mask, SPEAKER_SIDE_LEFT);
|
||||
FLAG(channel_mask, SPEAKER_SIDE_RIGHT);
|
||||
FLAG(channel_mask, SPEAKER_TOP_CENTER);
|
||||
FLAG(channel_mask, SPEAKER_TOP_FRONT_LEFT);
|
||||
FLAG(channel_mask, SPEAKER_TOP_FRONT_CENTER);
|
||||
FLAG(channel_mask, SPEAKER_TOP_FRONT_RIGHT);
|
||||
FLAG(channel_mask, SPEAKER_TOP_BACK_LEFT);
|
||||
FLAG(channel_mask, SPEAKER_TOP_BACK_CENTER);
|
||||
FLAG(channel_mask, SPEAKER_TOP_BACK_RIGHT);
|
||||
FLAGS_END(channel_mask);
|
||||
}
|
||||
|
||||
std::string share_mode_str(AUDCLNT_SHAREMODE share_mode) {
|
||||
switch (share_mode) {
|
||||
ENUM_VARIANT(AUDCLNT_SHAREMODE_SHARED);
|
||||
ENUM_VARIANT(AUDCLNT_SHAREMODE_EXCLUSIVE);
|
||||
default:
|
||||
return fmt::format("ShareMode(0x{:08x})", share_mode);
|
||||
}
|
||||
}
|
||||
|
||||
void copy_wave_format(WAVEFORMATEXTENSIBLE *destination, const WAVEFORMATEX *source) {
|
||||
if (source->wFormatTag == WAVE_FORMAT_EXTENSIBLE) {
|
||||
memcpy(destination, source, sizeof(WAVEFORMATEXTENSIBLE));
|
||||
} else {
|
||||
memcpy(destination, source, sizeof(WAVEFORMATEX));
|
||||
}
|
||||
}
|
||||
|
||||
void print_format(const WAVEFORMATEX *pFormat) {
|
||||
log_info("audio", "Wave Format:");
|
||||
|
||||
// format specific
|
||||
if (pFormat->wFormatTag == WAVE_FORMAT_EXTENSIBLE) {
|
||||
auto format = reinterpret_cast<const WAVEFORMATEXTENSIBLE *>(pFormat);
|
||||
log_info("audio", "... SubFormat : {}", guid2s(format->SubFormat));
|
||||
} else {
|
||||
log_info("audio", "... wFormatTag : {}", pFormat->wFormatTag);
|
||||
}
|
||||
|
||||
// generic
|
||||
log_info("audio", "... nChannels : {}", pFormat->nChannels);
|
||||
log_info("audio", "... nSamplesPerSec : {}", pFormat->nSamplesPerSec);
|
||||
log_info("audio", "... nAvgBytesPerSec : {}", pFormat->nAvgBytesPerSec);
|
||||
log_info("audio", "... nBlockAlign : {}", pFormat->nBlockAlign);
|
||||
log_info("audio", "... wBitsPerSample : {}", pFormat->wBitsPerSample);
|
||||
|
||||
// format specific
|
||||
if (pFormat->wFormatTag == WAVE_FORMAT_EXTENSIBLE) {
|
||||
auto format = reinterpret_cast<const WAVEFORMATEXTENSIBLE *>(pFormat);
|
||||
|
||||
if (pFormat->wBitsPerSample == 0) {
|
||||
log_info("audio", "... wSamplesPerBlock : {}", format->Samples.wSamplesPerBlock);
|
||||
} else {
|
||||
log_info("audio", "... wValidBitsPerSample : {}", format->Samples.wValidBitsPerSample);
|
||||
}
|
||||
|
||||
log_info("audio", "... dwChannelMask : {}", channel_mask_str(format->dwChannelMask));
|
||||
}
|
||||
}
|
||||
12
hooks/audio/util.h
Normal file
12
hooks/audio/util.h
Normal file
@@ -0,0 +1,12 @@
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
|
||||
#include <windows.h>
|
||||
#include <audiosessiontypes.h>
|
||||
#include <mmreg.h>
|
||||
|
||||
std::string channel_mask_str(DWORD channel_mask);
|
||||
std::string share_mode_str(AUDCLNT_SHAREMODE share_mode);
|
||||
void copy_wave_format(WAVEFORMATEXTENSIBLE *destination, const WAVEFORMATEX *source);
|
||||
void print_format(const WAVEFORMATEX *pFormat);
|
||||
293
hooks/avshook.cpp
Normal file
293
hooks/avshook.cpp
Normal file
@@ -0,0 +1,293 @@
|
||||
#include "avshook.h"
|
||||
|
||||
#include <map>
|
||||
#include <optional>
|
||||
|
||||
#include "avs/core.h"
|
||||
#include "avs/ea3.h"
|
||||
#include "avs/game.h"
|
||||
#include "external/layeredfs/hook.h"
|
||||
#include "util/detour.h"
|
||||
#include "util/fileutils.h"
|
||||
#include "util/logging.h"
|
||||
#include "util/utils.h"
|
||||
#include "external/layeredfs/hook.h"
|
||||
|
||||
#ifdef min
|
||||
#undef min
|
||||
#endif
|
||||
|
||||
static bool FAKE_FILE_OPEN = false;
|
||||
|
||||
static std::map<std::string, std::string> ROM_FILE_MAP;
|
||||
static std::string *ROM_FILE_CONTENTS = nullptr;
|
||||
|
||||
namespace hooks::avs::config {
|
||||
bool DISABLE_VFS_DRIVE_REDIRECTION = false;
|
||||
bool LOG = false;
|
||||
};
|
||||
|
||||
using namespace hooks::avs;
|
||||
|
||||
#define WRAP_DEBUG_FMT(format, ...) \
|
||||
if (config::LOG) { \
|
||||
log_misc("avshook", "{}: " format " = 0x{:x}", __FUNCTION__, __VA_ARGS__, static_cast<unsigned>(value)); \
|
||||
}
|
||||
#define AVS_HOOK(f) hook_function(::avs::core::IMPORT_NAMES.f, #f, &::avs::core::f, f)
|
||||
|
||||
template<typename T>
|
||||
static void hook_function(const char *source_name, const char *target_name, T **source, T *target) {
|
||||
if (!detour::trampoline_try(avs::core::DLL_NAME.c_str(), source_name, target, source)) {
|
||||
log_warning("avshook", "could not hook {} ({})", target_name, source_name);
|
||||
}
|
||||
}
|
||||
|
||||
static inline bool is_fake_fd(avs::core::avs_file_t fd) {
|
||||
return FAKE_FILE_OPEN && fd == 1337;
|
||||
}
|
||||
|
||||
static bool is_dest_file(const char *name) {
|
||||
static std::string path_dest = fmt::format("/dev/raw/{}.dest", avs::game::DEST[0]);
|
||||
static std::string path_bin = fmt::format("/dev/raw/{}.bin", avs::game::DEST[0]);
|
||||
|
||||
return !_stricmp(name, path_dest.c_str()) || !_stricmp(name, path_bin.c_str());
|
||||
}
|
||||
|
||||
static bool is_dest_file(const char *name, uint16_t mode) {
|
||||
return mode == 1 && is_dest_file(name);
|
||||
}
|
||||
|
||||
static bool is_dest_spec_ea3_config(const char *name) {
|
||||
static std::string path = fmt::format("/prop/ea3-config-{}{}.xml", avs::game::DEST[0], avs::game::SPEC[0]);
|
||||
|
||||
return !_stricmp(name, path.c_str());
|
||||
}
|
||||
|
||||
static bool is_spam_file(const char *file) {
|
||||
static const char *spam_prefixes[] = {
|
||||
"/mnt/bm2d/ngp",
|
||||
"/afp",
|
||||
"/dev/nvram/pm_eco.xml",
|
||||
"/dev/nvram/pm_gamesys.xml",
|
||||
"/dev/nvram/pm_clock.xml",
|
||||
};
|
||||
|
||||
for (auto &spam : spam_prefixes) {
|
||||
if (string_begins_with(file, spam)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static int avs_fs_fstat(avs::core::avs_file_t fd, struct avs::core::avs_stat *st) {
|
||||
if (is_fake_fd(fd) && ROM_FILE_CONTENTS) {
|
||||
if (st) {
|
||||
st->filesize = static_cast<uint32_t>(ROM_FILE_CONTENTS->length());
|
||||
st->padding.st_dev = 0;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
return avs::core::avs_fs_fstat(fd, st);
|
||||
}
|
||||
|
||||
static int avs_fs_lstat(const char *name, struct avs::core::avs_stat *st) {
|
||||
if (name == nullptr) {
|
||||
return avs::core::avs_fs_lstat(name, st);
|
||||
}
|
||||
|
||||
if (is_dest_file(name) || is_dest_spec_ea3_config(name)) {
|
||||
if (st) {
|
||||
st->filesize = 0;
|
||||
st->padding.st_dev = 0;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
auto value = layeredfs::initialized
|
||||
? layeredfs::hook_avs_fs_lstat(name, st) : avs::core::avs_fs_lstat(name, st);
|
||||
|
||||
if (!is_spam_file(name)) {
|
||||
WRAP_DEBUG_FMT("name: {}", name);
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
static avs::core::avs_file_t avs_fs_open(const char *name, uint16_t mode, int flags) {
|
||||
if (name == nullptr) {
|
||||
return avs::core::avs_fs_open(name, mode, flags);
|
||||
}
|
||||
|
||||
if (!FAKE_FILE_OPEN && (is_dest_file(name, mode) || ROM_FILE_MAP.contains(name))) {
|
||||
FAKE_FILE_OPEN = true;
|
||||
|
||||
if (ROM_FILE_MAP.contains(name)) {
|
||||
ROM_FILE_CONTENTS = &ROM_FILE_MAP.at(name);
|
||||
}
|
||||
|
||||
log_info("avshook", "opening fake file '{}'", name);
|
||||
|
||||
return 1337;
|
||||
}
|
||||
|
||||
auto value = layeredfs::initialized
|
||||
? layeredfs::hook_avs_fs_open(name, mode, flags) : avs::core::avs_fs_open(name, mode, flags);
|
||||
|
||||
if (!is_spam_file(name)) {
|
||||
WRAP_DEBUG_FMT("name: {} mode: {} flags: {}", name, mode, flags);
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
static void avs_fs_close(avs::core::avs_file_t fd) {
|
||||
if (is_fake_fd(fd)) {
|
||||
FAKE_FILE_OPEN = false;
|
||||
ROM_FILE_CONTENTS = nullptr;
|
||||
|
||||
log_info("hooks::avs", "closing fake fd");
|
||||
} else {
|
||||
avs::core::avs_fs_close(fd);
|
||||
}
|
||||
}
|
||||
|
||||
static int avs_fs_copy(const char *sname, const char *dname) {
|
||||
if (sname == nullptr || dname == nullptr) {
|
||||
return avs::core::avs_fs_copy(sname, dname);
|
||||
}
|
||||
|
||||
auto value = avs::core::avs_fs_copy(sname, dname);
|
||||
WRAP_DEBUG_FMT("sname: {} dname {}", sname, dname);
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
static avs::core::avs_file_t avs_fs_opendir(const char *path) {
|
||||
if (path == nullptr) {
|
||||
return avs::core::avs_fs_opendir(path);
|
||||
}
|
||||
|
||||
auto value = avs::core::avs_fs_opendir(path);
|
||||
WRAP_DEBUG_FMT("path: {}", path);
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
static int avs_fs_mount(const char *mountpoint, const char *fsroot, const char *fstype, void *data) {
|
||||
// avs redirection breaks ongaku paradise
|
||||
if (mountpoint == nullptr ||
|
||||
fsroot == nullptr ||
|
||||
fstype == nullptr ||
|
||||
avs::game::is_model("JC9")) {
|
||||
return avs::core::avs_fs_mount(mountpoint, fsroot, fstype, data);
|
||||
}
|
||||
|
||||
std::optional<std::string> new_fs_root = std::nullopt;
|
||||
|
||||
if (_stricmp(mountpoint, "/mnt/ea3-config.xml") == 0 && is_dest_spec_ea3_config(fsroot)) {
|
||||
new_fs_root = fmt::format("/{}", avs::ea3::CFG_PATH);
|
||||
}
|
||||
|
||||
// remap drive mounts to `dev/vfs/drive_x` where x is the drive letter
|
||||
if (!config::DISABLE_VFS_DRIVE_REDIRECTION &&
|
||||
(_strnicmp(fsroot, "d:", 2) == 0 ||
|
||||
_strnicmp(fsroot, "e:", 2) == 0 ||
|
||||
_strnicmp(fsroot, "f:", 2) == 0) &&
|
||||
_stricmp(fstype, "fs") == 0)
|
||||
{
|
||||
// sub path is everything after the drive and colon characters
|
||||
const char drive_letter[2] {
|
||||
static_cast<char>(std::tolower(static_cast<unsigned char>(fsroot[0]))),
|
||||
'\0',
|
||||
};
|
||||
const auto separator = fsroot[2] == '/' ? "" : "/";
|
||||
const auto sub_path = &fsroot[2];
|
||||
const std::filesystem::path mapped_path = fmt::format(
|
||||
"dev/vfs/drive_{}{}{}",
|
||||
drive_letter,
|
||||
separator,
|
||||
sub_path);
|
||||
|
||||
// create the mapped directory path
|
||||
std::error_code err;
|
||||
std::filesystem::create_directories(mapped_path, err);
|
||||
|
||||
if (err) {
|
||||
log_warning("hooks::avs", "failed to create '{}': {}", mapped_path.string(), err.message());
|
||||
} else {
|
||||
|
||||
// if this is the `e:\`, then create the special directories
|
||||
if (drive_letter[0] == 'e' &&
|
||||
(sub_path[0] == '/' || sub_path[0] == '\\') &&
|
||||
sub_path[1] == '\0')
|
||||
{
|
||||
fileutils::dir_create_log("hooks::avs", mapped_path / "tmp");
|
||||
fileutils::dir_create_log("hooks::avs", mapped_path / "up");
|
||||
}
|
||||
|
||||
log_misc("hooks::avs", "source directory '{}' remapped to '{}'",
|
||||
fsroot,
|
||||
mapped_path.string());
|
||||
}
|
||||
|
||||
new_fs_root = mapped_path.string();
|
||||
}
|
||||
|
||||
auto fs_root_data = new_fs_root.has_value() ? new_fs_root->c_str() : fsroot;
|
||||
auto value = avs::core::avs_fs_mount(mountpoint, fs_root_data, fstype, data);
|
||||
|
||||
WRAP_DEBUG_FMT("mountpoint: {}, fsroot: {}, fstype: {}", mountpoint, fs_root_data, fstype);
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
static size_t avs_fs_read(avs::core::avs_file_t fd, uint8_t *data, uint32_t data_size) {
|
||||
if (is_fake_fd(fd) && ROM_FILE_CONTENTS) {
|
||||
const auto size = std::min(static_cast<size_t>(data_size), ROM_FILE_CONTENTS->length());
|
||||
|
||||
memcpy(data, ROM_FILE_CONTENTS->c_str(), size);
|
||||
|
||||
return size;
|
||||
}
|
||||
|
||||
return avs::core::avs_fs_read(fd, data, data_size);
|
||||
}
|
||||
|
||||
static int property_file_write(avs::core::property_ptr prop, const char* path) {
|
||||
if (prop == nullptr || path == nullptr) {
|
||||
return avs::core::property_file_write(prop, path);
|
||||
}
|
||||
|
||||
// resort anthem dumps eacoin to /dev/nvram/eacoin AND /eacoin.xml
|
||||
// it never reads /eacoin.xml, so it's probably a development leftover
|
||||
if (avs::game::is_model("JDZ") && _stricmp(path, "/eacoin.xml") == 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
auto value = avs::core::property_file_write(prop, path);
|
||||
|
||||
WRAP_DEBUG_FMT("path: {}", path);
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
void hooks::avs::init() {
|
||||
log_info("hooks::avs", "initializing");
|
||||
|
||||
AVS_HOOK(avs_fs_fstat);
|
||||
AVS_HOOK(avs_fs_lstat);
|
||||
AVS_HOOK(avs_fs_open);
|
||||
AVS_HOOK(avs_fs_close);
|
||||
AVS_HOOK(avs_fs_copy);
|
||||
AVS_HOOK(avs_fs_opendir);
|
||||
AVS_HOOK(avs_fs_mount);
|
||||
AVS_HOOK(avs_fs_read);
|
||||
AVS_HOOK(property_file_write);
|
||||
}
|
||||
|
||||
void hooks::avs::set_rom(const char *path, const char *contents) {
|
||||
ROM_FILE_MAP.emplace(path, contents);
|
||||
}
|
||||
17
hooks/avshook.h
Normal file
17
hooks/avshook.h
Normal file
@@ -0,0 +1,17 @@
|
||||
#pragma once
|
||||
|
||||
#include <string_view>
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
#include "avs/core.h"
|
||||
|
||||
namespace hooks::avs {
|
||||
namespace config {
|
||||
extern bool DISABLE_VFS_DRIVE_REDIRECTION;
|
||||
extern bool LOG;
|
||||
};
|
||||
|
||||
void init();
|
||||
void set_rom(const char *path, const char *contents);
|
||||
}
|
||||
84
hooks/cfgmgr32hook.cpp
Normal file
84
hooks/cfgmgr32hook.cpp
Normal file
@@ -0,0 +1,84 @@
|
||||
#include "cfgmgr32hook.h"
|
||||
|
||||
#include <vector>
|
||||
#include <windows.h>
|
||||
#include <cfgmgr32.h>
|
||||
|
||||
#include "util/detour.h"
|
||||
|
||||
typedef CONFIGRET (WINAPI *CM_Locate_DevNodeA_t)(PDEVINST, DEVINSTID_A, ULONG);
|
||||
typedef CONFIGRET (WINAPI *CM_Get_Parent_t)(PDEVINST, DEVINST, ULONG);
|
||||
typedef CONFIGRET (WINAPI *CM_Get_Device_IDA_t)(DEVINST, PSTR, ULONG, ULONG);
|
||||
|
||||
static CM_Locate_DevNodeA_t CM_Locate_DevNodeA_real = nullptr;
|
||||
static CM_Get_Parent_t CM_Get_Parent_real = nullptr;
|
||||
static CM_Get_Device_IDA_t CM_Get_Device_IDA_real = nullptr;
|
||||
|
||||
static std::vector<CFGMGR32_HOOK_SETTING> CFGMGR32_HOOK_SETTINGS;
|
||||
|
||||
static CONFIGRET WINAPI CM_Locate_DevNodeA_hook(PDEVINST pdnDevInst, DEVINSTID_A pDeviceID, ULONG ulFlags) {
|
||||
|
||||
// check device ID
|
||||
if (!pDeviceID)
|
||||
return CM_Locate_DevNodeA_real(pdnDevInst, pDeviceID, ulFlags);
|
||||
|
||||
// custom
|
||||
for (auto &setting : CFGMGR32_HOOK_SETTINGS) {
|
||||
if (_stricmp(pDeviceID, setting.device_node_id.c_str()) == 0) {
|
||||
*pdnDevInst = (DEVINST) setting.device_instance;
|
||||
return CR_SUCCESS;
|
||||
}
|
||||
}
|
||||
|
||||
// fallback
|
||||
return CM_Locate_DevNodeA_real(pdnDevInst, pDeviceID, ulFlags);
|
||||
}
|
||||
|
||||
static CONFIGRET WINAPI CM_Get_Parent_hook(PDEVINST pdnDevInst, DEVINST dnDevInst, ULONG ulFlags) {
|
||||
|
||||
// custom
|
||||
for (auto &setting : CFGMGR32_HOOK_SETTINGS) {
|
||||
if (dnDevInst == (DEVINST) setting.device_instance) {
|
||||
*pdnDevInst = (DEVINST) setting.parent_instance;
|
||||
return CR_SUCCESS;
|
||||
}
|
||||
}
|
||||
|
||||
// fallback
|
||||
return CM_Get_Parent_real(pdnDevInst, dnDevInst, ulFlags);
|
||||
}
|
||||
|
||||
static CONFIGRET WINAPI CM_Get_Device_IDA_hook(DEVINST dnDevInst, PSTR Buffer, ULONG BufferLen, ULONG ulFlags) {
|
||||
|
||||
// custom
|
||||
for (auto &setting : CFGMGR32_HOOK_SETTINGS) {
|
||||
if (dnDevInst == (DEVINST) setting.parent_instance) {
|
||||
|
||||
// check buffer size
|
||||
if (BufferLen <= setting.device_id.length())
|
||||
return CR_BUFFER_SMALL;
|
||||
|
||||
// copy device ID to buffer
|
||||
memcpy(Buffer, setting.device_id.c_str(), setting.device_id.length() + 1);
|
||||
return CR_SUCCESS;
|
||||
}
|
||||
}
|
||||
|
||||
// fallback
|
||||
return CM_Get_Device_IDA_real(dnDevInst, Buffer, BufferLen, ulFlags);
|
||||
}
|
||||
|
||||
void cfgmgr32hook_init(HINSTANCE module) {
|
||||
|
||||
// hook functions
|
||||
CM_Locate_DevNodeA_real = (CM_Locate_DevNodeA_t) detour::iat_try(
|
||||
"CM_Locate_DevNodeA", (void *) &CM_Locate_DevNodeA_hook, module);
|
||||
CM_Get_Parent_real = (CM_Get_Parent_t) detour::iat_try(
|
||||
"CM_Get_Parent", (void *) &CM_Get_Parent_hook, module);
|
||||
CM_Get_Device_IDA_real = (CM_Get_Device_IDA_t) detour::iat_try(
|
||||
"CM_Get_Device_IDA", (void *) &CM_Get_Device_IDA_hook, module);
|
||||
}
|
||||
|
||||
void cfgmgr32hook_add(CFGMGR32_HOOK_SETTING setting) {
|
||||
CFGMGR32_HOOK_SETTINGS.push_back(setting);
|
||||
}
|
||||
15
hooks/cfgmgr32hook.h
Normal file
15
hooks/cfgmgr32hook.h
Normal file
@@ -0,0 +1,15 @@
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
|
||||
#include <windows.h>
|
||||
|
||||
struct CFGMGR32_HOOK_SETTING {
|
||||
DWORD device_instance;
|
||||
DWORD parent_instance;
|
||||
std::string device_node_id;
|
||||
std::string device_id;
|
||||
};
|
||||
|
||||
void cfgmgr32hook_init(HINSTANCE module);
|
||||
void cfgmgr32hook_add(CFGMGR32_HOOK_SETTING setting);
|
||||
103
hooks/debughook.cpp
Normal file
103
hooks/debughook.cpp
Normal file
@@ -0,0 +1,103 @@
|
||||
#include "debughook.h"
|
||||
#include "util/utils.h"
|
||||
#include "util/detour.h"
|
||||
#include "util/libutils.h"
|
||||
#include "avs/core.h"
|
||||
#include "avs/ea3.h"
|
||||
#include "avs/game.h"
|
||||
|
||||
namespace debughook {
|
||||
|
||||
// settings
|
||||
bool DEBUGHOOK_LOGGING = true;
|
||||
|
||||
// function pointers
|
||||
static decltype(OutputDebugStringA) *OutputDebugStringA_orig = nullptr;
|
||||
static decltype(OutputDebugStringW) *OutputDebugStringW_orig = nullptr;
|
||||
|
||||
static void WINAPI OutputDebugStringA_hook(LPCTSTR str) {
|
||||
|
||||
// check if logging is enabled
|
||||
if (!DEBUGHOOK_LOGGING) {
|
||||
return;
|
||||
}
|
||||
|
||||
// create buffer
|
||||
auto len = strlen(str);
|
||||
auto buf = new TCHAR[len + 1];
|
||||
memset(buf, 0, len + 1);
|
||||
|
||||
// copy to buffer, log message on new lines
|
||||
size_t buf_i = 0;
|
||||
for (size_t i = 0; str[i] != 0 && i < len; i++) {
|
||||
if (str[i] == '\r') {
|
||||
|
||||
// skip carriage return
|
||||
continue;
|
||||
} else if (str[i] == '\n') {
|
||||
|
||||
// null terminate buffer
|
||||
buf[buf_i] = '\0';
|
||||
|
||||
// log buffer
|
||||
log_info("debughook", "{}", buf);
|
||||
|
||||
// reset buffer
|
||||
len -= buf_i;
|
||||
buf_i = 0;
|
||||
memset(buf, 0, len + 1);
|
||||
} else {
|
||||
buf[buf_i] = str[i];
|
||||
buf_i++;
|
||||
}
|
||||
}
|
||||
|
||||
// log buffer if there are remaining characters
|
||||
if (buf_i > 0) {
|
||||
log_info("debughook", "{}", buf);
|
||||
}
|
||||
|
||||
// delete buffer
|
||||
delete[] buf;
|
||||
}
|
||||
|
||||
static void WINAPI OutputDebugStringW_hook(const wchar_t *str) {
|
||||
|
||||
// check if logging is enabled
|
||||
if (!DEBUGHOOK_LOGGING) {
|
||||
return;
|
||||
}
|
||||
|
||||
OutputDebugStringA_hook(ws2s(str).c_str());
|
||||
}
|
||||
|
||||
void attach() {
|
||||
log_info("debughook", "attaching...");
|
||||
|
||||
HMODULE kernel32 = libutils::try_module("kernel32.dll");
|
||||
detour::inline_hook((void *) OutputDebugStringA_hook,
|
||||
libutils::try_proc(kernel32, "OutputDebugStringA"));
|
||||
detour::inline_hook((void *) OutputDebugStringW_hook,
|
||||
libutils::try_proc(kernel32, "OutputDebugStringW"));
|
||||
OutputDebugStringA_orig = detour::iat_try(
|
||||
"OutputDebugStringA", OutputDebugStringA_hook, nullptr);
|
||||
OutputDebugStringW_orig = detour::iat_try(
|
||||
"OutputDebugStringW", OutputDebugStringW_hook, nullptr);
|
||||
|
||||
log_info("debughook", "attached");
|
||||
}
|
||||
|
||||
void detach() {
|
||||
log_info("debughook", "detaching...");
|
||||
|
||||
if (OutputDebugStringA_orig != nullptr) {
|
||||
detour::iat_try("OutputDebugStringA", OutputDebugStringA_orig, nullptr);
|
||||
}
|
||||
if (OutputDebugStringW_orig != nullptr) {
|
||||
detour::iat_try("OutputDebugStringW", OutputDebugStringW_orig, nullptr);
|
||||
}
|
||||
|
||||
log_info("debughook", "detached");
|
||||
}
|
||||
|
||||
}
|
||||
16
hooks/debughook.h
Normal file
16
hooks/debughook.h
Normal file
@@ -0,0 +1,16 @@
|
||||
#pragma once
|
||||
|
||||
#include <windows.h>
|
||||
|
||||
#include "util/detour.h"
|
||||
#include "util/logging.h"
|
||||
|
||||
namespace debughook {
|
||||
|
||||
// settings
|
||||
extern bool DEBUGHOOK_LOGGING;
|
||||
|
||||
// functions
|
||||
void attach();
|
||||
void detach();
|
||||
}
|
||||
633
hooks/devicehook.cpp
Normal file
633
hooks/devicehook.cpp
Normal file
@@ -0,0 +1,633 @@
|
||||
#include "devicehook.h"
|
||||
|
||||
#include <vector>
|
||||
|
||||
#include "avs/game.h"
|
||||
#include "util/detour.h"
|
||||
#include "util/utils.h"
|
||||
|
||||
#include <tlhelp32.h>
|
||||
|
||||
// std::min
|
||||
#ifdef min
|
||||
#undef min
|
||||
#endif
|
||||
|
||||
namespace hooks::device {
|
||||
bool ENABLE = true;
|
||||
}
|
||||
|
||||
bool DEVICE_CREATEFILE_DEBUG = false;
|
||||
static std::string PATH_HARD_CODE_COMPARE = "d:/###-###/contents";
|
||||
|
||||
static decltype(ClearCommBreak) *ClearCommBreak_orig = nullptr;
|
||||
static decltype(ClearCommError) *ClearCommError_orig = nullptr;
|
||||
static decltype(CloseHandle) *CloseHandle_orig = nullptr;
|
||||
static decltype(CreateFileA) *CreateFileA_orig = nullptr;
|
||||
static decltype(CreateFileW) *CreateFileW_orig = nullptr;
|
||||
static decltype(DeviceIoControl) *DeviceIoControl_orig = nullptr;
|
||||
static decltype(EscapeCommFunction) *EscapeCommFunction_orig = nullptr;
|
||||
static decltype(GetCommState) *GetCommState_orig = nullptr;
|
||||
static decltype(GetFileSize) *GetFileSize_orig = nullptr;
|
||||
static decltype(GetFileSizeEx) *GetFileSizeEx_orig = nullptr;
|
||||
static decltype(GetFileInformationByHandle) *GetFileInformationByHandle_orig = nullptr;
|
||||
static decltype(PurgeComm) *PurgeComm_orig = nullptr;
|
||||
static decltype(ReadFile) *ReadFile_orig = nullptr;
|
||||
static decltype(SetupComm) *SetupComm_orig = nullptr;
|
||||
static decltype(SetCommBreak) *SetCommBreak_orig = nullptr;
|
||||
static decltype(SetCommMask) *SetCommMask_orig = nullptr;
|
||||
static decltype(SetCommState) *SetCommState_orig = nullptr;
|
||||
static decltype(SetCommTimeouts) *SetCommTimeouts_orig = nullptr;
|
||||
static decltype(WriteFile) *WriteFile_orig = nullptr;
|
||||
|
||||
static std::vector<CustomHandle *> CUSTOM_HANDLES;
|
||||
|
||||
MITMHandle::MITMHandle(LPCWSTR lpFileName, std::string rec_file, bool lpFileNameContains) {
|
||||
this->lpFileName = lpFileName;
|
||||
this->rec_file = rec_file;
|
||||
this->lpFileNameContains = lpFileNameContains;
|
||||
this->com_pass = true;
|
||||
}
|
||||
|
||||
bool MITMHandle::open(LPCWSTR lpFileName, DWORD dwDesiredAccess, DWORD dwShareMode,
|
||||
LPSECURITY_ATTRIBUTES lpSecurityAttributes, DWORD dwCreationDisposition,
|
||||
DWORD dwFlagsAndAttributes, HANDLE hTemplateFile) {
|
||||
|
||||
// check if file name matches
|
||||
if (this->lpFileNameContains) {
|
||||
if (wcsstr(lpFileName, this->lpFileName) == 0)
|
||||
return false;
|
||||
} else {
|
||||
if (wcscmp(lpFileName, this->lpFileName))
|
||||
return false;
|
||||
}
|
||||
|
||||
// create our own file handle
|
||||
handle = CreateFileW_orig(lpFileName, dwDesiredAccess, dwShareMode,
|
||||
lpSecurityAttributes, dwCreationDisposition,
|
||||
dwFlagsAndAttributes, hTemplateFile);
|
||||
|
||||
// check if it worked - if not device hook will try again without us
|
||||
return handle != INVALID_HANDLE_VALUE;
|
||||
}
|
||||
|
||||
int MITMHandle::read(LPVOID lpBuffer, DWORD nNumberOfBytesToRead) {
|
||||
DWORD lpNumberOfBytesRead = 0;
|
||||
auto res = ReadFile_orig(handle, lpBuffer, nNumberOfBytesToRead,
|
||||
&lpNumberOfBytesRead, NULL);
|
||||
if (res) {
|
||||
|
||||
// record
|
||||
log_info("mitm", "read: {}", bin2hex((uint8_t*) lpBuffer, lpNumberOfBytesRead));
|
||||
|
||||
// pass
|
||||
return lpNumberOfBytesRead;
|
||||
} else {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
int MITMHandle::write(LPCVOID lpBuffer, DWORD nNumberOfBytesToWrite) {
|
||||
DWORD lpNumberOfBytesWritten = 0;
|
||||
auto res = WriteFile_orig(handle, lpBuffer, nNumberOfBytesToWrite,
|
||||
&lpNumberOfBytesWritten, NULL);
|
||||
if (res) {
|
||||
|
||||
// record
|
||||
log_info("mitm", "write: {}", bin2hex((uint8_t*) lpBuffer, lpNumberOfBytesWritten));
|
||||
|
||||
// pass
|
||||
return lpNumberOfBytesWritten;
|
||||
} else {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
int MITMHandle::device_io(DWORD dwIoControlCode, LPVOID lpInBuffer,
|
||||
DWORD nInBufferSize, LPVOID lpOutBuffer, DWORD nOutBufferSize) {
|
||||
DWORD lpBytesReturned = 0;
|
||||
auto res = DeviceIoControl_orig(handle, dwIoControlCode, lpInBuffer, nInBufferSize,
|
||||
lpOutBuffer, nOutBufferSize, &lpBytesReturned, NULL);
|
||||
if (res) {
|
||||
|
||||
// record
|
||||
log_info("mitm", "device_io");
|
||||
|
||||
return lpBytesReturned;
|
||||
} else {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
size_t MITMHandle::bytes_available() {
|
||||
COMSTAT status;
|
||||
ClearCommError_orig(handle, NULL, &status);
|
||||
return status.cbInQue;
|
||||
}
|
||||
|
||||
bool MITMHandle::close() {
|
||||
return CloseHandle_orig(handle);
|
||||
}
|
||||
|
||||
static inline CustomHandle *get_custom_handle(HANDLE handle) {
|
||||
|
||||
// TODO: we can make a custom allocator for the handles and
|
||||
// add a simple range check instead of going through the
|
||||
// whole list each time
|
||||
|
||||
// find handle in list
|
||||
for (auto custom_handle : CUSTOM_HANDLES) {
|
||||
if (reinterpret_cast<HANDLE>(custom_handle) == handle
|
||||
|| custom_handle->handle == handle) {
|
||||
return custom_handle;
|
||||
}
|
||||
}
|
||||
|
||||
// no handle found - hooks will call original functions for this
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
static HANDLE WINAPI CreateFileA_hook(LPCSTR lpFileName, DWORD dwDesiredAccess, DWORD dwShareMode,
|
||||
LPSECURITY_ATTRIBUTES lpSecurityAttributes, DWORD dwCreationDisposition,
|
||||
DWORD dwFlagsAndAttributes, HANDLE hTemplateFile) {
|
||||
HANDLE result = INVALID_HANDLE_VALUE;
|
||||
|
||||
// convert to wide char
|
||||
WCHAR lpFileNameW[512] { 0 };
|
||||
if (!MultiByteToWideChar(CP_ACP, 0, lpFileName, -1, lpFileNameW, std::size(lpFileNameW))) {
|
||||
return result;
|
||||
}
|
||||
|
||||
// debug
|
||||
if (DEVICE_CREATEFILE_DEBUG && lpFileName != nullptr) {
|
||||
log_info("devicehook", "CreateFileA(\"{}\") => len: {}", lpFileName, strlen(lpFileName));
|
||||
}
|
||||
|
||||
// check custom handles
|
||||
if (!CUSTOM_HANDLES.empty()) {
|
||||
for (auto handle : CUSTOM_HANDLES) {
|
||||
if (handle->open(lpFileNameW, dwDesiredAccess, dwShareMode, lpSecurityAttributes,
|
||||
dwCreationDisposition, dwFlagsAndAttributes, hTemplateFile)) {
|
||||
SetLastError(0);
|
||||
if (handle->handle != INVALID_HANDLE_VALUE) {
|
||||
result = handle->handle;
|
||||
} else {
|
||||
result = (HANDLE) handle;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// hard coded paths fix
|
||||
auto lpFileNameLen = wcslen(lpFileNameW);
|
||||
bool fix = true;
|
||||
for (size_t i = 0, c = 0; i < lpFileNameLen && (c = PATH_HARD_CODE_COMPARE[i]) != 0; i++) {
|
||||
if (c != '#' && lpFileName[i] != (wchar_t) PATH_HARD_CODE_COMPARE[i]) {
|
||||
fix = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// do the fix
|
||||
if (fix && lpFileNameLen >= PATH_HARD_CODE_COMPARE.size()) {
|
||||
auto hcLen = PATH_HARD_CODE_COMPARE.size();
|
||||
auto buffer = std::make_unique<char[]>(lpFileNameLen + 1);
|
||||
|
||||
buffer[0] = '.';
|
||||
|
||||
for (size_t i = 0; i < lpFileNameLen - hcLen; i++) {
|
||||
buffer[i + 1] = lpFileName[hcLen + i];
|
||||
}
|
||||
|
||||
if (DEVICE_CREATEFILE_DEBUG) {
|
||||
log_info("devicehook", "CreateFileA (fix): {}", buffer.get());
|
||||
}
|
||||
|
||||
return CreateFileA_orig(buffer.get(), dwDesiredAccess, dwShareMode, lpSecurityAttributes,
|
||||
dwCreationDisposition, dwFlagsAndAttributes, hTemplateFile);
|
||||
}
|
||||
|
||||
// fallback
|
||||
if (result == INVALID_HANDLE_VALUE) {
|
||||
result = CreateFileA_orig(lpFileName, dwDesiredAccess, dwShareMode, lpSecurityAttributes,
|
||||
dwCreationDisposition, dwFlagsAndAttributes, hTemplateFile);
|
||||
}
|
||||
|
||||
// return result
|
||||
return result;
|
||||
}
|
||||
|
||||
static HANDLE WINAPI CreateFileW_hook(LPCWSTR lpFileName, DWORD dwDesiredAccess, DWORD dwShareMode,
|
||||
LPSECURITY_ATTRIBUTES lpSecurityAttributes, DWORD dwCreationDisposition,
|
||||
DWORD dwFlagsAndAttributes, HANDLE hTemplateFile)
|
||||
{
|
||||
HANDLE result = INVALID_HANDLE_VALUE;
|
||||
|
||||
// debug
|
||||
if (DEVICE_CREATEFILE_DEBUG && lpFileName != nullptr) {
|
||||
log_info("devicehook", "CreateFileW: {}", ws2s(lpFileName));
|
||||
}
|
||||
|
||||
// check custom handles
|
||||
if (!CUSTOM_HANDLES.empty()) {
|
||||
for (auto handle : CUSTOM_HANDLES) {
|
||||
if (handle->open(lpFileName, dwDesiredAccess, dwShareMode, lpSecurityAttributes,
|
||||
dwCreationDisposition, dwFlagsAndAttributes, hTemplateFile)) {
|
||||
SetLastError(0);
|
||||
if (handle->handle != INVALID_HANDLE_VALUE) {
|
||||
result = handle->handle;
|
||||
} else {
|
||||
result = (HANDLE) handle;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// hard coded paths fix
|
||||
bool fix = true;
|
||||
auto lpFileNameLen = wcslen(lpFileName);
|
||||
for (size_t i = 0, c = 0; i < lpFileNameLen && (c = PATH_HARD_CODE_COMPARE[i]) != 0; i++) {
|
||||
if (c != '#' && lpFileName[i] != (wchar_t) PATH_HARD_CODE_COMPARE[i]) {
|
||||
fix = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// do the fix
|
||||
if (fix && lpFileNameLen >= PATH_HARD_CODE_COMPARE.size()) {
|
||||
auto hcLen = PATH_HARD_CODE_COMPARE.size();
|
||||
auto buffer = std::make_unique<wchar_t[]>(lpFileNameLen + 1);
|
||||
|
||||
buffer[0] = '.';
|
||||
|
||||
for (size_t i = 0; i < lpFileNameLen - hcLen; i++) {
|
||||
buffer[i + 1] = lpFileName[hcLen + i];
|
||||
}
|
||||
|
||||
if (DEVICE_CREATEFILE_DEBUG) {
|
||||
log_info("devicehook", "CreateFileW (fix): {}", ws2s(buffer.get()));
|
||||
}
|
||||
|
||||
return CreateFileW_orig(buffer.get(), dwDesiredAccess, dwShareMode, lpSecurityAttributes,
|
||||
dwCreationDisposition, dwFlagsAndAttributes, hTemplateFile);
|
||||
}
|
||||
|
||||
// fallback
|
||||
if (result == INVALID_HANDLE_VALUE) {
|
||||
result = CreateFileW_orig(lpFileName, dwDesiredAccess, dwShareMode, lpSecurityAttributes,
|
||||
dwCreationDisposition, dwFlagsAndAttributes, hTemplateFile);
|
||||
}
|
||||
|
||||
// return result
|
||||
return result;
|
||||
}
|
||||
|
||||
static BOOL WINAPI ReadFile_hook(HANDLE hFile, LPVOID lpBuffer, DWORD nNumberOfBytesToRead, LPDWORD lpNumberOfBytesRead,
|
||||
LPOVERLAPPED lpOverlapped)
|
||||
{
|
||||
auto *custom_handle = get_custom_handle(hFile);
|
||||
if (custom_handle) {
|
||||
int value = custom_handle->read(lpBuffer, nNumberOfBytesToRead);
|
||||
if (value >= 0) {
|
||||
SetLastError(0);
|
||||
*lpNumberOfBytesRead = (DWORD) value;
|
||||
return true;
|
||||
} else {
|
||||
SetLastError(0xD);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// fallback
|
||||
return ReadFile_orig(hFile, lpBuffer, nNumberOfBytesToRead, lpNumberOfBytesRead, lpOverlapped);
|
||||
}
|
||||
|
||||
static BOOL WINAPI WriteFile_hook(HANDLE hFile, LPCVOID lpBuffer, DWORD nNumberOfBytesToWrite,
|
||||
LPDWORD lpNumberOfBytesWritten, LPOVERLAPPED lpOverlapped)
|
||||
{
|
||||
auto *custom_handle = get_custom_handle(hFile);
|
||||
if (custom_handle) {
|
||||
int value = custom_handle->write(lpBuffer, nNumberOfBytesToWrite);
|
||||
if (value >= 0) {
|
||||
SetLastError(0);
|
||||
*lpNumberOfBytesWritten = (DWORD) value;
|
||||
return true;
|
||||
} else {
|
||||
SetLastError(0xD);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// fallback
|
||||
return WriteFile_orig(hFile, lpBuffer, nNumberOfBytesToWrite, lpNumberOfBytesWritten, lpOverlapped);
|
||||
}
|
||||
|
||||
static BOOL WINAPI DeviceIoControl_hook(HANDLE hDevice, DWORD dwIoControlCode, LPVOID lpInBuffer, DWORD nInBufferSize,
|
||||
LPVOID lpOutBuffer, DWORD nOutBufferSize, LPDWORD lpBytesReturned,
|
||||
LPOVERLAPPED lpOverlapped)
|
||||
{
|
||||
auto *custom_handle = get_custom_handle(hDevice);
|
||||
if (custom_handle) {
|
||||
int count = custom_handle->device_io(dwIoControlCode, lpInBuffer, nInBufferSize, lpOutBuffer, nOutBufferSize);
|
||||
if (count >= 0) {
|
||||
SetLastError(0);
|
||||
*lpBytesReturned = (DWORD) count;
|
||||
if (lpOverlapped) {
|
||||
SetEvent(lpOverlapped->hEvent);
|
||||
}
|
||||
return true;
|
||||
} else {
|
||||
log_info("devicehook", "device_io failed");
|
||||
SetLastError(0xD);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// fallback
|
||||
return DeviceIoControl_orig(hDevice, dwIoControlCode, lpInBuffer, nInBufferSize, lpOutBuffer, nOutBufferSize,
|
||||
lpBytesReturned, lpOverlapped);
|
||||
}
|
||||
|
||||
static DWORD WINAPI GetFileSize_hook(HANDLE hFile, LPDWORD lpFileSizeHigh) {
|
||||
//log_info("devicehook", "GetFileSizeHook hit");
|
||||
return GetFileSize_orig(hFile, lpFileSizeHigh);
|
||||
}
|
||||
|
||||
static BOOL WINAPI GetFileSizeEx_hook(HANDLE hFile, PLARGE_INTEGER lpFileSizeHigh) {
|
||||
//log_info("devicehook", "GetFileSizeExHook hit");
|
||||
return GetFileSizeEx_orig(hFile, lpFileSizeHigh);
|
||||
}
|
||||
|
||||
static BOOL WINAPI GetFileInformationByHandle_hook(HANDLE hFile,
|
||||
LPBY_HANDLE_FILE_INFORMATION lpFileInformation)
|
||||
{
|
||||
// custom handle
|
||||
auto *custom_handle = get_custom_handle(hFile);
|
||||
if (custom_handle) {
|
||||
SetLastError(0);
|
||||
custom_handle->file_info(lpFileInformation);
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
return GetFileInformationByHandle_orig(hFile, lpFileInformation);
|
||||
}
|
||||
|
||||
static BOOL WINAPI ClearCommBreak_hook(HANDLE hFile) {
|
||||
auto *custom_handle = get_custom_handle(hFile);
|
||||
if (custom_handle && !custom_handle->com_pass) {
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
return ClearCommBreak_orig(hFile);
|
||||
}
|
||||
|
||||
static BOOL WINAPI ClearCommError_hook(HANDLE hFile, LPDWORD lpErrors, LPCOMSTAT lpStat) {
|
||||
auto *custom_handle = get_custom_handle(hFile);
|
||||
if (custom_handle && !custom_handle->com_pass) {
|
||||
if (lpStat) {
|
||||
lpStat->fXoffSent = 1;
|
||||
|
||||
/*
|
||||
* Some games may check the input queue size.
|
||||
* QMA does not even attempt to read if this is set to 0.
|
||||
* We just set this to 255 and hope games do not rely on this for buffer sizes.
|
||||
*
|
||||
* Message from the future: As it turned out, some games (CCJ) do in fact rely on this value.
|
||||
*/
|
||||
lpStat->cbInQue = custom_handle->bytes_available();
|
||||
}
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
return ClearCommError_orig(hFile, lpErrors, lpStat);
|
||||
}
|
||||
|
||||
static BOOL WINAPI EscapeCommFunction_hook(HANDLE hFile, DWORD dwFunc) {
|
||||
auto *custom_handle = get_custom_handle(hFile);
|
||||
if (custom_handle && !custom_handle->com_pass) {
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
return EscapeCommFunction_orig(hFile, dwFunc);
|
||||
}
|
||||
|
||||
static BOOL WINAPI GetCommState_hook(HANDLE hFile, LPDCB lpDCB) {
|
||||
auto *custom_handle = get_custom_handle(hFile);
|
||||
if (custom_handle && !custom_handle->com_pass) {
|
||||
auto *comm_state = &custom_handle->comm_state;
|
||||
|
||||
memcpy(lpDCB, comm_state, std::min(static_cast<size_t>(comm_state->DCBlength), sizeof(*comm_state)));
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
return GetCommState_orig(hFile, lpDCB);
|
||||
}
|
||||
|
||||
static BOOL WINAPI PurgeComm_hook(HANDLE hFile, DWORD dwFlags) {
|
||||
auto *custom_handle = get_custom_handle(hFile);
|
||||
if (custom_handle && !custom_handle->com_pass) {
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
return PurgeComm_orig(hFile, dwFlags);
|
||||
}
|
||||
|
||||
static BOOL WINAPI SetupComm_hook(HANDLE hFile, DWORD dwInQueue, DWORD dwOutQueue) {
|
||||
auto *custom_handle = get_custom_handle(hFile);
|
||||
if (custom_handle && !custom_handle->com_pass) {
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
return SetupComm_orig(hFile, dwInQueue, dwOutQueue);
|
||||
}
|
||||
|
||||
static BOOL WINAPI SetCommBreak_hook(HANDLE hFile) {
|
||||
auto *custom_handle = get_custom_handle(hFile);
|
||||
if (custom_handle && !custom_handle->com_pass) {
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
return SetCommBreak_orig(hFile);
|
||||
}
|
||||
|
||||
static BOOL WINAPI SetCommMask_hook(HANDLE hFile, DWORD dwEvtMask) {
|
||||
auto *custom_handle = get_custom_handle(hFile);
|
||||
if (custom_handle && !custom_handle->com_pass) {
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
return SetCommMask_orig(hFile, dwEvtMask);
|
||||
}
|
||||
|
||||
static BOOL WINAPI SetCommState_hook(HANDLE hFile, LPDCB lpDCB) {
|
||||
auto *custom_handle = get_custom_handle(hFile);
|
||||
if (custom_handle) {
|
||||
|
||||
// sanity check
|
||||
if (lpDCB->DCBlength <= sizeof(custom_handle->comm_state)) {
|
||||
memcpy(&custom_handle->comm_state, lpDCB, lpDCB->DCBlength);
|
||||
}
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
return SetCommState_orig(hFile, lpDCB);
|
||||
}
|
||||
|
||||
static BOOL WINAPI SetCommTimeouts_hook(HANDLE hFile, LPCOMMTIMEOUTS lpCommTimeouts) {
|
||||
auto *custom_handle = get_custom_handle(hFile);
|
||||
if (custom_handle && !custom_handle->com_pass) {
|
||||
memcpy(&custom_handle->comm_timeouts, lpCommTimeouts, sizeof(custom_handle->comm_timeouts));
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
return SetCommTimeouts_orig(hFile, lpCommTimeouts);
|
||||
}
|
||||
|
||||
static BOOL WINAPI CloseHandle_hook(HANDLE hObject) {
|
||||
auto *custom_handle = get_custom_handle(hObject);
|
||||
if (custom_handle) {
|
||||
SetLastError(0);
|
||||
return custom_handle->close();
|
||||
}
|
||||
|
||||
// call original
|
||||
return CloseHandle_orig(hObject);
|
||||
}
|
||||
|
||||
static void suspend_or_resume_other_threads(bool suspending) {
|
||||
HANDLE hThreadSnap = CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, 0);
|
||||
if (hThreadSnap == INVALID_HANDLE_VALUE) {
|
||||
return;
|
||||
}
|
||||
|
||||
THREADENTRY32 te32;
|
||||
te32.dwSize = sizeof(THREADENTRY32);
|
||||
|
||||
if (Thread32First(hThreadSnap, &te32)) {
|
||||
do {
|
||||
if (te32.th32OwnerProcessID == GetCurrentProcessId()) {
|
||||
if(te32.th32ThreadID == GetCurrentThreadId()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
HANDLE hThread = OpenThread(THREAD_SUSPEND_RESUME, FALSE, te32.th32ThreadID);
|
||||
if (hThread) {
|
||||
if (suspending) {
|
||||
SuspendThread(hThread);
|
||||
} else {
|
||||
ResumeThread(hThread);
|
||||
}
|
||||
CloseHandle(hThread);
|
||||
}
|
||||
}
|
||||
} while (Thread32Next(hThreadSnap, &te32));
|
||||
}
|
||||
|
||||
CloseHandle(hThreadSnap);
|
||||
}
|
||||
|
||||
void devicehook_init(HMODULE module) {
|
||||
if (!hooks::device::ENABLE) {
|
||||
return;
|
||||
}
|
||||
|
||||
#define STORE(value, expr) { \
|
||||
auto tmp = (expr); \
|
||||
if ((value) == nullptr) { \
|
||||
(value) = tmp; \
|
||||
} \
|
||||
}
|
||||
|
||||
/*
|
||||
// initialize only once
|
||||
static bool initialized = false;
|
||||
if (initialized) {
|
||||
return;
|
||||
} else {
|
||||
initialized = true;
|
||||
}
|
||||
*/
|
||||
|
||||
log_info("devicehook", "init");
|
||||
|
||||
suspend_or_resume_other_threads(true);
|
||||
|
||||
// IAT hooks
|
||||
STORE(ClearCommBreak_orig, detour::iat_try("ClearCommBreak", ClearCommBreak_hook, module));
|
||||
STORE(ClearCommError_orig, detour::iat_try("ClearCommError", ClearCommError_hook, module));
|
||||
STORE(CloseHandle_orig, detour::iat_try("CloseHandle", CloseHandle_hook, module));
|
||||
STORE(CreateFileA_orig, detour::iat_try("CreateFileA", CreateFileA_hook, module));
|
||||
STORE(CreateFileW_orig, detour::iat_try("CreateFileW", CreateFileW_hook, module));
|
||||
STORE(DeviceIoControl_orig, detour::iat_try("DeviceIoControl", DeviceIoControl_hook, module));
|
||||
STORE(EscapeCommFunction_orig, detour::iat_try("EscapeCommFunction", EscapeCommFunction_hook, module));
|
||||
STORE(GetCommState_orig, detour::iat_try("GetCommState", GetCommState_hook, module));
|
||||
STORE(GetFileSize_orig, detour::iat_try("GetFileSize", GetFileSize_hook, module));
|
||||
STORE(GetFileSizeEx_orig, detour::iat_try("GetFileSize", GetFileSizeEx_hook, module));
|
||||
STORE(GetFileInformationByHandle_orig, detour::iat_try(
|
||||
"GetFileInformationByHandle", GetFileInformationByHandle_hook, module));
|
||||
STORE(PurgeComm_orig, detour::iat_try("PurgeComm", PurgeComm_hook, module));
|
||||
STORE(ReadFile_orig, detour::iat_try("ReadFile", ReadFile_hook, module));
|
||||
STORE(SetupComm_orig, detour::iat_try("SetupComm", SetupComm_hook, module));
|
||||
STORE(SetCommBreak_orig, detour::iat_try("SetCommBreak", SetCommBreak_hook, module));
|
||||
STORE(SetCommMask_orig, detour::iat_try("SetCommMask", SetCommMask_hook, module));
|
||||
STORE(SetCommState_orig, detour::iat_try("SetCommState", SetCommState_hook, module));
|
||||
STORE(SetCommTimeouts_orig, detour::iat_try("SetCommTimeouts", SetCommTimeouts_hook, module));
|
||||
STORE(WriteFile_orig, detour::iat_try("WriteFile", WriteFile_hook, module));
|
||||
|
||||
suspend_or_resume_other_threads(false);
|
||||
|
||||
#undef STORE
|
||||
}
|
||||
|
||||
void devicehook_init_trampoline() {
|
||||
// initialize only once
|
||||
static bool initialized = false;
|
||||
if (initialized) {
|
||||
return;
|
||||
} else {
|
||||
initialized = true;
|
||||
}
|
||||
|
||||
suspend_or_resume_other_threads(true);
|
||||
|
||||
detour::trampoline_try("kernel32.dll", "ClearCommBreak", ClearCommBreak_hook, &ClearCommBreak_orig);
|
||||
detour::trampoline_try("kernel32.dll", "ClearCommError", ClearCommError_hook, &ClearCommError_orig);
|
||||
detour::trampoline_try("kernel32.dll", "CloseHandle", CloseHandle_hook, &CloseHandle_orig);
|
||||
detour::trampoline_try("kernel32.dll", "CreateFileA", CreateFileA_hook, &CreateFileA_orig);
|
||||
detour::trampoline_try("kernel32.dll", "CreateFileW", CreateFileW_hook, &CreateFileW_orig);
|
||||
detour::trampoline_try("kernel32.dll", "DeviceIoControl", DeviceIoControl_hook, &DeviceIoControl_orig);
|
||||
detour::trampoline_try("kernel32.dll", "EscapeCommFunction", EscapeCommFunction_hook, &EscapeCommFunction_orig);
|
||||
detour::trampoline_try("kernel32.dll", "WriteFile", WriteFile_hook, &WriteFile_orig);
|
||||
detour::trampoline_try("kernel32.dll", "GetFileSize", GetFileSize_hook, &GetFileSize_orig);
|
||||
detour::trampoline_try("kernel32.dll", "GetFileSizeEx", GetFileSizeEx_hook, &GetFileSizeEx_orig);
|
||||
detour::trampoline_try("kernel32.dll", "GetFileInformationByHandle",
|
||||
GetFileInformationByHandle_hook, &GetFileInformationByHandle_orig);
|
||||
detour::trampoline_try("kernel32.dll", "GetCommState", GetCommState_hook, &GetCommState_orig);
|
||||
detour::trampoline_try("kernel32.dll", "PurgeComm", PurgeComm_hook, &PurgeComm_orig);
|
||||
detour::trampoline_try("kernel32.dll", "ReadFile", ReadFile_hook, &ReadFile_orig);
|
||||
detour::trampoline_try("kernel32.dll", "SetupComm", SetupComm_hook, &SetupComm_orig);
|
||||
detour::trampoline_try("kernel32.dll", "SetCommBreak", SetCommBreak_hook, &SetCommBreak_orig);
|
||||
detour::trampoline_try("kernel32.dll", "SetCommMask", SetCommMask_hook, &SetCommMask_orig);
|
||||
detour::trampoline_try("kernel32.dll", "SetCommState", SetCommState_hook, &SetCommState_orig);
|
||||
detour::trampoline_try("kernel32.dll", "SetCommTimeouts", SetCommTimeouts_hook, &SetCommTimeouts_orig);
|
||||
|
||||
suspend_or_resume_other_threads(false);
|
||||
}
|
||||
|
||||
void devicehook_add(CustomHandle *device_handle) {
|
||||
CUSTOM_HANDLES.push_back(device_handle);
|
||||
}
|
||||
|
||||
void devicehook_dispose() {
|
||||
|
||||
// clean up custom handles
|
||||
for (auto handle : CUSTOM_HANDLES) {
|
||||
delete handle;
|
||||
}
|
||||
CUSTOM_HANDLES.clear();
|
||||
}
|
||||
81
hooks/devicehook.h
Normal file
81
hooks/devicehook.h
Normal file
@@ -0,0 +1,81 @@
|
||||
#pragma once
|
||||
|
||||
#include <windows.h>
|
||||
#include <string>
|
||||
|
||||
namespace hooks::device {
|
||||
extern bool ENABLE;
|
||||
}
|
||||
|
||||
extern bool DEVICE_CREATEFILE_DEBUG;
|
||||
|
||||
class CustomHandle {
|
||||
public:
|
||||
HANDLE handle = INVALID_HANDLE_VALUE;
|
||||
bool com_pass = false;
|
||||
|
||||
virtual ~CustomHandle() = default;
|
||||
|
||||
virtual bool open(LPCWSTR lpFileName, DWORD dwDesiredAccess, DWORD dwShareMode,
|
||||
LPSECURITY_ATTRIBUTES lpSecurityAttributes, DWORD dwCreationDisposition,
|
||||
DWORD dwFlagsAndAttributes, HANDLE hTemplateFile) {
|
||||
return open(lpFileName);
|
||||
}
|
||||
|
||||
virtual bool open(LPCWSTR lpFileName) {
|
||||
return false;
|
||||
};
|
||||
|
||||
virtual int read(LPVOID lpBuffer, DWORD nNumberOfBytesToRead) {
|
||||
return -1;
|
||||
};
|
||||
|
||||
virtual int write(LPCVOID lpBuffer, DWORD nNumberOfBytesToWrite) {
|
||||
return -1;
|
||||
};
|
||||
|
||||
virtual int device_io(DWORD dwIoControlCode, LPVOID lpInBuffer, DWORD nInBufferSize, LPVOID lpOutBuffer,
|
||||
DWORD nOutBufferSize) {
|
||||
return -1;
|
||||
};
|
||||
|
||||
virtual size_t bytes_available() {
|
||||
return 255;
|
||||
}
|
||||
|
||||
virtual bool close() {
|
||||
return true;
|
||||
};
|
||||
|
||||
virtual void file_info(LPBY_HANDLE_FILE_INFORMATION lpFileInformation) {
|
||||
memset(lpFileInformation, 0, sizeof(*lpFileInformation));
|
||||
};
|
||||
|
||||
DCB comm_state {};
|
||||
COMMTIMEOUTS comm_timeouts {};
|
||||
};
|
||||
|
||||
class MITMHandle : public CustomHandle {
|
||||
protected:
|
||||
LPCWSTR lpFileName = L"";
|
||||
bool lpFileNameContains = false;
|
||||
std::string rec_file = "";
|
||||
|
||||
public:
|
||||
MITMHandle(LPCWSTR lpFileName, std::string rec_file = "", bool lpFileNameContains = false);
|
||||
|
||||
bool open(LPCWSTR lpFileName, DWORD dwDesiredAccess, DWORD dwShareMode,
|
||||
LPSECURITY_ATTRIBUTES lpSecurityAttributes, DWORD dwCreationDisposition,
|
||||
DWORD dwFlagsAndAttributes, HANDLE hTemplateFile) override;
|
||||
int read(LPVOID lpBuffer, DWORD nNumberOfBytesToRead) override;
|
||||
int write(LPCVOID lpBuffer, DWORD nNumberOfBytesToWrite) override;
|
||||
int device_io(DWORD dwIoControlCode, LPVOID lpInBuffer, DWORD nInBufferSize, LPVOID lpOutBuffer,
|
||||
DWORD nOutBufferSize) override;
|
||||
size_t bytes_available() override;
|
||||
bool close() override;
|
||||
};
|
||||
|
||||
void devicehook_init(HMODULE module = nullptr);
|
||||
void devicehook_init_trampoline();
|
||||
void devicehook_add(CustomHandle *device_handle);
|
||||
void devicehook_dispose();
|
||||
1349
hooks/graphics/backends/d3d9/d3d9_backend.cpp
Normal file
1349
hooks/graphics/backends/d3d9/d3d9_backend.cpp
Normal file
File diff suppressed because it is too large
Load Diff
65
hooks/graphics/backends/d3d9/d3d9_backend.h
Normal file
65
hooks/graphics/backends/d3d9/d3d9_backend.h
Normal file
@@ -0,0 +1,65 @@
|
||||
#pragma once
|
||||
|
||||
#include <d3d9.h>
|
||||
|
||||
// {EEE9CCF6-53D6-4326-9AE5-60921B3DB394}
|
||||
static const GUID IID_WrappedIDirect3D9 = {
|
||||
0xeee9ccf6, 0x53d6, 0x4326, { 0x9a, 0xe5, 0x60, 0x92, 0x1b, 0x3d, 0xb3, 0x94 }
|
||||
};
|
||||
|
||||
void graphics_d3d9_init();
|
||||
void graphics_d3d9_on_present(
|
||||
HWND hFocusWindow,
|
||||
IDirect3DDevice9 *device,
|
||||
IDirect3DDevice9 *wrapped_device);
|
||||
|
||||
IDirect3DSurface9 *graphics_d3d9_ldj_get_sub_screen();
|
||||
|
||||
struct WrappedIDirect3D9 : IDirect3D9Ex {
|
||||
explicit WrappedIDirect3D9(IDirect3D9 *orig) : pReal(orig), is_d3d9ex(false) {}
|
||||
|
||||
explicit WrappedIDirect3D9(IDirect3D9Ex *orig) : pReal(orig), is_d3d9ex(true) {}
|
||||
|
||||
WrappedIDirect3D9(const WrappedIDirect3D9 &) = delete;
|
||||
WrappedIDirect3D9 &operator=(const WrappedIDirect3D9 &) = delete;
|
||||
|
||||
virtual ~WrappedIDirect3D9() = 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 IDirect3D9
|
||||
virtual HRESULT STDMETHODCALLTYPE RegisterSoftwareDevice(void *pInitializeFunction) override;
|
||||
virtual UINT STDMETHODCALLTYPE GetAdapterCount() override;
|
||||
virtual HRESULT STDMETHODCALLTYPE GetAdapterIdentifier(UINT Adapter, DWORD Flags, D3DADAPTER_IDENTIFIER9 *pIdentifier) override;
|
||||
virtual UINT STDMETHODCALLTYPE GetAdapterModeCount(UINT Adapter, D3DFORMAT Format) override;
|
||||
virtual HRESULT STDMETHODCALLTYPE EnumAdapterModes(UINT Adapter, D3DFORMAT Format, UINT Mode, D3DDISPLAYMODE *pMode) override;
|
||||
virtual HRESULT STDMETHODCALLTYPE GetAdapterDisplayMode(UINT Adapter, D3DDISPLAYMODE *pMode) override;
|
||||
virtual HRESULT STDMETHODCALLTYPE CheckDeviceType(UINT iAdapter, D3DDEVTYPE DevType, D3DFORMAT DisplayFormat, D3DFORMAT BackBufferFormat, BOOL bWindowed) override;
|
||||
virtual HRESULT STDMETHODCALLTYPE CheckDeviceFormat(UINT Adapter, D3DDEVTYPE DeviceType, D3DFORMAT AdapterFormat, DWORD Usage, D3DRESOURCETYPE RType, D3DFORMAT CheckFormat) override;
|
||||
virtual HRESULT STDMETHODCALLTYPE CheckDeviceMultiSampleType(UINT Adapter, D3DDEVTYPE DeviceType, D3DFORMAT SurfaceFormat, BOOL Windowed, D3DMULTISAMPLE_TYPE MultiSampleType, DWORD *pQualityLevels) override;
|
||||
virtual HRESULT STDMETHODCALLTYPE CheckDepthStencilMatch(UINT Adapter, D3DDEVTYPE DeviceType, D3DFORMAT AdapterFormat, D3DFORMAT RenderTargetFormat, D3DFORMAT DepthStencilFormat) override;
|
||||
virtual HRESULT STDMETHODCALLTYPE CheckDeviceFormatConversion(UINT Adapter, D3DDEVTYPE DeviceType, D3DFORMAT SourceFormat, D3DFORMAT TargetFormat) override;
|
||||
virtual HRESULT STDMETHODCALLTYPE GetDeviceCaps(UINT Adapter, D3DDEVTYPE DeviceType, D3DCAPS9 *pCaps) override;
|
||||
virtual HMONITOR STDMETHODCALLTYPE GetAdapterMonitor(UINT Adapter) override;
|
||||
virtual HRESULT STDMETHODCALLTYPE CreateDevice(UINT Adapter, D3DDEVTYPE DeviceType, HWND hFocusWindow, DWORD BehaviorFlags, D3DPRESENT_PARAMETERS *pPresentationParameters, IDirect3DDevice9 **ppReturnedDeviceInterface) override;
|
||||
#pragma endregion
|
||||
|
||||
#pragma region IDirect3D9Ex
|
||||
virtual UINT STDMETHODCALLTYPE GetAdapterModeCountEx(UINT Adapter, const D3DDISPLAYMODEFILTER *pFilter) override;
|
||||
virtual HRESULT STDMETHODCALLTYPE EnumAdapterModesEx(UINT Adapter, const D3DDISPLAYMODEFILTER *pFilter, UINT Mode, D3DDISPLAYMODEEX *pMode) override;
|
||||
virtual HRESULT STDMETHODCALLTYPE GetAdapterDisplayModeEx(UINT Adapter, D3DDISPLAYMODEEX *pMode, D3DDISPLAYROTATION *pRotation) override;
|
||||
virtual HRESULT STDMETHODCALLTYPE CreateDeviceEx(UINT Adapter, D3DDEVTYPE DeviceType, HWND hFocusWindow, DWORD BehaviorFlags, D3DPRESENT_PARAMETERS *pPresentationParameters, D3DDISPLAYMODEEX *pFullscreenDisplayMode, IDirect3DDevice9Ex **ppReturnedDeviceInterface) override;
|
||||
virtual HRESULT STDMETHODCALLTYPE GetAdapterLUID(UINT Adapter, LUID *pLUID) override;
|
||||
#pragma endregion
|
||||
|
||||
private:
|
||||
IDirect3D9 *pReal;
|
||||
bool is_d3d9ex = false;
|
||||
|
||||
//bool attempted_sub_swap_chain_acquire = false;
|
||||
//IDirect3DSwapChain9 *sub_swap_chain = nullptr;
|
||||
};
|
||||
1691
hooks/graphics/backends/d3d9/d3d9_device.cpp
Normal file
1691
hooks/graphics/backends/d3d9/d3d9_device.cpp
Normal file
File diff suppressed because it is too large
Load Diff
210
hooks/graphics/backends/d3d9/d3d9_device.h
Normal file
210
hooks/graphics/backends/d3d9/d3d9_device.h
Normal file
@@ -0,0 +1,210 @@
|
||||
#pragma once
|
||||
|
||||
#include <atomic>
|
||||
|
||||
#include <initguid.h>
|
||||
#include <d3d9.h>
|
||||
|
||||
#include "util/logging.h"
|
||||
|
||||
#include "d3d9_fake_swapchain.h"
|
||||
#include "d3d9_swapchain.h"
|
||||
|
||||
/*
|
||||
* Logging Helpers
|
||||
*/
|
||||
|
||||
#if 0
|
||||
#define WRAP_VERBOSE log_misc("graphics::d3d9", "{}", __FUNCTION__)
|
||||
#define WRAP_VERBOSE_FMT(format, ...) log_misc("graphics::d3d9", format, __VA_ARGS__)
|
||||
#else
|
||||
#define WRAP_VERBOSE do {} while (0)
|
||||
#define WRAP_VERBOSE_FMT(format, ...) do {} while (0)
|
||||
#endif
|
||||
|
||||
#if 0
|
||||
#define WRAP_DEBUG log_misc("graphics::d3d9", "{}", __FUNCTION__)
|
||||
#define WRAP_DEBUG_FMT(format, ...) log_misc("graphics::d3d9", format, __VA_ARGS__)
|
||||
#else
|
||||
#define WRAP_DEBUG do {} while (0)
|
||||
#define WRAP_DEBUG_FMT(format, ...) do {} while (0)
|
||||
#endif
|
||||
|
||||
// {6DEC0D40-1339-4BDA-A5F2-2231D4010FD1}
|
||||
static const GUID IID_WrappedIDirect3DDevice9 = {
|
||||
0x6dec0d40, 0x1339, 0x4bda, { 0xa5, 0xf2, 0x22, 0x31, 0xd4, 0x1, 0xf, 0xd1 }
|
||||
};
|
||||
|
||||
struct WrappedIDirect3DDevice9 : IDirect3DDevice9Ex {
|
||||
explicit WrappedIDirect3DDevice9(HWND hFocusWindow, IDirect3DDevice9 *orig)
|
||||
: hFocusWindow(hFocusWindow), pReal(orig), is_d3d9ex(false) {
|
||||
IDirect3DDevice9Ex *device = nullptr;
|
||||
|
||||
// attempt to upgrade handle
|
||||
if (SUCCEEDED(this->QueryInterface(IID_PPV_ARGS(&device))) && device != nullptr) {
|
||||
device->Release();
|
||||
}
|
||||
}
|
||||
|
||||
explicit WrappedIDirect3DDevice9(HWND hFocusWindow, IDirect3DDevice9Ex *orig)
|
||||
: hFocusWindow(hFocusWindow), pReal(orig), is_d3d9ex(true) {}
|
||||
|
||||
WrappedIDirect3DDevice9(const WrappedIDirect3DDevice9 &) = delete;
|
||||
WrappedIDirect3DDevice9 &operator=(const WrappedIDirect3DDevice9 &) = delete;
|
||||
|
||||
virtual ~WrappedIDirect3DDevice9() = 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 IDirect3DDevice9
|
||||
virtual HRESULT STDMETHODCALLTYPE TestCooperativeLevel() override;
|
||||
virtual UINT STDMETHODCALLTYPE GetAvailableTextureMem() override;
|
||||
virtual HRESULT STDMETHODCALLTYPE EvictManagedResources() override;
|
||||
virtual HRESULT STDMETHODCALLTYPE GetDirect3D(IDirect3D9 **ppD3D9) override;
|
||||
virtual HRESULT STDMETHODCALLTYPE GetDeviceCaps(D3DCAPS9 *pCaps) override;
|
||||
virtual HRESULT STDMETHODCALLTYPE GetDisplayMode(UINT iSwapChain, D3DDISPLAYMODE *pMode) override;
|
||||
virtual HRESULT STDMETHODCALLTYPE GetCreationParameters(D3DDEVICE_CREATION_PARAMETERS *pParameters) override;
|
||||
virtual HRESULT STDMETHODCALLTYPE SetCursorProperties(UINT XHotSpot, UINT YHotSpot, IDirect3DSurface9 *pCursorBitmap) override;
|
||||
virtual void STDMETHODCALLTYPE SetCursorPosition(int X, int Y, DWORD Flags) override;
|
||||
virtual BOOL STDMETHODCALLTYPE ShowCursor(BOOL bShow) override;
|
||||
virtual HRESULT STDMETHODCALLTYPE CreateAdditionalSwapChain(D3DPRESENT_PARAMETERS *pPresentationParameters, IDirect3DSwapChain9 **ppSwapChain) override;
|
||||
virtual HRESULT STDMETHODCALLTYPE GetSwapChain(UINT iSwapChain, IDirect3DSwapChain9 **ppSwapChain) override;
|
||||
virtual UINT STDMETHODCALLTYPE GetNumberOfSwapChains() override;
|
||||
virtual HRESULT STDMETHODCALLTYPE Reset(D3DPRESENT_PARAMETERS *pPresentationParameters) override;
|
||||
virtual HRESULT STDMETHODCALLTYPE Present(const RECT *pSourceRect, const RECT *pDestRect, HWND hDestWindowOverride, const RGNDATA *pDirtyRegion) override;
|
||||
virtual HRESULT STDMETHODCALLTYPE GetBackBuffer(UINT iSwapChain, UINT iBackBuffer, D3DBACKBUFFER_TYPE Type, IDirect3DSurface9 **ppBackBuffer) override;
|
||||
virtual HRESULT STDMETHODCALLTYPE GetRasterStatus(UINT iSwapChain, D3DRASTER_STATUS *pRasterStatus) override;
|
||||
virtual HRESULT STDMETHODCALLTYPE SetDialogBoxMode(BOOL bEnableDialogs) override;
|
||||
virtual void STDMETHODCALLTYPE SetGammaRamp(UINT iSwapChain, DWORD Flags, const D3DGAMMARAMP *pRamp) override;
|
||||
virtual void STDMETHODCALLTYPE GetGammaRamp(UINT iSwapChain, D3DGAMMARAMP *pRamp) override;
|
||||
virtual HRESULT STDMETHODCALLTYPE CreateTexture(UINT Width, UINT Height, UINT Levels, DWORD Usage, D3DFORMAT Format, D3DPOOL Pool, IDirect3DTexture9 **ppTexture, HANDLE *pSharedHandle) override;
|
||||
virtual HRESULT STDMETHODCALLTYPE CreateVolumeTexture(UINT Width, UINT Height, UINT Depth, UINT Levels, DWORD Usage, D3DFORMAT Format, D3DPOOL Pool, IDirect3DVolumeTexture9 **ppVolumeTexture, HANDLE *pSharedHandle) override;
|
||||
virtual HRESULT STDMETHODCALLTYPE CreateCubeTexture(UINT EdgeLength, UINT Levels, DWORD Usage, D3DFORMAT Format, D3DPOOL Pool, IDirect3DCubeTexture9 **ppCubeTexture, HANDLE *pSharedHandle) override;
|
||||
virtual HRESULT STDMETHODCALLTYPE CreateVertexBuffer(UINT Length, DWORD Usage, DWORD FVF, D3DPOOL Pool, IDirect3DVertexBuffer9 **ppVertexBuffer, HANDLE *pSharedHandle) override;
|
||||
virtual HRESULT STDMETHODCALLTYPE CreateIndexBuffer(UINT Length, DWORD Usage, D3DFORMAT Format, D3DPOOL Pool, IDirect3DIndexBuffer9 **ppIndexBuffer, HANDLE *pSharedHandle) override;
|
||||
virtual HRESULT STDMETHODCALLTYPE CreateRenderTarget(UINT Width, UINT Height, D3DFORMAT Format, D3DMULTISAMPLE_TYPE MultiSample, DWORD MultisampleQuality, BOOL Lockable, IDirect3DSurface9 **ppSurface, HANDLE *pSharedHandle) override;
|
||||
virtual HRESULT STDMETHODCALLTYPE CreateDepthStencilSurface(UINT Width, UINT Height, D3DFORMAT Format, D3DMULTISAMPLE_TYPE MultiSample, DWORD MultisampleQuality, BOOL Discard, IDirect3DSurface9 **ppSurface, HANDLE *pSharedHandle) override;
|
||||
virtual HRESULT STDMETHODCALLTYPE UpdateSurface(IDirect3DSurface9 *pSourceSurface, const RECT *pSourceRect, IDirect3DSurface9 *pDestinationSurface, const POINT *pDestPoint) override;
|
||||
virtual HRESULT STDMETHODCALLTYPE UpdateTexture(IDirect3DBaseTexture9 *pSourceTexture, IDirect3DBaseTexture9 *pDestinationTexture) override;
|
||||
virtual HRESULT STDMETHODCALLTYPE GetRenderTargetData(IDirect3DSurface9 *pRenderTarget, IDirect3DSurface9 *pDestSurface) override;
|
||||
virtual HRESULT STDMETHODCALLTYPE GetFrontBufferData(UINT iSwapChain, IDirect3DSurface9 *pDestSurface) override;
|
||||
virtual HRESULT STDMETHODCALLTYPE StretchRect(IDirect3DSurface9 *pSourceSurface, const RECT *pSourceRect, IDirect3DSurface9 *pDestSurface, const RECT *pDestRect, D3DTEXTUREFILTERTYPE Filter) override;
|
||||
virtual HRESULT STDMETHODCALLTYPE ColorFill(IDirect3DSurface9 *pSurface, const RECT *pRect, D3DCOLOR color) override;
|
||||
virtual HRESULT STDMETHODCALLTYPE CreateOffscreenPlainSurface(UINT Width, UINT Height, D3DFORMAT Format, D3DPOOL Pool, IDirect3DSurface9 **ppSurface, HANDLE *pSharedHandle) override;
|
||||
virtual HRESULT STDMETHODCALLTYPE SetRenderTarget(DWORD RenderTargetIndex, IDirect3DSurface9 *pRenderTarget) override;
|
||||
virtual HRESULT STDMETHODCALLTYPE GetRenderTarget(DWORD RenderTargetIndex, IDirect3DSurface9 **ppRenderTarget) override;
|
||||
virtual HRESULT STDMETHODCALLTYPE SetDepthStencilSurface(IDirect3DSurface9 *pNewZStencil) override;
|
||||
virtual HRESULT STDMETHODCALLTYPE GetDepthStencilSurface(IDirect3DSurface9 **ppZStencilSurface) override;
|
||||
virtual HRESULT STDMETHODCALLTYPE BeginScene() override;
|
||||
virtual HRESULT STDMETHODCALLTYPE EndScene() override;
|
||||
virtual HRESULT STDMETHODCALLTYPE Clear(DWORD Count, const D3DRECT *pRects, DWORD Flags, D3DCOLOR Color, float Z, DWORD Stencil) override;
|
||||
virtual HRESULT STDMETHODCALLTYPE SetTransform(D3DTRANSFORMSTATETYPE State, const D3DMATRIX *pMatrix) override;
|
||||
virtual HRESULT STDMETHODCALLTYPE GetTransform(D3DTRANSFORMSTATETYPE State, D3DMATRIX *pMatrix) override;
|
||||
virtual HRESULT STDMETHODCALLTYPE MultiplyTransform(D3DTRANSFORMSTATETYPE State, const D3DMATRIX *pMatrix) override;
|
||||
virtual HRESULT STDMETHODCALLTYPE SetViewport(const D3DVIEWPORT9 *pViewport) override;
|
||||
virtual HRESULT STDMETHODCALLTYPE GetViewport(D3DVIEWPORT9 *pViewport) override;
|
||||
virtual HRESULT STDMETHODCALLTYPE SetMaterial(const D3DMATERIAL9 *pMaterial) override;
|
||||
virtual HRESULT STDMETHODCALLTYPE GetMaterial(D3DMATERIAL9 *pMaterial) override;
|
||||
virtual HRESULT STDMETHODCALLTYPE SetLight(DWORD Index, const D3DLIGHT9 *pLight) override;
|
||||
virtual HRESULT STDMETHODCALLTYPE GetLight(DWORD Index, D3DLIGHT9 *pLight) override;
|
||||
virtual HRESULT STDMETHODCALLTYPE LightEnable(DWORD Index, BOOL Enable) override;
|
||||
virtual HRESULT STDMETHODCALLTYPE GetLightEnable(DWORD Index, BOOL *pEnable) override;
|
||||
virtual HRESULT STDMETHODCALLTYPE SetClipPlane(DWORD Index, const float *pPlane) override;
|
||||
virtual HRESULT STDMETHODCALLTYPE GetClipPlane(DWORD Index, float *pPlane) override;
|
||||
virtual HRESULT STDMETHODCALLTYPE SetRenderState(D3DRENDERSTATETYPE State, DWORD Value) override;
|
||||
virtual HRESULT STDMETHODCALLTYPE GetRenderState(D3DRENDERSTATETYPE State, DWORD *pValue) override;
|
||||
virtual HRESULT STDMETHODCALLTYPE CreateStateBlock(D3DSTATEBLOCKTYPE Type, IDirect3DStateBlock9 **ppSB) override;
|
||||
virtual HRESULT STDMETHODCALLTYPE BeginStateBlock() override;
|
||||
virtual HRESULT STDMETHODCALLTYPE EndStateBlock(IDirect3DStateBlock9 **ppSB) override;
|
||||
virtual HRESULT STDMETHODCALLTYPE SetClipStatus(const D3DCLIPSTATUS9 *pClipStatus) override;
|
||||
virtual HRESULT STDMETHODCALLTYPE GetClipStatus(D3DCLIPSTATUS9 *pClipStatus) override;
|
||||
virtual HRESULT STDMETHODCALLTYPE GetTexture(DWORD Stage, IDirect3DBaseTexture9 **ppTexture) override;
|
||||
virtual HRESULT STDMETHODCALLTYPE SetTexture(DWORD Stage, IDirect3DBaseTexture9 *pTexture) override;
|
||||
virtual HRESULT STDMETHODCALLTYPE GetTextureStageState(DWORD Stage, D3DTEXTURESTAGESTATETYPE Type, DWORD *pValue) override;
|
||||
virtual HRESULT STDMETHODCALLTYPE SetTextureStageState(DWORD Stage, D3DTEXTURESTAGESTATETYPE Type, DWORD Value) override;
|
||||
virtual HRESULT STDMETHODCALLTYPE GetSamplerState(DWORD Sampler, D3DSAMPLERSTATETYPE Type, DWORD *pValue) override;
|
||||
virtual HRESULT STDMETHODCALLTYPE SetSamplerState(DWORD Sampler, D3DSAMPLERSTATETYPE Type, DWORD Value) override;
|
||||
virtual HRESULT STDMETHODCALLTYPE ValidateDevice(DWORD *pNumPasses) override;
|
||||
virtual HRESULT STDMETHODCALLTYPE SetPaletteEntries(UINT PaletteNumber, const PALETTEENTRY *pEntries) override;
|
||||
virtual HRESULT STDMETHODCALLTYPE GetPaletteEntries(UINT PaletteNumber, PALETTEENTRY *pEntries) override;
|
||||
virtual HRESULT STDMETHODCALLTYPE SetCurrentTexturePalette(UINT PaletteNumber) override;
|
||||
virtual HRESULT STDMETHODCALLTYPE GetCurrentTexturePalette(UINT *PaletteNumber) override;
|
||||
virtual HRESULT STDMETHODCALLTYPE SetScissorRect(const RECT *pRect) override;
|
||||
virtual HRESULT STDMETHODCALLTYPE GetScissorRect(RECT *pRect) override;
|
||||
virtual HRESULT STDMETHODCALLTYPE SetSoftwareVertexProcessing(BOOL bSoftware) override;
|
||||
virtual BOOL STDMETHODCALLTYPE GetSoftwareVertexProcessing() override;
|
||||
virtual HRESULT STDMETHODCALLTYPE SetNPatchMode(float nSegments) override;
|
||||
virtual float STDMETHODCALLTYPE GetNPatchMode() override;
|
||||
virtual HRESULT STDMETHODCALLTYPE DrawPrimitive(D3DPRIMITIVETYPE PrimitiveType, UINT StartVertex, UINT PrimitiveCount) override;
|
||||
virtual HRESULT STDMETHODCALLTYPE DrawIndexedPrimitive(D3DPRIMITIVETYPE PrimitiveType, INT BaseVertexIndex, UINT MinVertexIndex, UINT NumVertices, UINT startIndex, UINT primCount) override;
|
||||
virtual HRESULT STDMETHODCALLTYPE DrawPrimitiveUP(D3DPRIMITIVETYPE PrimitiveType, UINT PrimitiveCount, const void *pVertexStreamZeroData, UINT VertexStreamZeroStride) override;
|
||||
virtual HRESULT STDMETHODCALLTYPE DrawIndexedPrimitiveUP(D3DPRIMITIVETYPE PrimitiveType, UINT MinVertexIndex, UINT NumVertices, UINT PrimitiveCount, const void *pIndexData, D3DFORMAT IndexDataFormat, const void *pVertexStreamZeroData, UINT VertexStreamZeroStride) override;
|
||||
virtual HRESULT STDMETHODCALLTYPE ProcessVertices(UINT SrcStartIndex, UINT DestIndex, UINT VertexCount, IDirect3DVertexBuffer9 *pDestBuffer, IDirect3DVertexDeclaration9 *pVertexDecl, DWORD Flags) override;
|
||||
virtual HRESULT STDMETHODCALLTYPE CreateVertexDeclaration(const D3DVERTEXELEMENT9 *pVertexElements, IDirect3DVertexDeclaration9 **ppDecl) override;
|
||||
virtual HRESULT STDMETHODCALLTYPE SetVertexDeclaration(IDirect3DVertexDeclaration9 *pDecl) override;
|
||||
virtual HRESULT STDMETHODCALLTYPE GetVertexDeclaration(IDirect3DVertexDeclaration9 **ppDecl) override;
|
||||
virtual HRESULT STDMETHODCALLTYPE SetFVF(DWORD FVF) override;
|
||||
virtual HRESULT STDMETHODCALLTYPE GetFVF(DWORD *pFVF) override;
|
||||
virtual HRESULT STDMETHODCALLTYPE CreateVertexShader(const DWORD *pFunction, IDirect3DVertexShader9 **ppShader) override;
|
||||
virtual HRESULT STDMETHODCALLTYPE SetVertexShader(IDirect3DVertexShader9 *pShader) override;
|
||||
virtual HRESULT STDMETHODCALLTYPE GetVertexShader(IDirect3DVertexShader9 **ppShader) override;
|
||||
virtual HRESULT STDMETHODCALLTYPE SetVertexShaderConstantF(UINT StartRegister, const float *pConstantData, UINT Vector4fCount) override;
|
||||
virtual HRESULT STDMETHODCALLTYPE GetVertexShaderConstantF(UINT StartRegister, float *pConstantData, UINT Vector4fCount) override;
|
||||
virtual HRESULT STDMETHODCALLTYPE SetVertexShaderConstantI(UINT StartRegister, const int *pConstantData, UINT Vector4iCount) override;
|
||||
virtual HRESULT STDMETHODCALLTYPE GetVertexShaderConstantI(UINT StartRegister, int *pConstantData, UINT Vector4iCount) override;
|
||||
virtual HRESULT STDMETHODCALLTYPE SetVertexShaderConstantB(UINT StartRegister, const BOOL *pConstantData, UINT BoolCount) override;
|
||||
virtual HRESULT STDMETHODCALLTYPE GetVertexShaderConstantB(UINT StartRegister, BOOL *pConstantData, UINT BoolCount) override;
|
||||
virtual HRESULT STDMETHODCALLTYPE SetStreamSource(UINT StreamNumber, IDirect3DVertexBuffer9 *pStreamData, UINT OffsetInBytes, UINT Stride) override;
|
||||
virtual HRESULT STDMETHODCALLTYPE GetStreamSource(UINT StreamNumber, IDirect3DVertexBuffer9 **ppStreamData, UINT *OffsetInBytes, UINT *pStride) override;
|
||||
virtual HRESULT STDMETHODCALLTYPE SetStreamSourceFreq(UINT StreamNumber, UINT Divider) override;
|
||||
virtual HRESULT STDMETHODCALLTYPE GetStreamSourceFreq(UINT StreamNumber, UINT *Divider) override;
|
||||
virtual HRESULT STDMETHODCALLTYPE SetIndices(IDirect3DIndexBuffer9 *pIndexData) override;
|
||||
virtual HRESULT STDMETHODCALLTYPE GetIndices(IDirect3DIndexBuffer9 **ppIndexData) override;
|
||||
virtual HRESULT STDMETHODCALLTYPE CreatePixelShader(const DWORD *pFunction, IDirect3DPixelShader9 **ppShader) override;
|
||||
virtual HRESULT STDMETHODCALLTYPE SetPixelShader(IDirect3DPixelShader9 *pShader) override;
|
||||
virtual HRESULT STDMETHODCALLTYPE GetPixelShader(IDirect3DPixelShader9 **ppShader) override;
|
||||
virtual HRESULT STDMETHODCALLTYPE SetPixelShaderConstantF(UINT StartRegister, const float *pConstantData, UINT Vector4fCount) override;
|
||||
virtual HRESULT STDMETHODCALLTYPE GetPixelShaderConstantF(UINT StartRegister, float *pConstantData, UINT Vector4fCount) override;
|
||||
virtual HRESULT STDMETHODCALLTYPE SetPixelShaderConstantI(UINT StartRegister, const int *pConstantData, UINT Vector4iCount) override;
|
||||
virtual HRESULT STDMETHODCALLTYPE GetPixelShaderConstantI(UINT StartRegister, int *pConstantData, UINT Vector4iCount) override;
|
||||
virtual HRESULT STDMETHODCALLTYPE SetPixelShaderConstantB(UINT StartRegister, const BOOL *pConstantData, UINT BoolCount) override;
|
||||
virtual HRESULT STDMETHODCALLTYPE GetPixelShaderConstantB(UINT StartRegister, BOOL *pConstantData, UINT BoolCount) override;
|
||||
virtual HRESULT STDMETHODCALLTYPE DrawRectPatch(UINT Handle, const float *pNumSegs, const D3DRECTPATCH_INFO *pRectPatchInfo) override;
|
||||
virtual HRESULT STDMETHODCALLTYPE DrawTriPatch(UINT Handle, const float *pNumSegs, const D3DTRIPATCH_INFO *pTriPatchInfo) override;
|
||||
virtual HRESULT STDMETHODCALLTYPE DeletePatch(UINT Handle) override;
|
||||
virtual HRESULT STDMETHODCALLTYPE CreateQuery(D3DQUERYTYPE Type, IDirect3DQuery9 **ppQuery) override;
|
||||
#pragma endregion
|
||||
|
||||
#pragma region IDirect3DDevice9Ex
|
||||
virtual HRESULT STDMETHODCALLTYPE SetConvolutionMonoKernel(UINT width, UINT height, float *rows, float *columns) override;
|
||||
virtual HRESULT STDMETHODCALLTYPE ComposeRects(IDirect3DSurface9 *pSrc, IDirect3DSurface9 *pDst, IDirect3DVertexBuffer9 *pSrcRectDescs, UINT NumRects, IDirect3DVertexBuffer9 *pDstRectDescs, D3DCOMPOSERECTSOP Operation, int Xoffset, int Yoffset) override;
|
||||
virtual HRESULT STDMETHODCALLTYPE PresentEx(const RECT *pSourceRect, const RECT *pDestRect, HWND hDestWindowOverride, const RGNDATA *pDirtyRegion, DWORD dwFlags) override;
|
||||
virtual HRESULT STDMETHODCALLTYPE GetGPUThreadPriority(INT *pPriority) override;
|
||||
virtual HRESULT STDMETHODCALLTYPE SetGPUThreadPriority(INT Priority) override;
|
||||
virtual HRESULT STDMETHODCALLTYPE WaitForVBlank(UINT iSwapChain) override;
|
||||
virtual HRESULT STDMETHODCALLTYPE CheckResourceResidency(IDirect3DResource9 **pResourceArray, UINT32 NumResources) override;
|
||||
virtual HRESULT STDMETHODCALLTYPE SetMaximumFrameLatency(UINT MaxLatency) override;
|
||||
virtual HRESULT STDMETHODCALLTYPE GetMaximumFrameLatency(UINT *pMaxLatency) override;
|
||||
virtual HRESULT STDMETHODCALLTYPE CheckDeviceState(HWND hDestinationWindow) override;
|
||||
virtual HRESULT STDMETHODCALLTYPE CreateRenderTargetEx(UINT Width, UINT Height, D3DFORMAT Format, D3DMULTISAMPLE_TYPE MultiSample, DWORD MultisampleQuality, BOOL Lockable, IDirect3DSurface9 **ppSurface, HANDLE *pSharedHandle, DWORD Usage) override;
|
||||
virtual HRESULT STDMETHODCALLTYPE CreateOffscreenPlainSurfaceEx(UINT Width, UINT Height, D3DFORMAT Format, D3DPOOL Pool, IDirect3DSurface9 **ppSurface, HANDLE *pSharedHandle, DWORD Usage) override;
|
||||
virtual HRESULT STDMETHODCALLTYPE CreateDepthStencilSurfaceEx(UINT Width, UINT Height, D3DFORMAT Format, D3DMULTISAMPLE_TYPE MultiSample, DWORD MultisampleQuality, BOOL Discard, IDirect3DSurface9 **ppSurface, HANDLE *pSharedHandle, DWORD Usage) override;
|
||||
virtual HRESULT STDMETHODCALLTYPE ResetEx(D3DPRESENT_PARAMETERS *pPresentationParameters, D3DDISPLAYMODEEX *pFullscreenDisplayMode) override;
|
||||
virtual HRESULT STDMETHODCALLTYPE GetDisplayModeEx(UINT iSwapChain, D3DDISPLAYMODEEX *pMode, D3DDISPLAYROTATION *pRotation) override;
|
||||
#pragma endregion
|
||||
|
||||
HWND const hFocusWindow;
|
||||
IDirect3DDevice9 *pReal;
|
||||
bool is_d3d9ex = false;
|
||||
|
||||
std::atomic_ulong refs = 1;
|
||||
|
||||
WrappedIDirect3DSwapChain9 *main_swapchain = nullptr;
|
||||
WrappedIDirect3DSwapChain9 *sub_swapchain = nullptr;
|
||||
FakeIDirect3DSwapChain9 *fake_sub_swapchain = nullptr;
|
||||
IDirect3DVertexShader9 *vertex_shader = nullptr;
|
||||
};
|
||||
130
hooks/graphics/backends/d3d9/d3d9_fake_swapchain.cpp
Normal file
130
hooks/graphics/backends/d3d9/d3d9_fake_swapchain.cpp
Normal file
@@ -0,0 +1,130 @@
|
||||
#include "d3d9_fake_swapchain.h"
|
||||
|
||||
#include <cassert>
|
||||
#include <mutex>
|
||||
|
||||
#include "util/logging.h"
|
||||
|
||||
#if 1
|
||||
#define WRAP_VERBOSE log_misc("graphics::d3d9", "FakeIDirect3DSwapChain9::{}", __FUNCTION__)
|
||||
#define WRAP_VERBOSE_FMT(format, ...) log_misc("graphics::d3d9", format, __VA_ARGS__)
|
||||
#else
|
||||
#define WRAP_VERBOSE
|
||||
#define WRAP_VERBOSE_FMT(format, ...)
|
||||
#endif
|
||||
|
||||
// IDirect3DSwapChain9
|
||||
HRESULT STDMETHODCALLTYPE FakeIDirect3DSwapChain9::QueryInterface(REFIID riid, void **ppvObj) {
|
||||
if (ppvObj == nullptr) {
|
||||
return E_POINTER;
|
||||
}
|
||||
|
||||
if (riid == IID_IUnknown ||
|
||||
riid == IID_IDirect3DSwapChain9 ||
|
||||
riid == IID_IDirect3DSwapChain9Ex)
|
||||
{
|
||||
#pragma region Update to IDirect3DSwapChain9Ex interface
|
||||
if (!is_d3d9ex && riid == IID_IDirect3DSwapChain9Ex) {
|
||||
is_d3d9ex = true;
|
||||
}
|
||||
#pragma endregion
|
||||
|
||||
AddRef();
|
||||
*ppvObj = this;
|
||||
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
return E_NOINTERFACE;
|
||||
}
|
||||
|
||||
ULONG STDMETHODCALLTYPE FakeIDirect3DSwapChain9::AddRef(void) {
|
||||
return ++this->ref_cnt;
|
||||
}
|
||||
ULONG STDMETHODCALLTYPE FakeIDirect3DSwapChain9::Release(void) {
|
||||
ULONG refs = --this->ref_cnt;
|
||||
|
||||
if (refs == 0) {
|
||||
delete this;
|
||||
}
|
||||
|
||||
return refs;
|
||||
}
|
||||
|
||||
HRESULT STDMETHODCALLTYPE FakeIDirect3DSwapChain9::Present(const RECT *pSourceRect, const RECT *pDestRect,
|
||||
HWND hDestWindowOverride, const RGNDATA *pDirtyRegion, DWORD dwFlags)
|
||||
{
|
||||
static std::once_flag printed;
|
||||
std::call_once(printed, []() {
|
||||
log_misc("graphics::d3d9", "FakeIDirect3DSwapChain9::Present");
|
||||
});
|
||||
|
||||
return D3DERR_INVALIDCALL;
|
||||
}
|
||||
HRESULT STDMETHODCALLTYPE FakeIDirect3DSwapChain9::GetFrontBufferData(IDirect3DSurface9 *pDestSurface) {
|
||||
WRAP_VERBOSE;
|
||||
return D3DERR_INVALIDCALL;
|
||||
}
|
||||
HRESULT STDMETHODCALLTYPE FakeIDirect3DSwapChain9::GetBackBuffer(UINT iBackBuffer, D3DBACKBUFFER_TYPE Type,
|
||||
IDirect3DSurface9 **ppBackBuffer)
|
||||
{
|
||||
static std::once_flag printed;
|
||||
std::call_once(printed, []() {
|
||||
log_misc("graphics::d3d9", "FakeIDirect3DSwapChain9::GetBackBuffer");
|
||||
});
|
||||
|
||||
if (iBackBuffer >= render_targets.size() || Type != D3DBACKBUFFER_TYPE_MONO || !ppBackBuffer) {
|
||||
return D3DERR_INVALIDCALL;
|
||||
}
|
||||
|
||||
auto &render_target = render_targets[iBackBuffer];
|
||||
|
||||
render_target->AddRef();
|
||||
*ppBackBuffer = render_target;
|
||||
|
||||
return D3D_OK;
|
||||
}
|
||||
HRESULT STDMETHODCALLTYPE FakeIDirect3DSwapChain9::GetRasterStatus(D3DRASTER_STATUS *pRasterStatus) {
|
||||
WRAP_VERBOSE;
|
||||
return D3DERR_INVALIDCALL;
|
||||
}
|
||||
HRESULT STDMETHODCALLTYPE FakeIDirect3DSwapChain9::GetDisplayMode(D3DDISPLAYMODE *pMode) {
|
||||
WRAP_VERBOSE;
|
||||
return D3DERR_INVALIDCALL;
|
||||
}
|
||||
HRESULT STDMETHODCALLTYPE FakeIDirect3DSwapChain9::GetDevice(IDirect3DDevice9 **ppDevice) {
|
||||
WRAP_VERBOSE;
|
||||
|
||||
if (ppDevice == nullptr) {
|
||||
return D3DERR_INVALIDCALL;
|
||||
}
|
||||
|
||||
pDev->AddRef();
|
||||
*ppDevice = pDev;
|
||||
|
||||
return D3D_OK;
|
||||
}
|
||||
HRESULT STDMETHODCALLTYPE FakeIDirect3DSwapChain9::GetPresentParameters(
|
||||
D3DPRESENT_PARAMETERS *pPresentationParameters)
|
||||
{
|
||||
WRAP_VERBOSE;
|
||||
return D3DERR_INVALIDCALL;
|
||||
}
|
||||
|
||||
// IDirect3DSwapChain9Ex
|
||||
HRESULT STDMETHODCALLTYPE FakeIDirect3DSwapChain9::GetLastPresentCount(UINT *pLastPresentCount) {
|
||||
assert(is_d3d9ex);
|
||||
return D3DERR_INVALIDCALL;
|
||||
}
|
||||
HRESULT STDMETHODCALLTYPE FakeIDirect3DSwapChain9::GetPresentStats(D3DPRESENTSTATS *pPresentationStatistics) {
|
||||
assert(is_d3d9ex);
|
||||
return D3DERR_INVALIDCALL;
|
||||
}
|
||||
HRESULT STDMETHODCALLTYPE FakeIDirect3DSwapChain9::GetDisplayModeEx(D3DDISPLAYMODEEX *pMode,
|
||||
D3DDISPLAYROTATION *pRotation)
|
||||
{
|
||||
WRAP_VERBOSE;
|
||||
|
||||
assert(is_d3d9ex);
|
||||
return D3DERR_INVALIDCALL;
|
||||
}
|
||||
86
hooks/graphics/backends/d3d9/d3d9_fake_swapchain.h
Normal file
86
hooks/graphics/backends/d3d9/d3d9_fake_swapchain.h
Normal file
@@ -0,0 +1,86 @@
|
||||
#pragma once
|
||||
|
||||
// this file uses the C++ interface of Direct3D9
|
||||
#ifdef CINTERFACE
|
||||
#undef CINTERFACE
|
||||
#endif
|
||||
|
||||
#include <atomic>
|
||||
|
||||
#include <d3d9.h>
|
||||
|
||||
#include "util/logging.h"
|
||||
|
||||
struct FakeIDirect3DSwapChain9 : IDirect3DSwapChain9Ex {
|
||||
FakeIDirect3DSwapChain9(IDirect3DDevice9 *pDev, D3DPRESENT_PARAMETERS *present_params, bool is_d3d9ex) :
|
||||
pDev(pDev), is_d3d9ex(is_d3d9ex)
|
||||
{
|
||||
// copy presentation parameters
|
||||
memcpy(&this->present_params, present_params, sizeof(this->present_params));
|
||||
|
||||
// From MSDN https://docs.microsoft.com/en-us/windows/win32/direct3d9/d3dpresent-parameters:
|
||||
// Values of 0 are treated as 1
|
||||
if (this->present_params.BackBufferCount == 0) {
|
||||
this->present_params.BackBufferCount = 1;
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < this->present_params.BackBufferCount; i++) {
|
||||
IDirect3DSurface9 *render_target = nullptr;
|
||||
HRESULT hr = pDev->CreateRenderTarget(
|
||||
this->present_params.BackBufferWidth,
|
||||
this->present_params.BackBufferHeight,
|
||||
this->present_params.BackBufferFormat,
|
||||
this->present_params.MultiSampleType,
|
||||
this->present_params.MultiSampleQuality,
|
||||
FALSE,
|
||||
&render_target,
|
||||
nullptr
|
||||
);
|
||||
if (SUCCEEDED(hr)) {
|
||||
this->render_targets.push_back(render_target);
|
||||
} else {
|
||||
log_warning("graphics::d3d9", "failed to create backing render target for fake swap chain, hr={}",
|
||||
FMT_HRESULT(hr));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
FakeIDirect3DSwapChain9(const FakeIDirect3DSwapChain9 &) = delete;
|
||||
FakeIDirect3DSwapChain9 &operator=(const FakeIDirect3DSwapChain9 &) = delete;
|
||||
|
||||
virtual ~FakeIDirect3DSwapChain9(void) {
|
||||
for (auto &render_target : this->render_targets) {
|
||||
render_target->Release();
|
||||
}
|
||||
}
|
||||
|
||||
#pragma region IUnknown
|
||||
virtual HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, void **ppvObj) override;
|
||||
virtual ULONG STDMETHODCALLTYPE AddRef(void) override;
|
||||
virtual ULONG STDMETHODCALLTYPE Release(void) override;
|
||||
#pragma endregion
|
||||
|
||||
#pragma region IDirect3DSwapChain9
|
||||
virtual HRESULT STDMETHODCALLTYPE Present(const RECT *pSourceRect, const RECT *pDestRect, HWND hDestWindowOverride, const RGNDATA *pDirtyRegion, DWORD dwFlags) override;
|
||||
virtual HRESULT STDMETHODCALLTYPE GetFrontBufferData(IDirect3DSurface9 *pDestSurface) override;
|
||||
virtual HRESULT STDMETHODCALLTYPE GetBackBuffer(UINT iBackBuffer, D3DBACKBUFFER_TYPE Type, IDirect3DSurface9 **ppBackBuffer) override;
|
||||
virtual HRESULT STDMETHODCALLTYPE GetRasterStatus(D3DRASTER_STATUS *pRasterStatus) override;
|
||||
virtual HRESULT STDMETHODCALLTYPE GetDisplayMode(D3DDISPLAYMODE *pMode) override;
|
||||
virtual HRESULT STDMETHODCALLTYPE GetDevice(IDirect3DDevice9 **ppDevice) override;
|
||||
virtual HRESULT STDMETHODCALLTYPE GetPresentParameters(D3DPRESENT_PARAMETERS *pPresentationParameters) override;
|
||||
#pragma endregion
|
||||
|
||||
#pragma region IDirect3DSwapChain9Ex
|
||||
virtual HRESULT STDMETHODCALLTYPE GetLastPresentCount(UINT *pLastPresentCount) override;
|
||||
virtual HRESULT STDMETHODCALLTYPE GetPresentStats(D3DPRESENTSTATS *pPresentationStatistics) override;
|
||||
virtual HRESULT STDMETHODCALLTYPE GetDisplayModeEx(D3DDISPLAYMODEEX *pMode, D3DDISPLAYROTATION *pRotation) override;
|
||||
#pragma endregion
|
||||
|
||||
IDirect3DDevice9 *const pDev;
|
||||
bool is_d3d9ex;
|
||||
|
||||
std::atomic<ULONG> ref_cnt = 1;
|
||||
|
||||
D3DPRESENT_PARAMETERS present_params {};
|
||||
std::vector<IDirect3DSurface9 *> render_targets;
|
||||
};
|
||||
141
hooks/graphics/backends/d3d9/d3d9_swapchain.cpp
Normal file
141
hooks/graphics/backends/d3d9/d3d9_swapchain.cpp
Normal file
@@ -0,0 +1,141 @@
|
||||
#include "d3d9_swapchain.h"
|
||||
|
||||
#include <cassert>
|
||||
#include <mutex>
|
||||
|
||||
#include "avs/game.h"
|
||||
#include "hooks/graphics/graphics.h"
|
||||
|
||||
#include "d3d9_backend.h"
|
||||
#include "d3d9_device.h"
|
||||
|
||||
// std::min
|
||||
#ifdef min
|
||||
#undef min
|
||||
#endif
|
||||
|
||||
#define CHECK_RESULT(x) \
|
||||
HRESULT ret = (x); \
|
||||
if (GRAPHICS_LOG_HRESULT && FAILED(ret)) [[unlikely]] { \
|
||||
log_warning("graphics::d3d9", "{} failed, hr={}", __FUNCTION__, FMT_HRESULT(ret)); \
|
||||
} \
|
||||
return ret
|
||||
|
||||
HRESULT STDMETHODCALLTYPE WrappedIDirect3DSwapChain9::QueryInterface(REFIID riid, void **ppvObj) {
|
||||
if (ppvObj == nullptr) {
|
||||
return E_POINTER;
|
||||
}
|
||||
|
||||
if (//riid == __uuidof(IUnknown) || Ignore IUnknown, it's often queried to test object equality between different interfaces
|
||||
riid == IID_IDirect3DSwapChain9 ||
|
||||
riid == IID_IDirect3DSwapChain9Ex)
|
||||
{
|
||||
#pragma region Update to IDirect3DSwapChain9Ex interface
|
||||
if (!is_d3d9ex && riid == IID_IDirect3DSwapChain9Ex) {
|
||||
IDirect3DSwapChain9Ex *swapchainex = nullptr;
|
||||
|
||||
if (FAILED(pReal->QueryInterface(IID_PPV_ARGS(&swapchainex)))) {
|
||||
return E_NOINTERFACE;
|
||||
}
|
||||
|
||||
pReal->Release();
|
||||
pReal = swapchainex;
|
||||
is_d3d9ex = true;
|
||||
}
|
||||
#pragma endregion
|
||||
|
||||
this->AddRef();
|
||||
*ppvObj = this;
|
||||
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
return pReal->QueryInterface(riid, ppvObj);
|
||||
}
|
||||
|
||||
ULONG STDMETHODCALLTYPE WrappedIDirect3DSwapChain9::AddRef(void) {
|
||||
return pReal->AddRef();
|
||||
}
|
||||
ULONG STDMETHODCALLTYPE WrappedIDirect3DSwapChain9::Release(void) {
|
||||
ULONG refs = pReal != nullptr ? pReal->Release() : 0;
|
||||
|
||||
if (refs == 0) {
|
||||
delete this;
|
||||
}
|
||||
|
||||
// Metal Gear Arcade expects the swap chain to only have one reference. The parent
|
||||
// `WrappedIDirect3DDevice9` holds a strong reference to this swap chain which means
|
||||
// the reference count will be above one. Workaround this by returning a maximum of one.
|
||||
if (avs::game::is_model("I36")) {
|
||||
return std::min(refs, 1lu);
|
||||
}
|
||||
|
||||
return refs;
|
||||
}
|
||||
|
||||
/*
|
||||
* IDirect3DSwapChain9
|
||||
*/
|
||||
|
||||
HRESULT STDMETHODCALLTYPE WrappedIDirect3DSwapChain9::Present(const RECT *pSourceRect, const RECT *pDestRect,
|
||||
HWND hDestWindowOverride, const RGNDATA *pDirtyRegion, DWORD dwFlags)
|
||||
{
|
||||
static std::once_flag printed;
|
||||
std::call_once(printed, []() {
|
||||
log_misc("graphics::d3d9", "WrappedIDirect3DSwapChain9::Present");
|
||||
});
|
||||
|
||||
if (should_run_hooks) {
|
||||
graphics_d3d9_on_present(pDev->hFocusWindow, pDev->pReal, pDev);
|
||||
}
|
||||
|
||||
CHECK_RESULT(pReal->Present(pSourceRect, pDestRect, hDestWindowOverride, pDirtyRegion, dwFlags));
|
||||
}
|
||||
HRESULT STDMETHODCALLTYPE WrappedIDirect3DSwapChain9::GetFrontBufferData(IDirect3DSurface9 *pDestSurface) {
|
||||
CHECK_RESULT(pReal->GetFrontBufferData(pDestSurface));
|
||||
}
|
||||
HRESULT STDMETHODCALLTYPE WrappedIDirect3DSwapChain9::GetBackBuffer(UINT iBackBuffer, D3DBACKBUFFER_TYPE Type,
|
||||
IDirect3DSurface9 **ppBackBuffer)
|
||||
{
|
||||
CHECK_RESULT(pReal->GetBackBuffer(iBackBuffer, Type, ppBackBuffer));
|
||||
}
|
||||
HRESULT STDMETHODCALLTYPE WrappedIDirect3DSwapChain9::GetRasterStatus(D3DRASTER_STATUS *pRasterStatus) {
|
||||
CHECK_RESULT(pReal->GetRasterStatus(pRasterStatus));
|
||||
}
|
||||
HRESULT STDMETHODCALLTYPE WrappedIDirect3DSwapChain9::GetDisplayMode(D3DDISPLAYMODE *pMode) {
|
||||
CHECK_RESULT(pReal->GetDisplayMode(pMode));
|
||||
}
|
||||
HRESULT STDMETHODCALLTYPE WrappedIDirect3DSwapChain9::GetDevice(IDirect3DDevice9 **ppDevice) {
|
||||
if (ppDevice == nullptr) {
|
||||
return D3DERR_INVALIDCALL;
|
||||
}
|
||||
|
||||
pDev->AddRef();
|
||||
*ppDevice = pDev;
|
||||
|
||||
return D3D_OK;
|
||||
}
|
||||
HRESULT STDMETHODCALLTYPE WrappedIDirect3DSwapChain9::GetPresentParameters(
|
||||
D3DPRESENT_PARAMETERS *pPresentationParameters)
|
||||
{
|
||||
CHECK_RESULT(pReal->GetPresentParameters(pPresentationParameters));
|
||||
}
|
||||
|
||||
/*
|
||||
* IDirect3DSwapChain9Ex
|
||||
*/
|
||||
|
||||
HRESULT STDMETHODCALLTYPE WrappedIDirect3DSwapChain9::GetLastPresentCount(UINT *pLastPresentCount) {
|
||||
assert(is_d3d9ex);
|
||||
CHECK_RESULT(static_cast<IDirect3DSwapChain9Ex *>(pReal)->GetLastPresentCount(pLastPresentCount));
|
||||
}
|
||||
HRESULT STDMETHODCALLTYPE WrappedIDirect3DSwapChain9::GetPresentStats(D3DPRESENTSTATS *pPresentationStatistics) {
|
||||
assert(is_d3d9ex);
|
||||
CHECK_RESULT(static_cast<IDirect3DSwapChain9Ex *>(pReal)->GetPresentStats(pPresentationStatistics));
|
||||
}
|
||||
HRESULT STDMETHODCALLTYPE WrappedIDirect3DSwapChain9::GetDisplayModeEx(D3DDISPLAYMODEEX *pMode,
|
||||
D3DDISPLAYROTATION *pRotation)
|
||||
{
|
||||
assert(is_d3d9ex);
|
||||
CHECK_RESULT(static_cast<IDirect3DSwapChain9Ex *>(pReal)->GetDisplayModeEx(pMode, pRotation));
|
||||
}
|
||||
58
hooks/graphics/backends/d3d9/d3d9_swapchain.h
Normal file
58
hooks/graphics/backends/d3d9/d3d9_swapchain.h
Normal file
@@ -0,0 +1,58 @@
|
||||
#pragma once
|
||||
|
||||
#include <d3d9.h>
|
||||
|
||||
interface WrappedIDirect3DDevice9;
|
||||
|
||||
struct WrappedIDirect3DSwapChain9 : IDirect3DSwapChain9Ex {
|
||||
WrappedIDirect3DSwapChain9(WrappedIDirect3DDevice9 *dev, IDirect3DSwapChain9 *orig) :
|
||||
pDev(dev), pReal(orig), is_d3d9ex(false)
|
||||
{
|
||||
IDirect3DSwapChain9Ex *swapchain = nullptr;
|
||||
|
||||
// attempt to upgrade handle
|
||||
if (SUCCEEDED(this->QueryInterface(IID_PPV_ARGS(&swapchain))) && swapchain != nullptr) {
|
||||
swapchain->Release();
|
||||
}
|
||||
}
|
||||
|
||||
WrappedIDirect3DSwapChain9(WrappedIDirect3DDevice9 *dev, IDirect3DSwapChain9Ex *orig) :
|
||||
pDev(dev), pReal(orig), is_d3d9ex(true)
|
||||
{
|
||||
}
|
||||
|
||||
virtual ~WrappedIDirect3DSwapChain9(void) {
|
||||
}
|
||||
|
||||
WrappedIDirect3DSwapChain9(const WrappedIDirect3DSwapChain9 &) = delete;
|
||||
WrappedIDirect3DSwapChain9 &operator=(const WrappedIDirect3DSwapChain9 &) = delete;
|
||||
|
||||
#pragma region IUnknown
|
||||
virtual HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, void **ppvObj) override;
|
||||
virtual ULONG STDMETHODCALLTYPE AddRef(void) override;
|
||||
virtual ULONG STDMETHODCALLTYPE Release(void) override;
|
||||
#pragma endregion
|
||||
|
||||
#pragma region IDirect3DSwapChain9
|
||||
virtual HRESULT STDMETHODCALLTYPE Present(const RECT *pSourceRect, const RECT *pDestRect, HWND hDestWindowOverride, const RGNDATA *pDirtyRegion, DWORD dwFlags) override;
|
||||
virtual HRESULT STDMETHODCALLTYPE GetFrontBufferData(IDirect3DSurface9 *pDestSurface) override;
|
||||
virtual HRESULT STDMETHODCALLTYPE GetBackBuffer(UINT iBackBuffer, D3DBACKBUFFER_TYPE Type, IDirect3DSurface9 **ppBackBuffer) override;
|
||||
virtual HRESULT STDMETHODCALLTYPE GetRasterStatus(D3DRASTER_STATUS *pRasterStatus) override;
|
||||
virtual HRESULT STDMETHODCALLTYPE GetDisplayMode(D3DDISPLAYMODE *pMode) override;
|
||||
virtual HRESULT STDMETHODCALLTYPE GetDevice(IDirect3DDevice9 **ppDevice) override;
|
||||
virtual HRESULT STDMETHODCALLTYPE GetPresentParameters(D3DPRESENT_PARAMETERS *pPresentationParameters) override;
|
||||
#pragma endregion
|
||||
|
||||
#pragma region IDirect3DSwapChain9Ex
|
||||
virtual HRESULT STDMETHODCALLTYPE GetLastPresentCount(UINT *pLastPresentCount) override;
|
||||
virtual HRESULT STDMETHODCALLTYPE GetPresentStats(D3DPRESENTSTATS *pPresentationStatistics) override;
|
||||
virtual HRESULT STDMETHODCALLTYPE GetDisplayModeEx(D3DDISPLAYMODEEX *pMode, D3DDISPLAYROTATION *pRotation) override;
|
||||
#pragma endregion
|
||||
|
||||
WrappedIDirect3DDevice9 *const pDev;
|
||||
|
||||
IDirect3DSwapChain9 *pReal;
|
||||
bool is_d3d9ex = false;
|
||||
|
||||
bool should_run_hooks = true;
|
||||
};
|
||||
127
hooks/graphics/backends/d3d9/d3d9_texture.cpp
Normal file
127
hooks/graphics/backends/d3d9/d3d9_texture.cpp
Normal file
@@ -0,0 +1,127 @@
|
||||
#include "d3d9_texture.h"
|
||||
|
||||
#include "hooks/graphics/graphics.h"
|
||||
#include "util/logging.h"
|
||||
#include "util/utils.h"
|
||||
|
||||
#if 0
|
||||
#define WRAP_DEBUG log_misc("graphics::d3d9::texture", "{}", __FUNCTION__)
|
||||
#define WRAP_DEBUG_FMT(format, ...) log_misc("graphics::d3d9::texture", format, __VA_ARGS__)
|
||||
#else
|
||||
#define WRAP_DEBUG do {} while (0)
|
||||
#define WRAP_DEBUG_FMT(format, ...) do {} while (0)
|
||||
#endif
|
||||
|
||||
#define CHECK_RESULT(x) \
|
||||
HRESULT ret = (x); \
|
||||
if (GRAPHICS_LOG_HRESULT && FAILED(ret)) [[unlikely]] { \
|
||||
log_warning("graphics::d3d9::texture", "{} failed, hr={}", __FUNCTION__, FMT_HRESULT(ret)); \
|
||||
} \
|
||||
return ret;
|
||||
|
||||
HRESULT STDMETHODCALLTYPE WrappedIDirect3DTexture9::QueryInterface(REFIID riid, void **ppvObj) {
|
||||
#ifndef __GNUC__
|
||||
|
||||
// fast path without incrementing the reference count for texture updates
|
||||
if (riid == IID_WrappedIDirect3DTexture9) {
|
||||
return S_OK;
|
||||
}
|
||||
#endif
|
||||
|
||||
if (ppvObj == nullptr) {
|
||||
return E_POINTER;
|
||||
}
|
||||
|
||||
if (/* riid == IID_IUnknown || */
|
||||
riid == IID_IDirect3DResource9 ||
|
||||
riid == IID_IDirect3DBaseTexture9 ||
|
||||
riid == IID_IDirect3DTexture9)
|
||||
{
|
||||
this->AddRef();
|
||||
*ppvObj = this;
|
||||
|
||||
log_info("graphics::d3d9::texture", "WrappedIDirect3DTexture9::QueryInterface({})", guid2s(riid));
|
||||
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
return pReal->QueryInterface(riid, ppvObj);
|
||||
}
|
||||
|
||||
ULONG STDMETHODCALLTYPE WrappedIDirect3DTexture9::AddRef(void) {
|
||||
return pReal->AddRef();
|
||||
}
|
||||
ULONG STDMETHODCALLTYPE WrappedIDirect3DTexture9::Release(void) {
|
||||
ULONG refs = (pReal != nullptr) ? pReal->Release() : 0;
|
||||
|
||||
if (refs == 0) {
|
||||
delete this;
|
||||
}
|
||||
|
||||
return refs;
|
||||
}
|
||||
|
||||
// IDirect3DResource9 methods
|
||||
HRESULT STDMETHODCALLTYPE WrappedIDirect3DTexture9::GetDevice(IDirect3DDevice9 **ppDevice) {
|
||||
CHECK_RESULT(pReal->GetDevice(ppDevice));
|
||||
}
|
||||
HRESULT STDMETHODCALLTYPE WrappedIDirect3DTexture9::SetPrivateData(REFGUID refguid, const void *pData, DWORD SizeOfData,
|
||||
DWORD Flags)
|
||||
{
|
||||
CHECK_RESULT(pReal->SetPrivateData(refguid, pData, SizeOfData, Flags));
|
||||
}
|
||||
HRESULT STDMETHODCALLTYPE WrappedIDirect3DTexture9::GetPrivateData(REFGUID refguid, void *pData, DWORD* pSizeOfData) {
|
||||
CHECK_RESULT(pReal->GetPrivateData(refguid, pData, pSizeOfData));
|
||||
}
|
||||
HRESULT STDMETHODCALLTYPE WrappedIDirect3DTexture9::FreePrivateData(REFGUID refguid) {
|
||||
CHECK_RESULT(pReal->FreePrivateData(refguid));
|
||||
}
|
||||
DWORD STDMETHODCALLTYPE WrappedIDirect3DTexture9::SetPriority(DWORD PriorityNew) {
|
||||
return pReal->SetPriority(PriorityNew);
|
||||
}
|
||||
DWORD STDMETHODCALLTYPE WrappedIDirect3DTexture9::GetPriority(void) {
|
||||
return pReal->GetPriority();
|
||||
}
|
||||
void STDMETHODCALLTYPE WrappedIDirect3DTexture9::PreLoad(void) {
|
||||
return pReal->PreLoad();
|
||||
}
|
||||
D3DRESOURCETYPE STDMETHODCALLTYPE WrappedIDirect3DTexture9::GetType(void) {
|
||||
return pReal->GetType();
|
||||
}
|
||||
|
||||
// IDirect3DBaseTexture9 methods
|
||||
DWORD STDMETHODCALLTYPE WrappedIDirect3DTexture9::SetLOD(DWORD LODNew) {
|
||||
return pReal->SetLOD(LODNew);
|
||||
}
|
||||
DWORD STDMETHODCALLTYPE WrappedIDirect3DTexture9::GetLOD(void) {
|
||||
return pReal->GetLOD();
|
||||
}
|
||||
DWORD STDMETHODCALLTYPE WrappedIDirect3DTexture9::GetLevelCount(void) {
|
||||
return pReal->GetLevelCount();
|
||||
}
|
||||
HRESULT STDMETHODCALLTYPE WrappedIDirect3DTexture9::SetAutoGenFilterType(D3DTEXTUREFILTERTYPE FilterType) {
|
||||
CHECK_RESULT(pReal->SetAutoGenFilterType(FilterType));
|
||||
}
|
||||
D3DTEXTUREFILTERTYPE STDMETHODCALLTYPE WrappedIDirect3DTexture9::GetAutoGenFilterType(void) {
|
||||
return pReal->GetAutoGenFilterType();
|
||||
}
|
||||
void STDMETHODCALLTYPE WrappedIDirect3DTexture9::GenerateMipSubLevels(void) {
|
||||
return pReal->GenerateMipSubLevels();
|
||||
}
|
||||
|
||||
HRESULT STDMETHODCALLTYPE WrappedIDirect3DTexture9::GetLevelDesc(UINT Level, D3DSURFACE_DESC *pDesc) {
|
||||
CHECK_RESULT(pReal->GetLevelDesc(Level, pDesc));
|
||||
}
|
||||
HRESULT STDMETHODCALLTYPE WrappedIDirect3DTexture9::GetSurfaceLevel(UINT Level, IDirect3DSurface9 **ppSurfaceLevel) {
|
||||
CHECK_RESULT(pReal->GetSurfaceLevel(Level, ppSurfaceLevel));
|
||||
}
|
||||
HRESULT STDMETHODCALLTYPE WrappedIDirect3DTexture9::LockRect(UINT Level, D3DLOCKED_RECT *pLockedRect, const RECT *pRect, DWORD Flags) {
|
||||
WRAP_DEBUG_FMT("LockRect({}, {}, {}, 0x{:x})", Level, fmt::ptr(pLockedRect), fmt::ptr(pRect), Flags);
|
||||
CHECK_RESULT(pReal->LockRect(Level, pLockedRect, pRect, Flags));
|
||||
}
|
||||
HRESULT STDMETHODCALLTYPE WrappedIDirect3DTexture9::UnlockRect(UINT Level) {
|
||||
CHECK_RESULT(pReal->UnlockRect(Level));
|
||||
}
|
||||
HRESULT STDMETHODCALLTYPE WrappedIDirect3DTexture9::AddDirtyRect(const RECT *pDirtyRect) {
|
||||
CHECK_RESULT(pReal->AddDirtyRect(pDirtyRect));
|
||||
}
|
||||
58
hooks/graphics/backends/d3d9/d3d9_texture.h
Normal file
58
hooks/graphics/backends/d3d9/d3d9_texture.h
Normal file
@@ -0,0 +1,58 @@
|
||||
#pragma once
|
||||
|
||||
#include <d3d9.h>
|
||||
|
||||
#include "util/logging.h"
|
||||
|
||||
// {22E9B203-6506-4BC5-B304-A48F3001630F}
|
||||
static const GUID IID_WrappedIDirect3DTexture9 = {
|
||||
0x22e9b203, 0x6506, 0x4bc5, { 0xb3, 0x04, 0xa4, 0x8f, 0x30, 0x01, 0x63, 0x0f }
|
||||
};
|
||||
|
||||
struct WrappedIDirect3DTexture9 : IDirect3DTexture9 {
|
||||
explicit WrappedIDirect3DTexture9(IDirect3DDevice9 *dev, IDirect3DTexture9 *orig) : pDev(dev), pReal(orig) {
|
||||
log_misc("graphics::d3d9::texture", "Creating texture wrapper around {} => {}", fmt::ptr(orig), fmt::ptr(this));
|
||||
}
|
||||
|
||||
WrappedIDirect3DTexture9(const WrappedIDirect3DTexture9 &) = delete;
|
||||
WrappedIDirect3DTexture9 &operator=(const WrappedIDirect3DTexture9 &) = delete;
|
||||
|
||||
virtual ~WrappedIDirect3DTexture9() = default;
|
||||
|
||||
#pragma region IUnknown
|
||||
virtual HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, void **ppvObj) override;
|
||||
virtual ULONG STDMETHODCALLTYPE AddRef(void) override;
|
||||
virtual ULONG STDMETHODCALLTYPE Release(void) override;
|
||||
#pragma endregion
|
||||
|
||||
#pragma region IDirect3DResource9
|
||||
virtual HRESULT STDMETHODCALLTYPE GetDevice(IDirect3DDevice9 **ppDevice) override;
|
||||
virtual HRESULT STDMETHODCALLTYPE SetPrivateData(REFGUID refguid, const void *pData, DWORD SizeOfData, DWORD Flags) override;
|
||||
virtual HRESULT STDMETHODCALLTYPE GetPrivateData(REFGUID refguid, void *pData, DWORD* pSizeOfData) override;
|
||||
virtual HRESULT STDMETHODCALLTYPE FreePrivateData(REFGUID refguid) override;
|
||||
virtual DWORD STDMETHODCALLTYPE SetPriority(DWORD PriorityNew) override;
|
||||
virtual DWORD STDMETHODCALLTYPE GetPriority(void) override;
|
||||
virtual void STDMETHODCALLTYPE PreLoad(void) override;
|
||||
virtual D3DRESOURCETYPE STDMETHODCALLTYPE GetType(void) override;
|
||||
#pragma endregion
|
||||
|
||||
#pragma region IDirect3DBaseTexture9
|
||||
virtual DWORD STDMETHODCALLTYPE SetLOD(DWORD LODNew) override;
|
||||
virtual DWORD STDMETHODCALLTYPE GetLOD(void) override;
|
||||
virtual DWORD STDMETHODCALLTYPE GetLevelCount(void) override;
|
||||
virtual HRESULT STDMETHODCALLTYPE SetAutoGenFilterType(D3DTEXTUREFILTERTYPE FilterType) override;
|
||||
virtual D3DTEXTUREFILTERTYPE STDMETHODCALLTYPE GetAutoGenFilterType(void) override;
|
||||
virtual void STDMETHODCALLTYPE GenerateMipSubLevels(void) override;
|
||||
#pragma endregion
|
||||
|
||||
#pragma region IDirect3DTexture9
|
||||
virtual HRESULT STDMETHODCALLTYPE GetLevelDesc(UINT Level, D3DSURFACE_DESC *pDesc) override;
|
||||
virtual HRESULT STDMETHODCALLTYPE GetSurfaceLevel(UINT Level, IDirect3DSurface9 **ppSurfaceLevel) override;
|
||||
virtual HRESULT STDMETHODCALLTYPE LockRect(UINT Level, D3DLOCKED_RECT *pLockedRect, const RECT *pRect, DWORD Flags) override;
|
||||
virtual HRESULT STDMETHODCALLTYPE UnlockRect(UINT Level) override;
|
||||
virtual HRESULT STDMETHODCALLTYPE AddDirtyRect(const RECT *pDirtyRect) override;
|
||||
#pragma endregion
|
||||
|
||||
IDirect3DDevice9 *const pDev;
|
||||
IDirect3DTexture9 *const pReal;
|
||||
};
|
||||
111
hooks/graphics/backends/d3d9/shaders/vertex_shader.h
Normal file
111
hooks/graphics/backends/d3d9/shaders/vertex_shader.h
Normal file
@@ -0,0 +1,111 @@
|
||||
#if 0
|
||||
//
|
||||
// Generated by Microsoft (R) HLSL Shader Compiler 10.1
|
||||
//
|
||||
// Parameters:
|
||||
//
|
||||
// float4 ColorMultiply;
|
||||
// float2 ConstantHalfTexelFixupOffset;
|
||||
// float4x4 WorldViewProjection;
|
||||
//
|
||||
//
|
||||
// Registers:
|
||||
//
|
||||
// Name Reg Size
|
||||
// ---------------------------- ----- ----
|
||||
// WorldViewProjection c0 4
|
||||
// ColorMultiply c4 1
|
||||
// ConstantHalfTexelFixupOffset c63 1
|
||||
//
|
||||
|
||||
vs_1_1
|
||||
dcl_position v0
|
||||
dcl_color v1
|
||||
dcl_texcoord v2
|
||||
dp4 oPos.z, v0, c2
|
||||
mul oD0, v1, c4
|
||||
dp4 r0.x, v0, c0
|
||||
dp4 r0.y, v0, c1
|
||||
dp4 r0.z, v0, c3
|
||||
mad oPos.xy, c63, r0.z, r0
|
||||
mov oPos.w, r0.z
|
||||
mov oT0.xy, v2
|
||||
|
||||
// approximately 8 instruction slots used
|
||||
#endif
|
||||
|
||||
const BYTE g_vs11_vs_main[] =
|
||||
{
|
||||
1, 1, 254, 255, 254, 255,
|
||||
64, 0, 67, 84, 65, 66,
|
||||
28, 0, 0, 0, 211, 0,
|
||||
0, 0, 1, 1, 254, 255,
|
||||
3, 0, 0, 0, 28, 0,
|
||||
0, 0, 0, 1, 0, 0,
|
||||
204, 0, 0, 0, 88, 0,
|
||||
0, 0, 2, 0, 4, 0,
|
||||
1, 0, 18, 0, 104, 0,
|
||||
0, 0, 0, 0, 0, 0,
|
||||
120, 0, 0, 0, 2, 0,
|
||||
63, 0, 1, 0, 254, 0,
|
||||
152, 0, 0, 0, 0, 0,
|
||||
0, 0, 168, 0, 0, 0,
|
||||
2, 0, 0, 0, 4, 0,
|
||||
2, 0, 188, 0, 0, 0,
|
||||
0, 0, 0, 0, 67, 111,
|
||||
108, 111, 114, 77, 117, 108,
|
||||
116, 105, 112, 108, 121, 0,
|
||||
171, 171, 1, 0, 3, 0,
|
||||
1, 0, 4, 0, 1, 0,
|
||||
0, 0, 0, 0, 0, 0,
|
||||
67, 111, 110, 115, 116, 97,
|
||||
110, 116, 72, 97, 108, 102,
|
||||
84, 101, 120, 101, 108, 70,
|
||||
105, 120, 117, 112, 79, 102,
|
||||
102, 115, 101, 116, 0, 171,
|
||||
171, 171, 1, 0, 3, 0,
|
||||
1, 0, 2, 0, 1, 0,
|
||||
0, 0, 0, 0, 0, 0,
|
||||
87, 111, 114, 108, 100, 86,
|
||||
105, 101, 119, 80, 114, 111,
|
||||
106, 101, 99, 116, 105, 111,
|
||||
110, 0, 3, 0, 3, 0,
|
||||
4, 0, 4, 0, 1, 0,
|
||||
0, 0, 0, 0, 0, 0,
|
||||
118, 115, 95, 49, 95, 49,
|
||||
0, 77, 105, 99, 114, 111,
|
||||
115, 111, 102, 116, 32, 40,
|
||||
82, 41, 32, 72, 76, 83,
|
||||
76, 32, 83, 104, 97, 100,
|
||||
101, 114, 32, 67, 111, 109,
|
||||
112, 105, 108, 101, 114, 32,
|
||||
49, 48, 46, 49, 0, 171,
|
||||
31, 0, 0, 0, 0, 0,
|
||||
0, 128, 0, 0, 15, 144,
|
||||
31, 0, 0, 0, 10, 0,
|
||||
0, 128, 1, 0, 15, 144,
|
||||
31, 0, 0, 0, 5, 0,
|
||||
0, 128, 2, 0, 15, 144,
|
||||
9, 0, 0, 0, 0, 0,
|
||||
4, 192, 0, 0, 228, 144,
|
||||
2, 0, 228, 160, 5, 0,
|
||||
0, 0, 0, 0, 15, 208,
|
||||
1, 0, 228, 144, 4, 0,
|
||||
228, 160, 9, 0, 0, 0,
|
||||
0, 0, 1, 128, 0, 0,
|
||||
228, 144, 0, 0, 228, 160,
|
||||
9, 0, 0, 0, 0, 0,
|
||||
2, 128, 0, 0, 228, 144,
|
||||
1, 0, 228, 160, 9, 0,
|
||||
0, 0, 0, 0, 4, 128,
|
||||
0, 0, 228, 144, 3, 0,
|
||||
228, 160, 4, 0, 0, 0,
|
||||
0, 0, 3, 192, 63, 0,
|
||||
228, 160, 0, 0, 170, 128,
|
||||
0, 0, 228, 128, 1, 0,
|
||||
0, 0, 0, 0, 8, 192,
|
||||
0, 0, 170, 128, 1, 0,
|
||||
0, 0, 0, 0, 3, 224,
|
||||
2, 0, 228, 144, 255, 255,
|
||||
0, 0
|
||||
};
|
||||
40
hooks/graphics/backends/d3d9/shaders/vertex_shader.hlsl
Normal file
40
hooks/graphics/backends/d3d9/shaders/vertex_shader.hlsl
Normal file
@@ -0,0 +1,40 @@
|
||||
/*
|
||||
* original source
|
||||
* vs.1.1 //Shader version 1.1
|
||||
* dcl_position v0;
|
||||
* dcl_color v1;
|
||||
* dcl_texcoord0 v2;
|
||||
* m4x4 oPos, v0, c0
|
||||
* mul oD0, v1, c4
|
||||
* mov oT0.xy, v2
|
||||
*
|
||||
* build command
|
||||
* fxc.exe /Vi vertex-shader.hlsl /Fh vertex-shader.h /T vs_1_1 /E vs_main
|
||||
*/
|
||||
|
||||
float4x4 WorldViewProjection : register(c0);
|
||||
float4 ColorMultiply : register(c4);
|
||||
float2 ConstantHalfTexelFixupOffset : register(c63);
|
||||
|
||||
struct VS {
|
||||
float4 Position : POSITION; // dcl_position v0;
|
||||
float4 Color : COLOR; // dcl_color v1;
|
||||
float2 TexCoord : TEXCOORD0; // dcl_texcoord0 v2;
|
||||
};
|
||||
|
||||
VS vs_main(VS input)
|
||||
{
|
||||
VS output;
|
||||
|
||||
output.Position = mul(input.Position, WorldViewProjection); // m4x4 oPos, v0, c0
|
||||
output.Color.x = mul(input.Color.x, ColorMultiply.x); // mul oD0, v1, c4
|
||||
output.Color.y = mul(input.Color.y, ColorMultiply.y);
|
||||
output.Color.z = mul(input.Color.z, ColorMultiply.z);
|
||||
output.Color.w = mul(input.Color.w, ColorMultiply.w);
|
||||
output.TexCoord = input.TexCoord; // mov oT0.xy, v2
|
||||
|
||||
// fix texture position
|
||||
output.Position.xy += ConstantHalfTexelFixupOffset.xy * output.Position.w;
|
||||
|
||||
return output;
|
||||
}
|
||||
1034
hooks/graphics/graphics.cpp
Normal file
1034
hooks/graphics/graphics.cpp
Normal file
File diff suppressed because it is too large
Load Diff
94
hooks/graphics/graphics.h
Normal file
94
hooks/graphics/graphics.h
Normal file
@@ -0,0 +1,94 @@
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <optional>
|
||||
#include <cstdint>
|
||||
|
||||
#include <windows.h>
|
||||
#include <d3d9.h>
|
||||
|
||||
#include "external/toojpeg/toojpeg.h"
|
||||
|
||||
// order must match spice2x_AutoOrientation UI enum order
|
||||
enum graphics_orientation {
|
||||
ORIENTATION_CW = 0,
|
||||
ORIENTATION_CCW = 1,
|
||||
ORIENTATION_NORMAL = 2,
|
||||
};
|
||||
|
||||
enum graphics_dx9on12_state {
|
||||
DX9ON12_AUTO,
|
||||
DX9ON12_FORCE_OFF,
|
||||
DX9ON12_FORCE_ON,
|
||||
};
|
||||
|
||||
// flag settings
|
||||
extern bool GRAPHICS_CAPTURE_CURSOR;
|
||||
extern bool GRAPHICS_LOG_HRESULT;
|
||||
extern bool GRAPHICS_SDVX_FORCE_720;
|
||||
extern bool GRAPHICS_SHOW_CURSOR;
|
||||
extern bool GRAPHICS_WINDOWED;
|
||||
extern graphics_orientation GRAPHICS_ADJUST_ORIENTATION;
|
||||
extern std::vector<HWND> GRAPHICS_WINDOWS;
|
||||
extern UINT GRAPHICS_FORCE_REFRESH;
|
||||
extern bool GRAPHICS_FORCE_SINGLE_ADAPTER;
|
||||
extern bool GRAPHICS_PREVENT_SECONDARY_WINDOW;
|
||||
|
||||
extern graphics_dx9on12_state GRAPHICS_9_ON_12_STATE;
|
||||
extern bool GRAPHICS_9_ON_12_REQUESTED_BY_GAME;
|
||||
|
||||
extern std::optional<int> GRAPHICS_WINDOW_STYLE;
|
||||
extern std::optional<std::string> GRAPHICS_WINDOW_SIZE;
|
||||
extern std::optional<std::string> GRAPHICS_WINDOW_POS;
|
||||
extern bool GRAPHICS_WINDOW_ALWAYS_ON_TOP;
|
||||
|
||||
extern bool GRAPHICS_IIDX_WSUB;
|
||||
extern std::optional<std::string> GRAPHICS_IIDX_WSUB_SIZE;
|
||||
extern std::optional<std::string> GRAPHICS_IIDX_WSUB_POS;
|
||||
extern int GRAPHICS_IIDX_WSUB_WIDTH;
|
||||
extern int GRAPHICS_IIDX_WSUB_HEIGHT;
|
||||
extern int GRAPHICS_IIDX_WSUB_X;
|
||||
extern int GRAPHICS_IIDX_WSUB_Y;
|
||||
extern HWND TDJ_SUBSCREEN_WINDOW;
|
||||
extern HWND SDVX_SUBSCREEN_WINDOW;
|
||||
|
||||
extern bool SUBSCREEN_FORCE_REDRAW;
|
||||
|
||||
// settings
|
||||
extern std::string GRAPHICS_DEVICEID;
|
||||
extern std::string GRAPHICS_SCREENSHOT_DIR;
|
||||
|
||||
// Direct3D 9 settings
|
||||
extern std::optional<UINT> D3D9_ADAPTER;
|
||||
extern DWORD D3D9_BEHAVIOR_DISABLE;
|
||||
extern bool D3D9_DEVICE_HOOK_DISABLE;
|
||||
|
||||
void graphics_init();
|
||||
void graphics_hook_window(HWND hWnd, D3DPRESENT_PARAMETERS *pPresentationParameters);
|
||||
void graphics_add_wnd_proc(WNDPROC wndProc);
|
||||
void graphics_remove_wnd_proc(WNDPROC wndProc);
|
||||
void graphics_hook_subscreen_window(HWND hWnd);
|
||||
void graphics_screens_register(int screen);
|
||||
void graphics_screens_unregister(int screen);
|
||||
void graphics_screens_get(std::vector<int> &screens);
|
||||
void graphics_screenshot_trigger();
|
||||
bool graphics_screenshot_consume();
|
||||
void graphics_capture_trigger(int screen);
|
||||
bool graphics_capture_consume(int *screen);
|
||||
void graphics_capture_enqueue(int screen, uint8_t *data, size_t width, size_t height);
|
||||
void graphics_capture_skip(int screen);
|
||||
bool graphics_capture_receive_jpeg(int screen, TooJpeg::WRITE_ONE_BYTE receiver,
|
||||
bool rgb = true, int quality = 80, bool downsample = true, int divide = 0,
|
||||
uint64_t *timestamp = nullptr,
|
||||
int *width = nullptr, int *height = nullptr);
|
||||
std::string graphics_screenshot_genpath();
|
||||
|
||||
// graphics_windowed.cpp
|
||||
void graphics_windowed_wndproc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam);
|
||||
void graphics_capture_initial_window(HWND hWnd);
|
||||
void graphics_update_window_style(HWND hWnd);
|
||||
void graphics_update_z_order(HWND hWnd);
|
||||
void graphics_move_resize_window(HWND hWnd);
|
||||
bool graphics_window_change_crashes_game();
|
||||
void graphics_load_windowed_subscreen_parameters();
|
||||
485
hooks/graphics/graphics_windowed.cpp
Normal file
485
hooks/graphics/graphics_windowed.cpp
Normal file
@@ -0,0 +1,485 @@
|
||||
#include "graphics.h"
|
||||
|
||||
#include "avs/game.h"
|
||||
#include "cfg/screen_resize.h"
|
||||
#include "overlay/overlay.h"
|
||||
#include "util/logging.h"
|
||||
#include "util/utils.h"
|
||||
#include "touch/touch.h"
|
||||
|
||||
#if 0
|
||||
#define log_debug(module, format_str, ...) logger::push( \
|
||||
LOG_FORMAT("M", module, format_str, ## __VA_ARGS__), logger::Style::GREY)
|
||||
#else
|
||||
#define log_debug(module, format_str, ...)
|
||||
#endif
|
||||
|
||||
void graphics_load_windowed_parameters();
|
||||
void graphics_wm_style_changed(HWND hWnd, bool changed);
|
||||
void graphics_wm_sizing_aspect_ratio(int edge, RECT& rect);
|
||||
|
||||
std::optional<int> GRAPHICS_WINDOW_STYLE;
|
||||
std::optional<std::string> GRAPHICS_WINDOW_SIZE;
|
||||
std::optional<std::string> GRAPHICS_WINDOW_POS;
|
||||
bool GRAPHICS_WINDOW_ALWAYS_ON_TOP = false;
|
||||
|
||||
// IIDX Windowed Subscreen - starts out as false, enabled by IIDX module on pre-attach as needed
|
||||
bool GRAPHICS_IIDX_WSUB = false;
|
||||
std::optional<std::string> GRAPHICS_IIDX_WSUB_SIZE;
|
||||
std::optional<std::string> GRAPHICS_IIDX_WSUB_POS;
|
||||
int GRAPHICS_IIDX_WSUB_WIDTH = 1280;
|
||||
int GRAPHICS_IIDX_WSUB_HEIGHT = 720;
|
||||
int GRAPHICS_IIDX_WSUB_X = 0;
|
||||
int GRAPHICS_IIDX_WSUB_Y = 0;
|
||||
|
||||
// these flags are carefully constructed to ensure maximum compatibility
|
||||
// (e.g., DDR likes to hang when SetWindowPos is called with certain params)
|
||||
static const DWORD SETWINDOWPOS_NOOP =
|
||||
SWP_NOMOVE |
|
||||
SWP_NOSIZE |
|
||||
SWP_NOREDRAW |
|
||||
SWP_NOCOPYBITS |
|
||||
SWP_NOACTIVATE |
|
||||
SWP_NOSENDCHANGING |
|
||||
SWP_DEFERERASE |
|
||||
SWP_NOZORDER |
|
||||
SWP_ASYNCWINDOWPOS;
|
||||
|
||||
void graphics_capture_initial_window(HWND hWnd) {
|
||||
if (!GRAPHICS_WINDOWED) {
|
||||
return;
|
||||
}
|
||||
|
||||
graphics_load_windowed_parameters();
|
||||
|
||||
cfg::SCREENRESIZE->init_window_style = GetWindowLong(hWnd, GWL_STYLE);
|
||||
|
||||
log_debug("graphics-windowed", "graphics_capture_initial_window called");
|
||||
|
||||
RECT rect;
|
||||
if (!GetClientRect(hWnd, &rect)) {
|
||||
log_warning(
|
||||
"graphics",
|
||||
"graphics_capture_initial_window - GetClientRect failed, GLE: {}",
|
||||
GetLastError());
|
||||
return;
|
||||
}
|
||||
const int client_w = rect.right - rect.left;
|
||||
const int client_h = rect.bottom - rect.top;
|
||||
|
||||
cfg::SCREENRESIZE->init_client_width = client_w;
|
||||
cfg::SCREENRESIZE->init_client_height = client_h;
|
||||
cfg::SCREENRESIZE->init_client_aspect_ratio = (float)client_w / (float)client_h;
|
||||
log_debug(
|
||||
"graphics-windowed",
|
||||
"graphics_capture_initial_window initial window size {}x{}, ratio {}",
|
||||
client_w, client_h, cfg::SCREENRESIZE->init_client_aspect_ratio);
|
||||
|
||||
// ensure frame size is captured
|
||||
graphics_wm_style_changed(hWnd, false);
|
||||
|
||||
// if there was no user-supplied dimension, seed it with the current size
|
||||
// so that the next resize operation will work
|
||||
if (cfg::SCREENRESIZE->client_width == 0) {
|
||||
cfg::SCREENRESIZE->client_width = client_w;
|
||||
}
|
||||
if (cfg::SCREENRESIZE->client_height == 0) {
|
||||
cfg::SCREENRESIZE->client_height = client_h;
|
||||
}
|
||||
|
||||
// apply the config that was loaded from disk
|
||||
// resize must be done before applying the border
|
||||
if (cfg::SCREENRESIZE->enable_window_resize) {
|
||||
log_info(
|
||||
"graphics-windowed", "change window rect - window offset: {}x{}, client size: {}x{}",
|
||||
cfg::SCREENRESIZE->window_offset_x,
|
||||
cfg::SCREENRESIZE->window_offset_y,
|
||||
cfg::SCREENRESIZE->client_width,
|
||||
cfg::SCREENRESIZE->client_height);
|
||||
graphics_move_resize_window(hWnd);
|
||||
}
|
||||
// ddr hangs when window frame doesn't have overlapped
|
||||
if (cfg::SCREENRESIZE->window_decoration != cfg::WindowDecorationMode::Default) {
|
||||
log_info(
|
||||
"graphics-windowed", "change window style - decoration: {}",
|
||||
cfg::SCREENRESIZE->window_decoration);
|
||||
graphics_update_window_style(hWnd);
|
||||
}
|
||||
if (cfg::SCREENRESIZE->window_always_on_top) {
|
||||
log_info("graphics-windowed", "change window z-order - always on top");
|
||||
graphics_update_z_order(hWnd);
|
||||
}
|
||||
|
||||
// ensure spictetouch coordinates are initialized
|
||||
update_spicetouch_window_dimensions(hWnd);
|
||||
|
||||
log_debug("graphics-windowed", "graphics_capture_initial_window returned");
|
||||
}
|
||||
|
||||
void graphics_load_windowed_parameters() {
|
||||
if (!GRAPHICS_WINDOWED) {
|
||||
return;
|
||||
}
|
||||
|
||||
log_debug("graphics-windowed", "graphics_load_windowed_parameters called");
|
||||
const auto remove_spaces = [](const char& c) {
|
||||
return c == ' ';
|
||||
};
|
||||
|
||||
if (GRAPHICS_WINDOW_STYLE.has_value()) {
|
||||
log_debug(
|
||||
"graphics-windowed",
|
||||
"graphics_load_windowed_parameters - load GRAPHICS_WINDOW_STYLE");
|
||||
cfg::SCREENRESIZE->window_decoration = GRAPHICS_WINDOW_STYLE.value();
|
||||
}
|
||||
|
||||
if (GRAPHICS_WINDOW_SIZE.has_value()) {
|
||||
log_debug(
|
||||
"graphics-windowed",
|
||||
"graphics_load_windowed_parameters - load GRAPHICS_WINDOW_SIZE");
|
||||
uint32_t w, h;
|
||||
auto s = GRAPHICS_WINDOW_SIZE.value();
|
||||
s.erase(std::remove_if(s.begin(), s.end(), remove_spaces), s.end());
|
||||
if (sscanf(s.c_str(), "%u,%u", &w, &h) == 2) {
|
||||
cfg::SCREENRESIZE->enable_window_resize = true;
|
||||
cfg::SCREENRESIZE->client_keep_aspect_ratio = false;
|
||||
cfg::SCREENRESIZE->client_width = w;
|
||||
cfg::SCREENRESIZE->client_height = h;
|
||||
} else {
|
||||
log_warning("graphics-windowed", "failed to parse -windowsize");
|
||||
}
|
||||
}
|
||||
|
||||
if (GRAPHICS_WINDOW_POS.has_value()) {
|
||||
log_debug(
|
||||
"graphics-windowed",
|
||||
"graphics_load_windowed_parameters - load GRAPHICS_WINDOW_POS");
|
||||
int32_t x, y;
|
||||
auto s = GRAPHICS_WINDOW_POS.value();
|
||||
s.erase(std::remove_if(s.begin(), s.end(), remove_spaces), s.end());
|
||||
if (sscanf(s.c_str(), "%d,%d", &x, &y) == 2) {
|
||||
cfg::SCREENRESIZE->enable_window_resize = true;
|
||||
cfg::SCREENRESIZE->window_offset_x = x;
|
||||
cfg::SCREENRESIZE->window_offset_y = y;
|
||||
} else {
|
||||
log_warning("graphics-windowed", "failed to parse -windowpos");
|
||||
}
|
||||
}
|
||||
|
||||
// only override if true; don't stomp on user setting
|
||||
if (GRAPHICS_WINDOW_ALWAYS_ON_TOP) {
|
||||
cfg::SCREENRESIZE->window_always_on_top = true;
|
||||
}
|
||||
|
||||
log_debug("graphics-windowed", "graphics_load_windowed_parameters returned");
|
||||
}
|
||||
|
||||
void graphics_load_windowed_subscreen_parameters() {
|
||||
if (!GRAPHICS_WINDOWED) {
|
||||
return;
|
||||
}
|
||||
|
||||
log_debug("graphics-windowed", "graphics_load_windowed_subscreen_parameters called");
|
||||
const auto remove_spaces = [](const char& c) {
|
||||
return c == ' ';
|
||||
};
|
||||
|
||||
if (GRAPHICS_IIDX_WSUB_SIZE.has_value()) {
|
||||
log_debug(
|
||||
"graphics-windowed",
|
||||
"graphics_load_windowed_parameters - load GRAPHICS_IIDX_WSUB_SIZE");
|
||||
uint32_t w, h;
|
||||
auto s = GRAPHICS_IIDX_WSUB_SIZE.value();
|
||||
s.erase(std::remove_if(s.begin(), s.end(), remove_spaces), s.end());
|
||||
if (sscanf(s.c_str(), "%u,%u", &w, &h) == 2) {
|
||||
GRAPHICS_IIDX_WSUB_WIDTH = w;
|
||||
GRAPHICS_IIDX_WSUB_HEIGHT = h;
|
||||
} else {
|
||||
log_warning("graphics-windowed", "failed to parse -wsubsize");
|
||||
}
|
||||
}
|
||||
|
||||
if (GRAPHICS_IIDX_WSUB_POS.has_value()) {
|
||||
log_debug(
|
||||
"graphics-windowed",
|
||||
"graphics_load_windowed_parameters - load GRAPHICS_IIDX_WSUB_POS");
|
||||
int32_t x, y;
|
||||
auto s = GRAPHICS_IIDX_WSUB_POS.value();
|
||||
s.erase(std::remove_if(s.begin(), s.end(), remove_spaces), s.end());
|
||||
if (sscanf(s.c_str(), "%d,%d", &x, &y) == 2) {
|
||||
GRAPHICS_IIDX_WSUB_X = x;
|
||||
GRAPHICS_IIDX_WSUB_Y = y;
|
||||
} else {
|
||||
log_warning("graphics-windowed", "failed to parse -wsubpos");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void graphics_windowed_wndproc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) {
|
||||
if (!GRAPHICS_WINDOWED) {
|
||||
return;
|
||||
}
|
||||
|
||||
switch (uMsg) {
|
||||
case WM_MOVE: {
|
||||
log_debug("graphics-windowed", "graphics_windowed_wndproc called with WM_MOVE");
|
||||
RECT rect;
|
||||
if (GetWindowRect(hWnd, &rect)) {
|
||||
cfg::SCREENRESIZE->window_offset_x = rect.left;
|
||||
cfg::SCREENRESIZE->window_offset_y = rect.top;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case WM_SIZE: {
|
||||
log_debug("graphics-windowed", "graphics_windowed_wndproc called with WM_SIZE");
|
||||
if (wParam == SIZE_MINIMIZED) {
|
||||
break;
|
||||
}
|
||||
RECT rect;
|
||||
if (GetClientRect(hWnd, &rect)) {
|
||||
cfg::SCREENRESIZE->client_width = rect.right - rect.left;
|
||||
cfg::SCREENRESIZE->client_height = rect.bottom - rect.top;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case WM_SIZING: {
|
||||
if (cfg::SCREENRESIZE->client_keep_aspect_ratio) {
|
||||
graphics_wm_sizing_aspect_ratio(
|
||||
static_cast<int>(wParam), *reinterpret_cast<LPRECT>(lParam));
|
||||
}
|
||||
break;
|
||||
}
|
||||
case WM_STYLECHANGED: {
|
||||
graphics_wm_style_changed(hWnd, true);
|
||||
break;
|
||||
}
|
||||
case WM_GETMINMAXINFO: {
|
||||
if (cfg::SCREENRESIZE->client_keep_aspect_ratio) {
|
||||
auto info = reinterpret_cast<MINMAXINFO *>(lParam);
|
||||
info->ptMinTrackSize.y =
|
||||
cfg::SCREENRESIZE->window_deco_height +
|
||||
((info->ptMinTrackSize.x - cfg::SCREENRESIZE->window_deco_width) /
|
||||
cfg::SCREENRESIZE->init_client_aspect_ratio);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void graphics_wm_style_changed(HWND hWnd, bool changed) {
|
||||
log_debug("graphics-windowed", "graphics_wm_style_changed called");
|
||||
RECT rect;
|
||||
|
||||
// ensure the style change takes in effect before doing the calculations
|
||||
if (changed) {
|
||||
// ensure client size doesn't change as a result of this
|
||||
// since SetWindowPos will still send WM_SIZE and WM_MOVE
|
||||
const auto client_w = cfg::SCREENRESIZE->client_width;
|
||||
const auto client_h = cfg::SCREENRESIZE->client_height;
|
||||
const auto flags = SETWINDOWPOS_NOOP | SWP_FRAMECHANGED;
|
||||
SetWindowPos(
|
||||
hWnd,
|
||||
nullptr,
|
||||
0, 0, 0, 0,
|
||||
flags);
|
||||
cfg::SCREENRESIZE->client_width = client_w;
|
||||
cfg::SCREENRESIZE->client_height = client_h;
|
||||
}
|
||||
|
||||
// get window size with decoration...
|
||||
if (!GetWindowRect(hWnd, &rect)) {
|
||||
log_warning(
|
||||
"graphics",
|
||||
"graphics_wm_style_changed - GetWindowRect failed, GLE: {}",
|
||||
GetLastError());
|
||||
return;
|
||||
}
|
||||
const int window_w = rect.right - rect.left;
|
||||
const int window_h = rect.bottom - rect.top;
|
||||
|
||||
// get client area (without decoration)...
|
||||
if (!GetClientRect(hWnd, &rect)) {
|
||||
return;
|
||||
}
|
||||
const int client_w = rect.right - rect.left;
|
||||
const int client_h = rect.bottom - rect.top;
|
||||
|
||||
// update window decoration size
|
||||
cfg::SCREENRESIZE->window_deco_width = window_w - client_w;
|
||||
cfg::SCREENRESIZE->window_deco_height = window_h - client_h;
|
||||
log_debug(
|
||||
"graphics-windowed",
|
||||
"graphics_wm_style_changed updating frame dimensions {}x{}",
|
||||
cfg::SCREENRESIZE->window_deco_width,
|
||||
cfg::SCREENRESIZE->window_deco_height);
|
||||
|
||||
// adjust window to ensure client area remains the same
|
||||
if (changed) {
|
||||
graphics_move_resize_window(hWnd);
|
||||
}
|
||||
|
||||
log_debug("graphics-windowed", "graphics_wm_style_changed returned");
|
||||
}
|
||||
|
||||
void graphics_wm_sizing_aspect_ratio(int edge, RECT& rect) {
|
||||
log_debug("graphics-windowed", "graphics_wm_sizing_aspect_ratio called");
|
||||
|
||||
const auto deco_w = cfg::SCREENRESIZE->window_deco_width;
|
||||
const auto deco_h = cfg::SCREENRESIZE->window_deco_height;
|
||||
const LONG desired_w = (rect.right - rect.left) - deco_w;
|
||||
const LONG desired_h = (rect.bottom - rect.top) - deco_h;
|
||||
const auto aspect_ratio = cfg::SCREENRESIZE->init_client_aspect_ratio;
|
||||
|
||||
// based on http://playtechs.blogspot.com/2007/10/forcing-window-to-maintain-particular.html
|
||||
switch (edge) {
|
||||
case WMSZ_BOTTOM:
|
||||
case WMSZ_TOP: {
|
||||
const LONG w = deco_w + (desired_h * aspect_ratio);
|
||||
rect.right = rect.left + w;
|
||||
break;
|
||||
}
|
||||
case WMSZ_LEFT:
|
||||
case WMSZ_RIGHT: {
|
||||
const LONG h = deco_h + (desired_w / aspect_ratio);
|
||||
rect.bottom = rect.top + h;
|
||||
break;
|
||||
}
|
||||
case WMSZ_TOPLEFT:
|
||||
case WMSZ_TOPRIGHT:
|
||||
case WMSZ_BOTTOMLEFT:
|
||||
case WMSZ_BOTTOMRIGHT: {
|
||||
int w;
|
||||
int h;
|
||||
if (desired_h * aspect_ratio < desired_w) {
|
||||
w = rect.right - rect.left;
|
||||
h = deco_h + (desired_w / aspect_ratio);
|
||||
} else {
|
||||
w = deco_w + (desired_h * aspect_ratio);
|
||||
h = rect.bottom - rect.top;
|
||||
}
|
||||
if (edge == WMSZ_TOPLEFT) {
|
||||
rect.left = rect.right - w;
|
||||
rect.top = rect.bottom - h;
|
||||
} else if (edge == WMSZ_TOPRIGHT) {
|
||||
rect.right = rect.left + w;
|
||||
rect.top = rect.bottom - h;
|
||||
} else if (edge == WMSZ_BOTTOMLEFT) {
|
||||
rect.left = rect.right - w;
|
||||
rect.bottom = rect.top + h;
|
||||
} else if (edge == WMSZ_BOTTOMRIGHT) {
|
||||
rect.right = rect.left + w;
|
||||
rect.bottom = rect.top + h;
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
log_debug("graphics-windowed", "graphics_wm_sizing_aspect_ratio returned");
|
||||
}
|
||||
|
||||
void graphics_update_window_style(HWND hWnd) {
|
||||
if (!GRAPHICS_WINDOWED) {
|
||||
return;
|
||||
}
|
||||
if (graphics_window_change_crashes_game()) {
|
||||
return;
|
||||
}
|
||||
|
||||
log_debug("graphics-windowed", "graphics_update_window_style called");
|
||||
|
||||
// update frame style
|
||||
auto style = cfg::SCREENRESIZE->init_window_style;
|
||||
switch (cfg::SCREENRESIZE->window_decoration) {
|
||||
case cfg::WindowDecorationMode::Borderless:
|
||||
style &= ~WS_OVERLAPPEDWINDOW;
|
||||
break;
|
||||
case cfg::WindowDecorationMode::ResizableFrame:
|
||||
style |= WS_OVERLAPPEDWINDOW;
|
||||
break;
|
||||
case cfg::WindowDecorationMode::Default:
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
log_debug(
|
||||
"graphics-windowed",
|
||||
"graphics_update_window_style - calling SetWindowLong with Mode {}, style 0x{:x}",
|
||||
static_cast<int>(cfg::SCREENRESIZE->window_decoration),
|
||||
style);
|
||||
SetWindowLong(hWnd, GWL_STYLE, style);
|
||||
|
||||
// SetWindowPos must be called after SetWindowLong if the frame style changed
|
||||
// this will be done in WM_STYLECHANGED handler
|
||||
log_debug("graphics-windowed", "graphics_update_window_style returned");
|
||||
}
|
||||
|
||||
void graphics_update_z_order(HWND hWnd) {
|
||||
if (!GRAPHICS_WINDOWED) {
|
||||
return;
|
||||
}
|
||||
|
||||
log_debug("graphics-windowed", "graphics_update_z_order called");
|
||||
|
||||
HWND insert_after = nullptr;
|
||||
if (cfg::SCREENRESIZE->window_always_on_top) {
|
||||
insert_after = HWND_TOPMOST;
|
||||
} else {
|
||||
insert_after = HWND_NOTOPMOST;
|
||||
}
|
||||
|
||||
auto flags = SETWINDOWPOS_NOOP;
|
||||
flags &= ~SWP_NOZORDER;
|
||||
SetWindowPos(
|
||||
hWnd,
|
||||
insert_after,
|
||||
0, 0, 0, 0,
|
||||
flags);
|
||||
|
||||
log_debug("graphics-windowed", "graphics_update_z_order returned");
|
||||
}
|
||||
|
||||
void graphics_move_resize_window(HWND hWnd) {
|
||||
if (!GRAPHICS_WINDOWED) {
|
||||
return;
|
||||
}
|
||||
|
||||
log_debug("graphics-windowed", "graphics_move_resize_window called");
|
||||
|
||||
cfg::SCREENRESIZE->client_width =
|
||||
CLAMP(cfg::SCREENRESIZE->client_width, 640, 1920 * 8);
|
||||
cfg::SCREENRESIZE->client_height =
|
||||
CLAMP(cfg::SCREENRESIZE->client_height, 480, 1080 * 8);
|
||||
|
||||
const auto w = cfg::SCREENRESIZE->client_width + cfg::SCREENRESIZE->window_deco_width;
|
||||
const auto h = cfg::SCREENRESIZE->client_height + cfg::SCREENRESIZE->window_deco_height;
|
||||
|
||||
auto flags = SETWINDOWPOS_NOOP;
|
||||
flags &= ~SWP_NOSIZE;
|
||||
flags &= ~SWP_NOMOVE;
|
||||
SetWindowPos(
|
||||
hWnd,
|
||||
nullptr,
|
||||
cfg::SCREENRESIZE->window_offset_x,
|
||||
cfg::SCREENRESIZE->window_offset_y,
|
||||
w, h,
|
||||
flags);
|
||||
|
||||
log_debug("graphics-windowed", "graphics_move_resize_window returned");
|
||||
}
|
||||
|
||||
bool graphics_window_change_crashes_game() {
|
||||
static std::once_flag flag;
|
||||
static bool result = false;
|
||||
std::call_once(flag, []() {
|
||||
// ddr crashes when frame style changes
|
||||
result = avs::game::is_model("MDX");
|
||||
if (result) {
|
||||
log_warning(
|
||||
"graphics-windowed",
|
||||
"ignoring changes to window style due to incompatibility with this game");
|
||||
}
|
||||
});
|
||||
return result;
|
||||
}
|
||||
59
hooks/graphics/nvapi_hook.cpp
Normal file
59
hooks/graphics/nvapi_hook.cpp
Normal file
@@ -0,0 +1,59 @@
|
||||
#include "external/nvapi/nvapi.h"
|
||||
#include "external/nvapi/NvApiDriverSettings.h"
|
||||
#include "hooks/libraryhook.h"
|
||||
#include "util/detour.h"
|
||||
#include "util/libutils.h"
|
||||
#include "util/logging.h"
|
||||
|
||||
#include "nvapi_hook.h"
|
||||
|
||||
namespace nvapi_hook {
|
||||
|
||||
bool BYPASS_NVAPI = false;
|
||||
|
||||
typedef uintptr_t *(*NvAPI_QueryInterface_t)(unsigned int);
|
||||
static NvAPI_QueryInterface_t NvAPI_QueryInterface_orig = nullptr;
|
||||
|
||||
static uintptr_t* __cdecl NvAPI_QueryInterface_hook(unsigned int func_code);
|
||||
static NvAPI_Status __cdecl NvAPI_DISP_SetDisplayConfig_hook(
|
||||
NvU32 pathInfoCount, NV_DISPLAYCONFIG_PATH_INFO *pathInfo, NvU32 flags);
|
||||
|
||||
void initialize(HINSTANCE dll) {
|
||||
|
||||
#ifdef SPICE64
|
||||
std::string nvapi_dll = "nvapi64.dll";
|
||||
#else
|
||||
std::string nvapi_dll = "nvapi.dll";
|
||||
#endif
|
||||
|
||||
detour::trampoline_try(
|
||||
nvapi_dll.c_str(), "nvapi_QueryInterface",
|
||||
NvAPI_QueryInterface_hook, &NvAPI_QueryInterface_orig);
|
||||
}
|
||||
|
||||
uintptr_t* __cdecl NvAPI_QueryInterface_hook(unsigned int func_code) {
|
||||
if (BYPASS_NVAPI) {
|
||||
log_misc(
|
||||
"nvapi_hook",
|
||||
"NvAPI_QueryInterface(0x{:x}) - block all calls to nvapi (-nonvapi)",
|
||||
func_code);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// NvAPI_DISP_SetDisplayConfig
|
||||
if (func_code == 0x5D8CF8DE) {
|
||||
log_misc("nvapi_hook", "NvAPI_QueryInterface(NvAPI_DISP_SetDisplayConfig) - hooked");
|
||||
return (uintptr_t *)NvAPI_DISP_SetDisplayConfig_hook;
|
||||
}
|
||||
|
||||
// all others: let the game call nvapi directly
|
||||
log_misc("nvapi_hook", "NvAPI_QueryInterface(0x{:x}) - pass through to nvapi", func_code);
|
||||
return NvAPI_QueryInterface_orig(func_code);
|
||||
}
|
||||
|
||||
NvAPI_Status __cdecl NvAPI_DISP_SetDisplayConfig_hook(
|
||||
NvU32 pathInfoCount, NV_DISPLAYCONFIG_PATH_INFO *pathInfo, NvU32 flags) {
|
||||
log_misc("nvapi_hook", "NvAPI_DISP_SetDisplayConfig_hook - do nothing and return");
|
||||
return NVAPI_OK;
|
||||
}
|
||||
}
|
||||
6
hooks/graphics/nvapi_hook.h
Normal file
6
hooks/graphics/nvapi_hook.h
Normal file
@@ -0,0 +1,6 @@
|
||||
#pragma once
|
||||
|
||||
namespace nvapi_hook {
|
||||
extern bool BYPASS_NVAPI;
|
||||
void initialize(HINSTANCE dll);
|
||||
}
|
||||
87
hooks/graphics/nvenc_hook.cpp
Normal file
87
hooks/graphics/nvenc_hook.cpp
Normal file
@@ -0,0 +1,87 @@
|
||||
#include <d3d9.h>
|
||||
|
||||
#include "avs/game.h"
|
||||
#include "external/nvenc/nvEncodeAPI.h"
|
||||
#include "hooks/libraryhook.h"
|
||||
#include "hooks/graphics/backends/d3d9/d3d9_device.h"
|
||||
#include "util/detour.h"
|
||||
#include "util/libutils.h"
|
||||
#include "util/logging.h"
|
||||
|
||||
#include "nvenc_hook.h"
|
||||
|
||||
#ifdef SPICE64
|
||||
|
||||
typedef NVENCSTATUS(NVENCAPI *NvEncodeAPICreateInstance_Type)(NV_ENCODE_API_FUNCTION_LIST*);
|
||||
static NvEncodeAPICreateInstance_Type NvEncodeAPICreateInstance_orig = nullptr;
|
||||
static PNVENCOPENENCODESESSIONEX nvEncOpenEncodeSessionEx_orig = nullptr;
|
||||
static BOOL initialized = false;
|
||||
|
||||
namespace nvenc_hook {
|
||||
|
||||
NVENCSTATUS nvEncOpenEncodeSessionEx_hook(
|
||||
NV_ENC_OPEN_ENCODE_SESSION_EX_PARAMS *openSessionExParams,
|
||||
void **encoder
|
||||
) {
|
||||
WrappedIDirect3DDevice9 *wrappedDevice;
|
||||
try {
|
||||
wrappedDevice = (WrappedIDirect3DDevice9*)openSessionExParams->device;
|
||||
// log_misc("nvenc_hook",
|
||||
// "nvEncOpenEncodeSessionEx hook hit (wrapped: {}) (real: {})",
|
||||
// fmt::ptr(wrappedDevice),
|
||||
// fmt::ptr(wrappedDevice->pReal)
|
||||
// );
|
||||
openSessionExParams->device = wrappedDevice->pReal;
|
||||
} catch (const std::exception &ex) {
|
||||
// log_misc("nvenc_hook", "Cannot cast to WrappedIDirect3DDevice9. D3D9 hooks might be disabled.");
|
||||
}
|
||||
return nvEncOpenEncodeSessionEx_orig(openSessionExParams, encoder);
|
||||
}
|
||||
|
||||
NVENCSTATUS NvEncodeAPICreateInstance_hook(NV_ENCODE_API_FUNCTION_LIST *pFunctionList) {
|
||||
// log_misc("nvenc_hook", "NvEncodeAPICreateInstance hook hit");
|
||||
auto status = NvEncodeAPICreateInstance_orig(pFunctionList);
|
||||
|
||||
// The game will call NvEncodeAPICreateInstance multiple times
|
||||
// Using a flag to avoid creating trampoline repeatedly
|
||||
if (!initialized) {
|
||||
// hook functions
|
||||
detour::trampoline_try(
|
||||
pFunctionList->nvEncOpenEncodeSessionEx,
|
||||
nvEncOpenEncodeSessionEx_hook,
|
||||
&nvEncOpenEncodeSessionEx_orig);
|
||||
// log_misc("nvenc_hook", "Created hook for nvEncOpenEncodeSessionEx");
|
||||
initialized = true;
|
||||
}
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
void initialize() {
|
||||
HMODULE nvenc = libutils::try_library("nvEncodeAPI64.dll");
|
||||
if (nvenc == nullptr) {
|
||||
log_warning("nvenc_hook", "Failed to find nvEncodeAPI64.dll");
|
||||
return;
|
||||
}
|
||||
bool success = detour::trampoline_try(
|
||||
(NvEncodeAPICreateInstance_Type)libutils::try_proc(nvenc, "NvEncodeAPICreateInstance"),
|
||||
NvEncodeAPICreateInstance_hook,
|
||||
&NvEncodeAPICreateInstance_orig
|
||||
);
|
||||
if (success) {
|
||||
log_misc("nvenc_hook", "Created hook for NvEncodeAPICreateInstance");
|
||||
} else {
|
||||
log_warning("nvenc_hook", "Failed to hook NvEncodeAPICreateInstance");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
namespace nvenc_hook {
|
||||
void initialize() {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
5
hooks/graphics/nvenc_hook.h
Normal file
5
hooks/graphics/nvenc_hook.h
Normal file
@@ -0,0 +1,5 @@
|
||||
#pragma once
|
||||
|
||||
namespace nvenc_hook {
|
||||
void initialize();
|
||||
}
|
||||
112
hooks/input/dinput8/fake_backend.cpp
Normal file
112
hooks/input/dinput8/fake_backend.cpp
Normal file
@@ -0,0 +1,112 @@
|
||||
#include <initguid.h>
|
||||
|
||||
#include "fake_backend.h"
|
||||
|
||||
#include <dinput.h>
|
||||
|
||||
#include "util/logging.h"
|
||||
|
||||
#include "fake_device.h"
|
||||
|
||||
HRESULT STDMETHODCALLTYPE FakeIDirectInput8W::QueryInterface(
|
||||
REFIID riid,
|
||||
void **ppvObj)
|
||||
{
|
||||
if (ppvObj == nullptr) {
|
||||
return E_POINTER;
|
||||
}
|
||||
|
||||
if (riid == IID_IDirectInput8A ||
|
||||
riid == IID_IDirectInput8W)
|
||||
{
|
||||
this->AddRef();
|
||||
*ppvObj = this;
|
||||
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
return E_NOINTERFACE;
|
||||
}
|
||||
ULONG STDMETHODCALLTYPE FakeIDirectInput8W::AddRef() {
|
||||
return ++this->ref_count;
|
||||
}
|
||||
ULONG STDMETHODCALLTYPE FakeIDirectInput8W::Release() {
|
||||
ULONG refs = --this->ref_count;
|
||||
|
||||
if (refs == 0) {
|
||||
delete this;
|
||||
}
|
||||
|
||||
return refs;
|
||||
}
|
||||
|
||||
HRESULT STDMETHODCALLTYPE FakeIDirectInput8W::CreateDevice(
|
||||
REFGUID rguid,
|
||||
LPDIRECTINPUTDEVICE8W *lplpDirectInputDevice,
|
||||
LPUNKNOWN pUnkOuter)
|
||||
{
|
||||
log_misc("input::dinput8", "IDirectInput8::CreateDevice hook hit");
|
||||
|
||||
if (lplpDirectInputDevice == nullptr) {
|
||||
return DIERR_INVALIDPARAM;
|
||||
}
|
||||
|
||||
if (rguid == GUID_SysKeyboard ||
|
||||
rguid == GUID_SysMouse)
|
||||
{
|
||||
log_misc("input::dinput8", "returning stub device");
|
||||
|
||||
*lplpDirectInputDevice = new FakeIDirectInputDevice8W();
|
||||
|
||||
return DI_OK;
|
||||
}
|
||||
|
||||
return DIERR_NOINTERFACE;
|
||||
}
|
||||
HRESULT STDMETHODCALLTYPE FakeIDirectInput8W::EnumDevices(
|
||||
DWORD dwDevType,
|
||||
LPDIENUMDEVICESCALLBACKW lpCallback,
|
||||
LPVOID pvRef,
|
||||
DWORD dwFlags)
|
||||
{
|
||||
log_misc("input::dinput8", "IDirectInput8::EnumDevices hook hit");
|
||||
|
||||
return DI_OK;
|
||||
}
|
||||
HRESULT STDMETHODCALLTYPE FakeIDirectInput8W::GetDeviceStatus(REFGUID rguidInstance) {
|
||||
return DIERR_GENERIC;
|
||||
}
|
||||
HRESULT STDMETHODCALLTYPE FakeIDirectInput8W::RunControlPanel(HWND hwndOwner, DWORD dwFlags) {
|
||||
return DIERR_GENERIC;
|
||||
}
|
||||
HRESULT STDMETHODCALLTYPE FakeIDirectInput8W::Initialize(HINSTANCE hinst, DWORD dwVersion) {
|
||||
log_misc("input::dinput8", "IDirectInput8::Initialize({}, 0x{:x})",
|
||||
fmt::ptr(hinst),
|
||||
dwVersion);
|
||||
|
||||
return DI_OK;
|
||||
}
|
||||
HRESULT STDMETHODCALLTYPE FakeIDirectInput8W::FindDevice(
|
||||
REFGUID rguid,
|
||||
LPCWSTR pszName,
|
||||
LPGUID pguidInstance)
|
||||
{
|
||||
return DIERR_GENERIC;
|
||||
}
|
||||
HRESULT STDMETHODCALLTYPE FakeIDirectInput8W::EnumDevicesBySemantics(
|
||||
LPCWSTR ptszUserName,
|
||||
LPDIACTIONFORMATW lpdiActionFormat,
|
||||
LPDIENUMDEVICESBYSEMANTICSCBW lpCallback,
|
||||
LPVOID pvRef,
|
||||
DWORD dwFlags)
|
||||
{
|
||||
return DIERR_GENERIC;
|
||||
}
|
||||
HRESULT STDMETHODCALLTYPE FakeIDirectInput8W::ConfigureDevices(
|
||||
LPDICONFIGUREDEVICESCALLBACK lpdiCallback,
|
||||
LPDICONFIGUREDEVICESPARAMSW lpdiCDParams,
|
||||
DWORD dwFlags,
|
||||
LPVOID pvRefData)
|
||||
{
|
||||
return DIERR_GENERIC;
|
||||
}
|
||||
35
hooks/input/dinput8/fake_backend.h
Normal file
35
hooks/input/dinput8/fake_backend.h
Normal file
@@ -0,0 +1,35 @@
|
||||
#pragma once
|
||||
|
||||
#include <atomic>
|
||||
|
||||
#include <dinput.h>
|
||||
|
||||
struct FakeIDirectInput8W : IDirectInput8W {
|
||||
explicit FakeIDirectInput8W() {
|
||||
}
|
||||
|
||||
FakeIDirectInput8W(const FakeIDirectInput8W &) = delete;
|
||||
FakeIDirectInput8W &operator=(const FakeIDirectInput8W &) = delete;
|
||||
|
||||
virtual ~FakeIDirectInput8W() = 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 IDirectInput8W
|
||||
virtual HRESULT STDMETHODCALLTYPE CreateDevice(REFGUID rguid, LPDIRECTINPUTDEVICE8W *lplpDirectInputDevice, LPUNKNOWN pUnkOuter) override;
|
||||
virtual HRESULT STDMETHODCALLTYPE EnumDevices(DWORD dwDevType, LPDIENUMDEVICESCALLBACKW lpCallback, LPVOID pvRef, DWORD dwFlags) override;
|
||||
virtual HRESULT STDMETHODCALLTYPE GetDeviceStatus(REFGUID rguidInstance) override;
|
||||
virtual HRESULT STDMETHODCALLTYPE RunControlPanel(HWND hwndOwner, DWORD dwFlags) override;
|
||||
virtual HRESULT STDMETHODCALLTYPE Initialize(HINSTANCE hinst, DWORD dwVersion) override;
|
||||
virtual HRESULT STDMETHODCALLTYPE FindDevice(REFGUID rguid, LPCWSTR pszName, LPGUID pguidInstance) override;
|
||||
virtual HRESULT STDMETHODCALLTYPE EnumDevicesBySemantics(LPCWSTR ptszUserName, LPDIACTIONFORMATW lpdiActionFormat, LPDIENUMDEVICESBYSEMANTICSCBW lpCallback, LPVOID pvRef, DWORD dwFlags) override;
|
||||
virtual HRESULT STDMETHODCALLTYPE ConfigureDevices(LPDICONFIGUREDEVICESCALLBACK lpdiCallback, LPDICONFIGUREDEVICESPARAMSW lpdiCDParams, DWORD dwFlags, LPVOID pvRefData) override;
|
||||
#pragma endregion
|
||||
|
||||
private:
|
||||
std::atomic<ULONG> ref_count = 1;
|
||||
};
|
||||
207
hooks/input/dinput8/fake_device.cpp
Normal file
207
hooks/input/dinput8/fake_device.cpp
Normal file
@@ -0,0 +1,207 @@
|
||||
#include <initguid.h>
|
||||
|
||||
#include "fake_device.h"
|
||||
|
||||
HRESULT STDMETHODCALLTYPE FakeIDirectInputDevice8W::QueryInterface(
|
||||
REFIID riid,
|
||||
void **ppvObj)
|
||||
{
|
||||
if (ppvObj == nullptr) {
|
||||
return E_POINTER;
|
||||
}
|
||||
|
||||
if (riid == IID_IDirectInputDeviceA ||
|
||||
riid == IID_IDirectInputDeviceW ||
|
||||
riid == IID_IDirectInputDevice2A ||
|
||||
riid == IID_IDirectInputDevice2W ||
|
||||
riid == IID_IDirectInputDevice7A ||
|
||||
riid == IID_IDirectInputDevice7W ||
|
||||
riid == IID_IDirectInputDevice8A ||
|
||||
riid == IID_IDirectInputDevice8W)
|
||||
{
|
||||
this->AddRef();
|
||||
*ppvObj = this;
|
||||
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
return E_NOINTERFACE;
|
||||
}
|
||||
ULONG STDMETHODCALLTYPE FakeIDirectInputDevice8W::AddRef() {
|
||||
return ++this->ref_count;
|
||||
}
|
||||
ULONG STDMETHODCALLTYPE FakeIDirectInputDevice8W::Release() {
|
||||
ULONG refs = --this->ref_count;
|
||||
|
||||
if (refs == 0) {
|
||||
delete this;
|
||||
}
|
||||
|
||||
return refs;
|
||||
}
|
||||
|
||||
HRESULT STDMETHODCALLTYPE FakeIDirectInputDevice8W::GetCapabilities(LPDIDEVCAPS lpDIDevCaps) {
|
||||
return DIERR_INVALIDPARAM;
|
||||
}
|
||||
HRESULT STDMETHODCALLTYPE FakeIDirectInputDevice8W::EnumObjects(
|
||||
LPDIENUMDEVICEOBJECTSCALLBACKW lpCallback,
|
||||
LPVOID pvRef,
|
||||
DWORD dwFlags)
|
||||
{
|
||||
return DIERR_INVALIDPARAM;
|
||||
}
|
||||
HRESULT STDMETHODCALLTYPE FakeIDirectInputDevice8W::GetProperty(
|
||||
REFGUID rguidProp,
|
||||
LPDIPROPHEADER pdiph)
|
||||
{
|
||||
return DIERR_INVALIDPARAM;
|
||||
}
|
||||
HRESULT STDMETHODCALLTYPE FakeIDirectInputDevice8W::SetProperty(
|
||||
REFGUID rguidProp,
|
||||
LPCDIPROPHEADER pdiph)
|
||||
{
|
||||
return DI_OK;
|
||||
}
|
||||
HRESULT STDMETHODCALLTYPE FakeIDirectInputDevice8W::Acquire() {
|
||||
return DIERR_INVALIDPARAM;
|
||||
}
|
||||
HRESULT STDMETHODCALLTYPE FakeIDirectInputDevice8W::Unacquire() {
|
||||
return DIERR_INVALIDPARAM;
|
||||
}
|
||||
HRESULT STDMETHODCALLTYPE FakeIDirectInputDevice8W::GetDeviceState(
|
||||
DWORD cbData,
|
||||
LPVOID lpvData)
|
||||
{
|
||||
return DIERR_INVALIDPARAM;
|
||||
}
|
||||
HRESULT STDMETHODCALLTYPE FakeIDirectInputDevice8W::GetDeviceData(
|
||||
DWORD cbObjectData,
|
||||
LPDIDEVICEOBJECTDATA rgdod,
|
||||
LPDWORD pdwInOut,
|
||||
DWORD dwFlags)
|
||||
{
|
||||
return DIERR_INVALIDPARAM;
|
||||
}
|
||||
HRESULT STDMETHODCALLTYPE FakeIDirectInputDevice8W::SetDataFormat(LPCDIDATAFORMAT lpdf) {
|
||||
return DI_OK;
|
||||
}
|
||||
HRESULT STDMETHODCALLTYPE FakeIDirectInputDevice8W::SetEventNotification(HANDLE hEvent) {
|
||||
return DIERR_INVALIDPARAM;
|
||||
}
|
||||
HRESULT STDMETHODCALLTYPE FakeIDirectInputDevice8W::SetCooperativeLevel(
|
||||
HWND hWnd,
|
||||
DWORD dwFlags)
|
||||
{
|
||||
return DI_OK;
|
||||
}
|
||||
HRESULT STDMETHODCALLTYPE FakeIDirectInputDevice8W::GetObjectInfo(
|
||||
LPDIDEVICEOBJECTINSTANCEW pdidoi,
|
||||
DWORD dwObj,
|
||||
DWORD dwHow)
|
||||
{
|
||||
return DIERR_INVALIDPARAM;
|
||||
}
|
||||
HRESULT STDMETHODCALLTYPE FakeIDirectInputDevice8W::GetDeviceInfo(
|
||||
LPDIDEVICEINSTANCEW pdidi)
|
||||
{
|
||||
return DIERR_INVALIDPARAM;
|
||||
}
|
||||
HRESULT STDMETHODCALLTYPE FakeIDirectInputDevice8W::RunControlPanel(
|
||||
HWND hwndOwner,
|
||||
DWORD dwFlags)
|
||||
{
|
||||
return DIERR_INVALIDPARAM;
|
||||
}
|
||||
HRESULT STDMETHODCALLTYPE FakeIDirectInputDevice8W::Initialize(
|
||||
HINSTANCE hinst,
|
||||
DWORD dwVersion,
|
||||
REFGUID rguid)
|
||||
{
|
||||
return DIERR_INVALIDPARAM;
|
||||
}
|
||||
|
||||
HRESULT STDMETHODCALLTYPE FakeIDirectInputDevice8W::CreateEffect(
|
||||
REFGUID rguid,
|
||||
LPCDIEFFECT lpeff,
|
||||
LPDIRECTINPUTEFFECT *ppdeff,
|
||||
LPUNKNOWN punkOuter)
|
||||
{
|
||||
return DIERR_INVALIDPARAM;
|
||||
}
|
||||
HRESULT STDMETHODCALLTYPE FakeIDirectInputDevice8W::EnumEffects(
|
||||
LPDIENUMEFFECTSCALLBACKW lpCallback,
|
||||
LPVOID pvRef,
|
||||
DWORD dwEffType)
|
||||
{
|
||||
return DIERR_INVALIDPARAM;
|
||||
}
|
||||
HRESULT STDMETHODCALLTYPE FakeIDirectInputDevice8W::GetEffectInfo(
|
||||
LPDIEFFECTINFOW pdei,
|
||||
REFGUID rguid)
|
||||
{
|
||||
return DIERR_INVALIDPARAM;
|
||||
}
|
||||
HRESULT STDMETHODCALLTYPE FakeIDirectInputDevice8W::GetForceFeedbackState(LPDWORD pdwOut) {
|
||||
return DIERR_INVALIDPARAM;
|
||||
}
|
||||
HRESULT STDMETHODCALLTYPE FakeIDirectInputDevice8W::SendForceFeedbackCommand(DWORD dwFlags) {
|
||||
return DIERR_INVALIDPARAM;
|
||||
}
|
||||
HRESULT STDMETHODCALLTYPE FakeIDirectInputDevice8W::EnumCreatedEffectObjects(
|
||||
LPDIENUMCREATEDEFFECTOBJECTSCALLBACK lpCallback,
|
||||
LPVOID pvRef,
|
||||
DWORD fl)
|
||||
{
|
||||
return DIERR_INVALIDPARAM;
|
||||
}
|
||||
HRESULT STDMETHODCALLTYPE FakeIDirectInputDevice8W::Escape(LPDIEFFESCAPE pesc) {
|
||||
return DIERR_INVALIDPARAM;
|
||||
}
|
||||
HRESULT STDMETHODCALLTYPE FakeIDirectInputDevice8W::Poll() {
|
||||
return DIERR_INVALIDPARAM;
|
||||
}
|
||||
HRESULT STDMETHODCALLTYPE FakeIDirectInputDevice8W::SendDeviceData(
|
||||
DWORD cbObjectData,
|
||||
LPCDIDEVICEOBJECTDATA rgdod,
|
||||
LPDWORD pdwInOut,
|
||||
DWORD fl)
|
||||
{
|
||||
return DIERR_INVALIDPARAM;
|
||||
}
|
||||
|
||||
HRESULT STDMETHODCALLTYPE FakeIDirectInputDevice8W::EnumEffectsInFile(
|
||||
LPCWSTR lpszFileName,
|
||||
LPDIENUMEFFECTSINFILECALLBACK pec,
|
||||
LPVOID pvRef,
|
||||
DWORD dwFlags)
|
||||
{
|
||||
return DIERR_INVALIDPARAM;
|
||||
}
|
||||
HRESULT STDMETHODCALLTYPE FakeIDirectInputDevice8W::WriteEffectToFile(
|
||||
LPCWSTR lpszFileName,
|
||||
DWORD dwEntries,
|
||||
LPDIFILEEFFECT rgDiFileEft,
|
||||
DWORD dwFlags)
|
||||
{
|
||||
return DIERR_INVALIDPARAM;
|
||||
}
|
||||
|
||||
HRESULT STDMETHODCALLTYPE FakeIDirectInputDevice8W::BuildActionMap(
|
||||
LPDIACTIONFORMATW lpdiaf,
|
||||
LPCWSTR lpszUserName,
|
||||
DWORD dwFlags)
|
||||
{
|
||||
return DIERR_INVALIDPARAM;
|
||||
}
|
||||
HRESULT STDMETHODCALLTYPE FakeIDirectInputDevice8W::SetActionMap(
|
||||
LPDIACTIONFORMATW lpdiaf,
|
||||
LPCWSTR lpszUserName,
|
||||
DWORD dwFlags)
|
||||
{
|
||||
return DIERR_INVALIDPARAM;
|
||||
}
|
||||
HRESULT STDMETHODCALLTYPE FakeIDirectInputDevice8W::GetImageInfo(
|
||||
LPDIDEVICEIMAGEINFOHEADERW lpdiDevImageInfoHeader)
|
||||
{
|
||||
return DIERR_INVALIDPARAM;
|
||||
}
|
||||
65
hooks/input/dinput8/fake_device.h
Normal file
65
hooks/input/dinput8/fake_device.h
Normal file
@@ -0,0 +1,65 @@
|
||||
#pragma once
|
||||
|
||||
#include <atomic>
|
||||
|
||||
#include <dinput.h>
|
||||
|
||||
struct FakeIDirectInputDevice8W : IDirectInputDevice8W {
|
||||
explicit FakeIDirectInputDevice8W() {
|
||||
}
|
||||
|
||||
FakeIDirectInputDevice8W(const FakeIDirectInputDevice8W &) = delete;
|
||||
FakeIDirectInputDevice8W &operator=(const FakeIDirectInputDevice8W &) = delete;
|
||||
|
||||
virtual ~FakeIDirectInputDevice8W() = 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 IDirectInputDeviceW
|
||||
virtual HRESULT STDMETHODCALLTYPE GetCapabilities(LPDIDEVCAPS lpDIDevCaps) override;
|
||||
virtual HRESULT STDMETHODCALLTYPE EnumObjects(LPDIENUMDEVICEOBJECTSCALLBACKW lpCallback, LPVOID pvRef, DWORD dwFlags) override;
|
||||
virtual HRESULT STDMETHODCALLTYPE GetProperty(REFGUID rguidProp, LPDIPROPHEADER pdiph) override;
|
||||
virtual HRESULT STDMETHODCALLTYPE SetProperty(REFGUID rguidProp, LPCDIPROPHEADER pdiph) override;
|
||||
virtual HRESULT STDMETHODCALLTYPE Acquire() override;
|
||||
virtual HRESULT STDMETHODCALLTYPE Unacquire() override;
|
||||
virtual HRESULT STDMETHODCALLTYPE GetDeviceState(DWORD cbData, LPVOID lpvData) override;
|
||||
virtual HRESULT STDMETHODCALLTYPE GetDeviceData(DWORD cbObjectData, LPDIDEVICEOBJECTDATA rgdod, LPDWORD pdwInOut, DWORD dwFlags) override;
|
||||
virtual HRESULT STDMETHODCALLTYPE SetDataFormat(LPCDIDATAFORMAT lpdf) override;
|
||||
virtual HRESULT STDMETHODCALLTYPE SetEventNotification(HANDLE hEvent) override;
|
||||
virtual HRESULT STDMETHODCALLTYPE SetCooperativeLevel(HWND hWnd, DWORD dwFlags) override;
|
||||
virtual HRESULT STDMETHODCALLTYPE GetObjectInfo(LPDIDEVICEOBJECTINSTANCEW pdidoi, DWORD dwObj, DWORD dwHow) override;
|
||||
virtual HRESULT STDMETHODCALLTYPE GetDeviceInfo(LPDIDEVICEINSTANCEW pdidi) override;
|
||||
virtual HRESULT STDMETHODCALLTYPE RunControlPanel(HWND hwndOwner, DWORD dwFlags) override;
|
||||
virtual HRESULT STDMETHODCALLTYPE Initialize(HINSTANCE hinst, DWORD dwVersion, REFGUID rguid) override;
|
||||
#pragma endregion
|
||||
|
||||
#pragma region IDirectInputDevice2W
|
||||
virtual HRESULT STDMETHODCALLTYPE CreateEffect(REFGUID rguid, LPCDIEFFECT lpeff, LPDIRECTINPUTEFFECT *ppdeff, LPUNKNOWN punkOuter) override;
|
||||
virtual HRESULT STDMETHODCALLTYPE EnumEffects(LPDIENUMEFFECTSCALLBACKW lpCallback, LPVOID pvRef, DWORD dwEffType) override;
|
||||
virtual HRESULT STDMETHODCALLTYPE GetEffectInfo(LPDIEFFECTINFOW pdei, REFGUID rguid) override;
|
||||
virtual HRESULT STDMETHODCALLTYPE GetForceFeedbackState(LPDWORD pdwOut) override;
|
||||
virtual HRESULT STDMETHODCALLTYPE SendForceFeedbackCommand(DWORD dwFlags) override;
|
||||
virtual HRESULT STDMETHODCALLTYPE EnumCreatedEffectObjects(LPDIENUMCREATEDEFFECTOBJECTSCALLBACK lpCallback, LPVOID pvRef, DWORD fl) override;
|
||||
virtual HRESULT STDMETHODCALLTYPE Escape(LPDIEFFESCAPE pesc) override;
|
||||
virtual HRESULT STDMETHODCALLTYPE Poll() override;
|
||||
virtual HRESULT STDMETHODCALLTYPE SendDeviceData(DWORD cbObjectData, LPCDIDEVICEOBJECTDATA rgdod, LPDWORD pdwInOut, DWORD fl) override;
|
||||
#pragma endregion
|
||||
|
||||
#pragma region IDirectInputDevice7W
|
||||
virtual HRESULT STDMETHODCALLTYPE EnumEffectsInFile(LPCWSTR lpszFileName, LPDIENUMEFFECTSINFILECALLBACK pec, LPVOID pvRef, DWORD dwFlags) override;
|
||||
virtual HRESULT STDMETHODCALLTYPE WriteEffectToFile(LPCWSTR lpszFileName, DWORD dwEntries, LPDIFILEEFFECT rgDiFileEft, DWORD dwFlags) override;
|
||||
#pragma endregion
|
||||
|
||||
#pragma region IDirectInputDevice8W
|
||||
virtual HRESULT STDMETHODCALLTYPE BuildActionMap(LPDIACTIONFORMATW lpdiaf, LPCWSTR lpszUserName, DWORD dwFlags) override;
|
||||
virtual HRESULT STDMETHODCALLTYPE SetActionMap(LPDIACTIONFORMATW lpdiaf, LPCWSTR lpszUserName, DWORD dwFlags) override;
|
||||
virtual HRESULT STDMETHODCALLTYPE GetImageInfo(LPDIDEVICEIMAGEINFOHEADERW lpdiDevImageInfoHeader) override;
|
||||
#pragma endregion
|
||||
|
||||
private:
|
||||
std::atomic<ULONG> ref_count = 1;
|
||||
};
|
||||
51
hooks/input/dinput8/hook.cpp
Normal file
51
hooks/input/dinput8/hook.cpp
Normal file
@@ -0,0 +1,51 @@
|
||||
#include "hook.h"
|
||||
|
||||
#include <windows.h>
|
||||
#include <dinput.h>
|
||||
|
||||
#include "util/detour.h"
|
||||
#include "util/logging.h"
|
||||
|
||||
#include "fake_backend.h"
|
||||
|
||||
//static decltype(DirectInput8Create) *DirectInput8Create_orig = nullptr;
|
||||
|
||||
static HRESULT WINAPI DirectInput8Create_hook(
|
||||
HINSTANCE hinst,
|
||||
DWORD dwVersion,
|
||||
REFIID riidltf,
|
||||
LPVOID *ppvOut,
|
||||
LPUNKNOWN punkOuter)
|
||||
{
|
||||
log_misc("input::dinput8", "DirectInput8Create hook hit");
|
||||
|
||||
if (ppvOut == nullptr) {
|
||||
return E_POINTER;
|
||||
}
|
||||
|
||||
*ppvOut = new FakeIDirectInput8W();
|
||||
|
||||
return DI_OK;
|
||||
}
|
||||
|
||||
void hooks::input::dinput8::init(HMODULE module) {
|
||||
|
||||
/*
|
||||
* This is for the games using DirectInput for keyboard/gamepad controls themselves,
|
||||
* for things such as debug controls. We don't want that, neither do we want the game to
|
||||
* interfere with our RawInput stuff.
|
||||
*/
|
||||
|
||||
log_info("input::dinput8", "attaching...");
|
||||
|
||||
// patch IAT
|
||||
detour::iat_try("DirectInput8Create", DirectInput8Create_hook, module, "dinput8.dll");
|
||||
|
||||
/*
|
||||
if (DirectInput8Create_orig == nullptr) {
|
||||
DirectInput8Create_orig = orig;
|
||||
}
|
||||
*/
|
||||
|
||||
log_info("input::dinput8", "attached");
|
||||
}
|
||||
7
hooks/input/dinput8/hook.h
Normal file
7
hooks/input/dinput8/hook.h
Normal file
@@ -0,0 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
#include <windows.h>
|
||||
|
||||
namespace hooks::input::dinput8 {
|
||||
void init(HMODULE module = nullptr);
|
||||
}
|
||||
130
hooks/lang.cpp
Normal file
130
hooks/lang.cpp
Normal file
@@ -0,0 +1,130 @@
|
||||
#include "lang.h"
|
||||
|
||||
#define WIN32_NO_STATUS
|
||||
#include <windows.h>
|
||||
#undef WIN32_NO_STATUS
|
||||
|
||||
#include <winternl.h>
|
||||
#include <ntstatus.h>
|
||||
|
||||
#include "avs/game.h"
|
||||
#include "util/detour.h"
|
||||
#include "util/logging.h"
|
||||
#include "util/utils.h"
|
||||
|
||||
// ANSI/OEM Japanese; Japanese (Shift-JIS)
|
||||
constexpr UINT CODEPAGE_SHIFT_JIS = 932;
|
||||
|
||||
static decltype(GetACP) *GetACP_orig = nullptr;
|
||||
static decltype(GetOEMCP) *GetOEMCP_orig = nullptr;
|
||||
static decltype(MultiByteToWideChar) *MultiByteToWideChar_orig = nullptr;
|
||||
|
||||
static NTSTATUS NTAPI RtlMultiByteToUnicodeN_hook(
|
||||
PWCH UnicodeString,
|
||||
ULONG MaxBytesInUnicodeString,
|
||||
PULONG BytesInUnicodeString,
|
||||
const CHAR *MultiByteString,
|
||||
ULONG BytesInMultiByteString)
|
||||
{
|
||||
// try to convert
|
||||
auto wc_num = MultiByteToWideChar(
|
||||
CODEPAGE_SHIFT_JIS,
|
||||
0,
|
||||
MultiByteString,
|
||||
static_cast<int>(BytesInMultiByteString),
|
||||
UnicodeString,
|
||||
static_cast<int>(MaxBytesInUnicodeString)
|
||||
);
|
||||
|
||||
// error handling
|
||||
if (!wc_num) {
|
||||
auto error = GetLastError();
|
||||
|
||||
switch (error) {
|
||||
case ERROR_INSUFFICIENT_BUFFER:
|
||||
return STATUS_BUFFER_TOO_SMALL;
|
||||
|
||||
case ERROR_INVALID_PARAMETER:
|
||||
case ERROR_INVALID_FLAGS:
|
||||
return STATUS_INVALID_PARAMETER;
|
||||
|
||||
case ERROR_NO_UNICODE_TRANSLATION:
|
||||
return STATUS_UNMAPPABLE_CHARACTER;
|
||||
|
||||
default:
|
||||
return STATUS_UNSUCCESSFUL;
|
||||
}
|
||||
}
|
||||
|
||||
// set byte count
|
||||
if (BytesInUnicodeString) {
|
||||
*BytesInUnicodeString = 2 * static_cast<UINT>(wc_num);
|
||||
}
|
||||
|
||||
// return success
|
||||
return STATUS_SUCCESS;
|
||||
}
|
||||
|
||||
static UINT WINAPI GetACP_hook() {
|
||||
return CODEPAGE_SHIFT_JIS;
|
||||
}
|
||||
|
||||
static UINT WINAPI GetOEMCP_hook() {
|
||||
return CODEPAGE_SHIFT_JIS;
|
||||
}
|
||||
|
||||
static int WINAPI MultiByteToWideChar_hook(
|
||||
UINT CodePage,
|
||||
DWORD dwFlags,
|
||||
LPCCH lpMultiByteStr,
|
||||
int cbMultiByte,
|
||||
LPWSTR lpWideCharStr,
|
||||
int cchWideChar)
|
||||
{
|
||||
switch (CodePage) {
|
||||
case CP_ACP:
|
||||
case CP_THREAD_ACP:
|
||||
|
||||
// this fixes pop'n music's mojibake issue with the system locale not set to Japanese
|
||||
SetThreadLocale(MAKELANGID(LANG_JAPANESE, SUBLANG_JAPANESE_JAPAN));
|
||||
|
||||
CodePage = CODEPAGE_SHIFT_JIS;
|
||||
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return MultiByteToWideChar_orig(
|
||||
CodePage,
|
||||
dwFlags,
|
||||
lpMultiByteStr,
|
||||
cbMultiByte,
|
||||
lpWideCharStr,
|
||||
cchWideChar);
|
||||
}
|
||||
|
||||
void hooks::lang::early_init() {
|
||||
log_info("hooks::lang", "early initialization");
|
||||
|
||||
// hooking these two functions fixes the jubeat mojibake
|
||||
detour::trampoline_try("kernel32.dll", "GetACP", GetACP_hook, &GetACP_orig);
|
||||
detour::trampoline_try("kernel32.dll", "GetOEMCP", GetOEMCP_hook, &GetOEMCP_orig);
|
||||
}
|
||||
|
||||
void hooks::lang::init() {
|
||||
log_info("hooks::lang", "initializing");
|
||||
|
||||
detour::iat_try("RtlMultiByteToUnicodeN", RtlMultiByteToUnicodeN_hook, nullptr, "ntdll.dll");
|
||||
|
||||
MultiByteToWideChar_orig = detour::iat_try(
|
||||
"MultiByteToWideChar",
|
||||
MultiByteToWideChar_hook,
|
||||
nullptr,
|
||||
"kernel32.dll");
|
||||
}
|
||||
|
||||
bool hooks::lang::is_native_shiftjis() {
|
||||
return GetACP() == CODEPAGE_SHIFT_JIS;
|
||||
}
|
||||
7
hooks/lang.h
Normal file
7
hooks/lang.h
Normal file
@@ -0,0 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
namespace hooks::lang {
|
||||
void early_init();
|
||||
void init();
|
||||
bool is_native_shiftjis();
|
||||
}
|
||||
125
hooks/libraryhook.cpp
Normal file
125
hooks/libraryhook.cpp
Normal file
@@ -0,0 +1,125 @@
|
||||
#include "libraryhook.h"
|
||||
|
||||
#include "external/robin_hood.h"
|
||||
#include "util/detour.h"
|
||||
#include "util/logging.h"
|
||||
#include "util/utils.h"
|
||||
|
||||
static bool LHOOK_ENABLED = false;
|
||||
static robin_hood::unordered_map<std::string, HMODULE> LIBRARIES_A;
|
||||
static robin_hood::unordered_map<std::wstring, HMODULE> LIBRARIES_W;
|
||||
static robin_hood::unordered_map<std::string, FARPROC> PROCS;
|
||||
|
||||
static decltype(LoadLibraryA) *LoadLibraryA_orig = nullptr;
|
||||
static decltype(LoadLibraryW) *LoadLibraryW_orig = nullptr;
|
||||
static decltype(GetModuleHandleA) *GetModuleHandleA_orig = nullptr;
|
||||
static decltype(GetModuleHandleW) *GetModuleHandleW_orig = nullptr;
|
||||
static decltype(GetProcAddress) *GetProcAddress_orig = nullptr;
|
||||
|
||||
static HMODULE WINAPI LoadLibraryA_hook(LPCTSTR lpFileName) {
|
||||
|
||||
// check hooks
|
||||
if (lpFileName) {
|
||||
auto module = LIBRARIES_A.find(lpFileName);
|
||||
if (module != LIBRARIES_A.end()) {
|
||||
return module->second;
|
||||
}
|
||||
}
|
||||
|
||||
// fallback
|
||||
return LoadLibraryA_orig(lpFileName);
|
||||
}
|
||||
|
||||
static HMODULE WINAPI LoadLibraryW_hook(LPCWSTR lpFileName) {
|
||||
|
||||
// check hooks
|
||||
if (lpFileName) {
|
||||
auto module = LIBRARIES_W.find(lpFileName);
|
||||
if (module != LIBRARIES_W.end()) {
|
||||
return module->second;
|
||||
}
|
||||
}
|
||||
|
||||
// fallback
|
||||
return LoadLibraryW_orig(lpFileName);
|
||||
}
|
||||
|
||||
static HMODULE WINAPI GetModuleHandleA_hook(LPCSTR lpModuleName) {
|
||||
|
||||
// check hooks
|
||||
if (lpModuleName) {
|
||||
auto module = LIBRARIES_A.find(lpModuleName);
|
||||
if (module != LIBRARIES_A.end()) {
|
||||
return module->second;
|
||||
}
|
||||
}
|
||||
|
||||
// fallback
|
||||
return GetModuleHandleA_orig(lpModuleName);
|
||||
}
|
||||
|
||||
static HMODULE WINAPI GetModuleHandleW_hook(LPCWSTR lpModuleName) {
|
||||
|
||||
// check hooks
|
||||
if (lpModuleName) {
|
||||
auto module = LIBRARIES_W.find(lpModuleName);
|
||||
if (module != LIBRARIES_W.end()) {
|
||||
return module->second;
|
||||
}
|
||||
}
|
||||
|
||||
// fallback
|
||||
return GetModuleHandleW_orig(lpModuleName);
|
||||
}
|
||||
|
||||
static FARPROC WINAPI GetProcAddress_hook(HMODULE hModule, LPCSTR lpProcName) {
|
||||
|
||||
// check for ordinal
|
||||
if (reinterpret_cast<uintptr_t>(lpProcName) <= UINT16_MAX) {
|
||||
|
||||
// fallback
|
||||
return GetProcAddress_orig(hModule, lpProcName);
|
||||
}
|
||||
|
||||
// check hooks
|
||||
if (lpProcName) {
|
||||
auto proc = PROCS.find(lpProcName);
|
||||
if (proc != PROCS.end()) {
|
||||
return proc->second;
|
||||
}
|
||||
}
|
||||
|
||||
// fallback
|
||||
return GetProcAddress_orig(hModule, lpProcName);
|
||||
}
|
||||
|
||||
void libraryhook_enable(HMODULE module) {
|
||||
log_info("libraryhook", "LibraryHook Attach");
|
||||
|
||||
if (LHOOK_ENABLED) {
|
||||
return;
|
||||
}
|
||||
|
||||
// detour
|
||||
detour::trampoline_try("kernel32.dll", "LoadLibraryA", LoadLibraryA_hook, &LoadLibraryA_orig);
|
||||
detour::trampoline_try("kernel32.dll", "LoadLibraryW", LoadLibraryW_hook, &LoadLibraryW_orig);
|
||||
detour::trampoline_try("kernel32.dll", "GetModuleHandleA", GetModuleHandleA_hook, &GetModuleHandleA_orig);
|
||||
detour::trampoline_try("kernel32.dll", "GetModuleHandleW", GetModuleHandleW_hook, &GetModuleHandleW_orig);
|
||||
detour::trampoline_try("kernel32.dll", "GetProcAddress", GetProcAddress_hook, &GetProcAddress_orig);
|
||||
|
||||
// set enabled
|
||||
LHOOK_ENABLED = true;
|
||||
}
|
||||
|
||||
void libraryhook_hook_library(std::string library_name, HMODULE library_address) {
|
||||
|
||||
// add library to list
|
||||
LIBRARIES_W.insert_or_assign(s2ws(library_name), library_address);
|
||||
LIBRARIES_A.insert_or_assign(std::move(library_name), library_address);
|
||||
}
|
||||
|
||||
void libraryhook_hook_proc(std::string proc_name, FARPROC proc_address) {
|
||||
|
||||
// add proc to list
|
||||
PROCS.insert_or_assign(std::move(proc_name), proc_address);
|
||||
}
|
||||
13
hooks/libraryhook.h
Normal file
13
hooks/libraryhook.h
Normal file
@@ -0,0 +1,13 @@
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <windows.h>
|
||||
|
||||
void libraryhook_enable(HMODULE module = nullptr);
|
||||
void libraryhook_hook_library(std::string library_name, HMODULE library_address);
|
||||
void libraryhook_hook_proc(std::string proc_name, FARPROC proc_address);
|
||||
|
||||
template<typename T>
|
||||
inline void libraryhook_hook_proc(std::string proc_name, T proc_address) {
|
||||
libraryhook_hook_proc(std::move(proc_name), reinterpret_cast<FARPROC>(proc_address));
|
||||
}
|
||||
223
hooks/networkhook.cpp
Normal file
223
hooks/networkhook.cpp
Normal file
@@ -0,0 +1,223 @@
|
||||
#include <winsock2.h>
|
||||
|
||||
#include <windows.h>
|
||||
#include <iphlpapi.h>
|
||||
#include <stdlib.h>
|
||||
#include <string>
|
||||
|
||||
#include "avs/core.h"
|
||||
#include "avs/ea3.h"
|
||||
#include "avs/game.h"
|
||||
#include "util/logging.h"
|
||||
#include "util/detour.h"
|
||||
#include "util/fileutils.h"
|
||||
#include "util/libutils.h"
|
||||
|
||||
// hooking related stuff
|
||||
static decltype(GetAdaptersInfo) *GetAdaptersInfo_orig = nullptr;
|
||||
static decltype(bind) *bind_orig = nullptr;
|
||||
|
||||
// settings
|
||||
std::string NETWORK_ADDRESS = "10.9.0.0";
|
||||
std::string NETWORK_SUBNET = "255.255.0.0";
|
||||
static bool GetAdaptersInfo_log = true;
|
||||
|
||||
// network structs
|
||||
static struct in_addr network;
|
||||
static struct in_addr prefix;
|
||||
static struct in_addr subnet;
|
||||
|
||||
static ULONG WINAPI GetAdaptersInfo_hook(PIP_ADAPTER_INFO pAdapterInfo, PULONG pOutBufLen) {
|
||||
|
||||
// call orig
|
||||
ULONG ret = GetAdaptersInfo_orig(pAdapterInfo, pOutBufLen);
|
||||
if (ret != ERROR_SUCCESS) {
|
||||
|
||||
// workaround for QMA not having enough buffer space
|
||||
if (pAdapterInfo != nullptr && avs::game::is_model({ "LMA", "MMA" })) {
|
||||
|
||||
// allocate the output buffer size
|
||||
auto pAdapterInfo2 = (PIP_ADAPTER_INFO) malloc(*pOutBufLen);
|
||||
|
||||
// call ourself with an appropriate buffer size
|
||||
ret = GetAdaptersInfo_hook(pAdapterInfo2, pOutBufLen);
|
||||
if (ret != ERROR_SUCCESS) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
// copy best interface
|
||||
memcpy(pAdapterInfo, pAdapterInfo2, sizeof(*pAdapterInfo));
|
||||
pAdapterInfo->Next = nullptr;
|
||||
|
||||
// free our allocated memory
|
||||
free(pAdapterInfo2);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
// set the best network adapter
|
||||
PIP_ADAPTER_INFO info = pAdapterInfo;
|
||||
while (info != nullptr) {
|
||||
|
||||
// set subnet
|
||||
struct in_addr info_subnet;
|
||||
info_subnet.s_addr = inet_addr(info->IpAddressList.IpMask.String);
|
||||
|
||||
// set prefix
|
||||
struct in_addr info_prefix;
|
||||
info_prefix.s_addr = inet_addr(info->IpAddressList.IpAddress.String) & info_subnet.s_addr;
|
||||
|
||||
// check base IP and subnet
|
||||
bool isCorrectBaseIp = prefix.s_addr == info_prefix.s_addr;
|
||||
bool isCorrectSubnetMask = subnet.s_addr == info_subnet.s_addr;
|
||||
|
||||
// check if requirements are met
|
||||
if (isCorrectBaseIp && isCorrectSubnetMask) {
|
||||
|
||||
// log adapter
|
||||
if (GetAdaptersInfo_log)
|
||||
log_info("network", "Using preferred network adapter: {}, {}",
|
||||
info->AdapterName,
|
||||
info->Description);
|
||||
|
||||
// set adapter information
|
||||
memcpy(pAdapterInfo, info, sizeof(*info));
|
||||
pAdapterInfo->Next = nullptr;
|
||||
|
||||
// we're done
|
||||
GetAdaptersInfo_log = false;
|
||||
return ret;
|
||||
}
|
||||
|
||||
// iterate
|
||||
info = info->Next;
|
||||
}
|
||||
|
||||
// get IP forward table
|
||||
PMIB_IPFORWARDTABLE pIpForwardTable = (MIB_IPFORWARDTABLE *) malloc(sizeof(MIB_IPFORWARDTABLE));
|
||||
DWORD dwSize = 0;
|
||||
if (GetIpForwardTable(pIpForwardTable, &dwSize, 1) == ERROR_INSUFFICIENT_BUFFER) {
|
||||
free(pIpForwardTable);
|
||||
pIpForwardTable = (MIB_IPFORWARDTABLE *) malloc(dwSize);
|
||||
}
|
||||
if (GetIpForwardTable(pIpForwardTable, &dwSize, 1) != NO_ERROR || pIpForwardTable->dwNumEntries == 0)
|
||||
return ret;
|
||||
|
||||
// determine best interface
|
||||
DWORD best = pIpForwardTable->table[0].dwForwardIfIndex;
|
||||
free(pIpForwardTable);
|
||||
|
||||
// find fallback adapter
|
||||
info = pAdapterInfo;
|
||||
while (info != nullptr) {
|
||||
|
||||
// check if this the adapter we search for
|
||||
if (info->Index == best) {
|
||||
|
||||
// log information
|
||||
if (GetAdaptersInfo_log)
|
||||
log_info("network", "Using fallback network adapter: {}, {}",
|
||||
info->AdapterName,
|
||||
info->Description);
|
||||
|
||||
|
||||
// set adapter information
|
||||
memcpy(pAdapterInfo, info, sizeof(*info));
|
||||
pAdapterInfo->Next = nullptr;
|
||||
|
||||
// exit the loop
|
||||
break;
|
||||
}
|
||||
|
||||
// iterate
|
||||
info = info->Next;
|
||||
}
|
||||
|
||||
// return original value
|
||||
GetAdaptersInfo_log = false;
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int WINAPI bind_hook(SOCKET s, const struct sockaddr *name, int namelen) {
|
||||
|
||||
#pragma clang diagnostic push
|
||||
#pragma ide diagnostic ignored "OCDFAInspection"
|
||||
|
||||
// cast to sockaddr_in
|
||||
struct sockaddr_in *in_name = (struct sockaddr_in *) name;
|
||||
|
||||
#pragma clang diagnostic pop
|
||||
|
||||
// override bind to allow all hosts
|
||||
in_name->sin_addr.s_addr = inet_addr("0.0.0.0");
|
||||
|
||||
// call original
|
||||
int ret = bind_orig(s, name, namelen);
|
||||
if (ret != 0) {
|
||||
log_warning("network", "bind failed: {}", WSAGetLastError());
|
||||
}
|
||||
|
||||
// return result
|
||||
return ret;
|
||||
}
|
||||
|
||||
void networkhook_init() {
|
||||
|
||||
// announce init
|
||||
log_info("network", "SpiceTools Network");
|
||||
|
||||
// set some same defaults
|
||||
network.s_addr = inet_addr(NETWORK_ADDRESS.c_str());
|
||||
subnet.s_addr = inet_addr(NETWORK_SUBNET.c_str());
|
||||
prefix.s_addr = network.s_addr & subnet.s_addr;
|
||||
|
||||
// inet_ntoa(...) reuses the same char array so the results must be copied
|
||||
char s_network[17]{}, s_subnet[17]{}, s_prefix[17]{};
|
||||
strncpy(s_network, inet_ntoa(network), 16);
|
||||
strncpy(s_subnet, inet_ntoa(subnet), 16);
|
||||
strncpy(s_prefix, inet_ntoa(prefix), 16);
|
||||
|
||||
// log preferences
|
||||
log_info("network", "Network preferences: {}", s_network, s_subnet, s_prefix);
|
||||
|
||||
// GetAdaptersInfo hook
|
||||
auto orig_addr = detour::iat_try(
|
||||
"GetAdaptersInfo", GetAdaptersInfo_hook, nullptr);
|
||||
if (!orig_addr) {
|
||||
log_warning("network", "Could not hook GetAdaptersInfo");
|
||||
} else if (GetAdaptersInfo_orig == nullptr) {
|
||||
GetAdaptersInfo_orig = orig_addr;
|
||||
}
|
||||
|
||||
/*
|
||||
* Bind Hook
|
||||
*/
|
||||
bool bind_hook_enabled = true;
|
||||
|
||||
// disable hook for DDR A since the bind hook crashes there for some reason
|
||||
if (fileutils::file_exists(MODULE_PATH / "gamemdx.dll")) {
|
||||
bind_hook_enabled = false;
|
||||
}
|
||||
|
||||
// hook bind
|
||||
if (bind_hook_enabled) {
|
||||
|
||||
// hook by name
|
||||
auto new_bind_orig = detour::iat_try("bind", bind_hook, nullptr);
|
||||
if (bind_orig == nullptr) {
|
||||
bind_orig = new_bind_orig;
|
||||
}
|
||||
|
||||
// hook ESS by ordinal
|
||||
HMODULE ess = libutils::try_module("ess.dll");
|
||||
if (ess) {
|
||||
auto new_bind_orig2 = detour::iat_try_ordinal("WS2_32.dll", 2, bind_hook, ess);
|
||||
|
||||
// try to get some valid pointer
|
||||
if (bind_orig == nullptr && new_bind_orig2 != nullptr) {
|
||||
bind_orig = new_bind_orig2;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
8
hooks/networkhook.h
Normal file
8
hooks/networkhook.h
Normal file
@@ -0,0 +1,8 @@
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
|
||||
extern std::string NETWORK_ADDRESS;
|
||||
extern std::string NETWORK_SUBNET;
|
||||
|
||||
void networkhook_init();
|
||||
49
hooks/powrprof.cpp
Normal file
49
hooks/powrprof.cpp
Normal file
@@ -0,0 +1,49 @@
|
||||
#include "powrprof.h"
|
||||
|
||||
#include <windows.h>
|
||||
#include <powrprof.h>
|
||||
|
||||
#include "util/detour.h"
|
||||
#include "util/logging.h"
|
||||
|
||||
/*
|
||||
* These hooks are required because Sound Voltex Exceed Gear sets `GUID_PROCESSOR_IDLE_DISABLE` to
|
||||
* minimize CPU idle downclocking. While this may be good for cab use, it causes unnecessary power
|
||||
* usage, battery drain on laptops, and requires a manual `powercfg` command to fix later on, which
|
||||
* is annoying.
|
||||
*/
|
||||
|
||||
//static decltype(PowerGetActiveScheme) *PowerGetActiveScheme_orig = nullptr;
|
||||
//static decltype(PowerSetActiveScheme) *PowerSetActiveScheme_orig = nullptr;
|
||||
//static decltype(PowerWriteACValueIndex) *PowerWriteACValueIndex_orig = nullptr;
|
||||
|
||||
static DWORD WINAPI PowerGetActiveScheme_hook(HKEY UserRootPowerKey, GUID **ActivePolicyGuid) {
|
||||
|
||||
// stubbed
|
||||
return ERROR_SUCCESS;
|
||||
}
|
||||
|
||||
static DWORD WINAPI PowerSetActiveScheme_hook(HKEY UserRootPowerKey, const GUID *SchemeGuid) {
|
||||
|
||||
// stubbed
|
||||
return ERROR_SUCCESS;
|
||||
}
|
||||
|
||||
static DWORD WINAPI PowerWriteACValueIndex_hook(
|
||||
HKEY RootPowerKey,
|
||||
const GUID *SchemeGuid,
|
||||
const GUID *SubGroupOfPowerSettingsGuid,
|
||||
const GUID *PowerSettingGuid,
|
||||
DWORD AcValueIndex)
|
||||
{
|
||||
// stubbed
|
||||
return ERROR_SUCCESS;
|
||||
}
|
||||
|
||||
void powrprof_hook_init(HMODULE module) {
|
||||
log_info("powrprof", "initializing");
|
||||
|
||||
detour::iat_try("PowerGetActiveScheme", &PowerGetActiveScheme_hook, module);
|
||||
detour::iat_try("PowerSetActiveScheme", &PowerSetActiveScheme_hook, module);
|
||||
detour::iat_try("PowerWriteACValueIndex", &PowerWriteACValueIndex_hook, module);
|
||||
}
|
||||
5
hooks/powrprof.h
Normal file
5
hooks/powrprof.h
Normal file
@@ -0,0 +1,5 @@
|
||||
#pragma once
|
||||
|
||||
#include <windows.h>
|
||||
|
||||
void powrprof_hook_init(HMODULE module);
|
||||
75
hooks/rom.cpp
Normal file
75
hooks/rom.cpp
Normal file
@@ -0,0 +1,75 @@
|
||||
#include "rom.h"
|
||||
#include "util/logging.h"
|
||||
#include "util/utils.h"
|
||||
#include "avs/game.h"
|
||||
#include "hooks/devicehook.h"
|
||||
|
||||
namespace hooks::rom {
|
||||
|
||||
static std::string MODEL;
|
||||
|
||||
class ROMFileHandle : public CustomHandle {
|
||||
private:
|
||||
int offset = 0;
|
||||
|
||||
public:
|
||||
|
||||
bool open(LPCWSTR lpFileName) override {
|
||||
if (wcsicmp(lpFileName, L"D:\\001rom.txt")
|
||||
&& wcsicmp(lpFileName, L"D:\\\\001rom.txt")) {
|
||||
return false;
|
||||
}
|
||||
log_info("romhook", "opened 001rom.txt");
|
||||
offset = 0;
|
||||
return true;
|
||||
}
|
||||
|
||||
int read(LPVOID lpBuffer, DWORD nNumberOfBytesToRead) override {
|
||||
int ret = 0;
|
||||
for (int i = 0; i < (signed) MIN(nNumberOfBytesToRead, MODEL.length() - offset); i++) {
|
||||
*((char*) lpBuffer + i) = MODEL[i + offset];
|
||||
ret++;
|
||||
}
|
||||
offset += ret;
|
||||
if (offset == (int) MODEL.length()) {
|
||||
log_info("romhook", "read complete: {}", MODEL);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
int write(LPCVOID lpBuffer, DWORD nNumberOfBytesToWrite) override {
|
||||
return 0;
|
||||
}
|
||||
|
||||
int device_io(DWORD dwIoControlCode, LPVOID lpInBuffer, DWORD nInBufferSize,
|
||||
LPVOID lpOutBuffer, DWORD nOutBufferSize) override {
|
||||
return -1;
|
||||
}
|
||||
|
||||
bool close() override {
|
||||
return true;
|
||||
}
|
||||
|
||||
void file_info(LPBY_HANDLE_FILE_INFORMATION lpFileInformation) override {
|
||||
*lpFileInformation = BY_HANDLE_FILE_INFORMATION {};
|
||||
lpFileInformation->nFileSizeLow = MODEL.length();
|
||||
}
|
||||
};
|
||||
|
||||
void init() {
|
||||
log_info("romhook", "init");
|
||||
|
||||
// populate model
|
||||
if (MODEL.empty()) {
|
||||
MODEL = avs::game::MODEL;
|
||||
}
|
||||
|
||||
// add device hook
|
||||
devicehook_init();
|
||||
devicehook_add(new ROMFileHandle());
|
||||
}
|
||||
|
||||
void set_model(const std::string &model) {
|
||||
MODEL = model;
|
||||
}
|
||||
}
|
||||
9
hooks/rom.h
Normal file
9
hooks/rom.h
Normal file
@@ -0,0 +1,9 @@
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace hooks::rom {
|
||||
|
||||
void init();
|
||||
void set_model(const std::string &model);
|
||||
}
|
||||
361
hooks/setupapihook.cpp
Normal file
361
hooks/setupapihook.cpp
Normal file
@@ -0,0 +1,361 @@
|
||||
#include "setupapihook.h"
|
||||
|
||||
#include <windows.h>
|
||||
#include <setupapi.h>
|
||||
|
||||
#include "util/detour.h"
|
||||
#include "util/utils.h"
|
||||
|
||||
// constants
|
||||
static const GUID GUID_ZERO = { 0x0, 0x0, 0x0, { 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0 } };
|
||||
|
||||
// state
|
||||
static std::vector<SETUPAPI_SETTINGS> SETUPAPI_SETTINGS_LIST;
|
||||
|
||||
// original function pointers
|
||||
static decltype(SetupDiDestroyDeviceInfoList) *SetupDiDestroyDeviceInfoList_real = nullptr;
|
||||
static decltype(SetupDiEnumDeviceInfo) *SetupDiEnumDeviceInfo_real = nullptr;
|
||||
static decltype(SetupDiEnumDeviceInterfaces) *SetupDiEnumDeviceInterfaces_real = nullptr;
|
||||
static decltype(SetupDiGetClassDevsA) *SetupDiGetClassDevsA_real = nullptr;
|
||||
static decltype(SetupDiGetClassDevsW) *SetupDiGetClassDevsW_real = nullptr;
|
||||
static decltype(SetupDiGetDeviceInfoListDetailA) *SetupDiGetDeviceInfoListDetailA_real = nullptr;
|
||||
static decltype(SetupDiGetDeviceInstanceIdA) *SetupDiGetDeviceInstanceIdA_real = nullptr;
|
||||
static decltype(SetupDiGetDeviceInterfaceDetailA) *SetupDiGetDeviceInterfaceDetailA_real = nullptr;
|
||||
static decltype(SetupDiGetDeviceInterfaceDetailW) *SetupDiGetDeviceInterfaceDetailW_real = nullptr;
|
||||
static decltype(SetupDiGetDeviceRegistryPropertyA) *SetupDiGetDeviceRegistryPropertyA_real = nullptr;
|
||||
|
||||
static HDEVINFO WINAPI SetupDiGetClassDevsA_hook(
|
||||
const GUID *ClassGuid,
|
||||
LPCSTR Enumerator,
|
||||
HWND hwndParent,
|
||||
DWORD Flags
|
||||
) {
|
||||
|
||||
// custom
|
||||
for (size_t i = 0; i < SETUPAPI_SETTINGS_LIST.size(); i++) {
|
||||
const SETUPAPI_SETTINGS *settings = &SETUPAPI_SETTINGS_LIST[i];
|
||||
const GUID *guid = ClassGuid ? ClassGuid : &GUID_ZERO;
|
||||
|
||||
if (!memcmp(guid, settings->class_guid, 16)) {
|
||||
SetLastError(ERROR_SUCCESS);
|
||||
return (HDEVINFO) &SETUPAPI_SETTINGS_LIST[i];
|
||||
}
|
||||
}
|
||||
|
||||
// fallback
|
||||
return SetupDiGetClassDevsA_real(ClassGuid, Enumerator, hwndParent, Flags);
|
||||
}
|
||||
|
||||
static HDEVINFO WINAPI SetupDiGetClassDevsW_hook(
|
||||
const GUID *ClassGuid,
|
||||
LPCWSTR Enumerator,
|
||||
HWND hwndParent,
|
||||
DWORD Flags
|
||||
) {
|
||||
|
||||
// custom
|
||||
for (size_t i = 0; i < SETUPAPI_SETTINGS_LIST.size(); i++) {
|
||||
const SETUPAPI_SETTINGS *settings = &SETUPAPI_SETTINGS_LIST[i];
|
||||
|
||||
if (ClassGuid && !memcmp(ClassGuid, settings->class_guid, 16)) {
|
||||
SetLastError(ERROR_SUCCESS);
|
||||
return (HDEVINFO) &SETUPAPI_SETTINGS_LIST[i];
|
||||
}
|
||||
}
|
||||
|
||||
// fallback
|
||||
return SetupDiGetClassDevsW_real(ClassGuid, Enumerator, hwndParent, Flags);
|
||||
}
|
||||
|
||||
static BOOL WINAPI SetupDiEnumDeviceInfo_hook(
|
||||
HDEVINFO DeviceInfoSet,
|
||||
DWORD MemberIndex,
|
||||
PSP_DEVINFO_DATA DeviceInfoData
|
||||
) {
|
||||
|
||||
// custom
|
||||
for (size_t i = 0; i < SETUPAPI_SETTINGS_LIST.size(); i++) {
|
||||
if (DeviceInfoSet == &SETUPAPI_SETTINGS_LIST[i]) {
|
||||
if (MemberIndex) {
|
||||
SetLastError(ERROR_NO_MORE_ITEMS);
|
||||
return false;
|
||||
} else {
|
||||
SetLastError(ERROR_SUCCESS);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// fallback
|
||||
return SetupDiEnumDeviceInfo_real(DeviceInfoSet, MemberIndex, DeviceInfoData);
|
||||
}
|
||||
|
||||
static BOOL WINAPI SetupDiEnumDeviceInterfaces_hook(
|
||||
HDEVINFO DeviceInfoSet,
|
||||
PSP_DEVINFO_DATA DeviceInfoData,
|
||||
const GUID *InterfaceClassGuid,
|
||||
DWORD MemberIndex,
|
||||
PSP_DEVICE_INTERFACE_DATA DeviceInterfaceData
|
||||
) {
|
||||
|
||||
// custom
|
||||
for (size_t i = 0; i < SETUPAPI_SETTINGS_LIST.size(); i++) {
|
||||
if (DeviceInfoSet == &SETUPAPI_SETTINGS_LIST[i]) {
|
||||
SetLastError(ERROR_SUCCESS);
|
||||
return MemberIndex == 0;
|
||||
}
|
||||
}
|
||||
|
||||
// fallback
|
||||
return SetupDiEnumDeviceInterfaces_real(DeviceInfoSet, DeviceInfoData, InterfaceClassGuid, MemberIndex,
|
||||
DeviceInterfaceData);
|
||||
}
|
||||
|
||||
static BOOL WINAPI SetupDiGetDeviceRegistryPropertyA_hook(
|
||||
HDEVINFO DeviceInfoSet,
|
||||
PSP_DEVINFO_DATA DeviceInfoData,
|
||||
DWORD Property,
|
||||
PDWORD PropertyRegDataType,
|
||||
PBYTE PropertyBuffer,
|
||||
DWORD PropertyBufferSize,
|
||||
PDWORD RequiredSize
|
||||
) {
|
||||
|
||||
// custom
|
||||
for (size_t i = 0; i < SETUPAPI_SETTINGS_LIST.size(); i++) {
|
||||
const SETUPAPI_SETTINGS *settings = &SETUPAPI_SETTINGS_LIST[i];
|
||||
|
||||
if (DeviceInfoSet == settings) {
|
||||
|
||||
// dummy data
|
||||
const void *property_data = nullptr;
|
||||
DWORD property_size = 0;
|
||||
static int property_index = 0;
|
||||
|
||||
// get property data
|
||||
switch (Property) {
|
||||
case SPDRP_DEVICEDESC:
|
||||
property_data = settings->property_devicedesc;
|
||||
property_size = (DWORD) (strlen(settings->property_devicedesc) + 1);
|
||||
break;
|
||||
case SPDRP_HARDWAREID:
|
||||
property_data = settings->property_hardwareid;
|
||||
property_size = (DWORD) (strlen(settings->property_hardwareid) + 1);
|
||||
break;
|
||||
case SPDRP_ADDRESS:
|
||||
/*
|
||||
* The property index thing is just because IIDX 25+ requires two different addresses to
|
||||
* be spoofed for cameras. Doing this in a clean way requires a complete hook rewrite.
|
||||
*/
|
||||
property_data = &settings->property_address[property_index];
|
||||
property_size = sizeof(DWORD);
|
||||
property_index = (property_index + 1) & 1;
|
||||
break;
|
||||
case SPDRP_DRIVER:
|
||||
// hides warnings from IIDX
|
||||
property_data = "";
|
||||
property_size = 0;
|
||||
break;
|
||||
default:
|
||||
log_warning("setupapi", "unknown property type: {}", Property);
|
||||
|
||||
SetLastError(ERROR_INVALID_DATA);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// check buffer size
|
||||
if (PropertyBufferSize < property_size) {
|
||||
if (RequiredSize) {
|
||||
*RequiredSize = property_size;
|
||||
}
|
||||
SetLastError(ERROR_INSUFFICIENT_BUFFER);
|
||||
return false;
|
||||
}
|
||||
|
||||
// write property
|
||||
if (PropertyRegDataType) {
|
||||
*PropertyRegDataType = 1;
|
||||
}
|
||||
memcpy(PropertyBuffer, property_data, property_size);
|
||||
SetLastError(ERROR_SUCCESS);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// fallback
|
||||
return SetupDiGetDeviceRegistryPropertyA_real(DeviceInfoSet, DeviceInfoData, Property, PropertyRegDataType,
|
||||
PropertyBuffer, PropertyBufferSize, RequiredSize);
|
||||
}
|
||||
|
||||
static BOOL WINAPI SetupDiGetDeviceInterfaceDetailA_hook(
|
||||
HDEVINFO DeviceInfoSet,
|
||||
PSP_DEVICE_INTERFACE_DATA DeviceInterfaceData,
|
||||
PSP_DEVICE_INTERFACE_DETAIL_DATA_A DeviceInterfaceDetailData,
|
||||
DWORD DeviceInterfaceDetailDataSize,
|
||||
PDWORD RequiredSize,
|
||||
PSP_DEVINFO_DATA DeviceInfoData
|
||||
) {
|
||||
|
||||
// custom
|
||||
for (size_t i = 0; i < SETUPAPI_SETTINGS_LIST.size(); i++) {
|
||||
const SETUPAPI_SETTINGS *settings = &SETUPAPI_SETTINGS_LIST[i];
|
||||
|
||||
if (DeviceInfoSet == settings) {
|
||||
|
||||
// get data length
|
||||
DWORD data_size = (DWORD) (strlen(settings->interface_detail) + 1);
|
||||
|
||||
// check size
|
||||
if (DeviceInterfaceDetailDataSize < data_size) {
|
||||
SetLastError(ERROR_INSUFFICIENT_BUFFER);
|
||||
*RequiredSize = sizeof(DeviceInterfaceDetailData->cbSize) + data_size;
|
||||
return false;
|
||||
}
|
||||
|
||||
// clear error
|
||||
SetLastError(ERROR_SUCCESS);
|
||||
|
||||
// copy data size + data
|
||||
DeviceInterfaceDetailData->cbSize = data_size;
|
||||
memcpy(DeviceInterfaceDetailData->DevicePath, settings->interface_detail, data_size);
|
||||
|
||||
// success
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// fallback
|
||||
return SetupDiGetDeviceInterfaceDetailA_real(DeviceInfoSet, DeviceInterfaceData, DeviceInterfaceDetailData,
|
||||
DeviceInterfaceDetailDataSize, RequiredSize, DeviceInfoData);
|
||||
}
|
||||
|
||||
static BOOL WINAPI SetupDiGetDeviceInterfaceDetailW_hook(
|
||||
HDEVINFO DeviceInfoSet,
|
||||
PSP_DEVICE_INTERFACE_DATA DeviceInterfaceData,
|
||||
PSP_DEVICE_INTERFACE_DETAIL_DATA_W DeviceInterfaceDetailData,
|
||||
DWORD DeviceInterfaceDetailDataSize,
|
||||
PDWORD RequiredSize,
|
||||
PSP_DEVINFO_DATA DeviceInfoData
|
||||
) {
|
||||
|
||||
// custom
|
||||
for (size_t i = 0; i < SETUPAPI_SETTINGS_LIST.size(); i++) {
|
||||
const SETUPAPI_SETTINGS *settings = &SETUPAPI_SETTINGS_LIST[i];
|
||||
|
||||
if (DeviceInfoSet == settings) {
|
||||
|
||||
// convert to wstring
|
||||
std::wstring ws = s2ws(std::string(settings->interface_detail));
|
||||
|
||||
// get data length
|
||||
DWORD data_size = (DWORD) (ws.length() * 2 + 2);
|
||||
DWORD struct_size = data_size + sizeof(DWORD);
|
||||
|
||||
// check size
|
||||
if (DeviceInterfaceDetailDataSize < struct_size) {
|
||||
SetLastError(ERROR_INSUFFICIENT_BUFFER);
|
||||
*RequiredSize = sizeof(DeviceInterfaceDetailData->cbSize) + struct_size;
|
||||
return false;
|
||||
}
|
||||
|
||||
// clear error
|
||||
SetLastError(ERROR_SUCCESS);
|
||||
|
||||
// copy data size + data
|
||||
DeviceInterfaceDetailData->cbSize = struct_size;
|
||||
memcpy(DeviceInterfaceDetailData->DevicePath, ws.c_str(), data_size);
|
||||
|
||||
// success
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// fallback
|
||||
return SetupDiGetDeviceInterfaceDetailW_real(DeviceInfoSet, DeviceInterfaceData, DeviceInterfaceDetailData,
|
||||
DeviceInterfaceDetailDataSize, RequiredSize, DeviceInfoData);
|
||||
}
|
||||
|
||||
static BOOL WINAPI SetupDiGetDeviceInstanceIdA_hook(HDEVINFO DeviceInfoSet, PSP_DEVINFO_DATA DeviceInfoData, PSTR DeviceInstanceId, DWORD DeviceInstanceIdSize, PDWORD RequiredSize) {
|
||||
log_misc("setupapi", "SetupDiGetDeviceInstanceIdA hook hit ({}, {}, {}, {}, {})", fmt::ptr(DeviceInfoSet), fmt::ptr(DeviceInfoData), fmt::ptr(DeviceInstanceId), DeviceInstanceIdSize, fmt::ptr(RequiredSize));
|
||||
|
||||
// custom
|
||||
for (size_t i = 0; i < SETUPAPI_SETTINGS_LIST.size(); i++) {
|
||||
const SETUPAPI_SETTINGS *settings = &SETUPAPI_SETTINGS_LIST[i];
|
||||
|
||||
if (DeviceInfoSet == settings) {
|
||||
log_misc("setupapi", "returning device instance: {}", settings->property_hardwareid);
|
||||
|
||||
strncpy(DeviceInstanceId, settings->property_hardwareid, DeviceInstanceIdSize);
|
||||
|
||||
if (RequiredSize) {
|
||||
*RequiredSize = 0;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return SetupDiGetDeviceInstanceIdA_real(DeviceInfoSet, DeviceInfoData, DeviceInstanceId, DeviceInstanceIdSize, RequiredSize);
|
||||
}
|
||||
|
||||
static BOOL WINAPI SetupDiDestroyDeviceInfoList_hook(HDEVINFO DeviceInfoSet) {
|
||||
|
||||
// custom
|
||||
for (size_t i = 0; i < SETUPAPI_SETTINGS_LIST.size(); i++) {
|
||||
if (DeviceInfoSet == &SETUPAPI_SETTINGS_LIST[i]) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// fallback
|
||||
return SetupDiDestroyDeviceInfoList_real(DeviceInfoSet);
|
||||
}
|
||||
|
||||
static BOOL WINAPI SetupDiGetDeviceInfoListDetailA_hook(HDEVINFO DeviceInfoSet,
|
||||
PSP_DEVINFO_LIST_DETAIL_DATA DeviceInfoSetDetailData) {
|
||||
// custom
|
||||
for (size_t i = 0; i < SETUPAPI_SETTINGS_LIST.size(); i++) {
|
||||
if (DeviceInfoSet == &SETUPAPI_SETTINGS_LIST[i]) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// fallback
|
||||
return SetupDiGetDeviceInfoListDetailA_real(DeviceInfoSet, DeviceInfoSetDetailData);
|
||||
}
|
||||
|
||||
void setupapihook_init(HINSTANCE module) {
|
||||
|
||||
// check for skip
|
||||
if (!detour::iat_find("SetupDiGetClassDevsA", module) && !detour::iat_find("SetupDiGetClassDevsW", module)) {
|
||||
log_info("setupapi", "no usage detected in module");
|
||||
return;
|
||||
}
|
||||
|
||||
// hook functions
|
||||
SetupDiDestroyDeviceInfoList_real = detour::iat_try(
|
||||
"SetupDiDestroyDeviceInfoList", SetupDiDestroyDeviceInfoList_hook, module);
|
||||
SetupDiEnumDeviceInfo_real = detour::iat_try(
|
||||
"SetupDiEnumDeviceInfo", SetupDiEnumDeviceInfo_hook, module);
|
||||
SetupDiEnumDeviceInterfaces_real = detour::iat_try(
|
||||
"SetupDiEnumDeviceInterfaces", SetupDiEnumDeviceInterfaces_hook, module);
|
||||
SetupDiGetClassDevsA_real = detour::iat_try(
|
||||
"SetupDiGetClassDevsA", SetupDiGetClassDevsA_hook, module);
|
||||
SetupDiGetClassDevsW_real = detour::iat_try(
|
||||
"SetupDiGetClassDevsW", SetupDiGetClassDevsW_hook, module);
|
||||
SetupDiGetDeviceInfoListDetailA_real = detour::iat_try(
|
||||
"SetupDiGetDeviceInfoListDetailA", SetupDiGetDeviceInfoListDetailA_hook, module);
|
||||
SetupDiGetDeviceInstanceIdA_real = detour::iat_try(
|
||||
"SetupDiGetDeviceInstanceIdA", SetupDiGetDeviceInstanceIdA_hook, module);
|
||||
SetupDiGetDeviceInterfaceDetailA_real = detour::iat_try(
|
||||
"SetupDiGetDeviceInterfaceDetailA", SetupDiGetDeviceInterfaceDetailA_hook, module);
|
||||
SetupDiGetDeviceInterfaceDetailW_real = detour::iat_try(
|
||||
"SetupDiGetDeviceInterfaceDetailW", SetupDiGetDeviceInterfaceDetailW_hook, module);
|
||||
SetupDiGetDeviceRegistryPropertyA_real = detour::iat_try(
|
||||
"SetupDiGetDeviceRegistryPropertyA", SetupDiGetDeviceRegistryPropertyA_hook, module);
|
||||
}
|
||||
|
||||
void setupapihook_add(SETUPAPI_SETTINGS settings) {
|
||||
SETUPAPI_SETTINGS_LIST.push_back(settings);
|
||||
}
|
||||
14
hooks/setupapihook.h
Normal file
14
hooks/setupapihook.h
Normal file
@@ -0,0 +1,14 @@
|
||||
#pragma once
|
||||
|
||||
#include <windows.h>
|
||||
|
||||
struct SETUPAPI_SETTINGS {
|
||||
unsigned int class_guid[4] {};
|
||||
char property_devicedesc[256] {};
|
||||
char property_hardwareid[256] {};
|
||||
DWORD property_address[2] {};
|
||||
char interface_detail[256] {};
|
||||
};
|
||||
|
||||
void setupapihook_init(HINSTANCE module);
|
||||
void setupapihook_add(SETUPAPI_SETTINGS settings);
|
||||
43
hooks/sleephook.cpp
Normal file
43
hooks/sleephook.cpp
Normal file
@@ -0,0 +1,43 @@
|
||||
#include "sleephook.h"
|
||||
|
||||
#include "avs/game.h"
|
||||
#include "util/detour.h"
|
||||
|
||||
// settings
|
||||
static DWORD SLEEPHOOK_MS_MAX;
|
||||
static DWORD SLEEPHOOK_MS_REPLACE;
|
||||
|
||||
static decltype(Sleep) *Sleep_orig = nullptr;
|
||||
static decltype(SleepEx) *SleepEx_orig = nullptr;
|
||||
|
||||
static VOID WINAPI Sleep_hook(DWORD dwMilliseconds) {
|
||||
if (dwMilliseconds > SLEEPHOOK_MS_MAX) {
|
||||
dwMilliseconds = SLEEPHOOK_MS_REPLACE;
|
||||
}
|
||||
|
||||
Sleep_orig(dwMilliseconds);
|
||||
}
|
||||
|
||||
static DWORD WINAPI SleepEx_hook(DWORD dwMilliseconds, BOOL bAltertable) {
|
||||
if (dwMilliseconds > SLEEPHOOK_MS_MAX) {
|
||||
dwMilliseconds = SLEEPHOOK_MS_REPLACE;
|
||||
}
|
||||
|
||||
return SleepEx_orig(dwMilliseconds, bAltertable);
|
||||
}
|
||||
|
||||
void hooks::sleep::init(DWORD ms_max, DWORD ms_replace, HMODULE module) {
|
||||
|
||||
// auto module
|
||||
if (!module) {
|
||||
module = avs::game::DLL_INSTANCE;
|
||||
}
|
||||
|
||||
// settings
|
||||
SLEEPHOOK_MS_MAX = ms_max;
|
||||
SLEEPHOOK_MS_REPLACE = ms_replace;
|
||||
|
||||
// hook functions
|
||||
Sleep_orig = detour::iat_try("Sleep", Sleep_hook, module);
|
||||
SleepEx_orig = detour::iat_try("SleepEx", SleepEx_hook, module);
|
||||
}
|
||||
7
hooks/sleephook.h
Normal file
7
hooks/sleephook.h
Normal file
@@ -0,0 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
#include <windows.h>
|
||||
|
||||
namespace hooks::sleep {
|
||||
void init(DWORD ms_max, DWORD ms_replace, HMODULE module = nullptr);
|
||||
}
|
||||
144
hooks/unisintrhook.cpp
Normal file
144
hooks/unisintrhook.cpp
Normal file
@@ -0,0 +1,144 @@
|
||||
#include "unisintrhook.h"
|
||||
|
||||
#include "avs/game.h"
|
||||
#include "util/detour.h"
|
||||
#include "util/logging.h"
|
||||
#include "util/libutils.h"
|
||||
|
||||
bool CreateInstance() {
|
||||
return true;
|
||||
}
|
||||
|
||||
int GetGMT() {
|
||||
|
||||
// fallback to avs clock
|
||||
return -1;
|
||||
}
|
||||
|
||||
int GetQRcodeLen(void*) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
void GetQRcodeURL(void*, char* dest) {
|
||||
}
|
||||
|
||||
void InitModel(char* model, int) {
|
||||
}
|
||||
|
||||
void InitPlayerCount(int players) {
|
||||
}
|
||||
|
||||
void InitPrivilege(int) {
|
||||
}
|
||||
|
||||
void InitVersion(int unis_ver_major, int unis_ver_minor) {
|
||||
}
|
||||
|
||||
bool IsConnectServer(void) {
|
||||
if (avs::game::is_model("KFC") || avs::game::is_model("REC")) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool IsInComm(void) {
|
||||
return true;
|
||||
}
|
||||
|
||||
bool IsPlayerForbidState(int) {
|
||||
return false;
|
||||
}
|
||||
|
||||
void RcfAddCoin(void* callback) {}
|
||||
void RcfCommFailed(void* callback) {}
|
||||
void RcfCommSucceed(void* callback) {}
|
||||
void RcfCountdown(void* callback) {}
|
||||
void RcfDebugLog(void* callback) {}
|
||||
void RcfGameSwitch(void* callback) {}
|
||||
void RcfMachineEffects(void* callback) {}
|
||||
void RcfMachineMode(void* callback) {}
|
||||
void RcfOtherInfo(void* callback) {}
|
||||
void RcfPayoutFailed(void* callback) {}
|
||||
void RcfPayoutSucceed(void* callback) {}
|
||||
void RcfPlayerInfo(void* callback) {}
|
||||
void RcfPrintTicket(void* callback) {}
|
||||
void RcfPrivilege(void* callback) {}
|
||||
void RcfRankingResult(void* callback) {}
|
||||
void RcfRecvComm(void* callback) {}
|
||||
void RcfRecvPayout(void* callback) {}
|
||||
void RcfRecvTransp(void* callback) {}
|
||||
void RcfTranspFailed(void* callback) {}
|
||||
void RcfTranspSucceed(void* callback) {}
|
||||
void RcfWinPrize(void* callback) {}
|
||||
|
||||
int RefreshPlayerState(int player) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
void ReleaseInstance(void) {
|
||||
}
|
||||
|
||||
int SendCoinSignal(int, int, int) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
int SendTransp(int, int) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
void StartDevice(void) {
|
||||
}
|
||||
|
||||
void unisintrhook_init(void) {
|
||||
|
||||
// check for module
|
||||
auto unisintr = libutils::try_module("unisintr.dll");
|
||||
if (unisintr != nullptr) {
|
||||
log_info("unisintrhook", "attaching...");
|
||||
|
||||
// TODO: SDVX CN doesn't like this...?
|
||||
if (!avs::game::is_model("KFC")) {
|
||||
detour::iat_try("GetGMT", GetGMT, nullptr, "unisintr.dll");
|
||||
}
|
||||
|
||||
// hooks
|
||||
detour::iat_try("CreateInstance", CreateInstance, nullptr, "unisintr.dll");
|
||||
detour::iat_try("GetQRcodeLen", GetQRcodeLen, nullptr, "unisintr.dll");
|
||||
detour::iat_try("GetQRcodeURL", GetQRcodeURL, nullptr, "unisintr.dll");
|
||||
detour::iat_try("InitModel", InitModel, nullptr, "unisintr.dll");
|
||||
detour::iat_try("InitPlayerCount", InitPlayerCount, nullptr, "unisintr.dll");
|
||||
detour::iat_try("InitPrivilege", InitPrivilege, nullptr, "unisintr.dll");
|
||||
detour::iat_try("InitVersion", InitVersion, nullptr, "unisintr.dll");
|
||||
detour::iat_try("IsConnectServer", IsConnectServer, nullptr, "unisintr.dll");
|
||||
detour::iat_try("IsInComm", IsInComm, nullptr, "unisintr.dll");
|
||||
detour::iat_try("IsPlayerForbidState", IsPlayerForbidState, nullptr, "unisintr.dll");
|
||||
detour::iat_try("RcfAddCoin", RcfAddCoin, nullptr, "unisintr.dll");
|
||||
detour::iat_try("RcfCommFailed", RcfCommFailed, nullptr, "unisintr.dll");
|
||||
detour::iat_try("RcfCommSucceed", RcfCommSucceed, nullptr, "unisintr.dll");
|
||||
detour::iat_try("RcfCountdown", RcfCountdown, nullptr, "unisintr.dll");
|
||||
detour::iat_try("RcfDebugLog", RcfDebugLog, nullptr, "unisintr.dll");
|
||||
detour::iat_try("RcfGameSwitch", RcfGameSwitch, nullptr, "unisintr.dll");
|
||||
detour::iat_try("RcfMachineEffects", RcfMachineEffects, nullptr, "unisintr.dll");
|
||||
detour::iat_try("RcfMachineMode", RcfMachineMode, nullptr, "unisintr.dll");
|
||||
detour::iat_try("RcfOtherInfo", RcfOtherInfo, nullptr, "unisintr.dll");
|
||||
detour::iat_try("RcfPayoutFailed", RcfPayoutFailed, nullptr, "unisintr.dll");
|
||||
detour::iat_try("RcfPayoutSucceed", RcfPayoutSucceed, nullptr, "unisintr.dll");
|
||||
detour::iat_try("RcfPlayerInfo", RcfPlayerInfo, nullptr, "unisintr.dll");
|
||||
detour::iat_try("RcfPrintTicket", RcfPrintTicket, nullptr, "unisintr.dll");
|
||||
detour::iat_try("RcfPrivilege", RcfPrivilege, nullptr, "unisintr.dll");
|
||||
detour::iat_try("RcfRankingResult", RcfRankingResult, nullptr, "unisintr.dll");
|
||||
detour::iat_try("RcfRecvComm", RcfRecvComm, nullptr, "unisintr.dll");
|
||||
detour::iat_try("RcfRecvPayout", RcfRecvPayout, nullptr, "unisintr.dll");
|
||||
detour::iat_try("RcfRecvTransp", RcfRecvTransp, nullptr, "unisintr.dll");
|
||||
detour::iat_try("RcfTranspFailed", RcfTranspFailed, nullptr, "unisintr.dll");
|
||||
detour::iat_try("RcfTranspSucceed", RcfTranspSucceed, nullptr, "unisintr.dll");
|
||||
detour::iat_try("RcfWinPrize", RcfWinPrize, nullptr, "unisintr.dll");
|
||||
detour::iat_try("RefreshPlayerState", RefreshPlayerState, nullptr, "unisintr.dll");
|
||||
detour::iat_try("ReleaseInstance", ReleaseInstance, nullptr, "unisintr.dll");
|
||||
detour::iat_try("SendCoinSignal", SendCoinSignal, nullptr, "unisintr.dll");
|
||||
detour::iat_try("SendTransp", SendTransp, nullptr, "unisintr.dll");
|
||||
detour::iat_try("StartDevice", StartDevice, nullptr, "unisintr.dll");
|
||||
|
||||
log_info("unisintrhook", "attached");
|
||||
}
|
||||
}
|
||||
3
hooks/unisintrhook.h
Normal file
3
hooks/unisintrhook.h
Normal file
@@ -0,0 +1,3 @@
|
||||
#pragma once
|
||||
|
||||
void unisintrhook_init(void);
|
||||
23
hooks/winuser.cpp
Normal file
23
hooks/winuser.cpp
Normal file
@@ -0,0 +1,23 @@
|
||||
#include "winuser.h"
|
||||
|
||||
#include "util/detour.h"
|
||||
#include "util/logging.h"
|
||||
|
||||
// hooks to prevent display scaling changes (e.g., SDVX)
|
||||
|
||||
static decltype(DisplayConfigSetDeviceInfo) *DisplayConfigSetDeviceInfo_real = nullptr;
|
||||
|
||||
static LONG WINAPI DisplayConfigSetDeviceInfo_hook(DISPLAYCONFIG_DEVICE_INFO_HEADER *setPacket) {
|
||||
// seems to be using some undocumented API to change the monitor scaling (type == -4)
|
||||
if (setPacket && setPacket->type == 0xFFFFFFFC) {
|
||||
log_misc("winuser", "DisplayConfigSetDeviceInfo_hook: ignoring request to change monitor scaling");
|
||||
return ERROR_SUCCESS;
|
||||
}
|
||||
return DisplayConfigSetDeviceInfo_real(setPacket);
|
||||
}
|
||||
|
||||
void winuser_hook_init(HMODULE module) {
|
||||
log_info("winuser", "initializing");
|
||||
DisplayConfigSetDeviceInfo_real =
|
||||
detour::iat_try("DisplayConfigSetDeviceInfo", DisplayConfigSetDeviceInfo_hook, module);
|
||||
}
|
||||
5
hooks/winuser.h
Normal file
5
hooks/winuser.h
Normal file
@@ -0,0 +1,5 @@
|
||||
#pragma once
|
||||
|
||||
#include <windows.h>
|
||||
|
||||
void winuser_hook_init(HMODULE module);
|
||||
Reference in New Issue
Block a user