Compare commits

...

7 Commits

Author SHA1 Message Date
sp2xdev 3ef1b3ecaa update changelog 2025-09-20 04:31:11 -07:00
bicarus-dev 931811da14 iidx,sdvx: detect SuperstepSound init failures and log a message upon crash (#376)
## Link to GitHub Issue, if one exists
#345 

## Description of change
If we see SuperstepSound failure during boot, remember it, and when the
game eventually crashes, log a series of warning messages to surface why
the game likely failed to launch.

## Testing
Tested both iidx and sdvx with an audio device set to not allow
exclusive mode.
2025-09-20 04:17:40 -07:00
bicarus-dev 7624ded512 pc: add option to translate absolute analog values to fader input (#375)
## Link to GitHub Issue, if one exists
n/a

## Description of change
Add a new option that allows absolute position analog input, such as
SDVX controller knobs and IIDX turntables, to be bound as input for
Polaris Chord faders.

In practice this means turning a knob counter-clockwise results in fader
being held left (relative to velocity), and vice versa.

## Testing
Tested using FauceTwo and a mouse.
2025-09-20 03:40:18 -07:00
sp2xdev a5252939dd update iidx audio patch signature 2025-09-20 03:40:04 -07:00
GEEKiDoS 808ac0c557 Fix msvc compiler warnings (#374)
To be noticed I edited thirdparty header for fmt library (#373), idk if
it is acceptable
2025-09-18 21:57:38 -07:00
sp2xdev 4e138b244e add dsound.dll to custom DLL detection 2025-09-18 21:49:43 -07:00
sp2xdev 3fcd1bdbc9 fix option strings for force load 2025-09-18 15:59:06 -07:00
18 changed files with 157 additions and 13 deletions
+19
View File
@@ -80,6 +80,25 @@ if(MSVC)
# enable COMDAT folding for even smaller release builds # enable COMDAT folding for even smaller release builds
add_link_options("$<$<CONFIG:RELEASE,MINSIZEREL>:/OPT:ICF>") add_link_options("$<$<CONFIG:RELEASE,MINSIZEREL>:/OPT:ICF>")
# always use UTF-8 (fix 4819)
add_compile_options("/utf-8")
# spectre mitigation warning
add_compile_options("/wd5045")
# implicit type convert warnings
add_compile_options("/wd4244")
add_compile_options("/wd4267")
add_compile_options("/wd4305")
# unreferenced local variable
add_compile_options("/wd4101")
# warning in winbase.h??
add_compile_options("/wd5039")
elseif(CMAKE_CXX_COMPILER_ID STREQUAL "Clang") elseif(CMAKE_CXX_COMPILER_ID STREQUAL "Clang")
# disable warnings about using non _s variants like strncpy # disable warnings about using non _s variants like strncpy
add_compile_definitions(_CRT_SECURE_NO_WARNINGS) add_compile_definitions(_CRT_SECURE_NO_WARNINGS)
+1 -1
View File
@@ -373,7 +373,7 @@ void ICCADevice::update_card(int unit) {
bool kb_insert_press = false; bool kb_insert_press = false;
// eamio keypress // eamio keypress
kb_insert_press |= eamuse_get_keypad_state((size_t) unit) & (1 << EAM_IO_INSERT); kb_insert_press |= static_cast<bool>(eamuse_get_keypad_state((size_t) unit) & (1 << EAM_IO_INSERT));
// check for card // check for card
if (this->cards[unit] == nullptr && (eamuse_card_insert_consume(this->node_count, unit) || kb_insert_press)) { if (this->cards[unit] == nullptr && (eamuse_card_insert_consume(this->node_count, unit) || kb_insert_press)) {
+4
View File
@@ -1,3 +1,7 @@
09/20/2025 [spice2x]
Polaris Chord support for knobs/turntables/mouse as fader input
Misc fixes
09/18/2025 [spice2x] 09/18/2025 [spice2x]
Polaris Chord windowed mode fixes Polaris Chord windowed mode fixes
UI tweaks UI tweaks
+4
View File
@@ -1527,8 +1527,10 @@ FMT_CONSTEXPR auto make_arg(const T& value) -> basic_format_arg<Context> {
return arg; return arg;
} }
#ifdef __GNUC__
#pragma GCC diagnostic push #pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wdangling-reference" #pragma GCC diagnostic ignored "-Wdangling-reference"
#endif
// The type template parameter is there to avoid an ODR violation when using // The type template parameter is there to avoid an ODR violation when using
// a fallback formatter in one translation unit and an implicit conversion in // a fallback formatter in one translation unit and an implicit conversion in
@@ -1544,7 +1546,9 @@ FMT_CONSTEXPR FMT_INLINE auto make_arg(const T& val) -> value<Context> {
return arg; return arg;
} }
#ifdef __GNUC__
#pragma GCC diagnostic pop #pragma GCC diagnostic pop
#endif
template <bool IS_PACKED, typename Context, type, typename T, template <bool IS_PACKED, typename Context, type, typename T,
FMT_ENABLE_IF(!IS_PACKED)> FMT_ENABLE_IF(!IS_PACKED)>
+1 -1
View File
@@ -14,7 +14,7 @@ namespace games::ccj {
bool MOUSE_TRACKBALL_USE_TOGGLE = false; bool MOUSE_TRACKBALL_USE_TOGGLE = false;
uint8_t TRACKBALL_SENSITIVITY = 10; uint8_t TRACKBALL_SENSITIVITY = 10;
static HANDLE fakeHandle = (HANDLE)0xDEADBEEF; static HANDLE fakeHandle = (HANDLE)0xDEADBEEFull;
static HWND hWnd = nullptr; static HWND hWnd = nullptr;
static WNDPROC wndProc = nullptr; static WNDPROC wndProc = nullptr;
static std::thread *tbThread = nullptr; static std::thread *tbThread = nullptr;
+5 -1
View File
@@ -27,6 +27,7 @@
#include "util/memutils.h" #include "util/memutils.h"
#include "util/sigscan.h" #include "util/sigscan.h"
#include "util/utils.h" #include "util/utils.h"
#include "launcher/signal.h"
#include "external/robin_hood.h" #include "external/robin_hood.h"
@@ -232,6 +233,9 @@ namespace games::iidx {
} else if (data.find(" W:touch: missing trigger:") != std::string::npos) { } else if (data.find(" W:touch: missing trigger:") != std::string::npos) {
out.clear(); out.clear();
return true; return true;
} else if (data.find("SuperstepSound: Audio device is not available") != std::string::npos) {
launcher::signal::SUPERSTEP_SOUND_ERROR = TRUE;
return false;
} else { } else {
return false; return false;
} }
@@ -781,7 +785,7 @@ namespace games::iidx {
if (SOUND_OUTPUT_DEVICE_IN_EFFECT.has_value() && SOUND_OUTPUT_DEVICE_IN_EFFECT.value() == "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????ECFF83780803740D", "FF5008E8??????FF83780803740D",
"??????BB00000000EB169090????", "??????BB00000000EB169090????",
0, 0); 0, 0);
+1 -1
View File
@@ -94,7 +94,7 @@ namespace games::otoca {
bool kb_insert_press = false; bool kb_insert_press = false;
// eamio keypress // eamio keypress
kb_insert_press |= eamuse_get_keypad_state(0) & (1 << EAM_IO_INSERT); kb_insert_press |= static_cast<bool>(eamuse_get_keypad_state(0) & (1 << EAM_IO_INSERT));
// check for card // check for card
if (CARD_DATA == nullptr && (eamuse_card_insert_consume(1, 0) || kb_insert_press)) { if (CARD_DATA == nullptr && (eamuse_card_insert_consume(1, 0) || kb_insert_press)) {
+72
View File
@@ -7,8 +7,68 @@
#include "games/io.h" #include "games/io.h"
#include "io.h" #include "io.h"
#include "util/tapeled.h" #include "util/tapeled.h"
#include "pc.h"
#include "util/utils.h"
namespace games::pc { namespace games::pc {
bool PC_KNOB_MODE = false;
constexpr float RELATIVE_DECAY = 0.02f;
struct FADER_RELATIVE {
// ranges from 0.f to 1.f
float previous_raw = 0.5f;
// ranges from -1.f to +1.f
float effective_value = 0.f;
};
FADER_RELATIVE fader_l_relative;
FADER_RELATIVE fader_r_relative;
float calculate_analog_raw_delta(float old_value, float new_value) {
// assumed that values are between 0.f and 1.f
float delta = 0.f;
if ((old_value < 0.25f) && (0.75f < new_value)) {
// wrapped around: going left
delta = -((1.f - new_value) + old_value);
} else if ((new_value < 0.25f) && (0.75f < old_value)) {
// wrapped around: going right
delta = (1.f - old_value) + new_value;
} else {
delta = new_value - old_value;
}
return delta;
}
float apply_decay(float val) {
// return to center over time
if (RELATIVE_DECAY < val) {
return val - RELATIVE_DECAY;
} else if (val < -RELATIVE_DECAY) {
return val + RELATIVE_DECAY;
} else {
return 0.f;
}
}
float get_fader_knob_mode_value(float raw_analog, FADER_RELATIVE &fader) {
// add new value...
const float delta = calculate_analog_raw_delta(fader.previous_raw, raw_analog);
fader.effective_value += (delta * 20.f);
fader.previous_raw = raw_analog;
// capture the result after adding new input value
fader.effective_value = CLAMP(fader.effective_value, -1.f, 1.f);
const float result = fader.effective_value;
// post-process for next iteration: apply decay (return to center over time)
fader.effective_value = apply_decay(fader.effective_value);
return result;
}
/* /*
* class definitions * class definitions
@@ -270,8 +330,14 @@ namespace games::pc {
// FADER-L // FADER-L
float val = 0.f; float val = 0.f;
if (analogs[Analogs::FaderL].isSet()) { if (analogs[Analogs::FaderL].isSet()) {
if (PC_KNOB_MODE) {
val = get_fader_knob_mode_value(
GameAPI::Analogs::getState(RI_MGR, analogs[Analogs::FaderL]),
fader_l_relative);
} else {
val = (GameAPI::Analogs::getState(RI_MGR, analogs[Analogs::FaderL]) - 0.5f) * 2; val = (GameAPI::Analogs::getState(RI_MGR, analogs[Analogs::FaderL]) - 0.5f) * 2;
} }
}
if (GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::FaderL_Left]) && if (GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::FaderL_Left]) &&
GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::FaderL_Right])) { GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::FaderL_Right])) {
val = 0.f; val = 0.f;
@@ -289,8 +355,14 @@ namespace games::pc {
// FADER-R // FADER-R
val = 0.f; val = 0.f;
if (analogs[Analogs::FaderR].isSet()) { if (analogs[Analogs::FaderR].isSet()) {
if (PC_KNOB_MODE) {
val = get_fader_knob_mode_value(
GameAPI::Analogs::getState(RI_MGR, analogs[Analogs::FaderR]),
fader_r_relative);
} else {
val = (GameAPI::Analogs::getState(RI_MGR, analogs[Analogs::FaderR]) - 0.5f) * 2; val = (GameAPI::Analogs::getState(RI_MGR, analogs[Analogs::FaderR]) - 0.5f) * 2;
} }
}
if (GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::FaderR_Left]) && if (GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::FaderR_Left]) &&
GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::FaderR_Right])) { GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::FaderR_Right])) {
val = 0.f; val = 0.f;
+1
View File
@@ -5,6 +5,7 @@
namespace games::pc { namespace games::pc {
extern std::string PC_INJECT_ARGS; extern std::string PC_INJECT_ARGS;
extern bool PC_NO_IO; extern bool PC_NO_IO;
extern bool PC_KNOB_MODE;
class PCGame : public games::Game { class PCGame : public games::Game {
public: public:
+5
View File
@@ -24,6 +24,7 @@
#include "io.h" #include "io.h"
#include "acioemu/handle.h" #include "acioemu/handle.h"
#include "cfg/configurator.h" #include "cfg/configurator.h"
#include "launcher/signal.h"
static decltype(RegCloseKey) *RegCloseKey_orig = nullptr; static decltype(RegCloseKey) *RegCloseKey_orig = nullptr;
static decltype(RegEnumKeyA) *RegEnumKeyA_orig = nullptr; static decltype(RegEnumKeyA) *RegEnumKeyA_orig = nullptr;
@@ -209,6 +210,10 @@ namespace games::sdvx {
out = ""; out = "";
return true; return true;
} }
if (data.find("SuperstepSound: Audio device is not available") != std::string::npos) {
launcher::signal::SUPERSTEP_SOUND_ERROR = TRUE;
return false;
}
return false; return false;
} }
+4
View File
@@ -141,13 +141,17 @@ static ULONG WINAPI GetAdaptersInfo_hook(PIP_ADAPTER_INFO pAdapterInfo, PULONG p
static int WINAPI bind_hook(SOCKET s, const struct sockaddr *name, int namelen) { static int WINAPI bind_hook(SOCKET s, const struct sockaddr *name, int namelen) {
#ifdef __clang__
#pragma clang diagnostic push #pragma clang diagnostic push
#pragma ide diagnostic ignored "OCDFAInspection" #pragma ide diagnostic ignored "OCDFAInspection"
#endif
// cast to sockaddr_in // cast to sockaddr_in
struct sockaddr_in *in_name = (struct sockaddr_in *) name; struct sockaddr_in *in_name = (struct sockaddr_in *) name;
#ifdef __clang__
#pragma clang diagnostic pop #pragma clang diagnostic pop
#endif
// override bind to allow all hosts // override bind to allow all hosts
in_name->sin_addr.s_addr = inet_addr("0.0.0.0"); in_name->sin_addr.s_addr = inet_addr("0.0.0.0");
+4
View File
@@ -1112,6 +1112,9 @@ int main_implementation(int argc, char *argv[]) {
if (options[launcher::Options::PCNoIO].is_active()) { if (options[launcher::Options::PCNoIO].is_active()) {
games::pc::PC_NO_IO = options[launcher::Options::PCNoIO].value_bool(); games::pc::PC_NO_IO = options[launcher::Options::PCNoIO].value_bool();
} }
if (options[launcher::Options::PCKnobMode].value_bool()) {
games::pc::PC_KNOB_MODE = true;
}
if (options[launcher::Options::spice2x_EnableSMXStage].value_bool()) { if (options[launcher::Options::spice2x_EnableSMXStage].value_bool()) {
rawinput::ENABLE_SMX_STAGE = true; rawinput::ENABLE_SMX_STAGE = true;
} }
@@ -1964,6 +1967,7 @@ int main_implementation(int argc, char *argv[]) {
libutils::warn_if_dll_exists("nvcuvid.dll"); libutils::warn_if_dll_exists("nvcuvid.dll");
libutils::warn_if_dll_exists("nvEncodeAPI64.dll"); libutils::warn_if_dll_exists("nvEncodeAPI64.dll");
libutils::warn_if_dll_exists("msvcr100.dll"); libutils::warn_if_dll_exists("msvcr100.dll");
libutils::warn_if_dll_exists("dsound.dll");
// complain loudly & early about dll load ordering issue // complain loudly & early about dll load ordering issue
libutils::check_duplicate_dlls(); libutils::check_duplicate_dlls();
+17 -6
View File
@@ -1049,7 +1049,7 @@ static const std::vector<OptionDefinition> OPTION_DEFINITIONS = {
}, },
{ {
// LoadCCJModule // LoadCCJModule
.title = "Force Load Chase Chase Jokers", .title = "Force Load Chase Chase Jokers Module",
.name = "ccj", .name = "ccj",
.desc = "manually enable Chase Chase Jokers module", .desc = "manually enable Chase Chase Jokers module",
.type = OptionType::Bool, .type = OptionType::Bool,
@@ -1058,7 +1058,7 @@ static const std::vector<OptionDefinition> OPTION_DEFINITIONS = {
}, },
{ {
// LoadQKSModule // LoadQKSModule
.title = "Force Load QuizKnock STADIUM", .title = "Force Load QuizKnock STADIUM Module",
.name = "qks", .name = "qks",
.desc = "manually enable QuizKnock STADIUM module", .desc = "manually enable QuizKnock STADIUM module",
.type = OptionType::Bool, .type = OptionType::Bool,
@@ -1067,7 +1067,7 @@ static const std::vector<OptionDefinition> OPTION_DEFINITIONS = {
}, },
{ {
// LoadMFGModule // LoadMFGModule
.title = "Force Load Mahjong Fight Girl", .title = "Force Load Mahjong Fight Girl Module",
.name = "mfg", .name = "mfg",
.desc = "manually enable Mahjong Fight Girl module", .desc = "manually enable Mahjong Fight Girl module",
.type = OptionType::Bool, .type = OptionType::Bool,
@@ -1076,7 +1076,7 @@ static const std::vector<OptionDefinition> OPTION_DEFINITIONS = {
}, },
{ {
// LoadPCModule // LoadPCModule
.title = "Force Load Polaris Chord", .title = "Force Load Polaris Chord Module",
.name = "pc", .name = "pc",
.desc = "manually enable Polaris Chord module", .desc = "manually enable Polaris Chord module",
.type = OptionType::Bool, .type = OptionType::Bool,
@@ -1085,7 +1085,7 @@ static const std::vector<OptionDefinition> OPTION_DEFINITIONS = {
}, },
{ {
// LoadMusecaModule // LoadMusecaModule
.title = "Force Load Museca", .title = "Force Load Museca Module",
.name = "museca", .name = "museca",
.desc = "manually enable Museca module", .desc = "manually enable Museca module",
.type = OptionType::Bool, .type = OptionType::Bool,
@@ -1806,6 +1806,7 @@ static const std::vector<OptionDefinition> OPTION_DEFINITIONS = {
.category = "Game Options (Advanced)" .category = "Game Options (Advanced)"
}, },
{ {
// PCArgs
.title = "PC Arguments Override", .title = "PC Arguments Override",
.name = "pcargs", .name = "pcargs",
.desc = "Command line arguments passed to the game.", .desc = "Command line arguments passed to the game.",
@@ -1815,6 +1816,7 @@ static const std::vector<OptionDefinition> OPTION_DEFINITIONS = {
.category = "Game Options (Advanced)", .category = "Game Options (Advanced)",
}, },
{ {
// PCNoIO
.title = "PC Disable IO Emulation", .title = "PC Disable IO Emulation",
.name = "pcnoio", .name = "pcnoio",
.desc = "Disables BI2X hooks for PC", .desc = "Disables BI2X hooks for PC",
@@ -1823,6 +1825,15 @@ static const std::vector<OptionDefinition> OPTION_DEFINITIONS = {
.game_name = "Polaris Chord", .game_name = "Polaris Chord",
.category = "Game Options (Advanced)" .category = "Game Options (Advanced)"
}, },
{
// PCKnobMode
.title = "PC Fader Knobs Mode",
.name = "pcknobs",
.desc = "Allows SDVX knobs and IIDX turntables to be bound as Polaris Chord faders",
.type = OptionType::Bool,
.game_name = "Polaris Chord",
.category = "Game Options",
},
{ {
// spice2x_LightsOverallBrightness // spice2x_LightsOverallBrightness
.title = "Lights Brightness Adjustment", .title = "Lights Brightness Adjustment",
@@ -2445,7 +2456,7 @@ std::unique_ptr<std::vector<Option>> launcher::parse_options(int argc, char *arg
} else { } else {
// validate it is an integer // validate it is an integer
try { try {
std::stoull(argv[i], nullptr, 16); auto _ = std::stoull(argv[i], nullptr, 16);
} catch (const std::exception &ex) { } catch (const std::exception &ex) {
log_fatal("options", "parameter for -{} is not a hex number: {}", alias, argv[i]); log_fatal("options", "parameter for -{} is not a hex number: {}", alias, argv[i]);
} }
+1
View File
@@ -205,6 +205,7 @@ namespace launcher {
MFGNoIO, MFGNoIO,
PCArgs, PCArgs,
PCNoIO, PCNoIO,
PCKnobMode,
spice2x_LightsOverallBrightness, spice2x_LightsOverallBrightness,
spice2x_WindowBorder, spice2x_WindowBorder,
spice2x_WindowSize, spice2x_WindowSize,
+10
View File
@@ -29,6 +29,9 @@ namespace launcher::signal {
// settings // settings
bool DISABLE = false; bool DISABLE = false;
bool USE_VEH_WORKAROUND = false; bool USE_VEH_WORKAROUND = false;
// states
bool SUPERSTEP_SOUND_ERROR = false;
} }
#define V(variant) case variant: return #variant #define V(variant) case variant: return #variant
@@ -111,6 +114,13 @@ static LONG WINAPI TopLevelExceptionFilter(struct _EXCEPTION_POINTERS *Exception
" RivaTuner Statistics Server (RTSS), MSI Afterburner, kernel mode anti-cheat"); " RivaTuner Statistics Server (RTSS), MSI Afterburner, kernel mode anti-cheat");
} }
if (launcher::signal::SUPERSTEP_SOUND_ERROR) {
log_warning("signal", "audio initialization error was previously detected during boot!");
log_warning("signal", " (W:SuperstepSound: Audio device is not available!!!)");
log_warning("signal", " this crash is most likely related to audio init failure");
log_warning("signal", " fix your audio device, double check your audio options/patches, and try again");
}
// walk the exception chain // walk the exception chain
struct _EXCEPTION_RECORD *record_cause = ExceptionRecord->ExceptionRecord; struct _EXCEPTION_RECORD *record_cause = ExceptionRecord->ExceptionRecord;
while (record_cause != nullptr) { while (record_cause != nullptr) {
+1
View File
@@ -5,6 +5,7 @@ namespace launcher::signal {
// settings // settings
extern bool DISABLE; extern bool DISABLE;
extern bool USE_VEH_WORKAROUND; extern bool USE_VEH_WORKAROUND;
extern bool SUPERSTEP_SOUND_ERROR;
//void print_stacktrace(); //void print_stacktrace();
void attach(); void attach();
+1 -1
View File
@@ -162,7 +162,7 @@ static void __cdecl cardunit_update() {
bool kb_insert_press = false; bool kb_insert_press = false;
// eamio keypress // eamio keypress
kb_insert_press |= eamuse_get_keypad_state(unit) & (1 << EAM_IO_INSERT); kb_insert_press |= static_cast<bool>(eamuse_get_keypad_state(unit) & (1 << EAM_IO_INSERT));
// update card inserts // update card inserts
if (eamuse_card_insert_consume((int) EXTDEV_CARDUNIT_COUNT, unit) || if (eamuse_card_insert_consume((int) EXTDEV_CARDUNIT_COUNT, unit) ||
+4
View File
@@ -106,8 +106,10 @@ bool detour::inline_restore(void *address, char *data) {
#endif #endif
} }
#ifdef __clang__
#pragma clang diagnostic push #pragma clang diagnostic push
#pragma ide diagnostic ignored "OCDFAInspection" #pragma ide diagnostic ignored "OCDFAInspection"
#endif
static void *pe_offset(void *ptr, size_t offset) { static void *pe_offset(void *ptr, size_t offset) {
if (offset == 0) { if (offset == 0) {
@@ -282,7 +284,9 @@ void **detour::iat_find_proc(const char *iid_name, void *proc, HMODULE module) {
return nullptr; return nullptr;
} }
#ifdef __clang__
#pragma clang diagnostic pop #pragma clang diagnostic pop
#endif
void *detour::iat_try(const char *function, void *new_func, HMODULE module, const char *iid_name) { void *detour::iat_try(const char *function, void *new_func, HMODULE module, const char *iid_name) {