mirror of
https://github.com/spice2x/spice2x.github.io.git
synced 2026-08-02 06:40:42 -07:00
Compare commits
89 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| dc82c980b1 | |||
| 408ed17521 | |||
| f4050e9adb | |||
| badae01357 | |||
| 38d9939eb7 | |||
| f5a3caf536 | |||
| 027445afb1 | |||
| 63a3900b79 | |||
| f542a78347 | |||
| 4d32dde83e | |||
| 927170ce73 | |||
| 875a0f0765 | |||
| 3ad88ef15c | |||
| 7a6cadb176 | |||
| 4feca23240 | |||
| 2e52bebd3d | |||
| aa218d5be7 | |||
| 94f009991a | |||
| c3b9ce3fdc | |||
| 1517cf9a7c | |||
| f0f46296f2 | |||
| 1d1981fcde | |||
| 2f538fe2e4 | |||
| 8e01b2e5c8 | |||
| cd14ca800e | |||
| cb59fe14e9 | |||
| 9fd9290cd3 | |||
| 1dd99aac74 | |||
| 9e376f373e | |||
| d4e51b77d1 | |||
| 61181836b0 | |||
| 4e26c8afa5 | |||
| 7684028f99 | |||
| 1d6be8fe4b | |||
| c70cf40af3 | |||
| 5715d4e605 | |||
| 821a705a7d | |||
| 86f2a1fd39 | |||
| 86f218329a | |||
| 66094de38a | |||
| c00cff2afd | |||
| b2cc8d404c | |||
| a0a04ab62f | |||
| a3a59e689e | |||
| a6f29d6a5a | |||
| 9be618d791 | |||
| 6bf7ae9074 | |||
| e3d63c65c1 | |||
| 5d2a9fa1cf | |||
| 0078b72b03 | |||
| b1ee37a066 | |||
| ebf6382e7f | |||
| 8e23ef224a | |||
| 6706ffc321 | |||
| e755b06e35 | |||
| 9ef22712c0 | |||
| 7b903d35e2 | |||
| 7f281a50c5 | |||
| 1354d63b85 | |||
| e745cdcac8 | |||
| bf7666939a | |||
| 7e89037db3 | |||
| 6ffaa77f58 | |||
| 7a1f46d1be | |||
| cf59ae5f89 | |||
| 9c06dd36a0 | |||
| d5d082ec37 | |||
| 17723d290a | |||
| 8de7e5a9e4 | |||
| b8e99e75e5 | |||
| 46dc22dd63 | |||
| 69d78e0a87 | |||
| ab3d41432a | |||
| 73e10cac0c | |||
| c6242233a7 | |||
| 6b8335780f | |||
| 21f469b260 | |||
| 07a0df5fdc | |||
| d4f1768bbd | |||
| 96e7e4949f | |||
| 24d3062c66 | |||
| 6cd212aac2 | |||
| 064031bc12 | |||
| 4c8d4ebab6 | |||
| 462a07680b | |||
| 081def30db | |||
| 6776b08bce | |||
| 6cb507f4fa | |||
| de05af052f |
+12
-3
@@ -1,8 +1,7 @@
|
||||
## Contributing
|
||||
Baseline rules for patch submissions are as follows. Any patches violating the rules below will not be accepted.
|
||||
|
||||
### New as of March 2025
|
||||
Pull requests in GitHub are open. You don't need to bother with patch file submissions anymore. To contribute, fork the repo (just the main branch), make changes in your fork, and contribute to upstream by opening a pull request to the main repo.
|
||||
To contribute, fork the repo (just the main branch), make changes in your fork, and contribute to upstream by opening a pull request to the main repo.
|
||||
|
||||
### Adding support for games
|
||||
|
||||
@@ -12,6 +11,14 @@ Pull requests in GitHub are open. You don't need to bother with patch file submi
|
||||
* For a new game that was never supported (not a new version of a game, but rather a new series): please wait 1 year after official AC release in Japan.
|
||||
* For new version of an already supported game: proceed with caution and use generally-accepted community guidelines.
|
||||
|
||||
### Forbidden "features" that will never be accepted
|
||||
|
||||
* Performing decryption of encrypted files
|
||||
* Including (bundling), distributing, or linking to game data, or making it easier to find/download game data
|
||||
* Any features that [phone home](https://en.wikipedia.org/wiki/Phoning_home)
|
||||
* This includes automatic updaters.
|
||||
* Exception: we have allowed the "patch import from URL" feature, but this has only been done under very careful considerations and plenty of warnings to the user.
|
||||
|
||||
### Avoiding regressions
|
||||
|
||||
The biggest risk in making code changes to spice is the risk of regressions. There is a **lot** of shared code used by many different games, reaching back game versions that are more than a decade old. It is practically impossible to test all supported games and versions.
|
||||
@@ -22,7 +29,7 @@ Therefore, when making code changes, please be extremely careful about containin
|
||||
|
||||
#### Config file compatibility
|
||||
|
||||
Do not change the names of options, buttons binds, analogs, etc - since they are used as unique identifiers in config files. If they are changed, you will introduce an incompatibility with previous versions of config file.
|
||||
Do not change the names of buttons binds, analogs, strings for command line parameters, etc - since they are used as unique identifiers in config files. If they are changed, you will introduce an incompatibility with previous versions of config file.
|
||||
|
||||
### Code quality requirements
|
||||
|
||||
@@ -49,6 +56,8 @@ Watch out for legacy OS compatibility. Currently, the minimum support floor is W
|
||||
|
||||
### Code style requirements
|
||||
* Indents are four spaces.
|
||||
* Try to keep under 100 characters per line. This includes comments and string literals.
|
||||
* Avoid making unnecessary formatting or whitespace changes in a PR; e.g., watch out for text editor automatically stripping trailing whitespace. It pollutes the diff and slows down code review.
|
||||
* Always use \{ curly braces \} when appropriate; do not omit them even when it's optional; such as `for` `if` `else`, etc.
|
||||
|
||||
OK:
|
||||
|
||||
@@ -118,6 +118,9 @@ elseif(CMAKE_CXX_COMPILER_ID STREQUAL "Clang")
|
||||
set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -static")
|
||||
else()
|
||||
|
||||
# warnings as errors
|
||||
set(CMAKE_COMPILE_WARNING_AS_ERROR ON)
|
||||
|
||||
# warnings
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wextra") # enable stuff
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-pointer-arith") # but we love pointer arithmetic :)
|
||||
@@ -167,8 +170,8 @@ else()
|
||||
set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -DNDEBUG")
|
||||
|
||||
# file prefix map for relative working directory
|
||||
set(CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE} -ffile-prefix-map=${CMAKE_SOURCE_DIR}=.")
|
||||
set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -ffile-prefix-map=${CMAKE_SOURCE_DIR}=.")
|
||||
set(CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE} -ffile-prefix-map=\"${CMAKE_SOURCE_DIR}=.\"")
|
||||
set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -ffile-prefix-map=\"${CMAKE_SOURCE_DIR}=.\"")
|
||||
endif()
|
||||
|
||||
# release with debug info flags
|
||||
@@ -398,6 +401,10 @@ set(SOURCE_FILES ${SOURCE_FILES}
|
||||
games/nost/poke.cpp
|
||||
games/gitadora/gitadora.cpp
|
||||
games/gitadora/io.cpp
|
||||
games/gitadora/handle.cpp
|
||||
games/gitadora/j32d.cpp
|
||||
games/gitadora/j33i.cpp
|
||||
games/gitadora/bi2x_hook.cpp
|
||||
games/mga/mga.cpp
|
||||
games/mga/io.cpp
|
||||
games/mga/gunio.cpp
|
||||
@@ -434,6 +441,7 @@ set(SOURCE_FILES ${SOURCE_FILES}
|
||||
games/scotto/io.cpp
|
||||
games/drs/drs.cpp
|
||||
games/drs/io.cpp
|
||||
games/drs/rgb_cam.cpp
|
||||
games/we/we.cpp
|
||||
games/we/io.cpp
|
||||
games/we/touchpanel.cpp
|
||||
@@ -470,10 +478,12 @@ set(SOURCE_FILES ${SOURCE_FILES}
|
||||
hooks/audio/acm.cpp
|
||||
hooks/audio/audio.cpp
|
||||
hooks/audio/buffer.cpp
|
||||
hooks/audio/mme.cpp
|
||||
hooks/audio/util.cpp
|
||||
hooks/audio/backends/dsound/dsound_backend.cpp
|
||||
hooks/audio/backends/mmdevice/audio_endpoint_volume.cpp
|
||||
hooks/audio/backends/mmdevice/device.cpp
|
||||
hooks/audio/backends/mmdevice/device_collection.cpp
|
||||
hooks/audio/backends/mmdevice/device_enumerator.cpp
|
||||
hooks/audio/backends/wasapi/audio_client.cpp
|
||||
hooks/audio/backends/wasapi/audio_render_client.cpp
|
||||
@@ -530,6 +540,7 @@ set(SOURCE_FILES ${SOURCE_FILES}
|
||||
misc/sciunit.cpp
|
||||
misc/sde.cpp
|
||||
misc/wintouchemu.cpp
|
||||
misc/ami2000.cpp
|
||||
|
||||
# nvapi
|
||||
nvapi/nvapi.cpp
|
||||
@@ -544,6 +555,7 @@ set(SOURCE_FILES ${SOURCE_FILES}
|
||||
overlay/windows/camera_control.cpp
|
||||
overlay/windows/card_manager.cpp
|
||||
overlay/windows/drs_dancefloor.cpp
|
||||
overlay/windows/gfdm_sub.cpp
|
||||
overlay/windows/screen_resize.cpp
|
||||
overlay/windows/sdvx_sub.cpp
|
||||
overlay/windows/config.cpp
|
||||
@@ -614,6 +626,7 @@ set(SOURCE_FILES ${SOURCE_FILES}
|
||||
util/execexe.cpp
|
||||
util/dependencies.cpp
|
||||
util/deferlog.cpp
|
||||
util/socd_cleaner.cpp
|
||||
)
|
||||
|
||||
source_group(TREE ${CMAKE_CURRENT_SOURCE_DIR} PREFIX "Source Files" FILES ${SOURCE_FILES})
|
||||
|
||||
@@ -9,11 +9,22 @@
|
||||
#include "games/drs/drs.h"
|
||||
#include "misc/eamuse.h"
|
||||
#include "util/logging.h"
|
||||
#include "util/socd_cleaner.h"
|
||||
#include "util/time.h"
|
||||
#include "util/utils.h"
|
||||
#include "util/tapeled.h"
|
||||
|
||||
using namespace GameAPI;
|
||||
|
||||
#define DEBUG_VERBOSE 0
|
||||
|
||||
#if DEBUG_VERBOSE
|
||||
#define log_debug(module, format_str, ...) logger::push( \
|
||||
LOG_FORMAT("M", module, format_str, ## __VA_ARGS__), logger::Style::GREY)
|
||||
#else
|
||||
#define log_debug(module, format_str, ...)
|
||||
#endif
|
||||
|
||||
// state
|
||||
static uint8_t STATUS_BUFFER[272] {};
|
||||
static bool STATUS_BUFFER_FREEZE = false;
|
||||
@@ -94,18 +105,25 @@ static bool __cdecl ac_io_bi2a_update_control_status_buffer() {
|
||||
}
|
||||
|
||||
// volume left
|
||||
if (Buttons::getState(RI_MGR, buttons.at(games::sdvx::Buttons::VOL_L_Left))) {
|
||||
const auto now = get_performance_milliseconds();
|
||||
const auto vol_l_state = socd::socd_clean(0,
|
||||
Buttons::getState(RI_MGR, buttons.at(games::sdvx::Buttons::VOL_L_Left)),
|
||||
Buttons::getState(RI_MGR, buttons.at(games::sdvx::Buttons::VOL_L_Right)),
|
||||
now);
|
||||
if (vol_l_state == socd::SocdCCW) {
|
||||
BI2A_VOLL = (BI2A_VOLL - games::sdvx::DIGITAL_KNOB_SENS) & 1023;
|
||||
}
|
||||
if (Buttons::getState(RI_MGR, buttons.at(games::sdvx::Buttons::VOL_L_Right))) {
|
||||
} else if (vol_l_state == socd::SocdCW) {
|
||||
BI2A_VOLL = (BI2A_VOLL + games::sdvx::DIGITAL_KNOB_SENS) & 1023;
|
||||
}
|
||||
|
||||
// volume right
|
||||
if (Buttons::getState(RI_MGR, buttons.at(games::sdvx::Buttons::VOL_R_Left))) {
|
||||
const auto vol_r_state = socd::socd_clean(1,
|
||||
Buttons::getState(RI_MGR, buttons.at(games::sdvx::Buttons::VOL_R_Left)),
|
||||
Buttons::getState(RI_MGR, buttons.at(games::sdvx::Buttons::VOL_R_Right)),
|
||||
now);
|
||||
if (vol_r_state == socd::SocdCCW) {
|
||||
BI2A_VOLR = (BI2A_VOLR - games::sdvx::DIGITAL_KNOB_SENS) & 1023;
|
||||
}
|
||||
if (Buttons::getState(RI_MGR, buttons.at(games::sdvx::Buttons::VOL_R_Right))) {
|
||||
} else if (vol_r_state == socd::SocdCW) {
|
||||
BI2A_VOLR = (BI2A_VOLR + games::sdvx::DIGITAL_KNOB_SENS) & 1023;
|
||||
}
|
||||
|
||||
@@ -127,6 +145,12 @@ static bool __cdecl ac_io_bi2a_update_control_status_buffer() {
|
||||
// save volumes in buffer
|
||||
*((uint16_t*) &STATUS_BUFFER[17]) = (uint16_t) ((vol_left) << 2);
|
||||
*((uint16_t*) &STATUS_BUFFER[19]) = (uint16_t) ((vol_right) << 2);
|
||||
|
||||
log_debug(
|
||||
"bi2a",
|
||||
"knobs = {} {}",
|
||||
*((uint16_t*) &STATUS_BUFFER[17]),
|
||||
*((uint16_t*) &STATUS_BUFFER[19]));
|
||||
}
|
||||
|
||||
// DanceDanceRevolution
|
||||
|
||||
@@ -52,7 +52,7 @@ static bool __cdecl ac_io_j32d_update_control_status_buffer(size_t a1) {
|
||||
float pad3_vel = Buttons::getVelocity(RI_MGR, buttons.at(games::ftt::Buttons::Pad3));
|
||||
float pad4_vel = Buttons::getVelocity(RI_MGR, buttons.at(games::ftt::Buttons::Pad4));
|
||||
// FIXME(felix): this logic seems wrong for analog handling but correct for digital inputs
|
||||
if (pad1_vel > 0.f) {
|
||||
if (Buttons::getState(RI_MGR, buttons.at(games::ftt::Buttons::Pad1))) {
|
||||
STATUS_BUFFER[6] = (int) (51.f * pad1_vel + 0.5f);
|
||||
}
|
||||
if (Buttons::getState(RI_MGR, buttons.at(games::ftt::Buttons::Pad2))) {
|
||||
@@ -67,10 +67,10 @@ static bool __cdecl ac_io_j32d_update_control_status_buffer(size_t a1) {
|
||||
|
||||
// process analogs
|
||||
auto &analogs = games::ftt::get_analogs();
|
||||
auto pad1_analog = analogs.at(games::ftt::Analogs::Pad1);
|
||||
auto pad2_analog = analogs.at(games::ftt::Analogs::Pad2);
|
||||
auto pad3_analog = analogs.at(games::ftt::Analogs::Pad3);
|
||||
auto pad4_analog = analogs.at(games::ftt::Analogs::Pad4);
|
||||
auto &pad1_analog = analogs.at(games::ftt::Analogs::Pad1);
|
||||
auto &pad2_analog = analogs.at(games::ftt::Analogs::Pad2);
|
||||
auto &pad3_analog = analogs.at(games::ftt::Analogs::Pad3);
|
||||
auto &pad4_analog = analogs.at(games::ftt::Analogs::Pad4);
|
||||
if (pad1_analog.isSet()) {
|
||||
auto val = (uint32_t) (51.f * Analogs::getState(RI_MGR, pad1_analog) + 0.5f);
|
||||
STATUS_BUFFER[6] = MAX(STATUS_BUFFER[6], val);
|
||||
|
||||
@@ -8,10 +8,21 @@
|
||||
#include "games/sdvx/io.h"
|
||||
#include "misc/eamuse.h"
|
||||
#include "rawinput/rawinput.h"
|
||||
#include "util/socd_cleaner.h"
|
||||
#include "util/time.h"
|
||||
#include "util/utils.h"
|
||||
|
||||
using namespace GameAPI;
|
||||
|
||||
#define DEBUG_VERBOSE 0
|
||||
|
||||
#if DEBUG_VERBOSE
|
||||
#define log_debug(module, format_str, ...) logger::push( \
|
||||
LOG_FORMAT("M", module, format_str, ## __VA_ARGS__), logger::Style::GREY)
|
||||
#else
|
||||
#define log_debug(module, format_str, ...)
|
||||
#endif
|
||||
|
||||
// globals
|
||||
uint8_t KFCA_VOL_SOUND = 96;
|
||||
uint8_t KFCA_VOL_HEADPHONE = 96;
|
||||
@@ -342,18 +353,25 @@ static char __cdecl ac_io_kfca_update_control_status_buffer() {
|
||||
}
|
||||
|
||||
// volume left
|
||||
if (Buttons::getState(RI_MGR, buttons.at(games::sdvx::Buttons::VOL_L_Left))) {
|
||||
const auto now = get_performance_milliseconds();
|
||||
const auto vol_l_state = socd::socd_clean(0,
|
||||
Buttons::getState(RI_MGR, buttons.at(games::sdvx::Buttons::VOL_L_Left)),
|
||||
Buttons::getState(RI_MGR, buttons.at(games::sdvx::Buttons::VOL_L_Right)),
|
||||
now);
|
||||
if (vol_l_state == socd::SocdCCW) {
|
||||
KFCA_VOLL = (KFCA_VOLL - games::sdvx::DIGITAL_KNOB_SENS) & 1023;
|
||||
}
|
||||
if (Buttons::getState(RI_MGR, buttons.at(games::sdvx::Buttons::VOL_L_Right))) {
|
||||
} else if (vol_l_state == socd::SocdCW) {
|
||||
KFCA_VOLL = (KFCA_VOLL + games::sdvx::DIGITAL_KNOB_SENS) & 1023;
|
||||
}
|
||||
|
||||
// volume right
|
||||
if (Buttons::getState(RI_MGR, buttons.at(games::sdvx::Buttons::VOL_R_Left))) {
|
||||
const auto vol_r_state = socd::socd_clean(1,
|
||||
Buttons::getState(RI_MGR, buttons.at(games::sdvx::Buttons::VOL_R_Left)),
|
||||
Buttons::getState(RI_MGR, buttons.at(games::sdvx::Buttons::VOL_R_Right)),
|
||||
now);
|
||||
if (vol_r_state == socd::SocdCCW) {
|
||||
KFCA_VOLR = (KFCA_VOLR - games::sdvx::DIGITAL_KNOB_SENS) & 1023;
|
||||
}
|
||||
if (Buttons::getState(RI_MGR, buttons.at(games::sdvx::Buttons::VOL_R_Right))) {
|
||||
} else if (vol_r_state == socd::SocdCW) {
|
||||
KFCA_VOLR = (KFCA_VOLR + games::sdvx::DIGITAL_KNOB_SENS) & 1023;
|
||||
}
|
||||
|
||||
@@ -371,6 +389,8 @@ static char __cdecl ac_io_kfca_update_control_status_buffer() {
|
||||
// proper loops
|
||||
vol_left %= 1024;
|
||||
vol_right %= 1024;
|
||||
|
||||
log_debug("kfca", "knobs = {} {}", vol_left, vol_right);
|
||||
|
||||
// save volumes in buffer
|
||||
STATUS_BUFFER[input_offset + 16 + 0] |= (unsigned char) ((vol_left << 6) & 0xFF);
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
#include "acio/icca/icca.h"
|
||||
#include "avs/game.h"
|
||||
#include "games/sdvx/sdvx.h"
|
||||
#include "misc/eamuse.h"
|
||||
#include "util/logging.h"
|
||||
#include "util/utils.h"
|
||||
@@ -101,13 +102,10 @@ bool ICCADevice::parse_msg(MessageData *msg_in,
|
||||
|
||||
// send version data
|
||||
auto msg = this->create_msg(msg_in, MSG_VERSION_SIZE);
|
||||
if (
|
||||
avs::game::is_model({"LDJ", "TBS", "UJK", "XIF"}) ||
|
||||
// SDVX Valkyrie cabinet mode
|
||||
(avs::game::is_model("KFC") && (avs::game::SPEC[0] == 'G' || avs::game::SPEC[0] == 'H'))
|
||||
) {
|
||||
if (avs::game::is_model({"LDJ", "TBS", "UJK", "XIF"}) ||
|
||||
games::sdvx::is_valkyrie_model()) {
|
||||
this->set_version(msg, 0x3, 0, 1, 7, 0, "ICCA");
|
||||
} else if (avs::game::is_model({"VFG"})) {
|
||||
} else if (avs::game::is_model("VFG")) {
|
||||
this->set_version(msg, 0x3, 0, 1, 7, 0, "ICCB");
|
||||
} else {
|
||||
this->set_version(msg, 0x3, 0, 1, 6, 0, "ICCA");
|
||||
@@ -520,11 +518,7 @@ void ICCADevice::update_status(int unit) {
|
||||
buffer[1] = felica ? 0x01 : 0x00;
|
||||
buffer[10] = felica ? 0x01 : 0x00;
|
||||
|
||||
} else if (
|
||||
avs::game::is_model({"LDJ", "TBS", "XIF"}) ||
|
||||
// SDVX Valkyrie cabinet mode
|
||||
(avs::game::is_model("KFC") && (avs::game::SPEC[0] == 'G' || avs::game::SPEC[0] == 'H'))) {
|
||||
|
||||
} else if (avs::game::is_model({"LDJ", "TBS", "XIF"}) || games::sdvx::is_valkyrie_model()) {
|
||||
// set status to 0 otherwise reader power on fails
|
||||
buffer[0] = 0x00;
|
||||
} else {
|
||||
|
||||
@@ -1108,6 +1108,8 @@ namespace avs {
|
||||
|
||||
// SOUND VOLTEX
|
||||
{"soundvoltex.dll", 0x10000000},
|
||||
// GITADORA
|
||||
{"gdxg.dll", 0x2000000},
|
||||
#endif
|
||||
// jubeat
|
||||
{"jubeat.dll", 0x2000000},
|
||||
@@ -1232,6 +1234,7 @@ namespace avs {
|
||||
break;
|
||||
case 'F':
|
||||
new_style = logger::Style::RED;
|
||||
deferredlogs::report_fatal_message();
|
||||
break;
|
||||
default:
|
||||
position = 0;
|
||||
@@ -1300,6 +1303,7 @@ namespace avs {
|
||||
break;
|
||||
case 'F':
|
||||
style = logger::Style::RED;
|
||||
deferredlogs::report_fatal_message();
|
||||
break;
|
||||
case 'W':
|
||||
style = logger::Style::YELLOW;
|
||||
|
||||
+28
-3
@@ -5,8 +5,9 @@
|
||||
#include "build/defs.h"
|
||||
#include "easrv/easrv.h"
|
||||
#include "easrv/smartea.h"
|
||||
#include "games/mfc/mfc.h"
|
||||
#include "games/gitadora/gitadora.h"
|
||||
#include "hooks/avshook.h"
|
||||
#include "hooks/graphics/graphics.h"
|
||||
#include "util/detour.h"
|
||||
#include "util/deferlog.h"
|
||||
#include "util/fileutils.h"
|
||||
@@ -494,7 +495,9 @@ namespace avs {
|
||||
std::ostringstream init_code;
|
||||
init_code << EA3_MODEL;
|
||||
init_code << EA3_DEST;
|
||||
if (strcmp(EA3_MODEL, "MDX") == 0 && strcmp(EA3_SPEC, "I") == 0) {
|
||||
if (avs::game::DLL_NAME == "arkmdxbio2.dll" &&
|
||||
strcmp(EA3_MODEL, "MDX") == 0 && strcmp(EA3_SPEC, "I") == 0) {
|
||||
// ddr gold cab
|
||||
init_code << "G";
|
||||
init_code << "B";
|
||||
} else {
|
||||
@@ -526,10 +529,32 @@ namespace avs {
|
||||
}
|
||||
}
|
||||
|
||||
// update -WINDOW in app-config
|
||||
if (games::gitadora::is_arena_model()) {
|
||||
avs::core::property_search_remove_safe(app_config, nullptr, "/param/cmdline");
|
||||
std::string cmdline{};
|
||||
if (games::gitadora::is_drum()) {
|
||||
if (GRAPHICS_WINDOWED) {
|
||||
cmdline = "-d -DM -WINDOW";
|
||||
} else {
|
||||
cmdline = "-d -DM";
|
||||
}
|
||||
} else {
|
||||
if (GRAPHICS_WINDOWED) {
|
||||
cmdline = "-g -GF -WINDOW";
|
||||
} else {
|
||||
cmdline = "-g -GF";
|
||||
}
|
||||
}
|
||||
log_info("avs-ea3", "using cmdline: `{}`", cmdline);
|
||||
avs::core::property_node_create(app_config, nullptr,
|
||||
avs::core::NODE_TYPE_str, "/param/cmdline", cmdline.c_str());
|
||||
}
|
||||
|
||||
auto app_param = avs::core::property_search_safe(app_config, nullptr, "/param");
|
||||
|
||||
// call the game init
|
||||
log_info("avs-ea3", "calling entry init");
|
||||
log_info("avs-ea3", "calling entry init (init code = {})", init_code_str);
|
||||
if (!avs::game::entry_init(init_code_str.data(), app_param)) {
|
||||
log_fatal("avs-ea3", "entry init failed :(");
|
||||
}
|
||||
|
||||
@@ -74,7 +74,7 @@ then
|
||||
BUILD_TYPE="Debug"
|
||||
BUILDDIR_32=${BUILDDIR_32_DEBUG}
|
||||
BUILDDIR_64=${BUILDDIR_64_DEBUG}
|
||||
DIST_NAME=$(echo ${DIST_NAME} | sed 's/\.[^.]*$/-dbg&/')
|
||||
DIST_NAME=$(echo "${DIST_NAME}" | sed 's/\.[^.]*$/-dbg&/')
|
||||
fi
|
||||
|
||||
# determine number of cores
|
||||
@@ -114,7 +114,7 @@ time (
|
||||
fi
|
||||
mkdir -p ${BUILDDIR_32}
|
||||
pushd ${BUILDDIR_32} > /dev/null
|
||||
cmake -G "Ninja" -DCMAKE_TOOLCHAIN_FILE=${TOOLCHAIN_32} -DCMAKE_BUILD_TYPE=${BUILD_TYPE} $OLDPWD && ninja ${TARGETS_32}
|
||||
cmake -G "Ninja" -DCMAKE_TOOLCHAIN_FILE=${TOOLCHAIN_32} -DCMAKE_BUILD_TYPE=${BUILD_TYPE} "$OLDPWD" && ninja ${TARGETS_32}
|
||||
popd > /dev/null
|
||||
|
||||
# 64 bit
|
||||
@@ -127,7 +127,7 @@ time (
|
||||
fi
|
||||
mkdir -p ${BUILDDIR_64}
|
||||
pushd ${BUILDDIR_64} > /dev/null
|
||||
cmake -G "Ninja" -DCMAKE_TOOLCHAIN_FILE=${TOOLCHAIN_64} -DCMAKE_BUILD_TYPE=${BUILD_TYPE} $OLDPWD && ninja ${TARGETS_64}
|
||||
cmake -G "Ninja" -DCMAKE_TOOLCHAIN_FILE=${TOOLCHAIN_64} -DCMAKE_BUILD_TYPE=${BUILD_TYPE} "$OLDPWD" && ninja ${TARGETS_64}
|
||||
popd > /dev/null
|
||||
|
||||
echo ""
|
||||
@@ -234,7 +234,7 @@ mkdir -p ${OUTDIR}/src
|
||||
if ((INCLUDE_SRC > 0))
|
||||
then
|
||||
echo "Generating source file archive..."
|
||||
git archive --format tar.gz --prefix=spice2x/ HEAD ./ > ${OUTDIR}/src/spice2x-${GIT_BRANCH}.tar.gz 2>/dev/null || \
|
||||
git archive --format tar.gz --prefix=spice2x/ HEAD ./ > "${OUTDIR}/src/spice2x-${GIT_BRANCH}.tar.gz" 2>/dev/null || \
|
||||
echo "WARNING: Couldn't get git to create the archive. Is this a git repository?"
|
||||
fi
|
||||
|
||||
@@ -249,11 +249,11 @@ if ((DIST_ENABLE > 0))
|
||||
then
|
||||
echo "Building dist..."
|
||||
mkdir -p ${DIST_FOLDER}
|
||||
rm -rf ${DIST_FOLDER}/${DIST_NAME}
|
||||
rm -rf "${DIST_FOLDER}/${DIST_NAME}"
|
||||
pushd ${OUTDIR}/.. > /dev/null
|
||||
zip -qrXT9 $OLDPWD/${DIST_FOLDER}/${DIST_NAME} spice2x -x "spice2x/extras/*" -z <<< "$DIST_COMMENT"
|
||||
zip -qrXT9 "$OLDPWD/${DIST_FOLDER}/${DIST_NAME}" spice2x -x "spice2x/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}" spice2x -z <<< "$DIST_COMMENT"
|
||||
popd > /dev/null
|
||||
fi
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#!/bin/bash
|
||||
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 "$@"
|
||||
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 "$@"
|
||||
|
||||
@@ -644,10 +644,12 @@ float GameAPI::Analogs::getState(rawinput::Device *device, Analog &analog) {
|
||||
case rawinput::HID: {
|
||||
|
||||
// get value
|
||||
if (inverted) {
|
||||
value = 1.f - device->hidInfo->value_states[index];
|
||||
} else {
|
||||
value = device->hidInfo->value_states[index];
|
||||
if (index < device->hidInfo->value_states.size()) {
|
||||
if (inverted) {
|
||||
value = 1.f - device->hidInfo->value_states[index];
|
||||
} else {
|
||||
value = device->hidInfo->value_states[index];
|
||||
}
|
||||
}
|
||||
|
||||
// deadzone
|
||||
|
||||
+33
-13
@@ -58,8 +58,28 @@ std::string Button::getVKeyString() {
|
||||
return "Pause";
|
||||
case 0x14:
|
||||
return "Caps Lock";
|
||||
case 0x15:
|
||||
return "IME Kana/Hangul";
|
||||
case 0x16:
|
||||
return "IME On";
|
||||
case 0x17:
|
||||
return "IME Junja";
|
||||
case 0x18:
|
||||
return "IME Final";
|
||||
case 0x19:
|
||||
return "IME Hanja/Kanji";
|
||||
case 0x1A:
|
||||
return "IME Off";
|
||||
case 0x1B:
|
||||
return "Escape";
|
||||
case 0x1C:
|
||||
return "IME Convert";
|
||||
case 0x1D:
|
||||
return "IME Nonconvert";
|
||||
case 0x1E:
|
||||
return "IME Accept";
|
||||
case 0x1F:
|
||||
return "IME Mode Change";
|
||||
case 0x20:
|
||||
return "Space";
|
||||
case 0x21:
|
||||
@@ -255,9 +275,9 @@ std::string Button::getVKeyString() {
|
||||
case 0xA3:
|
||||
return "Right Control";
|
||||
case 0xA4:
|
||||
return "Left Menu";
|
||||
return "Left Alt";
|
||||
case 0xA5:
|
||||
return "Right Menu";
|
||||
return "Right Alt";
|
||||
default:
|
||||
|
||||
// check win API
|
||||
@@ -301,17 +321,17 @@ std::string Button::getDisplayString(rawinput::RawInputManager* manager) {
|
||||
switch (device->type) {
|
||||
case rawinput::MOUSE: {
|
||||
const char *btn = "Unknown";
|
||||
static const char *MOUSE_NAMES[] = {
|
||||
"Left Mouse",
|
||||
"Right Mouse",
|
||||
"Middle Mouse",
|
||||
"Mouse 1",
|
||||
"Mouse 2",
|
||||
"Mouse 3",
|
||||
"Mouse 4",
|
||||
"Mouse 5",
|
||||
};
|
||||
if (vKey < sizeof(MOUSE_NAMES)) {
|
||||
static const std::array<const char *, 8>MOUSE_NAMES = {
|
||||
"Left Mouse",
|
||||
"Right Mouse",
|
||||
"Middle Mouse",
|
||||
"Mouse 1",
|
||||
"Mouse 2",
|
||||
"Mouse 3",
|
||||
"Mouse 4",
|
||||
"Mouse 5",
|
||||
};
|
||||
if (vKey < MOUSE_NAMES.size()) {
|
||||
btn = MOUSE_NAMES[vKey];
|
||||
}
|
||||
return fmt::format("{} ({})", btn, device->desc);
|
||||
|
||||
@@ -45,6 +45,7 @@ private:
|
||||
double debounce_up = 0.0;
|
||||
double debounce_down = 0.0;
|
||||
bool invert = false;
|
||||
bool is_temporary = false;
|
||||
|
||||
GameAPI::Buttons::State last_state = GameAPI::Buttons::BUTTON_NOT_PRESSED;
|
||||
float last_velocity = 0.f;
|
||||
@@ -164,6 +165,14 @@ public:
|
||||
this->last_velocity = last_velocity;
|
||||
}
|
||||
|
||||
inline bool isTemporary() const {
|
||||
return this->is_temporary;
|
||||
}
|
||||
|
||||
inline void setTemporary(bool is_temporary) {
|
||||
this->is_temporary = is_temporary;
|
||||
}
|
||||
|
||||
inline unsigned short getVelocityThreshold() const {
|
||||
return this->velocity_threshold;
|
||||
}
|
||||
@@ -172,6 +181,20 @@ public:
|
||||
this->velocity_threshold = velocity_threshold;
|
||||
}
|
||||
|
||||
inline bool isValid() {
|
||||
// temporarily created by UI
|
||||
if (isTemporary()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// nothing is set
|
||||
if (getDeviceIdentifier().empty() && getVKey() == INVALID_VKEY) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void getMidiVKey(int& channel, int& index);
|
||||
void setMidiVKey(rawinput::RawInputManager* manager, bool is_note, int channel, int index);
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#include "config.h"
|
||||
#include "util/logging.h"
|
||||
#include "cfg/button.h"
|
||||
|
||||
/*
|
||||
* This code absolutely sucks.
|
||||
@@ -10,6 +11,14 @@
|
||||
// settings
|
||||
std::string CONFIG_PATH_OVERRIDE = "";
|
||||
|
||||
#define CFG_DEBUG_VERBOSE 0
|
||||
|
||||
#if CFG_DEBUG_VERBOSE
|
||||
#define log_debug(module, format_str, ...) logger::push( \
|
||||
LOG_FORMAT("M", module, format_str, ## __VA_ARGS__), logger::Style::GREY)
|
||||
#else
|
||||
#define log_debug(module, format_str, ...)
|
||||
#endif
|
||||
|
||||
///////////////////
|
||||
/// Constructor ///
|
||||
@@ -510,8 +519,10 @@ bool Config::updateBinding(const Game &game, const Button &button, int alternati
|
||||
return false;
|
||||
}
|
||||
|
||||
tinyxml2::XMLElement *gameButtonNode = nullptr;
|
||||
|
||||
// iterate button nodes
|
||||
tinyxml2::XMLElement *gameButtonNode = gameButtonsNode->FirstChildElement("button");
|
||||
gameButtonNode = gameButtonsNode->FirstChildElement("button");
|
||||
int button_count = 0;
|
||||
while (gameButtonNode != nullptr) {
|
||||
const char *buttonNodeName = gameButtonNode->Attribute("name");
|
||||
@@ -574,6 +585,39 @@ bool Config::updateBinding(const Game &game, const Button &button, int alternati
|
||||
}
|
||||
}
|
||||
|
||||
// trim the trailing invalid (unbound) alternatives
|
||||
tinyxml2::XMLElement *to_delete = nullptr;
|
||||
gameButtonNode = gameButtonsNode->LastChildElement("button");
|
||||
while (gameButtonNode != nullptr) {
|
||||
const char *name = gameButtonNode->Attribute("name");
|
||||
const char *devid = gameButtonNode->Attribute("devid");
|
||||
int vkey = INVALID_VKEY;
|
||||
gameButtonNode->QueryIntAttribute("vkey", &vkey);
|
||||
|
||||
// wrong button, ignore and keep iterating backwards
|
||||
if (name == nullptr || std::string(name) != button.getName()) {
|
||||
gameButtonNode = gameButtonNode->PreviousSiblingElement("button");
|
||||
continue;
|
||||
}
|
||||
|
||||
// found a node for this button, which means to_delete is not the first one
|
||||
// delete the node for to_delete
|
||||
if (to_delete != nullptr) {
|
||||
log_debug("cfg", "deleting excessive button binding for {}", name);
|
||||
gameButtonsNode->DeleteChild(to_delete);
|
||||
to_delete = nullptr;
|
||||
}
|
||||
|
||||
if ((devid == nullptr || strlen(devid) == 0) && vkey == INVALID_VKEY) {
|
||||
// unbound button with no device ID, make a note of it so it can be deleted later
|
||||
to_delete = gameButtonNode;
|
||||
gameButtonNode = gameButtonNode->PreviousSiblingElement("button");
|
||||
} else {
|
||||
// valid binding found; stop iterating
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// check if button was not found
|
||||
if (button_count == 0) {
|
||||
return false;
|
||||
@@ -760,6 +804,37 @@ bool Config::updateBinding(const Game &game, const Light &light, int alternative
|
||||
}
|
||||
}
|
||||
|
||||
// trim the trailing invalid (unbound) alternatives
|
||||
tinyxml2::XMLElement *to_delete = nullptr;
|
||||
gameLightNode = gameLightsNode->LastChildElement("light");
|
||||
while (gameLightNode != nullptr) {
|
||||
const char *name = gameLightNode->Attribute("name");
|
||||
const char *devid = gameLightNode->Attribute("devid");
|
||||
|
||||
// wrong light, ignore and keep iterating backwards
|
||||
if (name == nullptr || std::string(name) != light.getName()) {
|
||||
gameLightNode = gameLightNode->PreviousSiblingElement("light");
|
||||
continue;
|
||||
}
|
||||
|
||||
// found a node for this button, which means to_delete is not the first one
|
||||
// delete the node for to_delete
|
||||
if (to_delete != nullptr) {
|
||||
log_debug("cfg", "deleting excessive light binding for {}", name);
|
||||
gameLightsNode->DeleteChild(to_delete);
|
||||
to_delete = nullptr;
|
||||
}
|
||||
|
||||
if (devid == nullptr || strlen(devid) == 0) {
|
||||
// unbound light, make a note of it so it can be deleted later
|
||||
to_delete = gameLightNode;
|
||||
gameLightNode = gameLightNode->PreviousSiblingElement("light");
|
||||
} else {
|
||||
// valid binding found; stop iterating
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// check if light was not found
|
||||
if (light_count == 0) {
|
||||
return false;
|
||||
|
||||
@@ -13,6 +13,7 @@ private:
|
||||
std::string lightName;
|
||||
std::string deviceIdentifier = "";
|
||||
unsigned int index = 0;
|
||||
bool is_temporary = false;
|
||||
|
||||
public:
|
||||
float last_state = 0.f;
|
||||
@@ -65,4 +66,26 @@ public:
|
||||
inline void setIndex(unsigned int index) {
|
||||
this->index = index;
|
||||
}
|
||||
|
||||
inline bool isTemporary() const {
|
||||
return this->is_temporary;
|
||||
}
|
||||
|
||||
inline void setTemporary(bool is_temporary) {
|
||||
this->is_temporary = is_temporary;
|
||||
}
|
||||
|
||||
inline bool isValid() {
|
||||
// temporarily created by UI
|
||||
if (isTemporary()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// nothing is set
|
||||
if (getDeviceIdentifier().empty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
#include "screen_resize.h"
|
||||
|
||||
#include "avs/game.h"
|
||||
#include "external/rapidjson/document.h"
|
||||
#include "external/rapidjson/pointer.h"
|
||||
#include "external/rapidjson/prettywriter.h"
|
||||
#include "misc/eamuse.h"
|
||||
#include "util/deferlog.h"
|
||||
#include "util/utils.h"
|
||||
#include "util/fileutils.h"
|
||||
#include "hooks/graphics/graphics.h"
|
||||
@@ -78,6 +80,10 @@ namespace cfg {
|
||||
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");
|
||||
}
|
||||
|
||||
load_bool_value(doc, root + "enable_linear_filter", this->enable_linear_filter);
|
||||
for (size_t i = 0; i < std::size(this->scene_settings); i++) {
|
||||
auto& scene = this->scene_settings[i];
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#pragma once
|
||||
|
||||
#include <atomic>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <optional>
|
||||
@@ -51,6 +52,9 @@ namespace cfg {
|
||||
bool enable_linear_filter = true;
|
||||
fullscreen_setting scene_settings[4];
|
||||
|
||||
// state
|
||||
std::atomic<bool> need_surface_clean = false;
|
||||
|
||||
// windowed mode sizing
|
||||
// Windows terminology:
|
||||
// window = rectangle including the frame
|
||||
@@ -69,6 +73,7 @@ namespace cfg {
|
||||
uint32_t init_client_height = 0;
|
||||
float init_client_aspect_ratio = 1.f;
|
||||
uint32_t init_window_style = 0;
|
||||
uint32_t init_window_style_ex = 0;
|
||||
uint32_t window_deco_width = 0;
|
||||
uint32_t window_deco_height = 0;
|
||||
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
#include "bi2x_hook.h"
|
||||
|
||||
#if SPICE64
|
||||
|
||||
#include <cstdint>
|
||||
#include "util/detour.h"
|
||||
#include "util/logging.h"
|
||||
@@ -16,15 +18,19 @@ namespace games::ccj {
|
||||
*/
|
||||
|
||||
struct AIO_SCI_COMM {
|
||||
uint8_t data[0xf8];
|
||||
};
|
||||
|
||||
struct AIO_NMGR_IOB2 {
|
||||
uint8_t data[0xe30];
|
||||
};
|
||||
|
||||
struct AIO_IOB2_BI2X_TBS {
|
||||
uint8_t data[0x4680];
|
||||
};
|
||||
|
||||
struct AIO_IOB2_BI2X_WRFIRM {
|
||||
uint8_t data[0x20f48];
|
||||
};
|
||||
|
||||
struct AIO_NMGR_IOB__NODEINFO {
|
||||
@@ -72,6 +78,12 @@ namespace games::ccj {
|
||||
AIO_IOB2_BI2X_AC1__OUTPUTDATA OutputData;
|
||||
};
|
||||
|
||||
// checked with UJK-001-2024060400
|
||||
static_assert(sizeof(AIO_SCI_COMM) == 0xf8);
|
||||
static_assert(sizeof(AIO_NMGR_IOB2) == 0xe30);
|
||||
static_assert(sizeof(AIO_IOB2_BI2X_TBS) == 0x4680);
|
||||
static_assert(sizeof(AIO_IOB2_BI2X_WRFIRM) == 0x20f48);
|
||||
|
||||
static void write_iccr_led(Lights::ccj_lights_t light, uint8_t value);
|
||||
|
||||
/*
|
||||
@@ -621,4 +633,6 @@ namespace games::ccj {
|
||||
detour::trampoline_try(libaioDll, "aioNodeCtl_IsError",
|
||||
aioNodeCtl_IsError, &aioNodeCtl_IsError_orig);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
#pragma once
|
||||
|
||||
#if SPICE64
|
||||
|
||||
namespace games::ccj {
|
||||
void bi2x_hook_init();
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
@@ -68,9 +68,13 @@ namespace games::ccj {
|
||||
detour::trampoline_try("execexe.dll", MAKEINTRESOURCE(11),
|
||||
(void*)execexe_CreateFileW_hook,(void**)&execexe_CreateFileW_orig);
|
||||
|
||||
#if SPICE64
|
||||
|
||||
// insert BI2X hooks
|
||||
bi2x_hook_init();
|
||||
|
||||
#endif
|
||||
|
||||
// insert trackball hooks
|
||||
trackball_hook_init();
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
#include "hooks/setupapihook.h"
|
||||
#include "hooks/sleephook.h"
|
||||
#include "hooks/input/dinput8/hook.h"
|
||||
#include "util/deferlog.h"
|
||||
#include "util/utils.h"
|
||||
#include "util/libutils.h"
|
||||
#include "util/fileutils.h"
|
||||
@@ -108,18 +109,32 @@ namespace games::ddr {
|
||||
log_info("ddr", "`{}` returned {}", cmd, result);
|
||||
} else {
|
||||
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)});
|
||||
}
|
||||
|
||||
if (!contains_only_ascii(file.path().string())) {
|
||||
log_warning(
|
||||
"ddr",
|
||||
"BAD PATH ERROR\n\n\n"
|
||||
"!!! !!!\n"
|
||||
"!!! filesystem path to codec contains non-ASCII characters! !!!\n"
|
||||
"!!! this may cause the game to crash! !!!\n"
|
||||
"!!! !!!\n"
|
||||
);
|
||||
}
|
||||
static std::once_flag printed;
|
||||
std::call_once(printed, [file]() {
|
||||
if (!contains_only_ascii(file.path().string())) {
|
||||
log_warning(
|
||||
"ddr",
|
||||
"BAD PATH ERROR\n\n"
|
||||
"!!! !!!\n"
|
||||
"!!! filesystem path to codec contains non-ASCII characters! !!!\n"
|
||||
"!!! this may cause failures when loading codecs, and !!!\n"
|
||||
"!!! potentially leading the game to crash! !!!\n"
|
||||
"!!! !!!\n"
|
||||
);
|
||||
|
||||
deferredlogs::defer_error_messages({
|
||||
"path to DDR codecs contain non-Latin characters",
|
||||
" this may lead to movies not playing, white screen issue,",
|
||||
" game hanging, or potentially a crash",
|
||||
" move the game to a directory that does not contain non-Latin characters",
|
||||
" if you encountered any issues"});
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -253,28 +253,25 @@ namespace games::ddr {
|
||||
if(((dwIoControlCode>>16)&0xFFFF) == FILE_DEVICE_UNKNOWN) {
|
||||
switch((dwIoControlCode>>2)&0x3FFF) {
|
||||
case P4IO_IOCTL_GET_INPUTS: {
|
||||
|
||||
// Prevents this function from being called at its normal 2000-2500 kHz cadence and overloading the CPU, instead reduces it to 250 Hz
|
||||
Sleep(4);
|
||||
|
||||
memset(lpOutBuffer, 0, 16);
|
||||
auto controls = (uint32_t*) lpOutBuffer;
|
||||
|
||||
const std::pair<size_t, uint8_t> buttonMapping[] =
|
||||
{
|
||||
// Panel input comes from MDXF Control Status Buffer functions
|
||||
{Buttons::SERVICE, 25}, //Good
|
||||
{Buttons::TEST, 28}, //Good
|
||||
{Buttons::COIN_MECH, 24}, //Good
|
||||
{Buttons::P1_START, 0}, //Good
|
||||
{Buttons::P1_PANEL_UP, 5}, //Good
|
||||
{Buttons::P1_PANEL_DOWN, 6}, //Good
|
||||
{Buttons::P1_PANEL_LEFT, 7}, //Goood
|
||||
{Buttons::P1_PANEL_RIGHT, 16}, //Good
|
||||
{Buttons::P1_MENU_UP, 1}, //Good
|
||||
{Buttons::P1_MENU_DOWN, 2}, //Good
|
||||
{Buttons::P1_MENU_LEFT, 3}, //Good
|
||||
{Buttons::P1_MENU_RIGHT, 4}, //Good
|
||||
{Buttons::P2_START, 8}, //Good
|
||||
{Buttons::P2_PANEL_UP, 13}, //Good
|
||||
{Buttons::P2_PANEL_DOWN, 14}, //Good
|
||||
{Buttons::P2_PANEL_LEFT, 15}, //Good
|
||||
{Buttons::P2_PANEL_RIGHT, 20}, //Good
|
||||
{Buttons::P2_MENU_UP, 9}, //Good
|
||||
{Buttons::P2_MENU_DOWN, 10}, //Good
|
||||
{Buttons::P2_MENU_LEFT, 11}, //Good
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
#include "util/detour.h"
|
||||
#include "util/logging.h"
|
||||
#include "util/memutils.h"
|
||||
#include "rgb_cam.h"
|
||||
|
||||
#pragma pack(push)
|
||||
|
||||
@@ -150,6 +151,7 @@ namespace games::drs {
|
||||
char DRS_TAPELED[DRS_TAPELED_COLS_TOTAL][3] {};
|
||||
bool DISABLE_TOUCH = false;
|
||||
bool TRANSPOSE_TOUCH = false;
|
||||
bool RGB_CAMERA_HOOK = false;
|
||||
|
||||
std::vector<TouchEvent> TOUCH_EVENTS;
|
||||
|
||||
@@ -313,6 +315,10 @@ namespace games::drs {
|
||||
} else {
|
||||
log_info("drs", "no native input method detected");
|
||||
}
|
||||
|
||||
if (RGB_CAMERA_HOOK) {
|
||||
init_rgb_camera_hook();
|
||||
}
|
||||
}
|
||||
|
||||
void DRSGame::detach() {
|
||||
|
||||
@@ -31,6 +31,7 @@ namespace games::drs {
|
||||
extern std::vector<TouchEvent> TOUCH_EVENTS;
|
||||
extern bool DISABLE_TOUCH;
|
||||
extern bool TRANSPOSE_TOUCH;
|
||||
extern bool RGB_CAMERA_HOOK;
|
||||
|
||||
void fire_touches(drs_touch_t *events, size_t event_count);
|
||||
void start_touch();
|
||||
|
||||
@@ -0,0 +1,170 @@
|
||||
#include "games/drs/rgb_cam.h"
|
||||
|
||||
// set version to Windows 7 to enable Media Foundation functions
|
||||
#ifdef _WIN32_WINNT
|
||||
#undef _WIN32_WINNT
|
||||
#endif
|
||||
#define _WIN32_WINNT 0x0601
|
||||
|
||||
// turn on C-style COM objects
|
||||
#define CINTERFACE
|
||||
|
||||
#include <mfobjects.h>
|
||||
#include <mfidl.h>
|
||||
#include <string>
|
||||
|
||||
#include "avs/game.h"
|
||||
#include "cfg/configurator.h"
|
||||
#include "hooks/cfgmgr32hook.h"
|
||||
#include "util/detour.h"
|
||||
#include "util/memutils.h"
|
||||
#include "util/logging.h"
|
||||
#include "util/utils.h"
|
||||
|
||||
static VTBL_TYPE(IMFActivate, GetAllocatedString) GetAllocatedString_orig = nullptr;
|
||||
static decltype(MFEnumDeviceSources) *MFEnumDeviceSources_orig = nullptr;
|
||||
|
||||
namespace games::drs {
|
||||
|
||||
/*
|
||||
* Camera related stuff
|
||||
*/
|
||||
static std::wstring CAMERA0_ID;
|
||||
|
||||
static HRESULT WINAPI GetAllocatedString_hook(IMFActivate* This, REFGUID guidKey, LPWSTR *ppwszValue,
|
||||
UINT32 *pcchLength)
|
||||
{
|
||||
// call the original
|
||||
HRESULT result = GetAllocatedString_orig(This, guidKey, ppwszValue, pcchLength);
|
||||
|
||||
// log the cam name
|
||||
log_misc("drs::rgbcam", "obtained {}", ws2s(*ppwszValue));
|
||||
|
||||
// try first camera
|
||||
wchar_t *pwc = nullptr;
|
||||
if (CAMERA0_ID.length() == 23) {
|
||||
pwc = wcsstr(*ppwszValue, CAMERA0_ID.c_str());
|
||||
}
|
||||
|
||||
// check if camera could be identified
|
||||
if (pwc) {
|
||||
|
||||
// fake the USB IDs
|
||||
pwc[4] = L'2';
|
||||
pwc[5] = L'8';
|
||||
pwc[6] = L'8';
|
||||
pwc[7] = L'c';
|
||||
|
||||
pwc[13] = L'0';
|
||||
pwc[14] = L'0';
|
||||
pwc[15] = L'0';
|
||||
pwc[16] = L'2';
|
||||
|
||||
pwc[21] = L'0';
|
||||
pwc[22] = L'0';
|
||||
|
||||
log_misc("drs::rgbcam", "replaced {}", ws2s(*ppwszValue));
|
||||
}
|
||||
|
||||
// return original result
|
||||
return result;
|
||||
}
|
||||
|
||||
static void hook_camera(std::wstring camera_id, std::string camera_instance) {
|
||||
// don't hook if camera 0 is already hooked
|
||||
if (CAMERA0_ID.length() > 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
// save the camera ID
|
||||
CAMERA0_ID = camera_id;
|
||||
|
||||
// cfgmgr hook
|
||||
CFGMGR32_HOOK_SETTING camera_setting;
|
||||
camera_setting.device_instance = 0xDEADBEEF;
|
||||
camera_setting.parent_instance = ~camera_setting.device_instance;
|
||||
camera_setting.device_id = "USB\\VEN_8086&DEV_8C2D";
|
||||
camera_setting.device_node_id = "USB\\VID_288C&PID_0002&MI_00\\?&????????&?&????";
|
||||
if (camera_instance.length() == 17) {
|
||||
for (int i = 0; i < 17; i++) {
|
||||
camera_setting.device_node_id[28 + i] = camera_instance[i];
|
||||
}
|
||||
}
|
||||
cfgmgr32hook_add(camera_setting);
|
||||
|
||||
log_info("drs::rgbcam", "hooked camera @ {}", ws2s(camera_id));
|
||||
}
|
||||
|
||||
static void hook_camera_vtable(IMFActivate *camera) {
|
||||
|
||||
// hook allocated string method for camera identification
|
||||
memutils::VProtectGuard camera_guard(camera->lpVtbl);
|
||||
camera->lpVtbl->GetAllocatedString = GetAllocatedString_hook;
|
||||
}
|
||||
|
||||
static HRESULT WINAPI MFEnumDeviceSources_hook(IMFAttributes *pAttributes, IMFActivate ***pppSourceActivate,
|
||||
UINT32 *pcSourceActivate) {
|
||||
|
||||
// call original function
|
||||
HRESULT result_orig = MFEnumDeviceSources_orig(pAttributes, pppSourceActivate, pcSourceActivate);
|
||||
|
||||
// check for capture devices
|
||||
if (FAILED(result_orig) || !*pcSourceActivate) {
|
||||
return result_orig;
|
||||
}
|
||||
|
||||
// get camera
|
||||
IMFActivate *camera = **pppSourceActivate;
|
||||
|
||||
// save original method for later use
|
||||
if (GetAllocatedString_orig == nullptr) {
|
||||
GetAllocatedString_orig = camera->lpVtbl->GetAllocatedString;
|
||||
}
|
||||
|
||||
// hook allocated string method for camera identification
|
||||
hook_camera_vtable(camera);
|
||||
|
||||
// get camera link
|
||||
LPWSTR camera_link_lpwstr;
|
||||
UINT32 camera_link_length;
|
||||
if (SUCCEEDED(GetAllocatedString_orig(
|
||||
camera,
|
||||
MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE_VIDCAP_SYMBOLIC_LINK,
|
||||
&camera_link_lpwstr,
|
||||
&camera_link_length))) {
|
||||
|
||||
// cut name to make ID
|
||||
std::wstring camera_link_ws = std::wstring(camera_link_lpwstr);
|
||||
std::wstring camera_id = camera_link_ws.substr(8, 23);
|
||||
|
||||
log_info("drs::rgbcam", "found video capture device: {}", ws2s(camera_link_ws));
|
||||
|
||||
// only support cameras that start with \\?\usb
|
||||
if (!string_begins_with(camera_link_ws, L"\\\\?\\usb")) {
|
||||
return result_orig;
|
||||
}
|
||||
|
||||
// get camera instance
|
||||
std::string camera_link = ws2s(camera_link_ws);
|
||||
std::string camera_instance = camera_link.substr(32, 17);
|
||||
|
||||
// hook the camera
|
||||
hook_camera(camera_id, camera_instance);
|
||||
} else {
|
||||
log_warning("drs::rgbcam", "failed to open camera");
|
||||
}
|
||||
|
||||
// return result
|
||||
return result_orig;
|
||||
}
|
||||
|
||||
void init_rgb_camera_hook() {
|
||||
log_info("drs::rgbcam", "initializing camera hook");
|
||||
|
||||
cfgmgr32hook_init(avs::game::DLL_INSTANCE);
|
||||
|
||||
// camera media framework hook
|
||||
MFEnumDeviceSources_orig = detour::iat_try(
|
||||
"MFEnumDeviceSources", MFEnumDeviceSources_hook, avs::game::DLL_INSTANCE);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
#pragma once
|
||||
|
||||
namespace games::drs {
|
||||
void init_rgb_camera_hook();
|
||||
}
|
||||
@@ -0,0 +1,722 @@
|
||||
#include "bi2x_hook.h"
|
||||
|
||||
#if SPICE64
|
||||
|
||||
#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 "io.h"
|
||||
#include "util/tapeled.h"
|
||||
#include <typeinfo>
|
||||
|
||||
namespace games::gitadora {
|
||||
|
||||
// /*
|
||||
// * class definitions
|
||||
// */
|
||||
|
||||
struct AIO_NMGR_IOB2 {
|
||||
uint8_t data[0xe18];
|
||||
};
|
||||
|
||||
struct AIO_IOB2_BI2X_AC1__SETTING {
|
||||
};
|
||||
|
||||
struct AIO_NMGR_IOB5 {
|
||||
uint8_t data[0xb10];
|
||||
};
|
||||
|
||||
struct AIO_SCI_COMM {
|
||||
uint8_t data[0x100];
|
||||
};
|
||||
|
||||
struct AIO_IOB5_Y32D {
|
||||
uint8_t data[0x2e8];
|
||||
};
|
||||
|
||||
// struct AIO_IOB5_Y33I {
|
||||
// };
|
||||
|
||||
struct AIO_IOB2_BI2X_AC1 {
|
||||
uint8_t data[0x4570];
|
||||
};
|
||||
|
||||
struct AIO_NMGR_IOB__NODEINFO {
|
||||
uint8_t data[0xA8];
|
||||
};
|
||||
|
||||
struct AIO_IOB2_BI2X_WRFIRM {
|
||||
uint8_t data[0x20f48];
|
||||
};
|
||||
|
||||
struct AIO_SCI_COMM_T {
|
||||
uint64_t unk_0;
|
||||
struct AIO_SCI_COMM *aioSciComm;
|
||||
};
|
||||
|
||||
struct AIO_COMM_STATUS {
|
||||
uint64_t unk[5];
|
||||
};
|
||||
|
||||
struct AIO_IOB2_BI2X_AC1__INPUT {
|
||||
uint8_t DevIoCounter;
|
||||
uint8_t bExIoAErr;
|
||||
uint8_t bExIoBErr;
|
||||
uint8_t bPcPowerOn;
|
||||
uint8_t bPcPowerCheck;
|
||||
uint8_t bCoin1Jam;
|
||||
uint8_t bCoin2Jam;
|
||||
uint8_t bCoin3Jam;
|
||||
uint8_t bCoin4Jam;
|
||||
uint8_t Coin1Count;
|
||||
uint8_t Coin2Count;
|
||||
uint8_t Coin3Count;
|
||||
uint8_t Coin4Count;
|
||||
uint16_t AnalogCh1;
|
||||
uint16_t AnalogCh2;
|
||||
uint16_t AnalogCh3;
|
||||
uint16_t AnalogCh4;
|
||||
uint8_t CN8_8;
|
||||
uint8_t CN8_9;
|
||||
uint8_t CN8_10;
|
||||
uint8_t CN9_8;
|
||||
uint8_t CN9_9;
|
||||
uint8_t CN9_10;
|
||||
uint8_t CN11_11;
|
||||
uint8_t CN11_12;
|
||||
uint8_t CN11_13;
|
||||
uint8_t CN11_14;
|
||||
uint8_t CN11_15;
|
||||
uint8_t CN11_16;
|
||||
uint8_t CN11_17;
|
||||
uint8_t CN11_18;
|
||||
uint8_t CN11_19;
|
||||
uint8_t CN11_20;
|
||||
uint8_t CN12_11;
|
||||
uint8_t CN12_12;
|
||||
uint8_t CN12_13;
|
||||
uint8_t CN12_14;
|
||||
uint8_t CN12_15;
|
||||
uint8_t CN12_16;
|
||||
uint8_t CN12_17;
|
||||
uint8_t CN12_18;
|
||||
uint8_t CN12_19;
|
||||
uint8_t CN12_20;
|
||||
uint8_t CN12_21;
|
||||
uint8_t CN12_22;
|
||||
uint8_t CN12_23;
|
||||
uint8_t CN12_24;
|
||||
uint8_t CN15_3;
|
||||
uint8_t CN15_4;
|
||||
uint8_t CN15_5;
|
||||
uint8_t CN15_6;
|
||||
uint8_t CN15_7;
|
||||
uint8_t CN15_8;
|
||||
uint8_t CN15_9;
|
||||
uint8_t CN15_10;
|
||||
uint8_t CN15_11;
|
||||
uint8_t CN15_12;
|
||||
uint8_t CN15_13;
|
||||
uint8_t CN15_14;
|
||||
uint8_t CN15_15;
|
||||
uint8_t CN15_16;
|
||||
uint8_t CN15_17;
|
||||
uint8_t CN15_18;
|
||||
uint8_t CN15_19;
|
||||
uint8_t CN15_20;
|
||||
uint8_t CN19_8;
|
||||
uint8_t CN19_9;
|
||||
uint8_t CN19_10;
|
||||
uint8_t CN19_11;
|
||||
uint8_t CN19_12;
|
||||
uint8_t CN19_13;
|
||||
uint8_t CN19_14;
|
||||
uint8_t CN19_15;
|
||||
};
|
||||
|
||||
struct AIO_IOB2_BI2X_AC1__INPUTDATA {
|
||||
uint8_t data[247];
|
||||
};
|
||||
|
||||
struct AIO_IOB2_BI2X_AC1__OUTPUTDATA {
|
||||
uint8_t data[48];
|
||||
};
|
||||
|
||||
struct AIO_IOB2_BI2X_AC1__ICNPIN {
|
||||
uint16_t Ain[4];
|
||||
uint64_t CnPin;
|
||||
};
|
||||
|
||||
struct AIO_IOB2_BI2X_AC1__DEVSTATUS {
|
||||
uint8_t InputCounter;
|
||||
uint8_t OutputCounter;
|
||||
uint8_t IoResetCounter;
|
||||
uint8_t TapeLedCounter;
|
||||
uint8_t TapeLedRate[8];
|
||||
AIO_IOB2_BI2X_AC1__INPUT Input;
|
||||
AIO_IOB2_BI2X_AC1__INPUTDATA InputData;
|
||||
AIO_IOB2_BI2X_AC1__OUTPUTDATA OutputData;
|
||||
AIO_IOB2_BI2X_AC1__ICNPIN ICnPinHist[20];
|
||||
};
|
||||
|
||||
struct AIO_IOB5_Y32D__DEVSTATUS {
|
||||
uint8_t unk[0xB1];
|
||||
};
|
||||
|
||||
// verified with M32-007-2025092400
|
||||
static_assert(sizeof(AIO_NMGR_IOB2) == 0xe18);
|
||||
static_assert(sizeof(AIO_NMGR_IOB5) == 0xb10);
|
||||
static_assert(sizeof(AIO_SCI_COMM) == 0x100);
|
||||
static_assert(sizeof(AIO_IOB5_Y32D) == 0x2e8);
|
||||
static_assert(sizeof(AIO_IOB2_BI2X_AC1) == 0x4570);
|
||||
static_assert(sizeof(AIO_IOB2_BI2X_WRFIRM) == 0x20f48);
|
||||
|
||||
/*
|
||||
* typedefs
|
||||
*/
|
||||
|
||||
// libaio-iob2_video.dll
|
||||
typedef AIO_IOB2_BI2X_AC1 *(__fastcall *aioIob2Bi2xAC1_Create_t)(AIO_NMGR_IOB2 *i_pNodeMgr, uint32_t i_DevId,
|
||||
AIO_IOB2_BI2X_AC1__SETTING *i_Setting);
|
||||
typedef void(__fastcall *aioIob2Bi2xAC1_GetDeviceStatus_t)(AIO_IOB2_BI2X_AC1 *i_pNodeCtl,
|
||||
AIO_IOB2_BI2X_AC1__DEVSTATUS *o_DevStatus);
|
||||
typedef void(__fastcall *aioIob2Bi2xAC1_SetWatchDogTimer_t)(AIO_IOB2_BI2X_AC1 *i_pNodeCtl, uint8_t i_Count);
|
||||
typedef void(__fastcall *aioIob2Bi2xAC1_AddCounter_t)(AIO_IOB2_BI2X_AC1 *i_pNodeCtl, uint32_t i_Counter,
|
||||
uint32_t i_Count);
|
||||
typedef void(__fastcall *aioIob2Bi2xAC1_SetOutputData_t)(AIO_IOB2_BI2X_AC1 *i_pNodeCtl, uint32_t i_CnPin,
|
||||
uint8_t i_Data);
|
||||
typedef void(__fastcall *aioIob2Bi2xAC1_SetTapeLedDataPart_t)(AIO_IOB2_BI2X_AC1 *i_pNodeCtl, uint32_t i_TapeLedCh,
|
||||
uint32_t i_Offset, uint8_t *i_pData,
|
||||
uint32_t i_cntTapeLed, bool i_bReverse);
|
||||
typedef void(__fastcall *aioIob2Bi2x_SetTapeLedDataLimit_t)(AIO_IOB2_BI2X_AC1 *i_pNodeCtl, uint32_t i_Channel,
|
||||
uint8_t i_Scale, uint8_t i_Limit);
|
||||
typedef AIO_IOB2_BI2X_WRFIRM *(__fastcall *aioIob2Bi2x_CreateWriteFirmContext_t)(uint32_t i_SerialNumber,
|
||||
uint32_t i_bfIob);
|
||||
typedef void(__fastcall *aioIob2Bi2x_DestroyWriteFirmContext_t)(AIO_IOB2_BI2X_WRFIRM *i_pWrFirm);
|
||||
typedef int32_t(__fastcall *aioIob2Bi2x_WriteFirmGetState_t)(AIO_IOB2_BI2X_WRFIRM *i_pWrFirm);
|
||||
typedef bool(__fastcall *aioIob2Bi2x_WriteFirmIsCompleted_t)(int32_t i_State);
|
||||
|
||||
// libaio-iob.dll
|
||||
typedef AIO_NMGR_IOB2 *(__fastcall *aioNMgrIob2_Create_t)(AIO_SCI_COMM *i_pSci, uint32_t i_bfMode);
|
||||
typedef void(__fastcall *aioNMgrIob_BeginManage_t)(AIO_NMGR_IOB2 *i_pNodeMgr);
|
||||
typedef void(__fastcall *aioNCtlIob_GetNodeInfo_t)(AIO_IOB5_Y32D *i_pNodeCtl,
|
||||
AIO_NMGR_IOB__NODEINFO *o_NodeInfo);
|
||||
|
||||
// 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_Y32D *i_pNodeCtl);
|
||||
typedef int32_t(__fastcall *aioNodeCtl_GetState_t)(AIO_IOB5_Y32D *i_pNodeCtl);
|
||||
typedef bool(__fastcall *aioNodeCtl_IsReady_t)(AIO_IOB5_Y32D *i_pNodeCtl, int32_t i_State);
|
||||
typedef bool(__fastcall *aioNodeCtl_IsError_t)(AIO_IOB5_Y32D *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);
|
||||
|
||||
// libaio-iob5_y32.dll
|
||||
typedef AIO_IOB5_Y32D *(__fastcall *aioIob5Y32d_Create_t)(AIO_NMGR_IOB5 *i_pSci, uint32_t i_bfMode);
|
||||
// typedef AIO_IOB5_Y33I *(__fastcall *aioIob5Y33i_Create_t)(AIO_NMGR_IOB5 *i_pSci, uint32_t i_bfMode);
|
||||
|
||||
typedef void(__fastcall *aioIob5Y32d_GetDeviceStatus_t)(AIO_IOB5_Y32D *i_pNodeCtl,
|
||||
AIO_IOB5_Y32D__DEVSTATUS *o_DevStatus);
|
||||
// typedef void(__fastcall *aioIob5Y33i_GetDeviceStatus_t)(AIO_IOB5_Y33I *i_pNodeCtl,
|
||||
// AIO_IOB2_BI2X_AC1__DEVSTATUS *o_DevStatus);
|
||||
|
||||
/*
|
||||
* function pointers
|
||||
*/
|
||||
|
||||
// libaio-iob2_video.dll
|
||||
static aioIob2Bi2xAC1_Create_t aioIob2Bi2xAC1_Create_orig = nullptr;
|
||||
static aioIob2Bi2xAC1_GetDeviceStatus_t aioIob2Bi2xAC1_GetDeviceStatus_orig = nullptr;
|
||||
static aioIob2Bi2xAC1_SetWatchDogTimer_t aioIob2Bi2xAC1_SetWatchDogTimer_orig = nullptr;
|
||||
static aioIob2Bi2xAC1_AddCounter_t aioIob2Bi2xAC1_AddCounter_orig = nullptr;
|
||||
static aioIob2Bi2xAC1_SetOutputData_t aioIob2Bi2xAC1_SetOutputData_orig = nullptr;
|
||||
static aioIob2Bi2xAC1_SetTapeLedDataPart_t aioIob2Bi2xAC1_SetTapeLedDataPart_orig = nullptr;
|
||||
static aioIob2Bi2x_SetTapeLedDataLimit_t aioIob2Bi2x_SetTapeLedDataLimit_orig = nullptr;
|
||||
static aioIob2Bi2x_CreateWriteFirmContext_t aioIob2Bi2x_CreateWriteFirmContext_orig = nullptr;
|
||||
static aioIob2Bi2x_DestroyWriteFirmContext_t aioIob2Bi2x_DestroyWriteFirmContext_orig = nullptr;
|
||||
static aioIob2Bi2x_WriteFirmGetState_t aioIob2Bi2x_WriteFirmGetState_orig = nullptr;
|
||||
static aioIob2Bi2x_WriteFirmIsCompleted_t aioIob2Bi2x_WriteFirmIsCompleted_orig = nullptr;
|
||||
|
||||
// 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;
|
||||
|
||||
// libaio-iob.dll
|
||||
static aioNMgrIob2_Create_t aioNMgrIob2_Create_orig = nullptr;
|
||||
static aioNMgrIob_BeginManage_t aioNMgrIob_BeginManage_orig = nullptr;
|
||||
static aioNCtlIob_GetNodeInfo_t aioNCtlIob_GetNodeInfo_orig = nullptr;
|
||||
|
||||
// libaio-iob5_y32.dll
|
||||
static aioIob5Y32d_Create_t aioIob5Y32d_Create_orig = nullptr;
|
||||
static aioIob5Y32d_GetDeviceStatus_t aioIob5Y32d_GetDeviceStatus_orig = nullptr;
|
||||
|
||||
/*
|
||||
* variables
|
||||
*/
|
||||
static AIO_IOB2_BI2X_AC1 *aioIob2Bi2xAc1;
|
||||
static AIO_NMGR_IOB2 *aioNmgrIob2;
|
||||
static AIO_NMGR_IOB5 *aioNmgrIob5;
|
||||
static AIO_IOB5_Y32D *aioIob5Y32d;
|
||||
static AIO_SCI_COMM *aioSciComm;
|
||||
static AIO_IOB2_BI2X_WRFIRM *aioIob2Bi2xWrfirm;
|
||||
|
||||
/*
|
||||
* implementations
|
||||
*/
|
||||
|
||||
// libaio-iob2_video.dll
|
||||
|
||||
static AIO_IOB2_BI2X_AC1 *__fastcall aioIob2Bi2xAC1_Create(AIO_NMGR_IOB2 *i_pNodeMgr, uint32_t i_DevId,
|
||||
AIO_IOB2_BI2X_AC1__SETTING *i_Setting) {
|
||||
|
||||
if (i_pNodeMgr == aioNmgrIob2) {
|
||||
aioIob2Bi2xAc1 = new AIO_IOB2_BI2X_AC1;
|
||||
return aioIob2Bi2xAc1;
|
||||
} else {
|
||||
return aioIob2Bi2xAC1_Create_orig(i_pNodeMgr, i_DevId, i_Setting);
|
||||
}
|
||||
}
|
||||
|
||||
void __fastcall aioIob2Bi2xAC1_GetDeviceStatus(AIO_IOB2_BI2X_AC1 *i_pNodeCtl,
|
||||
AIO_IOB2_BI2X_AC1__DEVSTATUS *o_DevStatus) {
|
||||
|
||||
RI_MGR->devices_flush_output();
|
||||
if (i_pNodeCtl != aioIob2Bi2xAc1) {
|
||||
return aioIob2Bi2xAC1_GetDeviceStatus_orig(i_pNodeCtl, o_DevStatus);
|
||||
}
|
||||
|
||||
memset(o_DevStatus, 0x00, sizeof(AIO_IOB2_BI2X_AC1__DEVSTATUS));
|
||||
|
||||
auto &buttons = get_buttons();
|
||||
// struct may be misaligned
|
||||
o_DevStatus->Input.CN8_10 = GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::Test]) ? 0 : 0xFF;
|
||||
o_DevStatus->Input.CN9_8 = GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::Service]) ? 0 : 0xFF;
|
||||
o_DevStatus->Input.CN9_9 = GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::Coin]) ? 0 : 0xFF;
|
||||
o_DevStatus->Input.CN12_14 = GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::Headphone]) ? 0xFF : 0;
|
||||
|
||||
// coin
|
||||
o_DevStatus->Input.Coin1Count = eamuse_coin_get_stock();
|
||||
}
|
||||
|
||||
void __fastcall aioIob2Bi2xAC1_SetWatchDogTimer(AIO_IOB2_BI2X_AC1 *i_pNodeCtl, uint8_t i_Count) {
|
||||
|
||||
if (i_pNodeCtl != aioIob2Bi2xAc1) {
|
||||
return aioIob2Bi2xAC1_SetWatchDogTimer_orig(i_pNodeCtl, i_Count);
|
||||
}
|
||||
}
|
||||
|
||||
void __fastcall aioIob2Bi2xAC1_AddCounter(AIO_IOB2_BI2X_AC1 *i_pNodeCtl, uint32_t i_Counter, uint32_t i_Count) {
|
||||
|
||||
if (i_pNodeCtl == aioIob2Bi2xAc1 && i_Count == 0) {
|
||||
eamuse_coin_set_stock((uint16_t)i_Count);
|
||||
} else {
|
||||
return aioIob2Bi2xAC1_AddCounter_orig(i_pNodeCtl, i_Counter, i_Count);
|
||||
}
|
||||
}
|
||||
|
||||
void __fastcall aioIob2Bi2xAC1_SetOutputData(AIO_IOB2_BI2X_AC1 *i_pNodeCtl, uint32_t i_CnPin, uint8_t i_Data) {
|
||||
|
||||
if (i_pNodeCtl != aioIob2Bi2xAc1) {
|
||||
return aioIob2Bi2xAC1_SetOutputData_orig(i_pNodeCtl, i_CnPin, i_Data);
|
||||
}
|
||||
|
||||
std::optional<Lights::gitadora_lights_t> light;
|
||||
switch (i_CnPin) {
|
||||
case 14:
|
||||
light = Lights::ArenaWooferR;
|
||||
break;
|
||||
case 15:
|
||||
light = Lights::ArenaWooferG;
|
||||
break;
|
||||
case 16:
|
||||
light = Lights::ArenaWooferB;
|
||||
break;
|
||||
case 17:
|
||||
light = Lights::ArenaCardR;
|
||||
break;
|
||||
case 18:
|
||||
light = Lights::ArenaCardG;
|
||||
break;
|
||||
case 19:
|
||||
light = Lights::ArenaCardB;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
if (light.has_value()) {
|
||||
auto &lights = games::gitadora::get_lights();
|
||||
GameAPI::Lights::writeLight(RI_MGR, lights.at(light.value()), i_Data / 255.f);
|
||||
}
|
||||
}
|
||||
|
||||
void __fastcall aioIob2Bi2xAC1_SetTapeLedDataPart(AIO_IOB2_BI2X_AC1 *i_pNodeCtl, uint32_t i_TapeLedCh,
|
||||
uint32_t i_Offset, uint8_t *i_pData,
|
||||
uint32_t i_cntTapeLed, bool i_bReverse) {
|
||||
|
||||
if (i_pNodeCtl != aioIob2Bi2xAc1) {
|
||||
return aioIob2Bi2xAC1_SetTapeLedDataPart_orig(i_pNodeCtl, i_TapeLedCh, i_Offset, i_pData, i_cntTapeLed, i_bReverse);
|
||||
}
|
||||
|
||||
if (tapeledutils::is_enabled() &&
|
||||
i_TapeLedCh == 0 && i_Offset == 0 && i_cntTapeLed == 62) {
|
||||
|
||||
auto &lights = get_lights();
|
||||
const auto rgb = tapeledutils::pick_color_from_led_tape(i_pData, i_cntTapeLed);
|
||||
GameAPI::Lights::writeLight(RI_MGR, lights[Lights::ArenaTitleAvgR], rgb.r);
|
||||
GameAPI::Lights::writeLight(RI_MGR, lights[Lights::ArenaTitleAvgG], rgb.g);
|
||||
GameAPI::Lights::writeLight(RI_MGR, lights[Lights::ArenaTitleAvgB], rgb.b);
|
||||
}
|
||||
}
|
||||
|
||||
void __fastcall aioIob2Bi2x_SetTapeLedDataLimit(AIO_IOB2_BI2X_AC1 *i_pNodeCtl, uint32_t i_Channel,
|
||||
uint8_t i_Scale, uint8_t i_Limit) {
|
||||
|
||||
if (i_pNodeCtl != aioIob2Bi2xAc1) {
|
||||
return aioIob2Bi2x_SetTapeLedDataLimit_orig(i_pNodeCtl, i_Channel, i_Scale, i_Limit);
|
||||
}
|
||||
}
|
||||
|
||||
static AIO_IOB2_BI2X_WRFIRM *__fastcall aioIob2Bi2x_CreateWriteFirmContext(
|
||||
uint32_t i_SerialNumber, uint32_t i_bfIob) {
|
||||
|
||||
aioIob2Bi2xWrfirm = new AIO_IOB2_BI2X_WRFIRM;
|
||||
return aioIob2Bi2xWrfirm;
|
||||
}
|
||||
|
||||
static void __fastcall aioIob2Bi2x_DestroyWriteFirmContext(AIO_IOB2_BI2X_WRFIRM *i_pWrFirm) {
|
||||
|
||||
if (i_pWrFirm == aioIob2Bi2xWrfirm) {
|
||||
delete aioIob2Bi2xWrfirm;
|
||||
aioIob2Bi2xWrfirm = nullptr;
|
||||
} else {
|
||||
return aioIob2Bi2x_DestroyWriteFirmContext_orig(i_pWrFirm);
|
||||
}
|
||||
}
|
||||
|
||||
static int32_t __fastcall aioIob2Bi2x_WriteFirmGetState(AIO_IOB2_BI2X_WRFIRM *i_pWrFirm) {
|
||||
|
||||
if (i_pWrFirm == aioIob2Bi2xWrfirm) {
|
||||
return 8;
|
||||
} else {
|
||||
return aioIob2Bi2x_WriteFirmGetState_orig(i_pWrFirm);
|
||||
}
|
||||
}
|
||||
|
||||
static bool __fastcall aioIob2Bi2x_WriteFirmIsCompleted(int32_t i_State) {
|
||||
|
||||
if (aioIob2Bi2xWrfirm != nullptr) {
|
||||
return true;
|
||||
} else {
|
||||
return aioIob2Bi2x_WriteFirmIsCompleted_orig(i_State);
|
||||
}
|
||||
}
|
||||
|
||||
// libaio-iob.dll
|
||||
static AIO_NMGR_IOB2 *__fastcall aioNMgrIob2_Create(AIO_SCI_COMM *i_pSci, uint32_t i_bfMode) {
|
||||
|
||||
if (i_pSci == aioSciComm) {
|
||||
aioNmgrIob2 = new AIO_NMGR_IOB2;
|
||||
return aioNmgrIob2;
|
||||
} else {
|
||||
return aioNMgrIob2_Create_orig(i_pSci, i_bfMode);
|
||||
}
|
||||
}
|
||||
|
||||
static void __fastcall aioNMgrIob_BeginManage(AIO_NMGR_IOB2 *i_pNodeMgr) {
|
||||
|
||||
if (i_pNodeMgr == aioNmgrIob2) {
|
||||
} else {
|
||||
return aioNMgrIob_BeginManage_orig(i_pNodeMgr);
|
||||
}
|
||||
}
|
||||
|
||||
static void __fastcall aioNCtlIob_GetNodeInfo(
|
||||
AIO_IOB5_Y32D *i_pNodeCtl, AIO_NMGR_IOB__NODEINFO *o_NodeInfo) {
|
||||
|
||||
if (i_pNodeCtl == aioIob5Y32d) {
|
||||
memset(o_NodeInfo, 0, sizeof(AIO_NMGR_IOB__NODEINFO));
|
||||
} else {
|
||||
return aioNCtlIob_GetNodeInfo_orig(i_pNodeCtl, o_NodeInfo);
|
||||
}
|
||||
}
|
||||
|
||||
// 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 *)aioIob2Bi2xAc1 ||
|
||||
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 *)aioIob2Bi2xAc1 ||
|
||||
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 *)aioIob2Bi2xAc1 ||
|
||||
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 *)aioIob2Bi2xAc1 ||
|
||||
i_pNodeMgr == (AIO_NMGR_IOB5 *)aioNmgrIob2) {
|
||||
|
||||
return false;
|
||||
} else {
|
||||
return aioNodeMgr_IsError_orig(i_pNodeMgr, i_State);
|
||||
}
|
||||
}
|
||||
|
||||
static void __fastcall aioNodeCtl_Destroy(AIO_IOB5_Y32D *i_pNodeCtl) {
|
||||
|
||||
if (i_pNodeCtl == aioIob5Y32d ||
|
||||
i_pNodeCtl == (AIO_IOB5_Y32D *)aioIob2Bi2xAc1 ||
|
||||
i_pNodeCtl == (AIO_IOB5_Y32D *)aioNmgrIob2) {
|
||||
|
||||
delete aioIob5Y32d;
|
||||
aioIob5Y32d = nullptr;
|
||||
} else {
|
||||
return aioNodeCtl_Destroy_orig(i_pNodeCtl);
|
||||
}
|
||||
}
|
||||
|
||||
static int32_t __fastcall aioNodeCtl_GetState(AIO_IOB5_Y32D *i_pNodeCtl) {
|
||||
|
||||
if (i_pNodeCtl == aioIob5Y32d ||
|
||||
i_pNodeCtl == (AIO_IOB5_Y32D *)aioIob2Bi2xAc1 ||
|
||||
i_pNodeCtl == (AIO_IOB5_Y32D *)aioNmgrIob2) {
|
||||
|
||||
return 1;
|
||||
} else {
|
||||
return aioNodeCtl_GetState_orig(i_pNodeCtl);
|
||||
}
|
||||
}
|
||||
|
||||
static bool __fastcall aioNodeCtl_IsReady(AIO_IOB5_Y32D *i_pNodeCtl, int32_t i_State) {
|
||||
|
||||
if (i_pNodeCtl == aioIob5Y32d ||
|
||||
i_pNodeCtl == (AIO_IOB5_Y32D *)aioIob2Bi2xAc1 ||
|
||||
i_pNodeCtl == (AIO_IOB5_Y32D *)aioNmgrIob2) {
|
||||
|
||||
return true;
|
||||
} else {
|
||||
return aioNodeCtl_IsReady_orig(i_pNodeCtl, i_State);
|
||||
}
|
||||
}
|
||||
|
||||
static bool __fastcall aioNodeCtl_IsError(AIO_IOB5_Y32D *i_pNodeCtl, int32_t i_State) {
|
||||
|
||||
if (i_pNodeCtl == aioIob5Y32d ||
|
||||
i_pNodeCtl == (AIO_IOB5_Y32D *)aioIob2Bi2xAc1 ||
|
||||
i_pNodeCtl == (AIO_IOB5_Y32D *)aioNmgrIob2) {
|
||||
|
||||
return false;
|
||||
} else {
|
||||
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;
|
||||
return aioNmgrIob5;
|
||||
} else {
|
||||
return aioNMgrIob5_Create_orig(i_pSci, i_bfMode);
|
||||
}
|
||||
}
|
||||
|
||||
// libaio-iob5_y32.dll
|
||||
|
||||
static AIO_IOB5_Y32D *__fastcall aioIob5Y32d_Create(AIO_NMGR_IOB5 *i_pSci, uint32_t i_bfMode) {
|
||||
|
||||
if (i_pSci == aioNmgrIob5) {
|
||||
aioIob5Y32d = new AIO_IOB5_Y32D;
|
||||
return aioIob5Y32d;
|
||||
} else {
|
||||
return aioIob5Y32d_Create_orig(i_pSci, i_bfMode);
|
||||
}
|
||||
}
|
||||
|
||||
void __fastcall aioIob5Y32d_GetDeviceStatus(AIO_IOB5_Y32D *i_pNodeCtl,
|
||||
AIO_IOB5_Y32D__DEVSTATUS *o_DevStatus) {
|
||||
|
||||
if (i_pNodeCtl != aioIob5Y32d) {
|
||||
return aioIob5Y32d_GetDeviceStatus_orig(i_pNodeCtl, o_DevStatus);
|
||||
}
|
||||
|
||||
memset(o_DevStatus, 0, sizeof(AIO_IOB5_Y32D__DEVSTATUS));
|
||||
}
|
||||
|
||||
void bi2x_hook_init() {
|
||||
|
||||
// avoid double init
|
||||
static bool initialized = false;
|
||||
if (initialized) {
|
||||
return;
|
||||
} else {
|
||||
initialized = true;
|
||||
}
|
||||
|
||||
// announce
|
||||
log_info("bi2x_hook", "init");
|
||||
|
||||
// libaio-iob5_y32.dll
|
||||
const auto libaioIob5Y32Dll = "libaio-iob5_y32.dll";
|
||||
detour::trampoline_try(libaioIob5Y32Dll, "aioIob5Y32d_Create",
|
||||
aioIob5Y32d_Create, &aioIob5Y32d_Create_orig);
|
||||
detour::trampoline_try(libaioIob5Y32Dll, "aioIob5Y32i_Create",
|
||||
aioIob5Y32d_Create, &aioIob5Y32d_Create_orig);
|
||||
detour::trampoline_try(libaioIob5Y32Dll, "aioIob5Y32d_GetDeviceStatus",
|
||||
aioIob5Y32d_GetDeviceStatus, &aioIob5Y32d_GetDeviceStatus_orig);
|
||||
detour::trampoline_try(libaioIob5Y32Dll, "aioIob5Y32i_GetDeviceStatus",
|
||||
aioIob5Y32d_GetDeviceStatus, &aioIob5Y32d_GetDeviceStatus_orig);
|
||||
|
||||
// libaio-iob5.dll
|
||||
const auto libaioIob5Dll = "libaio-iob5.dll";
|
||||
detour::trampoline_try(libaioIob5Dll, "aioNMgrIob5_Create",
|
||||
aioNMgrIob5_Create, &aioNMgrIob5_Create_orig);
|
||||
|
||||
// libaio-iob2_video.dll
|
||||
const auto libaioIob2VideoDll = "libaio-iob2_video.dll";
|
||||
detour::trampoline_try(libaioIob2VideoDll, "aioIob2Bi2xAC1_Create",
|
||||
aioIob2Bi2xAC1_Create, &aioIob2Bi2xAC1_Create_orig);
|
||||
detour::trampoline_try(libaioIob2VideoDll, "aioIob2Bi2xAC1_GetDeviceStatus",
|
||||
aioIob2Bi2xAC1_GetDeviceStatus, &aioIob2Bi2xAC1_GetDeviceStatus_orig);
|
||||
detour::trampoline_try(libaioIob2VideoDll, "aioIob2Bi2xAC1_SetWatchDogTimer",
|
||||
aioIob2Bi2xAC1_SetWatchDogTimer, &aioIob2Bi2xAC1_SetWatchDogTimer_orig);
|
||||
detour::trampoline_try(libaioIob2VideoDll, "aioIob2Bi2xAC1_AddCounter",
|
||||
aioIob2Bi2xAC1_AddCounter, &aioIob2Bi2xAC1_AddCounter_orig);
|
||||
detour::trampoline_try(libaioIob2VideoDll, "aioIob2Bi2xAC1_SetOutputData",
|
||||
aioIob2Bi2xAC1_SetOutputData, &aioIob2Bi2xAC1_SetOutputData_orig);
|
||||
detour::trampoline_try(libaioIob2VideoDll, "aioIob2Bi2xAC1_SetTapeLedDataPart",
|
||||
aioIob2Bi2xAC1_SetTapeLedDataPart, &aioIob2Bi2xAC1_SetTapeLedDataPart_orig);
|
||||
detour::trampoline_try(libaioIob2VideoDll, "aioIob2Bi2x_SetTapeLedDataLimit",
|
||||
aioIob2Bi2x_SetTapeLedDataLimit, &aioIob2Bi2x_SetTapeLedDataLimit_orig);
|
||||
detour::trampoline_try(libaioIob2VideoDll, "?CreateWriteFirmContext@AIO_IOB2_BI2X@@SAPEAUWRFIRM@1@II@Z",
|
||||
aioIob2Bi2x_CreateWriteFirmContext, &aioIob2Bi2x_CreateWriteFirmContext_orig);
|
||||
detour::trampoline_try(libaioIob2VideoDll, "?DestroyWriteFirmContext@AIO_IOB2_BI2X@@SAXPEAUWRFIRM@1@@Z",
|
||||
aioIob2Bi2x_DestroyWriteFirmContext, &aioIob2Bi2x_DestroyWriteFirmContext_orig);
|
||||
detour::trampoline_try(libaioIob2VideoDll, "?WriteFirmGetState@AIO_IOB2_BI2X@@SAHPEAUWRFIRM@1@@Z",
|
||||
aioIob2Bi2x_WriteFirmGetState, &aioIob2Bi2x_WriteFirmGetState_orig);
|
||||
detour::trampoline_try(libaioIob2VideoDll, "?WriteFirmIsCompleted@AIO_IOB2_BI2X@@SA_NH@Z",
|
||||
aioIob2Bi2x_WriteFirmIsCompleted, &aioIob2Bi2x_WriteFirmIsCompleted_orig);
|
||||
|
||||
// libaio-iob.dll
|
||||
const auto libaioIobDll = "libaio-iob.dll";
|
||||
detour::trampoline_try(libaioIobDll, "aioNMgrIob2_Create",
|
||||
aioNMgrIob2_Create, &aioNMgrIob2_Create_orig);
|
||||
detour::trampoline_try(libaioIobDll, "aioNMgrIob_BeginManage",
|
||||
aioNMgrIob_BeginManage, &aioNMgrIob_BeginManage_orig);
|
||||
detour::trampoline_try(libaioIobDll, "aioNCtlIob_GetNodeInfo",
|
||||
aioNCtlIob_GetNodeInfo, &aioNCtlIob_GetNodeInfo_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@@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
|
||||
|
||||
namespace games::gitadora {
|
||||
void bi2x_hook_init();
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,10 @@
|
||||
#pragma once
|
||||
|
||||
#include "acioemu/acioemu.h"
|
||||
|
||||
namespace games::gitadora {
|
||||
class GitadoraDevice : public acioemu::ACIODeviceEmu {
|
||||
public:
|
||||
bool is_ready = false;
|
||||
};
|
||||
}
|
||||
@@ -1,20 +1,56 @@
|
||||
#include "gitadora.h"
|
||||
|
||||
#include "handle.h"
|
||||
#include "bi2x_hook.h"
|
||||
#include <unordered_map>
|
||||
|
||||
#include "cfg/configurator.h"
|
||||
#include "hooks/audio/mme.h"
|
||||
#include "hooks/graphics/graphics.h"
|
||||
#include "misc/wintouchemu.h"
|
||||
#include "overlay/overlay.h"
|
||||
#include "util/cpuutils.h"
|
||||
#include "util/detour.h"
|
||||
#include "util/libutils.h"
|
||||
#include "util/logging.h"
|
||||
#include "util/sigscan.h"
|
||||
#include "util/socd_cleaner.h"
|
||||
#include "hooks/setupapihook.h"
|
||||
|
||||
namespace games::gitadora {
|
||||
|
||||
// settings
|
||||
bool TWOCHANNEL = false;
|
||||
std::optional<unsigned int> CAB_TYPE = std::nullopt;
|
||||
bool ARENA_SINGLE_WINDOW = false;
|
||||
bool P1_LEFTY = false;
|
||||
bool P2_LEFTY = false;
|
||||
std::optional<std::string> SUBSCREEN_OVERLAY_SIZE;
|
||||
std::optional<socd::SocdAlgorithm> PICK_ALGO = socd::SocdAlgorithm::PreferRecent;
|
||||
|
||||
/*
|
||||
* Prevent GitaDora from creating folders on F drive
|
||||
*/
|
||||
|
||||
#ifdef SPICE64
|
||||
static DWORD WINAPI GetLogicalDrives_hook() {
|
||||
return GetLogicalDrives() | 0x20;
|
||||
}
|
||||
|
||||
static UINT WINAPI GetDriveTypeA_hook(LPCSTR lpRootPathName) {
|
||||
if (!strcmp(lpRootPathName, "F:\\")) {
|
||||
return DRIVE_FIXED;
|
||||
}
|
||||
|
||||
return GetDriveTypeA(lpRootPathName);
|
||||
}
|
||||
|
||||
static BOOL WINAPI CreateDirectoryA_hook(LPCSTR lpPathName, LPSECURITY_ATTRIBUTES lpSecurityAttributes) {
|
||||
if (!strncmp(lpPathName, "F:/", 3)) {
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
return CreateDirectoryA(lpPathName, lpSecurityAttributes);
|
||||
}
|
||||
#endif
|
||||
|
||||
/*
|
||||
* GitaDora checks if the IP address has changed, and if it has it throws 5-1506-0000 like jubeat.
|
||||
@@ -184,6 +220,23 @@ namespace games::gitadora {
|
||||
log_fatal("gitadora", "Cabinet type 3 (SD2) not supported on XG series");
|
||||
}
|
||||
#endif
|
||||
|
||||
// arena model launches a tiny window yet backbuffer at 4k, resulting in unusable overlay
|
||||
// force scaling to make things usable
|
||||
if (!overlay::UI_SCALE_PERCENT.has_value() && is_arena_model() &&
|
||||
GRAPHICS_WINDOWED && !cfg::CONFIGURATOR_STANDALONE) {
|
||||
overlay::UI_SCALE_PERCENT = 250;
|
||||
}
|
||||
|
||||
// for guitar wail SOCD cleaning
|
||||
socd::ALGORITHM = socd::SocdAlgorithm::PreferRecent;
|
||||
|
||||
// for guitar picking
|
||||
if (PICK_ALGO.has_value()) {
|
||||
log_info("gitadora", "guitar pick SOCD algorithm: {}", static_cast<int>(PICK_ALGO.value()));
|
||||
} else {
|
||||
log_info("gitadora", "guitar pick SOCD algorithm: legacy");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -214,6 +267,44 @@ namespace games::gitadora {
|
||||
system_module, "sys_debug_dip_set_param"));
|
||||
|
||||
#ifdef SPICE64
|
||||
// gitadora arena model
|
||||
auto aio = libutils::try_library("libaio.dll");
|
||||
if (aio != nullptr) {
|
||||
SETUPAPI_SETTINGS settings{};
|
||||
settings.class_guid[0] = 0x86E0D1E0;
|
||||
settings.class_guid[1] = 0x11D08089;
|
||||
settings.class_guid[2] = 0x0008E49C;
|
||||
settings.class_guid[3] = 0x731F303E;
|
||||
const char property[] = "1CCF(8050)_000";
|
||||
const char property_hardwareid[] = "USB\\VID_1CCF&PID_8050&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);
|
||||
|
||||
// Gitadora IO(J32D/J33I) board emulation
|
||||
devicehook_init(avs::game::DLL_INSTANCE);
|
||||
devicehook_add(new GitaDoraSerialHandle());
|
||||
|
||||
// test/service/coin buttons
|
||||
bi2x_hook_init();
|
||||
|
||||
// f: drive hook
|
||||
detour::iat_try("GetLogicalDrives", GetLogicalDrives_hook, avs::game::DLL_INSTANCE);
|
||||
detour::iat_try("GetDriveTypeA", GetDriveTypeA_hook, avs::game::DLL_INSTANCE);
|
||||
detour::iat_try("CreateDirectoryA", CreateDirectoryA_hook, avs::game::DLL_INSTANCE);
|
||||
|
||||
// volume change prevention
|
||||
hooks::audio::mme::init(avs::game::DLL_INSTANCE);
|
||||
|
||||
// touch hook
|
||||
if (ARENA_SINGLE_WINDOW) {
|
||||
wintouchemu::FORCE = true;
|
||||
wintouchemu::INJECT_MOUSE_AS_WM_TOUCH = true;
|
||||
wintouchemu::hook("GITADORA", avs::game::DLL_INSTANCE);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
HMODULE gdme_module = libutils::try_module("libgdme.dll");
|
||||
|
||||
|
||||
@@ -4,12 +4,18 @@
|
||||
|
||||
#include "avs/game.h"
|
||||
#include "games/game.h"
|
||||
#include "util/socd_cleaner.h"
|
||||
|
||||
namespace games::gitadora {
|
||||
|
||||
// settings
|
||||
extern bool TWOCHANNEL;
|
||||
extern std::optional<unsigned int> CAB_TYPE;
|
||||
extern bool ARENA_SINGLE_WINDOW;
|
||||
extern bool P1_LEFTY;
|
||||
extern bool P2_LEFTY;
|
||||
extern std::optional<std::string> SUBSCREEN_OVERLAY_SIZE;
|
||||
extern std::optional<socd::SocdAlgorithm> PICK_ALGO;
|
||||
|
||||
class GitaDoraGame : public games::Game {
|
||||
public:
|
||||
@@ -21,14 +27,29 @@ namespace games::gitadora {
|
||||
static inline bool is_drum() {
|
||||
return (
|
||||
avs::game::is_model({ "J32", "K32", "L32" }) ||
|
||||
(avs::game::is_model("M32") && avs::game::SPEC[0] == 'B')
|
||||
(avs::game::is_model("M32") && (avs::game::SPEC[0] == 'B' || avs::game::SPEC[0] == 'D'))
|
||||
);
|
||||
}
|
||||
static inline bool is_guitar() {
|
||||
return (
|
||||
avs::game::is_model({ "J33", "K33", "L33" }) ||
|
||||
(avs::game::is_model("M32") && avs::game::SPEC[0] == 'A')
|
||||
(avs::game::is_model("M32") && (avs::game::SPEC[0] == 'A' || avs::game::SPEC[0] == 'C'))
|
||||
);
|
||||
}
|
||||
|
||||
static inline bool is_arena_model() {
|
||||
return (
|
||||
avs::game::is_model("M32") &&
|
||||
(avs::game::SPEC[0] == 'C' || avs::game::SPEC[0] == 'D')
|
||||
);
|
||||
}
|
||||
|
||||
static inline bool is_player_lefty(size_t player) {
|
||||
if (player == 0) {
|
||||
return P1_LEFTY;
|
||||
} else if (player == 1) {
|
||||
return P2_LEFTY;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,110 @@
|
||||
#include "handle.h"
|
||||
#include "misc/eamuse.h"
|
||||
#include "rawinput/rawinput.h"
|
||||
#include "util/utils.h"
|
||||
#include "util/time.h"
|
||||
#include "io.h"
|
||||
#include "gitadora.h"
|
||||
#include "j32d.h"
|
||||
#include "j33i.h"
|
||||
#include "device.h"
|
||||
|
||||
|
||||
using namespace acioemu;
|
||||
static games::gitadora::GitadoraDevice *device;
|
||||
|
||||
bool games::gitadora::GitaDoraSerialHandle::open(LPCWSTR lpFileName) {
|
||||
if (wcscmp(lpFileName, L"COM1") != 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool is_drum = games::gitadora::is_drum();
|
||||
|
||||
if (is_drum) {
|
||||
device = new J32DSerialDevice();
|
||||
} else {
|
||||
device = new J33ISerialDevice();
|
||||
}
|
||||
|
||||
acio_emu.add_device(device);
|
||||
log_info("gitadora", "Opened COM1 ({} DEVICE)", is_drum ? "J32D" : "J33I");
|
||||
return true;
|
||||
}
|
||||
|
||||
int games::gitadora::GitaDoraSerialHandle::read(LPVOID lpBuffer, DWORD nNumberOfBytesToRead) {
|
||||
|
||||
auto buffer = reinterpret_cast<uint8_t *>(lpBuffer);
|
||||
|
||||
// the game does not send cmd 0x12F to the IO
|
||||
// but it is expected to receive 0x12F for user input
|
||||
// so i manually created a payload so that parse_msg can be used
|
||||
// to write to the response_buffer
|
||||
if (device->is_ready) {
|
||||
uint8_t chk = 0;
|
||||
MessageData* msg = ACIODeviceEmu::create_msg(0x1, 0x12F, 0, 0);
|
||||
msg->addr = 0x1;
|
||||
auto write_buffer = reinterpret_cast<uint8_t *>(msg);
|
||||
while (acio_emu.read() != std::nullopt); // there may be old stuffs in the buffer
|
||||
acio_emu.write(0xAA);
|
||||
acio_emu.write(0xAA);
|
||||
|
||||
for(DWORD i = 0; i < 5; i++) {
|
||||
chk += write_buffer[i];
|
||||
acio_emu.write(write_buffer[i]);
|
||||
}
|
||||
// direct copy and paste from write_msg
|
||||
if (chk == ACIO_SOF || chk == ACIO_ESCAPE) {
|
||||
acio_emu.write(ACIO_ESCAPE);
|
||||
acio_emu.write(~chk);
|
||||
} else {
|
||||
acio_emu.write(chk);
|
||||
}
|
||||
}
|
||||
|
||||
// read from emu
|
||||
DWORD bytes_read = 0;
|
||||
while (bytes_read < nNumberOfBytesToRead) {
|
||||
auto cur_byte = acio_emu.read();
|
||||
|
||||
if (cur_byte.has_value()) {
|
||||
buffer[bytes_read++] = cur_byte.value();
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef ACIOEMU_LOG
|
||||
log_info("gitadora", "Read IO Data: {}", bin2hex(buffer, bytes_read));
|
||||
#endif
|
||||
// return amount of bytes read
|
||||
return (int)bytes_read;
|
||||
}
|
||||
|
||||
|
||||
int games::gitadora::GitaDoraSerialHandle::write(LPCVOID lpBuffer, DWORD nNumberOfBytesToWrite)
|
||||
{
|
||||
auto buffer = reinterpret_cast<const uint8_t *>(lpBuffer);
|
||||
|
||||
// write to emu
|
||||
for (DWORD i = 0; i < nNumberOfBytesToWrite; i++) {
|
||||
acio_emu.write(buffer[i]);
|
||||
}
|
||||
|
||||
#ifdef ACIOEMU_LOG
|
||||
log_info("gitadora", "Write IO Data: {}", bin2hex(buffer, nNumberOfBytesToWrite));
|
||||
#endif
|
||||
// return all data written
|
||||
return (int)nNumberOfBytesToWrite;
|
||||
}
|
||||
|
||||
int games::gitadora::GitaDoraSerialHandle::device_io(DWORD dwIoControlCode, LPVOID lpInBuffer, DWORD nInBufferSize,
|
||||
LPVOID lpOutBuffer, DWORD nOutBufferSize) {
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
bool games::gitadora::GitaDoraSerialHandle::close() {
|
||||
|
||||
log_info("gitadora", "Closed COM1");
|
||||
return true;
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
#pragma once
|
||||
|
||||
#include "acioemu/acioemu.h"
|
||||
#include "hooks/devicehook.h"
|
||||
|
||||
namespace games::gitadora {
|
||||
|
||||
class GitaDoraSerialHandle : public CustomHandle {
|
||||
private:
|
||||
acioemu::ACIOEmu acio_emu;
|
||||
|
||||
public:
|
||||
bool open(LPCWSTR lpFileName) override;
|
||||
|
||||
int read(LPVOID lpBuffer, DWORD nNumberOfBytesToRead) override;
|
||||
|
||||
int write(LPCVOID lpBuffer, DWORD nNumberOfBytesToWrite) override;
|
||||
|
||||
int device_io(DWORD dwIoControlCode, LPVOID lpInBuffer, DWORD nInBufferSize, LPVOID lpOutBuffer,
|
||||
DWORD nOutBufferSize) override;
|
||||
bool close() override;
|
||||
|
||||
};
|
||||
}
|
||||
@@ -10,6 +10,7 @@ std::vector<Button> &games::gitadora::get_buttons() {
|
||||
"Service",
|
||||
"Test",
|
||||
"Coin",
|
||||
"Headphone",
|
||||
"Guitar P1 Start",
|
||||
"Guitar P1 Up",
|
||||
"Guitar P1 Down",
|
||||
@@ -64,17 +65,18 @@ std::vector<Button> &games::gitadora::get_buttons() {
|
||||
"Drum Help",
|
||||
"Drum Button Extra 1",
|
||||
"Drum Button Extra 2",
|
||||
|
||||
"Drum Left Cymbal",
|
||||
"Drum Hi-Hat",
|
||||
"Drum Hi-Hat Closed",
|
||||
"Drum Hi-Hat Half-Open",
|
||||
"Drum Left Pedal",
|
||||
"Drum Snare",
|
||||
"Drum Hi-Tom",
|
||||
"Drum Low-Tom",
|
||||
"Drum Right Cymbal",
|
||||
"Drum Bass Pedal",
|
||||
"Drum Left Cymbal",
|
||||
"Drum Left Pedal",
|
||||
"Drum Floor Tom"
|
||||
"Drum Low-Tom",
|
||||
"Drum Floor Tom",
|
||||
"Drum Right Cymbal",
|
||||
"Drum Hi-Hat Closed",
|
||||
"Drum Hi-Hat Half-Open"
|
||||
);
|
||||
}
|
||||
|
||||
@@ -106,9 +108,18 @@ std::string games::gitadora::get_buttons_help() {
|
||||
// keep to max 100 characters wide
|
||||
return
|
||||
"guitar:\n"
|
||||
"\n"
|
||||
" < R G B Y P --- Pick ] \n"
|
||||
"\n"
|
||||
" If you normally hold your guitar left-handed (frets on right hand):\n"
|
||||
" * here, hold the guitar as right-handed (frets on left hand),\n"
|
||||
" and bind the buttons, pick, and wail controls\n"
|
||||
" * in Options tab, turn on GitaDora Lefty Guitar\n"
|
||||
" * finally, use the in-game option to turn on LEFT mode\n"
|
||||
"\n"
|
||||
"\n"
|
||||
"drums:\n"
|
||||
"\n"
|
||||
" LeftCymbal RightCymbal\n"
|
||||
" HiHat HiTom LowTom \n"
|
||||
" Snare FloorTom \n"
|
||||
@@ -116,10 +127,28 @@ std::string games::gitadora::get_buttons_help() {
|
||||
" Left Bass\n"
|
||||
" Pedal Pedal\n"
|
||||
"\n"
|
||||
"Drums are NOT velocity-sensitive!\n"
|
||||
" For MIDI drums with Open/Closed HiHat configurations or pads with\n"
|
||||
" multiple hit zones, ensure you bind all variation using the Pages\n"
|
||||
" button at the bottom."
|
||||
;
|
||||
}
|
||||
std::string games::gitadora::get_analogs_help() {
|
||||
// keep to max 100 characters wide
|
||||
return
|
||||
"guitar:\n"
|
||||
"\n"
|
||||
"For MIDI drums with Open/Closed HiHat configurations, bind variations below. "
|
||||
"v2_drum algorithm might work better for those drums."
|
||||
"X axis: 0% when body is held facing monitor, 50% when flat on a table\n"
|
||||
"Y axis: 50% when held horizontal, 0% when neck is raised, 100% pointing down\n"
|
||||
"Z axis: 50% at rest, 100% when swinging neck toward monitor (only used in XG series)\n"
|
||||
"\n"
|
||||
"You need both X and Y axis for up/down wail to work correctly in-game.\n"
|
||||
"If you only have Y axis, consider using digital wailing instead.\n"
|
||||
"\n"
|
||||
"Ensure you clear all analog bindings if you want to use digital wailing.\n"
|
||||
"\n"
|
||||
"If you hold your guitar left-handed (frets on the right hand),\n"
|
||||
"analog bindings will likely not work as intended.\n"
|
||||
"Consider using digital wailing in Buttons tab."
|
||||
;
|
||||
}
|
||||
|
||||
@@ -210,7 +239,19 @@ std::vector<Light> &games::gitadora::get_lights() {
|
||||
"Guitar Right Speaker Mid Low Right B (DX)",
|
||||
"Guitar Right Speaker Lower R (DX)",
|
||||
"Guitar Right Speaker Lower G (DX)",
|
||||
"Guitar Right Speaker Lower B (DX)"
|
||||
"Guitar Right Speaker Lower B (DX)",
|
||||
|
||||
"Title Average R (Arena)",
|
||||
"Title Average G (Arena)",
|
||||
"Title Average B (Arena)",
|
||||
|
||||
"Woofer R (Arena)",
|
||||
"Woofer G (Arena)",
|
||||
"Woofer B (Arena)",
|
||||
|
||||
"Card Reader R (Arena)",
|
||||
"Card Reader G (Arena)",
|
||||
"Card Reader B (Arena)"
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -11,6 +11,7 @@ namespace games::gitadora {
|
||||
Service,
|
||||
Test,
|
||||
Coin,
|
||||
Headphone,
|
||||
GuitarP1Start,
|
||||
GuitarP1Up,
|
||||
GuitarP1Down,
|
||||
@@ -65,18 +66,21 @@ namespace games::gitadora {
|
||||
DrumHelp,
|
||||
DrumButtonExtra1,
|
||||
DrumButtonExtra2,
|
||||
// note: this is the order they appear in the test menu
|
||||
// note: this is the order of drums in the default layout
|
||||
DrumLeftCymbal,
|
||||
DrumHiHat,
|
||||
DrumHiHatClosed,
|
||||
DrumHiHatHalfOpen,
|
||||
DrumLeftPedal,
|
||||
DrumSnare,
|
||||
DrumHiTom,
|
||||
DrumLowTom,
|
||||
DrumRightCymbal,
|
||||
DrumBassPedal,
|
||||
DrumLeftCymbal,
|
||||
DrumLeftPedal,
|
||||
DrumFloorTom
|
||||
DrumLowTom,
|
||||
DrumFloorTom,
|
||||
DrumRightCymbal,
|
||||
|
||||
// these only exist for back compat
|
||||
// (created before it was possible to map multiple input to a button)
|
||||
DrumHiHatClosed,
|
||||
DrumHiHatHalfOpen
|
||||
};
|
||||
}
|
||||
|
||||
@@ -190,13 +194,25 @@ namespace games::gitadora {
|
||||
GuitarRightSpeakerMidLowRightB,
|
||||
GuitarRightSpeakerLowerR,
|
||||
GuitarRightSpeakerLowerG,
|
||||
GuitarRightSpeakerLowerB
|
||||
GuitarRightSpeakerLowerB,
|
||||
|
||||
// arena model (common for both guitar and drum)
|
||||
ArenaTitleAvgR,
|
||||
ArenaTitleAvgG,
|
||||
ArenaTitleAvgB,
|
||||
ArenaWooferR,
|
||||
ArenaWooferG,
|
||||
ArenaWooferB,
|
||||
ArenaCardR,
|
||||
ArenaCardG,
|
||||
ArenaCardB,
|
||||
} gitadora_lights_t;
|
||||
}
|
||||
|
||||
// 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();
|
||||
}
|
||||
|
||||
@@ -0,0 +1,100 @@
|
||||
|
||||
#include "misc/eamuse.h"
|
||||
#include "util/utils.h"
|
||||
#include "io.h"
|
||||
#include "j32d.h"
|
||||
|
||||
|
||||
using namespace acioemu;
|
||||
|
||||
games::gitadora::J32DSerialDevice::J32DSerialDevice() {
|
||||
this->node_count = 1;
|
||||
log_info("gitadora", "J32D device created.");
|
||||
}
|
||||
|
||||
bool games::gitadora::J32DSerialDevice::parse_msg(
|
||||
MessageData *msg_in,
|
||||
circular_buffer<uint8_t> *response_buffer) {
|
||||
|
||||
#ifdef ACIOEMU_LOG
|
||||
log_info("gitadora", "GTDR ADDR: {}, CMD: 0x{:04x}", msg_in->addr, msg_in->cmd.code);
|
||||
#endif
|
||||
|
||||
static uint8_t pid = 0;
|
||||
// check command
|
||||
switch (msg_in->cmd.code) {
|
||||
case ACIO_CMD_GET_VERSION: {
|
||||
// send version data
|
||||
auto msg = this->create_msg(msg_in, MSG_VERSION_SIZE);
|
||||
this->set_version(msg, 0x20a, 0, 0, 1, 0, "J32D");
|
||||
write_msg(msg, response_buffer);
|
||||
delete msg;
|
||||
break;
|
||||
}
|
||||
case ACIO_CMD_STARTUP:
|
||||
case ACIO_CMD_CLEAR:
|
||||
case 0x0120: {
|
||||
if (msg_in->cmd.code == 0x0120) {
|
||||
pid = msg_in->cmd.pid;
|
||||
this->is_ready = true;
|
||||
}
|
||||
// send status 0
|
||||
auto msg = this->create_msg_status(msg_in, 0);
|
||||
write_msg(msg, response_buffer);
|
||||
delete msg;
|
||||
break;
|
||||
}
|
||||
// IO
|
||||
case 0x012F: {
|
||||
auto msg = this->create_msg(msg_in, 0x11);
|
||||
msg->cmd.pid = pid;
|
||||
// don't know if it's needed/correct,
|
||||
// without it the game seems to work just fine
|
||||
pid += 1;
|
||||
J32D_IO_STATUS payload;
|
||||
memset(&payload, 0, sizeof(J32D_IO_STATUS));
|
||||
|
||||
auto &buttons = get_buttons();
|
||||
auto high_tom = (uint16_t) (GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::DrumHiTom]) * 1023);
|
||||
auto low_tom = (uint16_t) (GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::DrumLowTom]) * 1023);
|
||||
auto snare = (uint16_t) (GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::DrumSnare]) * 1023);
|
||||
auto floor_tom = (uint16_t) (GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::DrumFloorTom]) * 1023);
|
||||
auto left_cymbal = (uint16_t) (GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::DrumLeftCymbal]) * 1023);
|
||||
auto right_cymbal = (uint16_t) (GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::DrumRightCymbal]) * 1023);
|
||||
auto hihat = (uint16_t) ((GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::DrumHiHat]) ||
|
||||
GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::DrumHiHatClosed]) ||
|
||||
GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::DrumHiHatHalfOpen])) * 1023);
|
||||
|
||||
#define ENCODE_ANALOG(__val) (((__val & 0x3f) << 10) | ((__val & 0x3c0) >> 6))
|
||||
payload.high_tom = ENCODE_ANALOG(high_tom);
|
||||
payload.low_tom = ENCODE_ANALOG(low_tom);
|
||||
payload.snare = ENCODE_ANALOG(snare);
|
||||
payload.floor_tom = ENCODE_ANALOG(floor_tom);
|
||||
payload.left_cymbal = ENCODE_ANALOG(left_cymbal);
|
||||
payload.right_cymbal = ENCODE_ANALOG(right_cymbal);
|
||||
payload.hihat = ENCODE_ANALOG(hihat);
|
||||
#undef ENCODE_ANALOG
|
||||
|
||||
if (GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::DrumBassPedal])) {
|
||||
payload.pedals |= 1;
|
||||
}
|
||||
|
||||
if (GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::DrumLeftPedal])) {
|
||||
payload.pedals |= 2;
|
||||
}
|
||||
|
||||
memcpy(msg->cmd.raw, &payload, sizeof(J32D_IO_STATUS));
|
||||
write_msg(msg, response_buffer);
|
||||
delete msg;
|
||||
break;
|
||||
}
|
||||
default:
|
||||
#ifdef ACIOEMU_LOG
|
||||
log_info("gitadora", "Unhandled cmd {} received.", msg_in->cmd.code);
|
||||
#endif
|
||||
return false;
|
||||
}
|
||||
|
||||
// mark as handled
|
||||
return true;
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
#pragma once
|
||||
#include "acioemu/device.h"
|
||||
#include "device.h"
|
||||
|
||||
namespace games::gitadora {
|
||||
|
||||
class J32DSerialDevice : public GitadoraDevice {
|
||||
private:
|
||||
uint8_t coin_counter = 0;
|
||||
public:
|
||||
J32DSerialDevice();
|
||||
bool parse_msg(acioemu::MessageData *msg_in, circular_buffer<uint8_t> *response_buffer) override;
|
||||
struct J32D_IO_STATUS {
|
||||
uint16_t high_tom;
|
||||
uint16_t low_tom;
|
||||
uint16_t snare;
|
||||
uint16_t floor_tom;
|
||||
uint16_t left_cymbal;
|
||||
uint16_t right_cymbal;
|
||||
uint16_t hihat;
|
||||
uint16_t unk7;
|
||||
uint8_t pedals;
|
||||
};
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,203 @@
|
||||
|
||||
#include "misc/eamuse.h"
|
||||
#include "games/gitadora/gitadora.h"
|
||||
#include "util/socd_cleaner.h"
|
||||
#include "util/time.h"
|
||||
#include "util/utils.h"
|
||||
#include "io.h"
|
||||
#include "j33i.h"
|
||||
|
||||
#define GUITAR_BTN_R 0x7
|
||||
#define GUITAR_BTN_G 0x6
|
||||
#define GUITAR_BTN_B 0x5
|
||||
#define GUITAR_BTN_Y 0x4
|
||||
#define GUITAR_BTN_P 0x3
|
||||
#define GUITAR_PICK_DOWN 0xC
|
||||
#define GUITAR_PICK_UP 0xD
|
||||
|
||||
using namespace acioemu;
|
||||
|
||||
// why this number?? no clue, but this is what the test menu shows as the max
|
||||
// value in the green box with the red dot; the value display itself can go
|
||||
// higher but the red dot will reach the edge at +-189
|
||||
#define ARENA_MODEL_WAIL_MAX 189
|
||||
|
||||
static bool GFDM_GF_PICK_STATE_UP;
|
||||
static bool GFDM_GF_PICK_STATE_DOWN;
|
||||
|
||||
games::gitadora::J33ISerialDevice::J33ISerialDevice() {
|
||||
this->node_count = 1;
|
||||
log_info("gitadora", "J33I device created.");
|
||||
}
|
||||
|
||||
bool games::gitadora::J33ISerialDevice::parse_msg(
|
||||
MessageData *msg_in,
|
||||
circular_buffer<uint8_t> *response_buffer) {
|
||||
|
||||
#ifdef ACIOEMU_LOG
|
||||
log_info("gitadora", "GTDR ADDR: {}, CMD: 0x{:04x}", msg_in->addr, msg_in->cmd.code);
|
||||
#endif
|
||||
|
||||
static uint8_t pid = 0;
|
||||
// check command
|
||||
switch (msg_in->cmd.code)
|
||||
{
|
||||
case ACIO_CMD_GET_VERSION: {
|
||||
// send version data
|
||||
auto msg = this->create_msg(msg_in, MSG_VERSION_SIZE);
|
||||
this->set_version(msg, 0x10a, 0, 1, 0, 2, "J33I");
|
||||
write_msg(msg, response_buffer);
|
||||
delete msg;
|
||||
break;
|
||||
}
|
||||
case ACIO_CMD_STARTUP:
|
||||
case ACIO_CMD_CLEAR:
|
||||
case 0x0120: {
|
||||
if (msg_in->cmd.code == 0x0120) {
|
||||
pid = msg_in->cmd.pid;
|
||||
this->is_ready = true;
|
||||
}
|
||||
// send status 0
|
||||
auto msg = this->create_msg_status(msg_in, 0);
|
||||
write_msg(msg, response_buffer);
|
||||
delete msg;
|
||||
break;
|
||||
}
|
||||
// IO
|
||||
case 0x012F: {
|
||||
auto msg = this->create_msg(msg_in, 0x8);
|
||||
msg->cmd.pid = pid;
|
||||
pid += 1;
|
||||
J33I_IO_STATUS payload;
|
||||
memset(&payload, 0, sizeof(J33I_IO_STATUS));
|
||||
|
||||
auto &buttons = get_buttons();
|
||||
if (GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::GuitarP1R])) {
|
||||
payload.buttons |= 1 << GUITAR_BTN_R;
|
||||
}
|
||||
|
||||
if (GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::GuitarP1G])) {
|
||||
payload.buttons |= 1 << GUITAR_BTN_G;
|
||||
}
|
||||
|
||||
if (GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::GuitarP1B])) {
|
||||
payload.buttons |= 1 << GUITAR_BTN_B;
|
||||
}
|
||||
|
||||
if (GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::GuitarP1Y])) {
|
||||
payload.buttons |= 1 << GUITAR_BTN_Y;
|
||||
}
|
||||
|
||||
if (GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::GuitarP1P])) {
|
||||
payload.buttons |= 1 << GUITAR_BTN_P;
|
||||
}
|
||||
|
||||
|
||||
const auto pick_up = GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::GuitarP1PickUp]);
|
||||
const auto pick_down = GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::GuitarP1PickDown]);
|
||||
if (!games::gitadora::PICK_ALGO.has_value()) {
|
||||
// legacy behavior (detect rising edges only, input for 1 frame)
|
||||
if (pick_up) {
|
||||
if (!GFDM_GF_PICK_STATE_UP) {
|
||||
GFDM_GF_PICK_STATE_UP = true;
|
||||
payload.buttons |= 1 << GUITAR_PICK_UP;
|
||||
}
|
||||
} else {
|
||||
GFDM_GF_PICK_STATE_UP = false;
|
||||
}
|
||||
|
||||
if (pick_down) {
|
||||
if (!GFDM_GF_PICK_STATE_DOWN) {
|
||||
GFDM_GF_PICK_STATE_DOWN = true;
|
||||
payload.buttons |= 1 << GUITAR_PICK_DOWN;
|
||||
}
|
||||
} else {
|
||||
GFDM_GF_PICK_STATE_DOWN = false;
|
||||
}
|
||||
} else {
|
||||
const auto socd = socd::socd_clean(
|
||||
0,
|
||||
pick_up,
|
||||
pick_down,
|
||||
get_performance_milliseconds(),
|
||||
games::gitadora::PICK_ALGO.value()
|
||||
);
|
||||
if (socd == socd::SocdCCW) {
|
||||
payload.buttons |= 1 << GUITAR_PICK_UP;
|
||||
} else if (socd == socd::SocdCW) {
|
||||
payload.buttons |= 1 << GUITAR_PICK_DOWN;
|
||||
} else if (socd == socd::SocdBoth) {
|
||||
payload.buttons |= 1 << GUITAR_PICK_UP;
|
||||
payload.buttons |= 1 << GUITAR_PICK_DOWN;
|
||||
}
|
||||
}
|
||||
|
||||
auto &analogs = get_analogs();
|
||||
|
||||
// get x,y,z analog values [-0.5f, 0.5f]
|
||||
float x = is_player_lefty(0) ? 0.3f : -0.5f;
|
||||
auto x_analog = analogs[Analogs::GuitarP1WailX];
|
||||
if (x_analog.isSet()) {
|
||||
x = GameAPI::Analogs::getState(RI_MGR, x_analog) - 0.5f;
|
||||
}
|
||||
|
||||
float y = 0.f;
|
||||
auto y_analog = analogs[Analogs::GuitarP1WailY];
|
||||
if (y_analog.isSet()) {
|
||||
y = GameAPI::Analogs::getState(RI_MGR, y_analog) - 0.5f;
|
||||
}
|
||||
|
||||
float z = 0.f;
|
||||
auto z_analog = analogs[Analogs::GuitarP1WailZ];
|
||||
if (z_analog.isSet()) {
|
||||
z = GameAPI::Analogs::getState(RI_MGR, z_analog) - 0.5f;
|
||||
}
|
||||
|
||||
// handle digital wail up/down
|
||||
const auto wail_up = GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::GuitarP1WailUp]);
|
||||
const auto wail_down = GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::GuitarP1WailDown]);
|
||||
const auto wail_result =
|
||||
socd::get_guitar_wail(0, wail_up, wail_down, get_performance_milliseconds());
|
||||
if (!is_player_lefty(0)) {
|
||||
// righty
|
||||
if (wail_result == socd::TiltUp) {
|
||||
x = 0;
|
||||
y = -0.5f;
|
||||
} else if (wail_result == socd::TiltDown) {
|
||||
x = -0.5f;
|
||||
y = 0.5f;
|
||||
}
|
||||
} else {
|
||||
// lefty
|
||||
if (wail_result == socd::TiltUp) {
|
||||
x = 0.f;
|
||||
y = 0.f;
|
||||
} else if (wail_result == socd::TiltDown) {
|
||||
x = 0.5f;
|
||||
y = 0.f;
|
||||
}
|
||||
}
|
||||
|
||||
// convert x,y,z to unsigned integer values that the game expects
|
||||
const uint16_t x_int = x * ARENA_MODEL_WAIL_MAX * 2;
|
||||
const uint16_t y_int = y * ARENA_MODEL_WAIL_MAX * 2;
|
||||
const uint16_t z_int = z * ARENA_MODEL_WAIL_MAX * 2;
|
||||
payload.gyroscope_x = static_cast<uint16_t>(((x_int >> 3) & (0xFF)) | ((x_int & 0x07) << 12));
|
||||
payload.gyroscope_y = static_cast<uint16_t>(((y_int >> 3) & (0xFF)) | ((y_int & 0x07) << 12));
|
||||
payload.gyroscope_z = static_cast<uint16_t>(((z_int >> 3) & (0xFF)) | ((z_int & 0x07) << 12));
|
||||
|
||||
memcpy(msg->cmd.raw, &payload, sizeof(J33I_IO_STATUS));
|
||||
write_msg(msg, response_buffer);
|
||||
delete msg;
|
||||
break;
|
||||
}
|
||||
default:
|
||||
#ifdef ACIOEMU_LOG
|
||||
log_info("gitadora", "Unhandled cmd {} received.", msg_in->cmd.code);
|
||||
#endif
|
||||
return false;
|
||||
}
|
||||
|
||||
// mark as handled
|
||||
return true;
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
#pragma once
|
||||
|
||||
#include "acioemu/device.h"
|
||||
#include "device.h"
|
||||
|
||||
namespace games::gitadora {
|
||||
|
||||
class J33ISerialDevice : public GitadoraDevice {
|
||||
private:
|
||||
uint8_t coin_counter = 0;
|
||||
public:
|
||||
J33ISerialDevice();
|
||||
bool parse_msg(acioemu::MessageData *msg_in, circular_buffer<uint8_t> *response_buffer) override;
|
||||
|
||||
// there may be more, but the game doesn't seem to care
|
||||
// TODO: add support for gyroscope, knob and wail
|
||||
struct J33I_IO_STATUS {
|
||||
uint16_t gyroscope_x;
|
||||
uint16_t gyroscope_y;
|
||||
uint16_t gyroscope_z;
|
||||
uint16_t buttons;
|
||||
};
|
||||
};
|
||||
}
|
||||
@@ -195,11 +195,11 @@ bool games::iidx::IIDXFMSerialHandle::open(LPCWSTR lpFileName) {
|
||||
return false;
|
||||
}
|
||||
|
||||
log_info("iidx", "Opened COM2 (FM DEVICE)");
|
||||
log_info("iidx", "Opened COM2 (FM DEVICE / bi2a)");
|
||||
games::iidx::update_io_emulation_state(games::iidx::iidx_aio_emulation_state::bi2a_com2);
|
||||
|
||||
// ACIO device
|
||||
acio_emu.add_device(new FMSerialDevice());
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -240,7 +240,7 @@ int games::iidx::IIDXFMSerialHandle::device_io(DWORD dwIoControlCode, LPVOID lpI
|
||||
}
|
||||
|
||||
bool games::iidx::IIDXFMSerialHandle::close() {
|
||||
log_info("iidx", "Closed COM2 (FM DEVICE)");
|
||||
log_info("iidx", "Closed COM2 (FM DEVICE / bi2a)");
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
#include "bi2x_hook.h"
|
||||
|
||||
#if SPICE64
|
||||
|
||||
#include <cstdint>
|
||||
#include "util/detour.h"
|
||||
#include "util/logging.h"
|
||||
@@ -24,17 +26,28 @@ namespace games::iidx {
|
||||
uint8_t data[0x10];
|
||||
};
|
||||
|
||||
struct AIO_NMGR_IOB2 {
|
||||
uint8_t dummy0[0x50];
|
||||
struct AIO_NMGR_IOB2_VTABLE {
|
||||
// other functions here, but they never get called
|
||||
uint8_t dummy[0x50];
|
||||
void (__fastcall *pAIO_NMGR_IOB_BeginManage)(int64_t a1);
|
||||
uint8_t dummy1[0x9A0];
|
||||
};
|
||||
|
||||
struct AIO_NMGR_IOB2 {
|
||||
AIO_NMGR_IOB2_VTABLE *vptr;
|
||||
uint8_t dummy[0x9F0];
|
||||
};
|
||||
|
||||
// confirmed 0x9F8 in iidx27, iidx32 aioNMgrIob2_Create
|
||||
static_assert(sizeof(AIO_NMGR_IOB2) == 0x9F8);
|
||||
|
||||
struct AIO_IOB2_BI2X_TDJ {
|
||||
// who knows
|
||||
uint8_t data[0x13F8];
|
||||
};
|
||||
|
||||
// confirmed 0x13F8 in iidx27, iidx32 in aioIob2Bi2xTDJ_Create
|
||||
static_assert(sizeof(AIO_IOB2_BI2X_TDJ) == 0x13F8);
|
||||
|
||||
struct AIO_IOB2_BI2X_TDJ__DEVSTATUS {
|
||||
// of course you could work with variables here
|
||||
uint8_t buffer[0xCA];
|
||||
@@ -78,7 +91,7 @@ namespace games::iidx {
|
||||
// libaio-iob.dll
|
||||
typedef AC_HNDLIF* (__fastcall *aioIob2Bi2x_OpenSciUsbCdc_t)(uint8_t device_num);
|
||||
typedef int64_t (__fastcall *aioIob2Bi2x_WriteFirmGetState_t)(int64_t a1);
|
||||
typedef AIO_NMGR_IOB2** (__fastcall *aioNMgrIob2_Create_t)(AC_HNDLIF *a1, unsigned int a2);
|
||||
typedef AIO_NMGR_IOB2* (__fastcall *aioNMgrIob2_Create_t)(AC_HNDLIF *a1, unsigned int a2);
|
||||
|
||||
/*
|
||||
* function pointers
|
||||
@@ -120,6 +133,11 @@ namespace games::iidx {
|
||||
|
||||
static AIO_IOB2_BI2X_TDJ* __fastcall aioIob2Bi2xTDJ_Create(AIO_NMGR_IOB2 *nmgr, int a2) {
|
||||
if (!BI2X_PASSTHROUGH) {
|
||||
|
||||
log_info("bi2x_hook", "aioIob2Bi2xTDJ_Create called (TDJ I/O emulation active)");
|
||||
games::iidx::update_io_emulation_state(
|
||||
games::iidx::iidx_aio_emulation_state::bi2x_hook);
|
||||
|
||||
custom_node = new AIO_IOB2_BI2X_TDJ;
|
||||
memset(&custom_node->data, 0, sizeof(custom_node->data));
|
||||
|
||||
@@ -438,7 +456,7 @@ namespace games::iidx {
|
||||
}
|
||||
}
|
||||
|
||||
static AC_HNDLIF* __fastcall aioIob2Bi2X_OpenSciUsbCdc(uint8_t device_num) {
|
||||
static AC_HNDLIF* __fastcall aioIob2Bi2x_OpenSciUsbCdc(uint8_t device_num) {
|
||||
if (acHndlif == nullptr) {
|
||||
acHndlif = new AC_HNDLIF;
|
||||
memset(acHndlif->data, 0x0, sizeof(acHndlif->data));
|
||||
@@ -451,14 +469,16 @@ namespace games::iidx {
|
||||
log_info("bi2x_hook", "AIO_NMGR_IOB::BeginManage");
|
||||
};
|
||||
|
||||
static AIO_NMGR_IOB2** __fastcall aioNMgrIob2_Create(AC_HNDLIF *a1, unsigned int a2) {
|
||||
static AIO_NMGR_IOB2* __fastcall aioNMgrIob2_Create(AC_HNDLIF *a1, unsigned int a2) {
|
||||
if (aioNmgrIob2 == nullptr) {
|
||||
aioNmgrIob2 = new AIO_NMGR_IOB2;
|
||||
memset(aioNmgrIob2, 0x0, sizeof(AIO_NMGR_IOB2));
|
||||
aioNmgrIob2->pAIO_NMGR_IOB_BeginManage = AIO_NMGR_IOB_BeginManageStub;
|
||||
aioNmgrIob2 = new AIO_NMGR_IOB2{};
|
||||
aioNmgrIob2->vptr = new AIO_NMGR_IOB2_VTABLE{};
|
||||
aioNmgrIob2->vptr->pAIO_NMGR_IOB_BeginManage = AIO_NMGR_IOB_BeginManageStub;
|
||||
}
|
||||
log_info("bi2x_hook", "aioNMgrIob2_Create");
|
||||
return &aioNmgrIob2;
|
||||
log_info("bi2x_hook", "aioNMgrIob2_Create returned {}, size=0x{:x}, vptr @ {}",
|
||||
fmt::ptr(aioNmgrIob2), sizeof(*aioNmgrIob2), fmt::ptr(aioNmgrIob2->vptr));
|
||||
|
||||
return aioNmgrIob2;
|
||||
}
|
||||
|
||||
static int64_t __fastcall aioIob2Bi2x_WriteFirmGetState(int64_t a1) {
|
||||
@@ -515,10 +535,12 @@ namespace games::iidx {
|
||||
// hook IOB
|
||||
const auto libaioIobDll = "libaio-iob.dll";
|
||||
detour::trampoline_try(libaioIobDll, "aioIob2Bi2x_OpenSciUsbCdc",
|
||||
aioIob2Bi2X_OpenSciUsbCdc, &aioIob2Bi2x_OpenSciUsbCdc_orig);
|
||||
aioIob2Bi2x_OpenSciUsbCdc, &aioIob2Bi2x_OpenSciUsbCdc_orig);
|
||||
detour::trampoline_try(libaioIobDll, "aioIob2Bi2x_WriteFirmGetState",
|
||||
aioIob2Bi2x_WriteFirmGetState, &aioIob2Bi2x_WriteFirmGetState_orig);
|
||||
detour::trampoline_try(libaioIobDll, "aioNMgrIob2_Create",
|
||||
aioNMgrIob2_Create, &aioNMgrIob2_Create_orig);
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
#pragma once
|
||||
|
||||
#if SPICE64
|
||||
|
||||
namespace games::iidx {
|
||||
|
||||
void bi2x_hook_init();
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
@@ -29,6 +29,8 @@
|
||||
#include "util/libutils.h"
|
||||
#include "util/memutils.h"
|
||||
#include "util/sigscan.h"
|
||||
#include "util/socd_cleaner.h"
|
||||
#include "util/time.h"
|
||||
#include "util/utils.h"
|
||||
#include "launcher/signal.h"
|
||||
|
||||
@@ -79,6 +81,11 @@ namespace games::iidx {
|
||||
char IIDXIO_LED_TICKER[10] = {' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', '\x00'};
|
||||
bool IIDXIO_LED_TICKER_READONLY = false;
|
||||
std::mutex IIDX_LED_TICKER_LOCK;
|
||||
bool IIDX_TDJ_MONITOR_WARNING = false;
|
||||
iidx_aio_emulation_state CURRENT_IO_EMULATION_STATE = iidx_aio_emulation_state::unknown;
|
||||
|
||||
// io
|
||||
static bool HAS_LIBAIO; // this is how we detect iidx27+
|
||||
|
||||
tapeledutils::tape_led TAPELED_MAPPING[IIDX_TAPELED_TOTAL] = {
|
||||
{ 19, Lights::StageLeftAvgR, Lights::StageLeftAvgG, Lights::StageLeftAvgB, "Stage Left" },
|
||||
@@ -247,6 +254,8 @@ namespace games::iidx {
|
||||
}
|
||||
}
|
||||
|
||||
#if SPICE64
|
||||
|
||||
typedef void* (*aioIob2Bi2x_CreateWriteFirmContext_t)(unsigned int, int);
|
||||
static aioIob2Bi2x_CreateWriteFirmContext_t aioIob2Bi2x_CreateWriteFirmContext_orig = nullptr;
|
||||
|
||||
@@ -265,6 +274,8 @@ namespace games::iidx {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
IIDXGame::IIDXGame() : Game("Beatmania IIDX") {
|
||||
logger::hook_add(log_hook, this);
|
||||
}
|
||||
@@ -311,13 +322,16 @@ namespace games::iidx {
|
||||
memcpy(settings2.interface_detail, interface_detail2, sizeof(interface_detail2));
|
||||
setupapihook_add(settings2);
|
||||
|
||||
// IIDX 25-27 BIO2 BI2A input device
|
||||
// IIDX 25+ BIO2 BI2A input device
|
||||
devicehook_add(new IIDXFMSerialHandle());
|
||||
}
|
||||
|
||||
#if SPICE64
|
||||
|
||||
// check for new I/O DLL
|
||||
auto aio = libutils::try_library("libaio.dll");
|
||||
if (aio != nullptr) {
|
||||
HAS_LIBAIO = true;
|
||||
|
||||
// check TDJ mode
|
||||
TDJ_MODE |= fileutils::text_read("C:\\000rom.txt") == "TDJ-JA";
|
||||
@@ -379,6 +393,8 @@ namespace games::iidx {
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
if (hooks::audio::ENABLED) {
|
||||
apply_audio_hacks();
|
||||
} else {
|
||||
@@ -438,7 +454,7 @@ namespace games::iidx {
|
||||
}
|
||||
|
||||
// windowed subscreen, enabled by default, unless turned off by user
|
||||
if (GRAPHICS_WINDOWED && !options->at(launcher::Options::spice2x_IIDXNoSub).value_bool()) {
|
||||
if (GRAPHICS_WINDOWED && !options->at(launcher::Options::spice2x_IIDXNoSub).value_bool() && TDJ_MODE) {
|
||||
GRAPHICS_IIDX_WSUB = true;
|
||||
}
|
||||
|
||||
@@ -471,9 +487,8 @@ namespace games::iidx {
|
||||
}
|
||||
|
||||
// check -monitor + TDJ mode
|
||||
if (!GRAPHICS_WINDOWED &&
|
||||
options->at(launcher::Options::DisplayAdapter).is_active() &&
|
||||
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"
|
||||
@@ -495,6 +510,16 @@ namespace games::iidx {
|
||||
" monitor in Windows settings before launching the game"
|
||||
});
|
||||
}
|
||||
|
||||
// socd
|
||||
socd::ALGORITHM = socd::SocdAlgorithm::Neutral;
|
||||
if (options->at(launcher::Options::IIDXDigitalTTSocd).is_active()) {
|
||||
if (options->at(launcher::Options::IIDXDigitalTTSocd).value_text() == "last") {
|
||||
socd::ALGORITHM = socd::SocdAlgorithm::PreferRecent;
|
||||
} else if (options->at(launcher::Options::IIDXDigitalTTSocd).value_text() == "first") {
|
||||
socd::ALGORITHM = socd::SocdAlgorithm::PreferFirst;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void IIDXGame::detach() {
|
||||
@@ -718,13 +743,12 @@ namespace games::iidx {
|
||||
bool ttpm_alt = GameAPI::Buttons::getState(RI_MGR, buttons.at(
|
||||
player != 0 ? Buttons::P2_TTPlusMinusAlt : Buttons::P1_TTPlusMinusAlt));
|
||||
|
||||
// TT+
|
||||
if (ttp)
|
||||
const auto tt_socd = socd::socd_clean(player, ttm, ttp, get_performance_milliseconds());
|
||||
if (tt_socd == socd::SocdCW) {
|
||||
IIDXIO_TT_STATE[player] += change;
|
||||
|
||||
// TT-
|
||||
if (ttm)
|
||||
} else if (tt_socd == socd::SocdCCW) {
|
||||
IIDXIO_TT_STATE[player] -= change;
|
||||
}
|
||||
|
||||
// TT+/-
|
||||
bool ttpm_rising_edge = !IIDXIO_TT_PRESSED[player] && ttpm;
|
||||
@@ -895,4 +919,54 @@ namespace games::iidx {
|
||||
#endif
|
||||
|
||||
}
|
||||
|
||||
void update_io_emulation_state(iidx_aio_emulation_state state) {
|
||||
// <iidx27, don't care, since tdj wasn't a thing before
|
||||
// this is also how we detect that iidx module is attached
|
||||
if (!HAS_LIBAIO) {
|
||||
return;
|
||||
}
|
||||
|
||||
// in iidx27-30, most DLL types can handle both, caching things in nvram and possibly end
|
||||
// up with wrong i/o (temporarily until nvram is initialized again)
|
||||
// to avoid false positives, only do this for iidx31+, since we know for sure that iidx31
|
||||
// has a clean 010/012 split
|
||||
if (avs::game::is_ext(0, 2023101000)) {
|
||||
return;
|
||||
}
|
||||
|
||||
CURRENT_IO_EMULATION_STATE = state;
|
||||
|
||||
// log_warning below could be turned into log_fatal in the future once we gain confidence
|
||||
// that this isn't triggering when it's not supposed to
|
||||
if (state == iidx_aio_emulation_state::bi2a_com2 && TDJ_MODE) {
|
||||
deferredlogs::defer_error_messages({
|
||||
"IIDX I/O mode conflict",
|
||||
" game is currently configured to use LDJ I/O;",
|
||||
" however, you turned on IIDX TDJ Mode (-iidxtdj)",
|
||||
" two options to fix this:",
|
||||
" For TDJ mode (120Hz): import patches and enable Force TDJ Mode patch",
|
||||
" For LDJ mode (60Hz): turn off IIDX TDJ Mode option",
|
||||
});
|
||||
log_warning(
|
||||
"iidx",
|
||||
"game is using LDJ I/O but spice TDJ mode is turned on! "
|
||||
"enable Force TDJ I/O patch, OR turn off -iidxtdj");
|
||||
} else if (state == iidx_aio_emulation_state::bi2x_hook && !TDJ_MODE) {
|
||||
deferredlogs::defer_error_messages({
|
||||
"IIDX I/O mode conflict",
|
||||
" game is currently configured to use TDJ I/O;",
|
||||
" however, you turned off IIDX TDJ Mode (-iidxtdj)",
|
||||
" two options to fix this:",
|
||||
" For TDJ mode (120Hz): turn on IIDX TDJ Mode option",
|
||||
" For LDJ mode (60Hz): import patches and enable Force LDJ Mode patch",
|
||||
});
|
||||
log_warning(
|
||||
"iidx",
|
||||
"game is using TDJ I/O but spice TDJ mode is turned off! "
|
||||
"turn on -iidxtdj, OR enable Force LDJ I/O patch");
|
||||
} else {
|
||||
log_misc("iidx", "i/o mode validation passed");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,6 +11,12 @@
|
||||
|
||||
namespace games::iidx {
|
||||
|
||||
enum class iidx_aio_emulation_state {
|
||||
unknown,
|
||||
bi2a_com2,
|
||||
bi2x_hook
|
||||
};
|
||||
|
||||
// settings
|
||||
|
||||
extern bool FLIP_CAMS;
|
||||
@@ -33,10 +39,12 @@ namespace games::iidx {
|
||||
extern char IIDXIO_LED_TICKER[10];
|
||||
extern bool IIDXIO_LED_TICKER_READONLY;
|
||||
extern std::mutex IIDX_LED_TICKER_LOCK;
|
||||
extern bool IIDX_TDJ_MONITOR_WARNING;
|
||||
|
||||
constexpr int IIDX_TAPELED_TOTAL = 17;
|
||||
// data mapping
|
||||
extern tapeledutils::tape_led TAPELED_MAPPING[IIDX_TAPELED_TOTAL];
|
||||
extern iidx_aio_emulation_state CURRENT_IO_EMULATION_STATE;
|
||||
|
||||
class IIDXGame : public games::Game {
|
||||
public:
|
||||
@@ -62,4 +70,6 @@ namespace games::iidx {
|
||||
const char* get_16seg();
|
||||
bool is_tdj_fhd();
|
||||
void apply_audio_hacks();
|
||||
|
||||
void update_io_emulation_state(iidx_aio_emulation_state state);
|
||||
}
|
||||
|
||||
@@ -47,6 +47,7 @@ namespace games {
|
||||
static robin_hood::unordered_map<std::string, std::vector<Button>> buttons_keypads;
|
||||
static robin_hood::unordered_map<std::string, std::vector<Button>> buttons_overlay;
|
||||
static robin_hood::unordered_map<std::string, std::string> buttons_help;
|
||||
static robin_hood::unordered_map<std::string, std::string> analogs_help;
|
||||
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;
|
||||
@@ -107,6 +108,7 @@ namespace games {
|
||||
games.push_back(gitadora);
|
||||
buttons.insert({ gitadora, gitadora::get_buttons() });
|
||||
buttons_help.insert({ gitadora, gitadora::get_buttons_help() });
|
||||
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");
|
||||
@@ -322,7 +324,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_Data");
|
||||
file_hints[pc].emplace_back("game/svm.exe");
|
||||
}
|
||||
|
||||
const std::vector<std::string> &get_games() {
|
||||
@@ -349,6 +351,15 @@ namespace games {
|
||||
return it->second;
|
||||
}
|
||||
|
||||
std::string get_analogs_help(const std::string &game) {
|
||||
initialize();
|
||||
auto it = analogs_help.find(game);
|
||||
if (it == analogs_help.end()) {
|
||||
return "";
|
||||
}
|
||||
return it->second;
|
||||
}
|
||||
|
||||
static std::vector<Button> gen_buttons_keypads(const std::string &game) {
|
||||
auto buttons = GameAPI::Buttons::getButtons(game);
|
||||
std::vector<std::string> names;
|
||||
|
||||
@@ -58,6 +58,7 @@ namespace games {
|
||||
const std::vector<std::string> &get_games();
|
||||
std::vector<Button> *get_buttons(const std::string &game);
|
||||
std::string get_buttons_help(const std::string &game);
|
||||
std::string get_analogs_help(const std::string &game);
|
||||
std::vector<Button> *get_buttons_keypads(const std::string &game);
|
||||
std::vector<Button> *get_buttons_overlay(const std::string &game);
|
||||
std::vector<Analog> *get_analogs(const std::string &game);
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
#include "bi2x_hook.h"
|
||||
|
||||
#if SPICE64
|
||||
|
||||
#include "util/execexe.h"
|
||||
#include "util/logging.h"
|
||||
#include "rawinput/rawinput.h"
|
||||
@@ -9,9 +11,10 @@
|
||||
#include "util/tapeled.h"
|
||||
#include "pc.h"
|
||||
#include "util/utils.h"
|
||||
#include "util/time.h"
|
||||
#include "util/socd_cleaner.h"
|
||||
|
||||
namespace games::pc {
|
||||
bool PC_KNOB_MODE = false;
|
||||
|
||||
constexpr float RELATIVE_DECAY = 0.02f;
|
||||
struct FADER_RELATIVE {
|
||||
@@ -75,18 +78,22 @@ namespace games::pc {
|
||||
*/
|
||||
|
||||
struct AIO_SCI_COMM {
|
||||
uint8_t data[0xf8];
|
||||
};
|
||||
|
||||
struct AIO_NMGR_IOB2 {
|
||||
uint8_t data[0xe30];
|
||||
};
|
||||
|
||||
struct AIO_IOB2_BI2X_AC1 {
|
||||
uint8_t data[0x4538];
|
||||
};
|
||||
|
||||
struct AIO_IOB2_BI2X_AC1__SETTING {
|
||||
};
|
||||
|
||||
struct AIO_IOB2_BI2X_WRFIRM {
|
||||
uint8_t data[0x20f48];
|
||||
};
|
||||
|
||||
struct AIO_IOB2_BI2X_AC1__INPUT
|
||||
@@ -191,6 +198,12 @@ namespace games::pc {
|
||||
AIO_IOB2_BI2X_AC1__ICNPIN ICnPinHist[20];
|
||||
};
|
||||
|
||||
// verified with XIF-2024122300
|
||||
static_assert(sizeof(AIO_SCI_COMM) == 0xf8);
|
||||
static_assert(sizeof(AIO_NMGR_IOB2) == 0xe30);
|
||||
static_assert(sizeof(AIO_IOB2_BI2X_AC1) == 0x4538);
|
||||
static_assert(sizeof(AIO_IOB2_BI2X_WRFIRM) == 0x20f48);
|
||||
|
||||
/*
|
||||
* typedefs
|
||||
*/
|
||||
@@ -338,14 +351,21 @@ namespace games::pc {
|
||||
val = (GameAPI::Analogs::getState(RI_MGR, analogs[Analogs::FaderL]) - 0.5f) * 2;
|
||||
}
|
||||
}
|
||||
if (GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::FaderL_Left]) &&
|
||||
GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::FaderL_Right])) {
|
||||
val = 0.f;
|
||||
} else if (GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::FaderL_Left])) {
|
||||
|
||||
const auto now = get_performance_milliseconds();
|
||||
|
||||
const auto fader_l_socd = socd::socd_clean(
|
||||
0, // Fader-L
|
||||
GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::FaderL_Left]),
|
||||
GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::FaderL_Right]),
|
||||
now);
|
||||
|
||||
if (fader_l_socd == socd::SocdCCW) {
|
||||
val = -1.0f;
|
||||
} else if (GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::FaderL_Right])) {
|
||||
} else if (fader_l_socd == socd::SocdCW) {
|
||||
val = +1.0f;
|
||||
}
|
||||
|
||||
o_DevStatus->Input.CN15_7 = (val < 0.2f) ? 0xFF : 0;
|
||||
o_DevStatus->Input.CN15_6 = (val > -0.85f && val < 0.35f) ? 0xFF : 0;
|
||||
o_DevStatus->Input.CN15_5 = (val > -0.6f && val < 0.6f) ? 0xFF : 0;
|
||||
@@ -363,14 +383,19 @@ namespace games::pc {
|
||||
val = (GameAPI::Analogs::getState(RI_MGR, analogs[Analogs::FaderR]) - 0.5f) * 2;
|
||||
}
|
||||
}
|
||||
if (GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::FaderR_Left]) &&
|
||||
GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::FaderR_Right])) {
|
||||
val = 0.f;
|
||||
} else if (GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::FaderR_Left])) {
|
||||
|
||||
const auto fader_r_socd = socd::socd_clean(
|
||||
1, // Fader-R
|
||||
GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::FaderR_Left]),
|
||||
GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::FaderR_Right]),
|
||||
now);
|
||||
|
||||
if (fader_r_socd == socd::SocdCCW) {
|
||||
val = -1.0f;
|
||||
} else if (GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::FaderR_Right])) {
|
||||
} else if (fader_r_socd == socd::SocdCW) {
|
||||
val = +1.0f;
|
||||
}
|
||||
|
||||
o_DevStatus->Input.CN11_20 = (val < 0.2f) ? 0xFF : 0;
|
||||
o_DevStatus->Input.CN11_19 = (val > -0.85f && val < 0.35f) ? 0xFF : 0;
|
||||
o_DevStatus->Input.CN9_10 = (val > -0.6f && val < 0.6f) ? 0xFF : 0;
|
||||
@@ -737,3 +762,5 @@ namespace games::pc {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -1,5 +1,9 @@
|
||||
#pragma once
|
||||
|
||||
#if SPICE64
|
||||
|
||||
namespace games::pc {
|
||||
void bi2x_hook_init();
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
@@ -12,10 +12,12 @@
|
||||
#include "misc/wintouchemu.h"
|
||||
#include "hooks/graphics/graphics.h"
|
||||
#include "rawinput/rawinput.h"
|
||||
#include "util/socd_cleaner.h"
|
||||
|
||||
namespace games::pc {
|
||||
std::string PC_INJECT_ARGS = "";
|
||||
bool PC_NO_IO = false;
|
||||
bool PC_KNOB_MODE = false;
|
||||
|
||||
static acioemu::ACIOHandle *acioHandle = nullptr;
|
||||
static std::wstring portName = L"COM1";
|
||||
@@ -61,6 +63,11 @@ namespace games::pc {
|
||||
return ERROR_NOT_SUPPORTED;
|
||||
}
|
||||
|
||||
void PCGame::pre_attach() {
|
||||
// SOCD
|
||||
socd::ALGORITHM = socd::SocdAlgorithm::PreferRecent;
|
||||
}
|
||||
|
||||
void PCGame::attach() {
|
||||
Game::attach();
|
||||
|
||||
@@ -78,9 +85,14 @@ namespace games::pc {
|
||||
execexe::load_library("libaio-iob2_video.dll");
|
||||
execexe::load_library("win10actlog.dll");
|
||||
|
||||
#if SPICE64
|
||||
|
||||
if (!PC_NO_IO) {
|
||||
bi2x_hook_init();
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
});
|
||||
|
||||
const auto user32Dll = "user32.dll";
|
||||
|
||||
@@ -11,6 +11,7 @@ namespace games::pc {
|
||||
public:
|
||||
PCGame() : Game("Polaris Chord") {}
|
||||
|
||||
virtual void pre_attach() override;
|
||||
virtual void attach() override;
|
||||
virtual void detach() override;
|
||||
};
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
#include "bi2x_hook.h"
|
||||
|
||||
#if SPICE64
|
||||
|
||||
#include "util/detour.h"
|
||||
#include "util/logging.h"
|
||||
#include "rawinput/rawinput.h"
|
||||
@@ -15,15 +17,19 @@ namespace games::qks {
|
||||
*/
|
||||
|
||||
struct AIO_SCI_COMM {
|
||||
uint8_t data[0xf8];
|
||||
};
|
||||
|
||||
struct AIO_NMGR_IOB {
|
||||
uint8_t data[0xa00];
|
||||
};
|
||||
|
||||
struct AIO_IOB2_BI2X_AC1 {
|
||||
uint8_t data[0x3908];
|
||||
};
|
||||
|
||||
struct AIO_IOB2_BI2X_WRFIRM {
|
||||
uint8_t data[0x20f48];
|
||||
};
|
||||
|
||||
struct AIO_NMGR_IOB__NODEINFO {
|
||||
@@ -174,6 +180,12 @@ namespace games::qks {
|
||||
AIO_IOB2_BI2X_AC1__SETTING Setting;
|
||||
};
|
||||
|
||||
// verified with UKS-001-2024030600
|
||||
static_assert(sizeof(AIO_IOB2_BI2X_AC1) == 0x3908);
|
||||
static_assert(sizeof(AIO_SCI_COMM) == 0xf8);
|
||||
static_assert(sizeof(AIO_NMGR_IOB) == 0xa00);
|
||||
static_assert(sizeof(AIO_IOB2_BI2X_WRFIRM) == 0x20f48);
|
||||
|
||||
/*
|
||||
* typedefs
|
||||
*/
|
||||
@@ -602,4 +614,6 @@ namespace games::qks {
|
||||
detour::trampoline_try(libaioDll, "aioNodeCtl_IsError",
|
||||
aioNodeCtl_IsError, &aioNodeCtl_IsError_orig);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
@@ -1,7 +1,11 @@
|
||||
#pragma once
|
||||
|
||||
#if SPICE64
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
namespace games::qks {
|
||||
void bi2x_hook_init();
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
@@ -86,9 +86,13 @@ namespace games::qks {
|
||||
detour::trampoline_try("execexe.dll", MAKEINTRESOURCE(9),
|
||||
execexe_CreateFileA_hook,&execexe_CreateFileA_orig);
|
||||
|
||||
#if SPICE64
|
||||
|
||||
// insert BI2X hooks
|
||||
bi2x_hook_init();
|
||||
|
||||
#endif
|
||||
|
||||
// add card reader
|
||||
acioHandle = new acioemu::ACIOHandle(portName.c_str());
|
||||
devicehook_init_trampoline();
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
#include "bi2x_hook.h"
|
||||
|
||||
#if SPICE64
|
||||
|
||||
#include <cstdint>
|
||||
#include "util/detour.h"
|
||||
#include "util/logging.h"
|
||||
@@ -10,12 +12,22 @@
|
||||
#include "launcher/options.h"
|
||||
#include "io.h"
|
||||
#include "games/sdvx/sdvx.h"
|
||||
#include "util/socd_cleaner.h"
|
||||
#include "util/tapeled.h"
|
||||
#include "util/time.h"
|
||||
#include "acioemu/icca.h"
|
||||
|
||||
#define DEBUG_VERBOSE 0
|
||||
|
||||
#if DEBUG_VERBOSE
|
||||
#define log_debug(module, format_str, ...) logger::push( \
|
||||
LOG_FORMAT("M", module, format_str, ## __VA_ARGS__), logger::Style::GREY)
|
||||
#else
|
||||
#define log_debug(module, format_str, ...)
|
||||
#endif
|
||||
|
||||
namespace games::sdvx {
|
||||
constexpr bool BI2X_PASSTHROUGH = false;
|
||||
bool BI2X_INITIALIZED = false;
|
||||
|
||||
/*
|
||||
* class definitions
|
||||
@@ -26,20 +38,54 @@ namespace games::sdvx {
|
||||
uint8_t data[0x10];
|
||||
};
|
||||
|
||||
struct AIO_NMGR_IOB2 {
|
||||
uint8_t dummy0[0x50];
|
||||
struct AIO_NMGR_IOB2_VTABLE {
|
||||
// other functions here, but they never get called
|
||||
uint8_t dummy[0x50];
|
||||
void (__fastcall *pAIO_NMGR_IOB_BeginManage)(int64_t a1);
|
||||
uint8_t dummy1[0x9A0];
|
||||
};
|
||||
|
||||
struct AIO_NMGR_IOB2 {
|
||||
AIO_NMGR_IOB2_VTABLE *vptr;
|
||||
uint8_t dummy[0x9F0];
|
||||
};
|
||||
|
||||
// confirmed in EG final in aioNMgrIob2_Create
|
||||
static_assert(sizeof(AIO_NMGR_IOB2) == 0x9F8);
|
||||
|
||||
struct AIO_IOB2_BI2X_UFC {
|
||||
// who knows
|
||||
uint8_t data[0x13F8];
|
||||
uint8_t data[0x39D8];
|
||||
};
|
||||
|
||||
// confirmed in EG final in aioIob2Bi2xUFC_Create
|
||||
static_assert(sizeof(AIO_IOB2_BI2X_UFC) == 0x39D8);
|
||||
|
||||
struct AIO_IOB2_BI2X_UFC__INPUT {
|
||||
uint8_t DevIoCounter;
|
||||
uint8_t bExIoAErr;
|
||||
uint8_t bExIoBErr;
|
||||
uint8_t bPcPowerOn;
|
||||
uint8_t bPcPowerCheck;
|
||||
uint8_t CoinCount;
|
||||
uint8_t bTest;
|
||||
uint8_t bService;
|
||||
uint8_t bCoinSw;
|
||||
uint8_t bCoinJam;
|
||||
uint8_t bHPDetect;
|
||||
};
|
||||
|
||||
struct AIO_IOB2_BI2X_UFC__DEVSTATUS {
|
||||
// of course you could work with variables here
|
||||
uint8_t buffer[0x19E];
|
||||
uint8_t InputCounter;
|
||||
uint8_t OutputCounter;
|
||||
uint8_t IoResetCounter;
|
||||
uint8_t TapeLedCounter;
|
||||
uint8_t TapeLedRate[8];
|
||||
AIO_IOB2_BI2X_UFC__INPUT Input;
|
||||
uint8_t unk_1[289];
|
||||
uint16_t AnalogLeft;
|
||||
uint16_t AnalogRight;
|
||||
uint8_t Buttons;
|
||||
uint8_t unk_2[97];
|
||||
};
|
||||
|
||||
/*
|
||||
@@ -66,7 +112,7 @@ namespace games::sdvx {
|
||||
// libaio-iob.dll
|
||||
typedef AC_HNDLIF* (__fastcall *aioIob2Bi2x_OpenSciUsbCdc_t)(uint8_t device_num);
|
||||
typedef int64_t (__fastcall *aioIob2Bi2x_WriteFirmGetState_t)(int64_t a1);
|
||||
typedef AIO_NMGR_IOB2** (__fastcall *aioNMgrIob2_Create_t)(AC_HNDLIF *a1, unsigned int a2);
|
||||
typedef AIO_NMGR_IOB2* (__fastcall *aioNMgrIob2_Create_t)(AC_HNDLIF *a1, unsigned int a2);
|
||||
|
||||
/*
|
||||
* function pointers
|
||||
@@ -133,8 +179,8 @@ namespace games::sdvx {
|
||||
AIO_IOB2_BI2X_UFC__GetDeviceStatus_orig(This, status);
|
||||
}
|
||||
|
||||
status->buffer[0] = count;
|
||||
status->buffer[12] = count;
|
||||
status->InputCounter = count;
|
||||
status->Input.DevIoCounter = count;
|
||||
count++;
|
||||
|
||||
// get buttons
|
||||
@@ -142,41 +188,51 @@ namespace games::sdvx {
|
||||
|
||||
// control buttons
|
||||
if (GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::Test]))
|
||||
status->buffer[18] = 0x01;
|
||||
status->Input.bTest = 0x01;
|
||||
if (GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::Service]))
|
||||
status->buffer[19] = 0x01;
|
||||
status->Input.bService = 0x01;
|
||||
if (GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::CoinMech]))
|
||||
status->buffer[20] = 0x01;
|
||||
status->Input.bCoinSw = 0x01;
|
||||
if (GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::Start]))
|
||||
status->buffer[316] |= 0x01;
|
||||
status->Buttons |= 0x01;
|
||||
if (GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::BT_A]))
|
||||
status->buffer[316] |= 0x02;
|
||||
status->Buttons |= 0x02;
|
||||
if (GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::BT_B]))
|
||||
status->buffer[316] |= 0x04;
|
||||
status->Buttons |= 0x04;
|
||||
if (GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::BT_C]))
|
||||
status->buffer[316] |= 0x08;
|
||||
status->Buttons |= 0x08;
|
||||
if (GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::BT_D]))
|
||||
status->buffer[316] |= 0x10;
|
||||
status->Buttons |= 0x10;
|
||||
if (GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::FX_L]))
|
||||
status->buffer[316] |= 0x20;
|
||||
status->Buttons |= 0x20;
|
||||
if (GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::FX_R]))
|
||||
status->buffer[316] |= 0x40;
|
||||
status->Buttons |= 0x40;
|
||||
if (GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::Headphone]))
|
||||
status->buffer[22] = 0x01;
|
||||
status->Input.bHPDetect = 0x01;
|
||||
|
||||
status->Input.CoinCount += eamuse_coin_get_stock();
|
||||
|
||||
const auto now = get_performance_milliseconds();
|
||||
|
||||
// volume left
|
||||
if (GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::VOL_L_Left])) {
|
||||
const auto vol_l_state = socd::socd_clean(0,
|
||||
GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::VOL_L_Left]),
|
||||
GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::VOL_L_Right]),
|
||||
now);
|
||||
if (vol_l_state == socd::SocdCCW) {
|
||||
VOL_L -= ((uint16_t)DIGITAL_KNOB_SENS * 4);
|
||||
}
|
||||
if (GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::VOL_L_Right])) {
|
||||
} else if (vol_l_state == socd::SocdCW) {
|
||||
VOL_L += ((uint16_t)DIGITAL_KNOB_SENS * 4);
|
||||
}
|
||||
|
||||
// volume right
|
||||
if (GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::VOL_R_Left])) {
|
||||
const auto vol_r_state = socd::socd_clean(1,
|
||||
GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::VOL_R_Left]),
|
||||
GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::VOL_R_Right]),
|
||||
now);
|
||||
if (vol_r_state == socd::SocdCCW) {
|
||||
VOL_R -= ((uint16_t)DIGITAL_KNOB_SENS * 4);
|
||||
}
|
||||
if (GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::VOL_R_Right])) {
|
||||
} else if (vol_r_state == socd::SocdCW) {
|
||||
VOL_R += ((uint16_t)DIGITAL_KNOB_SENS * 4);
|
||||
}
|
||||
|
||||
@@ -189,8 +245,14 @@ namespace games::sdvx {
|
||||
vol_right += (uint16_t) (GameAPI::Analogs::getState(RI_MGR, analogs[Analogs::VOL_R]) * 65535);
|
||||
}
|
||||
|
||||
*((uint16_t*) &status->buffer[312]) = vol_left;
|
||||
*((uint16_t*) &status->buffer[314]) = vol_right;
|
||||
status->AnalogLeft = vol_left;
|
||||
status->AnalogRight = vol_right;
|
||||
|
||||
log_debug(
|
||||
"bi2x_hook",
|
||||
"knobs = {} {}",
|
||||
status->AnalogLeft,
|
||||
status->AnalogRight);
|
||||
}
|
||||
|
||||
static void __fastcall AIO_IOB2_BI2X_UFC__IoReset(AIO_IOB2_BI2X_UFC *This,
|
||||
@@ -345,21 +407,21 @@ namespace games::sdvx {
|
||||
log_info("bi2x_hook", "AIO_NMGR_IOB::BeginManage");
|
||||
}
|
||||
|
||||
static AIO_NMGR_IOB2** __fastcall aioNMgrIob2_Create(AC_HNDLIF *a1, unsigned int a2) {
|
||||
static AIO_NMGR_IOB2* __fastcall aioNMgrIob2_Create(AC_HNDLIF *a1, unsigned int a2) {
|
||||
if (aioNmgrIob2 == nullptr) {
|
||||
aioNmgrIob2 = new AIO_NMGR_IOB2;
|
||||
memset(aioNmgrIob2, 0x0, sizeof(AIO_NMGR_IOB2));
|
||||
aioNmgrIob2->pAIO_NMGR_IOB_BeginManage = AIO_NMGR_IOB_BeginManageStub;
|
||||
aioNmgrIob2 = new AIO_NMGR_IOB2{};
|
||||
aioNmgrIob2->vptr = new AIO_NMGR_IOB2_VTABLE{};
|
||||
aioNmgrIob2->vptr->pAIO_NMGR_IOB_BeginManage = AIO_NMGR_IOB_BeginManageStub;
|
||||
}
|
||||
log_info("bi2x_hook", "aioNMgrIob2_Create");
|
||||
BI2X_INITIALIZED = true;
|
||||
log_info("bi2x_hook", "aioNMgrIob2_Create returned {}, size=0x{:x}, vptr @ {}",
|
||||
fmt::ptr(aioNmgrIob2), sizeof(*aioNmgrIob2), fmt::ptr(aioNmgrIob2->vptr));
|
||||
|
||||
// enable hack to make PIN pad work for KFC in BI2X mode
|
||||
// this explicit check in the I/O init path is necessary
|
||||
// (as opposed to just doing a check for "isValkyrieCabMode?")
|
||||
// because there are hex edits that allow you to use legacy (KFC/BIO2) IO while in Valk mode
|
||||
acioemu::ICCA_DEVICE_HACK = true;
|
||||
return &aioNmgrIob2;
|
||||
return aioNmgrIob2;
|
||||
}
|
||||
|
||||
static int64_t __fastcall aioIob2Bi2x_WriteFirmGetState(int64_t a1) {
|
||||
@@ -411,3 +473,5 @@ namespace games::sdvx {
|
||||
aioNMgrIob2_Create, &aioNMgrIob2_Create_orig);
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -1,6 +1,10 @@
|
||||
#pragma once
|
||||
|
||||
#if SPICE64
|
||||
|
||||
namespace games::sdvx {
|
||||
|
||||
void bi2x_hook_init();
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -2,7 +2,6 @@
|
||||
|
||||
#include <external/robin_hood.h>
|
||||
|
||||
#include "avs/game.h"
|
||||
#include "games/shared/lcdhandle.h"
|
||||
#include "games/io.h"
|
||||
#include "hooks/audio/audio.h"
|
||||
@@ -19,6 +18,8 @@
|
||||
#include "util/detour.h"
|
||||
#include "util/logging.h"
|
||||
#include "util/sigscan.h"
|
||||
#include "util/socd_cleaner.h"
|
||||
#include "util/time.h"
|
||||
#include "util/libutils.h"
|
||||
#include "misc/wintouchemu.h"
|
||||
#include "misc/eamuse.h"
|
||||
@@ -48,21 +49,27 @@ namespace games::sdvx {
|
||||
SdvxOverlayPosition OVERLAY_POS = SDVX_OVERLAY_BOTTOM;
|
||||
bool ENABLE_COM_PORT_SCAN_HOOK = false;
|
||||
|
||||
std::optional<std::string> SOUND_OUTPUT_DEVICE = std::nullopt;
|
||||
bool USE_ASIO = false;
|
||||
std::optional<std::string> ASIO_DRIVER = std::nullopt;
|
||||
|
||||
// states
|
||||
bool SHOW_VM_MONITOR_WARNING = false;
|
||||
static HKEY real_asio_reg_handle = nullptr;
|
||||
static HKEY real_asio_device_reg_handle = nullptr;
|
||||
|
||||
static LONG WINAPI RegOpenKeyA_hook(HKEY hKey, LPCSTR lpSubKey, PHKEY phkResult) {
|
||||
if (lpSubKey != nullptr && phkResult != nullptr) {
|
||||
if (hKey == HKEY_LOCAL_MACHINE &&
|
||||
ASIO_DRIVER.has_value() &&
|
||||
_stricmp(lpSubKey, "software\\asio") == 0)
|
||||
{
|
||||
*phkResult = PARENT_ASIO_REG_HANDLE;
|
||||
if (lpSubKey != nullptr &&
|
||||
phkResult != nullptr &&
|
||||
hKey == HKEY_LOCAL_MACHINE &&
|
||||
_stricmp(lpSubKey, "software\\asio") == 0) {
|
||||
|
||||
// prevent the game from randomly picking an ASIO device
|
||||
if (!USE_ASIO) {
|
||||
return ERROR_FILE_NOT_FOUND;
|
||||
}
|
||||
// convince the game to use our hook (so we can swap in the preferred device)
|
||||
if (ASIO_DRIVER.has_value()) {
|
||||
*phkResult = PARENT_ASIO_REG_HANDLE;
|
||||
return RegOpenKeyA_orig(hKey, lpSubKey, &real_asio_reg_handle);
|
||||
}
|
||||
}
|
||||
@@ -109,7 +116,7 @@ namespace games::sdvx {
|
||||
lpSubKey != nullptr && phkResult != nullptr &&
|
||||
_stricmp(lpSubKey, "HARDWARE\\DEVICEMAP\\SERIALCOMM") == 0) {
|
||||
log_info("sdvx::io", "hook access to HKLM\\HARDWARE\\DEVICEMAP\\SERIALCOMM to force COM1 ICCA");
|
||||
return 2; // ERROR_FILE_NOT_FOUND
|
||||
return ERROR_FILE_NOT_FOUND;
|
||||
}
|
||||
|
||||
return RegOpenKeyExA_orig(hKey, lpSubKey, ulOptions, samDesired, phkResult);
|
||||
@@ -229,6 +236,32 @@ namespace games::sdvx {
|
||||
return false;
|
||||
}
|
||||
|
||||
void SDVXGame::detect_sound_output_device() {
|
||||
USE_ASIO = false;
|
||||
// if the user specified a value for -sdvxasio, use asio
|
||||
if (ASIO_DRIVER.has_value()) {
|
||||
log_misc(
|
||||
"sdvx",
|
||||
"-sdvxasio is set to \"{}\"", ASIO_DRIVER.value());
|
||||
USE_ASIO = true;
|
||||
} else {
|
||||
// see if XONAR AE is present; if so, use that
|
||||
HKEY subkey;
|
||||
LSTATUS result;
|
||||
result = RegOpenKey(HKEY_LOCAL_MACHINE, "SOFTWARE\\ASIO\\XONAR SOUND CARD(64)", &subkey);
|
||||
if (result == ERROR_SUCCESS) {
|
||||
RegCloseKey(subkey);
|
||||
USE_ASIO = true;
|
||||
log_misc(
|
||||
"sdvx",
|
||||
"found HKLM\\SOFTWARE\\ASIO\\XONAR SOUND CARD(64)");
|
||||
}
|
||||
}
|
||||
if (!USE_ASIO) {
|
||||
log_misc("sdvx", "asio not configured");
|
||||
}
|
||||
}
|
||||
|
||||
typedef void **(__fastcall *volume_set_t)(uint64_t, uint64_t, uint64_t);
|
||||
static volume_set_t volume_set_orig = nullptr;
|
||||
static void **__fastcall volume_set_hook(uint64_t vol_sound, uint64_t vol_woofer, uint64_t vol_headphone) {
|
||||
@@ -295,12 +328,9 @@ namespace games::sdvx {
|
||||
}
|
||||
|
||||
#ifdef SPICE64 // SDVX5+ specific code
|
||||
bool isValkyrieCabinetMode = avs::game::SPEC[0] == 'G' || avs::game::SPEC[0] == 'H';
|
||||
auto options = games::get_options(eamuse_get_game());
|
||||
// check -monitor + UFC mode
|
||||
if (!GRAPHICS_WINDOWED &&
|
||||
options->at(launcher::Options::DisplayAdapter).is_active() &&
|
||||
isValkyrieCabinetMode) {
|
||||
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"
|
||||
@@ -324,16 +354,29 @@ namespace games::sdvx {
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef SPICE64
|
||||
this->detect_sound_output_device();
|
||||
#endif
|
||||
|
||||
// SOCD
|
||||
auto options = games::get_options(eamuse_get_game());
|
||||
socd::ALGORITHM = socd::SocdAlgorithm::PreferRecent;
|
||||
if (options->at(launcher::Options::SDVXDigitalKnobSocd).is_active()) {
|
||||
if (options->at(launcher::Options::SDVXDigitalKnobSocd).value_text() == "neutral") {
|
||||
socd::ALGORITHM = socd::SocdAlgorithm::Neutral;
|
||||
} else if (options->at(launcher::Options::SDVXDigitalKnobSocd).value_text() == "last") {
|
||||
socd::ALGORITHM = socd::SocdAlgorithm::PreferRecent;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void SDVXGame::attach() {
|
||||
Game::attach();
|
||||
|
||||
#ifdef SPICE64 // SDVX5+ specific code
|
||||
bool isValkyrieCabinetMode = avs::game::SPEC[0] == 'G' || avs::game::SPEC[0] == 'H';
|
||||
|
||||
// LCD handle
|
||||
if (!isValkyrieCabinetMode) {
|
||||
if (!is_valkyrie_model()) {
|
||||
devicehook_init();
|
||||
devicehook_add(new games::shared::LCDHandle());
|
||||
}
|
||||
@@ -372,8 +415,7 @@ namespace games::sdvx {
|
||||
nvapi_hook::initialize(avs::game::DLL_INSTANCE);
|
||||
}
|
||||
|
||||
// check for Valkyrie cabinet mode
|
||||
if (isValkyrieCabinetMode) {
|
||||
if (is_valkyrie_model()) {
|
||||
// hook touch window
|
||||
// in windowed mode, game can accept mouse input on the second screen
|
||||
if (!NATIVETOUCH && !GRAPHICS_WINDOWED) {
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
#include <optional>
|
||||
#include <cstdint>
|
||||
|
||||
#include "avs/game.h"
|
||||
#include "games/game.h"
|
||||
|
||||
namespace games::sdvx {
|
||||
@@ -20,9 +21,18 @@ namespace games::sdvx {
|
||||
extern bool NATIVETOUCH;
|
||||
extern uint8_t DIGITAL_KNOB_SENS;
|
||||
extern std::optional<std::string> ASIO_DRIVER;
|
||||
extern bool BI2X_INITIALIZED;
|
||||
extern SdvxOverlayPosition OVERLAY_POS;
|
||||
|
||||
// states
|
||||
extern bool SHOW_VM_MONITOR_WARNING;
|
||||
|
||||
static inline bool is_valkyrie_model() {
|
||||
return (
|
||||
avs::game::is_model("KFC") &&
|
||||
(avs::game::SPEC[0] == 'G' || avs::game::SPEC[0] == 'H')
|
||||
);
|
||||
}
|
||||
|
||||
class SDVXGame : public games::Game {
|
||||
public:
|
||||
SDVXGame();
|
||||
@@ -30,5 +40,7 @@ namespace games::sdvx {
|
||||
virtual void attach() override;
|
||||
virtual void post_attach() override;
|
||||
virtual void detach() override;
|
||||
private:
|
||||
void detect_sound_output_device();
|
||||
};
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
#include "hooks/audio/audio_private.h"
|
||||
#include "hooks/audio/backends/mmdevice/audio_endpoint_volume.h"
|
||||
#include "hooks/audio/backends/wasapi/audio_client.h"
|
||||
#include "util/utils.h"
|
||||
|
||||
#define PRINT_FAILED_RESULT(name, ret) \
|
||||
do { \
|
||||
@@ -56,6 +57,7 @@ ULONG STDMETHODCALLTYPE WrappedIMMDevice::Release() {
|
||||
ULONG refs = pReal != nullptr ? pReal->Release() : 0;
|
||||
|
||||
if (refs == 0) {
|
||||
log_misc("audio::mmdevice", "WrappedIMMDevice::Release");
|
||||
delete this;
|
||||
}
|
||||
|
||||
@@ -69,7 +71,7 @@ HRESULT STDMETHODCALLTYPE WrappedIMMDevice::Activate(
|
||||
PROPVARIANT *pActivationParams,
|
||||
void **ppInterface)
|
||||
{
|
||||
log_misc("audio::mmdevice", "WrappedIMMDevice::Activate");
|
||||
log_misc("audio::mmdevice", "WrappedIMMDevice::Activate {}", guid2s(iid));
|
||||
|
||||
// call original
|
||||
HRESULT ret = pReal->Activate(iid, dwClsCtx, pActivationParams, ppInterface);
|
||||
@@ -80,7 +82,10 @@ HRESULT STDMETHODCALLTYPE WrappedIMMDevice::Activate(
|
||||
return ret;
|
||||
}
|
||||
|
||||
if (iid == IID_IAudioClient) {
|
||||
// almost all games request IAudioClient
|
||||
// so far we have not seen any games request IAudioClient2
|
||||
// SDVX EG Final uses IID_IAudioClient3, but only when shared mode patch is on
|
||||
if (iid == IID_IAudioClient || iid == IID_IAudioClient3) {
|
||||
|
||||
// prevent initialization recursion when using some ASIO backends that proxy to DirectSound, WASAPI, or WDM
|
||||
// like ASIO4All or FlexASIO
|
||||
@@ -90,23 +95,18 @@ HRESULT STDMETHODCALLTYPE WrappedIMMDevice::Activate(
|
||||
}
|
||||
std::lock_guard initialize_guard(hooks::audio::INITIALIZE_LOCK, std::adopt_lock);
|
||||
|
||||
auto client = reinterpret_cast<IAudioClient *>(*ppInterface);
|
||||
|
||||
// release old audio client if initialized
|
||||
if (hooks::audio::CLIENT) {
|
||||
hooks::audio::CLIENT->Release();
|
||||
}
|
||||
|
||||
/*
|
||||
ret = wrap_audio_client(pReal, dwClsCtx, pActivationParams, &client);
|
||||
if (FAILED(ret)) {
|
||||
return ret;
|
||||
IAudioClient *client = nullptr;
|
||||
if (iid == IID_IAudioClient) {
|
||||
client = wrap_audio_client(reinterpret_cast<IAudioClient *>(*ppInterface));
|
||||
} else { // IID_IAudioClient3
|
||||
client = wrap_audio_client3(reinterpret_cast<IAudioClient3 *>(*ppInterface));
|
||||
}
|
||||
*/
|
||||
client = wrap_audio_client(client);
|
||||
|
||||
*ppInterface = client;
|
||||
|
||||
// persist the audio client
|
||||
hooks::audio::CLIENT = client;
|
||||
hooks::audio::CLIENT->AddRef();
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
#include "device_collection.h"
|
||||
#include "device.h"
|
||||
#include "util/utils.h"
|
||||
#include "util/logging.h"
|
||||
|
||||
HRESULT STDMETHODCALLTYPE WrappedIMMDeviceCollection::QueryInterface(REFIID riid, void **ppvObj) {
|
||||
if (ppvObj == nullptr) {
|
||||
return E_POINTER;
|
||||
}
|
||||
if (riid == __uuidof(IMMDeviceCollection)) {
|
||||
this->AddRef();
|
||||
*ppvObj = this;
|
||||
return S_OK;
|
||||
}
|
||||
return pReal->QueryInterface(riid, ppvObj);
|
||||
}
|
||||
|
||||
ULONG STDMETHODCALLTYPE WrappedIMMDeviceCollection::AddRef() {
|
||||
return pReal->AddRef();
|
||||
}
|
||||
|
||||
ULONG STDMETHODCALLTYPE WrappedIMMDeviceCollection::Release() {
|
||||
// get reference count of underlying interface
|
||||
ULONG refs = pReal != nullptr ? pReal->Release() : 0;
|
||||
if (refs == 0) {
|
||||
delete this;
|
||||
}
|
||||
return refs;
|
||||
}
|
||||
|
||||
HRESULT STDMETHODCALLTYPE WrappedIMMDeviceCollection::GetCount(UINT *pcDevices) {
|
||||
return pReal->GetCount(pcDevices);
|
||||
}
|
||||
|
||||
HRESULT STDMETHODCALLTYPE WrappedIMMDeviceCollection::Item(UINT nDevice, IMMDevice **ppDevice) {
|
||||
log_info("audio", "WrappedIMMDeviceCollection::Item[{}]", nDevice);
|
||||
|
||||
// call original
|
||||
const auto hr = pReal->Item(nDevice, ppDevice);
|
||||
|
||||
// wrap interface
|
||||
*ppDevice = new WrappedIMMDevice(*ppDevice);
|
||||
return hr;
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
#pragma once
|
||||
|
||||
#include <initguid.h>
|
||||
#include <mmdeviceapi.h>
|
||||
|
||||
struct WrappedIMMDeviceCollection : IMMDeviceCollection {
|
||||
explicit WrappedIMMDeviceCollection(IMMDeviceCollection *orig) : pReal(orig) {
|
||||
}
|
||||
|
||||
WrappedIMMDeviceCollection(const WrappedIMMDeviceCollection &) = delete;
|
||||
WrappedIMMDeviceCollection &operator=(const WrappedIMMDeviceCollection &) = delete;
|
||||
|
||||
virtual ~WrappedIMMDeviceCollection() = default;
|
||||
|
||||
#pragma region IUnknown
|
||||
virtual HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, void **ppvObj) override;
|
||||
virtual ULONG STDMETHODCALLTYPE AddRef() override;
|
||||
virtual ULONG STDMETHODCALLTYPE Release() override;
|
||||
#pragma endregion
|
||||
|
||||
#pragma region IMMDeviceCollection
|
||||
virtual HRESULT STDMETHODCALLTYPE GetCount(UINT *pcDevices) override;
|
||||
virtual HRESULT STDMETHODCALLTYPE Item(UINT nDevice, IMMDevice **ppDevice) override;
|
||||
#pragma endregion
|
||||
|
||||
private:
|
||||
IMMDeviceCollection *const pReal;
|
||||
};
|
||||
@@ -1,4 +1,5 @@
|
||||
#include "device_enumerator.h"
|
||||
#include "device_collection.h"
|
||||
#include "device.h"
|
||||
#include "util/utils.h"
|
||||
|
||||
@@ -42,7 +43,11 @@ HRESULT STDMETHODCALLTYPE WrappedIMMDeviceEnumerator::EnumAudioEndpoints(
|
||||
DWORD dwStateMask,
|
||||
IMMDeviceCollection **ppDevices)
|
||||
{
|
||||
return pReal->EnumAudioEndpoints(dataFlow, dwStateMask, ppDevices);
|
||||
const auto hr = pReal->EnumAudioEndpoints(dataFlow, dwStateMask, ppDevices);
|
||||
if (SUCCEEDED(hr) && (ppDevices != nullptr) && (*ppDevices != nullptr)) {
|
||||
*ppDevices = new WrappedIMMDeviceCollection(*ppDevices);
|
||||
}
|
||||
return hr;
|
||||
}
|
||||
|
||||
HRESULT STDMETHODCALLTYPE WrappedIMMDeviceEnumerator::GetDefaultAudioEndpoint(
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
#include "hooks/audio/implementations/asio.h"
|
||||
#include "hooks/audio/implementations/wave_out.h"
|
||||
//#include "util/co_task_mem_ptr.h"
|
||||
#include "util/utils.h"
|
||||
|
||||
#include "defs.h"
|
||||
#include "dummy_audio_client.h"
|
||||
@@ -40,114 +41,9 @@ static void fix_rec_format(WAVEFORMATEX *pFormat) {
|
||||
pFormat->nAvgBytesPerSec = pFormat->nSamplesPerSec * pFormat->nBlockAlign;
|
||||
}
|
||||
|
||||
// TODO(felix): is it appropriate to automatically switch to shared mode? should we do a
|
||||
// `MessageBox` to notify the user?
|
||||
/*
|
||||
static bool check_for_exclusive_access(IAudioClient *client) {
|
||||
static bool checked_once = false;
|
||||
static bool previous_check_result = false;
|
||||
|
||||
CoTaskMemPtr<WAVEFORMATEX> mix_format;
|
||||
REFERENCE_TIME requested_duration = 0;
|
||||
|
||||
if (checked_once) {
|
||||
return previous_check_result;
|
||||
}
|
||||
if (audio::BACKEND.has_value()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// scope function so it has access to the local static variables
|
||||
auto set_result = [](bool result) {
|
||||
checked_once = true;
|
||||
previous_check_result = result;
|
||||
|
||||
return result;
|
||||
};
|
||||
|
||||
HRESULT ret = client->GetMixFormat(mix_format.ppv());
|
||||
if (FAILED(ret)) {
|
||||
PRINT_FAILED_RESULT("IAudioClient::GetMixFormat", ret);
|
||||
return set_result(false);
|
||||
}
|
||||
|
||||
log_info("audio::wasapi", "Mix Format:");
|
||||
print_format(mix_format.data());
|
||||
|
||||
ret = client->IsFormatSupported(AUDCLNT_SHAREMODE_EXCLUSIVE, mix_format.data(), nullptr);
|
||||
if (ret == AUDCLNT_E_UNSUPPORTED_FORMAT) {
|
||||
auto mix_format_ex = reinterpret_cast<WAVEFORMATEXTENSIBLE *>(mix_format.data());
|
||||
|
||||
log_warning("audio::wasapi", "device does not natively support the mix format, converting to PCM");
|
||||
|
||||
if (mix_format->wFormatTag == WAVE_FORMAT_EXTENSIBLE &&
|
||||
IsEqualGUID(GUID_KSDATAFORMAT_SUBTYPE_IEEE_FLOAT, mix_format_ex->SubFormat))
|
||||
{
|
||||
mix_format_ex->Format.wBitsPerSample = 16;
|
||||
mix_format_ex->Format.nBlockAlign = mix_format_ex->Format.nChannels * (mix_format_ex->Format.wBitsPerSample / 8);
|
||||
mix_format_ex->Format.nAvgBytesPerSec = mix_format_ex->Format.nSamplesPerSec * mix_format_ex->Format.nBlockAlign;
|
||||
mix_format_ex->Samples.wValidBitsPerSample = 16;
|
||||
mix_format_ex->SubFormat = GUID_KSDATAFORMAT_SUBTYPE_PCM;
|
||||
} else if (mix_format->wFormatTag == WAVE_FORMAT_IEEE_FLOAT) {
|
||||
mix_format->wBitsPerSample = 16;
|
||||
mix_format->nBlockAlign = mix_format->nChannels * (mix_format->wBitsPerSample / 8);
|
||||
mix_format->nAvgBytesPerSec = mix_format->nSamplesPerSec * mix_format->nBlockAlign;
|
||||
mix_format->wFormatTag = WAVE_FORMAT_PCM;
|
||||
} else {
|
||||
log_warning("audio::wasapi", "mix format is not a floating point format");
|
||||
return set_result(false);
|
||||
}
|
||||
|
||||
ret = client->IsFormatSupported(AUDCLNT_SHAREMODE_EXCLUSIVE, mix_format.data(), nullptr);
|
||||
if (FAILED(ret)) {
|
||||
log_warning("audio::wasapi", "mix format is not supported");
|
||||
return set_result(false);
|
||||
}
|
||||
}
|
||||
|
||||
ret = client->GetDevicePeriod(nullptr, &requested_duration);
|
||||
if (FAILED(ret)) {
|
||||
PRINT_FAILED_RESULT("IAudioClient::GetDevicePeriod", ret);
|
||||
return false;
|
||||
}
|
||||
|
||||
ret = client->Initialize(
|
||||
AUDCLNT_SHAREMODE_EXCLUSIVE,
|
||||
AUDCLNT_STREAMFLAGS_EVENTCALLBACK,
|
||||
requested_duration,
|
||||
requested_duration,
|
||||
mix_format.data(),
|
||||
nullptr);
|
||||
|
||||
if (ret == AUDCLNT_E_BUFFER_SIZE_NOT_ALIGNED || SUCCEEDED(ret)) {
|
||||
log_info("audio::wasapi", "exclusive mode is available, disabling backend");
|
||||
return set_result(true);
|
||||
} else {
|
||||
log_warning("audio::wasapi", "exclusive mode is not available, enabling backend, hr={}", FMT_HRESULT(ret));
|
||||
}
|
||||
|
||||
return set_result(false);
|
||||
}
|
||||
|
||||
HRESULT wrap_audio_client(
|
||||
IMMDevice *device,
|
||||
DWORD cls_ctx,
|
||||
PROPVARIANT *activation_params,
|
||||
IAudioClient **audio_client)
|
||||
{
|
||||
auto exclusive_available = check_for_exclusive_access(*audio_client);
|
||||
(*audio_client)->Stop();
|
||||
(*audio_client)->Reset();
|
||||
(*audio_client)->Release();
|
||||
*audio_client = nullptr;
|
||||
|
||||
SAFE_CALL("IMMDevice", "Activate", device->Activate(
|
||||
IID_IAudioClient,
|
||||
cls_ctx,
|
||||
activation_params,
|
||||
reinterpret_cast<void **>(audio_client)));
|
||||
*/
|
||||
IAudioClient *wrap_audio_client(IAudioClient *audio_client) {
|
||||
log_misc("audio::wasapi", "wrapping IAudioClient");
|
||||
|
||||
AudioBackend *backend = nullptr;
|
||||
bool requires_dummy = false;
|
||||
|
||||
@@ -164,9 +60,6 @@ IAudioClient *wrap_audio_client(IAudioClient *audio_client) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
//} else if (!exclusive_available) {
|
||||
// backend = new WaveOutBackend();
|
||||
//}
|
||||
|
||||
IAudioClient *new_client;
|
||||
|
||||
@@ -182,6 +75,23 @@ IAudioClient *wrap_audio_client(IAudioClient *audio_client) {
|
||||
|
||||
return new_client;
|
||||
}
|
||||
IAudioClient3 *wrap_audio_client3(IAudioClient3 *audio_client) {
|
||||
// TODO: ASIO backend for IAudioClient3, if there is a game that needs it
|
||||
log_misc("audio::wasapi", "wrapping IAudioClient3");
|
||||
|
||||
if (hooks::audio::BACKEND.has_value()) {
|
||||
log_fatal(
|
||||
"audio::wasapi",
|
||||
"IAudioClient3 does not currently support backends! clear -audiobackend and try again");
|
||||
}
|
||||
if (hooks::audio::USE_DUMMY) {
|
||||
log_fatal(
|
||||
"audio::wasapi",
|
||||
"IAudioClient3 does not currently support dummy context, clear -audiodummy and try again");
|
||||
}
|
||||
|
||||
return new WrappedIAudioClient(audio_client, nullptr);
|
||||
}
|
||||
|
||||
// IUnknown
|
||||
HRESULT STDMETHODCALLTYPE WrappedIAudioClient::QueryInterface(REFIID riid, void **ppvObj) {
|
||||
@@ -190,8 +100,8 @@ HRESULT STDMETHODCALLTYPE WrappedIAudioClient::QueryInterface(REFIID riid, void
|
||||
}
|
||||
|
||||
if (riid == IID_WrappedIAudioClient ||
|
||||
riid == IID_IAudioClient)
|
||||
{
|
||||
riid == IID_IAudioClient ||
|
||||
riid == IID_IAudioClient3) {
|
||||
this->AddRef();
|
||||
*ppvObj = this;
|
||||
|
||||
@@ -282,20 +192,6 @@ HRESULT STDMETHODCALLTYPE WrappedIAudioClient::Initialize(
|
||||
}
|
||||
|
||||
log_info("audio::wasapi", "IAudioClient::Initialize success, hr={}", FMT_HRESULT(ret));
|
||||
|
||||
/*
|
||||
if (ShareMode == AUDCLNT_SHAREMODE_SHARED) {
|
||||
IAudioClockAdjustment *clock = nullptr;
|
||||
|
||||
SAFE_CALL("IAudioClient", "GetService", pReal->GetService(
|
||||
IID_IAudioClockAdjustment,
|
||||
reinterpret_cast<void **>(&clock)));
|
||||
|
||||
SAFE_CALL("IAudioClockAdjustment", "SetSampleRate", clock->SetSampleRate(
|
||||
static_cast<float>(pFormat->nSamplesPerSec)));
|
||||
}
|
||||
*/
|
||||
|
||||
copy_wave_format(&hooks::audio::FORMAT, pFormat);
|
||||
|
||||
return ret;
|
||||
@@ -482,3 +378,93 @@ HRESULT STDMETHODCALLTYPE WrappedIAudioClient::GetService(REFIID riid, void **pp
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
HRESULT STDMETHODCALLTYPE WrappedIAudioClient::IsOffloadCapable(
|
||||
AUDIO_STREAM_CATEGORY Category,
|
||||
BOOL *pbOffloadCapable) {
|
||||
WRAP_VERBOSE;
|
||||
|
||||
CHECK_RESULT(pReal3->IsOffloadCapable(Category, pbOffloadCapable));
|
||||
}
|
||||
|
||||
HRESULT STDMETHODCALLTYPE WrappedIAudioClient::SetClientProperties(
|
||||
const AudioClientProperties *pProperties) {
|
||||
|
||||
WRAP_VERBOSE;
|
||||
CHECK_RESULT(pReal3->SetClientProperties(pProperties));
|
||||
}
|
||||
|
||||
HRESULT STDMETHODCALLTYPE WrappedIAudioClient::GetBufferSizeLimits(
|
||||
const WAVEFORMATEX *pFormat,
|
||||
BOOL bEventDriven,
|
||||
REFERENCE_TIME *phnsMinBufferDuration,
|
||||
REFERENCE_TIME *phnsMaxBufferDuration) {
|
||||
|
||||
WRAP_VERBOSE;
|
||||
CHECK_RESULT(pReal3->GetBufferSizeLimits(
|
||||
pFormat,
|
||||
bEventDriven,
|
||||
phnsMinBufferDuration,
|
||||
phnsMaxBufferDuration));
|
||||
}
|
||||
|
||||
HRESULT STDMETHODCALLTYPE WrappedIAudioClient::GetSharedModeEnginePeriod(
|
||||
const WAVEFORMATEX *pFormat,
|
||||
UINT32 *pDefaultPeriodInFrames,
|
||||
UINT32 *pFundamentalPeriodInFrames,
|
||||
UINT32 *pMinPeriodInFrames,
|
||||
UINT32 *pMaxPeriodInFrames) {
|
||||
|
||||
WRAP_VERBOSE;
|
||||
CHECK_RESULT(pReal3->GetSharedModeEnginePeriod(
|
||||
pFormat,
|
||||
pDefaultPeriodInFrames,
|
||||
pFundamentalPeriodInFrames,
|
||||
pMinPeriodInFrames,
|
||||
pMaxPeriodInFrames));
|
||||
}
|
||||
|
||||
HRESULT STDMETHODCALLTYPE WrappedIAudioClient::GetCurrentSharedModeEnginePeriod(
|
||||
WAVEFORMATEX **ppFormat,
|
||||
UINT32 *pCurrentPeriodInFrames) {
|
||||
|
||||
WRAP_VERBOSE;
|
||||
CHECK_RESULT(pReal3->GetCurrentSharedModeEnginePeriod(
|
||||
ppFormat,
|
||||
pCurrentPeriodInFrames));
|
||||
}
|
||||
|
||||
HRESULT STDMETHODCALLTYPE WrappedIAudioClient::InitializeSharedAudioStream(
|
||||
DWORD StreamFlags,
|
||||
UINT32 PeriodInFrames,
|
||||
const WAVEFORMATEX *pFormat,
|
||||
LPCGUID AudioSessionGuid) {
|
||||
|
||||
if (!pFormat) {
|
||||
return E_POINTER;
|
||||
}
|
||||
|
||||
// verbose output
|
||||
log_info("audio::wasapi", "IAudioClient3::InitializeSharedAudioStream hook hit");
|
||||
log_info("audio::wasapi", "... ShareMode : {}", share_mode_str(AUDCLNT_SHAREMODE_SHARED));
|
||||
log_info("audio::wasapi", "... StreamFlags : {}", stream_flags_str(StreamFlags));
|
||||
log_info("audio::wasapi", "... PeriodInFrames : {}", PeriodInFrames);
|
||||
print_format(pFormat);
|
||||
|
||||
// call next
|
||||
HRESULT ret = pReal3->InitializeSharedAudioStream(
|
||||
StreamFlags,
|
||||
PeriodInFrames,
|
||||
pFormat,
|
||||
AudioSessionGuid);
|
||||
|
||||
// check for failure
|
||||
if (FAILED(ret)) {
|
||||
PRINT_FAILED_RESULT("IAudioClient3", "InitializeSharedAudioStream", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
log_info("audio::wasapi", "IAudioClient3::InitializeSharedAudioStream success, hr={}", FMT_HRESULT(ret));
|
||||
copy_wave_format(&hooks::audio::FORMAT, pFormat);
|
||||
return ret;
|
||||
}
|
||||
|
||||
@@ -16,9 +16,16 @@ static const GUID IID_WrappedIAudioClient = {
|
||||
};
|
||||
|
||||
IAudioClient *wrap_audio_client(IAudioClient *client);
|
||||
IAudioClient3 *wrap_audio_client3(IAudioClient3 *client);
|
||||
|
||||
struct WrappedIAudioClient : IAudioClient {
|
||||
explicit WrappedIAudioClient(IAudioClient *orig, AudioBackend *backend) : pReal(orig), backend(backend) {
|
||||
struct WrappedIAudioClient : IAudioClient3 {
|
||||
|
||||
explicit WrappedIAudioClient(IAudioClient3 *orig3, AudioBackend *backend) :
|
||||
pReal(orig3), pReal3(orig3), backend(backend) {
|
||||
}
|
||||
|
||||
explicit WrappedIAudioClient(IAudioClient *orig, AudioBackend *backend) :
|
||||
pReal(orig), pReal3(nullptr), backend(backend) {
|
||||
}
|
||||
|
||||
WrappedIAudioClient(const WrappedIAudioClient &) = delete;
|
||||
@@ -47,9 +54,43 @@ struct WrappedIAudioClient : IAudioClient {
|
||||
HRESULT STDMETHODCALLTYPE GetService(REFIID riid, void **ppv) override;
|
||||
#pragma endregion
|
||||
|
||||
IAudioClient *const pReal;
|
||||
AudioBackend *const backend;
|
||||
#pragma region IAudioClient2
|
||||
HRESULT STDMETHODCALLTYPE IsOffloadCapable(
|
||||
AUDIO_STREAM_CATEGORY Category,
|
||||
BOOL *pbOffloadCapable) override;
|
||||
|
||||
HRESULT STDMETHODCALLTYPE SetClientProperties(
|
||||
const AudioClientProperties *pProperties) override;
|
||||
|
||||
HRESULT STDMETHODCALLTYPE GetBufferSizeLimits(
|
||||
const WAVEFORMATEX *pFormat,
|
||||
BOOL bEventDriven,
|
||||
REFERENCE_TIME *phnsMinBufferDuration,
|
||||
REFERENCE_TIME *phnsMaxBufferDuration) override;
|
||||
#pragma endregion
|
||||
|
||||
#pragma region IAudioClient3
|
||||
HRESULT STDMETHODCALLTYPE GetSharedModeEnginePeriod(
|
||||
const WAVEFORMATEX *pFormat,
|
||||
UINT32 *pDefaultPeriodInFrames,
|
||||
UINT32 *pFundamentalPeriodInFrames,
|
||||
UINT32 *pMinPeriodInFrames,
|
||||
UINT32 *pMaxPeriodInFrames) override;
|
||||
|
||||
HRESULT STDMETHODCALLTYPE GetCurrentSharedModeEnginePeriod(
|
||||
WAVEFORMATEX **ppFormat,
|
||||
UINT32 *pCurrentPeriodInFrames) override;
|
||||
|
||||
HRESULT STDMETHODCALLTYPE InitializeSharedAudioStream(
|
||||
DWORD StreamFlags,
|
||||
UINT32 PeriodInFrames,
|
||||
const WAVEFORMATEX *pFormat,
|
||||
LPCGUID AudioSessionGuid) override;
|
||||
#pragma endregion
|
||||
|
||||
IAudioClient *const pReal;
|
||||
IAudioClient3 *const pReal3;
|
||||
AudioBackend *const backend;
|
||||
bool exclusive_mode = false;
|
||||
int frame_size = 0;
|
||||
};
|
||||
|
||||
@@ -149,5 +149,12 @@ LowLatencyAudioClient *LowLatencyAudioClient::Create(IMMDevice *device) {
|
||||
log_info("audio::lowlatency", "... max buffer size : {} samples ({} ms)", maxPeriod, 1000.0f * maxPeriod / pFormat->nSamplesPerSec);
|
||||
log_info("audio::lowlatency", "... default buffer size : {} samples ({} ms)", defaultPeriod, 1000.0f * defaultPeriod / pFormat->nSamplesPerSec);
|
||||
log_info("audio::lowlatency", "... Windows will use minimum buffer size (instead of default) for shared mode audio clients from now on");
|
||||
if (minPeriod < defaultPeriod) {
|
||||
log_info("audio::lowlatency", "minimum period is less than default period, you will see latency improvement");
|
||||
} else {
|
||||
log_warning("audio::lowlatency", "minimum period is not less than default period, you will see NO latency improvement");
|
||||
log_warning("audio::lowlatency", "this an audio driver / hardware limitation");
|
||||
}
|
||||
|
||||
return new LowLatencyAudioClient(audioClient);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
#include "mme.h"
|
||||
#include <mmeapi.h>
|
||||
|
||||
#include "util/detour.h"
|
||||
#include "util/logging.h"
|
||||
|
||||
namespace hooks::audio::mme {
|
||||
|
||||
static decltype(mixerSetControlDetails) *mixerSetControlDetails_orig = nullptr;
|
||||
|
||||
MMRESULT
|
||||
WINAPI
|
||||
mixerSetControlDetails_hook(
|
||||
HMIXEROBJ hmxobj, LPMIXERCONTROLDETAILS pmxcd, DWORD fdwDetails) {
|
||||
log_misc("audio::mme", "mixerSetControlDetails_hook called; ignoring volume change");
|
||||
return MMSYSERR_NOERROR;
|
||||
}
|
||||
|
||||
void init(HINSTANCE module) {
|
||||
mixerSetControlDetails_orig =
|
||||
detour::iat_try("mixerSetControlDetails", mixerSetControlDetails_hook, module);
|
||||
|
||||
if (mixerSetControlDetails_orig != nullptr) {
|
||||
log_misc("audio::mme", "mixerSetControlDetails hooked");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
#include <windows.h>
|
||||
|
||||
namespace hooks::audio::mme {
|
||||
void init(HINSTANCE module);
|
||||
}
|
||||
@@ -1,7 +1,10 @@
|
||||
#include "avshook.h"
|
||||
|
||||
#include <cassert>
|
||||
#include <mutex>
|
||||
#include <map>
|
||||
#include <optional>
|
||||
#include <external/robin_hood.h>
|
||||
|
||||
#include "avs/core.h"
|
||||
#include "avs/ea3.h"
|
||||
@@ -20,8 +23,12 @@ static bool FAKE_FILE_OPEN = false;
|
||||
static std::map<std::string, std::string> ROM_FILE_MAP;
|
||||
static std::string *ROM_FILE_CONTENTS = nullptr;
|
||||
|
||||
static std::mutex lstat_cache_mutex;
|
||||
static robin_hood::unordered_map<std::string, std::pair<int, struct avs::core::avs_stat>> lstat_cache;
|
||||
|
||||
namespace hooks::avs::config {
|
||||
bool DISABLE_VFS_DRIVE_REDIRECTION = false;
|
||||
bool DISABLE_CACHE = false;
|
||||
bool LOG = false;
|
||||
};
|
||||
|
||||
@@ -79,6 +86,74 @@ static bool is_spam_file(const char *file) {
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool is_ldj_cacheable_file(const char *file) {
|
||||
static const char *spam_prefixes[] = {
|
||||
// iidx33: profile background customization, spams on every frame during card entry
|
||||
"/data/graphic/entry_card",
|
||||
// iidx32+: movie thumbnails, spams on every frame during song select
|
||||
"/data/graphic/thumbnail/",
|
||||
"/data/graphic/movie_thumbnail/",
|
||||
};
|
||||
|
||||
for (auto &spam : spam_prefixes) {
|
||||
if (string_begins_with(file, spam)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static int cached_avs_fs_lstat(const char *name, struct avs::core::avs_stat *st) {
|
||||
assert(name != nullptr);
|
||||
assert(st != nullptr);
|
||||
|
||||
const std::string filename(name);
|
||||
int value = 0;
|
||||
std::lock_guard<std::mutex> lock(lstat_cache_mutex);
|
||||
|
||||
// if this was already seen, return cached result
|
||||
if (lstat_cache.contains(filename)) {
|
||||
const auto &result = lstat_cache.at(filename);
|
||||
value = result.first;
|
||||
memcpy(st, &result.second, sizeof(*st));
|
||||
WRAP_DEBUG_FMT(
|
||||
"(cached) file={}, a={}, m={}, c={}, size={}, u={}",
|
||||
name,
|
||||
st->st_atime,
|
||||
st->st_mtime,
|
||||
st->st_ctime,
|
||||
st->filesize,
|
||||
st->unk1);
|
||||
return value;
|
||||
}
|
||||
|
||||
// if not, call the original avs function
|
||||
// this returns 1 on success with valid values in st
|
||||
// return 0 on failure, with filesize set to 0
|
||||
// maybe this could be used for future optimization
|
||||
value = avs::core::avs_fs_lstat(name, st);
|
||||
WRAP_DEBUG_FMT(
|
||||
"file={} a={}, m={}, c={}, size={}, u={}",
|
||||
name,
|
||||
st->st_atime,
|
||||
st->st_mtime,
|
||||
st->st_ctime,
|
||||
st->filesize,
|
||||
st->unk1);
|
||||
|
||||
// and cache it if there is room (prevent unconstrained growth)
|
||||
if (lstat_cache.size() <= 4096) {
|
||||
// c++ was a mistake
|
||||
lstat_cache.emplace(
|
||||
std::piecewise_construct,
|
||||
std::forward_as_tuple(filename),
|
||||
std::forward_as_tuple(value, *st));
|
||||
}
|
||||
|
||||
// done
|
||||
return value;
|
||||
}
|
||||
|
||||
static int avs_fs_fstat(avs::core::avs_file_t fd, struct avs::core::avs_stat *st) {
|
||||
if (is_fake_fd(fd) && ROM_FILE_CONTENTS) {
|
||||
if (st) {
|
||||
@@ -104,7 +179,12 @@ static int avs_fs_lstat(const char *name, struct avs::core::avs_stat *st) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
auto value = avs::core::avs_fs_lstat(name, st);
|
||||
if (!hooks::avs::config::DISABLE_CACHE && (st != nullptr) &&
|
||||
avs::game::is_model("LDJ") && is_ldj_cacheable_file(name)) {
|
||||
return cached_avs_fs_lstat(name, st);
|
||||
}
|
||||
|
||||
const auto value = avs::core::avs_fs_lstat(name, st);
|
||||
if (!is_spam_file(name)) {
|
||||
WRAP_DEBUG_FMT("name: {}", name);
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
namespace hooks::avs {
|
||||
namespace config {
|
||||
extern bool DISABLE_VFS_DRIVE_REDIRECTION;
|
||||
extern bool DISABLE_CACHE;
|
||||
extern bool LOG;
|
||||
};
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
#include <vector>
|
||||
|
||||
#include "avs/game.h"
|
||||
#include "games/gitadora/gitadora.h"
|
||||
#include "util/detour.h"
|
||||
#include "util/utils.h"
|
||||
|
||||
@@ -399,6 +400,12 @@ static BOOL WINAPI ClearCommError_hook(HANDLE hFile, LPDWORD lpErrors, LPCOMSTAT
|
||||
lpStat->cbInQue = custom_handle->bytes_available();
|
||||
}
|
||||
|
||||
// gitadora arena model needs this, or else
|
||||
// the game will keep spamming 0xAA
|
||||
if (games::gitadora::is_arena_model() && lpErrors) {
|
||||
*lpErrors = 0;
|
||||
}
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
|
||||
#include "avs/game.h"
|
||||
#include "cfg/screen_resize.h"
|
||||
#include "games/gitadora/gitadora.h"
|
||||
#include "games/iidx/iidx.h"
|
||||
#include "games/sdvx/sdvx.h"
|
||||
#include "games/mfc/mfc.h"
|
||||
@@ -41,6 +42,15 @@
|
||||
#undef min
|
||||
#endif
|
||||
|
||||
#define D3D9_BACKEND_DEBUG 0
|
||||
|
||||
#if D3D9_BACKEND_DEBUG
|
||||
#define log_debug(module, format_str, ...) logger::push( \
|
||||
LOG_FORMAT("M", module, format_str, ## __VA_ARGS__), logger::Style::GREY)
|
||||
#else
|
||||
#define log_debug(module, format_str, ...)
|
||||
#endif
|
||||
|
||||
#define CHECK_RESULT(x) \
|
||||
do { \
|
||||
HRESULT __ret = (x); \
|
||||
@@ -514,7 +524,7 @@ HRESULT STDMETHODCALLTYPE WrappedIDirect3D9::EnumAdapterModes(
|
||||
if (!modified && games::iidx::is_tdj_fhd() && refresh < 110) {
|
||||
if ((width == 1920 && height == 1080) ||
|
||||
(width == 1280 && height == 720)){
|
||||
log_misc(
|
||||
log_debug(
|
||||
"graphics::d3d9", "removing mode {}, {}x{} @ {}Hz (for TDJ FHD)",
|
||||
Mode, width, height, refresh);
|
||||
memset(pMode, 0, sizeof(*pMode));
|
||||
@@ -527,7 +537,7 @@ HRESULT STDMETHODCALLTYPE WrappedIDirect3D9::EnumAdapterModes(
|
||||
// - skip 75 and 90 Hz entries because LDJ game timing is messed up with it
|
||||
// (TDJ should be fine as it assumes a fixed 60 Hz timing)
|
||||
if (!modified && (width == 1360 || width == 1366 || refresh == 75 || refresh == 90)) {
|
||||
log_misc(
|
||||
log_debug(
|
||||
"graphics::d3d9", "removing mode {}, {}x{} @ {}Hz (for LDJ/TDJ)",
|
||||
Mode, width, height, refresh);
|
||||
memset(pMode, 0, sizeof(*pMode));
|
||||
@@ -535,7 +545,7 @@ HRESULT STDMETHODCALLTYPE WrappedIDirect3D9::EnumAdapterModes(
|
||||
}
|
||||
|
||||
if (!modified && !games::iidx::TDJ_MODE && games::iidx::FORCE_720P && (height > 720)) {
|
||||
log_misc(
|
||||
log_debug(
|
||||
"graphics::d3d9", "removing mode {}, {}x{} @ {}Hz (-iidxforce720p)",
|
||||
Mode, width, height, refresh);
|
||||
memset(pMode, 0, sizeof(*pMode));
|
||||
@@ -803,6 +813,14 @@ HRESULT STDMETHODCALLTYPE WrappedIDirect3D9::CreateDevice(
|
||||
"either use -graphics-force-refresh option or change the desktop resolution beforehand.");
|
||||
}
|
||||
|
||||
if (!GRAPHICS_WINDOWED && num_adapters >= 2 && GRAPHICS_FORCE_REFRESH_SUB.has_value()) {
|
||||
log_info("graphics::d3d9", "force sub refresh rate: {} => {} Hz (-graphics-force-refresh-sub option)",
|
||||
pPresentationParameters[1].FullScreen_RefreshRateInHz,
|
||||
GRAPHICS_FORCE_REFRESH_SUB.value());
|
||||
|
||||
pPresentationParameters[1].FullScreen_RefreshRateInHz = GRAPHICS_FORCE_REFRESH_SUB.value();
|
||||
}
|
||||
|
||||
// force single adapter
|
||||
if (GRAPHICS_FORCE_SINGLE_ADAPTER) {
|
||||
log_info("graphics::d3d9", "disabling adapter group device with force single adapter mode");
|
||||
@@ -927,26 +945,37 @@ HRESULT STDMETHODCALLTYPE WrappedIDirect3D9::CreateDeviceEx(
|
||||
|
||||
for (size_t i = 0; i < num_adapters; i++) {
|
||||
auto *params = &pPresentationParameters[i];
|
||||
if (!GRAPHICS_WINDOWED && i == 0) {
|
||||
GRAPHICS_FS_ORIGINAL_WIDTH = params->BackBufferWidth;
|
||||
GRAPHICS_FS_ORIGINAL_HEIGHT = params->BackBufferHeight;
|
||||
log_misc("graphics::d3d9", "original resolution: {}x{}", GRAPHICS_FS_ORIGINAL_WIDTH, GRAPHICS_FS_ORIGINAL_HEIGHT);
|
||||
if (GRAPHICS_FS_CUSTOM_RESOLUTION.has_value()) {
|
||||
if (!GRAPHICS_WINDOWED){
|
||||
if (i == 0) {
|
||||
GRAPHICS_FS_ORIGINAL_WIDTH = params->BackBufferWidth;
|
||||
GRAPHICS_FS_ORIGINAL_HEIGHT = params->BackBufferHeight;
|
||||
log_misc("graphics::d3d9", "original resolution: {}x{}", GRAPHICS_FS_ORIGINAL_WIDTH, GRAPHICS_FS_ORIGINAL_HEIGHT);
|
||||
if (GRAPHICS_FS_CUSTOM_RESOLUTION.has_value()) {
|
||||
log_misc(
|
||||
"graphics::d3d9",
|
||||
"use custom resolution {}x{} => {}x{}",
|
||||
params->BackBufferWidth, params->BackBufferHeight,
|
||||
GRAPHICS_FS_CUSTOM_RESOLUTION.value().first,
|
||||
GRAPHICS_FS_CUSTOM_RESOLUTION.value().second);
|
||||
params->BackBufferWidth = GRAPHICS_FS_CUSTOM_RESOLUTION.value().first;
|
||||
params->BackBufferHeight = GRAPHICS_FS_CUSTOM_RESOLUTION.value().second;
|
||||
} else if (GRAPHICS_FS_ORIENTATION_SWAP) {
|
||||
log_misc(
|
||||
"graphics::d3d9",
|
||||
"swap full orientation {}x{} => {}x{}",
|
||||
params->BackBufferWidth, params->BackBufferHeight,
|
||||
params->BackBufferHeight, params->BackBufferWidth);
|
||||
std::swap(params->BackBufferWidth, params->BackBufferHeight);
|
||||
}
|
||||
} else if (i == 1 && GRAPHICS_FS_CUSTOM_RESOLUTION_SUB.has_value()) {
|
||||
log_misc(
|
||||
"graphics::d3d9",
|
||||
"use custom resolution {}x{} => {}x{}",
|
||||
"use custom sub resolution {}x{} => {}x{}",
|
||||
params->BackBufferWidth, params->BackBufferHeight,
|
||||
GRAPHICS_FS_CUSTOM_RESOLUTION.value().first,
|
||||
GRAPHICS_FS_CUSTOM_RESOLUTION.value().second);
|
||||
params->BackBufferWidth = GRAPHICS_FS_CUSTOM_RESOLUTION.value().first;
|
||||
params->BackBufferHeight = GRAPHICS_FS_CUSTOM_RESOLUTION.value().second;
|
||||
} else if (GRAPHICS_FS_ORIENTATION_SWAP) {
|
||||
log_misc(
|
||||
"graphics::d3d9",
|
||||
"swap full orientation {}x{} => {}x{}",
|
||||
params->BackBufferWidth, params->BackBufferHeight,
|
||||
params->BackBufferHeight, params->BackBufferWidth);
|
||||
std::swap(params->BackBufferWidth, params->BackBufferHeight);
|
||||
GRAPHICS_FS_CUSTOM_RESOLUTION_SUB.value().first,
|
||||
GRAPHICS_FS_CUSTOM_RESOLUTION_SUB.value().second);
|
||||
params->BackBufferWidth = GRAPHICS_FS_CUSTOM_RESOLUTION_SUB.value().first;
|
||||
params->BackBufferHeight = GRAPHICS_FS_CUSTOM_RESOLUTION_SUB.value().second;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -974,12 +1003,17 @@ HRESULT STDMETHODCALLTYPE WrappedIDirect3D9::CreateDeviceEx(
|
||||
for (size_t i = 0; i < num_adapters; i++) {
|
||||
auto *fullscreen_display_mode = &pFullscreenDisplayMode[i];
|
||||
|
||||
if (!GRAPHICS_WINDOWED && i == 0) {
|
||||
if (GRAPHICS_FS_CUSTOM_RESOLUTION.has_value()) {
|
||||
fullscreen_display_mode->Width = GRAPHICS_FS_CUSTOM_RESOLUTION.value().first;
|
||||
fullscreen_display_mode->Height = GRAPHICS_FS_CUSTOM_RESOLUTION.value().second;
|
||||
} else if (GRAPHICS_FS_ORIENTATION_SWAP) {
|
||||
std::swap(fullscreen_display_mode->Width, fullscreen_display_mode->Height);
|
||||
if (!GRAPHICS_WINDOWED) {
|
||||
if (i == 0) {
|
||||
if (GRAPHICS_FS_CUSTOM_RESOLUTION.has_value()) {
|
||||
fullscreen_display_mode->Width = GRAPHICS_FS_CUSTOM_RESOLUTION.value().first;
|
||||
fullscreen_display_mode->Height = GRAPHICS_FS_CUSTOM_RESOLUTION.value().second;
|
||||
} else if (GRAPHICS_FS_ORIENTATION_SWAP) {
|
||||
std::swap(fullscreen_display_mode->Width, fullscreen_display_mode->Height);
|
||||
}
|
||||
} else if (i == 1 && GRAPHICS_FS_CUSTOM_RESOLUTION_SUB.has_value()) {
|
||||
fullscreen_display_mode->Width = GRAPHICS_FS_CUSTOM_RESOLUTION_SUB.value().first;
|
||||
fullscreen_display_mode->Height = GRAPHICS_FS_CUSTOM_RESOLUTION_SUB.value().second;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1022,6 +1056,17 @@ HRESULT STDMETHODCALLTYPE WrappedIDirect3D9::CreateDeviceEx(
|
||||
}
|
||||
}
|
||||
|
||||
if (!GRAPHICS_WINDOWED && num_adapters >= 2 && GRAPHICS_FORCE_REFRESH_SUB.has_value()) {
|
||||
log_info("graphics::d3d9", "force sub refresh rate: {} => {} Hz (-graphics-force-refresh-sub option)",
|
||||
pPresentationParameters[1].FullScreen_RefreshRateInHz,
|
||||
GRAPHICS_FORCE_REFRESH_SUB.value());
|
||||
|
||||
pPresentationParameters[1].FullScreen_RefreshRateInHz = GRAPHICS_FORCE_REFRESH_SUB.value();
|
||||
if (pFullscreenDisplayMode) {
|
||||
pFullscreenDisplayMode[1].RefreshRate = GRAPHICS_FORCE_REFRESH_SUB.value();
|
||||
}
|
||||
}
|
||||
|
||||
// force single adapter
|
||||
if (GRAPHICS_FORCE_SINGLE_ADAPTER) {
|
||||
log_info("graphics::d3d9", "disabling adapter group device with force single adapter mode");
|
||||
@@ -1142,9 +1187,15 @@ IDirect3DSurface9 *graphics_d3d9_ldj_get_sub_screen() {
|
||||
}
|
||||
|
||||
static void graphics_d3d9_ldj_on_present(IDirect3DDevice9 *wrapped_device) {
|
||||
// iidx/sdvx
|
||||
int swapchain = 1;
|
||||
if (games::gitadora::is_arena_model()) {
|
||||
swapchain = 3;
|
||||
}
|
||||
|
||||
if (!ATTEMPTED_SUB_SWAP_CHAIN_ACQUIRE && SUB_SWAP_CHAIN == nullptr) {
|
||||
ATTEMPTED_SUB_SWAP_CHAIN_ACQUIRE = true;
|
||||
HRESULT hr = wrapped_device->GetSwapChain(1, &SUB_SWAP_CHAIN);
|
||||
HRESULT hr = wrapped_device->GetSwapChain(swapchain, &SUB_SWAP_CHAIN);
|
||||
if (FAILED(hr)) {
|
||||
log_warning(
|
||||
"graphics::d3d9",
|
||||
@@ -1353,11 +1404,11 @@ void graphics_d3d9_on_present(
|
||||
}
|
||||
|
||||
// for IIDX TDJ / SDVX UFC, handle subscreen
|
||||
const bool is_vm =
|
||||
avs::game::is_model("KFC") &&
|
||||
(avs::game::SPEC[0] == 'G' || avs::game::SPEC[0] == 'H');
|
||||
const bool is_vm = games::sdvx::is_valkyrie_model();
|
||||
const bool is_tdj = avs::game::is_model("LDJ") && games::iidx::TDJ_MODE;
|
||||
if (is_vm || is_tdj) {
|
||||
const bool is_gfdm_arena =
|
||||
games::gitadora::is_arena_model() && games::gitadora::ARENA_SINGLE_WINDOW;
|
||||
if (is_vm || is_tdj || is_gfdm_arena) {
|
||||
graphics_d3d9_ldj_on_present(wrapped_device);
|
||||
}
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
#include <mutex>
|
||||
|
||||
#include "avs/game.h"
|
||||
#include "games/gitadora/gitadora.h"
|
||||
#include "hooks/graphics/graphics.h"
|
||||
#include "overlay/overlay.h"
|
||||
#include "util/flags_helper.h"
|
||||
@@ -249,8 +250,16 @@ HRESULT STDMETHODCALLTYPE WrappedIDirect3DDevice9::CreateAdditionalSwapChain(
|
||||
WRAP_VERBOSE;
|
||||
|
||||
HRESULT hr = pReal->CreateAdditionalSwapChain(pPresentationParameters, ppSwapChain);
|
||||
|
||||
bool create_swap_chain = false;
|
||||
if (avs::game::is_model({"LDJ", "KFC"})) {
|
||||
create_swap_chain = true;
|
||||
} else if (games::gitadora::is_arena_model() &&
|
||||
games::gitadora::ARENA_SINGLE_WINDOW &&
|
||||
pPresentationParameters->BackBufferWidth == 800) {
|
||||
create_swap_chain = true;
|
||||
}
|
||||
|
||||
if (create_swap_chain) {
|
||||
if (SUCCEEDED(hr) && !sub_swapchain) {
|
||||
sub_swapchain = new WrappedIDirect3DSwapChain9(this, *ppSwapChain);
|
||||
sub_swapchain->should_run_hooks = false;
|
||||
@@ -295,7 +304,17 @@ HRESULT STDMETHODCALLTYPE WrappedIDirect3DDevice9::GetSwapChain(
|
||||
return D3D_OK;
|
||||
}
|
||||
|
||||
bool swap = false;
|
||||
if (iSwapChain == 1 && avs::game::is_model({"LDJ", "KFC"})) {
|
||||
swap = true;
|
||||
}
|
||||
if (games::gitadora::is_arena_model() &&
|
||||
games::gitadora::ARENA_SINGLE_WINDOW &&
|
||||
iSwapChain == 3) {
|
||||
swap = true;
|
||||
}
|
||||
|
||||
if (swap) {
|
||||
if (sub_swapchain) {
|
||||
sub_swapchain->AddRef();
|
||||
*ppSwapChain = static_cast<IDirect3DSwapChain9 *>(sub_swapchain);
|
||||
@@ -742,14 +761,12 @@ void SurfaceHook(IDirect3DDevice9 *pReal) {
|
||||
const int w = param.BackBufferWidth;
|
||||
const int h = param.BackBufferHeight;
|
||||
|
||||
if (cfg::SCREENRESIZE->enable_screen_resize) {
|
||||
RECT rect {
|
||||
0,
|
||||
0,
|
||||
(LONG)topSurface_width,
|
||||
(LONG)topSurface_height,
|
||||
};
|
||||
pReal->ColorFill(topSurface, &rect, D3DCOLOR_XRGB(0, 0, 0));
|
||||
// this code used to clear the surface using ColorFill on every call, but
|
||||
// this turned out to be very expensive, leading to major frame drops in
|
||||
// SDVX Live2D scenario
|
||||
if (cfg::SCREENRESIZE->need_surface_clean) {
|
||||
pReal->ColorFill(topSurface, nullptr, D3DCOLOR_XRGB(0, 0, 0));
|
||||
cfg::SCREENRESIZE->need_surface_clean = false;
|
||||
}
|
||||
|
||||
D3DLOCKED_RECT rect;
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
#include "cfg/icon.h"
|
||||
#include "cfg/screen_resize.h"
|
||||
#include "games/ddr/ddr.h"
|
||||
#include "games/gitadora/gitadora.h"
|
||||
#include "games/iidx/iidx.h"
|
||||
#include "hooks/graphics/backends/d3d9/d3d9_backend.h"
|
||||
#include "launcher/shutdown.h"
|
||||
@@ -33,6 +34,7 @@ struct CaptureData {
|
||||
|
||||
HWND TDJ_SUBSCREEN_WINDOW = nullptr;
|
||||
HWND SDVX_SUBSCREEN_WINDOW = nullptr;
|
||||
HWND GFDM_SUBSCREEN_WINDOW = nullptr;
|
||||
|
||||
// icon
|
||||
static HICON WINDOW_ICON = LoadIcon(GetModuleHandle(nullptr), MAKEINTRESOURCE(MAINICON));
|
||||
@@ -60,6 +62,7 @@ graphics_orientation GRAPHICS_ADJUST_ORIENTATION = ORIENTATION_NORMAL;
|
||||
bool GRAPHICS_WINDOWED = false;
|
||||
std::vector<HWND> GRAPHICS_WINDOWS;
|
||||
UINT GRAPHICS_FORCE_REFRESH = 0;
|
||||
std::optional<uint32_t> GRAPHICS_FORCE_REFRESH_SUB;
|
||||
std::optional<int> GRAPHICS_FORCE_VSYNC_BUFFER;
|
||||
bool GRAPHICS_FORCE_SINGLE_ADAPTER = false;
|
||||
bool GRAPHICS_PREVENT_SECONDARY_WINDOW = false;
|
||||
@@ -68,6 +71,7 @@ bool GRAPHICS_9_ON_12_REQUESTED_BY_GAME = false;
|
||||
bool SUBSCREEN_FORCE_REDRAW = false;
|
||||
bool D3D9_DEVICE_HOOK_DISABLE = false;
|
||||
std::optional<std::pair<uint32_t, uint32_t>> GRAPHICS_FS_CUSTOM_RESOLUTION;
|
||||
std::optional<std::pair<uint32_t, uint32_t>> GRAPHICS_FS_CUSTOM_RESOLUTION_SUB;
|
||||
bool GRAPHICS_FS_ORIENTATION_SWAP = false;
|
||||
uint32_t GRAPHICS_FS_ORIGINAL_WIDTH = 0;
|
||||
uint32_t GRAPHICS_FS_ORIGINAL_HEIGHT = 0;
|
||||
@@ -94,6 +98,7 @@ static decltype(SetCursor) *SetCursor_orig = nullptr;
|
||||
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 void reset_window_hook(HWND hWnd) {
|
||||
overlay::destroy(hWnd);
|
||||
@@ -296,6 +301,30 @@ static BOOL WINAPI ClipCursor_hook(const RECT *lpRect) {
|
||||
return ClipCursor_orig(lpRect);
|
||||
}
|
||||
|
||||
static void fix_up_rb_auto_rotate_dimensions(DWORD &w, DWORD &h) {
|
||||
// reflec beat
|
||||
// for whatever reason, rb creates the first window it weird dimensions (774x1389 for reflesia, for example)
|
||||
// so doing ChangeDisplaySettings as-is ends up with DISP_CHANGE_BADMODE
|
||||
// while a second window (with title D3DProxyWindow) does get created with correct dimensions, it's
|
||||
// too late to rotate the window then; as a workaround force the display change now with patched up dimensions
|
||||
|
||||
bool changed = false;
|
||||
if (w == 774) {
|
||||
// LBR, MBR reflesia
|
||||
w = 768;
|
||||
h = 1360;
|
||||
changed = true;
|
||||
} else if (w == 1086) {
|
||||
// MBR (before reflesia)
|
||||
w = 1080;
|
||||
h = 1920;
|
||||
changed = true;
|
||||
}
|
||||
if (changed) {
|
||||
log_misc("graphics", "fix auto-rotate dimensions for reflec beat: w={}, h={}", w, h);
|
||||
}
|
||||
}
|
||||
|
||||
static HWND WINAPI CreateWindowExA_hook(DWORD dwExStyle, LPCSTR lpClassName, LPCSTR lpWindowName,
|
||||
DWORD dwStyle, int x, int y, int nWidth, int nHeight,
|
||||
HWND hWndParent, HMENU hMenu, HINSTANCE hInstance,
|
||||
@@ -320,10 +349,19 @@ static HWND WINAPI CreateWindowExA_hook(DWORD dwExStyle, LPCSTR lpClassName, LPC
|
||||
// set display orientation and/or refresh rate
|
||||
// only set orientation when the target window is portrait
|
||||
// (avoid doing this for SDVX subscreen which is in landscape, for example)
|
||||
const auto adjust_orientation =
|
||||
auto adjust_orientation =
|
||||
(GRAPHICS_ADJUST_ORIENTATION == ORIENTATION_CW ||
|
||||
GRAPHICS_ADJUST_ORIENTATION == ORIENTATION_CCW);
|
||||
|
||||
// reflec beat
|
||||
if (avs::game::is_model("KBR")) {
|
||||
// KBR (rb1) launches landscape and draws rotated by itself, don't do any rotation
|
||||
adjust_orientation = false;
|
||||
} else if (avs::game::is_model({"LBR", "MBR"}) && window_name == "D3DProxyWindow") {
|
||||
// do not auto-rotate for second window (D3DProxyWindow)
|
||||
adjust_orientation = false;
|
||||
}
|
||||
|
||||
if ((nHeight > nWidth && adjust_orientation) || GRAPHICS_FORCE_REFRESH > 0) {
|
||||
DEVMODE mode {};
|
||||
|
||||
@@ -333,11 +371,16 @@ static HWND WINAPI CreateWindowExA_hook(DWORD dwExStyle, LPCSTR lpClassName, LPC
|
||||
DWORD orientation = GRAPHICS_ADJUST_ORIENTATION == ORIENTATION_CW ? DMDO_90 : DMDO_270;
|
||||
log_misc(
|
||||
"graphics",
|
||||
"auto-rotate: call ChangeDisplaySettings and rotate display to DMDO_xx mode {}",
|
||||
orientation);
|
||||
"auto-rotate: call ChangeDisplaySettings and rotate display to DMDO_xx mode {}, w={}, h={}",
|
||||
orientation, nWidth, nHeight);
|
||||
mode.dmPelsWidth = nWidth;
|
||||
mode.dmPelsHeight = nHeight;
|
||||
mode.dmDisplayOrientation = orientation;
|
||||
|
||||
// reflec beat
|
||||
if (avs::game::is_model({"LBR", "MBR"})) {
|
||||
fix_up_rb_auto_rotate_dimensions(mode.dmPelsWidth, mode.dmPelsHeight);
|
||||
}
|
||||
}
|
||||
|
||||
if (GRAPHICS_FORCE_REFRESH > 0) {
|
||||
@@ -352,7 +395,7 @@ static HWND WINAPI CreateWindowExA_hook(DWORD dwExStyle, LPCSTR lpClassName, LPC
|
||||
|
||||
const auto disp_res = ChangeDisplaySettings(&mode, CDS_FULLSCREEN);
|
||||
if (disp_res != DISP_CHANGE_SUCCESSFUL) {
|
||||
log_warning("graphics", "failed to change display settings: {}", disp_res);
|
||||
log_warning("graphics", "ChangeDisplaySettings failed: {}", disp_res);
|
||||
}
|
||||
} else {
|
||||
log_warning("graphics", "failed to get display settings");
|
||||
@@ -369,11 +412,16 @@ 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_gfdm_sub_window = games::gitadora::is_arena_model() && window_name.ends_with("SMALL");
|
||||
|
||||
// TDJ windowed mode with subscreen: hide maximize button
|
||||
if ((is_tdj_sub_window && GRAPHICS_IIDX_WSUB) || is_sdvx_sub_window) {
|
||||
// hide maximize button (prevent misaligned touches)
|
||||
if ((is_tdj_sub_window && GRAPHICS_IIDX_WSUB) || is_sdvx_sub_window || is_gfdm_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) {
|
||||
dwStyle &= ~(WS_OVERLAPPEDWINDOW);
|
||||
}
|
||||
@@ -417,7 +465,19 @@ static HWND WINAPI CreateWindowExA_hook(DWORD dwExStyle, LPCSTR lpClassName, LPC
|
||||
graphics_hook_subscreen_window(SDVX_SUBSCREEN_WINDOW);
|
||||
}
|
||||
|
||||
// only hook touch window if multiple windows are allowed
|
||||
if (is_gfdm_sub_window && !games::gitadora::ARENA_SINGLE_WINDOW) {
|
||||
GFDM_SUBSCREEN_WINDOW = result;
|
||||
graphics_hook_subscreen_window(GFDM_SUBSCREEN_WINDOW);
|
||||
}
|
||||
|
||||
disable_touch_indicators(result);
|
||||
log_misc(
|
||||
"graphics",
|
||||
"CreateWindowExA returned {}, {}",
|
||||
fmt::ptr(result),
|
||||
lpWindowName ? lpWindowName : "(null)");
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -449,7 +509,8 @@ static HWND WINAPI CreateWindowExW_hook(DWORD dwExStyle, LPCWSTR lpClassName, LP
|
||||
}
|
||||
|
||||
// windowed mode adjustments
|
||||
if (GRAPHICS_WINDOWED) {
|
||||
// check the width to filter out invisible system-created windows
|
||||
if (GRAPHICS_WINDOWED && nWidth > 0) {
|
||||
|
||||
// change window style
|
||||
dwExStyle = 0;
|
||||
@@ -484,6 +545,12 @@ static HWND WINAPI CreateWindowExW_hook(DWORD dwExStyle, LPCWSTR lpClassName, LP
|
||||
hWndParent, hMenu, hInstance, lpParam);
|
||||
GRAPHICS_WINDOWS.push_back(result);
|
||||
|
||||
log_misc(
|
||||
"graphics",
|
||||
"CreateWindowExW returned {}, {}",
|
||||
fmt::ptr(result),
|
||||
lpWindowName ? ws2s(lpWindowName) : "(null)");
|
||||
|
||||
disable_touch_indicators(result);
|
||||
return result;
|
||||
}
|
||||
@@ -648,10 +715,29 @@ static BOOL WINAPI SetWindowPos_hook(HWND hWnd, HWND hWndInsertAfter,
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
// prevent gitadora arena model from shifting windows around if the user has preferences
|
||||
if (GRAPHICS_WINDOWED && games::gitadora::is_arena_model() &&
|
||||
GRAPHICS_HOOKED_WINDOW.has_value() && hWnd == GRAPHICS_HOOKED_WINDOW.value() &&
|
||||
cfg::SCREENRESIZE->enable_window_resize) {
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
// call original
|
||||
return SetWindowPos_orig(hWnd, hWndInsertAfter, X, Y, cx, cy, uFlags);
|
||||
}
|
||||
|
||||
static BOOL WINAPI ShowWindow_hook(HWND hWnd, int nCmdShow) {
|
||||
if (games::gitadora::is_arena_model() &&
|
||||
games::gitadora::ARENA_SINGLE_WINDOW &&
|
||||
hWnd != GRAPHICS_HOOKED_WINDOW) {
|
||||
log_info("graphics", "ShowWindow_hook - hiding sub window {}", fmt::ptr(hWnd));
|
||||
return true;
|
||||
}
|
||||
|
||||
// call original
|
||||
return ShowWindow_orig(hWnd, nCmdShow);
|
||||
}
|
||||
|
||||
static ATOM WINAPI RegisterClassA_hook(const WNDCLASSA *lpWndClass) {
|
||||
|
||||
// check for null
|
||||
@@ -794,6 +880,7 @@ void graphics_init() {
|
||||
SetWindowLongA_orig = detour::iat_try("SetWindowLongA", SetWindowLongA_hook);
|
||||
SetWindowLongW_orig = detour::iat_try("SetWindowLongW", SetWindowLongW_hook);
|
||||
SetWindowPos_orig = detour::iat_try("SetWindowPos", SetWindowPos_hook);
|
||||
ShowWindow_orig = detour::iat_try("ShowWindow", ShowWindow_hook);
|
||||
|
||||
detour::iat_try("MessageBoxA", MessageBoxA_hook);
|
||||
detour::iat_try("MessageBoxExA", MessageBoxExA_hook);
|
||||
|
||||
@@ -32,10 +32,12 @@ extern bool GRAPHICS_WINDOWED;
|
||||
extern graphics_orientation GRAPHICS_ADJUST_ORIENTATION;
|
||||
extern std::vector<HWND> GRAPHICS_WINDOWS;
|
||||
extern UINT GRAPHICS_FORCE_REFRESH;
|
||||
extern std::optional<uint32_t> GRAPHICS_FORCE_REFRESH_SUB;
|
||||
extern std::optional<int> GRAPHICS_FORCE_VSYNC_BUFFER;
|
||||
extern bool GRAPHICS_FORCE_SINGLE_ADAPTER;
|
||||
extern bool GRAPHICS_PREVENT_SECONDARY_WINDOW;
|
||||
extern std::optional<std::pair<uint32_t, uint32_t>> GRAPHICS_FS_CUSTOM_RESOLUTION;
|
||||
extern std::optional<std::pair<uint32_t, uint32_t>> GRAPHICS_FS_CUSTOM_RESOLUTION_SUB;
|
||||
extern bool GRAPHICS_FS_ORIENTATION_SWAP;
|
||||
extern uint32_t GRAPHICS_FS_ORIGINAL_WIDTH;
|
||||
extern uint32_t GRAPHICS_FS_ORIGINAL_HEIGHT;
|
||||
@@ -48,6 +50,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 std::optional<HWND> GRAPHICS_HOOKED_WINDOW;
|
||||
|
||||
extern bool GRAPHICS_IIDX_WSUB;
|
||||
extern std::optional<std::string> GRAPHICS_WSUB_SIZE;
|
||||
@@ -60,6 +63,7 @@ extern bool GRAPHICS_WSUB_BORDERLESS;
|
||||
extern bool GRAPHICS_WSUB_ALWAYS_ON_TOP;
|
||||
extern HWND TDJ_SUBSCREEN_WINDOW;
|
||||
extern HWND SDVX_SUBSCREEN_WINDOW;
|
||||
extern HWND GFDM_SUBSCREEN_WINDOW;
|
||||
|
||||
extern bool SUBSCREEN_FORCE_REDRAW;
|
||||
|
||||
@@ -98,5 +102,8 @@ void graphics_capture_initial_window(HWND hWnd);
|
||||
void graphics_update_window_style(HWND hWnd);
|
||||
void graphics_update_z_order(HWND hWnd, bool always_on_top);
|
||||
void graphics_move_resize_window(HWND hWnd);
|
||||
bool graphics_window_change_crashes_game();
|
||||
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();
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#include "graphics.h"
|
||||
|
||||
#include "avs/game.h"
|
||||
#include "games/gitadora/gitadora.h"
|
||||
#include "cfg/screen_resize.h"
|
||||
#include "overlay/overlay.h"
|
||||
#include "util/logging.h"
|
||||
@@ -23,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;
|
||||
std::optional<HWND> GRAPHICS_HOOKED_WINDOW;
|
||||
|
||||
// IIDX Windowed Subscreen - starts out as false, enabled by IIDX module on pre-attach as needed
|
||||
bool GRAPHICS_IIDX_WSUB = false;
|
||||
@@ -55,15 +57,22 @@ void graphics_capture_initial_window(HWND hWnd) {
|
||||
|
||||
graphics_load_windowed_parameters();
|
||||
|
||||
cfg::SCREENRESIZE->init_window_style = GetWindowLong(hWnd, GWL_STYLE);
|
||||
GRAPHICS_HOOKED_WINDOW = hWnd;
|
||||
|
||||
log_debug("graphics-windowed", "graphics_capture_initial_window called");
|
||||
cfg::SCREENRESIZE->init_window_style = GetWindowLong(hWnd, GWL_STYLE);
|
||||
cfg::SCREENRESIZE->init_window_style_ex = GetWindowLong(hWnd, GWL_EXSTYLE);
|
||||
|
||||
log_debug(
|
||||
"graphics-windowed",
|
||||
"graphics_capture_initial_window called, hWnd={}",
|
||||
fmt::ptr(hWnd));
|
||||
|
||||
RECT rect;
|
||||
if (!GetClientRect(hWnd, &rect)) {
|
||||
log_warning(
|
||||
"graphics",
|
||||
"graphics_capture_initial_window - GetClientRect failed, GLE: {}",
|
||||
"graphics-windowed",
|
||||
"[{}] graphics_capture_initial_window - GetClientRect failed, GLE: {}",
|
||||
fmt::ptr(hWnd),
|
||||
GetLastError());
|
||||
return;
|
||||
}
|
||||
@@ -75,12 +84,22 @@ void graphics_capture_initial_window(HWND hWnd) {
|
||||
cfg::SCREENRESIZE->init_client_aspect_ratio = (float)client_w / (float)client_h;
|
||||
log_debug(
|
||||
"graphics-windowed",
|
||||
"graphics_capture_initial_window initial window size {}x{}, ratio {}",
|
||||
"[{}] graphics_capture_initial_window initial window size {}x{}, ratio {}",
|
||||
fmt::ptr(hWnd),
|
||||
client_w, client_h, cfg::SCREENRESIZE->init_client_aspect_ratio);
|
||||
|
||||
// ensure frame size is captured
|
||||
graphics_wm_style_changed(hWnd, false);
|
||||
|
||||
// if the game is not compatible, ignore user-supplied values and keep the current size
|
||||
if (graphics_window_resize_breaks_game()) {
|
||||
cfg::SCREENRESIZE->client_width = client_w;
|
||||
cfg::SCREENRESIZE->client_height = client_h;
|
||||
}
|
||||
if (graphics_window_move_and_resize_breaks_game()) {
|
||||
cfg::SCREENRESIZE->enable_window_resize = false;
|
||||
}
|
||||
|
||||
// if there was no user-supplied dimension, seed it with the current size
|
||||
// so that the next resize operation will work
|
||||
if (cfg::SCREENRESIZE->client_width == 0) {
|
||||
@@ -94,7 +113,8 @@ void graphics_capture_initial_window(HWND hWnd) {
|
||||
// resize must be done before applying the border
|
||||
if (cfg::SCREENRESIZE->enable_window_resize) {
|
||||
log_info(
|
||||
"graphics-windowed", "change window rect - window offset: {}x{}, client size: {}x{}",
|
||||
"graphics-windowed", "[{}] change window rect - window offset: {}x{}, client size: {}x{}",
|
||||
fmt::ptr(hWnd),
|
||||
cfg::SCREENRESIZE->window_offset_x,
|
||||
cfg::SCREENRESIZE->window_offset_y,
|
||||
cfg::SCREENRESIZE->client_width,
|
||||
@@ -104,12 +124,13 @@ void graphics_capture_initial_window(HWND 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: {}",
|
||||
"graphics-windowed", "[{}] change window style - decoration: {}",
|
||||
fmt::ptr(hWnd),
|
||||
cfg::SCREENRESIZE->window_decoration);
|
||||
graphics_update_window_style(hWnd);
|
||||
}
|
||||
if (cfg::SCREENRESIZE->window_always_on_top) {
|
||||
log_info("graphics-windowed", "change window z-order - always on top");
|
||||
log_info("graphics-windowed", "[{}] change window z-order - always on top", fmt::ptr(hWnd));
|
||||
graphics_update_z_order(hWnd, true);
|
||||
}
|
||||
|
||||
@@ -152,7 +173,7 @@ void graphics_load_windowed_parameters() {
|
||||
cfg::SCREENRESIZE->window_offset_x = result.first;
|
||||
cfg::SCREENRESIZE->window_offset_y = result.second;
|
||||
} else {
|
||||
log_warning("graphics-windowed", "failed to parse -windowpos");
|
||||
log_fatal("graphics-windowed", "failed to parse -windowpos");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -181,7 +202,7 @@ void graphics_load_windowed_subscreen_parameters() {
|
||||
GRAPHICS_WSUB_WIDTH = result.first;
|
||||
GRAPHICS_WSUB_HEIGHT = result.second;
|
||||
} else {
|
||||
log_warning("graphics-windowed", "failed to parse -wsubsize");
|
||||
log_fatal("graphics-windowed", "failed to parse -iidxwsubsize / -sdvxwsubsize");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -195,7 +216,7 @@ void graphics_load_windowed_subscreen_parameters() {
|
||||
GRAPHICS_WSUB_X = result.first;
|
||||
GRAPHICS_WSUB_Y = result.second;
|
||||
} else {
|
||||
log_warning("graphics-windowed", "failed to parse -wsubpos");
|
||||
log_fatal("graphics-windowed", "failed to parse -iidxwsubpos / -sdvxwsubpos");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -368,20 +389,29 @@ void graphics_update_window_style(HWND hWnd) {
|
||||
if (!GRAPHICS_WINDOWED) {
|
||||
return;
|
||||
}
|
||||
if (graphics_window_change_crashes_game()) {
|
||||
if (graphics_window_decoration_change_crashes_game()) {
|
||||
return;
|
||||
}
|
||||
|
||||
log_debug("graphics-windowed", "graphics_update_window_style called");
|
||||
log_debug(
|
||||
"graphics-windowed",
|
||||
"graphics_update_window_style called for hWnd={}",
|
||||
fmt::ptr(hWnd));
|
||||
|
||||
// update frame style
|
||||
auto style = cfg::SCREENRESIZE->init_window_style;
|
||||
auto style_ex = cfg::SCREENRESIZE->init_window_style_ex;
|
||||
switch (cfg::SCREENRESIZE->window_decoration) {
|
||||
case cfg::WindowDecorationMode::Borderless:
|
||||
style &= ~WS_OVERLAPPEDWINDOW;
|
||||
style_ex &= ~WS_EX_CLIENTEDGE;
|
||||
style_ex &= ~WS_EX_WINDOWEDGE;
|
||||
break;
|
||||
case cfg::WindowDecorationMode::ResizableFrame:
|
||||
style |= WS_OVERLAPPEDWINDOW;
|
||||
if (graphics_window_resize_breaks_game()) {
|
||||
style &= ~WS_SIZEBOX;
|
||||
}
|
||||
break;
|
||||
case cfg::WindowDecorationMode::Default:
|
||||
default:
|
||||
@@ -393,11 +423,26 @@ void graphics_update_window_style(HWND hWnd) {
|
||||
"graphics_update_window_style - calling SetWindowLong with Mode {}, style 0x{:x}",
|
||||
static_cast<int>(cfg::SCREENRESIZE->window_decoration),
|
||||
style);
|
||||
SetWindowLong(hWnd, GWL_STYLE, style);
|
||||
|
||||
const auto ret = SetWindowLong(hWnd, GWL_STYLE, style);
|
||||
(void)ret;
|
||||
|
||||
log_debug(
|
||||
"graphics-windowed",
|
||||
"graphics_update_window_style - SetWindowLong(GWL_STYLE) returned {:x}, GLE={}",
|
||||
ret,
|
||||
GetLastError());
|
||||
|
||||
const auto ret2 = SetWindowLong(hWnd, GWL_EXSTYLE, style_ex);
|
||||
(void)ret2;
|
||||
log_debug(
|
||||
"graphics-windowed",
|
||||
"graphics_update_window_style SetWindowLong(GWL_EXSTYLE) returned {:x}, GLE={}",
|
||||
ret2,
|
||||
GetLastError());
|
||||
|
||||
// SetWindowPos must be called after SetWindowLong if the frame style changed
|
||||
// this will be done in WM_STYLECHANGED handler
|
||||
log_debug("graphics-windowed", "graphics_update_window_style returned");
|
||||
}
|
||||
|
||||
void graphics_update_z_order(HWND hWnd, bool always_on_top) {
|
||||
@@ -454,12 +499,19 @@ void graphics_move_resize_window(HWND hWnd) {
|
||||
log_debug("graphics-windowed", "graphics_move_resize_window returned");
|
||||
}
|
||||
|
||||
bool graphics_window_change_crashes_game() {
|
||||
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");
|
||||
|
||||
// changing window decoration also changes dimensions and position
|
||||
// so this must be prevented
|
||||
if (graphics_window_move_and_resize_breaks_game()) {
|
||||
result = true;
|
||||
}
|
||||
|
||||
if (result) {
|
||||
log_warning(
|
||||
"graphics-windowed",
|
||||
@@ -468,3 +520,43 @@ bool graphics_window_change_crashes_game() {
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
bool graphics_window_move_and_resize_breaks_game() {
|
||||
static std::once_flag flag;
|
||||
static bool result = false;
|
||||
|
||||
std::call_once(flag, []() {
|
||||
// games like reflec beat in use spice touch overlay for windowed mode
|
||||
// and mouse input breaks if window is moved as the overlay does not
|
||||
// currently support window moves/resizes
|
||||
result = avs::game::is_model({"KBR", "LBR", "MBR"});
|
||||
if (result) {
|
||||
log_warning(
|
||||
"graphics-windowed",
|
||||
"ignoring changes to window properties due to incompatibility with this game");
|
||||
}
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
bool graphics_window_resize_breaks_game() {
|
||||
// for whatever reason, gitadora games on dx/sd/sd2 cabs behave poorly when window is resized
|
||||
// (eihter at launch or while it's running)
|
||||
// usually results in: soft lock during scene transtions or really long transitions,
|
||||
// backgrounds of menus not rendering, etc.
|
||||
bool is_gitadora = avs::game::is_model({ "J32", "J33", "K32", "K33", "L32", "L33", "M32" });
|
||||
bool is_arena = games::gitadora::is_arena_model();
|
||||
|
||||
bool result = graphics_window_move_and_resize_breaks_game() || (is_gitadora && !is_arena);
|
||||
|
||||
static std::once_flag flag;
|
||||
std::call_once(flag, [&result]() {
|
||||
if (result) {
|
||||
log_warning(
|
||||
"graphics-windowed",
|
||||
"ignoring changes to window size due to incompatibility with this game");
|
||||
}
|
||||
});
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -9,6 +9,8 @@
|
||||
|
||||
#include "avs/game.h"
|
||||
#include "games/iidx/iidx.h"
|
||||
#include "games/gitadora/gitadora.h"
|
||||
#include "games/sdvx/sdvx.h"
|
||||
#include "util/detour.h"
|
||||
#include "util/logging.h"
|
||||
#include "util/utils.h"
|
||||
@@ -24,6 +26,7 @@ static decltype(GetLocaleInfoEx) *GetLocaleInfoEx_orig = nullptr;
|
||||
#ifdef SPICE64
|
||||
static decltype(GetSystemDefaultLCID) *GetSystemDefaultLCID_orig = nullptr;
|
||||
static decltype(IsDBCSLeadByte) *IsDBCSLeadByte_orig = nullptr;
|
||||
static decltype(WideCharToMultiByte) *WideCharToMultiByte_orig = nullptr;
|
||||
#endif
|
||||
|
||||
static NTSTATUS NTAPI RtlMultiByteToUnicodeN_hook(
|
||||
@@ -173,6 +176,41 @@ static BOOL WINAPI IsDBCSLeadByte_hook (
|
||||
return IsDBCSLeadByteEx(CODEPAGE_SHIFT_JIS, TestChar);
|
||||
}
|
||||
|
||||
static
|
||||
int
|
||||
WINAPI
|
||||
WideCharToMultiByte_hook(
|
||||
UINT CodePage,
|
||||
DWORD dwFlags,
|
||||
LPCWCH lpWideCharStr,
|
||||
int cchWideChar,
|
||||
LPSTR lpMultiByteStr,
|
||||
int cbMultiByte,
|
||||
LPCCH lpDefaultChar,
|
||||
LPBOOL lpUsedDefaultChar
|
||||
)
|
||||
{
|
||||
switch (CodePage) {
|
||||
case CP_ACP:
|
||||
case CP_THREAD_ACP:
|
||||
SetThreadLocale(MAKELANGID(LANG_JAPANESE, SUBLANG_JAPANESE_JAPAN));
|
||||
CodePage = CODEPAGE_SHIFT_JIS;
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return WideCharToMultiByte_orig(
|
||||
CodePage,
|
||||
dwFlags,
|
||||
lpWideCharStr,
|
||||
cchWideChar,
|
||||
lpMultiByteStr,
|
||||
cbMultiByte,
|
||||
lpDefaultChar,
|
||||
lpUsedDefaultChar);
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
void hooks::lang::early_init() {
|
||||
@@ -183,8 +221,7 @@ void hooks::lang::early_init() {
|
||||
detour::trampoline_try("kernel32.dll", "GetOEMCP", GetOEMCP_hook, &GetOEMCP_orig);
|
||||
|
||||
#ifdef SPICE64 // SDVX5+ specific code
|
||||
bool isValkyrieCabinetMode = avs::game::SPEC[0] == 'G' || avs::game::SPEC[0] == 'H';
|
||||
if (avs::game::is_model("KFC") && isValkyrieCabinetMode) {
|
||||
if (games::sdvx::is_valkyrie_model()) {
|
||||
log_info("hooks::lang", "hooking GetSystemDefaultLCID");
|
||||
detour::trampoline_try(
|
||||
"kernel32.dll",
|
||||
@@ -213,6 +250,15 @@ void hooks::lang::early_init() {
|
||||
IsDBCSLeadByte_hook,
|
||||
&IsDBCSLeadByte_orig);
|
||||
}
|
||||
|
||||
if (games::gitadora::is_arena_model()) {
|
||||
log_info("hooks::lang", "hooking WideCharToMultiByte");
|
||||
detour::trampoline_try(
|
||||
"kernel32.dll",
|
||||
"WideCharToMultiByte",
|
||||
WideCharToMultiByte_hook,
|
||||
&WideCharToMultiByte_orig);
|
||||
}
|
||||
#endif
|
||||
|
||||
}
|
||||
|
||||
@@ -88,6 +88,7 @@
|
||||
#include "misc/device.h"
|
||||
#include "misc/eamuse.h"
|
||||
#include "misc/extdev.h"
|
||||
#include "misc/ami2000.h"
|
||||
#include "misc/sciunit.h"
|
||||
#include "misc/sde.h"
|
||||
#include "misc/wintouchemu.h"
|
||||
@@ -106,6 +107,7 @@
|
||||
#include "util/libutils.h"
|
||||
#include "util/logging.h"
|
||||
#include "util/peb.h"
|
||||
#include "util/socd_cleaner.h"
|
||||
#include "util/sysutils.h"
|
||||
#include "util/tapeled.h"
|
||||
#include "util/time.h"
|
||||
@@ -195,6 +197,7 @@ int main_implementation(int argc, char *argv[]) {
|
||||
bool attach_icca = false;
|
||||
bool attach_device = false;
|
||||
bool attach_extdev = false;
|
||||
bool attach_ami2000 = false;
|
||||
bool attach_sciunit = false;
|
||||
bool attach_cpusbxpkm_printer = false;
|
||||
bool attach_iidx = false;
|
||||
@@ -324,6 +327,10 @@ int main_implementation(int argc, char *argv[]) {
|
||||
if (options[launcher::Options::GraphicsForceRefresh].is_active()) {
|
||||
GRAPHICS_FORCE_REFRESH = options[launcher::Options::GraphicsForceRefresh].value_uint32();
|
||||
}
|
||||
if (options[launcher::Options::FullscreenSubRefreshRate].is_active()) {
|
||||
GRAPHICS_FORCE_REFRESH_SUB =
|
||||
options[launcher::Options::FullscreenSubRefreshRate].value_uint32();
|
||||
}
|
||||
if (options[launcher::Options::GraphicsForceSingleAdapter].value_bool()) {
|
||||
GRAPHICS_FORCE_SINGLE_ADAPTER = true;
|
||||
}
|
||||
@@ -344,8 +351,13 @@ int main_implementation(int argc, char *argv[]) {
|
||||
GRAPHICS_FORCE_SINGLE_ADAPTER = true;
|
||||
GRAPHICS_PREVENT_SECONDARY_WINDOW = true;
|
||||
}
|
||||
if (options[launcher::Options::DisplayAdapter].is_active()) {
|
||||
if (options[launcher::Options::DisplayAdapter].is_active() &&
|
||||
options[launcher::Options::DisplayAdapter].value_uint32() != D3DADAPTER_DEFAULT) {
|
||||
D3D9_ADAPTER = options[launcher::Options::DisplayAdapter].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
|
||||
GRAPHICS_FORCE_SINGLE_ADAPTER = true;
|
||||
}
|
||||
if (options[launcher::Options::CaptureCursor].value_bool()) {
|
||||
GRAPHICS_CAPTURE_CURSOR = true;
|
||||
@@ -423,8 +435,8 @@ int main_implementation(int argc, char *argv[]) {
|
||||
if (options[launcher::Options::EnableEXTDEVModule].value_bool()) {
|
||||
attach_extdev = true;
|
||||
}
|
||||
if (options[launcher::Options::EnableSCIUNITModule].value_bool()) {
|
||||
attach_sciunit = true;
|
||||
if (options[launcher::Options::EnableAMI2000Module].value_bool()) {
|
||||
attach_ami2000 = true;
|
||||
}
|
||||
if (options[launcher::Options::EnableDevicePassthrough].value_bool()) {
|
||||
hooks::device::ENABLE = false;
|
||||
@@ -523,6 +535,9 @@ int main_implementation(int argc, char *argv[]) {
|
||||
overlay::windows::IIDX_SEGMENT_LOCATION =
|
||||
options[launcher::Options::spice2x_IIDXLEDPos].value_text();
|
||||
}
|
||||
if (options[launcher::Options::IIDXLEDBorderless].value_bool()) {
|
||||
overlay::windows::IIDX_SEGMENT_BORDERLESS = true;
|
||||
}
|
||||
if (options[launcher::Options::spice2x_IIDXNoESpec].value_bool()) {
|
||||
games::iidx::DISABLE_ESPEC_IO = true;
|
||||
}
|
||||
@@ -574,6 +589,25 @@ int main_implementation(int argc, char *argv[]) {
|
||||
if (options[launcher::Options::GitaDoraCabinetType].is_active()) {
|
||||
games::gitadora::CAB_TYPE = options[launcher::Options::GitaDoraCabinetType].value_uint32();
|
||||
}
|
||||
if (options[launcher::Options::GitaDoraArenaSingleWindow].value_bool() && GRAPHICS_WINDOWED) {
|
||||
games::gitadora::ARENA_SINGLE_WINDOW = true;
|
||||
}
|
||||
if (options[launcher::Options::GitaDoraWailHold].is_active()) {
|
||||
socd::TILT_HOLD_MS = options[launcher::Options::GitaDoraWailHold].value_uint32();
|
||||
}
|
||||
if (options[launcher::Options::GitaDoraPickAlgo].is_active()) {
|
||||
const auto text = options[launcher::Options::GitaDoraPickAlgo].value_text();
|
||||
if (text == "legacy") {
|
||||
games::gitadora::PICK_ALGO.reset();
|
||||
} else if (text == "neutral") {
|
||||
games::gitadora::PICK_ALGO = socd::SocdAlgorithm::Neutral;
|
||||
} else if (text == "raw") {
|
||||
games::gitadora::PICK_ALGO = socd::SocdAlgorithm::None;
|
||||
}
|
||||
}
|
||||
if (options[launcher::Options::GitaDoraSubOverlaySize].is_active()) {
|
||||
games::gitadora::SUBSCREEN_OVERLAY_SIZE = options[launcher::Options::GitaDoraSubOverlaySize].value_text();
|
||||
}
|
||||
if (options[launcher::Options::LoadNostalgiaModule].value_bool()) {
|
||||
attach_nostalgia = true;
|
||||
}
|
||||
@@ -595,6 +629,17 @@ int main_implementation(int argc, char *argv[]) {
|
||||
if (options[launcher::Options::GitaDoraTwoChannelAudio].value_bool()) {
|
||||
games::gitadora::TWOCHANNEL = true;
|
||||
}
|
||||
if (options[launcher::Options::GitaDoraLefty].is_active()) {
|
||||
const auto text = options[launcher::Options::GitaDoraLefty].value_text();
|
||||
if (text == "p1") {
|
||||
games::gitadora::P1_LEFTY = true;
|
||||
} else if (text == "p2") {
|
||||
games::gitadora::P2_LEFTY = true;
|
||||
} else if (text == "both") {
|
||||
games::gitadora::P1_LEFTY = true;
|
||||
games::gitadora::P2_LEFTY = true;
|
||||
}
|
||||
}
|
||||
if (options[launcher::Options::LoadDDRModule].value_bool()) {
|
||||
attach_ddr = true;
|
||||
}
|
||||
@@ -744,6 +789,9 @@ int main_implementation(int argc, char *argv[]) {
|
||||
if (options[launcher::Options::DisableAvsVfsDriveMountRedirection].is_active()) {
|
||||
hooks::avs::config::DISABLE_VFS_DRIVE_REDIRECTION = true;
|
||||
}
|
||||
if (options[launcher::Options::DisableAvsCache].is_active()) {
|
||||
hooks::avs::config::DISABLE_CACHE = true;
|
||||
}
|
||||
if (options[launcher::Options::ScreenResizeConfigPath].is_active()) {
|
||||
cfg::SCREEN_RESIZE_CFG_PATH_OVERRIDE =
|
||||
options[launcher::Options::ScreenResizeConfigPath].value_text();
|
||||
@@ -946,6 +994,12 @@ int main_implementation(int argc, char *argv[]) {
|
||||
if (options[launcher::Options::DisableOverlay].value_bool()) {
|
||||
overlay::ENABLED = false;
|
||||
}
|
||||
if (options[launcher::Options::OverlayScaling].is_active() && !cfg::CONFIGURATOR_STANDALONE && !cfg_run) {
|
||||
const auto val = options[launcher::Options::OverlayScaling].value_uint32();
|
||||
if (10 <= val && val <= 400 && val != 100) {
|
||||
overlay::UI_SCALE_PERCENT = options[launcher::Options::OverlayScaling].value_uint32();
|
||||
}
|
||||
}
|
||||
if (options[launcher::Options::DisableAudioHooks].value_bool()) {
|
||||
hooks::audio::ENABLED = false;
|
||||
deferredlogs::defer_error_messages({
|
||||
@@ -1029,8 +1083,8 @@ int main_implementation(int argc, char *argv[]) {
|
||||
std::pair<uint32_t, uint32_t> result;
|
||||
if (parse_width_height(options[launcher::Options::spice2x_WindowSize].value_text(), result)) {
|
||||
GRAPHICS_WINDOW_SIZE = result;
|
||||
} else {
|
||||
log_warning("launcher", "failed to parse -windowsize");
|
||||
} else if (!cfg_run && !cfg::CONFIGURATOR_STANDALONE) {
|
||||
log_fatal("launcher", "failed to parse -windowsize");
|
||||
}
|
||||
}
|
||||
if (options[launcher::Options::spice2x_WindowPosition].is_active()) {
|
||||
@@ -1074,6 +1128,9 @@ int main_implementation(int argc, char *argv[]) {
|
||||
if (options[launcher::Options::spice2x_DRSTransposeTouch].value_bool()) {
|
||||
games::drs::TRANSPOSE_TOUCH = true;
|
||||
}
|
||||
if (options[launcher::Options::DRSRGBCameraHook].value_bool()) {
|
||||
games::drs::RGB_CAMERA_HOOK = true;
|
||||
}
|
||||
if (options[launcher::Options::spice2x_AutoCard].is_active()) {
|
||||
const auto text = options[launcher::Options::spice2x_AutoCard].value_text();
|
||||
if (text == "p1") {
|
||||
@@ -1328,12 +1385,33 @@ int main_implementation(int argc, char *argv[]) {
|
||||
);
|
||||
}
|
||||
|
||||
if (launcher::signal::DISABLE && !cfg::CONFIGURATOR_STANDALONE) {
|
||||
log_warning(
|
||||
"launcher",
|
||||
"WARNING - user specified -signaldisable option\n\n\n"
|
||||
"!!! !!!\n"
|
||||
"!!! Using -signaldisable option disables all exception handling, !!!\n"
|
||||
"!!! which means when the game crashes it will likely just vanish !!!\n"
|
||||
"!!! without troubleshooting information in the logs. !!!\n"
|
||||
"!!! !!!\n"
|
||||
);
|
||||
}
|
||||
|
||||
if (options[launcher::Options::FullscreenResolution].is_active()) {
|
||||
std::pair<uint32_t, uint32_t> result;
|
||||
if (parse_width_height(options[launcher::Options::FullscreenResolution].value_text(), result)) {
|
||||
GRAPHICS_FS_CUSTOM_RESOLUTION = result;
|
||||
} else {
|
||||
log_warning("launcher", "failed to parse -forceres");
|
||||
} else if (!cfg_run && !cfg::CONFIGURATOR_STANDALONE) {
|
||||
log_fatal("launcher", "failed to parse -forceres");
|
||||
}
|
||||
}
|
||||
|
||||
if (options[launcher::Options::FullscreenSubResolution].is_active()) {
|
||||
std::pair<uint32_t, uint32_t> result;
|
||||
if (parse_width_height(options[launcher::Options::FullscreenSubResolution].value_text(), result)) {
|
||||
GRAPHICS_FS_CUSTOM_RESOLUTION_SUB = result;
|
||||
} else if (!cfg_run && !cfg::CONFIGURATOR_STANDALONE) {
|
||||
log_fatal("launcher", "failed to parse -forceressub");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1629,6 +1707,7 @@ int main_implementation(int argc, char *argv[]) {
|
||||
attach_io = true;
|
||||
attach_device = true;
|
||||
attach_extdev = true;
|
||||
attach_ami2000 = true;
|
||||
attach_gitadora = true;
|
||||
break;
|
||||
}
|
||||
@@ -1809,7 +1888,7 @@ int main_implementation(int argc, char *argv[]) {
|
||||
}
|
||||
|
||||
// Polaris Chord
|
||||
if (check_dll("kamunity.dll") && fileutils::dir_exists("game/svm_Data")) {
|
||||
if (check_dll("kamunity.dll") && fileutils::file_exists("game/svm.exe")) {
|
||||
avs::game::DLL_NAME = "kamunity.dll";
|
||||
attach_io = true;
|
||||
attach_pc = true;
|
||||
@@ -1848,7 +1927,7 @@ int main_implementation(int argc, char *argv[]) {
|
||||
"This usually means one of the following:\n\n"
|
||||
" 1. You put spice executables in the wrong place, or\n"
|
||||
" 2. XML files in prop directory are malformed or missing, or\n"
|
||||
" 3. You passed in invalid options (usually, path overrides like -exec)\n\n"
|
||||
" 3. You passed in invalid options (do not specify -exec or -modules if you don't know what you're doing)\n\n"
|
||||
);
|
||||
|
||||
log_fatal("launcher", "module auto detection failed, see log.txt for troubleshooting");
|
||||
@@ -2191,6 +2270,11 @@ int main_implementation(int argc, char *argv[]) {
|
||||
extdev_attach();
|
||||
}
|
||||
|
||||
// ami 2000 card reader attach
|
||||
if (attach_io || attach_ami2000) {
|
||||
ami2000_attach();
|
||||
}
|
||||
|
||||
// sci unit attach
|
||||
if (attach_io || attach_sciunit) {
|
||||
sciunit_attach();
|
||||
|
||||
@@ -12,9 +12,11 @@
|
||||
#include "launcher/launcher.h"
|
||||
#include "util/utils.h"
|
||||
|
||||
#define FOREGROUND_GREY (8)
|
||||
#define FOREGROUND_WHITE (FOREGROUND_RED | FOREGROUND_BLUE | FOREGROUND_GREEN)
|
||||
#define FOREGROUND_YELLOW (FOREGROUND_RED | FOREGROUND_GREEN)
|
||||
#define FOREGROUND_GREY (8)
|
||||
#define FOREGROUND_WHITE (FOREGROUND_RED | FOREGROUND_BLUE | FOREGROUND_GREEN)
|
||||
#define FOREGROUND_YELLOW (FOREGROUND_RED | FOREGROUND_GREEN)
|
||||
#define FOREGROUND_CYAN (FOREGROUND_GREEN | FOREGROUND_BLUE)
|
||||
#define FOREGROUND_MAGENTA (FOREGROUND_RED | FOREGROUND_BLUE)
|
||||
|
||||
namespace logger {
|
||||
|
||||
@@ -103,9 +105,6 @@ namespace logger {
|
||||
last_style = content.second;
|
||||
|
||||
switch (content.second) {
|
||||
case Style::DEFAULT:
|
||||
set_console_color(hTerminal, FOREGROUND_WHITE);
|
||||
break;
|
||||
case Style::GREY:
|
||||
set_console_color(hTerminal, FOREGROUND_GREY);
|
||||
break;
|
||||
@@ -115,6 +114,13 @@ namespace logger {
|
||||
case Style::RED:
|
||||
set_console_color(hTerminal, FOREGROUND_RED);
|
||||
break;
|
||||
case Style::SPECIAL:
|
||||
set_console_color(hTerminal, FOREGROUND_CYAN);
|
||||
break;
|
||||
case Style::DEFAULT:
|
||||
default:
|
||||
set_console_color(hTerminal, FOREGROUND_WHITE);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -12,7 +12,8 @@ namespace logger {
|
||||
DEFAULT = 0,
|
||||
GREY = 1,
|
||||
YELLOW = 2,
|
||||
RED = 3
|
||||
RED = 3,
|
||||
SPECIAL = 4,
|
||||
};
|
||||
|
||||
void start();
|
||||
|
||||
+419
-235
File diff suppressed because it is too large
Load Diff
@@ -31,6 +31,8 @@ namespace launcher {
|
||||
GraphicsForceRefresh,
|
||||
FullscreenResolution,
|
||||
FullscreenOrientationFlip,
|
||||
FullscreenSubResolution,
|
||||
FullscreenSubRefreshRate,
|
||||
Graphics9On12,
|
||||
spice2x_Dx9On12,
|
||||
NoLegacy,
|
||||
@@ -47,6 +49,7 @@ namespace launcher {
|
||||
SOFTID,
|
||||
VREnable,
|
||||
DisableOverlay,
|
||||
OverlayScaling,
|
||||
spice2x_FpsAutoShow,
|
||||
spice2x_FpsOpposite,
|
||||
spice2x_SubScreenAutoShow,
|
||||
@@ -66,11 +69,13 @@ namespace launcher {
|
||||
IIDXBIO2FW,
|
||||
IIDXTDJMode,
|
||||
spice2x_IIDXDigitalTTSensitivity,
|
||||
IIDXDigitalTTSocd,
|
||||
spice2x_IIDXLDJForce720p,
|
||||
spice2x_IIDXTDJSubSize,
|
||||
spice2x_IIDXLEDFontSize,
|
||||
spice2x_IIDXLEDColor,
|
||||
spice2x_IIDXLEDPos,
|
||||
IIDXLEDBorderless,
|
||||
LoadSoundVoltexModule,
|
||||
SDVXForce720p,
|
||||
SDVXPrinterEmulation,
|
||||
@@ -81,10 +86,11 @@ namespace launcher {
|
||||
SDVXPrinterJPGQuality,
|
||||
SDVXDisableCameras,
|
||||
SDVXNativeTouch,
|
||||
spice2x_SDVXSubRedraw,
|
||||
spice2x_SDVXDigitalKnobSensitivity,
|
||||
SDVXDigitalKnobSocd,
|
||||
spice2x_SDVXAsioDriver,
|
||||
spice2x_SDVXSubPos,
|
||||
spice2x_SDVXSubRedraw,
|
||||
LoadDDRModule,
|
||||
DDR43Mode,
|
||||
DDRSkipCodecRegisteration,
|
||||
@@ -95,6 +101,11 @@ namespace launcher {
|
||||
LoadGitaDoraModule,
|
||||
GitaDoraTwoChannelAudio,
|
||||
GitaDoraCabinetType,
|
||||
GitaDoraArenaSingleWindow,
|
||||
GitaDoraLefty,
|
||||
GitaDoraWailHold,
|
||||
GitaDoraPickAlgo,
|
||||
GitaDoraSubOverlaySize,
|
||||
LoadJubeatModule,
|
||||
LoadReflecBeatModule,
|
||||
LoadShogikaiModule,
|
||||
@@ -146,6 +157,7 @@ namespace launcher {
|
||||
EnableICCAModule,
|
||||
EnableDEVICEModule,
|
||||
EnableEXTDEVModule,
|
||||
EnableAMI2000Module,
|
||||
EnableSCIUNITModule,
|
||||
EnableDevicePassthrough,
|
||||
ForceWinTouch,
|
||||
@@ -195,6 +207,7 @@ namespace launcher {
|
||||
DisableSignalHandling,
|
||||
DisableDebugHooks,
|
||||
DisableAvsVfsDriveMountRedirection,
|
||||
DisableAvsCache,
|
||||
OutputPEB,
|
||||
DumpSystemInfo,
|
||||
QKSArgs,
|
||||
@@ -225,6 +238,7 @@ namespace launcher {
|
||||
spice2x_IIDXWindowedTDJ,
|
||||
spice2x_DRSDisableTouch,
|
||||
spice2x_DRSTransposeTouch,
|
||||
DRSRGBCameraHook,
|
||||
spice2x_IIDXNativeTouch,
|
||||
spice2x_IIDXNoSub,
|
||||
spice2x_IIDXEmulateSubscreenKeypadTouch,
|
||||
|
||||
@@ -185,21 +185,30 @@ static LONG WINAPI TopLevelExceptionFilter(struct _EXCEPTION_POINTERS *Exception
|
||||
}
|
||||
|
||||
static BOOL WINAPI SetConsoleCtrlHandler_hook(PHANDLER_ROUTINE pHandlerRoutine, BOOL Add) {
|
||||
log_misc("signal", "SetConsoleCtrlHandler hook hit");
|
||||
static std::once_flag printed;
|
||||
std::call_once(printed, []() {
|
||||
log_misc("signal", "SetConsoleCtrlHandler hook hit (printing once)");
|
||||
});
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static LPTOP_LEVEL_EXCEPTION_FILTER WINAPI SetUnhandledExceptionFilter_hook(
|
||||
LPTOP_LEVEL_EXCEPTION_FILTER lpTopLevelExceptionFilter)
|
||||
{
|
||||
log_info("signal", "SetUnhandledExceptionFilter hook hit");
|
||||
LPTOP_LEVEL_EXCEPTION_FILTER lpTopLevelExceptionFilter) {
|
||||
|
||||
static std::once_flag printed;
|
||||
std::call_once(printed, []() {
|
||||
log_info("signal", "SetUnhandledExceptionFilter hook hit (printing once)");
|
||||
});
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
static PVOID WINAPI AddVectoredExceptionHandler_hook(ULONG First, PVECTORED_EXCEPTION_HANDLER Handler) {
|
||||
log_info("signal", "AddVectoredExceptionHandler hook hit");
|
||||
static std::once_flag printed;
|
||||
std::call_once(printed, []() {
|
||||
log_info("signal", "AddVectoredExceptionHandler hook hit (printing once)");
|
||||
});
|
||||
|
||||
return launcher::signal::USE_VEH_WORKAROUND ? INVALID_HANDLE_VALUE : nullptr;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,85 @@
|
||||
#include "ami2000.h"
|
||||
#include "util/utils.h"
|
||||
#include "util/detour.h"
|
||||
#include "util/libutils.h"
|
||||
#include "misc/eamuse.h"
|
||||
#include "acio/icca/icca.h"
|
||||
|
||||
|
||||
static int CARD_TYPE = 0;
|
||||
static uint8_t CARD_UID[8];
|
||||
|
||||
static void __fastcall AMI2000_Initialize() {
|
||||
log_info("ami2000", "ami2000 initialize.");
|
||||
}
|
||||
|
||||
static void __fastcall AMI2000_Startup() {
|
||||
log_info("ami2000", "ami2000 startup.");
|
||||
}
|
||||
|
||||
static void __fastcall AMI2000_Cleanup() {
|
||||
log_info("ami2000", "ami2000 cleanup.");
|
||||
}
|
||||
|
||||
static int __fastcall AMI2000_IsDeviceError() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int __fastcall AMI2000_ReadCtrl() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const char *__fastcall AMI2000_GetRomVersion() {
|
||||
return "DUMMY";
|
||||
}
|
||||
|
||||
static int __fastcall AMI2000_GetCardType() {
|
||||
return CARD_TYPE;
|
||||
}
|
||||
|
||||
static uint64_t __fastcall AMI2000_GetCardIdentifier() {
|
||||
if (eamuse_card_insert_consume(1, 0)) {
|
||||
eamuse_get_card(1, 0, CARD_UID);
|
||||
CARD_TYPE = is_card_uid_felica(CARD_UID) ? 2 : 1;
|
||||
log_info("ami2000", "AMI2000_GetCardIdentifier: {:X}", bswap64(*(uint64_t*)CARD_UID));
|
||||
} else {
|
||||
CARD_TYPE = 0;
|
||||
return 0;
|
||||
}
|
||||
return bswap64(*(uint64_t *)CARD_UID);
|
||||
}
|
||||
|
||||
|
||||
void ami2000_attach() {
|
||||
auto ami2000 = libutils::try_module("iccr-ami2000-api.dll");
|
||||
|
||||
if (!ami2000) {
|
||||
log_warning("ami2000", "module not found, skipping.");
|
||||
return;
|
||||
}
|
||||
|
||||
// card unit
|
||||
detour::inline_hook((void *)AMI2000_Initialize, libutils::try_proc(
|
||||
ami2000, "AMI2000_Initialize"));
|
||||
detour::inline_hook((void *)AMI2000_Startup, libutils::try_proc(
|
||||
ami2000, "AMI2000_Startup"));
|
||||
detour::inline_hook((void *)AMI2000_IsDeviceError, libutils::try_proc(
|
||||
ami2000, "AMI2000_IsDeviceError"));
|
||||
detour::inline_hook((void *)AMI2000_ReadCtrl, libutils::try_proc(
|
||||
ami2000, "AMI2000_ReadCtrl"));
|
||||
detour::inline_hook((void *)AMI2000_Cleanup, libutils::try_proc(
|
||||
ami2000, "AMI2000_Cleanup"));
|
||||
detour::inline_hook((void *)AMI2000_GetRomVersion, libutils::try_proc(
|
||||
ami2000, "AMI2000_GetRomVersion"));
|
||||
detour::inline_hook((void *)AMI2000_GetCardType, libutils::try_proc(
|
||||
ami2000, "AMI2000_GetCardType"));
|
||||
detour::inline_hook((void *)AMI2000_GetCardIdentifier, libutils::try_proc(
|
||||
ami2000, "AMI2000_GetCardIdentifier"));
|
||||
|
||||
log_info("ami2000", "attached");
|
||||
}
|
||||
|
||||
void ami2000_detach()
|
||||
{
|
||||
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
#pragma once
|
||||
|
||||
void ami2000_attach();
|
||||
void ami2000_detach();
|
||||
+52
-20
@@ -119,6 +119,7 @@ static std::string EAMIO_DLL_NAME = "eamio.dll";
|
||||
static robin_hood::unordered_map<int, std::thread *> BT5API_THREADS;
|
||||
static robin_hood::unordered_map<int, int> BT5API_THREAD_RESULTS;
|
||||
static uint8_t BT5API_CARD_STATES[] = {0, 0};
|
||||
static uint16_t BT5API_KEYPAD_STATES[] = {0, 0};
|
||||
|
||||
static void bt5api_log(const char *bt5_module, const char *fmt, ...) {
|
||||
|
||||
@@ -240,46 +241,77 @@ void bt5api_hook(HINSTANCE module) {
|
||||
|
||||
void bt5api_poll_reader_card(uint8_t unit_no) {
|
||||
|
||||
// check if initialized
|
||||
if (!EAMIO_DLL) {
|
||||
// check if initialized or out of bounds
|
||||
if (!EAMIO_DLL || unit_no >= eamuse_get_game_keypads()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// poll
|
||||
if (!eam_io_poll(unit_no)) {
|
||||
log_warning("bt5api", "polling bt5api reader {} returned failure",
|
||||
log_warning("bt5api", "polling bt5api reader card {} returned failure",
|
||||
static_cast<int>(unit_no));
|
||||
return;
|
||||
}
|
||||
|
||||
// get sensor state
|
||||
uint8_t sensor_state = eam_io_get_sensor_state(unit_no);
|
||||
|
||||
// if we have a change in state.
|
||||
if(sensor_state != BT5API_CARD_STATES[unit_no]) {
|
||||
|
||||
// check for card in
|
||||
if (sensor_state > BT5API_CARD_STATES[unit_no]) {
|
||||
uint8_t card_id[8];
|
||||
eam_io_read_card(unit_no, card_id, 8);
|
||||
eamuse_card_insert(unit_no, card_id);
|
||||
// card inserted into the back.
|
||||
if(sensor_state & (1 << EAM_IO_SENSOR_BACK)) {
|
||||
uint8_t card_id[8];
|
||||
|
||||
log_info("bt5api", "card unit {} inserted", static_cast<int>(unit_no));
|
||||
|
||||
// do the bt5 dance
|
||||
eam_io_card_slot_cmd(unit_no, EAM_IO_CARD_SLOT_CMD_CLOSE);
|
||||
eam_io_poll(unit_no);
|
||||
eam_io_card_slot_cmd(unit_no, EAM_IO_CARD_SLOT_CMD_READ);
|
||||
eam_io_poll(unit_no);
|
||||
|
||||
// ask the api for the card, then insert it into our engine.
|
||||
eam_io_read_card(unit_no, card_id, 8);
|
||||
eamuse_card_insert(unit_no, card_id);
|
||||
}
|
||||
|
||||
// if card removed entirely, do another bt5 dance!
|
||||
if(sensor_state == 0) {
|
||||
eam_io_card_slot_cmd(unit_no, EAM_IO_CARD_SLOT_CMD_CLOSE);
|
||||
eam_io_poll(unit_no);
|
||||
eam_io_card_slot_cmd(unit_no, EAM_IO_CARD_SLOT_CMD_OPEN);
|
||||
|
||||
log_info("bt5api", "card unit {} removed", static_cast<int>(unit_no));
|
||||
}
|
||||
}
|
||||
|
||||
// save state
|
||||
|
||||
// save prev state
|
||||
BT5API_CARD_STATES[unit_no] = sensor_state;
|
||||
}
|
||||
|
||||
|
||||
void bt5api_poll_reader_keypad(uint8_t unit_no) {
|
||||
|
||||
// check if initialized
|
||||
if (!EAMIO_DLL) {
|
||||
// check if initialized or out of bounds
|
||||
if (!EAMIO_DLL || unit_no >= eamuse_get_game_keypads()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// poll
|
||||
if (!eam_io_poll(unit_no)) {
|
||||
log_warning("bt5api", "polling bt5api reader {} returned failure",
|
||||
static_cast<int>(unit_no));
|
||||
|
||||
uint16_t keypad_current = eam_io_get_keypad_state(unit_no);
|
||||
uint16_t keypad_changes = ~BT5API_KEYPAD_STATES[unit_no] & keypad_current;
|
||||
|
||||
// if user has pressed decimal for the first time, eject the slotted reader.
|
||||
if(keypad_changes & (1 << EAM_IO_KEYPAD_DECIMAL)) {
|
||||
log_info("bt5api", "card unit {} ejecting", static_cast<int>(unit_no));
|
||||
|
||||
eam_io_card_slot_cmd(unit_no, EAM_IO_CARD_SLOT_CMD_EJECT);
|
||||
}
|
||||
|
||||
// save the last pressed.
|
||||
BT5API_KEYPAD_STATES[unit_no] = keypad_current;
|
||||
|
||||
// get keypad
|
||||
eamuse_set_keypad_overrides_bt5(unit_no, eam_io_get_keypad_state(unit_no));
|
||||
// send off the state to the main engine.
|
||||
eamuse_set_keypad_overrides_bt5(unit_no, keypad_current);
|
||||
}
|
||||
|
||||
void bt5api_dispose() {
|
||||
|
||||
@@ -202,7 +202,14 @@ bool eamuse_card_insert_consume(int active_count, int unit_id) {
|
||||
|
||||
// bt5api
|
||||
if (BT5API_ENABLED) {
|
||||
bt5api_poll_reader_card((uint8_t) index);
|
||||
// some bt5api want to be polled in order.
|
||||
if (eamuse_get_game_keypads() > 1) {
|
||||
bt5api_poll_reader_card(1);
|
||||
bt5api_poll_reader_card(0);
|
||||
}
|
||||
else {
|
||||
bt5api_poll_reader_card((uint8_t) index);
|
||||
}
|
||||
}
|
||||
|
||||
// auto insert card
|
||||
|
||||
+120
-56
@@ -7,6 +7,8 @@
|
||||
#include "util/detour.h"
|
||||
#include "util/libutils.h"
|
||||
#include "util/circular_buffer.h"
|
||||
#include "util/socd_cleaner.h"
|
||||
#include "util/time.h"
|
||||
#include "misc/eamuse.h"
|
||||
#include "cfg/api.h"
|
||||
#include "acio/icca/icca.h"
|
||||
@@ -336,6 +338,9 @@ static int __cdecl gfdm_unit2_boot_initialize() {
|
||||
return 1;
|
||||
}
|
||||
|
||||
constexpr int LEFTY_X_CENTER = 2040;
|
||||
|
||||
// this seems to be only used in the I/O test menu, not in gameplay
|
||||
static void *__cdecl gfdm_unit_get_button_p(void *a1, int a2, size_t player) {
|
||||
memset(a1, 0, 64);
|
||||
|
||||
@@ -344,31 +349,61 @@ static void *__cdecl gfdm_unit_get_button_p(void *a1, int a2, size_t player) {
|
||||
return a1;
|
||||
}
|
||||
|
||||
// log_info("gitadora", "gfdm_unit_get_button_p: a2={}, player={}", a2, player);
|
||||
|
||||
// get buttons
|
||||
auto &buttons = games::gitadora::get_buttons();
|
||||
auto &analogs = games::gitadora::get_analogs();
|
||||
const auto wail_up =
|
||||
Buttons::getState(RI_MGR, buttons.at(gitadora_button_mapping[12 + 11 * (size_t) player]));
|
||||
const auto wail_down =
|
||||
Buttons::getState(RI_MGR, buttons.at(gitadora_button_mapping[13 + 11 * (size_t) player]));
|
||||
const auto wail_result =
|
||||
socd::get_guitar_wail(player, wail_up, wail_down, get_performance_milliseconds());
|
||||
|
||||
// X
|
||||
((int *) a1)[4] = a2 == 1 ? 4080 : -4080;
|
||||
if (analogs.at(player * 3 + 0).isSet())
|
||||
((int *) a1)[4] = lroundf(Analogs::getState(
|
||||
RI_MGR, analogs.at(gitadora_analog_mapping[player * 4 + 0])) * 8160.f) - 4080;
|
||||
// wail X
|
||||
((int *) a1)[4] = games::gitadora::is_player_lefty(player) ? LEFTY_X_CENTER : -4080;
|
||||
auto &analog_x = analogs.at(gitadora_analog_mapping[player * 4 + 0]);
|
||||
if (analog_x.isSet())
|
||||
((int *) a1)[4] = lroundf(Analogs::getState(RI_MGR, analog_x) * 8160.f) - 4080;
|
||||
|
||||
// Y
|
||||
// wail Y
|
||||
((int *) a1)[5] = 0;
|
||||
if (analogs.at(player * 3 + 1).isSet())
|
||||
((int *) a1)[5] = lroundf(Analogs::getState(
|
||||
RI_MGR, analogs.at(gitadora_analog_mapping[player * 4 + 1])) * 8160.f) - 4080;
|
||||
if (Buttons::getState(RI_MGR, buttons.at(gitadora_button_mapping[12 + 11 * (size_t) player])))
|
||||
((int *) a1)[5] = -4080;
|
||||
if (Buttons::getState(RI_MGR, buttons.at(gitadora_button_mapping[13 + 11 * (size_t) player])))
|
||||
((int *) a1)[5] = 4080;
|
||||
auto &analog_y = analogs.at(gitadora_analog_mapping[player * 4 + 1]);
|
||||
if (analog_y.isSet()) {
|
||||
((int *) a1)[5] = lroundf(Analogs::getState(RI_MGR, analog_y) * 8160.f) - 4080;
|
||||
}
|
||||
|
||||
// Z
|
||||
// digital wailing
|
||||
if (!games::gitadora::is_player_lefty(player)) {
|
||||
// righty
|
||||
if (wail_result == socd::TiltUp) {
|
||||
// top center
|
||||
((int *) a1)[4] = 0; // X
|
||||
((int *) a1)[5] = -4080; // Y
|
||||
} else if (wail_result == socd::TiltDown) {
|
||||
// bottom left; for lefty, bottom right
|
||||
((int *) a1)[4] = -4080; // X
|
||||
((int *) a1)[5] = 4080; // Y
|
||||
}
|
||||
} else {
|
||||
// lefty
|
||||
if (wail_result == socd::TiltUp) {
|
||||
// left
|
||||
((int *) a1)[4] = -4080; // X
|
||||
((int *) a1)[5] = 0; // Y
|
||||
} else if (wail_result == socd::TiltDown) {
|
||||
// right
|
||||
((int *) a1)[4] = 4080; // X
|
||||
((int *) a1)[5] = 0; // Y
|
||||
}
|
||||
}
|
||||
|
||||
// wail Z
|
||||
((int *) a1)[6] = 0;
|
||||
if (analogs.at(player * 3 + 2).isSet())
|
||||
((int *) a1)[6] = lroundf(Analogs::getState(
|
||||
RI_MGR, analogs.at(gitadora_analog_mapping[player * 4 + 2])) * 8160.f) - 4080;
|
||||
auto &analog_z = analogs.at(gitadora_analog_mapping[player * 4 + 2]);
|
||||
if (analog_z.isSet())
|
||||
((int *) a1)[6] = lroundf(Analogs::getState(RI_MGR, analog_z) * 8160.f) - 4080;
|
||||
|
||||
// return the same buffer
|
||||
return a1;
|
||||
@@ -487,24 +522,42 @@ static long __cdecl gfdm_unit_get_input_p(int device, size_t player) {
|
||||
if (games::gitadora::is_guitar()) {
|
||||
auto offset = player * 11;
|
||||
|
||||
// pick up
|
||||
if (Buttons::getState(RI_MGR, buttons.at(gitadora_button_mapping[3 + offset]))) {
|
||||
if (!GFDM_GF_PICK_STATE_UP[player]) {
|
||||
GFDM_GF_PICK_STATE_UP[player] = true;
|
||||
ret |= 0x80 | 0x20;
|
||||
const auto pick_up = Buttons::getState(RI_MGR, buttons.at(gitadora_button_mapping[3 + offset]));
|
||||
const auto pick_down = Buttons::getState(RI_MGR, buttons.at(gitadora_button_mapping[4 + offset]));
|
||||
if (!games::gitadora::PICK_ALGO.has_value()) {
|
||||
// legacy behavior (detect rising edges only, input for 1 frame)
|
||||
if (pick_up) {
|
||||
if (!GFDM_GF_PICK_STATE_UP[player]) {
|
||||
GFDM_GF_PICK_STATE_UP[player] = true;
|
||||
ret |= 0x80 | 0x20;
|
||||
}
|
||||
} else {
|
||||
GFDM_GF_PICK_STATE_UP[player] = false;
|
||||
}
|
||||
} else {
|
||||
GFDM_GF_PICK_STATE_UP[player] = false;
|
||||
}
|
||||
|
||||
// pick down
|
||||
if (Buttons::getState(RI_MGR, buttons.at(gitadora_button_mapping[4 + offset]))) {
|
||||
if (!GFDM_GF_PICK_STATE_DOWN[player]) {
|
||||
GFDM_GF_PICK_STATE_DOWN[player] = true;
|
||||
ret |= 0x100 | 0x20;
|
||||
if (pick_down) {
|
||||
if (!GFDM_GF_PICK_STATE_DOWN[player]) {
|
||||
GFDM_GF_PICK_STATE_DOWN[player] = true;
|
||||
ret |= 0x100 | 0x20;
|
||||
}
|
||||
} else {
|
||||
GFDM_GF_PICK_STATE_DOWN[player] = false;
|
||||
}
|
||||
} else {
|
||||
GFDM_GF_PICK_STATE_DOWN[player] = false;
|
||||
const auto socd = socd::socd_clean(
|
||||
player,
|
||||
pick_up,
|
||||
pick_down,
|
||||
get_performance_milliseconds(),
|
||||
games::gitadora::PICK_ALGO.value()
|
||||
);
|
||||
if (socd == socd::SocdCCW) {
|
||||
ret |= 0x80 | 0x20; // pick up
|
||||
} else if (socd == socd::SocdCW) {
|
||||
ret |= 0x100 | 0x20; // pick down
|
||||
} else if (socd == socd::SocdBoth) {
|
||||
ret |= 0x100 | 0x80 | 0x20; // up and down
|
||||
}
|
||||
}
|
||||
|
||||
// button R
|
||||
@@ -585,63 +638,74 @@ static long __cdecl gfdm_unit2_get_input(int device) {
|
||||
return gfdm_unit_get_input_p(device, 1);
|
||||
}
|
||||
|
||||
// this is used in gameplay
|
||||
static long __cdecl gfdm_unit_get_sensor_gf_p(int a1, int a2, size_t player) {
|
||||
|
||||
// return if it's actually drum mania
|
||||
if (games::gitadora::is_drum())
|
||||
return 0;
|
||||
|
||||
// log_info("gitadora", "gfdm_unit_get_sensor_gf_p: a1={}, a2={}, player={}", a1, a2, player);
|
||||
|
||||
// get buttons and analogs
|
||||
auto &buttons = games::gitadora::get_buttons();
|
||||
auto &analogs = games::gitadora::get_analogs();
|
||||
|
||||
// X
|
||||
// figure out digital wail Y
|
||||
const size_t offset = (size_t) player * 11;
|
||||
const auto wail_up = Buttons::getState(RI_MGR, buttons.at(gitadora_button_mapping[12 + offset]));
|
||||
const auto wail_down = Buttons::getState(RI_MGR, buttons.at(gitadora_button_mapping[13 + offset]));
|
||||
const auto wail_result = socd::get_guitar_wail(player, wail_up, wail_down, get_performance_milliseconds());
|
||||
|
||||
// wail X
|
||||
if (a2 == 0) {
|
||||
long ret = games::gitadora::is_player_lefty(player) ? LEFTY_X_CENTER : -4080;
|
||||
|
||||
// analog override
|
||||
if (analogs.at(gitadora_analog_mapping[player * 3 + 0]).isSet()) {
|
||||
return lroundf(Analogs::getState(
|
||||
RI_MGR, analogs.at(gitadora_analog_mapping[player * 4 + 0])) * 8160.f) - 4080;
|
||||
auto &analog_x = analogs.at(gitadora_analog_mapping[player * 4 + 0]);
|
||||
if (analog_x.isSet()) {
|
||||
ret = lroundf(Analogs::getState(RI_MGR, analog_x) * 8160.f) - 4080;
|
||||
}
|
||||
|
||||
// digital wail up/down
|
||||
if (wail_result == socd::TiltUp) {
|
||||
ret = games::gitadora::is_player_lefty(player) ? -4080 : 0;
|
||||
} else if (wail_result == socd::TiltDown) {
|
||||
ret = games::gitadora::is_player_lefty(player) ? +4080 : -4080;
|
||||
}
|
||||
|
||||
// default
|
||||
return a1 == 1 ? 4080 : -4080;
|
||||
return ret;
|
||||
}
|
||||
|
||||
// Y
|
||||
// wail Y
|
||||
if (a2 == 1) {
|
||||
long ret = 0;
|
||||
|
||||
// analog override
|
||||
if (analogs.at(player * 3 + 1).isSet()) {
|
||||
return lroundf(Analogs::getState(
|
||||
RI_MGR, analogs.at(gitadora_analog_mapping[player * 4 + 1])) * 8160.f) - 4080;
|
||||
auto &analog_y = analogs.at(gitadora_analog_mapping[player * 4 + 1]);
|
||||
if (analog_y.isSet()) {
|
||||
ret = lroundf(Analogs::getState(RI_MGR, analog_y) * 8160.f) - 4080;
|
||||
}
|
||||
|
||||
// variables
|
||||
long ret = 0;
|
||||
size_t offset = (size_t) player * 11;
|
||||
|
||||
// wail up
|
||||
if (Buttons::getState(RI_MGR, buttons.at(gitadora_button_mapping[12 + offset]))) {
|
||||
ret -= 4080;
|
||||
}
|
||||
|
||||
// wail down
|
||||
if (Buttons::getState(RI_MGR, buttons.at(gitadora_button_mapping[13 + offset]))) {
|
||||
ret += 4080;
|
||||
// digital wail up/down
|
||||
if (wail_result == socd::TiltUp) {
|
||||
ret = games::gitadora::is_player_lefty(player) ? 0 : -4080;
|
||||
} else if (wail_result == socd::TiltDown) {
|
||||
ret = games::gitadora::is_player_lefty(player) ? 0 : 4080;
|
||||
}
|
||||
|
||||
// return value
|
||||
return ret;
|
||||
}
|
||||
|
||||
// Z
|
||||
// wail Z
|
||||
if (a2 == 2) {
|
||||
|
||||
// analog override
|
||||
if (analogs.at(gitadora_analog_mapping[player * 3 + 2]).isSet()) {
|
||||
return lroundf(Analogs::getState(
|
||||
RI_MGR, analogs.at(gitadora_analog_mapping[player * 4 + 2])) * 8160.f) - 4080;
|
||||
auto &analog_z = analogs.at(gitadora_analog_mapping[player * 4 + 2]);
|
||||
if (analog_z.isSet()) {
|
||||
return lroundf(Analogs::getState(RI_MGR, analog_z) * 8160.f) - 4080;
|
||||
}
|
||||
|
||||
// default
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
#include <thread>
|
||||
|
||||
#include "cfg/screen_resize.h"
|
||||
#include "games/gitadora/gitadora.h"
|
||||
#include "games/iidx/iidx.h"
|
||||
#include "hooks/graphics/graphics.h"
|
||||
#include "overlay/overlay.h"
|
||||
@@ -390,7 +391,10 @@ namespace wintouchemu {
|
||||
} else if (avs::game::is_model("LDJ") && !GENERIC_SUB_WINDOW_FULLSIZE) {
|
||||
// overlay subscreen in IIDX
|
||||
// use mouse position as ImGui overlay will block the touch window
|
||||
log_info("wintouchemu", "use mouse cursor API for overlay subscreen");
|
||||
log_info("wintouchemu", "use mouse cursor API for ldj 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;
|
||||
} else {
|
||||
// create touch window - create overlay if not yet existing at this point
|
||||
|
||||
@@ -236,6 +236,26 @@ namespace nvapi {
|
||||
return NvAPI_DRS_SetSetting(h_session, h_profile, &drs_setting);
|
||||
}
|
||||
|
||||
NvAPI_Status set_refresh_rate(NvDRSSessionHandle h_session, NvDRSProfileHandle h_profile) {
|
||||
auto drs_setting = NVDRS_SETTING {};
|
||||
drs_setting.version = NVDRS_SETTING_VER;
|
||||
drs_setting.settingId = REFRESH_RATE_OVERRIDE_ID;
|
||||
drs_setting.settingType = NVDRS_DWORD_TYPE;
|
||||
drs_setting.u32PredefinedValue = REFRESH_RATE_OVERRIDE_APPLICATION_CONTROLLED;
|
||||
drs_setting.u32CurrentValue = REFRESH_RATE_OVERRIDE_APPLICATION_CONTROLLED;
|
||||
return NvAPI_DRS_SetSetting(h_session, h_profile, &drs_setting);
|
||||
}
|
||||
|
||||
NvAPI_Status set_max_fps(NvDRSSessionHandle h_session, NvDRSProfileHandle h_profile) {
|
||||
auto drs_setting = NVDRS_SETTING {};
|
||||
drs_setting.version = NVDRS_SETTING_VER;
|
||||
drs_setting.settingId = FRL_FPS_ID;
|
||||
drs_setting.settingType = NVDRS_DWORD_TYPE;
|
||||
drs_setting.u32PredefinedValue = FRL_FPS_DISABLED;
|
||||
drs_setting.u32CurrentValue = FRL_FPS_DISABLED;
|
||||
return NvAPI_DRS_SetSetting(h_session, h_profile, &drs_setting);
|
||||
}
|
||||
|
||||
void set_profile_settings() {
|
||||
if (!NVAPI_MODULE_LOAD_SUCCEEDED) {
|
||||
return;
|
||||
@@ -266,22 +286,26 @@ namespace nvapi {
|
||||
log_info("nvapi", "setting VSync to Application Controlled...");
|
||||
if ((status = set_vsync_mode(h_session, h_profile)) != NVAPI_OK) {
|
||||
log_warning("nvapi", "could not set VSync mode: {}", get_error_message(status));
|
||||
NvAPI_DRS_DestroySession(h_session);
|
||||
return;
|
||||
}
|
||||
|
||||
log_info("nvapi", "applying preferred PState to Maximum Performance...");
|
||||
if ((status = set_gpu_power_state(h_session, h_profile)) != NVAPI_OK) {
|
||||
log_warning("nvapi", "could not set preferred PState: {}", get_error_message(status));
|
||||
NvAPI_DRS_DestroySession(h_session);
|
||||
return;
|
||||
}
|
||||
|
||||
log_info("nvapi", "disabling G-SYNC...");
|
||||
if ((status = set_gsync_mode(h_session, h_profile)) != NVAPI_OK) {
|
||||
log_warning("nvapi", "could not set G-Sync mode: {}", get_error_message(status));
|
||||
NvAPI_DRS_DestroySession(h_session);
|
||||
return;
|
||||
}
|
||||
|
||||
log_info("nvapi", "setting preferred refresh rate to Application Controlled...");
|
||||
if ((status = set_refresh_rate(h_session, h_profile)) != NVAPI_OK) {
|
||||
log_warning("nvapi", "could not set preferred refresh rate: {}", get_error_message(status));
|
||||
}
|
||||
|
||||
log_info("nvapi", "disabling max framerate...");
|
||||
if ((status = set_max_fps(h_session, h_profile)) != NVAPI_OK) {
|
||||
log_warning("nvapi", "could not disable max framerate: {}", get_error_message(status));
|
||||
}
|
||||
|
||||
log_info("nvapi", "saving settings for DRS session...");
|
||||
|
||||
@@ -26,7 +26,7 @@ namespace ImGui {
|
||||
|
||||
void HelpMarker(const char* desc) {
|
||||
ImGui::TextDisabled("(?)");
|
||||
if (ImGui::IsItemHovered()) {
|
||||
if (ImGui::IsItemHovered(TOOLTIP_FLAGS)) {
|
||||
HelpTooltip(desc);
|
||||
}
|
||||
}
|
||||
@@ -59,7 +59,7 @@ namespace ImGui {
|
||||
ImGui::PushStyleColor(ImGuiCol_TextDisabled, ImVec4(1.f, 1.f, 0.f, 1.f));
|
||||
ImGui::TextDisabled("(!)");
|
||||
ImGui::PopStyleColor();
|
||||
if (ImGui::IsItemHovered()) {
|
||||
if (ImGui::IsItemHovered(TOOLTIP_FLAGS)) {
|
||||
WarnTooltip(desc, warn);
|
||||
}
|
||||
}
|
||||
@@ -92,4 +92,77 @@ namespace ImGui {
|
||||
ImGui::GetColorU32(ImGuiCol_PlotHistogram),
|
||||
thickness);
|
||||
}
|
||||
|
||||
std::string TruncateText(const std::string& p_text, float p_truncated_width, bool &truncated) {
|
||||
std::string truncated_text = p_text;
|
||||
|
||||
const float text_width =
|
||||
ImGui::CalcTextSize(p_text.c_str(), nullptr, true).x;
|
||||
|
||||
if (text_width > p_truncated_width) {
|
||||
truncated = true;
|
||||
constexpr const char* ELLIPSIS = " ...";
|
||||
const float ellipsis_size = ImGui::CalcTextSize(ELLIPSIS).x;
|
||||
|
||||
int visible_chars = 0;
|
||||
for (size_t i = 0; i < p_text.size(); i++) {
|
||||
const float current_width = ImGui::CalcTextSize(
|
||||
p_text.substr(0, i).c_str(), nullptr, true)
|
||||
.x;
|
||||
if (current_width + ellipsis_size > p_truncated_width) {
|
||||
break;
|
||||
}
|
||||
|
||||
visible_chars = i;
|
||||
}
|
||||
|
||||
truncated_text = (p_text.substr(0, visible_chars) + ELLIPSIS).c_str();
|
||||
}
|
||||
|
||||
return truncated_text;
|
||||
}
|
||||
|
||||
void TextTruncated(const std::string& p_text, float p_truncated_width) {
|
||||
if (p_text.empty()) {
|
||||
ImGui::TextDisabled("-");
|
||||
return;
|
||||
}
|
||||
bool truncated = false;
|
||||
ImGui::TextUnformatted(TruncateText(p_text, p_truncated_width, truncated).c_str());
|
||||
if (truncated && ImGui::IsItemHovered(TOOLTIP_FLAGS)) {
|
||||
ImGui::HelpTooltip(p_text.c_str());
|
||||
}
|
||||
}
|
||||
|
||||
bool DeleteButton(const std::string& tooltip) {
|
||||
ImGui::PushID(tooltip.c_str());
|
||||
ImGui::PushStyleColor(ImGuiCol_Button, ImGui::GetColorU32(ImGuiCol_Button, 0.7f));
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(3.f, 2.f));
|
||||
bool clicked = ImGui::SmallButton("\u00D7"); // multiplication sign (×)
|
||||
ImGui::PopStyleVar();
|
||||
ImGui::PopStyleColor();
|
||||
if (!tooltip.empty() && ImGui::IsItemHovered(TOOLTIP_FLAGS)) {
|
||||
ImGui::SameLine();
|
||||
ImGui::HelpTooltip(tooltip.c_str());
|
||||
}
|
||||
ImGui::PopID();
|
||||
return clicked;
|
||||
}
|
||||
|
||||
bool ClearButton(const std::string& tooltip) {
|
||||
ImGui::PushID(tooltip.c_str());
|
||||
// same colors as a checkbox
|
||||
ImGui::PushStyleColor(ImGuiCol_Button, ImGui::GetColorU32(ImGuiCol_FrameBg));
|
||||
ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImGui::GetColorU32(ImGuiCol_FrameBgHovered));
|
||||
ImGui::PushStyleColor(ImGuiCol_ButtonActive, ImGui::GetColorU32(ImGuiCol_FrameBgActive));
|
||||
ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(0.97f, 0.97f, 0.97f, 1.00f));
|
||||
bool clicked = ImGui::Button("\u00D7"); // multiplication sign (×)
|
||||
ImGui::PopStyleColor(4);
|
||||
if (!tooltip.empty() && ImGui::IsItemHovered(TOOLTIP_FLAGS)) {
|
||||
ImGui::SameLine();
|
||||
ImGui::HelpTooltip(tooltip.c_str());
|
||||
}
|
||||
ImGui::PopID();
|
||||
return clicked;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,12 +1,23 @@
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include "external/imgui/imgui.h"
|
||||
|
||||
namespace ImGui {
|
||||
|
||||
constexpr ImGuiHoveredFlags TOOLTIP_FLAGS =
|
||||
(ImGuiHoveredFlags_ForTooltip | ImGuiHoveredFlags_AllowWhenDisabled);
|
||||
|
||||
void HelpTooltip(const char* desc);
|
||||
void HelpMarker(const char* desc);
|
||||
void WarnTooltip(const char* desc, const char* warn);
|
||||
void WarnMarker(const char* desc, const char* warn);
|
||||
void DummyMarker();
|
||||
|
||||
void Knob(float fraction, float size, float thickness = 2.f,
|
||||
float pos_x = -1.f, float pos_y = -1.f);
|
||||
|
||||
void TextTruncated(const std::string& p_text, float p_truncated_width);
|
||||
bool DeleteButton(const std::string& tooltip);
|
||||
bool ClearButton(const std::string& tooltip);
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
#include "avs/game.h"
|
||||
#include "cfg/configurator.h"
|
||||
#include "games/io.h"
|
||||
#include "games/gitadora/gitadora.h"
|
||||
#include "games/iidx/iidx.h"
|
||||
#include "hooks/graphics/graphics.h"
|
||||
#include "misc/eamuse.h"
|
||||
@@ -28,6 +29,7 @@
|
||||
#include "windows/generic_sub.h"
|
||||
#include "windows/iidx_seg.h"
|
||||
#include "windows/iidx_sub.h"
|
||||
#include "windows/gfdm_sub.h"
|
||||
#include "windows/drs_dancefloor.h"
|
||||
#include "windows/iopanel.h"
|
||||
#include "windows/iopanel_ddr.h"
|
||||
@@ -51,16 +53,26 @@ namespace overlay {
|
||||
bool AUTO_SHOW_IOPANEL = false;
|
||||
bool AUTO_SHOW_KEYPAD_P1 = false;
|
||||
bool AUTO_SHOW_KEYPAD_P2 = false;
|
||||
|
||||
bool USE_WM_CHAR_FOR_IMGUI_CHAR_INPUT = false;
|
||||
|
||||
bool FPS_SHOULD_FLIP = false;
|
||||
std::optional<uint32_t> UI_SCALE_PERCENT;
|
||||
|
||||
// global
|
||||
std::mutex OVERLAY_MUTEX;
|
||||
std::unique_ptr<overlay::SpiceOverlay> OVERLAY = nullptr;
|
||||
ImFont* DSEG_FONT = nullptr;
|
||||
bool SHOW_DEBUG_LOG_WINDOW = false;
|
||||
|
||||
ImVec2 apply_scaling_to_vector(float x, float y) {
|
||||
if (!UI_SCALE_PERCENT.has_value()) {
|
||||
return ImVec2(x, y);
|
||||
}
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
static void *ImGui_Alloc(size_t sz, void *user_data) {
|
||||
@@ -170,6 +182,12 @@ void overlay::SpiceOverlay::init() {
|
||||
style.WindowRounding = 0;
|
||||
}
|
||||
|
||||
if (UI_SCALE_PERCENT.has_value()) {
|
||||
const auto scale = static_cast<float>(UI_SCALE_PERCENT.value()) / 100.f;
|
||||
ImGui::GetStyle().ScaleAllSizes(scale);
|
||||
ImGui::GetIO().FontGlobalScale = scale;
|
||||
}
|
||||
|
||||
// red theme based on:
|
||||
// https://github.com/ocornut/imgui/issues/707#issuecomment-760220280
|
||||
// r, g, b, a
|
||||
@@ -245,7 +263,6 @@ void overlay::SpiceOverlay::init() {
|
||||
|
||||
if (!cfg::CONFIGURATOR_STANDALONE) {
|
||||
io.ConfigFlags |= ImGuiConfigFlags_DockingEnable;
|
||||
io.ConfigFlags |= ImGuiConfigFlags_ViewportsEnable;
|
||||
}
|
||||
if (is_touch_available("SpiceOverlay::init")) {
|
||||
io.ConfigFlags |= ImGuiConfigFlags_IsTouchScreen;
|
||||
@@ -366,7 +383,8 @@ void overlay::SpiceOverlay::init() {
|
||||
window_iopanel = new overlay::windows::IIDXIOPanel(this);
|
||||
} else if (avs::game::is_model("MDX")) {
|
||||
window_iopanel = new overlay::windows::DDRIOPanel(this);
|
||||
} else if (avs::game::is_model({"J32", "J33", "K32", "K33", "L32", "L33", "M32"})) {
|
||||
} else if (avs::game::is_model({"J32", "J33", "K32", "K33", "L32", "L33", "M32"}) &&
|
||||
!games::gitadora::is_arena_model()) {
|
||||
window_iopanel = new overlay::windows::GitadoraIOPanel(this);
|
||||
} else {
|
||||
window_iopanel = new overlay::windows::IOPanel(this);
|
||||
@@ -393,7 +411,10 @@ void overlay::SpiceOverlay::init() {
|
||||
window_sub = new overlay::windows::DRSDanceFloorDisplay(this);
|
||||
} else if (avs::game::is_model("KFC")) {
|
||||
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);
|
||||
}
|
||||
|
||||
if (window_sub) {
|
||||
this->window_add(window_sub);
|
||||
if (AUTO_SHOW_SUBSCREEN) {
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <functional>
|
||||
#include <optional>
|
||||
#include <vector>
|
||||
#include <windows.h>
|
||||
#include <d3d9.h>
|
||||
@@ -27,6 +28,18 @@ namespace overlay {
|
||||
extern bool USE_WM_CHAR_FOR_IMGUI_CHAR_INPUT;
|
||||
extern bool FPS_SHOULD_FLIP;
|
||||
extern bool SHOW_DEBUG_LOG_WINDOW;
|
||||
extern std::optional<uint32_t> UI_SCALE_PERCENT;
|
||||
|
||||
template <typename T>
|
||||
float apply_scaling(T input) {
|
||||
if (!UI_SCALE_PERCENT.has_value()) {
|
||||
return input;
|
||||
}
|
||||
return static_cast<float>(input) * UI_SCALE_PERCENT.value() / 100.f;
|
||||
}
|
||||
|
||||
ImVec2 apply_scaling_to_vector(const ImVec2& input);
|
||||
ImVec2 apply_scaling_to_vector(float x, float y);
|
||||
|
||||
class SpiceOverlay {
|
||||
public:
|
||||
|
||||
@@ -21,7 +21,7 @@ namespace overlay::windows {
|
||||
CameraControl::CameraControl(SpiceOverlay *overlay) : Window(overlay) {
|
||||
this->title = "IIDX Camera Control";
|
||||
this->flags |= ImGuiWindowFlags_AlwaysAutoResize;
|
||||
this->init_pos = ImVec2(40, 40);
|
||||
this->init_pos = overlay::apply_scaling_to_vector(40, 40);
|
||||
this->toggle_button = games::OverlayButtons::ToggleCameraControl;
|
||||
}
|
||||
|
||||
|
||||
@@ -17,7 +17,7 @@ namespace overlay::windows {
|
||||
|
||||
CardManager::CardManager(SpiceOverlay *overlay) : Window(overlay) {
|
||||
this->title = "Card Manager";
|
||||
this->init_size = ImVec2(420, 420);
|
||||
this->init_size = overlay::apply_scaling_to_vector(ImVec2(420, 420));
|
||||
|
||||
if (cfg::CONFIGURATOR_STANDALONE) {
|
||||
this->init_pos = ImVec2(40, 40);
|
||||
@@ -303,7 +303,7 @@ namespace overlay::windows {
|
||||
//
|
||||
// setting ImGuiInputTextFlags_CallbackCharFilter and pressing escape doesn't cause below
|
||||
// to return true, making it necessary to provide a callback...
|
||||
ImGui::SetNextItemWidth(240);
|
||||
ImGui::SetNextItemWidth(overlay::apply_scaling(240));
|
||||
if (ImGui::InputTextWithHint("", "Type here to search..", &this->search_filter)) {
|
||||
this->current_card = nullptr;
|
||||
this->search_filter_in_lower_case = strtolower(this->search_filter);
|
||||
|
||||
+2001
-1733
File diff suppressed because it is too large
Load Diff
@@ -38,7 +38,6 @@ namespace overlay::windows {
|
||||
ConfigTab tab_selected = ConfigTab::CONFIG_TAB_INVALID;
|
||||
|
||||
// buttons tab
|
||||
int buttons_page = 0;
|
||||
bool buttons_keyboard_state[0xFF];
|
||||
bool buttons_bind_active = false;
|
||||
bool buttons_many_active = false;
|
||||
@@ -55,7 +54,6 @@ namespace overlay::windows {
|
||||
int analogs_devices_control_selected = -1;
|
||||
|
||||
// lights tab
|
||||
int lights_page = 0;
|
||||
std::vector<rawinput::Device *> lights_devices;
|
||||
int lights_devices_selected = -1;
|
||||
int lights_devices_control_selected = -1;
|
||||
@@ -78,17 +76,33 @@ namespace overlay::windows {
|
||||
std::string search_filter = "";
|
||||
std::string search_filter_in_lower_case = "";
|
||||
|
||||
public:
|
||||
Config(SpiceOverlay *overlay);
|
||||
~Config() override;
|
||||
void build_buttons(const std::string &name, std::vector<Button> *buttons, int min = 0, int max = -1);
|
||||
void build_button(
|
||||
const std::string &name,
|
||||
Button &primary_button,
|
||||
Button *button,
|
||||
const int button_it,
|
||||
const int button_it_max,
|
||||
const int alt_index);
|
||||
|
||||
void bind_button_popup(const std::string &bind_name, Button *button, const int button_it_max, const int alt_index);
|
||||
void naive_button_popup(const std::string &naive_string, Button *button, const int button_it_max, const int alt_index);
|
||||
void edit_button_popup(
|
||||
const std::string &edit_name,
|
||||
const std::string &button_display,
|
||||
Button *button,
|
||||
const float button_velocity,
|
||||
const int alt_index);
|
||||
void clear_button(Button *button, const int alt_index);
|
||||
|
||||
void read_card(int player = -1);
|
||||
void write_card(int player);
|
||||
void build_content() override;
|
||||
void build_buttons(const std::string &name, std::vector<Button> *buttons,
|
||||
int min = 0, int max = -1);
|
||||
void build_analogs(const std::string &name, std::vector<Analog> *analogs);
|
||||
void edit_analog_popup(Analog &analog);
|
||||
|
||||
void build_lights(const std::string &name, std::vector<Light> *lights);
|
||||
void build_light(Light &primary_light, Light *light, const int light_index, const int alt_index);
|
||||
void clear_light(Light *light, const int alt_index);
|
||||
void edit_light_popup(Light &primary_light, Light *light, const int alt_index);
|
||||
|
||||
void build_cards();
|
||||
void build_options(
|
||||
std::vector<Option> *options, const std::string &category, const std::string *filter=nullptr);
|
||||
@@ -96,8 +110,18 @@ namespace overlay::windows {
|
||||
void build_launcher();
|
||||
void launch_shell(LPCSTR app, LPCSTR file=nullptr);
|
||||
|
||||
static void build_page_selector(int *page);
|
||||
void build_menu(int *game_selected);
|
||||
void shutdown_system(bool force, bool reboot_instead);
|
||||
|
||||
void set_alternating_row_colors(const int row_index);
|
||||
|
||||
public:
|
||||
Config(SpiceOverlay *overlay);
|
||||
~Config() override;
|
||||
|
||||
void read_card(int player = -1);
|
||||
void write_card(int player);
|
||||
void build_content() override;
|
||||
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
#include "exitprompt.h"
|
||||
#include "avs/game.h"
|
||||
#include "misc/eamuse.h"
|
||||
#include "util/logging.h"
|
||||
#include "games/iidx/iidx.h"
|
||||
#include "games/gitadora/gitadora.h"
|
||||
|
||||
namespace overlay::windows {
|
||||
|
||||
@@ -8,10 +11,10 @@ namespace overlay::windows {
|
||||
this->title = "spice2x";
|
||||
this->init_size = ImVec2(
|
||||
(ImGui::GetFontSize() * 14) + (ImGui::GetStyle().ItemSpacing.x * 2),
|
||||
120);
|
||||
overlay::apply_scaling(120));
|
||||
this->init_pos = ImVec2(
|
||||
ImGui::GetIO().DisplaySize.x / 2 - this->init_size.x / 2,
|
||||
10);
|
||||
overlay::apply_scaling(10));
|
||||
|
||||
this->flags = ImGuiWindowFlags_NoResize
|
||||
| ImGuiWindowFlags_NoCollapse
|
||||
@@ -55,7 +58,23 @@ namespace overlay::windows {
|
||||
}
|
||||
ImGui::Spacing();
|
||||
build_button(this->overlay->window_config, "Show Config", size, NextItem::NEW_LINE, false);
|
||||
build_button(this->overlay->window_sub, "Show Subscreen", size, NextItem::NEW_LINE, false);
|
||||
|
||||
std::string sub = "Show Subscreen";
|
||||
if (avs::game::is_model("LDJ")) {
|
||||
if (games::iidx::TDJ_MODE) {
|
||||
sub = "Show TDJ Subscreen";
|
||||
} else {
|
||||
sub = "Show LDJ LED Ticker";
|
||||
}
|
||||
} else if (avs::game::is_model("KFC")) {
|
||||
sub = "Show Valkyrie Subscreen";
|
||||
} else if (avs::game::is_model("REC")) {
|
||||
sub = "Show DRS Dance Floor";
|
||||
} else if (games::gitadora::is_arena_model() && games::gitadora::ARENA_SINGLE_WINDOW) {
|
||||
sub = "Show GITADORA Subscreen";
|
||||
}
|
||||
|
||||
build_button(this->overlay->window_sub, sub, size, NextItem::NEW_LINE, false);
|
||||
|
||||
ImGui::TextDisabled("Graphics");
|
||||
build_button(this->overlay->window_camera, "Camera control", size, NextItem::NEW_LINE);
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#include <iomanip>
|
||||
#include <sstream>
|
||||
#include "external/fmt/include/fmt/chrono.h"
|
||||
#include "fps.h"
|
||||
|
||||
namespace overlay::windows {
|
||||
@@ -15,13 +16,17 @@ namespace overlay::windows {
|
||||
| ImGuiWindowFlags_NoNavInputs
|
||||
| ImGuiWindowFlags_NoDocking;
|
||||
this->bg_alpha = 0.5f;
|
||||
this->start_time = std::chrono::system_clock::now();
|
||||
this->start_time =
|
||||
std::chrono::floor<std::chrono::seconds>(std::chrono::system_clock::now());
|
||||
}
|
||||
|
||||
void FPS::calculate_initial_window() {
|
||||
// width is 114x82 px with window decoration, 98x47 for the content
|
||||
int pos_x = overlay::FPS_SHOULD_FLIP ? 8 : ImGui::GetIO().DisplaySize.x - 122;
|
||||
this->init_pos = ImVec2(pos_x, 8);
|
||||
int pos_x =
|
||||
overlay::FPS_SHOULD_FLIP ?
|
||||
overlay::apply_scaling(8) :
|
||||
ImGui::GetIO().DisplaySize.x - overlay::apply_scaling(122);
|
||||
this->init_pos = ImVec2(pos_x, overlay::apply_scaling(8));
|
||||
}
|
||||
|
||||
void FPS::build_content() {
|
||||
@@ -31,23 +36,23 @@ namespace overlay::windows {
|
||||
ImGui::Text("FPS: %.1f", io.Framerate);
|
||||
// ImGui::Text("FT: %.2fms", 1000 / io.Framerate);
|
||||
|
||||
auto now = std::chrono::system_clock::now();
|
||||
const auto now = std::chrono::system_clock::now();
|
||||
const auto now_s = std::chrono::floor<std::chrono::seconds>(now);
|
||||
|
||||
// current time
|
||||
{
|
||||
auto now_t = std::chrono::system_clock::to_time_t(now);
|
||||
static CHAR buf[48];
|
||||
std::strftime(buf, sizeof(buf), "Time: %H:%M:%S", std::localtime(&now_t));
|
||||
ImGui::Text(buf);
|
||||
const std::time_t tt = std::chrono::system_clock::to_time_t(now_s);
|
||||
std::tm local_tm{};
|
||||
localtime_s(&local_tm, &tt);
|
||||
ImGui::TextUnformatted(fmt::format("Time: {:%H:%M:%S}", local_tm).c_str());
|
||||
}
|
||||
|
||||
// elapsed time
|
||||
{
|
||||
auto d = now - this->start_time;
|
||||
const auto h = std::chrono::duration_cast<std::chrono::hours>(d);
|
||||
const auto m = std::chrono::duration_cast<std::chrono::minutes>(d - h);
|
||||
const auto s = std::chrono::duration_cast<std::chrono::seconds>(d - h - m);
|
||||
ImGui::Text("Up: %02d:%02d:%02d", (int)h.count(), (int)m.count(), (int)s.count());
|
||||
const auto uptime = now_s - this->start_time;
|
||||
ImGui::TextUnformatted(
|
||||
fmt::format("Up: {:%H:%M:%S}",
|
||||
std::chrono::floor<std::chrono::seconds>(uptime)).c_str());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -85,10 +85,12 @@ namespace overlay::windows {
|
||||
}
|
||||
|
||||
void GenericSubScreen::touch_transform(const ImVec2 xy_in, LONG *x_out, LONG *y_out) {}
|
||||
void GenericSubScreen::check_for_errors() {}
|
||||
|
||||
void GenericSubScreen::build_content() {
|
||||
this->flags |= ImGuiWindowFlags_NoBackground;
|
||||
|
||||
check_for_errors();
|
||||
if (this->disabled_message.has_value()) {
|
||||
this->draws_window = true;
|
||||
this->flags &= ~ImGuiWindowFlags_NoBackground;
|
||||
|
||||
@@ -31,6 +31,7 @@ namespace overlay::windows {
|
||||
|
||||
protected:
|
||||
virtual void touch_transform(const ImVec2 xy_in, LONG *x_out, LONG *y_out);
|
||||
virtual void check_for_errors();
|
||||
ImVec2 overlay_content_top_left;
|
||||
ImVec2 overlay_content_size;
|
||||
std::optional<std::string> disabled_message = std::nullopt;
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user