mirror of
https://github.com/spice2x/spice2x.github.io.git
synced 2026-08-01 22:30:42 -07:00
Compare commits
60 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 | |||
| 15bfffa1d3 | |||
| 18a9a47c33 | |||
| 6838e69ebe | |||
| 26b3b64794 | |||
| aadd2bdd59 | |||
| d0e24e31ca | |||
| 4fb7f20c7b | |||
| 6ddae70c5a | |||
| b38160d6c2 | |||
| 231c998cc0 | |||
| 127d31a85c | |||
| a8419bbaa0 | |||
| 0fc430ceb3 | |||
| e59a50b64c | |||
| 2042b629e9 | |||
| 440b9ed749 | |||
| 9936088286 | |||
| 4d8a6c1952 | |||
| af69cf9251 | |||
| 1d5550b1e8 | |||
| ec3f6639b7 | |||
| 727b99effd | |||
| 8afff3aaca | |||
| b281517429 | |||
| 54ec9f272e | |||
| a159691097 | |||
| ca70f7b19c | |||
| 7b4227d2a9 | |||
| f89efa2558 | |||
| 6f4824b648 | |||
| 45debeb5c9 | |||
| a01f452eba | |||
| e452734bc1 | |||
| d1779b93fa | |||
| fe9aac7e29 | |||
| f3d887ebcf |
@@ -383,6 +383,7 @@ set(SOURCE_FILES ${SOURCE_FILES}
|
||||
games/shared/twtouch.cpp
|
||||
games/popn/popn.cpp
|
||||
games/popn/io.cpp
|
||||
games/popn/bi3a_hook.cpp
|
||||
games/bbc/bbc.cpp
|
||||
games/bbc/io.cpp
|
||||
games/hpm/hpm.cpp
|
||||
@@ -582,6 +583,7 @@ set(SOURCE_FILES ${SOURCE_FILES}
|
||||
overlay/windows/log.cpp
|
||||
overlay/windows/midi.cpp
|
||||
overlay/windows/patch_manager.cpp
|
||||
overlay/windows/popn_sub.cpp
|
||||
overlay/windows/wnd_manager.cpp
|
||||
|
||||
# rawinput
|
||||
@@ -596,6 +598,8 @@ set(SOURCE_FILES ${SOURCE_FILES}
|
||||
rawinput/smxstage.h
|
||||
rawinput/smxdedicab.cpp
|
||||
rawinput/smxdedicab.h
|
||||
rawinput/xinput.cpp
|
||||
rawinput/xinput.h
|
||||
|
||||
# reader
|
||||
reader/reader.cpp
|
||||
|
||||
+122
-20
@@ -35,24 +35,33 @@ struct ICCA_STATUS_LA9 {
|
||||
static_assert(sizeof(struct ICCA_STATUS) == 24, "ICCA_STATUS must be 24 bytes");
|
||||
|
||||
enum ICCA_WORKFLOW {
|
||||
STEP,
|
||||
SLEEP,
|
||||
START,
|
||||
INIT,
|
||||
READY,
|
||||
GET_USERID,
|
||||
ACTIVE,
|
||||
EJECT,
|
||||
EJECT_CHECK,
|
||||
END,
|
||||
CLOSE_EJECT,
|
||||
CLOSE_E_CHK,
|
||||
CLOSE_END,
|
||||
ERR_GETUID = -2
|
||||
STEP = 0,
|
||||
SLEEP = 1,
|
||||
START = 2,
|
||||
INIT = 3,
|
||||
READY = 4,
|
||||
GET_USERID = 5,
|
||||
ACTIVE = 6,
|
||||
EJECT = 7,
|
||||
EJECT_CHECK = 8,
|
||||
END = 9,
|
||||
CLOSE_EJECT = 10,
|
||||
CLOSE_E_CHK = 11,
|
||||
CLOSE_END = 12,
|
||||
ERR_GETUID = -2,
|
||||
ERR_REMOVED = -11
|
||||
};
|
||||
|
||||
// used by RA as the status_code
|
||||
enum ICCA_STATUS_CODE {
|
||||
EMPTY = 1,
|
||||
ENGAGED = 2
|
||||
};
|
||||
|
||||
struct ICCA_UNIT {
|
||||
struct ICCA_STATUS status {};
|
||||
enum ICCA_WORKFLOW state = STEP;
|
||||
enum ICCA_WORKFLOW state_jdz = SLEEP;
|
||||
bool card_cmd_pressed = false;
|
||||
bool card_in = false;
|
||||
double card_in_time = 0.0;
|
||||
@@ -75,6 +84,11 @@ static inline int icca_get_active_count() {
|
||||
}
|
||||
|
||||
static inline int icca_get_unit_id(int unit_id) {
|
||||
// on RA the unit_id is zero-indexed
|
||||
if (avs::game::is_model("JDZ")) {
|
||||
return unit_id;
|
||||
}
|
||||
|
||||
if (icca_get_active_count() < 2)
|
||||
return 1;
|
||||
else {
|
||||
@@ -122,9 +136,15 @@ static inline void update_card(int unit_id) {
|
||||
IS_LAST_CARD_FELICA = is_card_uid_felica(unit->status.uid);
|
||||
|
||||
unit->state = acio::ICCA_COMPAT_ACTIVE ? START : ACTIVE;
|
||||
unit->state_jdz = ACTIVE;
|
||||
unit->status.error = 0;
|
||||
unit->status.front_sensor = 1;
|
||||
unit->status.rear_sensor = 1;
|
||||
} else {
|
||||
unit->state = ERR_GETUID;
|
||||
unit->state_jdz = ERR_GETUID;
|
||||
unit->status.front_sensor = 1;
|
||||
unit->status.rear_sensor = 1;
|
||||
memset(unit->status.uid, 0, 8);
|
||||
}
|
||||
unit->card_in = true;
|
||||
@@ -132,10 +152,25 @@ static inline void update_card(int unit_id) {
|
||||
} else if (unit->state == EJECT_CHECK) {
|
||||
unit->state = SLEEP;
|
||||
unit->card_in = false;
|
||||
unit->status.front_sensor = 0;
|
||||
unit->status.rear_sensor = 0;
|
||||
} else if (unit->state_jdz == READY) {
|
||||
unit->state = GET_USERID;
|
||||
unit->state_jdz = GET_USERID;
|
||||
unit->status.error = 0;
|
||||
unit->status.front_sensor = 1;
|
||||
unit->status.rear_sensor = 1;
|
||||
unit->card_in = true;
|
||||
unit->card_in_time = get_performance_seconds();
|
||||
}
|
||||
} else {
|
||||
unit->state = acio::ICCA_COMPAT_ACTIVE ? START : ACTIVE;
|
||||
if (unit->state_jdz == READY) {
|
||||
unit->state_jdz = GET_USERID;
|
||||
}
|
||||
unit->status.error = 0;
|
||||
unit->status.front_sensor = 1;
|
||||
unit->status.rear_sensor = 1;
|
||||
unit->card_in = true;
|
||||
unit->card_in_time = get_performance_seconds();
|
||||
}
|
||||
@@ -144,6 +179,12 @@ static inline void update_card(int unit_id) {
|
||||
unit->state = CLOSE_EJECT;
|
||||
if (fabs(get_performance_seconds() - unit->card_in_time) > CARD_TIMEOUT) {
|
||||
unit->card_in = false;
|
||||
memset(unit->status.uid, 0, 8);
|
||||
if (unit->state_jdz == ACTIVE || unit->state_jdz == GET_USERID) {
|
||||
unit->state_jdz = ERR_REMOVED;
|
||||
}
|
||||
unit->status.front_sensor = 0;
|
||||
unit->status.rear_sensor = 0;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -302,7 +343,11 @@ static char __cdecl ac_io_icca_get_status(void *a1, void *a2) {
|
||||
|
||||
// copy state to output buffer
|
||||
ICCA_UNIT *unit = &ICCA_UNITS[unit_id];
|
||||
unit->status.status_code = unit->state;
|
||||
if (avs::game::is_model("JDZ")) {
|
||||
unit->status.status_code = unit->card_in ? ENGAGED : EMPTY;
|
||||
} else {
|
||||
unit->status.status_code = unit->state;
|
||||
}
|
||||
memcpy(status, &unit->status, sizeof(struct ICCA_STATUS));
|
||||
|
||||
// funny workaround
|
||||
@@ -387,6 +432,11 @@ static char __cdecl ac_io_icca_req_uid(int unit_id) {
|
||||
static int __cdecl ac_io_icca_req_uid_isfinished(int unit_id, DWORD *read_state) {
|
||||
unit_id = icca_get_unit_id(unit_id);
|
||||
ICCA_UNIT *unit = &ICCA_UNITS[unit_id];
|
||||
if (avs::game::is_model("JDZ")) {
|
||||
// hack for RA - only ever seen it as this
|
||||
*read_state = 8;
|
||||
return 1;
|
||||
}
|
||||
if (unit->card_in) {
|
||||
if (fabs(get_performance_seconds() - unit->card_in_time) < CARD_TIMEOUT) {
|
||||
unit->state = END;
|
||||
@@ -403,16 +453,61 @@ static int __cdecl ac_io_icca_send_keep_alive_packet(int a1, int a2, int a3) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int __cdecl ac_io_icca_workflow_jdz(int workflow, int unit_id) {
|
||||
|
||||
unit_id = icca_get_unit_id(unit_id);
|
||||
ICCA_UNIT *unit = &ICCA_UNITS[unit_id];
|
||||
switch (workflow) {
|
||||
// RA uses "STEP" as more of a polling thing it seems
|
||||
case STEP:
|
||||
switch (unit->state_jdz) {
|
||||
case CLOSE_EJECT:
|
||||
unit->state_jdz = CLOSE_E_CHK;
|
||||
break;
|
||||
case CLOSE_E_CHK:
|
||||
unit->state_jdz = CLOSE_END;
|
||||
break;
|
||||
case CLOSE_END:
|
||||
unit->state_jdz = SLEEP;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case SLEEP:
|
||||
unit->state = SLEEP;
|
||||
break;
|
||||
case START:
|
||||
unit->state_jdz = INIT;
|
||||
break;
|
||||
case INIT:
|
||||
case READY:
|
||||
unit->state_jdz = READY;
|
||||
break;
|
||||
case CLOSE_EJECT:
|
||||
unit->state_jdz = CLOSE_EJECT;
|
||||
return CLOSE_E_CHK;
|
||||
case CLOSE_END:
|
||||
unit->state_jdz = SLEEP;
|
||||
break;
|
||||
default:
|
||||
// should probably log a warning here
|
||||
break;
|
||||
}
|
||||
return unit->state_jdz;
|
||||
}
|
||||
|
||||
static int __cdecl ac_io_icca_workflow(int workflow, int unit_id) {
|
||||
// RA uses a different workflow process for the slotted readers; treat it differently
|
||||
if (avs::game::is_model("JDZ")) {
|
||||
return ac_io_icca_workflow_jdz(workflow, unit_id);
|
||||
}
|
||||
|
||||
unit_id = icca_get_unit_id(unit_id);
|
||||
ICCA_UNIT *unit = &ICCA_UNITS[unit_id];
|
||||
switch (workflow) {
|
||||
case STEP:
|
||||
if (avs::game::is_model("JDZ"))
|
||||
unit->state = SLEEP;
|
||||
else
|
||||
unit->state = STEP;
|
||||
unit->state = STEP;
|
||||
break;
|
||||
case SLEEP:
|
||||
unit->state = SLEEP;
|
||||
@@ -441,7 +536,6 @@ static int __cdecl ac_io_icca_workflow(int workflow, int unit_id) {
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return unit->state;
|
||||
}
|
||||
|
||||
@@ -467,6 +561,14 @@ acio::ICCAModule::ICCAModule(HMODULE module, acio::HookMode hookMode) : ACIOModu
|
||||
void acio::ICCAModule::attach() {
|
||||
ACIOModule::attach();
|
||||
|
||||
// workaround for RA; ac_io_icca_cardunit_init is never called so treat like both are initialized
|
||||
if (avs::game::is_model("JDZ")) {
|
||||
ICCA_UNITS[0].initialized = true;
|
||||
ICCA_UNITS[1].initialized = true;
|
||||
|
||||
CARD_TIMEOUT = 10.0; // keep the card in the slot for longer
|
||||
}
|
||||
|
||||
// hooks
|
||||
ACIO_MODULE_HOOK(ac_io_icca_cardunit_init);
|
||||
ACIO_MODULE_HOOK(ac_io_icca_cardunit_init_isfinished);
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
#include <stdint.h>
|
||||
|
||||
#include "external/robin_hood.h"
|
||||
#include "games/popn/popn.h"
|
||||
#include "launcher/logger.h"
|
||||
#include "launcher/signal.h"
|
||||
#include "util/deferlog.h"
|
||||
@@ -1167,7 +1168,7 @@ namespace avs {
|
||||
// Ongaku Paradise
|
||||
{"arkjc9.dll", 0xA00000},
|
||||
|
||||
// KAMUNITY
|
||||
// KAMUNITY
|
||||
{"kamunity.dll", 33554432},
|
||||
};
|
||||
|
||||
@@ -1179,6 +1180,15 @@ namespace avs {
|
||||
return;
|
||||
}
|
||||
|
||||
// hack, both hello! popn and popn hc using popn.dll
|
||||
if (games::popn::is_pikapika_model()) {
|
||||
auto old_size = HEAP_SIZE;
|
||||
HEAP_SIZE = 0x20000000;
|
||||
DEFAULT_HEAP_SIZE_SET = true;
|
||||
log_info("avs-core", "updated heap size: {} -> {}", old_size, avs::core::HEAP_SIZE);
|
||||
return;
|
||||
}
|
||||
|
||||
if (HEAP_SIZE_DEFAULTS.find(dll_name) != HEAP_SIZE_DEFAULTS.end()) {
|
||||
auto old_size = HEAP_SIZE;
|
||||
|
||||
@@ -1506,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);
|
||||
}
|
||||
@@ -1747,9 +1757,9 @@ 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()),
|
||||
" fix the XML file and try again"
|
||||
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"
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1770,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());
|
||||
}
|
||||
}
|
||||
@@ -1911,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,8 +151,8 @@ 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"/>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#!/bin/bash
|
||||
#!/usr/bin/env bash
|
||||
|
||||
# set to EN-US for consistency
|
||||
LANG=en_us_8859_1
|
||||
@@ -43,10 +43,16 @@ GIT_BRANCH=$(git rev-parse --abbrev-ref HEAD 2> /dev/null || echo "none")
|
||||
GIT_HEAD=$(git rev-parse HEAD || echo "none")
|
||||
TOOLCHAIN_32="${TOOLCHAIN_32:-"/usr/share/mingw/toolchain-i686-w64-mingw32.cmake"}"
|
||||
TOOLCHAIN_64="${TOOLCHAIN_64:-"/usr/share/mingw/toolchain-x86_64-w64-mingw32.cmake"}"
|
||||
TOOLCHAIN_WINXP_32="${TOOLCHAIN_WINXP_32:-"/opt/llvm-mingw-xp/toolchain-files/cmake/i686-mingw32-clang.cmake"}"
|
||||
TOOLCHAIN_WINXP_64="${TOOLCHAIN_WINXP_64:-"/opt/llvm-mingw-xp/toolchain-files/cmake/x86_64-mingw32-clang.cmake"}"
|
||||
BUILDDIR_32_RELEASE="./cmake-build-release-32"
|
||||
BUILDDIR_32_DEBUG="./cmake-build-debug-32"
|
||||
BUILDDIR_64_RELEASE="./cmake-build-release-64"
|
||||
BUILDDIR_64_DEBUG="./cmake-build-debug-64"
|
||||
BUILDDIR_WINXP_32_RELEASE="./cmake-build-release-winxp-32"
|
||||
BUILDDIR_WINXP_32_DEBUG="./cmake-build-debug-winxp-32"
|
||||
BUILDDIR_WINXP_64_RELEASE="./cmake-build-release-winxp-64"
|
||||
BUILDDIR_WINXP_64_DEBUG="./cmake-build-debug-winxp-64"
|
||||
DEBUG=0
|
||||
OUTDIR="./bin/spice2x"
|
||||
OUTDIR_EXTRAS="./bin/spice2x/extras"
|
||||
@@ -64,19 +70,36 @@ DIST_NAME_EXTRAS="spice2x-$(date +%y)-$(date +%m)-$(date +%d)-full.zip"
|
||||
DIST_COMMENT=${DIST_NAME}$'\n'"$GIT_BRANCH - $GIT_HEAD"$'\nThank you for playing.'
|
||||
TARGETS_32="spicetools_stubs_kbt spicetools_stubs_kld spicetools_cfg spicetools_cfg_linux spicetools_spice spicetools_spice_laa spicetools_spice_linux spicetools_stubs_cpusbxpkm"
|
||||
TARGETS_64="spicetools_stubs_kbt64 spicetools_stubs_kld64 spicetools_stubs_nvEncodeAPI64 spicetools_stubs_nvcuvid spicetools_stubs_nvcuda spicetools_spice64 spicetools_spice64_linux"
|
||||
TARGETS_XP32="spicetools_cfg spicetools_spice"
|
||||
TARGETS_XP64="spicetools_spice64"
|
||||
|
||||
# determine build type
|
||||
BUILD_TYPE="Release"
|
||||
BUILDDIR_32=${BUILDDIR_32_RELEASE}
|
||||
BUILDDIR_64=${BUILDDIR_64_RELEASE}
|
||||
BUILDDIR_WINXP_32=${BUILDDIR_WINXP_32_RELEASE}
|
||||
BUILDDIR_WINXP_64=${BUILDDIR_WINXP_64_RELEASE}
|
||||
if ((DEBUG > 0))
|
||||
then
|
||||
BUILD_TYPE="Debug"
|
||||
BUILDDIR_32=${BUILDDIR_32_DEBUG}
|
||||
BUILDDIR_64=${BUILDDIR_64_DEBUG}
|
||||
BUILDDIR_WINXP_32=${BUILDDIR_WINXP_32_DEBUG}
|
||||
BUILDDIR_WINXP_64=${BUILDDIR_WINXP_64_DEBUG}
|
||||
DIST_NAME=$(echo "${DIST_NAME}" | sed 's/\.[^.]*$/-dbg&/')
|
||||
fi
|
||||
|
||||
# is the XP-compatible toolchain installed?
|
||||
XP_MUST_BUILD=0
|
||||
BUILD_XP=0
|
||||
if [ -f "$TOOLCHAIN_WINXP_32" ] && [ -f "$TOOLCHAIN_WINXP_64" ]; then
|
||||
BUILD_XP=1;
|
||||
elif ((XP_MUST_BUILD > 0))
|
||||
then
|
||||
echo "WinXP toolchain not available, aborting"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# determine number of cores
|
||||
CORES=$(nproc)
|
||||
|
||||
@@ -90,6 +113,13 @@ echo "Git Branch: $GIT_BRANCH"
|
||||
echo "Git Head: $GIT_HEAD"
|
||||
echo "Toolchain for 32bit targets: $TOOLCHAIN_32"
|
||||
echo "Toolchain for 64bit targets: $TOOLCHAIN_64"
|
||||
if ((BUILD_XP > 0))
|
||||
then
|
||||
echo "Toolchain for WinXP 32bit targets: $TOOLCHAIN_WINXP_32"
|
||||
echo "Toolchain for WinXP 64bit targets: $TOOLCHAIN_WINXP_64"
|
||||
else
|
||||
echo "WinXP toolchain not available, skipping WinXP builds"
|
||||
fi
|
||||
echo "Distribution Name: $DIST_NAME"
|
||||
echo "Build Type: $BUILD_TYPE"
|
||||
echo "Cores: $CORES"
|
||||
@@ -130,11 +160,59 @@ time (
|
||||
cmake -G "Ninja" -DCMAKE_TOOLCHAIN_FILE=${TOOLCHAIN_64} -DCMAKE_BUILD_TYPE=${BUILD_TYPE} "$OLDPWD" && ninja ${TARGETS_64}
|
||||
popd > /dev/null
|
||||
|
||||
if ((BUILD_XP > 0))
|
||||
then
|
||||
# 32 bit Windows XP
|
||||
echo ""
|
||||
echo "Building 32bit targets (WinXP toolchain)..."
|
||||
echo "========================="
|
||||
if ((CLEAN_BUILD > 0))
|
||||
then
|
||||
rm -rf ${BUILDDIR_WINXP_32}
|
||||
fi
|
||||
mkdir -p ${BUILDDIR_WINXP_32}
|
||||
pushd ${BUILDDIR_WINXP_32} > /dev/null
|
||||
CXXFLAGS="$CXXFLAGS -DSPICE_XP=1" cmake -G "Ninja" -DCMAKE_TOOLCHAIN_FILE=${TOOLCHAIN_WINXP_32} -DCMAKE_BUILD_TYPE=${BUILD_TYPE} "$OLDPWD" && ninja ${TARGETS_XP32}
|
||||
popd > /dev/null
|
||||
|
||||
# 64 bit Windows XP
|
||||
echo ""
|
||||
echo "Building 64bit targets (WinXP toolchain)..."
|
||||
echo "========================="
|
||||
if ((CLEAN_BUILD > 0))
|
||||
then
|
||||
rm -rf ${BUILDDIR_WINXP_64}
|
||||
fi
|
||||
mkdir -p ${BUILDDIR_WINXP_64}
|
||||
pushd ${BUILDDIR_WINXP_64} > /dev/null
|
||||
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
|
||||
|
||||
echo ""
|
||||
echo "Compilation process done :)"
|
||||
echo "==========================="
|
||||
)
|
||||
|
||||
if ((BUILD_XP > 0))
|
||||
then
|
||||
echo ""
|
||||
echo "Checking XP compatibility..."
|
||||
echo "============================="
|
||||
if ! command -v windows_dll_compat_checker &> /dev/null; then
|
||||
echo "WARNING: windows_dll_compat_checker not found, skipping XP compatibility check"
|
||||
else
|
||||
windows_dll_compat_checker -s PREMADE/winxp_x86_64.ini \
|
||||
${BUILDDIR_WINXP_64}/spicetools/64/spice64.exe
|
||||
windows_dll_compat_checker -s PREMADE/winxp_x86_64_32bit_dlls.ini \
|
||||
${BUILDDIR_WINXP_32}/spicetools/spicecfg.exe \
|
||||
${BUILDDIR_WINXP_32}/spicetools/32/spice.exe
|
||||
fi
|
||||
fi
|
||||
|
||||
# generate PDBs
|
||||
if false # ((DEBUG > 0))
|
||||
then
|
||||
@@ -191,6 +269,10 @@ rm -rf ${OUTDIR_EXTRAS}
|
||||
mkdir -p ${OUTDIR_EXTRAS}
|
||||
mkdir -p ${OUTDIR_EXTRAS}/largeaddressaware
|
||||
mkdir -p ${OUTDIR_EXTRAS}/linux
|
||||
if ((BUILD_XP > 0))
|
||||
then
|
||||
mkdir -p ${OUTDIR_EXTRAS}/winxp
|
||||
fi
|
||||
|
||||
if false # ((DEBUG > 0))
|
||||
then
|
||||
@@ -226,6 +308,12 @@ else
|
||||
cp ${BUILDDIR_64}/spicetools/64/nvcuda.dll ${OUTDIR}/stubs/64 2>/dev/null
|
||||
cp ${BUILDDIR_64}/spicetools/64/nvcuvid.dll ${OUTDIR}/stubs/64 2>/dev/null
|
||||
cp ${BUILDDIR_32}/spicetools/32/cpusbxpkm.dll ${OUTDIR}/stubs/32 2>/dev/null
|
||||
if ((BUILD_XP > 0))
|
||||
then
|
||||
cp ${BUILDDIR_WINXP_32}/spicetools/spicecfg.exe ${OUTDIR_EXTRAS}/winxp 2>/dev/null
|
||||
cp ${BUILDDIR_WINXP_32}/spicetools/32/spice.exe ${OUTDIR_EXTRAS}/winxp 2>/dev/null
|
||||
cp ${BUILDDIR_WINXP_64}/spicetools/64/spice64.exe ${OUTDIR_EXTRAS}/winxp 2>/dev/null
|
||||
fi
|
||||
fi
|
||||
|
||||
# pack source files to output directory
|
||||
@@ -250,10 +338,10 @@ then
|
||||
echo "Building dist..."
|
||||
mkdir -p ${DIST_FOLDER}
|
||||
rm -rf "${DIST_FOLDER}/${DIST_NAME}"
|
||||
pushd ${OUTDIR}/.. > /dev/null
|
||||
zip -qrXT9 "$OLDPWD/${DIST_FOLDER}/${DIST_NAME}" spice2x -x "spice2x/extras/*" -z <<< "$DIST_COMMENT"
|
||||
pushd ${OUTDIR} > /dev/null
|
||||
zip -qrXT9 "$OLDPWD/${DIST_FOLDER}/${DIST_NAME}" . -x "extras/*" -z <<< "$DIST_COMMENT"
|
||||
echo "Building extras..."
|
||||
zip -qrXT9 "$OLDPWD/${DIST_FOLDER}/${DIST_NAME_EXTRAS}" spice2x -z <<< "$DIST_COMMENT"
|
||||
zip -qrXT9 "$OLDPWD/${DIST_FOLDER}/${DIST_NAME_EXTRAS}" . -z <<< "$DIST_COMMENT"
|
||||
popd > /dev/null
|
||||
fi
|
||||
|
||||
|
||||
@@ -1,4 +1,12 @@
|
||||
#!/bin/bash
|
||||
#!/usr/bin/env bash
|
||||
|
||||
set -eu
|
||||
|
||||
docker build --pull "$PWD/external/docker" -t spicetools/deps --platform linux/x86_64
|
||||
docker build --build-context gitroot="$PWD/../../.git" . -t spicetools/spice:latest
|
||||
docker run --rm -v "$PWD/dist:/src/src/spice2x/dist" -v "$PWD/bin:/src/src/spice2x/bin" -v "$PWD/.ccache:/src/src/spice2x/.ccache" spicetools/spice "$@"
|
||||
|
||||
# Interactive TTY if available, so docker build can be Ctrl+C'd
|
||||
DOCKER_FLAGS=""
|
||||
[ -t 0 ] && DOCKER_FLAGS="-it"
|
||||
|
||||
docker run $DOCKER_FLAGS --rm -v "$PWD/dist:/src/src/spice2x/dist" -v "$PWD/bin:/src/src/spice2x/bin" -v "$PWD/.ccache:/src/src/spice2x/.ccache" spicetools/spice "$@"
|
||||
|
||||
+33
-14
@@ -90,6 +90,10 @@ std::string Analog::getDisplayString(rawinput::RawInputManager *manager) {
|
||||
return "MIDI Unknown Index " + indexString + " (" + device->desc + ")";
|
||||
}
|
||||
}
|
||||
case rawinput::XINPUT_GAMEPAD:
|
||||
return fmt::format("{} ({})",
|
||||
xinput::get_analog_string(static_cast<xinput::XInputAnalogEnum>(index)),
|
||||
device->desc);
|
||||
case rawinput::DESTROYED:
|
||||
return "Device unplugged (" + indexString + ")";
|
||||
default:
|
||||
@@ -203,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;
|
||||
@@ -251,7 +270,7 @@ float Analog::applyDeadzone(float raw_value) {
|
||||
}
|
||||
}
|
||||
|
||||
} else if (deadzone < 0) {
|
||||
} else {
|
||||
|
||||
// invert for mirror
|
||||
if (this->getDeadzoneMirror()) {
|
||||
@@ -259,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;
|
||||
}
|
||||
};
|
||||
|
||||
+132
-47
@@ -1,5 +1,6 @@
|
||||
#include "api.h"
|
||||
|
||||
#include <cassert>
|
||||
#include <optional>
|
||||
|
||||
#include "launcher/superexit.h"
|
||||
@@ -198,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;
|
||||
}
|
||||
@@ -389,6 +400,16 @@ GameAPI::Buttons::State GameAPI::Buttons::getState(rawinput::RawInputManager *ma
|
||||
}
|
||||
case rawinput::PIUIO_DEVICE: {
|
||||
state = device->piuioDev->IsPressed(vKey) ? BUTTON_PRESSED : BUTTON_NOT_PRESSED;
|
||||
break;
|
||||
}
|
||||
case rawinput::XINPUT_GAMEPAD: {
|
||||
assert(reinterpret_cast<uintptr_t>(device->handle) < XUSER_MAX_COUNT);
|
||||
const auto player = static_cast<uint8_t>(reinterpret_cast<uintptr_t>(device->handle));
|
||||
state = manager->XINPUT_MGR->is_button_pressed(
|
||||
player,
|
||||
static_cast<xinput::XInputButtonEnum>(vKey)) ?
|
||||
BUTTON_PRESSED : BUTTON_NOT_PRESSED;
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
@@ -586,7 +607,7 @@ float Buttons::getVelocity(std::unique_ptr<rawinput::RawInputManager> &manager,
|
||||
}
|
||||
}
|
||||
|
||||
float GameAPI::Analogs::getState(rawinput::Device *device, Analog &analog) {
|
||||
float GameAPI::Analogs::getState(rawinput::RawInputManager *manager, rawinput::Device *device, Analog &analog) {
|
||||
float value = 0.5f;
|
||||
if (!device) {
|
||||
return value;
|
||||
@@ -658,32 +679,12 @@ float GameAPI::Analogs::getState(rawinput::Device *device, Analog &analog) {
|
||||
}
|
||||
|
||||
// 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);
|
||||
|
||||
@@ -711,30 +712,54 @@ float GameAPI::Analogs::getState(rawinput::Device *device, Analog &analog) {
|
||||
// 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: {
|
||||
@@ -767,6 +792,18 @@ float GameAPI::Analogs::getState(rawinput::Device *device, Analog &analog) {
|
||||
}
|
||||
break;
|
||||
}
|
||||
case rawinput::XINPUT_GAMEPAD: {
|
||||
assert(reinterpret_cast<uintptr_t>(device->handle) < XUSER_MAX_COUNT);
|
||||
const auto player = static_cast<uint8_t>(reinterpret_cast<uintptr_t>(device->handle));
|
||||
value = manager->XINPUT_MGR->get_analog_state(
|
||||
player,
|
||||
static_cast<xinput::XInputAnalogEnum>(index));
|
||||
// invert value
|
||||
if (inverted) {
|
||||
value = 1.f - value;
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
@@ -806,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
|
||||
@@ -822,7 +895,7 @@ float GameAPI::Analogs::getState(rawinput::RawInputManager *manager, Analog &ana
|
||||
return analog.getLastState();
|
||||
}
|
||||
|
||||
float state = getState(device, analog);
|
||||
float state = getState(manager, device, analog);
|
||||
analog.setLastState(state);
|
||||
|
||||
return state;
|
||||
@@ -904,7 +977,7 @@ void GameAPI::Lights::sortLightsWithCategory(
|
||||
}
|
||||
|
||||
|
||||
void GameAPI::Lights::writeLight(rawinput::Device *device, int index, float value) {
|
||||
void GameAPI::Lights::writeLight(rawinput::RawInputManager *manager, rawinput::Device *device, int index, float value) {
|
||||
|
||||
// check device
|
||||
if (!device) {
|
||||
@@ -991,6 +1064,18 @@ void GameAPI::Lights::writeLight(rawinput::Device *device, int index, float valu
|
||||
}
|
||||
break;
|
||||
}
|
||||
case rawinput::XINPUT_GAMEPAD: {
|
||||
assert(reinterpret_cast<uintptr_t>(device->handle) < XUSER_MAX_COUNT);
|
||||
const auto player = static_cast<uint8_t>(reinterpret_cast<uintptr_t>(device->handle));
|
||||
if (index < static_cast<int>(xinput::XInputOutputEnum::COUNT)) {
|
||||
manager->XINPUT_MGR->set_output_state(
|
||||
player, static_cast<xinput::XInputOutputEnum>(index), value);
|
||||
// no need to set output_pending; xinput output handled immediately
|
||||
} else {
|
||||
log_warning("api", "invalid xinput light index: {}", index);
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
@@ -1016,9 +1101,9 @@ void GameAPI::Lights::writeLight(rawinput::RawInputManager *manager, Light &ligh
|
||||
|
||||
// write state
|
||||
if (light.override_enabled) {
|
||||
writeLight(device, light.getIndex(), light.override_state);
|
||||
writeLight(manager, device, light.getIndex(), light.override_state);
|
||||
} else {
|
||||
writeLight(device, light.getIndex(), value);
|
||||
writeLight(manager, device, light.getIndex(), value);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+10
-9
@@ -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) {
|
||||
@@ -103,7 +104,7 @@ namespace GameAPI {
|
||||
}
|
||||
}
|
||||
|
||||
float getState(rawinput::Device *device, Analog &analog);
|
||||
float getState(rawinput::RawInputManager *manager, rawinput::Device *device, Analog &analog);
|
||||
float getState(rawinput::RawInputManager *manager, Analog &analog);
|
||||
float getState(std::unique_ptr<rawinput::RawInputManager> &manager, Analog &analog);
|
||||
}
|
||||
@@ -135,7 +136,7 @@ namespace GameAPI {
|
||||
|
||||
void sortLightsWithCategory(std::vector<Light> *lights, const std::initializer_list<LightAndCategory> list);
|
||||
|
||||
void writeLight(rawinput::Device *device, int index, float value);
|
||||
void writeLight(rawinput::RawInputManager *manager, rawinput::Device *device, int index, float value);
|
||||
void writeLight(rawinput::RawInputManager *manager, Light &light, float value);
|
||||
void writeLight(std::unique_ptr<rawinput::RawInputManager> &manager, Light &light, float value);
|
||||
|
||||
|
||||
@@ -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 "";
|
||||
}
|
||||
|
||||
@@ -417,6 +420,10 @@ std::string Button::getDisplayString(rawinput::RawInputManager* manager) {
|
||||
}
|
||||
case rawinput::PIUIO_DEVICE:
|
||||
return "PIUIO " + vKeyString;
|
||||
case rawinput::XINPUT_GAMEPAD:
|
||||
return fmt::format("{} ({})",
|
||||
xinput::get_button_string(static_cast<xinput::XInputButtonEnum>(vKey)),
|
||||
device->desc);
|
||||
case rawinput::DESTROYED:
|
||||
return "Device unplugged (" + vKeyString + ")";
|
||||
default:
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
+17
-27
@@ -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...
|
||||
@@ -1325,4 +1315,4 @@ void Config::saveConfigFile() {
|
||||
}
|
||||
// delete the .tmp file (not critical if this fails)
|
||||
DeleteFileW(this->configLocationTemp.c_str());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -85,6 +85,15 @@ std::string Light::getDisplayString(rawinput::RawInputManager* manager) {
|
||||
|
||||
return "Invalid SMX Dedicab Light (" + index_string + ")";
|
||||
}
|
||||
case rawinput::XINPUT_GAMEPAD: {
|
||||
if (index < static_cast<size_t>(xinput::XInputOutputEnum::COUNT)) {
|
||||
return fmt::format("{} ({})",
|
||||
xinput::get_output_string(static_cast<xinput::XInputOutputEnum>(index)),
|
||||
device->desc);
|
||||
}
|
||||
|
||||
return "Invalid XInput Gamepad Light (" + index_string + ")";
|
||||
}
|
||||
case rawinput::DESTROYED:
|
||||
return "Unplugged device (" + index_string + ")";
|
||||
default:
|
||||
|
||||
@@ -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 {
|
||||
@@ -35,7 +35,7 @@ namespace cfg {
|
||||
|
||||
ScreenResize::~ScreenResize() {
|
||||
}
|
||||
|
||||
|
||||
void ScreenResize::config_load() {
|
||||
std::string config = fileutils::text_read(this->config_path);
|
||||
if (config.empty()) {
|
||||
@@ -78,7 +78,7 @@ namespace cfg {
|
||||
eamuse_get_game(),
|
||||
use_game_setting,
|
||||
root);
|
||||
|
||||
|
||||
load_bool_value(doc, root + "enable_screen_resize", this->enable_screen_resize);
|
||||
if (this->enable_screen_resize) {
|
||||
log_misc("ScreenResize", "enabled by config file");
|
||||
|
||||
@@ -60,6 +60,7 @@ namespace cfg {
|
||||
// window = rectangle including the frame
|
||||
// client = just the content area without frames.
|
||||
bool window_always_on_top = false;
|
||||
bool window_disable_round_corners = false;
|
||||
bool client_keep_aspect_ratio = true;
|
||||
bool enable_window_resize = false;
|
||||
int window_decoration = 0; // enum type WindowDecorationMode
|
||||
|
||||
+8
@@ -15,3 +15,11 @@ RUN pacman --noconfirm -Syu git \
|
||||
ccache
|
||||
RUN useradd user -m && echo "ALL ALL=(ALL) NOPASSWD: ALL" >> /etc/sudoers
|
||||
RUN su user -c "cd /tmp/ && git clone https://aur.archlinux.org/yay-bin.git --depth=1 && cd yay-bin && makepkg --noconfirm -si && yay --noconfirm -S mingw-w64-cmake"
|
||||
|
||||
RUN mkdir -p /opt/llvm-mingw-xp \
|
||||
&& curl -fsSL "https://github.com/mon/llvm-mingw-xp/releases/download/llvm-mingw-xp-22.1.0/llvm-mingw-llvm-mingw-xp-22.1.0-msvcrt-ubuntu-22.04-x86_64.tar.xz" \
|
||||
| tar -xJ --strip-components=1 -C /opt/llvm-mingw-xp
|
||||
ENV PATH="$PATH:/opt/llvm-mingw-xp/bin"
|
||||
|
||||
RUN curl -fsSL "https://github.com/mon/windows-dll-compat-checker/releases/download/v1.3/windows_dll_compat_checker-linux-x86_64.tar.xz" \
|
||||
| tar -xJ -C /usr/local/bin
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -174,6 +174,7 @@ namespace games::gitadora {
|
||||
static_assert(sizeof(AIO_IOB5_Y32D) == 0x2e8);
|
||||
static_assert(sizeof(AIO_IOB2_BI2X_AC1) == 0x4570);
|
||||
static_assert(sizeof(AIO_IOB2_BI2X_WRFIRM) == 0x20f48);
|
||||
static_assert(sizeof(AIO_IOB2_BI2X_AC1__DEVSTATUS) == 0x2C8);
|
||||
|
||||
/*
|
||||
* typedefs
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#include "camera.h"
|
||||
|
||||
#if SPICE64
|
||||
#if SPICE64 && !SPICE_XP
|
||||
|
||||
#include <d3d9.h>
|
||||
#include "mf_wrappers.h"
|
||||
@@ -84,7 +84,7 @@ namespace games::iidx {
|
||||
|
||||
bool find_camera_hooks() {
|
||||
std::string dll_name = avs::game::DLL_NAME;
|
||||
dll_path = MODULE_PATH / dll_name;
|
||||
dll_path = MODULE_PATH / dll_name;
|
||||
|
||||
iidx_module = libutils::try_module(dll_path);
|
||||
if (!iidx_module) {
|
||||
@@ -149,7 +149,7 @@ namespace games::iidx {
|
||||
"E800000000488BC8488BD34883C420",
|
||||
"X????XXXXXXXXXX",
|
||||
0, 0));
|
||||
|
||||
|
||||
if (addr_textures_ptr == nullptr) {
|
||||
log_warning("iidx:camhook", "failed to find hook: addr_textures (part 1)");
|
||||
return FALSE;
|
||||
@@ -232,7 +232,7 @@ namespace games::iidx {
|
||||
}
|
||||
|
||||
static void **__fastcall camera_hook_a(PBYTE a1) {
|
||||
std::call_once(hook_a_init, [&]{
|
||||
std::call_once(hook_a_init, [&]{
|
||||
device = *reinterpret_cast<LPDIRECT3DDEVICE9EX*>(a1 + addr_device_offset);
|
||||
auto const preview = *reinterpret_cast<LPDIRECT3DTEXTURE9**>((uint8_t*)iidx_module + addr_textures);
|
||||
auto const manager = reinterpret_cast<Camera::CCameraManager2*>((uint8_t*)iidx_module + addr_camera_manager);
|
||||
@@ -298,11 +298,11 @@ namespace games::iidx {
|
||||
// Ask for source type = video capture devices.
|
||||
if (SUCCEEDED(hr)) {
|
||||
hr = pAttributes->SetGUID(
|
||||
MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE,
|
||||
MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE,
|
||||
MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE_VIDCAP_GUID
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
// Enumerate devices.
|
||||
if (SUCCEEDED(hr)) {
|
||||
hr = WrappedMFEnumDeviceSources(pAttributes, &ppDevices, &numDevices);
|
||||
@@ -478,7 +478,7 @@ namespace games::iidx {
|
||||
init_mf_library();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#pragma once
|
||||
|
||||
#if SPICE64
|
||||
#if SPICE64 && !SPICE_XP
|
||||
|
||||
#include <vector>
|
||||
#include "games/iidx/local_camera.h"
|
||||
|
||||
@@ -30,6 +30,7 @@
|
||||
#include "util/memutils.h"
|
||||
#include "util/sigscan.h"
|
||||
#include "util/socd_cleaner.h"
|
||||
#include "util/sysutils.h"
|
||||
#include "util/time.h"
|
||||
#include "util/utils.h"
|
||||
#include "launcher/signal.h"
|
||||
@@ -66,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;
|
||||
@@ -423,6 +426,14 @@ namespace games::iidx {
|
||||
init_legacy_camera_hook(FLIP_CAMS);
|
||||
}
|
||||
|
||||
#if SPICE64
|
||||
|
||||
if (TDJ_MODE) {
|
||||
sysutils::hook_EnumDisplayDevicesA();
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
// init cfgmgr32 hooks
|
||||
cfgmgr32hook_init(avs::game::DLL_INSTANCE);
|
||||
}
|
||||
@@ -486,28 +497,28 @@ namespace games::iidx {
|
||||
log_fatal("iidx", "BAD MODEL NAME ERROR - TDJ specified, must be LDJ instead");
|
||||
}
|
||||
|
||||
// check -monitor + TDJ mode
|
||||
// check -dxmainadapter + TDJ mode
|
||||
if (!GRAPHICS_WINDOWED && D3D9_ADAPTER.has_value() && TDJ_MODE) {
|
||||
IIDX_TDJ_MONITOR_WARNING = true;
|
||||
log_warning(
|
||||
"iidx",
|
||||
"\n\n!!! using -monitor option with TDJ is NOT recommended due to known !!!\n"
|
||||
"!!! compatibility issues with the game !!!\n"
|
||||
"\n\n"
|
||||
"!!! using -dxmainadapter option with TDJ is NOT recommended !!!\n"
|
||||
"!!! due to known compatibility issues with the game !!!\n"
|
||||
"!!! !!!\n"
|
||||
"!!! * game may launch in wrong resolution or refresh rate !!!\n"
|
||||
"!!! * touch / mouse input may stop working in subscreen / overlay !!!\n"
|
||||
"!!! !!!\n"
|
||||
"!!! recommendation is to NOT use -monitor and instead set the !!!\n"
|
||||
"!!! primary monitor in Windows settings before launching the game !!!\n\n"
|
||||
"!!! recommendation is to use the Change Main Monitor (-mainmonitor) !!!\n"
|
||||
"!!! option instead of -dxmainadapter !!!\n\n"
|
||||
);
|
||||
|
||||
deferredlogs::defer_error_messages({
|
||||
"-monitor option is NOT recommended when running with TDJ mode",
|
||||
"-dxmainadapter option is NOT recommended when running with TDJ mode",
|
||||
" due to known compatibility issues with the game:",
|
||||
" * game may launch in wrong resolution or refresh rate",
|
||||
" * touch / mouse input may stop working in subscreen / overlay",
|
||||
" recommended fix is to NOT use -monitor and instead set the primary",
|
||||
" monitor in Windows settings before launching the game"
|
||||
" use Change Main Monitor (-mainmonitor) option instead"
|
||||
});
|
||||
}
|
||||
|
||||
@@ -772,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) {
|
||||
@@ -906,8 +952,7 @@ namespace games::iidx {
|
||||
log_warning(
|
||||
"iidx",
|
||||
"Failed to apply ASIO compatibility fix for iidx32+. "
|
||||
"Unless patches are applied, your ASIO device ({}) may hang or fail to work",
|
||||
ASIO_DRIVER->c_str());
|
||||
"Unless patches are applied, your ASIO device may hang or fail to work");
|
||||
} else {
|
||||
log_info(
|
||||
"iidx",
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#include "games/iidx/local_camera.h"
|
||||
|
||||
#if SPICE64
|
||||
#if SPICE64 && !SPICE_XP
|
||||
|
||||
#include "util/logging.h"
|
||||
#include "util/utils.h"
|
||||
@@ -128,7 +128,7 @@ namespace games::iidx {
|
||||
// TODO: Color space conversion
|
||||
// if (SUCCEEDED(hr)) {
|
||||
// hr = pAttributes->SetUINT32(MF_SOURCE_READER_ENABLE_ADVANCED_VIDEO_PROCESSING, TRUE);
|
||||
// }
|
||||
// }
|
||||
// if (SUCCEEDED(hr)) {
|
||||
// hr = pAttributes->SetUINT32(MF_READWRITE_DISABLE_CONVERTERS, FALSE);
|
||||
// }
|
||||
@@ -189,11 +189,11 @@ namespace games::iidx {
|
||||
&pType
|
||||
);
|
||||
|
||||
if (FAILED(hr)) {
|
||||
if (FAILED(hr)) {
|
||||
if (hr != MF_E_NO_MORE_TYPES) {
|
||||
log_warning("iidx:camhook", "[{}] Cannot get media type {} {:#x}", m_name, i, (ULONG)hr);
|
||||
}
|
||||
break;
|
||||
break;
|
||||
}
|
||||
|
||||
hr = TryMediaType(pType, &bestWidth, &bestFrameRate);
|
||||
@@ -246,7 +246,7 @@ namespace games::iidx {
|
||||
pSelectedMediaType = m_pAutoMediaType;
|
||||
}
|
||||
|
||||
if (SUCCEEDED(hr)) {
|
||||
if (SUCCEEDED(hr)) {
|
||||
hr = ChangeMediaType(pSelectedMediaType);
|
||||
}
|
||||
|
||||
@@ -281,7 +281,7 @@ namespace games::iidx {
|
||||
m_cameraHeight = info.height;
|
||||
UpdateDrawRect();
|
||||
}
|
||||
|
||||
|
||||
return hr;
|
||||
}
|
||||
|
||||
@@ -395,7 +395,7 @@ namespace games::iidx {
|
||||
|
||||
m_device->ColorFill(m_pDestSurf, &targetRect, D3DCOLOR_XRGB(0, 0, 0));
|
||||
}
|
||||
|
||||
|
||||
void IIDXLocalCamera::CreateThread() {
|
||||
// Create thread
|
||||
m_drawThread = new std::thread([this]() {
|
||||
@@ -411,7 +411,7 @@ namespace games::iidx {
|
||||
if (accumulator > 1.0) {
|
||||
accumulator -= 1.0;
|
||||
floorFrameTimeMicroSec += 1;
|
||||
}
|
||||
}
|
||||
std::this_thread::sleep_for(std::chrono::microseconds(floorFrameTimeMicroSec));
|
||||
}
|
||||
});
|
||||
@@ -430,12 +430,12 @@ namespace games::iidx {
|
||||
|
||||
log_misc("iidx:camhook", "[{}] Init camera control", m_name);
|
||||
|
||||
hr = m_pSource->QueryInterface(IID_IAMCameraControl, (void**)&m_pCameraControl);
|
||||
if (FAILED(hr)) {
|
||||
// The device does not support IAMCameraControl
|
||||
hr = m_pSource->QueryInterface(IID_IAMCameraControl, (void**)&m_pCameraControl);
|
||||
if (FAILED(hr)) {
|
||||
// The device does not support IAMCameraControl
|
||||
log_warning("iidx:camhook", "[{}] Camera control not supported", m_name);
|
||||
return E_FAIL;
|
||||
}
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < CAMERA_CONTROL_PROP_SIZE; i++) {
|
||||
long minValue = 0;
|
||||
@@ -472,9 +472,9 @@ namespace games::iidx {
|
||||
CameraControlProp prop = m_controlProps.at(i);
|
||||
|
||||
log_misc(
|
||||
"iidx:camhook", "[{}] >> {} range=({}, {}) default={} delta={} dFlags={} value={} vFlags={}",
|
||||
"iidx:camhook", "[{}] >> {} range=({}, {}) default={} delta={} dFlags={} value={} vFlags={}",
|
||||
m_name,
|
||||
CAMERA_CONTROL_LABELS[i],
|
||||
CAMERA_CONTROL_LABELS[i],
|
||||
prop.minValue, prop.maxValue,
|
||||
prop.defaultValue,
|
||||
prop.delta,
|
||||
@@ -570,9 +570,9 @@ namespace games::iidx {
|
||||
|
||||
// Get frame rate
|
||||
hr = MFGetAttributeRatio(
|
||||
pType,
|
||||
MF_MT_FRAME_RATE,
|
||||
(UINT32*)&frameRate.Numerator,
|
||||
pType,
|
||||
MF_MT_FRAME_RATE,
|
||||
(UINT32*)&frameRate.Numerator,
|
||||
(UINT32*)&frameRate.Denominator
|
||||
);
|
||||
if (FAILED(hr)) { goto done; }
|
||||
@@ -580,7 +580,7 @@ namespace games::iidx {
|
||||
info.frameRate = frameRate.Numerator / frameRate.Denominator;
|
||||
|
||||
info.description = fmt::format(
|
||||
"{}x{} @{}FPS {}",
|
||||
"{}x{} @{}FPS {}",
|
||||
info.width,
|
||||
info.height,
|
||||
(int)info.frameRate,
|
||||
@@ -606,7 +606,7 @@ namespace games::iidx {
|
||||
return "Unknown";
|
||||
}
|
||||
|
||||
/**
|
||||
/**
|
||||
* Return values:
|
||||
* S_OK: this is a "better" media type than the existing one
|
||||
* S_FALSE: valid media type, but not "better"
|
||||
@@ -620,17 +620,17 @@ namespace games::iidx {
|
||||
|
||||
hr = pType->GetGUID(MF_MT_SUBTYPE, &subtype);
|
||||
|
||||
if (FAILED(hr)) {
|
||||
if (FAILED(hr)) {
|
||||
log_warning("iidx:camhook", "[{}] Failed to get subtype: {:#x}", m_name, (ULONG)hr);
|
||||
return hr;
|
||||
return hr;
|
||||
}
|
||||
|
||||
hr = MFGetAttributeSize(pType, MF_MT_FRAME_SIZE, &width, &height);
|
||||
if (FAILED(hr)) {
|
||||
if (FAILED(hr)) {
|
||||
log_warning("iidx:camhook", "[{}] Failed to get frame size: {:#x}", m_name, (ULONG)hr);
|
||||
return hr;
|
||||
}
|
||||
|
||||
|
||||
// Only support format with converters
|
||||
// TODO: verify conversion support with DXVA
|
||||
if (subtype != MFVideoFormat_YUY2 && subtype != MFVideoFormat_NV12) {
|
||||
@@ -639,12 +639,12 @@ namespace games::iidx {
|
||||
|
||||
// Frame rate
|
||||
hr = MFGetAttributeRatio(
|
||||
pType,
|
||||
MF_MT_FRAME_RATE,
|
||||
(UINT32*)&frameRate.Numerator,
|
||||
pType,
|
||||
MF_MT_FRAME_RATE,
|
||||
(UINT32*)&frameRate.Numerator,
|
||||
(UINT32*)&frameRate.Denominator
|
||||
);
|
||||
if (FAILED(hr)) {
|
||||
if (FAILED(hr)) {
|
||||
log_warning("iidx:camhook", "[{}] Failed to get frame rate: {:#x}", m_name, (ULONG)hr);
|
||||
return hr;
|
||||
}
|
||||
@@ -667,7 +667,7 @@ namespace games::iidx {
|
||||
// Check if this format has better resolution / frame rate
|
||||
if ((width > *pBestWidth) || (width >= (UINT32)TARGET_SURFACE_WIDTH && frameRateValue >= *pBestFrameRate)) {
|
||||
// log_misc(
|
||||
// "iidx:camhook", "Better media type {} ({}x{}) @({} FPS)",
|
||||
// "iidx:camhook", "Better media type {} ({}x{}) @({} FPS)",
|
||||
// GetVideoFormatName(subtype),
|
||||
// width,
|
||||
// height,
|
||||
@@ -730,7 +730,7 @@ namespace games::iidx {
|
||||
|
||||
HRESULT IIDXLocalCamera::FlushDrawCommands() {
|
||||
IDirect3DQuery9* pEventQuery = nullptr;
|
||||
// It is necessary to flush the command queue
|
||||
// It is necessary to flush the command queue
|
||||
// or the data is not ready for the receiver to read.
|
||||
// Adapted from : https://msdn.microsoft.com/en-us/library/windows/desktop/bb172234%28v=vs.85%29.aspx
|
||||
// Also see : http://www.ogre3d.org/forums/viewtopic.php?f=5&t=50486
|
||||
@@ -784,8 +784,8 @@ namespace games::iidx {
|
||||
for (int y = 0; y < TARGET_SURFACE_HEIGHT; y++) {
|
||||
for (int x = 0; x < TARGET_SURFACE_WIDTH; x++) {
|
||||
memcpy(
|
||||
pDest + x * pixelSize,
|
||||
pSrc + (flip_h ? (TARGET_SURFACE_WIDTH - x - 1) : x) * pixelSize,
|
||||
pDest + x * pixelSize,
|
||||
pSrc + (flip_h ? (TARGET_SURFACE_WIDTH - x - 1) : x) * pixelSize,
|
||||
pixelSize
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#pragma once
|
||||
|
||||
#if SPICE64
|
||||
#if SPICE64 && !SPICE_XP
|
||||
|
||||
#include <d3d9.h>
|
||||
#include <dxva2api.h>
|
||||
|
||||
+50
-46
@@ -51,7 +51,7 @@ namespace games {
|
||||
static robin_hood::unordered_map<std::string, std::vector<Analog> &> analogs;
|
||||
static robin_hood::unordered_map<std::string, std::vector<Light> &> lights;
|
||||
static robin_hood::unordered_map<std::string, std::vector<Option>> options;
|
||||
static robin_hood::unordered_map<std::string, std::vector<std::string>> file_hints;
|
||||
static robin_hood::unordered_map<std::string, std::vector<std::vector<std::string>>> file_hints;
|
||||
|
||||
static void initialize() {
|
||||
|
||||
@@ -68,7 +68,7 @@ namespace games {
|
||||
buttons_help.insert({ bbc, bbc::get_buttons_help() });
|
||||
analogs.insert({ bbc, bbc::get_analogs() });
|
||||
lights.insert({ bbc, bbc::get_lights() });
|
||||
file_hints[bbc].emplace_back("bsch.dll");
|
||||
file_hints[bbc].push_back({"bsch.dll"});
|
||||
|
||||
// hpm
|
||||
const std::string hpm("HELLO! Pop'n Music");
|
||||
@@ -76,32 +76,35 @@ namespace games {
|
||||
buttons.insert({ hpm, hpm::get_buttons() });
|
||||
buttons_help.insert({ hpm, hpm::get_buttons_help() });
|
||||
lights.insert({ hpm, hpm::get_lights() });
|
||||
file_hints[hpm].emplace_back("popn.dll");
|
||||
file_hints[hpm].push_back({"popn.dll", "pdh.dll"});
|
||||
|
||||
// bs
|
||||
const std::string bs("Beatstream");
|
||||
games.push_back(bs);
|
||||
buttons.insert({ bs, bs::get_buttons() });
|
||||
lights.insert({ bs, bs::get_lights() });
|
||||
file_hints[bs].emplace_back("beatstream.dll");
|
||||
file_hints[bs].emplace_back("beatstream1.dll");
|
||||
file_hints[bs].emplace_back("beatstream2.dll");
|
||||
file_hints[bs].push_back({"beatstream.dll"});
|
||||
file_hints[bs].push_back({"beatstream1.dll"});
|
||||
file_hints[bs].push_back({"beatstream2.dll"});
|
||||
|
||||
// ddr
|
||||
const std::string ddr("Dance Dance Revolution");
|
||||
games.push_back(ddr);
|
||||
buttons.insert({ ddr, ddr::get_buttons() });
|
||||
lights.insert({ ddr, ddr::get_lights() });
|
||||
file_hints[ddr].emplace_back("ddr.dll");
|
||||
file_hints[ddr].emplace_back("mdxja_945.dll");
|
||||
file_hints[ddr].emplace_back("arkmdxp3.dll");
|
||||
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"});
|
||||
|
||||
// dea
|
||||
const std::string dea("Dance Evolution");
|
||||
games.push_back(dea);
|
||||
buttons.insert({ dea, dea::get_buttons() });
|
||||
lights.insert({ dea, dea::get_lights() });
|
||||
file_hints[dea].emplace_back("arkkdm.dll");
|
||||
file_hints[dea].push_back({"arkkdm.dll"});
|
||||
|
||||
// gitadora
|
||||
const std::string gitadora("GitaDora");
|
||||
@@ -111,7 +114,7 @@ namespace games {
|
||||
analogs_help.insert({ gitadora, gitadora::get_analogs_help() });
|
||||
analogs.insert({ gitadora, gitadora::get_analogs() });
|
||||
lights.insert({ gitadora, gitadora::get_lights() });
|
||||
file_hints[gitadora].emplace_back("gdxg.dll");
|
||||
file_hints[gitadora].push_back({"gdxg.dll"});
|
||||
|
||||
// iidx
|
||||
const std::string iidx("Beatmania IIDX");
|
||||
@@ -120,7 +123,7 @@ namespace games {
|
||||
buttons_help.insert({ iidx, iidx::get_buttons_help() });
|
||||
analogs.insert({ iidx, iidx::get_analogs() });
|
||||
lights.insert({ iidx, iidx::get_lights() });
|
||||
file_hints[iidx].emplace_back("bm2dx.dll");
|
||||
file_hints[iidx].push_back({"bm2dx.dll"});
|
||||
|
||||
// jb
|
||||
const std::string jb("Jubeat");
|
||||
@@ -128,7 +131,7 @@ namespace games {
|
||||
buttons.insert({ jb, jb::get_buttons() });
|
||||
buttons_help.insert({ jb, jb::get_buttons_help() });
|
||||
lights.insert({ jb, jb::get_lights() });
|
||||
file_hints[jb].emplace_back("jubeat.dll");
|
||||
file_hints[jb].push_back({"jubeat.dll"});
|
||||
|
||||
// mga
|
||||
const std::string mga("Metal Gear");
|
||||
@@ -136,7 +139,7 @@ namespace games {
|
||||
buttons.insert({ mga, mga::get_buttons() });
|
||||
analogs.insert({ mga, mga::get_analogs() });
|
||||
lights.insert({ mga, mga::get_lights() });
|
||||
file_hints[mga].emplace_back("launch.dll");
|
||||
file_hints[mga].push_back({"launch.dll"});
|
||||
|
||||
// museca
|
||||
const std::string museca("Museca");
|
||||
@@ -145,7 +148,7 @@ namespace games {
|
||||
buttons_help.insert({ museca, museca::get_buttons_help() });
|
||||
analogs.insert({ museca, museca::get_analogs() });
|
||||
lights.insert({ museca, museca::get_lights() });
|
||||
file_hints[museca].emplace_back("museca.dll");
|
||||
file_hints[museca].push_back({"museca.dll"});
|
||||
|
||||
// nost
|
||||
const std::string nost("Nostalgia");
|
||||
@@ -154,7 +157,7 @@ namespace games {
|
||||
buttons_help.insert({ nost, nost::get_buttons_help() });
|
||||
analogs.insert({ nost, nost::get_analogs() });
|
||||
lights.insert({ nost, nost::get_lights() });
|
||||
file_hints[nost].emplace_back("nostalgia.dll");
|
||||
file_hints[nost].push_back({"nostalgia.dll"});
|
||||
|
||||
// popn
|
||||
const std::string popn("Pop'n Music");
|
||||
@@ -162,41 +165,42 @@ namespace games {
|
||||
buttons.insert({ popn, popn::get_buttons() });
|
||||
buttons_help.insert({ popn, popn::get_buttons_help() });
|
||||
lights.insert({ popn, popn::get_lights() });
|
||||
file_hints[popn].emplace_back("popn19.dll");
|
||||
file_hints[popn].emplace_back("popn20.dll");
|
||||
file_hints[popn].emplace_back("popn21.dll");
|
||||
file_hints[popn].emplace_back("popn22.dll");
|
||||
file_hints[popn].emplace_back("popn23.dll");
|
||||
file_hints[popn].emplace_back("popn24.dll");
|
||||
file_hints[popn].emplace_back("popn25.dll");
|
||||
file_hints[popn].push_back({"popn19.dll"});
|
||||
file_hints[popn].push_back({"popn20.dll"});
|
||||
file_hints[popn].push_back({"popn21.dll"});
|
||||
file_hints[popn].push_back({"popn22.dll"});
|
||||
file_hints[popn].push_back({"popn23.dll"});
|
||||
file_hints[popn].push_back({"popn24.dll"});
|
||||
file_hints[popn].push_back({"popn25.dll"});
|
||||
file_hints[popn].push_back({"popn.dll", "libaio-iob2_video.dll"});
|
||||
|
||||
// qma
|
||||
const std::string qma("Quiz Magic Academy");
|
||||
games.push_back(qma);
|
||||
buttons.insert({ qma, qma::get_buttons() });
|
||||
lights.insert({ qma, qma::get_lights() });
|
||||
file_hints[qma].emplace_back("client.dll");
|
||||
file_hints[qma].push_back({"client.dll"});
|
||||
|
||||
// rb
|
||||
const std::string rb("Reflec Beat");
|
||||
games.push_back(rb);
|
||||
buttons.insert({ rb, rb::get_buttons() });
|
||||
lights.insert({ rb, rb::get_lights() });
|
||||
file_hints[rb].emplace_back("reflecbeat.dll");
|
||||
file_hints[rb].push_back({"reflecbeat.dll"});
|
||||
|
||||
// shogikai
|
||||
std::string shogikai("Tenkaichi Shogikai");
|
||||
games.push_back(shogikai);
|
||||
buttons.insert({ shogikai, shogikai::get_buttons() });
|
||||
lights.insert({ shogikai, shogikai::get_lights() });
|
||||
file_hints[shogikai].emplace_back("shogi_engine.dll");
|
||||
file_hints[shogikai].push_back({"shogi_engine.dll"});
|
||||
|
||||
// rf3d
|
||||
const std::string rf3d("Road Fighters 3D");
|
||||
games.push_back(rf3d);
|
||||
buttons.insert({ rf3d, rf3d::get_buttons() });
|
||||
analogs.insert({ rf3d, rf3d::get_analogs() });
|
||||
file_hints[rf3d].emplace_back("jgt.dll");
|
||||
file_hints[rf3d].push_back({"jgt.dll"});
|
||||
|
||||
// sc
|
||||
const std::string sc("Steel Chronicle");
|
||||
@@ -204,7 +208,7 @@ namespace games {
|
||||
buttons.insert({ sc, sc::get_buttons() });
|
||||
analogs.insert({ sc, sc::get_analogs() });
|
||||
lights.insert({ sc, sc::get_lights() });
|
||||
file_hints[sc].emplace_back("gamekgg.dll");
|
||||
file_hints[sc].push_back({"gamekgg.dll"});
|
||||
|
||||
// sdvx
|
||||
const std::string sdvx("Sound Voltex");
|
||||
@@ -213,13 +217,13 @@ namespace games {
|
||||
buttons_help.insert({ sdvx, sdvx::get_buttons_help() });
|
||||
analogs.insert({ sdvx, sdvx::get_analogs() });
|
||||
lights.insert({ sdvx, sdvx::get_lights() });
|
||||
file_hints[sdvx].emplace_back("soundvoltex.dll");
|
||||
file_hints[sdvx].push_back({"soundvoltex.dll"});
|
||||
|
||||
// mfc
|
||||
const std::string mfc("Mahjong Fight Club");
|
||||
games.push_back(mfc);
|
||||
buttons.insert({ mfc, mfc::get_buttons() });
|
||||
file_hints[mfc].emplace_back("allinone.dll");
|
||||
file_hints[mfc].push_back({"allinone.dll"});
|
||||
|
||||
// ftt
|
||||
const std::string ftt("FutureTomTom");
|
||||
@@ -228,21 +232,21 @@ namespace games {
|
||||
buttons_help.insert({ ftt, ftt::get_buttons_help() });
|
||||
analogs.insert({ ftt, ftt::get_analogs() });
|
||||
lights.insert({ ftt, ftt::get_lights() });
|
||||
file_hints[ftt].emplace_back("arkmmd.dll");
|
||||
file_hints[ftt].push_back({"arkmmd.dll"});
|
||||
|
||||
// loveplus
|
||||
const std::string loveplus("LovePlus");
|
||||
games.push_back(loveplus);
|
||||
buttons.insert({ loveplus, loveplus::get_buttons() });
|
||||
lights.insert({ loveplus, loveplus::get_lights() });
|
||||
file_hints[loveplus].emplace_back("arkklp.dll");
|
||||
file_hints[loveplus].push_back({"arkklp.dll"});
|
||||
|
||||
// scotto
|
||||
const std::string scotto("Scotto");
|
||||
games.push_back(scotto);
|
||||
buttons.insert({ scotto, scotto::get_buttons() });
|
||||
lights.insert({ scotto, scotto::get_lights() });
|
||||
file_hints[scotto].emplace_back("scotto.dll");
|
||||
file_hints[scotto].push_back({"scotto.dll"});
|
||||
|
||||
// drs
|
||||
const std::string drs("DANCERUSH");
|
||||
@@ -250,14 +254,14 @@ namespace games {
|
||||
buttons.insert({ drs, drs::get_buttons() });
|
||||
buttons_help.insert({ drs, drs::get_buttons_help() });
|
||||
lights.insert({ drs, drs::get_lights() });
|
||||
file_hints[drs].emplace_back("superstep.dll");
|
||||
file_hints[drs].push_back({"superstep.dll"});
|
||||
|
||||
// otoca
|
||||
const std::string otoca("Otoca D'or");
|
||||
games.push_back(otoca);
|
||||
buttons.insert({ otoca, otoca::get_buttons() });
|
||||
buttons_help.insert({ otoca, otoca::get_buttons_help() });
|
||||
file_hints[otoca].emplace_back("arkkep.dll");
|
||||
file_hints[otoca].push_back({"arkkep.dll"});
|
||||
|
||||
// winning eleven
|
||||
const std::string we("Winning Eleven");
|
||||
@@ -265,27 +269,27 @@ namespace games {
|
||||
buttons.insert({ we, we::get_buttons() });
|
||||
analogs.insert({ we, we::get_analogs() });
|
||||
lights.insert({ we, we::get_lights() });
|
||||
file_hints[we].emplace_back("weac12_bootstrap_release.dll");
|
||||
file_hints[we].emplace_back("arknck.dll");
|
||||
file_hints[we].push_back({"weac12_bootstrap_release.dll"});
|
||||
file_hints[we].push_back({"arknck.dll"});
|
||||
|
||||
// silent scope: bone eater
|
||||
const std::string silentscope("Silent Scope: Bone Eater");
|
||||
games.push_back(silentscope);
|
||||
buttons.insert({ silentscope, silentscope::get_buttons() });
|
||||
analogs.insert({ silentscope, silentscope::get_analogs() });
|
||||
file_hints[silentscope].emplace_back("arkndd.dll");
|
||||
file_hints[silentscope].push_back({"arkndd.dll"});
|
||||
|
||||
// charge machine
|
||||
const std::string pcm("Charge Machine");
|
||||
games.push_back(pcm);
|
||||
buttons.insert({ pcm, pcm::get_buttons() });
|
||||
file_hints[pcm].emplace_back("launch.dll");
|
||||
file_hints[pcm].push_back({"launch.dll"});
|
||||
|
||||
// ongaku paradise
|
||||
const std::string op("Ongaku Paradise");
|
||||
games.push_back(op);
|
||||
buttons.insert({ op, onpara::get_buttons() });
|
||||
file_hints[op].emplace_back("arkjc9.dll");
|
||||
file_hints[op].push_back({"arkjc9.dll"});
|
||||
|
||||
// bc
|
||||
const std::string bc("Busou Shinki: Armored Princess Battle Conductor");
|
||||
@@ -293,7 +297,7 @@ namespace games {
|
||||
buttons.insert({ bc, bc::get_buttons() });
|
||||
buttons_help.insert({ bc, bc::get_buttons_help() });
|
||||
analogs.insert({ bc, bc::get_analogs() });
|
||||
file_hints[bc].emplace_back("game/bsac_app.exe");
|
||||
file_hints[bc].push_back({"game/bsac_app.exe"});
|
||||
|
||||
// ccj
|
||||
const std::string ccj("Chase Chase Jokers");
|
||||
@@ -302,21 +306,21 @@ namespace games {
|
||||
buttons_help.insert({ ccj, ccj::get_buttons_help() });
|
||||
analogs.insert({ ccj, ccj::get_analogs() });
|
||||
lights.insert({ ccj, ccj::get_lights() });
|
||||
file_hints[ccj].emplace_back("game/chaseproject.exe");
|
||||
file_hints[ccj].push_back({"game/chaseproject.exe"});
|
||||
|
||||
// QuizKnock STADIUM
|
||||
const std::string qks("QuizKnock STADIUM");
|
||||
games.push_back(qks);
|
||||
buttons.insert({ qks, qks::get_buttons() });
|
||||
buttons_help.insert({ qks, qks::get_buttons_help() });
|
||||
file_hints[qks].emplace_back("game/uks.exe");
|
||||
file_hints[qks].push_back({"game/uks.exe"});
|
||||
|
||||
// Mahjong Fight Girl
|
||||
const std::string mfg("Mahjong Fight Girl");
|
||||
games.push_back(mfg);
|
||||
buttons.insert({ mfg, mfg::get_buttons() });
|
||||
buttons_help.insert({ mfg, mfg::get_buttons_help() });
|
||||
file_hints[mfg].emplace_back("game/MFGClient_Data");
|
||||
file_hints[mfg].push_back({"game/MFGClient_Data"});
|
||||
|
||||
// Polaris Chord
|
||||
const std::string pc("Polaris Chord");
|
||||
@@ -325,7 +329,7 @@ namespace games {
|
||||
buttons_help.insert({ pc, pc::get_buttons_help() });
|
||||
analogs.insert({ pc, pc::get_analogs() });
|
||||
lights.insert({ pc, pc::get_lights() });
|
||||
file_hints[pc].emplace_back("game/svm.exe");
|
||||
file_hints[pc].push_back({"game/svm.exe"});
|
||||
}
|
||||
|
||||
const std::vector<std::string> &get_games() {
|
||||
@@ -559,7 +563,7 @@ namespace games {
|
||||
return &it->second;
|
||||
}
|
||||
|
||||
std::vector<std::string> *get_game_file_hints(const std::string &game) {
|
||||
std::vector<std::vector<std::string>> *get_game_file_hints(const std::string &game) {
|
||||
initialize();
|
||||
auto it = file_hints.find(game);
|
||||
if (it == file_hints.end()) {
|
||||
|
||||
@@ -64,5 +64,5 @@ namespace games {
|
||||
std::vector<Analog> *get_analogs(const std::string &game);
|
||||
std::vector<Light> *get_lights(const std::string &game);
|
||||
std::vector<Option> *get_options(const std::string &game);
|
||||
std::vector<std::string> *get_game_file_hints(const std::string &game);
|
||||
std::vector<std::vector<std::string>> *get_game_file_hints(const std::string &game);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -0,0 +1,552 @@
|
||||
#include "bi3a_hook.h"
|
||||
|
||||
#if SPICE64 && !SPICE_XP
|
||||
|
||||
#include <optional>
|
||||
#include <cstdint>
|
||||
#include "util/detour.h"
|
||||
#include "util/logging.h"
|
||||
#include "rawinput/rawinput.h"
|
||||
#include "misc/eamuse.h"
|
||||
#include "games/io.h"
|
||||
#include "popn.h"
|
||||
#include "io.h"
|
||||
#include "util/tapeled.h"
|
||||
#include <typeinfo>
|
||||
|
||||
#ifdef min
|
||||
#undef min
|
||||
#endif
|
||||
#ifdef max
|
||||
#undef max
|
||||
#endif
|
||||
|
||||
namespace games::popn {
|
||||
|
||||
/*
|
||||
* class definitions
|
||||
*/
|
||||
|
||||
struct AIO_NMGR_IOB2 {
|
||||
uint8_t data[0xe18];
|
||||
};
|
||||
|
||||
struct AIO_NMGR_IOB5 {
|
||||
uint8_t data[0xb10];
|
||||
};
|
||||
|
||||
struct AIO_SCI_COMM {
|
||||
uint8_t data[0x108];
|
||||
};
|
||||
|
||||
struct AIO_SCI_COMM_T {
|
||||
uint64_t unk_0;
|
||||
struct AIO_SCI_COMM *aioSciComm;
|
||||
};
|
||||
|
||||
struct AIO_COMM_STATUS {
|
||||
uint64_t unk[5];
|
||||
};
|
||||
|
||||
struct AIO_IOB5_BI3A {
|
||||
uint8_t data[0x4FF0];
|
||||
};
|
||||
|
||||
union AIO_IOB5_BI3A__DEVSTATUS {
|
||||
uint8_t RawBytes[648];
|
||||
struct {
|
||||
uint8_t Reserved0[27]; // 0-26
|
||||
uint8_t Coin0; // 27
|
||||
uint8_t Coin1; // 28
|
||||
uint8_t Reserved1[15]; // 29-43
|
||||
uint8_t TestButton; // 44, io code offset rcx+0xe4
|
||||
uint8_t ServiceButton; // 45, io code offset rcx+0xe5
|
||||
uint8_t CoinMech; // 46, io code offset rcx+0xe6
|
||||
uint8_t Reserved2[19]; // 47-65
|
||||
uint8_t JOYL_SW; // 66
|
||||
uint8_t Reserved3[4]; // 67-70
|
||||
uint8_t JOYR_SW; // 71
|
||||
uint8_t Headphones; // 72
|
||||
uint8_t Recorder; // 73, unused
|
||||
uint8_t Reserved4[295]; // 74-368
|
||||
uint8_t Buttons_0_8; // 369
|
||||
uint8_t Button_9; // 370
|
||||
} Input;
|
||||
};
|
||||
|
||||
// verified with M39-004-2025121500
|
||||
static_assert(sizeof(AIO_NMGR_IOB2) == 0xe18);
|
||||
static_assert(sizeof(AIO_NMGR_IOB5) == 0xb10);
|
||||
static_assert(sizeof(AIO_SCI_COMM) == 0x108);
|
||||
static_assert(sizeof(AIO_IOB5_BI3A) == 0x4FF0);
|
||||
static_assert(sizeof(AIO_IOB5_BI3A__DEVSTATUS) == 0x288);
|
||||
|
||||
/*
|
||||
* typedefs
|
||||
*/
|
||||
|
||||
// libaio.dll
|
||||
typedef AIO_SCI_COMM_T *(__fastcall *aioSciComm_Open_t)(AIO_SCI_COMM_T *unk);
|
||||
typedef void(__fastcall *aioSci_Destroy_t)(AIO_SCI_COMM *i_pNodeMgr);
|
||||
typedef void(__fastcall *aioSci_GetCommStatus_t)(AIO_SCI_COMM *i_pNodeMgr, AIO_COMM_STATUS *i_Status);
|
||||
typedef void(__fastcall *aioNodeMgr_Destroy_t)(AIO_NMGR_IOB5 *i_pNodeMgr);
|
||||
typedef int32_t(__fastcall *aioNodeMgr_GetState_t)(AIO_NMGR_IOB5 *i_pNodeMgr);
|
||||
typedef bool(__fastcall *aioNodeMgr_IsReady_t)(AIO_NMGR_IOB5 *i_pNodeMgr, int32_t i_State);
|
||||
typedef bool(__fastcall *aioNodeMgr_IsError_t)(AIO_NMGR_IOB5 *i_pNodeMgr, int32_t i_State);
|
||||
typedef void (__fastcall *aioNodeCtl_Destroy_t)(AIO_IOB5_BI3A *i_pNodeCtl);
|
||||
typedef int32_t (__fastcall *aioNodeCtl_GetState_t)(AIO_IOB5_BI3A *i_pNodeCtl);
|
||||
typedef bool (__fastcall *aioNodeCtl_IsReady_t)(AIO_IOB5_BI3A *i_pNodeCtl, int32_t i_State);
|
||||
typedef bool (__fastcall *aioNodeCtl_IsError_t)(AIO_IOB5_BI3A *i_pNodeCtl, int32_t i_State);
|
||||
typedef void(__fastcall *aioNodeCtl_UpdateDevicesStatus_t)();
|
||||
|
||||
// libaio-iob5.dll
|
||||
typedef AIO_NMGR_IOB5 *(__fastcall *aioNMgrIob5_Create_t)(AIO_SCI_COMM *i_pSci, uint32_t i_bfMode);
|
||||
typedef AIO_IOB5_BI3A *(__fastcall *aioIob5Bi3a_Create_t)(AIO_NMGR_IOB5 *i_pNodeMgr, uint8_t i, void *p);
|
||||
typedef void(__fastcall *aioIob5Bi3a_GetDeviceStatus_t)(AIO_IOB5_BI3A *i_pNodeCtl, AIO_IOB5_BI3A__DEVSTATUS *o_DevStatus);
|
||||
typedef void(__fastcall *aioIob5Bi3a_SetOutputData_t)(AIO_IOB5_BI3A *a1, unsigned int a2, unsigned __int8 a3);
|
||||
typedef void(__fastcall *aioIob5Bi3a_SetTapeLedDataPart_t)(AIO_IOB5_BI3A *a1, unsigned int a2, char a3, const void *a4, unsigned int a5, bool a6);
|
||||
typedef int64_t (__fastcall *aioIob5Bi3a_SetTapeLedDataLimit_t)(AIO_IOB5_BI3A *i_pNodeCtl, uint32_t i_Channel, uint8_t i_Scale, uint8_t i_Limit);
|
||||
|
||||
/*
|
||||
* function pointers
|
||||
*/
|
||||
|
||||
// libaio.dll
|
||||
static aioSciComm_Open_t aioSciComm_Open_orig = nullptr;
|
||||
static aioSci_Destroy_t aioSci_Destroy_orig = nullptr;
|
||||
static aioSci_GetCommStatus_t aioSci_GetCommStatus_orig = nullptr;
|
||||
static aioNodeMgr_Destroy_t aioNodeMgr_Destroy_orig = nullptr;
|
||||
static aioNodeMgr_GetState_t aioNodeMgr_GetState_orig = nullptr;
|
||||
static aioNodeMgr_IsReady_t aioNodeMgr_IsReady_orig = nullptr;
|
||||
static aioNodeMgr_IsError_t aioNodeMgr_IsError_orig = nullptr;
|
||||
static aioNodeCtl_Destroy_t aioNodeCtl_Destroy_orig = nullptr;
|
||||
static aioNodeCtl_GetState_t aioNodeCtl_GetState_orig = nullptr;
|
||||
static aioNodeCtl_IsReady_t aioNodeCtl_IsReady_orig = nullptr;
|
||||
static aioNodeCtl_IsError_t aioNodeCtl_IsError_orig = nullptr;
|
||||
static aioNodeCtl_UpdateDevicesStatus_t aioNodeCtl_UpdateDevicesStatus_orig = nullptr;
|
||||
|
||||
// libaio-iob5.dll
|
||||
static aioNMgrIob5_Create_t aioNMgrIob5_Create_orig = nullptr;
|
||||
static aioIob5Bi3a_Create_t aioIob5Bi3a_Create_orig = nullptr;
|
||||
static aioIob5Bi3a_GetDeviceStatus_t aioIob5Bi3a_GetDeviceStatus_orig = nullptr;
|
||||
static aioIob5Bi3a_SetOutputData_t aioIob5Bi3a_SetOutputData_orig = nullptr;
|
||||
static aioIob5Bi3a_SetTapeLedDataPart_t aioIob5Bi3a_SetTapeLedDataPart_orig = nullptr;
|
||||
static aioIob5Bi3a_SetTapeLedDataLimit_t aioIob5Bi3a_SetTapeLedDataLimit_orig = nullptr;
|
||||
|
||||
/*
|
||||
* variables
|
||||
*/
|
||||
static AIO_NMGR_IOB2 *aioNmgrIob2;
|
||||
static AIO_NMGR_IOB5 *aioNmgrIob5;
|
||||
static AIO_SCI_COMM *aioSciComm;
|
||||
static AIO_IOB5_BI3A *aioIob5Bi3a;
|
||||
|
||||
/*
|
||||
* implementations
|
||||
*/
|
||||
|
||||
// libaio.dll
|
||||
|
||||
static AIO_SCI_COMM_T *__fastcall aioSciComm_Open(AIO_SCI_COMM_T *unk) {
|
||||
aioSciComm = new AIO_SCI_COMM;
|
||||
memset(unk, 0, sizeof(AIO_SCI_COMM_T));
|
||||
unk->aioSciComm = aioSciComm;
|
||||
return unk;
|
||||
}
|
||||
|
||||
static void __fastcall aioSci_GetCommStatus(AIO_SCI_COMM *i_pNodeMgr, AIO_COMM_STATUS *i_Status) {
|
||||
if (i_pNodeMgr == aioSciComm) {
|
||||
memset(i_Status, 0, sizeof(AIO_COMM_STATUS));
|
||||
} else {
|
||||
return aioSci_GetCommStatus_orig(i_pNodeMgr, i_Status);
|
||||
}
|
||||
}
|
||||
|
||||
static void __fastcall aioSci_Destroy(AIO_SCI_COMM *i_pNodeMgr) {
|
||||
if (i_pNodeMgr == aioSciComm) {
|
||||
delete aioSciComm;
|
||||
} else {
|
||||
return aioSci_Destroy_orig(i_pNodeMgr);
|
||||
}
|
||||
}
|
||||
|
||||
static void __fastcall aioNodeMgr_Destroy(AIO_NMGR_IOB5 *i_pNodeMgr) {
|
||||
if (i_pNodeMgr == aioNmgrIob5 ||
|
||||
i_pNodeMgr == (AIO_NMGR_IOB5 *)aioNmgrIob2) {
|
||||
|
||||
delete aioNmgrIob5;
|
||||
aioNmgrIob5 = nullptr;
|
||||
} else {
|
||||
return aioNodeMgr_Destroy_orig(i_pNodeMgr);
|
||||
}
|
||||
}
|
||||
|
||||
static int32_t __fastcall aioNodeMgr_GetState(AIO_NMGR_IOB5 *i_pNodeMgr) {
|
||||
if (i_pNodeMgr == aioNmgrIob5 ||
|
||||
i_pNodeMgr == (AIO_NMGR_IOB5 *)aioNmgrIob2) {
|
||||
return 1;
|
||||
} else {
|
||||
return aioNodeMgr_GetState_orig(i_pNodeMgr);
|
||||
}
|
||||
}
|
||||
|
||||
static bool __fastcall aioNodeMgr_IsReady(AIO_NMGR_IOB5 *i_pNodeMgr, int32_t i_State) {
|
||||
if (i_pNodeMgr == aioNmgrIob5 ||
|
||||
i_pNodeMgr == (AIO_NMGR_IOB5 *)aioNmgrIob2) {
|
||||
|
||||
return true;
|
||||
} else {
|
||||
return aioNodeMgr_IsReady_orig(i_pNodeMgr, i_State);
|
||||
}
|
||||
}
|
||||
|
||||
static bool __fastcall aioNodeMgr_IsError(AIO_NMGR_IOB5 *i_pNodeMgr, int32_t i_State) {
|
||||
if (i_pNodeMgr == aioNmgrIob5 ||
|
||||
i_pNodeMgr == (AIO_NMGR_IOB5 *)aioNmgrIob2) {
|
||||
|
||||
return false;
|
||||
} else {
|
||||
return aioNodeMgr_IsError_orig(i_pNodeMgr, i_State);
|
||||
}
|
||||
}
|
||||
|
||||
static void __fastcall aioNodeCtl_Destroy(AIO_IOB5_BI3A *i_pNodeCtl) {
|
||||
if (i_pNodeCtl != aioIob5Bi3a) {
|
||||
return aioNodeCtl_Destroy_orig(i_pNodeCtl);
|
||||
}
|
||||
delete aioIob5Bi3a;
|
||||
aioIob5Bi3a = nullptr;
|
||||
}
|
||||
|
||||
static int32_t __fastcall aioNodeCtl_GetState(AIO_IOB5_BI3A *i_pNodeCtl) {
|
||||
if (i_pNodeCtl == aioIob5Bi3a) {
|
||||
return 1;
|
||||
}
|
||||
return aioNodeCtl_GetState_orig(i_pNodeCtl);
|
||||
}
|
||||
|
||||
static bool __fastcall aioNodeCtl_IsReady(AIO_IOB5_BI3A *i_pNodeCtl, int32_t i_State) {
|
||||
if (i_pNodeCtl == aioIob5Bi3a) {
|
||||
return true;
|
||||
}
|
||||
return aioNodeCtl_IsReady_orig(i_pNodeCtl, i_State);
|
||||
}
|
||||
|
||||
static bool __fastcall aioNodeCtl_IsError(AIO_IOB5_BI3A *i_pNodeCtl, int32_t i_State) {
|
||||
if (i_pNodeCtl == aioIob5Bi3a) {
|
||||
return false;
|
||||
}
|
||||
return aioNodeCtl_IsError_orig(i_pNodeCtl, i_State);
|
||||
}
|
||||
|
||||
static void __fastcall aioNodeCtl_UpdateDevicesStatus() {
|
||||
}
|
||||
|
||||
// libaio-iob5.dll
|
||||
|
||||
static AIO_NMGR_IOB5 *__fastcall aioNMgrIob5_Create(AIO_SCI_COMM *i_pSci, uint32_t i_bfMode) {
|
||||
|
||||
if (i_pSci == aioSciComm) {
|
||||
aioNmgrIob5 = new AIO_NMGR_IOB5;
|
||||
log_info("bi3a_hook", "aioNMgrIob5_Create called, returning custom AIO_NMGR_IOB5: {}", fmt::ptr(aioNmgrIob5));
|
||||
return aioNmgrIob5;
|
||||
} else {
|
||||
return aioNMgrIob5_Create_orig(i_pSci, i_bfMode);
|
||||
}
|
||||
}
|
||||
|
||||
static AIO_IOB5_BI3A *__fastcall aioIob5Bi3a_Create(AIO_NMGR_IOB5 *i_pNodeMgr, uint8_t i, void *p) {
|
||||
|
||||
log_info("bi3a_hook", "aioIob5Bi3a_Create called with i_pNodeMgr={}, i={}, p={}", fmt::ptr(i_pNodeMgr), i, fmt::ptr(p));
|
||||
|
||||
if (i_pNodeMgr == aioNmgrIob5) {
|
||||
aioIob5Bi3a = new AIO_IOB5_BI3A;
|
||||
log_info("bi3a_hook", "aioIob5Bi3a_Create: returning custom AIO_IOB5_BI3A: {}", fmt::ptr(aioIob5Bi3a));
|
||||
return aioIob5Bi3a;
|
||||
} else {
|
||||
return aioIob5Bi3a_Create_orig(i_pNodeMgr, i, p);
|
||||
}
|
||||
}
|
||||
|
||||
static void __fastcall aioIob5Bi3a_GetDeviceStatus(AIO_IOB5_BI3A *i_pNodeCtl, AIO_IOB5_BI3A__DEVSTATUS *o_DevStatus) {
|
||||
RI_MGR->devices_flush_output();
|
||||
if (i_pNodeCtl != aioIob5Bi3a) {
|
||||
return aioIob5Bi3a_GetDeviceStatus_orig(i_pNodeCtl, o_DevStatus);
|
||||
}
|
||||
|
||||
memset(o_DevStatus, 0, sizeof(*o_DevStatus));
|
||||
auto &buttons = get_buttons();
|
||||
|
||||
// operator
|
||||
if (GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::Test])) {
|
||||
o_DevStatus->Input.TestButton = 1;
|
||||
}
|
||||
if (GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::Service])) {
|
||||
o_DevStatus->Input.ServiceButton = 1;
|
||||
}
|
||||
if (GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::CoinMech])) {
|
||||
o_DevStatus->Input.CoinMech = 1;
|
||||
}
|
||||
|
||||
// buttons 1-9
|
||||
if (GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::Button1])) {
|
||||
o_DevStatus->Input.Buttons_0_8 |= (1 << 0);
|
||||
}
|
||||
if (GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::Button2])) {
|
||||
o_DevStatus->Input.Buttons_0_8 |= (1 << 1);
|
||||
}
|
||||
if (GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::Button3])) {
|
||||
o_DevStatus->Input.Buttons_0_8 |= (1 << 2);
|
||||
}
|
||||
if (GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::Button4])) {
|
||||
o_DevStatus->Input.Buttons_0_8 |= (1 << 3);
|
||||
}
|
||||
if (GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::Button5])) {
|
||||
o_DevStatus->Input.Buttons_0_8 |= (1 << 4);
|
||||
}
|
||||
if (GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::Button6])) {
|
||||
o_DevStatus->Input.Buttons_0_8 |= (1 << 5);
|
||||
}
|
||||
if (GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::Button7])) {
|
||||
o_DevStatus->Input.Buttons_0_8 |= (1 << 6);
|
||||
}
|
||||
if (GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::Button8])) {
|
||||
o_DevStatus->Input.Buttons_0_8 |= (1 << 7);
|
||||
}
|
||||
if (GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::Button9])) {
|
||||
o_DevStatus->Input.Button_9 = 1;
|
||||
}
|
||||
|
||||
// deka pop-kuns
|
||||
if (GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::RedPop])) {
|
||||
o_DevStatus->Input.JOYL_SW = 1;
|
||||
}
|
||||
if (GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::BluePop])) {
|
||||
o_DevStatus->Input.JOYR_SW = 1;
|
||||
}
|
||||
|
||||
// audio
|
||||
if (GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::Headphone])) {
|
||||
o_DevStatus->Input.Headphones = 1;
|
||||
}
|
||||
|
||||
// coin
|
||||
o_DevStatus->Input.Coin0 += eamuse_coin_get_stock();
|
||||
}
|
||||
|
||||
static void __fastcall aioIob5Bi3a_SetOutputData(AIO_IOB5_BI3A *i_pNodeCtl, uint32_t i_CnPin, uint8_t i_Data) {
|
||||
if (i_pNodeCtl != aioIob5Bi3a) {
|
||||
return aioIob5Bi3a_SetOutputData_orig(i_pNodeCtl, i_CnPin, i_Data);
|
||||
}
|
||||
|
||||
// 7 = IC_CARD_R
|
||||
// 8 = IC_CARD_G
|
||||
// 9 = IC_CARD_B
|
||||
// 10 = WOOFER_R
|
||||
// 11 = WOOFER_G
|
||||
// 12 = WOOFER_B
|
||||
// values = 0 to 255 in test menu
|
||||
|
||||
std::optional<Lights::popn_lights_t> light;
|
||||
switch (i_CnPin) {
|
||||
case 7:
|
||||
light = Lights::IC_Card_R;
|
||||
break;
|
||||
case 8:
|
||||
light = Lights::IC_Card_G;
|
||||
break;
|
||||
case 9:
|
||||
light = Lights::IC_Card_B;
|
||||
break;
|
||||
case 10:
|
||||
light = Lights::WooferLED_R;
|
||||
break;
|
||||
case 11:
|
||||
light = Lights::WooferLED_G;
|
||||
break;
|
||||
case 12:
|
||||
light = Lights::WooferLED_B;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
if (light.has_value()) {
|
||||
auto &lights = get_lights();
|
||||
GameAPI::Lights::writeLight(RI_MGR, lights.at(light.value()), i_Data / 255.f);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
static int64_t __fastcall aioIob5Bi3a_SetTapeLedDataLimit(
|
||||
AIO_IOB5_BI3A *i_pNodeCtl, uint32_t i_Channel, uint8_t i_Scale, uint8_t i_Limit) {
|
||||
|
||||
if (i_pNodeCtl != aioIob5Bi3a) {
|
||||
return aioIob5Bi3a_SetTapeLedDataLimit_orig(i_pNodeCtl, i_Channel, i_Scale, i_Limit);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void __fastcall aioIob5Bi3a_SetTapeLedDataPart(
|
||||
AIO_IOB5_BI3A *i_pNodeCtl, uint32_t i_CnPin, char i_LedType, const void *i_pData, uint32_t i_DataSize, bool i_bIsLast) {
|
||||
|
||||
if (i_pNodeCtl != aioIob5Bi3a) {
|
||||
return aioIob5Bi3a_SetTapeLedDataPart_orig(i_pNodeCtl, i_CnPin, i_LedType, i_pData, i_DataSize, i_bIsLast);
|
||||
}
|
||||
|
||||
auto number_of_leds = i_DataSize;
|
||||
// buttons 1-9, but game passes in wrong length; fix it up
|
||||
if (i_CnPin == 0 && i_DataSize == 9) {
|
||||
number_of_leds = i_DataSize * 3;
|
||||
}
|
||||
|
||||
if (FALSE) {
|
||||
std::string data;
|
||||
for (uint32_t i = 0; i < number_of_leds; i++) {
|
||||
auto dataByte = ((uint8_t *)i_pData)[i];
|
||||
data += fmt::format("{:02X} ", dataByte);
|
||||
}
|
||||
|
||||
log_info(
|
||||
"bi3a_hook",
|
||||
"aioIob5Bi3a_SetTapeLedDataPart Pin={}, Size={}, Type={}, data@{} = {}",
|
||||
i_CnPin, number_of_leds, (uint8_t)i_LedType, fmt::ptr(i_pData), data);
|
||||
}
|
||||
|
||||
auto &lights = get_lights();
|
||||
if (i_CnPin == 0 && number_of_leds == 9 * 3) {
|
||||
// special handling converting to non-RGB lights
|
||||
// take the max(R, G, B) and use it to write the light value
|
||||
uint8_t light_value[9] = { 0 };
|
||||
for (uint32_t i = 0; i < number_of_leds; i += 3) {
|
||||
light_value[i / 3] =
|
||||
std::max({ ((uint8_t *)i_pData)[i], ((uint8_t *)i_pData)[i + 1], ((uint8_t *)i_pData)[i + 2] });
|
||||
}
|
||||
|
||||
constexpr Lights::popn_lights_t legacy_button_lights[] = {
|
||||
Lights::popn_lights_t::Button1,
|
||||
Lights::popn_lights_t::Button2,
|
||||
Lights::popn_lights_t::Button3,
|
||||
Lights::popn_lights_t::Button4,
|
||||
Lights::popn_lights_t::Button5,
|
||||
Lights::popn_lights_t::Button6,
|
||||
Lights::popn_lights_t::Button7,
|
||||
Lights::popn_lights_t::Button8,
|
||||
Lights::popn_lights_t::Button9
|
||||
};
|
||||
|
||||
static_assert(std::size(legacy_button_lights) == 9);
|
||||
|
||||
for (size_t light = 0; light < 9; light++) {
|
||||
// on the new cab, buttons are colorless plastic and they rely on RGB to be lit at all times, even when "off"
|
||||
// when translating to legacy on/off lights, treat anything above ~60% brightness as fully on,
|
||||
// otherwise off, to avoid dimly lit "off" state
|
||||
GameAPI::Lights::writeLight(
|
||||
RI_MGR,
|
||||
lights.at(legacy_button_lights[light]),
|
||||
light_value[light] > 150 ? 1.f : 0.f);
|
||||
}
|
||||
|
||||
// color buttons
|
||||
constexpr Lights::popn_lights_t button_lights[] = {
|
||||
Lights::popn_lights_t::PikaButton1_R, Lights::popn_lights_t::PikaButton1_G, Lights::popn_lights_t::PikaButton1_B,
|
||||
Lights::popn_lights_t::PikaButton2_R, Lights::popn_lights_t::PikaButton2_G, Lights::popn_lights_t::PikaButton2_B,
|
||||
Lights::popn_lights_t::PikaButton3_R, Lights::popn_lights_t::PikaButton3_G, Lights::popn_lights_t::PikaButton3_B,
|
||||
Lights::popn_lights_t::PikaButton4_R, Lights::popn_lights_t::PikaButton4_G, Lights::popn_lights_t::PikaButton4_B,
|
||||
Lights::popn_lights_t::PikaButton5_R, Lights::popn_lights_t::PikaButton5_G, Lights::popn_lights_t::PikaButton5_B,
|
||||
Lights::popn_lights_t::PikaButton6_R, Lights::popn_lights_t::PikaButton6_G, Lights::popn_lights_t::PikaButton6_B,
|
||||
Lights::popn_lights_t::PikaButton7_R, Lights::popn_lights_t::PikaButton7_G, Lights::popn_lights_t::PikaButton7_B,
|
||||
Lights::popn_lights_t::PikaButton8_R, Lights::popn_lights_t::PikaButton8_G, Lights::popn_lights_t::PikaButton8_B,
|
||||
Lights::popn_lights_t::PikaButton9_R, Lights::popn_lights_t::PikaButton9_G, Lights::popn_lights_t::PikaButton9_B
|
||||
};
|
||||
|
||||
static_assert(std::size(button_lights) == 9 * 3);
|
||||
|
||||
for (size_t light = 0; light < number_of_leds; light++) {
|
||||
GameAPI::Lights::writeLight(
|
||||
RI_MGR,
|
||||
lights.at(button_lights[light]),
|
||||
((uint8_t *)i_pData)[light] / 255.f);
|
||||
}
|
||||
}
|
||||
|
||||
if (tapeledutils::is_enabled() && i_CnPin > 0 && i_CnPin < std::size(TAPELED_MAPPING)) {
|
||||
auto &map = TAPELED_MAPPING[i_CnPin];
|
||||
const auto data_size = std::min(map.data.capacity(), (size_t)number_of_leds / 3);
|
||||
|
||||
// pick a color to use
|
||||
const auto rgb = tapeledutils::pick_color_from_led_tape(map, (uint8_t *)i_pData, data_size);
|
||||
|
||||
// program the lights into API
|
||||
auto &lights = get_lights();
|
||||
GameAPI::Lights::writeLight(RI_MGR, lights[map.index_r], rgb.r);
|
||||
GameAPI::Lights::writeLight(RI_MGR, lights[map.index_g], rgb.g);
|
||||
GameAPI::Lights::writeLight(RI_MGR, lights[map.index_b], rgb.b);
|
||||
|
||||
// tape LED output over API not implemented
|
||||
// for (size_t i = 0; i < data_size; i++) {
|
||||
// map.data[i].r = ((uint8_t *)i_pData)[i * 3];
|
||||
// map.data[i].g = ((uint8_t *)i_pData)[i * 3 + 1];
|
||||
// map.data[i].b = ((uint8_t *)i_pData)[i * 3 + 2];
|
||||
// }
|
||||
}
|
||||
}
|
||||
|
||||
void bi3a_hook_init() {
|
||||
|
||||
// avoid double init
|
||||
static bool initialized = false;
|
||||
if (initialized) {
|
||||
return;
|
||||
} else {
|
||||
initialized = true;
|
||||
}
|
||||
|
||||
// announce
|
||||
log_info("bi3a_hook", "init");
|
||||
|
||||
// libaio-iob5.dll
|
||||
const auto libaioIob5Dll = "libaio-iob5.dll";
|
||||
detour::trampoline_try(libaioIob5Dll, "aioNMgrIob5_Create",
|
||||
aioNMgrIob5_Create, &aioNMgrIob5_Create_orig);
|
||||
detour::trampoline_try(libaioIob5Dll, "aioIob5Bi3a_Create",
|
||||
aioIob5Bi3a_Create, &aioIob5Bi3a_Create_orig);
|
||||
detour::trampoline_try(libaioIob5Dll, "aioIob5Bi3a_GetDeviceStatus",
|
||||
aioIob5Bi3a_GetDeviceStatus, &aioIob5Bi3a_GetDeviceStatus_orig);
|
||||
detour::trampoline_try(libaioIob5Dll, "aioIob5Bi3a_SetOutputData",
|
||||
aioIob5Bi3a_SetOutputData, &aioIob5Bi3a_SetOutputData_orig);
|
||||
detour::trampoline_try(libaioIob5Dll, "aioIob5Bi3a_SetTapeLedDataPart",
|
||||
aioIob5Bi3a_SetTapeLedDataPart, &aioIob5Bi3a_SetTapeLedDataPart_orig);
|
||||
detour::trampoline_try(libaioIob5Dll, "aioIob5Bi3a_SetTapeLedDataLimit",
|
||||
aioIob5Bi3a_SetTapeLedDataLimit, &aioIob5Bi3a_SetTapeLedDataLimit_orig);
|
||||
|
||||
// libaio.dll
|
||||
const auto libaioDll = "libaio.dll";
|
||||
detour::trampoline_try(libaioDll, "?Open@AIO_SCI_COMM@@SA?AU?$AC_ERESULT_T@PEAVAIO_SCI_COMM@@@@PEBD0AEBUSETTING@1@I@Z",
|
||||
aioSciComm_Open, &aioSciComm_Open_orig);
|
||||
detour::trampoline_try(libaioDll, "aioSci_GetCommStatus",
|
||||
aioSci_GetCommStatus, &aioSci_GetCommStatus_orig);
|
||||
detour::trampoline_try(libaioDll, "aioSci_Destroy",
|
||||
aioSci_Destroy, &aioSci_Destroy_orig);
|
||||
detour::trampoline_try(libaioDll, "aioNodeMgr_Destroy",
|
||||
aioNodeMgr_Destroy, &aioNodeMgr_Destroy_orig);
|
||||
detour::trampoline_try(libaioDll, "aioNodeMgr_GetState",
|
||||
aioNodeMgr_GetState, &aioNodeMgr_GetState_orig);
|
||||
detour::trampoline_try(libaioDll, "aioNodeMgr_IsReady",
|
||||
aioNodeMgr_IsReady, &aioNodeMgr_IsReady_orig);
|
||||
detour::trampoline_try(libaioDll, "aioNodeMgr_IsError",
|
||||
aioNodeMgr_IsError, &aioNodeMgr_IsError_orig);
|
||||
detour::trampoline_try(libaioDll, "aioNodeCtl_Destroy",
|
||||
aioNodeCtl_Destroy, &aioNodeCtl_Destroy_orig);
|
||||
detour::trampoline_try(libaioDll, "aioNodeCtl_GetState",
|
||||
aioNodeCtl_GetState, &aioNodeCtl_GetState_orig);
|
||||
detour::trampoline_try(libaioDll, "aioNodeCtl_IsReady",
|
||||
aioNodeCtl_IsReady, &aioNodeCtl_IsReady_orig);
|
||||
detour::trampoline_try(libaioDll, "aioNodeCtl_IsError",
|
||||
aioNodeCtl_IsError, &aioNodeCtl_IsError_orig);
|
||||
detour::trampoline_try(libaioDll, "aioNodeCtl_UpdateDevicesStatus",
|
||||
aioNodeCtl_UpdateDevicesStatus, &aioNodeCtl_UpdateDevicesStatus_orig);
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,9 @@
|
||||
#pragma once
|
||||
|
||||
#if SPICE64 && !SPICE_XP
|
||||
|
||||
namespace games::popn {
|
||||
void bi3a_hook_init();
|
||||
}
|
||||
|
||||
#endif
|
||||
+128
-78
@@ -11,37 +11,7 @@ std::vector<Button> &games::popn::get_buttons() {
|
||||
"Service",
|
||||
"Test",
|
||||
"Coin Mech",
|
||||
"Button 1",
|
||||
"Button 2",
|
||||
"Button 3",
|
||||
"Button 4",
|
||||
"Button 5",
|
||||
"Button 6",
|
||||
"Button 7",
|
||||
"Button 8",
|
||||
"Button 9"
|
||||
);
|
||||
}
|
||||
|
||||
return buttons;
|
||||
}
|
||||
|
||||
std::string games::popn::get_buttons_help() {
|
||||
// keep to max 100 characters wide
|
||||
return
|
||||
" 2 4 6 8 \n"
|
||||
" 1 3 5 7 9 "
|
||||
;
|
||||
}
|
||||
|
||||
std::vector<Light> &games::popn::get_lights() {
|
||||
static std::vector<Light> lights;
|
||||
|
||||
if (lights.empty()) {
|
||||
lights = GameAPI::Lights::getLights("Pop'n Music");
|
||||
|
||||
GameAPI::Lights::sortLights(
|
||||
&lights,
|
||||
"Headphones",
|
||||
"Button 1",
|
||||
"Button 2",
|
||||
"Button 3",
|
||||
@@ -51,55 +21,135 @@ std::vector<Light> &games::popn::get_lights() {
|
||||
"Button 7",
|
||||
"Button 8",
|
||||
"Button 9",
|
||||
"Top LED 1",
|
||||
"Top LED 2",
|
||||
"Top LED 3",
|
||||
"Top LED 4",
|
||||
"Top LED 5",
|
||||
"Top LED 6",
|
||||
"Top LED 7",
|
||||
"Top LED 8",
|
||||
"Top LED 9",
|
||||
"Top LED 10",
|
||||
"Top LED 11",
|
||||
"Top LED 12",
|
||||
"Top LED 13",
|
||||
"Top LED 14",
|
||||
"Top LED 15",
|
||||
"Top LED 16",
|
||||
"Top LED 17",
|
||||
"Top LED 18",
|
||||
"Top LED 19",
|
||||
"Top LED 20",
|
||||
"Top LED 21",
|
||||
"Top LED 22",
|
||||
"Top LED 23",
|
||||
"Top LED 24",
|
||||
"Top LED 25",
|
||||
"Top LED 26",
|
||||
"Top LED 27",
|
||||
"Top LED 28",
|
||||
"Top LED 29",
|
||||
"Top LED 30",
|
||||
"Top LED 31",
|
||||
"Top LED 32",
|
||||
"Top LED R",
|
||||
"Top LED G",
|
||||
"Top LED B",
|
||||
"Hi Lamp 1",
|
||||
"Hi Lamp 2",
|
||||
"Hi Lamp 3",
|
||||
"Hi Lamp 4",
|
||||
"Hi Lamp 5",
|
||||
"Left Lamp 1",
|
||||
"Left Lamp 2",
|
||||
"Right Lamp 1",
|
||||
"Right Lamp 2",
|
||||
"Woofer LED R",
|
||||
"Woofer LED G",
|
||||
"Woofer LED B"
|
||||
"Red Pop-Kun",
|
||||
"Blue Pop-Kun"
|
||||
);
|
||||
}
|
||||
|
||||
return buttons;
|
||||
}
|
||||
|
||||
std::string games::popn::get_buttons_help() {
|
||||
// keep to max 100 characters wide
|
||||
return
|
||||
"RED 2 4 6 8 BLUE\n"
|
||||
" 1 3 5 7 9"
|
||||
;
|
||||
}
|
||||
|
||||
std::vector<Light> &games::popn::get_lights() {
|
||||
static std::vector<Light> lights;
|
||||
|
||||
if (lights.empty()) {
|
||||
lights = GameAPI::Lights::getLights("Pop'n Music");
|
||||
|
||||
GameAPI::Lights::sortLightsWithCategory(&lights, {
|
||||
{"Common", "Button 1"},
|
||||
{"Common", "Button 2"},
|
||||
{"Common", "Button 3"},
|
||||
{"Common", "Button 4"},
|
||||
{"Common", "Button 5"},
|
||||
{"Common", "Button 6"},
|
||||
{"Common", "Button 7"},
|
||||
{"Common", "Button 8"},
|
||||
{"Common", "Button 9"},
|
||||
{"SD", "Hi Lamp 1"},
|
||||
{"SD", "Hi Lamp 2"},
|
||||
{"SD", "Hi Lamp 3"},
|
||||
{"SD", "Hi Lamp 4"},
|
||||
{"SD", "Hi Lamp 5"},
|
||||
{"SD", "Left Lamp 1"},
|
||||
{"SD", "Left Lamp 2"},
|
||||
{"SD", "Right Lamp 1"},
|
||||
{"SD", "Right Lamp 2"},
|
||||
{"HD", "Top LED 1"},
|
||||
{"HD", "Top LED 2"},
|
||||
{"HD", "Top LED 3"},
|
||||
{"HD", "Top LED 4"},
|
||||
{"HD", "Top LED 5"},
|
||||
{"HD", "Top LED 6"},
|
||||
{"HD", "Top LED 7"},
|
||||
{"HD", "Top LED 8"},
|
||||
{"HD", "Top LED 9"},
|
||||
{"HD", "Top LED 10"},
|
||||
{"HD", "Top LED 11"},
|
||||
{"HD", "Top LED 12"},
|
||||
{"HD", "Top LED 13"},
|
||||
{"HD", "Top LED 14"},
|
||||
{"HD", "Top LED 15"},
|
||||
{"HD", "Top LED 16"},
|
||||
{"HD", "Top LED 17"},
|
||||
{"HD", "Top LED 18"},
|
||||
{"HD", "Top LED 19"},
|
||||
{"HD", "Top LED 20"},
|
||||
{"HD", "Top LED 21"},
|
||||
{"HD", "Top LED 22"},
|
||||
{"HD", "Top LED 23"},
|
||||
{"HD", "Top LED 24"},
|
||||
{"HD", "Top LED 25"},
|
||||
{"HD", "Top LED 26"},
|
||||
{"HD", "Top LED 27"},
|
||||
{"HD", "Top LED 28"},
|
||||
{"HD", "Top LED 29"},
|
||||
{"HD", "Top LED 30"},
|
||||
{"HD", "Top LED 31"},
|
||||
{"HD", "Top LED 32"},
|
||||
{"HD/Pika", "Top LED R"},
|
||||
{"HD/Pika", "Top LED G"},
|
||||
{"HD/Pika", "Top LED B"},
|
||||
{"HD/Pika", "Woofer LED R"},
|
||||
{"HD/Pika", "Woofer LED G"},
|
||||
{"HD/Pika", "Woofer LED B"},
|
||||
{"Pika", "Button 1 R"},
|
||||
{"Pika", "Button 1 G"},
|
||||
{"Pika", "Button 1 B"},
|
||||
{"Pika", "Button 2 R"},
|
||||
{"Pika", "Button 2 G"},
|
||||
{"Pika", "Button 2 B"},
|
||||
{"Pika", "Button 3 R"},
|
||||
{"Pika", "Button 3 G"},
|
||||
{"Pika", "Button 3 B"},
|
||||
{"Pika", "Button 4 R"},
|
||||
{"Pika", "Button 4 G"},
|
||||
{"Pika", "Button 4 B"},
|
||||
{"Pika", "Button 5 R"},
|
||||
{"Pika", "Button 5 G"},
|
||||
{"Pika", "Button 5 B"},
|
||||
{"Pika", "Button 6 R"},
|
||||
{"Pika", "Button 6 G"},
|
||||
{"Pika", "Button 6 B"},
|
||||
{"Pika", "Button 7 R"},
|
||||
{"Pika", "Button 7 G"},
|
||||
{"Pika", "Button 7 B"},
|
||||
{"Pika", "Button 8 R"},
|
||||
{"Pika", "Button 8 G"},
|
||||
{"Pika", "Button 8 B"},
|
||||
{"Pika", "Button 9 R"},
|
||||
{"Pika", "Button 9 G"},
|
||||
{"Pika", "Button 9 B"},
|
||||
{"Pika", "Red Pop-Kun R"},
|
||||
{"Pika", "Red Pop-Kun G"},
|
||||
{"Pika", "Red Pop-Kun B"},
|
||||
{"Pika", "Blue Pop-Kun R"},
|
||||
{"Pika", "Blue Pop-Kun G"},
|
||||
{"Pika", "Blue Pop-Kun B"},
|
||||
{"Pika", "IC Card R"},
|
||||
{"Pika", "IC Card G"},
|
||||
{"Pika", "IC Card B"},
|
||||
{"Pika", "Speakers R"},
|
||||
{"Pika", "Speakers G"},
|
||||
{"Pika", "Speakers B"},
|
||||
{"Pika", "Monitor R"},
|
||||
{"Pika", "Monitor G"},
|
||||
{"Pika", "Monitor B"},
|
||||
{"Pika", "Control Panel R"},
|
||||
{"Pika", "Control Panel G"},
|
||||
{"Pika", "Control Panel B"},
|
||||
{"Pika", "Cabinet R"},
|
||||
{"Pika", "Cabinet G"},
|
||||
{"Pika", "Cabinet B"},
|
||||
});
|
||||
}
|
||||
|
||||
return lights;
|
||||
}
|
||||
|
||||
+62
-11
@@ -11,6 +11,7 @@ namespace games::popn {
|
||||
Service,
|
||||
Test,
|
||||
CoinMech,
|
||||
Headphone,
|
||||
Button1,
|
||||
Button2,
|
||||
Button3,
|
||||
@@ -20,12 +21,14 @@ namespace games::popn {
|
||||
Button7,
|
||||
Button8,
|
||||
Button9,
|
||||
RedPop,
|
||||
BluePop
|
||||
};
|
||||
}
|
||||
|
||||
// all lights in correct order
|
||||
namespace Lights {
|
||||
enum {
|
||||
typedef enum {
|
||||
Button1,
|
||||
Button2,
|
||||
Button3,
|
||||
@@ -35,6 +38,15 @@ namespace games::popn {
|
||||
Button7,
|
||||
Button8,
|
||||
Button9,
|
||||
HiLamp1,
|
||||
HiLamp2,
|
||||
HiLamp3,
|
||||
HiLamp4,
|
||||
HiLamp5,
|
||||
LeftLamp1,
|
||||
LeftLamp2,
|
||||
RightLamp1,
|
||||
RightLamp2,
|
||||
TopLED1,
|
||||
TopLED2,
|
||||
TopLED3,
|
||||
@@ -70,19 +82,58 @@ namespace games::popn {
|
||||
TopLED_R,
|
||||
TopLED_G,
|
||||
TopLED_B,
|
||||
HiLamp1,
|
||||
HiLamp2,
|
||||
HiLamp3,
|
||||
HiLamp4,
|
||||
HiLamp5,
|
||||
LeftLamp1,
|
||||
LeftLamp2,
|
||||
RightLamp1,
|
||||
RightLamp2,
|
||||
WooferLED_R,
|
||||
WooferLED_G,
|
||||
WooferLED_B,
|
||||
};
|
||||
PikaButton1_R,
|
||||
PikaButton1_G,
|
||||
PikaButton1_B,
|
||||
PikaButton2_R,
|
||||
PikaButton2_G,
|
||||
PikaButton2_B,
|
||||
PikaButton3_R,
|
||||
PikaButton3_G,
|
||||
PikaButton3_B,
|
||||
PikaButton4_R,
|
||||
PikaButton4_G,
|
||||
PikaButton4_B,
|
||||
PikaButton5_R,
|
||||
PikaButton5_G,
|
||||
PikaButton5_B,
|
||||
PikaButton6_R,
|
||||
PikaButton6_G,
|
||||
PikaButton6_B,
|
||||
PikaButton7_R,
|
||||
PikaButton7_G,
|
||||
PikaButton7_B,
|
||||
PikaButton8_R,
|
||||
PikaButton8_G,
|
||||
PikaButton8_B,
|
||||
PikaButton9_R,
|
||||
PikaButton9_G,
|
||||
PikaButton9_B,
|
||||
RedPopKun_R,
|
||||
RedPopKun_G,
|
||||
RedPopKun_B,
|
||||
BluePopKun_R,
|
||||
BluePopKun_G,
|
||||
BluePopKun_B,
|
||||
IC_Card_R,
|
||||
IC_Card_G,
|
||||
IC_Card_B,
|
||||
Speaker_R,
|
||||
Speaker_G,
|
||||
Speaker_B,
|
||||
Monitor_R,
|
||||
Monitor_G,
|
||||
Monitor_B,
|
||||
ControlPanel_R,
|
||||
ControlPanel_G,
|
||||
ControlPanel_B,
|
||||
Cabinet_R,
|
||||
Cabinet_G,
|
||||
Cabinet_B,
|
||||
} popn_lights_t;
|
||||
}
|
||||
|
||||
// getters
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
#include "avs/game.h"
|
||||
#include "popn.h"
|
||||
#include "bi3a_hook.h"
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
#include "hooks/graphics/graphics.h"
|
||||
#include "rawinput/rawinput.h"
|
||||
#include "util/detour.h"
|
||||
#include "util/fileutils.h"
|
||||
@@ -9,14 +11,327 @@
|
||||
#include "util/utils.h"
|
||||
#include "cfg/button.h"
|
||||
#include "cfg/api.h"
|
||||
#include "hooks/setupapihook.h"
|
||||
#include "hooks/sleephook.h"
|
||||
#include "launcher/launcher.h"
|
||||
#include "launcher/logger.h"
|
||||
#include "misc/eamuse.h"
|
||||
#include "util/sysutils.h"
|
||||
#include "io.h"
|
||||
#include "util/deferlog.h"
|
||||
#include "misc/nativetouchhook.h"
|
||||
#include "misc/wintouchemu.h"
|
||||
|
||||
namespace games::popn {
|
||||
|
||||
bool SHOW_PIKA_MONITOR_WARNING = false;
|
||||
bool NATIVE_TOUCH = false;
|
||||
|
||||
#if SPICE64 && !SPICE_XP
|
||||
|
||||
const HMONITOR FAKE_MONITOR_HMONITOR = (HMONITOR)-1;
|
||||
constexpr LONG FAKE_MONITOR_OFFSET_X_Y = -999999;
|
||||
constexpr LONG FAKE_MONITOR_WIDTH = 1280;
|
||||
constexpr LONG FAKE_MONITOR_HEIGHT = 800;
|
||||
|
||||
// pin 0 = buttons 1-3, size 9 (but it's actually triple that in reality - game passes in wrong length), values 0-0xFF
|
||||
// pin 1 = left pop (red pop kun), size 16, values 0-0xFF
|
||||
// pin 2 = right pop (blue pop kun), size 16, values 0-0xFF
|
||||
// pin 3 = title, size 44, values 0-0xFF
|
||||
// pin 4 = speaker, size 40, values 0-0x7F - U region only
|
||||
// pin 5 = monitor, size 18, values 0-0x7F - U region only
|
||||
// pin 6 = control panel, size 28, values 0-0x7F - U region only
|
||||
// pin 7 = cabinet, size 22, values 0-0x7F - U region only
|
||||
tapeledutils::tape_led TAPELED_MAPPING[POPN_TAPELED_TOTAL] = {
|
||||
{
|
||||
// [0] button lights: these are button lights and handled specially
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
"Invalid"
|
||||
},
|
||||
{
|
||||
// [1] red pop-kun
|
||||
5, // 5*3; game reports 16 but make it 15
|
||||
Lights::popn_lights_t::RedPopKun_R,
|
||||
Lights::popn_lights_t::RedPopKun_G,
|
||||
Lights::popn_lights_t::RedPopKun_B,
|
||||
0xFF,
|
||||
"Red Pop-Kun"
|
||||
},
|
||||
{
|
||||
// [2] blue pop-kun
|
||||
5, // 5*3; game reports 16 but make it 15
|
||||
Lights::popn_lights_t::BluePopKun_R,
|
||||
Lights::popn_lights_t::BluePopKun_G,
|
||||
Lights::popn_lights_t::BluePopKun_B,
|
||||
0xFF,
|
||||
"Blue Pop-Kun"
|
||||
},
|
||||
{
|
||||
// [3] top LED
|
||||
14, // 14*3; game reports 44 but make it 42
|
||||
Lights::popn_lights_t::TopLED_R,
|
||||
Lights::popn_lights_t::TopLED_G,
|
||||
Lights::popn_lights_t::TopLED_B,
|
||||
0xFF,
|
||||
"Top"
|
||||
},
|
||||
{
|
||||
// [4] speakers
|
||||
13, // 13*3; game reports 40 but make it 39
|
||||
Lights::popn_lights_t::Speaker_G,
|
||||
Lights::popn_lights_t::Speaker_R,
|
||||
Lights::popn_lights_t::Speaker_B,
|
||||
0x7F,
|
||||
"Speakers"
|
||||
},
|
||||
{
|
||||
// [5] monitor
|
||||
6,
|
||||
Lights::popn_lights_t::Monitor_G,
|
||||
Lights::popn_lights_t::Monitor_R,
|
||||
Lights::popn_lights_t::Monitor_B,
|
||||
0x7F,
|
||||
"Monitor"
|
||||
},
|
||||
{
|
||||
// [6] control panel
|
||||
9, // 9*3; game reports 28
|
||||
Lights::popn_lights_t::ControlPanel_G,
|
||||
Lights::popn_lights_t::ControlPanel_R,
|
||||
Lights::popn_lights_t::ControlPanel_B,
|
||||
0x7F,
|
||||
"Control Panel"
|
||||
},
|
||||
{
|
||||
// [7] Cabinet
|
||||
7, // 7*3; game reports 22
|
||||
Lights::popn_lights_t::Cabinet_G,
|
||||
Lights::popn_lights_t::Cabinet_R,
|
||||
Lights::popn_lights_t::Cabinet_B,
|
||||
0x7F,
|
||||
"Cabinet"
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
static decltype(GetDisplayConfigBufferSizes) *GetDisplayConfigBufferSizes_orig = nullptr;
|
||||
static decltype(QueryDisplayConfig) *QueryDisplayConfig_orig = nullptr;
|
||||
static decltype(DisplayConfigGetDeviceInfo) *DisplayConfigGetDeviceInfo_orig = nullptr;
|
||||
|
||||
static UINT32 pNumPathArrayElements_original = 0;
|
||||
static UINT32 pNumModeInfoArrayElements_original = 0;
|
||||
|
||||
static
|
||||
LONG
|
||||
WINAPI
|
||||
GetDisplayConfigBufferSizes_hook(
|
||||
UINT32 Flags,
|
||||
UINT32 *pNumPathArrayElements,
|
||||
UINT32 *pNumModeInfoArrayElements)
|
||||
{
|
||||
const auto ret = GetDisplayConfigBufferSizes_orig(Flags, pNumPathArrayElements, pNumModeInfoArrayElements);
|
||||
if (FAKE_SUBSCREEN_ADAPTER) {
|
||||
log_misc("popn", "GetDisplayConfigBufferSizes returning fake monitor paths and modes");
|
||||
pNumPathArrayElements_original = *pNumPathArrayElements;
|
||||
pNumModeInfoArrayElements_original = *pNumModeInfoArrayElements;
|
||||
*pNumPathArrayElements += 1;
|
||||
*pNumModeInfoArrayElements += 2;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
static
|
||||
LONG
|
||||
WINAPI
|
||||
QueryDisplayConfig_hook(
|
||||
UINT32 flags,
|
||||
UINT32* numPathArrayElements,
|
||||
DISPLAYCONFIG_PATH_INFO* pathArray,
|
||||
UINT32* numModeInfoArrayElements,
|
||||
DISPLAYCONFIG_MODE_INFO* modeInfoArray,
|
||||
DISPLAYCONFIG_TOPOLOGY_ID* currentTopologyId)
|
||||
{
|
||||
|
||||
if (!FAKE_SUBSCREEN_ADAPTER) {
|
||||
return QueryDisplayConfig_orig(
|
||||
flags,
|
||||
numPathArrayElements, pathArray,
|
||||
numModeInfoArrayElements, modeInfoArray,
|
||||
currentTopologyId);
|
||||
}
|
||||
|
||||
// call original to fill in real monitor info
|
||||
UINT32 num_paths = pNumPathArrayElements_original;
|
||||
UINT32 num_modes = pNumModeInfoArrayElements_original;
|
||||
const auto ret = QueryDisplayConfig_orig(
|
||||
flags,
|
||||
&num_paths, pathArray,
|
||||
&num_modes, modeInfoArray,
|
||||
currentTopologyId);
|
||||
|
||||
if (ret != ERROR_SUCCESS) {
|
||||
log_warning("popn", "QueryDisplayConfig failed with error code {}", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
log_misc("popn", "QueryDisplayConfig returning fake monitor paths and modes");
|
||||
|
||||
// insert a fake path
|
||||
DISPLAYCONFIG_PATH_INFO *path = &pathArray[pNumPathArrayElements_original];
|
||||
*path = {};
|
||||
path->flags = DISPLAYCONFIG_PATH_ACTIVE;
|
||||
|
||||
path->sourceInfo.adapterId.HighPart = -1;
|
||||
path->sourceInfo.adapterId.LowPart = -1;
|
||||
path->sourceInfo.id = -1;
|
||||
path->sourceInfo.modeInfoIdx = pNumModeInfoArrayElements_original;
|
||||
path->sourceInfo.statusFlags = DISPLAYCONFIG_SOURCE_IN_USE;
|
||||
|
||||
path->targetInfo.adapterId.HighPart = -1;
|
||||
path->targetInfo.adapterId.LowPart = -1;
|
||||
path->targetInfo.id = -2;
|
||||
path->targetInfo.modeInfoIdx = pNumModeInfoArrayElements_original + 1;
|
||||
path->targetInfo.outputTechnology = DISPLAYCONFIG_OUTPUT_TECHNOLOGY_HDMI;
|
||||
path->targetInfo.rotation = DISPLAYCONFIG_ROTATION_IDENTITY;
|
||||
path->targetInfo.scaling = DISPLAYCONFIG_SCALING_IDENTITY;
|
||||
path->targetInfo.refreshRate.Numerator = 60000;
|
||||
path->targetInfo.refreshRate.Denominator = 1000;
|
||||
path->targetInfo.scanLineOrdering = DISPLAYCONFIG_SCANLINE_ORDERING_PROGRESSIVE;
|
||||
path->targetInfo.targetAvailable = true;
|
||||
path->targetInfo.statusFlags = DISPLAYCONFIG_TARGET_IN_USE;
|
||||
|
||||
// insert fake mode source
|
||||
DISPLAYCONFIG_MODE_INFO *mode_source = &modeInfoArray[pNumModeInfoArrayElements_original];
|
||||
*mode_source = {};
|
||||
mode_source->infoType = DISPLAYCONFIG_MODE_INFO_TYPE_SOURCE;
|
||||
mode_source->id = -1;
|
||||
mode_source->adapterId.HighPart = -1;
|
||||
mode_source->adapterId.LowPart = -1;
|
||||
mode_source->sourceMode.width = FAKE_MONITOR_WIDTH;
|
||||
mode_source->sourceMode.height = FAKE_MONITOR_HEIGHT;
|
||||
mode_source->sourceMode.pixelFormat = DISPLAYCONFIG_PIXELFORMAT_32BPP;
|
||||
mode_source->sourceMode.position.x = FAKE_MONITOR_OFFSET_X_Y;
|
||||
mode_source->sourceMode.position.y = FAKE_MONITOR_OFFSET_X_Y;
|
||||
|
||||
// insert fake mode target
|
||||
DISPLAYCONFIG_MODE_INFO *mode_target = &modeInfoArray[pNumModeInfoArrayElements_original+1];
|
||||
*mode_target = {};
|
||||
mode_target->infoType = DISPLAYCONFIG_MODE_INFO_TYPE_TARGET;
|
||||
mode_target->id = -2;
|
||||
mode_target->adapterId.HighPart = -1;
|
||||
mode_target->adapterId.LowPart = -1;
|
||||
return ret;
|
||||
}
|
||||
|
||||
static
|
||||
LONG
|
||||
WINAPI
|
||||
DisplayConfigGetDeviceInfo_hook(DISPLAYCONFIG_DEVICE_INFO_HEADER* requestPacket)
|
||||
{
|
||||
if (requestPacket == nullptr) {
|
||||
return DisplayConfigGetDeviceInfo_orig(requestPacket);
|
||||
}
|
||||
|
||||
// fake monitor
|
||||
if (FAKE_SUBSCREEN_ADAPTER &&
|
||||
(requestPacket->id == static_cast<UINT32>(-1) ||
|
||||
requestPacket->id == static_cast<UINT32>(-2)) &&
|
||||
requestPacket->adapterId.HighPart == static_cast<LONG>(-1) &&
|
||||
requestPacket->adapterId.LowPart == static_cast<DWORD>(-1)) {
|
||||
log_misc(
|
||||
"popn",
|
||||
"DisplayConfigGetDeviceInfo hook hit for fake monitor, type={}, size={}, id={}, luid={}/{}",
|
||||
static_cast<int>(requestPacket->type),
|
||||
requestPacket->size,
|
||||
requestPacket->id,
|
||||
requestPacket->adapterId.HighPart,
|
||||
requestPacket->adapterId.LowPart);
|
||||
|
||||
if (requestPacket->type == DISPLAYCONFIG_DEVICE_INFO_GET_TARGET_NAME) {
|
||||
const auto target = reinterpret_cast<DISPLAYCONFIG_TARGET_DEVICE_NAME*>(requestPacket);
|
||||
target->flags.value = 0;
|
||||
target->outputTechnology = DISPLAYCONFIG_OUTPUT_TECHNOLOGY_HDMI;
|
||||
target->connectorInstance = 0;
|
||||
wcscpy(target->monitorFriendlyDeviceName, L"Spice Fake Monitor");
|
||||
wcscpy(target->monitorDevicePath, L"\\\\?\\SpiceFakeMonitor");
|
||||
} else if (requestPacket->type == DISPLAYCONFIG_DEVICE_INFO_GET_SOURCE_NAME) {
|
||||
const auto source = reinterpret_cast<DISPLAYCONFIG_SOURCE_DEVICE_NAME*>(requestPacket);
|
||||
// value must match WrappedIDirect3D9::GetAdapterIdentifier
|
||||
wcscpy(source->viewGdiDeviceName, L"\\\\.\\DISPLAY_SPICE_FAKE");
|
||||
} else {
|
||||
log_fatal(
|
||||
"popn",
|
||||
"unexpected device info type {} for fake monitor",
|
||||
static_cast<int>(requestPacket->type));
|
||||
}
|
||||
|
||||
return ERROR_SUCCESS;
|
||||
}
|
||||
|
||||
const auto ret = DisplayConfigGetDeviceInfo_orig(requestPacket);
|
||||
log_misc(
|
||||
"popn",
|
||||
"DisplayConfigGetDeviceInfo returned {}, type={}, size={}, id={}, luid={}/{}",
|
||||
ret,
|
||||
static_cast<int>(requestPacket->type),
|
||||
requestPacket->size,
|
||||
requestPacket->id,
|
||||
requestPacket->adapterId.HighPart,
|
||||
requestPacket->adapterId.LowPart);
|
||||
if (ret == ERROR_SUCCESS) {
|
||||
if (requestPacket->type == DISPLAYCONFIG_DEVICE_INFO_GET_SOURCE_NAME) {
|
||||
const auto sourceName = reinterpret_cast<DISPLAYCONFIG_SOURCE_DEVICE_NAME*>(requestPacket);
|
||||
log_misc(
|
||||
"popn",
|
||||
"... name={}",
|
||||
ws2s(sourceName->viewGdiDeviceName));
|
||||
} else if (requestPacket->type == DISPLAYCONFIG_DEVICE_INFO_GET_TARGET_NAME) {
|
||||
const auto targetName = reinterpret_cast<DISPLAYCONFIG_TARGET_DEVICE_NAME*>(requestPacket);
|
||||
log_misc(
|
||||
"popn",
|
||||
"... flags={}, outputTechnology: {}, connectorInstance={}, friendlyname={} path={}",
|
||||
targetName->flags.value,
|
||||
static_cast<int>(targetName->outputTechnology),
|
||||
targetName->connectorInstance,
|
||||
ws2s(targetName->monitorFriendlyDeviceName),
|
||||
ws2s(targetName->monitorDevicePath));
|
||||
|
||||
// need to fix up some values for the primary monitor...
|
||||
const auto monitors = sysutils::enumerate_monitors();
|
||||
for (const auto& monitor : monitors) {
|
||||
if (monitor.id == targetName->header.id &&
|
||||
monitor.adapter_id_HighPart == targetName->header.adapterId.HighPart &&
|
||||
monitor.adapter_id_LowPart == targetName->header.adapterId.LowPart) {
|
||||
|
||||
if (monitor.is_primary) {
|
||||
targetName->outputTechnology = DISPLAYCONFIG_OUTPUT_TECHNOLOGY_DISPLAYPORT_EXTERNAL;
|
||||
targetName->connectorInstance = 2;
|
||||
log_info(
|
||||
"popn",
|
||||
"... overriding primary monitor ({}) to pretend to be DP port #2",
|
||||
monitor.display_name);
|
||||
} else {
|
||||
// TODO: is this what the game expects for subscreen?
|
||||
// TODO: what if there are 3+ monitors?
|
||||
targetName->outputTechnology = DISPLAYCONFIG_OUTPUT_TECHNOLOGY_HDMI;
|
||||
targetName->connectorInstance = 0;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
#if !SPICE64
|
||||
|
||||
static int __cdecl usbCheckAlive() {
|
||||
return 1;
|
||||
}
|
||||
@@ -227,6 +542,8 @@ namespace games::popn {
|
||||
return MMSYSERR_NOERROR;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
static bool log_hook(void *user, const std::string &data, logger::Style style, std::string &out) {
|
||||
|
||||
// get rid of the log spam
|
||||
@@ -249,6 +566,8 @@ namespace games::popn {
|
||||
void POPNGame::pre_attach() {
|
||||
Game::pre_attach();
|
||||
|
||||
#if !SPICE64
|
||||
|
||||
// only needed for older versions (19-21, inclusive) but create them anyway since 21 and above are all M39
|
||||
fileutils::dir_create_recursive_log("popn", "dev\\raw\\bookkeeping");
|
||||
fileutils::dir_create_recursive_log("popn", "dev\\raw\\ranking");
|
||||
@@ -257,11 +576,15 @@ namespace games::popn {
|
||||
// load without resolving references
|
||||
// makes game not trigger DLLMain which results in an error due to missing EZUSB device
|
||||
LoadLibraryExW((MODULE_PATH / "ezusb.dll").c_str(), nullptr, DONT_RESOLVE_DLL_REFERENCES);
|
||||
|
||||
#endif
|
||||
|
||||
}
|
||||
|
||||
void POPNGame::attach() {
|
||||
Game::attach();
|
||||
|
||||
#if !SPICE64
|
||||
/*
|
||||
* Fast Boot (TM) Patch
|
||||
* Game tries to create some directories and if it fails it will sleep for 1 second
|
||||
@@ -320,5 +643,91 @@ namespace games::popn {
|
||||
detour::inline_hook((void *) usbWdtStartDone,
|
||||
libutils::try_proc(ezusb, "?usbWdtStartDone@@YAHXZ"));
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
#if SPICE64 && !SPICE_XP
|
||||
if (!GRAPHICS_WINDOWED && D3D9_ADAPTER.has_value()) {
|
||||
SHOW_PIKA_MONITOR_WARNING = true;
|
||||
log_warning(
|
||||
"popn",
|
||||
"\n\n"
|
||||
"!!! using -dxmainadapter option is NOT recommended due to known !!!\n"
|
||||
"!!! compatibility issues with the game !!!\n"
|
||||
"!!! !!!\n"
|
||||
"!!! * game may launch in wrong resolution or refresh rate !!!\n"
|
||||
"!!! * touch / mouse input may stop working in subscreen / overlay !!!\n"
|
||||
"!!! !!!\n"
|
||||
"!!! recommendation is to use the Change Main Monitor (-mainmonitor) !!!\n"
|
||||
"!!! option instead of -dxmainadapter !!!\n\n"
|
||||
);
|
||||
|
||||
deferredlogs::defer_error_messages({
|
||||
"-dxmainadapter option is NOT recommended due to known compatibility ",
|
||||
" issues with the game!",
|
||||
" * game may launch in wrong resolution or refresh rate",
|
||||
" * touch / mouse input may stop working in subscreen / overlay",
|
||||
" use Change Main Monitor (-mainmonitor) option instead"
|
||||
});
|
||||
}
|
||||
|
||||
// monitor hook
|
||||
GetDisplayConfigBufferSizes_orig =
|
||||
detour::iat_try("GetDisplayConfigBufferSizes",
|
||||
GetDisplayConfigBufferSizes_hook, avs::game::DLL_INSTANCE);
|
||||
QueryDisplayConfig_orig =
|
||||
detour::iat_try("QueryDisplayConfig",
|
||||
QueryDisplayConfig_hook, avs::game::DLL_INSTANCE);
|
||||
DisplayConfigGetDeviceInfo_orig =
|
||||
detour::iat_try("DisplayConfigGetDeviceInfo",
|
||||
DisplayConfigGetDeviceInfo_hook, avs::game::DLL_INSTANCE);
|
||||
|
||||
// GUID_DEVCLASS_USB = {86E0D1E0-11D0-89B0-00A0C9054129}
|
||||
SETUPAPI_SETTINGS settings{};
|
||||
settings.class_guid[0] = 0x86E0D1E0;
|
||||
settings.class_guid[1] = 0x11D08089;
|
||||
settings.class_guid[2] = 0x0008E49C;
|
||||
settings.class_guid[3] = 0x731F303E;
|
||||
|
||||
// BIO3 - 8058 (maybe U region cabs with more lights?)
|
||||
// BIO3 - 8054 (probably J region?)
|
||||
// BIO2 - 8050 (need bi2x_hook, everything will work except buttons 1-9)
|
||||
|
||||
const char property[] = "1CCF(8058)_000";
|
||||
const char property_hardwareid[] = "USB\\VID_1CCF&PID_8058&MI_00\\000";
|
||||
memcpy(settings.property_devicedesc, property, sizeof(property));
|
||||
memcpy(settings.property_hardwareid, property_hardwareid, sizeof(property_hardwareid));
|
||||
setupapihook_init(avs::game::DLL_INSTANCE);
|
||||
setupapihook_add(settings);
|
||||
|
||||
bi3a_hook_init();
|
||||
|
||||
// note: if for whatever reason you want to use BIO2, the game needs to be patched
|
||||
// check popn.dll and look for static table that looks like this:
|
||||
// 00000001 00000003 00000001 (button 1)
|
||||
// 00000002 00000004 00000001 (button 2)
|
||||
// 00000004 00000005 00000001 (button 3)
|
||||
// 00000008 00000006 00000001 (button 4)
|
||||
// 00000010 00000007 00000001 (button 5)
|
||||
// 00000020 00000008 00000001 (button 6)
|
||||
// 00000040 00000009 00000001 (button 7)
|
||||
// 00000080 0000000A 00000001 (button 8)
|
||||
// 00000100 0000000B 00000001 (button 9)
|
||||
// set third column to 0 and it will work with BIO2
|
||||
|
||||
if (!GRAPHICS_WINDOWED) {
|
||||
if (NATIVE_TOUCH) {
|
||||
nativetouchhook::hook(avs::game::DLL_INSTANCE);
|
||||
} else {
|
||||
wintouchemu::FORCE = true;
|
||||
wintouchemu::INJECT_MOUSE_AS_WM_TOUCH = true;
|
||||
wintouchemu::hook_title_ends("", "Main Screen", avs::game::DLL_INSTANCE);
|
||||
}
|
||||
}
|
||||
|
||||
sysutils::hook_EnumDisplayDevicesA();
|
||||
|
||||
#endif
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +1,29 @@
|
||||
#pragma once
|
||||
|
||||
#include "avs/game.h"
|
||||
#include "games/game.h"
|
||||
#include "util/tapeled.h"
|
||||
|
||||
namespace games::popn {
|
||||
|
||||
static inline bool is_pikapika_model() {
|
||||
|
||||
#if SPICE64
|
||||
return (avs::game::is_model("M39") && avs::game::SPEC[0] == 'D');
|
||||
#else
|
||||
return false;
|
||||
#endif
|
||||
|
||||
}
|
||||
|
||||
#if SPICE64
|
||||
constexpr int POPN_TAPELED_TOTAL = 8;
|
||||
extern tapeledutils::tape_led TAPELED_MAPPING[POPN_TAPELED_TOTAL];
|
||||
#endif
|
||||
|
||||
extern bool SHOW_PIKA_MONITOR_WARNING;
|
||||
extern bool NATIVE_TOUCH;
|
||||
|
||||
class POPNGame : public games::Game {
|
||||
public:
|
||||
POPNGame();
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -21,6 +21,7 @@
|
||||
#include "util/socd_cleaner.h"
|
||||
#include "util/time.h"
|
||||
#include "util/libutils.h"
|
||||
#include "util/sysutils.h"
|
||||
#include "misc/eamuse.h"
|
||||
#include "misc/nativetouchhook.h"
|
||||
#include "misc/wintouchemu.h"
|
||||
@@ -350,28 +351,28 @@ namespace games::sdvx {
|
||||
}
|
||||
|
||||
#ifdef SPICE64 // SDVX5+ specific code
|
||||
// check -monitor + UFC mode
|
||||
// check -dxmainadapter + UFC mode
|
||||
if (!GRAPHICS_WINDOWED && D3D9_ADAPTER.has_value() && is_valkyrie_model()) {
|
||||
SHOW_VM_MONITOR_WARNING = true;
|
||||
log_warning(
|
||||
"sdvx",
|
||||
"\n\n!!! using -monitor option with VM mode is NOT recommended due to !!!\n"
|
||||
"!!! known compatibility issues with the game !!!\n"
|
||||
"\n\n"
|
||||
"!!! using -dxmainadapter option with Valkyrie mode is NOT !!!\n"
|
||||
"!!! recommended due to known compatibility issues with the game !!!\n"
|
||||
"!!! !!!\n"
|
||||
"!!! * game may launch in wrong resolution or refresh rate !!!\n"
|
||||
"!!! * touch / mouse input may stop working in subscreen / overlay !!!\n"
|
||||
"!!! !!!\n"
|
||||
"!!! recommendation is to NOT use -monitor and instead set the !!!\n"
|
||||
"!!! primary monitor in Windows settings before launching the game !!!\n\n"
|
||||
"!!! recommendation is to use the Change Main Monitor (-mainmonitor) !!!\n"
|
||||
"!!! option instead of -dxmainadapter !!!\n\n"
|
||||
);
|
||||
|
||||
deferredlogs::defer_error_messages({
|
||||
"-monitor option is NOT recommended when running with Valkyrie mode",
|
||||
"-dxmainadapter option is NOT recommended when running with Valkyrie mode",
|
||||
" due to known compatibility issues with the game:",
|
||||
" * game may launch in wrong resolution or refresh rate",
|
||||
" * touch / mouse input may stop working in subscreen / overlay",
|
||||
" recommended fix is to NOT use -monitor and instead set the primary",
|
||||
" monitor in Windows settings before launching the game"
|
||||
" use Change Main Monitor (-mainmonitor) option instead"
|
||||
});
|
||||
}
|
||||
#endif
|
||||
@@ -391,7 +392,7 @@ namespace games::sdvx {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void SDVXGame::attach() {
|
||||
Game::attach();
|
||||
|
||||
@@ -466,6 +467,7 @@ namespace games::sdvx {
|
||||
ENABLE_COM_PORT_SCAN_HOOK = true;
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
// ASIO device hook
|
||||
@@ -501,6 +503,9 @@ namespace games::sdvx {
|
||||
// remove log spam
|
||||
logger::hook_add(sdvx64_spam_remover, nullptr);
|
||||
|
||||
if (is_valkyrie_model()) {
|
||||
sysutils::hook_EnumDisplayDevicesA();
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
#include "cfg/screen_resize.h"
|
||||
#include "games/gitadora/gitadora.h"
|
||||
#include "games/iidx/iidx.h"
|
||||
#include "games/popn/popn.h"
|
||||
#include "games/sdvx/sdvx.h"
|
||||
#include "games/mfc/mfc.h"
|
||||
#include "games/io.h"
|
||||
@@ -462,7 +463,20 @@ HRESULT STDMETHODCALLTYPE WrappedIDirect3D9::RegisterSoftwareDevice(void *pIniti
|
||||
}
|
||||
|
||||
UINT STDMETHODCALLTYPE WrappedIDirect3D9::GetAdapterCount() {
|
||||
return pReal->GetAdapterCount();
|
||||
UINT result = pReal->GetAdapterCount();
|
||||
|
||||
if (!FAKE_SUBSCREEN_ADAPTER && games::popn::is_pikapika_model() && result == 1) {
|
||||
FAKE_SUBSCREEN_ADAPTER = true;
|
||||
log_info(
|
||||
"graphics::d3d9",
|
||||
"GetAdapterCount returned 1, popn needs 2 adapters - enabliing fake submonitor mode");
|
||||
}
|
||||
|
||||
if (FAKE_SUBSCREEN_ADAPTER) {
|
||||
return 2;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
HRESULT STDMETHODCALLTYPE WrappedIDirect3D9::GetAdapterIdentifier(
|
||||
@@ -470,10 +484,26 @@ HRESULT STDMETHODCALLTYPE WrappedIDirect3D9::GetAdapterIdentifier(
|
||||
DWORD Flags,
|
||||
D3DADAPTER_IDENTIFIER9 *pIdentifier)
|
||||
{
|
||||
|
||||
if (FAKE_SUBSCREEN_ADAPTER && Adapter == 1 && pIdentifier) {
|
||||
*pIdentifier = {};
|
||||
strcpy(pIdentifier->DeviceName, "\\\\.\\DISPLAY_SPICE_FAKE");
|
||||
log_misc(
|
||||
"graphics::d3d9",
|
||||
"GetAdapterIdentifier called for fake subscreen adapter 1: {}",
|
||||
pIdentifier->DeviceName);
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
CHECK_RESULT(pReal->GetAdapterIdentifier(Adapter, Flags, pIdentifier));
|
||||
}
|
||||
|
||||
UINT STDMETHODCALLTYPE WrappedIDirect3D9::GetAdapterModeCount(UINT Adapter, D3DFORMAT Format) {
|
||||
if (FAKE_SUBSCREEN_ADAPTER && Adapter == 1) {
|
||||
log_misc("graphics::d3d9", "GetAdapterModeCount called for fake subscreen adapter");
|
||||
return 1;
|
||||
}
|
||||
|
||||
return pReal->GetAdapterModeCount(Adapter, Format);
|
||||
}
|
||||
|
||||
@@ -483,6 +513,19 @@ HRESULT STDMETHODCALLTYPE WrappedIDirect3D9::EnumAdapterModes(
|
||||
UINT Mode,
|
||||
D3DDISPLAYMODE *pMode)
|
||||
{
|
||||
if (FAKE_SUBSCREEN_ADAPTER && Adapter == 1) {
|
||||
log_misc("graphics::d3d9", "EnumAdapterModes called for fake subscreen adapter");
|
||||
if (Mode == 0 && pMode) {
|
||||
pMode->Width = 1280;
|
||||
pMode->Height = 800;
|
||||
pMode->RefreshRate = 60;
|
||||
pMode->Format = D3DFMT_X8R8G8B8;
|
||||
return S_OK;
|
||||
} else {
|
||||
return D3DERR_INVALIDCALL;
|
||||
}
|
||||
}
|
||||
|
||||
HRESULT ret = pReal->EnumAdapterModes(Adapter, Format, Mode, pMode);
|
||||
|
||||
if (SUCCEEDED(ret) && pMode) {
|
||||
@@ -579,6 +622,11 @@ HRESULT STDMETHODCALLTYPE WrappedIDirect3D9::CheckDeviceFormat(
|
||||
D3DRESOURCETYPE RType,
|
||||
D3DFORMAT CheckFormat)
|
||||
{
|
||||
if (FAKE_SUBSCREEN_ADAPTER && Adapter == 1) {
|
||||
log_misc("graphics::d3d9", "CheckDeviceFormat called for fake subscreen adapter");
|
||||
return D3D_OK;
|
||||
}
|
||||
|
||||
CHECK_RESULT(pReal->CheckDeviceFormat(Adapter, DeviceType, AdapterFormat, Usage, RType, CheckFormat));
|
||||
}
|
||||
|
||||
@@ -660,7 +708,7 @@ HRESULT STDMETHODCALLTYPE WrappedIDirect3D9::GetDeviceCaps(UINT Adapter, D3DDEVT
|
||||
}
|
||||
// in windowed mode, LDJ will always launch two windows, no special handling needed here
|
||||
} else if (avs::game::is_model("KFC")) {
|
||||
if (GRAPHICS_WINDOWED & GRAPHICS_PREVENT_SECONDARY_WINDOW) {
|
||||
if (GRAPHICS_WINDOWED && GRAPHICS_PREVENT_SECONDARY_WINDOW) {
|
||||
// user wants windowed mode but does not want subscreen at all
|
||||
pCaps->NumberOfAdaptersInGroup = 1;
|
||||
} else {
|
||||
@@ -675,6 +723,8 @@ HRESULT STDMETHODCALLTYPE WrappedIDirect3D9::GetDeviceCaps(UINT Adapter, D3DDEVT
|
||||
} else if (GRAPHICS_FORCE_SINGLE_ADAPTER) {
|
||||
pCaps->NumberOfAdaptersInGroup = 1;
|
||||
}
|
||||
} else if (games::popn::is_pikapika_model()) {
|
||||
pCaps->NumberOfAdaptersInGroup = 2;
|
||||
}
|
||||
|
||||
return ret;
|
||||
@@ -1021,7 +1071,8 @@ HRESULT STDMETHODCALLTYPE WrappedIDirect3D9::CreateDeviceEx(
|
||||
//
|
||||
// note from MSDN: `pFullscreenDisplayMode` must be NULL for windowed mode.
|
||||
if (GRAPHICS_WINDOWED) {
|
||||
if (avs::game::is_model({"LDJ", "KFC"}) && (BehaviorFlags & D3DCREATE_ADAPTERGROUP_DEVICE)) {
|
||||
if (avs::game::is_model({"LDJ", "KFC", "M39"}) &&
|
||||
(BehaviorFlags & D3DCREATE_ADAPTERGROUP_DEVICE)) {
|
||||
log_misc("graphics::d3d9", "disabling adapter group device in windowed mode");
|
||||
|
||||
D3D9_BEHAVIOR_DISABLE |= D3DCREATE_ADAPTERGROUP_DEVICE;
|
||||
@@ -1093,7 +1144,8 @@ HRESULT STDMETHODCALLTYPE WrappedIDirect3D9::CreateDeviceEx(
|
||||
*ppReturnedDeviceInterface = new WrappedIDirect3DDevice9(hFocusWindow, *ppReturnedDeviceInterface);
|
||||
|
||||
// initialize sub screen if IIDX/SDVX requested a multi-head context
|
||||
if (avs::game::is_model({"LDJ", "KFC"}) && (orig_behavior_flags & D3DCREATE_ADAPTERGROUP_DEVICE)) {
|
||||
if (avs::game::is_model({"LDJ", "KFC", "M39"}) &&
|
||||
(orig_behavior_flags & D3DCREATE_ADAPTERGROUP_DEVICE)) {
|
||||
graphics_d3d9_ldj_init_sub_screen(*ppReturnedDeviceInterface, &pPresentationParameters[1]);
|
||||
}
|
||||
}
|
||||
@@ -1132,8 +1184,6 @@ static void graphics_d3d9_ldj_init_sub_screen(IDirect3DDevice9Ex *device, D3DPRE
|
||||
*/
|
||||
|
||||
if (GRAPHICS_WINDOWED) {
|
||||
log_info("graphics::d3d9", "creating additional swap chain");
|
||||
|
||||
present_params->Windowed = true;
|
||||
present_params->FullScreen_RefreshRateInHz = 0;
|
||||
|
||||
@@ -1142,18 +1192,23 @@ static void graphics_d3d9_ldj_init_sub_screen(IDirect3DDevice9Ex *device, D3DPRE
|
||||
hr = device->CreateAdditionalSwapChain(present_params, &SUB_SWAP_CHAIN);
|
||||
if (FAILED(hr)) {
|
||||
log_warning("graphics::d3d9", "failed to create additional swap chain, hr={}", FMT_HRESULT(hr));
|
||||
} else {
|
||||
log_info("graphics::d3d9", "created additional swap chain for windowed mode");
|
||||
}
|
||||
} else {
|
||||
hr = device->GetSwapChain(1, &SUB_SWAP_CHAIN);
|
||||
if (FAILED(hr)) {
|
||||
log_warning("graphics::d3d9", "failed to acquire fullscreen sub swap chain, hr={}", FMT_HRESULT(hr));
|
||||
} else {
|
||||
log_info("graphics::d3d9", "acquired fullscreen sub swap chain");
|
||||
return;
|
||||
}
|
||||
|
||||
hr = device->CreateAdditionalSwapChain(present_params, &SUB_SWAP_CHAIN);
|
||||
if (FAILED(hr)) {
|
||||
log_warning("graphics::d3d9", "failed to get additional swap chain, hr={}", FMT_HRESULT(hr));
|
||||
} else {
|
||||
log_info("graphics::d3d9", "created additional swap chain for fullscreen mode");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1196,7 +1251,10 @@ static void graphics_d3d9_ldj_on_present(IDirect3DDevice9 *wrapped_device) {
|
||||
if (SUB_SWAP_CHAIN != nullptr) {
|
||||
wintouchemu::update();
|
||||
|
||||
if (GRAPHICS_WINDOWED || SUBSCREEN_FORCE_REDRAW) {
|
||||
// newer versions of exceed gear needs SUBSCREEN_FORCE_REDRAW
|
||||
// (when enabled on older versions of EG, you end up with graphical glitches on the subscreen
|
||||
// early versions of popn HC needs this as well, otherwise the subscreen doesn't update at all
|
||||
if (GRAPHICS_WINDOWED || SUBSCREEN_FORCE_REDRAW || games::popn::is_pikapika_model()) {
|
||||
SUB_SWAP_CHAIN->Present(nullptr, nullptr, nullptr, nullptr, 0);
|
||||
}
|
||||
}
|
||||
@@ -1396,7 +1454,8 @@ void graphics_d3d9_on_present(
|
||||
const bool is_tdj = avs::game::is_model("LDJ") && games::iidx::TDJ_MODE;
|
||||
const bool is_gfdm_arena =
|
||||
games::gitadora::is_arena_model() && games::gitadora::ARENA_SINGLE_WINDOW;
|
||||
if (is_vm || is_tdj || is_gfdm_arena) {
|
||||
const bool is_pika = games::popn::is_pikapika_model();
|
||||
if (is_vm || is_tdj || is_gfdm_arena || is_pika) {
|
||||
graphics_d3d9_ldj_on_present(wrapped_device);
|
||||
}
|
||||
|
||||
|
||||
@@ -251,7 +251,7 @@ HRESULT STDMETHODCALLTYPE WrappedIDirect3DDevice9::CreateAdditionalSwapChain(
|
||||
|
||||
HRESULT hr = pReal->CreateAdditionalSwapChain(pPresentationParameters, ppSwapChain);
|
||||
bool create_swap_chain = false;
|
||||
if (avs::game::is_model({"LDJ", "KFC"})) {
|
||||
if (avs::game::is_model({"LDJ", "KFC", "M39"})) {
|
||||
create_swap_chain = true;
|
||||
} else if (games::gitadora::is_arena_model() &&
|
||||
games::gitadora::ARENA_SINGLE_WINDOW &&
|
||||
@@ -305,7 +305,7 @@ HRESULT STDMETHODCALLTYPE WrappedIDirect3DDevice9::GetSwapChain(
|
||||
}
|
||||
|
||||
bool swap = false;
|
||||
if (iSwapChain == 1 && avs::game::is_model({"LDJ", "KFC"})) {
|
||||
if (iSwapChain == 1 && avs::game::is_model({"LDJ", "KFC", "M39"})) {
|
||||
swap = true;
|
||||
}
|
||||
if (games::gitadora::is_arena_model() &&
|
||||
@@ -344,7 +344,7 @@ UINT STDMETHODCALLTYPE WrappedIDirect3DDevice9::GetNumberOfSwapChains() {
|
||||
|
||||
UINT n = pReal->GetNumberOfSwapChains();
|
||||
|
||||
if (sub_swapchain && avs::game::is_model({"LDJ", "KFC"})) {
|
||||
if (sub_swapchain && avs::game::is_model({"LDJ", "KFC", "M39"})) {
|
||||
n += 1;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
// GetDisplayConfigBufferSizes etc is Vista+
|
||||
#define _WIN32_WINNT 0x0601
|
||||
|
||||
#include <initguid.h>
|
||||
|
||||
#include "graphics.h"
|
||||
@@ -13,6 +16,7 @@
|
||||
#include "games/ddr/ddr.h"
|
||||
#include "games/gitadora/gitadora.h"
|
||||
#include "games/iidx/iidx.h"
|
||||
#include "games/popn/popn.h"
|
||||
#include "hooks/graphics/backends/d3d9/d3d9_backend.h"
|
||||
#include "launcher/shutdown.h"
|
||||
#include "overlay/overlay.h"
|
||||
@@ -35,6 +39,8 @@ struct CaptureData {
|
||||
HWND TDJ_SUBSCREEN_WINDOW = nullptr;
|
||||
HWND SDVX_SUBSCREEN_WINDOW = nullptr;
|
||||
HWND GFDM_SUBSCREEN_WINDOW = nullptr;
|
||||
HWND POPN_SUBSCREEN_WINDOW = nullptr;
|
||||
bool FAKE_SUBSCREEN_ADAPTER = false;
|
||||
|
||||
// icon
|
||||
static HICON WINDOW_ICON = LoadIcon(GetModuleHandle(nullptr), MAKEINTRESOURCE(MAINICON));
|
||||
@@ -53,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;
|
||||
@@ -98,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);
|
||||
@@ -226,15 +238,15 @@ 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.
|
||||
update_spicetouch_window_dimensions(hWnd);
|
||||
// log_misc(
|
||||
// "graphics", "detected window change ({}x{} @ {}, {}), updating touch coord-space to match",
|
||||
// "graphics", "detected window change ({}x{} @ {}, {}), updating touch coord-space to match",
|
||||
// SPICETOUCH_TOUCH_WIDTH, SPICETOUCH_TOUCH_HEIGHT, SPICETOUCH_TOUCH_X, SPICETOUCH_TOUCH_Y);
|
||||
|
||||
|
||||
// Update SPICETOUCH window if present
|
||||
if (SPICETOUCH_TOUCH_HWND) {
|
||||
SetWindowPos(
|
||||
@@ -243,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;
|
||||
}
|
||||
@@ -331,17 +352,18 @@ static HWND WINAPI CreateWindowExA_hook(DWORD dwExStyle, LPCSTR lpClassName, LPC
|
||||
|
||||
bool is_tdj_sub_window = avs::game::is_model("LDJ") && window_name.ends_with(" sub");
|
||||
bool is_sdvx_sub_window = avs::game::is_model("KFC") && window_name.ends_with(" Sub Screen");
|
||||
bool is_popn_sub_window = avs::game::is_model("M39") && window_name.ends_with("Sub Screen");
|
||||
bool is_gfdm_sub_window = games::gitadora::is_arena_model() && window_name.ends_with("SMALL");
|
||||
|
||||
// hide maximize button (prevent misaligned touches)
|
||||
if ((is_tdj_sub_window && GRAPHICS_IIDX_WSUB) || is_sdvx_sub_window || is_gfdm_sub_window) {
|
||||
if ((is_tdj_sub_window && GRAPHICS_IIDX_WSUB) || is_sdvx_sub_window || is_gfdm_sub_window || is_popn_sub_window) {
|
||||
dwStyle &= ~(WS_MAXIMIZEBOX);
|
||||
}
|
||||
// mouse clicks become misaligned when resized
|
||||
if (is_gfdm_sub_window) {
|
||||
dwStyle &= ~(WS_SIZEBOX);
|
||||
}
|
||||
if ((is_tdj_sub_window || is_sdvx_sub_window) && GRAPHICS_WSUB_BORDERLESS) {
|
||||
if ((is_tdj_sub_window || is_sdvx_sub_window || is_popn_sub_window) && GRAPHICS_WSUB_BORDERLESS) {
|
||||
dwStyle &= ~(WS_OVERLAPPEDWINDOW);
|
||||
}
|
||||
|
||||
@@ -363,6 +385,10 @@ static HWND WINAPI CreateWindowExA_hook(DWORD dwExStyle, LPCSTR lpClassName, LPC
|
||||
}
|
||||
}
|
||||
|
||||
if (GRAPHICS_WINDOWED) {
|
||||
graphics_window_check_bounds_before_creation(x, y, nWidth, nHeight);
|
||||
}
|
||||
|
||||
// call original
|
||||
HWND result = CreateWindowExA_orig(dwExStyle, lpClassName, lpWindowName, dwStyle, x, y, nWidth, nHeight,
|
||||
hWndParent, hMenu, hInstance, lpParam);
|
||||
@@ -371,7 +397,7 @@ static HWND WINAPI CreateWindowExA_hook(DWORD dwExStyle, LPCSTR lpClassName, LPC
|
||||
if (is_tdj_sub_window) {
|
||||
// TDJ windowed mode: remember the subscreen window handle for later
|
||||
TDJ_SUBSCREEN_WINDOW = result;
|
||||
|
||||
|
||||
// hook for preventing the closing of subscreen window
|
||||
if (GRAPHICS_IIDX_WSUB) {
|
||||
graphics_hook_subscreen_window(TDJ_SUBSCREEN_WINDOW);
|
||||
@@ -390,6 +416,13 @@ static HWND WINAPI CreateWindowExA_hook(DWORD dwExStyle, LPCSTR lpClassName, LPC
|
||||
graphics_hook_subscreen_window(GFDM_SUBSCREEN_WINDOW);
|
||||
}
|
||||
|
||||
if (is_popn_sub_window) {
|
||||
POPN_SUBSCREEN_WINDOW = result;
|
||||
if (!GRAPHICS_PREVENT_SECONDARY_WINDOW) {
|
||||
graphics_hook_subscreen_window(POPN_SUBSCREEN_WINDOW);
|
||||
}
|
||||
}
|
||||
|
||||
disable_touch_indicators(result);
|
||||
log_misc(
|
||||
"graphics",
|
||||
@@ -458,6 +491,18 @@ static HWND WINAPI CreateWindowExW_hook(DWORD dwExStyle, LPCWSTR lpClassName, LP
|
||||
}
|
||||
}
|
||||
|
||||
// FTT
|
||||
if (avs::game::is_model("MMD")) {
|
||||
// set window name
|
||||
if (!lpWindowName) {
|
||||
lpWindowName = L"Future TomTom";
|
||||
}
|
||||
}
|
||||
|
||||
if (GRAPHICS_WINDOWED) {
|
||||
graphics_window_check_bounds_before_creation(x, y, nWidth, nHeight);
|
||||
}
|
||||
|
||||
// call original
|
||||
HWND result = CreateWindowExW_orig(
|
||||
dwExStyle, lpClassName, lpWindowName, dwStyle, x, y, nWidth, nHeight,
|
||||
@@ -530,7 +575,7 @@ static BOOL WINAPI MoveWindow_hook(HWND hWnd, int X, int Y, int nWidth, int nHei
|
||||
if (GRAPHICS_IIDX_WSUB) {
|
||||
// (Experimental) Show subscreen in windowed mode
|
||||
graphics_load_windowed_subscreen_parameters();
|
||||
|
||||
|
||||
RECT rect {};
|
||||
DWORD dwStyle;
|
||||
|
||||
@@ -653,6 +698,13 @@ static BOOL WINAPI ShowWindow_hook(HWND hWnd, int nCmdShow) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (games::popn::is_pikapika_model() &&
|
||||
GRAPHICS_PREVENT_SECONDARY_WINDOW &&
|
||||
hWnd == POPN_SUBSCREEN_WINDOW) {
|
||||
log_info("graphics", "ShowWindow_hook - hiding sub window {}", fmt::ptr(hWnd));
|
||||
return true;
|
||||
}
|
||||
|
||||
// call original
|
||||
return ShowWindow_orig(hWnd, nCmdShow);
|
||||
}
|
||||
@@ -895,6 +947,9 @@ void graphics_hook_subscreen_window(HWND hWnd) {
|
||||
if (GRAPHICS_WSUB_ALWAYS_ON_TOP) {
|
||||
graphics_update_z_order(hWnd, true);
|
||||
}
|
||||
if (GRAPHICS_WINDOW_DISABLE_ROUNDED_CORNERS) {
|
||||
graphics_set_corner_preference(hWnd, true);
|
||||
}
|
||||
}
|
||||
|
||||
void graphics_screens_register(int screen) {
|
||||
@@ -1085,7 +1140,7 @@ static std::string get_dmdo_string(DWORD dmdo) {
|
||||
}
|
||||
|
||||
void change_primary_monitor(const std::string &monitor_name) {
|
||||
log_misc("graphics", "try changing primary monitor to {}...", monitor_name);
|
||||
log_misc("graphics", "try changing primary monitor to {}...", monitor_name);
|
||||
|
||||
// for WinXP, since these are Vista+ or 7+ APIs
|
||||
const auto user32 = LoadLibraryA("user32.dll");
|
||||
@@ -1102,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 ||
|
||||
@@ -1116,7 +1171,7 @@ void change_primary_monitor(const std::string &monitor_name) {
|
||||
std::vector<DISPLAYCONFIG_PATH_INFO> paths;
|
||||
std::vector<DISPLAYCONFIG_MODE_INFO> modes;
|
||||
bool succeeded = false;
|
||||
|
||||
|
||||
// in a retry loop, try to query for display config
|
||||
// retry loop is needed because it can fail with ERROR_INSUFFICIENT_BUFFER
|
||||
for (int attempt = 0; attempt < 5; ++attempt) {
|
||||
@@ -1217,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
|
||||
@@ -1290,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);
|
||||
@@ -1302,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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,6 +7,9 @@
|
||||
|
||||
#include <windows.h>
|
||||
#include <d3d9.h>
|
||||
#if !SPICE_XP
|
||||
#include <dwmapi.h>
|
||||
#endif
|
||||
|
||||
#include "external/toojpeg/toojpeg.h"
|
||||
|
||||
@@ -50,6 +53,7 @@ extern std::optional<std::pair<uint32_t, uint32_t>> GRAPHICS_WINDOW_SIZE;
|
||||
extern std::optional<std::string> GRAPHICS_WINDOW_POS;
|
||||
extern bool GRAPHICS_WINDOW_ALWAYS_ON_TOP;
|
||||
extern bool GRAPHICS_WINDOW_BACKBUFFER_SCALE;
|
||||
extern bool GRAPHICS_WINDOW_DISABLE_ROUNDED_CORNERS;
|
||||
extern std::optional<HWND> GRAPHICS_HOOKED_WINDOW;
|
||||
|
||||
extern bool GRAPHICS_IIDX_WSUB;
|
||||
@@ -63,9 +67,11 @@ extern bool GRAPHICS_WSUB_BORDERLESS;
|
||||
extern bool GRAPHICS_WSUB_ALWAYS_ON_TOP;
|
||||
extern HWND TDJ_SUBSCREEN_WINDOW;
|
||||
extern HWND SDVX_SUBSCREEN_WINDOW;
|
||||
extern HWND POPN_SUBSCREEN_WINDOW;
|
||||
extern HWND GFDM_SUBSCREEN_WINDOW;
|
||||
|
||||
extern bool SUBSCREEN_FORCE_REDRAW;
|
||||
extern bool FAKE_SUBSCREEN_ADAPTER;
|
||||
|
||||
// settings
|
||||
extern std::string GRAPHICS_DEVICEID;
|
||||
@@ -99,14 +105,18 @@ std::string graphics_screenshot_genpath();
|
||||
// graphics_windowed.cpp
|
||||
void graphics_windowed_wndproc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam);
|
||||
void graphics_capture_initial_window(HWND hWnd);
|
||||
void graphics_update_window_style(HWND hWnd);
|
||||
void graphics_update_window_style(HWND hWnd, bool force_update=false);
|
||||
void graphics_update_z_order(HWND hWnd, bool always_on_top);
|
||||
void graphics_set_corner_preference(HWND hWnd, bool disable);
|
||||
void graphics_move_resize_window(HWND hWnd);
|
||||
bool graphics_window_options_breaks_game();
|
||||
bool graphics_window_decoration_change_crashes_game();
|
||||
bool graphics_window_resize_breaks_game();
|
||||
bool graphics_window_move_and_resize_breaks_game();
|
||||
void graphics_load_windowed_subscreen_parameters();
|
||||
void graphics_window_check_bounds_before_creation(int &x, int &y, const int width, const int height);
|
||||
|
||||
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_on_boot(std::optional<graphics_orientation> target_orientation, UINT target_refresh_rate);
|
||||
void update_monitor_at_runtime();
|
||||
void reset_monitor_on_exit();
|
||||
@@ -24,6 +24,7 @@ std::optional<std::pair<uint32_t, uint32_t>> GRAPHICS_WINDOW_SIZE;
|
||||
std::optional<std::string> GRAPHICS_WINDOW_POS;
|
||||
bool GRAPHICS_WINDOW_ALWAYS_ON_TOP = false;
|
||||
bool GRAPHICS_WINDOW_BACKBUFFER_SCALE = false;
|
||||
bool GRAPHICS_WINDOW_DISABLE_ROUNDED_CORNERS = false;
|
||||
std::optional<HWND> GRAPHICS_HOOKED_WINDOW;
|
||||
|
||||
// IIDX Windowed Subscreen - starts out as false, enabled by IIDX module on pre-attach as needed
|
||||
@@ -121,18 +122,21 @@ void graphics_capture_initial_window(HWND hWnd) {
|
||||
cfg::SCREENRESIZE->client_height);
|
||||
graphics_move_resize_window(hWnd);
|
||||
}
|
||||
// ddr hangs when window frame doesn't have overlapped
|
||||
if (cfg::SCREENRESIZE->window_decoration != cfg::WindowDecorationMode::Default) {
|
||||
log_info(
|
||||
"graphics-windowed", "[{}] change window style - decoration: {}",
|
||||
fmt::ptr(hWnd),
|
||||
cfg::SCREENRESIZE->window_decoration);
|
||||
graphics_update_window_style(hWnd);
|
||||
graphics_update_window_style(hWnd, true); // force
|
||||
}
|
||||
if (cfg::SCREENRESIZE->window_always_on_top) {
|
||||
log_info("graphics-windowed", "[{}] change window z-order - always on top", fmt::ptr(hWnd));
|
||||
graphics_update_z_order(hWnd, true);
|
||||
}
|
||||
if (cfg::SCREENRESIZE->window_disable_round_corners) {
|
||||
log_info("graphics-windowed", "[{}] disabling rounded corners", fmt::ptr(hWnd));
|
||||
graphics_set_corner_preference(hWnd, true);
|
||||
}
|
||||
|
||||
// ensure spicetouch coordinates are initialized
|
||||
update_spicetouch_window_dimensions(hWnd);
|
||||
@@ -182,6 +186,10 @@ void graphics_load_windowed_parameters() {
|
||||
cfg::SCREENRESIZE->window_always_on_top = true;
|
||||
}
|
||||
|
||||
if (GRAPHICS_WINDOW_DISABLE_ROUNDED_CORNERS) {
|
||||
cfg::SCREENRESIZE->window_disable_round_corners = true;
|
||||
}
|
||||
|
||||
log_debug("graphics-windowed", "graphics_load_windowed_parameters returned");
|
||||
}
|
||||
|
||||
@@ -385,11 +393,11 @@ void graphics_wm_sizing_aspect_ratio(int edge, RECT& rect) {
|
||||
log_debug("graphics-windowed", "graphics_wm_sizing_aspect_ratio returned");
|
||||
}
|
||||
|
||||
void graphics_update_window_style(HWND hWnd) {
|
||||
void graphics_update_window_style(HWND hWnd, bool force_update) {
|
||||
if (!GRAPHICS_WINDOWED) {
|
||||
return;
|
||||
}
|
||||
if (graphics_window_decoration_change_crashes_game()) {
|
||||
if (!force_update && graphics_window_decoration_change_crashes_game()) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -470,6 +478,27 @@ void graphics_update_z_order(HWND hWnd, bool always_on_top) {
|
||||
log_debug("graphics-windowed", "graphics_update_z_order returned");
|
||||
}
|
||||
|
||||
void graphics_set_corner_preference(HWND hWnd, bool disable) {
|
||||
if (!GRAPHICS_WINDOWED) {
|
||||
return;
|
||||
}
|
||||
|
||||
#if !SPICE_XP
|
||||
log_debug("graphics-windowed", "graphics_set_corner_preference called");
|
||||
|
||||
DWM_WINDOW_CORNER_PREFERENCE corner_preference = disable ? DWMWCP_DONOTROUND : DWMWCP_DEFAULT;
|
||||
DwmSetWindowAttribute(
|
||||
hWnd,
|
||||
DWMWA_WINDOW_CORNER_PREFERENCE,
|
||||
&corner_preference, sizeof(corner_preference));
|
||||
|
||||
log_debug("graphics-windowed", "graphics_set_corner_preference returned");
|
||||
#else
|
||||
// don't link against DwmSetWindowAttribute on XP
|
||||
log_debug("graphics-windowed", "graphics_set_corner_preference called; ignoring");
|
||||
#endif
|
||||
}
|
||||
|
||||
void graphics_move_resize_window(HWND hWnd) {
|
||||
if (!GRAPHICS_WINDOWED) {
|
||||
return;
|
||||
@@ -503,8 +532,15 @@ bool graphics_window_decoration_change_crashes_game() {
|
||||
static std::once_flag flag;
|
||||
static bool result = false;
|
||||
std::call_once(flag, []() {
|
||||
// ddr crashes when frame style changes
|
||||
result = avs::game::is_model("MDX");
|
||||
// ddr and ftt crash when frame style changes
|
||||
if (avs::game::is_model("MDX") || avs::game::is_model("MMD")) {
|
||||
result = true;
|
||||
}
|
||||
|
||||
// arena model hangs immediately when decoration changes
|
||||
if (games::gitadora::is_arena_model()) {
|
||||
result = true;
|
||||
}
|
||||
|
||||
// changing window decoration also changes dimensions and position
|
||||
// so this must be prevented
|
||||
@@ -560,3 +596,56 @@ bool graphics_window_resize_breaks_game() {
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
void graphics_window_check_bounds_before_creation(int &x, int &y, const int width, const int height) {
|
||||
RECT rect = {
|
||||
.left = x,
|
||||
.top = y,
|
||||
.right = x + width,
|
||||
.bottom = y + height
|
||||
};
|
||||
|
||||
HMONITOR mon = MonitorFromRect(&rect, MONITOR_DEFAULTTONULL);
|
||||
if (mon != nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
log_warning(
|
||||
"graphics-windowed",
|
||||
"window is completely out of bounds, resetting position: ({}, {}) => (0, 0)",
|
||||
x, y);
|
||||
x = 0;
|
||||
y = 0;
|
||||
rect = {
|
||||
.left = x,
|
||||
.top = y,
|
||||
.right = x + width,
|
||||
.bottom = y + height
|
||||
};
|
||||
|
||||
mon = MonitorFromRect(&rect, MONITOR_DEFAULTTONEAREST);
|
||||
MONITORINFO mi = {};
|
||||
mi.cbSize = sizeof(mi);
|
||||
if (!GetMonitorInfo(mon, &mi)) {
|
||||
log_warning(
|
||||
"graphics-windowed",
|
||||
"failed to get monitor info for out of bounds window");
|
||||
return;
|
||||
}
|
||||
|
||||
log_misc(
|
||||
"graphics-windowed",
|
||||
"nearest monitor for this window has work area ({}, {}),({}, {})",
|
||||
mi.rcWork.left, mi.rcWork.top, mi.rcWork.right, mi.rcWork.bottom);
|
||||
|
||||
RECT bounds = mi.rcWork;
|
||||
const bool fully_visible =
|
||||
(rect.left >= bounds.left &&
|
||||
rect.top >= bounds.top &&
|
||||
rect.right <= bounds.right &&
|
||||
rect.bottom <= bounds.bottom);
|
||||
|
||||
if (!fully_visible) {
|
||||
log_warning("graphics-windowed", "window will not be fully visible on monitor");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,6 +30,8 @@ static decltype(GetLocaleInfoEx) *GetLocaleInfoEx_orig = nullptr;
|
||||
static decltype(GetSystemDefaultLCID) *GetSystemDefaultLCID_orig = nullptr;
|
||||
static decltype(IsDBCSLeadByte) *IsDBCSLeadByte_orig = nullptr;
|
||||
static decltype(WideCharToMultiByte) *WideCharToMultiByte_orig = nullptr;
|
||||
static decltype(GetLocaleInfoA) *GetLocaleInfoA_orig = nullptr;
|
||||
static decltype(GetThreadLocale) *GetThreadLocale_orig = nullptr;
|
||||
#endif
|
||||
|
||||
static NTSTATUS NTAPI RtlMultiByteToUnicodeN_hook(
|
||||
@@ -94,6 +96,10 @@ static LCID WINAPI GetSystemDefaultLCID_hook() {
|
||||
return 0x411;
|
||||
}
|
||||
|
||||
static LCID WINAPI GetThreadLocale_hook() {
|
||||
return 0x411;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
static int WINAPI MultiByteToWideChar_hook(
|
||||
@@ -214,6 +220,31 @@ WideCharToMultiByte_hook(
|
||||
lpUsedDefaultChar);
|
||||
}
|
||||
|
||||
int
|
||||
WINAPI
|
||||
GetLocaleInfoA_hook(
|
||||
LCID Locale,
|
||||
LCTYPE LCType,
|
||||
LPSTR lpLCData,
|
||||
int cchData) {
|
||||
|
||||
if (LCType == LOCALE_SISO639LANGNAME && lpLCData != NULL && cchData >= 3) {
|
||||
log_misc("hooks::lang", "GetLocaleInfoA_hook hit ({:#x}, LOCALE_SISO639LANGNAME), return `ja`", Locale);
|
||||
strcpy(lpLCData, "ja");
|
||||
return 3;
|
||||
}
|
||||
|
||||
if (LCType == LOCALE_SISO3166CTRYNAME && lpLCData != NULL && cchData >= 3) {
|
||||
log_misc("hooks::lang",
|
||||
"GetLocaleInfoA_hook hit ({:#x}, LOCALE_SISO3166CTRYNAME), return `JP`", Locale);
|
||||
strcpy(lpLCData, "JP");
|
||||
return 3;
|
||||
}
|
||||
|
||||
log_misc("hooks::lang", "GetLocaleInfoA_hook hit, {:#x}, {:#x}", Locale, LCType);
|
||||
return GetLocaleInfoA_orig(Locale, LCType, lpLCData, cchData);
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
void hooks::lang::early_init() {
|
||||
@@ -244,6 +275,27 @@ void hooks::lang::early_init() {
|
||||
}
|
||||
|
||||
#ifdef SPICE64
|
||||
|
||||
if (avs::game::is_model("UJK")) {
|
||||
// CCJ build of Unity calls GetThreadLocale,
|
||||
// and then GetLocaleInfoA to figure out the locale; eventually this is
|
||||
// used to figure out the decimal separator, which is then used in parsing
|
||||
// Double values - which fails carding in if it isn't "."
|
||||
log_info("hooks::lang", "hooking GetThreadLocale");
|
||||
detour::trampoline_try(
|
||||
"kernel32.dll",
|
||||
"GetThreadLocale",
|
||||
GetThreadLocale_hook,
|
||||
&GetThreadLocale_orig);
|
||||
|
||||
log_info("hooks::lang", "hooking GetLocaleInfoA");
|
||||
detour::trampoline_try(
|
||||
"kernel32.dll",
|
||||
"GetLocaleInfoA",
|
||||
GetLocaleInfoA_hook,
|
||||
&GetLocaleInfoA_orig);
|
||||
}
|
||||
|
||||
// for TDJ subscreen search keyboard
|
||||
if (avs::game::is_model("LDJ") && games::iidx::TDJ_MODE) {
|
||||
log_info("hooks::lang", "hooking IsDBCSLeadByte");
|
||||
|
||||
@@ -351,9 +351,9 @@ int main_implementation(int argc, char *argv[]) {
|
||||
GRAPHICS_FORCE_SINGLE_ADAPTER = true;
|
||||
GRAPHICS_PREVENT_SECONDARY_WINDOW = true;
|
||||
}
|
||||
if (options[launcher::Options::DX9DisplayAdapter].is_active() &&
|
||||
options[launcher::Options::DX9DisplayAdapter].value_uint32() != D3DADAPTER_DEFAULT) {
|
||||
D3D9_ADAPTER = options[launcher::Options::DX9DisplayAdapter].value_uint32();
|
||||
if (options[launcher::Options::DXDisplayAdapter].is_active() &&
|
||||
options[launcher::Options::DXDisplayAdapter].value_uint32() != D3DADAPTER_DEFAULT) {
|
||||
D3D9_ADAPTER = options[launcher::Options::DXDisplayAdapter].value_uint32();
|
||||
|
||||
// when we fix up adapter numbers, we only fix the first adapter, and not any subsequent
|
||||
// adapters, so we can't deal with multi-monitor games
|
||||
@@ -473,6 +473,9 @@ int main_implementation(int argc, char *argv[]) {
|
||||
if (options[launcher::Options::spice2x_SDVXSubRedraw].value_bool()) {
|
||||
SUBSCREEN_FORCE_REDRAW = true;
|
||||
}
|
||||
if (options[launcher::Options::SDVXSubMonitorOverride].is_active()) {
|
||||
sysutils::SECOND_MONITOR_OVERRIDE = options[launcher::Options::SDVXSubMonitorOverride].value_text();
|
||||
}
|
||||
if (options[launcher::Options::LoadIIDXModule].value_bool()) {
|
||||
attach_iidx = true;
|
||||
}
|
||||
@@ -515,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();
|
||||
@@ -587,6 +596,16 @@ int main_implementation(int argc, char *argv[]) {
|
||||
if (options[launcher::Options::PopnMusicForceSDMode].value_bool()) {
|
||||
avs::ea3::PCB_TYPE = 0;
|
||||
}
|
||||
if (options[launcher::Options::PopnNoSub].value_bool()) {
|
||||
GRAPHICS_FORCE_SINGLE_ADAPTER = true;
|
||||
GRAPHICS_PREVENT_SECONDARY_WINDOW = true;
|
||||
}
|
||||
if (options[launcher::Options::PopnSubMonitorOverride].is_active()) {
|
||||
sysutils::SECOND_MONITOR_OVERRIDE = options[launcher::Options::PopnSubMonitorOverride].value_text();
|
||||
}
|
||||
if (options[launcher::Options::PopnNativeTouch].value_bool()) {
|
||||
games::popn::NATIVE_TOUCH = true;
|
||||
}
|
||||
if (options[launcher::Options::LoadMetalGearArcadeModule].value_bool()) {
|
||||
attach_mga = true;
|
||||
}
|
||||
@@ -1107,6 +1126,7 @@ int main_implementation(int argc, char *argv[]) {
|
||||
}
|
||||
GRAPHICS_WINDOW_ALWAYS_ON_TOP = options[launcher::Options::spice2x_WindowAlwaysOnTop].value_bool();
|
||||
GRAPHICS_WINDOW_BACKBUFFER_SCALE = options[launcher::Options::WindowForceScaling].value_bool();
|
||||
GRAPHICS_WINDOW_DISABLE_ROUNDED_CORNERS = options[launcher::Options::WindowDisableRoundedCorners].value_bool();
|
||||
|
||||
// IIDX/SDVX Windowed Subscreen
|
||||
if (options[launcher::Options::spice2x_IIDXWindowedSubscreenSize].is_active()) {
|
||||
@@ -1127,6 +1147,9 @@ int main_implementation(int argc, char *argv[]) {
|
||||
options[launcher::Options::SDVXWindowedSubscreenAlwaysOnTop].value_bool()) {
|
||||
GRAPHICS_WSUB_ALWAYS_ON_TOP = true;
|
||||
}
|
||||
if (options[launcher::Options::IIDXSubMonitorOverride].is_active()) {
|
||||
sysutils::SECOND_MONITOR_OVERRIDE = options[launcher::Options::IIDXSubMonitorOverride].value_text();
|
||||
}
|
||||
|
||||
if (options[launcher::Options::spice2x_JubeatLegacyTouch].value_bool()) {
|
||||
games::jb::TOUCH_LEGACY_BOX = true;
|
||||
@@ -1227,7 +1250,7 @@ int main_implementation(int argc, char *argv[]) {
|
||||
acio::MDXF_BUFFER_FILL_MODE = acio::MDXFBufferFillMode::THREAD_MODE;
|
||||
} else if (options[launcher::Options::DDRP4IOBufferMode].value_text() == "backfill") {
|
||||
acio::MDXF_BUFFER_FILL_MODE = acio::MDXFBufferFillMode::BACKFILL_MODE;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (options[launcher::Options::MidiAlgoVer].is_active()) {
|
||||
@@ -1294,7 +1317,19 @@ int main_implementation(int argc, char *argv[]) {
|
||||
}
|
||||
|
||||
// log
|
||||
#ifndef SPICE_LINUX
|
||||
#if SPICE_LINUX
|
||||
#ifdef SPICE64
|
||||
log_info("launcher", "SpiceTools Bootstrap (x64) (spice2x) for Linux");
|
||||
#else
|
||||
log_info("launcher", "SpiceTools Bootstrap (x32) (spice2x) for Linux");
|
||||
#endif
|
||||
#elif SPICE_XP
|
||||
#ifdef SPICE64
|
||||
log_info("launcher", "SpiceTools Bootstrap (x64) (spice2x) for WinXP");
|
||||
#else
|
||||
log_info("launcher", "SpiceTools Bootstrap (x32) (spice2x) for WinXP");
|
||||
#endif
|
||||
#else
|
||||
#ifdef SPICE64
|
||||
log_info("launcher", "SpiceTools Bootstrap (x64) (spice2x)");
|
||||
#elif SPICE32_LARGE_ADDRESS_AWARE
|
||||
@@ -1302,12 +1337,6 @@ int main_implementation(int argc, char *argv[]) {
|
||||
#else
|
||||
log_info("launcher", "SpiceTools Bootstrap (x32) (spice2x)");
|
||||
#endif
|
||||
#else
|
||||
#ifdef SPICE64
|
||||
log_info("launcher", "SpiceTools Bootstrap (x64) (spice2x) for Linux");
|
||||
#else
|
||||
log_info("launcher", "SpiceTools Bootstrap (x32) (spice2x) for Linux");
|
||||
#endif
|
||||
#endif
|
||||
|
||||
log_info("launcher", "{}", VERSION_STRING);
|
||||
@@ -1364,7 +1393,7 @@ int main_implementation(int argc, char *argv[]) {
|
||||
log_warning(
|
||||
"launcher",
|
||||
"multiple values for -{}, command line args take precedence: {}",
|
||||
option.get_definition().name,
|
||||
option.get_definition().name,
|
||||
value);
|
||||
} else {
|
||||
log_warning(
|
||||
@@ -1379,7 +1408,7 @@ int main_implementation(int argc, char *argv[]) {
|
||||
if (launcher::USE_CMD_OVERRIDE) {
|
||||
log_info(
|
||||
"launcher",
|
||||
"user specified -cmdoverride, therefore command line args took precedence over spicecfg");
|
||||
"user specified -cmdoverride, therefore command line args took precedence over spicecfg");
|
||||
} else {
|
||||
log_warning(
|
||||
"launcher",
|
||||
@@ -1745,11 +1774,21 @@ int main_implementation(int argc, char *argv[]) {
|
||||
break;
|
||||
}
|
||||
|
||||
// HELLO! Pop'n Music
|
||||
if (check_dll("popn.dll")) {
|
||||
avs::game::DLL_NAME = "popn.dll";
|
||||
attach_io = true;
|
||||
attach_hpm = true;
|
||||
|
||||
if (check_dll("libaio-iob2_video.dll")) {
|
||||
// pop'n music (pika cabinet)
|
||||
attach_popn = true;
|
||||
// automatically show cursor in windowed mode to interact with second window (sub)
|
||||
if (GRAPHICS_WINDOWED) {
|
||||
GRAPHICS_SHOW_CURSOR = true;
|
||||
}
|
||||
} else {
|
||||
// HELLO! Pop'n Music
|
||||
attach_hpm = true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -1802,9 +1841,7 @@ int main_implementation(int argc, char *argv[]) {
|
||||
attach_ftt = true;
|
||||
|
||||
// the game is windowed by default unless we set the env
|
||||
if (GRAPHICS_WINDOWED) {
|
||||
GRAPHICS_WINDOWED = false;
|
||||
} else {
|
||||
if (!GRAPHICS_WINDOWED) {
|
||||
SetEnvironmentVariable("DAMAC_VIEWER_FULLSCREEN", "0");
|
||||
}
|
||||
|
||||
@@ -2226,7 +2263,7 @@ int main_implementation(int argc, char *argv[]) {
|
||||
game->attach();
|
||||
}
|
||||
|
||||
#ifdef SPICE64
|
||||
#if SPICE64 && !SPICE_XP
|
||||
if (!cfg::CONFIGURATOR_STANDALONE) {
|
||||
if (games::iidx::TDJ_CAMERA) {
|
||||
games::iidx::init_camera_hooks();
|
||||
@@ -2333,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"
|
||||
@@ -2507,7 +2544,7 @@ int main_implementation(int argc, char *argv[]) {
|
||||
// disable poke
|
||||
games::iidx::poke::disable();
|
||||
|
||||
#ifdef SPICE64
|
||||
#if SPICE64 && !SPICE_XP
|
||||
games::iidx::camera_release();
|
||||
#endif
|
||||
|
||||
|
||||
@@ -32,6 +32,7 @@ static const std::vector<std::string> CATEGORY_ORDER_BASIC = {
|
||||
|
||||
static const std::vector<std::string> CATEGORY_ORDER_ADVANCED = {
|
||||
"Game Options (Advanced)",
|
||||
"Game Options (Peripherals)",
|
||||
"Overlay",
|
||||
"Network (Advanced)",
|
||||
"Performance",
|
||||
@@ -218,11 +219,11 @@ static const std::vector<OptionDefinition> OPTION_DEFINITIONS = {
|
||||
.picker = OptionPickerType::Monitor,
|
||||
},
|
||||
{
|
||||
// DX9DisplayAdapter
|
||||
.title = "DX9 Primary Display Adapter Override",
|
||||
// DXDisplayAdapter
|
||||
.title = "DX Primary Display Adapter Override",
|
||||
.name = "monitor",
|
||||
.display_name = "dx9mainadapter",
|
||||
.aliases = "dx9mainadapter",
|
||||
.display_name = "dxmainadapter",
|
||||
.aliases = "dxmainadapter",
|
||||
.desc = "Prefer to use Change Main Monitor option instead of this one.\n\n"
|
||||
"Sets the display that the game will be opened in, for multiple monitors.\n\n"
|
||||
"0 is the primary monitor, 1 is the second monitor, and so on.\n\n"
|
||||
@@ -513,7 +514,7 @@ static const std::vector<OptionDefinition> OPTION_DEFINITIONS = {
|
||||
.desc = "Flip the camera order.",
|
||||
.type = OptionType::Bool,
|
||||
.game_name = "Beatmania IIDX",
|
||||
.category = "Game Options (Advanced)",
|
||||
.category = "Game Options (Peripherals)",
|
||||
},
|
||||
{
|
||||
// IIDXDisableCameras
|
||||
@@ -523,7 +524,7 @@ static const std::vector<OptionDefinition> OPTION_DEFINITIONS = {
|
||||
.type = OptionType::Bool,
|
||||
.hidden = true,
|
||||
.game_name = "Beatmania IIDX",
|
||||
.category = "Game Options",
|
||||
.category = "Game Options (Peripherals)",
|
||||
},
|
||||
{
|
||||
// IIDXCabCamAccess
|
||||
@@ -533,7 +534,7 @@ static const std::vector<OptionDefinition> OPTION_DEFINITIONS = {
|
||||
"Only turn this on if you have OFFICIAL arcade cameras connected to the correct USB ports. Default: auto.",
|
||||
.type = OptionType::Enum,
|
||||
.game_name = "Beatmania IIDX",
|
||||
.category = "Game Options (Advanced)",
|
||||
.category = "Game Options (Peripherals)",
|
||||
.elements = {
|
||||
{"auto", ""},
|
||||
{"off", ""},
|
||||
@@ -549,7 +550,7 @@ static const std::vector<OptionDefinition> OPTION_DEFINITIONS = {
|
||||
.desc = "Experimental camera support for IIDX 27+ via texture hooking. Requires camera(s) that support YUY2 or NV12 encoding.",
|
||||
.type = OptionType::Bool,
|
||||
.game_name = "Beatmania IIDX",
|
||||
.category = "Game Options (Advanced)",
|
||||
.category = "Game Options (Peripherals)",
|
||||
},
|
||||
{
|
||||
// IIDXCamHookRatio
|
||||
@@ -561,7 +562,7 @@ static const std::vector<OptionDefinition> OPTION_DEFINITIONS = {
|
||||
.type = OptionType::Enum,
|
||||
.hidden = true,
|
||||
.game_name = "Beatmania IIDX",
|
||||
.category = "Game Options (Advanced)",
|
||||
.category = "Game Options (Peripherals)",
|
||||
.elements = {
|
||||
{"43", "4:3"},
|
||||
{"169", "16:9"},
|
||||
@@ -579,7 +580,7 @@ static const std::vector<OptionDefinition> OPTION_DEFINITIONS = {
|
||||
.type = OptionType::Text,
|
||||
.setting_name = "0x5817a0,0x6fffbd8,0xbbae40,0xe0,0x30",
|
||||
.game_name = "Beatmania IIDX",
|
||||
.category = "Game Options (Advanced)",
|
||||
.category = "Game Options (Peripherals)",
|
||||
},
|
||||
{
|
||||
// IIDXCamHookTopId
|
||||
@@ -594,7 +595,7 @@ static const std::vector<OptionDefinition> OPTION_DEFINITIONS = {
|
||||
.type = OptionType::Text,
|
||||
.setting_name = "vid_1234&pid_5678",
|
||||
.game_name = "Beatmania IIDX",
|
||||
.category = "Game Options (Advanced)",
|
||||
.category = "Game Options (Peripherals)",
|
||||
},
|
||||
{
|
||||
// IIDXCamHookFrontId
|
||||
@@ -609,7 +610,7 @@ static const std::vector<OptionDefinition> OPTION_DEFINITIONS = {
|
||||
.type = OptionType::Text,
|
||||
.setting_name = "vid_90ab&pid_cdef",
|
||||
.game_name = "Beatmania IIDX",
|
||||
.category = "Game Options (Advanced)",
|
||||
.category = "Game Options (Peripherals)",
|
||||
},
|
||||
{
|
||||
.title = "IIDX Sound Output Device",
|
||||
@@ -654,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",
|
||||
@@ -772,11 +797,11 @@ static const std::vector<OptionDefinition> OPTION_DEFINITIONS = {
|
||||
.category = "Game Options (Advanced)",
|
||||
},
|
||||
{
|
||||
.title = "SDVX Force 720p",
|
||||
.title = "SDVX Vivid Wave Force 720p Window (DEPRECATED - use -windowsize instead)",
|
||||
.name = "sdvx720",
|
||||
.desc = "Force Sound Voltex 720p display mode, used by older games. "
|
||||
"For newer games, use window resize function instead.",
|
||||
.desc = "Old & deprecated option for launching Vivid Wave in 720p when using windowed mode.",
|
||||
.type = OptionType::Bool,
|
||||
.hidden = true,
|
||||
.game_name = "Sound Voltex",
|
||||
.category = "Game Options (Advanced)",
|
||||
},
|
||||
@@ -786,7 +811,7 @@ static const std::vector<OptionDefinition> OPTION_DEFINITIONS = {
|
||||
.desc = "Enable Sound Voltex printer emulation.",
|
||||
.type = OptionType::Bool,
|
||||
.game_name = "Sound Voltex",
|
||||
.category = "Game Options (Advanced)",
|
||||
.category = "Game Options (Peripherals)",
|
||||
},
|
||||
{
|
||||
// SDVXPrinterOutputPath
|
||||
@@ -795,7 +820,7 @@ static const std::vector<OptionDefinition> OPTION_DEFINITIONS = {
|
||||
.desc = "Path to folder where images will be stored.",
|
||||
.type = OptionType::Text,
|
||||
.game_name = "Sound Voltex",
|
||||
.category = "Game Options (Advanced)",
|
||||
.category = "Game Options (Peripherals)",
|
||||
.picker = OptionPickerType::DirectoryPath,
|
||||
},
|
||||
{
|
||||
@@ -804,7 +829,7 @@ static const std::vector<OptionDefinition> OPTION_DEFINITIONS = {
|
||||
.desc = "Clean up saved images in the output directory on startup.",
|
||||
.type = OptionType::Bool,
|
||||
.game_name = "Sound Voltex",
|
||||
.category = "Game Options (Advanced)",
|
||||
.category = "Game Options (Peripherals)",
|
||||
},
|
||||
{
|
||||
.title = "SDVX Printer Output Overwrite",
|
||||
@@ -812,7 +837,7 @@ static const std::vector<OptionDefinition> OPTION_DEFINITIONS = {
|
||||
.desc = "Always overwrite the same file in output directory.",
|
||||
.type = OptionType::Bool,
|
||||
.game_name = "Sound Voltex",
|
||||
.category = "Game Options (Advanced)",
|
||||
.category = "Game Options (Peripherals)",
|
||||
},
|
||||
{
|
||||
// SDVXPrinterOutputFormat
|
||||
@@ -822,7 +847,7 @@ static const std::vector<OptionDefinition> OPTION_DEFINITIONS = {
|
||||
.type = OptionType::Text,
|
||||
.setting_name = "(png/bmp/tga/jpg)",
|
||||
.game_name = "Sound Voltex",
|
||||
.category = "Game Options (Advanced)",
|
||||
.category = "Game Options (Peripherals)",
|
||||
},
|
||||
{
|
||||
.title = "SDVX Printer JPG Quality",
|
||||
@@ -831,7 +856,7 @@ static const std::vector<OptionDefinition> OPTION_DEFINITIONS = {
|
||||
.type = OptionType::Integer,
|
||||
.setting_name = "(0-100)",
|
||||
.game_name = "Sound Voltex",
|
||||
.category = "Game Options (Advanced)",
|
||||
.category = "Game Options (Peripherals)",
|
||||
},
|
||||
{
|
||||
.title = "SDVX Disable Cameras (DEPRECATED - no longer needed)",
|
||||
@@ -842,7 +867,7 @@ static const std::vector<OptionDefinition> OPTION_DEFINITIONS = {
|
||||
.type = OptionType::Bool,
|
||||
.hidden = true,
|
||||
.game_name = "Sound Voltex",
|
||||
.category = "Game Options",
|
||||
.category = "Game Options (Peripherals)",
|
||||
},
|
||||
{
|
||||
.title = "SDVX FS Subscreen Native Touch Handling",
|
||||
@@ -928,6 +953,17 @@ static const std::vector<OptionDefinition> OPTION_DEFINITIONS = {
|
||||
{"bottomright", "for landscape"},
|
||||
},
|
||||
},
|
||||
{
|
||||
// SDVXSubMonitorOverride
|
||||
.title = "SDVX Subscreen Monitor Override",
|
||||
.name = "sdvxsubmonitor",
|
||||
.desc = "If you have three or more monitors, this option can be set to tell the game which monitor is the subscreen.",
|
||||
.type = OptionType::Text,
|
||||
.setting_name = "\\\\.\\DISPLAY3",
|
||||
.game_name = "Sound Voltex",
|
||||
.category = "Monitor",
|
||||
.picker = OptionPickerType::Monitor,
|
||||
},
|
||||
{
|
||||
.title = "Force Load DDR Module",
|
||||
.name = "ddr",
|
||||
@@ -977,6 +1013,38 @@ static const std::vector<OptionDefinition> OPTION_DEFINITIONS = {
|
||||
.game_name = "Pop'n Music",
|
||||
.category = "Game Options",
|
||||
},
|
||||
{
|
||||
// PopnNoSub
|
||||
.title = "Pop'n Music PikaPika Subscreen Disable",
|
||||
.name = "popnnosub",
|
||||
.desc = "Prevents PikaPika model subscreen from launching a separate window.",
|
||||
.type = OptionType::Bool,
|
||||
.game_name = "Pop'n Music",
|
||||
.category = "Game Options",
|
||||
},
|
||||
{
|
||||
// PopnSubMonitorOverride
|
||||
.title = "Pop'n Music PikaPika Subscreen Monitor Override",
|
||||
.name = "popnsubmonitor",
|
||||
.desc = "If you have three or more monitors, this option can be set to tell the game which monitor is the subscreen.",
|
||||
.type = OptionType::Text,
|
||||
.setting_name = "\\\\.\\DISPLAY3",
|
||||
.game_name = "Pop'n Music",
|
||||
.category = "Monitor",
|
||||
.picker = OptionPickerType::Monitor,
|
||||
},
|
||||
{
|
||||
// PopnNativeTouch
|
||||
.title = "Pop'n Music PikaPika Native Touch Handling",
|
||||
.name = "popnnativetouch",
|
||||
.desc = "Disables touch hooks and lets the game access a touch screen directly. "
|
||||
"Requires a touch screen to be connected as a secondary monitor. "
|
||||
"Touch input must be routed to the primary screen via Windows Tablet PC settings. "
|
||||
"Enable this when you get duplicate touch inputs from an actual touch screen.",
|
||||
.type = OptionType::Bool,
|
||||
.game_name = "Pop'n Music",
|
||||
.category = "Game Options (Advanced)",
|
||||
},
|
||||
{
|
||||
.title = "Force Load HELLO! Pop'n Music Module",
|
||||
.name = "hpm",
|
||||
@@ -2187,6 +2255,14 @@ static const std::vector<OptionDefinition> OPTION_DEFINITIONS = {
|
||||
.type = OptionType::Bool,
|
||||
.category = "Graphics (Windowed)",
|
||||
},
|
||||
{
|
||||
// WindowDisableRoundedCorners
|
||||
.title = "Disable Round Window Corners",
|
||||
.name = "windownoroundcorners",
|
||||
.desc = "Windows 11 and above only: Disables rounded corners on the game window(s).",
|
||||
.type = OptionType::Bool,
|
||||
.category = "Graphics (Windowed)",
|
||||
},
|
||||
{
|
||||
// spice2x_IIDXWindowedSubscreenSize
|
||||
.title = "IIDX Windowed Subscreen Size",
|
||||
@@ -2316,7 +2392,7 @@ static const std::vector<OptionDefinition> OPTION_DEFINITIONS = {
|
||||
"Note: this is NOT for the motion camera - you still need a real RealSense camera for that!",
|
||||
.type = OptionType::Bool,
|
||||
.game_name = "DANCERUSH",
|
||||
.category = "Game Options",
|
||||
.category = "Game Options (Peripherals)",
|
||||
},
|
||||
{
|
||||
// spice2x_IIDXNativeTouch
|
||||
@@ -2343,6 +2419,17 @@ static const std::vector<OptionDefinition> OPTION_DEFINITIONS = {
|
||||
.game_name = "Beatmania IIDX",
|
||||
.category = "Game Options",
|
||||
},
|
||||
{
|
||||
// IIDXSubMonitorOverride
|
||||
.title = "IIDX TDJ Subscreen Monitor Override",
|
||||
.name = "iidxsubmonitor",
|
||||
.desc = "If you have three or more monitors, this option can be set to tell the game which monitor is the subscreen.",
|
||||
.type = OptionType::Text,
|
||||
.setting_name = "\\\\.\\DISPLAY3",
|
||||
.game_name = "Beatmania IIDX",
|
||||
.category = "Monitor",
|
||||
.picker = OptionPickerType::Monitor,
|
||||
},
|
||||
{
|
||||
// spice2x_IIDXEmulateSubscreenKeypadTouch
|
||||
.title = "IIDX TDJ Subscreen Keypad Touch Emulation",
|
||||
@@ -2639,7 +2726,7 @@ static const std::vector<OptionDefinition> OPTION_DEFINITIONS = {
|
||||
.desc = "Allow game to access camera; camera must be compatible with game.",
|
||||
.type = OptionType::Bool,
|
||||
.game_name = "LovePlus",
|
||||
.category = "Game Options",
|
||||
.category = "Game Options (Peripherals)",
|
||||
},
|
||||
{
|
||||
// LovePlusPrinterOutputPath
|
||||
@@ -2648,7 +2735,7 @@ static const std::vector<OptionDefinition> OPTION_DEFINITIONS = {
|
||||
.desc = "Path to folder where images will be stored.",
|
||||
.type = OptionType::Text,
|
||||
.game_name = "LovePlus",
|
||||
.category = "Game Options (Advanced)",
|
||||
.category = "Game Options (Peripherals)",
|
||||
.picker = OptionPickerType::DirectoryPath,
|
||||
},
|
||||
{
|
||||
@@ -2657,7 +2744,7 @@ static const std::vector<OptionDefinition> OPTION_DEFINITIONS = {
|
||||
.desc = "Clean up saved images in the output directory on startup.",
|
||||
.type = OptionType::Bool,
|
||||
.game_name = "LovePlus",
|
||||
.category = "Game Options (Advanced)",
|
||||
.category = "Game Options (Peripherals)",
|
||||
},
|
||||
{
|
||||
.title = "LovePlus Printer Output Overwrite",
|
||||
@@ -2665,7 +2752,7 @@ static const std::vector<OptionDefinition> OPTION_DEFINITIONS = {
|
||||
.desc = "Always overwrite the same file in output directory.",
|
||||
.type = OptionType::Bool,
|
||||
.game_name = "LovePlus",
|
||||
.category = "Game Options (Advanced)",
|
||||
.category = "Game Options (Peripherals)",
|
||||
},
|
||||
{
|
||||
// LovePlusPrinterOutputFormat
|
||||
@@ -2675,7 +2762,7 @@ static const std::vector<OptionDefinition> OPTION_DEFINITIONS = {
|
||||
.type = OptionType::Text,
|
||||
.setting_name = "(png/bmp/tga/jpg)",
|
||||
.game_name = "LovePlus",
|
||||
.category = "Game Options (Advanced)",
|
||||
.category = "Game Options (Peripherals)",
|
||||
},
|
||||
{
|
||||
.title = "LovePlus Printer JPG Quality",
|
||||
@@ -2684,7 +2771,7 @@ static const std::vector<OptionDefinition> OPTION_DEFINITIONS = {
|
||||
.type = OptionType::Integer,
|
||||
.setting_name = "(0-100)",
|
||||
.game_name = "LovePlus",
|
||||
.category = "Game Options (Advanced)",
|
||||
.category = "Game Options (Peripherals)",
|
||||
},
|
||||
{
|
||||
// OptionConflictResolution
|
||||
@@ -2705,7 +2792,7 @@ static const std::vector<OptionDefinition> OPTION_DEFINITIONS = {
|
||||
"option to bypass camera error during boot, allowing you to try out the game.",
|
||||
.type = OptionType::Bool,
|
||||
.game_name = "Otoca D'or",
|
||||
.category = "Game Options",
|
||||
.category = "Game Options (Peripherals)",
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -27,7 +27,7 @@ namespace launcher {
|
||||
CaptureCursor,
|
||||
ShowCursor,
|
||||
PrimaryMonitor,
|
||||
DX9DisplayAdapter,
|
||||
DXDisplayAdapter,
|
||||
GraphicsForceSingleAdapter,
|
||||
GraphicsForceRefresh,
|
||||
FullscreenResolution,
|
||||
@@ -69,6 +69,8 @@ namespace launcher {
|
||||
IIDXAsioDriver,
|
||||
IIDXBIO2FW,
|
||||
IIDXTDJMode,
|
||||
IIDXTTDelayP1,
|
||||
IIDXTTDelayP2,
|
||||
spice2x_IIDXDigitalTTSensitivity,
|
||||
IIDXDigitalTTSocd,
|
||||
spice2x_IIDXLDJForce720p,
|
||||
@@ -92,12 +94,16 @@ namespace launcher {
|
||||
SDVXDigitalKnobSocd,
|
||||
spice2x_SDVXAsioDriver,
|
||||
spice2x_SDVXSubPos,
|
||||
SDVXSubMonitorOverride,
|
||||
LoadDDRModule,
|
||||
DDR43Mode,
|
||||
DDRSkipCodecRegisteration,
|
||||
LoadPopnMusicModule,
|
||||
PopnMusicForceHDMode,
|
||||
PopnMusicForceSDMode,
|
||||
PopnNoSub,
|
||||
PopnSubMonitorOverride,
|
||||
PopnNativeTouch,
|
||||
LoadHelloPopnMusicModule,
|
||||
LoadGitaDoraModule,
|
||||
GitaDoraTwoChannelAudio,
|
||||
@@ -229,6 +235,7 @@ namespace launcher {
|
||||
spice2x_WindowPosition,
|
||||
spice2x_WindowAlwaysOnTop,
|
||||
WindowForceScaling,
|
||||
WindowDisableRoundedCorners,
|
||||
spice2x_IIDXWindowedSubscreenSize,
|
||||
spice2x_IIDXWindowedSubscreenPosition,
|
||||
IIDXWindowedSubscreenBorderless,
|
||||
@@ -243,6 +250,7 @@ namespace launcher {
|
||||
DRSRGBCameraHook,
|
||||
spice2x_IIDXNativeTouch,
|
||||
spice2x_IIDXNoSub,
|
||||
IIDXSubMonitorOverride,
|
||||
spice2x_IIDXEmulateSubscreenKeypadTouch,
|
||||
spice2x_AutoCard,
|
||||
spice2x_LowLatencySharedAudio,
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -86,13 +90,12 @@ namespace launcher {
|
||||
if (LAUNCHER_ARGC > 0) {
|
||||
|
||||
// build cmd line
|
||||
std::stringstream cmd_line;
|
||||
cmd_line << "START \"\" ";
|
||||
std::string cmd_line = "START \"\" ";
|
||||
for (int i = 0; i < LAUNCHER_ARGC; i++)
|
||||
cmd_line << " \"" << LAUNCHER_ARGV[i] << "\"";
|
||||
cmd_line += " \"" + std::string(LAUNCHER_ARGV[i]) + "\"";
|
||||
|
||||
// run command
|
||||
system(cmd_line.str().c_str());
|
||||
system(cmd_line.c_str());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -87,7 +87,7 @@ bool eamuse_get_card(const std::filesystem::path &path, uint8_t *card, int index
|
||||
std::unique_lock<std::mutex> lock(CARD_OVERRIDES_LOCK);
|
||||
const auto card_override = CARD_OVERRIDES[index];
|
||||
lock.unlock();
|
||||
|
||||
|
||||
// Check if card overrides are present
|
||||
if (!card_override.empty()) {
|
||||
|
||||
@@ -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);
|
||||
@@ -454,7 +454,7 @@ uint16_t eamuse_get_keypad_state(size_t unit) {
|
||||
if (unit >= std::size(KEYPAD_STATE)) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
// reset
|
||||
KEYPAD_STATE[unit] = KEYPAD_STATE_OVERRIDES[unit];
|
||||
KEYPAD_STATE[unit] |= KEYPAD_STATE_OVERRIDES_BT5[unit];
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
#include "cfg/screen_resize.h"
|
||||
#include "games/gitadora/gitadora.h"
|
||||
#include "games/iidx/iidx.h"
|
||||
#include "games/popn/popn.h"
|
||||
#include "hooks/graphics/graphics.h"
|
||||
#include "overlay/overlay.h"
|
||||
#include "overlay/windows/generic_sub.h"
|
||||
@@ -405,6 +406,10 @@ namespace wintouchemu {
|
||||
// use mouse position as ImGui overlay will block the touch window
|
||||
log_info("wintouchemu", "use mouse cursor API for ldj overlay subscreen");
|
||||
USE_MOUSE = true;
|
||||
} else if (games::popn::is_pikapika_model()) {
|
||||
// same as iidx case above
|
||||
log_info("wintouchemu", "use mouse cursor API for popn overlay subscreen");
|
||||
USE_MOUSE = true;
|
||||
} else if (games::gitadora::is_arena_model() && games::gitadora::ARENA_SINGLE_WINDOW) {
|
||||
log_info("wintouchemu", "use mouse cursor API for gitadora overlay subscreen");
|
||||
USE_MOUSE = true;
|
||||
|
||||
@@ -169,8 +169,10 @@ namespace ImGui {
|
||||
ImGui::PushStyleColor(ImGuiCol_Header, 0);
|
||||
ImGui::PushStyleColor(ImGuiCol_HeaderHovered, 0);
|
||||
ImGui::PushStyleColor(ImGuiCol_HeaderActive, 0);
|
||||
ImGui::PushTabStop(false); // prevent tab navigation
|
||||
ImGui::Selectable("##row", false,
|
||||
ImGuiSelectableFlags_SpanAllColumns | ImGuiSelectableFlags_AllowOverlap);
|
||||
ImGui::PopTabStop();
|
||||
ImGui::PopStyleColor(3);
|
||||
if (ImGui::IsItemHovered()) {
|
||||
ImGui::TableSetBgColor(ImGuiTableBgTarget_RowBg1, IM_COL32(200, 200, 200, 24));
|
||||
|
||||
@@ -265,7 +265,8 @@ static void ImGui_ImplSpice_UpdateMousePos() {
|
||||
// trigger any overlay, don't process anything else
|
||||
const auto is_windowed_subscreen =
|
||||
(GRAPHICS_IIDX_WSUB && active_window == TDJ_SUBSCREEN_WINDOW) ||
|
||||
(active_window == SDVX_SUBSCREEN_WINDOW);
|
||||
(active_window == SDVX_SUBSCREEN_WINDOW) ||
|
||||
(active_window == POPN_SUBSCREEN_WINDOW);
|
||||
if (is_windowed_subscreen) {
|
||||
io.MousePos = ImVec2(-FLT_MAX, -FLT_MAX);
|
||||
return;
|
||||
@@ -348,6 +349,10 @@ void ImGui_ImplSpice_NewFrame() {
|
||||
io.DeltaTime = (float) (current_time - g_Time) / g_TicksPerSecond;
|
||||
g_Time = current_time;
|
||||
|
||||
// only process new input from devices if window is in focus
|
||||
// (when not in focus, all mouse/keyboard events are treated as released)
|
||||
const auto accept_new_input = superexit::has_focus() && !rawinput::OS_WINDOW_ACTIVE;
|
||||
|
||||
// remember old state
|
||||
std::array<BYTE, VKEY_MAX> KeysDownOld;
|
||||
for (size_t i = 0; i < VKEY_MAX; i++) {
|
||||
@@ -360,11 +365,6 @@ void ImGui_ImplSpice_NewFrame() {
|
||||
g_MouseDown.fill(false);
|
||||
g_KeysDown.fill(false);
|
||||
|
||||
// early quit if window not in focus
|
||||
if (!superexit::has_focus() || rawinput::OS_WINDOW_ACTIVE) {
|
||||
return;
|
||||
}
|
||||
|
||||
// apply windows mouse buttons
|
||||
g_MouseDown[ImGuiMouseButton_Left] |= (get_async_primary_mouse()) != 0;
|
||||
g_MouseDown[ImGuiMouseButton_Right] |= (get_async_secondary_mouse()) != 0;
|
||||
@@ -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,13 +470,17 @@ 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;
|
||||
ImGui_ImplSpice_UpdateMousePos();
|
||||
if (accept_new_input) {
|
||||
ImGui_ImplSpice_UpdateMousePos();
|
||||
}
|
||||
const auto new_mouse_pos = io.MousePos;
|
||||
|
||||
// update mouse buttons
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
#include "games/io.h"
|
||||
#include "games/gitadora/gitadora.h"
|
||||
#include "games/iidx/iidx.h"
|
||||
#include "games/popn/popn.h"
|
||||
#include "hooks/graphics/graphics.h"
|
||||
#include "misc/eamuse.h"
|
||||
#include "touch/touch.h"
|
||||
@@ -35,6 +36,7 @@
|
||||
#include "windows/iopanel_ddr.h"
|
||||
#include "windows/iopanel_gfdm.h"
|
||||
#include "windows/iopanel_iidx.h"
|
||||
#include "windows/popn_sub.h"
|
||||
#include "windows/sdvx_sub.h"
|
||||
#include "windows/keypad.h"
|
||||
#include "windows/log.h"
|
||||
@@ -69,7 +71,7 @@ namespace overlay {
|
||||
}
|
||||
return ImVec2(apply_scaling(x), apply_scaling(y));
|
||||
}
|
||||
|
||||
|
||||
ImVec2 apply_scaling_to_vector(const ImVec2& input) {
|
||||
return apply_scaling_to_vector(input.x, input.y);
|
||||
}
|
||||
@@ -350,7 +352,7 @@ void overlay::SpiceOverlay::init() {
|
||||
this->window_add(window_config = new overlay::windows::Config(this));
|
||||
this->window_add(window_control = new overlay::windows::Control(this));
|
||||
this->window_add(window_log = new overlay::windows::Log(this));
|
||||
#ifdef SPICE64
|
||||
#if SPICE64 && !SPICE_XP
|
||||
if (avs::game::is_model("LDJ")) {
|
||||
this->window_add(window_camera = new overlay::windows::CameraControl(this));
|
||||
}
|
||||
@@ -415,6 +417,8 @@ void overlay::SpiceOverlay::init() {
|
||||
window_sub = new overlay::windows::SDVXSubScreen(this);
|
||||
} else if (games::gitadora::is_arena_model() && games::gitadora::ARENA_SINGLE_WINDOW) {
|
||||
window_sub = new overlay::windows::GitaDoraSubScreen(this);
|
||||
} else if (games::popn::is_pikapika_model()) {
|
||||
window_sub = new overlay::windows::PopnSubScreen(this);
|
||||
}
|
||||
|
||||
if (window_sub) {
|
||||
@@ -729,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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#include "camera_control.h"
|
||||
|
||||
#if SPICE64
|
||||
#if SPICE64 && !SPICE_XP
|
||||
|
||||
#include <games/io.h>
|
||||
#include <strmif.h>
|
||||
@@ -193,7 +193,7 @@ namespace overlay::windows {
|
||||
}
|
||||
|
||||
ImGui::Separator();
|
||||
|
||||
|
||||
// reset button
|
||||
if (ImGui::Button("Reset")) {
|
||||
selectedCamera->ResetCameraControlProps();
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#pragma once
|
||||
|
||||
#if SPICE64
|
||||
#if SPICE64 && !SPICE_XP
|
||||
|
||||
#include "overlay/window.h"
|
||||
#include <strmif.h>
|
||||
|
||||
@@ -144,7 +144,7 @@ namespace overlay::windows {
|
||||
" xxxx xxxx xxxx xxxx ");
|
||||
ImGui::EndDisabled();
|
||||
}
|
||||
|
||||
|
||||
ImGui::PopID();
|
||||
return clicked;
|
||||
}
|
||||
@@ -152,8 +152,8 @@ namespace overlay::windows {
|
||||
void CardManager::open_card_editor() {
|
||||
if (this->current_card) {
|
||||
const auto card = this->current_card;
|
||||
strcpy_s(this->name_buffer, std::size(this->name_buffer), card->name.c_str());
|
||||
strcpy_s(this->card_buffer, std::size(this->card_buffer), card->id.c_str());
|
||||
snprintf(this->name_buffer, std::size(this->name_buffer), "%s", card->name.c_str());
|
||||
snprintf(this->card_buffer, std::size(this->card_buffer), "%s", card->id.c_str());
|
||||
this->color_buffer[0] = card->color[0];
|
||||
this->color_buffer[1] = card->color[1];
|
||||
this->color_buffer[2] = card->color[2];
|
||||
@@ -227,7 +227,7 @@ namespace overlay::windows {
|
||||
};
|
||||
generate_search_string(&card);
|
||||
this->cards.emplace_back(card);
|
||||
|
||||
|
||||
// mark this card as the selected one
|
||||
this->current_card = &this->cards.back();
|
||||
}
|
||||
@@ -301,7 +301,7 @@ namespace overlay::windows {
|
||||
|
||||
// search for card
|
||||
//
|
||||
// setting ImGuiInputTextFlags_CallbackCharFilter and pressing escape doesn't cause below
|
||||
// setting ImGuiInputTextFlags_CallbackCharFilter and pressing escape doesn't cause below
|
||||
// to return true, making it necessary to provide a callback...
|
||||
ImGui::SetNextItemWidth(overlay::apply_scaling(240));
|
||||
if (ImGui::InputTextWithHint("", "Type here to search..", &this->search_filter)) {
|
||||
@@ -399,11 +399,11 @@ namespace overlay::windows {
|
||||
|
||||
// cards from card manager JSON
|
||||
for (auto &card : this->cards) {
|
||||
|
||||
|
||||
if (!this->search_filter_in_lower_case.empty() && !card.search_string.empty()) {
|
||||
const bool matched =
|
||||
card.search_string.find(this->search_filter_in_lower_case) != std::string::npos;
|
||||
|
||||
|
||||
if (!matched) {
|
||||
continue;
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -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();
|
||||
@@ -193,13 +193,15 @@ namespace overlay::windows {
|
||||
std::vector<Option> *options, const std::string &category, const std::string *filter=nullptr);
|
||||
void build_about();
|
||||
void build_launcher();
|
||||
void launch_shell(LPCSTR app, LPCSTR file=nullptr);
|
||||
void build_keypad_warning();
|
||||
|
||||
void launch_shell(LPCSTR app, LPCSTR file=nullptr);
|
||||
|
||||
void build_menu(int *game_selected);
|
||||
void shutdown_system(bool force, bool reboot_instead);
|
||||
|
||||
|
||||
void set_alternating_row_colors(const int row_index);
|
||||
|
||||
|
||||
bool validate_ea_card(char card_number[16]);
|
||||
|
||||
public:
|
||||
|
||||
@@ -628,13 +628,12 @@ namespace overlay::windows {
|
||||
ImGui::Text("Wheel: %ld", mouse->pos_wheel);
|
||||
|
||||
// keys
|
||||
std::stringstream keys;
|
||||
keys << "[";
|
||||
std::string keys = "[";
|
||||
for (auto key : mouse->key_states) {
|
||||
keys << (key ? "1," : "0,");
|
||||
keys += (key ? "1," : "0,");
|
||||
}
|
||||
keys << "]";
|
||||
ImGui::Text("Keys: %s", keys.str().c_str());
|
||||
keys += "]";
|
||||
ImGui::Text("Keys: %s", keys.c_str());
|
||||
|
||||
break;
|
||||
}
|
||||
@@ -643,15 +642,14 @@ namespace overlay::windows {
|
||||
ImGui::Text("Type: Keyboard");
|
||||
|
||||
// keys
|
||||
std::stringstream keys;
|
||||
keys << "[";
|
||||
std::string keys = "[";
|
||||
for (size_t i = 0; i < std::size(keyboard->key_states); i++) {
|
||||
if (keyboard->key_states[i]) {
|
||||
keys << i << ",";
|
||||
keys += std::to_string(i) + ",";
|
||||
}
|
||||
}
|
||||
keys << "]";
|
||||
ImGui::Text("Keys: %s", keys.str().c_str());
|
||||
keys += "]";
|
||||
ImGui::Text("Keys: %s", keys.c_str());
|
||||
|
||||
break;
|
||||
}
|
||||
@@ -732,6 +730,10 @@ namespace overlay::windows {
|
||||
ImGui::Text("Type: PIUIO");
|
||||
break;
|
||||
}
|
||||
case rawinput::XINPUT_GAMEPAD: {
|
||||
ImGui::Text("Type: XInput");
|
||||
break;
|
||||
}
|
||||
case rawinput::DESTROYED: {
|
||||
ImGui::Text("Disconnected.");
|
||||
break;
|
||||
@@ -739,6 +741,7 @@ namespace overlay::windows {
|
||||
case rawinput::UNKNOWN:
|
||||
default:
|
||||
ImGui::Text("Type: Unknown");
|
||||
break;
|
||||
}
|
||||
ImGui::TreePop();
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
#include "util/logging.h"
|
||||
#include "games/iidx/iidx.h"
|
||||
#include "games/gitadora/gitadora.h"
|
||||
#include "games/popn/popn.h"
|
||||
|
||||
namespace overlay::windows {
|
||||
|
||||
@@ -72,6 +73,8 @@ namespace overlay::windows {
|
||||
sub = "Show DRS Dance Floor";
|
||||
} else if (games::gitadora::is_arena_model() && games::gitadora::ARENA_SINGLE_WINDOW) {
|
||||
sub = "Show GITADORA Subscreen";
|
||||
} else if (games::popn::is_pikapika_model()) {
|
||||
sub = "Show Pop'n Subscreen";
|
||||
}
|
||||
|
||||
build_button(this->overlay->window_sub, sub, size, NextItem::NEW_LINE, false);
|
||||
|
||||
@@ -14,7 +14,7 @@ namespace overlay::windows {
|
||||
if (GRAPHICS_IIDX_WSUB) {
|
||||
this->disabled_message = "Close this overlay and use the second window. (try ALT+TAB)";
|
||||
} else if (games::iidx::IIDX_TDJ_MONITOR_WARNING) {
|
||||
this->disabled_message = "TDJ mode subscreen overlay is not compatible with -monitor option.";
|
||||
this->disabled_message = "TDJ mode subscreen overlay is not compatible with -dxmainadapter option; use -mainmonitor instead";
|
||||
}
|
||||
|
||||
float size = 0.5f;
|
||||
|
||||
@@ -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,11 +162,11 @@ 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);
|
||||
|
||||
|
||||
uint64_t parse_json_data_offset(
|
||||
const std::string &patch_name, const rapidjson::Value &value);
|
||||
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
#undef CINTERFACE
|
||||
|
||||
#include "avs/game.h"
|
||||
#include "popn_sub.h"
|
||||
#include "games/popn/popn.h"
|
||||
#include "hooks/graphics/graphics.h"
|
||||
#include "touch/touch.h"
|
||||
|
||||
namespace overlay::windows {
|
||||
|
||||
PopnSubScreen::PopnSubScreen(SpiceOverlay *overlay) : GenericSubScreen(overlay) {
|
||||
this->title = "Pop'n Subscreen";
|
||||
|
||||
if (!games::popn::is_pikapika_model()) {
|
||||
this->disabled_message = "Game did not launch as Pikapika Pop-kun (invalid <spec>)!";
|
||||
} else if (games::popn::SHOW_PIKA_MONITOR_WARNING) {
|
||||
this->disabled_message = "Subscreen overlay is not compatible with -dxmainadapter option, use -mainmonitor instead";
|
||||
} else if (GRAPHICS_WINDOWED && !GRAPHICS_PREVENT_SECONDARY_WINDOW) {
|
||||
this->disabled_message = "Subscren overlay was not enabled in spicecfg. Use the second window (ALT+TAB).";
|
||||
}
|
||||
|
||||
this->resize_callback = keep_16_by_10;
|
||||
float size = 0.5f;
|
||||
this->init_size = ImVec2(
|
||||
ImGui::GetIO().DisplaySize.x * size,
|
||||
(ImGui::GetIO().DisplaySize.x * size * 10 / 16) + ImGui::GetFrameHeight());
|
||||
|
||||
this->size_max = ImVec2(
|
||||
ImGui::GetIO().DisplaySize.x - ImGui::GetFrameHeight() * 2,
|
||||
ImGui::GetIO().DisplaySize.y - ImGui::GetFrameHeight() * 2);
|
||||
|
||||
// middle / bottom
|
||||
this->init_pos = ImVec2(
|
||||
ImGui::GetIO().DisplaySize.x / 2 - this->init_size.x / 2,
|
||||
ImGui::GetIO().DisplaySize.y - this->init_size.y - (ImGui::GetFrameHeight() / 2));
|
||||
}
|
||||
|
||||
void PopnSubScreen::touch_transform(const ImVec2 xy_in, LONG *x_out, LONG *y_out) {
|
||||
if (!this->get_active()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (GRAPHICS_WINDOWED) {
|
||||
// Touch needs to be registered on global coords
|
||||
*x_out = SPICETOUCH_TOUCH_X + xy_in.x * SPICETOUCH_TOUCH_WIDTH;
|
||||
*y_out = SPICETOUCH_TOUCH_Y + xy_in.y * SPICETOUCH_TOUCH_HEIGHT;
|
||||
} else {
|
||||
// Fullscreen mode, scale to game coords
|
||||
*x_out = xy_in.x * ImGui::GetIO().DisplaySize.x;
|
||||
*y_out = xy_in.y * ImGui::GetIO().DisplaySize.y;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
#pragma once
|
||||
|
||||
#include <windows.h>
|
||||
|
||||
#include "overlay/window.h"
|
||||
#include "overlay/windows/generic_sub.h"
|
||||
|
||||
namespace overlay::windows {
|
||||
|
||||
class PopnSubScreen : public GenericSubScreen {
|
||||
public:
|
||||
PopnSubScreen(SpiceOverlay *overlay);
|
||||
|
||||
protected:
|
||||
void touch_transform(const ImVec2 xy_in, LONG *x_out, LONG *y_out) override;
|
||||
|
||||
private:
|
||||
static void keep_16_by_10(ImGuiSizeCallbackData* data) {
|
||||
data->DesiredSize.y = (data->DesiredSize.x * 10.f / 16.f) + ImGui::GetFrameHeight();
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -213,7 +213,7 @@ namespace overlay::windows {
|
||||
ImGui::SameLine();
|
||||
ImGui::HelpMarker(
|
||||
"Change window decoration. Resizable Window may not cause your mouse cursor to change, "
|
||||
"but you can still drag to resize. Disabled for some games due to incompatibility.");
|
||||
"but you can still drag to resize. Disabled for some games due to incompatibility; change in spicecfg instead.");
|
||||
|
||||
if (ImGui::Checkbox("Always On Top", &cfg::SCREENRESIZE->window_always_on_top) ) {
|
||||
graphics_update_z_order(window, cfg::SCREENRESIZE->window_always_on_top);
|
||||
|
||||
@@ -13,7 +13,7 @@ namespace overlay::windows {
|
||||
if (!games::sdvx::is_valkyrie_model()) {
|
||||
this->disabled_message = "Valkyrie Model mode is not enabled!";
|
||||
} else if (games::sdvx::SHOW_VM_MONITOR_WARNING) {
|
||||
this->disabled_message = "VM mode subscreen overlay is not compatible with -monitor option";
|
||||
this->disabled_message = "VM mode subscreen overlay is not compatible with -dxmainadapter option, use -mainmonitor instead";
|
||||
} else if (GRAPHICS_WINDOWED) {
|
||||
if (GRAPHICS_PREVENT_SECONDARY_WINDOW) {
|
||||
this->disabled_message = "Subscreen has been disabled by the user (-sdvxnosub).";
|
||||
|
||||
@@ -31,7 +31,8 @@ namespace rawinput {
|
||||
SEXTET_OUTPUT,
|
||||
PIUIO_DEVICE,
|
||||
SMX_STAGE,
|
||||
SMX_DEDICAB
|
||||
SMX_DEDICAB,
|
||||
XINPUT_GAMEPAD,
|
||||
};
|
||||
|
||||
enum MouseKeys {
|
||||
|
||||
@@ -119,8 +119,10 @@ namespace rawinput {
|
||||
// success
|
||||
return TRUE;
|
||||
}
|
||||
case 7: { // windows 10 reports this for MIDI?
|
||||
case DBT_DEVNODES_CHANGED: {
|
||||
// TODO: this can be a little noisy as it gets called on every device
|
||||
this->ri_mgr->devices_scan_midi();
|
||||
this->ri_mgr->devices_scan_xinput();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
#include <setupapi.h>
|
||||
|
||||
#include "util/logging.h"
|
||||
#include "external/robin_hood.h"
|
||||
#include "util/time.h"
|
||||
#include "util/utils.h"
|
||||
|
||||
@@ -70,6 +71,8 @@ void rawinput::set_midi_algorithm(rawinput::MidiNoteAlgorithm new_algo) {
|
||||
|
||||
rawinput::RawInputManager::RawInputManager() {
|
||||
|
||||
XINPUT_MGR = std::make_unique<xinput::XInputManager>();
|
||||
|
||||
// create input window and load in devices
|
||||
this->input_hwnd_create();
|
||||
this->devices_reload();
|
||||
@@ -106,6 +109,8 @@ void rawinput::RawInputManager::stop() {
|
||||
// destruct all devices and input window
|
||||
this->devices_destruct();
|
||||
this->input_hwnd_destroy();
|
||||
|
||||
XINPUT_MGR.reset();
|
||||
}
|
||||
|
||||
void rawinput::RawInputManager::input_hwnd_create() {
|
||||
@@ -191,6 +196,8 @@ void rawinput::RawInputManager::devices_reload() {
|
||||
this->devices_scan_smxdedicab();
|
||||
}
|
||||
|
||||
this->devices_scan_xinput();
|
||||
|
||||
// check for LIT Board
|
||||
sextet_register("COM54", "LIT Board", false);
|
||||
|
||||
@@ -598,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
|
||||
@@ -931,6 +946,7 @@ void rawinput::RawInputManager::devices_scan_piuio() {
|
||||
|
||||
// add device to vector first so pointer is valid
|
||||
auto *new_piuio_device = new Device();
|
||||
new_piuio_device->id = this->devices.size() + 1;
|
||||
new_piuio_device->type = PIUIO_DEVICE;
|
||||
new_piuio_device->name = "piuio";
|
||||
new_piuio_device->desc = "PIUIO";
|
||||
@@ -961,6 +977,7 @@ void rawinput::RawInputManager::devices_scan_smxstage() {
|
||||
log_misc("rawinput", "scan SMX Stage devices...");
|
||||
|
||||
auto *new_smxstage_device = new Device();
|
||||
new_smxstage_device->id = this->devices.size() + 1;
|
||||
new_smxstage_device->type = SMX_STAGE;
|
||||
new_smxstage_device->name = "smxstage";
|
||||
new_smxstage_device->desc = "SMX Stage";
|
||||
@@ -987,6 +1004,7 @@ void rawinput::RawInputManager::devices_scan_smxdedicab() {
|
||||
log_misc("rawinput", "scan SMX Dedicated Cabinet devices...");
|
||||
|
||||
auto *new_smxdedicab_device = new Device();
|
||||
new_smxdedicab_device->id = this->devices.size() + 1;
|
||||
new_smxdedicab_device->type = SMX_DEDICAB;
|
||||
new_smxdedicab_device->name = "smxdedicab";
|
||||
new_smxdedicab_device->desc = "SMX Dedicated Cabinet";
|
||||
@@ -1009,6 +1027,76 @@ void rawinput::RawInputManager::devices_scan_smxdedicab() {
|
||||
}
|
||||
}
|
||||
|
||||
void rawinput::RawInputManager::devices_scan_xinput() {
|
||||
log_misc("rawinput", "scan XInput devices...");
|
||||
|
||||
const auto connected_players = XINPUT_MGR->get_available_players();
|
||||
|
||||
// first, destroy missing devices
|
||||
std::vector<std::string> devices_to_remove;
|
||||
for (auto &device : this->devices) {
|
||||
if (device.type != XINPUT_GAMEPAD) {
|
||||
continue;
|
||||
}
|
||||
const uint8_t player = static_cast<uint8_t>(reinterpret_cast<uintptr_t>(device.handle));
|
||||
if (std::find(connected_players.begin(), connected_players.end(), player) == connected_players.end()) {
|
||||
devices_to_remove.push_back(device.name);
|
||||
}
|
||||
}
|
||||
for (const auto &name : devices_to_remove) {
|
||||
this->devices_remove(name);
|
||||
}
|
||||
|
||||
auto create_device = [](const uint8_t player) -> Device {
|
||||
Device device = {};
|
||||
device.type = XINPUT_GAMEPAD;
|
||||
device.name = xinput::get_device_desc(player);
|
||||
device.desc = fmt::format("XInput Gamepad P{}", player + 1);
|
||||
device.handle = reinterpret_cast<HANDLE>(player);
|
||||
device.mutex = new std::mutex();
|
||||
device.mutex_out = new std::mutex();
|
||||
return device;
|
||||
};
|
||||
|
||||
// add new devices
|
||||
for (const auto player : connected_players) {
|
||||
bool duplicate_found = false;
|
||||
|
||||
// check for duplicates first
|
||||
for (auto &prev_device : this->devices) {
|
||||
if (prev_device.name != xinput::get_device_desc(player)) {
|
||||
continue;
|
||||
}
|
||||
if (prev_device.type == DESTROYED) {
|
||||
log_info("rawinput", "overwriting previously destroyed XInput device: {}", prev_device.name);
|
||||
const auto old_id = prev_device.id;
|
||||
prev_device = create_device(player);
|
||||
prev_device.id = old_id;
|
||||
|
||||
// notify change
|
||||
for (auto &cb : this->callback_change) {
|
||||
cb.f(cb.data, &prev_device);
|
||||
}
|
||||
}
|
||||
duplicate_found = true;
|
||||
break;
|
||||
}
|
||||
|
||||
if (!duplicate_found) {
|
||||
// add new device
|
||||
log_info("rawinput", "adding new XInput device: player {}", player + 1);
|
||||
auto new_xinput_device = create_device(player);
|
||||
new_xinput_device.id = this->devices.size() + 1;
|
||||
auto &device = this->devices.emplace_back(new_xinput_device);
|
||||
|
||||
// notify add
|
||||
for (auto &cb : this->callback_add) {
|
||||
cb.f(cb.data, &device);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void rawinput::RawInputManager::flush_start() {
|
||||
|
||||
// start flush thread
|
||||
@@ -1834,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) {
|
||||
@@ -1855,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;
|
||||
@@ -2567,6 +2656,10 @@ void rawinput::RawInputManager::device_write_output(Device *device, bool only_up
|
||||
device->smxdedicabInfo->Update();
|
||||
break;
|
||||
}
|
||||
case XINPUT_GAMEPAD: {
|
||||
// nothing - updates to these are instant
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
@@ -2734,6 +2827,10 @@ void rawinput::RawInputManager::devices_print() {
|
||||
log_misc("rawinput", "device type: SMX_DEDICAB");
|
||||
break;
|
||||
}
|
||||
case XINPUT_GAMEPAD: {
|
||||
log_misc("rawinput", "device type: XINPUT");
|
||||
break;
|
||||
}
|
||||
case UNKNOWN:
|
||||
default:
|
||||
log_warning("rawinput", "device type: UNKNOWN");
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
|
||||
#include "device.h"
|
||||
#include "hotplug.h"
|
||||
#include "rawinput/xinput.h"
|
||||
#include "util/scope_guard.h"
|
||||
|
||||
namespace rawinput {
|
||||
@@ -101,6 +102,7 @@ namespace rawinput {
|
||||
public:
|
||||
|
||||
HWND input_hwnd = nullptr;
|
||||
std::unique_ptr<xinput::XInputManager> XINPUT_MGR = nullptr;
|
||||
|
||||
RawInputManager();
|
||||
~RawInputManager();
|
||||
@@ -114,6 +116,7 @@ namespace rawinput {
|
||||
|
||||
void devices_scan_rawinput(const std::string &device_name = "");
|
||||
void devices_scan_midi();
|
||||
void devices_scan_xinput();
|
||||
void devices_remove(const std::string &name);
|
||||
|
||||
void devices_register();
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -192,8 +192,10 @@ namespace rawinput::touch {
|
||||
contact_count = std::max(contact_count, (size_t) hid->value_states_raw[index]);
|
||||
}
|
||||
|
||||
// https://learn.microsoft.com/en-us/windows-hardware/design/component-guidelines/touchscreen-packet-reporting-modes
|
||||
// hybrid mode devices will report a contact count of 0 for subsequent reports that are
|
||||
// part of the same initial frame
|
||||
// most laptops and touchscreens are like this - they have the contact count set to 5 but can actually report up to 10 fingers
|
||||
if (contact_count > 0) {
|
||||
if (contact_count > touch_report_count) {
|
||||
touch.remaining_contact_count = contact_count - touch_report_count;
|
||||
@@ -216,6 +218,12 @@ namespace rawinput::touch {
|
||||
touch_points.reserve(touch.elements_x.size());
|
||||
for (size_t i = 0; i < touch.elements_x.size(); i++) {
|
||||
|
||||
// if there are no more touch events to handle, exit out early
|
||||
if (touch_report_count == 0) {
|
||||
break;
|
||||
}
|
||||
touch_report_count--;
|
||||
|
||||
// build touch point
|
||||
HIDTouchPoint hid_tp{};
|
||||
auto pos_x = hid->value_states[touch.elements_x[i]];
|
||||
@@ -247,17 +255,6 @@ namespace rawinput::touch {
|
||||
hid_tp.y = 1.f - hid_tp.y;
|
||||
}
|
||||
|
||||
// check if this touch point should be considered valid
|
||||
//
|
||||
// If "Contact count" reports there are no touch reports remaining and the X and Y
|
||||
// coordinates of this touch point are zero, then this report element should be
|
||||
// skipped.
|
||||
if (touch_report_count == 0 && hid_tp.x == 0.f && hid_tp.y == 0.f) {
|
||||
continue;
|
||||
} else if (touch_report_count > 0) {
|
||||
touch_report_count--;
|
||||
}
|
||||
|
||||
// generate ID (hopefully unique)
|
||||
hid_tp.id = (DWORD) hid->value_states_raw[touch.elements_contact_identifier[i]];
|
||||
hid_tp.id += (DWORD) (0xFFFFFF + device->id * 512);
|
||||
|
||||
@@ -0,0 +1,465 @@
|
||||
#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...
|
||||
|
||||
#define XINPUT_GAMEPAD_TRIGGER_THRESHOLD (30 / 255.0f)
|
||||
#define XINPUT_GAMEPAD_LEFT_THUMB_DEADZONE 7849
|
||||
#define XINPUT_GAMEPAD_RIGHT_THUMB_DEADZONE 8689
|
||||
|
||||
#define XINPUT_GAMEPAD_DPAD_UP 0x0001
|
||||
#define XINPUT_GAMEPAD_DPAD_DOWN 0x0002
|
||||
#define XINPUT_GAMEPAD_DPAD_LEFT 0x0004
|
||||
#define XINPUT_GAMEPAD_DPAD_RIGHT 0x0008
|
||||
#define XINPUT_GAMEPAD_START 0x0010
|
||||
#define XINPUT_GAMEPAD_BACK 0x0020
|
||||
#define XINPUT_GAMEPAD_LEFT_THUMB 0x0040
|
||||
#define XINPUT_GAMEPAD_RIGHT_THUMB 0x0080
|
||||
#define XINPUT_GAMEPAD_LEFT_SHOULDER 0x0100
|
||||
#define XINPUT_GAMEPAD_RIGHT_SHOULDER 0x0200
|
||||
#define XINPUT_GAMEPAD_A 0x1000
|
||||
#define XINPUT_GAMEPAD_B 0x2000
|
||||
#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;
|
||||
} XINPUT_STATE;
|
||||
|
||||
DWORD
|
||||
WINAPI
|
||||
XInputGetState(
|
||||
DWORD dwUserIndex,
|
||||
XINPUT_STATE *pState
|
||||
);
|
||||
|
||||
typedef struct {
|
||||
uint16_t wLeftMotorSpeed;
|
||||
uint16_t wRightMotorSpeed;
|
||||
} XINPUT_VIBRATION;
|
||||
|
||||
DWORD
|
||||
WINAPI
|
||||
XInputSetState(
|
||||
DWORD dwUserIndex,
|
||||
XINPUT_VIBRATION* pVibration
|
||||
);
|
||||
|
||||
// end xinput definitions
|
||||
|
||||
std::string get_button_string(XInputButtonEnum button) {
|
||||
switch (button) {
|
||||
case XInputButtonEnum::DPAD_UP:
|
||||
return "Dpad Up";
|
||||
case XInputButtonEnum::DPAD_DOWN:
|
||||
return "Dpad Down";
|
||||
case XInputButtonEnum::DPAD_LEFT:
|
||||
return "Dpad Left";
|
||||
case XInputButtonEnum::DPAD_RIGHT:
|
||||
return "Dpad Right";
|
||||
case XInputButtonEnum::START:
|
||||
return "Start";
|
||||
case XInputButtonEnum::BACK:
|
||||
return "Back";
|
||||
case XInputButtonEnum::LEFT_STICK:
|
||||
return "Left Stick Click";
|
||||
case XInputButtonEnum::RIGHT_STICK:
|
||||
return "Right Stick Click";
|
||||
case XInputButtonEnum::LEFT_SHOULDER:
|
||||
return "Left Bumper";
|
||||
case XInputButtonEnum::RIGHT_SHOULDER:
|
||||
return "Right Bumper";
|
||||
case XInputButtonEnum::BUTTON_A:
|
||||
return "Button A";
|
||||
case XInputButtonEnum::BUTTON_B:
|
||||
return "Button B";
|
||||
case XInputButtonEnum::BUTTON_X:
|
||||
return "Button X";
|
||||
case XInputButtonEnum::BUTTON_Y:
|
||||
return "Button Y";
|
||||
case XInputButtonEnum::LEFT_TRIGGER:
|
||||
return "Left Trigger";
|
||||
case XInputButtonEnum::RIGHT_TRIGGER:
|
||||
return "Right Trigger";
|
||||
case XInputButtonEnum::LEFT_STICK_UP:
|
||||
return "Left Stick, Up";
|
||||
case XInputButtonEnum::LEFT_STICK_DOWN:
|
||||
return "Left Stick, Down";
|
||||
case XInputButtonEnum::LEFT_STICK_LEFT:
|
||||
return "Left Stick, Left";
|
||||
case XInputButtonEnum::LEFT_STICK_RIGHT:
|
||||
return "Left Stick, Right";
|
||||
case XInputButtonEnum::RIGHT_STICK_UP:
|
||||
return "Right Stick, Up";
|
||||
case XInputButtonEnum::RIGHT_STICK_DOWN:
|
||||
return "Right Stick, Down";
|
||||
case XInputButtonEnum::RIGHT_STICK_LEFT:
|
||||
return "Right Stick, Left";
|
||||
case XInputButtonEnum::RIGHT_STICK_RIGHT:
|
||||
return "Right Stick, Right";
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return fmt::format("Unknown Button ({})", static_cast<int>(button));
|
||||
}
|
||||
|
||||
std::string get_analog_string(XInputAnalogEnum analog) {
|
||||
switch (analog) {
|
||||
case XInputAnalogEnum::LEFT_TRIGGER:
|
||||
return "Left Trigger";
|
||||
case XInputAnalogEnum::RIGHT_TRIGGER:
|
||||
return "Right Trigger";
|
||||
case XInputAnalogEnum::LEFT_STICK_X:
|
||||
return "Left Stick X";
|
||||
case XInputAnalogEnum::LEFT_STICK_Y:
|
||||
return "Left Stick Y";
|
||||
case XInputAnalogEnum::RIGHT_STICK_X:
|
||||
return "Right Stick X";
|
||||
case XInputAnalogEnum::RIGHT_STICK_Y:
|
||||
return "Right Stick Y";
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return fmt::format("Unknown Analog ({})", static_cast<int>(analog));
|
||||
}
|
||||
|
||||
std::string get_output_string(XInputOutputEnum output) {
|
||||
switch (output) {
|
||||
case XInputOutputEnum::LEFT_RUMBLE:
|
||||
return "Left Rumble";
|
||||
case XInputOutputEnum::RIGHT_RUMBLE:
|
||||
return "Right Rumble";
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return fmt::format("Unknown Output ({})", static_cast<int>(output));
|
||||
}
|
||||
|
||||
std::string get_device_desc(uint8_t player) {
|
||||
return fmt::format(";XINPUT;{}", player);
|
||||
}
|
||||
|
||||
#if defined(SPICE_XP)
|
||||
|
||||
XInputManager::XInputManager() {}
|
||||
XInputManager::~XInputManager() {}
|
||||
void XInputManager::stop() {}
|
||||
std::vector<uint8_t> XInputManager::get_available_players() {
|
||||
return {};
|
||||
}
|
||||
float XInputManager::get_analog_state(uint8_t player, XInputAnalogEnum analog) {
|
||||
return 0.5f;
|
||||
}
|
||||
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) {
|
||||
return false;
|
||||
}
|
||||
void XInputManager::set_output_state(uint8_t player, XInputOutputEnum output, float value) {
|
||||
return;
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
static decltype(XInputGetState) *XInputGetState_addr = nullptr;
|
||||
static decltype(XInputSetState) *XInputSetState_addr = nullptr;
|
||||
|
||||
XInputManager::XInputManager() {
|
||||
log_info("xinput", "initialize...");
|
||||
this->xinput_lib = LoadLibraryA("xinput1_3.dll");
|
||||
if (!this->xinput_lib) {
|
||||
log_warning("xinput", "failed to load xinput1_3.dll");
|
||||
return;
|
||||
}
|
||||
XInputGetState_addr = reinterpret_cast<decltype(XInputGetState) *>(
|
||||
GetProcAddress(this->xinput_lib, "XInputGetState"));
|
||||
if (!XInputGetState_addr) {
|
||||
log_warning("xinput", "failed to get XInputGetState address");
|
||||
this->stop();
|
||||
return;
|
||||
}
|
||||
XInputSetState_addr = reinterpret_cast<decltype(XInputSetState) *>(
|
||||
GetProcAddress(this->xinput_lib, "XInputSetState"));
|
||||
if (!XInputSetState_addr) {
|
||||
log_warning("xinput", "failed to get XInputSetState address");
|
||||
this->stop();
|
||||
return;
|
||||
}
|
||||
initialized = true;
|
||||
log_info("xinput", "initialized");
|
||||
}
|
||||
|
||||
XInputManager::~XInputManager() {
|
||||
this->stop();
|
||||
}
|
||||
|
||||
void XInputManager::stop() {
|
||||
this->initialized = false;
|
||||
if (this->xinput_lib) {
|
||||
FreeLibrary(this->xinput_lib);
|
||||
this->xinput_lib = nullptr;
|
||||
}
|
||||
XInputGetState_addr = nullptr;
|
||||
XInputSetState_addr = nullptr;
|
||||
log_info("xinput", "destroyed");
|
||||
}
|
||||
|
||||
std::vector<uint8_t> XInputManager::get_available_players() {
|
||||
std::vector<uint8_t> players;
|
||||
if (!this->initialized) {
|
||||
return players;
|
||||
}
|
||||
|
||||
for (uint8_t i = 0; i < XUSER_MAX_COUNT; i++) {
|
||||
XINPUT_STATE x;
|
||||
if (XInputGetState_addr(i, &x) == ERROR_SUCCESS) {
|
||||
players.push_back(i);
|
||||
}
|
||||
}
|
||||
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;
|
||||
state.sThumbLY = 0.5f;
|
||||
state.sThumbRX = 0.5f;
|
||||
state.sThumbRY = 0.5f;
|
||||
if (!this->initialized) {
|
||||
return;
|
||||
}
|
||||
|
||||
XINPUT_STATE x = {};
|
||||
if (XInputGetState_addr(player, &x) != ERROR_SUCCESS) {
|
||||
return;
|
||||
}
|
||||
|
||||
state.wButtons = x.Gamepad.wButtons;
|
||||
state.bLeftTrigger = x.Gamepad.bLeftTrigger / 255.0f;
|
||||
state.bRightTrigger = x.Gamepad.bRightTrigger / 255.0f;
|
||||
|
||||
// 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);
|
||||
|
||||
// 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_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);
|
||||
}
|
||||
|
||||
switch (button) {
|
||||
case XInputButtonEnum::DPAD_UP:
|
||||
return (state->wButtons & XINPUT_GAMEPAD_DPAD_UP) != 0;
|
||||
case XInputButtonEnum::DPAD_DOWN:
|
||||
return (state->wButtons & XINPUT_GAMEPAD_DPAD_DOWN) != 0;
|
||||
case XInputButtonEnum::DPAD_LEFT:
|
||||
return (state->wButtons & XINPUT_GAMEPAD_DPAD_LEFT) != 0;
|
||||
case XInputButtonEnum::DPAD_RIGHT:
|
||||
return (state->wButtons & XINPUT_GAMEPAD_DPAD_RIGHT) != 0;
|
||||
case XInputButtonEnum::START:
|
||||
return (state->wButtons & XINPUT_GAMEPAD_START) != 0;
|
||||
case XInputButtonEnum::BACK:
|
||||
return (state->wButtons & XINPUT_GAMEPAD_BACK) != 0;
|
||||
case XInputButtonEnum::LEFT_STICK:
|
||||
return (state->wButtons & XINPUT_GAMEPAD_LEFT_THUMB) != 0;
|
||||
case XInputButtonEnum::RIGHT_STICK:
|
||||
return (state->wButtons & XINPUT_GAMEPAD_RIGHT_THUMB) != 0;
|
||||
case XInputButtonEnum::LEFT_SHOULDER:
|
||||
return (state->wButtons & XINPUT_GAMEPAD_LEFT_SHOULDER) != 0;
|
||||
case XInputButtonEnum::RIGHT_SHOULDER:
|
||||
return (state->wButtons & XINPUT_GAMEPAD_RIGHT_SHOULDER) != 0;
|
||||
case XInputButtonEnum::BUTTON_A:
|
||||
return (state->wButtons & XINPUT_GAMEPAD_A) != 0;
|
||||
case XInputButtonEnum::BUTTON_B:
|
||||
return (state->wButtons & XINPUT_GAMEPAD_B) != 0;
|
||||
case XInputButtonEnum::BUTTON_X:
|
||||
return (state->wButtons & XINPUT_GAMEPAD_X) != 0;
|
||||
case XInputButtonEnum::BUTTON_Y:
|
||||
return (state->wButtons & XINPUT_GAMEPAD_Y) != 0;
|
||||
case XInputButtonEnum::LEFT_TRIGGER:
|
||||
return state->bLeftTrigger >= XINPUT_GAMEPAD_TRIGGER_THRESHOLD;
|
||||
case XInputButtonEnum::RIGHT_TRIGGER:
|
||||
return state->bRightTrigger >= XINPUT_GAMEPAD_TRIGGER_THRESHOLD;
|
||||
case XInputButtonEnum::LEFT_STICK_UP:
|
||||
return state->sThumbLY < (0.5f - GAMEPAD_THUMB_DIGITAL_THRESHOLD);
|
||||
case XInputButtonEnum::LEFT_STICK_DOWN:
|
||||
return state->sThumbLY > (0.5f + GAMEPAD_THUMB_DIGITAL_THRESHOLD);
|
||||
case XInputButtonEnum::LEFT_STICK_LEFT:
|
||||
return state->sThumbLX < (0.5f - GAMEPAD_THUMB_DIGITAL_THRESHOLD);
|
||||
case XInputButtonEnum::LEFT_STICK_RIGHT:
|
||||
return state->sThumbLX > (0.5f + GAMEPAD_THUMB_DIGITAL_THRESHOLD);
|
||||
case XInputButtonEnum::RIGHT_STICK_UP:
|
||||
return state->sThumbRY < (0.5f - GAMEPAD_THUMB_DIGITAL_THRESHOLD);
|
||||
case XInputButtonEnum::RIGHT_STICK_DOWN:
|
||||
return state->sThumbRY > (0.5f + GAMEPAD_THUMB_DIGITAL_THRESHOLD);
|
||||
case XInputButtonEnum::RIGHT_STICK_LEFT:
|
||||
return state->sThumbRX < (0.5f - GAMEPAD_THUMB_DIGITAL_THRESHOLD);
|
||||
case XInputButtonEnum::RIGHT_STICK_RIGHT:
|
||||
return state->sThumbRX > (0.5f + GAMEPAD_THUMB_DIGITAL_THRESHOLD);
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
float XInputManager::get_analog_state(uint8_t player, XInputAnalogEnum analog) {
|
||||
XINPUT_GAMEPAD_STATE_NORMALIZED state;
|
||||
|
||||
get_state(player, state);
|
||||
switch (analog) {
|
||||
case XInputAnalogEnum::LEFT_TRIGGER:
|
||||
return state.bLeftTrigger;
|
||||
case XInputAnalogEnum::RIGHT_TRIGGER:
|
||||
return state.bRightTrigger;
|
||||
case XInputAnalogEnum::LEFT_STICK_X:
|
||||
return state.sThumbLX;
|
||||
case XInputAnalogEnum::LEFT_STICK_Y:
|
||||
return state.sThumbLY;
|
||||
case XInputAnalogEnum::RIGHT_STICK_X:
|
||||
return state.sThumbRX;
|
||||
case XInputAnalogEnum::RIGHT_STICK_Y:
|
||||
return state.sThumbRY;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return 0.5f;
|
||||
}
|
||||
|
||||
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, button_priority[b], &state)) {
|
||||
button.player = player;
|
||||
button.button = button_priority[b];
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void XInputManager::set_output_state(uint8_t player, XInputOutputEnum output, float value) {
|
||||
if (!this->initialized) {
|
||||
return;
|
||||
}
|
||||
if (player >= XUSER_MAX_COUNT) {
|
||||
return;
|
||||
}
|
||||
XINPUT_VIBRATION vibration = {};
|
||||
if (output == XInputOutputEnum::LEFT_RUMBLE) {
|
||||
vibration.wLeftMotorSpeed = static_cast<uint16_t>(value * 65535);
|
||||
} else if (output == XInputOutputEnum::RIGHT_RUMBLE) {
|
||||
vibration.wRightMotorSpeed = static_cast<uint16_t>(value * 65535);
|
||||
}
|
||||
XInputSetState_addr(player, &vibration);
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
}
|
||||
@@ -0,0 +1,112 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include <array>
|
||||
#include <vector>
|
||||
#include <string>
|
||||
|
||||
#include <windows.h>
|
||||
|
||||
namespace xinput {
|
||||
|
||||
#define XUSER_MAX_COUNT 4
|
||||
|
||||
struct XINPUT_GAMEPAD_STATE {
|
||||
uint16_t wButtons;
|
||||
uint8_t bLeftTrigger;
|
||||
uint8_t bRightTrigger;
|
||||
int16_t sThumbLX;
|
||||
int16_t sThumbLY;
|
||||
int16_t sThumbRX;
|
||||
int16_t sThumbRY;
|
||||
};
|
||||
|
||||
struct XINPUT_GAMEPAD_STATE_NORMALIZED {
|
||||
uint16_t wButtons;
|
||||
float bLeftTrigger;
|
||||
float bRightTrigger;
|
||||
float sThumbLX;
|
||||
float sThumbLY;
|
||||
float sThumbRX;
|
||||
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,
|
||||
DPAD_DOWN,
|
||||
DPAD_LEFT,
|
||||
DPAD_RIGHT,
|
||||
START,
|
||||
BACK,
|
||||
LEFT_STICK,
|
||||
RIGHT_STICK,
|
||||
LEFT_SHOULDER,
|
||||
RIGHT_SHOULDER,
|
||||
BUTTON_A,
|
||||
BUTTON_B,
|
||||
BUTTON_X,
|
||||
BUTTON_Y,
|
||||
|
||||
// analog values that can be used as buttons
|
||||
LEFT_TRIGGER,
|
||||
RIGHT_TRIGGER,
|
||||
LEFT_STICK_UP,
|
||||
LEFT_STICK_DOWN,
|
||||
LEFT_STICK_LEFT,
|
||||
LEFT_STICK_RIGHT,
|
||||
RIGHT_STICK_UP,
|
||||
RIGHT_STICK_DOWN,
|
||||
RIGHT_STICK_LEFT,
|
||||
RIGHT_STICK_RIGHT,
|
||||
|
||||
COUNT
|
||||
};
|
||||
|
||||
enum class XInputAnalogEnum {
|
||||
// analog values
|
||||
LEFT_TRIGGER,
|
||||
RIGHT_TRIGGER,
|
||||
LEFT_STICK_X,
|
||||
LEFT_STICK_Y,
|
||||
RIGHT_STICK_X,
|
||||
RIGHT_STICK_Y,
|
||||
|
||||
COUNT
|
||||
};
|
||||
|
||||
enum class XInputOutputEnum : uint16_t {
|
||||
LEFT_RUMBLE,
|
||||
RIGHT_RUMBLE,
|
||||
|
||||
COUNT
|
||||
};
|
||||
|
||||
struct XINPUT_NEW_BUTTON {
|
||||
uint8_t player;
|
||||
XInputButtonEnum button;
|
||||
};
|
||||
|
||||
std::string get_button_string(XInputButtonEnum button);
|
||||
std::string get_analog_string(XInputAnalogEnum analog);
|
||||
std::string get_output_string(XInputOutputEnum output);
|
||||
std::string get_device_desc(uint8_t player);
|
||||
|
||||
class XInputManager {
|
||||
private:
|
||||
bool initialized = false;
|
||||
HMODULE xinput_lib = nullptr;
|
||||
void get_state(uint8_t player, XINPUT_GAMEPAD_STATE_NORMALIZED &state);
|
||||
public:
|
||||
XInputManager();
|
||||
~XInputManager();
|
||||
void stop();
|
||||
std::vector<uint8_t> get_available_players();
|
||||
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);
|
||||
};
|
||||
}
|
||||
@@ -243,6 +243,7 @@ static LRESULT CALLBACK SpiceTouchWndProc(HWND hWnd, UINT msg, WPARAM wParam, LP
|
||||
const auto is_windowed_sub =
|
||||
(GRAPHICS_IIDX_WSUB && hWnd == TDJ_SUBSCREEN_WINDOW) ||
|
||||
(hWnd == SDVX_SUBSCREEN_WINDOW) ||
|
||||
(hWnd == POPN_SUBSCREEN_WINDOW) ||
|
||||
(hWnd == GFDM_SUBSCREEN_WINDOW);
|
||||
|
||||
if (msg == WM_CLOSE) {
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user