mirror of
https://github.com/spice2x/spice2x.github.io.git
synced 2026-08-05 08:10:40 -07:00
d0db547dd0
## Link to GitHub Issue or related Pull Request, if one exists n/a ## Description of change In summary: for SDVX VM mode, this PR eliminates the need for `Note FPS Target 120Hz` / `Game FPS Target 120Hz` patches typically used for AMD GPU via synthetic responses provided via a fake NVAPI layer. Before this change, playing on AMD GPU required patches to force the game to render at 60Hz (See #107 for relevant discussion). Basically, if NVAPI query fails, the game shows `I:NvDisplayConfig: GetMainDisplayRefreshRate = 60.000hz (DUMMY)` the game renders at 120Hz but the notes render at 60Hz, which would look stuttery. After this change, even without a real `nvapi64.dll`, spice will provide a stub implementation, just enough to convince the game that the main display is 120Hz, which causes notes to render at 120Hz as well. This can be overridden by `-graphics-force-refresh` option. Important note: if user has the `Note FPS Target 60Hz` patch but running the game at 120Hz, before this change the notes would render at 60Hz, but after this they will be at 120Hz. I don't know why anyone would intentionally lower the notes framerate, so this is not going to be fixed. If the user wants to run the game at 60Hz they can do so via `-graphics-force-refresh`. Another change - `-nonvapi` will always force down this synthetic nvapi path, even if the system has the real `nvapi64.dll`. Even on NVDIA GPU this option can be used to force this. ## Testing
125 lines
4.7 KiB
C++
125 lines
4.7 KiB
C++
#include "external/nvapi/nvapi.h"
|
|
#include "external/nvapi/NvApiDriverSettings.h"
|
|
#include "avs/game.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 decltype(NvAPI_GPU_GetConnectedDisplayIds) *NvAPI_GPU_GetConnectedDisplayIds_orig = nullptr;
|
|
static decltype(NvAPI_DISP_GetGDIPrimaryDisplayId) *NvAPI_DISP_GetGDIPrimaryDisplayId_orig = nullptr;
|
|
|
|
static uintptr_t* __cdecl NvAPI_QueryInterface_hook(unsigned int func_code);
|
|
static NvAPI_Status __cdecl NvAPI_GPU_GetConnectedDisplayIds_hook(
|
|
NvPhysicalGpuHandle hPhysicalGpu,
|
|
NV_GPU_DISPLAYIDS *pDisplayIds,
|
|
NvU32 *pDisplayIdCount,
|
|
NvU32 flags);
|
|
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;
|
|
}
|
|
|
|
// spoof display connector types only for SDVX
|
|
if (func_code == 0x0078DBA2 && avs::game::is_model("KFC")) {
|
|
NvAPI_GPU_GetConnectedDisplayIds_orig =
|
|
reinterpret_cast<decltype(NvAPI_GPU_GetConnectedDisplayIds) *>(
|
|
NvAPI_QueryInterface_orig(func_code));
|
|
NvAPI_DISP_GetGDIPrimaryDisplayId_orig =
|
|
reinterpret_cast<decltype(NvAPI_DISP_GetGDIPrimaryDisplayId) *>(
|
|
NvAPI_QueryInterface_orig(0x1E9D8A31));
|
|
log_misc("nvapi_hook", "NvAPI_QueryInterface(NvAPI_GPU_GetConnectedDisplayIds) - hooked");
|
|
return (uintptr_t *)NvAPI_GPU_GetConnectedDisplayIds_hook;
|
|
}
|
|
|
|
// 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_GPU_GetConnectedDisplayIds_hook(
|
|
NvPhysicalGpuHandle hPhysicalGpu,
|
|
NV_GPU_DISPLAYIDS *pDisplayIds,
|
|
NvU32 *pDisplayIdCount,
|
|
NvU32 flags) {
|
|
|
|
if (NvAPI_GPU_GetConnectedDisplayIds_orig == nullptr) {
|
|
return NVAPI_NO_IMPLEMENTATION;
|
|
}
|
|
|
|
const NvAPI_Status status = NvAPI_GPU_GetConnectedDisplayIds_orig(
|
|
hPhysicalGpu,
|
|
pDisplayIds,
|
|
pDisplayIdCount,
|
|
flags);
|
|
|
|
if (status != NVAPI_OK || pDisplayIds == nullptr || pDisplayIdCount == nullptr ||
|
|
NvAPI_DISP_GetGDIPrimaryDisplayId_orig == nullptr) {
|
|
return status;
|
|
}
|
|
|
|
NvU32 primary_display_id = 0;
|
|
if (NvAPI_DISP_GetGDIPrimaryDisplayId_orig(&primary_display_id) != NVAPI_OK) {
|
|
return status;
|
|
}
|
|
|
|
// report the primary display adapter as DP and all other displays as HDMI
|
|
// (the game expects this since the VM cab is like that)
|
|
//
|
|
// this doesn't actually affect gameplay; only how
|
|
// NvDisplayConfig: MainDisplay={}hz / SubDisplay={}hz
|
|
// gets printed.
|
|
//
|
|
// for the actual value that matters for gameplay, it's this:
|
|
// NvDisplayConfig: GetMainDisplayRefreshRate = {}hz
|
|
for (NvU32 index = 0; index < *pDisplayIdCount; index++) {
|
|
auto &display = pDisplayIds[index];
|
|
display.connectorType = display.displayId == primary_display_id ?
|
|
NV_MONITOR_CONN_TYPE_DP : NV_MONITOR_CONN_TYPE_HDMI;
|
|
}
|
|
|
|
return status;
|
|
}
|
|
|
|
NvAPI_Status __cdecl NvAPI_DISP_SetDisplayConfig_hook(
|
|
NvU32 pathInfoCount, NV_DISPLAYCONFIG_PATH_INFO *pathInfo, NvU32 flags) {
|
|
// do not let the game apply its emulated cabinet topology to Windows
|
|
log_misc("nvapi_hook", "NvAPI_DISP_SetDisplayConfig_hook - do nothing and return");
|
|
return NVAPI_OK;
|
|
}
|
|
}
|