Compare commits

...

11 Commits

Author SHA1 Message Date
bicarus-dev 6856a0950c iidx: fix play record feature broken by new NVIDIA driver update (#444)
## Link to GitHub Issue, if one exists
fixes #442

## Description of change
New NVIDIA driver update (591.44) dropped support for older NVENC API.
Specifically,

* TDJ uses `NV_ENC_PRESET_HQ_GUID` but this is no longer supported; must
be swapped with new encoder presets
* `NvEncGetEncodePresetConfig` does not take new encoder presets, the
-Ex version must be used

All of this happens because bm2dx.dll takes code from an older NVENC
sample.

To address this,

* Install additional hooks for NVENC API
* Hijack calls to `NvEncGetEncodePresetConfig` and instead swap with
`NvEncGetEncodePresetConfigEx` (only if the initial call fails, which is
how we detect old vs new driver), replacing `NV_ENC_PRESET_HQ_GUID` with
`NV_ENC_PRESET_P4_GUID` and `NV_ENC_TUNING_INFO_HIGH_QUALITY`
(functionally equivalent according to NVIDIA docs -
https://docs.nvidia.com/video-technologies/video-codec-sdk/11.1/nvenc-preset-migration-guide/index.html)
* When calling `nvEncInitializeEncoder`, hijack and swap in the preset
GUID again.

## Testing
Tested on 591.44 driver on a RTX 4070.

The resulting videos were compared as well, and they are practically
identical from encoder perspective.
2025-12-11 16:09:09 -08:00
cchike dc7d8515d3 MDX: Fixed Ring Buffer Traversal Direction (P4IO Timing Fix) (#441)
## Description of change
In the real `libacio2.dll` implementation,
`ac_io_mdxf_get_control_status_buffer` returns the ring buffer entry at
`(head - index) mod size` but this function was using `(head + index)
mod size` instead. Basically, `index` is supposed to indicate how many
entries back, not forward. This was causing the polling history of each
player to be returned backwards. A few other things were updated to
bring the function's behavior closer to the real implementation:

1. Number of ring buffer entries per player were increased to 16 (this
is the size of them in the real implementation)
2. `ac_io_mdxf_get_control_status_buffer` now returns the current head
of the ring buffer (instead of a success/failure boolean), which is what
`arkmdxp4.dll` expects.

## Testing
I played a few songs to make sure nothing was broken. I also attached a
debugger and verified the ring buffer entries are *actually* now being
returned in reverse chronological order as intended.
2025-12-11 10:38:47 -08:00
bicarus-dev e100093ead iidx: dump more log from nvenc hooks (#443)
## Link to GitHub Issue, if one exists
For #442

## Description of change
Logs more info about nvenc hook calls to detect failures

## Testing
2025-12-11 09:39:23 -08:00
cchike e5808257ca MDX: Fixed Status Control Buffer Emulators (P4IO Timing Fix) (#439)
## Description of change
In its current state, the emulated `libacio2.dll` functions
`ac_io_mdxf_get_control_status_buffer` and
`ac_io_mdxf_update_control_status_buffer` only operate using a single
head pointer (`COUNTER`) for the ring buffers of
`[panel_state,timestamp]` (simplified for brevity here) polls for both
players, when they should be maintaining a separate head pointer for
each ring buffer. On every frame, `arkmdxp4.dll` makes calls to
`ac_io_mdxf_update_control_status_buffer`, which [in this emulated
version] advances the head pointer, creates a new entry for the latest
panel state info along with the current time, and stores it at the head
of the ring buffer. This function is called for each player: After the
call for 1P, the head pointer is advanced by 1, so by the time it's
called for 2P, the head pointer had already been advanced by 1, meaning
the new entry for 2P's panel state ring buffer is written two entries
ahead of where the last entry for 2P was written. This is an example of
how the entries were written for each ring buffer:

```
Frame 1:
1P[0] = t0
2P[1] = t0

Frame 2: 
1P[2] = t1
2P[3] = t1

Frame 3:
1P[4] = t2
2P[5] = t2

Frame 4:
1P[6] = t3
2P[0] = t3

...

Frame 7:
1P[5] = t6
2P[6] = t6

Frame 8:
1P[0] = t7
2P[1] = t7
```

This is an issue because `arkmdxp4.dll` calls
`ac_io_mdxf_get_control_status_buffer` on every frame to load the last 7
polls for each player, which depends on the ring buffer entries being in
reverse chronological order to properly determine when an arrow is
pressed and when it was released, but this is what it sees from the ring
buffers after 8 frames:

```
1P: t7 -> t3 -> t6 -> t2 -> t5 -> t1 -> t4
2P: t7 -> t3 -> t6 -> t2 -> t5 -> t1 -> t4
```

In this case, `arkmdxp4.dll` assigns step times based on polls that are
out of order, resulting in unpredictable timestamp assignment to each
step, with the issue compounding at lower framerates since the timestamp
deltas between each poll will be greater (see side note). After
implementing two separate head pointers for each ring buffer, those same
two arrays become:

```
1P: t7 -> t6 -> t5 -> t4 -> t3 -> t2 -> t1
2P: t7 -> t6 -> t5 -> t4 -> t3 -> t2 -> t1
```

This is how `arkmdxp4.dll` expects this data to be structured, so timing
should become more consistent after this change.

Side note: in this particular p4io implementation, the only place the
polls are updated is once per frame on the calls to
`ac_io_mdxf_update_control_status_buffer`, meaning the polling rate of
the controller used is locked to the framerate of the game. There should
be another thread not tied to the main one that listens to input from
the OS and updates these ring buffers on a much faster cadence.

## Testing
I played a few songs to make sure nothing was broken. I also attached a
debugger and verified the ring buffer entries are now being returned in
reverse chronological order as intended.
2025-12-09 19:15:15 -08:00
bicarus-dev 283ec3960a ea3: add warning message for missing <soft> node (#440)
## Link to GitHub Issue, if one exists
#345 

## Description of change
Add some descriptive error message for the case where we couldn't find
softid in prop files.

## Testing
manual
2025-12-09 19:14:58 -08:00
bicarus-dev 0ec37ac6ea troubleshooter: log soft id (#436)
## Link to GitHub Issue, if one exists
n/a

## Description of change
Log soft ID (game datecode etc) at the top of auto-troubleshooter output
2025-12-08 17:35:59 -08:00
duel0213 0ef0b84308 mdx: change init_code instead of security_code for gold cab (#438)
## Link to GitHub Issue, if one exists
n/a

## Description of change
This changes previous security code change to init code change (#435)

## Testing
2025-12-08 17:14:35 -08:00
duel0213 62c30d8895 mdx: fix io mode bio2 boot crash (#435)
## Link to GitHub Issue, if one exists
n/a

## Description of change
Currently when 'bio2' specified on 'io' parameter of app-config the game
crashed due to security code check.
These codes changes security code to corresponding one and adds fake
BIO2 entry to satisfy BIO2 check.

## Testing
Tested with MDX-003 and J:I:B ident.
The game boots and A3 now displays gold cab theme and displays MATCHING
GROUP, MATCHING SIDE on NETWORK OPTIONS test mode menu.
2025-12-08 10:16:16 -08:00
bicarus-dev 4e86cb16a2 cfg: tweaks to analog binding UI (#434)
## Link to GitHub Issue, if one exists
n/a

## Description of change
Add the raw device handle string to tooltip.
Add `Reset` button that clears all values.

## Testing
manual testing
2025-12-06 01:47:00 -08:00
bicarus-dev 76f401de95 iidx: rework wasapi/asio detection for iidx33+ (#433)
## Link to GitHub Issue, if one exists
Fixes #432 

## Description of change
Newer versions of iidx33+ reintroduces `SOUND_OUTPUT_DEVICE` env var.
iidx module must account for its return

## Testing
2025-12-05 15:33:09 -08:00
bicarus-dev 5947779502 iidx,sdvx: warn user when using -monitor with TDJ/UFC mode (#431)
## Link to GitHub Issue, if one exists
#345 

## Description of change
Warn user via warning message in log + auto-troubleshooter entry when
-monitor option is used in full screen with TDJ and UFC mode.

This covers yet another case where TDJ subscreen does not respond to
mouse clicks (because the game expects them to come through the primary
display). For UFC, -monitor usually results in a failure to create DX
adapter.

## Testing
Manual verification with TDJ/UFC modes and two monitors.
2025-12-04 21:57:36 -08:00
10 changed files with 341 additions and 75 deletions
+44 -21
View File
@@ -11,13 +11,14 @@
const size_t STATUS_BUFFER_SIZE = 32; const size_t STATUS_BUFFER_SIZE = 32;
// static stuff // static stuff
static uint8_t COUNTER = 0; static uint8_t HEAD_P1 = 0;
static uint8_t HEAD_P2 = 0;
// buffers // buffers
#pragma pack(push, 1) #pragma pack(push, 1)
static struct { static struct {
uint8_t STATUS_BUFFER_P1[7][STATUS_BUFFER_SIZE] {}; uint8_t STATUS_BUFFER_P1[16][STATUS_BUFFER_SIZE] {};
uint8_t STATUS_BUFFER_P2[7][STATUS_BUFFER_SIZE] {}; uint8_t STATUS_BUFFER_P2[16][STATUS_BUFFER_SIZE] {};
} BUFFERS {}; } BUFFERS {};
#pragma pack(pop) #pragma pack(pop)
@@ -40,29 +41,50 @@ static uint64_t arkGetTickTime64() {
* Implementations * Implementations
*/ */
static bool __cdecl ac_io_mdxf_get_control_status_buffer(int node, void *buffer, uint8_t a3, uint8_t a4) { static uint64_t __cdecl ac_io_mdxf_get_control_status_buffer(int node, void *out, uint8_t index, uint8_t head_in) {
// Default error value (matches original mask behavior)
auto error_ret = static_cast<uint64_t>(node - 0x11) & 0xFFFFFFFFFFFFFF00;
// Dance Dance Revolution // Dance Dance Revolution
if (avs::game::is_model("MDX")) { if (avs::game::is_model("MDX")) {
// get buffer index // Select player-specific state
auto i = (COUNTER + a3) % std::size(BUFFERS.STATUS_BUFFER_P1); uint8_t head;
uint8_t (*buffer)[STATUS_BUFFER_SIZE];
size_t size;
// copy buffer
if (node == 17 || node == 25) { if (node == 17 || node == 25) {
memcpy(buffer, BUFFERS.STATUS_BUFFER_P1[i], STATUS_BUFFER_SIZE); head = HEAD_P1;
buffer = BUFFERS.STATUS_BUFFER_P1;
size = std::size(BUFFERS.STATUS_BUFFER_P1);
} else if (node == 18 || node == 26) { } else if (node == 18 || node == 26) {
memcpy(buffer, BUFFERS.STATUS_BUFFER_P2[i], STATUS_BUFFER_SIZE); head = HEAD_P2;
buffer = BUFFERS.STATUS_BUFFER_P2;
size = std::size(BUFFERS.STATUS_BUFFER_P2);
} else { } else {
memset(out, 0, STATUS_BUFFER_SIZE);
// fill with zeros on unknown node return error_ret;
memset(buffer, 0, STATUS_BUFFER_SIZE);
return false;
}
} }
// return success if (head_in != 0xFF) {
return true; head = head_in;
}
// Compute ring index: walk backwards from head as index increases
// Assumes ring buffer size is a power of two
const size_t mask = size - 1;
const size_t offset = static_cast<size_t>(index) & mask;
const size_t i = (static_cast<size_t>(head) - offset + size) & mask;
// Copy the chosen entry
memcpy(out, buffer[i], STATUS_BUFFER_SIZE);
// Return the head value actually used
return static_cast<uint64_t>(head);
}
return error_ret;
} }
static bool __cdecl ac_io_mdxf_set_output_level(unsigned int a1, unsigned int a2, uint8_t value) { static bool __cdecl ac_io_mdxf_set_output_level(unsigned int a1, unsigned int a2, uint8_t value) {
@@ -106,9 +128,6 @@ static bool __cdecl ac_io_mdxf_set_output_level(unsigned int a1, unsigned int a2
static bool __cdecl ac_io_mdxf_update_control_status_buffer(int node) { static bool __cdecl ac_io_mdxf_update_control_status_buffer(int node) {
// increase counter
COUNTER = (COUNTER + 1) % std::size(BUFFERS.STATUS_BUFFER_P1);
// check freeze // check freeze
if (STATUS_BUFFER_FREEZE) { if (STATUS_BUFFER_FREEZE) {
return true; return true;
@@ -119,11 +138,15 @@ static bool __cdecl ac_io_mdxf_update_control_status_buffer(int node) {
switch (node) { switch (node) {
case 17: case 17:
case 25: case 25:
buffer = BUFFERS.STATUS_BUFFER_P1[COUNTER]; // increase counter
HEAD_P1 = (HEAD_P1 + 1) % std::size(BUFFERS.STATUS_BUFFER_P1);
buffer = BUFFERS.STATUS_BUFFER_P1[HEAD_P1];
break; break;
case 18: case 18:
case 26: case 26:
buffer = BUFFERS.STATUS_BUFFER_P2[COUNTER]; // increase counter
HEAD_P2 = (HEAD_P2 + 1) % std::size(BUFFERS.STATUS_BUFFER_P2);
buffer = BUFFERS.STATUS_BUFFER_P2[HEAD_P2];
break; break;
default: default:
+17 -1
View File
@@ -8,6 +8,7 @@
#include "games/mfc/mfc.h" #include "games/mfc/mfc.h"
#include "hooks/avshook.h" #include "hooks/avshook.h"
#include "util/detour.h" #include "util/detour.h"
#include "util/deferlog.h"
#include "util/fileutils.h" #include "util/fileutils.h"
#include "util/libutils.h" #include "util/libutils.h"
#include "util/logging.h" #include "util/logging.h"
@@ -282,6 +283,7 @@ namespace avs {
// read software nodes // read software nodes
if (ea3_soft != nullptr) { if (ea3_soft != nullptr) {
log_misc("avs-ea3", "reading soft id from {}...", ea3_config_name);
avs::core::property_node_refer(ea3_config, ea3_soft, "model", avs::core::property_node_refer(ea3_config, ea3_soft, "model",
avs::core::NODE_TYPE_str, EA3_MODEL, 4); avs::core::NODE_TYPE_str, EA3_MODEL, 4);
avs::core::property_node_refer(ea3_config, ea3_soft, "dest", avs::core::property_node_refer(ea3_config, ea3_soft, "dest",
@@ -293,6 +295,7 @@ namespace avs {
avs::core::property_node_refer(ea3_config, ea3_soft, "ext", avs::core::property_node_refer(ea3_config, ea3_soft, "ext",
avs::core::NODE_TYPE_str, EA3_EXT, 11); avs::core::NODE_TYPE_str, EA3_EXT, 11);
} else if (fileutils::file_exists("prop/ea3-ident.xml")) { } else if (fileutils::file_exists("prop/ea3-ident.xml")) {
log_misc("avs-ea3", "reading soft id from prop/ea3-ident.xml...");
// read ident config // read ident config
auto ea3_ident = avs::core::config_read("prop/ea3-ident.xml"); auto ea3_ident = avs::core::config_read("prop/ea3-ident.xml");
@@ -316,7 +319,14 @@ namespace avs {
// clean up // clean up
avs::core::config_destroy(ea3_ident); avs::core::config_destroy(ea3_ident);
} else { } else {
log_fatal("avs-ea3", "node not found in '{}': /ea3/soft", ea3_config_name); // <ea3><soft> node missing error
log_warning("avs-ea3", "soft id (datecode) not found in prop XML files");
log_warning("avs-ea3", "get fixed prop files and try again - you have incomplete data");
log_warning("avs-ea3", "for experts only:");
log_warning("avs-ea3", " * add the missing <soft>...</soft> node to {}", ea3_config_name);
log_warning("avs-ea3", " * alternatively, provide ea3-ident.xml with <ea3_conf><soft>...");
log_fatal("avs-ea3", "datecode missing in '{}'; 'prop/ea3-ident.xml' also missing", ea3_config_name);
} }
// set account id (`EA3_PCBID` is valid if and only if `/ea3/id` is present) // set account id (`EA3_PCBID` is valid if and only if `/ea3/id` is present)
@@ -484,8 +494,13 @@ namespace avs {
std::ostringstream init_code; std::ostringstream init_code;
init_code << EA3_MODEL; init_code << EA3_MODEL;
init_code << EA3_DEST; init_code << EA3_DEST;
if (strcmp(EA3_MODEL, "MDX") == 0 && strcmp(EA3_SPEC, "I") == 0) {
init_code << "G";
init_code << "B";
} else {
init_code << EA3_SPEC; init_code << EA3_SPEC;
init_code << EA3_REV; init_code << EA3_REV;
}
init_code << EA3_EXT; init_code << EA3_EXT;
std::string init_code_str = init_code.str(); std::string init_code_str = init_code.str();
@@ -563,6 +578,7 @@ namespace avs {
soft_id_code << EA3_EXT; soft_id_code << EA3_EXT;
std::string soft_id_code_str = soft_id_code.str(); std::string soft_id_code_str = soft_id_code.str();
log_info("avs-ea3", "soft id code: {}", soft_id_code_str); log_info("avs-ea3", "soft id code: {}", soft_id_code_str);
deferredlogs::set_softid(soft_id_code_str);
// set soft ID code // set soft ID code
avs::core::avs_std_setenv("/env/profile/soft_id_code", soft_id_code_str.c_str()); avs::core::avs_std_setenv("/env/profile/soft_id_code", soft_id_code_str.c_str());
+8 -3
View File
@@ -82,18 +82,23 @@ public:
return this->index != 0xFF; return this->index != 0xFF;
} }
inline void clearBindings() { inline void resetValues() {
device_identifier = "";
index = 0xFF;
setSensitivity(1.f); setSensitivity(1.f);
setDeadzone(0.f); setDeadzone(0.f);
invert = false; invert = false;
smoothing = false; smoothing = false;
deadzone_mirror = false;
setMultiplier(1); setMultiplier(1);
setRelativeMode(false); setRelativeMode(false);
setDelayBufferDepth(0); setDelayBufferDepth(0);
} }
inline void clearBindings() {
device_identifier = "";
index = 0xFF;
resetValues();
}
inline const std::string &getName() const { inline const std::string &getName() const {
return this->name; return this->name;
} }
+30 -9
View File
@@ -2,6 +2,8 @@
#include "acioemu/handle.h" #include "acioemu/handle.h"
#include "avs/game.h" #include "avs/game.h"
#include "hooks/avshook.h"
#include "hooks/cfgmgr32hook.h"
#include "hooks/devicehook.h" #include "hooks/devicehook.h"
#include "hooks/setupapihook.h" #include "hooks/setupapihook.h"
#include "hooks/sleephook.h" #include "hooks/sleephook.h"
@@ -183,10 +185,37 @@ namespace games::ddr {
// init device hook // init device hook
devicehook_init(); devicehook_init();
// init SETUP API
setupapihook_init(avs::game::DLL_INSTANCE);
// DDR ACE actually uses another DLL for things
if (game_mdx != nullptr) {
setupapihook_init(game_mdx);
}
// add fake devices // add fake devices
if (avs::game::DLL_NAME == "arkmdxbio2.dll") { if (avs::game::DLL_NAME == "arkmdxbio2.dll") {
devicehook_add(new acioemu::ACIOHandle(L"COM1")); devicehook_add(new acioemu::ACIOHandle(L"COM1"));
} else if(avs::game::DLL_NAME == "arkmdxp4.dll") {
if (avs::game::SPEC[0] == 'I') {
// settings (bio2)
SETUPAPI_SETTINGS settingsbio2 {};
const char property2[] = "BIO2(VIDEO)";
settingsbio2.class_guid[0] = 0x4D36E978;
settingsbio2.class_guid[1] = 0x11CEE325;
settingsbio2.class_guid[2] = 0x8C1BF;
settingsbio2.class_guid[3] = 0x1803E12B;
memcpy(settingsbio2.property_devicedesc, property2, sizeof(property2));
setupapihook_add(settingsbio2);
// cfgmgr (bio2)
CFGMGR32_HOOK_SETTING settingbio2 {};
settingbio2.device_id = "USB\\VID_1CCF&PID_804C";
settingbio2.device_node_id = "USB\\VID_1CCF&PID_804C&MI_00\\?&????????&?&????";
cfgmgr32hook_init(avs::game::DLL_INSTANCE);
cfgmgr32hook_add(settingbio2);
}
} else if (avs::game::DLL_NAME == "arkmdxp4.dll") {
devicehook_add(new DDRP4IOHandle()); devicehook_add(new DDRP4IOHandle());
} else { } else {
devicehook_add(new DDRFOOTHandle()); devicehook_add(new DDRFOOTHandle());
@@ -228,14 +257,6 @@ namespace games::ddr {
memcpy(settingsp4io.property_devicedesc, settings_property, strlen(settings_property) + 1); memcpy(settingsp4io.property_devicedesc, settings_property, strlen(settings_property) + 1);
memcpy(settingsp4io.interface_detail, settings_detail_p4io, sizeof(settings_detail_p4io)); memcpy(settingsp4io.interface_detail, settings_detail_p4io, sizeof(settings_detail_p4io));
// init SETUP API
setupapihook_init(avs::game::DLL_INSTANCE);
// DDR ACE actually uses another DLL for things
if (game_mdx != nullptr) {
setupapihook_init(game_mdx);
}
// add settings // add settings
setupapihook_add(settings1); setupapihook_add(settings1);
setupapihook_add(settings2); setupapihook_add(settings2);
+41 -14
View File
@@ -463,6 +463,32 @@ namespace games::iidx {
log_fatal("iidx", "BAD MODEL NAME ERROR - TDJ specified, must be LDJ instead"); log_fatal("iidx", "BAD MODEL NAME ERROR - TDJ specified, must be LDJ instead");
} }
// check -monitor + TDJ mode
if (!GRAPHICS_WINDOWED &&
options->at(launcher::Options::DisplayAdapter).is_active() &&
TDJ_MODE) {
log_warning(
"iidx",
"\n\n!!! using -monitor option with TDJ is NOT recommended due to known !!!\n"
"!!! compatibility issues with the game !!!\n"
"!!! !!!\n"
"!!! * game may launch in wrong resolution or refresh rate !!!\n"
"!!! * touch / mouse input may stop working in subscreen / overlay !!!\n"
"!!! !!!\n"
"!!! recommendation is to NOT use -monitor and instead set the !!!\n"
"!!! primary monitor in Windows settings before launching the game !!!\n\n"
);
deferredlogs::defer_error_messages({
"-monitor option is NOT recommended when running with TDJ mode",
" due to known compatibility issues with the game:",
" * game may launch in wrong resolution or refresh rate",
" * touch / mouse input may stop working in subscreen / overlay",
" recommended fix is to NOT use -monitor and instead set the primary",
" monitor in Windows settings before launching the game"
});
}
} }
void IIDXGame::detach() { void IIDXGame::detach() {
@@ -787,7 +813,15 @@ namespace games::iidx {
// <=24 : 32-bit only // <=24 : 32-bit only
// 25-26: has neither (no patch needed - WASAPI Exclusive by default) // 25-26: has neither (no patch needed - WASAPI Exclusive by default)
// 27-30: has both (envvar will be respected, ASIO or WASAPI) // 27-30: has both (envvar will be respected, ASIO or WASAPI)
// 31+: only has XONAR (ASIO by default, signature patch can be used to force WASAPI - for now) // 31-32: only has XONAR (ASIO by default, signature patch can be used to force WASAPI - for now)
// 33 : early versions only have XONAR, but later datecodes have both; SOUND_OUTPUT_DEVICE
// returned and it works again when set as env var
log_info(
"iidx",
"has_SOUND_OUTPUT_DEVICE: {}, has_XONAR_SOUND_CARD: {}",
(has_SOUND_OUTPUT_DEVICE != 0),
(has_XONAR_SOUND_CARD != 0));
if (!has_SOUND_OUTPUT_DEVICE && !has_XONAR_SOUND_CARD) { if (!has_SOUND_OUTPUT_DEVICE && !has_XONAR_SOUND_CARD) {
// iidx 25-26 // iidx 25-26
@@ -795,16 +829,10 @@ namespace games::iidx {
return; return;
} }
if (has_SOUND_OUTPUT_DEVICE && has_XONAR_SOUND_CARD) { // if the game doesn't accept SOUND_OUTPUT_DEVICE, patch game to force wasapi
// iidx 27-30 if (!has_SOUND_OUTPUT_DEVICE &&
log_info("iidx", "This game accepts SOUND_OUTPUT_DEVICE environment variable"); SOUND_OUTPUT_DEVICE_IN_EFFECT.has_value() &&
return; SOUND_OUTPUT_DEVICE_IN_EFFECT.value() == "wasapi") {
}
log_info("iidx", "This game supports ASIO but does not accept SOUND_OUTPUT_DEVICE environment variable");
// patch game to force wasapi
if (SOUND_OUTPUT_DEVICE_IN_EFFECT.has_value() && SOUND_OUTPUT_DEVICE_IN_EFFECT.value() == "wasapi") {
intptr_t result = replace_pattern( intptr_t result = replace_pattern(
avs::game::DLL_INSTANCE, avs::game::DLL_INSTANCE,
"FF5008E8??????FF83780803740D", "FF5008E8??????FF83780803740D",
@@ -822,8 +850,6 @@ namespace games::iidx {
"Successfully forced WASAPI as audio engine using signature matching @ 0x{:x}.", "Successfully forced WASAPI as audio engine using signature matching @ 0x{:x}.",
result); result);
} }
} else {
log_info("iidx", "Not applying force wasapi patch; game will use ASIO");
} }
// patch iidx32+ for asio compatibility // patch iidx32+ for asio compatibility
@@ -831,7 +857,8 @@ namespace games::iidx {
// the patch is only really needed for (some) non-XONAR devices but since people sometimes disguise // the patch is only really needed for (some) non-XONAR devices but since people sometimes disguise
// other devices as a XONAR, don't check for the exact string (common ASIO workaround for INF) // other devices as a XONAR, don't check for the exact string (common ASIO workaround for INF)
if (avs::game::is_ext(2024090100, MAXINT) && if (avs::game::is_ext(2024090100, MAXINT) &&
!(SOUND_OUTPUT_DEVICE_IN_EFFECT.has_value() && SOUND_OUTPUT_DEVICE_IN_EFFECT.value() == "wasapi")) { !(SOUND_OUTPUT_DEVICE_IN_EFFECT.has_value() &&
SOUND_OUTPUT_DEVICE_IN_EFFECT.value() == "wasapi")) {
// in iidx32 final: // in iidx32 final:
// ff 50 08 call QWORD PTR [rax+0x8] ; ASIO instance AddRef // ff 50 08 call QWORD PTR [rax+0x8] ; ASIO instance AddRef
+33
View File
@@ -4,6 +4,7 @@
#include "avs/game.h" #include "avs/game.h"
#include "games/shared/lcdhandle.h" #include "games/shared/lcdhandle.h"
#include "games/io.h"
#include "hooks/audio/audio.h" #include "hooks/audio/audio.h"
#include "hooks/graphics/graphics.h" #include "hooks/graphics/graphics.h"
#include "hooks/devicehook.h" #include "hooks/devicehook.h"
@@ -12,6 +13,7 @@
#include "hooks/powrprof.h" #include "hooks/powrprof.h"
#include "hooks/sleephook.h" #include "hooks/sleephook.h"
#include "hooks/winuser.h" #include "hooks/winuser.h"
#include "launcher/options.h"
#include "touch/touch.h" #include "touch/touch.h"
#include "util/deferlog.h" #include "util/deferlog.h"
#include "util/detour.h" #include "util/detour.h"
@@ -291,6 +293,37 @@ namespace games::sdvx {
"sdvx", "sdvx",
"BAD MODEL NAME ERROR - model name set to UFC, must be KFC instead"); "BAD MODEL NAME ERROR - model name set to UFC, must be KFC instead");
} }
#ifdef SPICE64 // SDVX5+ specific code
bool isValkyrieCabinetMode = avs::game::SPEC[0] == 'G' || avs::game::SPEC[0] == 'H';
auto options = games::get_options(eamuse_get_game());
// check -monitor + UFC mode
if (!GRAPHICS_WINDOWED &&
options->at(launcher::Options::DisplayAdapter).is_active() &&
isValkyrieCabinetMode) {
log_warning(
"sdvx",
"\n\n!!! using -monitor option with VM mode is NOT recommended due to !!!\n"
"!!! known compatibility issues with the game !!!\n"
"!!! !!!\n"
"!!! * game may launch in wrong resolution or refresh rate !!!\n"
"!!! * touch / mouse input may stop working in subscreen / overlay !!!\n"
"!!! !!!\n"
"!!! recommendation is to NOT use -monitor and instead set the !!!\n"
"!!! primary monitor in Windows settings before launching the game !!!\n\n"
);
deferredlogs::defer_error_messages({
"-monitor option is NOT recommended when running with Valkyrie mode",
" due to known compatibility issues with the game:",
" * game may launch in wrong resolution or refresh rate",
" * touch / mouse input may stop working in subscreen / overlay",
" recommended fix is to NOT use -monitor and instead set the primary",
" monitor in Windows settings before launching the game"
});
}
#endif
} }
void SDVXGame::attach() { void SDVXGame::attach() {
+117 -8
View File
@@ -9,6 +9,7 @@
#include "util/detour.h" #include "util/detour.h"
#include "util/libutils.h" #include "util/libutils.h"
#include "util/logging.h" #include "util/logging.h"
#include "util/utils.h"
#include "nvenc_hook.h" #include "nvenc_hook.h"
@@ -16,9 +17,13 @@
typedef NVENCSTATUS(NVENCAPI *NvEncodeAPICreateInstance_Type)(NV_ENCODE_API_FUNCTION_LIST*); typedef NVENCSTATUS(NVENCAPI *NvEncodeAPICreateInstance_Type)(NV_ENCODE_API_FUNCTION_LIST*);
static NvEncodeAPICreateInstance_Type NvEncodeAPICreateInstance_orig = nullptr; static NvEncodeAPICreateInstance_Type NvEncodeAPICreateInstance_orig = nullptr;
static PNVENCOPENENCODESESSIONEX nvEncOpenEncodeSessionEx_orig = nullptr; static PNVENCOPENENCODESESSIONEX nvEncOpenEncodeSessionEx_orig = nullptr;
static PNVENCINITIALIZEENCODER nvEncInitializeEncoder_orig = nullptr; static PNVENCINITIALIZEENCODER nvEncInitializeEncoder_orig = nullptr;
static PNVENCGETENCODEPRESETCONFIG nvEncGetEncodePresetConfig_orig = nullptr;
static PNVENCGETENCODEPRESETCONFIGEX nvEncGetEncodePresetConfigEx_orig = nullptr;
static BOOL initialized = false; static BOOL initialized = false;
static BOOL new_preset_guids = false;
namespace nvenc_hook { namespace nvenc_hook {
@@ -27,6 +32,37 @@ namespace nvenc_hook {
void parse_qcp_params(); 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( NVENCSTATUS NVENCAPI nvEncOpenEncodeSessionEx_hook(
NV_ENC_OPEN_ENCODE_SESSION_EX_PARAMS *openSessionExParams, NV_ENC_OPEN_ENCODE_SESSION_EX_PARAMS *openSessionExParams,
void **encoder void **encoder
@@ -51,19 +87,22 @@ namespace nvenc_hook {
try { try {
// check input params // check input params
if (createEncodeParams == nullptr || createEncodeParams->encodeConfig == nullptr) { if (createEncodeParams == nullptr || createEncodeParams->encodeConfig == nullptr) {
log_warning("nvenc_hook", "nvEncInitializeEncoder called with invalid params");
goto done; goto done;
} }
const auto rc = &createEncodeParams->encodeConfig->rcParams; const auto rc = &createEncodeParams->encodeConfig->rcParams;
if (rc->rateControlMode != NV_ENC_PARAMS_RC_CONSTQP) {
log_warning(
"nvenc_hook",
"nvEncInitializeEncoder: unexpected rateControlMode, expected: NV_ENC_PARAMS_RC_CONSTQP, actual: {}",
rc->rateControlMode);
goto done;
}
log_misc("nvenc_hook", "nvEncInitializeEncoder hook hit with expected params"); 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 // print out most relevant video quality settings
// note: NvEncoder.cpp sample uses {28, 31, 25} (and that's what some hex edits modify) // 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 // but bm2dx later adds 8 to each value, before calling this routine
@@ -78,11 +117,75 @@ namespace nvenc_hook {
"nvenc_hook", "nvEncInitializeEncoder: user overriden constQP p={}, b={}, i={}", "nvenc_hook", "nvEncInitializeEncoder: user overriden constQP p={}, b={}, i={}",
rc->constQP.qpInterP, rc->constQP.qpInterB, rc->constQP.qpIntra); 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);
}
} catch (const std::exception &ex) {} } catch (const std::exception &ex) {}
done: 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) { NVENCSTATUS NVENCAPI NvEncodeAPICreateInstance_hook(NV_ENCODE_API_FUNCTION_LIST *pFunctionList) {
@@ -101,6 +204,11 @@ namespace nvenc_hook {
pFunctionList->nvEncInitializeEncoder, pFunctionList->nvEncInitializeEncoder,
nvEncInitializeEncoder_hook, nvEncInitializeEncoder_hook,
&nvEncInitializeEncoder_orig); &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"); log_misc("nvenc_hook", "NvEncodeAPICreateInstance_hook called and functions hooked");
initialized = true; initialized = true;
} }
@@ -123,6 +231,7 @@ namespace nvenc_hook {
} else { } else {
log_warning("nvenc_hook", "failed to hook NvEncodeAPICreateInstance"); log_warning("nvenc_hook", "failed to hook NvEncodeAPICreateInstance");
} }
parse_qcp_params(); parse_qcp_params();
} }
+12
View File
@@ -1727,6 +1727,13 @@ namespace overlay::windows {
std::vector<int> analogs_midi_indices; std::vector<int> analogs_midi_indices;
if (this->analogs_devices_selected >= 0) { if (this->analogs_devices_selected >= 0) {
auto device = this->analogs_devices.at(this->analogs_devices_selected); auto device = this->analogs_devices.at(this->analogs_devices_selected);
// add a tooltip to devices selector
if (!device->name.empty()) {
ImGui::SameLine();
ImGui::HelpMarker(device->name.c_str());
}
switch (device->type) { switch (device->type) {
case rawinput::MOUSE: { case rawinput::MOUSE: {
@@ -2036,6 +2043,11 @@ namespace overlay::windows {
ImGui::CloseCurrentPopup(); ImGui::CloseCurrentPopup();
} }
ImGui::SameLine();
if (ImGui::Button("Reset")) {
analog.resetValues();
}
// clean up // clean up
ImGui::EndPopup(); ImGui::EndPopup();
} }
+19
View File
@@ -18,6 +18,14 @@ namespace deferredlogs {
std::mutex deferred_errors_mutex; std::mutex deferred_errors_mutex;
std::vector<std::vector<std::string>> deferred_errors; std::vector<std::vector<std::string>> deferred_errors;
std::mutex softid_mutex;
std::string softid;
void set_softid(const std::string& softid_new) {
std::lock_guard<std::mutex> lock(softid_mutex);
softid = softid_new;
}
void defer_error_messages(std::initializer_list<std::string> messages) { void defer_error_messages(std::initializer_list<std::string> messages) {
std::lock_guard<std::mutex> lock(deferred_errors_mutex); std::lock_guard<std::mutex> lock(deferred_errors_mutex);
deferred_errors.emplace_back(messages); deferred_errors.emplace_back(messages);
@@ -45,6 +53,17 @@ namespace deferredlogs {
msg += "/-------------------------- spice2x auto-troubleshooter -----------------------\\\n"; msg += "/-------------------------- spice2x auto-troubleshooter -----------------------\\\n";
msg += "\n"; msg += "\n";
msg += " spice2x version: " + to_string(VERSION_STRING_CFG) + "\n"; msg += " spice2x version: " + to_string(VERSION_STRING_CFG) + "\n";
// soft ID
{
std::lock_guard<std::mutex> lock(softid_mutex);
if (!softid.empty()) {
msg += " game version: " + softid + "\n";
} else {
msg += " game version: unknown\n";
}
}
msg += "\n"; msg += "\n";
if (is_crash) { if (is_crash) {
+1
View File
@@ -9,6 +9,7 @@ namespace deferredlogs {
// some shared error messages // some shared error messages
extern const std::initializer_list<std::string> SUPERSTEP_SOUND_ERROR_MESSAGE; extern const std::initializer_list<std::string> SUPERSTEP_SOUND_ERROR_MESSAGE;
void set_softid(const std::string& softid);
void defer_error_messages(std::initializer_list<std::string> messages); void defer_error_messages(std::initializer_list<std::string> messages);
void dump_to_logger(bool is_crash=false); void dump_to_logger(bool is_crash=false);
} }