Update to spice2x-25-04-25 (pre-apply)
> broken commit
This commit is contained in:
+34
-3
@@ -20,6 +20,16 @@ DEFINE_GUID(CLSID_MMDeviceEnumerator,
|
||||
0x8E, 0x3D, 0xC4, 0x57, 0x92, 0x91, 0x69, 0x2E);
|
||||
#endif
|
||||
|
||||
// {ef985e71-d5c7-42d4-ba4d-2d073e2e96f4}
|
||||
DEFINE_GUID(CLSID_DirectSoundI3DL2ReverbDMO,
|
||||
0xef985e71, 0xd5c7, 0x42d4,
|
||||
0xba, 0x4d, 0x2d, 0x07, 0x3e, 0x2e, 0x96, 0xf4);
|
||||
|
||||
// {87fc0268-9a55-4360-95aa-004a1d9de26c}
|
||||
DEFINE_GUID(CLSID_DirectSoundWavesReverbDMO,
|
||||
0x87fc0268, 0x9a55, 0x4360,
|
||||
0x95, 0xaa, 0x00, 0x4a, 0x1d, 0x9d, 0xe2, 0x6c);
|
||||
|
||||
// function pointers
|
||||
static decltype(CoCreateInstance) *CoCreateInstance_orig = nullptr;
|
||||
|
||||
@@ -27,14 +37,16 @@ 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;
|
||||
std::mutex INITIALIZE_LOCK; // for asio
|
||||
}
|
||||
|
||||
static HRESULT STDAPICALLTYPE CoCreateInstance_hook(
|
||||
@@ -44,12 +56,28 @@ static HRESULT STDAPICALLTYPE CoCreateInstance_hook(
|
||||
REFIID riid,
|
||||
LPVOID *ppv)
|
||||
{
|
||||
|
||||
HRESULT ret;
|
||||
|
||||
// call original
|
||||
HRESULT ret = CoCreateInstance_orig(rclsid, pUnkOuter, dwClsContext, riid, ppv);
|
||||
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));
|
||||
log_warning("audio", "CoCreateInstance failed for CLSID_MMDeviceEnumerator, hr={}", FMT_HRESULT(ret));
|
||||
}
|
||||
|
||||
// Windows 11 KB5052093 issue - reverb DMO went missing from dsdmo.dll (fails with 0x80040154)
|
||||
if (IsEqualCLSID(rclsid, CLSID_DirectSoundI3DL2ReverbDMO) && ret == (HRESULT)0x80040154) {
|
||||
log_warning(
|
||||
"audio",
|
||||
"CoCreateInstance for CLSID_DirectSoundI3DL2ReverbDMO failed with 0x80040154, swap with CLSID_DirectSoundWavesReverbDMO "
|
||||
"(workaround for Windows 11 KB5052093 issue); REVERB EX replaced with REVERB");
|
||||
ret = CoCreateInstance_orig(CLSID_DirectSoundWavesReverbDMO, pUnkOuter, dwClsContext, riid, ppv);
|
||||
if (FAILED(ret)) {
|
||||
log_warning("audio", "CoCreateInstance(CLSID_DirectSoundWavesReverbDMO...) failed, hr={}", FMT_HRESULT(ret));
|
||||
}
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
@@ -72,16 +100,19 @@ namespace hooks::audio {
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,8 +4,10 @@
|
||||
|
||||
#include <windows.h>
|
||||
#include <mmreg.h>
|
||||
#ifndef _MSC_VER
|
||||
#include <ks.h>
|
||||
#include <ksmedia.h>
|
||||
#endif
|
||||
|
||||
namespace hooks::audio {
|
||||
enum class Backend {
|
||||
@@ -14,10 +16,13 @@ namespace hooks::audio {
|
||||
};
|
||||
|
||||
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();
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
|
||||
#include <windows.h>
|
||||
#include <audioclient.h>
|
||||
#include "hooks/audio/backends/wasapi/low_latency_client.h"
|
||||
|
||||
constexpr bool AUDIO_LOG_HRESULT = true;
|
||||
|
||||
@@ -11,4 +12,6 @@ namespace hooks::audio {
|
||||
|
||||
extern IAudioClient *CLIENT;
|
||||
extern std::mutex INITIALIZE_LOCK;
|
||||
extern bool VOLUME_HOOK_ENABLED;
|
||||
extern LowLatencyAudioClient *LOW_LATENCY_CLIENT;
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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;
|
||||
};
|
||||
@@ -3,8 +3,10 @@
|
||||
#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) \
|
||||
@@ -108,6 +110,9 @@ HRESULT STDMETHODCALLTYPE WrappedIMMDevice::Activate(
|
||||
// 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;
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
#include "device_enumerator.h"
|
||||
|
||||
#include "util/logging.h"
|
||||
|
||||
#include "device.h"
|
||||
#include "util/utils.h"
|
||||
|
||||
#ifdef _MSC_VER
|
||||
DEFINE_GUID(IID_IMMDeviceEnumerator,
|
||||
@@ -61,6 +59,38 @@ HRESULT STDMETHODCALLTYPE WrappedIMMDeviceEnumerator::GetDefaultAudioEndpoint(
|
||||
return ret;
|
||||
}
|
||||
|
||||
// dump friendly name of audio endpoint
|
||||
// https://learn.microsoft.com/en-us/windows/win32/coreaudio/device-properties?redirectedfrom=MSDN
|
||||
{
|
||||
IPropertyStore *pProps = nullptr;
|
||||
HRESULT hr;
|
||||
|
||||
hr = (*ppEndpoint)->OpenPropertyStore(STGM_READ, &pProps);
|
||||
if (SUCCEEDED(hr)) {
|
||||
PROPVARIANT varName;
|
||||
|
||||
// instead of including giant windows headers, hardcoding the GUID here
|
||||
PROPERTYKEY key;
|
||||
GUID IDevice_FriendlyName = { 0xa45c254e, 0xdf1c, 0x4efd, { 0x80, 0x20, 0x67, 0xd1, 0x46, 0xa8, 0x50, 0xe0 } };
|
||||
key.pid = 14;
|
||||
key.fmtid = IDevice_FriendlyName;
|
||||
|
||||
// get friendly name
|
||||
PropVariantInit(&varName);
|
||||
hr = pProps->GetValue(key, &varName);
|
||||
if (SUCCEEDED(hr) && varName.vt != VT_EMPTY) {
|
||||
log_info("audio", "IMMDeviceEnumerator::GetDefaultAudioEndpoint: using {}", ws2s(varName.pwszVal));
|
||||
}
|
||||
|
||||
// cleanup
|
||||
PropVariantClear(&varName);
|
||||
}
|
||||
if (pProps) {
|
||||
pProps->Release();
|
||||
pProps = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
// wrap interface
|
||||
*ppEndpoint = new WrappedIMMDevice(*ppEndpoint);
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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;
|
||||
};
|
||||
}
|
||||
@@ -968,7 +968,15 @@ HRESULT AsioBackend::on_stop() noexcept {
|
||||
return AUDCLNT_E_NOT_INITIALIZED;
|
||||
}
|
||||
|
||||
if (hooks::audio::ASIO_FORCE_UNLOAD_ON_STOP) {
|
||||
log_misc("audio::asio", "AsioBackend::on_stop - using ASIO_FORCE_UNLOAD_ON_STOP workaround");
|
||||
}
|
||||
|
||||
auto result = this->run_on_asio_thread([this]() {
|
||||
if (hooks::audio::ASIO_FORCE_UNLOAD_ON_STOP) {
|
||||
this->unload_driver();
|
||||
return (AsioError)ASE_OK;
|
||||
}
|
||||
return this->asio_driver->stop();
|
||||
});
|
||||
if (result != ASE_OK) {
|
||||
|
||||
+21
-26
@@ -1,25 +1,24 @@
|
||||
#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 bool ROM_FILE_OPEN = false;
|
||||
static const char *ROM_FILE_PATH = nullptr;
|
||||
static const char *ROM_FILE_CONTENTS = nullptr;
|
||||
|
||||
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;
|
||||
@@ -62,10 +61,6 @@ static bool is_dest_spec_ea3_config(const char *name) {
|
||||
return !_stricmp(name, path.c_str());
|
||||
}
|
||||
|
||||
static bool is_rom_file(const char *name) {
|
||||
return ROM_FILE_PATH && _stricmp(name, ROM_FILE_PATH) == 0;
|
||||
}
|
||||
|
||||
static bool is_spam_file(const char *file) {
|
||||
static const char *spam_prefixes[] = {
|
||||
"/mnt/bm2d/ngp",
|
||||
@@ -85,9 +80,9 @@ static bool is_spam_file(const char *file) {
|
||||
}
|
||||
|
||||
static int avs_fs_fstat(avs::core::avs_file_t fd, struct avs::core::avs_stat *st) {
|
||||
if (is_fake_fd(fd) && ROM_FILE_OPEN) {
|
||||
if (is_fake_fd(fd) && ROM_FILE_CONTENTS) {
|
||||
if (st) {
|
||||
st->filesize = static_cast<uint32_t>(strlen(ROM_FILE_CONTENTS));
|
||||
st->filesize = static_cast<uint32_t>(ROM_FILE_CONTENTS->length());
|
||||
st->padding.st_dev = 0;
|
||||
}
|
||||
return 1;
|
||||
@@ -109,9 +104,7 @@ static int avs_fs_lstat(const char *name, struct avs::core::avs_stat *st) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
auto value = layeredfs::initialized
|
||||
? layeredfs::hook_avs_fs_lstat(name, st) : avs::core::avs_fs_lstat(name, st);
|
||||
|
||||
auto value = avs::core::avs_fs_lstat(name, st);
|
||||
if (!is_spam_file(name)) {
|
||||
WRAP_DEBUG_FMT("name: {}", name);
|
||||
}
|
||||
@@ -124,11 +117,11 @@ static avs::core::avs_file_t avs_fs_open(const char *name, uint16_t mode, int fl
|
||||
return avs::core::avs_fs_open(name, mode, flags);
|
||||
}
|
||||
|
||||
if (!FAKE_FILE_OPEN && (is_dest_file(name, mode) || is_rom_file(name))) {
|
||||
if (!FAKE_FILE_OPEN && (is_dest_file(name, mode) || ROM_FILE_MAP.contains(name))) {
|
||||
FAKE_FILE_OPEN = true;
|
||||
|
||||
if (is_rom_file(name)) {
|
||||
ROM_FILE_OPEN = true;
|
||||
if (ROM_FILE_MAP.contains(name)) {
|
||||
ROM_FILE_CONTENTS = &ROM_FILE_MAP.at(name);
|
||||
}
|
||||
|
||||
log_info("avshook", "opening fake file '{}'", name);
|
||||
@@ -136,8 +129,7 @@ static avs::core::avs_file_t avs_fs_open(const char *name, uint16_t mode, int fl
|
||||
return 1337;
|
||||
}
|
||||
|
||||
auto value = layeredfs::initialized
|
||||
? layeredfs::hook_avs_fs_open(name, mode, flags) : avs::core::avs_fs_open(name, mode, flags);
|
||||
auto value = avs::core::avs_fs_open(name, mode, flags);
|
||||
|
||||
if (!is_spam_file(name)) {
|
||||
WRAP_DEBUG_FMT("name: {} mode: {} flags: {}", name, mode, flags);
|
||||
@@ -149,7 +141,7 @@ static avs::core::avs_file_t avs_fs_open(const char *name, uint16_t mode, int fl
|
||||
static void avs_fs_close(avs::core::avs_file_t fd) {
|
||||
if (is_fake_fd(fd)) {
|
||||
FAKE_FILE_OPEN = false;
|
||||
ROM_FILE_OPEN = false;
|
||||
ROM_FILE_CONTENTS = nullptr;
|
||||
|
||||
log_info("hooks::avs", "closing fake fd");
|
||||
} else {
|
||||
@@ -180,7 +172,11 @@ static avs::core::avs_file_t avs_fs_opendir(const char *path) {
|
||||
}
|
||||
|
||||
static int avs_fs_mount(const char *mountpoint, const char *fsroot, const char *fstype, void *data) {
|
||||
if (mountpoint == nullptr || fsroot == nullptr || fstype == nullptr) {
|
||||
// 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);
|
||||
}
|
||||
|
||||
@@ -244,10 +240,10 @@ static int avs_fs_mount(const char *mountpoint, const char *fsroot, const char *
|
||||
}
|
||||
|
||||
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_OPEN) {
|
||||
const auto size = std::min(static_cast<size_t>(data_size), strlen(ROM_FILE_CONTENTS));
|
||||
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, size);
|
||||
memcpy(data, ROM_FILE_CONTENTS->c_str(), size);
|
||||
|
||||
return size;
|
||||
}
|
||||
@@ -288,6 +284,5 @@ void hooks::avs::init() {
|
||||
}
|
||||
|
||||
void hooks::avs::set_rom(const char *path, const char *contents) {
|
||||
ROM_FILE_PATH = path;
|
||||
ROM_FILE_CONTENTS = contents;
|
||||
ROM_FILE_MAP.emplace(path, contents);
|
||||
}
|
||||
|
||||
+64
-6
@@ -6,6 +6,8 @@
|
||||
#include "util/detour.h"
|
||||
#include "util/utils.h"
|
||||
|
||||
#include <tlhelp32.h>
|
||||
|
||||
// std::min
|
||||
#ifdef min
|
||||
#undef min
|
||||
@@ -117,6 +119,12 @@ int MITMHandle::device_io(DWORD dwIoControlCode, LPVOID lpInBuffer,
|
||||
}
|
||||
}
|
||||
|
||||
size_t MITMHandle::bytes_available() {
|
||||
COMSTAT status;
|
||||
ClearCommError_orig(handle, NULL, &status);
|
||||
return status.cbInQue;
|
||||
}
|
||||
|
||||
bool MITMHandle::close() {
|
||||
return CloseHandle_orig(handle);
|
||||
}
|
||||
@@ -385,8 +393,10 @@ static BOOL WINAPI ClearCommError_hook(HANDLE hFile, LPDWORD lpErrors, LPCOMSTAT
|
||||
* 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 = 255;
|
||||
lpStat->cbInQue = custom_handle->bytes_available();
|
||||
}
|
||||
|
||||
return TRUE;
|
||||
@@ -489,6 +499,38 @@ static BOOL WINAPI CloseHandle_hook(HANDLE hObject) {
|
||||
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;
|
||||
@@ -513,6 +555,8 @@ void devicehook_init(HMODULE module) {
|
||||
|
||||
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));
|
||||
@@ -535,8 +579,22 @@ void devicehook_init(HMODULE module) {
|
||||
STORE(SetCommTimeouts_orig, detour::iat_try("SetCommTimeouts", SetCommTimeouts_hook, module));
|
||||
STORE(WriteFile_orig, detour::iat_try("WriteFile", WriteFile_hook, module));
|
||||
|
||||
// global trampolines
|
||||
/*
|
||||
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);
|
||||
@@ -548,7 +606,7 @@ void devicehook_init(HMODULE module) {
|
||||
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);
|
||||
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);
|
||||
@@ -557,8 +615,8 @@ void devicehook_init(HMODULE module) {
|
||||
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);
|
||||
*/
|
||||
#undef STORE
|
||||
|
||||
suspend_or_resume_other_threads(false);
|
||||
}
|
||||
|
||||
void devicehook_add(CustomHandle *device_handle) {
|
||||
|
||||
@@ -39,6 +39,10 @@ public:
|
||||
return -1;
|
||||
};
|
||||
|
||||
virtual size_t bytes_available() {
|
||||
return 255;
|
||||
}
|
||||
|
||||
virtual bool close() {
|
||||
return true;
|
||||
};
|
||||
@@ -67,9 +71,11 @@ public:
|
||||
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();
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
#endif
|
||||
|
||||
#include "avs/game.h"
|
||||
#include "cfg/screen_resize.h"
|
||||
#include "games/iidx/iidx.h"
|
||||
#include "games/sdvx/sdvx.h"
|
||||
#include "games/io.h"
|
||||
@@ -200,6 +201,34 @@ static std::string presentation_interval2s(UINT presentation_interval) {
|
||||
FLAGS_END(presentation_interval);
|
||||
}
|
||||
|
||||
static void update_backbuffer_dimensions(D3DPRESENT_PARAMETERS *params);
|
||||
|
||||
static bool is_dx9_on_12_enabled() {
|
||||
switch (GRAPHICS_9_ON_12_STATE) {
|
||||
case DX9ON12_FORCE_OFF:
|
||||
log_info("graphics::d3d9", "DirectX 9on12: forced OFF by user (-dx9on12)");
|
||||
return false;
|
||||
|
||||
case DX9ON12_FORCE_ON:
|
||||
log_info("graphics::d3d9", "DirectX 9on12: forced ON by user (-9on12 or -dx9on12)");
|
||||
return true;
|
||||
|
||||
case DX9ON12_AUTO:
|
||||
default:
|
||||
if (GRAPHICS_9_ON_12_REQUESTED_BY_GAME) {
|
||||
log_info(
|
||||
"graphics::d3d9",
|
||||
"DirectX 9on12: enabled automatically for current game (tip: use -dx9on12 to force on or off)");
|
||||
return true;
|
||||
} else {
|
||||
log_info(
|
||||
"graphics::d3d9",
|
||||
"DirectX 9on12: disabled by default, using DX9 (tip: use -dx9on12 to force on or off)");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static IDirect3D9 *WINAPI Direct3DCreate9_hook(UINT SDKVersion) {
|
||||
log_misc("graphics::d3d9", "Direct3DCreate9 hook hit");
|
||||
|
||||
@@ -214,7 +243,7 @@ static IDirect3D9 *WINAPI Direct3DCreate9_hook(UINT SDKVersion) {
|
||||
|
||||
// create interface
|
||||
IDirect3D9 *value;
|
||||
if (GRAPHICS_9_ON_12) {
|
||||
if (is_dx9_on_12_enabled()) {
|
||||
if (!Direct3DCreate9On12_orig) {
|
||||
log_fatal("graphics::d3d9", "unable to find Direct3DCreate9On12");
|
||||
} else {
|
||||
@@ -247,7 +276,7 @@ static HRESULT WINAPI Direct3DCreate9Ex_hook(UINT SDKVersion, IDirect3D9Ex **d3d
|
||||
|
||||
// call original
|
||||
HRESULT result;
|
||||
if (GRAPHICS_9_ON_12) {
|
||||
if (is_dx9_on_12_enabled()) {
|
||||
if (!Direct3DCreate9On12Ex_orig) {
|
||||
log_fatal("graphics::d3d9", "unable to find Direct3DCreate9On12Ex");
|
||||
} else {
|
||||
@@ -389,7 +418,7 @@ HRESULT STDMETHODCALLTYPE WrappedIDirect3D9::EnumAdapterModes(
|
||||
{
|
||||
HRESULT ret = pReal->EnumAdapterModes(Adapter, Format, Mode, pMode);
|
||||
|
||||
if (SUCCEEDED(ret) && pMode && avs::game::is_model("LDJ")) {
|
||||
if (SUCCEEDED(ret) && pMode) {
|
||||
/*
|
||||
log_misc("graphics::d3d9", "IDirect3D9::EnumAdapterMode({}, {}, {}) => {}x{} @ {} Hz ({})",
|
||||
Adapter,
|
||||
@@ -406,52 +435,66 @@ HRESULT STDMETHODCALLTYPE WrappedIDirect3D9::EnumAdapterModes(
|
||||
auto height = pMode->Height;
|
||||
auto refresh = pMode->RefreshRate;
|
||||
|
||||
if (Mode == 0 && games::iidx::TDJ_MODE) {
|
||||
if (games::iidx::is_tdj_fhd()) {
|
||||
log_misc("graphics::d3d9", "overriding mode 0 to 1920x1080 @ 120 Hz (for TDJ FHD)");
|
||||
pMode->Width = 1920;
|
||||
pMode->Height = 1080;
|
||||
pMode->RefreshRate = 120;
|
||||
modified = true;
|
||||
} else {
|
||||
log_misc("graphics::d3d9", "overriding mode 0 to 1280x720 @ 120 Hz (for TDJ HD)");
|
||||
pMode->Width = 1280;
|
||||
pMode->Height = 720;
|
||||
pMode->RefreshRate = 120;
|
||||
if (avs::game::is_model("LDJ")) {
|
||||
if (Mode == 0 && games::iidx::TDJ_MODE) {
|
||||
if (games::iidx::is_tdj_fhd()) {
|
||||
log_misc("graphics::d3d9", "overriding mode 0 to 1920x1080 @ 120 Hz (for TDJ FHD)");
|
||||
pMode->Width = 1920;
|
||||
pMode->Height = 1080;
|
||||
pMode->RefreshRate = 120;
|
||||
modified = true;
|
||||
} else {
|
||||
log_misc("graphics::d3d9", "overriding mode 0 to 1280x720 @ 120 Hz (for TDJ HD)");
|
||||
pMode->Width = 1280;
|
||||
pMode->Height = 720;
|
||||
pMode->RefreshRate = 120;
|
||||
modified = true;
|
||||
}
|
||||
}
|
||||
|
||||
// For whatever reason, TDJ FHD mode prefers to pick lower (~60Hz) resolutions instead
|
||||
// of 1080p@120Hz. Remove them here and try to force 120+ Hz.
|
||||
if (!modified && games::iidx::is_tdj_fhd() && refresh < 110) {
|
||||
if ((width == 1920 && height == 1080) ||
|
||||
(width == 1280 && height == 720)){
|
||||
log_misc(
|
||||
"graphics::d3d9", "removing mode {}, {}x{} @ {}Hz (for TDJ FHD)",
|
||||
Mode, width, height, refresh);
|
||||
memset(pMode, 0, sizeof(*pMode));
|
||||
modified = true;
|
||||
}
|
||||
}
|
||||
|
||||
// zero out display mode for bad entries
|
||||
// - skip non-native resolutions
|
||||
// - skip 75 and 90 Hz entries because LDJ game timing is messed up with it
|
||||
// (TDJ should be fine as it assumes a fixed 60 Hz timing)
|
||||
if (!modified && (width == 1360 || width == 1366 || refresh == 75 || refresh == 90)) {
|
||||
log_misc(
|
||||
"graphics::d3d9", "removing mode {}, {}x{} @ {}Hz (for LDJ/TDJ)",
|
||||
Mode, width, height, refresh);
|
||||
memset(pMode, 0, sizeof(*pMode));
|
||||
modified = true;
|
||||
}
|
||||
}
|
||||
|
||||
// For whatever reason, TDJ FHD mode prefers to pick these resolutions over 1080p@120Hz.
|
||||
// Remove them entirely.
|
||||
if (!modified && games::iidx::is_tdj_fhd() && refresh == 60) {
|
||||
if ((width == 1920 && height == 1080) ||
|
||||
(width == 1280 && height == 720)){
|
||||
if (!modified && !games::iidx::TDJ_MODE && games::iidx::FORCE_720P && (height > 720)) {
|
||||
log_misc(
|
||||
"graphics::d3d9", "removing mode {}, {}x{} @ {}Hz (for TDJ FHD)",
|
||||
"graphics::d3d9", "removing mode {}, {}x{} @ {}Hz (-iidxforce720p)",
|
||||
Mode, width, height, refresh);
|
||||
memset(pMode, 0, sizeof(*pMode));
|
||||
modified = true;
|
||||
}
|
||||
}
|
||||
|
||||
// zero out display mode for bad entries
|
||||
// - skip non-native resolutions
|
||||
// - skip 75 and 90 Hz entries because LDJ game timing is messed up with it
|
||||
// (TDJ should be fine as it assumes a fixed 60 Hz timing)
|
||||
if (!modified && (width == 1360 || width == 1366 || refresh == 75 || refresh == 90)) {
|
||||
log_misc(
|
||||
"graphics::d3d9", "removing mode {}, {}x{} @ {}Hz (for LDJ/TDJ)",
|
||||
Mode, width, height, refresh);
|
||||
memset(pMode, 0, sizeof(*pMode));
|
||||
modified = true;
|
||||
}
|
||||
|
||||
if (!modified && !games::iidx::TDJ_MODE && games::iidx::FORCE_720P && (height > 720)) {
|
||||
if (!modified &&
|
||||
(width > height) &&
|
||||
(GRAPHICS_ADJUST_ORIENTATION == ORIENTATION_CW ||
|
||||
GRAPHICS_ADJUST_ORIENTATION == ORIENTATION_CCW)) {
|
||||
log_misc(
|
||||
"graphics::d3d9", "removing mode {}, {}x{} @ {}Hz (-sp2x-iidxforce720p)",
|
||||
"graphics::d3d9", "swapping width and height for mode {}, {}x{} @ {}Hz (-autoorientation)",
|
||||
Mode, width, height, refresh);
|
||||
memset(pMode, 0, sizeof(*pMode));
|
||||
pMode->Height = width;
|
||||
pMode->Width = height;
|
||||
modified = true;
|
||||
}
|
||||
}
|
||||
@@ -551,15 +594,31 @@ HRESULT STDMETHODCALLTYPE WrappedIDirect3D9::GetDeviceCaps(UINT Adapter, D3DDEVT
|
||||
}
|
||||
|
||||
HRESULT ret = this->pReal->GetDeviceCaps(Adapter, DeviceType, pCaps);
|
||||
|
||||
// IIDX Lightning Cabinet & SDVX Valkyrie Cabinet
|
||||
if (SUCCEEDED(ret) && !GRAPHICS_WINDOWED && avs::game::is_model({"LDJ", "KFC"})) {
|
||||
pCaps->NumberOfAdaptersInGroup = 2;
|
||||
if (FAILED(ret)) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
if (SUCCEEDED(ret)) {
|
||||
if (GRAPHICS_WINDOWED && (avs::game::is_model("NBT") || avs::game::is_model("PAN"))) {
|
||||
if (avs::game::is_model("LDJ")) {
|
||||
if (!GRAPHICS_WINDOWED) {
|
||||
// use 2 so that the subscreen overlay can be drawn in full screen with single monitor
|
||||
pCaps->NumberOfAdaptersInGroup = 2;
|
||||
}
|
||||
// in windowed mode, LDJ will always launch two windows, no special handling needed here
|
||||
} else if (avs::game::is_model("KFC")) {
|
||||
if (GRAPHICS_WINDOWED & GRAPHICS_PREVENT_SECONDARY_WINDOW) {
|
||||
// user wants windowed mode but does not want subscreen at all
|
||||
pCaps->NumberOfAdaptersInGroup = 1;
|
||||
} else {
|
||||
// in both full screen and windowed mode, use 2 so that the game draws the subscreen
|
||||
// (if this is 1, the game won't even draw the second window, causing subscreen overlay to not work)
|
||||
pCaps->NumberOfAdaptersInGroup = 2;
|
||||
}
|
||||
} else if (avs::game::is_model({"NBT", "PAN"})) {
|
||||
// beatstream, nostalgia
|
||||
if (GRAPHICS_WINDOWED) {
|
||||
pCaps->NumberOfAdaptersInGroup = std::min(pCaps->NumberOfAdaptersInGroup, 1u);
|
||||
} else if (GRAPHICS_FORCE_SINGLE_ADAPTER) {
|
||||
pCaps->NumberOfAdaptersInGroup = 1;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -621,7 +680,29 @@ HRESULT STDMETHODCALLTYPE WrappedIDirect3D9::CreateDevice(
|
||||
|
||||
// dump presentation parameters
|
||||
for (size_t i = 0; i < num_adapters; i++) {
|
||||
const auto *params = &pPresentationParameters[i];
|
||||
auto *params = &pPresentationParameters[i];
|
||||
if (!GRAPHICS_WINDOWED && i == 0) {
|
||||
GRAPHICS_FS_ORIGINAL_WIDTH = params->BackBufferWidth;
|
||||
GRAPHICS_FS_ORIGINAL_HEIGHT = params->BackBufferHeight;
|
||||
log_misc("graphics::d3d9", "original resolution: {}x{}", GRAPHICS_FS_ORIGINAL_WIDTH, GRAPHICS_FS_ORIGINAL_HEIGHT);
|
||||
if (GRAPHICS_FS_CUSTOM_RESOLUTION.has_value()) {
|
||||
log_misc(
|
||||
"graphics::d3d9",
|
||||
"use custom resolution {}x{} => {}x{}",
|
||||
params->BackBufferWidth, params->BackBufferHeight,
|
||||
GRAPHICS_FS_CUSTOM_RESOLUTION.value().first,
|
||||
GRAPHICS_FS_CUSTOM_RESOLUTION.value().second);
|
||||
params->BackBufferWidth = GRAPHICS_FS_CUSTOM_RESOLUTION.value().first;
|
||||
params->BackBufferHeight = GRAPHICS_FS_CUSTOM_RESOLUTION.value().second;
|
||||
} else if (GRAPHICS_FS_ORIENTATION_SWAP) {
|
||||
log_misc(
|
||||
"graphics::d3d9",
|
||||
"swap full orientation {}x{} => {}x{}",
|
||||
params->BackBufferWidth, params->BackBufferHeight,
|
||||
params->BackBufferHeight, params->BackBufferWidth);
|
||||
std::swap(params->BackBufferWidth, params->BackBufferHeight);
|
||||
}
|
||||
}
|
||||
|
||||
log_info("graphics::d3d9",
|
||||
"D3D9 presentation parameters for adapter {}: BackBufferWidth: {}, BackBufferHeight: {}, "
|
||||
@@ -648,6 +729,7 @@ HRESULT STDMETHODCALLTYPE WrappedIDirect3D9::CreateDevice(
|
||||
if (GRAPHICS_WINDOWED) {
|
||||
pPresentationParameters->Windowed = true;
|
||||
pPresentationParameters->FullScreen_RefreshRateInHz = 0;
|
||||
update_backbuffer_dimensions(pPresentationParameters);
|
||||
|
||||
} else if (GRAPHICS_FORCE_REFRESH > 0) {
|
||||
log_info("graphics::d3d9", "force refresh rate: {} => {} Hz (-graphics-force-refresh option)",
|
||||
@@ -672,6 +754,13 @@ HRESULT STDMETHODCALLTYPE WrappedIDirect3D9::CreateDevice(
|
||||
BehaviorFlags &= ~D3DCREATE_ADAPTERGROUP_DEVICE;
|
||||
}
|
||||
|
||||
if (GRAPHICS_FORCE_VSYNC_BUFFER.has_value()) {
|
||||
log_info("graphics::d3d9", "force BackBufferCount: {} => {}",
|
||||
pPresentationParameters->BackBufferCount,
|
||||
GRAPHICS_FORCE_VSYNC_BUFFER.value());
|
||||
pPresentationParameters->BackBufferCount = GRAPHICS_FORCE_VSYNC_BUFFER.value();
|
||||
}
|
||||
|
||||
// call original
|
||||
HRESULT ret = this->pReal->CreateDevice(
|
||||
Adapter,
|
||||
@@ -686,7 +775,7 @@ HRESULT STDMETHODCALLTYPE WrappedIDirect3D9::CreateDevice(
|
||||
|
||||
// log error
|
||||
log_info("graphics::d3d9", "IDirect3D9::CreateDevice failed, hr={}", FMT_HRESULT(ret));
|
||||
} else {
|
||||
} else if (!D3D9_DEVICE_HOOK_DISABLE) {
|
||||
graphics_hook_window(hFocusWindow, pPresentationParameters);
|
||||
|
||||
*ppReturnedDeviceInterface = new WrappedIDirect3DDevice9(hFocusWindow, *ppReturnedDeviceInterface);
|
||||
@@ -736,7 +825,13 @@ HRESULT STDMETHODCALLTYPE WrappedIDirect3D9::CreateDeviceEx(
|
||||
{
|
||||
assert(is_d3d9ex);
|
||||
|
||||
log_misc("graphics::d3d9", "IDirect3D9Ex::CreateDeviceEx hook hit");
|
||||
log_misc("graphics::d3d9", "IDirect3D9Ex::CreateDeviceEx hook hit ({}, {}, {}, {}, {}, {})",
|
||||
Adapter,
|
||||
DeviceType,
|
||||
fmt::ptr(hFocusWindow),
|
||||
behavior2s(BehaviorFlags),
|
||||
fmt::ptr(pPresentationParameters),
|
||||
fmt::ptr(ppReturnedDeviceInterface));
|
||||
|
||||
// check parameters
|
||||
if (!pPresentationParameters || !ppReturnedDeviceInterface) {
|
||||
@@ -773,10 +868,32 @@ HRESULT STDMETHODCALLTYPE WrappedIDirect3D9::CreateDeviceEx(
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < num_adapters; i++) {
|
||||
const auto *params = &pPresentationParameters[i];
|
||||
auto *params = &pPresentationParameters[i];
|
||||
if (!GRAPHICS_WINDOWED && i == 0) {
|
||||
GRAPHICS_FS_ORIGINAL_WIDTH = params->BackBufferWidth;
|
||||
GRAPHICS_FS_ORIGINAL_HEIGHT = params->BackBufferHeight;
|
||||
log_misc("graphics::d3d9", "original resolution: {}x{}", GRAPHICS_FS_ORIGINAL_WIDTH, GRAPHICS_FS_ORIGINAL_HEIGHT);
|
||||
if (GRAPHICS_FS_CUSTOM_RESOLUTION.has_value()) {
|
||||
log_misc(
|
||||
"graphics::d3d9",
|
||||
"use custom resolution {}x{} => {}x{}",
|
||||
params->BackBufferWidth, params->BackBufferHeight,
|
||||
GRAPHICS_FS_CUSTOM_RESOLUTION.value().first,
|
||||
GRAPHICS_FS_CUSTOM_RESOLUTION.value().second);
|
||||
params->BackBufferWidth = GRAPHICS_FS_CUSTOM_RESOLUTION.value().first;
|
||||
params->BackBufferHeight = GRAPHICS_FS_CUSTOM_RESOLUTION.value().second;
|
||||
} else if (GRAPHICS_FS_ORIENTATION_SWAP) {
|
||||
log_misc(
|
||||
"graphics::d3d9",
|
||||
"swap full orientation {}x{} => {}x{}",
|
||||
params->BackBufferWidth, params->BackBufferHeight,
|
||||
params->BackBufferHeight, params->BackBufferWidth);
|
||||
std::swap(params->BackBufferWidth, params->BackBufferHeight);
|
||||
}
|
||||
}
|
||||
|
||||
log_info("graphics::d3d9",
|
||||
"D3D9 presentation parameters for adapter {}: BackBufferWidth: {}, BackBufferHeight: {}, "
|
||||
"D3D9Ex presentation parameters for adapter {}: BackBufferWidth: {}, BackBufferHeight: {}, "
|
||||
"Format: {}, BackBufferCount: {}, MultiSampleType: {}, MultiSampleQuality: {}, "
|
||||
"SwapEffect: {}, Windowed: {}, EnableAutoDepthStencil: {}, AutoDepthStencilFormat: {}, "
|
||||
"Flags: {}, FullScreen_RefreshRateInHz: {}, PresentationInterval: {}",
|
||||
@@ -797,7 +914,16 @@ HRESULT STDMETHODCALLTYPE WrappedIDirect3D9::CreateDeviceEx(
|
||||
}
|
||||
if (pFullscreenDisplayMode) {
|
||||
for (size_t i = 0; i < num_adapters; i++) {
|
||||
const auto *fullscreen_display_mode = &pFullscreenDisplayMode[i];
|
||||
auto *fullscreen_display_mode = &pFullscreenDisplayMode[i];
|
||||
|
||||
if (!GRAPHICS_WINDOWED && i == 0) {
|
||||
if (GRAPHICS_FS_CUSTOM_RESOLUTION.has_value()) {
|
||||
fullscreen_display_mode->Width = GRAPHICS_FS_CUSTOM_RESOLUTION.value().first;
|
||||
fullscreen_display_mode->Height = GRAPHICS_FS_CUSTOM_RESOLUTION.value().second;
|
||||
} else if (GRAPHICS_FS_ORIENTATION_SWAP) {
|
||||
std::swap(fullscreen_display_mode->Width, fullscreen_display_mode->Height);
|
||||
}
|
||||
}
|
||||
|
||||
log_info("graphics::d3d9",
|
||||
"D3D9Ex fullscreen display mode for adapter {}: Width: {}, Height: {}, RefreshRate: {}, "
|
||||
@@ -824,6 +950,7 @@ HRESULT STDMETHODCALLTYPE WrappedIDirect3D9::CreateDeviceEx(
|
||||
|
||||
pPresentationParameters->Windowed = true;
|
||||
pPresentationParameters->FullScreen_RefreshRateInHz = 0;
|
||||
update_backbuffer_dimensions(pPresentationParameters);
|
||||
pFullscreenDisplayMode = nullptr;
|
||||
} else if (GRAPHICS_FORCE_REFRESH > 0) {
|
||||
log_info("graphics::d3d9", "force refresh rate: {} => {} Hz",
|
||||
@@ -845,6 +972,13 @@ HRESULT STDMETHODCALLTYPE WrappedIDirect3D9::CreateDeviceEx(
|
||||
BehaviorFlags &= ~D3DCREATE_ADAPTERGROUP_DEVICE;
|
||||
}
|
||||
|
||||
if (GRAPHICS_FORCE_VSYNC_BUFFER.has_value()) {
|
||||
log_info("graphics::d3d9", "force BackBufferCount: {} => {}",
|
||||
pPresentationParameters->BackBufferCount,
|
||||
GRAPHICS_FORCE_VSYNC_BUFFER.value());
|
||||
pPresentationParameters->BackBufferCount = GRAPHICS_FORCE_VSYNC_BUFFER.value();
|
||||
}
|
||||
|
||||
// call original
|
||||
HRESULT result = static_cast<IDirect3D9Ex *>(this->pReal)->CreateDeviceEx(
|
||||
Adapter,
|
||||
@@ -860,7 +994,7 @@ HRESULT STDMETHODCALLTYPE WrappedIDirect3D9::CreateDeviceEx(
|
||||
|
||||
// log error
|
||||
log_warning("graphics::d3d9", "CreateDeviceEx failed, hr={}", FMT_HRESULT(result));
|
||||
} else {
|
||||
} else if (!D3D9_DEVICE_HOOK_DISABLE) {
|
||||
graphics_hook_window(hFocusWindow, pPresentationParameters);
|
||||
|
||||
*ppReturnedDeviceInterface = new WrappedIDirect3DDevice9(hFocusWindow, *ppReturnedDeviceInterface);
|
||||
@@ -950,13 +1084,11 @@ IDirect3DSurface9 *graphics_d3d9_ldj_get_sub_screen() {
|
||||
static void graphics_d3d9_ldj_on_present(IDirect3DDevice9 *wrapped_device) {
|
||||
if (!ATTEMPTED_SUB_SWAP_CHAIN_ACQUIRE && SUB_SWAP_CHAIN == nullptr) {
|
||||
ATTEMPTED_SUB_SWAP_CHAIN_ACQUIRE = true;
|
||||
|
||||
HRESULT hr = wrapped_device->GetSwapChain(1, &SUB_SWAP_CHAIN);
|
||||
if (FAILED(hr)) {
|
||||
log_warning(
|
||||
"graphics::d3d9",
|
||||
"failed to acquire sub screeen swap chain; "
|
||||
"this can be ignored if game does not have sub screen, hr={}",
|
||||
"failed to acquire sub screeen swap chain! hr={}",
|
||||
FMT_HRESULT(hr));
|
||||
return;
|
||||
}
|
||||
@@ -1160,7 +1292,12 @@ void graphics_d3d9_on_present(
|
||||
device->EndScene();
|
||||
}
|
||||
|
||||
if (avs::game::is_model({"LDJ", "KFC"})) {
|
||||
// for IIDX TDJ / SDVX UFC, handle subscreen
|
||||
const bool is_vm =
|
||||
avs::game::is_model("KFC") &&
|
||||
(avs::game::SPEC[0] == 'G' || avs::game::SPEC[0] == 'H');
|
||||
const bool is_tdj = avs::game::is_model("LDJ") && games::iidx::TDJ_MODE;
|
||||
if (is_vm || is_tdj) {
|
||||
graphics_d3d9_ldj_on_present(wrapped_device);
|
||||
}
|
||||
|
||||
@@ -1282,3 +1419,39 @@ void graphics_d3d9_on_present(
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void update_backbuffer_dimensions(D3DPRESENT_PARAMETERS *params) {
|
||||
if (!GRAPHICS_WINDOW_BACKBUFFER_SCALE) {
|
||||
return;
|
||||
}
|
||||
|
||||
// only do this for primary adapter
|
||||
static bool first_adapter_hooked = false;
|
||||
if (first_adapter_hooked) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (GRAPHICS_WINDOW_SIZE.has_value()) {
|
||||
first_adapter_hooked = true;
|
||||
params->BackBufferWidth = GRAPHICS_WINDOW_SIZE.value().first;
|
||||
params->BackBufferHeight = GRAPHICS_WINDOW_SIZE.value().second;
|
||||
log_info(
|
||||
"graphics::d3d9",
|
||||
"override BackBufferWidth / BackBufferHeight with {}x{} (from -windowresize)",
|
||||
params->BackBufferWidth,
|
||||
params->BackBufferHeight);
|
||||
return;
|
||||
}
|
||||
|
||||
if (cfg::SCREENRESIZE->client_width != 0 && cfg::SCREENRESIZE->client_height != 0) {
|
||||
first_adapter_hooked = true;
|
||||
params->BackBufferWidth = cfg::SCREENRESIZE->client_width;
|
||||
params->BackBufferHeight = cfg::SCREENRESIZE->client_height;
|
||||
log_info(
|
||||
"graphics::d3d9",
|
||||
"override BackBufferWidth / BackBufferHeight with {}x{} (from screen resize config file)",
|
||||
params->BackBufferWidth,
|
||||
params->BackBufferHeight);
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -7,6 +7,7 @@
|
||||
#include "hooks/graphics/graphics.h"
|
||||
#include "overlay/overlay.h"
|
||||
#include "util/flags_helper.h"
|
||||
#include "util/utils.h"
|
||||
#include "cfg/screen_resize.h"
|
||||
|
||||
#include "d3d9_backend.h"
|
||||
@@ -692,88 +693,181 @@ static IDirect3DSurface9 *backbuffer = nullptr;
|
||||
static LPDIRECT3DSWAPCHAIN9 mSwapChain = nullptr;
|
||||
static IDirect3DTexture9* tex;
|
||||
|
||||
static UINT topSurface_width = 0;
|
||||
static UINT topSurface_height = 0;
|
||||
static float topSurface_aspect_ratio = 1.f;
|
||||
|
||||
void SurfaceHook(IDirect3DDevice9 *pReal) {
|
||||
// log_misc("graphics::d3d9", "SurfaceHook called");
|
||||
D3DPRESENT_PARAMETERS param {};
|
||||
|
||||
pReal->GetSwapChain(0, &mSwapChain);
|
||||
mSwapChain->GetPresentParameters(¶m);
|
||||
|
||||
// phase 0 - create a surface that has the same aspect ratio as the original back buffer
|
||||
// but larger in size. this is needed to ensure that when the user zooms out, we have
|
||||
// sufficient buffer space (black border) around the original image
|
||||
//
|
||||
// (only done once)
|
||||
if (!topSurface) {
|
||||
if (pReal->CreateTexture(4096, 4096, 1,
|
||||
topSurface_width = param.BackBufferWidth * 3;
|
||||
topSurface_height = param.BackBufferHeight * 3;
|
||||
topSurface_aspect_ratio = (float)topSurface_width / (float)topSurface_height;
|
||||
if (pReal->CreateTexture(topSurface_width, topSurface_height, 1,
|
||||
D3DUSAGE_RENDERTARGET, D3DFMT_A8R8G8B8,
|
||||
D3DPOOL_DEFAULT, &tex, NULL) != D3D_OK) {
|
||||
log_misc("graphics::d3d9", "create texture failed");
|
||||
|
||||
log_warning("graphics::d3d9", "SurfaceHook - create texture failed");
|
||||
}
|
||||
log_misc("graphics::d3d9", "Backbuffer: {} {} {}",
|
||||
param.BackBufferWidth, param.BackBufferHeight, param.BackBufferCount);
|
||||
tex->GetSurfaceLevel(0, &topSurface);
|
||||
|
||||
log_misc(
|
||||
"graphics::d3d9", "SurfaceHook - Backbuffer: {} {} {}",
|
||||
param.BackBufferWidth, param.BackBufferHeight, param.BackBufferCount);
|
||||
|
||||
tex->GetSurfaceLevel(0, &topSurface);
|
||||
if (mSwapChain->GetBackBuffer(0, D3DBACKBUFFER_TYPE_MONO, &backbuffer) != D3D_OK) {
|
||||
log_misc("graphics::d3d9", "GetBackBuffer failed");
|
||||
log_warning("graphics::d3d9", "SurfaceHook - GetBackBuffer failed");
|
||||
}
|
||||
}
|
||||
|
||||
const int rectLeft = 1024;
|
||||
const int rectTop = 576;
|
||||
// pre-calculate dimensions used for phase 1
|
||||
const int rectLeft = param.BackBufferWidth;
|
||||
const int rectTop = param.BackBufferHeight;
|
||||
const int w = param.BackBufferWidth;
|
||||
const int h = param.BackBufferHeight;
|
||||
|
||||
D3DLOCKED_RECT rect;
|
||||
HRESULT hr;
|
||||
topSurface->LockRect(&rect, NULL, D3DLOCK_DONOTWAIT);
|
||||
|
||||
// phase 1 - copy the original back buffer onto the new surface.
|
||||
// we draw it 1:1 in the center of the surface.
|
||||
// if orientation is swapped, fix up the rect and draw it in the center.
|
||||
//
|
||||
// for this phase we always render with the same rect so that it's always overwritten
|
||||
// by a new frame on the same spot.
|
||||
RECT originRect {
|
||||
rectLeft,
|
||||
rectTop,
|
||||
(LONG)(rectLeft + w),
|
||||
(LONG)(rectTop + h),
|
||||
};
|
||||
if (GRAPHICS_FS_ORIENTATION_SWAP) {
|
||||
originRect.left = (topSurface_width / 2) - (GRAPHICS_FS_ORIGINAL_WIDTH / 2);
|
||||
originRect.top = (topSurface_height / 2) - (GRAPHICS_FS_ORIGINAL_HEIGHT / 2);
|
||||
originRect.right = originRect.left + GRAPHICS_FS_ORIGINAL_WIDTH;
|
||||
originRect.bottom = originRect.top + GRAPHICS_FS_ORIGINAL_HEIGHT;
|
||||
}
|
||||
|
||||
// log_misc(
|
||||
// "graphics::d3d9", "originRect: {} {} {} {}",
|
||||
// originRect.left, originRect.top, originRect.right, originRect.bottom);
|
||||
|
||||
hr = pReal->StretchRect(
|
||||
backbuffer, nullptr,
|
||||
topSurface, &originRect,
|
||||
D3DTEXF_LINEAR);
|
||||
if (hr != D3D_OK) {
|
||||
log_warning(
|
||||
"graphics::d3d9",
|
||||
"SurfaceHook - StretchRect backbuffer failed, rect: {} {} {} {}",
|
||||
originRect.left, originRect.top, originRect.right, originRect.bottom);
|
||||
}
|
||||
topSurface->UnlockRect();
|
||||
|
||||
// phase 2 - viewport calculation - do the actual zoom / offset math
|
||||
// figure out what region of the surface to copy back to the back buffer.
|
||||
RECT targetRect {
|
||||
rectLeft,
|
||||
rectTop,
|
||||
(LONG)(rectLeft + w),
|
||||
(LONG)(rectTop + h),
|
||||
};
|
||||
if (cfg::SCREENRESIZE->enable_screen_resize) {
|
||||
auto& scene = cfg::SCREENRESIZE->scene_settings[cfg::SCREENRESIZE->screen_resize_current_scene];
|
||||
auto w_new = w / scene.scale_x;
|
||||
auto h_new = h / scene.scale_y;
|
||||
|
||||
// stretch to add top/left offset to avoid going negative
|
||||
topSurface->LockRect(&rect, NULL, D3DLOCK_DONOTWAIT);
|
||||
auto hr = pReal->StretchRect(
|
||||
backbuffer, nullptr,
|
||||
topSurface, &targetRect,
|
||||
D3DTEXF_LINEAR);
|
||||
// make sure the scaling is within bounds
|
||||
if (cfg::SCREENRESIZE->client_keep_aspect_ratio) {
|
||||
if (w_new > topSurface_width || h_new > topSurface_height) {
|
||||
w_new = topSurface_width;
|
||||
h_new = topSurface_height;
|
||||
}
|
||||
} else {
|
||||
w_new = MIN(w_new, topSurface_width);
|
||||
h_new = MIN(h_new, topSurface_height);
|
||||
}
|
||||
|
||||
if (hr != D3D_OK) {
|
||||
log_misc("graphics::d3d9", "StretchRect backbuffer failed");
|
||||
}
|
||||
topSurface->UnlockRect();
|
||||
targetRect.left = (topSurface_width / 2) - (w_new / 2) - scene.offset_x;
|
||||
targetRect.top = (topSurface_height / 2) - (h_new / 2) - scene.offset_y;
|
||||
targetRect.right = targetRect.left + w_new;
|
||||
targetRect.bottom = targetRect.top + h_new;
|
||||
|
||||
// do the actual zoom / offset math
|
||||
if (cfg::SCREENRESIZE->centered) {
|
||||
targetRect.right = (w + rectLeft) / cfg::SCREENRESIZE->scale_x;
|
||||
targetRect.bottom = (h + rectTop) / cfg::SCREENRESIZE->scale_y;
|
||||
const LONG deltaH = ((targetRect.bottom - targetRect.top) - h) / 2;
|
||||
const LONG deltaW = ((targetRect.right - targetRect.left) - w) / 2;
|
||||
targetRect.top -= deltaH;
|
||||
targetRect.bottom -= deltaH;
|
||||
targetRect.left -= deltaW;
|
||||
targetRect.right -= deltaW;
|
||||
} else {
|
||||
targetRect.left -= cfg::SCREENRESIZE->offset_x;
|
||||
targetRect.top += cfg::SCREENRESIZE->offset_y;
|
||||
targetRect.right = -cfg::SCREENRESIZE->offset_x;
|
||||
targetRect.right += (w + rectLeft) / cfg::SCREENRESIZE->scale_x;
|
||||
targetRect.bottom = cfg::SCREENRESIZE->offset_y;
|
||||
targetRect.bottom += (h + rectTop) / cfg::SCREENRESIZE->scale_y;
|
||||
// boundary check to prevent StretchRect failures
|
||||
if (targetRect.left < 0) {
|
||||
targetRect.left = 0;
|
||||
targetRect.right = w_new;
|
||||
} else if (targetRect.right > (LONG)topSurface_width) {
|
||||
targetRect.left = topSurface_width - w_new;
|
||||
targetRect.right = topSurface_width;
|
||||
}
|
||||
if (targetRect.top < 0) {
|
||||
targetRect.top = 0;
|
||||
targetRect.bottom = h_new;
|
||||
} else if (targetRect.bottom > (LONG)topSurface_height) {
|
||||
targetRect.top = topSurface_height - h_new;
|
||||
targetRect.bottom = topSurface_height;
|
||||
}
|
||||
|
||||
} else if (GRAPHICS_FS_ORIENTATION_SWAP) {
|
||||
// increase the viewport to fit the image on screen (effectively, zoom out a little)
|
||||
if (GRAPHICS_FS_ORIGINAL_WIDTH < GRAPHICS_FS_ORIGINAL_HEIGHT) {
|
||||
// portrait game on landscape display
|
||||
// height should match the original image
|
||||
targetRect.top = originRect.top;
|
||||
targetRect.bottom = originRect.bottom;
|
||||
|
||||
// width of viewport should be wider, effectively shrinking the image on x-axis when rendered
|
||||
const auto w_new = GRAPHICS_FS_ORIGINAL_HEIGHT * topSurface_aspect_ratio;
|
||||
targetRect.left = (topSurface_width / 2) - (w_new / 2);
|
||||
targetRect.right = targetRect.left + w_new;
|
||||
} else {
|
||||
// landscape game on portrait display
|
||||
targetRect.left = originRect.left;
|
||||
targetRect.right = originRect.right;
|
||||
|
||||
// height of viewport should be taller, effectively shrinking the image on y-axis when rendered
|
||||
const auto h_new = GRAPHICS_FS_ORIGINAL_WIDTH / topSurface_aspect_ratio;
|
||||
targetRect.top = (topSurface_height / 2) - (h_new / 2);
|
||||
targetRect.bottom = targetRect.top + h_new;
|
||||
}
|
||||
}
|
||||
|
||||
// draw to back buffer
|
||||
// log_misc("graphics::d3d9", "targetRect: {} {} {} {}",
|
||||
// targetRect.left, targetRect.top, targetRect.right, targetRect.bottom);
|
||||
|
||||
// phase 3 - draw the surface to back buffer
|
||||
backbuffer->LockRect(&rect, NULL, D3DLOCK_DONOTWAIT);
|
||||
bool use_linear_filter = true;
|
||||
if (cfg::SCREENRESIZE->enable_screen_resize) {
|
||||
use_linear_filter = cfg::SCREENRESIZE->enable_linear_filter;
|
||||
}
|
||||
hr = pReal->StretchRect(
|
||||
topSurface, &targetRect,
|
||||
backbuffer, nullptr,
|
||||
cfg::SCREENRESIZE->enable_linear_filter ? D3DTEXF_LINEAR : D3DTEXF_NONE);
|
||||
use_linear_filter ? D3DTEXF_LINEAR : D3DTEXF_NONE);
|
||||
backbuffer->UnlockRect();
|
||||
if (hr != D3D_OK) {
|
||||
log_misc("graphics::d3d9", "StretchRect targetRect failed");
|
||||
log_warning(
|
||||
"graphics::d3d9",
|
||||
"SurfaceHook - StretchRect targetRect failed, you must reset image scaling to default values! rect: {} {} {} {}",
|
||||
targetRect.left, targetRect.top, targetRect.right, targetRect.bottom);
|
||||
}
|
||||
}
|
||||
|
||||
HRESULT STDMETHODCALLTYPE WrappedIDirect3DDevice9::EndScene() {
|
||||
WRAP_DEBUG;
|
||||
|
||||
if (cfg::SCREENRESIZE->enable_screen_resize) {
|
||||
if (cfg::SCREENRESIZE->enable_screen_resize || GRAPHICS_FS_ORIENTATION_SWAP) {
|
||||
SurfaceHook(pReal);
|
||||
}
|
||||
|
||||
|
||||
+198
-68
@@ -11,16 +11,19 @@
|
||||
#include "cfg/icon.h"
|
||||
#include "cfg/screen_resize.h"
|
||||
#include "games/ddr/ddr.h"
|
||||
#include "games/iidx/iidx.h"
|
||||
#include "hooks/graphics/backends/d3d9/d3d9_backend.h"
|
||||
#include "launcher/shutdown.h"
|
||||
#include "overlay/overlay.h"
|
||||
#include "touch/touch.h"
|
||||
#include "touch/touch_indicators.h"
|
||||
#include "util/detour.h"
|
||||
#include "util/logging.h"
|
||||
#include "util/fileutils.h"
|
||||
#include "util/utils.h"
|
||||
#include "misc/wintouchemu.h"
|
||||
#include "util/time.h"
|
||||
#include "rawinput/rawinput.h"
|
||||
|
||||
struct CaptureData {
|
||||
std::shared_ptr<uint8_t[]> data;
|
||||
@@ -28,11 +31,15 @@ struct CaptureData {
|
||||
uint64_t timestamp;
|
||||
};
|
||||
|
||||
HWND TDJ_SUBSCREEN_WINDOW = nullptr;
|
||||
HWND SDVX_SUBSCREEN_WINDOW = nullptr;
|
||||
|
||||
// icon
|
||||
static HICON WINDOW_ICON = LoadIcon(GetModuleHandle(nullptr), MAKEINTRESOURCE(MAINICON));
|
||||
|
||||
// state
|
||||
static WNDPROC WNDPROC_ORIG = nullptr;
|
||||
static WNDPROC WSUB_WNDPROC_ORIG = nullptr;
|
||||
static std::vector<WNDPROC> WNDPROC_CUSTOM {};
|
||||
static bool GRAPHICS_SCREENSHOT_TRIGGER = false;
|
||||
static std::set<int> GRAPHICS_SCREENS { 0 };
|
||||
@@ -53,9 +60,17 @@ graphics_orientation GRAPHICS_ADJUST_ORIENTATION = ORIENTATION_NORMAL;
|
||||
bool GRAPHICS_WINDOWED = false;
|
||||
std::vector<HWND> GRAPHICS_WINDOWS;
|
||||
UINT GRAPHICS_FORCE_REFRESH = 0;
|
||||
std::optional<int> GRAPHICS_FORCE_VSYNC_BUFFER;
|
||||
bool GRAPHICS_FORCE_SINGLE_ADAPTER = false;
|
||||
bool GRAPHICS_9_ON_12 = false;
|
||||
bool GRAPHICS_PREVENT_SECONDARY_WINDOW = false;
|
||||
graphics_dx9on12_state GRAPHICS_9_ON_12_STATE = DX9ON12_AUTO;
|
||||
bool GRAPHICS_9_ON_12_REQUESTED_BY_GAME = false;
|
||||
bool SUBSCREEN_FORCE_REDRAW = false;
|
||||
bool D3D9_DEVICE_HOOK_DISABLE = false;
|
||||
std::optional<std::pair<uint32_t, uint32_t>> GRAPHICS_FS_CUSTOM_RESOLUTION;
|
||||
bool GRAPHICS_FS_ORIENTATION_SWAP = false;
|
||||
uint32_t GRAPHICS_FS_ORIGINAL_WIDTH = 0;
|
||||
uint32_t GRAPHICS_FS_ORIGINAL_HEIGHT = 0;
|
||||
|
||||
// settings
|
||||
std::string GRAPHICS_DEVICEID = "PCI\\VEN_1002&DEV_7146";
|
||||
@@ -154,26 +169,44 @@ static LRESULT CALLBACK WindowProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM l
|
||||
|
||||
// capture mouse
|
||||
if (GRAPHICS_CAPTURE_CURSOR) {
|
||||
bool free_cursor = false;
|
||||
bool capture_cursor = false;
|
||||
bool early_return = false;
|
||||
switch (uMsg) {
|
||||
case WM_SETFOCUS: {
|
||||
|
||||
// capture mouse
|
||||
RECT WINDOW_RECT;
|
||||
GetWindowRect(hWnd, &WINDOW_RECT);
|
||||
ClipCursor(&WINDOW_RECT);
|
||||
|
||||
return true;
|
||||
}
|
||||
case WM_KILLFOCUS: {
|
||||
|
||||
// free mouse
|
||||
ClipCursor(0);
|
||||
|
||||
return true;
|
||||
}
|
||||
case WM_SETFOCUS:
|
||||
capture_cursor = true;
|
||||
early_return = true;
|
||||
break;
|
||||
case WM_KILLFOCUS:
|
||||
free_cursor = true;
|
||||
early_return = true;
|
||||
break;
|
||||
case WM_WINDOWPOSCHANGED:
|
||||
// known issue: dragging with the title bar results in WM_WINDOWPOSCHANGED
|
||||
// getting called, which calls ClipCursor successfully, but doesn't actually
|
||||
// confine the cursor for some reason; seems like odd Windows behavior
|
||||
// (can be fixed if focus is shifted to another window and then back to the game
|
||||
// window)
|
||||
if (hWnd == GetActiveWindow()) {
|
||||
capture_cursor = true;
|
||||
} else {
|
||||
free_cursor = true;
|
||||
}
|
||||
// do not return early; may result in WM_SIZE / WM_MOVE no longer being called
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
if (free_cursor) {
|
||||
ClipCursor(nullptr);
|
||||
} else if (capture_cursor) {
|
||||
RECT WINDOW_RECT;
|
||||
GetWindowRect(hWnd, &WINDOW_RECT);
|
||||
ClipCursor(&WINDOW_RECT);
|
||||
}
|
||||
if (early_return) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// drop keydown messages
|
||||
@@ -187,47 +220,44 @@ static LRESULT CALLBACK WindowProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM l
|
||||
break;
|
||||
}
|
||||
|
||||
// update the size of an emulated touch window is the window changes size or moves
|
||||
if (SPICETOUCH_TOUCH_HWND) {
|
||||
switch (uMsg) {
|
||||
case WM_MOVE:
|
||||
case WM_SIZE: {
|
||||
// get window rect
|
||||
RECT windowRect {};
|
||||
GetWindowRect(hWnd, &windowRect);
|
||||
|
||||
// adjust to client area
|
||||
POINT p {};
|
||||
ClientToScreen(hWnd, &p);
|
||||
windowRect.top = p.y;
|
||||
|
||||
int new_x = windowRect.left;
|
||||
int new_y = windowRect.top;
|
||||
int new_width = windowRect.right - new_x;
|
||||
int new_height = windowRect.bottom - new_y;
|
||||
|
||||
log_misc("graphics", "detected window change ({}x{} @ {}, {} -> {}x{} @ {}, {}), updating touch window to match",
|
||||
SPICETOUCH_TOUCH_WIDTH, SPICETOUCH_TOUCH_HEIGHT, SPICETOUCH_TOUCH_X, SPICETOUCH_TOUCH_Y,
|
||||
new_width, new_height, new_x, new_y);
|
||||
|
||||
SPICETOUCH_TOUCH_X = new_x;
|
||||
SPICETOUCH_TOUCH_Y = new_y;
|
||||
SPICETOUCH_TOUCH_WIDTH = new_width;
|
||||
SPICETOUCH_TOUCH_HEIGHT = new_height;
|
||||
SetWindowPos(SPICETOUCH_TOUCH_HWND, HWND_TOP,
|
||||
new_x, new_y,
|
||||
new_width, new_height,
|
||||
SWP_NOZORDER | SWP_NOREDRAW | SWP_NOREPOSITION | SWP_NOACTIVATE);
|
||||
switch (uMsg) {
|
||||
case WM_MOVE:
|
||||
case WM_SIZE: {
|
||||
// Update SPICETOUCH space when the main window changes size or moves.
|
||||
// The update happens regardless of whether the "fake" spicetouch window is present or not.
|
||||
// This allows touches received on subscreen window to be translated correctly.
|
||||
update_spicetouch_window_dimensions(hWnd);
|
||||
// log_misc(
|
||||
// "graphics", "detected window change ({}x{} @ {}, {}), updating touch coord-space to match",
|
||||
// SPICETOUCH_TOUCH_WIDTH, SPICETOUCH_TOUCH_HEIGHT, SPICETOUCH_TOUCH_X, SPICETOUCH_TOUCH_Y);
|
||||
|
||||
// Update SPICETOUCH window if present
|
||||
if (SPICETOUCH_TOUCH_HWND) {
|
||||
SetWindowPos(
|
||||
SPICETOUCH_TOUCH_HWND, HWND_TOP,
|
||||
SPICETOUCH_TOUCH_X, SPICETOUCH_TOUCH_Y,
|
||||
SPICETOUCH_TOUCH_WIDTH, SPICETOUCH_TOUCH_HEIGHT,
|
||||
SWP_NOZORDER | SWP_NOREDRAW | SWP_NOREPOSITION | SWP_NOACTIVATE);
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
// call default
|
||||
return CallWindowProcA(WNDPROC_ORIG, hWnd, uMsg, wParam, lParam);
|
||||
}
|
||||
|
||||
// window procedure for subscreen
|
||||
// this might be replaced by spicetouch hook later
|
||||
static LRESULT CALLBACK WsubWindowProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) {
|
||||
if (uMsg == WM_CLOSE) {
|
||||
log_misc("graphics", "ignore WM_CLOSE for subscreen window");
|
||||
return false;
|
||||
}
|
||||
return CallWindowProcA(WSUB_WNDPROC_ORIG, hWnd, uMsg, wParam, lParam);
|
||||
}
|
||||
|
||||
static LONG WINAPI ChangeDisplaySettingsA_hook(DEVMODEA *lpDevMode, DWORD dwflags) {
|
||||
log_misc("graphics", "ChangeDisplaySettingsA hook hit");
|
||||
|
||||
@@ -271,10 +301,12 @@ static HWND WINAPI CreateWindowExA_hook(DWORD dwExStyle, LPCSTR lpClassName, LPC
|
||||
HWND hWndParent, HMENU hMenu, HINSTANCE hInstance,
|
||||
LPVOID lpParam)
|
||||
{
|
||||
const std::string window_name(lpWindowName != nullptr ? lpWindowName : "(null)");
|
||||
|
||||
log_misc("graphics", "CreateWindowExA hook hit (0x{:08x}, {}, {}, 0x{:08x}, {}, {}, {}, {}, {}, {}, {}, {})",
|
||||
dwExStyle,
|
||||
fmt::ptr(lpClassName),
|
||||
lpWindowName != nullptr ? lpWindowName : "(null)",
|
||||
window_name,
|
||||
dwStyle,
|
||||
x,
|
||||
y,
|
||||
@@ -285,43 +317,86 @@ static HWND WINAPI CreateWindowExA_hook(DWORD dwExStyle, LPCSTR lpClassName, LPC
|
||||
fmt::ptr(hInstance),
|
||||
fmt::ptr(lpParam));
|
||||
|
||||
// set display orientation
|
||||
// only do this when the target window is portrait
|
||||
// set display orientation and/or refresh rate
|
||||
// only set orientation when the target window is portrait
|
||||
// (avoid doing this for SDVX subscreen which is in landscape, for example)
|
||||
if ((nHeight > nWidth) &&
|
||||
const auto adjust_orientation =
|
||||
(GRAPHICS_ADJUST_ORIENTATION == ORIENTATION_CW ||
|
||||
GRAPHICS_ADJUST_ORIENTATION == ORIENTATION_CCW)) {
|
||||
DWORD orientation = (GRAPHICS_ADJUST_ORIENTATION == ORIENTATION_CW) ? DMDO_90 : DMDO_270;
|
||||
GRAPHICS_ADJUST_ORIENTATION == ORIENTATION_CCW);
|
||||
|
||||
if ((nHeight > nWidth && adjust_orientation) || GRAPHICS_FORCE_REFRESH > 0) {
|
||||
DEVMODE mode {};
|
||||
|
||||
// get display settings
|
||||
if (EnumDisplaySettings(0, ENUM_CURRENT_SETTINGS, &mode)) {
|
||||
log_misc("graphics", "auto-rotate: rotating display to DMDO_xx mode {}", orientation);
|
||||
mode.dmPelsWidth = nWidth;
|
||||
mode.dmPelsHeight = nHeight;
|
||||
mode.dmDisplayOrientation = orientation;
|
||||
if (EnumDisplaySettings(nullptr, ENUM_CURRENT_SETTINGS, &mode)) {
|
||||
if (adjust_orientation) {
|
||||
DWORD orientation = GRAPHICS_ADJUST_ORIENTATION == ORIENTATION_CW ? DMDO_90 : DMDO_270;
|
||||
log_misc(
|
||||
"graphics",
|
||||
"auto-rotate: call ChangeDisplaySettings and rotate display to DMDO_xx mode {}",
|
||||
orientation);
|
||||
mode.dmPelsWidth = nWidth;
|
||||
mode.dmPelsHeight = nHeight;
|
||||
mode.dmDisplayOrientation = orientation;
|
||||
}
|
||||
|
||||
if (GRAPHICS_FORCE_REFRESH > 0) {
|
||||
log_info(
|
||||
"graphics",
|
||||
"auto-rotate: force refresh rate: {} => {} Hz",
|
||||
"call ChangeDisplaySettings to force refresh rate: {} => {} Hz (-graphics-force-refresh)",
|
||||
mode.dmDisplayFrequency,
|
||||
GRAPHICS_FORCE_REFRESH);
|
||||
|
||||
mode.dmDisplayFrequency = GRAPHICS_FORCE_REFRESH;
|
||||
}
|
||||
auto disp_res = ChangeDisplaySettings(&mode, CDS_FULLSCREEN);
|
||||
|
||||
const auto disp_res = ChangeDisplaySettings(&mode, CDS_FULLSCREEN);
|
||||
if (disp_res != DISP_CHANGE_SUCCESSFUL) {
|
||||
log_warning("graphics", "auto-rotate: failed to change display settings: {}", disp_res);
|
||||
log_warning("graphics", "failed to change display settings: {}", disp_res);
|
||||
}
|
||||
} else {
|
||||
log_warning("graphics", "auto-rotate: failed to get display settings");
|
||||
log_warning("graphics", "failed to get display settings");
|
||||
}
|
||||
}
|
||||
|
||||
// gfdm
|
||||
if (avs::game::is_model({"J32", "J33", "K32", "K33", "L32", "L33", "M32"})) {
|
||||
// set window name
|
||||
if (!lpWindowName) {
|
||||
lpWindowName = "GITADORA";
|
||||
}
|
||||
}
|
||||
|
||||
bool is_tdj_sub_window = avs::game::is_model("LDJ") && window_name.ends_with(" sub");
|
||||
bool is_sdvx_sub_window = avs::game::is_model("KFC") && window_name.ends_with(" Sub Screen");
|
||||
|
||||
// TDJ windowed mode with subscreen: hide maximize button
|
||||
if ((is_tdj_sub_window && GRAPHICS_IIDX_WSUB) || is_sdvx_sub_window) {
|
||||
dwStyle &= ~(WS_MAXIMIZEBOX);
|
||||
}
|
||||
|
||||
// call original
|
||||
HWND result = CreateWindowExA_orig(dwExStyle, lpClassName, lpWindowName, dwStyle, x, y, nWidth, nHeight,
|
||||
hWndParent, hMenu, hInstance, lpParam);
|
||||
GRAPHICS_WINDOWS.push_back(result);
|
||||
|
||||
if (is_tdj_sub_window) {
|
||||
// TDJ windowed mode: remember the subscreen window handle for later
|
||||
TDJ_SUBSCREEN_WINDOW = result;
|
||||
|
||||
// hook for preventing the closing of subscreen window
|
||||
if (GRAPHICS_IIDX_WSUB) {
|
||||
graphics_hook_subscreen_window(TDJ_SUBSCREEN_WINDOW);
|
||||
}
|
||||
}
|
||||
|
||||
// hook for preventing the closing of subscreen window
|
||||
if (is_sdvx_sub_window) {
|
||||
SDVX_SUBSCREEN_WINDOW = result;
|
||||
graphics_hook_subscreen_window(SDVX_SUBSCREEN_WINDOW);
|
||||
}
|
||||
|
||||
disable_touch_indicators(result);
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -387,6 +462,8 @@ static HWND WINAPI CreateWindowExW_hook(DWORD dwExStyle, LPCWSTR lpClassName, LP
|
||||
dwExStyle, lpClassName, lpWindowName, dwStyle, x, y, nWidth, nHeight,
|
||||
hWndParent, hMenu, hInstance, lpParam);
|
||||
GRAPHICS_WINDOWS.push_back(result);
|
||||
|
||||
disable_touch_indicators(result);
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -400,10 +477,17 @@ static BOOL WINAPI EnumDisplayDevicesA_hook(LPCTSTR lpDevice, DWORD iDevNum,
|
||||
// call original
|
||||
BOOL value = EnumDisplayDevicesA_orig(lpDevice, iDevNum, lpDisplayDevice, dwFlags);
|
||||
|
||||
// device ID override
|
||||
if (value) {
|
||||
#ifndef SPICE64
|
||||
// older IIDX games check for hardcoded PCI vendor/device ID pair of GPU
|
||||
if ((avs::game::is_model("JDZ") || avs::game::is_model("KDZ")) && value) {
|
||||
log_info(
|
||||
"graphics",
|
||||
"EnumDisplayDevicesA_hook: swap DeviceID {} with {} (for IIDX 18/19)",
|
||||
lpDisplayDevice->DeviceID,
|
||||
GRAPHICS_DEVICEID.c_str());
|
||||
memcpy(&lpDisplayDevice->DeviceID, GRAPHICS_DEVICEID.c_str(), GRAPHICS_DEVICEID.size() + 1);
|
||||
}
|
||||
#endif
|
||||
|
||||
// return original result
|
||||
return value;
|
||||
@@ -434,6 +518,39 @@ static BOOL WINAPI MoveWindow_hook(HWND hWnd, int X, int Y, int nWidth, int nHei
|
||||
nHeight = rect.bottom - rect.top;
|
||||
}
|
||||
|
||||
// iidx windowed TDJ mode
|
||||
if (GRAPHICS_WINDOWED && TDJ_SUBSCREEN_WINDOW && hWnd == TDJ_SUBSCREEN_WINDOW) {
|
||||
if (GRAPHICS_IIDX_WSUB) {
|
||||
// (Experimental) Show subscreen in windowed mode
|
||||
graphics_load_windowed_subscreen_parameters();
|
||||
|
||||
RECT rect {};
|
||||
DWORD dwStyle;
|
||||
|
||||
dwStyle = GetWindowLongA(hWnd, GWL_STYLE);
|
||||
|
||||
SetRect(&rect, 0, 0, GRAPHICS_IIDX_WSUB_WIDTH, GRAPHICS_IIDX_WSUB_HEIGHT);
|
||||
AdjustWindowRect(&rect, dwStyle, 0);
|
||||
|
||||
X = GRAPHICS_IIDX_WSUB_X;
|
||||
Y = GRAPHICS_IIDX_WSUB_Y;
|
||||
|
||||
nWidth = rect.right - rect.left;
|
||||
nHeight = rect.bottom - rect.top;
|
||||
|
||||
touch_attach_wnd(TDJ_SUBSCREEN_WINDOW);
|
||||
} else {
|
||||
// Existing behaviour: suppress subscreen window and prompt user to use overlay instead
|
||||
log_info(
|
||||
"graphics",
|
||||
"MoveWindow hook - hiding TDJ subscreen window {}; please use subscreen overlay instead (-iidxtdjw)",
|
||||
fmt::ptr(hWnd));
|
||||
SendMessage(hWnd, WM_CLOSE, 0, 0);
|
||||
TDJ_SUBSCREEN_WINDOW = nullptr;
|
||||
return TRUE;
|
||||
}
|
||||
}
|
||||
|
||||
// call original
|
||||
return MoveWindow_orig(hWnd, X, Y, nWidth, nHeight, bRepaint);
|
||||
}
|
||||
@@ -720,7 +837,12 @@ void graphics_hook_window(HWND hWnd, D3DPRESENT_PARAMETERS *pPresentationParamet
|
||||
if (WNDPROC_ORIG == nullptr) {
|
||||
WNDPROC_ORIG = reinterpret_cast<WNDPROC>(GetWindowLongPtrA(hWnd, GWLP_WNDPROC));
|
||||
SetWindowLongPtrA(hWnd, GWLP_WNDPROC, reinterpret_cast<LONG_PTR>(WindowProc));
|
||||
overlay::USE_WM_CHAR_FOR_IMGUI_CHAR_INPUT = true;
|
||||
|
||||
// NOLEGACY causes WM_CHAR to be not received
|
||||
// reflec beat game engine does not pass WM_CHAR through for some reason (unrelated to SpiceTouch)
|
||||
if (!rawinput::NOLEGACY && !(avs::game::is_model({"KBR", "LBR", "MBR"}))) {
|
||||
overlay::USE_WM_CHAR_FOR_IMGUI_CHAR_INPUT = true;
|
||||
}
|
||||
graphics_capture_initial_window(hWnd);
|
||||
}
|
||||
}
|
||||
@@ -737,6 +859,14 @@ void graphics_remove_wnd_proc(WNDPROC wndProc) {
|
||||
}
|
||||
}
|
||||
|
||||
void graphics_hook_subscreen_window(HWND hWnd) {
|
||||
// hook window procedure
|
||||
if (WSUB_WNDPROC_ORIG == nullptr) {
|
||||
WSUB_WNDPROC_ORIG = reinterpret_cast<WNDPROC>(GetWindowLongPtrA(hWnd, GWLP_WNDPROC));
|
||||
SetWindowLongPtrA(hWnd, GWLP_WNDPROC, reinterpret_cast<LONG_PTR>(WsubWindowProc));
|
||||
}
|
||||
}
|
||||
|
||||
void graphics_screens_register(int screen) {
|
||||
std::lock_guard<std::mutex> lock(GRAPHICS_SCREENS_M);
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <optional>
|
||||
#include <cstdint>
|
||||
|
||||
#include <windows.h>
|
||||
#include <d3d9.h>
|
||||
@@ -16,6 +17,12 @@ enum graphics_orientation {
|
||||
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;
|
||||
@@ -25,13 +32,32 @@ extern bool GRAPHICS_WINDOWED;
|
||||
extern graphics_orientation GRAPHICS_ADJUST_ORIENTATION;
|
||||
extern std::vector<HWND> GRAPHICS_WINDOWS;
|
||||
extern UINT GRAPHICS_FORCE_REFRESH;
|
||||
extern std::optional<int> GRAPHICS_FORCE_VSYNC_BUFFER;
|
||||
extern bool GRAPHICS_FORCE_SINGLE_ADAPTER;
|
||||
extern bool GRAPHICS_9_ON_12;
|
||||
extern bool GRAPHICS_PREVENT_SECONDARY_WINDOW;
|
||||
extern std::optional<std::pair<uint32_t, uint32_t>> GRAPHICS_FS_CUSTOM_RESOLUTION;
|
||||
extern bool GRAPHICS_FS_ORIENTATION_SWAP;
|
||||
extern uint32_t GRAPHICS_FS_ORIGINAL_WIDTH;
|
||||
extern uint32_t GRAPHICS_FS_ORIGINAL_HEIGHT;
|
||||
|
||||
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::pair<uint32_t, uint32_t>> GRAPHICS_WINDOW_SIZE;
|
||||
extern std::optional<std::string> GRAPHICS_WINDOW_POS;
|
||||
extern bool GRAPHICS_WINDOW_ALWAYS_ON_TOP;
|
||||
extern bool GRAPHICS_WINDOW_BACKBUFFER_SCALE;
|
||||
|
||||
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;
|
||||
|
||||
@@ -42,11 +68,13 @@ 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);
|
||||
@@ -69,3 +97,4 @@ 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();
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
#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( \
|
||||
@@ -18,9 +19,19 @@ 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::pair<uint32_t, uint32_t>> GRAPHICS_WINDOW_SIZE;
|
||||
std::optional<std::string> GRAPHICS_WINDOW_POS;
|
||||
bool GRAPHICS_WINDOW_ALWAYS_ON_TOP = false;
|
||||
bool GRAPHICS_WINDOW_BACKBUFFER_SCALE = 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)
|
||||
@@ -100,6 +111,9 @@ void graphics_capture_initial_window(HWND hWnd) {
|
||||
graphics_update_z_order(hWnd);
|
||||
}
|
||||
|
||||
// ensure spicetouch coordinates are initialized
|
||||
update_spicetouch_window_dimensions(hWnd);
|
||||
|
||||
log_debug("graphics-windowed", "graphics_capture_initial_window returned");
|
||||
}
|
||||
|
||||
@@ -109,10 +123,6 @@ void graphics_load_windowed_parameters() {
|
||||
}
|
||||
|
||||
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",
|
||||
@@ -124,32 +134,23 @@ void graphics_load_windowed_parameters() {
|
||||
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 -sp2x-windowsize");
|
||||
}
|
||||
cfg::SCREENRESIZE->enable_window_resize = true;
|
||||
cfg::SCREENRESIZE->client_keep_aspect_ratio = false;
|
||||
cfg::SCREENRESIZE->client_width = GRAPHICS_WINDOW_SIZE.value().first;
|
||||
cfg::SCREENRESIZE->client_height = GRAPHICS_WINDOW_SIZE.value().second;
|
||||
}
|
||||
|
||||
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) {
|
||||
std::pair<uint32_t, uint32_t> result;
|
||||
if (parse_width_height(GRAPHICS_WINDOW_POS.value(), result)) {
|
||||
cfg::SCREENRESIZE->enable_window_resize = true;
|
||||
cfg::SCREENRESIZE->window_offset_x = x;
|
||||
cfg::SCREENRESIZE->window_offset_y = y;
|
||||
cfg::SCREENRESIZE->window_offset_x = result.first;
|
||||
cfg::SCREENRESIZE->window_offset_y = result.second;
|
||||
} else {
|
||||
log_warning("graphics-windowed", "failed to parse -sp2x-windowpos");
|
||||
log_warning("graphics-windowed", "failed to parse -windowpos");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -161,6 +162,42 @@ void graphics_load_windowed_parameters() {
|
||||
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");
|
||||
|
||||
if (GRAPHICS_IIDX_WSUB_SIZE.has_value()) {
|
||||
log_debug(
|
||||
"graphics-windowed",
|
||||
"graphics_load_windowed_parameters - load GRAPHICS_IIDX_WSUB_SIZE");
|
||||
|
||||
std::pair<uint32_t, uint32_t> result;
|
||||
if (parse_width_height(GRAPHICS_IIDX_WSUB_SIZE.value(), result)) {
|
||||
GRAPHICS_IIDX_WSUB_WIDTH = result.first;
|
||||
GRAPHICS_IIDX_WSUB_HEIGHT = result.second;
|
||||
} 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");
|
||||
|
||||
std::pair<uint32_t, uint32_t> result;
|
||||
if (parse_width_height(GRAPHICS_IIDX_WSUB_POS.value(), result)) {
|
||||
GRAPHICS_IIDX_WSUB_X = result.first;
|
||||
GRAPHICS_IIDX_WSUB_Y = result.second;
|
||||
} 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;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
#pragma once
|
||||
|
||||
namespace nvapi_hook {
|
||||
extern bool BYPASS_NVAPI;
|
||||
void initialize(HINSTANCE dll);
|
||||
}
|
||||
@@ -0,0 +1,175 @@
|
||||
#include <d3d9.h>
|
||||
|
||||
#include <optional>
|
||||
|
||||
#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 PNVENCINITIALIZEENCODER nvEncInitializeEncoder_orig = nullptr;
|
||||
static BOOL initialized = false;
|
||||
|
||||
namespace nvenc_hook {
|
||||
|
||||
std::optional<std::string> VIDEO_CQP_STRING_OVERRIDE;
|
||||
std::optional<NV_ENC_QP> VIDEO_CQP_OVERRIDE;
|
||||
|
||||
void parse_qcp_params();
|
||||
|
||||
NVENCSTATUS NVENCAPI 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) {}
|
||||
return nvEncOpenEncodeSessionEx_orig(openSessionExParams, encoder);
|
||||
}
|
||||
|
||||
NVENCSTATUS NVENCAPI nvEncInitializeEncoder_hook(
|
||||
void* encoder,
|
||||
NV_ENC_INITIALIZE_PARAMS* createEncodeParams
|
||||
) {
|
||||
try {
|
||||
// check input params
|
||||
if (createEncodeParams == nullptr || createEncodeParams->encodeConfig == nullptr) {
|
||||
goto done;
|
||||
}
|
||||
const auto rc = &createEncodeParams->encodeConfig->rcParams;
|
||||
if (rc->rateControlMode != NV_ENC_PARAMS_RC_CONSTQP) {
|
||||
log_warning(
|
||||
"nvenc_hook",
|
||||
"nvEncInitializeEncoder: unexpected rateControlMode, expected: NV_ENC_PARAMS_RC_CONSTQP, actual: {}",
|
||||
rc->rateControlMode);
|
||||
goto done;
|
||||
}
|
||||
|
||||
log_misc("nvenc_hook", "nvEncInitializeEncoder hook hit with expected params");
|
||||
|
||||
// print out most relevant video quality settings
|
||||
// note: NvEncoder.cpp sample uses {28, 31, 25} (and that's what some hex edits modify)
|
||||
// but bm2dx later adds 8 to each value, before calling this routine
|
||||
log_misc(
|
||||
"nvenc_hook", "nvEncInitializeEncoder: original constQP p={}, b={}, i={}",
|
||||
rc->constQP.qpInterP, rc->constQP.qpInterB, rc->constQP.qpIntra);
|
||||
|
||||
// video quality override
|
||||
if (VIDEO_CQP_OVERRIDE.has_value()) {
|
||||
rc->constQP = VIDEO_CQP_OVERRIDE.value();
|
||||
log_misc(
|
||||
"nvenc_hook", "nvEncInitializeEncoder: user overriden constQP p={}, b={}, i={}",
|
||||
rc->constQP.qpInterP, rc->constQP.qpInterB, rc->constQP.qpIntra);
|
||||
}
|
||||
|
||||
} catch (const std::exception &ex) {}
|
||||
|
||||
done:
|
||||
return nvEncInitializeEncoder_orig(encoder, createEncodeParams);
|
||||
}
|
||||
|
||||
NVENCSTATUS NVENCAPI 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);
|
||||
detour::trampoline_try(
|
||||
pFunctionList->nvEncInitializeEncoder,
|
||||
nvEncInitializeEncoder_hook,
|
||||
&nvEncInitializeEncoder_orig);
|
||||
log_misc("nvenc_hook", "NvEncodeAPICreateInstance_hook called and functions hooked");
|
||||
initialized = true;
|
||||
}
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
void initialize() {
|
||||
HMODULE nvenc = libutils::try_library("nvEncodeAPI64.dll");
|
||||
if (nvenc == nullptr) {
|
||||
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");
|
||||
}
|
||||
parse_qcp_params();
|
||||
}
|
||||
|
||||
void parse_qcp_params() {
|
||||
if (!VIDEO_CQP_STRING_OVERRIDE.has_value() || VIDEO_CQP_STRING_OVERRIDE.value().empty()) {
|
||||
return;
|
||||
}
|
||||
const auto remove_spaces = [](const char& c) {
|
||||
return c == ' ';
|
||||
};
|
||||
|
||||
auto s = VIDEO_CQP_STRING_OVERRIDE.value();
|
||||
s.erase(std::remove_if(s.begin(), s.end(), remove_spaces), s.end());
|
||||
|
||||
int cqp = -1;
|
||||
NV_ENC_QP nvenc_qp;
|
||||
|
||||
// try 3-param format first, and then fall back to single parameter format
|
||||
if (sscanf(
|
||||
s.c_str(),
|
||||
"%i,%i,%i",
|
||||
(int*)&(nvenc_qp.qpInterP),
|
||||
(int*)&(nvenc_qp.qpInterB),
|
||||
(int*)&(nvenc_qp.qpIntra)) == 3) {
|
||||
|
||||
VIDEO_CQP_OVERRIDE = nvenc_qp;
|
||||
} else if (sscanf(s.c_str(), "%i", &cqp) == 1) {
|
||||
nvenc_qp.qpInterP = cqp;
|
||||
nvenc_qp.qpInterB = cqp;
|
||||
nvenc_qp.qpIntra = cqp;
|
||||
VIDEO_CQP_OVERRIDE = nvenc_qp;
|
||||
} else {
|
||||
log_fatal("nvenc_hook", "failed to parse -iidxreccqp");
|
||||
}
|
||||
|
||||
log_info("nvenc_hook", "parsed NVENC ConstQP params: p={}, b={}, i={} (-iidxreccqp)",
|
||||
nvenc_qp.qpInterP, nvenc_qp.qpInterB, nvenc_qp.qpIntra);
|
||||
}
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
namespace nvenc_hook {
|
||||
void initialize() {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
namespace nvenc_hook {
|
||||
|
||||
extern std::optional<std::string> VIDEO_CQP_STRING_OVERRIDE;
|
||||
void initialize();
|
||||
}
|
||||
@@ -19,6 +19,10 @@ static decltype(GetACP) *GetACP_orig = nullptr;
|
||||
static decltype(GetOEMCP) *GetOEMCP_orig = nullptr;
|
||||
static decltype(MultiByteToWideChar) *MultiByteToWideChar_orig = nullptr;
|
||||
|
||||
#ifdef SPICE64
|
||||
static decltype(GetSystemDefaultLCID) *GetSystemDefaultLCID_orig = nullptr;
|
||||
#endif
|
||||
|
||||
static NTSTATUS NTAPI RtlMultiByteToUnicodeN_hook(
|
||||
PWCH UnicodeString,
|
||||
ULONG MaxBytesInUnicodeString,
|
||||
@@ -73,6 +77,16 @@ static UINT WINAPI GetOEMCP_hook() {
|
||||
return CODEPAGE_SHIFT_JIS;
|
||||
}
|
||||
|
||||
#ifdef SPICE64
|
||||
|
||||
static LCID WINAPI GetSystemDefaultLCID_hook() {
|
||||
// ja-JP per https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-lcid/a9eac961-e77d-41a6-90a5-ce1a8b0cdb9c
|
||||
// this is passed to LCMapStringA for kana conversions (e.g., for subscreen song search)
|
||||
return 0x411;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
static int WINAPI MultiByteToWideChar_hook(
|
||||
UINT CodePage,
|
||||
DWORD dwFlags,
|
||||
@@ -111,6 +125,19 @@ void hooks::lang::early_init() {
|
||||
// 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);
|
||||
|
||||
#ifdef SPICE64 // SDVX5+ specific code
|
||||
bool isValkyrieCabinetMode = avs::game::SPEC[0] == 'G' || avs::game::SPEC[0] == 'H';
|
||||
if (avs::game::is_model("KFC") && isValkyrieCabinetMode) {
|
||||
log_info("hooks::lang", "hooking GetSystemDefaultLCID");
|
||||
detour::trampoline_try(
|
||||
"kernel32.dll",
|
||||
"GetSystemDefaultLCID",
|
||||
GetSystemDefaultLCID_hook,
|
||||
&GetSystemDefaultLCID_orig);
|
||||
}
|
||||
#endif
|
||||
|
||||
}
|
||||
|
||||
void hooks::lang::init() {
|
||||
|
||||
@@ -77,10 +77,9 @@ static ULONG WINAPI GetAdaptersInfo_hook(PIP_ADAPTER_INFO pAdapterInfo, PULONG p
|
||||
|
||||
// log adapter
|
||||
if (GetAdaptersInfo_log)
|
||||
log_info("network", "Using preferred network adapter: {}, {}, {}",
|
||||
log_info("network", "Using preferred network adapter: {}, {}",
|
||||
info->AdapterName,
|
||||
info->IpAddressList.IpAddress.String,
|
||||
info->IpAddressList.IpMask.String);
|
||||
info->Description);
|
||||
|
||||
// set adapter information
|
||||
memcpy(pAdapterInfo, info, sizeof(*info));
|
||||
@@ -118,10 +117,10 @@ static ULONG WINAPI GetAdaptersInfo_hook(PIP_ADAPTER_INFO pAdapterInfo, PULONG p
|
||||
|
||||
// log information
|
||||
if (GetAdaptersInfo_log)
|
||||
log_info("network", "Using fallback adapter: {}, {}, {}",
|
||||
log_info("network", "Using fallback network adapter: {}, {}",
|
||||
info->AdapterName,
|
||||
info->IpAddressList.IpAddress.String,
|
||||
info->IpAddressList.IpMask.String);
|
||||
info->Description);
|
||||
|
||||
|
||||
// set adapter information
|
||||
memcpy(pAdapterInfo, info, sizeof(*info));
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
#pragma once
|
||||
|
||||
#include <windows.h>
|
||||
|
||||
void winuser_hook_init(HMODULE module);
|
||||
Reference in New Issue
Block a user