mirror of
https://github.com/spice2x/spice2x.github.io.git
synced 2026-08-01 22:30:42 -07:00
Compare commits
43 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 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 | |||
| c767918729 | |||
| fbc8bc2502 | |||
| c38f42cb19 | |||
| 42d8bbc92f | |||
| a09905792d | |||
| f113eb4f52 | |||
| f726748ebe |
+11
-2
@@ -1,8 +1,7 @@
|
|||||||
## Contributing
|
## Contributing
|
||||||
Baseline rules for patch submissions are as follows. Any patches violating the rules below will not be accepted.
|
Baseline rules for patch submissions are as follows. Any patches violating the rules below will not be accepted.
|
||||||
|
|
||||||
### New as of March 2025
|
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.
|
||||||
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.
|
|
||||||
|
|
||||||
### Adding support for games
|
### 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 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.
|
* 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
|
### 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.
|
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.
|
||||||
@@ -49,6 +56,8 @@ Watch out for legacy OS compatibility. Currently, the minimum support floor is W
|
|||||||
|
|
||||||
### Code style requirements
|
### Code style requirements
|
||||||
* Indents are four spaces.
|
* 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.
|
* Always use \{ curly braces \} when appropriate; do not omit them even when it's optional; such as `for` `if` `else`, etc.
|
||||||
|
|
||||||
OK:
|
OK:
|
||||||
|
|||||||
@@ -118,6 +118,9 @@ elseif(CMAKE_CXX_COMPILER_ID STREQUAL "Clang")
|
|||||||
set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -static")
|
set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -static")
|
||||||
else()
|
else()
|
||||||
|
|
||||||
|
# warnings as errors
|
||||||
|
set(CMAKE_COMPILE_WARNING_AS_ERROR ON)
|
||||||
|
|
||||||
# warnings
|
# warnings
|
||||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wextra") # enable stuff
|
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 :)
|
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-pointer-arith") # but we love pointer arithmetic :)
|
||||||
@@ -398,6 +401,10 @@ set(SOURCE_FILES ${SOURCE_FILES}
|
|||||||
games/nost/poke.cpp
|
games/nost/poke.cpp
|
||||||
games/gitadora/gitadora.cpp
|
games/gitadora/gitadora.cpp
|
||||||
games/gitadora/io.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/mga.cpp
|
||||||
games/mga/io.cpp
|
games/mga/io.cpp
|
||||||
games/mga/gunio.cpp
|
games/mga/gunio.cpp
|
||||||
@@ -470,10 +477,12 @@ set(SOURCE_FILES ${SOURCE_FILES}
|
|||||||
hooks/audio/acm.cpp
|
hooks/audio/acm.cpp
|
||||||
hooks/audio/audio.cpp
|
hooks/audio/audio.cpp
|
||||||
hooks/audio/buffer.cpp
|
hooks/audio/buffer.cpp
|
||||||
|
hooks/audio/mme.cpp
|
||||||
hooks/audio/util.cpp
|
hooks/audio/util.cpp
|
||||||
hooks/audio/backends/dsound/dsound_backend.cpp
|
hooks/audio/backends/dsound/dsound_backend.cpp
|
||||||
hooks/audio/backends/mmdevice/audio_endpoint_volume.cpp
|
hooks/audio/backends/mmdevice/audio_endpoint_volume.cpp
|
||||||
hooks/audio/backends/mmdevice/device.cpp
|
hooks/audio/backends/mmdevice/device.cpp
|
||||||
|
hooks/audio/backends/mmdevice/device_collection.cpp
|
||||||
hooks/audio/backends/mmdevice/device_enumerator.cpp
|
hooks/audio/backends/mmdevice/device_enumerator.cpp
|
||||||
hooks/audio/backends/wasapi/audio_client.cpp
|
hooks/audio/backends/wasapi/audio_client.cpp
|
||||||
hooks/audio/backends/wasapi/audio_render_client.cpp
|
hooks/audio/backends/wasapi/audio_render_client.cpp
|
||||||
@@ -526,9 +535,11 @@ set(SOURCE_FILES ${SOURCE_FILES}
|
|||||||
misc/device.cpp
|
misc/device.cpp
|
||||||
misc/eamuse.cpp
|
misc/eamuse.cpp
|
||||||
misc/extdev.cpp
|
misc/extdev.cpp
|
||||||
|
misc/nativetouchhook.cpp
|
||||||
misc/sciunit.cpp
|
misc/sciunit.cpp
|
||||||
misc/sde.cpp
|
misc/sde.cpp
|
||||||
misc/wintouchemu.cpp
|
misc/wintouchemu.cpp
|
||||||
|
misc/ami2000.cpp
|
||||||
|
|
||||||
# nvapi
|
# nvapi
|
||||||
nvapi/nvapi.cpp
|
nvapi/nvapi.cpp
|
||||||
@@ -613,6 +624,7 @@ set(SOURCE_FILES ${SOURCE_FILES}
|
|||||||
util/execexe.cpp
|
util/execexe.cpp
|
||||||
util/dependencies.cpp
|
util/dependencies.cpp
|
||||||
util/deferlog.cpp
|
util/deferlog.cpp
|
||||||
|
util/socd_cleaner.cpp
|
||||||
)
|
)
|
||||||
|
|
||||||
source_group(TREE ${CMAKE_CURRENT_SOURCE_DIR} PREFIX "Source Files" FILES ${SOURCE_FILES})
|
source_group(TREE ${CMAKE_CURRENT_SOURCE_DIR} PREFIX "Source Files" FILES ${SOURCE_FILES})
|
||||||
|
|||||||
@@ -9,11 +9,22 @@
|
|||||||
#include "games/drs/drs.h"
|
#include "games/drs/drs.h"
|
||||||
#include "misc/eamuse.h"
|
#include "misc/eamuse.h"
|
||||||
#include "util/logging.h"
|
#include "util/logging.h"
|
||||||
|
#include "util/socd_cleaner.h"
|
||||||
|
#include "util/time.h"
|
||||||
#include "util/utils.h"
|
#include "util/utils.h"
|
||||||
#include "util/tapeled.h"
|
#include "util/tapeled.h"
|
||||||
|
|
||||||
using namespace GameAPI;
|
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
|
// state
|
||||||
static uint8_t STATUS_BUFFER[272] {};
|
static uint8_t STATUS_BUFFER[272] {};
|
||||||
static bool STATUS_BUFFER_FREEZE = false;
|
static bool STATUS_BUFFER_FREEZE = false;
|
||||||
@@ -94,18 +105,25 @@ static bool __cdecl ac_io_bi2a_update_control_status_buffer() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// volume left
|
// 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;
|
BI2A_VOLL = (BI2A_VOLL - games::sdvx::DIGITAL_KNOB_SENS) & 1023;
|
||||||
}
|
} else if (vol_l_state == socd::SocdCW) {
|
||||||
if (Buttons::getState(RI_MGR, buttons.at(games::sdvx::Buttons::VOL_L_Right))) {
|
|
||||||
BI2A_VOLL = (BI2A_VOLL + games::sdvx::DIGITAL_KNOB_SENS) & 1023;
|
BI2A_VOLL = (BI2A_VOLL + games::sdvx::DIGITAL_KNOB_SENS) & 1023;
|
||||||
}
|
}
|
||||||
|
|
||||||
// volume right
|
// 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;
|
BI2A_VOLR = (BI2A_VOLR - games::sdvx::DIGITAL_KNOB_SENS) & 1023;
|
||||||
}
|
} else if (vol_r_state == socd::SocdCW) {
|
||||||
if (Buttons::getState(RI_MGR, buttons.at(games::sdvx::Buttons::VOL_R_Right))) {
|
|
||||||
BI2A_VOLR = (BI2A_VOLR + games::sdvx::DIGITAL_KNOB_SENS) & 1023;
|
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
|
// save volumes in buffer
|
||||||
*((uint16_t*) &STATUS_BUFFER[17]) = (uint16_t) ((vol_left) << 2);
|
*((uint16_t*) &STATUS_BUFFER[17]) = (uint16_t) ((vol_left) << 2);
|
||||||
*((uint16_t*) &STATUS_BUFFER[19]) = (uint16_t) ((vol_right) << 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
|
// DanceDanceRevolution
|
||||||
|
|||||||
@@ -8,10 +8,21 @@
|
|||||||
#include "games/sdvx/io.h"
|
#include "games/sdvx/io.h"
|
||||||
#include "misc/eamuse.h"
|
#include "misc/eamuse.h"
|
||||||
#include "rawinput/rawinput.h"
|
#include "rawinput/rawinput.h"
|
||||||
|
#include "util/socd_cleaner.h"
|
||||||
|
#include "util/time.h"
|
||||||
#include "util/utils.h"
|
#include "util/utils.h"
|
||||||
|
|
||||||
using namespace GameAPI;
|
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
|
// globals
|
||||||
uint8_t KFCA_VOL_SOUND = 96;
|
uint8_t KFCA_VOL_SOUND = 96;
|
||||||
uint8_t KFCA_VOL_HEADPHONE = 96;
|
uint8_t KFCA_VOL_HEADPHONE = 96;
|
||||||
@@ -342,18 +353,25 @@ static char __cdecl ac_io_kfca_update_control_status_buffer() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// volume left
|
// 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;
|
KFCA_VOLL = (KFCA_VOLL - games::sdvx::DIGITAL_KNOB_SENS) & 1023;
|
||||||
}
|
} else if (vol_l_state == socd::SocdCW) {
|
||||||
if (Buttons::getState(RI_MGR, buttons.at(games::sdvx::Buttons::VOL_L_Right))) {
|
|
||||||
KFCA_VOLL = (KFCA_VOLL + games::sdvx::DIGITAL_KNOB_SENS) & 1023;
|
KFCA_VOLL = (KFCA_VOLL + games::sdvx::DIGITAL_KNOB_SENS) & 1023;
|
||||||
}
|
}
|
||||||
|
|
||||||
// volume right
|
// 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;
|
KFCA_VOLR = (KFCA_VOLR - games::sdvx::DIGITAL_KNOB_SENS) & 1023;
|
||||||
}
|
} else if (vol_r_state == socd::SocdCW) {
|
||||||
if (Buttons::getState(RI_MGR, buttons.at(games::sdvx::Buttons::VOL_R_Right))) {
|
|
||||||
KFCA_VOLR = (KFCA_VOLR + games::sdvx::DIGITAL_KNOB_SENS) & 1023;
|
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
|
// proper loops
|
||||||
vol_left %= 1024;
|
vol_left %= 1024;
|
||||||
vol_right %= 1024;
|
vol_right %= 1024;
|
||||||
|
|
||||||
|
log_debug("kfca", "knobs = {} {}", vol_left, vol_right);
|
||||||
|
|
||||||
// save volumes in buffer
|
// save volumes in buffer
|
||||||
STATUS_BUFFER[input_offset + 16 + 0] |= (unsigned char) ((vol_left << 6) & 0xFF);
|
STATUS_BUFFER[input_offset + 16 + 0] |= (unsigned char) ((vol_left << 6) & 0xFF);
|
||||||
|
|||||||
+168
-16
@@ -9,6 +9,15 @@
|
|||||||
#include "util/utils.h"
|
#include "util/utils.h"
|
||||||
#include <mutex>
|
#include <mutex>
|
||||||
|
|
||||||
|
#define MDFX_DEBUG_VERBOSE 0
|
||||||
|
|
||||||
|
#if MDFX_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
|
||||||
|
|
||||||
// constants
|
// constants
|
||||||
const size_t STATUS_BUFFER_SIZE = 32;
|
const size_t STATUS_BUFFER_SIZE = 32;
|
||||||
const size_t STATUS_BUFFER_NUM_ENTRIES = 16;
|
const size_t STATUS_BUFFER_NUM_ENTRIES = 16;
|
||||||
@@ -30,6 +39,21 @@ static bool IS_MDXF_ACTIVE = false;
|
|||||||
static const uint8_t BACKFILL_INTERVAL_MS = 4;
|
static const uint8_t BACKFILL_INTERVAL_MS = 4;
|
||||||
static const uint8_t BACKFILL_PADDING_MS = 2;
|
static const uint8_t BACKFILL_PADDING_MS = 2;
|
||||||
|
|
||||||
|
// These are used to determine if a thread needs to be spun up to keep pad state ring buffers populated with enough recent polls
|
||||||
|
static uint64_t START_TIME = 0;
|
||||||
|
static int CALL_COUNT = 0;
|
||||||
|
static const int THRESHOLD_REFRESH_RATE = 120;
|
||||||
|
|
||||||
|
static std::atomic<bool> IS_REFRESH_RATE_MEASUREMENT_STARTED{false};
|
||||||
|
static std::atomic<bool> IS_REFRESH_RATE_DETERMINED{false};
|
||||||
|
static std::atomic<bool> IS_THREAD_NEEDED{false};
|
||||||
|
static std::atomic<bool> MDXF_THREAD_RUNNING{false};
|
||||||
|
|
||||||
|
static std::thread MDXF_THREAD;
|
||||||
|
|
||||||
|
static constexpr int THREAD_REFRESH_RATE_HZ = 125;
|
||||||
|
static constexpr auto THREAD_PERIOD = std::chrono::milliseconds(1000 / THREAD_REFRESH_RATE_HZ);
|
||||||
|
|
||||||
// buffers
|
// buffers
|
||||||
#pragma pack(push, 1)
|
#pragma pack(push, 1)
|
||||||
static struct {
|
static struct {
|
||||||
@@ -40,9 +64,20 @@ static struct {
|
|||||||
|
|
||||||
static bool STATUS_BUFFER_FREEZE = false;
|
static bool STATUS_BUFFER_FREEZE = false;
|
||||||
|
|
||||||
|
// Decides which method to use for populating ring buffer entries for "padding".
|
||||||
|
// Overwritten in spicecfg using P4IO Buffer Algorithm option.
|
||||||
|
// THREAD_MODE: Spins a thread running at THREAD_REFRESH_RATE_HZ which periodically fills the ring
|
||||||
|
// buffer with auxiliary entries. Falls back on BACKFILL_MODE
|
||||||
|
// BACKFILL_MODE: On every update cycle, fill the ring buffer with entries for the last known state
|
||||||
|
// BACKFILL_INTERVAL_MS apart from each other from the time of the last entry to the
|
||||||
|
// current time before adding the entry for the current state.
|
||||||
|
// AUTO_MODE: thread mode if <120Hz, backfill if >=120Hz
|
||||||
|
acio::MDXFBufferFillMode acio::MDXF_BUFFER_FILL_MODE = acio::MDXFBufferFillMode::AUTO_MODE;
|
||||||
|
|
||||||
typedef enum {
|
typedef enum {
|
||||||
ARKMDXP4_POLL = 0,
|
ARKMDXP4_POLL = 0,
|
||||||
EXTERNAL_POLL = 1
|
INTERNAL_POLL = 1,
|
||||||
|
EXTERNAL_POLL = 2
|
||||||
} MDXFPollSource;
|
} MDXFPollSource;
|
||||||
|
|
||||||
typedef uint64_t (__cdecl *ARK_GET_TICK_TIME64_T)();
|
typedef uint64_t (__cdecl *ARK_GET_TICK_TIME64_T)();
|
||||||
@@ -61,6 +96,106 @@ static uint64_t arkGetTickTime64() {
|
|||||||
return getTickTime64 ? getTickTime64() : timeGetTime();
|
return getTickTime64 ? getTickTime64() : timeGetTime();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Used to keep the ring buffer populated with steady updates. 60Hz interval is too slow
|
||||||
|
static void mdxf_thread_start() {
|
||||||
|
bool expected = false;
|
||||||
|
if (!MDXF_THREAD_RUNNING.compare_exchange_strong(expected, true)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
log_info("mdxf", "starting poll thread");
|
||||||
|
|
||||||
|
MDXF_THREAD = std::thread([] {
|
||||||
|
SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_BELOW_NORMAL);
|
||||||
|
|
||||||
|
while (MDXF_THREAD_RUNNING.load(std::memory_order_acquire)) {
|
||||||
|
mdxf_poll(false);
|
||||||
|
std::this_thread::sleep_for(THREAD_PERIOD);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
static void mdxf_thread_stop() {
|
||||||
|
if (!MDXF_THREAD_RUNNING.exchange(false)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (MDXF_THREAD.joinable()) {
|
||||||
|
MDXF_THREAD.join();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// Snaps measured refresh rate to best fit
|
||||||
|
static int snap_refresh_rate(int measured_hz) {
|
||||||
|
static constexpr std::array<int, 6> rates = {
|
||||||
|
60, 120, 144, 165, 180, 240
|
||||||
|
};
|
||||||
|
|
||||||
|
int best = rates[0];
|
||||||
|
int best_err = std::fabs(measured_hz - best);
|
||||||
|
|
||||||
|
for (int r : rates) {
|
||||||
|
int err = std::fabs(measured_hz - r);
|
||||||
|
if (err < best_err) {
|
||||||
|
best = r;
|
||||||
|
best_err = err;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return best;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Increments the number of times the update function was called,
|
||||||
|
// then calculates the current refresh rate of the game
|
||||||
|
// (20 seconds after the game starts, until 25 seconds)
|
||||||
|
static void count_calls_from_game() {
|
||||||
|
if (IS_REFRESH_RATE_DETERMINED) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const uint64_t current_time = arkGetTickTime64();
|
||||||
|
|
||||||
|
if (!IS_REFRESH_RATE_MEASUREMENT_STARTED) {
|
||||||
|
if (START_TIME == 0) {
|
||||||
|
START_TIME = current_time;
|
||||||
|
}
|
||||||
|
|
||||||
|
// boot screen takes about 10 seconds, so let's wait for double that
|
||||||
|
if ((current_time - START_TIME) < 20000) {
|
||||||
|
// too early, do nothing
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
// 20s has passed for the first time, start measuring on next call
|
||||||
|
IS_REFRESH_RATE_MEASUREMENT_STARTED = true;
|
||||||
|
START_TIME = current_time;
|
||||||
|
log_debug("mdxf", "measurement begin");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const uint64_t elapsed_time = current_time - START_TIME;
|
||||||
|
CALL_COUNT++;
|
||||||
|
if (elapsed_time >= 5000) {
|
||||||
|
double measured_hz = static_cast<double>(CALL_COUNT) * 1000.0 / static_cast<double>(elapsed_time);
|
||||||
|
|
||||||
|
// Account for the main loop calling this twice per iteration
|
||||||
|
measured_hz *= 0.5;
|
||||||
|
const int snapped_hz = snap_refresh_rate(static_cast<int>(measured_hz));
|
||||||
|
|
||||||
|
IS_REFRESH_RATE_DETERMINED = true;
|
||||||
|
IS_THREAD_NEEDED = (snapped_hz < THRESHOLD_REFRESH_RATE);
|
||||||
|
log_info(
|
||||||
|
"mdxf",
|
||||||
|
"detected: {} Hz, best fit: {} Hz",
|
||||||
|
static_cast<int>(measured_hz),
|
||||||
|
snapped_hz);
|
||||||
|
|
||||||
|
if (IS_THREAD_NEEDED) {
|
||||||
|
mdxf_thread_start();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Implementations
|
* Implementations
|
||||||
*/
|
*/
|
||||||
@@ -92,22 +227,20 @@ static uint64_t __cdecl ac_io_mdxf_get_control_status_buffer(int node, void *out
|
|||||||
}
|
}
|
||||||
|
|
||||||
std::lock_guard<std::mutex> lock(*mutex);
|
std::lock_guard<std::mutex> lock(*mutex);
|
||||||
|
|
||||||
|
const uint8_t start_index = (head_in == 0xFF) ? *head : head_in;
|
||||||
|
|
||||||
if (head_in != 0xFF) {
|
// Compute ring index: walk backwards from start_index as index increases
|
||||||
*head = head_in;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Compute ring index: walk backwards from head as index increases
|
|
||||||
// Assumes ring buffer size is a power of two
|
// Assumes ring buffer size is a power of two
|
||||||
const size_t mask = size - 1;
|
const size_t mask = size - 1;
|
||||||
const size_t offset = static_cast<size_t>(index) & mask;
|
const size_t offset = static_cast<size_t>(index) & mask;
|
||||||
const size_t i = (static_cast<size_t>(*head) - offset + size) & mask;
|
const size_t i = (static_cast<size_t>(start_index) - offset + size) & mask;
|
||||||
|
|
||||||
// Copy the chosen entry
|
// Copy the chosen entry
|
||||||
memcpy(out, buffer[i], STATUS_BUFFER_SIZE);
|
memcpy(out, buffer[i], STATUS_BUFFER_SIZE);
|
||||||
|
|
||||||
// Return the head value actually used
|
// Return the start value actually used
|
||||||
return static_cast<uint64_t>(*head);
|
return static_cast<uint64_t>(start_index);
|
||||||
}
|
}
|
||||||
|
|
||||||
return error_ret;
|
return error_ret;
|
||||||
@@ -162,8 +295,18 @@ static bool __cdecl ac_io_mdxf_update_control_status_buffer_impl(int node, MDXFP
|
|||||||
// Dance Dance Revolution
|
// Dance Dance Revolution
|
||||||
if (avs::game::is_model("MDX")) {
|
if (avs::game::is_model("MDX")) {
|
||||||
// Marks this module as actively being used, allowing this function to be called from other sources
|
// Marks this module as actively being used, allowing this function to be called from other sources
|
||||||
if (!IS_MDXF_ACTIVE && source == ARKMDXP4_POLL) {
|
if (source == ARKMDXP4_POLL) {
|
||||||
IS_MDXF_ACTIVE = true;
|
if (!IS_MDXF_ACTIVE) {
|
||||||
|
log_debug("mdxf", "initializing mdxf I/O support");
|
||||||
|
IS_MDXF_ACTIVE = true;
|
||||||
|
if (acio::MDXF_BUFFER_FILL_MODE == acio::MDXFBufferFillMode::THREAD_MODE) {
|
||||||
|
IS_THREAD_NEEDED = true;
|
||||||
|
mdxf_thread_start();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (acio::MDXF_BUFFER_FILL_MODE == acio::MDXFBufferFillMode::AUTO_MODE) {
|
||||||
|
count_calls_from_game();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
uint8_t (*buffer)[STATUS_BUFFER_SIZE];
|
uint8_t (*buffer)[STATUS_BUFFER_SIZE];
|
||||||
@@ -226,6 +369,8 @@ static bool __cdecl ac_io_mdxf_update_control_status_buffer_impl(int node, MDXFP
|
|||||||
}
|
}
|
||||||
|
|
||||||
uint16_t current_state;
|
uint16_t current_state;
|
||||||
|
|
||||||
|
// Only call getState() if called externally when actual input events happen, otherwise use previous known state
|
||||||
if (source == EXTERNAL_POLL) {
|
if (source == EXTERNAL_POLL) {
|
||||||
// get buttons
|
// get buttons
|
||||||
auto &buttons = games::ddr::get_buttons();
|
auto &buttons = games::ddr::get_buttons();
|
||||||
@@ -276,8 +421,8 @@ static bool __cdecl ac_io_mdxf_update_control_status_buffer_impl(int node, MDXFP
|
|||||||
start_time = min_time;
|
start_time = min_time;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Only write one entry if called externally
|
// Don't backfill entries if called externally or if a separate thread is being used to fill auxiliary entries
|
||||||
if (source == EXTERNAL_POLL) {
|
if (source == EXTERNAL_POLL || IS_THREAD_NEEDED) {
|
||||||
start_time = stop_time - 1;
|
start_time = stop_time - 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -323,11 +468,12 @@ static bool __cdecl ac_io_mdxf_update_control_status_buffer(int node) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Used for triggering updates of the controller states from outside arkmdxp4.dll main refresh loop (i.e. within rawinput.cpp on controller events)
|
// Used for triggering updates of the controller states from outside arkmdxp4.dll main refresh loop (i.e. within rawinput.cpp on controller events)
|
||||||
void mdxf_poll() {
|
void mdxf_poll(bool isExternal) {
|
||||||
if (IS_MDXF_ACTIVE) {
|
if (IS_MDXF_ACTIVE) {
|
||||||
|
const MDXFPollSource source = isExternal ? EXTERNAL_POLL : INTERNAL_POLL;
|
||||||
const uint64_t call_time_ms = arkGetTickTime64();
|
const uint64_t call_time_ms = arkGetTickTime64();
|
||||||
ac_io_mdxf_update_control_status_buffer_impl(17, EXTERNAL_POLL, call_time_ms);
|
ac_io_mdxf_update_control_status_buffer_impl(17, source, call_time_ms);
|
||||||
ac_io_mdxf_update_control_status_buffer_impl(18, EXTERNAL_POLL, call_time_ms);
|
ac_io_mdxf_update_control_status_buffer_impl(18, source, call_time_ms);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -348,4 +494,10 @@ void acio::MDXFModule::attach() {
|
|||||||
ACIO_MODULE_HOOK(ac_io_mdxf_get_control_status_buffer);
|
ACIO_MODULE_HOOK(ac_io_mdxf_get_control_status_buffer);
|
||||||
ACIO_MODULE_HOOK(ac_io_mdxf_set_output_level);
|
ACIO_MODULE_HOOK(ac_io_mdxf_set_output_level);
|
||||||
ACIO_MODULE_HOOK(ac_io_mdxf_update_control_status_buffer);
|
ACIO_MODULE_HOOK(ac_io_mdxf_update_control_status_buffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
acio::MDXFModule::~MDXFModule() {
|
||||||
|
if (IS_THREAD_NEEDED) {
|
||||||
|
mdxf_thread_stop();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -4,10 +4,24 @@
|
|||||||
|
|
||||||
namespace acio {
|
namespace acio {
|
||||||
|
|
||||||
|
enum class MDXFBufferFillMode {
|
||||||
|
// backfill mode, but if <120Hz, enable poll thread
|
||||||
|
AUTO_MODE,
|
||||||
|
|
||||||
|
// forces poll thread
|
||||||
|
THREAD_MODE,
|
||||||
|
|
||||||
|
// forces backfill mode (no poll thread)
|
||||||
|
BACKFILL_MODE
|
||||||
|
};
|
||||||
|
|
||||||
|
extern MDXFBufferFillMode MDXF_BUFFER_FILL_MODE;
|
||||||
|
|
||||||
class MDXFModule : public ACIOModule {
|
class MDXFModule : public ACIOModule {
|
||||||
public:
|
public:
|
||||||
MDXFModule(HMODULE module, HookMode hookMode);
|
MDXFModule(HMODULE module, HookMode hookMode);
|
||||||
|
|
||||||
virtual void attach() override;
|
virtual void attach() override;
|
||||||
|
~MDXFModule() override;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,4 +2,4 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
// Called from rawinput thread whenever inputs have just been updated
|
// Called from rawinput thread whenever inputs have just been updated
|
||||||
void mdxf_poll();
|
void mdxf_poll(bool isExternal);
|
||||||
@@ -84,7 +84,7 @@ static bool __cdecl ac_io_panb_start_auto_input() {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
static uint8_t panb_get_button_velocity(Button button, Button button_soft, Button button_medium, Button button_hard) {
|
static uint8_t panb_get_button_velocity(Button& button, Button& button_soft, Button& button_medium, Button& button_hard) {
|
||||||
const auto velocity = Buttons::getVelocity(RI_MGR, button);
|
const auto velocity = Buttons::getVelocity(RI_MGR, button);
|
||||||
const auto velocity_soft = Buttons::getVelocity(RI_MGR, button_soft);
|
const auto velocity_soft = Buttons::getVelocity(RI_MGR, button_soft);
|
||||||
const auto velocity_medium = Buttons::getVelocity(RI_MGR, button_medium);
|
const auto velocity_medium = Buttons::getVelocity(RI_MGR, button_medium);
|
||||||
|
|||||||
@@ -95,12 +95,19 @@ namespace acio2emu {
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
log_fatal("acio2emu", "cannot set step: unknown value: {}", s);
|
log_fatal(
|
||||||
|
"acio2emu",
|
||||||
|
"cannot set step: unknown value: {}",
|
||||||
|
static_cast<uint32_t>(s));
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!valid) {
|
if (!valid) {
|
||||||
log_fatal("acio2emu", "illegal transition detected: {} -> {}", step_, s);
|
log_fatal(
|
||||||
|
"acio2emu",
|
||||||
|
"illegal transition detected: {} -> {}",
|
||||||
|
static_cast<uint32_t>(step_),
|
||||||
|
static_cast<uint32_t>(s));
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
step_ = s;
|
step_ = s;
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
#include "acio/icca/icca.h"
|
#include "acio/icca/icca.h"
|
||||||
#include "avs/game.h"
|
#include "avs/game.h"
|
||||||
|
#include "games/sdvx/sdvx.h"
|
||||||
#include "misc/eamuse.h"
|
#include "misc/eamuse.h"
|
||||||
#include "util/logging.h"
|
#include "util/logging.h"
|
||||||
#include "util/utils.h"
|
#include "util/utils.h"
|
||||||
@@ -101,13 +102,10 @@ bool ICCADevice::parse_msg(MessageData *msg_in,
|
|||||||
|
|
||||||
// send version data
|
// send version data
|
||||||
auto msg = this->create_msg(msg_in, MSG_VERSION_SIZE);
|
auto msg = this->create_msg(msg_in, MSG_VERSION_SIZE);
|
||||||
if (
|
if (avs::game::is_model({"LDJ", "TBS", "UJK", "XIF"}) ||
|
||||||
avs::game::is_model({"LDJ", "TBS", "UJK", "XIF"}) ||
|
games::sdvx::is_valkyrie_model()) {
|
||||||
// SDVX Valkyrie cabinet mode
|
|
||||||
(avs::game::is_model("KFC") && (avs::game::SPEC[0] == 'G' || avs::game::SPEC[0] == 'H'))
|
|
||||||
) {
|
|
||||||
this->set_version(msg, 0x3, 0, 1, 7, 0, "ICCA");
|
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");
|
this->set_version(msg, 0x3, 0, 1, 7, 0, "ICCB");
|
||||||
} else {
|
} else {
|
||||||
this->set_version(msg, 0x3, 0, 1, 6, 0, "ICCA");
|
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[1] = felica ? 0x01 : 0x00;
|
||||||
buffer[10] = felica ? 0x01 : 0x00;
|
buffer[10] = felica ? 0x01 : 0x00;
|
||||||
|
|
||||||
} else if (
|
} else if (avs::game::is_model({"LDJ", "TBS", "XIF"}) || games::sdvx::is_valkyrie_model()) {
|
||||||
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'))) {
|
|
||||||
|
|
||||||
// set status to 0 otherwise reader power on fails
|
// set status to 0 otherwise reader power on fails
|
||||||
buffer[0] = 0x00;
|
buffer[0] = 0x00;
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -1108,6 +1108,8 @@ namespace avs {
|
|||||||
|
|
||||||
// SOUND VOLTEX
|
// SOUND VOLTEX
|
||||||
{"soundvoltex.dll", 0x10000000},
|
{"soundvoltex.dll", 0x10000000},
|
||||||
|
// GITADORA
|
||||||
|
{"gdxg.dll", 0x2000000},
|
||||||
#endif
|
#endif
|
||||||
// jubeat
|
// jubeat
|
||||||
{"jubeat.dll", 0x2000000},
|
{"jubeat.dll", 0x2000000},
|
||||||
@@ -1232,6 +1234,7 @@ namespace avs {
|
|||||||
break;
|
break;
|
||||||
case 'F':
|
case 'F':
|
||||||
new_style = logger::Style::RED;
|
new_style = logger::Style::RED;
|
||||||
|
deferredlogs::report_fatal_message();
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
position = 0;
|
position = 0;
|
||||||
@@ -1300,6 +1303,7 @@ namespace avs {
|
|||||||
break;
|
break;
|
||||||
case 'F':
|
case 'F':
|
||||||
style = logger::Style::RED;
|
style = logger::Style::RED;
|
||||||
|
deferredlogs::report_fatal_message();
|
||||||
break;
|
break;
|
||||||
case 'W':
|
case 'W':
|
||||||
style = logger::Style::YELLOW;
|
style = logger::Style::YELLOW;
|
||||||
|
|||||||
@@ -494,7 +494,9 @@ namespace avs {
|
|||||||
std::ostringstream init_code;
|
std::ostringstream init_code;
|
||||||
init_code << EA3_MODEL;
|
init_code << EA3_MODEL;
|
||||||
init_code << EA3_DEST;
|
init_code << EA3_DEST;
|
||||||
if (strcmp(EA3_MODEL, "MDX") == 0 && strcmp(EA3_SPEC, "I") == 0) {
|
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 << "G";
|
||||||
init_code << "B";
|
init_code << "B";
|
||||||
} else {
|
} else {
|
||||||
@@ -529,7 +531,7 @@ namespace avs {
|
|||||||
auto app_param = avs::core::property_search_safe(app_config, nullptr, "/param");
|
auto app_param = avs::core::property_search_safe(app_config, nullptr, "/param");
|
||||||
|
|
||||||
// call the game init
|
// 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)) {
|
if (!avs::game::entry_init(init_code_str.data(), app_param)) {
|
||||||
log_fatal("avs-ea3", "entry init failed :(");
|
log_fatal("avs-ea3", "entry init failed :(");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -69,6 +69,7 @@ namespace cfg {
|
|||||||
uint32_t init_client_height = 0;
|
uint32_t init_client_height = 0;
|
||||||
float init_client_aspect_ratio = 1.f;
|
float init_client_aspect_ratio = 1.f;
|
||||||
uint32_t init_window_style = 0;
|
uint32_t init_window_style = 0;
|
||||||
|
uint32_t init_window_style_ex = 0;
|
||||||
uint32_t window_deco_width = 0;
|
uint32_t window_deco_width = 0;
|
||||||
uint32_t window_deco_height = 0;
|
uint32_t window_deco_height = 0;
|
||||||
|
|
||||||
|
|||||||
@@ -253,28 +253,25 @@ namespace games::ddr {
|
|||||||
if(((dwIoControlCode>>16)&0xFFFF) == FILE_DEVICE_UNKNOWN) {
|
if(((dwIoControlCode>>16)&0xFFFF) == FILE_DEVICE_UNKNOWN) {
|
||||||
switch((dwIoControlCode>>2)&0x3FFF) {
|
switch((dwIoControlCode>>2)&0x3FFF) {
|
||||||
case P4IO_IOCTL_GET_INPUTS: {
|
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);
|
memset(lpOutBuffer, 0, 16);
|
||||||
auto controls = (uint32_t*) lpOutBuffer;
|
auto controls = (uint32_t*) lpOutBuffer;
|
||||||
|
|
||||||
const std::pair<size_t, uint8_t> buttonMapping[] =
|
const std::pair<size_t, uint8_t> buttonMapping[] =
|
||||||
{
|
{
|
||||||
|
// Panel input comes from MDXF Control Status Buffer functions
|
||||||
{Buttons::SERVICE, 25}, //Good
|
{Buttons::SERVICE, 25}, //Good
|
||||||
{Buttons::TEST, 28}, //Good
|
{Buttons::TEST, 28}, //Good
|
||||||
{Buttons::COIN_MECH, 24}, //Good
|
{Buttons::COIN_MECH, 24}, //Good
|
||||||
{Buttons::P1_START, 0}, //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_UP, 1}, //Good
|
||||||
{Buttons::P1_MENU_DOWN, 2}, //Good
|
{Buttons::P1_MENU_DOWN, 2}, //Good
|
||||||
{Buttons::P1_MENU_LEFT, 3}, //Good
|
{Buttons::P1_MENU_LEFT, 3}, //Good
|
||||||
{Buttons::P1_MENU_RIGHT, 4}, //Good
|
{Buttons::P1_MENU_RIGHT, 4}, //Good
|
||||||
{Buttons::P2_START, 8}, //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_UP, 9}, //Good
|
||||||
{Buttons::P2_MENU_DOWN, 10}, //Good
|
{Buttons::P2_MENU_DOWN, 10}, //Good
|
||||||
{Buttons::P2_MENU_LEFT, 11}, //Good
|
{Buttons::P2_MENU_LEFT, 11}, //Good
|
||||||
|
|||||||
@@ -0,0 +1,704 @@
|
|||||||
|
#include "bi2x_hook.h"
|
||||||
|
|
||||||
|
#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 {
|
||||||
|
};
|
||||||
|
|
||||||
|
struct AIO_IOB2_BI2X_AC1__SETTING {
|
||||||
|
};
|
||||||
|
|
||||||
|
struct AIO_NMGR_IOB5 {
|
||||||
|
};
|
||||||
|
|
||||||
|
struct AIO_SCI_COMM {
|
||||||
|
};
|
||||||
|
|
||||||
|
struct AIO_IOB5_Y32D {
|
||||||
|
};
|
||||||
|
|
||||||
|
// struct AIO_IOB5_Y33I {
|
||||||
|
// };
|
||||||
|
|
||||||
|
struct AIO_IOB2_BI2X_AC1 {
|
||||||
|
};
|
||||||
|
|
||||||
|
struct AIO_NMGR_IOB__NODEINFO {
|
||||||
|
uint8_t data[0xA8];
|
||||||
|
};
|
||||||
|
|
||||||
|
struct AIO_IOB2_BI2X_WRFIRM {
|
||||||
|
};
|
||||||
|
|
||||||
|
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];
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
* 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
namespace games::gitadora {
|
||||||
|
void bi2x_hook_init();
|
||||||
|
}
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "acioemu/acioemu.h"
|
||||||
|
|
||||||
|
namespace games::gitadora {
|
||||||
|
class GitadoraDevice : public acioemu::ACIODeviceEmu {
|
||||||
|
public:
|
||||||
|
bool is_ready = false;
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -1,14 +1,17 @@
|
|||||||
#include "gitadora.h"
|
#include "gitadora.h"
|
||||||
|
#include "handle.h"
|
||||||
|
#include "bi2x_hook.h"
|
||||||
#include <unordered_map>
|
#include <unordered_map>
|
||||||
|
|
||||||
#include "cfg/configurator.h"
|
#include "cfg/configurator.h"
|
||||||
|
#include "hooks/audio/mme.h"
|
||||||
#include "hooks/graphics/graphics.h"
|
#include "hooks/graphics/graphics.h"
|
||||||
|
#include "overlay/overlay.h"
|
||||||
#include "util/cpuutils.h"
|
#include "util/cpuutils.h"
|
||||||
#include "util/detour.h"
|
#include "util/detour.h"
|
||||||
#include "util/libutils.h"
|
#include "util/libutils.h"
|
||||||
#include "util/logging.h"
|
#include "util/logging.h"
|
||||||
#include "util/sigscan.h"
|
#include "util/sigscan.h"
|
||||||
|
#include "hooks/setupapihook.h"
|
||||||
|
|
||||||
namespace games::gitadora {
|
namespace games::gitadora {
|
||||||
|
|
||||||
@@ -16,6 +19,32 @@ namespace games::gitadora {
|
|||||||
bool TWOCHANNEL = false;
|
bool TWOCHANNEL = false;
|
||||||
std::optional<unsigned int> CAB_TYPE = std::nullopt;
|
std::optional<unsigned int> CAB_TYPE = std::nullopt;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* 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.
|
* GitaDora checks if the IP address has changed, and if it has it throws 5-1506-0000 like jubeat.
|
||||||
* We don't want this so we patch it out.
|
* We don't want this so we patch it out.
|
||||||
@@ -184,6 +213,13 @@ namespace games::gitadora {
|
|||||||
log_fatal("gitadora", "Cabinet type 3 (SD2) not supported on XG series");
|
log_fatal("gitadora", "Cabinet type 3 (SD2) not supported on XG series");
|
||||||
}
|
}
|
||||||
#endif
|
#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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -214,6 +250,37 @@ namespace games::gitadora {
|
|||||||
system_module, "sys_debug_dip_set_param"));
|
system_module, "sys_debug_dip_set_param"));
|
||||||
|
|
||||||
#ifdef SPICE64
|
#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);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
HMODULE gdme_module = libutils::try_module("libgdme.dll");
|
HMODULE gdme_module = libutils::try_module("libgdme.dll");
|
||||||
|
|
||||||
|
|||||||
@@ -21,13 +21,20 @@ namespace games::gitadora {
|
|||||||
static inline bool is_drum() {
|
static inline bool is_drum() {
|
||||||
return (
|
return (
|
||||||
avs::game::is_model({ "J32", "K32", "L32" }) ||
|
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() {
|
static inline bool is_guitar() {
|
||||||
return (
|
return (
|
||||||
avs::game::is_model({ "J33", "K33", "L33" }) ||
|
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')
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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",
|
"Service",
|
||||||
"Test",
|
"Test",
|
||||||
"Coin",
|
"Coin",
|
||||||
|
"Headphone",
|
||||||
"Guitar P1 Start",
|
"Guitar P1 Start",
|
||||||
"Guitar P1 Up",
|
"Guitar P1 Up",
|
||||||
"Guitar P1 Down",
|
"Guitar P1 Down",
|
||||||
@@ -210,7 +211,19 @@ std::vector<Light> &games::gitadora::get_lights() {
|
|||||||
"Guitar Right Speaker Mid Low Right B (DX)",
|
"Guitar Right Speaker Mid Low Right B (DX)",
|
||||||
"Guitar Right Speaker Lower R (DX)",
|
"Guitar Right Speaker Lower R (DX)",
|
||||||
"Guitar Right Speaker Lower G (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,
|
Service,
|
||||||
Test,
|
Test,
|
||||||
Coin,
|
Coin,
|
||||||
|
Headphone,
|
||||||
GuitarP1Start,
|
GuitarP1Start,
|
||||||
GuitarP1Up,
|
GuitarP1Up,
|
||||||
GuitarP1Down,
|
GuitarP1Down,
|
||||||
@@ -190,7 +191,18 @@ namespace games::gitadora {
|
|||||||
GuitarRightSpeakerMidLowRightB,
|
GuitarRightSpeakerMidLowRightB,
|
||||||
GuitarRightSpeakerLowerR,
|
GuitarRightSpeakerLowerR,
|
||||||
GuitarRightSpeakerLowerG,
|
GuitarRightSpeakerLowerG,
|
||||||
GuitarRightSpeakerLowerB
|
GuitarRightSpeakerLowerB,
|
||||||
|
|
||||||
|
// arena model (common for both guitar and drum)
|
||||||
|
ArenaTitleAvgR,
|
||||||
|
ArenaTitleAvgG,
|
||||||
|
ArenaTitleAvgB,
|
||||||
|
ArenaWooferR,
|
||||||
|
ArenaWooferG,
|
||||||
|
ArenaWooferB,
|
||||||
|
ArenaCardR,
|
||||||
|
ArenaCardG,
|
||||||
|
ArenaCardB,
|
||||||
} gitadora_lights_t;
|
} gitadora_lights_t;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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,160 @@
|
|||||||
|
|
||||||
|
#include "misc/eamuse.h"
|
||||||
|
#include "util/utils.h"
|
||||||
|
#include "util/time.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;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::GuitarP1PickUp])) {
|
||||||
|
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 (GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::GuitarP1PickDown])) {
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto &analogs = get_analogs();
|
||||||
|
|
||||||
|
// get x,y,z analog values [-0.5f, 0.5f], centered at 0.f
|
||||||
|
float x = 0.f;
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
if (GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::GuitarP1WailUp])) {
|
||||||
|
y = -0.5f;
|
||||||
|
} else if (GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::GuitarP1WailDown])) {
|
||||||
|
y = 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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;
|
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 device
|
||||||
acio_emu.add_device(new FMSerialDevice());
|
acio_emu.add_device(new FMSerialDevice());
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -240,7 +240,7 @@ int games::iidx::IIDXFMSerialHandle::device_io(DWORD dwIoControlCode, LPVOID lpI
|
|||||||
}
|
}
|
||||||
|
|
||||||
bool games::iidx::IIDXFMSerialHandle::close() {
|
bool games::iidx::IIDXFMSerialHandle::close() {
|
||||||
log_info("iidx", "Closed COM2 (FM DEVICE)");
|
log_info("iidx", "Closed COM2 (FM DEVICE / bi2a)");
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
#include "bi2x_hook.h"
|
#include "bi2x_hook.h"
|
||||||
|
|
||||||
|
#if SPICE64
|
||||||
|
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
#include "util/detour.h"
|
#include "util/detour.h"
|
||||||
#include "util/logging.h"
|
#include "util/logging.h"
|
||||||
@@ -24,17 +26,28 @@ namespace games::iidx {
|
|||||||
uint8_t data[0x10];
|
uint8_t data[0x10];
|
||||||
};
|
};
|
||||||
|
|
||||||
struct AIO_NMGR_IOB2 {
|
struct AIO_NMGR_IOB2_VTABLE {
|
||||||
uint8_t dummy0[0x50];
|
// other functions here, but they never get called
|
||||||
|
uint8_t dummy[0x50];
|
||||||
void (__fastcall *pAIO_NMGR_IOB_BeginManage)(int64_t a1);
|
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 {
|
struct AIO_IOB2_BI2X_TDJ {
|
||||||
// who knows
|
// who knows
|
||||||
uint8_t data[0x13F8];
|
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 {
|
struct AIO_IOB2_BI2X_TDJ__DEVSTATUS {
|
||||||
// of course you could work with variables here
|
// of course you could work with variables here
|
||||||
uint8_t buffer[0xCA];
|
uint8_t buffer[0xCA];
|
||||||
@@ -78,7 +91,7 @@ namespace games::iidx {
|
|||||||
// libaio-iob.dll
|
// libaio-iob.dll
|
||||||
typedef AC_HNDLIF* (__fastcall *aioIob2Bi2x_OpenSciUsbCdc_t)(uint8_t device_num);
|
typedef AC_HNDLIF* (__fastcall *aioIob2Bi2x_OpenSciUsbCdc_t)(uint8_t device_num);
|
||||||
typedef int64_t (__fastcall *aioIob2Bi2x_WriteFirmGetState_t)(int64_t a1);
|
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
|
* function pointers
|
||||||
@@ -120,6 +133,11 @@ namespace games::iidx {
|
|||||||
|
|
||||||
static AIO_IOB2_BI2X_TDJ* __fastcall aioIob2Bi2xTDJ_Create(AIO_NMGR_IOB2 *nmgr, int a2) {
|
static AIO_IOB2_BI2X_TDJ* __fastcall aioIob2Bi2xTDJ_Create(AIO_NMGR_IOB2 *nmgr, int a2) {
|
||||||
if (!BI2X_PASSTHROUGH) {
|
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;
|
custom_node = new AIO_IOB2_BI2X_TDJ;
|
||||||
memset(&custom_node->data, 0, sizeof(custom_node->data));
|
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) {
|
if (acHndlif == nullptr) {
|
||||||
acHndlif = new AC_HNDLIF;
|
acHndlif = new AC_HNDLIF;
|
||||||
memset(acHndlif->data, 0x0, sizeof(acHndlif->data));
|
memset(acHndlif->data, 0x0, sizeof(acHndlif->data));
|
||||||
@@ -451,14 +469,16 @@ namespace games::iidx {
|
|||||||
log_info("bi2x_hook", "AIO_NMGR_IOB::BeginManage");
|
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) {
|
if (aioNmgrIob2 == nullptr) {
|
||||||
aioNmgrIob2 = new AIO_NMGR_IOB2;
|
aioNmgrIob2 = new AIO_NMGR_IOB2{};
|
||||||
memset(aioNmgrIob2, 0x0, sizeof(AIO_NMGR_IOB2));
|
aioNmgrIob2->vptr = new AIO_NMGR_IOB2_VTABLE{};
|
||||||
aioNmgrIob2->pAIO_NMGR_IOB_BeginManage = AIO_NMGR_IOB_BeginManageStub;
|
aioNmgrIob2->vptr->pAIO_NMGR_IOB_BeginManage = AIO_NMGR_IOB_BeginManageStub;
|
||||||
}
|
}
|
||||||
log_info("bi2x_hook", "aioNMgrIob2_Create");
|
log_info("bi2x_hook", "aioNMgrIob2_Create returned {}, size=0x{:x}, vptr @ {}",
|
||||||
return &aioNmgrIob2;
|
fmt::ptr(aioNmgrIob2), sizeof(*aioNmgrIob2), fmt::ptr(aioNmgrIob2->vptr));
|
||||||
|
|
||||||
|
return aioNmgrIob2;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int64_t __fastcall aioIob2Bi2x_WriteFirmGetState(int64_t a1) {
|
static int64_t __fastcall aioIob2Bi2x_WriteFirmGetState(int64_t a1) {
|
||||||
@@ -515,10 +535,12 @@ namespace games::iidx {
|
|||||||
// hook IOB
|
// hook IOB
|
||||||
const auto libaioIobDll = "libaio-iob.dll";
|
const auto libaioIobDll = "libaio-iob.dll";
|
||||||
detour::trampoline_try(libaioIobDll, "aioIob2Bi2x_OpenSciUsbCdc",
|
detour::trampoline_try(libaioIobDll, "aioIob2Bi2x_OpenSciUsbCdc",
|
||||||
aioIob2Bi2X_OpenSciUsbCdc, &aioIob2Bi2x_OpenSciUsbCdc_orig);
|
aioIob2Bi2x_OpenSciUsbCdc, &aioIob2Bi2x_OpenSciUsbCdc_orig);
|
||||||
detour::trampoline_try(libaioIobDll, "aioIob2Bi2x_WriteFirmGetState",
|
detour::trampoline_try(libaioIobDll, "aioIob2Bi2x_WriteFirmGetState",
|
||||||
aioIob2Bi2x_WriteFirmGetState, &aioIob2Bi2x_WriteFirmGetState_orig);
|
aioIob2Bi2x_WriteFirmGetState, &aioIob2Bi2x_WriteFirmGetState_orig);
|
||||||
detour::trampoline_try(libaioIobDll, "aioNMgrIob2_Create",
|
detour::trampoline_try(libaioIobDll, "aioNMgrIob2_Create",
|
||||||
aioNMgrIob2_Create, &aioNMgrIob2_Create_orig);
|
aioNMgrIob2_Create, &aioNMgrIob2_Create_orig);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|||||||
@@ -1,6 +1,10 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#if SPICE64
|
||||||
|
|
||||||
namespace games::iidx {
|
namespace games::iidx {
|
||||||
|
|
||||||
void bi2x_hook_init();
|
void bi2x_hook_init();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|||||||
@@ -20,6 +20,7 @@
|
|||||||
#include "hooks/sleephook.h"
|
#include "hooks/sleephook.h"
|
||||||
#include "launcher/options.h"
|
#include "launcher/options.h"
|
||||||
#include "touch/touch.h"
|
#include "touch/touch.h"
|
||||||
|
#include "misc/nativetouchhook.h"
|
||||||
#include "misc/wintouchemu.h"
|
#include "misc/wintouchemu.h"
|
||||||
#include "misc/eamuse.h"
|
#include "misc/eamuse.h"
|
||||||
#include "util/detour.h"
|
#include "util/detour.h"
|
||||||
@@ -28,6 +29,8 @@
|
|||||||
#include "util/libutils.h"
|
#include "util/libutils.h"
|
||||||
#include "util/memutils.h"
|
#include "util/memutils.h"
|
||||||
#include "util/sigscan.h"
|
#include "util/sigscan.h"
|
||||||
|
#include "util/socd_cleaner.h"
|
||||||
|
#include "util/time.h"
|
||||||
#include "util/utils.h"
|
#include "util/utils.h"
|
||||||
#include "launcher/signal.h"
|
#include "launcher/signal.h"
|
||||||
|
|
||||||
@@ -78,6 +81,10 @@ namespace games::iidx {
|
|||||||
char IIDXIO_LED_TICKER[10] = {' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', '\x00'};
|
char IIDXIO_LED_TICKER[10] = {' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', '\x00'};
|
||||||
bool IIDXIO_LED_TICKER_READONLY = false;
|
bool IIDXIO_LED_TICKER_READONLY = false;
|
||||||
std::mutex IIDX_LED_TICKER_LOCK;
|
std::mutex IIDX_LED_TICKER_LOCK;
|
||||||
|
bool IIDX_TDJ_MONITOR_WARNING = false;
|
||||||
|
|
||||||
|
// io
|
||||||
|
static bool HAS_LIBAIO; // this is how we detect iidx27+
|
||||||
|
|
||||||
tapeledutils::tape_led TAPELED_MAPPING[IIDX_TAPELED_TOTAL] = {
|
tapeledutils::tape_led TAPELED_MAPPING[IIDX_TAPELED_TOTAL] = {
|
||||||
{ 19, Lights::StageLeftAvgR, Lights::StageLeftAvgG, Lights::StageLeftAvgB, "Stage Left" },
|
{ 19, Lights::StageLeftAvgR, Lights::StageLeftAvgG, Lights::StageLeftAvgB, "Stage Left" },
|
||||||
@@ -246,6 +253,8 @@ namespace games::iidx {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#if SPICE64
|
||||||
|
|
||||||
typedef void* (*aioIob2Bi2x_CreateWriteFirmContext_t)(unsigned int, int);
|
typedef void* (*aioIob2Bi2x_CreateWriteFirmContext_t)(unsigned int, int);
|
||||||
static aioIob2Bi2x_CreateWriteFirmContext_t aioIob2Bi2x_CreateWriteFirmContext_orig = nullptr;
|
static aioIob2Bi2x_CreateWriteFirmContext_t aioIob2Bi2x_CreateWriteFirmContext_orig = nullptr;
|
||||||
|
|
||||||
@@ -264,6 +273,8 @@ namespace games::iidx {
|
|||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
IIDXGame::IIDXGame() : Game("Beatmania IIDX") {
|
IIDXGame::IIDXGame() : Game("Beatmania IIDX") {
|
||||||
logger::hook_add(log_hook, this);
|
logger::hook_add(log_hook, this);
|
||||||
}
|
}
|
||||||
@@ -310,13 +321,16 @@ namespace games::iidx {
|
|||||||
memcpy(settings2.interface_detail, interface_detail2, sizeof(interface_detail2));
|
memcpy(settings2.interface_detail, interface_detail2, sizeof(interface_detail2));
|
||||||
setupapihook_add(settings2);
|
setupapihook_add(settings2);
|
||||||
|
|
||||||
// IIDX 25-27 BIO2 BI2A input device
|
// IIDX 25+ BIO2 BI2A input device
|
||||||
devicehook_add(new IIDXFMSerialHandle());
|
devicehook_add(new IIDXFMSerialHandle());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#if SPICE64
|
||||||
|
|
||||||
// check for new I/O DLL
|
// check for new I/O DLL
|
||||||
auto aio = libutils::try_library("libaio.dll");
|
auto aio = libutils::try_library("libaio.dll");
|
||||||
if (aio != nullptr) {
|
if (aio != nullptr) {
|
||||||
|
HAS_LIBAIO = true;
|
||||||
|
|
||||||
// check TDJ mode
|
// check TDJ mode
|
||||||
TDJ_MODE |= fileutils::text_read("C:\\000rom.txt") == "TDJ-JA";
|
TDJ_MODE |= fileutils::text_read("C:\\000rom.txt") == "TDJ-JA";
|
||||||
@@ -332,7 +346,9 @@ namespace games::iidx {
|
|||||||
// need to hook `avs2-core.dll` so AVS win32fs operations go through rom hook
|
// need to hook `avs2-core.dll` so AVS win32fs operations go through rom hook
|
||||||
devicehook_init(avs::core::DLL_INSTANCE);
|
devicehook_init(avs::core::DLL_INSTANCE);
|
||||||
|
|
||||||
if (!NATIVE_TOUCH) {
|
if (NATIVE_TOUCH) {
|
||||||
|
nativetouchhook::hook(avs::game::DLL_INSTANCE);
|
||||||
|
} else {
|
||||||
wintouchemu::FORCE = true;
|
wintouchemu::FORCE = true;
|
||||||
wintouchemu::INJECT_MOUSE_AS_WM_TOUCH = true;
|
wintouchemu::INJECT_MOUSE_AS_WM_TOUCH = true;
|
||||||
wintouchemu::hook_title_ends("beatmania IIDX", "main", avs::game::DLL_INSTANCE);
|
wintouchemu::hook_title_ends("beatmania IIDX", "main", avs::game::DLL_INSTANCE);
|
||||||
@@ -376,6 +392,8 @@ namespace games::iidx {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
if (hooks::audio::ENABLED) {
|
if (hooks::audio::ENABLED) {
|
||||||
apply_audio_hacks();
|
apply_audio_hacks();
|
||||||
} else {
|
} else {
|
||||||
@@ -468,9 +486,8 @@ namespace games::iidx {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// check -monitor + TDJ mode
|
// check -monitor + TDJ mode
|
||||||
if (!GRAPHICS_WINDOWED &&
|
if (!GRAPHICS_WINDOWED && D3D9_ADAPTER.has_value() && TDJ_MODE) {
|
||||||
options->at(launcher::Options::DisplayAdapter).is_active() &&
|
IIDX_TDJ_MONITOR_WARNING = true;
|
||||||
TDJ_MODE) {
|
|
||||||
log_warning(
|
log_warning(
|
||||||
"iidx",
|
"iidx",
|
||||||
"\n\n!!! using -monitor option with TDJ is NOT recommended due to known !!!\n"
|
"\n\n!!! using -monitor option with TDJ is NOT recommended due to known !!!\n"
|
||||||
@@ -492,6 +509,16 @@ namespace games::iidx {
|
|||||||
" monitor in Windows settings before launching the game"
|
" 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() {
|
void IIDXGame::detach() {
|
||||||
@@ -715,13 +742,12 @@ namespace games::iidx {
|
|||||||
bool ttpm_alt = GameAPI::Buttons::getState(RI_MGR, buttons.at(
|
bool ttpm_alt = GameAPI::Buttons::getState(RI_MGR, buttons.at(
|
||||||
player != 0 ? Buttons::P2_TTPlusMinusAlt : Buttons::P1_TTPlusMinusAlt));
|
player != 0 ? Buttons::P2_TTPlusMinusAlt : Buttons::P1_TTPlusMinusAlt));
|
||||||
|
|
||||||
// TT+
|
const auto tt_socd = socd::socd_clean(player, ttm, ttp, get_performance_milliseconds());
|
||||||
if (ttp)
|
if (tt_socd == socd::SocdCW) {
|
||||||
IIDXIO_TT_STATE[player] += change;
|
IIDXIO_TT_STATE[player] += change;
|
||||||
|
} else if (tt_socd == socd::SocdCCW) {
|
||||||
// TT-
|
|
||||||
if (ttm)
|
|
||||||
IIDXIO_TT_STATE[player] -= change;
|
IIDXIO_TT_STATE[player] -= change;
|
||||||
|
}
|
||||||
|
|
||||||
// TT+/-
|
// TT+/-
|
||||||
bool ttpm_rising_edge = !IIDXIO_TT_PRESSED[player] && ttpm;
|
bool ttpm_rising_edge = !IIDXIO_TT_PRESSED[player] && ttpm;
|
||||||
@@ -892,4 +918,52 @@ namespace games::iidx {
|
|||||||
#endif
|
#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;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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");
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -33,6 +33,7 @@ namespace games::iidx {
|
|||||||
extern char IIDXIO_LED_TICKER[10];
|
extern char IIDXIO_LED_TICKER[10];
|
||||||
extern bool IIDXIO_LED_TICKER_READONLY;
|
extern bool IIDXIO_LED_TICKER_READONLY;
|
||||||
extern std::mutex IIDX_LED_TICKER_LOCK;
|
extern std::mutex IIDX_LED_TICKER_LOCK;
|
||||||
|
extern bool IIDX_TDJ_MONITOR_WARNING;
|
||||||
|
|
||||||
constexpr int IIDX_TAPELED_TOTAL = 17;
|
constexpr int IIDX_TAPELED_TOTAL = 17;
|
||||||
// data mapping
|
// data mapping
|
||||||
@@ -62,4 +63,10 @@ namespace games::iidx {
|
|||||||
const char* get_16seg();
|
const char* get_16seg();
|
||||||
bool is_tdj_fhd();
|
bool is_tdj_fhd();
|
||||||
void apply_audio_hacks();
|
void apply_audio_hacks();
|
||||||
|
|
||||||
|
enum class iidx_aio_emulation_state {
|
||||||
|
bi2a_com2,
|
||||||
|
bi2x_hook
|
||||||
|
};
|
||||||
|
void update_io_emulation_state(iidx_aio_emulation_state state);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
#include "bi2x_hook.h"
|
#include "bi2x_hook.h"
|
||||||
|
|
||||||
|
#if SPICE64
|
||||||
|
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
#include "util/detour.h"
|
#include "util/detour.h"
|
||||||
#include "util/logging.h"
|
#include "util/logging.h"
|
||||||
@@ -10,12 +12,22 @@
|
|||||||
#include "launcher/options.h"
|
#include "launcher/options.h"
|
||||||
#include "io.h"
|
#include "io.h"
|
||||||
#include "games/sdvx/sdvx.h"
|
#include "games/sdvx/sdvx.h"
|
||||||
|
#include "util/socd_cleaner.h"
|
||||||
#include "util/tapeled.h"
|
#include "util/tapeled.h"
|
||||||
|
#include "util/time.h"
|
||||||
#include "acioemu/icca.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 {
|
namespace games::sdvx {
|
||||||
constexpr bool BI2X_PASSTHROUGH = false;
|
constexpr bool BI2X_PASSTHROUGH = false;
|
||||||
bool BI2X_INITIALIZED = false;
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* class definitions
|
* class definitions
|
||||||
@@ -26,17 +38,28 @@ namespace games::sdvx {
|
|||||||
uint8_t data[0x10];
|
uint8_t data[0x10];
|
||||||
};
|
};
|
||||||
|
|
||||||
struct AIO_NMGR_IOB2 {
|
struct AIO_NMGR_IOB2_VTABLE {
|
||||||
uint8_t dummy0[0x50];
|
// other functions here, but they never get called
|
||||||
|
uint8_t dummy[0x50];
|
||||||
void (__fastcall *pAIO_NMGR_IOB_BeginManage)(int64_t a1);
|
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 {
|
struct AIO_IOB2_BI2X_UFC {
|
||||||
// who knows
|
// 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__DEVSTATUS {
|
struct AIO_IOB2_BI2X_UFC__DEVSTATUS {
|
||||||
// of course you could work with variables here
|
// of course you could work with variables here
|
||||||
uint8_t buffer[0x19E];
|
uint8_t buffer[0x19E];
|
||||||
@@ -66,7 +89,7 @@ namespace games::sdvx {
|
|||||||
// libaio-iob.dll
|
// libaio-iob.dll
|
||||||
typedef AC_HNDLIF* (__fastcall *aioIob2Bi2x_OpenSciUsbCdc_t)(uint8_t device_num);
|
typedef AC_HNDLIF* (__fastcall *aioIob2Bi2x_OpenSciUsbCdc_t)(uint8_t device_num);
|
||||||
typedef int64_t (__fastcall *aioIob2Bi2x_WriteFirmGetState_t)(int64_t a1);
|
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
|
* function pointers
|
||||||
@@ -164,19 +187,27 @@ namespace games::sdvx {
|
|||||||
if (GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::Headphone]))
|
if (GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::Headphone]))
|
||||||
status->buffer[22] = 0x01;
|
status->buffer[22] = 0x01;
|
||||||
|
|
||||||
|
const auto now = get_performance_milliseconds();
|
||||||
|
|
||||||
// volume left
|
// 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);
|
VOL_L -= ((uint16_t)DIGITAL_KNOB_SENS * 4);
|
||||||
}
|
} else if (vol_l_state == socd::SocdCW) {
|
||||||
if (GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::VOL_L_Right])) {
|
|
||||||
VOL_L += ((uint16_t)DIGITAL_KNOB_SENS * 4);
|
VOL_L += ((uint16_t)DIGITAL_KNOB_SENS * 4);
|
||||||
}
|
}
|
||||||
|
|
||||||
// volume right
|
// 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);
|
VOL_R -= ((uint16_t)DIGITAL_KNOB_SENS * 4);
|
||||||
}
|
} else if (vol_r_state == socd::SocdCW) {
|
||||||
if (GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::VOL_R_Right])) {
|
|
||||||
VOL_R += ((uint16_t)DIGITAL_KNOB_SENS * 4);
|
VOL_R += ((uint16_t)DIGITAL_KNOB_SENS * 4);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -191,6 +222,12 @@ namespace games::sdvx {
|
|||||||
|
|
||||||
*((uint16_t*) &status->buffer[312]) = vol_left;
|
*((uint16_t*) &status->buffer[312]) = vol_left;
|
||||||
*((uint16_t*) &status->buffer[314]) = vol_right;
|
*((uint16_t*) &status->buffer[314]) = vol_right;
|
||||||
|
|
||||||
|
log_debug(
|
||||||
|
"bi2x_hook",
|
||||||
|
"knobs = {} {}",
|
||||||
|
*((uint16_t*) &status->buffer[312]),
|
||||||
|
*((uint16_t*) &status->buffer[314]));
|
||||||
}
|
}
|
||||||
|
|
||||||
static void __fastcall AIO_IOB2_BI2X_UFC__IoReset(AIO_IOB2_BI2X_UFC *This,
|
static void __fastcall AIO_IOB2_BI2X_UFC__IoReset(AIO_IOB2_BI2X_UFC *This,
|
||||||
@@ -345,21 +382,21 @@ namespace games::sdvx {
|
|||||||
log_info("bi2x_hook", "AIO_NMGR_IOB::BeginManage");
|
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) {
|
if (aioNmgrIob2 == nullptr) {
|
||||||
aioNmgrIob2 = new AIO_NMGR_IOB2;
|
aioNmgrIob2 = new AIO_NMGR_IOB2{};
|
||||||
memset(aioNmgrIob2, 0x0, sizeof(AIO_NMGR_IOB2));
|
aioNmgrIob2->vptr = new AIO_NMGR_IOB2_VTABLE{};
|
||||||
aioNmgrIob2->pAIO_NMGR_IOB_BeginManage = AIO_NMGR_IOB_BeginManageStub;
|
aioNmgrIob2->vptr->pAIO_NMGR_IOB_BeginManage = AIO_NMGR_IOB_BeginManageStub;
|
||||||
}
|
}
|
||||||
log_info("bi2x_hook", "aioNMgrIob2_Create");
|
log_info("bi2x_hook", "aioNMgrIob2_Create returned {}, size=0x{:x}, vptr @ {}",
|
||||||
BI2X_INITIALIZED = true;
|
fmt::ptr(aioNmgrIob2), sizeof(*aioNmgrIob2), fmt::ptr(aioNmgrIob2->vptr));
|
||||||
|
|
||||||
// enable hack to make PIN pad work for KFC in BI2X mode
|
// enable hack to make PIN pad work for KFC in BI2X mode
|
||||||
// this explicit check in the I/O init path is necessary
|
// this explicit check in the I/O init path is necessary
|
||||||
// (as opposed to just doing a check for "isValkyrieCabMode?")
|
// (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
|
// because there are hex edits that allow you to use legacy (KFC/BIO2) IO while in Valk mode
|
||||||
acioemu::ICCA_DEVICE_HACK = true;
|
acioemu::ICCA_DEVICE_HACK = true;
|
||||||
return &aioNmgrIob2;
|
return aioNmgrIob2;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int64_t __fastcall aioIob2Bi2x_WriteFirmGetState(int64_t a1) {
|
static int64_t __fastcall aioIob2Bi2x_WriteFirmGetState(int64_t a1) {
|
||||||
@@ -411,3 +448,5 @@ namespace games::sdvx {
|
|||||||
aioNMgrIob2_Create, &aioNMgrIob2_Create_orig);
|
aioNMgrIob2_Create, &aioNMgrIob2_Create_orig);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
@@ -1,6 +1,10 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#if SPICE64
|
||||||
|
|
||||||
namespace games::sdvx {
|
namespace games::sdvx {
|
||||||
|
|
||||||
void bi2x_hook_init();
|
void bi2x_hook_init();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
@@ -2,7 +2,6 @@
|
|||||||
|
|
||||||
#include <external/robin_hood.h>
|
#include <external/robin_hood.h>
|
||||||
|
|
||||||
#include "avs/game.h"
|
|
||||||
#include "games/shared/lcdhandle.h"
|
#include "games/shared/lcdhandle.h"
|
||||||
#include "games/io.h"
|
#include "games/io.h"
|
||||||
#include "hooks/audio/audio.h"
|
#include "hooks/audio/audio.h"
|
||||||
@@ -19,6 +18,8 @@
|
|||||||
#include "util/detour.h"
|
#include "util/detour.h"
|
||||||
#include "util/logging.h"
|
#include "util/logging.h"
|
||||||
#include "util/sigscan.h"
|
#include "util/sigscan.h"
|
||||||
|
#include "util/socd_cleaner.h"
|
||||||
|
#include "util/time.h"
|
||||||
#include "util/libutils.h"
|
#include "util/libutils.h"
|
||||||
#include "misc/wintouchemu.h"
|
#include "misc/wintouchemu.h"
|
||||||
#include "misc/eamuse.h"
|
#include "misc/eamuse.h"
|
||||||
@@ -48,21 +49,31 @@ namespace games::sdvx {
|
|||||||
SdvxOverlayPosition OVERLAY_POS = SDVX_OVERLAY_BOTTOM;
|
SdvxOverlayPosition OVERLAY_POS = SDVX_OVERLAY_BOTTOM;
|
||||||
bool ENABLE_COM_PORT_SCAN_HOOK = false;
|
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;
|
std::optional<std::string> ASIO_DRIVER = std::nullopt;
|
||||||
|
|
||||||
// states
|
// states
|
||||||
|
bool SHOW_VM_MONITOR_WARNING = false;
|
||||||
static HKEY real_asio_reg_handle = nullptr;
|
static HKEY real_asio_reg_handle = nullptr;
|
||||||
static HKEY real_asio_device_reg_handle = nullptr;
|
static HKEY real_asio_device_reg_handle = nullptr;
|
||||||
|
|
||||||
static LONG WINAPI RegOpenKeyA_hook(HKEY hKey, LPCSTR lpSubKey, PHKEY phkResult) {
|
static LONG WINAPI RegOpenKeyA_hook(HKEY hKey, LPCSTR lpSubKey, PHKEY phkResult) {
|
||||||
if (lpSubKey != nullptr && phkResult != nullptr) {
|
if (lpSubKey != nullptr &&
|
||||||
if (hKey == HKEY_LOCAL_MACHINE &&
|
phkResult != nullptr &&
|
||||||
ASIO_DRIVER.has_value() &&
|
hKey == HKEY_LOCAL_MACHINE &&
|
||||||
_stricmp(lpSubKey, "software\\asio") == 0)
|
_stricmp(lpSubKey, "software\\asio") == 0) {
|
||||||
{
|
|
||||||
*phkResult = PARENT_ASIO_REG_HANDLE;
|
|
||||||
|
|
||||||
|
// prevent the game from randomly picking an ASIO device
|
||||||
|
if (!USE_ASIO) {
|
||||||
|
log_info(
|
||||||
|
"sdvx::asio",
|
||||||
|
"game tried to access HKLM\\SOFTWARE\\ASIO but ASIO is disabled; "
|
||||||
|
"access prevented, ASIO init will fail");
|
||||||
|
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);
|
return RegOpenKeyA_orig(hKey, lpSubKey, &real_asio_reg_handle);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -109,7 +120,7 @@ namespace games::sdvx {
|
|||||||
lpSubKey != nullptr && phkResult != nullptr &&
|
lpSubKey != nullptr && phkResult != nullptr &&
|
||||||
_stricmp(lpSubKey, "HARDWARE\\DEVICEMAP\\SERIALCOMM") == 0) {
|
_stricmp(lpSubKey, "HARDWARE\\DEVICEMAP\\SERIALCOMM") == 0) {
|
||||||
log_info("sdvx::io", "hook access to HKLM\\HARDWARE\\DEVICEMAP\\SERIALCOMM to force COM1 ICCA");
|
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);
|
return RegOpenKeyExA_orig(hKey, lpSubKey, ulOptions, samDesired, phkResult);
|
||||||
@@ -229,6 +240,32 @@ namespace games::sdvx {
|
|||||||
return false;
|
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);
|
typedef void **(__fastcall *volume_set_t)(uint64_t, uint64_t, uint64_t);
|
||||||
static volume_set_t volume_set_orig = nullptr;
|
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) {
|
static void **__fastcall volume_set_hook(uint64_t vol_sound, uint64_t vol_woofer, uint64_t vol_headphone) {
|
||||||
@@ -295,12 +332,9 @@ namespace games::sdvx {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#ifdef SPICE64 // SDVX5+ specific code
|
#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
|
// check -monitor + UFC mode
|
||||||
if (!GRAPHICS_WINDOWED &&
|
if (!GRAPHICS_WINDOWED && D3D9_ADAPTER.has_value() && is_valkyrie_model()) {
|
||||||
options->at(launcher::Options::DisplayAdapter).is_active() &&
|
SHOW_VM_MONITOR_WARNING = true;
|
||||||
isValkyrieCabinetMode) {
|
|
||||||
log_warning(
|
log_warning(
|
||||||
"sdvx",
|
"sdvx",
|
||||||
"\n\n!!! using -monitor option with VM mode is NOT recommended due to !!!\n"
|
"\n\n!!! using -monitor option with VM mode is NOT recommended due to !!!\n"
|
||||||
@@ -324,16 +358,29 @@ namespace games::sdvx {
|
|||||||
}
|
}
|
||||||
#endif
|
#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() {
|
void SDVXGame::attach() {
|
||||||
Game::attach();
|
Game::attach();
|
||||||
|
|
||||||
#ifdef SPICE64 // SDVX5+ specific code
|
#ifdef SPICE64 // SDVX5+ specific code
|
||||||
bool isValkyrieCabinetMode = avs::game::SPEC[0] == 'G' || avs::game::SPEC[0] == 'H';
|
|
||||||
|
|
||||||
// LCD handle
|
// LCD handle
|
||||||
if (!isValkyrieCabinetMode) {
|
if (!is_valkyrie_model()) {
|
||||||
devicehook_init();
|
devicehook_init();
|
||||||
devicehook_add(new games::shared::LCDHandle());
|
devicehook_add(new games::shared::LCDHandle());
|
||||||
}
|
}
|
||||||
@@ -372,8 +419,7 @@ namespace games::sdvx {
|
|||||||
nvapi_hook::initialize(avs::game::DLL_INSTANCE);
|
nvapi_hook::initialize(avs::game::DLL_INSTANCE);
|
||||||
}
|
}
|
||||||
|
|
||||||
// check for Valkyrie cabinet mode
|
if (is_valkyrie_model()) {
|
||||||
if (isValkyrieCabinetMode) {
|
|
||||||
// hook touch window
|
// hook touch window
|
||||||
// in windowed mode, game can accept mouse input on the second screen
|
// in windowed mode, game can accept mouse input on the second screen
|
||||||
if (!NATIVETOUCH && !GRAPHICS_WINDOWED) {
|
if (!NATIVETOUCH && !GRAPHICS_WINDOWED) {
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
#include <optional>
|
#include <optional>
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
|
|
||||||
|
#include "avs/game.h"
|
||||||
#include "games/game.h"
|
#include "games/game.h"
|
||||||
|
|
||||||
namespace games::sdvx {
|
namespace games::sdvx {
|
||||||
@@ -20,9 +21,18 @@ namespace games::sdvx {
|
|||||||
extern bool NATIVETOUCH;
|
extern bool NATIVETOUCH;
|
||||||
extern uint8_t DIGITAL_KNOB_SENS;
|
extern uint8_t DIGITAL_KNOB_SENS;
|
||||||
extern std::optional<std::string> ASIO_DRIVER;
|
extern std::optional<std::string> ASIO_DRIVER;
|
||||||
extern bool BI2X_INITIALIZED;
|
|
||||||
extern SdvxOverlayPosition OVERLAY_POS;
|
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 {
|
class SDVXGame : public games::Game {
|
||||||
public:
|
public:
|
||||||
SDVXGame();
|
SDVXGame();
|
||||||
@@ -30,5 +40,7 @@ namespace games::sdvx {
|
|||||||
virtual void attach() override;
|
virtual void attach() override;
|
||||||
virtual void post_attach() override;
|
virtual void post_attach() override;
|
||||||
virtual void detach() override;
|
virtual void detach() override;
|
||||||
|
private:
|
||||||
|
void detect_sound_output_device();
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,6 +8,7 @@
|
|||||||
#include "hooks/audio/audio_private.h"
|
#include "hooks/audio/audio_private.h"
|
||||||
#include "hooks/audio/backends/mmdevice/audio_endpoint_volume.h"
|
#include "hooks/audio/backends/mmdevice/audio_endpoint_volume.h"
|
||||||
#include "hooks/audio/backends/wasapi/audio_client.h"
|
#include "hooks/audio/backends/wasapi/audio_client.h"
|
||||||
|
#include "util/utils.h"
|
||||||
|
|
||||||
#define PRINT_FAILED_RESULT(name, ret) \
|
#define PRINT_FAILED_RESULT(name, ret) \
|
||||||
do { \
|
do { \
|
||||||
@@ -56,6 +57,7 @@ ULONG STDMETHODCALLTYPE WrappedIMMDevice::Release() {
|
|||||||
ULONG refs = pReal != nullptr ? pReal->Release() : 0;
|
ULONG refs = pReal != nullptr ? pReal->Release() : 0;
|
||||||
|
|
||||||
if (refs == 0) {
|
if (refs == 0) {
|
||||||
|
log_misc("audio::mmdevice", "WrappedIMMDevice::Release");
|
||||||
delete this;
|
delete this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -69,7 +71,7 @@ HRESULT STDMETHODCALLTYPE WrappedIMMDevice::Activate(
|
|||||||
PROPVARIANT *pActivationParams,
|
PROPVARIANT *pActivationParams,
|
||||||
void **ppInterface)
|
void **ppInterface)
|
||||||
{
|
{
|
||||||
log_misc("audio::mmdevice", "WrappedIMMDevice::Activate");
|
log_misc("audio::mmdevice", "WrappedIMMDevice::Activate {}", guid2s(iid));
|
||||||
|
|
||||||
// call original
|
// call original
|
||||||
HRESULT ret = pReal->Activate(iid, dwClsCtx, pActivationParams, ppInterface);
|
HRESULT ret = pReal->Activate(iid, dwClsCtx, pActivationParams, ppInterface);
|
||||||
|
|||||||
@@ -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_enumerator.h"
|
||||||
|
#include "device_collection.h"
|
||||||
#include "device.h"
|
#include "device.h"
|
||||||
#include "util/utils.h"
|
#include "util/utils.h"
|
||||||
|
|
||||||
@@ -42,7 +43,11 @@ HRESULT STDMETHODCALLTYPE WrappedIMMDeviceEnumerator::EnumAudioEndpoints(
|
|||||||
DWORD dwStateMask,
|
DWORD dwStateMask,
|
||||||
IMMDeviceCollection **ppDevices)
|
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(
|
HRESULT STDMETHODCALLTYPE WrappedIMMDeviceEnumerator::GetDefaultAudioEndpoint(
|
||||||
|
|||||||
@@ -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 "avshook.h"
|
||||||
|
|
||||||
|
#include <cassert>
|
||||||
|
#include <mutex>
|
||||||
#include <map>
|
#include <map>
|
||||||
#include <optional>
|
#include <optional>
|
||||||
|
#include <external/robin_hood.h>
|
||||||
|
|
||||||
#include "avs/core.h"
|
#include "avs/core.h"
|
||||||
#include "avs/ea3.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::map<std::string, std::string> ROM_FILE_MAP;
|
||||||
static std::string *ROM_FILE_CONTENTS = nullptr;
|
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 {
|
namespace hooks::avs::config {
|
||||||
bool DISABLE_VFS_DRIVE_REDIRECTION = false;
|
bool DISABLE_VFS_DRIVE_REDIRECTION = false;
|
||||||
|
bool DISABLE_CACHE = false;
|
||||||
bool LOG = false;
|
bool LOG = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -79,6 +86,74 @@ static bool is_spam_file(const char *file) {
|
|||||||
return false;
|
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) {
|
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 (is_fake_fd(fd) && ROM_FILE_CONTENTS) {
|
||||||
if (st) {
|
if (st) {
|
||||||
@@ -104,7 +179,12 @@ static int avs_fs_lstat(const char *name, struct avs::core::avs_stat *st) {
|
|||||||
return 1;
|
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)) {
|
if (!is_spam_file(name)) {
|
||||||
WRAP_DEBUG_FMT("name: {}", name);
|
WRAP_DEBUG_FMT("name: {}", name);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,6 +9,7 @@
|
|||||||
namespace hooks::avs {
|
namespace hooks::avs {
|
||||||
namespace config {
|
namespace config {
|
||||||
extern bool DISABLE_VFS_DRIVE_REDIRECTION;
|
extern bool DISABLE_VFS_DRIVE_REDIRECTION;
|
||||||
|
extern bool DISABLE_CACHE;
|
||||||
extern bool LOG;
|
extern bool LOG;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
#include "avs/game.h"
|
#include "avs/game.h"
|
||||||
|
#include "games/gitadora/gitadora.h"
|
||||||
#include "util/detour.h"
|
#include "util/detour.h"
|
||||||
#include "util/utils.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();
|
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;
|
return TRUE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -41,6 +41,15 @@
|
|||||||
#undef min
|
#undef min
|
||||||
#endif
|
#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) \
|
#define CHECK_RESULT(x) \
|
||||||
do { \
|
do { \
|
||||||
HRESULT __ret = (x); \
|
HRESULT __ret = (x); \
|
||||||
@@ -514,7 +523,7 @@ HRESULT STDMETHODCALLTYPE WrappedIDirect3D9::EnumAdapterModes(
|
|||||||
if (!modified && games::iidx::is_tdj_fhd() && refresh < 110) {
|
if (!modified && games::iidx::is_tdj_fhd() && refresh < 110) {
|
||||||
if ((width == 1920 && height == 1080) ||
|
if ((width == 1920 && height == 1080) ||
|
||||||
(width == 1280 && height == 720)){
|
(width == 1280 && height == 720)){
|
||||||
log_misc(
|
log_debug(
|
||||||
"graphics::d3d9", "removing mode {}, {}x{} @ {}Hz (for TDJ FHD)",
|
"graphics::d3d9", "removing mode {}, {}x{} @ {}Hz (for TDJ FHD)",
|
||||||
Mode, width, height, refresh);
|
Mode, width, height, refresh);
|
||||||
memset(pMode, 0, sizeof(*pMode));
|
memset(pMode, 0, sizeof(*pMode));
|
||||||
@@ -527,7 +536,7 @@ HRESULT STDMETHODCALLTYPE WrappedIDirect3D9::EnumAdapterModes(
|
|||||||
// - skip 75 and 90 Hz entries because LDJ game timing is messed up with it
|
// - 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)
|
// (TDJ should be fine as it assumes a fixed 60 Hz timing)
|
||||||
if (!modified && (width == 1360 || width == 1366 || refresh == 75 || refresh == 90)) {
|
if (!modified && (width == 1360 || width == 1366 || refresh == 75 || refresh == 90)) {
|
||||||
log_misc(
|
log_debug(
|
||||||
"graphics::d3d9", "removing mode {}, {}x{} @ {}Hz (for LDJ/TDJ)",
|
"graphics::d3d9", "removing mode {}, {}x{} @ {}Hz (for LDJ/TDJ)",
|
||||||
Mode, width, height, refresh);
|
Mode, width, height, refresh);
|
||||||
memset(pMode, 0, sizeof(*pMode));
|
memset(pMode, 0, sizeof(*pMode));
|
||||||
@@ -535,7 +544,7 @@ HRESULT STDMETHODCALLTYPE WrappedIDirect3D9::EnumAdapterModes(
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!modified && !games::iidx::TDJ_MODE && games::iidx::FORCE_720P && (height > 720)) {
|
if (!modified && !games::iidx::TDJ_MODE && games::iidx::FORCE_720P && (height > 720)) {
|
||||||
log_misc(
|
log_debug(
|
||||||
"graphics::d3d9", "removing mode {}, {}x{} @ {}Hz (-iidxforce720p)",
|
"graphics::d3d9", "removing mode {}, {}x{} @ {}Hz (-iidxforce720p)",
|
||||||
Mode, width, height, refresh);
|
Mode, width, height, refresh);
|
||||||
memset(pMode, 0, sizeof(*pMode));
|
memset(pMode, 0, sizeof(*pMode));
|
||||||
@@ -1353,9 +1362,7 @@ void graphics_d3d9_on_present(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// for IIDX TDJ / SDVX UFC, handle subscreen
|
// for IIDX TDJ / SDVX UFC, handle subscreen
|
||||||
const bool is_vm =
|
const bool is_vm = games::sdvx::is_valkyrie_model();
|
||||||
avs::game::is_model("KFC") &&
|
|
||||||
(avs::game::SPEC[0] == 'G' || avs::game::SPEC[0] == 'H');
|
|
||||||
const bool is_tdj = avs::game::is_model("LDJ") && games::iidx::TDJ_MODE;
|
const bool is_tdj = avs::game::is_model("LDJ") && games::iidx::TDJ_MODE;
|
||||||
if (is_vm || is_tdj) {
|
if (is_vm || is_tdj) {
|
||||||
graphics_d3d9_ldj_on_present(wrapped_device);
|
graphics_d3d9_ldj_on_present(wrapped_device);
|
||||||
|
|||||||
@@ -11,6 +11,7 @@
|
|||||||
#include "cfg/icon.h"
|
#include "cfg/icon.h"
|
||||||
#include "cfg/screen_resize.h"
|
#include "cfg/screen_resize.h"
|
||||||
#include "games/ddr/ddr.h"
|
#include "games/ddr/ddr.h"
|
||||||
|
#include "games/gitadora/gitadora.h"
|
||||||
#include "games/iidx/iidx.h"
|
#include "games/iidx/iidx.h"
|
||||||
#include "hooks/graphics/backends/d3d9/d3d9_backend.h"
|
#include "hooks/graphics/backends/d3d9/d3d9_backend.h"
|
||||||
#include "launcher/shutdown.h"
|
#include "launcher/shutdown.h"
|
||||||
@@ -33,6 +34,7 @@ struct CaptureData {
|
|||||||
|
|
||||||
HWND TDJ_SUBSCREEN_WINDOW = nullptr;
|
HWND TDJ_SUBSCREEN_WINDOW = nullptr;
|
||||||
HWND SDVX_SUBSCREEN_WINDOW = nullptr;
|
HWND SDVX_SUBSCREEN_WINDOW = nullptr;
|
||||||
|
HWND GFDM_SUBSCREEN_WINDOW = nullptr;
|
||||||
|
|
||||||
// icon
|
// icon
|
||||||
static HICON WINDOW_ICON = LoadIcon(GetModuleHandle(nullptr), MAKEINTRESOURCE(MAINICON));
|
static HICON WINDOW_ICON = LoadIcon(GetModuleHandle(nullptr), MAKEINTRESOURCE(MAINICON));
|
||||||
@@ -59,6 +61,7 @@ bool GRAPHICS_SHOW_CURSOR = false;
|
|||||||
graphics_orientation GRAPHICS_ADJUST_ORIENTATION = ORIENTATION_NORMAL;
|
graphics_orientation GRAPHICS_ADJUST_ORIENTATION = ORIENTATION_NORMAL;
|
||||||
bool GRAPHICS_WINDOWED = false;
|
bool GRAPHICS_WINDOWED = false;
|
||||||
std::vector<HWND> GRAPHICS_WINDOWS;
|
std::vector<HWND> GRAPHICS_WINDOWS;
|
||||||
|
std::optional<HWND> GRAPHICS_WINDOW_MAIN;
|
||||||
UINT GRAPHICS_FORCE_REFRESH = 0;
|
UINT GRAPHICS_FORCE_REFRESH = 0;
|
||||||
std::optional<int> GRAPHICS_FORCE_VSYNC_BUFFER;
|
std::optional<int> GRAPHICS_FORCE_VSYNC_BUFFER;
|
||||||
bool GRAPHICS_FORCE_SINGLE_ADAPTER = false;
|
bool GRAPHICS_FORCE_SINGLE_ADAPTER = false;
|
||||||
@@ -360,21 +363,28 @@ static HWND WINAPI CreateWindowExA_hook(DWORD dwExStyle, LPCSTR lpClassName, LPC
|
|||||||
}
|
}
|
||||||
|
|
||||||
// gfdm
|
// gfdm
|
||||||
|
bool is_gitadora_main_window = false;
|
||||||
if (avs::game::is_model({"J32", "J33", "K32", "K33", "L32", "L33", "M32"})) {
|
if (avs::game::is_model({"J32", "J33", "K32", "K33", "L32", "L33", "M32"})) {
|
||||||
// set window name
|
// set window name
|
||||||
if (!lpWindowName) {
|
if (!lpWindowName) {
|
||||||
lpWindowName = "GITADORA";
|
lpWindowName = "GITADORA";
|
||||||
|
is_gitadora_main_window = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool is_tdj_sub_window = avs::game::is_model("LDJ") && window_name.ends_with(" sub");
|
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_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
|
// TDJ windowed mode with subscreen: hide maximize button
|
||||||
if ((is_tdj_sub_window && GRAPHICS_IIDX_WSUB) || is_sdvx_sub_window) {
|
if ((is_tdj_sub_window && GRAPHICS_IIDX_WSUB) || is_sdvx_sub_window || is_gfdm_sub_window) {
|
||||||
dwStyle &= ~(WS_MAXIMIZEBOX);
|
dwStyle &= ~(WS_MAXIMIZEBOX);
|
||||||
}
|
}
|
||||||
if ((is_tdj_sub_window || is_sdvx_sub_window) && GRAPHICS_WSUB_BORDERLESS) {
|
// mouse clicks become misaligned when resized
|
||||||
|
if (is_gfdm_sub_window) {
|
||||||
|
dwStyle &= ~(WS_SIZEBOX);
|
||||||
|
}
|
||||||
|
if ((is_tdj_sub_window || is_sdvx_sub_window || is_gfdm_sub_window) && GRAPHICS_WSUB_BORDERLESS) {
|
||||||
dwStyle &= ~(WS_OVERLAPPEDWINDOW);
|
dwStyle &= ~(WS_OVERLAPPEDWINDOW);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -401,6 +411,11 @@ static HWND WINAPI CreateWindowExA_hook(DWORD dwExStyle, LPCSTR lpClassName, LPC
|
|||||||
hWndParent, hMenu, hInstance, lpParam);
|
hWndParent, hMenu, hInstance, lpParam);
|
||||||
GRAPHICS_WINDOWS.push_back(result);
|
GRAPHICS_WINDOWS.push_back(result);
|
||||||
|
|
||||||
|
// this is only really needed for arena model, but pre-arena model was single window anyway
|
||||||
|
if (is_gitadora_main_window) {
|
||||||
|
GRAPHICS_WINDOW_MAIN = result;
|
||||||
|
}
|
||||||
|
|
||||||
if (is_tdj_sub_window) {
|
if (is_tdj_sub_window) {
|
||||||
// TDJ windowed mode: remember the subscreen window handle for later
|
// TDJ windowed mode: remember the subscreen window handle for later
|
||||||
TDJ_SUBSCREEN_WINDOW = result;
|
TDJ_SUBSCREEN_WINDOW = result;
|
||||||
@@ -417,7 +432,13 @@ static HWND WINAPI CreateWindowExA_hook(DWORD dwExStyle, LPCSTR lpClassName, LPC
|
|||||||
graphics_hook_subscreen_window(SDVX_SUBSCREEN_WINDOW);
|
graphics_hook_subscreen_window(SDVX_SUBSCREEN_WINDOW);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (is_gfdm_sub_window) {
|
||||||
|
GFDM_SUBSCREEN_WINDOW = result;
|
||||||
|
graphics_hook_subscreen_window(GFDM_SUBSCREEN_WINDOW);
|
||||||
|
}
|
||||||
|
|
||||||
disable_touch_indicators(result);
|
disable_touch_indicators(result);
|
||||||
|
log_misc("graphics", "CreateWindowExA returned {}", fmt::ptr(result));
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -484,6 +505,7 @@ static HWND WINAPI CreateWindowExW_hook(DWORD dwExStyle, LPCWSTR lpClassName, LP
|
|||||||
hWndParent, hMenu, hInstance, lpParam);
|
hWndParent, hMenu, hInstance, lpParam);
|
||||||
GRAPHICS_WINDOWS.push_back(result);
|
GRAPHICS_WINDOWS.push_back(result);
|
||||||
|
|
||||||
|
log_misc("graphics", "CreateWindowExW returned {}", fmt::ptr(result));
|
||||||
disable_touch_indicators(result);
|
disable_touch_indicators(result);
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
@@ -648,6 +670,13 @@ static BOOL WINAPI SetWindowPos_hook(HWND hWnd, HWND hWndInsertAfter,
|
|||||||
return TRUE;
|
return TRUE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// prevent gitadora arena model from shifting windows around if the user has preferences
|
||||||
|
if (GRAPHICS_WINDOWED && games::gitadora::is_arena_model() &&
|
||||||
|
GRAPHICS_WINDOW_MAIN.has_value() && hWnd == GRAPHICS_WINDOW_MAIN.value() &&
|
||||||
|
cfg::SCREENRESIZE->enable_window_resize) {
|
||||||
|
return TRUE;
|
||||||
|
}
|
||||||
|
|
||||||
// call original
|
// call original
|
||||||
return SetWindowPos_orig(hWnd, hWndInsertAfter, X, Y, cx, cy, uFlags);
|
return SetWindowPos_orig(hWnd, hWndInsertAfter, X, Y, cx, cy, uFlags);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -31,6 +31,7 @@ extern bool GRAPHICS_SHOW_CURSOR;
|
|||||||
extern bool GRAPHICS_WINDOWED;
|
extern bool GRAPHICS_WINDOWED;
|
||||||
extern graphics_orientation GRAPHICS_ADJUST_ORIENTATION;
|
extern graphics_orientation GRAPHICS_ADJUST_ORIENTATION;
|
||||||
extern std::vector<HWND> GRAPHICS_WINDOWS;
|
extern std::vector<HWND> GRAPHICS_WINDOWS;
|
||||||
|
extern std::optional<HWND> GRAPHICS_WINDOW_MAIN;
|
||||||
extern UINT GRAPHICS_FORCE_REFRESH;
|
extern UINT GRAPHICS_FORCE_REFRESH;
|
||||||
extern std::optional<int> GRAPHICS_FORCE_VSYNC_BUFFER;
|
extern std::optional<int> GRAPHICS_FORCE_VSYNC_BUFFER;
|
||||||
extern bool GRAPHICS_FORCE_SINGLE_ADAPTER;
|
extern bool GRAPHICS_FORCE_SINGLE_ADAPTER;
|
||||||
@@ -60,6 +61,7 @@ extern bool GRAPHICS_WSUB_BORDERLESS;
|
|||||||
extern bool GRAPHICS_WSUB_ALWAYS_ON_TOP;
|
extern bool GRAPHICS_WSUB_ALWAYS_ON_TOP;
|
||||||
extern HWND TDJ_SUBSCREEN_WINDOW;
|
extern HWND TDJ_SUBSCREEN_WINDOW;
|
||||||
extern HWND SDVX_SUBSCREEN_WINDOW;
|
extern HWND SDVX_SUBSCREEN_WINDOW;
|
||||||
|
extern HWND GFDM_SUBSCREEN_WINDOW;
|
||||||
|
|
||||||
extern bool SUBSCREEN_FORCE_REDRAW;
|
extern bool SUBSCREEN_FORCE_REDRAW;
|
||||||
|
|
||||||
|
|||||||
@@ -56,8 +56,12 @@ void graphics_capture_initial_window(HWND hWnd) {
|
|||||||
graphics_load_windowed_parameters();
|
graphics_load_windowed_parameters();
|
||||||
|
|
||||||
cfg::SCREENRESIZE->init_window_style = GetWindowLong(hWnd, GWL_STYLE);
|
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");
|
log_debug(
|
||||||
|
"graphics-windowed",
|
||||||
|
"graphics_capture_initial_window called, hWnd={}",
|
||||||
|
fmt::ptr(hWnd));
|
||||||
|
|
||||||
RECT rect;
|
RECT rect;
|
||||||
if (!GetClientRect(hWnd, &rect)) {
|
if (!GetClientRect(hWnd, &rect)) {
|
||||||
@@ -372,13 +376,19 @@ void graphics_update_window_style(HWND hWnd) {
|
|||||||
return;
|
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
|
// update frame style
|
||||||
auto style = cfg::SCREENRESIZE->init_window_style;
|
auto style = cfg::SCREENRESIZE->init_window_style;
|
||||||
|
auto style_ex = cfg::SCREENRESIZE->init_window_style_ex;
|
||||||
switch (cfg::SCREENRESIZE->window_decoration) {
|
switch (cfg::SCREENRESIZE->window_decoration) {
|
||||||
case cfg::WindowDecorationMode::Borderless:
|
case cfg::WindowDecorationMode::Borderless:
|
||||||
style &= ~WS_OVERLAPPEDWINDOW;
|
style &= ~WS_OVERLAPPEDWINDOW;
|
||||||
|
style_ex &= ~WS_EX_CLIENTEDGE;
|
||||||
|
style_ex &= ~WS_EX_WINDOWEDGE;
|
||||||
break;
|
break;
|
||||||
case cfg::WindowDecorationMode::ResizableFrame:
|
case cfg::WindowDecorationMode::ResizableFrame:
|
||||||
style |= WS_OVERLAPPEDWINDOW;
|
style |= WS_OVERLAPPEDWINDOW;
|
||||||
@@ -393,11 +403,26 @@ void graphics_update_window_style(HWND hWnd) {
|
|||||||
"graphics_update_window_style - calling SetWindowLong with Mode {}, style 0x{:x}",
|
"graphics_update_window_style - calling SetWindowLong with Mode {}, style 0x{:x}",
|
||||||
static_cast<int>(cfg::SCREENRESIZE->window_decoration),
|
static_cast<int>(cfg::SCREENRESIZE->window_decoration),
|
||||||
style);
|
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
|
// SetWindowPos must be called after SetWindowLong if the frame style changed
|
||||||
// this will be done in WM_STYLECHANGED handler
|
// 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) {
|
void graphics_update_z_order(HWND hWnd, bool always_on_top) {
|
||||||
|
|||||||
@@ -9,6 +9,8 @@
|
|||||||
|
|
||||||
#include "avs/game.h"
|
#include "avs/game.h"
|
||||||
#include "games/iidx/iidx.h"
|
#include "games/iidx/iidx.h"
|
||||||
|
#include "games/gitadora/gitadora.h"
|
||||||
|
#include "games/sdvx/sdvx.h"
|
||||||
#include "util/detour.h"
|
#include "util/detour.h"
|
||||||
#include "util/logging.h"
|
#include "util/logging.h"
|
||||||
#include "util/utils.h"
|
#include "util/utils.h"
|
||||||
@@ -24,6 +26,7 @@ static decltype(GetLocaleInfoEx) *GetLocaleInfoEx_orig = nullptr;
|
|||||||
#ifdef SPICE64
|
#ifdef SPICE64
|
||||||
static decltype(GetSystemDefaultLCID) *GetSystemDefaultLCID_orig = nullptr;
|
static decltype(GetSystemDefaultLCID) *GetSystemDefaultLCID_orig = nullptr;
|
||||||
static decltype(IsDBCSLeadByte) *IsDBCSLeadByte_orig = nullptr;
|
static decltype(IsDBCSLeadByte) *IsDBCSLeadByte_orig = nullptr;
|
||||||
|
static decltype(WideCharToMultiByte) *WideCharToMultiByte_orig = nullptr;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
static NTSTATUS NTAPI RtlMultiByteToUnicodeN_hook(
|
static NTSTATUS NTAPI RtlMultiByteToUnicodeN_hook(
|
||||||
@@ -173,6 +176,41 @@ static BOOL WINAPI IsDBCSLeadByte_hook (
|
|||||||
return IsDBCSLeadByteEx(CODEPAGE_SHIFT_JIS, TestChar);
|
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
|
#endif
|
||||||
|
|
||||||
void hooks::lang::early_init() {
|
void hooks::lang::early_init() {
|
||||||
@@ -183,8 +221,7 @@ void hooks::lang::early_init() {
|
|||||||
detour::trampoline_try("kernel32.dll", "GetOEMCP", GetOEMCP_hook, &GetOEMCP_orig);
|
detour::trampoline_try("kernel32.dll", "GetOEMCP", GetOEMCP_hook, &GetOEMCP_orig);
|
||||||
|
|
||||||
#ifdef SPICE64 // SDVX5+ specific code
|
#ifdef SPICE64 // SDVX5+ specific code
|
||||||
bool isValkyrieCabinetMode = avs::game::SPEC[0] == 'G' || avs::game::SPEC[0] == 'H';
|
if (games::sdvx::is_valkyrie_model()) {
|
||||||
if (avs::game::is_model("KFC") && isValkyrieCabinetMode) {
|
|
||||||
log_info("hooks::lang", "hooking GetSystemDefaultLCID");
|
log_info("hooks::lang", "hooking GetSystemDefaultLCID");
|
||||||
detour::trampoline_try(
|
detour::trampoline_try(
|
||||||
"kernel32.dll",
|
"kernel32.dll",
|
||||||
@@ -213,6 +250,15 @@ void hooks::lang::early_init() {
|
|||||||
IsDBCSLeadByte_hook,
|
IsDBCSLeadByte_hook,
|
||||||
&IsDBCSLeadByte_orig);
|
&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
|
#endif
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,6 +10,7 @@
|
|||||||
#include "api/modules/capture.h"
|
#include "api/modules/capture.h"
|
||||||
#include "acio/acio.h"
|
#include "acio/acio.h"
|
||||||
#include "acio/icca/icca.h"
|
#include "acio/icca/icca.h"
|
||||||
|
#include "acio/mdxf/mdxf.h"
|
||||||
#include "api/controller.h"
|
#include "api/controller.h"
|
||||||
#include "avs/automap.h"
|
#include "avs/automap.h"
|
||||||
#include "avs/core.h"
|
#include "avs/core.h"
|
||||||
@@ -87,6 +88,7 @@
|
|||||||
#include "misc/device.h"
|
#include "misc/device.h"
|
||||||
#include "misc/eamuse.h"
|
#include "misc/eamuse.h"
|
||||||
#include "misc/extdev.h"
|
#include "misc/extdev.h"
|
||||||
|
#include "misc/ami2000.h"
|
||||||
#include "misc/sciunit.h"
|
#include "misc/sciunit.h"
|
||||||
#include "misc/sde.h"
|
#include "misc/sde.h"
|
||||||
#include "misc/wintouchemu.h"
|
#include "misc/wintouchemu.h"
|
||||||
@@ -194,6 +196,7 @@ int main_implementation(int argc, char *argv[]) {
|
|||||||
bool attach_icca = false;
|
bool attach_icca = false;
|
||||||
bool attach_device = false;
|
bool attach_device = false;
|
||||||
bool attach_extdev = false;
|
bool attach_extdev = false;
|
||||||
|
bool attach_ami2000 = false;
|
||||||
bool attach_sciunit = false;
|
bool attach_sciunit = false;
|
||||||
bool attach_cpusbxpkm_printer = false;
|
bool attach_cpusbxpkm_printer = false;
|
||||||
bool attach_iidx = false;
|
bool attach_iidx = false;
|
||||||
@@ -343,8 +346,13 @@ int main_implementation(int argc, char *argv[]) {
|
|||||||
GRAPHICS_FORCE_SINGLE_ADAPTER = true;
|
GRAPHICS_FORCE_SINGLE_ADAPTER = true;
|
||||||
GRAPHICS_PREVENT_SECONDARY_WINDOW = 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();
|
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()) {
|
if (options[launcher::Options::CaptureCursor].value_bool()) {
|
||||||
GRAPHICS_CAPTURE_CURSOR = true;
|
GRAPHICS_CAPTURE_CURSOR = true;
|
||||||
@@ -422,8 +430,8 @@ int main_implementation(int argc, char *argv[]) {
|
|||||||
if (options[launcher::Options::EnableEXTDEVModule].value_bool()) {
|
if (options[launcher::Options::EnableEXTDEVModule].value_bool()) {
|
||||||
attach_extdev = true;
|
attach_extdev = true;
|
||||||
}
|
}
|
||||||
if (options[launcher::Options::EnableSCIUNITModule].value_bool()) {
|
if (options[launcher::Options::EnableAMI2000Module].value_bool()) {
|
||||||
attach_sciunit = true;
|
attach_ami2000 = true;
|
||||||
}
|
}
|
||||||
if (options[launcher::Options::EnableDevicePassthrough].value_bool()) {
|
if (options[launcher::Options::EnableDevicePassthrough].value_bool()) {
|
||||||
hooks::device::ENABLE = false;
|
hooks::device::ENABLE = false;
|
||||||
@@ -522,6 +530,9 @@ int main_implementation(int argc, char *argv[]) {
|
|||||||
overlay::windows::IIDX_SEGMENT_LOCATION =
|
overlay::windows::IIDX_SEGMENT_LOCATION =
|
||||||
options[launcher::Options::spice2x_IIDXLEDPos].value_text();
|
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()) {
|
if (options[launcher::Options::spice2x_IIDXNoESpec].value_bool()) {
|
||||||
games::iidx::DISABLE_ESPEC_IO = true;
|
games::iidx::DISABLE_ESPEC_IO = true;
|
||||||
}
|
}
|
||||||
@@ -743,6 +754,9 @@ int main_implementation(int argc, char *argv[]) {
|
|||||||
if (options[launcher::Options::DisableAvsVfsDriveMountRedirection].is_active()) {
|
if (options[launcher::Options::DisableAvsVfsDriveMountRedirection].is_active()) {
|
||||||
hooks::avs::config::DISABLE_VFS_DRIVE_REDIRECTION = true;
|
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()) {
|
if (options[launcher::Options::ScreenResizeConfigPath].is_active()) {
|
||||||
cfg::SCREEN_RESIZE_CFG_PATH_OVERRIDE =
|
cfg::SCREEN_RESIZE_CFG_PATH_OVERRIDE =
|
||||||
options[launcher::Options::ScreenResizeConfigPath].value_text();
|
options[launcher::Options::ScreenResizeConfigPath].value_text();
|
||||||
@@ -945,6 +959,12 @@ int main_implementation(int argc, char *argv[]) {
|
|||||||
if (options[launcher::Options::DisableOverlay].value_bool()) {
|
if (options[launcher::Options::DisableOverlay].value_bool()) {
|
||||||
overlay::ENABLED = false;
|
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()) {
|
if (options[launcher::Options::DisableAudioHooks].value_bool()) {
|
||||||
hooks::audio::ENABLED = false;
|
hooks::audio::ENABLED = false;
|
||||||
deferredlogs::defer_error_messages({
|
deferredlogs::defer_error_messages({
|
||||||
@@ -1149,6 +1169,13 @@ int main_implementation(int argc, char *argv[]) {
|
|||||||
rawinput::RAWINPUT_REQUIRE_FOCUS = false;
|
rawinput::RAWINPUT_REQUIRE_FOCUS = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (options[launcher::Options::DDRP4IOBufferMode].is_active()) {
|
||||||
|
if (options[launcher::Options::DDRP4IOBufferMode].value_text() == "thread") {
|
||||||
|
acio::MDXF_BUFFER_FILL_MODE = acio::MDXFBufferFillMode::THREAD_MODE;
|
||||||
|
} else if (options[launcher::Options::DDRP4IOBufferMode].value_text() == "backfill") {
|
||||||
|
acio::MDXF_BUFFER_FILL_MODE = acio::MDXFBufferFillMode::BACKFILL_MODE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (options[launcher::Options::MidiAlgoVer].is_active()) {
|
if (options[launcher::Options::MidiAlgoVer].is_active()) {
|
||||||
if (options[launcher::Options::MidiAlgoVer].value_text() == "legacy") {
|
if (options[launcher::Options::MidiAlgoVer].value_text() == "legacy") {
|
||||||
@@ -1621,6 +1648,7 @@ int main_implementation(int argc, char *argv[]) {
|
|||||||
attach_io = true;
|
attach_io = true;
|
||||||
attach_device = true;
|
attach_device = true;
|
||||||
attach_extdev = true;
|
attach_extdev = true;
|
||||||
|
attach_ami2000 = true;
|
||||||
attach_gitadora = true;
|
attach_gitadora = true;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -2183,6 +2211,11 @@ int main_implementation(int argc, char *argv[]) {
|
|||||||
extdev_attach();
|
extdev_attach();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ami 2000 card reader attach
|
||||||
|
if (attach_io || attach_ami2000) {
|
||||||
|
ami2000_attach();
|
||||||
|
}
|
||||||
|
|
||||||
// sci unit attach
|
// sci unit attach
|
||||||
if (attach_io || attach_sciunit) {
|
if (attach_io || attach_sciunit) {
|
||||||
sciunit_attach();
|
sciunit_attach();
|
||||||
|
|||||||
@@ -12,9 +12,11 @@
|
|||||||
#include "launcher/launcher.h"
|
#include "launcher/launcher.h"
|
||||||
#include "util/utils.h"
|
#include "util/utils.h"
|
||||||
|
|
||||||
#define FOREGROUND_GREY (8)
|
#define FOREGROUND_GREY (8)
|
||||||
#define FOREGROUND_WHITE (FOREGROUND_RED | FOREGROUND_BLUE | FOREGROUND_GREEN)
|
#define FOREGROUND_WHITE (FOREGROUND_RED | FOREGROUND_BLUE | FOREGROUND_GREEN)
|
||||||
#define FOREGROUND_YELLOW (FOREGROUND_RED | 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 {
|
namespace logger {
|
||||||
|
|
||||||
@@ -103,9 +105,6 @@ namespace logger {
|
|||||||
last_style = content.second;
|
last_style = content.second;
|
||||||
|
|
||||||
switch (content.second) {
|
switch (content.second) {
|
||||||
case Style::DEFAULT:
|
|
||||||
set_console_color(hTerminal, FOREGROUND_WHITE);
|
|
||||||
break;
|
|
||||||
case Style::GREY:
|
case Style::GREY:
|
||||||
set_console_color(hTerminal, FOREGROUND_GREY);
|
set_console_color(hTerminal, FOREGROUND_GREY);
|
||||||
break;
|
break;
|
||||||
@@ -115,6 +114,13 @@ namespace logger {
|
|||||||
case Style::RED:
|
case Style::RED:
|
||||||
set_console_color(hTerminal, FOREGROUND_RED);
|
set_console_color(hTerminal, FOREGROUND_RED);
|
||||||
break;
|
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,
|
DEFAULT = 0,
|
||||||
GREY = 1,
|
GREY = 1,
|
||||||
YELLOW = 2,
|
YELLOW = 2,
|
||||||
RED = 3
|
RED = 3,
|
||||||
|
SPECIAL = 4,
|
||||||
};
|
};
|
||||||
|
|
||||||
void start();
|
void start();
|
||||||
|
|||||||
@@ -207,7 +207,7 @@ static const std::vector<OptionDefinition> OPTION_DEFINITIONS = {
|
|||||||
.name = "monitor",
|
.name = "monitor",
|
||||||
.desc = "Sets the display that the game will be opened in, for multiple monitors.\n\n"
|
.desc = "Sets the display that the game will be opened in, for multiple monitors.\n\n"
|
||||||
"0 is the primary monitor, 1 is the second monitor, and so on.\n\n"
|
"0 is the primary monitor, 1 is the second monitor, and so on.\n\n"
|
||||||
"Not all games will respect this. Does not work at all with multi-monitor games like TDJ/UFC. "
|
"Not all games will respect this. Not recommended for multi-monitor games like Lightning Model / Valkyrie Model modes. "
|
||||||
"Disable Full Screen Optimizations for best results",
|
"Disable Full Screen Optimizations for best results",
|
||||||
.type = OptionType::Integer,
|
.type = OptionType::Integer,
|
||||||
.category = "Graphics (Full Screen)",
|
.category = "Graphics (Full Screen)",
|
||||||
@@ -383,12 +383,24 @@ static const std::vector<OptionDefinition> OPTION_DEFINITIONS = {
|
|||||||
.disabled = true,
|
.disabled = true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
// DisableOverlay
|
||||||
.title = "Disable Spice Overlay",
|
.title = "Disable Spice Overlay",
|
||||||
.name = "overlaydisable",
|
.name = "overlaydisable",
|
||||||
.desc = "Disables the in-game overlay",
|
.desc = "Disables the in-game overlay",
|
||||||
.type = OptionType::Bool,
|
.type = OptionType::Bool,
|
||||||
.category = "Overlay",
|
.category = "Overlay",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
// OverlayScaling
|
||||||
|
.title = "Spice Overlay UI Scale %",
|
||||||
|
.name = "overlayscale",
|
||||||
|
.desc = "Forces UI scaling for the overlay, "
|
||||||
|
"things can look off since the UI was written for 100% scaling. "
|
||||||
|
"Enter value in percentage, between 10-400, inclusive",
|
||||||
|
.type = OptionType::Integer,
|
||||||
|
.setting_name = "200",
|
||||||
|
.category = "Overlay",
|
||||||
|
},
|
||||||
{
|
{
|
||||||
// spice2x_FpsAutoShow
|
// spice2x_FpsAutoShow
|
||||||
.title = "Auto Show FPS/Clock",
|
.title = "Auto Show FPS/Clock",
|
||||||
@@ -590,9 +602,9 @@ static const std::vector<OptionDefinition> OPTION_DEFINITIONS = {
|
|||||||
.category = "Game Options (Advanced)",
|
.category = "Game Options (Advanced)",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
.title = "IIDX TDJ Mode",
|
.title = "IIDX TDJ Mode (Lightning Model)",
|
||||||
.name = "iidxtdj",
|
.name = "iidxtdj",
|
||||||
.desc = "Enables TDJ cabinet mode. Ensure you also set -iidxsounddevice to desired option",
|
.desc = "Enables TDJ mode (Lightning Model cabinet).",
|
||||||
.type = OptionType::Bool,
|
.type = OptionType::Bool,
|
||||||
.game_name = "Beatmania IIDX",
|
.game_name = "Beatmania IIDX",
|
||||||
.category = "Game Options",
|
.category = "Game Options",
|
||||||
@@ -609,6 +621,23 @@ static const std::vector<OptionDefinition> OPTION_DEFINITIONS = {
|
|||||||
.game_name = "Beatmania IIDX",
|
.game_name = "Beatmania IIDX",
|
||||||
.category = "Game Options (Advanced)",
|
.category = "Game Options (Advanced)",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
// IIDXDigitalTTSocd
|
||||||
|
.title = "IIDX Digital TT SOCD Cleaner",
|
||||||
|
.name = "iidxsocd",
|
||||||
|
.desc = "SOCD for turntables when using button input; what happens when both directions are pressed.\n\n"
|
||||||
|
"last: most recently pressed direction takes priority\n\n"
|
||||||
|
"first: first pressed direction takes priority\n\n"
|
||||||
|
"neutral (default): TT does not move when both directions are pressed, recommended to avoid excessive POORs",
|
||||||
|
.type = OptionType::Enum,
|
||||||
|
.game_name = "Beatmania IIDX",
|
||||||
|
.category = "Game Options (Advanced)",
|
||||||
|
.elements = {
|
||||||
|
{"last", ""},
|
||||||
|
{"first", ""},
|
||||||
|
{"neutral", ""},
|
||||||
|
},
|
||||||
|
},
|
||||||
{
|
{
|
||||||
// spice2x_IIDXLDJForce720p
|
// spice2x_IIDXLDJForce720p
|
||||||
.title = "IIDX LDJ Force 720p (HD)",
|
.title = "IIDX LDJ Force 720p (HD)",
|
||||||
@@ -679,6 +708,16 @@ static const std::vector<OptionDefinition> OPTION_DEFINITIONS = {
|
|||||||
{"bottomright", ""},
|
{"bottomright", ""},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
// IIDXLEDBorderless
|
||||||
|
.title = "IIDX LED Ticker Borderless",
|
||||||
|
.name = "iidxledborderless",
|
||||||
|
.aliases= "iidxledborderless",
|
||||||
|
.desc = "Make LED ticker overlay window borderless (remove window decoration)",
|
||||||
|
.type = OptionType::Bool,
|
||||||
|
.game_name = "Beatmania IIDX",
|
||||||
|
.category = "Overlay",
|
||||||
|
},
|
||||||
{
|
{
|
||||||
.title = "Force Load Sound Voltex Module",
|
.title = "Force Load Sound Voltex Module",
|
||||||
.name = "sdvx",
|
.name = "sdvx",
|
||||||
@@ -782,6 +821,23 @@ static const std::vector<OptionDefinition> OPTION_DEFINITIONS = {
|
|||||||
.game_name = "Sound Voltex",
|
.game_name = "Sound Voltex",
|
||||||
.category = "Game Options (Advanced)",
|
.category = "Game Options (Advanced)",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
// SDVXDigitalKnobSocd
|
||||||
|
.title = "SDVX Digital Knob SOCD Cleaner",
|
||||||
|
.name = "sdvxsocd",
|
||||||
|
.desc = "SOCD for knobs when using button input; what happens when both directions are pressed.\n\n"
|
||||||
|
"last (default): most recently pressed direction takes priority, recommended to deal with slams\n\n"
|
||||||
|
"first: first pressed direction takes priority\n\n"
|
||||||
|
"neutral: knob does not move when both directions are pressed",
|
||||||
|
.type = OptionType::Enum,
|
||||||
|
.game_name = "Sound Voltex",
|
||||||
|
.category = "Game Options (Advanced)",
|
||||||
|
.elements = {
|
||||||
|
{"last", ""},
|
||||||
|
{"first", ""},
|
||||||
|
{"neutral", ""},
|
||||||
|
},
|
||||||
|
},
|
||||||
{
|
{
|
||||||
// spice2x_SDVXAsioDriver
|
// spice2x_SDVXAsioDriver
|
||||||
.title = "SDVX ASIO driver",
|
.title = "SDVX ASIO driver",
|
||||||
@@ -1311,6 +1367,13 @@ static const std::vector<OptionDefinition> OPTION_DEFINITIONS = {
|
|||||||
.type = OptionType::Bool,
|
.type = OptionType::Bool,
|
||||||
.category = "I/O Modules",
|
.category = "I/O Modules",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
.title = "Enable AMI2000 Module",
|
||||||
|
.name = "ami2000",
|
||||||
|
.desc = "Manually enable AMI2000 emulation",
|
||||||
|
.type = OptionType::Bool,
|
||||||
|
.category = "I/O Modules",
|
||||||
|
},
|
||||||
{
|
{
|
||||||
.title = "Enable SCIUNIT Module",
|
.title = "Enable SCIUNIT Module",
|
||||||
.name = "sciunit",
|
.name = "sciunit",
|
||||||
@@ -1756,6 +1819,14 @@ static const std::vector<OptionDefinition> OPTION_DEFINITIONS = {
|
|||||||
.type = OptionType::Bool,
|
.type = OptionType::Bool,
|
||||||
.category = "Development",
|
.category = "Development",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
// DisableAvsCache
|
||||||
|
.title = "Disable AVS Cache",
|
||||||
|
.name = "avscachedisable",
|
||||||
|
.desc = "Disable optimization used for some games to cache data file info (only for IIDX32+ for now)",
|
||||||
|
.type = OptionType::Bool,
|
||||||
|
.category = "Development",
|
||||||
|
},
|
||||||
{
|
{
|
||||||
.title = "Output PEB",
|
.title = "Output PEB",
|
||||||
.name = "pebprint",
|
.name = "pebprint",
|
||||||
@@ -2291,6 +2362,26 @@ static const std::vector<OptionDefinition> OPTION_DEFINITIONS = {
|
|||||||
.setting_name = "20",
|
.setting_name = "20",
|
||||||
.category = "I/O Options",
|
.category = "I/O Options",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
// DDRP4IOBufferMode
|
||||||
|
.title = "DDR P4IO Buffer Algorithm",
|
||||||
|
.name = "ddrp4iobuffer",
|
||||||
|
.desc =
|
||||||
|
"Remember to restart after changing this value.\n\n"
|
||||||
|
"Sets the algorithm used to populate entries to the buffer of controller polls read by the game.\n\n"
|
||||||
|
"auto (default): thread mode if <120Hz, backfill if >=120Hz\n\n"
|
||||||
|
"thread: starts a thread to periodically insert polls into the buffer\n\n"
|
||||||
|
"backfill: fills the buffer on each frame with last known state info at short regular intervals up to the current time, then writes the current state.\n\n"
|
||||||
|
"Only has an effect when emulating P4IO (arkmdxp4.dll)",
|
||||||
|
.type = OptionType::Enum,
|
||||||
|
.game_name = "Dance Dance Revolution",
|
||||||
|
.category = "Game Options (Advanced)",
|
||||||
|
.elements = {
|
||||||
|
{"auto", ""},
|
||||||
|
{"thread", ""},
|
||||||
|
{"backfill", ""},
|
||||||
|
},
|
||||||
|
},
|
||||||
{
|
{
|
||||||
// InputRequiresFocus
|
// InputRequiresFocus
|
||||||
.title = "Input Requires Focus",
|
.title = "Input Requires Focus",
|
||||||
|
|||||||
@@ -47,6 +47,7 @@ namespace launcher {
|
|||||||
SOFTID,
|
SOFTID,
|
||||||
VREnable,
|
VREnable,
|
||||||
DisableOverlay,
|
DisableOverlay,
|
||||||
|
OverlayScaling,
|
||||||
spice2x_FpsAutoShow,
|
spice2x_FpsAutoShow,
|
||||||
spice2x_FpsOpposite,
|
spice2x_FpsOpposite,
|
||||||
spice2x_SubScreenAutoShow,
|
spice2x_SubScreenAutoShow,
|
||||||
@@ -66,11 +67,13 @@ namespace launcher {
|
|||||||
IIDXBIO2FW,
|
IIDXBIO2FW,
|
||||||
IIDXTDJMode,
|
IIDXTDJMode,
|
||||||
spice2x_IIDXDigitalTTSensitivity,
|
spice2x_IIDXDigitalTTSensitivity,
|
||||||
|
IIDXDigitalTTSocd,
|
||||||
spice2x_IIDXLDJForce720p,
|
spice2x_IIDXLDJForce720p,
|
||||||
spice2x_IIDXTDJSubSize,
|
spice2x_IIDXTDJSubSize,
|
||||||
spice2x_IIDXLEDFontSize,
|
spice2x_IIDXLEDFontSize,
|
||||||
spice2x_IIDXLEDColor,
|
spice2x_IIDXLEDColor,
|
||||||
spice2x_IIDXLEDPos,
|
spice2x_IIDXLEDPos,
|
||||||
|
IIDXLEDBorderless,
|
||||||
LoadSoundVoltexModule,
|
LoadSoundVoltexModule,
|
||||||
SDVXForce720p,
|
SDVXForce720p,
|
||||||
SDVXPrinterEmulation,
|
SDVXPrinterEmulation,
|
||||||
@@ -82,6 +85,7 @@ namespace launcher {
|
|||||||
SDVXDisableCameras,
|
SDVXDisableCameras,
|
||||||
SDVXNativeTouch,
|
SDVXNativeTouch,
|
||||||
spice2x_SDVXDigitalKnobSensitivity,
|
spice2x_SDVXDigitalKnobSensitivity,
|
||||||
|
SDVXDigitalKnobSocd,
|
||||||
spice2x_SDVXAsioDriver,
|
spice2x_SDVXAsioDriver,
|
||||||
spice2x_SDVXSubPos,
|
spice2x_SDVXSubPos,
|
||||||
spice2x_SDVXSubRedraw,
|
spice2x_SDVXSubRedraw,
|
||||||
@@ -146,6 +150,7 @@ namespace launcher {
|
|||||||
EnableICCAModule,
|
EnableICCAModule,
|
||||||
EnableDEVICEModule,
|
EnableDEVICEModule,
|
||||||
EnableEXTDEVModule,
|
EnableEXTDEVModule,
|
||||||
|
EnableAMI2000Module,
|
||||||
EnableSCIUNITModule,
|
EnableSCIUNITModule,
|
||||||
EnableDevicePassthrough,
|
EnableDevicePassthrough,
|
||||||
ForceWinTouch,
|
ForceWinTouch,
|
||||||
@@ -195,6 +200,7 @@ namespace launcher {
|
|||||||
DisableSignalHandling,
|
DisableSignalHandling,
|
||||||
DisableDebugHooks,
|
DisableDebugHooks,
|
||||||
DisableAvsVfsDriveMountRedirection,
|
DisableAvsVfsDriveMountRedirection,
|
||||||
|
DisableAvsCache,
|
||||||
OutputPEB,
|
OutputPEB,
|
||||||
DumpSystemInfo,
|
DumpSystemInfo,
|
||||||
QKSArgs,
|
QKSArgs,
|
||||||
@@ -241,6 +247,7 @@ namespace launcher {
|
|||||||
IIDXRecDisable,
|
IIDXRecDisable,
|
||||||
MidiAlgoVer,
|
MidiAlgoVer,
|
||||||
MidiNoteSustain,
|
MidiNoteSustain,
|
||||||
|
DDRP4IOBufferMode,
|
||||||
InputRequiresFocus,
|
InputRequiresFocus,
|
||||||
NostalgiaPoke,
|
NostalgiaPoke,
|
||||||
ForceBackBufferCount,
|
ForceBackBufferCount,
|
||||||
|
|||||||
@@ -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();
|
||||||
@@ -0,0 +1,91 @@
|
|||||||
|
// enable touch functions - set version to windows 7
|
||||||
|
// mingw otherwise doesn't load touch stuff
|
||||||
|
#define _WIN32_WINNT 0x0601
|
||||||
|
|
||||||
|
#include "wintouchemu.h"
|
||||||
|
|
||||||
|
#include "util/detour.h"
|
||||||
|
#include "util/logging.h"
|
||||||
|
|
||||||
|
#define TOUCH_SIMULATE_FAT_FINGERS 0
|
||||||
|
#define TOUCH_DEBUG_VERBOSE 0
|
||||||
|
|
||||||
|
#if TOUCH_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 nativetouchhook {
|
||||||
|
|
||||||
|
static decltype(GetTouchInputInfo) *GetTouchInputInfo_orig = nullptr;
|
||||||
|
|
||||||
|
static void strip_contact_size(PTOUCHINPUT point) {
|
||||||
|
|
||||||
|
#if TOUCH_SIMULATE_FAT_FINGERS
|
||||||
|
point->dwMask |= 0x004;
|
||||||
|
point->cxContact = 80 * 100;
|
||||||
|
point->cyContact = 60 * 100;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// most monitors do not set TOUCHEVENTFMASK_CONTACTAREA, but for
|
||||||
|
// monitors that do set it, IIDX can get very confused (SDVX is not
|
||||||
|
// affected)
|
||||||
|
//
|
||||||
|
// while the test menu and the touch "glow" seem to work properly,
|
||||||
|
// interacting with subscreen menu items or entering PIN becomes
|
||||||
|
// very unpredictable
|
||||||
|
//
|
||||||
|
// to fix this, simply remove the contact area width and height
|
||||||
|
//
|
||||||
|
// note: test menu > I/O > touch test gives 5 numbers:
|
||||||
|
// n: x, y, w, h
|
||||||
|
// where
|
||||||
|
// n is the nth touch input since boot
|
||||||
|
// x, y are coordinates (center of finger)
|
||||||
|
// w, h are contact width and height
|
||||||
|
//
|
||||||
|
// when TOUCHEVENTFMASK_CONTACTAREA is not set, w/h will
|
||||||
|
// automatically be seen as 1x1, which works perfectly fine
|
||||||
|
|
||||||
|
log_debug(
|
||||||
|
"touch::native",
|
||||||
|
"[{}, {}] dwMask = 0x{:x}, cxContact = {}, cyContact = {}",
|
||||||
|
point->x / 100,
|
||||||
|
point->y / 100,
|
||||||
|
point->dwMask,
|
||||||
|
point->cxContact,
|
||||||
|
point->cyContact);
|
||||||
|
|
||||||
|
point->dwMask &= ~(0x004ul); // clear TOUCHEVENTFMASK_CONTACTAREA
|
||||||
|
point->cxContact = 0;
|
||||||
|
point->cyContact = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static BOOL WINAPI GetTouchInputInfoHook(
|
||||||
|
HTOUCHINPUT hTouchInput, UINT cInputs, PTOUCHINPUT pInputs, int cbSize) {
|
||||||
|
|
||||||
|
// call the original fist
|
||||||
|
const auto result = GetTouchInputInfo_orig(hTouchInput, cInputs, pInputs, cbSize);
|
||||||
|
if (result == 0) {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (size_t i = 0; i < cInputs; i++) {
|
||||||
|
PTOUCHINPUT point = &pInputs[i];
|
||||||
|
strip_contact_size(point);
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
void hook(HMODULE module) {
|
||||||
|
GetTouchInputInfo_orig = detour::iat_try("GetTouchInputInfo", GetTouchInputInfoHook, module);
|
||||||
|
if (GetTouchInputInfo_orig != nullptr) {
|
||||||
|
log_misc("touch::native", "GetTouchInputInfo hooked");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
#include <windows.h>
|
||||||
|
|
||||||
|
namespace nativetouchhook {
|
||||||
|
void hook(HMODULE module);
|
||||||
|
}
|
||||||
|
|
||||||
@@ -3,6 +3,7 @@
|
|||||||
#include "avs/game.h"
|
#include "avs/game.h"
|
||||||
#include "cfg/configurator.h"
|
#include "cfg/configurator.h"
|
||||||
#include "games/io.h"
|
#include "games/io.h"
|
||||||
|
#include "games/gitadora/gitadora.h"
|
||||||
#include "games/iidx/iidx.h"
|
#include "games/iidx/iidx.h"
|
||||||
#include "hooks/graphics/graphics.h"
|
#include "hooks/graphics/graphics.h"
|
||||||
#include "misc/eamuse.h"
|
#include "misc/eamuse.h"
|
||||||
@@ -51,16 +52,26 @@ namespace overlay {
|
|||||||
bool AUTO_SHOW_IOPANEL = false;
|
bool AUTO_SHOW_IOPANEL = false;
|
||||||
bool AUTO_SHOW_KEYPAD_P1 = false;
|
bool AUTO_SHOW_KEYPAD_P1 = false;
|
||||||
bool AUTO_SHOW_KEYPAD_P2 = false;
|
bool AUTO_SHOW_KEYPAD_P2 = false;
|
||||||
|
|
||||||
bool USE_WM_CHAR_FOR_IMGUI_CHAR_INPUT = false;
|
bool USE_WM_CHAR_FOR_IMGUI_CHAR_INPUT = false;
|
||||||
|
|
||||||
bool FPS_SHOULD_FLIP = false;
|
bool FPS_SHOULD_FLIP = false;
|
||||||
|
std::optional<uint32_t> UI_SCALE_PERCENT;
|
||||||
|
|
||||||
// global
|
// global
|
||||||
std::mutex OVERLAY_MUTEX;
|
std::mutex OVERLAY_MUTEX;
|
||||||
std::unique_ptr<overlay::SpiceOverlay> OVERLAY = nullptr;
|
std::unique_ptr<overlay::SpiceOverlay> OVERLAY = nullptr;
|
||||||
ImFont* DSEG_FONT = nullptr;
|
ImFont* DSEG_FONT = nullptr;
|
||||||
bool SHOW_DEBUG_LOG_WINDOW = false;
|
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) {
|
static void *ImGui_Alloc(size_t sz, void *user_data) {
|
||||||
@@ -170,6 +181,12 @@ void overlay::SpiceOverlay::init() {
|
|||||||
style.WindowRounding = 0;
|
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:
|
// red theme based on:
|
||||||
// https://github.com/ocornut/imgui/issues/707#issuecomment-760220280
|
// https://github.com/ocornut/imgui/issues/707#issuecomment-760220280
|
||||||
// r, g, b, a
|
// r, g, b, a
|
||||||
@@ -245,7 +262,6 @@ void overlay::SpiceOverlay::init() {
|
|||||||
|
|
||||||
if (!cfg::CONFIGURATOR_STANDALONE) {
|
if (!cfg::CONFIGURATOR_STANDALONE) {
|
||||||
io.ConfigFlags |= ImGuiConfigFlags_DockingEnable;
|
io.ConfigFlags |= ImGuiConfigFlags_DockingEnable;
|
||||||
io.ConfigFlags |= ImGuiConfigFlags_ViewportsEnable;
|
|
||||||
}
|
}
|
||||||
if (is_touch_available("SpiceOverlay::init")) {
|
if (is_touch_available("SpiceOverlay::init")) {
|
||||||
io.ConfigFlags |= ImGuiConfigFlags_IsTouchScreen;
|
io.ConfigFlags |= ImGuiConfigFlags_IsTouchScreen;
|
||||||
@@ -366,7 +382,8 @@ void overlay::SpiceOverlay::init() {
|
|||||||
window_iopanel = new overlay::windows::IIDXIOPanel(this);
|
window_iopanel = new overlay::windows::IIDXIOPanel(this);
|
||||||
} else if (avs::game::is_model("MDX")) {
|
} else if (avs::game::is_model("MDX")) {
|
||||||
window_iopanel = new overlay::windows::DDRIOPanel(this);
|
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);
|
window_iopanel = new overlay::windows::GitadoraIOPanel(this);
|
||||||
} else {
|
} else {
|
||||||
window_iopanel = new overlay::windows::IOPanel(this);
|
window_iopanel = new overlay::windows::IOPanel(this);
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
#include <memory>
|
#include <memory>
|
||||||
#include <mutex>
|
#include <mutex>
|
||||||
#include <functional>
|
#include <functional>
|
||||||
|
#include <optional>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
#include <windows.h>
|
#include <windows.h>
|
||||||
#include <d3d9.h>
|
#include <d3d9.h>
|
||||||
@@ -27,6 +28,18 @@ namespace overlay {
|
|||||||
extern bool USE_WM_CHAR_FOR_IMGUI_CHAR_INPUT;
|
extern bool USE_WM_CHAR_FOR_IMGUI_CHAR_INPUT;
|
||||||
extern bool FPS_SHOULD_FLIP;
|
extern bool FPS_SHOULD_FLIP;
|
||||||
extern bool SHOW_DEBUG_LOG_WINDOW;
|
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 {
|
class SpiceOverlay {
|
||||||
public:
|
public:
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ namespace overlay::windows {
|
|||||||
CameraControl::CameraControl(SpiceOverlay *overlay) : Window(overlay) {
|
CameraControl::CameraControl(SpiceOverlay *overlay) : Window(overlay) {
|
||||||
this->title = "IIDX Camera Control";
|
this->title = "IIDX Camera Control";
|
||||||
this->flags |= ImGuiWindowFlags_AlwaysAutoResize;
|
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;
|
this->toggle_button = games::OverlayButtons::ToggleCameraControl;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ namespace overlay::windows {
|
|||||||
|
|
||||||
CardManager::CardManager(SpiceOverlay *overlay) : Window(overlay) {
|
CardManager::CardManager(SpiceOverlay *overlay) : Window(overlay) {
|
||||||
this->title = "Card Manager";
|
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) {
|
if (cfg::CONFIGURATOR_STANDALONE) {
|
||||||
this->init_pos = ImVec2(40, 40);
|
this->init_pos = ImVec2(40, 40);
|
||||||
@@ -303,7 +303,7 @@ namespace overlay::windows {
|
|||||||
//
|
//
|
||||||
// setting ImGuiInputTextFlags_CallbackCharFilter and pressing escape doesn't cause below
|
// setting ImGuiInputTextFlags_CallbackCharFilter and pressing escape doesn't cause below
|
||||||
// to return true, making it necessary to provide a callback...
|
// 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)) {
|
if (ImGui::InputTextWithHint("", "Type here to search..", &this->search_filter)) {
|
||||||
this->current_card = nullptr;
|
this->current_card = nullptr;
|
||||||
this->search_filter_in_lower_case = strtolower(this->search_filter);
|
this->search_filter_in_lower_case = strtolower(this->search_filter);
|
||||||
|
|||||||
@@ -42,8 +42,8 @@ namespace overlay::windows {
|
|||||||
Config::Config(overlay::SpiceOverlay *overlay) : Window(overlay) {
|
Config::Config(overlay::SpiceOverlay *overlay) : Window(overlay) {
|
||||||
this->title = "Configuration";
|
this->title = "Configuration";
|
||||||
this->toggle_button = games::OverlayButtons::ToggleConfig;
|
this->toggle_button = games::OverlayButtons::ToggleConfig;
|
||||||
this->init_size = ImVec2(800, 600);
|
this->init_size = overlay::apply_scaling_to_vector(ImVec2(800, 600));
|
||||||
this->size_min = ImVec2(100, 200);
|
this->size_min = overlay::apply_scaling_to_vector(ImVec2(100, 200));
|
||||||
this->init_pos = ImVec2(0, 0);
|
this->init_pos = ImVec2(0, 0);
|
||||||
if (cfg::CONFIGURATOR_STANDALONE && cfg::CONFIGURATOR_TYPE == cfg::ConfigType::Config) {
|
if (cfg::CONFIGURATOR_STANDALONE && cfg::CONFIGURATOR_TYPE == cfg::ConfigType::Config) {
|
||||||
this->active = true;
|
this->active = true;
|
||||||
@@ -238,8 +238,8 @@ namespace overlay::windows {
|
|||||||
// tab selection
|
// tab selection
|
||||||
auto tab_selected_new = ConfigTab::CONFIG_TAB_INVALID;
|
auto tab_selected_new = ConfigTab::CONFIG_TAB_INVALID;
|
||||||
if (ImGui::BeginTabBar("Config Tabs", ImGuiTabBarFlags_NoCloseWithMiddleMouseButton)) {
|
if (ImGui::BeginTabBar("Config Tabs", ImGuiTabBarFlags_NoCloseWithMiddleMouseButton)) {
|
||||||
const int page_offset = cfg::CONFIGURATOR_STANDALONE ? 88 : 110;
|
const int page_offset = overlay::apply_scaling(cfg::CONFIGURATOR_STANDALONE ? 88 : 110);
|
||||||
const int page_offset2 = cfg::CONFIGURATOR_STANDALONE ? 65 : 87;
|
const int page_offset2 = overlay::apply_scaling(cfg::CONFIGURATOR_STANDALONE ? 65 : 87);
|
||||||
|
|
||||||
if (ImGui::BeginTabItem("Buttons")) {
|
if (ImGui::BeginTabItem("Buttons")) {
|
||||||
tab_selected_new = ConfigTab::CONFIG_TAB_BUTTONS;
|
tab_selected_new = ConfigTab::CONFIG_TAB_BUTTONS;
|
||||||
@@ -575,7 +575,7 @@ namespace overlay::windows {
|
|||||||
if (ImGui::BeginTable("ButtonsTable", 3, ImGuiTableFlags_Resizable | ImGuiTableFlags_RowBg)) {
|
if (ImGui::BeginTable("ButtonsTable", 3, ImGuiTableFlags_Resizable | ImGuiTableFlags_RowBg)) {
|
||||||
ImGui::TableSetupColumn("Name", ImGuiTableColumnFlags_WidthStretch);
|
ImGui::TableSetupColumn("Name", ImGuiTableColumnFlags_WidthStretch);
|
||||||
ImGui::TableSetupColumn("Binding", ImGuiTableColumnFlags_WidthStretch);
|
ImGui::TableSetupColumn("Binding", ImGuiTableColumnFlags_WidthStretch);
|
||||||
ImGui::TableSetupColumn("Actions", ImGuiTableColumnFlags_WidthFixed, 240);
|
ImGui::TableSetupColumn("Actions", ImGuiTableColumnFlags_WidthFixed, overlay::apply_scaling(240));
|
||||||
|
|
||||||
// check if empty
|
// check if empty
|
||||||
if (!buttons || buttons->empty()) {
|
if (!buttons || buttons->empty()) {
|
||||||
@@ -1644,7 +1644,7 @@ namespace overlay::windows {
|
|||||||
if (ImGui::BeginTable("AnalogsTable", 3, ImGuiTableFlags_Resizable | ImGuiTableFlags_RowBg)) {
|
if (ImGui::BeginTable("AnalogsTable", 3, ImGuiTableFlags_Resizable | ImGuiTableFlags_RowBg)) {
|
||||||
ImGui::TableSetupColumn("Name", ImGuiTableColumnFlags_WidthStretch);
|
ImGui::TableSetupColumn("Name", ImGuiTableColumnFlags_WidthStretch);
|
||||||
ImGui::TableSetupColumn("Binding", ImGuiTableColumnFlags_WidthStretch);
|
ImGui::TableSetupColumn("Binding", ImGuiTableColumnFlags_WidthStretch);
|
||||||
ImGui::TableSetupColumn("Actions", ImGuiTableColumnFlags_WidthFixed, 240);
|
ImGui::TableSetupColumn("Actions", ImGuiTableColumnFlags_WidthFixed, overlay::apply_scaling(240));
|
||||||
|
|
||||||
// check if empty
|
// check if empty
|
||||||
if (!analogs || analogs->empty()) {
|
if (!analogs || analogs->empty()) {
|
||||||
@@ -2086,7 +2086,7 @@ namespace overlay::windows {
|
|||||||
if (ImGui::BeginTable("LightsTable", 3, ImGuiTableFlags_Resizable | ImGuiTableFlags_RowBg)) {
|
if (ImGui::BeginTable("LightsTable", 3, ImGuiTableFlags_Resizable | ImGuiTableFlags_RowBg)) {
|
||||||
ImGui::TableSetupColumn("Name", ImGuiTableColumnFlags_WidthStretch);
|
ImGui::TableSetupColumn("Name", ImGuiTableColumnFlags_WidthStretch);
|
||||||
ImGui::TableSetupColumn("Binding", ImGuiTableColumnFlags_WidthStretch);
|
ImGui::TableSetupColumn("Binding", ImGuiTableColumnFlags_WidthStretch);
|
||||||
ImGui::TableSetupColumn("Actions", ImGuiTableColumnFlags_WidthFixed, 240);
|
ImGui::TableSetupColumn("Actions", ImGuiTableColumnFlags_WidthFixed, overlay::apply_scaling(240));
|
||||||
|
|
||||||
// check if empty
|
// check if empty
|
||||||
if (!lights || lights->empty()) {
|
if (!lights || lights->empty()) {
|
||||||
@@ -2643,8 +2643,14 @@ namespace overlay::windows {
|
|||||||
// tables must share the same ID to have synced column settings
|
// tables must share the same ID to have synced column settings
|
||||||
if (ImGui::BeginTable("OptionsTable", 3, ImGuiTableFlags_Resizable | ImGuiTableFlags_RowBg)) {
|
if (ImGui::BeginTable("OptionsTable", 3, ImGuiTableFlags_Resizable | ImGuiTableFlags_RowBg)) {
|
||||||
ImGui::TableSetupColumn("Option", ImGuiTableColumnFlags_WidthStretch);
|
ImGui::TableSetupColumn("Option", ImGuiTableColumnFlags_WidthStretch);
|
||||||
ImGui::TableSetupColumn("CMD Line Parameter", ImGuiTableColumnFlags_WidthFixed, 216);
|
ImGui::TableSetupColumn(
|
||||||
ImGui::TableSetupColumn("Setting", ImGuiTableColumnFlags_WidthFixed, 240);
|
"CMD Line Parameter",
|
||||||
|
ImGuiTableColumnFlags_WidthFixed,
|
||||||
|
overlay::apply_scaling(216));
|
||||||
|
ImGui::TableSetupColumn(
|
||||||
|
"Setting",
|
||||||
|
ImGuiTableColumnFlags_WidthFixed,
|
||||||
|
overlay::apply_scaling(240));
|
||||||
|
|
||||||
// iterate options
|
// iterate options
|
||||||
options_count = 0;
|
options_count = 0;
|
||||||
@@ -3056,7 +3062,11 @@ namespace overlay::windows {
|
|||||||
ImGui::PushStyleColor(ImGuiCol_FrameBg, ImVec4(0.34f, 0.14f, 0.14f, 0.54f));
|
ImGui::PushStyleColor(ImGuiCol_FrameBg, ImVec4(0.34f, 0.14f, 0.14f, 0.54f));
|
||||||
ImGui::PushStyleColor(ImGuiCol_FrameBgActive, ImVec4(0.34f, 0.14f, 0.14f, 0.54f));
|
ImGui::PushStyleColor(ImGuiCol_FrameBgActive, ImVec4(0.34f, 0.14f, 0.14f, 0.54f));
|
||||||
ImGui::PushStyleColor(ImGuiCol_FrameBgHovered, ImVec4(0.34f, 0.14f, 0.14f, 0.64f));
|
ImGui::PushStyleColor(ImGuiCol_FrameBgHovered, ImVec4(0.34f, 0.14f, 0.14f, 0.64f));
|
||||||
ImGui::PushItemWidth(MIN(700, MAX(100, ImGui::GetWindowSize().x - 400)));
|
ImGui::PushItemWidth(
|
||||||
|
MIN(overlay::apply_scaling(700),
|
||||||
|
MAX(overlay::apply_scaling(100),
|
||||||
|
ImGui::GetWindowSize().x - overlay::apply_scaling(400))));
|
||||||
|
|
||||||
ImGui::Combo("##game_selector", game_selected, games_names.data(), (int)games_list.size());
|
ImGui::Combo("##game_selector", game_selected, games_names.data(), (int)games_list.size());
|
||||||
ImGui::PopItemWidth();
|
ImGui::PopItemWidth();
|
||||||
ImGui::PopStyleColor(3);
|
ImGui::PopStyleColor(3);
|
||||||
@@ -3080,8 +3090,8 @@ namespace overlay::windows {
|
|||||||
// draw popups
|
// draw popups
|
||||||
{
|
{
|
||||||
const ImVec2 popup_size(
|
const ImVec2 popup_size(
|
||||||
std::min(ImGui::GetIO().DisplaySize.x * 0.9f, 800.f),
|
std::min(ImGui::GetIO().DisplaySize.x * 0.9f, overlay::apply_scaling(800.f)),
|
||||||
std::min(ImGui::GetIO().DisplaySize.y * 0.9f, 800.f));
|
std::min(ImGui::GetIO().DisplaySize.y * 0.9f, overlay::apply_scaling(800.f)));
|
||||||
|
|
||||||
const ImVec2 popup_pos(
|
const ImVec2 popup_pos(
|
||||||
ImGui::GetIO().DisplaySize.x / 2 - popup_size.x / 2,
|
ImGui::GetIO().DisplaySize.x / 2 - popup_size.x / 2,
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
#include "exitprompt.h"
|
#include "exitprompt.h"
|
||||||
|
#include "avs/game.h"
|
||||||
#include "misc/eamuse.h"
|
#include "misc/eamuse.h"
|
||||||
#include "util/logging.h"
|
#include "util/logging.h"
|
||||||
|
#include "games/iidx/iidx.h"
|
||||||
|
|
||||||
namespace overlay::windows {
|
namespace overlay::windows {
|
||||||
|
|
||||||
@@ -8,10 +10,10 @@ namespace overlay::windows {
|
|||||||
this->title = "spice2x";
|
this->title = "spice2x";
|
||||||
this->init_size = ImVec2(
|
this->init_size = ImVec2(
|
||||||
(ImGui::GetFontSize() * 14) + (ImGui::GetStyle().ItemSpacing.x * 2),
|
(ImGui::GetFontSize() * 14) + (ImGui::GetStyle().ItemSpacing.x * 2),
|
||||||
120);
|
overlay::apply_scaling(120));
|
||||||
this->init_pos = ImVec2(
|
this->init_pos = ImVec2(
|
||||||
ImGui::GetIO().DisplaySize.x / 2 - this->init_size.x / 2,
|
ImGui::GetIO().DisplaySize.x / 2 - this->init_size.x / 2,
|
||||||
10);
|
overlay::apply_scaling(10));
|
||||||
|
|
||||||
this->flags = ImGuiWindowFlags_NoResize
|
this->flags = ImGuiWindowFlags_NoResize
|
||||||
| ImGuiWindowFlags_NoCollapse
|
| ImGuiWindowFlags_NoCollapse
|
||||||
@@ -55,7 +57,21 @@ namespace overlay::windows {
|
|||||||
}
|
}
|
||||||
ImGui::Spacing();
|
ImGui::Spacing();
|
||||||
build_button(this->overlay->window_config, "Show Config", size, NextItem::NEW_LINE, false);
|
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";
|
||||||
|
}
|
||||||
|
|
||||||
|
build_button(this->overlay->window_sub, sub, size, NextItem::NEW_LINE, false);
|
||||||
|
|
||||||
ImGui::TextDisabled("Graphics");
|
ImGui::TextDisabled("Graphics");
|
||||||
build_button(this->overlay->window_camera, "Camera control", size, NextItem::NEW_LINE);
|
build_button(this->overlay->window_camera, "Camera control", size, NextItem::NEW_LINE);
|
||||||
|
|||||||
@@ -20,8 +20,11 @@ namespace overlay::windows {
|
|||||||
|
|
||||||
void FPS::calculate_initial_window() {
|
void FPS::calculate_initial_window() {
|
||||||
// width is 114x82 px with window decoration, 98x47 for the content
|
// width is 114x82 px with window decoration, 98x47 for the content
|
||||||
int pos_x = overlay::FPS_SHOULD_FLIP ? 8 : ImGui::GetIO().DisplaySize.x - 122;
|
int pos_x =
|
||||||
this->init_pos = ImVec2(pos_x, 8);
|
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() {
|
void FPS::build_content() {
|
||||||
|
|||||||
@@ -10,12 +10,13 @@ namespace overlay::windows {
|
|||||||
uint32_t IIDX_SEGMENT_FONT_SIZE = 64;
|
uint32_t IIDX_SEGMENT_FONT_SIZE = 64;
|
||||||
std::optional<uint32_t> IIDX_SEGMENT_FONT_COLOR = std::nullopt;
|
std::optional<uint32_t> IIDX_SEGMENT_FONT_COLOR = std::nullopt;
|
||||||
std::string IIDX_SEGMENT_LOCATION = "bottom";
|
std::string IIDX_SEGMENT_LOCATION = "bottom";
|
||||||
|
bool IIDX_SEGMENT_BORDERLESS = false;
|
||||||
|
|
||||||
static const size_t TICKER_SIZE = 9;
|
static const size_t TICKER_SIZE = 9;
|
||||||
static const ImVec4 DARK_GRAY(0.1f, 0.1f, 0.1f, 1.f);
|
static const ImVec4 DARK_GRAY(0.1f, 0.1f, 0.1f, 1.f);
|
||||||
static const ImVec4 RED(1.f, 0.f, 0.f, 1.f);
|
static const ImVec4 RED(1.f, 0.f, 0.f, 1.f);
|
||||||
static const int PADDING_Y = 8;
|
static const float PADDING_Y = 8.f;
|
||||||
static const int PADDING_X = 4;
|
static const float PADDING_X = 4.f;
|
||||||
|
|
||||||
static const std::map<char, std::pair<char, char>> CHARMAP = {\
|
static const std::map<char, std::pair<char, char>> CHARMAP = {\
|
||||||
// period - add a space afterwards (game sends 'm' for period)
|
// period - add a space afterwards (game sends 'm' for period)
|
||||||
@@ -31,16 +32,18 @@ namespace overlay::windows {
|
|||||||
log_fatal("iidx_seg", "DSEG_FONT is null");
|
log_fatal("iidx_seg", "DSEG_FONT is null");
|
||||||
}
|
}
|
||||||
|
|
||||||
this->title = "IIDX LED Segment Display";
|
this->title = "IIDX LDJ LED Ticker";
|
||||||
this->toggle_button = games::OverlayButtons::ToggleSubScreen;
|
this->toggle_button = games::OverlayButtons::ToggleSubScreen;
|
||||||
this->remove_window_padding = true;
|
this->remove_window_padding = true;
|
||||||
this->size_max = ImVec2(ImGui::GetIO().DisplaySize.x, ImGui::GetIO().DisplaySize.y);
|
this->size_max = ImVec2(ImGui::GetIO().DisplaySize.x, ImGui::GetIO().DisplaySize.y);
|
||||||
this->flags = ImGuiWindowFlags_NoTitleBar
|
this->flags = ImGuiWindowFlags_NoScrollbar
|
||||||
| ImGuiWindowFlags_NoScrollbar
|
|
||||||
| ImGuiWindowFlags_NoResize
|
| ImGuiWindowFlags_NoResize
|
||||||
| ImGuiWindowFlags_NoNavFocus
|
| ImGuiWindowFlags_NoNavFocus
|
||||||
| ImGuiWindowFlags_NoNavInputs
|
| ImGuiWindowFlags_NoNavInputs
|
||||||
| ImGuiWindowFlags_NoDocking;
|
| ImGuiWindowFlags_NoDocking;
|
||||||
|
if (IIDX_SEGMENT_BORDERLESS) {
|
||||||
|
this->flags |= ImGuiWindowFlags_NoTitleBar;
|
||||||
|
}
|
||||||
|
|
||||||
if (IIDX_SEGMENT_FONT_COLOR.has_value()) {
|
if (IIDX_SEGMENT_FONT_COLOR.has_value()) {
|
||||||
const auto rgb = IIDX_SEGMENT_FONT_COLOR.value();
|
const auto rgb = IIDX_SEGMENT_FONT_COLOR.value();
|
||||||
@@ -51,13 +54,22 @@ namespace overlay::windows {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
float IIDXSegmentDisplay::get_title_bar_height() {
|
||||||
|
// make sure you don't call this within PushFont/PopFont block
|
||||||
|
// since text line height calculation is dependent on current font size
|
||||||
|
return ImGui::GetTextLineHeight() + ImGui::GetStyle().FramePadding.y * 2.0f;
|
||||||
|
}
|
||||||
|
|
||||||
void IIDXSegmentDisplay::calculate_initial_window() {
|
void IIDXSegmentDisplay::calculate_initial_window() {
|
||||||
// ImGui::CalcTextSize doesn't seem to work here, so manually calculate
|
// ImGui::CalcTextSize doesn't seem to work here, so manually calculate
|
||||||
ImGui::PushFont(DSEG_FONT);
|
ImGui::PushFont(DSEG_FONT);
|
||||||
this->init_size = ImGui::CalcTextSize("~.~.~.~.~.~.~.~.~.");
|
this->init_size = ImGui::CalcTextSize("~.~.~.~.~.~.~.~.~.");
|
||||||
|
ImGui::PopFont();
|
||||||
this->init_size.x += PADDING_X * 2;
|
this->init_size.x += PADDING_X * 2;
|
||||||
this->init_size.y += PADDING_Y * 2;
|
this->init_size.y += PADDING_Y * 2;
|
||||||
ImGui::PopFont();
|
if (!IIDX_SEGMENT_BORDERLESS) {
|
||||||
|
this->init_size.y += get_title_bar_height();
|
||||||
|
}
|
||||||
|
|
||||||
// initial horizontal position
|
// initial horizontal position
|
||||||
if (IIDX_SEGMENT_LOCATION.find("left") != std::string::npos) {
|
if (IIDX_SEGMENT_LOCATION.find("left") != std::string::npos) {
|
||||||
@@ -127,9 +139,13 @@ namespace overlay::windows {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void IIDXSegmentDisplay::draw_ticker(char *ticker_string) {
|
void IIDXSegmentDisplay::draw_ticker(char *ticker_string) {
|
||||||
ImGui::PushFont(DSEG_FONT);
|
float pad_y = PADDING_Y;
|
||||||
|
if (!IIDX_SEGMENT_BORDERLESS) {
|
||||||
|
pad_y += get_title_bar_height();
|
||||||
|
}
|
||||||
|
const auto pos = ImVec2(PADDING_X, pad_y);
|
||||||
|
|
||||||
const auto pos = ImVec2(PADDING_X, PADDING_Y);
|
ImGui::PushFont(DSEG_FONT);
|
||||||
|
|
||||||
// to imitate LED "off"
|
// to imitate LED "off"
|
||||||
ImGui::SetCursorPos(pos);
|
ImGui::SetCursorPos(pos);
|
||||||
|
|||||||
@@ -8,12 +8,14 @@ namespace overlay::windows {
|
|||||||
extern uint32_t IIDX_SEGMENT_FONT_SIZE;
|
extern uint32_t IIDX_SEGMENT_FONT_SIZE;
|
||||||
extern std::optional<uint32_t> IIDX_SEGMENT_FONT_COLOR;
|
extern std::optional<uint32_t> IIDX_SEGMENT_FONT_COLOR;
|
||||||
extern std::string IIDX_SEGMENT_LOCATION;
|
extern std::string IIDX_SEGMENT_LOCATION;
|
||||||
|
extern bool IIDX_SEGMENT_BORDERLESS;
|
||||||
|
|
||||||
class IIDXSegmentDisplay : public Window {
|
class IIDXSegmentDisplay : public Window {
|
||||||
public:
|
public:
|
||||||
IIDXSegmentDisplay(SpiceOverlay *overlay);
|
IIDXSegmentDisplay(SpiceOverlay *overlay);
|
||||||
void calculate_initial_window() override;
|
void calculate_initial_window() override;
|
||||||
void build_content() override;
|
void build_content() override;
|
||||||
|
float get_title_bar_height();
|
||||||
private:
|
private:
|
||||||
ImVec4 color;
|
ImVec4 color;
|
||||||
void draw_ticker(char *ticker_string);
|
void draw_ticker(char *ticker_string);
|
||||||
|
|||||||
@@ -9,12 +9,14 @@
|
|||||||
namespace overlay::windows {
|
namespace overlay::windows {
|
||||||
|
|
||||||
IIDXSubScreen::IIDXSubScreen(SpiceOverlay *overlay) : GenericSubScreen(overlay) {
|
IIDXSubScreen::IIDXSubScreen(SpiceOverlay *overlay) : GenericSubScreen(overlay) {
|
||||||
this->title = "IIDX Sub Screen";
|
this->title = "IIDX TDJ Subscreen";
|
||||||
|
|
||||||
if (GRAPHICS_IIDX_WSUB) {
|
if (GRAPHICS_IIDX_WSUB) {
|
||||||
this->disabled_message =
|
this->disabled_message =
|
||||||
"Close this overlay and use the second window.\n"
|
"Close this overlay and use the second window.\n"
|
||||||
"If you don't see the window, double check your DLL type and apply TDJ I/O patches as needed.";
|
"If you don't see the window, double check your DLL type and apply TDJ I/O patches as needed.";
|
||||||
|
} else if (games::iidx::IIDX_TDJ_MONITOR_WARNING) {
|
||||||
|
this->disabled_message = "TDJ mode subscreen overlay is not compatible with -monitor option";
|
||||||
}
|
}
|
||||||
|
|
||||||
float size = 0.5f;
|
float size = 0.5f;
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ namespace overlay::windows {
|
|||||||
this->flags = ImGuiWindowFlags_AlwaysAutoResize;
|
this->flags = ImGuiWindowFlags_AlwaysAutoResize;
|
||||||
|
|
||||||
this->init_size = ImVec2(
|
this->init_size = ImVec2(
|
||||||
80,
|
overlay::apply_scaling(80),
|
||||||
ImGui::GetFrameHeight() +
|
ImGui::GetFrameHeight() +
|
||||||
ImGui::GetStyle().WindowPadding.y * 2 +
|
ImGui::GetStyle().WindowPadding.y * 2 +
|
||||||
ImGui::GetFrameHeightWithSpacing() * 3 +
|
ImGui::GetFrameHeightWithSpacing() * 3 +
|
||||||
@@ -58,7 +58,7 @@ namespace overlay::windows {
|
|||||||
void IOPanel::build_io_panel() {}
|
void IOPanel::build_io_panel() {}
|
||||||
|
|
||||||
void IOPanel::build_operator_menu() {
|
void IOPanel::build_operator_menu() {
|
||||||
const ImVec2 wide(60, 0);
|
const ImVec2 wide = overlay::apply_scaling_to_vector(60, 0);
|
||||||
const float spacing_x = ImGui::GetStyle().ItemSpacing.y;
|
const float spacing_x = ImGui::GetStyle().ItemSpacing.y;
|
||||||
const ImVec2 tall(
|
const ImVec2 tall(
|
||||||
ImGui::GetFrameHeight(),
|
ImGui::GetFrameHeight(),
|
||||||
|
|||||||
@@ -68,7 +68,7 @@ namespace overlay::windows {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void GitadoraIOPanel::build_io_panel() {
|
void GitadoraIOPanel::build_io_panel() {
|
||||||
ImGui::Dummy(ImVec2(12, 0));
|
ImGui::Dummy(overlay::apply_scaling_to_vector(12, 0));
|
||||||
|
|
||||||
ImGui::SameLine();
|
ImGui::SameLine();
|
||||||
this->draw_buttons(0);
|
this->draw_buttons(0);
|
||||||
@@ -80,7 +80,7 @@ namespace overlay::windows {
|
|||||||
// draw p2 only if guitar freaks
|
// draw p2 only if guitar freaks
|
||||||
if (this->two_players) {
|
if (this->two_players) {
|
||||||
ImGui::SameLine();
|
ImGui::SameLine();
|
||||||
ImGui::Dummy(ImVec2(12, 0));
|
ImGui::Dummy(overlay::apply_scaling_to_vector(12, 0));
|
||||||
ImGui::SameLine();
|
ImGui::SameLine();
|
||||||
this->draw_buttons(1);
|
this->draw_buttons(1);
|
||||||
if (this->has_guitar_knobs) {
|
if (this->has_guitar_knobs) {
|
||||||
@@ -146,7 +146,7 @@ namespace overlay::windows {
|
|||||||
games::gitadora::Analogs::GuitarP1Knob :
|
games::gitadora::Analogs::GuitarP1Knob :
|
||||||
games::gitadora::Analogs::GuitarP2Knob;
|
games::gitadora::Analogs::GuitarP2Knob;
|
||||||
|
|
||||||
const ImVec2 slider_size(32, get_suggested_height());
|
const ImVec2 slider_size(overlay::apply_scaling(32), get_suggested_height());
|
||||||
|
|
||||||
// get analog
|
// get analog
|
||||||
const auto analogs = games::get_analogs(eamuse_get_game());
|
const auto analogs = games::get_analogs(eamuse_get_game());
|
||||||
|
|||||||
@@ -19,15 +19,15 @@ namespace overlay::windows {
|
|||||||
case 0: {
|
case 0: {
|
||||||
this->toggle_button = games::OverlayButtons::ToggleVirtualKeypadP1;
|
this->toggle_button = games::OverlayButtons::ToggleVirtualKeypadP1;
|
||||||
this->init_pos = ImVec2(
|
this->init_pos = ImVec2(
|
||||||
26,
|
overlay::apply_scaling(26),
|
||||||
ImGui::GetIO().DisplaySize.y - 264);
|
ImGui::GetIO().DisplaySize.y - overlay::apply_scaling(264));
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case 1: {
|
case 1: {
|
||||||
this->toggle_button = games::OverlayButtons::ToggleVirtualKeypadP2;
|
this->toggle_button = games::OverlayButtons::ToggleVirtualKeypadP2;
|
||||||
this->init_pos = ImVec2(
|
this->init_pos = ImVec2(
|
||||||
ImGui::GetIO().DisplaySize.x - 220,
|
ImGui::GetIO().DisplaySize.x - overlay::apply_scaling(220),
|
||||||
ImGui::GetIO().DisplaySize.y - 264);
|
ImGui::GetIO().DisplaySize.y - overlay::apply_scaling(264));
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -82,9 +82,17 @@ namespace overlay::windows {
|
|||||||
|
|
||||||
// add selectable (fill last line)
|
// add selectable (fill last line)
|
||||||
if (i == std::size(BUTTONS) - 1) {
|
if (i == std::size(BUTTONS) - 1) {
|
||||||
ImGui::Selectable(button.text, false, 0, ImVec2(112, 32));
|
ImGui::Selectable(
|
||||||
|
button.text,
|
||||||
|
false,
|
||||||
|
0,
|
||||||
|
overlay::apply_scaling_to_vector(ImVec2(112, 32)));
|
||||||
} else {
|
} else {
|
||||||
ImGui::Selectable(button.text, false, 0, ImVec2(32, 32));
|
ImGui::Selectable(
|
||||||
|
button.text,
|
||||||
|
false,
|
||||||
|
0,
|
||||||
|
overlay::apply_scaling_to_vector(ImVec2(32, 32)));
|
||||||
}
|
}
|
||||||
|
|
||||||
// mouse down handler
|
// mouse down handler
|
||||||
|
|||||||
@@ -27,6 +27,11 @@ namespace overlay::windows {
|
|||||||
}
|
}
|
||||||
|
|
||||||
HWND ScreenResize::get_first_window() {
|
HWND ScreenResize::get_first_window() {
|
||||||
|
// hack for gitadora arena model which races to create many windows
|
||||||
|
// so [0] is not always the main one
|
||||||
|
if (GRAPHICS_WINDOW_MAIN.has_value()) {
|
||||||
|
return GRAPHICS_WINDOW_MAIN.value();
|
||||||
|
}
|
||||||
if (GRAPHICS_WINDOWS.size() == 0) {
|
if (GRAPHICS_WINDOWS.size() == 0) {
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,11 +8,12 @@
|
|||||||
namespace overlay::windows {
|
namespace overlay::windows {
|
||||||
|
|
||||||
SDVXSubScreen::SDVXSubScreen(SpiceOverlay *overlay) : GenericSubScreen(overlay) {
|
SDVXSubScreen::SDVXSubScreen(SpiceOverlay *overlay) : GenericSubScreen(overlay) {
|
||||||
this->title = "SDVX Sub Screen";
|
this->title = "SDVX Subscreen";
|
||||||
|
|
||||||
bool isValkyrieCabinetMode = avs::game::SPEC[0] == 'G' || avs::game::SPEC[0] == 'H';
|
if (!games::sdvx::is_valkyrie_model()) {
|
||||||
if (!isValkyrieCabinetMode) {
|
|
||||||
this->disabled_message = "Valkyrie Model mode is not enabled!";
|
this->disabled_message = "Valkyrie Model mode is not enabled!";
|
||||||
|
} else if (games::sdvx::SHOW_VM_MONITOR_WARNING) {
|
||||||
|
this->disabled_message = "VM mode subscreen overlay is not compatible with -monitor option";
|
||||||
} else if (GRAPHICS_WINDOWED) {
|
} else if (GRAPHICS_WINDOWED) {
|
||||||
if (GRAPHICS_PREVENT_SECONDARY_WINDOW) {
|
if (GRAPHICS_PREVENT_SECONDARY_WINDOW) {
|
||||||
this->disabled_message = "Subscreen has been disabled by the user (-sdvxnosub).";
|
this->disabled_message = "Subscreen has been disabled by the user (-sdvxnosub).";
|
||||||
|
|||||||
@@ -1850,7 +1850,7 @@ LRESULT CALLBACK rawinput::RawInputManager::input_wnd_proc(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// update controller state ring buffers (DDR/MDXF)
|
// update controller state ring buffers (DDR/MDXF)
|
||||||
mdxf_poll();
|
mdxf_poll(true);
|
||||||
|
|
||||||
// call the default window handler for cleanup
|
// call the default window handler for cleanup
|
||||||
DefWindowProc(hWnd, msg, wparam, lParam);
|
DefWindowProc(hWnd, msg, wparam, lParam);
|
||||||
@@ -2835,4 +2835,4 @@ void rawinput::RawInputManager::remove_callback_midi(void * data, const std::fun
|
|||||||
[data, callback](MidiCallback const &cb) {
|
[data, callback](MidiCallback const &cb) {
|
||||||
return cb.data == data && cb.f.target<void>() == callback.target<void>();
|
return cb.data == data && cb.f.target<void>() == callback.target<void>();
|
||||||
}), this->callback_midi.end());
|
}), this->callback_midi.end());
|
||||||
}
|
}
|
||||||
@@ -241,7 +241,9 @@ static LRESULT CALLBACK SpiceTouchWndProc(HWND hWnd, UINT msg, WPARAM wParam, LP
|
|||||||
}
|
}
|
||||||
|
|
||||||
const auto is_windowed_sub =
|
const auto is_windowed_sub =
|
||||||
(GRAPHICS_IIDX_WSUB && hWnd == TDJ_SUBSCREEN_WINDOW) || (hWnd == SDVX_SUBSCREEN_WINDOW);
|
(GRAPHICS_IIDX_WSUB && hWnd == TDJ_SUBSCREEN_WINDOW) ||
|
||||||
|
(hWnd == SDVX_SUBSCREEN_WINDOW) ||
|
||||||
|
(hWnd == GFDM_SUBSCREEN_WINDOW);
|
||||||
|
|
||||||
if (msg == WM_CLOSE) {
|
if (msg == WM_CLOSE) {
|
||||||
if (is_windowed_sub) {
|
if (is_windowed_sub) {
|
||||||
|
|||||||
@@ -12,7 +12,8 @@ namespace deferredlogs {
|
|||||||
" this crash is most likely related to audio init failure",
|
" this crash is most likely related to audio init failure",
|
||||||
" * check if the default audio device has changed",
|
" * check if the default audio device has changed",
|
||||||
" * fix your audio device settings (e.g., sample rate)",
|
" * fix your audio device settings (e.g., sample rate)",
|
||||||
" * double check your spice audio options and patches"
|
" * double check your spice audio options and patches",
|
||||||
|
" * ensure no other application is using the device in exclusive mode"
|
||||||
};
|
};
|
||||||
|
|
||||||
std::mutex deferred_errors_mutex;
|
std::mutex deferred_errors_mutex;
|
||||||
@@ -31,6 +32,16 @@ namespace deferredlogs {
|
|||||||
deferred_errors.emplace_back(messages);
|
deferred_errors.emplace_back(messages);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void report_fatal_message() {
|
||||||
|
static std::once_flag printed;
|
||||||
|
std::call_once(printed, []() {
|
||||||
|
deferredlogs::defer_error_messages({
|
||||||
|
"game reported FATAL error(s)",
|
||||||
|
" look for messages containing F: in the log",
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
void dump_to_logger(bool is_crash) {
|
void dump_to_logger(bool is_crash) {
|
||||||
static std::once_flag printed;
|
static std::once_flag printed;
|
||||||
std::call_once(printed, [is_crash]() {
|
std::call_once(printed, [is_crash]() {
|
||||||
@@ -75,8 +86,8 @@ namespace deferredlogs {
|
|||||||
msg += "\n";
|
msg += "\n";
|
||||||
}
|
}
|
||||||
|
|
||||||
for (auto messages : errors) {
|
for (const auto &messages : errors) {
|
||||||
for (auto message : messages) {
|
for (const auto &message : messages) {
|
||||||
msg += " " + message + "\n";
|
msg += " " + message + "\n";
|
||||||
}
|
}
|
||||||
msg += "\n";
|
msg += "\n";
|
||||||
@@ -84,13 +95,13 @@ namespace deferredlogs {
|
|||||||
|
|
||||||
msg += " unsure what to do next?\n";
|
msg += " unsure what to do next?\n";
|
||||||
msg += " * update to the latest version:\n";
|
msg += " * update to the latest version:\n";
|
||||||
msg += " https://github.com/spice2x/spice2x.github.io/releases/latest\n";
|
msg += " https://github.com/spice2x/spice2x.github.io/releases\n";
|
||||||
msg += " * check the FAQ:\n";
|
msg += " * check the FAQ:\n";
|
||||||
msg += " https://github.com/spice2x/spice2x.github.io/wiki/Known-issues\n";
|
msg += " https://github.com/spice2x/spice2x.github.io/wiki/Known-issues\n";
|
||||||
msg += "\n";
|
msg += "\n";
|
||||||
msg += "\\------------------------- spice2x auto-troubleshooter ------------------------/\n";
|
msg += "\\------------------------- spice2x auto-troubleshooter ------------------------/\n";
|
||||||
|
|
||||||
log_warning("troubleshooter", "{}", msg);
|
log_special("troubleshooter", "{}", msg);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ namespace deferredlogs {
|
|||||||
extern const std::initializer_list<std::string> SUPERSTEP_SOUND_ERROR_MESSAGE;
|
extern const std::initializer_list<std::string> SUPERSTEP_SOUND_ERROR_MESSAGE;
|
||||||
|
|
||||||
void set_softid(const std::string& softid);
|
void set_softid(const std::string& softid);
|
||||||
|
void report_fatal_message();
|
||||||
void defer_error_messages(std::initializer_list<std::string> messages);
|
void defer_error_messages(std::initializer_list<std::string> messages);
|
||||||
void dump_to_logger(bool is_crash=false);
|
void dump_to_logger(bool is_crash=false);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -87,6 +87,9 @@ void show_popup_for_fatal_error(std::string message);
|
|||||||
#define log_warning(module, format_str, ...) logger::push( \
|
#define log_warning(module, format_str, ...) logger::push( \
|
||||||
LOG_FORMAT("W", module, format_str, ## __VA_ARGS__), logger::Style::YELLOW)
|
LOG_FORMAT("W", module, format_str, ## __VA_ARGS__), logger::Style::YELLOW)
|
||||||
|
|
||||||
|
#define log_special(module, format_str, ...) logger::push( \
|
||||||
|
LOG_FORMAT("W", module, format_str, ## __VA_ARGS__), logger::Style::SPECIAL)
|
||||||
|
|
||||||
#define log_fatal(module, format_str, ...) { \
|
#define log_fatal(module, format_str, ...) { \
|
||||||
logger::push(LOG_FORMAT("F", module, format_str, ## __VA_ARGS__), logger::Style::RED); \
|
logger::push(LOG_FORMAT("F", module, format_str, ## __VA_ARGS__), logger::Style::RED); \
|
||||||
show_popup_for_fatal_error(LOG_FORMAT_POPUP(module, format_str, ## __VA_ARGS__)); \
|
show_popup_for_fatal_error(LOG_FORMAT_POPUP(module, format_str, ## __VA_ARGS__)); \
|
||||||
|
|||||||
@@ -0,0 +1,87 @@
|
|||||||
|
#include "socd_cleaner.h"
|
||||||
|
|
||||||
|
#include "util/logging.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 socd {
|
||||||
|
|
||||||
|
SocdAlgorithm ALGORITHM = SocdAlgorithm::Neutral;
|
||||||
|
|
||||||
|
static double last_rising_edge[2][2] = {};
|
||||||
|
static bool last_button_state[2][2] = {};
|
||||||
|
|
||||||
|
// this has no locking, use only if you know there is only one i/o thread that will call this
|
||||||
|
SocdResult socd_clean(uint8_t device, bool ccw, bool cw, double time_now) {
|
||||||
|
if (device >= 2) {
|
||||||
|
log_fatal("socd", "invalid device index in socd_clean: {}", device);
|
||||||
|
}
|
||||||
|
|
||||||
|
// SOCD last input algorithm needs to keep track of rising edge times
|
||||||
|
if (ALGORITHM != SocdAlgorithm::Neutral) {
|
||||||
|
// detect rising edges
|
||||||
|
if (!last_button_state[device][SocdCCW] && ccw) {
|
||||||
|
last_rising_edge[device][SocdCCW] = time_now;
|
||||||
|
}
|
||||||
|
if (!last_button_state[device][SocdCW] && cw) {
|
||||||
|
last_rising_edge[device][SocdCW] = time_now;
|
||||||
|
}
|
||||||
|
|
||||||
|
// update button state for next time
|
||||||
|
last_button_state[device][SocdCCW] = ccw;
|
||||||
|
last_button_state[device][SocdCW] = cw;
|
||||||
|
}
|
||||||
|
|
||||||
|
// determine direction: easy cases
|
||||||
|
if (!ccw && !cw) {
|
||||||
|
return SocdNone;
|
||||||
|
}
|
||||||
|
if (ccw && !cw) {
|
||||||
|
return SocdCCW;
|
||||||
|
}
|
||||||
|
if (!ccw && cw) {
|
||||||
|
return SocdCW;
|
||||||
|
}
|
||||||
|
|
||||||
|
// SOCD logic; depends on algorithm in use
|
||||||
|
const auto ccw_time = last_rising_edge[device][SocdCCW];
|
||||||
|
const auto cw_time = last_rising_edge[device][SocdCW];
|
||||||
|
log_debug("socd", "ccw={}, cw ={}", ccw_time, cw_time);
|
||||||
|
|
||||||
|
if (ALGORITHM == SocdAlgorithm::PreferRecent) {
|
||||||
|
// SOCD: prefer last input
|
||||||
|
if (ccw_time < cw_time) {
|
||||||
|
// while CCW is being held, CW got pressed
|
||||||
|
return SocdCW;
|
||||||
|
} else if (ccw_time > cw_time) {
|
||||||
|
// while CW is being held, CCW got pressed
|
||||||
|
return SocdCCW;
|
||||||
|
} else {
|
||||||
|
// it's a tie; instead of none, we'll pick a direction
|
||||||
|
return SocdCW;
|
||||||
|
}
|
||||||
|
} else if (ALGORITHM == SocdAlgorithm::PreferFirst) {
|
||||||
|
// SOCD: keep first input
|
||||||
|
if (ccw_time < cw_time) {
|
||||||
|
// while CCW is being held, CW got pressed
|
||||||
|
return SocdCCW;
|
||||||
|
} else if (ccw_time > cw_time) {
|
||||||
|
// while CW is being held, CCW got pressed
|
||||||
|
return SocdCW;
|
||||||
|
} else {
|
||||||
|
// it's a tie; instead of none, we'll pick a direction
|
||||||
|
return SocdCW;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// SOCD: neutral when both are pressed
|
||||||
|
return SocdNone;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
|
|
||||||
|
namespace socd {
|
||||||
|
|
||||||
|
enum class SocdAlgorithm {
|
||||||
|
Neutral,
|
||||||
|
PreferRecent,
|
||||||
|
PreferFirst
|
||||||
|
};
|
||||||
|
|
||||||
|
extern SocdAlgorithm ALGORITHM;
|
||||||
|
|
||||||
|
typedef enum _SocdResult {
|
||||||
|
SocdCCW = 0,
|
||||||
|
SocdCW = 1,
|
||||||
|
SocdNone = 2
|
||||||
|
} SocdResult;
|
||||||
|
|
||||||
|
SocdResult socd_clean(uint8_t device, bool ccw, bool cw, double time_now);
|
||||||
|
}
|
||||||
@@ -350,4 +350,23 @@ static inline std::string wchar_to_u8(const wchar_t* wstr) {
|
|||||||
std::string out(size - 1, 0);
|
std::string out(size - 1, 0);
|
||||||
WideCharToMultiByte(CP_UTF8, 0, wstr, -1, out.data(), size - 1, nullptr, nullptr);
|
WideCharToMultiByte(CP_UTF8, 0, wstr, -1, out.data(), size - 1, nullptr, nullptr);
|
||||||
return out;
|
return out;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// convert litte vs big endian
|
||||||
|
inline uint64_t bswap64(uint64_t x) {
|
||||||
|
#if defined(__GNUC__) || defined(__clang__)
|
||||||
|
return __builtin_bswap64(x);
|
||||||
|
#endif
|
||||||
|
#ifdef _MSC_VER
|
||||||
|
return _byteswap_uint64(x);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
return (x >> 56) |
|
||||||
|
((x >> 40) & 0x000000000000FF00ULL) |
|
||||||
|
((x >> 24) & 0x0000000000FF0000ULL) |
|
||||||
|
((x >> 8) & 0x00000000FF000000ULL) |
|
||||||
|
((x << 8) & 0x000000FF00000000ULL) |
|
||||||
|
((x << 24) & 0x0000FF0000000000ULL) |
|
||||||
|
((x << 40) & 0x00FF000000000000ULL) |
|
||||||
|
(x << 56);
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user