Merge branch 'spice2x' into spice22x
This commit is contained in:
@@ -0,0 +1,230 @@
|
||||
#include "audio.h"
|
||||
|
||||
#include <mutex>
|
||||
|
||||
#include <external/robin_hood.h>
|
||||
|
||||
#include <windows.h>
|
||||
#include <mmsystem.h>
|
||||
#include <msacm.h>
|
||||
|
||||
#include "avs/game.h"
|
||||
#include "util/detour.h"
|
||||
#include "util/libutils.h"
|
||||
#include "util/logging.h"
|
||||
#include "util/memutils.h"
|
||||
#include "util/time.h"
|
||||
#include "util/unique_plain_ptr.h"
|
||||
|
||||
#define ACM_CACHE_ENABLED 1
|
||||
#define ACM_DEBUG_PERF 0
|
||||
#define ACM_DEBUG_VERBOSE 0
|
||||
|
||||
#if ACM_DEBUG_VERBOSE
|
||||
#define log_debug(module, format_str, ...) logger::push( \
|
||||
LOG_FORMAT("M", module, format_str, ## __VA_ARGS__), logger::Style::GREY)
|
||||
#else
|
||||
#define log_debug(module, format_str, ...)
|
||||
#endif
|
||||
|
||||
namespace hooks::audio::acm {
|
||||
struct WAVE_FORMAT {
|
||||
uint32_t suggest;
|
||||
// for WAVEFORMATEX
|
||||
std::vector<uint8_t> wave_format_ex;
|
||||
|
||||
bool operator==(const WAVE_FORMAT& other) const {
|
||||
return (this->suggest == other.suggest &&
|
||||
this->wave_format_ex == other.wave_format_ex);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
namespace robin_hood {
|
||||
template <>
|
||||
struct hash<hooks::audio::acm::WAVE_FORMAT> {
|
||||
std::size_t operator()(const hooks::audio::acm::WAVE_FORMAT& k) const noexcept {
|
||||
std::size_t h = robin_hood::hash_bytes(
|
||||
k.wave_format_ex.data(), k.wave_format_ex.size());
|
||||
|
||||
h = h ^ robin_hood::hash<uint32_t>{}(k.suggest) << 1;
|
||||
return h;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
namespace hooks::audio::acm {
|
||||
|
||||
static decltype(acmFormatSuggest) *acmFormatSuggest_orig = nullptr;
|
||||
static std::mutex acm_formats_mutex;
|
||||
static robin_hood::unordered_map<WAVE_FORMAT, std::vector<uint8_t>> acm_formats;
|
||||
|
||||
// hooks calls to acmFormatSuggest and returns cached results from previous calls
|
||||
//
|
||||
// iidx calls this many times during song load and reload (function of # of keysounds)
|
||||
// - with near-identical arguments over and over again
|
||||
// (when hovering over song in song select, selecting a song, and exiting result screen)
|
||||
//
|
||||
// on Linux this results in ACM module load/unload which is very expensive,
|
||||
// resulting in significant perf improvement (seconds per song load);
|
||||
// on Windows this saves milliseconds per song, at best
|
||||
//
|
||||
// https://codeberg.org/nixac/spicetools/issues/2
|
||||
// https://codeberg.org/nixac/spicetools/issues/4
|
||||
MMRESULT
|
||||
acmFormatSuggest_cached (
|
||||
HACMDRIVER had,
|
||||
LPWAVEFORMATEX pwfxSrc,
|
||||
LPWAVEFORMATEX pwfxDst,
|
||||
DWORD cbwfxDst,
|
||||
DWORD fdwSuggest) {
|
||||
|
||||
if (had != 0 || cbwfxDst == 0) {
|
||||
return acmFormatSuggest_orig(had, pwfxSrc, pwfxDst, cbwfxDst, fdwSuggest);
|
||||
}
|
||||
|
||||
WAVE_FORMAT key;
|
||||
key.suggest = fdwSuggest;
|
||||
const size_t src_size = sizeof(*pwfxSrc) + pwfxSrc->cbSize;
|
||||
key.wave_format_ex.insert(
|
||||
key.wave_format_ex.end(),
|
||||
reinterpret_cast<uint8_t *>(pwfxSrc),
|
||||
reinterpret_cast<uint8_t *>(pwfxSrc) + src_size);
|
||||
|
||||
std::lock_guard<std::mutex> lock(acm_formats_mutex);
|
||||
|
||||
MMRESULT mmresult = 0;
|
||||
|
||||
if (acm_formats.contains(key)) {
|
||||
// found in cache
|
||||
const auto &result = acm_formats.at(key);
|
||||
|
||||
if (cbwfxDst >= result.size()) {
|
||||
// dest buffer big enough, return cached result
|
||||
log_debug("audio::acm", "acmFormatSuggest cache hit, copying {} bytes", result.size());
|
||||
std::memcpy(reinterpret_cast<uint8_t *>(pwfxDst), result.data(), result.size());
|
||||
mmresult = 0;
|
||||
} else {
|
||||
// dest buffer not big enough, call original
|
||||
log_debug("audio::acm", "acmFormatSuggest cache fail; cbwfxDst too small ({})", cbwfxDst);
|
||||
mmresult = acmFormatSuggest_orig(had, pwfxSrc, pwfxDst, cbwfxDst, fdwSuggest);
|
||||
}
|
||||
|
||||
} else {
|
||||
log_debug("audio::acm", "acmFormatSuggest cache miss; calling original");
|
||||
mmresult = acmFormatSuggest_orig(had, pwfxSrc, pwfxDst, cbwfxDst, fdwSuggest);
|
||||
|
||||
// cache the result, but don't allow unconstrained growth
|
||||
if (mmresult == 0 && acm_formats.size() < 128) {
|
||||
const size_t dest_size = sizeof(*pwfxDst) + pwfxDst->cbSize;
|
||||
log_debug(
|
||||
"audio::acm",
|
||||
"acmFormatSuggest cache add; current cache size {}, new data {} bytes",
|
||||
acm_formats.size(),
|
||||
dest_size);
|
||||
|
||||
acm_formats[key] = std::vector<uint8_t>();
|
||||
acm_formats[key].reserve(dest_size);
|
||||
acm_formats[key].insert(
|
||||
acm_formats[key].begin(),
|
||||
reinterpret_cast<uint8_t *>(pwfxDst),
|
||||
reinterpret_cast<uint8_t *>(pwfxDst) + dest_size);
|
||||
}
|
||||
}
|
||||
|
||||
return mmresult;
|
||||
}
|
||||
|
||||
MMRESULT
|
||||
ACMAPI
|
||||
acmFormatSuggest_hook (
|
||||
HACMDRIVER had,
|
||||
LPWAVEFORMATEX pwfxSrc,
|
||||
LPWAVEFORMATEX pwfxDst,
|
||||
DWORD cbwfxDst,
|
||||
DWORD fdwSuggest) {
|
||||
|
||||
log_debug(
|
||||
"audio::acm",
|
||||
"acmFormatSuggest called: had={}, "
|
||||
"formattag={}, ch={}, samplespersec={}, bytespersec={}, blockalign={}, bits={}, extrasize={}",
|
||||
fmt::ptr(had),
|
||||
pwfxSrc->wFormatTag,
|
||||
pwfxSrc->nChannels,
|
||||
pwfxSrc->nSamplesPerSec,
|
||||
pwfxSrc->nAvgBytesPerSec,
|
||||
pwfxSrc->nBlockAlign,
|
||||
pwfxSrc->wBitsPerSample,
|
||||
pwfxSrc->cbSize);
|
||||
|
||||
#if ACM_DEBUG_PERF
|
||||
const auto start = get_performance_milliseconds();
|
||||
#endif
|
||||
|
||||
// make a call to acmFormatSuggest
|
||||
|
||||
#if ACM_CACHE_ENABLED
|
||||
const auto result = acmFormatSuggest_cached(had, pwfxSrc, pwfxDst, cbwfxDst, fdwSuggest);
|
||||
#else
|
||||
const auto result = acmFormatSuggest_orig(had, pwfxSrc, pwfxDst, cbwfxDst, fdwSuggest);
|
||||
#endif
|
||||
|
||||
// log result
|
||||
|
||||
#if ACM_DEBUG_PERF
|
||||
const auto delta = get_performance_milliseconds() - start;
|
||||
static double delta_total = 0;
|
||||
delta_total += delta;
|
||||
|
||||
log_info(
|
||||
"audio::acm",
|
||||
"acmFormatSuggest_hook returned {}, took {} us (running total {} ms)",
|
||||
result,
|
||||
delta * 1000,
|
||||
delta_total);
|
||||
#else
|
||||
log_debug("audio::acm", "acmFormatSuggest_hook returned {}", result);
|
||||
#endif
|
||||
|
||||
if (result == 0 && cbwfxDst > sizeof(WAVEFORMATEX)) {
|
||||
log_debug(
|
||||
"audio::acm",
|
||||
"....formattag={}, ch={}, samplespersec={}, bytespersec={}, blockalign={}, bits={}, extrasize={}",
|
||||
pwfxDst->wFormatTag,
|
||||
pwfxDst->nChannels,
|
||||
pwfxDst->nSamplesPerSec,
|
||||
pwfxDst->nAvgBytesPerSec,
|
||||
pwfxDst->nBlockAlign,
|
||||
pwfxDst->wBitsPerSample,
|
||||
pwfxDst->cbSize);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
void init() {
|
||||
|
||||
// only enabled on Linux as the performance gains are negligible on Windows
|
||||
#if SPICE_LINUX
|
||||
|
||||
HMODULE msacm = libutils::try_library("msacm32.dll");
|
||||
if (msacm == nullptr) {
|
||||
log_info("audio::acm", "msacm32.dll failed to hook");
|
||||
return;
|
||||
}
|
||||
|
||||
acmFormatSuggest_orig = detour::iat_try(
|
||||
"acmFormatSuggest", acmFormatSuggest_hook, avs::game::DLL_INSTANCE);
|
||||
if (acmFormatSuggest_orig != nullptr) {
|
||||
log_misc("audio::acm", "acmFormatSuggest hooked");
|
||||
|
||||
#if !ACM_CACHE_ENABLED
|
||||
log_warning("audio::acm", "acmFormatSuggest cache DISABLED");
|
||||
#endif
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
#pragma once
|
||||
|
||||
namespace hooks::audio::acm {
|
||||
void init();
|
||||
}
|
||||
@@ -13,6 +13,7 @@
|
||||
#include "util/memutils.h"
|
||||
|
||||
#include "audio_private.h"
|
||||
#include "acm.h"
|
||||
|
||||
#ifdef _MSC_VER
|
||||
DEFINE_GUID(CLSID_MMDeviceEnumerator,
|
||||
@@ -101,6 +102,7 @@ namespace hooks::audio {
|
||||
|
||||
log_info("audio", "initializing");
|
||||
init_low_latency();
|
||||
hooks::audio::acm::init();
|
||||
|
||||
// general hooks
|
||||
CoCreateInstance_orig = detour::iat_try("CoCreateInstance", CoCreateInstance_hook);
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
#include "hooks/audio/audio_private.h"
|
||||
#include "hooks/audio/backends/mmdevice/audio_endpoint_volume.h"
|
||||
#include "hooks/audio/backends/wasapi/audio_client.h"
|
||||
#include "util/utils.h"
|
||||
|
||||
#define PRINT_FAILED_RESULT(name, ret) \
|
||||
do { \
|
||||
@@ -56,6 +57,7 @@ ULONG STDMETHODCALLTYPE WrappedIMMDevice::Release() {
|
||||
ULONG refs = pReal != nullptr ? pReal->Release() : 0;
|
||||
|
||||
if (refs == 0) {
|
||||
log_misc("audio::mmdevice", "WrappedIMMDevice::Release");
|
||||
delete this;
|
||||
}
|
||||
|
||||
@@ -69,7 +71,7 @@ HRESULT STDMETHODCALLTYPE WrappedIMMDevice::Activate(
|
||||
PROPVARIANT *pActivationParams,
|
||||
void **ppInterface)
|
||||
{
|
||||
log_misc("audio::mmdevice", "WrappedIMMDevice::Activate");
|
||||
log_misc("audio::mmdevice", "WrappedIMMDevice::Activate {}", guid2s(iid));
|
||||
|
||||
// call original
|
||||
HRESULT ret = pReal->Activate(iid, dwClsCtx, pActivationParams, ppInterface);
|
||||
@@ -80,7 +82,10 @@ HRESULT STDMETHODCALLTYPE WrappedIMMDevice::Activate(
|
||||
return ret;
|
||||
}
|
||||
|
||||
if (iid == IID_IAudioClient) {
|
||||
// almost all games request IAudioClient
|
||||
// so far we have not seen any games request IAudioClient2
|
||||
// SDVX EG Final uses IID_IAudioClient3, but only when shared mode patch is on
|
||||
if (iid == IID_IAudioClient || iid == IID_IAudioClient3) {
|
||||
|
||||
// prevent initialization recursion when using some ASIO backends that proxy to DirectSound, WASAPI, or WDM
|
||||
// like ASIO4All or FlexASIO
|
||||
@@ -90,23 +95,18 @@ HRESULT STDMETHODCALLTYPE WrappedIMMDevice::Activate(
|
||||
}
|
||||
std::lock_guard initialize_guard(hooks::audio::INITIALIZE_LOCK, std::adopt_lock);
|
||||
|
||||
auto client = reinterpret_cast<IAudioClient *>(*ppInterface);
|
||||
|
||||
// release old audio client if initialized
|
||||
if (hooks::audio::CLIENT) {
|
||||
hooks::audio::CLIENT->Release();
|
||||
}
|
||||
|
||||
/*
|
||||
ret = wrap_audio_client(pReal, dwClsCtx, pActivationParams, &client);
|
||||
if (FAILED(ret)) {
|
||||
return ret;
|
||||
IAudioClient *client = nullptr;
|
||||
if (iid == IID_IAudioClient) {
|
||||
client = wrap_audio_client(reinterpret_cast<IAudioClient *>(*ppInterface));
|
||||
} else { // IID_IAudioClient3
|
||||
client = wrap_audio_client3(reinterpret_cast<IAudioClient3 *>(*ppInterface));
|
||||
}
|
||||
*/
|
||||
client = wrap_audio_client(client);
|
||||
|
||||
*ppInterface = client;
|
||||
|
||||
// persist the audio client
|
||||
hooks::audio::CLIENT = client;
|
||||
hooks::audio::CLIENT->AddRef();
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
#include "device_collection.h"
|
||||
#include "device.h"
|
||||
#include "util/utils.h"
|
||||
#include "util/logging.h"
|
||||
|
||||
HRESULT STDMETHODCALLTYPE WrappedIMMDeviceCollection::QueryInterface(REFIID riid, void **ppvObj) {
|
||||
if (ppvObj == nullptr) {
|
||||
return E_POINTER;
|
||||
}
|
||||
if (riid == __uuidof(IMMDeviceCollection)) {
|
||||
this->AddRef();
|
||||
*ppvObj = this;
|
||||
return S_OK;
|
||||
}
|
||||
return pReal->QueryInterface(riid, ppvObj);
|
||||
}
|
||||
|
||||
ULONG STDMETHODCALLTYPE WrappedIMMDeviceCollection::AddRef() {
|
||||
return pReal->AddRef();
|
||||
}
|
||||
|
||||
ULONG STDMETHODCALLTYPE WrappedIMMDeviceCollection::Release() {
|
||||
// get reference count of underlying interface
|
||||
ULONG refs = pReal != nullptr ? pReal->Release() : 0;
|
||||
if (refs == 0) {
|
||||
delete this;
|
||||
}
|
||||
return refs;
|
||||
}
|
||||
|
||||
HRESULT STDMETHODCALLTYPE WrappedIMMDeviceCollection::GetCount(UINT *pcDevices) {
|
||||
return pReal->GetCount(pcDevices);
|
||||
}
|
||||
|
||||
HRESULT STDMETHODCALLTYPE WrappedIMMDeviceCollection::Item(UINT nDevice, IMMDevice **ppDevice) {
|
||||
log_info("audio", "WrappedIMMDeviceCollection::Item[{}]", nDevice);
|
||||
|
||||
// call original
|
||||
const auto hr = pReal->Item(nDevice, ppDevice);
|
||||
|
||||
// wrap interface
|
||||
*ppDevice = new WrappedIMMDevice(*ppDevice);
|
||||
return hr;
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
#pragma once
|
||||
|
||||
#include <initguid.h>
|
||||
#include <mmdeviceapi.h>
|
||||
|
||||
struct WrappedIMMDeviceCollection : IMMDeviceCollection {
|
||||
explicit WrappedIMMDeviceCollection(IMMDeviceCollection *orig) : pReal(orig) {
|
||||
}
|
||||
|
||||
WrappedIMMDeviceCollection(const WrappedIMMDeviceCollection &) = delete;
|
||||
WrappedIMMDeviceCollection &operator=(const WrappedIMMDeviceCollection &) = delete;
|
||||
|
||||
virtual ~WrappedIMMDeviceCollection() = default;
|
||||
|
||||
#pragma region IUnknown
|
||||
virtual HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, void **ppvObj) override;
|
||||
virtual ULONG STDMETHODCALLTYPE AddRef() override;
|
||||
virtual ULONG STDMETHODCALLTYPE Release() override;
|
||||
#pragma endregion
|
||||
|
||||
#pragma region IMMDeviceCollection
|
||||
virtual HRESULT STDMETHODCALLTYPE GetCount(UINT *pcDevices) override;
|
||||
virtual HRESULT STDMETHODCALLTYPE Item(UINT nDevice, IMMDevice **ppDevice) override;
|
||||
#pragma endregion
|
||||
|
||||
private:
|
||||
IMMDeviceCollection *const pReal;
|
||||
};
|
||||
@@ -1,4 +1,5 @@
|
||||
#include "device_enumerator.h"
|
||||
#include "device_collection.h"
|
||||
#include "device.h"
|
||||
#include "util/utils.h"
|
||||
|
||||
@@ -42,7 +43,11 @@ HRESULT STDMETHODCALLTYPE WrappedIMMDeviceEnumerator::EnumAudioEndpoints(
|
||||
DWORD dwStateMask,
|
||||
IMMDeviceCollection **ppDevices)
|
||||
{
|
||||
return pReal->EnumAudioEndpoints(dataFlow, dwStateMask, ppDevices);
|
||||
const auto hr = pReal->EnumAudioEndpoints(dataFlow, dwStateMask, ppDevices);
|
||||
if (SUCCEEDED(hr) && (ppDevices != nullptr) && (*ppDevices != nullptr)) {
|
||||
*ppDevices = new WrappedIMMDeviceCollection(*ppDevices);
|
||||
}
|
||||
return hr;
|
||||
}
|
||||
|
||||
HRESULT STDMETHODCALLTYPE WrappedIMMDeviceEnumerator::GetDefaultAudioEndpoint(
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
#include "hooks/audio/implementations/pipewire.h"
|
||||
#include "hooks/audio/implementations/none.h"
|
||||
//#include "util/co_task_mem_ptr.h"
|
||||
#include "util/utils.h"
|
||||
|
||||
#include "defs.h"
|
||||
#include "dummy_audio_client.h"
|
||||
@@ -42,114 +43,9 @@ static void fix_rec_format(WAVEFORMATEX *pFormat) {
|
||||
pFormat->nAvgBytesPerSec = pFormat->nSamplesPerSec * pFormat->nBlockAlign;
|
||||
}
|
||||
|
||||
// TODO(felix): is it appropriate to automatically switch to shared mode? should we do a
|
||||
// `MessageBox` to notify the user?
|
||||
/*
|
||||
static bool check_for_exclusive_access(IAudioClient *client) {
|
||||
static bool checked_once = false;
|
||||
static bool previous_check_result = false;
|
||||
|
||||
CoTaskMemPtr<WAVEFORMATEX> mix_format;
|
||||
REFERENCE_TIME requested_duration = 0;
|
||||
|
||||
if (checked_once) {
|
||||
return previous_check_result;
|
||||
}
|
||||
if (audio::BACKEND.has_value()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// scope function so it has access to the local static variables
|
||||
auto set_result = [](bool result) {
|
||||
checked_once = true;
|
||||
previous_check_result = result;
|
||||
|
||||
return result;
|
||||
};
|
||||
|
||||
HRESULT ret = client->GetMixFormat(mix_format.ppv());
|
||||
if (FAILED(ret)) {
|
||||
PRINT_FAILED_RESULT("IAudioClient::GetMixFormat", ret);
|
||||
return set_result(false);
|
||||
}
|
||||
|
||||
log_info("audio::wasapi", "Mix Format:");
|
||||
print_format(mix_format.data());
|
||||
|
||||
ret = client->IsFormatSupported(AUDCLNT_SHAREMODE_EXCLUSIVE, mix_format.data(), nullptr);
|
||||
if (ret == AUDCLNT_E_UNSUPPORTED_FORMAT) {
|
||||
auto mix_format_ex = reinterpret_cast<WAVEFORMATEXTENSIBLE *>(mix_format.data());
|
||||
|
||||
log_warning("audio::wasapi", "device does not natively support the mix format, converting to PCM");
|
||||
|
||||
if (mix_format->wFormatTag == WAVE_FORMAT_EXTENSIBLE &&
|
||||
IsEqualGUID(GUID_KSDATAFORMAT_SUBTYPE_IEEE_FLOAT, mix_format_ex->SubFormat))
|
||||
{
|
||||
mix_format_ex->Format.wBitsPerSample = 16;
|
||||
mix_format_ex->Format.nBlockAlign = mix_format_ex->Format.nChannels * (mix_format_ex->Format.wBitsPerSample / 8);
|
||||
mix_format_ex->Format.nAvgBytesPerSec = mix_format_ex->Format.nSamplesPerSec * mix_format_ex->Format.nBlockAlign;
|
||||
mix_format_ex->Samples.wValidBitsPerSample = 16;
|
||||
mix_format_ex->SubFormat = GUID_KSDATAFORMAT_SUBTYPE_PCM;
|
||||
} else if (mix_format->wFormatTag == WAVE_FORMAT_IEEE_FLOAT) {
|
||||
mix_format->wBitsPerSample = 16;
|
||||
mix_format->nBlockAlign = mix_format->nChannels * (mix_format->wBitsPerSample / 8);
|
||||
mix_format->nAvgBytesPerSec = mix_format->nSamplesPerSec * mix_format->nBlockAlign;
|
||||
mix_format->wFormatTag = WAVE_FORMAT_PCM;
|
||||
} else {
|
||||
log_warning("audio::wasapi", "mix format is not a floating point format");
|
||||
return set_result(false);
|
||||
}
|
||||
|
||||
ret = client->IsFormatSupported(AUDCLNT_SHAREMODE_EXCLUSIVE, mix_format.data(), nullptr);
|
||||
if (FAILED(ret)) {
|
||||
log_warning("audio::wasapi", "mix format is not supported");
|
||||
return set_result(false);
|
||||
}
|
||||
}
|
||||
|
||||
ret = client->GetDevicePeriod(nullptr, &requested_duration);
|
||||
if (FAILED(ret)) {
|
||||
PRINT_FAILED_RESULT("IAudioClient::GetDevicePeriod", ret);
|
||||
return false;
|
||||
}
|
||||
|
||||
ret = client->Initialize(
|
||||
AUDCLNT_SHAREMODE_EXCLUSIVE,
|
||||
AUDCLNT_STREAMFLAGS_EVENTCALLBACK,
|
||||
requested_duration,
|
||||
requested_duration,
|
||||
mix_format.data(),
|
||||
nullptr);
|
||||
|
||||
if (ret == AUDCLNT_E_BUFFER_SIZE_NOT_ALIGNED || SUCCEEDED(ret)) {
|
||||
log_info("audio::wasapi", "exclusive mode is available, disabling backend");
|
||||
return set_result(true);
|
||||
} else {
|
||||
log_warning("audio::wasapi", "exclusive mode is not available, enabling backend, hr={}", FMT_HRESULT(ret));
|
||||
}
|
||||
|
||||
return set_result(false);
|
||||
}
|
||||
|
||||
HRESULT wrap_audio_client(
|
||||
IMMDevice *device,
|
||||
DWORD cls_ctx,
|
||||
PROPVARIANT *activation_params,
|
||||
IAudioClient **audio_client)
|
||||
{
|
||||
auto exclusive_available = check_for_exclusive_access(*audio_client);
|
||||
(*audio_client)->Stop();
|
||||
(*audio_client)->Reset();
|
||||
(*audio_client)->Release();
|
||||
*audio_client = nullptr;
|
||||
|
||||
SAFE_CALL("IMMDevice", "Activate", device->Activate(
|
||||
IID_IAudioClient,
|
||||
cls_ctx,
|
||||
activation_params,
|
||||
reinterpret_cast<void **>(audio_client)));
|
||||
*/
|
||||
IAudioClient *wrap_audio_client(IAudioClient *audio_client) {
|
||||
log_misc("audio::wasapi", "wrapping IAudioClient");
|
||||
|
||||
AudioBackend *backend = nullptr;
|
||||
bool requires_dummy = false;
|
||||
|
||||
@@ -172,9 +68,6 @@ IAudioClient *wrap_audio_client(IAudioClient *audio_client) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
//} else if (!exclusive_available) {
|
||||
// backend = new WaveOutBackend();
|
||||
//}
|
||||
|
||||
IAudioClient *new_client;
|
||||
|
||||
@@ -190,6 +83,23 @@ IAudioClient *wrap_audio_client(IAudioClient *audio_client) {
|
||||
|
||||
return new_client;
|
||||
}
|
||||
IAudioClient3 *wrap_audio_client3(IAudioClient3 *audio_client) {
|
||||
// TODO: ASIO backend for IAudioClient3, if there is a game that needs it
|
||||
log_misc("audio::wasapi", "wrapping IAudioClient3");
|
||||
|
||||
if (hooks::audio::BACKEND.has_value()) {
|
||||
log_fatal(
|
||||
"audio::wasapi",
|
||||
"IAudioClient3 does not currently support backends! clear -audiobackend and try again");
|
||||
}
|
||||
if (hooks::audio::USE_DUMMY) {
|
||||
log_fatal(
|
||||
"audio::wasapi",
|
||||
"IAudioClient3 does not currently support dummy context, clear -audiodummy and try again");
|
||||
}
|
||||
|
||||
return new WrappedIAudioClient(audio_client, nullptr);
|
||||
}
|
||||
|
||||
// IUnknown
|
||||
HRESULT STDMETHODCALLTYPE WrappedIAudioClient::QueryInterface(REFIID riid, void **ppvObj) {
|
||||
@@ -198,8 +108,8 @@ HRESULT STDMETHODCALLTYPE WrappedIAudioClient::QueryInterface(REFIID riid, void
|
||||
}
|
||||
|
||||
if (riid == IID_WrappedIAudioClient ||
|
||||
riid == IID_IAudioClient)
|
||||
{
|
||||
riid == IID_IAudioClient ||
|
||||
riid == IID_IAudioClient3) {
|
||||
this->AddRef();
|
||||
*ppvObj = this;
|
||||
|
||||
@@ -290,20 +200,6 @@ HRESULT STDMETHODCALLTYPE WrappedIAudioClient::Initialize(
|
||||
}
|
||||
|
||||
log_info("audio::wasapi", "IAudioClient::Initialize success, hr={}", FMT_HRESULT(ret));
|
||||
|
||||
/*
|
||||
if (ShareMode == AUDCLNT_SHAREMODE_SHARED) {
|
||||
IAudioClockAdjustment *clock = nullptr;
|
||||
|
||||
SAFE_CALL("IAudioClient", "GetService", pReal->GetService(
|
||||
IID_IAudioClockAdjustment,
|
||||
reinterpret_cast<void **>(&clock)));
|
||||
|
||||
SAFE_CALL("IAudioClockAdjustment", "SetSampleRate", clock->SetSampleRate(
|
||||
static_cast<float>(pFormat->nSamplesPerSec)));
|
||||
}
|
||||
*/
|
||||
|
||||
copy_wave_format(&hooks::audio::FORMAT, pFormat);
|
||||
|
||||
return ret;
|
||||
@@ -490,3 +386,93 @@ HRESULT STDMETHODCALLTYPE WrappedIAudioClient::GetService(REFIID riid, void **pp
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
HRESULT STDMETHODCALLTYPE WrappedIAudioClient::IsOffloadCapable(
|
||||
AUDIO_STREAM_CATEGORY Category,
|
||||
BOOL *pbOffloadCapable) {
|
||||
WRAP_VERBOSE;
|
||||
|
||||
CHECK_RESULT(pReal3->IsOffloadCapable(Category, pbOffloadCapable));
|
||||
}
|
||||
|
||||
HRESULT STDMETHODCALLTYPE WrappedIAudioClient::SetClientProperties(
|
||||
const AudioClientProperties *pProperties) {
|
||||
|
||||
WRAP_VERBOSE;
|
||||
CHECK_RESULT(pReal3->SetClientProperties(pProperties));
|
||||
}
|
||||
|
||||
HRESULT STDMETHODCALLTYPE WrappedIAudioClient::GetBufferSizeLimits(
|
||||
const WAVEFORMATEX *pFormat,
|
||||
BOOL bEventDriven,
|
||||
REFERENCE_TIME *phnsMinBufferDuration,
|
||||
REFERENCE_TIME *phnsMaxBufferDuration) {
|
||||
|
||||
WRAP_VERBOSE;
|
||||
CHECK_RESULT(pReal3->GetBufferSizeLimits(
|
||||
pFormat,
|
||||
bEventDriven,
|
||||
phnsMinBufferDuration,
|
||||
phnsMaxBufferDuration));
|
||||
}
|
||||
|
||||
HRESULT STDMETHODCALLTYPE WrappedIAudioClient::GetSharedModeEnginePeriod(
|
||||
const WAVEFORMATEX *pFormat,
|
||||
UINT32 *pDefaultPeriodInFrames,
|
||||
UINT32 *pFundamentalPeriodInFrames,
|
||||
UINT32 *pMinPeriodInFrames,
|
||||
UINT32 *pMaxPeriodInFrames) {
|
||||
|
||||
WRAP_VERBOSE;
|
||||
CHECK_RESULT(pReal3->GetSharedModeEnginePeriod(
|
||||
pFormat,
|
||||
pDefaultPeriodInFrames,
|
||||
pFundamentalPeriodInFrames,
|
||||
pMinPeriodInFrames,
|
||||
pMaxPeriodInFrames));
|
||||
}
|
||||
|
||||
HRESULT STDMETHODCALLTYPE WrappedIAudioClient::GetCurrentSharedModeEnginePeriod(
|
||||
WAVEFORMATEX **ppFormat,
|
||||
UINT32 *pCurrentPeriodInFrames) {
|
||||
|
||||
WRAP_VERBOSE;
|
||||
CHECK_RESULT(pReal3->GetCurrentSharedModeEnginePeriod(
|
||||
ppFormat,
|
||||
pCurrentPeriodInFrames));
|
||||
}
|
||||
|
||||
HRESULT STDMETHODCALLTYPE WrappedIAudioClient::InitializeSharedAudioStream(
|
||||
DWORD StreamFlags,
|
||||
UINT32 PeriodInFrames,
|
||||
const WAVEFORMATEX *pFormat,
|
||||
LPCGUID AudioSessionGuid) {
|
||||
|
||||
if (!pFormat) {
|
||||
return E_POINTER;
|
||||
}
|
||||
|
||||
// verbose output
|
||||
log_info("audio::wasapi", "IAudioClient3::InitializeSharedAudioStream hook hit");
|
||||
log_info("audio::wasapi", "... ShareMode : {}", share_mode_str(AUDCLNT_SHAREMODE_SHARED));
|
||||
log_info("audio::wasapi", "... StreamFlags : {}", stream_flags_str(StreamFlags));
|
||||
log_info("audio::wasapi", "... PeriodInFrames : {}", PeriodInFrames);
|
||||
print_format(pFormat);
|
||||
|
||||
// call next
|
||||
HRESULT ret = pReal3->InitializeSharedAudioStream(
|
||||
StreamFlags,
|
||||
PeriodInFrames,
|
||||
pFormat,
|
||||
AudioSessionGuid);
|
||||
|
||||
// check for failure
|
||||
if (FAILED(ret)) {
|
||||
PRINT_FAILED_RESULT("IAudioClient3", "InitializeSharedAudioStream", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
log_info("audio::wasapi", "IAudioClient3::InitializeSharedAudioStream success, hr={}", FMT_HRESULT(ret));
|
||||
copy_wave_format(&hooks::audio::FORMAT, pFormat);
|
||||
return ret;
|
||||
}
|
||||
|
||||
@@ -16,9 +16,16 @@ static const GUID IID_WrappedIAudioClient = {
|
||||
};
|
||||
|
||||
IAudioClient *wrap_audio_client(IAudioClient *client);
|
||||
IAudioClient3 *wrap_audio_client3(IAudioClient3 *client);
|
||||
|
||||
struct WrappedIAudioClient : IAudioClient {
|
||||
explicit WrappedIAudioClient(IAudioClient *orig, AudioBackend *backend) : pReal(orig), backend(backend) {
|
||||
struct WrappedIAudioClient : IAudioClient3 {
|
||||
|
||||
explicit WrappedIAudioClient(IAudioClient3 *orig3, AudioBackend *backend) :
|
||||
pReal(orig3), pReal3(orig3), backend(backend) {
|
||||
}
|
||||
|
||||
explicit WrappedIAudioClient(IAudioClient *orig, AudioBackend *backend) :
|
||||
pReal(orig), pReal3(nullptr), backend(backend) {
|
||||
}
|
||||
|
||||
WrappedIAudioClient(const WrappedIAudioClient &) = delete;
|
||||
@@ -47,9 +54,43 @@ struct WrappedIAudioClient : IAudioClient {
|
||||
HRESULT STDMETHODCALLTYPE GetService(REFIID riid, void **ppv) override;
|
||||
#pragma endregion
|
||||
|
||||
IAudioClient *const pReal;
|
||||
AudioBackend *const backend;
|
||||
#pragma region IAudioClient2
|
||||
HRESULT STDMETHODCALLTYPE IsOffloadCapable(
|
||||
AUDIO_STREAM_CATEGORY Category,
|
||||
BOOL *pbOffloadCapable) override;
|
||||
|
||||
HRESULT STDMETHODCALLTYPE SetClientProperties(
|
||||
const AudioClientProperties *pProperties) override;
|
||||
|
||||
HRESULT STDMETHODCALLTYPE GetBufferSizeLimits(
|
||||
const WAVEFORMATEX *pFormat,
|
||||
BOOL bEventDriven,
|
||||
REFERENCE_TIME *phnsMinBufferDuration,
|
||||
REFERENCE_TIME *phnsMaxBufferDuration) override;
|
||||
#pragma endregion
|
||||
|
||||
#pragma region IAudioClient3
|
||||
HRESULT STDMETHODCALLTYPE GetSharedModeEnginePeriod(
|
||||
const WAVEFORMATEX *pFormat,
|
||||
UINT32 *pDefaultPeriodInFrames,
|
||||
UINT32 *pFundamentalPeriodInFrames,
|
||||
UINT32 *pMinPeriodInFrames,
|
||||
UINT32 *pMaxPeriodInFrames) override;
|
||||
|
||||
HRESULT STDMETHODCALLTYPE GetCurrentSharedModeEnginePeriod(
|
||||
WAVEFORMATEX **ppFormat,
|
||||
UINT32 *pCurrentPeriodInFrames) override;
|
||||
|
||||
HRESULT STDMETHODCALLTYPE InitializeSharedAudioStream(
|
||||
DWORD StreamFlags,
|
||||
UINT32 PeriodInFrames,
|
||||
const WAVEFORMATEX *pFormat,
|
||||
LPCGUID AudioSessionGuid) override;
|
||||
#pragma endregion
|
||||
|
||||
IAudioClient *const pReal;
|
||||
IAudioClient3 *const pReal3;
|
||||
AudioBackend *const backend;
|
||||
bool exclusive_mode = false;
|
||||
int frame_size = 0;
|
||||
};
|
||||
|
||||
@@ -149,5 +149,12 @@ LowLatencyAudioClient *LowLatencyAudioClient::Create(IMMDevice *device) {
|
||||
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");
|
||||
if (minPeriod < defaultPeriod) {
|
||||
log_info("audio::lowlatency", "minimum period is less than default period, you will see latency improvement");
|
||||
} else {
|
||||
log_warning("audio::lowlatency", "minimum period is not less than default period, you will see NO latency improvement");
|
||||
log_warning("audio::lowlatency", "this an audio driver / hardware limitation");
|
||||
}
|
||||
|
||||
return new LowLatencyAudioClient(audioClient);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
#include "mme.h"
|
||||
#include <mmeapi.h>
|
||||
|
||||
#include "util/detour.h"
|
||||
#include "util/logging.h"
|
||||
|
||||
namespace hooks::audio::mme {
|
||||
|
||||
static decltype(mixerSetControlDetails) *mixerSetControlDetails_orig = nullptr;
|
||||
|
||||
MMRESULT
|
||||
WINAPI
|
||||
mixerSetControlDetails_hook(
|
||||
HMIXEROBJ hmxobj, LPMIXERCONTROLDETAILS pmxcd, DWORD fdwDetails) {
|
||||
log_misc("audio::mme", "mixerSetControlDetails_hook called; ignoring volume change");
|
||||
return MMSYSERR_NOERROR;
|
||||
}
|
||||
|
||||
void init(HINSTANCE module) {
|
||||
mixerSetControlDetails_orig =
|
||||
detour::iat_try("mixerSetControlDetails", mixerSetControlDetails_hook, module);
|
||||
|
||||
if (mixerSetControlDetails_orig != nullptr) {
|
||||
log_misc("audio::mme", "mixerSetControlDetails hooked");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
#include <windows.h>
|
||||
|
||||
namespace hooks::audio::mme {
|
||||
void init(HINSTANCE module);
|
||||
}
|
||||
@@ -35,7 +35,7 @@ std::string share_mode_str(AUDCLNT_SHAREMODE share_mode) {
|
||||
ENUM_VARIANT(AUDCLNT_SHAREMODE_SHARED);
|
||||
ENUM_VARIANT(AUDCLNT_SHAREMODE_EXCLUSIVE);
|
||||
default:
|
||||
return fmt::format("ShareMode(0x{:08x})", share_mode);
|
||||
return fmt::format("ShareMode(0x{:08x})", static_cast<uint32_t>(share_mode));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+81
-1
@@ -1,7 +1,10 @@
|
||||
#include "avshook.h"
|
||||
|
||||
#include <cassert>
|
||||
#include <mutex>
|
||||
#include <map>
|
||||
#include <optional>
|
||||
#include <external/robin_hood.h>
|
||||
|
||||
#include "avs/core.h"
|
||||
#include "avs/ea3.h"
|
||||
@@ -20,8 +23,12 @@ static bool FAKE_FILE_OPEN = false;
|
||||
static std::map<std::string, std::string> ROM_FILE_MAP;
|
||||
static std::string *ROM_FILE_CONTENTS = nullptr;
|
||||
|
||||
static std::mutex lstat_cache_mutex;
|
||||
static robin_hood::unordered_map<std::string, std::pair<int, struct avs::core::avs_stat>> lstat_cache;
|
||||
|
||||
namespace hooks::avs::config {
|
||||
bool DISABLE_VFS_DRIVE_REDIRECTION = false;
|
||||
bool DISABLE_CACHE = false;
|
||||
bool LOG = false;
|
||||
};
|
||||
|
||||
@@ -79,6 +86,74 @@ static bool is_spam_file(const char *file) {
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool is_ldj_cacheable_file(const char *file) {
|
||||
static const char *spam_prefixes[] = {
|
||||
// iidx33: profile background customization, spams on every frame during card entry
|
||||
"/data/graphic/entry_card",
|
||||
// iidx32+: movie thumbnails, spams on every frame during song select
|
||||
"/data/graphic/thumbnail/",
|
||||
"/data/graphic/movie_thumbnail/",
|
||||
};
|
||||
|
||||
for (auto &spam : spam_prefixes) {
|
||||
if (string_begins_with(file, spam)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static int cached_avs_fs_lstat(const char *name, struct avs::core::avs_stat *st) {
|
||||
assert(name != nullptr);
|
||||
assert(st != nullptr);
|
||||
|
||||
const std::string filename(name);
|
||||
int value = 0;
|
||||
std::lock_guard<std::mutex> lock(lstat_cache_mutex);
|
||||
|
||||
// if this was already seen, return cached result
|
||||
if (lstat_cache.contains(filename)) {
|
||||
const auto &result = lstat_cache.at(filename);
|
||||
value = result.first;
|
||||
memcpy(st, &result.second, sizeof(*st));
|
||||
WRAP_DEBUG_FMT(
|
||||
"(cached) file={}, a={}, m={}, c={}, size={}, u={}",
|
||||
name,
|
||||
st->st_atime,
|
||||
st->st_mtime,
|
||||
st->st_ctime,
|
||||
st->filesize,
|
||||
st->unk1);
|
||||
return value;
|
||||
}
|
||||
|
||||
// if not, call the original avs function
|
||||
// this returns 1 on success with valid values in st
|
||||
// return 0 on failure, with filesize set to 0
|
||||
// maybe this could be used for future optimization
|
||||
value = avs::core::avs_fs_lstat(name, st);
|
||||
WRAP_DEBUG_FMT(
|
||||
"file={} a={}, m={}, c={}, size={}, u={}",
|
||||
name,
|
||||
st->st_atime,
|
||||
st->st_mtime,
|
||||
st->st_ctime,
|
||||
st->filesize,
|
||||
st->unk1);
|
||||
|
||||
// and cache it if there is room (prevent unconstrained growth)
|
||||
if (lstat_cache.size() <= 4096) {
|
||||
// c++ was a mistake
|
||||
lstat_cache.emplace(
|
||||
std::piecewise_construct,
|
||||
std::forward_as_tuple(filename),
|
||||
std::forward_as_tuple(value, *st));
|
||||
}
|
||||
|
||||
// done
|
||||
return value;
|
||||
}
|
||||
|
||||
static int avs_fs_fstat(avs::core::avs_file_t fd, struct avs::core::avs_stat *st) {
|
||||
if (is_fake_fd(fd) && ROM_FILE_CONTENTS) {
|
||||
if (st) {
|
||||
@@ -104,7 +179,12 @@ static int avs_fs_lstat(const char *name, struct avs::core::avs_stat *st) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
auto value = avs::core::avs_fs_lstat(name, st);
|
||||
if (!hooks::avs::config::DISABLE_CACHE && (st != nullptr) &&
|
||||
avs::game::is_model("LDJ") && is_ldj_cacheable_file(name)) {
|
||||
return cached_avs_fs_lstat(name, st);
|
||||
}
|
||||
|
||||
const auto value = avs::core::avs_fs_lstat(name, st);
|
||||
if (!is_spam_file(name)) {
|
||||
WRAP_DEBUG_FMT("name: {}", name);
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
namespace hooks::avs {
|
||||
namespace config {
|
||||
extern bool DISABLE_VFS_DRIVE_REDIRECTION;
|
||||
extern bool DISABLE_CACHE;
|
||||
extern bool LOG;
|
||||
};
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
#include <vector>
|
||||
|
||||
#include "avs/game.h"
|
||||
#include "games/gitadora/gitadora.h"
|
||||
#include "util/detour.h"
|
||||
#include "util/utils.h"
|
||||
|
||||
@@ -399,6 +400,12 @@ static BOOL WINAPI ClearCommError_hook(HANDLE hFile, LPDWORD lpErrors, LPCOMSTAT
|
||||
lpStat->cbInQue = custom_handle->bytes_available();
|
||||
}
|
||||
|
||||
// gitadora arena model needs this, or else
|
||||
// the game will keep spamming 0xAA
|
||||
if (games::gitadora::is_arena_model() && lpErrors) {
|
||||
*lpErrors = 0;
|
||||
}
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
|
||||
@@ -13,8 +13,10 @@
|
||||
|
||||
#include "avs/game.h"
|
||||
#include "cfg/screen_resize.h"
|
||||
#include "games/gitadora/gitadora.h"
|
||||
#include "games/iidx/iidx.h"
|
||||
#include "games/sdvx/sdvx.h"
|
||||
#include "games/mfc/mfc.h"
|
||||
#include "games/io.h"
|
||||
#include "hooks/graphics/graphics.h"
|
||||
#include "launcher/launcher.h"
|
||||
@@ -40,6 +42,15 @@
|
||||
#undef min
|
||||
#endif
|
||||
|
||||
#define D3D9_BACKEND_DEBUG 0
|
||||
|
||||
#if D3D9_BACKEND_DEBUG
|
||||
#define log_debug(module, format_str, ...) logger::push( \
|
||||
LOG_FORMAT("M", module, format_str, ## __VA_ARGS__), logger::Style::GREY)
|
||||
#else
|
||||
#define log_debug(module, format_str, ...)
|
||||
#endif
|
||||
|
||||
#define CHECK_RESULT(x) \
|
||||
do { \
|
||||
HRESULT __ret = (x); \
|
||||
@@ -188,7 +199,7 @@ static std::string format2s(D3DFORMAT format) {
|
||||
ENUM_VARIANT(D3DFMT_A2B10G10R10_XR_BIAS);
|
||||
ENUM_VARIANT(D3DFMT_BINARYBUFFER);
|
||||
default:
|
||||
return fmt::to_string(format);
|
||||
return fmt::to_string(static_cast<uint32_t>(format));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -238,14 +249,17 @@ static void log_create_device_failure(HRESULT hresult) {
|
||||
}
|
||||
|
||||
static bool is_dx9_on_12_enabled() {
|
||||
bool result = false;
|
||||
switch (GRAPHICS_9_ON_12_STATE) {
|
||||
case DX9ON12_FORCE_OFF:
|
||||
log_info("graphics::d3d9", "DirectX 9on12: forced OFF by user (-dx9on12)");
|
||||
return false;
|
||||
result = false;
|
||||
break;
|
||||
|
||||
case DX9ON12_FORCE_ON:
|
||||
log_info("graphics::d3d9", "DirectX 9on12: forced ON by user (-9on12 or -dx9on12)");
|
||||
return true;
|
||||
result = true;
|
||||
break;
|
||||
|
||||
case DX9ON12_AUTO:
|
||||
default:
|
||||
@@ -253,14 +267,33 @@ static bool is_dx9_on_12_enabled() {
|
||||
log_info(
|
||||
"graphics::d3d9",
|
||||
"DirectX 9on12: enabled automatically for current game (tip: use -dx9on12 to force on or off)");
|
||||
return true;
|
||||
result = true;
|
||||
} else {
|
||||
log_info(
|
||||
"graphics::d3d9",
|
||||
"DirectX 9on12: disabled by default, using DX9 (tip: use -dx9on12 to force on or off)");
|
||||
return false;
|
||||
result = false;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
if (GRAPHICS_9_ON_12_STATE == DX9ON12_FORCE_ON) {
|
||||
if (avs::game::is_model("LDJ") && avs::game::is_ext(2023091500, MAXINT)) {
|
||||
deferredlogs::defer_error_messages({
|
||||
"dx9on12 was force enabled by user (-dx9on12)",
|
||||
" IIDX31+ is known to be incompatible with DX 9on12, leading to blank screen or crashes",
|
||||
" try again with -dx9on12 option set to default value"
|
||||
});
|
||||
} else {
|
||||
deferredlogs::defer_error_messages({
|
||||
"dx9on12 was force enabled by user (-dx9on12)",
|
||||
" not very game is compatible with this, and can lead to crashes",
|
||||
" try without force enabling this if you are seeing issues"
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
static IDirect3D9 *WINAPI Direct3DCreate9_hook(UINT SDKVersion) {
|
||||
@@ -491,7 +524,7 @@ HRESULT STDMETHODCALLTYPE WrappedIDirect3D9::EnumAdapterModes(
|
||||
if (!modified && games::iidx::is_tdj_fhd() && refresh < 110) {
|
||||
if ((width == 1920 && height == 1080) ||
|
||||
(width == 1280 && height == 720)){
|
||||
log_misc(
|
||||
log_debug(
|
||||
"graphics::d3d9", "removing mode {}, {}x{} @ {}Hz (for TDJ FHD)",
|
||||
Mode, width, height, refresh);
|
||||
memset(pMode, 0, sizeof(*pMode));
|
||||
@@ -504,7 +537,7 @@ HRESULT STDMETHODCALLTYPE WrappedIDirect3D9::EnumAdapterModes(
|
||||
// - 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(
|
||||
log_debug(
|
||||
"graphics::d3d9", "removing mode {}, {}x{} @ {}Hz (for LDJ/TDJ)",
|
||||
Mode, width, height, refresh);
|
||||
memset(pMode, 0, sizeof(*pMode));
|
||||
@@ -512,7 +545,7 @@ HRESULT STDMETHODCALLTYPE WrappedIDirect3D9::EnumAdapterModes(
|
||||
}
|
||||
|
||||
if (!modified && !games::iidx::TDJ_MODE && games::iidx::FORCE_720P && (height > 720)) {
|
||||
log_misc(
|
||||
log_debug(
|
||||
"graphics::d3d9", "removing mode {}, {}x{} @ {}Hz (-iidxforce720p)",
|
||||
Mode, width, height, refresh);
|
||||
memset(pMode, 0, sizeof(*pMode));
|
||||
@@ -673,7 +706,7 @@ HRESULT STDMETHODCALLTYPE WrappedIDirect3D9::CreateDevice(
|
||||
{
|
||||
log_misc("graphics::d3d9", "IDirect3D9::CreateDevice hook hit ({}, {}, {}, {}, {}, {})",
|
||||
Adapter,
|
||||
DeviceType,
|
||||
static_cast<uint32_t>(DeviceType),
|
||||
fmt::ptr(hFocusWindow),
|
||||
behavior2s(BehaviorFlags),
|
||||
fmt::ptr(pPresentationParameters),
|
||||
@@ -748,9 +781,9 @@ HRESULT STDMETHODCALLTYPE WrappedIDirect3D9::CreateDevice(
|
||||
params->BackBufferHeight,
|
||||
format2s(params->BackBufferFormat),
|
||||
params->BackBufferCount,
|
||||
params->MultiSampleType,
|
||||
static_cast<uint32_t>(params->MultiSampleType),
|
||||
params->MultiSampleQuality,
|
||||
params->SwapEffect,
|
||||
static_cast<uint32_t>(params->SwapEffect),
|
||||
params->Windowed,
|
||||
params->EnableAutoDepthStencil,
|
||||
format2s(params->AutoDepthStencilFormat),
|
||||
@@ -780,6 +813,14 @@ HRESULT STDMETHODCALLTYPE WrappedIDirect3D9::CreateDevice(
|
||||
"either use -graphics-force-refresh option or change the desktop resolution beforehand.");
|
||||
}
|
||||
|
||||
if (!GRAPHICS_WINDOWED && num_adapters >= 2 && GRAPHICS_FORCE_REFRESH_SUB.has_value()) {
|
||||
log_info("graphics::d3d9", "force sub refresh rate: {} => {} Hz (-graphics-force-refresh-sub option)",
|
||||
pPresentationParameters[1].FullScreen_RefreshRateInHz,
|
||||
GRAPHICS_FORCE_REFRESH_SUB.value());
|
||||
|
||||
pPresentationParameters[1].FullScreen_RefreshRateInHz = GRAPHICS_FORCE_REFRESH_SUB.value();
|
||||
}
|
||||
|
||||
// force single adapter
|
||||
if (GRAPHICS_FORCE_SINGLE_ADAPTER) {
|
||||
log_info("graphics::d3d9", "disabling adapter group device with force single adapter mode");
|
||||
@@ -862,7 +903,7 @@ HRESULT STDMETHODCALLTYPE WrappedIDirect3D9::CreateDeviceEx(
|
||||
|
||||
log_misc("graphics::d3d9", "IDirect3D9Ex::CreateDeviceEx hook hit ({}, {}, {}, {}, {}, {})",
|
||||
Adapter,
|
||||
DeviceType,
|
||||
static_cast<uint32_t>(DeviceType),
|
||||
fmt::ptr(hFocusWindow),
|
||||
behavior2s(BehaviorFlags),
|
||||
fmt::ptr(pPresentationParameters),
|
||||
@@ -904,26 +945,37 @@ HRESULT STDMETHODCALLTYPE WrappedIDirect3D9::CreateDeviceEx(
|
||||
|
||||
for (size_t i = 0; i < num_adapters; 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()) {
|
||||
if (!GRAPHICS_WINDOWED){
|
||||
if (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);
|
||||
}
|
||||
} else if (i == 1 && GRAPHICS_FS_CUSTOM_RESOLUTION_SUB.has_value()) {
|
||||
log_misc(
|
||||
"graphics::d3d9",
|
||||
"use custom resolution {}x{} => {}x{}",
|
||||
"use custom sub 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);
|
||||
GRAPHICS_FS_CUSTOM_RESOLUTION_SUB.value().first,
|
||||
GRAPHICS_FS_CUSTOM_RESOLUTION_SUB.value().second);
|
||||
params->BackBufferWidth = GRAPHICS_FS_CUSTOM_RESOLUTION_SUB.value().first;
|
||||
params->BackBufferHeight = GRAPHICS_FS_CUSTOM_RESOLUTION_SUB.value().second;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -937,9 +989,9 @@ HRESULT STDMETHODCALLTYPE WrappedIDirect3D9::CreateDeviceEx(
|
||||
params->BackBufferHeight,
|
||||
format2s(params->BackBufferFormat),
|
||||
params->BackBufferCount,
|
||||
params->MultiSampleType,
|
||||
static_cast<uint32_t>(params->MultiSampleType),
|
||||
params->MultiSampleQuality,
|
||||
params->SwapEffect,
|
||||
static_cast<uint32_t>(params->SwapEffect),
|
||||
params->Windowed,
|
||||
params->EnableAutoDepthStencil,
|
||||
format2s(params->AutoDepthStencilFormat),
|
||||
@@ -951,12 +1003,17 @@ HRESULT STDMETHODCALLTYPE WrappedIDirect3D9::CreateDeviceEx(
|
||||
for (size_t i = 0; i < num_adapters; 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);
|
||||
if (!GRAPHICS_WINDOWED) {
|
||||
if (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);
|
||||
}
|
||||
} else if (i == 1 && GRAPHICS_FS_CUSTOM_RESOLUTION_SUB.has_value()) {
|
||||
fullscreen_display_mode->Width = GRAPHICS_FS_CUSTOM_RESOLUTION_SUB.value().first;
|
||||
fullscreen_display_mode->Height = GRAPHICS_FS_CUSTOM_RESOLUTION_SUB.value().second;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -968,7 +1025,7 @@ HRESULT STDMETHODCALLTYPE WrappedIDirect3D9::CreateDeviceEx(
|
||||
fullscreen_display_mode->Height,
|
||||
fullscreen_display_mode->RefreshRate,
|
||||
format2s(fullscreen_display_mode->Format),
|
||||
fullscreen_display_mode->ScanLineOrdering);
|
||||
static_cast<uint32_t>(fullscreen_display_mode->ScanLineOrdering));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -999,6 +1056,17 @@ HRESULT STDMETHODCALLTYPE WrappedIDirect3D9::CreateDeviceEx(
|
||||
}
|
||||
}
|
||||
|
||||
if (!GRAPHICS_WINDOWED && num_adapters >= 2 && GRAPHICS_FORCE_REFRESH_SUB.has_value()) {
|
||||
log_info("graphics::d3d9", "force sub refresh rate: {} => {} Hz (-graphics-force-refresh-sub option)",
|
||||
pPresentationParameters[1].FullScreen_RefreshRateInHz,
|
||||
GRAPHICS_FORCE_REFRESH_SUB.value());
|
||||
|
||||
pPresentationParameters[1].FullScreen_RefreshRateInHz = GRAPHICS_FORCE_REFRESH_SUB.value();
|
||||
if (pFullscreenDisplayMode) {
|
||||
pFullscreenDisplayMode[1].RefreshRate = GRAPHICS_FORCE_REFRESH_SUB.value();
|
||||
}
|
||||
}
|
||||
|
||||
// force single adapter
|
||||
if (GRAPHICS_FORCE_SINGLE_ADAPTER) {
|
||||
log_info("graphics::d3d9", "disabling adapter group device with force single adapter mode");
|
||||
@@ -1119,9 +1187,15 @@ IDirect3DSurface9 *graphics_d3d9_ldj_get_sub_screen() {
|
||||
}
|
||||
|
||||
static void graphics_d3d9_ldj_on_present(IDirect3DDevice9 *wrapped_device) {
|
||||
// iidx/sdvx
|
||||
int swapchain = 1;
|
||||
if (games::gitadora::is_arena_model()) {
|
||||
swapchain = 3;
|
||||
}
|
||||
|
||||
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);
|
||||
HRESULT hr = wrapped_device->GetSwapChain(swapchain, &SUB_SWAP_CHAIN);
|
||||
if (FAILED(hr)) {
|
||||
log_warning(
|
||||
"graphics::d3d9",
|
||||
@@ -1330,14 +1404,19 @@ void graphics_d3d9_on_present(
|
||||
}
|
||||
|
||||
// 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_vm = games::sdvx::is_valkyrie_model();
|
||||
const bool is_tdj = avs::game::is_model("LDJ") && games::iidx::TDJ_MODE;
|
||||
if (is_vm || is_tdj) {
|
||||
const bool is_gfdm_arena =
|
||||
games::gitadora::is_arena_model() && games::gitadora::ARENA_SINGLE_WINDOW;
|
||||
if (is_vm || is_tdj || is_gfdm_arena) {
|
||||
graphics_d3d9_ldj_on_present(wrapped_device);
|
||||
}
|
||||
|
||||
const bool is_mfc = avs::game::is_model("KK9") && games::mfc::HG_MODE;
|
||||
if (is_mfc) {
|
||||
wintouchemu::update();
|
||||
}
|
||||
|
||||
// check screenshot key
|
||||
static bool trigger_last = false;
|
||||
auto buttons = games::get_buttons_overlay(eamuse_get_game());
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
#include <mutex>
|
||||
|
||||
#include "avs/game.h"
|
||||
#include "games/gitadora/gitadora.h"
|
||||
#include "hooks/graphics/graphics.h"
|
||||
#include "overlay/overlay.h"
|
||||
#include "util/flags_helper.h"
|
||||
@@ -36,6 +37,14 @@ constexpr bool CUSTOM_RESET = false;
|
||||
|
||||
constexpr D3DFORMAT D3DFMT_DF24 = static_cast<D3DFORMAT>(MAKEFOURCC('D', 'F', '2', '4'));
|
||||
|
||||
auto format_as(D3DPOOL f) {
|
||||
return fmt::underlying(f);
|
||||
}
|
||||
|
||||
auto format_as(D3DFORMAT f) {
|
||||
return fmt::underlying(f);
|
||||
}
|
||||
|
||||
std::string usage2s(DWORD dwUsage) {
|
||||
FLAGS_START(dwUsage);
|
||||
FLAG(dwUsage, D3DUSAGE_RENDERTARGET);
|
||||
@@ -241,8 +250,16 @@ HRESULT STDMETHODCALLTYPE WrappedIDirect3DDevice9::CreateAdditionalSwapChain(
|
||||
WRAP_VERBOSE;
|
||||
|
||||
HRESULT hr = pReal->CreateAdditionalSwapChain(pPresentationParameters, ppSwapChain);
|
||||
|
||||
bool create_swap_chain = false;
|
||||
if (avs::game::is_model({"LDJ", "KFC"})) {
|
||||
create_swap_chain = true;
|
||||
} else if (games::gitadora::is_arena_model() &&
|
||||
games::gitadora::ARENA_SINGLE_WINDOW &&
|
||||
pPresentationParameters->BackBufferWidth == 800) {
|
||||
create_swap_chain = true;
|
||||
}
|
||||
|
||||
if (create_swap_chain) {
|
||||
if (SUCCEEDED(hr) && !sub_swapchain) {
|
||||
sub_swapchain = new WrappedIDirect3DSwapChain9(this, *ppSwapChain);
|
||||
sub_swapchain->should_run_hooks = false;
|
||||
@@ -287,7 +304,17 @@ HRESULT STDMETHODCALLTYPE WrappedIDirect3DDevice9::GetSwapChain(
|
||||
return D3D_OK;
|
||||
}
|
||||
|
||||
bool swap = false;
|
||||
if (iSwapChain == 1 && avs::game::is_model({"LDJ", "KFC"})) {
|
||||
swap = true;
|
||||
}
|
||||
if (games::gitadora::is_arena_model() &&
|
||||
games::gitadora::ARENA_SINGLE_WINDOW &&
|
||||
iSwapChain == 3) {
|
||||
swap = true;
|
||||
}
|
||||
|
||||
if (swap) {
|
||||
if (sub_swapchain) {
|
||||
sub_swapchain->AddRef();
|
||||
*ppSwapChain = static_cast<IDirect3DSwapChain9 *>(sub_swapchain);
|
||||
@@ -733,6 +760,14 @@ void SurfaceHook(IDirect3DDevice9 *pReal) {
|
||||
const int rectTop = param.BackBufferHeight;
|
||||
const int w = param.BackBufferWidth;
|
||||
const int h = param.BackBufferHeight;
|
||||
|
||||
// this code used to clear the surface using ColorFill on every call, but
|
||||
// this turned out to be very expensive, leading to major frame drops in
|
||||
// SDVX Live2D scenario
|
||||
if (cfg::SCREENRESIZE->need_surface_clean) {
|
||||
pReal->ColorFill(topSurface, nullptr, D3DCOLOR_XRGB(0, 0, 0));
|
||||
cfg::SCREENRESIZE->need_surface_clean = false;
|
||||
}
|
||||
|
||||
D3DLOCKED_RECT rect;
|
||||
HRESULT hr;
|
||||
@@ -750,6 +785,7 @@ void SurfaceHook(IDirect3DDevice9 *pReal) {
|
||||
(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);
|
||||
@@ -757,6 +793,21 @@ void SurfaceHook(IDirect3DDevice9 *pReal) {
|
||||
originRect.bottom = originRect.top + GRAPHICS_FS_ORIGINAL_HEIGHT;
|
||||
}
|
||||
|
||||
bool duplicate = false;
|
||||
RECT originRect2 = originRect;
|
||||
if (cfg::SCREENRESIZE->enable_screen_resize) {
|
||||
auto& scene = cfg::SCREENRESIZE->scene_settings[cfg::SCREENRESIZE->screen_resize_current_scene];
|
||||
if (scene.duplicate == cfg::ScreenDuplicateMode::CopyLeft) {
|
||||
duplicate = true;
|
||||
originRect2.right = originRect.left;
|
||||
originRect2.left = (LONG)(originRect2.left - (originRect.right - originRect.left));
|
||||
} else if (scene.duplicate == cfg::ScreenDuplicateMode::CopyRight) {
|
||||
duplicate = true;
|
||||
originRect2.left = originRect.right;
|
||||
originRect2.right = (LONG)(originRect2.left + (originRect.right - originRect.left));
|
||||
}
|
||||
}
|
||||
|
||||
// log_misc(
|
||||
// "graphics::d3d9", "originRect: {} {} {} {}",
|
||||
// originRect.left, originRect.top, originRect.right, originRect.bottom);
|
||||
@@ -768,9 +819,21 @@ void SurfaceHook(IDirect3DDevice9 *pReal) {
|
||||
if (hr != D3D_OK) {
|
||||
log_warning(
|
||||
"graphics::d3d9",
|
||||
"SurfaceHook - StretchRect backbuffer failed, rect: {} {} {} {}",
|
||||
"SurfaceHook - StretchRect backbuffer failed for main image, rect: {} {} {} {}",
|
||||
originRect.left, originRect.top, originRect.right, originRect.bottom);
|
||||
}
|
||||
if (duplicate) {
|
||||
hr = pReal->StretchRect(
|
||||
backbuffer, nullptr,
|
||||
topSurface, &originRect2,
|
||||
D3DTEXF_LINEAR);
|
||||
if (hr != D3D_OK) {
|
||||
log_warning(
|
||||
"graphics::d3d9",
|
||||
"SurfaceHook - StretchRect backbuffer failed for the duplicate, rect: {} {} {} {}",
|
||||
originRect2.left, originRect2.top, originRect2.right, originRect2.bottom);
|
||||
}
|
||||
}
|
||||
topSurface->UnlockRect();
|
||||
|
||||
// phase 2 - viewport calculation - do the actual zoom / offset math
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
#include "cfg/icon.h"
|
||||
#include "cfg/screen_resize.h"
|
||||
#include "games/ddr/ddr.h"
|
||||
#include "games/gitadora/gitadora.h"
|
||||
#include "games/iidx/iidx.h"
|
||||
#include "hooks/graphics/backends/d3d9/d3d9_backend.h"
|
||||
#include "launcher/shutdown.h"
|
||||
@@ -33,6 +34,7 @@ struct CaptureData {
|
||||
|
||||
HWND TDJ_SUBSCREEN_WINDOW = nullptr;
|
||||
HWND SDVX_SUBSCREEN_WINDOW = nullptr;
|
||||
HWND GFDM_SUBSCREEN_WINDOW = nullptr;
|
||||
|
||||
// icon
|
||||
static HICON WINDOW_ICON = LoadIcon(GetModuleHandle(nullptr), MAKEINTRESOURCE(MAINICON));
|
||||
@@ -60,6 +62,7 @@ graphics_orientation GRAPHICS_ADJUST_ORIENTATION = ORIENTATION_NORMAL;
|
||||
bool GRAPHICS_WINDOWED = false;
|
||||
std::vector<HWND> GRAPHICS_WINDOWS;
|
||||
UINT GRAPHICS_FORCE_REFRESH = 0;
|
||||
std::optional<uint32_t> GRAPHICS_FORCE_REFRESH_SUB;
|
||||
std::optional<int> GRAPHICS_FORCE_VSYNC_BUFFER;
|
||||
bool GRAPHICS_FORCE_SINGLE_ADAPTER = false;
|
||||
bool GRAPHICS_PREVENT_SECONDARY_WINDOW = false;
|
||||
@@ -68,6 +71,7 @@ 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;
|
||||
std::optional<std::pair<uint32_t, uint32_t>> GRAPHICS_FS_CUSTOM_RESOLUTION_SUB;
|
||||
bool GRAPHICS_FS_ORIENTATION_SWAP = false;
|
||||
uint32_t GRAPHICS_FS_ORIGINAL_WIDTH = 0;
|
||||
uint32_t GRAPHICS_FS_ORIGINAL_HEIGHT = 0;
|
||||
@@ -94,6 +98,7 @@ static decltype(SetCursor) *SetCursor_orig = nullptr;
|
||||
static decltype(SetWindowLongA) *SetWindowLongA_orig = nullptr;
|
||||
static decltype(SetWindowLongW) *SetWindowLongW_orig = nullptr;
|
||||
static decltype(SetWindowPos) *SetWindowPos_orig = nullptr;
|
||||
static decltype(ShowWindow) *ShowWindow_orig = nullptr;
|
||||
|
||||
static void reset_window_hook(HWND hWnd) {
|
||||
overlay::destroy(hWnd);
|
||||
@@ -296,6 +301,30 @@ static BOOL WINAPI ClipCursor_hook(const RECT *lpRect) {
|
||||
return ClipCursor_orig(lpRect);
|
||||
}
|
||||
|
||||
static void fix_up_rb_auto_rotate_dimensions(DWORD &w, DWORD &h) {
|
||||
// reflec beat
|
||||
// for whatever reason, rb creates the first window it weird dimensions (774x1389 for reflesia, for example)
|
||||
// so doing ChangeDisplaySettings as-is ends up with DISP_CHANGE_BADMODE
|
||||
// while a second window (with title D3DProxyWindow) does get created with correct dimensions, it's
|
||||
// too late to rotate the window then; as a workaround force the display change now with patched up dimensions
|
||||
|
||||
bool changed = false;
|
||||
if (w == 774) {
|
||||
// LBR, MBR reflesia
|
||||
w = 768;
|
||||
h = 1360;
|
||||
changed = true;
|
||||
} else if (w == 1086) {
|
||||
// MBR (before reflesia)
|
||||
w = 1080;
|
||||
h = 1920;
|
||||
changed = true;
|
||||
}
|
||||
if (changed) {
|
||||
log_misc("graphics", "fix auto-rotate dimensions for reflec beat: w={}, h={}", w, h);
|
||||
}
|
||||
}
|
||||
|
||||
static HWND WINAPI CreateWindowExA_hook(DWORD dwExStyle, LPCSTR lpClassName, LPCSTR lpWindowName,
|
||||
DWORD dwStyle, int x, int y, int nWidth, int nHeight,
|
||||
HWND hWndParent, HMENU hMenu, HINSTANCE hInstance,
|
||||
@@ -320,10 +349,19 @@ static HWND WINAPI CreateWindowExA_hook(DWORD dwExStyle, LPCSTR lpClassName, LPC
|
||||
// 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)
|
||||
const auto adjust_orientation =
|
||||
auto adjust_orientation =
|
||||
(GRAPHICS_ADJUST_ORIENTATION == ORIENTATION_CW ||
|
||||
GRAPHICS_ADJUST_ORIENTATION == ORIENTATION_CCW);
|
||||
|
||||
// reflec beat
|
||||
if (avs::game::is_model("KBR")) {
|
||||
// KBR (rb1) launches landscape and draws rotated by itself, don't do any rotation
|
||||
adjust_orientation = false;
|
||||
} else if (avs::game::is_model({"LBR", "MBR"}) && window_name == "D3DProxyWindow") {
|
||||
// do not auto-rotate for second window (D3DProxyWindow)
|
||||
adjust_orientation = false;
|
||||
}
|
||||
|
||||
if ((nHeight > nWidth && adjust_orientation) || GRAPHICS_FORCE_REFRESH > 0) {
|
||||
DEVMODE mode {};
|
||||
|
||||
@@ -333,11 +371,16 @@ static HWND WINAPI CreateWindowExA_hook(DWORD dwExStyle, LPCSTR lpClassName, LPC
|
||||
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);
|
||||
"auto-rotate: call ChangeDisplaySettings and rotate display to DMDO_xx mode {}, w={}, h={}",
|
||||
orientation, nWidth, nHeight);
|
||||
mode.dmPelsWidth = nWidth;
|
||||
mode.dmPelsHeight = nHeight;
|
||||
mode.dmDisplayOrientation = orientation;
|
||||
|
||||
// reflec beat
|
||||
if (avs::game::is_model({"LBR", "MBR"})) {
|
||||
fix_up_rb_auto_rotate_dimensions(mode.dmPelsWidth, mode.dmPelsHeight);
|
||||
}
|
||||
}
|
||||
|
||||
if (GRAPHICS_FORCE_REFRESH > 0) {
|
||||
@@ -352,7 +395,7 @@ static HWND WINAPI CreateWindowExA_hook(DWORD dwExStyle, LPCSTR lpClassName, LPC
|
||||
|
||||
const auto disp_res = ChangeDisplaySettings(&mode, CDS_FULLSCREEN);
|
||||
if (disp_res != DISP_CHANGE_SUCCESSFUL) {
|
||||
log_warning("graphics", "failed to change display settings: {}", disp_res);
|
||||
log_warning("graphics", "ChangeDisplaySettings failed: {}", disp_res);
|
||||
}
|
||||
} else {
|
||||
log_warning("graphics", "failed to get display settings");
|
||||
@@ -369,11 +412,16 @@ static HWND WINAPI CreateWindowExA_hook(DWORD dwExStyle, LPCSTR lpClassName, LPC
|
||||
|
||||
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");
|
||||
bool is_gfdm_sub_window = games::gitadora::is_arena_model() && window_name.ends_with("SMALL");
|
||||
|
||||
// TDJ windowed mode with subscreen: hide maximize button
|
||||
if ((is_tdj_sub_window && GRAPHICS_IIDX_WSUB) || is_sdvx_sub_window) {
|
||||
// hide maximize button (prevent misaligned touches)
|
||||
if ((is_tdj_sub_window && GRAPHICS_IIDX_WSUB) || is_sdvx_sub_window || is_gfdm_sub_window) {
|
||||
dwStyle &= ~(WS_MAXIMIZEBOX);
|
||||
}
|
||||
// mouse clicks become misaligned when resized
|
||||
if (is_gfdm_sub_window) {
|
||||
dwStyle &= ~(WS_SIZEBOX);
|
||||
}
|
||||
if ((is_tdj_sub_window || is_sdvx_sub_window) && GRAPHICS_WSUB_BORDERLESS) {
|
||||
dwStyle &= ~(WS_OVERLAPPEDWINDOW);
|
||||
}
|
||||
@@ -417,7 +465,19 @@ static HWND WINAPI CreateWindowExA_hook(DWORD dwExStyle, LPCSTR lpClassName, LPC
|
||||
graphics_hook_subscreen_window(SDVX_SUBSCREEN_WINDOW);
|
||||
}
|
||||
|
||||
// only hook touch window if multiple windows are allowed
|
||||
if (is_gfdm_sub_window && !games::gitadora::ARENA_SINGLE_WINDOW) {
|
||||
GFDM_SUBSCREEN_WINDOW = result;
|
||||
graphics_hook_subscreen_window(GFDM_SUBSCREEN_WINDOW);
|
||||
}
|
||||
|
||||
disable_touch_indicators(result);
|
||||
log_misc(
|
||||
"graphics",
|
||||
"CreateWindowExA returned {}, {}",
|
||||
fmt::ptr(result),
|
||||
lpWindowName ? lpWindowName : "(null)");
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -449,7 +509,8 @@ static HWND WINAPI CreateWindowExW_hook(DWORD dwExStyle, LPCWSTR lpClassName, LP
|
||||
}
|
||||
|
||||
// windowed mode adjustments
|
||||
if (GRAPHICS_WINDOWED) {
|
||||
// check the width to filter out invisible system-created windows
|
||||
if (GRAPHICS_WINDOWED && nWidth > 0) {
|
||||
|
||||
// change window style
|
||||
dwExStyle = 0;
|
||||
@@ -484,6 +545,12 @@ static HWND WINAPI CreateWindowExW_hook(DWORD dwExStyle, LPCWSTR lpClassName, LP
|
||||
hWndParent, hMenu, hInstance, lpParam);
|
||||
GRAPHICS_WINDOWS.push_back(result);
|
||||
|
||||
log_misc(
|
||||
"graphics",
|
||||
"CreateWindowExW returned {}, {}",
|
||||
fmt::ptr(result),
|
||||
lpWindowName ? ws2s(lpWindowName) : "(null)");
|
||||
|
||||
disable_touch_indicators(result);
|
||||
return result;
|
||||
}
|
||||
@@ -648,10 +715,29 @@ static BOOL WINAPI SetWindowPos_hook(HWND hWnd, HWND hWndInsertAfter,
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
// prevent gitadora arena model from shifting windows around if the user has preferences
|
||||
if (GRAPHICS_WINDOWED && games::gitadora::is_arena_model() &&
|
||||
GRAPHICS_HOOKED_WINDOW.has_value() && hWnd == GRAPHICS_HOOKED_WINDOW.value() &&
|
||||
cfg::SCREENRESIZE->enable_window_resize) {
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
// call original
|
||||
return SetWindowPos_orig(hWnd, hWndInsertAfter, X, Y, cx, cy, uFlags);
|
||||
}
|
||||
|
||||
static BOOL WINAPI ShowWindow_hook(HWND hWnd, int nCmdShow) {
|
||||
if (games::gitadora::is_arena_model() &&
|
||||
games::gitadora::ARENA_SINGLE_WINDOW &&
|
||||
hWnd != GRAPHICS_HOOKED_WINDOW) {
|
||||
log_info("graphics", "ShowWindow_hook - hiding sub window {}", fmt::ptr(hWnd));
|
||||
return true;
|
||||
}
|
||||
|
||||
// call original
|
||||
return ShowWindow_orig(hWnd, nCmdShow);
|
||||
}
|
||||
|
||||
static ATOM WINAPI RegisterClassA_hook(const WNDCLASSA *lpWndClass) {
|
||||
|
||||
// check for null
|
||||
@@ -794,6 +880,7 @@ void graphics_init() {
|
||||
SetWindowLongA_orig = detour::iat_try("SetWindowLongA", SetWindowLongA_hook);
|
||||
SetWindowLongW_orig = detour::iat_try("SetWindowLongW", SetWindowLongW_hook);
|
||||
SetWindowPos_orig = detour::iat_try("SetWindowPos", SetWindowPos_hook);
|
||||
ShowWindow_orig = detour::iat_try("ShowWindow", ShowWindow_hook);
|
||||
|
||||
detour::iat_try("MessageBoxA", MessageBoxA_hook);
|
||||
detour::iat_try("MessageBoxExA", MessageBoxExA_hook);
|
||||
|
||||
@@ -32,10 +32,12 @@ extern bool GRAPHICS_WINDOWED;
|
||||
extern graphics_orientation GRAPHICS_ADJUST_ORIENTATION;
|
||||
extern std::vector<HWND> GRAPHICS_WINDOWS;
|
||||
extern UINT GRAPHICS_FORCE_REFRESH;
|
||||
extern std::optional<uint32_t> GRAPHICS_FORCE_REFRESH_SUB;
|
||||
extern std::optional<int> GRAPHICS_FORCE_VSYNC_BUFFER;
|
||||
extern bool GRAPHICS_FORCE_SINGLE_ADAPTER;
|
||||
extern bool GRAPHICS_PREVENT_SECONDARY_WINDOW;
|
||||
extern std::optional<std::pair<uint32_t, uint32_t>> GRAPHICS_FS_CUSTOM_RESOLUTION;
|
||||
extern std::optional<std::pair<uint32_t, uint32_t>> GRAPHICS_FS_CUSTOM_RESOLUTION_SUB;
|
||||
extern bool GRAPHICS_FS_ORIENTATION_SWAP;
|
||||
extern uint32_t GRAPHICS_FS_ORIGINAL_WIDTH;
|
||||
extern uint32_t GRAPHICS_FS_ORIGINAL_HEIGHT;
|
||||
@@ -48,6 +50,7 @@ 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 std::optional<HWND> GRAPHICS_HOOKED_WINDOW;
|
||||
|
||||
extern bool GRAPHICS_IIDX_WSUB;
|
||||
extern std::optional<std::string> GRAPHICS_WSUB_SIZE;
|
||||
@@ -60,6 +63,7 @@ extern bool GRAPHICS_WSUB_BORDERLESS;
|
||||
extern bool GRAPHICS_WSUB_ALWAYS_ON_TOP;
|
||||
extern HWND TDJ_SUBSCREEN_WINDOW;
|
||||
extern HWND SDVX_SUBSCREEN_WINDOW;
|
||||
extern HWND GFDM_SUBSCREEN_WINDOW;
|
||||
|
||||
extern bool SUBSCREEN_FORCE_REDRAW;
|
||||
|
||||
@@ -98,5 +102,8 @@ void graphics_capture_initial_window(HWND hWnd);
|
||||
void graphics_update_window_style(HWND hWnd);
|
||||
void graphics_update_z_order(HWND hWnd, bool always_on_top);
|
||||
void graphics_move_resize_window(HWND hWnd);
|
||||
bool graphics_window_change_crashes_game();
|
||||
bool graphics_window_options_breaks_game();
|
||||
bool graphics_window_decoration_change_crashes_game();
|
||||
bool graphics_window_resize_breaks_game();
|
||||
bool graphics_window_move_and_resize_breaks_game();
|
||||
void graphics_load_windowed_subscreen_parameters();
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#include "graphics.h"
|
||||
|
||||
#include "avs/game.h"
|
||||
#include "games/gitadora/gitadora.h"
|
||||
#include "cfg/screen_resize.h"
|
||||
#include "overlay/overlay.h"
|
||||
#include "util/logging.h"
|
||||
@@ -23,6 +24,7 @@ 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;
|
||||
std::optional<HWND> GRAPHICS_HOOKED_WINDOW;
|
||||
|
||||
// IIDX Windowed Subscreen - starts out as false, enabled by IIDX module on pre-attach as needed
|
||||
bool GRAPHICS_IIDX_WSUB = false;
|
||||
@@ -55,15 +57,22 @@ void graphics_capture_initial_window(HWND hWnd) {
|
||||
|
||||
graphics_load_windowed_parameters();
|
||||
|
||||
cfg::SCREENRESIZE->init_window_style = GetWindowLong(hWnd, GWL_STYLE);
|
||||
GRAPHICS_HOOKED_WINDOW = hWnd;
|
||||
|
||||
log_debug("graphics-windowed", "graphics_capture_initial_window called");
|
||||
cfg::SCREENRESIZE->init_window_style = GetWindowLong(hWnd, GWL_STYLE);
|
||||
cfg::SCREENRESIZE->init_window_style_ex = GetWindowLong(hWnd, GWL_EXSTYLE);
|
||||
|
||||
log_debug(
|
||||
"graphics-windowed",
|
||||
"graphics_capture_initial_window called, hWnd={}",
|
||||
fmt::ptr(hWnd));
|
||||
|
||||
RECT rect;
|
||||
if (!GetClientRect(hWnd, &rect)) {
|
||||
log_warning(
|
||||
"graphics",
|
||||
"graphics_capture_initial_window - GetClientRect failed, GLE: {}",
|
||||
"graphics-windowed",
|
||||
"[{}] graphics_capture_initial_window - GetClientRect failed, GLE: {}",
|
||||
fmt::ptr(hWnd),
|
||||
GetLastError());
|
||||
return;
|
||||
}
|
||||
@@ -75,12 +84,22 @@ void graphics_capture_initial_window(HWND hWnd) {
|
||||
cfg::SCREENRESIZE->init_client_aspect_ratio = (float)client_w / (float)client_h;
|
||||
log_debug(
|
||||
"graphics-windowed",
|
||||
"graphics_capture_initial_window initial window size {}x{}, ratio {}",
|
||||
"[{}] graphics_capture_initial_window initial window size {}x{}, ratio {}",
|
||||
fmt::ptr(hWnd),
|
||||
client_w, client_h, cfg::SCREENRESIZE->init_client_aspect_ratio);
|
||||
|
||||
// ensure frame size is captured
|
||||
graphics_wm_style_changed(hWnd, false);
|
||||
|
||||
// if the game is not compatible, ignore user-supplied values and keep the current size
|
||||
if (graphics_window_resize_breaks_game()) {
|
||||
cfg::SCREENRESIZE->client_width = client_w;
|
||||
cfg::SCREENRESIZE->client_height = client_h;
|
||||
}
|
||||
if (graphics_window_move_and_resize_breaks_game()) {
|
||||
cfg::SCREENRESIZE->enable_window_resize = false;
|
||||
}
|
||||
|
||||
// if there was no user-supplied dimension, seed it with the current size
|
||||
// so that the next resize operation will work
|
||||
if (cfg::SCREENRESIZE->client_width == 0) {
|
||||
@@ -94,7 +113,8 @@ void graphics_capture_initial_window(HWND hWnd) {
|
||||
// resize must be done before applying the border
|
||||
if (cfg::SCREENRESIZE->enable_window_resize) {
|
||||
log_info(
|
||||
"graphics-windowed", "change window rect - window offset: {}x{}, client size: {}x{}",
|
||||
"graphics-windowed", "[{}] change window rect - window offset: {}x{}, client size: {}x{}",
|
||||
fmt::ptr(hWnd),
|
||||
cfg::SCREENRESIZE->window_offset_x,
|
||||
cfg::SCREENRESIZE->window_offset_y,
|
||||
cfg::SCREENRESIZE->client_width,
|
||||
@@ -104,12 +124,13 @@ void graphics_capture_initial_window(HWND hWnd) {
|
||||
// ddr hangs when window frame doesn't have overlapped
|
||||
if (cfg::SCREENRESIZE->window_decoration != cfg::WindowDecorationMode::Default) {
|
||||
log_info(
|
||||
"graphics-windowed", "change window style - decoration: {}",
|
||||
"graphics-windowed", "[{}] change window style - decoration: {}",
|
||||
fmt::ptr(hWnd),
|
||||
cfg::SCREENRESIZE->window_decoration);
|
||||
graphics_update_window_style(hWnd);
|
||||
}
|
||||
if (cfg::SCREENRESIZE->window_always_on_top) {
|
||||
log_info("graphics-windowed", "change window z-order - always on top");
|
||||
log_info("graphics-windowed", "[{}] change window z-order - always on top", fmt::ptr(hWnd));
|
||||
graphics_update_z_order(hWnd, true);
|
||||
}
|
||||
|
||||
@@ -152,7 +173,7 @@ void graphics_load_windowed_parameters() {
|
||||
cfg::SCREENRESIZE->window_offset_x = result.first;
|
||||
cfg::SCREENRESIZE->window_offset_y = result.second;
|
||||
} else {
|
||||
log_warning("graphics-windowed", "failed to parse -windowpos");
|
||||
log_fatal("graphics-windowed", "failed to parse -windowpos");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -181,7 +202,7 @@ void graphics_load_windowed_subscreen_parameters() {
|
||||
GRAPHICS_WSUB_WIDTH = result.first;
|
||||
GRAPHICS_WSUB_HEIGHT = result.second;
|
||||
} else {
|
||||
log_warning("graphics-windowed", "failed to parse -wsubsize");
|
||||
log_fatal("graphics-windowed", "failed to parse -iidxwsubsize / -sdvxwsubsize");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -195,7 +216,7 @@ void graphics_load_windowed_subscreen_parameters() {
|
||||
GRAPHICS_WSUB_X = result.first;
|
||||
GRAPHICS_WSUB_Y = result.second;
|
||||
} else {
|
||||
log_warning("graphics-windowed", "failed to parse -wsubpos");
|
||||
log_fatal("graphics-windowed", "failed to parse -iidxwsubpos / -sdvxwsubpos");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -368,20 +389,29 @@ void graphics_update_window_style(HWND hWnd) {
|
||||
if (!GRAPHICS_WINDOWED) {
|
||||
return;
|
||||
}
|
||||
if (graphics_window_change_crashes_game()) {
|
||||
if (graphics_window_decoration_change_crashes_game()) {
|
||||
return;
|
||||
}
|
||||
|
||||
log_debug("graphics-windowed", "graphics_update_window_style called");
|
||||
log_debug(
|
||||
"graphics-windowed",
|
||||
"graphics_update_window_style called for hWnd={}",
|
||||
fmt::ptr(hWnd));
|
||||
|
||||
// update frame style
|
||||
auto style = cfg::SCREENRESIZE->init_window_style;
|
||||
auto style_ex = cfg::SCREENRESIZE->init_window_style_ex;
|
||||
switch (cfg::SCREENRESIZE->window_decoration) {
|
||||
case cfg::WindowDecorationMode::Borderless:
|
||||
style &= ~WS_OVERLAPPEDWINDOW;
|
||||
style_ex &= ~WS_EX_CLIENTEDGE;
|
||||
style_ex &= ~WS_EX_WINDOWEDGE;
|
||||
break;
|
||||
case cfg::WindowDecorationMode::ResizableFrame:
|
||||
style |= WS_OVERLAPPEDWINDOW;
|
||||
if (graphics_window_resize_breaks_game()) {
|
||||
style &= ~WS_SIZEBOX;
|
||||
}
|
||||
break;
|
||||
case cfg::WindowDecorationMode::Default:
|
||||
default:
|
||||
@@ -393,11 +423,26 @@ void graphics_update_window_style(HWND hWnd) {
|
||||
"graphics_update_window_style - calling SetWindowLong with Mode {}, style 0x{:x}",
|
||||
static_cast<int>(cfg::SCREENRESIZE->window_decoration),
|
||||
style);
|
||||
SetWindowLong(hWnd, GWL_STYLE, style);
|
||||
|
||||
const auto ret = SetWindowLong(hWnd, GWL_STYLE, style);
|
||||
(void)ret;
|
||||
|
||||
log_debug(
|
||||
"graphics-windowed",
|
||||
"graphics_update_window_style - SetWindowLong(GWL_STYLE) returned {:x}, GLE={}",
|
||||
ret,
|
||||
GetLastError());
|
||||
|
||||
const auto ret2 = SetWindowLong(hWnd, GWL_EXSTYLE, style_ex);
|
||||
(void)ret2;
|
||||
log_debug(
|
||||
"graphics-windowed",
|
||||
"graphics_update_window_style SetWindowLong(GWL_EXSTYLE) returned {:x}, GLE={}",
|
||||
ret2,
|
||||
GetLastError());
|
||||
|
||||
// SetWindowPos must be called after SetWindowLong if the frame style changed
|
||||
// this will be done in WM_STYLECHANGED handler
|
||||
log_debug("graphics-windowed", "graphics_update_window_style returned");
|
||||
}
|
||||
|
||||
void graphics_update_z_order(HWND hWnd, bool always_on_top) {
|
||||
@@ -454,12 +499,19 @@ void graphics_move_resize_window(HWND hWnd) {
|
||||
log_debug("graphics-windowed", "graphics_move_resize_window returned");
|
||||
}
|
||||
|
||||
bool graphics_window_change_crashes_game() {
|
||||
bool graphics_window_decoration_change_crashes_game() {
|
||||
static std::once_flag flag;
|
||||
static bool result = false;
|
||||
std::call_once(flag, []() {
|
||||
// ddr crashes when frame style changes
|
||||
result = avs::game::is_model("MDX");
|
||||
|
||||
// changing window decoration also changes dimensions and position
|
||||
// so this must be prevented
|
||||
if (graphics_window_move_and_resize_breaks_game()) {
|
||||
result = true;
|
||||
}
|
||||
|
||||
if (result) {
|
||||
log_warning(
|
||||
"graphics-windowed",
|
||||
@@ -468,3 +520,43 @@ bool graphics_window_change_crashes_game() {
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
bool graphics_window_move_and_resize_breaks_game() {
|
||||
static std::once_flag flag;
|
||||
static bool result = false;
|
||||
|
||||
std::call_once(flag, []() {
|
||||
// games like reflec beat in use spice touch overlay for windowed mode
|
||||
// and mouse input breaks if window is moved as the overlay does not
|
||||
// currently support window moves/resizes
|
||||
result = avs::game::is_model({"KBR", "LBR", "MBR"});
|
||||
if (result) {
|
||||
log_warning(
|
||||
"graphics-windowed",
|
||||
"ignoring changes to window properties due to incompatibility with this game");
|
||||
}
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
bool graphics_window_resize_breaks_game() {
|
||||
// for whatever reason, gitadora games on dx/sd/sd2 cabs behave poorly when window is resized
|
||||
// (eihter at launch or while it's running)
|
||||
// usually results in: soft lock during scene transtions or really long transitions,
|
||||
// backgrounds of menus not rendering, etc.
|
||||
bool is_gitadora = avs::game::is_model({ "J32", "J33", "K32", "K33", "L32", "L33", "M32" });
|
||||
bool is_arena = games::gitadora::is_arena_model();
|
||||
|
||||
bool result = graphics_window_move_and_resize_breaks_game() || (is_gitadora && !is_arena);
|
||||
|
||||
static std::once_flag flag;
|
||||
std::call_once(flag, [&result]() {
|
||||
if (result) {
|
||||
log_warning(
|
||||
"graphics-windowed",
|
||||
"ignoring changes to window size due to incompatibility with this game");
|
||||
}
|
||||
});
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
+137
-21
@@ -9,6 +9,7 @@
|
||||
#include "util/detour.h"
|
||||
#include "util/libutils.h"
|
||||
#include "util/logging.h"
|
||||
#include "util/utils.h"
|
||||
|
||||
#include "nvenc_hook.h"
|
||||
|
||||
@@ -16,17 +17,54 @@
|
||||
|
||||
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 PNVENCGETENCODEPRESETCONFIG nvEncGetEncodePresetConfig_orig = nullptr;
|
||||
static PNVENCGETENCODEPRESETCONFIGEX nvEncGetEncodePresetConfigEx_orig = nullptr;
|
||||
static BOOL initialized = false;
|
||||
static BOOL new_preset_guids = false;
|
||||
|
||||
namespace nvenc_hook {
|
||||
|
||||
bool FORCE_DISABLE;
|
||||
|
||||
std::optional<std::string> VIDEO_CQP_STRING_OVERRIDE;
|
||||
std::optional<NV_ENC_QP> VIDEO_CQP_OVERRIDE;
|
||||
|
||||
void parse_qcp_params();
|
||||
|
||||
void dump_init_params(NV_ENC_INITIALIZE_PARAMS* encode) {
|
||||
log_misc("nvenc_hook", "NV_ENC_INITIALIZE_PARAMS:");
|
||||
log_misc("nvenc_hook", " version: {:x}", encode->version);
|
||||
log_misc("nvenc_hook", " encodeGUID: {}", guid2s(encode->encodeGUID));
|
||||
log_misc("nvenc_hook", " presetGUID: {}", guid2s(encode->presetGUID));
|
||||
log_misc("nvenc_hook", " encodeWidth: {}", encode->encodeWidth);
|
||||
log_misc("nvenc_hook", " encodeHeight: {}", encode->encodeHeight);
|
||||
log_misc("nvenc_hook", " darWidth: {}", encode->darWidth);
|
||||
log_misc("nvenc_hook", " darHeight: {}", encode->darHeight);
|
||||
log_misc("nvenc_hook", " frameRateNum: {}", encode->frameRateNum);
|
||||
log_misc("nvenc_hook", " frameRateDen: {}", encode->frameRateDen);
|
||||
log_misc("nvenc_hook", " enableEncodeAsync: {}", encode->enableEncodeAsync);
|
||||
log_misc("nvenc_hook", " enablePTD: {}", encode->enablePTD);
|
||||
log_misc("nvenc_hook", " maxEncodeWidth: {}", encode->maxEncodeWidth);
|
||||
log_misc("nvenc_hook", " maxEncodeHeight: {}", encode->maxEncodeHeight);
|
||||
log_misc("nvenc_hook", " tuningInfo: {}", static_cast<uint32_t>(encode->tuningInfo));
|
||||
log_misc("nvenc_hook", " bufferFormat: {}", static_cast<uint32_t>(encode->bufferFormat));
|
||||
log_misc("nvenc_hook", " encodeConfig.version: {:x}", encode->encodeConfig->version);
|
||||
log_misc("nvenc_hook", " encodeConfig.profileGUID: {}", guid2s(encode->encodeConfig->profileGUID));
|
||||
log_misc("nvenc_hook", " encodeConfig.gopLength: {}", encode->encodeConfig->gopLength);
|
||||
log_misc("nvenc_hook", " encodeConfig.frameIntervalP: {}", encode->encodeConfig->frameIntervalP);
|
||||
log_misc("nvenc_hook", " encodeConfig.monoChromeEncoding: {}", encode->encodeConfig->monoChromeEncoding);
|
||||
|
||||
const auto rc = &encode->encodeConfig->rcParams;
|
||||
const auto h264 = &encode->encodeConfig->encodeCodecConfig.h264Config;
|
||||
log_misc("nvenc_hook", " encodeConfig.encodeCodecConfig.h264Config.sliceMode: {}", h264->sliceMode);
|
||||
log_misc("nvenc_hook", " encodeConfig.encodeCodecConfig.h264Config.sliceModeData: {}", h264->sliceModeData);
|
||||
log_misc("nvenc_hook", " encodeConfig.rcParams.version: {:x}", rc->version);
|
||||
log_misc("nvenc_hook", " encodeConfig.rcParams.rateControlMode: {}", static_cast<uint32_t>(rc->rateControlMode));
|
||||
}
|
||||
|
||||
NVENCSTATUS NVENCAPI nvEncOpenEncodeSessionEx_hook(
|
||||
NV_ENC_OPEN_ENCODE_SESSION_EX_PARAMS *openSessionExParams,
|
||||
void **encoder
|
||||
@@ -51,41 +89,113 @@ namespace nvenc_hook {
|
||||
try {
|
||||
// check input params
|
||||
if (createEncodeParams == nullptr || createEncodeParams->encodeConfig == nullptr) {
|
||||
log_warning("nvenc_hook", "nvEncInitializeEncoder called with invalid params");
|
||||
goto done;
|
||||
}
|
||||
|
||||
const auto rc = &createEncodeParams->encodeConfig->rcParams;
|
||||
if (rc->rateControlMode != NV_ENC_PARAMS_RC_CONSTQP) {
|
||||
|
||||
log_misc("nvenc_hook", "nvEncInitializeEncoder hook hit with expected params");
|
||||
dump_init_params(createEncodeParams);
|
||||
if (new_preset_guids) {
|
||||
log_misc("nvenc_hook", "swapping out encoder preset for newer NVENC SDK");
|
||||
memcpy(&createEncodeParams->presetGUID, &NV_ENC_PRESET_P4_GUID, sizeof(GUID));
|
||||
createEncodeParams->tuningInfo = NV_ENC_TUNING_INFO_HIGH_QUALITY;
|
||||
}
|
||||
|
||||
// cqp p/b/i override
|
||||
if (rc->rateControlMode == NV_ENC_PARAMS_RC_CONSTQP) {
|
||||
// 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);
|
||||
}
|
||||
} else {
|
||||
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);
|
||||
static_cast<uint32_t>(rc->rateControlMode));
|
||||
}
|
||||
|
||||
} catch (const std::exception &ex) {}
|
||||
|
||||
done:
|
||||
return nvEncInitializeEncoder_orig(encoder, createEncodeParams);
|
||||
const auto status = nvEncInitializeEncoder_orig(encoder, createEncodeParams);
|
||||
log_misc(
|
||||
"nvenc_hook",
|
||||
"nvEncInitializeEncoder returned 0x{:x}",
|
||||
static_cast<uint32_t>(status));
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
NVENCSTATUS NVENCAPI nvEncGetEncodePresetConfig_hook (
|
||||
void* encoder, GUID encodeGUID, GUID presetGUID, NV_ENC_PRESET_CONFIG* presetConfig) {
|
||||
|
||||
// IIDX32 calls this with
|
||||
// presetGUID = {34DBA71D-A77B-4B8F-9C3E-B6D5DA24C012} (NV_ENC_PRESET_HQ_GUID) (for h264)
|
||||
// this preset is deprecated according to NVIDIA
|
||||
|
||||
const auto status = nvEncGetEncodePresetConfig_orig(
|
||||
encoder, encodeGUID, presetGUID, presetConfig);
|
||||
|
||||
log_misc(
|
||||
"nvenc_hook",
|
||||
"NvEncGetEncodePresetConfig called with encodeGUID = {}, presetGUID = {}. and returned 0x{:x}",
|
||||
guid2s(encodeGUID),
|
||||
guid2s(presetGUID),
|
||||
static_cast<uint32_t>(status));
|
||||
|
||||
if (status == NV_ENC_SUCCESS) {
|
||||
return status;
|
||||
}
|
||||
|
||||
// in NVIDIA driver 591.44 released in December 2025,
|
||||
// NvEncGetEncodePresetConfig started to fail with NV_ENC_ERR_UNSUPPORTED_PARAM and
|
||||
// eventually cause a crash
|
||||
//
|
||||
// IIDX32 calls this with
|
||||
// presetGUID = {34DBA71D-A77B-4B8F-9C3E-B6D5DA24C012} (NV_ENC_PRESET_HQ_GUID) (for h264)
|
||||
// this preset is deprecated according to NVIDIA
|
||||
// https://docs.nvidia.com/video-technologies/video-codec-sdk/13.0/deprecation-notices/index.html
|
||||
//
|
||||
// references:
|
||||
// https://github.com/NVIDIA/video-sdk-samples/tree/aa3544dcea2fe63122e4feb83bf805ea40e58dbe/Samples/NvCodec/NvEncoder
|
||||
// https://forums.developer.nvidia.com/t/drivers-591-44-broke-nvenc-getencodepresetconfig-no-longer-works/353613
|
||||
// https://docs.nvidia.com/video-technologies/video-codec-sdk/11.1/nvenc-preset-migration-guide/index.html
|
||||
|
||||
new_preset_guids = true;
|
||||
const auto status_ex =
|
||||
nvEncGetEncodePresetConfigEx_orig(
|
||||
encoder,
|
||||
encodeGUID,
|
||||
NV_ENC_PRESET_P4_GUID,
|
||||
NV_ENC_TUNING_INFO_HIGH_QUALITY,
|
||||
presetConfig);
|
||||
|
||||
log_misc(
|
||||
"nvenc_hook",
|
||||
"called NvEncGetEncodePresetConfigEx instead; returned 0x{:x}",
|
||||
static_cast<uint32_t>(status_ex));
|
||||
|
||||
return status_ex;
|
||||
}
|
||||
|
||||
NVENCSTATUS NVENCAPI NvEncodeAPICreateInstance_hook(NV_ENCODE_API_FUNCTION_LIST *pFunctionList) {
|
||||
|
||||
if (FORCE_DISABLE) {
|
||||
return NV_ENC_ERR_NO_ENCODE_DEVICE;
|
||||
}
|
||||
|
||||
// log_misc("nvenc_hook", "NvEncodeAPICreateInstance hook hit");
|
||||
auto status = NvEncodeAPICreateInstance_orig(pFunctionList);
|
||||
|
||||
@@ -101,6 +211,11 @@ namespace nvenc_hook {
|
||||
pFunctionList->nvEncInitializeEncoder,
|
||||
nvEncInitializeEncoder_hook,
|
||||
&nvEncInitializeEncoder_orig);
|
||||
detour::trampoline_try(
|
||||
pFunctionList->nvEncGetEncodePresetConfig,
|
||||
nvEncGetEncodePresetConfig_hook,
|
||||
&nvEncGetEncodePresetConfig_orig);
|
||||
nvEncGetEncodePresetConfigEx_orig = pFunctionList->nvEncGetEncodePresetConfigEx;
|
||||
log_misc("nvenc_hook", "NvEncodeAPICreateInstance_hook called and functions hooked");
|
||||
initialized = true;
|
||||
}
|
||||
@@ -123,6 +238,7 @@ namespace nvenc_hook {
|
||||
} else {
|
||||
log_warning("nvenc_hook", "failed to hook NvEncodeAPICreateInstance");
|
||||
}
|
||||
|
||||
parse_qcp_params();
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,12 @@
|
||||
#pragma once
|
||||
|
||||
#ifdef SPICE64
|
||||
|
||||
namespace nvenc_hook {
|
||||
|
||||
extern bool FORCE_DISABLE;
|
||||
extern std::optional<std::string> VIDEO_CQP_STRING_OVERRIDE;
|
||||
void initialize();
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
+74
-2
@@ -8,6 +8,9 @@
|
||||
#include <ntstatus.h>
|
||||
|
||||
#include "avs/game.h"
|
||||
#include "games/iidx/iidx.h"
|
||||
#include "games/gitadora/gitadora.h"
|
||||
#include "games/sdvx/sdvx.h"
|
||||
#include "util/detour.h"
|
||||
#include "util/logging.h"
|
||||
#include "util/utils.h"
|
||||
@@ -22,6 +25,8 @@ static decltype(GetLocaleInfoEx) *GetLocaleInfoEx_orig = nullptr;
|
||||
|
||||
#ifdef SPICE64
|
||||
static decltype(GetSystemDefaultLCID) *GetSystemDefaultLCID_orig = nullptr;
|
||||
static decltype(IsDBCSLeadByte) *IsDBCSLeadByte_orig = nullptr;
|
||||
static decltype(WideCharToMultiByte) *WideCharToMultiByte_orig = nullptr;
|
||||
#endif
|
||||
|
||||
static NTSTATUS NTAPI RtlMultiByteToUnicodeN_hook(
|
||||
@@ -162,6 +167,52 @@ static int WINAPI GetLocaleInfoEx_hook (
|
||||
return GetLocaleInfoEx_orig(lpLocaleName, LCType, lpLCData, cchData);
|
||||
}
|
||||
|
||||
#ifdef SPICE64
|
||||
|
||||
static BOOL WINAPI IsDBCSLeadByte_hook (
|
||||
BYTE TestChar
|
||||
)
|
||||
{
|
||||
return IsDBCSLeadByteEx(CODEPAGE_SHIFT_JIS, TestChar);
|
||||
}
|
||||
|
||||
static
|
||||
int
|
||||
WINAPI
|
||||
WideCharToMultiByte_hook(
|
||||
UINT CodePage,
|
||||
DWORD dwFlags,
|
||||
LPCWCH lpWideCharStr,
|
||||
int cchWideChar,
|
||||
LPSTR lpMultiByteStr,
|
||||
int cbMultiByte,
|
||||
LPCCH lpDefaultChar,
|
||||
LPBOOL lpUsedDefaultChar
|
||||
)
|
||||
{
|
||||
switch (CodePage) {
|
||||
case CP_ACP:
|
||||
case CP_THREAD_ACP:
|
||||
SetThreadLocale(MAKELANGID(LANG_JAPANESE, SUBLANG_JAPANESE_JAPAN));
|
||||
CodePage = CODEPAGE_SHIFT_JIS;
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return WideCharToMultiByte_orig(
|
||||
CodePage,
|
||||
dwFlags,
|
||||
lpWideCharStr,
|
||||
cchWideChar,
|
||||
lpMultiByteStr,
|
||||
cbMultiByte,
|
||||
lpDefaultChar,
|
||||
lpUsedDefaultChar);
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
void hooks::lang::early_init() {
|
||||
log_info("hooks::lang", "early initialization");
|
||||
|
||||
@@ -170,8 +221,7 @@ void hooks::lang::early_init() {
|
||||
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) {
|
||||
if (games::sdvx::is_valkyrie_model()) {
|
||||
log_info("hooks::lang", "hooking GetSystemDefaultLCID");
|
||||
detour::trampoline_try(
|
||||
"kernel32.dll",
|
||||
@@ -189,6 +239,28 @@ void hooks::lang::early_init() {
|
||||
GetLocaleInfoEx_hook,
|
||||
&GetLocaleInfoEx_orig);
|
||||
}
|
||||
|
||||
#ifdef SPICE64
|
||||
// for TDJ subscreen search keyboard
|
||||
if (avs::game::is_model("LDJ") && games::iidx::TDJ_MODE) {
|
||||
log_info("hooks::lang", "hooking IsDBCSLeadByte");
|
||||
detour::trampoline_try(
|
||||
"kernel32.dll",
|
||||
"IsDBCSLeadByte",
|
||||
IsDBCSLeadByte_hook,
|
||||
&IsDBCSLeadByte_orig);
|
||||
}
|
||||
|
||||
if (games::gitadora::is_arena_model()) {
|
||||
log_info("hooks::lang", "hooking WideCharToMultiByte");
|
||||
detour::trampoline_try(
|
||||
"kernel32.dll",
|
||||
"WideCharToMultiByte",
|
||||
WideCharToMultiByte_hook,
|
||||
&WideCharToMultiByte_orig);
|
||||
}
|
||||
#endif
|
||||
|
||||
}
|
||||
|
||||
void hooks::lang::init() {
|
||||
|
||||
+44
-1
@@ -4,6 +4,7 @@
|
||||
#include <iphlpapi.h>
|
||||
#include <stdlib.h>
|
||||
#include <string>
|
||||
#include <mutex>
|
||||
|
||||
#include "avs/core.h"
|
||||
#include "avs/ea3.h"
|
||||
@@ -12,6 +13,7 @@
|
||||
#include "util/detour.h"
|
||||
#include "util/fileutils.h"
|
||||
#include "util/libutils.h"
|
||||
#include "util/deferlog.h"
|
||||
|
||||
// hooking related stuff
|
||||
static decltype(GetAdaptersInfo) *GetAdaptersInfo_orig = nullptr;
|
||||
@@ -27,6 +29,19 @@ static struct in_addr network;
|
||||
static struct in_addr prefix;
|
||||
static struct in_addr subnet;
|
||||
|
||||
static void defer_network_adapter_error() {
|
||||
static std::once_flag printed;
|
||||
std::call_once(printed, []() {
|
||||
deferredlogs::defer_error_messages({
|
||||
"network adapter issue detected!",
|
||||
" ensure you have at least one network adapter with a valid IPv4 address",
|
||||
" the IPv4 address can be external or internal, it just needs to be valid",
|
||||
" the network adapter can be a wired or wireless connection",
|
||||
" you still need to do this even if you are connecting to a local server!",
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
static ULONG WINAPI GetAdaptersInfo_hook(PIP_ADAPTER_INFO pAdapterInfo, PULONG pOutBufLen) {
|
||||
|
||||
// call orig
|
||||
@@ -53,6 +68,15 @@ static ULONG WINAPI GetAdaptersInfo_hook(PIP_ADAPTER_INFO pAdapterInfo, PULONG p
|
||||
free(pAdapterInfo2);
|
||||
}
|
||||
|
||||
if (ret != ERROR_SUCCESS && ret != ERROR_BUFFER_OVERFLOW) {
|
||||
defer_network_adapter_error();
|
||||
log_warning(
|
||||
"network",
|
||||
"GetAdaptersInfo failed with {}; "
|
||||
"check if you have at least one network adapter with a valid IPv4 address!",
|
||||
ret);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
@@ -101,8 +125,13 @@ static ULONG WINAPI GetAdaptersInfo_hook(PIP_ADAPTER_INFO pAdapterInfo, PULONG p
|
||||
free(pIpForwardTable);
|
||||
pIpForwardTable = (MIB_IPFORWARDTABLE *) malloc(dwSize);
|
||||
}
|
||||
if (GetIpForwardTable(pIpForwardTable, &dwSize, 1) != NO_ERROR || pIpForwardTable->dwNumEntries == 0)
|
||||
if (GetIpForwardTable(pIpForwardTable, &dwSize, 1) != NO_ERROR || pIpForwardTable->dwNumEntries == 0) {
|
||||
defer_network_adapter_error();
|
||||
if (GetAdaptersInfo_log) {
|
||||
log_warning("network", "GetIpForwardTable failed");
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
// determine best interface
|
||||
DWORD best = pIpForwardTable->table[0].dwForwardIfIndex;
|
||||
@@ -134,6 +163,20 @@ static ULONG WINAPI GetAdaptersInfo_hook(PIP_ADAPTER_INFO pAdapterInfo, PULONG p
|
||||
info = info->Next;
|
||||
}
|
||||
|
||||
if (pAdapterInfo->IpAddressList.IpAddress.String[0] == 0 ||
|
||||
pAdapterInfo->IpAddressList.IpAddress.String[0] == '0') {
|
||||
defer_network_adapter_error();
|
||||
if (GetAdaptersInfo_log) {
|
||||
log_warning(
|
||||
"network",
|
||||
"invalid IPv4 address for adapter {}, {} = {}; "
|
||||
"ensure you have at least one network adapter with valid IPv4 address!",
|
||||
pAdapterInfo->AdapterName,
|
||||
pAdapterInfo->Description,
|
||||
pAdapterInfo->IpAddressList.IpAddress.String);
|
||||
}
|
||||
}
|
||||
|
||||
// return original value
|
||||
GetAdaptersInfo_log = false;
|
||||
return ret;
|
||||
|
||||
Reference in New Issue
Block a user