Initial re-upload of spice2x-24-08-24

This commit is contained in:
2024-08-28 11:10:34 -04:00
commit caa9e02285
1181 changed files with 380065 additions and 0 deletions

92
hooks/audio/audio.cpp Normal file
View 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
View 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;
}
}

View 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;
}

View 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);
}

View 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;
};

View 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);
}

View 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;
};

View 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));
}

View 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;
};

View 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);
}

View 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;
};

View 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;
}

View 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;
};

View 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));
}

View 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;
};

View 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

View 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);
}

View 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;
};

View 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);
}

View 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;
};

View 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));
}

View 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;
};

View 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;
}

View 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;
};

View 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);
}

View 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;
};
}

View 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);
}

View File

@@ -0,0 +1,8 @@
#pragma once
#include <string>
#include <windows.h>
#include <mmreg.h>
std::string stream_flags_str(DWORD flags);

View 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
View 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
View 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);

File diff suppressed because it is too large Load Diff

View 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;
};

View 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
};

View 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;
}

View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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();

File diff suppressed because it is too large Load Diff

View 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;
};

File diff suppressed because it is too large Load Diff

View 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;
};

View 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;
}

View 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;
};

View 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));
}

View 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;
};

View 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));
}

View 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;
};

View 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
};

View 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

File diff suppressed because it is too large Load Diff

94
hooks/graphics/graphics.h Normal file
View 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();

View 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;
}

View 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;
}
}

View File

@@ -0,0 +1,6 @@
#pragma once
namespace nvapi_hook {
extern bool BYPASS_NVAPI;
void initialize(HINSTANCE dll);
}

View 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

View File

@@ -0,0 +1,5 @@
#pragma once
namespace nvenc_hook {
void initialize();
}

View 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;
}

View 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;
};

View 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;
}

View 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;
};

View 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");
}

View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View File

@@ -0,0 +1,5 @@
#pragma once
#include <windows.h>
void powrprof_hook_init(HMODULE module);

75
hooks/rom.cpp Normal file
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View File

@@ -0,0 +1,3 @@
#pragma once
void unisintrhook_init(void);

23
hooks/winuser.cpp Normal file
View 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
View File

@@ -0,0 +1,5 @@
#pragma once
#include <windows.h>
void winuser_hook_init(HMODULE module);