mirror of
https://github.com/spice2x/spice2x.github.io.git
synced 2026-08-01 22:30:42 -07:00
Compare commits
24 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 5f94ef4478 | |||
| c4193eddc5 | |||
| c8962a0e77 | |||
| b6a749e5db | |||
| 7dbbe9809e | |||
| 79a3e32d5d | |||
| c5a4e954f9 | |||
| 7b862768b0 | |||
| af8d8dae9f | |||
| d9d5823fdb | |||
| 82dda33800 | |||
| 3876e38636 | |||
| 6bc1357d6a | |||
| 37218e7fe0 | |||
| 678e11eade | |||
| f72313fe45 | |||
| 06374ef78a | |||
| bdde1ec6b8 | |||
| d2176fe4f0 | |||
| fb9eef904a | |||
| 48903998a5 | |||
| f2dca0265f | |||
| 1147ed9858 | |||
| 19d93f4ffd |
@@ -2,6 +2,7 @@
|
||||
#include "mdxf_poll.h"
|
||||
|
||||
#include "avs/game.h"
|
||||
#include "games/ddr/ddr.h"
|
||||
#include "games/ddr/io.h"
|
||||
#include "launcher/launcher.h"
|
||||
#include "rawinput/rawinput.h"
|
||||
@@ -357,14 +358,17 @@ static bool __cdecl ac_io_mdxf_update_control_status_buffer_impl(int node, MDXFP
|
||||
|
||||
// decide on button map
|
||||
const size_t *button_map = nullptr;
|
||||
int player = 0;
|
||||
switch (node) {
|
||||
case 17:
|
||||
case 25:
|
||||
button_map = &buttons_p1[0];
|
||||
player = 1;
|
||||
break;
|
||||
case 18:
|
||||
case 26:
|
||||
button_map = &buttons_p2[0];
|
||||
player = 2;
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -374,19 +378,27 @@ static bool __cdecl ac_io_mdxf_update_control_status_buffer_impl(int node, MDXFP
|
||||
if (source == EXTERNAL_POLL) {
|
||||
// get buttons
|
||||
auto &buttons = games::ddr::get_buttons();
|
||||
|
||||
// get analogs
|
||||
bool analog_left = false;
|
||||
bool analog_right = false;
|
||||
games::ddr::get_analog_x_axis(player, analog_left, analog_right);
|
||||
bool analog_up = false;
|
||||
bool analog_down = false;
|
||||
games::ddr::get_analog_y_axis(player, analog_up, analog_down);
|
||||
|
||||
uint8_t up_down = 0;
|
||||
uint8_t left_right = 0;
|
||||
|
||||
if (GameAPI::Buttons::getState(RI_MGR, buttons.at(button_map[0]))) {
|
||||
if (GameAPI::Buttons::getState(RI_MGR, buttons.at(button_map[0])) || analog_up) {
|
||||
up_down |= 0xF0;
|
||||
}
|
||||
if (GameAPI::Buttons::getState(RI_MGR, buttons.at(button_map[1]))) {
|
||||
if (GameAPI::Buttons::getState(RI_MGR, buttons.at(button_map[1])) || analog_down) {
|
||||
up_down |= 0x0F;
|
||||
}
|
||||
if (GameAPI::Buttons::getState(RI_MGR, buttons.at(button_map[2]))) {
|
||||
if (GameAPI::Buttons::getState(RI_MGR, buttons.at(button_map[2])) || analog_left) {
|
||||
left_right |= 0xF0;
|
||||
}
|
||||
if (GameAPI::Buttons::getState(RI_MGR, buttons.at(button_map[3]))) {
|
||||
if (GameAPI::Buttons::getState(RI_MGR, buttons.at(button_map[3])) || analog_right) {
|
||||
left_right |= 0x0F;
|
||||
}
|
||||
current_state = (uint16_t(up_down) << 8) | left_right;
|
||||
|
||||
@@ -58,7 +58,7 @@ namespace api::modules {
|
||||
|
||||
// check if file exists in modules
|
||||
if (!fileutils::file_exists(dll_path)) {
|
||||
return error(res, "Couldn't find " + dll_path.string());
|
||||
return error(res, fmt::format("Couldn't find {}", dll_path));
|
||||
}
|
||||
|
||||
// get module
|
||||
@@ -118,7 +118,7 @@ namespace api::modules {
|
||||
|
||||
// check if file exists in modules
|
||||
if (!fileutils::file_exists(dll_path)) {
|
||||
return error(res, "Couldn't find " + dll_path.string());
|
||||
return error(res, fmt::format("Couldn't find {}", dll_path));
|
||||
}
|
||||
|
||||
// get module
|
||||
@@ -196,7 +196,7 @@ namespace api::modules {
|
||||
|
||||
// check if file exists in modules
|
||||
if (!fileutils::file_exists(dll_path)) {
|
||||
return error(res, "Couldn't find " + dll_path.string());
|
||||
return error(res, fmt::format("Couldn't find {}", dll_path));
|
||||
}
|
||||
|
||||
// get module
|
||||
|
||||
@@ -1516,7 +1516,7 @@ namespace avs {
|
||||
" * It's also possible that you have incomplete game data\n"
|
||||
" * Do NOT copy over random DLLs from another game installation; DLL must match game version\n"
|
||||
"\n"
|
||||
, DLL_NAME, MODULE_PATH.string()) };
|
||||
, DLL_NAME, MODULE_PATH) };
|
||||
log_warning("avs-ea3", "{}", info_str);
|
||||
log_fatal("avs-ea3", "Failed to find critical avs DLL on disk (avs2-core.dll OR {})", DLL_NAME);
|
||||
}
|
||||
@@ -1757,8 +1757,8 @@ namespace avs {
|
||||
{
|
||||
deferredlogs::defer_error_messages({
|
||||
"AVS filesystem initialization failure was previously detected during boot!",
|
||||
fmt::format(" ERROR: directory could not be created: {}", src_path.string().c_str()),
|
||||
fmt::format(" if you see a crash, it may have been caused by bad <mounttable> contents in {}", avs::core::CFG_PATH.c_str()),
|
||||
fmt::format(" ERROR: directory could not be created: {}", src_path),
|
||||
fmt::format(" if you see a crash, it may have been caused by bad <mounttable> contents in {}", avs::core::CFG_PATH),
|
||||
" fix the XML file and try again"
|
||||
});
|
||||
}
|
||||
@@ -1780,14 +1780,14 @@ namespace avs {
|
||||
auto created = std::filesystem::create_directories(real_path, err);
|
||||
|
||||
if (created) {
|
||||
log_info("avs-core", "created '{}' at '{}'", avs_path, real_path.string());
|
||||
log_info("avs-core", "created '{}' at '{}'", avs_path, real_path);
|
||||
}
|
||||
|
||||
if (err) {
|
||||
avs_dir_err(real_path);
|
||||
log_warning("avs-core", "failed to create '{}' folder at '{}': {}",
|
||||
avs_path,
|
||||
real_path.string(),
|
||||
real_path,
|
||||
err.message());
|
||||
}
|
||||
}
|
||||
@@ -1921,6 +1921,15 @@ namespace avs {
|
||||
// create nvram and raw directories if possible for mounttable configurations
|
||||
create_avs_config_fs_table(config, config_node);
|
||||
|
||||
#if !SPICE64
|
||||
// sdvx4 bad log config fix
|
||||
if (avs::game::DLL_NAME == "soundvoltex.dll" && // it's too early for avs::game::is_model
|
||||
property_search_safe(config, config_node, "/log/enable_console")) {
|
||||
log_info("avs-core", "applying SDVX4 avs-config.xml fix for <log><enable_console>");
|
||||
property_search_remove_safe(config, config_node, "/log/enable_console");
|
||||
}
|
||||
#endif
|
||||
|
||||
// set log level
|
||||
if (!LOG_LEVEL_CUSTOM.empty()) {
|
||||
property_search_remove_safe(config, config_node, "/log/level");
|
||||
|
||||
@@ -151,7 +151,7 @@ namespace avs {
|
||||
" * It's also possible that you have incomplete game data\n"
|
||||
" * Do NOT copy over random DLLs from another game installation; DLL must match game version\n"
|
||||
"\n"
|
||||
, DLL_NAME, MODULE_PATH.string()) };
|
||||
, DLL_NAME, MODULE_PATH) };
|
||||
|
||||
log_warning("avs-ea3", "{}", info_str);
|
||||
log_fatal("avs-ea3", "Failed to find critical ea3 DLL on disk (avs2-ea3.dll OR {})", DLL_NAME);
|
||||
@@ -217,8 +217,10 @@ namespace avs {
|
||||
ea3_config_name = "prop/eamuse-config.xml";
|
||||
}
|
||||
|
||||
if (avs::core::file_exists(ea3_config_name)) {
|
||||
log_info("avs-ea3", "booting (using {})", ea3_config_name);
|
||||
if (fileutils::file_exists(ea3_config_name)) {
|
||||
log_info("avs-ea3", "found {} on disk", ea3_config_name);
|
||||
} else if (CFG_PATH.size()) {
|
||||
log_fatal("avs-ea3", "user-specified ea3 config file is missing: {}", ea3_config_name);
|
||||
} else {
|
||||
log_warning("avs-ea3", "looked for the following files in order:");
|
||||
log_warning("avs-ea3", " * prop/ea3-config.xml");
|
||||
|
||||
@@ -73,11 +73,10 @@ namespace avs {
|
||||
|
||||
// load game instance
|
||||
const auto dll_path = MODULE_PATH / DLL_NAME;
|
||||
const auto dll_path_s = dll_path.string();
|
||||
log_info("avs-game", "DLL path: {}", dll_path_s.c_str());
|
||||
log_info("avs-game", "DLL path: {}", dll_path);
|
||||
|
||||
// MAX_PATH is 260
|
||||
if (130 <= dll_path_s.length()) {
|
||||
if (const auto dll_path_len = dll_path.wstring().length(); 130 <= dll_path_len) {
|
||||
log_warning(
|
||||
"avs-game",
|
||||
"PATH TOO LONG WARNING\n\n"
|
||||
@@ -93,7 +92,7 @@ namespace avs {
|
||||
"long, often resulting in random crashes. Move the game contents to\n"
|
||||
"a directory with shorter path.\n"
|
||||
"-------------------------------------------------------------------\n\n",
|
||||
dll_path_s, dll_path_s.length());
|
||||
dll_path, dll_path_len);
|
||||
}
|
||||
|
||||
// ddr gamemdx.dll user error
|
||||
@@ -112,7 +111,7 @@ namespace avs {
|
||||
|
||||
// file not found on disk
|
||||
if (!fileutils::file_exists(dll_path)) {
|
||||
log_warning("avs-game", "game DLL could not be found on disk: {}", dll_path.string().c_str());
|
||||
log_warning("avs-game", "game DLL could not be found on disk: {}", dll_path);
|
||||
log_warning("avs-game", "double check -exec and -modules parameters; unless you know what you're doing, leave them blank");
|
||||
}
|
||||
|
||||
|
||||
@@ -39,8 +39,8 @@
|
||||
<button name="VEFX" vkey="10" analogtype="0" devid="Player 1" invert="false" debounce_up="0" debounce_down="0" velocity_threshold="0"/>
|
||||
</buttons>
|
||||
<analogs>
|
||||
<analog name="Turntable P1" devid="Player 1" index="4" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1" relative="false" delay="0"/>
|
||||
<analog name="Turntable P2" devid="Player 2" index="4" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1" relative="false" delay="0"/>
|
||||
<analog name="Turntable P1" devid="Player 1" index="4" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1"/>
|
||||
<analog name="Turntable P2" devid="Player 2" index="4" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1"/>
|
||||
</analogs>
|
||||
<lights>
|
||||
<light name="P1 1" devid="Player 1" index="0"/>
|
||||
@@ -117,8 +117,8 @@
|
||||
<keypad_buttons/>
|
||||
<!-- Assumes analog mode. Need to invert direction since this FW is designed for INF. -->
|
||||
<analogs>
|
||||
<analog name="Turntable P1" devid="arcin (1p)" index="0" sensivity="1" deadzone="0" deadzone_mirror="false" invert="true" smoothing="false" multiplier="1" relative="false" delay="0"/>
|
||||
<analog name="Turntable P2" devid="arcin (2p)" index="0" sensivity="1" deadzone="0" deadzone_mirror="false" invert="true" smoothing="false" multiplier="1" relative="false" delay="0"/>
|
||||
<analog name="Turntable P1" devid="arcin (1p)" index="0" sensivity="1" deadzone="0" deadzone_mirror="false" invert="true" smoothing="false" multiplier="1"/>
|
||||
<analog name="Turntable P2" devid="arcin (2p)" index="0" sensivity="1" deadzone="0" deadzone_mirror="false" invert="true" smoothing="false" multiplier="1"/>
|
||||
</analogs>
|
||||
<lights>
|
||||
<light name="P1 1" devid="arcin (1p)" index="0"/>
|
||||
@@ -144,34 +144,34 @@
|
||||
<buttons/>
|
||||
<keypad_buttons/>
|
||||
<analogs>
|
||||
<analog name="Key 1" devid="Analogs key 01-28" index="4" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1" relative="false" delay="0"/>
|
||||
<analog name="Key 2" devid="Analogs key 01-28" index="5" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1" relative="false" delay="0"/>
|
||||
<analog name="Key 3" devid="Analogs key 01-28" index="6" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1" relative="false" delay="0"/>
|
||||
<analog name="Key 4" devid="Analogs key 01-28" index="7" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1" relative="false" delay="0"/>
|
||||
<analog name="Key 5" devid="Analogs key 01-28" index="8" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1" relative="false" delay="0"/>
|
||||
<analog name="Key 6" devid="Analogs key 01-28" index="9" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1" relative="false" delay="0"/>
|
||||
<analog name="Key 7" devid="Analogs key 01-28" index="10" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1" relative="false" delay="0"/>
|
||||
<analog name="Key 8" devid="Analogs key 01-28" index="11" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1" relative="false" delay="0"/>
|
||||
<analog name="Key 9" devid="Analogs key 01-28" index="12" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1" relative="false" delay="0"/>
|
||||
<analog name="Key 10" devid="Analogs key 01-28" index="13" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1" relative="false" delay="0"/>
|
||||
<analog name="Key 11" devid="Analogs key 01-28" index="14" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1" relative="false" delay="0"/>
|
||||
<analog name="Key 12" devid="Analogs key 01-28" index="15" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1" relative="false" delay="0"/>
|
||||
<analog name="Key 13" devid="Analogs key 01-28" index="16" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1" relative="false" delay="0"/>
|
||||
<analog name="Key 14" devid="Analogs key 01-28" index="17" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1" relative="false" delay="0"/>
|
||||
<analog name="Key 15" devid="Analogs key 01-28" index="18" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1" relative="false" delay="0"/>
|
||||
<analog name="Key 16" devid="Analogs key 01-28" index="19" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1" relative="false" delay="0"/>
|
||||
<analog name="Key 17" devid="Analogs key 01-28" index="20" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1" relative="false" delay="0"/>
|
||||
<analog name="Key 18" devid="Analogs key 01-28" index="21" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1" relative="false" delay="0"/>
|
||||
<analog name="Key 19" devid="Analogs key 01-28" index="22" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1" relative="false" delay="0"/>
|
||||
<analog name="Key 20" devid="Analogs key 01-28" index="23" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1" relative="false" delay="0"/>
|
||||
<analog name="Key 21" devid="Analogs key 01-28" index="24" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1" relative="false" delay="0"/>
|
||||
<analog name="Key 22" devid="Analogs key 01-28" index="25" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1" relative="false" delay="0"/>
|
||||
<analog name="Key 23" devid="Analogs key 01-28" index="26" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1" relative="false" delay="0"/>
|
||||
<analog name="Key 24" devid="Analogs key 01-28" index="27" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1" relative="false" delay="0"/>
|
||||
<analog name="Key 25" devid="Analogs key 01-28" index="28" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1" relative="false" delay="0"/>
|
||||
<analog name="Key 26" devid="Analogs key 01-28" index="29" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1" relative="false" delay="0"/>
|
||||
<analog name="Key 27" devid="Analogs key 01-28" index="30" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1" relative="false" delay="0"/>
|
||||
<analog name="Key 28" devid="Analogs key 01-28" index="31" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1" relative="false" delay="0"/>
|
||||
<analog name="Key 1" devid="Analogs key 01-28" index="4" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1"/>
|
||||
<analog name="Key 2" devid="Analogs key 01-28" index="5" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1"/>
|
||||
<analog name="Key 3" devid="Analogs key 01-28" index="6" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1"/>
|
||||
<analog name="Key 4" devid="Analogs key 01-28" index="7" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1"/>
|
||||
<analog name="Key 5" devid="Analogs key 01-28" index="8" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1"/>
|
||||
<analog name="Key 6" devid="Analogs key 01-28" index="9" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1"/>
|
||||
<analog name="Key 7" devid="Analogs key 01-28" index="10" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1"/>
|
||||
<analog name="Key 8" devid="Analogs key 01-28" index="11" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1"/>
|
||||
<analog name="Key 9" devid="Analogs key 01-28" index="12" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1"/>
|
||||
<analog name="Key 10" devid="Analogs key 01-28" index="13" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1"/>
|
||||
<analog name="Key 11" devid="Analogs key 01-28" index="14" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1"/>
|
||||
<analog name="Key 12" devid="Analogs key 01-28" index="15" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1"/>
|
||||
<analog name="Key 13" devid="Analogs key 01-28" index="16" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1"/>
|
||||
<analog name="Key 14" devid="Analogs key 01-28" index="17" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1"/>
|
||||
<analog name="Key 15" devid="Analogs key 01-28" index="18" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1"/>
|
||||
<analog name="Key 16" devid="Analogs key 01-28" index="19" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1"/>
|
||||
<analog name="Key 17" devid="Analogs key 01-28" index="20" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1"/>
|
||||
<analog name="Key 18" devid="Analogs key 01-28" index="21" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1"/>
|
||||
<analog name="Key 19" devid="Analogs key 01-28" index="22" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1"/>
|
||||
<analog name="Key 20" devid="Analogs key 01-28" index="23" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1"/>
|
||||
<analog name="Key 21" devid="Analogs key 01-28" index="24" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1"/>
|
||||
<analog name="Key 22" devid="Analogs key 01-28" index="25" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1"/>
|
||||
<analog name="Key 23" devid="Analogs key 01-28" index="26" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1"/>
|
||||
<analog name="Key 24" devid="Analogs key 01-28" index="27" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1"/>
|
||||
<analog name="Key 25" devid="Analogs key 01-28" index="28" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1"/>
|
||||
<analog name="Key 26" devid="Analogs key 01-28" index="29" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1"/>
|
||||
<analog name="Key 27" devid="Analogs key 01-28" index="30" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1"/>
|
||||
<analog name="Key 28" devid="Analogs key 01-28" index="31" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1"/>
|
||||
</analogs>
|
||||
<lights>
|
||||
<light name="Key 1 R" devid="Lights 01-14" index="0"/>
|
||||
@@ -277,9 +277,9 @@
|
||||
</buttons>
|
||||
<keypad_buttons/>
|
||||
<analogs>
|
||||
<analog name="Guitar P1 Wail X" devid="Gitaller P1" index="1" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1" relative="false" delay="0"/>
|
||||
<analog name="Guitar P1 Wail Y" devid="Gitaller P1" index="0" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1" relative="false" delay="0"/>
|
||||
<analog name="Guitar P1 Wail Z" devid="Gitaller P1" index="5" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1" relative="false" delay="0"/>
|
||||
<analog name="Guitar P1 Wail X" devid="Gitaller P1" index="1" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1"/>
|
||||
<analog name="Guitar P1 Wail Y" devid="Gitaller P1" index="0" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1"/>
|
||||
<analog name="Guitar P1 Wail Z" devid="Gitaller P1" index="5" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1"/>
|
||||
</analogs>
|
||||
<lights/>
|
||||
<!-- Gen 1 FAUCETWO in HID mode; should also work for later gens -->
|
||||
@@ -296,8 +296,8 @@
|
||||
</buttons>
|
||||
<keypad_buttons/>
|
||||
<analogs>
|
||||
<analog name="VOL-L" devid="Buttons (MI_01)" index="1" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1" relative="false" delay="0"/>
|
||||
<analog name="VOL-R" devid="Buttons (MI_01)" index="0" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1" relative="false" delay="0"/>
|
||||
<analog name="VOL-L" devid="Buttons (MI_01)" index="1" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1"/>
|
||||
<analog name="VOL-R" devid="Buttons (MI_01)" index="0" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1"/>
|
||||
</analogs>
|
||||
<lights>
|
||||
<light name="BT-A" devid="Lights (MI_02)" index="24"/>
|
||||
|
||||
@@ -163,6 +163,7 @@ time (
|
||||
if ((BUILD_XP > 0))
|
||||
then
|
||||
# 32 bit Windows XP
|
||||
echo ""
|
||||
echo "Building 32bit targets (WinXP toolchain)..."
|
||||
echo "========================="
|
||||
if ((CLEAN_BUILD > 0))
|
||||
@@ -187,6 +188,7 @@ time (
|
||||
CXXFLAGS="$CXXFLAGS -DSPICE_XP=1" cmake -G "Ninja" -DCMAKE_TOOLCHAIN_FILE=${TOOLCHAIN_WINXP_64} -DCMAKE_BUILD_TYPE=${BUILD_TYPE} "$OLDPWD" && ninja ${TARGETS_XP64}
|
||||
popd > /dev/null
|
||||
else
|
||||
echo ""
|
||||
echo "Skipping WinXP builds, toolchain not specified"
|
||||
fi
|
||||
|
||||
|
||||
+29
-14
@@ -207,22 +207,37 @@ float Analog::applyMultiplier(float value) {
|
||||
}
|
||||
|
||||
float Analog::normalizeAnalogValue(float value) {
|
||||
// effectively the same as fmodf(value, 1.f)
|
||||
// for small values, this is MUCH faster than fmodf.
|
||||
float new_value = value;
|
||||
while (new_value > 1.f) {
|
||||
new_value -= 1.f;
|
||||
if (getType() == GameAPI::Analogs::AnalogType::Circular) {
|
||||
// effectively the same as fmodf(value, 1.f)
|
||||
// for small values, this is MUCH faster than fmodf.
|
||||
float new_value = value;
|
||||
while (new_value > 1.f) {
|
||||
new_value -= 1.f;
|
||||
}
|
||||
while (new_value < 0.f) {
|
||||
new_value += 1.f;
|
||||
}
|
||||
return new_value;
|
||||
|
||||
} else {
|
||||
// clamp to [0, 1] range
|
||||
return std::clamp(value, 0.f, 1.f);
|
||||
}
|
||||
while (new_value < 0.f) {
|
||||
new_value += 1.f;
|
||||
}
|
||||
return new_value;
|
||||
}
|
||||
|
||||
float Analog::applyDeadzone(float raw_value) {
|
||||
float value = raw_value;
|
||||
const auto deadzone = this->getDeadzone();
|
||||
if (deadzone > 0) {
|
||||
auto deadzone = this->getDeadzone();
|
||||
|
||||
// in the past, positive deadzone applied in the center, negative deadzone applied to 0
|
||||
// after each analog value received a type (circular/linear) this has been simpliifed to
|
||||
// positive values only since we can figure out where the rest value is
|
||||
// for back compat, treat negative value as positive
|
||||
if (deadzone < 0.f) {
|
||||
deadzone = -deadzone;
|
||||
}
|
||||
|
||||
if (getType() != GameAPI::Analogs::AnalogType::LinearPositive) {
|
||||
|
||||
// calculate values
|
||||
const auto delta = value - 0.5f;
|
||||
@@ -255,7 +270,7 @@ float Analog::applyDeadzone(float raw_value) {
|
||||
}
|
||||
}
|
||||
|
||||
} else if (deadzone < 0) {
|
||||
} else {
|
||||
|
||||
// invert for mirror
|
||||
if (this->getDeadzoneMirror()) {
|
||||
@@ -263,8 +278,8 @@ float Analog::applyDeadzone(float raw_value) {
|
||||
}
|
||||
|
||||
// deadzone from minimum value
|
||||
if (deadzone > -1 && value > -deadzone) {
|
||||
value = std::min(1.f, (value + deadzone) / (1.f + deadzone));
|
||||
if (deadzone < 1.f && deadzone < value) {
|
||||
value = std::max(0.f, (value - deadzone) / (1.f - deadzone));
|
||||
} else {
|
||||
value = 0.f;
|
||||
}
|
||||
|
||||
+23
-35
@@ -13,6 +13,21 @@ namespace rawinput {
|
||||
class RawInputManager;
|
||||
}
|
||||
|
||||
namespace GameAPI::Analogs {
|
||||
enum class AnalogType {
|
||||
// default; values wrap around (below 0 turns into 1, over 1 is 0)
|
||||
// knobs, turntables
|
||||
Circular = 0,
|
||||
|
||||
// typical joystick that rests at the center and caps at [0, 1]
|
||||
LinearCentered = 1,
|
||||
|
||||
// one-directional value (sliders and instruments like piano/drum velocity)
|
||||
// starts at 0 and goes up to 1
|
||||
LinearPositive = 2,
|
||||
};
|
||||
}
|
||||
|
||||
struct AnalogMovingAverage {
|
||||
double time_in_ms;
|
||||
float sine;
|
||||
@@ -31,10 +46,11 @@ private:
|
||||
float last_state = 0.5f;
|
||||
bool sensitivity_set = false;
|
||||
bool deadzone_set = false;
|
||||
GameAPI::Analogs::AnalogType type = GameAPI::Analogs::AnalogType::Circular;
|
||||
|
||||
// smoothing function
|
||||
bool smoothing = false;
|
||||
std::array<AnalogMovingAverage, ANALOG_HISTORY_CNT> vector_history;
|
||||
std::array<AnalogMovingAverage, ANALOG_HISTORY_CNT> vector_history = {};
|
||||
int vector_history_index = 0;
|
||||
float smoothed_last_state = 0.f;
|
||||
|
||||
@@ -47,14 +63,6 @@ private:
|
||||
float divisor_previous_value = 0.5f;
|
||||
unsigned short divisor_region = 0;
|
||||
|
||||
// relative input mode
|
||||
float absolute_value_for_rel_mode = 0.5f;
|
||||
bool relative_mode = false;
|
||||
|
||||
// circular buffer (delayed input)
|
||||
int delay_buffer_depth = 0;
|
||||
std::queue<float> delay_buffer;
|
||||
|
||||
float calculateAngularDifference(float old_rads, float new_rads);
|
||||
float normalizeAngle(float rads);
|
||||
float normalizeAnalogValue(float value);
|
||||
@@ -66,7 +74,8 @@ public:
|
||||
float override_state = 0.5f;
|
||||
|
||||
explicit Analog(std::string name) : name(std::move(name)) {
|
||||
vector_history.fill({0.0, 0.f, 0.f});
|
||||
};
|
||||
explicit Analog(std::string name, GameAPI::Analogs::AnalogType type) : name(std::move(name)), type(type) {
|
||||
};
|
||||
|
||||
std::string getDisplayString(rawinput::RawInputManager* manager);
|
||||
@@ -89,8 +98,6 @@ public:
|
||||
smoothing = false;
|
||||
deadzone_mirror = false;
|
||||
setMultiplier(1);
|
||||
setRelativeMode(false);
|
||||
setDelayBufferDepth(0);
|
||||
setLastState(0.5f);
|
||||
}
|
||||
|
||||
@@ -188,30 +195,11 @@ public:
|
||||
this->last_state = last_state;
|
||||
}
|
||||
|
||||
inline bool isRelativeMode() const {
|
||||
return this->relative_mode;
|
||||
inline GameAPI::Analogs::AnalogType getType() const {
|
||||
return this->type;
|
||||
}
|
||||
|
||||
inline void setRelativeMode(bool relative_mode) {
|
||||
this->relative_mode = relative_mode;
|
||||
this->absolute_value_for_rel_mode = 0.5f;
|
||||
}
|
||||
|
||||
inline float getAbsoluteValue(float relative_delta) {
|
||||
this->absolute_value_for_rel_mode =
|
||||
normalizeAnalogValue(this->absolute_value_for_rel_mode + relative_delta);
|
||||
return this->absolute_value_for_rel_mode;
|
||||
}
|
||||
|
||||
inline int getDelayBufferDepth() const {
|
||||
return this->delay_buffer_depth;
|
||||
}
|
||||
|
||||
inline void setDelayBufferDepth(int depth) {
|
||||
this->delay_buffer_depth = depth;
|
||||
}
|
||||
|
||||
inline std::queue<float> &getDelayBuffer() {
|
||||
return this->delay_buffer;
|
||||
inline void setType(GameAPI::Analogs::AnalogType type) {
|
||||
this->type = type;
|
||||
}
|
||||
};
|
||||
|
||||
+92
-42
@@ -199,9 +199,19 @@ GameAPI::Buttons::State GameAPI::Buttons::getState(rawinput::RawInputManager *ma
|
||||
if (vKey < value_states->size()) {
|
||||
auto value = value_states->at(vKey);
|
||||
if (current_button->getAnalogType() == BAT_POSITIVE) {
|
||||
state = value > 0.6f ? BUTTON_PRESSED : BUTTON_NOT_PRESSED;
|
||||
float threshold = 0.6f;
|
||||
if (current_button->getBatThreshold() > 0) {
|
||||
threshold = std::clamp(current_button->getBatThreshold() / 100.f, 0.01f, 0.99f);
|
||||
}
|
||||
state = value > threshold ? BUTTON_PRESSED : BUTTON_NOT_PRESSED;
|
||||
|
||||
} else if (current_button->getAnalogType() == BAT_NEGATIVE) {
|
||||
state = value < 0.4f ? BUTTON_PRESSED : BUTTON_NOT_PRESSED;
|
||||
float threshold = 0.4f;
|
||||
if (current_button->getBatThreshold() > 0) {
|
||||
threshold = std::clamp((100 - current_button->getBatThreshold()) / 100.f, 0.01f, 0.99f);
|
||||
}
|
||||
state = value < threshold ? BUTTON_PRESSED : BUTTON_NOT_PRESSED;
|
||||
|
||||
} else {
|
||||
state = value > 0.01f ? BUTTON_PRESSED : BUTTON_NOT_PRESSED;
|
||||
}
|
||||
@@ -669,32 +679,12 @@ float GameAPI::Analogs::getState(rawinput::RawInputManager *manager, rawinput::D
|
||||
}
|
||||
|
||||
// deadzone
|
||||
if (analog.isDeadzoneSet()) {
|
||||
// do not apply deadzone to circular analogs since it doesn't make sense
|
||||
if (analog.isDeadzoneSet() && analog.getType() != AnalogType::Circular) {
|
||||
value = analog.applyDeadzone(value);
|
||||
}
|
||||
|
||||
if (analog.isRelativeMode()) {
|
||||
float relative_delta = value - 0.5f;
|
||||
// built-in scaling to make values reasonable
|
||||
relative_delta /= 80.f;
|
||||
|
||||
// integer multiplier/divisor
|
||||
const auto mult = analog.getMultiplier();
|
||||
if (mult < -1) {
|
||||
relative_delta /= -mult;
|
||||
} else if (1 < mult) {
|
||||
relative_delta *= mult;
|
||||
}
|
||||
|
||||
// sensitivity (ranges from 0.0 to 4.0)
|
||||
if (analog.isSensitivitySet()) {
|
||||
relative_delta *= analog.getSensitivity();
|
||||
}
|
||||
|
||||
// translate relative movement to absolute value
|
||||
value = analog.getAbsoluteValue(relative_delta);
|
||||
|
||||
} else {
|
||||
if (analog.getType() == AnalogType::Circular) {
|
||||
// integer multiplier
|
||||
value = analog.applyMultiplier(value);
|
||||
|
||||
@@ -722,30 +712,54 @@ float GameAPI::Analogs::getState(rawinput::RawInputManager *manager, rawinput::D
|
||||
// apply to value
|
||||
value = rads * (float) M_1_TAU;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// sensitivity
|
||||
if (analog.isSensitivitySet()) {
|
||||
// adjust curve
|
||||
// values < 1.f : less sensitive around neutral
|
||||
// values > 1.f : more sensitive around neutral
|
||||
float curve = analog.getSensitivity();
|
||||
if (curve <= 0.f) {
|
||||
curve = 0.01f;
|
||||
}
|
||||
curve = 1.f / curve;
|
||||
|
||||
// delay
|
||||
if (0 < analog.getDelayBufferDepth()) {
|
||||
auto& queue = analog.getDelayBuffer();
|
||||
if (analog.getType() == AnalogType::LinearCentered) {
|
||||
// convert 0.0..1.0 to -1.0..+1.0
|
||||
float signed_raw = (value - 0.5f) * 2.0f;
|
||||
// apply curve
|
||||
float sign = signed_raw < 0.0f ? -1.0f : 1.0f;
|
||||
float magnitude = fabsf(signed_raw);
|
||||
float curved = sign * powf(magnitude, curve);
|
||||
// convert back to 0.0..1.0
|
||||
value = curved * 0.5f + 0.5f;
|
||||
} else {
|
||||
value = powf(value, curve);
|
||||
}
|
||||
|
||||
// ensure the queue isn't too long; drop old values
|
||||
while (analog.getDelayBufferDepth() <= (int)queue.size()) {
|
||||
queue.pop();
|
||||
value = std::clamp(value, 0.f, 1.f);
|
||||
}
|
||||
|
||||
// always push new value
|
||||
queue.push(value);
|
||||
// multiplier / divisor
|
||||
if (analog.getMultiplier() < -1) {
|
||||
if (analog.getType() == AnalogType::LinearCentered) {
|
||||
value = (value - 0.5f) / (-analog.getMultiplier()) + 0.5f;
|
||||
} else {
|
||||
value /= -analog.getMultiplier();
|
||||
}
|
||||
|
||||
// get a new value to return
|
||||
if ((int)queue.size() < analog.getDelayBufferDepth()) {
|
||||
// not enough in the queue, stall for now, shouldn't happen often
|
||||
value = analog.getLastState();
|
||||
} else {
|
||||
value = queue.front();
|
||||
queue.pop();
|
||||
value = std::clamp(value, 0.f, 1.f);
|
||||
|
||||
} else if (analog.getMultiplier() > 1) {
|
||||
if (analog.getType() == AnalogType::LinearCentered) {
|
||||
value = (value - 0.5f) * analog.getMultiplier() + 0.5f;
|
||||
} else {
|
||||
value *= analog.getMultiplier();
|
||||
}
|
||||
|
||||
value = std::clamp(value, 0.f, 1.f);
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
case rawinput::MIDI: {
|
||||
@@ -829,6 +843,42 @@ std::vector<Analog> GameAPI::Analogs::sortAnalogs(
|
||||
return sorted;
|
||||
}
|
||||
|
||||
static std::vector<Analog> sortAnalogsWithTypeInternal(
|
||||
std::vector<Analog> &analogs,
|
||||
const std::initializer_list<GameAPI::Analogs::AnalogWithType> list) {
|
||||
|
||||
std::vector<Analog> sorted;
|
||||
|
||||
bool analog_found;
|
||||
for (auto &a : list) {
|
||||
analog_found = false;
|
||||
|
||||
for (auto &analog : analogs) {
|
||||
if (a.name == analog.getName()) {
|
||||
analog_found = true;
|
||||
analog.setType(a.type);
|
||||
sorted.push_back(analog);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!analog_found) {
|
||||
sorted.emplace_back(a.name, a.type);
|
||||
}
|
||||
}
|
||||
|
||||
return sorted;
|
||||
}
|
||||
|
||||
void GameAPI::Analogs::sortAnalogsWithType(
|
||||
std::vector<Analog> *analogs,
|
||||
const std::initializer_list<AnalogWithType> list) {
|
||||
|
||||
if (analogs) {
|
||||
*analogs = sortAnalogsWithTypeInternal(*analogs, list);
|
||||
}
|
||||
}
|
||||
|
||||
float GameAPI::Analogs::getState(rawinput::RawInputManager *manager, Analog &analog) {
|
||||
|
||||
// check override
|
||||
|
||||
@@ -85,14 +85,15 @@ namespace GameAPI {
|
||||
const std::vector<Analog> &analogs,
|
||||
const std::vector<std::string> &analog_names);
|
||||
|
||||
template<typename T>
|
||||
void sortAnalogs(std::vector<Analog> *analogs, T t) {
|
||||
const std::vector<std::string> analog_names { t };
|
||||
struct AnalogWithType {
|
||||
const std::string name;
|
||||
const AnalogType type;
|
||||
|
||||
if (analogs) {
|
||||
*analogs = GameAPI::Analogs::sortAnalogs(*analogs, analog_names);
|
||||
}
|
||||
}
|
||||
AnalogWithType(std::string name, AnalogType type) :
|
||||
name(std::move(name)), type(type) {}
|
||||
};
|
||||
|
||||
void sortAnalogsWithType(std::vector<Analog> *analogs, const std::initializer_list<AnalogWithType> list);
|
||||
|
||||
template<typename T, typename... Rest>
|
||||
void sortAnalogs(std::vector<Analog> *analogs, T t, Rest... rest) {
|
||||
|
||||
@@ -306,7 +306,10 @@ std::string Button::getDisplayString(rawinput::RawInputManager* manager) {
|
||||
std::string vKeyString = fmt::format("{:#x}", vKey);
|
||||
|
||||
// device must be existing
|
||||
if (this->device_identifier.empty() && vKey == INVALID_VKEY) {
|
||||
if (this->isNaive() && vKey == INVALID_VKEY) {
|
||||
if (this->getInvert()) {
|
||||
return "(always on)";
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
|
||||
@@ -56,6 +56,7 @@ private:
|
||||
ButtonAnalogType analog_type = BAT_NONE;
|
||||
double debounce_up = 0.0;
|
||||
double debounce_down = 0.0;
|
||||
int bat_threshold = 0; // for positive/negative only, value in percentage, zero is default
|
||||
bool invert = false;
|
||||
bool is_temporary = false;
|
||||
|
||||
@@ -109,6 +110,7 @@ public:
|
||||
alternatives.clear();
|
||||
device_identifier = "";
|
||||
analog_type = BAT_NONE;
|
||||
bat_threshold = 0;
|
||||
}
|
||||
|
||||
std::string getDisplayString(rawinput::RawInputManager* manager);
|
||||
@@ -169,6 +171,14 @@ public:
|
||||
this->debounce_down = debounce_time_down;
|
||||
}
|
||||
|
||||
inline void setBatThreshold(int bat_threshold) {
|
||||
this->bat_threshold = bat_threshold;
|
||||
}
|
||||
|
||||
inline int getBatThreshold() const {
|
||||
return this->bat_threshold;
|
||||
}
|
||||
|
||||
inline bool getInvert() const {
|
||||
return this->invert;
|
||||
}
|
||||
|
||||
+16
-26
@@ -28,7 +28,7 @@ Config::Config() {
|
||||
this->status = false;
|
||||
if (CONFIG_PATH_OVERRIDE.length() > 0) {
|
||||
this->configLocation = CONFIG_PATH_OVERRIDE;
|
||||
log_info("cfg", "using custom config file: {}", this->configLocation.string());
|
||||
log_info("cfg", "using custom config file: {}", this->configLocation);
|
||||
} else {
|
||||
this->configLocation = std::filesystem::path(_wgetenv(L"APPDATA")) / L"spicetools.xml";
|
||||
// avoids logging the expanded appdata path as it contains user name
|
||||
@@ -57,7 +57,7 @@ Config::Config() {
|
||||
this->firstFillConfigFile();
|
||||
break;
|
||||
case tinyxml2::XMLError::XML_ERROR_FILE_COULD_NOT_BE_OPENED:
|
||||
log_fatal("cfg", "could not open config file: {}", this->configLocation.string());
|
||||
log_fatal("cfg", "could not open config file: {}", this->configLocation);
|
||||
break;
|
||||
case tinyxml2::XMLError::XML_ERROR_FILE_NOT_FOUND:
|
||||
this->createConfigFile();
|
||||
@@ -72,7 +72,7 @@ Config::Config() {
|
||||
case tinyxml2::XMLError::XML_ERROR_PARSING_UNKNOWN:
|
||||
case tinyxml2::XMLError::XML_ERROR_MISMATCHED_ELEMENT:
|
||||
case tinyxml2::XMLError::XML_ERROR_PARSING:
|
||||
log_warning("cfg", "Couldn't read config file: {}", this->configLocation.string());
|
||||
log_warning("cfg", "Couldn't read config file: {}", this->configLocation);
|
||||
this->createConfigFile();
|
||||
this->firstFillConfigFile();
|
||||
break;
|
||||
@@ -162,6 +162,7 @@ bool Config::addGame(Game &game) {
|
||||
auto analogType = (int) BAT_NONE;
|
||||
double debounce_up = 0.0;
|
||||
double debounce_down = 0.0;
|
||||
int bat_threshold = 0;
|
||||
int velocity_threshold = 0;
|
||||
bool invert = false;
|
||||
tinyxml2::XMLError attrError = gameButtonNode->QueryIntAttribute("vkey", &vKey);
|
||||
@@ -169,6 +170,7 @@ bool Config::addGame(Game &game) {
|
||||
gameButtonNode->QueryIntAttribute("analogtype", &analogType);
|
||||
gameButtonNode->QueryDoubleAttribute("debounce_up", &debounce_up);
|
||||
gameButtonNode->QueryDoubleAttribute("debounce_down", &debounce_down);
|
||||
gameButtonNode->QueryIntAttribute("bat_threshold", &bat_threshold);
|
||||
gameButtonNode->QueryIntAttribute("velocity_threshold", &velocity_threshold);
|
||||
gameButtonNode->QueryBoolAttribute("invert", &invert);
|
||||
if (attrError != tinyxml2::XMLError::XML_SUCCESS) {
|
||||
@@ -180,6 +182,7 @@ bool Config::addGame(Game &game) {
|
||||
gameButtonNode->SetAttribute("devid", button->getDeviceIdentifier().c_str());
|
||||
gameButtonNode->SetAttribute("debounce_up", debounce_up);
|
||||
gameButtonNode->SetAttribute("debounce_down", debounce_down);
|
||||
gameButtonNode->SetAttribute("bat_threshold", bat_threshold);
|
||||
gameButtonNode->SetAttribute("velocity_threshold", velocity_threshold);
|
||||
gameButtonNode->SetAttribute("invert", invert);
|
||||
gameButtonsNode->InsertEndChild(gameButtonNode);
|
||||
@@ -188,6 +191,7 @@ bool Config::addGame(Game &game) {
|
||||
button->setAnalogType((ButtonAnalogType) analogType);
|
||||
button->setDebounceUp(debounce_up);
|
||||
button->setDebounceDown(debounce_down);
|
||||
button->setBatThreshold(bat_threshold);
|
||||
button->setVelocityThreshold(velocity_threshold);
|
||||
button->setInvert(invert);
|
||||
if (devid) {
|
||||
@@ -207,6 +211,7 @@ bool Config::addGame(Game &game) {
|
||||
gameButtonNode->SetAttribute("analogtype", (int) it.getAnalogType());
|
||||
gameButtonNode->SetAttribute("debounce_up", it.getDebounceUp());
|
||||
gameButtonNode->SetAttribute("debounce_down", it.getDebounceDown());
|
||||
gameButtonNode->SetAttribute("bat_threshold", it.getBatThreshold());
|
||||
gameButtonNode->SetAttribute("velocity_threshold", it.getVelocityThreshold());
|
||||
gameButtonNode->SetAttribute("invert", it.getInvert());
|
||||
gameButtonNode->SetAttribute("devid", it.getDeviceIdentifier().c_str());
|
||||
@@ -247,8 +252,6 @@ bool Config::addGame(Game &game) {
|
||||
bool invert = false;
|
||||
bool smoothing = false;
|
||||
int multiplier = 1;
|
||||
bool relative_mode = false;
|
||||
int delay_buffer_depth = 0;
|
||||
tinyxml2::XMLError err1 = gameAnalogNode->QueryIntAttribute("index", &index);
|
||||
gameAnalogNode->QueryFloatAttribute("sensivity", &sensitivity);
|
||||
gameAnalogNode->QueryFloatAttribute("deadzone", &deadzone);
|
||||
@@ -256,8 +259,6 @@ bool Config::addGame(Game &game) {
|
||||
gameAnalogNode->QueryBoolAttribute("invert", &invert);
|
||||
gameAnalogNode->QueryBoolAttribute("smoothing", &smoothing);
|
||||
gameAnalogNode->QueryIntAttribute("multiplier", &multiplier);
|
||||
gameAnalogNode->QueryBoolAttribute("relative", &relative_mode);
|
||||
gameAnalogNode->QueryIntAttribute("delay", &delay_buffer_depth);
|
||||
const char *devid = gameAnalogNode->Attribute("devid");
|
||||
|
||||
if (err1 != tinyxml2::XMLError::XML_SUCCESS || !devid) {
|
||||
@@ -272,8 +273,6 @@ bool Config::addGame(Game &game) {
|
||||
gameAnalogNode->SetAttribute("invert", it.getInvert());
|
||||
gameAnalogNode->SetAttribute("smoothing", it.getSmoothing());
|
||||
gameAnalogNode->SetAttribute("multiplier", it.getMultiplier());
|
||||
gameAnalogNode->SetAttribute("relative", it.isRelativeMode());
|
||||
gameAnalogNode->SetAttribute("delay", it.getDelayBufferDepth());
|
||||
gameAnalogsNode->InsertEndChild(gameAnalogNode);
|
||||
} else {
|
||||
it.setIndex(static_cast<unsigned short int>(index));
|
||||
@@ -284,8 +283,6 @@ bool Config::addGame(Game &game) {
|
||||
it.setInvert(invert);
|
||||
it.setSmoothing(smoothing);
|
||||
it.setMultiplier(multiplier);
|
||||
it.setRelativeMode(relative_mode);
|
||||
it.setDelayBufferDepth(delay_buffer_depth);
|
||||
}
|
||||
} else {
|
||||
gameAnalogNode = this->configFile.NewElement("analog");
|
||||
@@ -297,8 +294,6 @@ bool Config::addGame(Game &game) {
|
||||
gameAnalogNode->SetAttribute("invert", it.getInvert());
|
||||
gameAnalogNode->SetAttribute("smoothing", it.getSmoothing());
|
||||
gameAnalogNode->SetAttribute("multiplier", it.getMultiplier());
|
||||
gameAnalogNode->SetAttribute("relative", it.isRelativeMode());
|
||||
gameAnalogNode->SetAttribute("delay", it.getDelayBufferDepth());
|
||||
gameAnalogNode->SetAttribute("devid", it.getDeviceIdentifier().c_str());
|
||||
gameAnalogsNode->InsertEndChild(gameAnalogNode);
|
||||
}
|
||||
@@ -432,6 +427,7 @@ bool Config::addGame(Game &game) {
|
||||
gameButtonNode->SetAttribute("analogtype", it.getAnalogType());
|
||||
gameButtonNode->SetAttribute("debounce_up", it.getDebounceUp());
|
||||
gameButtonNode->SetAttribute("debounce_down", it.getDebounceDown());
|
||||
gameButtonNode->SetAttribute("bat_threshold", it.getBatThreshold());
|
||||
gameButtonNode->SetAttribute("velocity_threshold", it.getVelocityThreshold());
|
||||
gameButtonNode->SetAttribute("invert", it.getInvert());
|
||||
gameButtonNode->SetAttribute("devid", it.getDeviceIdentifier().c_str());
|
||||
@@ -451,8 +447,6 @@ bool Config::addGame(Game &game) {
|
||||
gameAnalogNode->SetAttribute("invert", it.getInvert());
|
||||
gameAnalogNode->SetAttribute("smoothing", it.getSmoothing());
|
||||
gameAnalogNode->SetAttribute("multiplier", it.getMultiplier());
|
||||
gameAnalogNode->SetAttribute("relative", it.isRelativeMode());
|
||||
gameAnalogNode->SetAttribute("delay", it.getDelayBufferDepth());
|
||||
gameAnalogsNode->InsertEndChild(gameAnalogNode);
|
||||
}
|
||||
|
||||
@@ -535,6 +529,7 @@ bool Config::updateBinding(const Game &game, const Button &button, int alternati
|
||||
gameButtonNode->SetAttribute("analogtype", (int) button.getAnalogType());
|
||||
gameButtonNode->SetAttribute("debounce_up", button.getDebounceUp());
|
||||
gameButtonNode->SetAttribute("debounce_down", button.getDebounceDown());
|
||||
gameButtonNode->SetAttribute("bat_threshold", button.getBatThreshold());
|
||||
gameButtonNode->SetAttribute("velocity_threshold", button.getVelocityThreshold());
|
||||
gameButtonNode->SetAttribute("invert", button.getInvert());
|
||||
gameButtonNode->SetAttribute("devid", button.getDeviceIdentifier().c_str());
|
||||
@@ -551,6 +546,7 @@ bool Config::updateBinding(const Game &game, const Button &button, int alternati
|
||||
gameButtonNode->SetAttribute("analogtype", 0);
|
||||
gameButtonNode->SetAttribute("debounce_up", 0.0);
|
||||
gameButtonNode->SetAttribute("debounce_down", 0.0);
|
||||
gameButtonNode->SetAttribute("bat_threshold", 0);
|
||||
gameButtonNode->SetAttribute("velocity_threshold", 0);
|
||||
gameButtonNode->SetAttribute("invert", false);
|
||||
gameButtonNode->SetAttribute("devid", "");
|
||||
@@ -683,8 +679,6 @@ bool Config::updateBinding(const Game &game, const Analog &analog) {
|
||||
gameAnalogNode->SetAttribute("invert", analog.getInvert());
|
||||
gameAnalogNode->SetAttribute("smoothing", analog.getSmoothing());
|
||||
gameAnalogNode->SetAttribute("multiplier", analog.getMultiplier());
|
||||
gameAnalogNode->SetAttribute("relative", analog.isRelativeMode());
|
||||
gameAnalogNode->SetAttribute("delay", analog.getDelayBufferDepth());
|
||||
gameAnalogNode->SetAttribute("devid", analog.getDeviceIdentifier().c_str());
|
||||
} else {
|
||||
gameAnalogNode = this->configFile.NewElement("analog");
|
||||
@@ -695,8 +689,6 @@ bool Config::updateBinding(const Game &game, const Analog &analog) {
|
||||
gameAnalogNode->SetAttribute("invert", analog.getInvert());
|
||||
gameAnalogNode->SetAttribute("smoothing", analog.getSmoothing());
|
||||
gameAnalogNode->SetAttribute("multiplier", analog.getMultiplier());
|
||||
gameAnalogNode->SetAttribute("relative", analog.isRelativeMode());
|
||||
gameAnalogNode->SetAttribute("delay", analog.getDelayBufferDepth());
|
||||
gameAnalogNode->SetAttribute("devid", analog.getDeviceIdentifier().c_str());
|
||||
gameAnalogsNode->InsertEndChild(gameAnalogNode);
|
||||
}
|
||||
@@ -946,12 +938,14 @@ std::vector<Button> Config::getButtons(const std::string &gameName) {
|
||||
auto analogType = (int) BAT_NONE;
|
||||
double debounce_up = 0.0;
|
||||
double debounce_down = 0.0;
|
||||
int bat_threshold = 0;
|
||||
int velocity_threshold = 0;
|
||||
bool invert = false;
|
||||
gameButtonNode->QueryIntAttribute("vkey", &vKey);
|
||||
gameButtonNode->QueryIntAttribute("analogtype", &analogType);
|
||||
gameButtonNode->QueryDoubleAttribute("debounce_up", &debounce_up);
|
||||
gameButtonNode->QueryDoubleAttribute("debounce_down", &debounce_down);
|
||||
gameButtonNode->QueryIntAttribute("bat_threshold", &bat_threshold);
|
||||
gameButtonNode->QueryIntAttribute("velocity_threshold", &velocity_threshold);
|
||||
gameButtonNode->QueryBoolAttribute("invert", &invert);
|
||||
const char *devid = gameButtonNode->Attribute("devid");
|
||||
@@ -966,6 +960,7 @@ std::vector<Button> Config::getButtons(const std::string &gameName) {
|
||||
alt.setAnalogType((ButtonAnalogType) analogType);
|
||||
alt.setDebounceUp(debounce_up);
|
||||
alt.setDebounceDown(debounce_down);
|
||||
alt.setBatThreshold(bat_threshold);
|
||||
alt.setVelocityThreshold(velocity_threshold);
|
||||
alt.setInvert(invert);
|
||||
if (devid) {
|
||||
@@ -984,6 +979,7 @@ std::vector<Button> Config::getButtons(const std::string &gameName) {
|
||||
button.setAnalogType((ButtonAnalogType) analogType);
|
||||
button.setDebounceUp(debounce_up);
|
||||
button.setDebounceDown(debounce_down);
|
||||
button.setBatThreshold(bat_threshold);
|
||||
button.setVelocityThreshold(velocity_threshold);
|
||||
button.setInvert(invert);
|
||||
if (devid) {
|
||||
@@ -1129,8 +1125,6 @@ std::vector<Analog> Config::getAnalogs(const std::string &gameName) {
|
||||
bool invert = false;
|
||||
bool smoothing = false;
|
||||
int multiplier = 1;
|
||||
bool relative_mode = false;
|
||||
int delay_buffer_depth = 0;
|
||||
gameAnalogNode->QueryIntAttribute("index", &index);
|
||||
gameAnalogNode->QueryFloatAttribute("sensivity", &sensitivity);
|
||||
gameAnalogNode->QueryFloatAttribute("deadzone", &deadzone);
|
||||
@@ -1138,8 +1132,6 @@ std::vector<Analog> Config::getAnalogs(const std::string &gameName) {
|
||||
gameAnalogNode->QueryBoolAttribute("invert", &invert);
|
||||
gameAnalogNode->QueryBoolAttribute("smoothing", &smoothing);
|
||||
gameAnalogNode->QueryIntAttribute("multiplier", &multiplier);
|
||||
gameAnalogNode->QueryBoolAttribute("relative", &relative_mode);
|
||||
gameAnalogNode->QueryIntAttribute("delay", &delay_buffer_depth);
|
||||
const char *devid = gameAnalogNode->Attribute("devid");
|
||||
|
||||
// create analog and add to list
|
||||
@@ -1151,8 +1143,6 @@ std::vector<Analog> Config::getAnalogs(const std::string &gameName) {
|
||||
analog.setInvert(invert);
|
||||
analog.setSmoothing(smoothing);
|
||||
analog.setMultiplier(multiplier);
|
||||
analog.setRelativeMode(relative_mode);
|
||||
analog.setDelayBufferDepth(delay_buffer_depth);
|
||||
if (devid) {
|
||||
analog.setDeviceIdentifier(devid);
|
||||
}
|
||||
@@ -1315,7 +1305,7 @@ void Config::saveConfigFile() {
|
||||
// create a .tmp file and write to it...
|
||||
const auto xml_result = this->configFile.SaveFile(this->configLocationTemp.c_str(), false);
|
||||
if (xml_result != tinyxml2::XMLError::XML_SUCCESS) {
|
||||
log_info("cfg", "failed to write file: {}", this->configLocationTemp.string());
|
||||
log_info("cfg", "failed to write file: {}", this->configLocationTemp);
|
||||
return;
|
||||
}
|
||||
// copy the .tmp file to the main file...
|
||||
|
||||
@@ -24,6 +24,7 @@ namespace overlay::windows {
|
||||
el->SetAttribute("invert", entry.invert);
|
||||
el->SetAttribute("debounce_up", entry.debounce_up);
|
||||
el->SetAttribute("debounce_down", entry.debounce_down);
|
||||
el->SetAttribute("bat_threshold", entry.bat_threshold);
|
||||
el->SetAttribute("velocity_threshold", entry.velocity_threshold);
|
||||
parent->InsertEndChild(el);
|
||||
}
|
||||
@@ -49,6 +50,10 @@ namespace overlay::windows {
|
||||
el->QueryIntAttribute("velocity_threshold", &vel);
|
||||
entry.velocity_threshold = (unsigned short)vel;
|
||||
|
||||
int bat = 0;
|
||||
el->QueryIntAttribute("bat_threshold", &bat);
|
||||
entry.bat_threshold = bat;
|
||||
|
||||
return entry;
|
||||
}
|
||||
|
||||
@@ -64,8 +69,6 @@ namespace overlay::windows {
|
||||
el->SetAttribute("invert", analog.invert);
|
||||
el->SetAttribute("smoothing", analog.smoothing);
|
||||
el->SetAttribute("multiplier", analog.multiplier);
|
||||
el->SetAttribute("relative", analog.relative_mode);
|
||||
el->SetAttribute("delay", analog.delay_buffer_depth);
|
||||
parent->InsertEndChild(el);
|
||||
}
|
||||
|
||||
@@ -87,8 +90,6 @@ namespace overlay::windows {
|
||||
el->QueryBoolAttribute("invert", &a.invert);
|
||||
el->QueryBoolAttribute("smoothing", &a.smoothing);
|
||||
el->QueryIntAttribute("multiplier", &a.multiplier);
|
||||
el->QueryBoolAttribute("relative", &a.relative_mode);
|
||||
el->QueryIntAttribute("delay", &a.delay_buffer_depth);
|
||||
|
||||
return a;
|
||||
}
|
||||
|
||||
@@ -21,7 +21,7 @@ namespace cfg {
|
||||
if (SCREEN_RESIZE_CFG_PATH_OVERRIDE.has_value()) {
|
||||
this->config_path = SCREEN_RESIZE_CFG_PATH_OVERRIDE.value();
|
||||
if (fileutils::file_exists(this->config_path)) {
|
||||
log_info("ScreenResize", "loading config from: {}", this->config_path.string());
|
||||
log_info("ScreenResize", "loading config from: {}", this->config_path);
|
||||
file_exists = true;
|
||||
}
|
||||
} else {
|
||||
|
||||
@@ -38,11 +38,12 @@ std::vector<Analog> &games::bc::get_analogs() {
|
||||
if (analogs.empty()) {
|
||||
analogs = GameAPI::Analogs::getAnalogs("Busou Shinki: Armored Princess Battle Conductor");
|
||||
|
||||
GameAPI::Analogs::sortAnalogs(
|
||||
&analogs,
|
||||
"Stick X",
|
||||
"Stick Y"
|
||||
);
|
||||
using GameAPI::Analogs::AnalogType;
|
||||
|
||||
GameAPI::Analogs::sortAnalogsWithType(&analogs, {
|
||||
{ "Stick X", AnalogType::LinearCentered },
|
||||
{ "Stick Y", AnalogType::LinearCentered }
|
||||
});
|
||||
}
|
||||
|
||||
return analogs;
|
||||
|
||||
@@ -45,13 +45,14 @@ std::vector<Analog> &games::ccj::get_analogs() {
|
||||
if (analogs.empty()) {
|
||||
analogs = GameAPI::Analogs::getAnalogs("Chase Chase Jokers");
|
||||
|
||||
GameAPI::Analogs::sortAnalogs(
|
||||
&analogs,
|
||||
"Joystick X",
|
||||
"Joystick Y",
|
||||
"Trackball DX",
|
||||
"Trackball DY"
|
||||
);
|
||||
using GameAPI::Analogs::AnalogType;
|
||||
|
||||
GameAPI::Analogs::sortAnalogsWithType(&analogs, {
|
||||
{ "Joystick X", AnalogType::LinearCentered },
|
||||
{ "Joystick Y", AnalogType::LinearCentered },
|
||||
{ "Trackball DX", AnalogType::LinearCentered },
|
||||
{ "Trackball DY", AnalogType::LinearCentered }
|
||||
});
|
||||
}
|
||||
|
||||
return analogs;
|
||||
|
||||
@@ -23,6 +23,7 @@
|
||||
#include "p3io/usbmem.h"
|
||||
|
||||
#include "p4io/p4io.h"
|
||||
#include <cstring>
|
||||
|
||||
using namespace acioemu;
|
||||
|
||||
@@ -53,9 +54,9 @@ namespace games::ddr {
|
||||
return SendMessage_real(hWnd, Msg, wParam, lParam);
|
||||
}
|
||||
|
||||
bool contains_only_ascii(const std::string& str) {
|
||||
bool contains_only_ascii(const std::wstring& str) {
|
||||
for (auto c: str) {
|
||||
if (static_cast<unsigned char>(c) > 127) {
|
||||
if (c > 127) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -80,24 +81,24 @@ namespace games::ddr {
|
||||
}
|
||||
|
||||
if (fileutils::dir_exists(dir)) {
|
||||
log_info("ddr", "looking for codecs in this directory: {}", dir.string());
|
||||
log_info("ddr", "looking for codecs in this directory: {}", dir);
|
||||
} else {
|
||||
log_info("ddr", "codecs directory not found: {}", dir.string());
|
||||
log_info("ddr", "codecs directory not found: {}", dir);
|
||||
return;
|
||||
}
|
||||
|
||||
for (const auto &file : std::filesystem::directory_iterator(dir)) {
|
||||
const auto &filename = file.path().filename();
|
||||
const auto extension = strtolower(filename.extension().string());
|
||||
const auto extension = filename.extension().wstring();
|
||||
|
||||
if (extension != ".dll") {
|
||||
if (wcsicmp(extension.c_str(), L".dll") != 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
log_info("ddr", "found DLL: {}, size: {} bytes", filename.string(), file.file_size());
|
||||
if (filename == "k-clvsd.dll" || filename.string().find("xactengine") == 0) {
|
||||
log_info("ddr", "found DLL: {}, size: {} bytes", filename, file.file_size());
|
||||
if (filename == "k-clvsd.dll" || filename.wstring().find(L"xactengine") == 0) {
|
||||
const std::wstring wcmd = L"regsvr32.exe /s \"" + file.path().wstring() + L"\"";
|
||||
const std::string cmd = "regsvr32.exe /s \"" + file.path().string() + "\"";
|
||||
const std::string cmd = fmt::format("regsvr32.exe /s \"{}\"", file.path());
|
||||
|
||||
int result = 0;
|
||||
std::thread t([wcmd, &result]() {
|
||||
@@ -111,12 +112,12 @@ namespace games::ddr {
|
||||
log_warning("ddr", "`{}` failed, returned {}", cmd, result);
|
||||
deferredlogs::defer_error_messages({
|
||||
"DDR codec registration failure",
|
||||
fmt::format(" {} failed to register with error {}", filename.string(), result)});
|
||||
fmt::format(" {} failed to register with error {}", filename, result)});
|
||||
}
|
||||
|
||||
static std::once_flag printed;
|
||||
std::call_once(printed, [file]() {
|
||||
if (!contains_only_ascii(file.path().string())) {
|
||||
if (!contains_only_ascii(file.path().wstring())) {
|
||||
log_warning(
|
||||
"ddr",
|
||||
"BAD PATH ERROR\n\n"
|
||||
@@ -290,4 +291,50 @@ namespace games::ddr {
|
||||
// dispose device hook
|
||||
devicehook_dispose();
|
||||
}
|
||||
|
||||
static void get_analog_xy_axis(Analog &analog, bool &less, bool &more) {
|
||||
if (!analog.isSet()) {
|
||||
return;
|
||||
}
|
||||
const auto value = GameAPI::Analogs::getState(RI_MGR, analog);
|
||||
|
||||
// stepmania linux source:
|
||||
// https://github.com/stepmania/stepmania/blob/d55acb1ba26f1c5b5e3048d6d6c0bd116625216f/src/arch/InputHandler/InputHandler_Linux_Event.cpp#L410
|
||||
|
||||
// for example, value cap with range [0, 255]:
|
||||
// 127 = 0.498, neutral
|
||||
// 128 = 0.502, both
|
||||
// apparently some adapters report values like above; this obviously makes a lot of
|
||||
// assumptions about bit width of the HID value cap and how the adapter reports
|
||||
// neutral/both values, but this is good enough for parity with stepmania and its many forks
|
||||
if (0.5001f < value && value < 0.75f) {
|
||||
less |= true;
|
||||
more |= true;
|
||||
|
||||
} else if (value <= 0.25f) {
|
||||
less |= true;
|
||||
} else if (value >= 0.75f) {
|
||||
more |= true;
|
||||
}
|
||||
}
|
||||
|
||||
void get_analog_x_axis(int player, bool &left, bool &right) {
|
||||
if (player != 1 && player != 2) {
|
||||
log_fatal("ddr", "invalid player number: {}, expected 1 or 2", player);
|
||||
return;
|
||||
}
|
||||
|
||||
auto &analog = get_analogs().at(player == 1 ? Analogs::P1_LEFT_RIGHT : Analogs::P2_LEFT_RIGHT);
|
||||
get_analog_xy_axis(analog, left, right);
|
||||
}
|
||||
|
||||
void get_analog_y_axis(int player, bool &up, bool &down) {
|
||||
if (player != 1 && player != 2) {
|
||||
log_fatal("ddr", "invalid player number: {}, expected 1 or 2", player);
|
||||
return;
|
||||
}
|
||||
|
||||
auto &analog = get_analogs().at(player == 1 ? Analogs::P1_UP_DOWN : Analogs::P2_UP_DOWN);
|
||||
get_analog_xy_axis(analog, up, down);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,4 +23,7 @@ namespace games::ddr {
|
||||
private:
|
||||
void register_codecs();
|
||||
};
|
||||
|
||||
void get_analog_x_axis(int player, bool &left, bool &right);
|
||||
void get_analog_y_axis(int player, bool &up, bool &down);
|
||||
}
|
||||
|
||||
@@ -35,6 +35,50 @@ std::vector<Button> &games::ddr::get_buttons() {
|
||||
return analogs;
|
||||
}
|
||||
|
||||
|
||||
std::string games::ddr::get_buttons_help() {
|
||||
// keep to max 100 characters wide
|
||||
return
|
||||
"For DDR pad arrows, double check that simultaneous Left+Right and Up+Down can be detected.\n"
|
||||
"You should boot the game, enter test menu, and use Foot Panel Check.\n\n"
|
||||
"When mapping arrows, try the following in order:\n\n"
|
||||
" 1. If your controller uses face buttons (A/B/X/Y), bind them here.\n"
|
||||
" 2. If your controller supports XInput, use that.\n"
|
||||
" 3. If your controller uses analog axis for arrows, try Analogs tab.\n"
|
||||
" 4. Otherwise, you will need to use remapping software or get a better adapter."
|
||||
;
|
||||
}
|
||||
|
||||
std::vector<Analog> &games::ddr::get_analogs() {
|
||||
static std::vector<Analog> analogs;
|
||||
|
||||
if (analogs.empty()) {
|
||||
analogs = GameAPI::Analogs::getAnalogs("Dance Dance Revolution");
|
||||
|
||||
using namespace GameAPI::Analogs;
|
||||
|
||||
GameAPI::Analogs::sortAnalogsWithType(&analogs, {
|
||||
{ "P1 Left-Right (Axis Fix)", AnalogType::LinearCentered },
|
||||
{ "P1 Up-Down (Axis Fix)", AnalogType::LinearCentered },
|
||||
{ "P2 Left-Right (Axis Fix)", AnalogType::LinearCentered },
|
||||
{ "P2 Up-Down (Axis Fix)", AnalogType::LinearCentered }
|
||||
});
|
||||
}
|
||||
return analogs;
|
||||
}
|
||||
|
||||
std::string games::ddr::get_analogs_help() {
|
||||
// keep to max 100 characters wide
|
||||
return
|
||||
"Only use this if your DDR pad outputs analog axis for arrows.\n\n"
|
||||
"If the pad uses face buttons (A/B/X/Y), use Buttons tab instead.\n\n"
|
||||
"Spice will treat values <=25% as Left, >=75% as Right, ~=50% as neutral,\n"
|
||||
"and value between 50% and 75% as both arrows.\n\n"
|
||||
"This is the classic Stepmania \"Axis Fix\" which may or may not work with\n"
|
||||
"your dance pad or your adapter."
|
||||
;
|
||||
}
|
||||
|
||||
std::vector<Light> &games::ddr::get_lights() {
|
||||
static std::vector<Light> lights;
|
||||
|
||||
|
||||
@@ -33,6 +33,16 @@ namespace games::ddr {
|
||||
};
|
||||
}
|
||||
|
||||
// all analogs in correct order
|
||||
namespace Analogs {
|
||||
enum {
|
||||
P1_LEFT_RIGHT,
|
||||
P1_UP_DOWN,
|
||||
P2_LEFT_RIGHT,
|
||||
P2_UP_DOWN
|
||||
};
|
||||
}
|
||||
|
||||
// all lights in correct order
|
||||
namespace Lights {
|
||||
enum {
|
||||
@@ -177,5 +187,8 @@ namespace games::ddr {
|
||||
|
||||
// getters
|
||||
std::vector<Button> &get_buttons();
|
||||
std::string get_buttons_help();
|
||||
std::string get_analogs_help();
|
||||
std::vector<Analog> &get_analogs();
|
||||
std::vector<Light> &get_lights();
|
||||
}
|
||||
|
||||
@@ -498,32 +498,80 @@ int games::ddr::DDRP3IOHandle::device_io(
|
||||
|
||||
// shift table
|
||||
static size_t shift_table[] = {
|
||||
30, 28, 29, 8, 9, 10, 11, 12, 24, 25, 14, 15, 16, 17, 18, 19, 20, 26, 27, 22, 23
|
||||
30, 28, 29, // service, test, coin
|
||||
8, // p1 start
|
||||
9, 10, 11, 12, // p1 panel
|
||||
24, 25, 14, 15, // p1 menu
|
||||
16, // p2 start
|
||||
17, 18, 19, 20, // p2 panel
|
||||
26, 27, 22, 23 // p2 menu
|
||||
};
|
||||
static size_t button_table[] = {
|
||||
Buttons::SERVICE,
|
||||
Buttons::TEST,
|
||||
Buttons::COIN_MECH,
|
||||
|
||||
Buttons::P1_START,
|
||||
|
||||
Buttons::P1_PANEL_UP,
|
||||
Buttons::P1_PANEL_DOWN,
|
||||
Buttons::P1_PANEL_LEFT,
|
||||
Buttons::P1_PANEL_RIGHT,
|
||||
|
||||
Buttons::P1_MENU_UP,
|
||||
Buttons::P1_MENU_DOWN,
|
||||
Buttons::P1_MENU_LEFT,
|
||||
Buttons::P1_MENU_RIGHT,
|
||||
|
||||
Buttons::P2_START,
|
||||
|
||||
Buttons::P2_PANEL_UP,
|
||||
Buttons::P2_PANEL_DOWN,
|
||||
Buttons::P2_PANEL_LEFT,
|
||||
Buttons::P2_PANEL_RIGHT,
|
||||
|
||||
Buttons::P2_MENU_UP,
|
||||
Buttons::P2_MENU_DOWN,
|
||||
Buttons::P2_MENU_LEFT,
|
||||
Buttons::P2_MENU_RIGHT,
|
||||
};
|
||||
|
||||
// get analogs
|
||||
struct {
|
||||
bool up;
|
||||
bool down;
|
||||
bool left;
|
||||
bool right;
|
||||
} analog_values[2] = {};
|
||||
games::ddr::get_analog_x_axis(1, analog_values[0].left, analog_values[0].right);
|
||||
games::ddr::get_analog_y_axis(1, analog_values[0].up, analog_values[0].down);
|
||||
games::ddr::get_analog_x_axis(2, analog_values[1].left, analog_values[1].right);
|
||||
games::ddr::get_analog_y_axis(2, analog_values[1].up, analog_values[1].down);
|
||||
if (analog_values[0].up) {
|
||||
controls |= 1 << 9;
|
||||
}
|
||||
if (analog_values[0].down) {
|
||||
controls |= 1 << 10;
|
||||
}
|
||||
if (analog_values[0].left) {
|
||||
controls |= 1 << 11;
|
||||
}
|
||||
if (analog_values[0].right) {
|
||||
controls |= 1 << 12;
|
||||
}
|
||||
if (analog_values[1].up) {
|
||||
controls |= 1 << 17;
|
||||
}
|
||||
if (analog_values[1].down) {
|
||||
controls |= 1 << 18;
|
||||
}
|
||||
if (analog_values[1].left) {
|
||||
controls |= 1 << 19;
|
||||
}
|
||||
if (analog_values[1].right) {
|
||||
controls |= 1 << 20;
|
||||
}
|
||||
|
||||
// update states
|
||||
auto &buttons = get_buttons();
|
||||
size_t count = 0;
|
||||
|
||||
@@ -35,13 +35,14 @@ std::vector<Analog> &games::ftt::get_analogs() {
|
||||
if (analogs.empty()) {
|
||||
analogs = GameAPI::Analogs::getAnalogs("FutureTomTom");
|
||||
|
||||
GameAPI::Analogs::sortAnalogs(
|
||||
&analogs,
|
||||
"Pad 1",
|
||||
"Pad 2",
|
||||
"Pad 3",
|
||||
"Pad 4"
|
||||
);
|
||||
using GameAPI::Analogs::AnalogType;
|
||||
|
||||
GameAPI::Analogs::sortAnalogsWithType(&analogs, {
|
||||
{ "Pad 1", AnalogType::LinearPositive },
|
||||
{ "Pad 2", AnalogType::LinearPositive },
|
||||
{ "Pad 3", AnalogType::LinearPositive },
|
||||
{ "Pad 4", AnalogType::LinearPositive }
|
||||
});
|
||||
}
|
||||
|
||||
return analogs;
|
||||
|
||||
@@ -86,19 +86,21 @@ std::vector<Button> &games::gitadora::get_buttons() {
|
||||
std::vector<Analog> &games::gitadora::get_analogs() {
|
||||
static std::vector<Analog> analogs;
|
||||
|
||||
using namespace GameAPI::Analogs;
|
||||
|
||||
if (analogs.empty()) {
|
||||
analogs = GameAPI::Analogs::getAnalogs("GitaDora");
|
||||
|
||||
GameAPI::Analogs::sortAnalogs(&analogs,
|
||||
"Guitar P1 Wail X",
|
||||
"Guitar P1 Wail Y",
|
||||
"Guitar P1 Wail Z",
|
||||
"Guitar P1 Knob",
|
||||
"Guitar P2 Wail X",
|
||||
"Guitar P2 Wail Y",
|
||||
"Guitar P2 Wail Z",
|
||||
"Guitar P2 Knob"
|
||||
);
|
||||
GameAPI::Analogs::sortAnalogsWithType(&analogs, {
|
||||
{"Guitar P1 Wail X", AnalogType::LinearCentered},
|
||||
{"Guitar P1 Wail Y", AnalogType::LinearCentered},
|
||||
{"Guitar P1 Wail Z", AnalogType::LinearCentered},
|
||||
{"Guitar P1 Knob", AnalogType::Circular},
|
||||
{"Guitar P2 Wail X", AnalogType::LinearCentered},
|
||||
{"Guitar P2 Wail Y", AnalogType::LinearCentered},
|
||||
{"Guitar P2 Wail Z", AnalogType::LinearCentered},
|
||||
{"Guitar P2 Knob", AnalogType::Circular}
|
||||
});
|
||||
}
|
||||
|
||||
return analogs;
|
||||
|
||||
@@ -67,6 +67,8 @@ namespace games::iidx {
|
||||
std::optional<std::string> SOUND_OUTPUT_DEVICE = std::nullopt;
|
||||
std::optional<std::string> SOUND_OUTPUT_DEVICE_IN_EFFECT = std::nullopt;
|
||||
std::optional<std::string> ASIO_DRIVER = std::nullopt;
|
||||
uint32_t TT_DELAY_P1 = 0;
|
||||
uint32_t TT_DELAY_P2 = 0;
|
||||
uint8_t DIGITAL_TT_SENS = 4;
|
||||
std::optional<std::string> SUBSCREEN_OVERLAY_SIZE = std::nullopt;
|
||||
std::optional<std::string> SCREEN_MODE = std::nullopt;
|
||||
@@ -781,7 +783,42 @@ namespace games::iidx {
|
||||
}
|
||||
|
||||
// return higher 8 bit
|
||||
return (uint8_t) (ret_value >> 2);
|
||||
uint8_t result = (uint8_t) (ret_value >> 2);
|
||||
|
||||
// delay
|
||||
if ((player == 0 && TT_DELAY_P1 > 0) ||
|
||||
(player == 1 && TT_DELAY_P2 > 0)) {
|
||||
|
||||
static std::queue<std::pair<double, uint8_t>> delay_queue[2];
|
||||
auto &queue = delay_queue[player];
|
||||
|
||||
const auto max_delta_ms =
|
||||
static_cast<double>((player == 0) ? TT_DELAY_P1 : TT_DELAY_P2);
|
||||
|
||||
// always push a new value
|
||||
const auto now = get_performance_milliseconds();
|
||||
queue.push(std::make_pair(now, result));
|
||||
|
||||
// drain the queue down to reasonable length to prevent unconstrained growth
|
||||
// this would accommodate 1 second at ~1000Hz
|
||||
// (in reality all three iidx I/O emulation runs well under 500Hz)
|
||||
while (queue.size() > 1024) {
|
||||
queue.pop();
|
||||
}
|
||||
|
||||
// pop until we find one that falls just under the time threshold
|
||||
while (!queue.empty()) {
|
||||
const auto delta_t = now - queue.front().first;
|
||||
if (delta_t <= max_delta_ms) {
|
||||
break;
|
||||
}
|
||||
queue.pop();
|
||||
}
|
||||
|
||||
result = queue.front().second;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
unsigned char get_slider(uint8_t slider) {
|
||||
|
||||
@@ -31,6 +31,8 @@ namespace games::iidx {
|
||||
extern bool NATIVE_TOUCH;
|
||||
extern std::optional<std::string> SOUND_OUTPUT_DEVICE;
|
||||
extern std::optional<std::string> ASIO_DRIVER;
|
||||
extern uint32_t TT_DELAY_P1;
|
||||
extern uint32_t TT_DELAY_P2;
|
||||
extern uint8_t DIGITAL_TT_SENS;
|
||||
extern std::optional<std::string> SUBSCREEN_OVERLAY_SIZE;
|
||||
extern std::optional<std::string> SCREEN_MODE;
|
||||
|
||||
@@ -63,16 +63,17 @@ std::vector<Analog> &games::iidx::get_analogs() {
|
||||
if (analogs.empty()) {
|
||||
analogs = GameAPI::Analogs::getAnalogs("Beatmania IIDX");
|
||||
|
||||
GameAPI::Analogs::sortAnalogs(
|
||||
&analogs,
|
||||
"Turntable P1",
|
||||
"Turntable P2",
|
||||
"VEFX",
|
||||
"Low-EQ",
|
||||
"Hi-EQ",
|
||||
"Filter",
|
||||
"Play Volume"
|
||||
);
|
||||
using namespace GameAPI::Analogs;
|
||||
|
||||
GameAPI::Analogs::sortAnalogsWithType(&analogs, {
|
||||
{ "Turntable P1", AnalogType::Circular },
|
||||
{ "Turntable P2", AnalogType::Circular },
|
||||
{ "VEFX", AnalogType::LinearPositive },
|
||||
{ "Low-EQ", AnalogType::LinearPositive },
|
||||
{ "Hi-EQ", AnalogType::LinearPositive },
|
||||
{ "Filter", AnalogType::LinearPositive },
|
||||
{ "Play Volume", AnalogType::LinearPositive }
|
||||
});
|
||||
}
|
||||
return analogs;
|
||||
}
|
||||
|
||||
@@ -92,6 +92,9 @@ namespace games {
|
||||
games.push_back(ddr);
|
||||
buttons.insert({ ddr, ddr::get_buttons() });
|
||||
lights.insert({ ddr, ddr::get_lights() });
|
||||
buttons_help.insert({ ddr, ddr::get_buttons_help() });
|
||||
analogs_help.insert({ ddr, ddr::get_analogs_help() });
|
||||
analogs.insert({ ddr, ddr::get_analogs() });
|
||||
file_hints[ddr].push_back({"ddr.dll"});
|
||||
file_hints[ddr].push_back({"mdxja_945.dll"});
|
||||
file_hints[ddr].push_back({"arkmdxp3.dll"});
|
||||
|
||||
@@ -167,7 +167,7 @@ namespace games::jb {
|
||||
void JBGame::pre_attach() {
|
||||
if (!cfg::CONFIGURATOR_STANDALONE) {
|
||||
const auto current_path = std::filesystem::current_path();
|
||||
log_misc("jubeat", "current working directory: {}", current_path.string());
|
||||
log_misc("jubeat", "current working directory: {}", current_path);
|
||||
if (current_path.parent_path() == current_path.root_path()) {
|
||||
log_warning(
|
||||
"jubeat",
|
||||
@@ -178,7 +178,7 @@ namespace games::jb {
|
||||
" c:\\jubeat\\contents\\spice.exe <- OK\n\n"
|
||||
"To fix this, create a new directory and move ALL game files there.\n\n"
|
||||
"Your current working directory: {}\n",
|
||||
current_path.string());
|
||||
current_path);
|
||||
|
||||
log_fatal(
|
||||
"jubeat",
|
||||
|
||||
@@ -36,11 +36,11 @@ std::vector<Analog> &games::mga::get_analogs() {
|
||||
if (analogs.empty()) {
|
||||
analogs = GameAPI::Analogs::getAnalogs("Metal Gear");
|
||||
|
||||
GameAPI::Analogs::sortAnalogs(
|
||||
&analogs,
|
||||
"Joy X",
|
||||
"Joy Y"
|
||||
);
|
||||
using namespace GameAPI::Analogs;
|
||||
GameAPI::Analogs::sortAnalogsWithType(&analogs, {
|
||||
{ "Joy X", AnalogType::LinearCentered },
|
||||
{ "Joy Y", AnalogType::LinearCentered }
|
||||
});
|
||||
}
|
||||
|
||||
return analogs;
|
||||
|
||||
@@ -165,37 +165,38 @@ std::vector<Analog> &games::nost::get_analogs() {
|
||||
if (analogs.empty()) {
|
||||
analogs = GameAPI::Analogs::getAnalogs("Nostalgia");
|
||||
|
||||
GameAPI::Analogs::sortAnalogs(
|
||||
&analogs,
|
||||
"Key 1",
|
||||
"Key 2",
|
||||
"Key 3",
|
||||
"Key 4",
|
||||
"Key 5",
|
||||
"Key 6",
|
||||
"Key 7",
|
||||
"Key 8",
|
||||
"Key 9",
|
||||
"Key 10",
|
||||
"Key 11",
|
||||
"Key 12",
|
||||
"Key 13",
|
||||
"Key 14",
|
||||
"Key 15",
|
||||
"Key 16",
|
||||
"Key 17",
|
||||
"Key 18",
|
||||
"Key 19",
|
||||
"Key 20",
|
||||
"Key 21",
|
||||
"Key 22",
|
||||
"Key 23",
|
||||
"Key 24",
|
||||
"Key 25",
|
||||
"Key 26",
|
||||
"Key 27",
|
||||
"Key 28"
|
||||
);
|
||||
using namespace GameAPI::Analogs;
|
||||
|
||||
GameAPI::Analogs::sortAnalogsWithType(&analogs, {
|
||||
{ "Key 1", AnalogType::LinearPositive },
|
||||
{ "Key 2", AnalogType::LinearPositive },
|
||||
{ "Key 3", AnalogType::LinearPositive },
|
||||
{ "Key 4", AnalogType::LinearPositive },
|
||||
{ "Key 5", AnalogType::LinearPositive },
|
||||
{ "Key 6", AnalogType::LinearPositive },
|
||||
{ "Key 7", AnalogType::LinearPositive },
|
||||
{ "Key 8", AnalogType::LinearPositive },
|
||||
{ "Key 9", AnalogType::LinearPositive },
|
||||
{ "Key 10", AnalogType::LinearPositive },
|
||||
{ "Key 11", AnalogType::LinearPositive },
|
||||
{ "Key 12", AnalogType::LinearPositive },
|
||||
{ "Key 13", AnalogType::LinearPositive },
|
||||
{ "Key 14", AnalogType::LinearPositive },
|
||||
{ "Key 15", AnalogType::LinearPositive },
|
||||
{ "Key 16", AnalogType::LinearPositive },
|
||||
{ "Key 17", AnalogType::LinearPositive },
|
||||
{ "Key 18", AnalogType::LinearPositive },
|
||||
{ "Key 19", AnalogType::LinearPositive },
|
||||
{ "Key 20", AnalogType::LinearPositive },
|
||||
{ "Key 21", AnalogType::LinearPositive },
|
||||
{ "Key 22", AnalogType::LinearPositive },
|
||||
{ "Key 23", AnalogType::LinearPositive },
|
||||
{ "Key 24", AnalogType::LinearPositive },
|
||||
{ "Key 25", AnalogType::LinearPositive },
|
||||
{ "Key 26", AnalogType::LinearPositive },
|
||||
{ "Key 27", AnalogType::LinearPositive },
|
||||
{ "Key 28", AnalogType::LinearPositive }
|
||||
});
|
||||
}
|
||||
return analogs;
|
||||
}
|
||||
|
||||
@@ -51,11 +51,12 @@ std::vector<Analog> &games::pc::get_analogs() {
|
||||
if (analogs.empty()) {
|
||||
analogs = GameAPI::Analogs::getAnalogs("Polaris Chord");
|
||||
|
||||
GameAPI::Analogs::sortAnalogs(
|
||||
&analogs,
|
||||
"Fader-L",
|
||||
"Fader-R"
|
||||
);
|
||||
using GameAPI::Analogs::AnalogType;
|
||||
|
||||
GameAPI::Analogs::sortAnalogsWithType(&analogs, {
|
||||
{ "Fader-L", AnalogType::LinearCentered },
|
||||
{ "Fader-R", AnalogType::LinearCentered }
|
||||
});
|
||||
}
|
||||
|
||||
return analogs;
|
||||
|
||||
@@ -35,12 +35,13 @@ std::vector<Analog> &games::rf3d::get_analogs() {
|
||||
if (analogs.empty()) {
|
||||
analogs = GameAPI::Analogs::getAnalogs("Road Fighters 3D");
|
||||
|
||||
GameAPI::Analogs::sortAnalogs(
|
||||
&analogs,
|
||||
"Wheel",
|
||||
"Accelerate",
|
||||
"Brake"
|
||||
);
|
||||
using GameAPI::Analogs::AnalogType;
|
||||
|
||||
GameAPI::Analogs::sortAnalogsWithType(&analogs, {
|
||||
{ "Wheel", AnalogType::LinearCentered },
|
||||
{ "Accelerate", AnalogType::LinearPositive },
|
||||
{ "Brake", AnalogType::LinearPositive }
|
||||
});
|
||||
}
|
||||
|
||||
return analogs;
|
||||
|
||||
@@ -31,13 +31,14 @@ std::vector<Analog> &games::sc::get_analogs() {
|
||||
if (analogs.empty()) {
|
||||
analogs = GameAPI::Analogs::getAnalogs("Steel Chronicle");
|
||||
|
||||
GameAPI::Analogs::sortAnalogs(
|
||||
&analogs,
|
||||
"Left Stick X",
|
||||
"Left Stick Y",
|
||||
"Right Stick X",
|
||||
"Right Stick Y"
|
||||
);
|
||||
using GameAPI::Analogs::AnalogType;
|
||||
|
||||
GameAPI::Analogs::sortAnalogsWithType(&analogs, {
|
||||
{ "Left Stick X", AnalogType::LinearCentered },
|
||||
{ "Left Stick Y", AnalogType::LinearCentered },
|
||||
{ "Right Stick X", AnalogType::LinearCentered },
|
||||
{ "Right Stick Y", AnalogType::LinearCentered }
|
||||
});
|
||||
}
|
||||
|
||||
return analogs;
|
||||
|
||||
@@ -31,11 +31,12 @@ std::vector<Analog> &games::silentscope::get_analogs() {
|
||||
if (analogs.empty()) {
|
||||
analogs = GameAPI::Analogs::getAnalogs("Silent Scope: Bone Eater");
|
||||
|
||||
GameAPI::Analogs::sortAnalogs(
|
||||
&analogs,
|
||||
"Gun X",
|
||||
"Gun Y"
|
||||
);
|
||||
using GameAPI::Analogs::AnalogType;
|
||||
|
||||
GameAPI::Analogs::sortAnalogsWithType(&analogs, {
|
||||
{ "Gun X", AnalogType::LinearCentered },
|
||||
{ "Gun Y", AnalogType::LinearCentered }
|
||||
});
|
||||
}
|
||||
|
||||
return analogs;
|
||||
|
||||
@@ -50,13 +50,14 @@ std::vector<Analog> &games::we::get_analogs() {
|
||||
if (analogs.empty()) {
|
||||
analogs = GameAPI::Analogs::getAnalogs("Winning Eleven");
|
||||
|
||||
GameAPI::Analogs::sortAnalogs(
|
||||
&analogs,
|
||||
"Pad Stick Left X",
|
||||
"Pad Stick Left Y",
|
||||
"Pad Stick Right X",
|
||||
"Pad Stick Right Y"
|
||||
);
|
||||
using GameAPI::Analogs::AnalogType;
|
||||
|
||||
GameAPI::Analogs::sortAnalogsWithType(&analogs, {
|
||||
{ "Pad Stick Left X", AnalogType::LinearCentered },
|
||||
{ "Pad Stick Left Y", AnalogType::LinearCentered },
|
||||
{ "Pad Stick Right X", AnalogType::LinearCentered },
|
||||
{ "Pad Stick Right Y", AnalogType::LinearCentered }
|
||||
});
|
||||
}
|
||||
|
||||
return analogs;
|
||||
|
||||
@@ -66,14 +66,16 @@ static HRESULT STDAPICALLTYPE CoCreateInstance_hook(
|
||||
if (FAILED(ret)) {
|
||||
if (IsEqualCLSID(rclsid, CLSID_MMDeviceEnumerator)) {
|
||||
log_warning("audio", "CoCreateInstance failed for CLSID_MMDeviceEnumerator, hr={}", FMT_HRESULT(ret));
|
||||
}
|
||||
|
||||
// Windows 11 KB5052093 issue - reverb DMO went missing from dsdmo.dll (fails with 0x80040154)
|
||||
if (IsEqualCLSID(rclsid, CLSID_DirectSoundI3DL2ReverbDMO) && ret == (HRESULT)0x80040154) {
|
||||
log_warning(
|
||||
"audio",
|
||||
"CoCreateInstance for CLSID_DirectSoundI3DL2ReverbDMO failed with 0x80040154, swap with CLSID_DirectSoundWavesReverbDMO "
|
||||
"(workaround for Windows 11 KB5052093 issue); REVERB EX replaced with REVERB");
|
||||
} else if (IsEqualCLSID(rclsid, CLSID_DirectSoundI3DL2ReverbDMO)) {
|
||||
// deal with reverb DMO missing from dsdmo.dll
|
||||
// it usually fails with 0x80040154 (REGDB_E_CLASSNOTREG), but we have also seen 0x8007007e (ERROR_MOD_NOT_FOUND)
|
||||
static std::once_flag printed;
|
||||
std::call_once(printed, [ret]() {
|
||||
log_warning(
|
||||
"audio",
|
||||
"CoCreateInstance for CLSID_DirectSoundI3DL2ReverbDMO failed with {}, swap with CLSID_DirectSoundWavesReverbDMO...",
|
||||
FMT_HRESULT(ret));
|
||||
});
|
||||
ret = CoCreateInstance_orig(CLSID_DirectSoundWavesReverbDMO, pUnkOuter, dwClsContext, riid, ppv);
|
||||
if (FAILED(ret)) {
|
||||
log_warning("audio", "CoCreateInstance(CLSID_DirectSoundWavesReverbDMO...) failed, hr={}", FMT_HRESULT(ret));
|
||||
|
||||
@@ -291,7 +291,7 @@ static int avs_fs_mount(const char *mountpoint, const char *fsroot, const char *
|
||||
std::filesystem::create_directories(mapped_path, err);
|
||||
|
||||
if (err) {
|
||||
log_warning("hooks::avs", "failed to create '{}': {}", mapped_path.string(), err.message());
|
||||
log_warning("hooks::avs", "failed to create '{}': {}", mapped_path, err.message());
|
||||
} else {
|
||||
|
||||
// if this is the `e:\`, then create the special directories
|
||||
@@ -305,7 +305,7 @@ static int avs_fs_mount(const char *mountpoint, const char *fsroot, const char *
|
||||
|
||||
log_misc("hooks::avs", "source directory '{}' remapped to '{}'",
|
||||
fsroot,
|
||||
mapped_path.string());
|
||||
mapped_path);
|
||||
}
|
||||
|
||||
new_fs_root = mapped_path.string();
|
||||
|
||||
@@ -59,6 +59,11 @@ static CaptureData GRAPHICS_CAPTURE_BUFFER[GRAPHICS_CAPTURE_SCREEN_NO] {};
|
||||
static std::mutex GRAPHICS_CAPTURE_BUFFER_M[GRAPHICS_CAPTURE_SCREEN_NO] {};
|
||||
static std::condition_variable GRAPHICS_CAPTURE_CV[GRAPHICS_CAPTURE_SCREEN_NO] {};
|
||||
|
||||
static std::optional<graphics_orientation> target_orientation_on_boot;
|
||||
static UINT target_refresh_rate_on_boot = 0;
|
||||
static bool monitor_settings_changed = false;
|
||||
static bool monitor_layout_needs_reset = false;
|
||||
|
||||
// flag settings
|
||||
bool GRAPHICS_CAPTURE_CURSOR = false;
|
||||
bool GRAPHICS_LOG_HRESULT = false;
|
||||
@@ -104,6 +109,7 @@ 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 decltype(SetDisplayConfig) *SetDisplayConfig_addr = nullptr;
|
||||
|
||||
static void reset_window_hook(HWND hWnd) {
|
||||
overlay::destroy(hWnd);
|
||||
@@ -232,7 +238,7 @@ static LRESULT CALLBACK WindowProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM l
|
||||
|
||||
switch (uMsg) {
|
||||
case WM_MOVE:
|
||||
case WM_SIZE: {
|
||||
case WM_SIZE:
|
||||
// Update SPICETOUCH space when the main window changes size or moves.
|
||||
// The update happens regardless of whether the "fake" spicetouch window is present or not.
|
||||
// This allows touches received on subscreen window to be translated correctly.
|
||||
@@ -249,7 +255,16 @@ static LRESULT CALLBACK WindowProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM l
|
||||
SPICETOUCH_TOUCH_WIDTH, SPICETOUCH_TOUCH_HEIGHT,
|
||||
SWP_NOZORDER | SWP_NOREDRAW | SWP_NOREPOSITION | SWP_NOACTIVATE);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case WM_ACTIVATEAPP:
|
||||
if (wParam) {
|
||||
// regained focus
|
||||
// this *can* get called twice in a row when restoring, but update_monitor_at_runtime
|
||||
// is idempotent (checks current display settings to see if changes are needed),
|
||||
// so it shouldn't cause any issues
|
||||
update_monitor_at_runtime();
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
@@ -1142,7 +1157,7 @@ void change_primary_monitor(const std::string &monitor_name) {
|
||||
const auto DisplayConfigGetDeviceInfo_addr =
|
||||
reinterpret_cast<decltype(DisplayConfigGetDeviceInfo) *>(
|
||||
GetProcAddress(user32, "DisplayConfigGetDeviceInfo"));
|
||||
const auto SetDisplayConfig_addr =
|
||||
SetDisplayConfig_addr =
|
||||
reinterpret_cast<decltype(SetDisplayConfig) *>(
|
||||
GetProcAddress(user32, "SetDisplayConfig"));
|
||||
if (GetDisplayConfigBufferSizes_addr == nullptr || QueryDisplayConfig_addr == nullptr ||
|
||||
@@ -1257,11 +1272,13 @@ void change_primary_monitor(const std::string &monitor_name) {
|
||||
log_fatal("graphics", "SetDisplayConfig failed, check -mainmonitor option: {}", status);
|
||||
}
|
||||
|
||||
monitor_layout_needs_reset = true;
|
||||
|
||||
// a little extra time for windows to settle and redraw things
|
||||
Sleep(2000);
|
||||
}
|
||||
|
||||
void update_monitor_on_boot(std::optional<graphics_orientation> target_orientation, UINT target_refresh_rate) {
|
||||
void update_monitor(bool is_boot, std::optional<graphics_orientation> target_orientation, UINT target_refresh_rate) {
|
||||
// note: all of this is only being done for the primary motnior
|
||||
|
||||
// get current settings
|
||||
@@ -1330,7 +1347,7 @@ void update_monitor_on_boot(std::optional<graphics_orientation> target_orientati
|
||||
}
|
||||
|
||||
// update refresh rate
|
||||
if (target_refresh_rate > 0) {
|
||||
if (target_refresh_rate > 0 && target_refresh_rate != dm.dmDisplayFrequency) {
|
||||
log_misc("graphics",
|
||||
"current refresh rate {} => desired refresh rate {}",
|
||||
dm.dmDisplayFrequency, target_refresh_rate);
|
||||
@@ -1342,25 +1359,76 @@ void update_monitor_on_boot(std::optional<graphics_orientation> target_orientati
|
||||
|
||||
if (!needs_update) {
|
||||
// nothing to do
|
||||
log_misc("graphics", "display settings are already up to date, no changes needed");
|
||||
return;
|
||||
}
|
||||
|
||||
const auto result = ChangeDisplaySettings(&dm, CDS_FULLSCREEN);
|
||||
if (result != DISP_CHANGE_SUCCESSFUL) {
|
||||
log_fatal(
|
||||
"graphics",
|
||||
"failed to update display settings ({}px x {}px @ {}Hz): error {}, double check options",
|
||||
dm.dmPelsWidth,
|
||||
dm.dmPelsHeight,
|
||||
dm.dmDisplayFrequency,
|
||||
result);
|
||||
if (is_boot) {
|
||||
log_fatal(
|
||||
"graphics",
|
||||
"failed to update display settings ({}px x {}px @ {}Hz): error {}, double check options",
|
||||
dm.dmPelsWidth,
|
||||
dm.dmPelsHeight,
|
||||
dm.dmDisplayFrequency,
|
||||
result);
|
||||
} else {
|
||||
log_warning(
|
||||
"graphics",
|
||||
"failed to update display settings ({}px x {}px @ {}Hz): error {}, double check options",
|
||||
dm.dmPelsWidth,
|
||||
dm.dmPelsHeight,
|
||||
dm.dmDisplayFrequency,
|
||||
result);
|
||||
}
|
||||
} else {
|
||||
monitor_settings_changed = true;
|
||||
// sleep for a little bit after changing monitor settings to delay game launch/resume
|
||||
Sleep(1000);
|
||||
log_info("graphics", "display settings updated successfully ({}px x {}px @ {}Hz)",
|
||||
dm.dmPelsWidth,
|
||||
dm.dmPelsHeight,
|
||||
dm.dmDisplayFrequency);
|
||||
}
|
||||
|
||||
// sleep for a little bit after changing monitor settings to delay game launch
|
||||
Sleep(1000);
|
||||
}
|
||||
|
||||
void update_monitor_on_boot(std::optional<graphics_orientation> target_orientation, UINT target_refresh_rate) {
|
||||
target_orientation_on_boot = target_orientation;
|
||||
target_refresh_rate_on_boot = target_refresh_rate;
|
||||
log_misc("graphics", "applying monitor updates at boot...");
|
||||
update_monitor(true, target_orientation, target_refresh_rate);
|
||||
}
|
||||
|
||||
void update_monitor_at_runtime() {
|
||||
if (monitor_settings_changed) {
|
||||
log_misc("graphics", "applying monitor updates at runtime as window regained focus...");
|
||||
update_monitor(false, target_orientation_on_boot, target_refresh_rate_on_boot);
|
||||
}
|
||||
}
|
||||
|
||||
void reset_monitor_on_exit() {
|
||||
|
||||
// while CDS_FULLSCREEN is *supposed* to be temporary & the OS attempts to
|
||||
// restore the original settings on exit, it can sometimes fail to do that;
|
||||
// therefore, we try our best to clean things up on the way out
|
||||
if (monitor_settings_changed) {
|
||||
monitor_settings_changed = false;
|
||||
log_misc("graphics", "resetting monitor settings on exit...");
|
||||
ChangeDisplaySettingsW(nullptr, 0);
|
||||
}
|
||||
|
||||
// same for this one.
|
||||
if (monitor_layout_needs_reset) {
|
||||
monitor_layout_needs_reset = false;
|
||||
log_misc("graphics", "restoring primary monitor on exit...");
|
||||
if (SetDisplayConfig_addr != nullptr) {
|
||||
SetDisplayConfig_addr(
|
||||
0,
|
||||
nullptr,
|
||||
0,
|
||||
nullptr,
|
||||
SDC_APPLY | SDC_USE_DATABASE_CURRENT);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -118,3 +118,5 @@ void graphics_window_check_bounds_before_creation(int &x, int &y, const int widt
|
||||
|
||||
void change_primary_monitor(const std::string &monitor_name);
|
||||
void update_monitor_on_boot(std::optional<graphics_orientation> target_orientation, UINT target_refresh_rate);
|
||||
void update_monitor_at_runtime();
|
||||
void reset_monitor_on_exit();
|
||||
@@ -518,6 +518,12 @@ int main_implementation(int argc, char *argv[]) {
|
||||
if (options[launcher::Options::IIDXTDJMode].value_bool()) {
|
||||
games::iidx::TDJ_MODE = true;
|
||||
}
|
||||
if (options[launcher::Options::IIDXTTDelayP1].is_active()) {
|
||||
games::iidx::TT_DELAY_P1 = options[launcher::Options::IIDXTTDelayP1].value_uint32();
|
||||
}
|
||||
if (options[launcher::Options::IIDXTTDelayP2].is_active()) {
|
||||
games::iidx::TT_DELAY_P2 = options[launcher::Options::IIDXTTDelayP2].value_uint32();
|
||||
}
|
||||
if (options[launcher::Options::spice2x_IIDXDigitalTTSensitivity].is_active()) {
|
||||
games::iidx::DIGITAL_TT_SENS = (uint8_t)
|
||||
options[launcher::Options::spice2x_IIDXDigitalTTSensitivity].value_uint32();
|
||||
@@ -2364,9 +2370,9 @@ int main_implementation(int argc, char *argv[]) {
|
||||
// layeredfs
|
||||
if (fileutils::dir_exists("data_mods") &&
|
||||
GetModuleHandleA("ifs_hook.dll") == nullptr) {
|
||||
log_warning("launcher", "data_mods directory found, but ifs_hook.dll is not present; mods will not load");
|
||||
log_warning("launcher", "data_mods directory found, but ifs_hook.dll is not loaded; mods will not load");
|
||||
deferredlogs::defer_error_messages({
|
||||
"data_mods directory was found, but ifs_hook.dll is not present",
|
||||
"data_mods directory was found, but ifs_hook.dll is not loaded",
|
||||
" your mods will not load",
|
||||
" to fix this, download ifs_layeredfs and add it as a DLL hook (-k)",
|
||||
" https://github.com/mon/ifs_layeredfs"
|
||||
|
||||
@@ -655,6 +655,30 @@ static const std::vector<OptionDefinition> OPTION_DEFINITIONS = {
|
||||
.game_name = "Beatmania IIDX",
|
||||
.category = "Game Options",
|
||||
},
|
||||
{
|
||||
// IIDXTTDelayP1
|
||||
.title = "IIDX TT Delay ms (Player 1)",
|
||||
.name = "iidxttdelayp1",
|
||||
.desc = "Delays turntable by number of milliseconds. "
|
||||
"Delay will be *at most* the specified period; maximum error is dependent on the game's poll rate. "
|
||||
"As usual, changing any option requires a restart. Default: 0 (no delay).",
|
||||
.type = OptionType::Integer,
|
||||
.setting_name = "(0-500)",
|
||||
.game_name = "Beatmania IIDX",
|
||||
.category = "Game Options (Advanced)",
|
||||
},
|
||||
{
|
||||
// IIDXTTDelayP2
|
||||
.title = "IIDX TT Delay ms (Player 2)",
|
||||
.name = "iidxttdelayp2",
|
||||
.desc = "Delays turntable by number of milliseconds. "
|
||||
"Delay will be *at most* the specified period; maximum error is dependent on the game's poll rate. "
|
||||
"As usual, changing any option requires a restart. Default: 0 (no delay).",
|
||||
.type = OptionType::Integer,
|
||||
.setting_name = "(0-500)",
|
||||
.game_name = "Beatmania IIDX",
|
||||
.category = "Game Options (Advanced)",
|
||||
},
|
||||
{
|
||||
// spice2x_IIDXDigitalTTSensitivity
|
||||
.title = "IIDX Digital TT Sensitivity",
|
||||
|
||||
@@ -69,6 +69,8 @@ namespace launcher {
|
||||
IIDXAsioDriver,
|
||||
IIDXBIO2FW,
|
||||
IIDXTDJMode,
|
||||
IIDXTTDelayP1,
|
||||
IIDXTTDelayP2,
|
||||
spice2x_IIDXDigitalTTSensitivity,
|
||||
IIDXDigitalTTSocd,
|
||||
spice2x_IIDXLDJForce720p,
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
#include "easrv/easrv.h"
|
||||
#include "rawinput/rawinput.h"
|
||||
#include "hooks/audio/audio.h"
|
||||
#include "hooks/graphics/graphics.h"
|
||||
#include "util/deferlog.h"
|
||||
#include "util/logging.h"
|
||||
|
||||
@@ -22,6 +23,9 @@ namespace launcher {
|
||||
// therefore, subsystems need to be guarded against multiple unload attempts
|
||||
log_info("launcher", "stopping subsystems");
|
||||
|
||||
// reset monitor settings
|
||||
reset_monitor_on_exit();
|
||||
|
||||
// before shutting down logger, dump any deferred log messages
|
||||
deferredlogs::dump_to_logger();
|
||||
|
||||
|
||||
@@ -131,7 +131,7 @@ bool eamuse_get_card_from_file(const std::filesystem::path &path, uint8_t *card,
|
||||
// open file
|
||||
std::ifstream f(path);
|
||||
if (!f) {
|
||||
log_warning("eamuse", "{} can not be opened!", path.string());
|
||||
log_warning("eamuse", "{} can not be opened!", path);
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -142,7 +142,7 @@ bool eamuse_get_card_from_file(const std::filesystem::path &path, uint8_t *card,
|
||||
|
||||
// check size
|
||||
if (length < 16) {
|
||||
log_warning("eamuse", "{} is too small (must be at least 16 characters)", path.string());
|
||||
log_warning("eamuse", "{} is too small (must be at least 16 characters)", path);
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -160,14 +160,14 @@ bool eamuse_get_card_from_file(const std::filesystem::path &path, uint8_t *card,
|
||||
if (!digit && !character_big && !character_small) {
|
||||
log_warning("eamuse",
|
||||
"{} contains an invalid character sequence at byte {} (16 characters, 0-9/A-F only)",
|
||||
path.string(), n);
|
||||
path, n);
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// info
|
||||
log_info("eamuse", "[P{}] Inserted {}: {}", index+1, path.string(), buffer);
|
||||
log_info("eamuse", "[P{}] Inserted {}: {}", index+1, path, buffer);
|
||||
|
||||
// convert hex to bytes
|
||||
hex2bin(buffer, card);
|
||||
|
||||
@@ -373,7 +373,7 @@ void ImGui_ImplSpice_NewFrame() {
|
||||
// read new keys state
|
||||
static long mouse_wheel_last = 0;
|
||||
long mouse_wheel = 0;
|
||||
if (RI_MGR != nullptr && accept_new_input) {
|
||||
if (RI_MGR != nullptr) {
|
||||
auto devices = RI_MGR->devices_get();
|
||||
for (auto &device : devices) {
|
||||
switch (device.type) {
|
||||
@@ -381,40 +381,46 @@ void ImGui_ImplSpice_NewFrame() {
|
||||
auto &mouse = device.mouseInfo;
|
||||
|
||||
// mouse button triggers
|
||||
if (GetSystemMetrics(SM_SWAPBUTTON)) {
|
||||
if (mouse->key_states[rawinput::MOUSEBTN_RIGHT]) {
|
||||
g_MouseDown[ImGuiMouseButton_Left] = true;
|
||||
if (accept_new_input) {
|
||||
if (GetSystemMetrics(SM_SWAPBUTTON)) {
|
||||
if (mouse->key_states[rawinput::MOUSEBTN_RIGHT]) {
|
||||
g_MouseDown[ImGuiMouseButton_Left] = true;
|
||||
}
|
||||
if (mouse->key_states[rawinput::MOUSEBTN_LEFT]) {
|
||||
g_MouseDown[ImGuiMouseButton_Right] = true;
|
||||
}
|
||||
} else {
|
||||
if (mouse->key_states[rawinput::MOUSEBTN_LEFT]) {
|
||||
g_MouseDown[ImGuiMouseButton_Left] = true;
|
||||
}
|
||||
if (mouse->key_states[rawinput::MOUSEBTN_RIGHT]) {
|
||||
g_MouseDown[ImGuiMouseButton_Right] = true;
|
||||
}
|
||||
}
|
||||
if (mouse->key_states[rawinput::MOUSEBTN_LEFT]) {
|
||||
g_MouseDown[ImGuiMouseButton_Right] = true;
|
||||
if (mouse->key_states[rawinput::MOUSEBTN_MIDDLE]) {
|
||||
g_MouseDown[ImGuiMouseButton_Middle] = true;
|
||||
}
|
||||
} else {
|
||||
if (mouse->key_states[rawinput::MOUSEBTN_LEFT]) {
|
||||
g_MouseDown[ImGuiMouseButton_Left] = true;
|
||||
}
|
||||
if (mouse->key_states[rawinput::MOUSEBTN_RIGHT]) {
|
||||
g_MouseDown[ImGuiMouseButton_Right] = true;
|
||||
}
|
||||
}
|
||||
if (mouse->key_states[rawinput::MOUSEBTN_MIDDLE]) {
|
||||
g_MouseDown[ImGuiMouseButton_Middle] = true;
|
||||
}
|
||||
|
||||
// final mouse wheel value should be all devices combined
|
||||
// need to continuously calculate mouse wheel position even when not in focus
|
||||
// to avoid values jumping (since mouse wheel values are absolute not relative)
|
||||
mouse_wheel += mouse->pos_wheel;
|
||||
|
||||
break;
|
||||
}
|
||||
case rawinput::KEYBOARD: {
|
||||
|
||||
// iterate all virtual key codes
|
||||
for (size_t vKey = 0; vKey < VKEY_MAX; vKey++) {
|
||||
// get state (combined from all pages)
|
||||
auto &key_states = device.keyboardInfo->key_states;
|
||||
for (size_t page_index = 0; page_index < 1024; page_index += 256) {
|
||||
g_KeysDown[vKey] |= key_states[page_index + vKey];
|
||||
if (accept_new_input) {
|
||||
// iterate all virtual key codes
|
||||
for (size_t vKey = 0; vKey < VKEY_MAX; vKey++) {
|
||||
// get state (combined from all pages)
|
||||
auto &key_states = device.keyboardInfo->key_states;
|
||||
for (size_t page_index = 0; page_index < 1024; page_index += 256) {
|
||||
g_KeysDown[vKey] |= key_states[page_index + vKey];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
default:
|
||||
@@ -464,9 +470,11 @@ void ImGui_ImplSpice_NewFrame() {
|
||||
}
|
||||
|
||||
// set mouse wheel
|
||||
auto wheel_diff = mouse_wheel - mouse_wheel_last;
|
||||
long wheel_diff = mouse_wheel - mouse_wheel_last;
|
||||
mouse_wheel_last = mouse_wheel;
|
||||
io.AddMouseWheelEvent(0, wheel_diff);
|
||||
if (wheel_diff != 0 && accept_new_input) {
|
||||
io.AddMouseWheelEvent(0, wheel_diff);
|
||||
}
|
||||
|
||||
// update OS mouse position
|
||||
const auto old_mouse_pos = io.MousePos;
|
||||
|
||||
@@ -733,13 +733,13 @@ void overlay::SpiceOverlay::add_font(const char* font, ImFontConfig* config, con
|
||||
full_path += font;
|
||||
|
||||
if (fileutils::file_exists(full_path)) {
|
||||
log_misc("overlay", "loading font: {}", full_path.string());
|
||||
log_misc("overlay", "loading font: {}", full_path);
|
||||
ImGui::GetIO().Fonts->AddFontFromFileTTF(
|
||||
full_path.string().c_str(),
|
||||
13.0f,
|
||||
config,
|
||||
glyphs);
|
||||
} else {
|
||||
log_misc("overlay", "font not found: {}", full_path.string());
|
||||
log_misc("overlay", "font not found: {}", full_path);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1063,6 +1063,7 @@ namespace overlay::windows {
|
||||
button->setAnalogType(BAT_NONE);
|
||||
button->setDebounceUp(0.0);
|
||||
button->setDebounceDown(0.0);
|
||||
button->setBatThreshold(0);
|
||||
button->setVelocityThreshold(0);
|
||||
button->setInvert(false);
|
||||
button->setLastState(GameAPI::Buttons::BUTTON_NOT_PRESSED);
|
||||
@@ -1205,6 +1206,16 @@ namespace overlay::windows {
|
||||
case rawinput::HID: {
|
||||
auto hid = device->hidInfo;
|
||||
|
||||
struct temp_button {
|
||||
std::string device_name;
|
||||
unsigned short vkey;
|
||||
ButtonAnalogType analog_type;
|
||||
};
|
||||
|
||||
// use this to prioritize button binding over value types
|
||||
// (for controllers that output both)
|
||||
std::optional<temp_button> button_to_bind;
|
||||
|
||||
// ignore touchscreen and digitizer button inputs
|
||||
// digitizer has funky stuff like "Touch Valid" "Data Valid" always held high
|
||||
if (!rawinput::touch::is_touchscreen(device) &&
|
||||
@@ -1218,18 +1229,11 @@ namespace overlay::windows {
|
||||
|
||||
// check if button is down
|
||||
if (button_states[i]) {
|
||||
|
||||
// bind key
|
||||
button->setDeviceIdentifier(device->name);
|
||||
button->setVKey(static_cast<unsigned short>(button_index + i));
|
||||
button->setAnalogType(BAT_NONE);
|
||||
::Config::getInstance().updateBinding(
|
||||
games_list[games_selected], *button,
|
||||
alt_index - 1);
|
||||
ImGui::CloseCurrentPopup();
|
||||
buttons_bind_active = false;
|
||||
inc_buttons_many_index(button_it_max);
|
||||
RI_MGR->devices_midi_freeze(false);
|
||||
button_to_bind = {
|
||||
device->name,
|
||||
static_cast<unsigned short>(button_index + i),
|
||||
BAT_NONE
|
||||
};
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -1238,93 +1242,100 @@ namespace overlay::windows {
|
||||
}
|
||||
|
||||
// value caps
|
||||
auto value_states = &hid->value_states;
|
||||
auto bind_value_states = &hid->bind_value_states;
|
||||
auto value_names = &hid->value_caps_names;
|
||||
for (size_t i = 0; i < value_states->size(); i++) {
|
||||
auto &state = value_states->at(i);
|
||||
auto &bind_state = bind_value_states->at(i);
|
||||
auto &value_name = value_names->at(i);
|
||||
if (!button_to_bind.has_value()) {
|
||||
auto value_states = &hid->value_states;
|
||||
auto bind_value_states = &hid->bind_value_states;
|
||||
auto value_names = &hid->value_caps_names;
|
||||
for (size_t i = 0; i < value_states->size(); i++) {
|
||||
auto &state = value_states->at(i);
|
||||
auto &bind_state = bind_value_states->at(i);
|
||||
auto &value_name = value_names->at(i);
|
||||
|
||||
// check for valid axis names
|
||||
if (value_name == "X" ||
|
||||
value_name == "Y" ||
|
||||
value_name == "Rx" ||
|
||||
value_name == "Ry" ||
|
||||
value_name == "Z")
|
||||
{
|
||||
// check if axis is in activation area
|
||||
float normalized = (state - 0.5f) * 2.f;
|
||||
float diff = std::fabs(state - bind_state);
|
||||
if (std::fabs(normalized) > 0.9f && diff > 0.1f) {
|
||||
auto bat = normalized > 0 ? BAT_POSITIVE : BAT_NEGATIVE;
|
||||
// check for valid axis names
|
||||
if (value_name == "X" ||
|
||||
value_name == "Y" ||
|
||||
value_name == "Rx" ||
|
||||
value_name == "Ry" ||
|
||||
value_name == "Z")
|
||||
{
|
||||
// check if axis is in activation area
|
||||
float normalized = (state - 0.5f) * 2.f;
|
||||
float diff = std::fabs(state - bind_state);
|
||||
if (std::fabs(normalized) > 0.9f && diff > 0.1f) {
|
||||
auto bat = normalized > 0 ? BAT_POSITIVE : BAT_NEGATIVE;
|
||||
|
||||
// bind value
|
||||
button->setDeviceIdentifier(device->name);
|
||||
button->setVKey(static_cast<unsigned short>(i));
|
||||
button->setAnalogType(bat);
|
||||
button->setDebounceUp(0.0);
|
||||
button->setDebounceDown(0.0);
|
||||
button->setVelocityThreshold(0);
|
||||
::Config::getInstance().updateBinding(
|
||||
games_list[games_selected], *button,
|
||||
alt_index - 1);
|
||||
ImGui::CloseCurrentPopup();
|
||||
buttons_bind_active = false;
|
||||
inc_buttons_many_index(button_it_max);
|
||||
RI_MGR->devices_midi_freeze(false);
|
||||
// bind value
|
||||
button_to_bind = {
|
||||
device->name,
|
||||
static_cast<unsigned short>(i),
|
||||
bat
|
||||
};
|
||||
|
||||
// usually, turntables are X, knobs are X and Y
|
||||
// gamepad triggers are Z, and Rx/Ry are right thumb stick
|
||||
// one day we will label all I/O modules and flag which one are button-as-analog
|
||||
// so that we don't have to do string comparions like below
|
||||
if ((value_name == "X" || value_name == "Y") &&
|
||||
(button->getName().find("Press") == std::string::npos) && // museca
|
||||
(button->getName().find("Slowdown") == std::string::npos) && // bishibashi
|
||||
(button->getName().find("TT+") != std::string::npos ||
|
||||
button->getName().find("TT-") != std::string::npos || // iidx
|
||||
button->getName().find("Knob") != std::string::npos || // gitadora guitar
|
||||
button->getName().find("Disk") != std::string::npos || // museca, bishibashi
|
||||
button->getName().find("VOL-") != std::string::npos)) { // sdvx
|
||||
this->analog_as_button_warning_show_next_frame =
|
||||
std::make_pair(button->getName(), alt_index);
|
||||
// usually, turntables are X, knobs are X and Y
|
||||
// gamepad triggers are Z, and Rx/Ry are right thumb stick
|
||||
// one day we will label all I/O modules and flag which one are button-as-analog
|
||||
// so that we don't have to do string comparions like below
|
||||
if ((value_name == "X" || value_name == "Y") &&
|
||||
(button->getName().find("Press") == std::string::npos) && // museca
|
||||
(button->getName().find("Slowdown") == std::string::npos) && // bishibashi
|
||||
(button->getName().find("TT+") != std::string::npos ||
|
||||
button->getName().find("TT-") != std::string::npos || // iidx
|
||||
button->getName().find("Knob") != std::string::npos || // gitadora guitar
|
||||
button->getName().find("Disk") != std::string::npos || // museca, bishibashi
|
||||
button->getName().find("P1 Panel") != std::string::npos || // ddr
|
||||
button->getName().find("P2 Panel") != std::string::npos || // ddr
|
||||
button->getName().find("VOL-") != std::string::npos)) { // sdvx
|
||||
this->analog_as_button_warning_show_next_frame =
|
||||
std::make_pair(button->getName(), alt_index);
|
||||
}
|
||||
break;
|
||||
|
||||
} else if (diff > 0.3f) {
|
||||
bind_state = state;
|
||||
}
|
||||
break;
|
||||
|
||||
} else if (diff > 0.3f) {
|
||||
bind_state = state;
|
||||
}
|
||||
}
|
||||
|
||||
// hat switch
|
||||
if (value_name == "Hat switch") {
|
||||
// hat switch
|
||||
if (value_name == "Hat switch") {
|
||||
|
||||
// get hat switch values
|
||||
ButtonAnalogType buffer[3], buffer_bind[3];
|
||||
Button::getHatSwitchValues(state, buffer);
|
||||
Button::getHatSwitchValues(bind_state, buffer_bind);
|
||||
// get hat switch values
|
||||
ButtonAnalogType buffer[3], buffer_bind[3];
|
||||
Button::getHatSwitchValues(state, buffer);
|
||||
Button::getHatSwitchValues(bind_state, buffer_bind);
|
||||
|
||||
// check the first entry only
|
||||
if (buffer[0] != BAT_NONE && buffer[0] != buffer_bind[0]) {
|
||||
// check the first entry only
|
||||
if (buffer[0] != BAT_NONE && buffer[0] != buffer_bind[0]) {
|
||||
|
||||
// bind value
|
||||
button->setDeviceIdentifier(device->name);
|
||||
button->setVKey(static_cast<unsigned short>(i));
|
||||
button->setAnalogType(buffer[0]);
|
||||
button->setDebounceUp(0.0);
|
||||
button->setDebounceDown(0.0);
|
||||
button->setVelocityThreshold(0);
|
||||
::Config::getInstance().updateBinding(
|
||||
games_list[games_selected], *button,
|
||||
alt_index - 1);
|
||||
ImGui::CloseCurrentPopup();
|
||||
buttons_bind_active = false;
|
||||
inc_buttons_many_index(button_it_max);
|
||||
RI_MGR->devices_midi_freeze(false);
|
||||
break;
|
||||
// bind value
|
||||
button_to_bind = {
|
||||
device->name,
|
||||
static_cast<unsigned short>(i),
|
||||
buffer[0]
|
||||
};
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (button_to_bind.has_value()) {
|
||||
const auto &b = button_to_bind.value();
|
||||
button->setDeviceIdentifier(b.device_name);
|
||||
button->setVKey(b.vkey);
|
||||
button->setAnalogType(b.analog_type);
|
||||
button->setDebounceUp(0.0);
|
||||
button->setDebounceDown(0.0);
|
||||
button->setBatThreshold(0);
|
||||
button->setVelocityThreshold(0);
|
||||
::Config::getInstance().updateBinding(
|
||||
games_list[games_selected], *button,
|
||||
alt_index - 1);
|
||||
ImGui::CloseCurrentPopup();
|
||||
buttons_bind_active = false;
|
||||
inc_buttons_many_index(button_it_max);
|
||||
RI_MGR->devices_midi_freeze(false);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
case rawinput::MIDI: {
|
||||
@@ -1360,6 +1371,7 @@ namespace overlay::windows {
|
||||
button->setAnalogType(BAT_NONE);
|
||||
button->setDebounceUp(0.0);
|
||||
button->setDebounceDown(0.0);
|
||||
button->setBatThreshold(0);
|
||||
// same idea as setMidiVKey - keep velocity threshold consistent
|
||||
button->setVelocityThreshold(
|
||||
device->midiInfo->v2_velocity_threshold[button->getVKey()]);
|
||||
@@ -1385,6 +1397,7 @@ namespace overlay::windows {
|
||||
button->setAnalogType(BAT_MIDI_CTRL_PRECISION);
|
||||
button->setDebounceUp(0.0);
|
||||
button->setDebounceDown(0.0);
|
||||
button->setBatThreshold(0);
|
||||
button->setVelocityThreshold(0);
|
||||
::Config::getInstance().updateBinding(
|
||||
games_list[games_selected], *button,
|
||||
@@ -1411,6 +1424,7 @@ namespace overlay::windows {
|
||||
button->setAnalogType(BAT_MIDI_CTRL_SINGLE);
|
||||
button->setDebounceUp(0.0);
|
||||
button->setDebounceDown(0.0);
|
||||
button->setBatThreshold(0);
|
||||
button->setVelocityThreshold(0);
|
||||
::Config::getInstance().updateBinding(
|
||||
games_list[games_selected], *button,
|
||||
@@ -1437,6 +1451,7 @@ namespace overlay::windows {
|
||||
button->setAnalogType(BAT_MIDI_CTRL_ONOFF);
|
||||
button->setDebounceUp(0.0);
|
||||
button->setDebounceDown(0.0);
|
||||
button->setBatThreshold(0);
|
||||
button->setVelocityThreshold(0);
|
||||
::Config::getInstance().updateBinding(
|
||||
games_list[games_selected], *button,
|
||||
@@ -1462,6 +1477,7 @@ namespace overlay::windows {
|
||||
button->setAnalogType(BAT_MIDI_PITCH_DOWN);
|
||||
button->setDebounceUp(0.0);
|
||||
button->setDebounceDown(0.0);
|
||||
button->setBatThreshold(0);
|
||||
button->setVelocityThreshold(0);
|
||||
::Config::getInstance().updateBinding(
|
||||
games_list[games_selected], *button,
|
||||
@@ -1482,6 +1498,7 @@ namespace overlay::windows {
|
||||
button->setAnalogType(BAT_MIDI_PITCH_UP);
|
||||
button->setDebounceUp(0.0);
|
||||
button->setDebounceDown(0.0);
|
||||
button->setBatThreshold(0);
|
||||
button->setVelocityThreshold(0);
|
||||
::Config::getInstance().updateBinding(
|
||||
games_list[games_selected], *button,
|
||||
@@ -1511,6 +1528,7 @@ namespace overlay::windows {
|
||||
button->setAnalogType(BAT_NONE);
|
||||
button->setDebounceUp(0.0);
|
||||
button->setDebounceDown(0.0);
|
||||
button->setBatThreshold(0);
|
||||
button->setVelocityThreshold(0);
|
||||
::Config::getInstance().updateBinding(
|
||||
games_list[games_selected], *button,
|
||||
@@ -1635,6 +1653,7 @@ namespace overlay::windows {
|
||||
button->setVKey(vKey);
|
||||
button->setDebounceUp(0.0);
|
||||
button->setDebounceDown(0.0);
|
||||
button->setBatThreshold(0);
|
||||
button->setVelocityThreshold(0);
|
||||
::Config::getInstance().updateBinding(
|
||||
games_list[games_selected], *button,
|
||||
@@ -1686,6 +1705,9 @@ namespace overlay::windows {
|
||||
button->setDeviceIdentifier(device.name);
|
||||
// reset controls when switching devices
|
||||
button->setAnalogType(ButtonAnalogType::BAT_NONE);
|
||||
button->setDebounceUp(0.0);
|
||||
button->setDebounceDown(0.0);
|
||||
button->setBatThreshold(0);
|
||||
button->setVelocityThreshold(0);
|
||||
button->setVKey(0);
|
||||
button->setInvert(false);
|
||||
@@ -1981,6 +2003,7 @@ namespace overlay::windows {
|
||||
int vKey = button->getVKey();
|
||||
if (ImGui::InputInt(button->isNaive() ? "Virtual Key" : "Index", &vKey, 1, 1)) {
|
||||
button->setVKey(vKey);
|
||||
button->setInvert(false);
|
||||
}
|
||||
if (ImGui::IsItemDeactivatedAfterEdit()) {
|
||||
dirty = true;
|
||||
@@ -2048,11 +2071,64 @@ namespace overlay::windows {
|
||||
"This setting will add noticable input lag.");
|
||||
}
|
||||
|
||||
// invert
|
||||
bool invert = button->getInvert();
|
||||
if (ImGui::Checkbox("Invert", &invert)) {
|
||||
button->setInvert(invert);
|
||||
dirty = true;
|
||||
// invert (widget hidden for naive + 0xff)
|
||||
if (!(button->isNaive() && button->getVKey() == INVALID_VKEY)) {
|
||||
bool invert = button->getInvert();
|
||||
if (ImGui::Checkbox("Invert Resulting Value", &invert)) {
|
||||
button->setInvert(invert);
|
||||
dirty = true;
|
||||
}
|
||||
}
|
||||
|
||||
// always on (naive + vkey 0xff + invert)
|
||||
if (button->isNaive() && alt_index == 0) {
|
||||
bool always_on = button->isNaive() && button->getVKey() == INVALID_VKEY && button->getInvert();
|
||||
if (ImGui::Checkbox("Always On", &always_on)) {
|
||||
if (always_on) {
|
||||
button->setVKey(INVALID_VKEY);
|
||||
button->setInvert(true);
|
||||
} else {
|
||||
button->setInvert(false);
|
||||
}
|
||||
dirty = true;
|
||||
}
|
||||
}
|
||||
|
||||
// bat threshold
|
||||
if (device != nullptr && device->type == rawinput::HID &&
|
||||
(button->getAnalogType() == BAT_POSITIVE || button->getAnalogType() == BAT_NEGATIVE)) {
|
||||
int bat_threshold = button->getBatThreshold();
|
||||
|
||||
bool is_active = (bat_threshold > 0);
|
||||
if (ImGui::Checkbox("Custom Analog Threshold", &is_active)) {
|
||||
if (!is_active) {
|
||||
button->setBatThreshold(0);
|
||||
} else if (bat_threshold == 0) {
|
||||
button->setBatThreshold(60);
|
||||
}
|
||||
dirty = true;
|
||||
}
|
||||
ImGui::SameLine();
|
||||
ImGui::HelpMarker(
|
||||
"Assuming that 50% is the center, value exceeding 60% triggers the button by default. "
|
||||
"With this option, you can customize the trigger threshold.\n\n"
|
||||
"Setting this to 51% means tiny movements will trigger the button (small deadzone), "
|
||||
"while setting this to 99% means the button will only trigger at maximum value (large deadzone).\n\n"
|
||||
"Setting this to below 50% adjusts the neutral / center position.");
|
||||
|
||||
if (is_active) {
|
||||
if (ImGui::SliderInt(
|
||||
"Analog Threshold",
|
||||
&bat_threshold,
|
||||
1, 99,
|
||||
"%d%%",
|
||||
ImGuiSliderFlags_AlwaysClamp)) {
|
||||
button->setBatThreshold(bat_threshold);
|
||||
}
|
||||
if (ImGui::IsItemDeactivatedAfterEdit()) {
|
||||
dirty = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// state display
|
||||
@@ -2133,9 +2209,9 @@ namespace overlay::windows {
|
||||
ImGui::Separator();
|
||||
if (ImGui::BeginTable("AnalogsTable", 3, ImGuiTableFlags_Resizable | ImGuiTableFlags_RowBg)) {
|
||||
|
||||
ImGui::TableSetupColumn("Name", ImGuiTableColumnFlags_WidthFixed, overlay::apply_scaling(220));
|
||||
ImGui::TableSetupColumn("Name", ImGuiTableColumnFlags_WidthFixed, overlay::apply_scaling(240));
|
||||
ImGui::TableSetupColumn("Binding", ImGuiTableColumnFlags_WidthStretch);
|
||||
ImGui::TableSetupColumn("Actions", ImGuiTableColumnFlags_WidthFixed, overlay::apply_scaling(100));
|
||||
ImGui::TableSetupColumn("Actions", ImGuiTableColumnFlags_WidthFixed, overlay::apply_scaling(80));
|
||||
|
||||
// check if empty
|
||||
if (!analogs || analogs->empty()) {
|
||||
@@ -2194,8 +2270,9 @@ namespace overlay::windows {
|
||||
}
|
||||
|
||||
// analog binding
|
||||
const std::string title = "Analog Binding (" + analog.getName() + ")";
|
||||
if (ImGui::Button("Set")) {
|
||||
ImGui::OpenPopup("Analog Binding");
|
||||
ImGui::OpenPopup(title.c_str());
|
||||
|
||||
// get devices
|
||||
this->analogs_devices.clear();
|
||||
@@ -2225,7 +2302,7 @@ namespace overlay::windows {
|
||||
}
|
||||
}
|
||||
|
||||
edit_analog_popup(analog);
|
||||
edit_analog_popup(analog, title);
|
||||
|
||||
// row hover detection (invisible selectable that spans entire row)
|
||||
ImGui::InvisibleTableRowSelectable();
|
||||
@@ -2239,8 +2316,8 @@ namespace overlay::windows {
|
||||
}
|
||||
}
|
||||
|
||||
void Config::edit_analog_popup(Analog &analog) {
|
||||
if (ImGui::BeginPopupModal("Analog Binding", NULL, ImGuiWindowFlags_AlwaysAutoResize)) {
|
||||
void Config::edit_analog_popup(Analog &analog, std::string title) {
|
||||
if (ImGui::BeginPopupModal(title.c_str(), NULL, ImGuiWindowFlags_AlwaysAutoResize)) {
|
||||
|
||||
// device selector
|
||||
auto analog_device_changed = ImGui::Combo(
|
||||
@@ -2447,31 +2524,45 @@ namespace overlay::windows {
|
||||
const bool value_changed =
|
||||
ImGui::SliderFloat("Sensitivity", &sensitivity, 0.f, 2.f, "%.3f");
|
||||
ImGui::SameLine();
|
||||
ImGui::HelpMarker(
|
||||
"Adjust floating point multiplier to relative movement.\n\n"
|
||||
"Value is squared before being multiplied (e.g., 1.44 is 2x sensitivity, 2.00 is 4x).\n\n"
|
||||
"Dependent on how often the game polls for input. Intended for angular input (knobs, turntables)");
|
||||
if (device->type == rawinput::HID && analog.getType() != GameAPI::Analogs::AnalogType::Circular) {
|
||||
ImGui::HelpMarker(
|
||||
"Adjust the analog sensitivity curve; "
|
||||
"values <1.0 are less sensitive around neutral, "
|
||||
"values >1.0 are more sensitive.");
|
||||
} else {
|
||||
ImGui::HelpMarker(
|
||||
"Adjust floating point multiplier to relative movement.\n\n"
|
||||
"Value is squared before being multiplied (e.g., 1.44 is 2x sensitivity, 2.00 is 4x).");
|
||||
}
|
||||
if (value_changed) {
|
||||
analog.setSensitivity(sensitivity * sensitivity);
|
||||
}
|
||||
}
|
||||
if (device->type == rawinput::HID || device->type == rawinput::MIDI) {
|
||||
|
||||
// hide deadzone for circular analog since it doesn't make any sense
|
||||
if ((device->type == rawinput::HID || device->type == rawinput::MIDI) &&
|
||||
analog.getType() != GameAPI::Analogs::AnalogType::Circular) {
|
||||
auto deadzone = analog.getDeadzone();
|
||||
|
||||
// for back compat (before each analog had a type)
|
||||
if (deadzone < 0.f) {
|
||||
deadzone = -deadzone;
|
||||
analog.setDeadzone(deadzone);
|
||||
}
|
||||
|
||||
const bool value_changed =
|
||||
ImGui::SliderFloat("Deadzone", &deadzone, -0.999f, 0.999f, "%.3f");
|
||||
ImGui::SliderFloat("Deadzone", &deadzone, 0.f, 0.999f, "%.3f");
|
||||
if (value_changed) {
|
||||
analog.setDeadzone(deadzone);
|
||||
}
|
||||
ImGui::SameLine();
|
||||
ImGui::HelpMarker("Positive values specify a deadzone around the middle.\n"
|
||||
"Negative values specify a deadzone from the minimum value.");
|
||||
ImGui::HelpMarker("Specify the deadzone that gets applied to at-rest (neutral) value.");
|
||||
|
||||
// deadzone mirror
|
||||
bool deadzone_mirror = analog.getDeadzoneMirror();
|
||||
ImGui::Checkbox("Deadzone Mirror", &deadzone_mirror);
|
||||
ImGui::SameLine();
|
||||
ImGui::HelpMarker("Positive deadzone values cut off at edges instead.\n"
|
||||
"Negative deadzone values cut off at maximum value instead.");
|
||||
ImGui::HelpMarker("Apply deadzone to extreme value(s) instead of neutral.");
|
||||
if (deadzone_mirror != analog.getDeadzoneMirror()) {
|
||||
analog.setDeadzoneMirror(deadzone_mirror);
|
||||
}
|
||||
@@ -2490,47 +2581,20 @@ namespace overlay::windows {
|
||||
if (this->analogs_devices_selected >= 0) {
|
||||
const auto device = this->analogs_devices.at(this->analogs_devices_selected);
|
||||
if (device->type == rawinput::HID) {
|
||||
// smoothing
|
||||
bool smoothing = analog.getSmoothing();
|
||||
ImGui::BeginDisabled(analog.isRelativeMode());
|
||||
ImGui::Checkbox("Smooth Axis (adds latency)", &smoothing);
|
||||
ImGui::SameLine();
|
||||
ImGui::HelpMarker(
|
||||
"Apply a moving average algorithm; intended for angular input (knobs, turntables). "
|
||||
"Adds a slight bit of latency to input as the algorithm averages out recent input. "
|
||||
"Only use in dire situations where the input is too jittery for the game.");
|
||||
ImGui::EndDisabled();
|
||||
if (smoothing != analog.getSmoothing()) {
|
||||
analog.setSmoothing(smoothing);
|
||||
}
|
||||
|
||||
// relative input mode
|
||||
bool relative_analog = analog.isRelativeMode();
|
||||
ImGui::Checkbox("Relative Axis (experimental)", &relative_analog);
|
||||
ImGui::SameLine();
|
||||
ImGui::HelpMarker(
|
||||
"Use relative directional input instead of positional values.\n\n"
|
||||
"Can be used to translate analog sticks to knob input, for example.\n\n"
|
||||
"WARNING: speed depends on how often the game polls for input! "
|
||||
"Strongly recommended that you go into the game's test menu instead "
|
||||
"of adjusting in spicecfg.");
|
||||
if (relative_analog != analog.isRelativeMode()) {
|
||||
analog.setRelativeMode(relative_analog);
|
||||
if (analog.getType() == GameAPI::Analogs::AnalogType::Circular) {
|
||||
// smoothing
|
||||
bool smoothing = analog.getSmoothing();
|
||||
ImGui::Checkbox("Smooth Axis (adds latency)", &smoothing);
|
||||
ImGui::SameLine();
|
||||
ImGui::HelpMarker(
|
||||
"Apply a moving average algorithm; intended for angular input (knobs, turntables). "
|
||||
"Adds a slight bit of latency to input as the algorithm averages out recent input. "
|
||||
"Only use in dire situations where the input is too jittery for the game.");
|
||||
if (smoothing != analog.getSmoothing()) {
|
||||
analog.setSmoothing(smoothing);
|
||||
}
|
||||
}
|
||||
|
||||
// delay buffer
|
||||
int delay = analog.getDelayBufferDepth();
|
||||
ImGui::InputInt("Delay (experimental)", &delay, 1, 10);
|
||||
if (ImGui::IsItemDeactivatedAfterEdit()) {
|
||||
delay = CLAMP(delay, 0, 256);
|
||||
analog.setDelayBufferDepth(delay);
|
||||
}
|
||||
ImGui::SameLine();
|
||||
ImGui::HelpMarker(
|
||||
"Adds a delay to input. This is poll-based, not time-based.\n\n"
|
||||
"WARNING: delay depends on how often the game polls for input! "
|
||||
"Strongly recommended that you go into the game's test menu instead "
|
||||
"of adjusting in spicecfg.");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2541,10 +2605,12 @@ namespace overlay::windows {
|
||||
ImGui::ProgressBar(value);
|
||||
|
||||
// centered knob preview
|
||||
const float knob_size = 64.f;
|
||||
auto width = ImGui::GetContentRegionAvail().x - knob_size;
|
||||
ImGui::SetCursorPosX(ImGui::GetCursorPosX() + (width / 2));
|
||||
ImGui::Knob(value, knob_size);
|
||||
if (analog.getType() == GameAPI::Analogs::AnalogType::Circular) {
|
||||
const float knob_size = 64.f;
|
||||
auto width = ImGui::GetContentRegionAvail().x - knob_size;
|
||||
ImGui::SetCursorPosX(ImGui::GetCursorPosX() + (width / 2));
|
||||
ImGui::Knob(value, knob_size);
|
||||
}
|
||||
|
||||
// update analog
|
||||
if (analogs_devices_selected >= 0 && analogs_devices_selected < (int) analogs_devices.size()) {
|
||||
@@ -5283,6 +5349,7 @@ namespace overlay::windows {
|
||||
btn.setInvert(entry->invert);
|
||||
btn.setDebounceUp(entry->debounce_up);
|
||||
btn.setDebounceDown(entry->debounce_down);
|
||||
btn.setBatThreshold(entry->bat_threshold);
|
||||
btn.setVelocityThreshold(entry->velocity_threshold);
|
||||
::Config::getInstance().updateBinding(game, btn, -1);
|
||||
} else {
|
||||
@@ -5293,6 +5360,7 @@ namespace overlay::windows {
|
||||
alt_btn.setInvert(entry->invert);
|
||||
alt_btn.setDebounceUp(entry->debounce_up);
|
||||
alt_btn.setDebounceDown(entry->debounce_down);
|
||||
alt_btn.setBatThreshold(entry->bat_threshold);
|
||||
alt_btn.setVelocityThreshold(entry->velocity_threshold);
|
||||
alt_btn.setTemporary(true);
|
||||
btn.getAlternatives().push_back(alt_btn);
|
||||
@@ -5338,8 +5406,6 @@ namespace overlay::windows {
|
||||
a.setInvert(ta.invert);
|
||||
a.setSmoothing(ta.smoothing);
|
||||
a.setMultiplier(ta.multiplier);
|
||||
a.setRelativeMode(ta.relative_mode);
|
||||
a.setDelayBufferDepth(ta.delay_buffer_depth);
|
||||
::Config::getInstance().updateBinding(game, a);
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -162,7 +162,7 @@ namespace overlay::windows {
|
||||
unsigned int get_keypad_top_row(const Button &button);
|
||||
|
||||
void build_analogs(const std::string &name, std::vector<Analog> *analogs);
|
||||
void edit_analog_popup(Analog &analog);
|
||||
void edit_analog_popup(Analog &analog, std::string title);
|
||||
|
||||
void update() override;
|
||||
void stop_lights_test();
|
||||
|
||||
@@ -17,6 +17,7 @@ namespace overlay::windows {
|
||||
bool invert = false;
|
||||
double debounce_up = 0.0;
|
||||
double debounce_down = 0.0;
|
||||
int bat_threshold = 0;
|
||||
unsigned short velocity_threshold = 0;
|
||||
|
||||
bool is_naive() const { return device_identifier.empty() && vKey != INVALID_VKEY; }
|
||||
@@ -31,6 +32,7 @@ namespace overlay::windows {
|
||||
e.invert = btn.getInvert();
|
||||
e.debounce_up = btn.getDebounceUp();
|
||||
e.debounce_down = btn.getDebounceDown();
|
||||
e.bat_threshold = btn.getBatThreshold();
|
||||
e.velocity_threshold = btn.getVelocityThreshold();
|
||||
return e;
|
||||
}
|
||||
@@ -69,8 +71,6 @@ namespace overlay::windows {
|
||||
bool invert = false;
|
||||
bool smoothing = false;
|
||||
int multiplier = 1;
|
||||
bool relative_mode = false;
|
||||
int delay_buffer_depth = 0;
|
||||
|
||||
bool is_device() const { return !device_identifier.empty(); }
|
||||
bool is_unbound() const { return device_identifier.empty() && index == 0xFF; }
|
||||
@@ -87,8 +87,6 @@ namespace overlay::windows {
|
||||
invert = a.getInvert();
|
||||
smoothing = a.getSmoothing();
|
||||
multiplier = a.getMultiplier();
|
||||
relative_mode = a.isRelativeMode();
|
||||
delay_buffer_depth = a.getDelayBufferDepth();
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
|
||||
#include "avs/game.h"
|
||||
#include "games/iidx/iidx.h"
|
||||
#include "games/popn/popn.h"
|
||||
#include "misc/eamuse.h"
|
||||
#include "util/logging.h"
|
||||
#include "overlay/imgui/extensions.h"
|
||||
@@ -42,6 +43,8 @@ namespace overlay::windows {
|
||||
void Keypad::build_content() {
|
||||
if (avs::game::is_model("LDJ") && games::iidx::TDJ_MODE) {
|
||||
build_tdj_keypad();
|
||||
} else if (games::popn::is_pikapika_model()) {
|
||||
build_popn_pika_keypad();
|
||||
} else {
|
||||
build_keypad();
|
||||
}
|
||||
@@ -132,4 +135,26 @@ namespace overlay::windows {
|
||||
eamuse_set_keypad_overrides_overlay(this->unit, 0);
|
||||
}
|
||||
}
|
||||
|
||||
void Keypad::build_popn_pika_keypad() {
|
||||
ImGui::AlignTextToFramePadding();
|
||||
ImGui::TextDisabled("Keypad disabled in Pop'n Pikapika model!\nUse subscreen overlay.");
|
||||
ImGui::SameLine();
|
||||
ImGui::WarnMarker(
|
||||
nullptr,
|
||||
"Pop'n Music Pikapika cabinets do not have any keypads; they use the subscreen.\n\n"
|
||||
"Fullscreen mode: bind a key in Overlay tab, and press it in game to show the subscreen, "
|
||||
"then use your mouse to click. Page Up button is the default binding.\n\n"
|
||||
"Windowed mode: look for the second window in the taskbar (or ALT+TAB).\n\n"
|
||||
"Windowed mode with -popnnosub: bring up the subscreen overlay (default Page Up).\n\n"
|
||||
);
|
||||
|
||||
ImGui::Spacing();
|
||||
|
||||
if (ImGui::Button("Insert Card")) {
|
||||
eamuse_set_keypad_overrides_overlay(this->unit, 1 << EAM_IO_INSERT);
|
||||
} else {
|
||||
eamuse_set_keypad_overrides_overlay(this->unit, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@ namespace overlay::windows {
|
||||
size_t unit = 0;
|
||||
void build_keypad();
|
||||
void build_tdj_keypad();
|
||||
void build_popn_pika_keypad();
|
||||
|
||||
public:
|
||||
|
||||
|
||||
@@ -74,6 +74,23 @@ namespace overlay::windows {
|
||||
{"Nostroller (Lights 15-28)", "Nostalgia", 0, 0, "LED_27", "", "", true, "Key 27", ""},
|
||||
{"Nostroller (Lights 15-28)", "Nostalgia", 0, 0, "LED_28", "", "", true, "Key 28", ""},
|
||||
|
||||
// icedragon.io snek board, ddr mode
|
||||
{"snek lights", "Dance Dance Revolution", 0x2e8a, 0x10a8, "neon", "", "", false, "Neon", ""},
|
||||
{"snek lights", "Dance Dance Revolution", 0x2e8a, 0x10a8, "mar p1 upper", "", "", false, "P1 Halogen Upper", ""},
|
||||
{"snek lights", "Dance Dance Revolution", 0x2e8a, 0x10a8, "mar p1 lower", "", "", false, "P1 Halogen Lower", ""},
|
||||
{"snek lights", "Dance Dance Revolution", 0x2e8a, 0x10a8, "mar p2 upper", "", "", false, "P2 Halogen Upper", ""},
|
||||
{"snek lights", "Dance Dance Revolution", 0x2e8a, 0x10a8, "mar p2 lower", "", "", false, "P2 Halogen Lower", ""},
|
||||
{"snek lights", "Dance Dance Revolution", 0x2e8a, 0x10a8, "p1 buttons", "", "", false, "P1 Button", ""},
|
||||
{"snek lights", "Dance Dance Revolution", 0x2e8a, 0x10a8, "p2 buttons", "", "", false, "P2 Button", ""},
|
||||
{"snek lights", "Dance Dance Revolution", 0x2e8a, 0x10a8, "p1 up", "", "", false, "P1 Foot Up", ""},
|
||||
{"snek lights", "Dance Dance Revolution", 0x2e8a, 0x10a8, "p1 down", "", "", false, "P1 Foot Down", ""},
|
||||
{"snek lights", "Dance Dance Revolution", 0x2e8a, 0x10a8, "p1 left", "", "", false, "P1 Foot Left", ""},
|
||||
{"snek lights", "Dance Dance Revolution", 0x2e8a, 0x10a8, "p1 right", "", "", false, "P1 Foot Right", ""},
|
||||
{"snek lights", "Dance Dance Revolution", 0x2e8a, 0x10a8, "p2 up", "", "", false, "P2 Foot Up", ""},
|
||||
{"snek lights", "Dance Dance Revolution", 0x2e8a, 0x10a8, "p2 down", "", "", false, "P2 Foot Down", ""},
|
||||
{"snek lights", "Dance Dance Revolution", 0x2e8a, 0x10a8, "p2 left", "", "", false, "P2 Foot Left", ""},
|
||||
{"snek lights", "Dance Dance Revolution", 0x2e8a, 0x10a8, "p2 right", "", "", false, "P2 Foot Right", ""},
|
||||
|
||||
};
|
||||
|
||||
static const int LIGHT_MATCH_MAP_COUNT =
|
||||
|
||||
@@ -95,6 +95,10 @@ namespace overlay::windows {
|
||||
}
|
||||
|
||||
// utility
|
||||
std::string displayPath(const std::filesystem::path &path) {
|
||||
return fmt::format(FMT_STRING("{}"), path);
|
||||
}
|
||||
|
||||
std::string getFromUrl(const std::string& dll_name, const std::string& url) {
|
||||
log_info("patchmanager", "getting patches from URL: {}, for file: {}", url, dll_name);
|
||||
std::string result;
|
||||
@@ -226,7 +230,7 @@ namespace overlay::windows {
|
||||
|
||||
if (PATCH_MANAGER_CFG_PATH_OVERRIDE.has_value()) {
|
||||
this->config_path = PATCH_MANAGER_CFG_PATH_OVERRIDE.value();
|
||||
log_info("patchmanager", "using custom config file path: {}", this->config_path.string().c_str());
|
||||
log_info("patchmanager", "using custom config file path: {}", this->config_path);
|
||||
} else {
|
||||
this->config_path =
|
||||
fileutils::get_config_file_path("patchmanager", "spicetools_patch_manager.json");
|
||||
@@ -330,7 +334,7 @@ namespace overlay::windows {
|
||||
"Wrong path? Run spicecfg from the correct directory, or fix your modules parameter before launching spicecfg.\n"
|
||||
"Make sure you're not using a different one when launching the game.");
|
||||
ImGui::SameLine();
|
||||
ImGui::Text("Modules Path: %s", MODULE_PATH.string().c_str());
|
||||
ImGui::Text("Modules Path: %s", displayPath(MODULE_PATH).c_str());
|
||||
|
||||
ImGui::AlignTextToFramePadding();
|
||||
ImGui::DummyMarker();
|
||||
@@ -878,7 +882,7 @@ namespace overlay::windows {
|
||||
}
|
||||
|
||||
void PatchManager::hard_apply_patches() {
|
||||
std::vector<std::string> written_list;
|
||||
std::vector<std::filesystem::path> written_list;
|
||||
for (auto& patch : patches) {
|
||||
switch (patch.type) {
|
||||
case PatchType::Memory:
|
||||
@@ -1218,7 +1222,7 @@ namespace overlay::windows {
|
||||
log_info(
|
||||
"patchmanager",
|
||||
"file: {}, patch id: {}, build timestamp of dll: {:%Y-%m-%d %H:%M}",
|
||||
dll_path.has_filename() ? dll_path.filename().string() : dll_path.string(),
|
||||
dll_path.has_filename() ? dll_path.filename() : dll_path,
|
||||
identifier,
|
||||
time);
|
||||
}
|
||||
@@ -1623,7 +1627,7 @@ namespace overlay::windows {
|
||||
// save to file
|
||||
std::filesystem::path save_path = LOCAL_PATCHES_PATH / (identifier + ".json");
|
||||
fileutils::text_write(save_path, patches_json);
|
||||
log_info("patchmanager", "remotely fetched JSON saved to: {}", save_path.string());
|
||||
log_info("patchmanager", "remotely fetched JSON saved to: {}", save_path);
|
||||
return true;
|
||||
} else {
|
||||
log_warning("patchmanager", "failed to fetch patches JSON for {}", dll_name);
|
||||
@@ -1675,19 +1679,19 @@ namespace overlay::windows {
|
||||
const size_t patches_size_previous = patches.size();
|
||||
for (const std::filesystem::path& patches_json_path: LOCAL_PATCHES_JSON_PATHS) {
|
||||
if (!fileutils::file_exists(patches_json_path)) {
|
||||
log_misc("patchmanager", "file does not exist, skipping: {}", patches_json_path.string());
|
||||
log_misc("patchmanager", "file does not exist, skipping: {}", patches_json_path);
|
||||
continue;
|
||||
}
|
||||
|
||||
log_misc("patchmanager", "reading from patches.json: {}", patches_json_path.string());
|
||||
log_misc("patchmanager", "reading from patches.json: {}", patches_json_path);
|
||||
std::string content = fileutils::text_read(patches_json_path);
|
||||
append_patches(content, apply_patches, filter);
|
||||
|
||||
const auto new_patches = patches.size() - patches_size_previous;
|
||||
log_info("patchmanager", "loaded {} patches from: {}", new_patches, patches_json_path.string());
|
||||
log_info("patchmanager", "loaded {} patches from: {}", new_patches, patches_json_path);
|
||||
if (0 < new_patches) {
|
||||
ret = true;
|
||||
ACTIVE_JSON_FILE = patches_json_path.string();
|
||||
ACTIVE_JSON_FILE = displayPath(patches_json_path);
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -1723,21 +1727,21 @@ namespace overlay::windows {
|
||||
|
||||
if (fileutils::file_exists(firstPath) || !extraDlls.empty()) {
|
||||
if (fileutils::file_exists(firstPath)) {
|
||||
log_info("patchmanager", "loaded patches for {} from {}", firstDll, firstPath.string());
|
||||
log_info("patchmanager", "loaded patches for {} from {}", firstDll, firstPath);
|
||||
std::string content = fileutils::text_read(firstPath);
|
||||
append_patches(content, apply_patches, nullptr, first_id);
|
||||
ACTIVE_JSON_FILE = firstPath.string();
|
||||
ACTIVE_JSON_FILE = displayPath(firstPath);
|
||||
}
|
||||
for (const std::string& dll : extraDlls) {
|
||||
auto extraId = get_game_identifier(MODULE_PATH / dll);
|
||||
auto extraPath = std::filesystem::path(fmt::format("patches/{}.json", extraId));
|
||||
log_info("patchmanager", "loaded patches for {} from {}", dll, extraPath.string());
|
||||
log_info("patchmanager", "loaded patches for {} from {}", dll, extraPath);
|
||||
std::string content = fileutils::text_read(extraPath);
|
||||
append_patches(content, apply_patches, nullptr, extraId);
|
||||
if (ACTIVE_JSON_FILE.empty()) {
|
||||
ACTIVE_JSON_FILE = extraPath.string();
|
||||
ACTIVE_JSON_FILE = displayPath(extraPath);
|
||||
} else {
|
||||
ACTIVE_JSON_FILE += ", " + extraPath.string();
|
||||
ACTIVE_JSON_FILE += ", " + displayPath(extraPath);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
@@ -3024,22 +3028,23 @@ namespace overlay::windows {
|
||||
}
|
||||
|
||||
void create_dll_backup(
|
||||
std::vector<std::string>& written_list, const std::filesystem::path& dll_path) {
|
||||
std::vector<std::filesystem::path>& written_list, const std::filesystem::path& dll_path) {
|
||||
|
||||
// if dll_path is not in written_list, create a file backup.
|
||||
if (std::find(written_list.begin(), written_list.end(), dll_path.string()) == written_list.end()) {
|
||||
written_list.push_back(dll_path.string());
|
||||
auto dll_bak_path = std::filesystem::path(dll_path.string() + ".bak");
|
||||
if (std::find(written_list.begin(), written_list.end(), dll_path) == written_list.end()) {
|
||||
written_list.push_back(dll_path);
|
||||
auto dll_bak_path = dll_path;
|
||||
dll_bak_path += ".bak";
|
||||
try {
|
||||
if (!fileutils::file_exists(dll_bak_path)) {
|
||||
std::filesystem::copy(dll_path, dll_bak_path);
|
||||
}
|
||||
log_info("patchmanager", "created DLL backup for: {}", dll_path.string());
|
||||
log_info("patchmanager", "created DLL backup for: {}", dll_path);
|
||||
} catch (const std::filesystem::filesystem_error& e) {
|
||||
log_warning(
|
||||
"patchmanager",
|
||||
"filesystem error while creating DLL backup for {}, error: {}",
|
||||
dll_path.string(), e.what());
|
||||
dll_path, e.what());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3065,14 +3070,14 @@ namespace overlay::windows {
|
||||
/// check if file exists
|
||||
auto dll_path = MODULE_PATH / dll_name;
|
||||
if (!fileutils::file_exists(dll_path)) {
|
||||
log_warning("patchmanager", "{} does not exist", dll_path.string());
|
||||
log_warning("patchmanager", "{} does not exist", dll_path);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// get module
|
||||
auto module = libutils::try_module(dll_path);
|
||||
if (!module) {
|
||||
log_warning("patchmanager", "cannot get module: {}", dll_path.string());
|
||||
log_warning("patchmanager", "cannot get module: {}", dll_path);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
@@ -3081,7 +3086,7 @@ namespace overlay::windows {
|
||||
if (offset == -1) {
|
||||
log_warning(
|
||||
"patchmanager", "cannot convert offset to RVA: {}, {}",
|
||||
dll_path.string(), data_offset);
|
||||
dll_path, data_offset);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
@@ -3095,7 +3100,7 @@ namespace overlay::windows {
|
||||
|
||||
log_warning(
|
||||
"patchmanager", "GetModuleInformation failed for {}, gle: {}",
|
||||
dll_path.string(), GetLastError());
|
||||
dll_path, GetLastError());
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
@@ -162,7 +162,7 @@ namespace overlay::windows {
|
||||
uint8_t* destination, const std::string& dll_name, size_t offset, size_t size);
|
||||
|
||||
void create_dll_backup(
|
||||
std::vector<std::string>& written_list, const std::filesystem::path& dll_path);
|
||||
std::vector<std::filesystem::path>& written_list, const std::filesystem::path& dll_path);
|
||||
std::string fix_up_dll_name(const std::string& dll_name);
|
||||
uint8_t* get_dll_offset_for_patch_apply(
|
||||
const std::string& dll_name, const uint64_t data_offset, const size_t size_in_bytes);
|
||||
|
||||
@@ -605,14 +605,22 @@ void rawinput::RawInputManager::devices_scan_rawinput(RAWINPUTDEVICELIST *device
|
||||
value_caps.LogicalMax = 255;
|
||||
}
|
||||
|
||||
// fix min and max values
|
||||
if (value_caps.BitSize > 0 && value_caps.BitSize <= sizeof(value_caps.LogicalMin) * 8) {
|
||||
auto shift_size = sizeof(value_caps.LogicalMin) * 8 - value_caps.BitSize + 1;
|
||||
auto mask = ((uint64_t) 1 << value_caps.BitSize) - 1;
|
||||
value_caps.LogicalMin &= mask;
|
||||
value_caps.LogicalMin <<= shift_size;
|
||||
value_caps.LogicalMin >>= shift_size;
|
||||
value_caps.LogicalMax &= mask;
|
||||
// fix up invalid max values (seen on xbox controllers where max is 0xffffffff despite being 16-bit)
|
||||
if (value_caps.LogicalMin == 0 && value_caps.BitSize > 0 && value_caps.BitSize < 32) {
|
||||
const uint32_t field_max = (1u << value_caps.BitSize) - 1u;
|
||||
const uint32_t logical_max = static_cast<uint32_t>(value_caps.LogicalMax);
|
||||
|
||||
if (logical_max > field_max) {
|
||||
log_info(
|
||||
"rawinput",
|
||||
"value cap {} LogicalMax exceeds bit width, fixing it up: {} -> {}",
|
||||
value_cap_num,
|
||||
value_caps.LogicalMax,
|
||||
field_max
|
||||
);
|
||||
|
||||
value_caps.LogicalMax = static_cast<LONG>(field_max);
|
||||
}
|
||||
}
|
||||
|
||||
// fix up hat switch to initially report as neutral position
|
||||
@@ -1914,15 +1922,6 @@ LRESULT CALLBACK rawinput::RawInputManager::input_wnd_proc(
|
||||
LONG value_min = value_caps.LogicalMin;
|
||||
LONG value_max = value_caps.LogicalMax;
|
||||
|
||||
// fix sign bits for signed values
|
||||
if (value_caps.LogicalMin < 0 &&
|
||||
value_caps.BitSize > 0 &&
|
||||
value_caps.BitSize <= sizeof(value_caps.LogicalMin) * 8) {
|
||||
auto shift_size = sizeof(value_caps.LogicalMin) * 8 - value_caps.BitSize + 1;
|
||||
value_raw <<= shift_size;
|
||||
value_raw >>= shift_size;
|
||||
}
|
||||
|
||||
float value;
|
||||
// 0x1 == generic desktop, 0x39 == hat switch
|
||||
if (value_caps.UsagePage == 0x1 && value_caps.Range.UsageMin == 0x39) {
|
||||
@@ -1935,6 +1934,16 @@ LRESULT CALLBACK rawinput::RawInputManager::input_wnd_proc(
|
||||
value = -1.f;
|
||||
}
|
||||
} else {
|
||||
|
||||
// fix sign bits for signed values
|
||||
if (value_caps.LogicalMin < 0 &&
|
||||
0 < value_caps.BitSize && value_caps.BitSize < 32) {
|
||||
|
||||
ULONG raw = static_cast<ULONG>(value_raw) & ((1u << value_caps.BitSize) - 1u);
|
||||
const ULONG sign_bit = 1u << (value_caps.BitSize - 1);
|
||||
value_raw = static_cast<LONG>((raw ^ sign_bit) - sign_bit);
|
||||
}
|
||||
|
||||
// automatic calibration
|
||||
if (value_raw < value_min) {
|
||||
value_caps.LogicalMin = value_raw;
|
||||
|
||||
@@ -99,7 +99,7 @@ namespace rawinput::touch {
|
||||
|
||||
// check type
|
||||
if (device->type != HID) {
|
||||
log_fatal("rawinput", "touch update called on non HID device");
|
||||
log_warning("rawinput", "touch update called on non HID device");
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
+133
-75
@@ -1,6 +1,11 @@
|
||||
#include "rawinput/xinput.h"
|
||||
#include "util/logging.h"
|
||||
|
||||
// std::min
|
||||
#ifdef min
|
||||
#undef min
|
||||
#endif
|
||||
|
||||
namespace xinput {
|
||||
|
||||
// this is all we need to emulate xinput.h which we avoid including here...
|
||||
@@ -24,6 +29,10 @@ namespace xinput {
|
||||
#define XINPUT_GAMEPAD_X 0x4000
|
||||
#define XINPUT_GAMEPAD_Y 0x8000
|
||||
|
||||
// custom
|
||||
|
||||
static constexpr float GAMEPAD_THUMB_DIGITAL_THRESHOLD = 0.2f;
|
||||
|
||||
typedef struct {
|
||||
uint32_t dwPacketNumber;
|
||||
XINPUT_GAMEPAD_STATE Gamepad;
|
||||
@@ -153,7 +162,7 @@ XInputSetState(
|
||||
float XInputManager::get_analog_state(uint8_t player, XInputAnalogEnum analog) {
|
||||
return 0.5f;
|
||||
}
|
||||
bool XInputManager::is_button_pressed(uint8_t player, XInputButtonEnum button) {
|
||||
bool XInputManager::is_button_pressed(uint8_t player, XInputButtonEnum button, XINPUT_GAMEPAD_STATE_NORMALIZED *state_in) {
|
||||
return false;
|
||||
}
|
||||
bool XInputManager::get_any_button_pressed(XINPUT_NEW_BUTTON &button) {
|
||||
@@ -223,6 +232,40 @@ XInputSetState(
|
||||
return players;
|
||||
}
|
||||
|
||||
static void normalize_stick(
|
||||
SHORT raw_x,
|
||||
SHORT raw_y,
|
||||
float deadzone,
|
||||
float &out_x,
|
||||
float &out_y) {
|
||||
|
||||
const int x_i = static_cast<int>(raw_x);
|
||||
const int y_i = -static_cast<int>(raw_y);
|
||||
|
||||
const float x_f = static_cast<float>(x_i);
|
||||
const float y_f = static_cast<float>(y_i);
|
||||
const float magnitude = std::sqrt(x_f * x_f + y_f * y_f);
|
||||
|
||||
// within deadzone; ignore
|
||||
if (magnitude <= deadzone) {
|
||||
out_x = 0.5f;
|
||||
out_y = 0.5f;
|
||||
return;
|
||||
}
|
||||
|
||||
// scale value starting with 0 from deadzone border
|
||||
const float magnitude_clamped = std::min(magnitude, 32767.0f);
|
||||
const float scaled = (magnitude_clamped - deadzone) / (32767.0f - deadzone);
|
||||
|
||||
// normalize
|
||||
const float normalized_x = (x_f / magnitude) * scaled;
|
||||
const float normalized_y = (y_f / magnitude) * scaled;
|
||||
|
||||
// convert range to [0, 1] with 0.5 as center
|
||||
out_x = std::clamp((normalized_x + 1.f) / 2.f, 0.f, 1.f);
|
||||
out_y = std::clamp((normalized_y + 1.f) / 2.f, 0.f, 1.f);
|
||||
}
|
||||
|
||||
void XInputManager::get_state(uint8_t player, XINPUT_GAMEPAD_STATE_NORMALIZED &state) {
|
||||
state = {};
|
||||
state.sThumbLX = 0.5f;
|
||||
@@ -233,7 +276,7 @@ XInputSetState(
|
||||
return;
|
||||
}
|
||||
|
||||
XINPUT_STATE x;
|
||||
XINPUT_STATE x = {};
|
||||
if (XInputGetState_addr(player, &x) != ERROR_SUCCESS) {
|
||||
return;
|
||||
}
|
||||
@@ -242,106 +285,83 @@ XInputSetState(
|
||||
state.bLeftTrigger = x.Gamepad.bLeftTrigger / 255.0f;
|
||||
state.bRightTrigger = x.Gamepad.bRightTrigger / 255.0f;
|
||||
|
||||
// left stick circular dead zone
|
||||
{
|
||||
const auto x_raw = x.Gamepad.sThumbLX;
|
||||
const auto y_raw = -x.Gamepad.sThumbLY; // flip to make [down = positive]
|
||||
const float magnitude = sqrtf(x_raw * x_raw + y_raw * y_raw);
|
||||
if (magnitude > XINPUT_GAMEPAD_LEFT_THUMB_DEADZONE) {
|
||||
const float scaled =
|
||||
(magnitude - XINPUT_GAMEPAD_LEFT_THUMB_DEADZONE) /
|
||||
(32767.0f - XINPUT_GAMEPAD_LEFT_THUMB_DEADZONE);
|
||||
// apply circular deadzone logic to left stick
|
||||
normalize_stick(
|
||||
x.Gamepad.sThumbLX,
|
||||
x.Gamepad.sThumbLY,
|
||||
static_cast<float>(XINPUT_GAMEPAD_LEFT_THUMB_DEADZONE),
|
||||
state.sThumbLX,
|
||||
state.sThumbLY);
|
||||
|
||||
state.sThumbLX = (x_raw / magnitude) * scaled;
|
||||
state.sThumbLY = (y_raw / magnitude) * scaled;
|
||||
|
||||
// normalize to [0, 1]
|
||||
state.sThumbLX = std::clamp((state.sThumbLX + 1.f) / 2.f, 0.f, 1.f);
|
||||
state.sThumbLY = std::clamp((state.sThumbLY + 1.f) / 2.f, 0.f, 1.f);
|
||||
} else {
|
||||
// within deadzone
|
||||
state.sThumbLX = 0.5f;
|
||||
state.sThumbLY = 0.5f;
|
||||
}
|
||||
}
|
||||
|
||||
// right stick circular dead zone
|
||||
{
|
||||
const auto x_raw = x.Gamepad.sThumbRX;
|
||||
const auto y_raw = -x.Gamepad.sThumbRY; // flip to make [down = positive]
|
||||
const float magnitude = sqrtf(x_raw * x_raw + y_raw * y_raw);
|
||||
if (magnitude > XINPUT_GAMEPAD_RIGHT_THUMB_DEADZONE) {
|
||||
const float scaled =
|
||||
(magnitude - XINPUT_GAMEPAD_RIGHT_THUMB_DEADZONE) /
|
||||
(32767.0f - XINPUT_GAMEPAD_RIGHT_THUMB_DEADZONE);
|
||||
|
||||
state.sThumbRX = (x_raw / magnitude) * scaled;
|
||||
state.sThumbRY = (y_raw / magnitude) * scaled;
|
||||
|
||||
// normalize to [0, 1]
|
||||
state.sThumbRX = std::clamp((state.sThumbRX + 1.f) / 2.f, 0.f, 1.f);
|
||||
state.sThumbRY = std::clamp((state.sThumbRY + 1.f) / 2.f, 0.f, 1.f);
|
||||
} else {
|
||||
// within deadzone
|
||||
state.sThumbRX = 0.5f;
|
||||
state.sThumbRY = 0.5f;
|
||||
}
|
||||
}
|
||||
// apply circular deadzone logic to right stick
|
||||
normalize_stick(
|
||||
x.Gamepad.sThumbRX,
|
||||
x.Gamepad.sThumbRY,
|
||||
static_cast<float>(XINPUT_GAMEPAD_RIGHT_THUMB_DEADZONE),
|
||||
state.sThumbRX,
|
||||
state.sThumbRY);
|
||||
}
|
||||
|
||||
bool XInputManager::is_button_pressed(uint8_t player, XInputButtonEnum button) {
|
||||
XINPUT_GAMEPAD_STATE_NORMALIZED state;
|
||||
bool XInputManager::is_button_pressed(uint8_t player, XInputButtonEnum button, XINPUT_GAMEPAD_STATE_NORMALIZED *state_in) {
|
||||
XINPUT_GAMEPAD_STATE_NORMALIZED state_on_stack;
|
||||
XINPUT_GAMEPAD_STATE_NORMALIZED *state;
|
||||
|
||||
if (state_in) {
|
||||
state = state_in;
|
||||
} else {
|
||||
state = &state_on_stack;
|
||||
get_state(player, *state);
|
||||
}
|
||||
|
||||
get_state(player, state);
|
||||
switch (button) {
|
||||
case XInputButtonEnum::DPAD_UP:
|
||||
return (state.wButtons & XINPUT_GAMEPAD_DPAD_UP) != 0;
|
||||
return (state->wButtons & XINPUT_GAMEPAD_DPAD_UP) != 0;
|
||||
case XInputButtonEnum::DPAD_DOWN:
|
||||
return (state.wButtons & XINPUT_GAMEPAD_DPAD_DOWN) != 0;
|
||||
return (state->wButtons & XINPUT_GAMEPAD_DPAD_DOWN) != 0;
|
||||
case XInputButtonEnum::DPAD_LEFT:
|
||||
return (state.wButtons & XINPUT_GAMEPAD_DPAD_LEFT) != 0;
|
||||
return (state->wButtons & XINPUT_GAMEPAD_DPAD_LEFT) != 0;
|
||||
case XInputButtonEnum::DPAD_RIGHT:
|
||||
return (state.wButtons & XINPUT_GAMEPAD_DPAD_RIGHT) != 0;
|
||||
return (state->wButtons & XINPUT_GAMEPAD_DPAD_RIGHT) != 0;
|
||||
case XInputButtonEnum::START:
|
||||
return (state.wButtons & XINPUT_GAMEPAD_START) != 0;
|
||||
return (state->wButtons & XINPUT_GAMEPAD_START) != 0;
|
||||
case XInputButtonEnum::BACK:
|
||||
return (state.wButtons & XINPUT_GAMEPAD_BACK) != 0;
|
||||
return (state->wButtons & XINPUT_GAMEPAD_BACK) != 0;
|
||||
case XInputButtonEnum::LEFT_STICK:
|
||||
return (state.wButtons & XINPUT_GAMEPAD_LEFT_THUMB) != 0;
|
||||
return (state->wButtons & XINPUT_GAMEPAD_LEFT_THUMB) != 0;
|
||||
case XInputButtonEnum::RIGHT_STICK:
|
||||
return (state.wButtons & XINPUT_GAMEPAD_RIGHT_THUMB) != 0;
|
||||
return (state->wButtons & XINPUT_GAMEPAD_RIGHT_THUMB) != 0;
|
||||
case XInputButtonEnum::LEFT_SHOULDER:
|
||||
return (state.wButtons & XINPUT_GAMEPAD_LEFT_SHOULDER) != 0;
|
||||
return (state->wButtons & XINPUT_GAMEPAD_LEFT_SHOULDER) != 0;
|
||||
case XInputButtonEnum::RIGHT_SHOULDER:
|
||||
return (state.wButtons & XINPUT_GAMEPAD_RIGHT_SHOULDER) != 0;
|
||||
return (state->wButtons & XINPUT_GAMEPAD_RIGHT_SHOULDER) != 0;
|
||||
case XInputButtonEnum::BUTTON_A:
|
||||
return (state.wButtons & XINPUT_GAMEPAD_A) != 0;
|
||||
return (state->wButtons & XINPUT_GAMEPAD_A) != 0;
|
||||
case XInputButtonEnum::BUTTON_B:
|
||||
return (state.wButtons & XINPUT_GAMEPAD_B) != 0;
|
||||
return (state->wButtons & XINPUT_GAMEPAD_B) != 0;
|
||||
case XInputButtonEnum::BUTTON_X:
|
||||
return (state.wButtons & XINPUT_GAMEPAD_X) != 0;
|
||||
return (state->wButtons & XINPUT_GAMEPAD_X) != 0;
|
||||
case XInputButtonEnum::BUTTON_Y:
|
||||
return (state.wButtons & XINPUT_GAMEPAD_Y) != 0;
|
||||
return (state->wButtons & XINPUT_GAMEPAD_Y) != 0;
|
||||
case XInputButtonEnum::LEFT_TRIGGER:
|
||||
return state.bLeftTrigger >= XINPUT_GAMEPAD_TRIGGER_THRESHOLD;
|
||||
return state->bLeftTrigger >= XINPUT_GAMEPAD_TRIGGER_THRESHOLD;
|
||||
case XInputButtonEnum::RIGHT_TRIGGER:
|
||||
return state.bRightTrigger >= XINPUT_GAMEPAD_TRIGGER_THRESHOLD;
|
||||
return state->bRightTrigger >= XINPUT_GAMEPAD_TRIGGER_THRESHOLD;
|
||||
case XInputButtonEnum::LEFT_STICK_UP:
|
||||
return state.sThumbLY > 0.6f;
|
||||
return state->sThumbLY < (0.5f - GAMEPAD_THUMB_DIGITAL_THRESHOLD);
|
||||
case XInputButtonEnum::LEFT_STICK_DOWN:
|
||||
return state.sThumbLY < 0.4f;
|
||||
return state->sThumbLY > (0.5f + GAMEPAD_THUMB_DIGITAL_THRESHOLD);
|
||||
case XInputButtonEnum::LEFT_STICK_LEFT:
|
||||
return state.sThumbLX < 0.4f;
|
||||
return state->sThumbLX < (0.5f - GAMEPAD_THUMB_DIGITAL_THRESHOLD);
|
||||
case XInputButtonEnum::LEFT_STICK_RIGHT:
|
||||
return state.sThumbLX > 0.6f;
|
||||
return state->sThumbLX > (0.5f + GAMEPAD_THUMB_DIGITAL_THRESHOLD);
|
||||
case XInputButtonEnum::RIGHT_STICK_UP:
|
||||
return state.sThumbRY > 0.6f;
|
||||
return state->sThumbRY < (0.5f - GAMEPAD_THUMB_DIGITAL_THRESHOLD);
|
||||
case XInputButtonEnum::RIGHT_STICK_DOWN:
|
||||
return state.sThumbRY < 0.4f;
|
||||
return state->sThumbRY > (0.5f + GAMEPAD_THUMB_DIGITAL_THRESHOLD);
|
||||
case XInputButtonEnum::RIGHT_STICK_LEFT:
|
||||
return state.sThumbRX < 0.4f;
|
||||
return state->sThumbRX < (0.5f - GAMEPAD_THUMB_DIGITAL_THRESHOLD);
|
||||
case XInputButtonEnum::RIGHT_STICK_RIGHT:
|
||||
return state.sThumbRX > 0.6f;
|
||||
return state->sThumbRX > (0.5f + GAMEPAD_THUMB_DIGITAL_THRESHOLD);
|
||||
default:
|
||||
break;
|
||||
}
|
||||
@@ -374,11 +394,49 @@ XInputSetState(
|
||||
}
|
||||
|
||||
bool XInputManager::get_any_button_pressed(XINPUT_NEW_BUTTON &button) {
|
||||
|
||||
constexpr std::array<XInputButtonEnum, static_cast<size_t>(XInputButtonEnum::COUNT)> button_priority = {
|
||||
// the ordering here is important; we want to check buttons first, then dpad, then analog
|
||||
// this is to help with cases like DDR pads that output multiple at the same time for arrows
|
||||
|
||||
// actual buttons
|
||||
XInputButtonEnum::BUTTON_A,
|
||||
XInputButtonEnum::BUTTON_B,
|
||||
XInputButtonEnum::BUTTON_X,
|
||||
XInputButtonEnum::BUTTON_Y,
|
||||
XInputButtonEnum::START,
|
||||
XInputButtonEnum::BACK,
|
||||
XInputButtonEnum::LEFT_STICK,
|
||||
XInputButtonEnum::RIGHT_STICK,
|
||||
XInputButtonEnum::LEFT_SHOULDER,
|
||||
XInputButtonEnum::RIGHT_SHOULDER,
|
||||
|
||||
// dpad
|
||||
XInputButtonEnum::DPAD_UP,
|
||||
XInputButtonEnum::DPAD_DOWN,
|
||||
XInputButtonEnum::DPAD_LEFT,
|
||||
XInputButtonEnum::DPAD_RIGHT,
|
||||
|
||||
// analog values that can be used as buttons
|
||||
XInputButtonEnum::LEFT_TRIGGER,
|
||||
XInputButtonEnum::RIGHT_TRIGGER,
|
||||
XInputButtonEnum::LEFT_STICK_UP,
|
||||
XInputButtonEnum::LEFT_STICK_DOWN,
|
||||
XInputButtonEnum::LEFT_STICK_LEFT,
|
||||
XInputButtonEnum::LEFT_STICK_RIGHT,
|
||||
XInputButtonEnum::RIGHT_STICK_UP,
|
||||
XInputButtonEnum::RIGHT_STICK_DOWN,
|
||||
XInputButtonEnum::RIGHT_STICK_LEFT,
|
||||
XInputButtonEnum::RIGHT_STICK_RIGHT,
|
||||
};
|
||||
|
||||
for (uint8_t player = 0; player < XUSER_MAX_COUNT; player++) {
|
||||
XINPUT_GAMEPAD_STATE_NORMALIZED state;
|
||||
get_state(player, state);
|
||||
for (uint16_t b = 0; b < static_cast<uint16_t>(XInputButtonEnum::COUNT); b++) {
|
||||
if (is_button_pressed(player, static_cast<XInputButtonEnum>(b))) {
|
||||
if (is_button_pressed(player, button_priority[b], &state)) {
|
||||
button.player = player;
|
||||
button.button = static_cast<XInputButtonEnum>(b);
|
||||
button.button = button_priority[b];
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,6 +31,8 @@ namespace xinput {
|
||||
float sThumbRY;
|
||||
};
|
||||
|
||||
// the order of this enum is used for vkey mapping (saved in config file)
|
||||
// therefore it can never be changed
|
||||
enum class XInputButtonEnum : uint16_t {
|
||||
// actual buttons
|
||||
DPAD_UP,
|
||||
@@ -102,7 +104,7 @@ namespace xinput {
|
||||
~XInputManager();
|
||||
void stop();
|
||||
std::vector<uint8_t> get_available_players();
|
||||
bool is_button_pressed(uint8_t player, XInputButtonEnum button);
|
||||
bool is_button_pressed(uint8_t player, XInputButtonEnum button, XINPUT_GAMEPAD_STATE_NORMALIZED *state_in=nullptr);
|
||||
float get_analog_state(uint8_t player, XInputAnalogEnum analog);
|
||||
bool get_any_button_pressed(XINPUT_NEW_BUTTON &button);
|
||||
void set_output_state(uint8_t player, XInputOutputEnum output, float value);
|
||||
|
||||
@@ -82,14 +82,14 @@ bool fileutils::verify_header_pe(const std::filesystem::path &file_path) {
|
||||
if (!valid) {
|
||||
log_fatal("fileutils",
|
||||
"{} (32 bit) can't be loaded using spice64.exe - please use spice.exe for this game.",
|
||||
file_path.string());
|
||||
file_path);
|
||||
}
|
||||
#else
|
||||
valid = dll_file_header->Machine == IMAGE_FILE_MACHINE_I386;
|
||||
if (!valid) {
|
||||
log_fatal("fileutils",
|
||||
"{} (64 bit) can't be loaded using spice.exe - please use spice64.exe for this game.",
|
||||
file_path.string());
|
||||
file_path);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
@@ -156,9 +156,9 @@ bool fileutils::dir_create_log(const std::string_view &module, const std::filesy
|
||||
auto ret = std::filesystem::create_directory(dir_path, err);
|
||||
|
||||
if (err) {
|
||||
log_warning(module, "failed to create directory '{}': {}", dir_path.string(), err.message());
|
||||
log_warning(module, "failed to create directory '{}': {}", dir_path, err.message());
|
||||
} else if (ret) {
|
||||
log_misc(module, "created directory '{}'", dir_path.string());
|
||||
log_misc(module, "created directory '{}'", dir_path);
|
||||
}
|
||||
|
||||
return ret && !err;
|
||||
@@ -178,39 +178,14 @@ bool fileutils::dir_create_recursive_log(const std::string_view &module, const s
|
||||
auto ret = std::filesystem::create_directories(dir_path, err);
|
||||
|
||||
if (err) {
|
||||
log_warning(module, "failed to create directory (recursive) '{}': {}", dir_path.string(), err.message());
|
||||
log_warning(module, "failed to create directory (recursive) '{}': {}", dir_path, err.message());
|
||||
} else if (ret) {
|
||||
log_misc(module, "created directory (recursive) '{}'", dir_path.string());
|
||||
log_misc(module, "created directory (recursive) '{}'", dir_path);
|
||||
}
|
||||
|
||||
return ret && !err;
|
||||
}
|
||||
|
||||
void fileutils::dir_scan(const std::string &path, std::vector<std::string> &vec, bool recursive) {
|
||||
|
||||
// check directory
|
||||
if (std::filesystem::exists(path) && std::filesystem::is_directory(path)) {
|
||||
if (recursive) {
|
||||
for (const auto &entry : std::filesystem::recursive_directory_iterator(path)) {
|
||||
if (!std::filesystem::is_directory(entry)) {
|
||||
auto path = entry.path().string();
|
||||
vec.emplace_back(std::move(path));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for (const auto &entry : std::filesystem::directory_iterator(path)) {
|
||||
if (!std::filesystem::is_directory(entry)) {
|
||||
auto path = entry.path().string();
|
||||
vec.emplace_back(std::move(path));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// determinism
|
||||
std::sort(vec.begin(), vec.end());
|
||||
}
|
||||
|
||||
bool fileutils::text_write(const std::filesystem::path &file_path, std::string text) {
|
||||
std::ofstream out(file_path, std::ios::out | std::ios::binary);
|
||||
if (out) {
|
||||
@@ -296,22 +271,24 @@ std::filesystem::path fileutils::get_config_file_path(const std::string module,
|
||||
|
||||
bool fileutils::write_config_file(const std::string_view &module, const std::filesystem::path path, std::string text) {
|
||||
// attempt to undo %appdata% expansion to hide user name
|
||||
const auto appdata = std::filesystem::path(_wgetenv(L"APPDATA")).string();
|
||||
auto censored = path.string();
|
||||
const auto appdata = std::filesystem::path(_wgetenv(L"APPDATA")).wstring();
|
||||
auto censored = path.wstring();
|
||||
const auto substr_offset = censored.find(appdata);
|
||||
if (substr_offset != std::string::npos) {
|
||||
censored.replace(substr_offset, appdata.length(), "%appdata%");
|
||||
censored.replace(substr_offset, appdata.length(), L"%appdata%");
|
||||
}
|
||||
|
||||
auto censored_display = fmt::detail::to_utf8<wchar_t>(censored, fmt::detail::to_utf8_error_policy::replace);
|
||||
|
||||
// create directory path up to where the config file lives
|
||||
if (!path.parent_path().empty() && !std::filesystem::exists(path.parent_path())) {
|
||||
log_misc(module, "creating directory path to config file: {}", censored);
|
||||
log_misc(module, "creating directory path to config file: {}", censored_display);
|
||||
if (!fileutils::dir_create_recursive(path.parent_path())) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// save file
|
||||
log_info(module, "saving config file: {}", censored);
|
||||
log_info(module, "saving config file: {}", censored_display);
|
||||
return fileutils::text_write(path, text);
|
||||
}
|
||||
@@ -26,7 +26,6 @@ namespace fileutils {
|
||||
bool dir_create_log(const std::string_view &module, const std::filesystem::path &dir_path);
|
||||
bool dir_create_recursive(const std::filesystem::path &dir_path);
|
||||
bool dir_create_recursive_log(const std::string_view &module, const std::filesystem::path &dir_path);
|
||||
void dir_scan(const std::string &path, std::vector<std::string> &vec, bool recursive);
|
||||
|
||||
// IO
|
||||
bool text_write(const std::filesystem::path &file_path, std::string text);
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#include "libutils.h"
|
||||
|
||||
#include <cstring>
|
||||
#include <windows.h>
|
||||
#include <psapi.h>
|
||||
#include <shlwapi.h>
|
||||
@@ -38,7 +39,7 @@ std::filesystem::path libutils::module_file_name(HMODULE module) {
|
||||
return std::filesystem::path(std::move(buf));
|
||||
}
|
||||
|
||||
static inline void load_library_fail(const std::string &file_name, bool fatal) {
|
||||
static inline void load_library_fail(const std::filesystem::path &file_name, bool fatal) {
|
||||
std::string info_str { fmt::format(
|
||||
"DLL failed to load - this is a common error. Please carefully read ALL of the following steps for a fix:\n"
|
||||
" 1. Confirm if the file ({}) exists on the disk and check the file permissions.\n"
|
||||
@@ -76,9 +77,9 @@ HMODULE libutils::load_library(const std::filesystem::path &path, bool fatal) {
|
||||
HMODULE module = LoadLibraryW(path.c_str());
|
||||
|
||||
if (!module) {
|
||||
log_warning("libutils", "'{}' couldn't be loaded: {}", path.string(), get_last_error_string());
|
||||
log_warning("libutils", "'{}' couldn't be loaded: {}", path, get_last_error_string());
|
||||
dependencies::walk(path);
|
||||
load_library_fail(path.filename().string(), fatal);
|
||||
load_library_fail(path.filename(), fatal);
|
||||
}
|
||||
|
||||
return module;
|
||||
@@ -236,7 +237,7 @@ intptr_t libutils::rva2offset(const std::filesystem::path &path, intptr_t rva) {
|
||||
HANDLE dll_mapping = CreateFileMappingW(dll_file, nullptr, PAGE_READONLY, 0, 0, nullptr);
|
||||
if (!dll_mapping) {
|
||||
CloseHandle(dll_file);
|
||||
log_warning("libutils", "could not create file mapping for {}", path.string());
|
||||
log_warning("libutils", "could not create file mapping for {}", path);
|
||||
return -1;
|
||||
}
|
||||
|
||||
@@ -245,7 +246,7 @@ intptr_t libutils::rva2offset(const std::filesystem::path &path, intptr_t rva) {
|
||||
if (!dll_file_base) {
|
||||
CloseHandle(dll_file);
|
||||
CloseHandle(dll_mapping);
|
||||
log_warning("libutils", "could not map view of file for {}", path.string());
|
||||
log_warning("libutils", "could not map view of file for {}", path);
|
||||
return -1;
|
||||
}
|
||||
|
||||
@@ -306,7 +307,7 @@ intptr_t libutils::offset2rva(const std::filesystem::path &path, intptr_t offset
|
||||
// create file mapping
|
||||
HANDLE dll_mapping = CreateFileMappingW(dll_file, nullptr, PAGE_READONLY, 0, 0, nullptr);
|
||||
if (!dll_mapping) {
|
||||
log_warning("libutils", "could not create file mapping for {}: {}", path.string(), get_last_error_string());
|
||||
log_warning("libutils", "could not create file mapping for {}: {}", path, get_last_error_string());
|
||||
CloseHandle(dll_file);
|
||||
return -1;
|
||||
}
|
||||
@@ -314,7 +315,7 @@ intptr_t libutils::offset2rva(const std::filesystem::path &path, intptr_t offset
|
||||
// map view of file
|
||||
LPVOID dll_file_base = MapViewOfFile(dll_mapping, FILE_MAP_READ, 0, 0, 0);
|
||||
if (!dll_file_base) {
|
||||
log_warning("libutils", "could not map view of file for {}: {}", path.string(), get_last_error_string());
|
||||
log_warning("libutils", "could not map view of file for {}: {}", path, get_last_error_string());
|
||||
CloseHandle(dll_file);
|
||||
CloseHandle(dll_mapping);
|
||||
return -1;
|
||||
@@ -344,9 +345,9 @@ void libutils::check_duplicate_dlls() {
|
||||
|
||||
for (const auto &file : std::filesystem::directory_iterator(MODULE_PATH)) {
|
||||
const auto &filename = file.path().filename();
|
||||
const auto extension = strtolower(filename.extension().string());
|
||||
const auto extension = filename.extension().wstring();
|
||||
|
||||
if (extension == ".dll" &&
|
||||
if (wcsicmp(extension.c_str(), L".dll") == 0 &&
|
||||
fileutils::file_exists(spice_bin_path / filename)) {
|
||||
log_warning(
|
||||
"libutils",
|
||||
@@ -361,9 +362,9 @@ void libutils::check_duplicate_dlls() {
|
||||
"this has unintended consequences and may crash your game!\n"
|
||||
"resolve the conflict by deleting the stale copy of the DLL\n"
|
||||
"-------------------------------------------------------------------\n\n\n",
|
||||
filename.string(),
|
||||
(spice_bin_path / filename).string(),
|
||||
(MODULE_PATH / filename).string());
|
||||
filename,
|
||||
(spice_bin_path / filename),
|
||||
(MODULE_PATH / filename));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -384,22 +385,22 @@ void libutils::warn_if_dll_exists(const std::string &file_name) {
|
||||
|
||||
void libutils::print_dll_info(std::filesystem::path filename) {
|
||||
DWORD handle;
|
||||
const auto size = GetFileVersionInfoSizeA(filename.string().c_str(), &handle);
|
||||
const auto size = GetFileVersionInfoSizeW(filename.wstring().c_str(), &handle);
|
||||
if (size == 0) {
|
||||
log_debug(
|
||||
"libutils",
|
||||
"GetFileVersionInfoSizeA failed for {}: {}",
|
||||
filename.filename().string(),
|
||||
filename.filename(),
|
||||
get_last_error_string());
|
||||
return;
|
||||
}
|
||||
|
||||
auto data = util::make_unique_plain<VOID>(size);
|
||||
if (!GetFileVersionInfoA(filename.string().c_str(), handle, size, data.get())) {
|
||||
if (!GetFileVersionInfoW(filename.wstring().c_str(), handle, size, data.get())) {
|
||||
log_debug(
|
||||
"libutils",
|
||||
"GetFileVersionInfoA failed for {}: {}",
|
||||
filename.filename().string(),
|
||||
filename.filename(),
|
||||
get_last_error_string());
|
||||
return;
|
||||
}
|
||||
@@ -414,7 +415,7 @@ void libutils::print_dll_info(std::filesystem::path filename) {
|
||||
log_debug(
|
||||
"libutils",
|
||||
"VerQueryValueA failed for {}: {}",
|
||||
filename.filename().string(),
|
||||
filename.filename(),
|
||||
get_last_error_string());
|
||||
return;
|
||||
}
|
||||
@@ -422,7 +423,7 @@ void libutils::print_dll_info(std::filesystem::path filename) {
|
||||
log_debug(
|
||||
"libutils",
|
||||
"VerQueryValueA returned invalid results for {}",
|
||||
filename.filename().string());
|
||||
filename.filename());
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -457,7 +458,7 @@ void libutils::print_dll_info(std::filesystem::path filename) {
|
||||
"libutils",
|
||||
"VerQueryValueA({}) failed for {}: {}",
|
||||
subBlock,
|
||||
filename.filename().string(),
|
||||
filename.filename(),
|
||||
get_last_error_string());
|
||||
return "";
|
||||
};
|
||||
@@ -469,7 +470,7 @@ void libutils::print_dll_info(std::filesystem::path filename) {
|
||||
log_info(
|
||||
"libutils",
|
||||
"DLL info for {}: CompanyName = {}, ProductName = {}, Version = {}",
|
||||
filename.filename().string(),
|
||||
filename.filename(),
|
||||
company_name.empty() ? "?" : company_name,
|
||||
product_name.empty() ? "?" : product_name,
|
||||
version_str.empty() ? "?" : version_str);
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
|
||||
#include "external/fmt/include/fmt/format.h"
|
||||
#include "external/fmt/include/fmt/compile.h"
|
||||
#include "external/fmt/include/fmt/std.h"
|
||||
|
||||
#include "launcher/launcher.h"
|
||||
#include "launcher/logger.h"
|
||||
|
||||
@@ -275,7 +275,7 @@ intptr_t replace_pattern(HMODULE module, const std::string &signature,
|
||||
bool get_pe_identifier(const std::filesystem::path& dll_path, uint32_t* time_date_stamp, uint32_t* address_of_entry_point) {
|
||||
std::ifstream file(dll_path, std::ios::binary);
|
||||
if (!file) {
|
||||
log_warning("sigscan", "Failed to open file: {}", dll_path.string().c_str());
|
||||
log_warning("sigscan", "Failed to open file: {}", dll_path);
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -283,7 +283,7 @@ bool get_pe_identifier(const std::filesystem::path& dll_path, uint32_t* time_dat
|
||||
IMAGE_DOS_HEADER dos_header;
|
||||
file.read(reinterpret_cast<char*>(&dos_header), sizeof(dos_header));
|
||||
if (dos_header.e_magic != IMAGE_DOS_SIGNATURE) {
|
||||
log_warning("sigscan", "Invalid DOS signature: {}", dll_path.string().c_str());
|
||||
log_warning("sigscan", "Invalid DOS signature: {}", dll_path);
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -294,7 +294,7 @@ bool get_pe_identifier(const std::filesystem::path& dll_path, uint32_t* time_dat
|
||||
IMAGE_NT_HEADERS nt_headers;
|
||||
file.read(reinterpret_cast<char*>(&nt_headers), sizeof(nt_headers));
|
||||
if (nt_headers.Signature != IMAGE_NT_SIGNATURE) {
|
||||
log_warning("sigscan", "Invalid NT signature: {}", dll_path.string().c_str());
|
||||
log_warning("sigscan", "Invalid NT signature: {}", dll_path);
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user