Compare commits

...

29 Commits

Author SHA1 Message Date
Isaac Drew 66094de38a Add coin support for SDVX BI2X (#518)
## Description of change
Add coin handling to SDVX BI2X. I also tried to populate the structs as
best I could.

## Testing
Tested via running spice2x on EG final, using F1 to insert coins.
2026-01-13 09:59:49 -08:00
bicarus c00cff2afd patcher: print patch identifier for each DLL (#517) 2026-01-13 04:21:22 -08:00
bicarus b2cc8d404c gitadora: digital wailing sensitivity (#516)
## Link to GitHub Issue, if one exists
#512 

## Description of change
Add an option to adjust the hold period for digital wailing.
2026-01-13 04:19:55 -08:00
bicarus a0a04ab62f gitadora: various fixes for wailing (#513)
## Link to GitHub Issue, if one exists
Fixes #512

## Description of change
Fix digital wailing not being recognized consistently.

Also address the fact that wailing was completely broken when lefty mode
was on. This requires a new option that the user needs to check off in
the configurator since we can't magically guess if the user is holding
the guitar in lefty mode.

- [x] fix downward wail
- [x] digital lefty mode
- [x] implement all of this for arena model i/o
- [x] test dx cab 2p
- [x] lefty toggle in overlay
- [x] fix analog not working for 2p guitar
- [ ] analog lefty mode?

## Testing
Checked GW and GW Delta
2026-01-12 21:52:48 -08:00
bicarus a3a59e689e ftt: clean up i/o (#515)
No functional change, just a code clean up.
2026-01-12 19:32:24 -08:00
bicarus a6f29d6a5a audio: implement wrapped IAudioClient3 (#511)
## Link to GitHub Issue, if one exists
Fixes #509

## Description of change
Implement `WrappedIAudioClient3`.

For now, we have only seen SDVX use it (EG final) and only when the
shared mode patch is enabled. Exclusive still goes through version 1.
This means that for V3 we can forego implementing backend conversion
logic (for now).

Only significant addition is `InitializeSharedAudioStream` and this IS
called by SDVX when shared mode patch is enabled.

## Testing
Tested EG final, tested year 1 data, also IIDX shared/exclusive wasapi.
2026-01-10 17:41:10 -08:00
bicarus 9be618d791 utils: log primary display adapter (#508)
## Description of change
Log a message so you can tell which display adapter is primary.
2026-01-10 03:03:08 -08:00
bicarus 6bf7ae9074 graphics: submonitor refresh rate option (#507)
## Description of change
Add an option that forces requested refresh rate value for the second
monitor; i.e., instead of 60Hz allow the user to override the adapter to
be 70Hz.

Useful for people who have a touch screen that can't do 60Hz.

I don't know if this would cause any timing issues in IIDX or SDVX, but
it seems to boot and monitor check is unaffected.

## Testing
Tested TDJ And UFC mode with a submonitor that can only do 61Hz.
2026-01-09 19:50:07 -08:00
bicarus e3d63c65c1 overlay: fps window clean up (#506)
## Link to GitHub Issue, if one exists
n/a

## Description of change
Use fmt library for formatting time

Round down sub-second units so that the FPS counter only updates once
per second (instead of Time and Uptime ticking independently)

clean up usage of `localtime` in `logging.cpp`
2026-01-08 21:52:13 -08:00
bicarus 5d2a9fa1cf audio: update logging for low latency option (#505)
## Description of change
Spit out an error if the minimum latency is not any lower than the
default.
2026-01-08 13:56:10 -08:00
bicarus 0078b72b03 graphics: address performance regression with Image Resize (#504)
## Link to GitHub Issue, if one exists
Regressed by #401 

## Description of change
Two things from testing SDVX Live2D -

1. ColorFill is almost always too expensive and will lead to drops
1. Scale factor of 0.5 causes GPU to sync and causes drops

(ARGB/XRGB doesn't matter, linear filtering doesn't matter)

1 is fixed by removing ColorFill calls on every frame - instead only do
this once when the user changes the duplicate option (on the next frame)

2 is fixed by updating the UI (limit the slider) though people can
ctrl+click to override these slider limits.

The warning label will be kept with slightly reworded text.

## Testing
tested in sdvx eg final (sudden death) - on my RTX 4070, my FPS is rock
solid 120 fps even with image resize on now.
2026-01-07 16:35:08 -08:00
bicarus b1ee37a066 troubleshooter: diagnose ddr codec issues (#503)
## Link to GitHub Issue, if one exists
#345 

## Description of change
Turn existing warnings for DDR codec issues into deferred error
messages.

## Testing
Tested DDR World with faked codec issues.
2026-01-06 23:20:05 -08:00
bicarus ebf6382e7f sdvx: add warning about Image Resize feature affecting Live2D performance (#502)
## Link to GitHub Issue, if one exists
n/a

## Description of change
Turning on Image Resize feature completely tanks performance of Live2D
in some SDVX songs. Add a giant warning next to the UI.

On my system with RTX 4070, Sudden Death is normally a rock solid
120FPS, but drops to 30-40FPS when resizing is on.

* linear filter does not affect this at all
* window resize options also have no effect

It's really just rendering on a larger surface + calling `StretchRect`
that does this.

Additionally, hide the resolution swap option since it sees very low
usage & has potential gotchas likes this.
2026-01-06 16:40:54 -08:00
bicarus 8e23ef224a Update CONTRIBUTING.md 2026-01-05 18:59:06 -08:00
bicarus 6706ffc321 Fix wording in CONTRIBUTING.md for clarity 2026-01-05 17:26:29 -08:00
bicarus e755b06e35 Update CONTRIBUTING.md 2026-01-05 17:25:10 -08:00
bicarus 9ef22712c0 Update CONTRIBUTING.md 2026-01-05 17:24:13 -08:00
bicarus 7b903d35e2 iidx, sdvx: SOCD cleaner for knobs and turntables (#497)
## Link to GitHub Issue, if one exists
n/a

## Description of change
Apply SOCD cleaner algorithm to deal with the case where both
counter-clockwise and clockwise buttons are pressed at the same time.

In SDVX, default algorithm is to prefer the most recently pressed
direction. This is better for dealing with slams in rapid succession,
for example, as you don't have to completely let go of one direction to
hit the other now.

In IIDX, default algorithm is neutral (same behavior as before). Using
last or first algorithm results in double scratches which means you'll
likely end up with an excessive poor after hitting a scratch, so neutral
is actually beneficial. Besides, if you are serious about playing on the
keyboard, you should be using `TT +/-` and `TT +/- Alternate`.

## Testing
Tested SDVX 3, EG (old cab), EG valk cab modes, IIDX 24, 27, 33
(tdj/ldj)
2026-01-04 19:50:11 -08:00
bicarus 7f281a50c5 sdvx: clean up valk cab check (#498) 2026-01-04 19:49:55 -08:00
bicarus 1354d63b85 iidx, sdvx: bi2x hook fix (attempt 2) (#499)
## Link to GitHub Issue, if one exists
see my previous attempt #496, which has been reverted by #500

## Description of change
Two issues:

1. the original implementation of bi2x_hook for iidx misunderstood what
`aioNMgrIob2_Create` does. In the original I/O code it returns a pointer
to a C++ class instance, which would also have a vptr (pointer to a
vtable) in the beginning. The original author of this code of the hook
in spice thought that `aioNMgrIob2_Create` returns a pointer to a
pointer. This may happen to work but it's not 100% accurate, so this has
been fixed by separating out the class and the vtable allocation.
Without this, the game could have accessed memory outside this buffer.

2. And then the SDVX implementation blindly copy-pasted the IIDX
`bi2x_hook`. Along with the issue above, they also forgot to check the
I/O DLLs to see how much memory needs to be allocated and change that.
Instead, they kept the same value as IIDX, which was too small. Fix that
as well.

## Testing
Doing some extensive testing with IIDX (27-33) and SDVX versions (EG).
2026-01-04 19:10:50 -08:00
bicarus e745cdcac8 Revert "iidx, sdvx: bi2x hook fix" (#500)
Reverts spice2x/spice2x.github.io#496
2026-01-04 17:50:21 -08:00
bicarus bf7666939a iidx, sdvx: bi2x hook fix (#496)
## Link to GitHub Issue, if one exists
n/a

## Description of change
Both games take the address returned by `aioNMgrIob2_Create` and reads
values offset from that address as part of the I/O routine. This means
that the I/O routines of these games have been accessing random
variables in data/bss segments for years, they just happened to get a
value that doesn't lead to a crash.

Stumbled upon this while trying to add a new feature that required
adding more globals, but suddenly the game stopped polling I/O when I
changed a value of my global. Scary.

## Testing
Tested both games I/O
2026-01-03 19:27:53 -08:00
bicarus-dev 7e89037db3 update github download url 2026-01-03 05:30:50 -08:00
bicarus 6ffaa77f58 overlay: UI scaling option (#495)
## Link to GitHub Issue, if one exists
#477 

## Description of change
Add a new option for scaling the overlay.

Gitadora Arena will receive 250% by default.

Various overlay windows have been updated to scale properly, not perfect
but they are at least usable.
2026-01-02 21:54:25 -08:00
bicarus 7a1f46d1be gitadora: (arena model) fix window settings not working (#494)
## Link to GitHub Issue, if one exists
#477 

## Description of change
F11 menu (window settings) did not work for arena model because the game
engine races to create many windows, so our logic of grabbing the first
window created doesn't always get the main window.

## Testing
2026-01-02 15:07:32 -08:00
bicarus cf59ae5f89 gitadora: (arena model) lights (#492)
## Link to GitHub Issue, if one exists
#477 

## Description of change
Add lights for J region cab. I can't figure out how the extra lights on
U region cab are wired up.

## Testing
Tested guitar and drums, J and U regions.
2026-01-02 03:49:01 -08:00
bicarus 9c06dd36a0 graphics: tone down adapter mode removal logging (#493)
## Link to GitHub Issue, if one exists
n/a

## Description of change
Get rid of log messages when adapter modes are removed, since these have
never been all that useful.
2026-01-02 03:48:28 -08:00
bicarus d5d082ec37 troubleshooter: deferred message for fatal errors (#491)
## Description of change
When the game logs a fatal error message, queue up a deferred message
for the troubleshooter.
2025-12-31 05:44:55 -08:00
bicarus 17723d290a troubleshooter: update formatting (#490)
## Link to GitHub Issue, if one exists
n/a

## Description of change
Make the text color cyan to make the messages stand out.

## Testing
Verified in terminal.
2025-12-31 01:15:19 -08:00
64 changed files with 1384 additions and 417 deletions
+10 -3
View File
@@ -1,8 +1,7 @@
## Contributing
Baseline rules for patch submissions are as follows. Any patches violating the rules below will not be accepted.
### New as of March 2025
Pull requests in GitHub are open. You don't need to bother with patch file submissions anymore. To contribute, fork the repo (just the main branch), make changes in your fork, and contribute to upstream by opening a pull request to the main repo.
To contribute, fork the repo (just the main branch), make changes in your fork, and contribute to upstream by opening a pull request to the main repo.
### Adding support for games
@@ -12,6 +11,14 @@ Pull requests in GitHub are open. You don't need to bother with patch file submi
* For a new game that was never supported (not a new version of a game, but rather a new series): please wait 1 year after official AC release in Japan.
* For new version of an already supported game: proceed with caution and use generally-accepted community guidelines.
### Forbidden "features" that will never be accepted
* Performing decryption of encrypted files
* Including (bundling), distributing, or linking to game data, or making it easier to find/download game data
* Any features that [phone home](https://en.wikipedia.org/wiki/Phoning_home)
* This includes automatic updaters.
* Exception: we have allowed the "patch import from URL" feature, but this has only been done under very careful considerations and plenty of warnings to the user.
### Avoiding regressions
The biggest risk in making code changes to spice is the risk of regressions. There is a **lot** of shared code used by many different games, reaching back game versions that are more than a decade old. It is practically impossible to test all supported games and versions.
@@ -22,7 +29,7 @@ Therefore, when making code changes, please be extremely careful about containin
#### Config file compatibility
Do not change the names of options, buttons binds, analogs, etc - since they are used as unique identifiers in config files. If they are changed, you will introduce an incompatibility with previous versions of config file.
Do not change the names of buttons binds, analogs, strings for command line parameters, etc - since they are used as unique identifiers in config files. If they are changed, you will introduce an incompatibility with previous versions of config file.
### Code quality requirements
+1
View File
@@ -624,6 +624,7 @@ set(SOURCE_FILES ${SOURCE_FILES}
util/execexe.cpp
util/dependencies.cpp
util/deferlog.cpp
util/socd_cleaner.cpp
)
source_group(TREE ${CMAKE_CURRENT_SOURCE_DIR} PREFIX "Source Files" FILES ${SOURCE_FILES})
+30 -6
View File
@@ -9,11 +9,22 @@
#include "games/drs/drs.h"
#include "misc/eamuse.h"
#include "util/logging.h"
#include "util/socd_cleaner.h"
#include "util/time.h"
#include "util/utils.h"
#include "util/tapeled.h"
using namespace GameAPI;
#define DEBUG_VERBOSE 0
#if DEBUG_VERBOSE
#define log_debug(module, format_str, ...) logger::push( \
LOG_FORMAT("M", module, format_str, ## __VA_ARGS__), logger::Style::GREY)
#else
#define log_debug(module, format_str, ...)
#endif
// state
static uint8_t STATUS_BUFFER[272] {};
static bool STATUS_BUFFER_FREEZE = false;
@@ -94,18 +105,25 @@ static bool __cdecl ac_io_bi2a_update_control_status_buffer() {
}
// volume left
if (Buttons::getState(RI_MGR, buttons.at(games::sdvx::Buttons::VOL_L_Left))) {
const auto now = get_performance_milliseconds();
const auto vol_l_state = socd::socd_clean(0,
Buttons::getState(RI_MGR, buttons.at(games::sdvx::Buttons::VOL_L_Left)),
Buttons::getState(RI_MGR, buttons.at(games::sdvx::Buttons::VOL_L_Right)),
now);
if (vol_l_state == socd::SocdCCW) {
BI2A_VOLL = (BI2A_VOLL - games::sdvx::DIGITAL_KNOB_SENS) & 1023;
}
if (Buttons::getState(RI_MGR, buttons.at(games::sdvx::Buttons::VOL_L_Right))) {
} else if (vol_l_state == socd::SocdCW) {
BI2A_VOLL = (BI2A_VOLL + games::sdvx::DIGITAL_KNOB_SENS) & 1023;
}
// volume right
if (Buttons::getState(RI_MGR, buttons.at(games::sdvx::Buttons::VOL_R_Left))) {
const auto vol_r_state = socd::socd_clean(1,
Buttons::getState(RI_MGR, buttons.at(games::sdvx::Buttons::VOL_R_Left)),
Buttons::getState(RI_MGR, buttons.at(games::sdvx::Buttons::VOL_R_Right)),
now);
if (vol_r_state == socd::SocdCCW) {
BI2A_VOLR = (BI2A_VOLR - games::sdvx::DIGITAL_KNOB_SENS) & 1023;
}
if (Buttons::getState(RI_MGR, buttons.at(games::sdvx::Buttons::VOL_R_Right))) {
} else if (vol_r_state == socd::SocdCW) {
BI2A_VOLR = (BI2A_VOLR + games::sdvx::DIGITAL_KNOB_SENS) & 1023;
}
@@ -127,6 +145,12 @@ static bool __cdecl ac_io_bi2a_update_control_status_buffer() {
// save volumes in buffer
*((uint16_t*) &STATUS_BUFFER[17]) = (uint16_t) ((vol_left) << 2);
*((uint16_t*) &STATUS_BUFFER[19]) = (uint16_t) ((vol_right) << 2);
log_debug(
"bi2a",
"knobs = {} {}",
*((uint16_t*) &STATUS_BUFFER[17]),
*((uint16_t*) &STATUS_BUFFER[19]));
}
// DanceDanceRevolution
+5 -5
View File
@@ -52,7 +52,7 @@ static bool __cdecl ac_io_j32d_update_control_status_buffer(size_t a1) {
float pad3_vel = Buttons::getVelocity(RI_MGR, buttons.at(games::ftt::Buttons::Pad3));
float pad4_vel = Buttons::getVelocity(RI_MGR, buttons.at(games::ftt::Buttons::Pad4));
// FIXME(felix): this logic seems wrong for analog handling but correct for digital inputs
if (pad1_vel > 0.f) {
if (Buttons::getState(RI_MGR, buttons.at(games::ftt::Buttons::Pad1))) {
STATUS_BUFFER[6] = (int) (51.f * pad1_vel + 0.5f);
}
if (Buttons::getState(RI_MGR, buttons.at(games::ftt::Buttons::Pad2))) {
@@ -67,10 +67,10 @@ static bool __cdecl ac_io_j32d_update_control_status_buffer(size_t a1) {
// process analogs
auto &analogs = games::ftt::get_analogs();
auto pad1_analog = analogs.at(games::ftt::Analogs::Pad1);
auto pad2_analog = analogs.at(games::ftt::Analogs::Pad2);
auto pad3_analog = analogs.at(games::ftt::Analogs::Pad3);
auto pad4_analog = analogs.at(games::ftt::Analogs::Pad4);
auto &pad1_analog = analogs.at(games::ftt::Analogs::Pad1);
auto &pad2_analog = analogs.at(games::ftt::Analogs::Pad2);
auto &pad3_analog = analogs.at(games::ftt::Analogs::Pad3);
auto &pad4_analog = analogs.at(games::ftt::Analogs::Pad4);
if (pad1_analog.isSet()) {
auto val = (uint32_t) (51.f * Analogs::getState(RI_MGR, pad1_analog) + 0.5f);
STATUS_BUFFER[6] = MAX(STATUS_BUFFER[6], val);
+26 -6
View File
@@ -8,10 +8,21 @@
#include "games/sdvx/io.h"
#include "misc/eamuse.h"
#include "rawinput/rawinput.h"
#include "util/socd_cleaner.h"
#include "util/time.h"
#include "util/utils.h"
using namespace GameAPI;
#define DEBUG_VERBOSE 0
#if DEBUG_VERBOSE
#define log_debug(module, format_str, ...) logger::push( \
LOG_FORMAT("M", module, format_str, ## __VA_ARGS__), logger::Style::GREY)
#else
#define log_debug(module, format_str, ...)
#endif
// globals
uint8_t KFCA_VOL_SOUND = 96;
uint8_t KFCA_VOL_HEADPHONE = 96;
@@ -342,18 +353,25 @@ static char __cdecl ac_io_kfca_update_control_status_buffer() {
}
// volume left
if (Buttons::getState(RI_MGR, buttons.at(games::sdvx::Buttons::VOL_L_Left))) {
const auto now = get_performance_milliseconds();
const auto vol_l_state = socd::socd_clean(0,
Buttons::getState(RI_MGR, buttons.at(games::sdvx::Buttons::VOL_L_Left)),
Buttons::getState(RI_MGR, buttons.at(games::sdvx::Buttons::VOL_L_Right)),
now);
if (vol_l_state == socd::SocdCCW) {
KFCA_VOLL = (KFCA_VOLL - games::sdvx::DIGITAL_KNOB_SENS) & 1023;
}
if (Buttons::getState(RI_MGR, buttons.at(games::sdvx::Buttons::VOL_L_Right))) {
} else if (vol_l_state == socd::SocdCW) {
KFCA_VOLL = (KFCA_VOLL + games::sdvx::DIGITAL_KNOB_SENS) & 1023;
}
// volume right
if (Buttons::getState(RI_MGR, buttons.at(games::sdvx::Buttons::VOL_R_Left))) {
const auto vol_r_state = socd::socd_clean(1,
Buttons::getState(RI_MGR, buttons.at(games::sdvx::Buttons::VOL_R_Left)),
Buttons::getState(RI_MGR, buttons.at(games::sdvx::Buttons::VOL_R_Right)),
now);
if (vol_r_state == socd::SocdCCW) {
KFCA_VOLR = (KFCA_VOLR - games::sdvx::DIGITAL_KNOB_SENS) & 1023;
}
if (Buttons::getState(RI_MGR, buttons.at(games::sdvx::Buttons::VOL_R_Right))) {
} else if (vol_r_state == socd::SocdCW) {
KFCA_VOLR = (KFCA_VOLR + games::sdvx::DIGITAL_KNOB_SENS) & 1023;
}
@@ -371,6 +389,8 @@ static char __cdecl ac_io_kfca_update_control_status_buffer() {
// proper loops
vol_left %= 1024;
vol_right %= 1024;
log_debug("kfca", "knobs = {} {}", vol_left, vol_right);
// save volumes in buffer
STATUS_BUFFER[input_offset + 16 + 0] |= (unsigned char) ((vol_left << 6) & 0xFF);
+5 -11
View File
@@ -2,6 +2,7 @@
#include "acio/icca/icca.h"
#include "avs/game.h"
#include "games/sdvx/sdvx.h"
#include "misc/eamuse.h"
#include "util/logging.h"
#include "util/utils.h"
@@ -101,13 +102,10 @@ bool ICCADevice::parse_msg(MessageData *msg_in,
// send version data
auto msg = this->create_msg(msg_in, MSG_VERSION_SIZE);
if (
avs::game::is_model({"LDJ", "TBS", "UJK", "XIF"}) ||
// SDVX Valkyrie cabinet mode
(avs::game::is_model("KFC") && (avs::game::SPEC[0] == 'G' || avs::game::SPEC[0] == 'H'))
) {
if (avs::game::is_model({"LDJ", "TBS", "UJK", "XIF"}) ||
games::sdvx::is_valkyrie_model()) {
this->set_version(msg, 0x3, 0, 1, 7, 0, "ICCA");
} else if (avs::game::is_model({"VFG"})) {
} else if (avs::game::is_model("VFG")) {
this->set_version(msg, 0x3, 0, 1, 7, 0, "ICCB");
} else {
this->set_version(msg, 0x3, 0, 1, 6, 0, "ICCA");
@@ -520,11 +518,7 @@ void ICCADevice::update_status(int unit) {
buffer[1] = felica ? 0x01 : 0x00;
buffer[10] = felica ? 0x01 : 0x00;
} else if (
avs::game::is_model({"LDJ", "TBS", "XIF"}) ||
// SDVX Valkyrie cabinet mode
(avs::game::is_model("KFC") && (avs::game::SPEC[0] == 'G' || avs::game::SPEC[0] == 'H'))) {
} else if (avs::game::is_model({"LDJ", "TBS", "XIF"}) || games::sdvx::is_valkyrie_model()) {
// set status to 0 otherwise reader power on fails
buffer[0] = 0x00;
} else {
+2
View File
@@ -1234,6 +1234,7 @@ namespace avs {
break;
case 'F':
new_style = logger::Style::RED;
deferredlogs::report_fatal_message();
break;
default:
position = 0;
@@ -1302,6 +1303,7 @@ namespace avs {
break;
case 'F':
style = logger::Style::RED;
deferredlogs::report_fatal_message();
break;
case 'W':
style = logger::Style::YELLOW;
+6
View File
@@ -1,9 +1,11 @@
#include "screen_resize.h"
#include "avs/game.h"
#include "external/rapidjson/document.h"
#include "external/rapidjson/pointer.h"
#include "external/rapidjson/prettywriter.h"
#include "misc/eamuse.h"
#include "util/deferlog.h"
#include "util/utils.h"
#include "util/fileutils.h"
#include "hooks/graphics/graphics.h"
@@ -78,6 +80,10 @@ namespace cfg {
root);
load_bool_value(doc, root + "enable_screen_resize", this->enable_screen_resize);
if (this->enable_screen_resize) {
log_misc("ScreenResize", "enabled by config file");
}
load_bool_value(doc, root + "enable_linear_filter", this->enable_linear_filter);
for (size_t i = 0; i < std::size(this->scene_settings); i++) {
auto& scene = this->scene_settings[i];
+5
View File
@@ -1,5 +1,6 @@
#pragma once
#include <atomic>
#include <memory>
#include <string>
#include <optional>
@@ -51,6 +52,9 @@ namespace cfg {
bool enable_linear_filter = true;
fullscreen_setting scene_settings[4];
// state
std::atomic<bool> need_surface_clean = false;
// windowed mode sizing
// Windows terminology:
// window = rectangle including the frame
@@ -69,6 +73,7 @@ namespace cfg {
uint32_t init_client_height = 0;
float init_client_aspect_ratio = 1.f;
uint32_t init_window_style = 0;
uint32_t init_window_style_ex = 0;
uint32_t window_deco_width = 0;
uint32_t window_deco_height = 0;
+25 -10
View File
@@ -8,6 +8,7 @@
#include "hooks/setupapihook.h"
#include "hooks/sleephook.h"
#include "hooks/input/dinput8/hook.h"
#include "util/deferlog.h"
#include "util/utils.h"
#include "util/libutils.h"
#include "util/fileutils.h"
@@ -108,18 +109,32 @@ namespace games::ddr {
log_info("ddr", "`{}` returned {}", cmd, result);
} else {
log_warning("ddr", "`{}` failed, returned {}", cmd, result);
deferredlogs::defer_error_messages({
"DDR codec registration failure",
fmt::format(" {} failed to register with error {}", filename.string(), result)});
}
if (!contains_only_ascii(file.path().string())) {
log_warning(
"ddr",
"BAD PATH ERROR\n\n\n"
"!!! !!!\n"
"!!! filesystem path to codec contains non-ASCII characters! !!!\n"
"!!! this may cause the game to crash! !!!\n"
"!!! !!!\n"
);
}
static std::once_flag printed;
std::call_once(printed, [file]() {
if (!contains_only_ascii(file.path().string())) {
log_warning(
"ddr",
"BAD PATH ERROR\n\n"
"!!! !!!\n"
"!!! filesystem path to codec contains non-ASCII characters! !!!\n"
"!!! this may cause failures when loading codecs, and !!!\n"
"!!! potentially leading the game to crash! !!!\n"
"!!! !!!\n"
);
deferredlogs::defer_error_messages({
"path to DDR codecs contain non-Latin characters",
" this may lead to movies not playing, white screen issue,",
" game hanging, or potentially a crash",
" move the game to a directory that does not contain non-Latin characters",
" if you encountered any issues"});
}
});
}
}
}
+41 -1
View File
@@ -1,5 +1,6 @@
#include "bi2x_hook.h"
#include <optional>
#include <cstdint>
#include "util/detour.h"
#include "util/logging.h"
@@ -323,10 +324,38 @@ namespace games::gitadora {
}
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,
@@ -336,6 +365,16 @@ namespace games::gitadora {
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,
@@ -551,6 +590,7 @@ namespace games::gitadora {
}
// libaio-iob5_y32.dll
static AIO_IOB5_Y32D *__fastcall aioIob5Y32d_Create(AIO_NMGR_IOB5 *i_pSci, uint32_t i_bfMode) {
if (i_pSci == aioNmgrIob5) {
+14
View File
@@ -5,11 +5,13 @@
#include "cfg/configurator.h"
#include "hooks/audio/mme.h"
#include "hooks/graphics/graphics.h"
#include "overlay/overlay.h"
#include "util/cpuutils.h"
#include "util/detour.h"
#include "util/libutils.h"
#include "util/logging.h"
#include "util/sigscan.h"
#include "util/socd_cleaner.h"
#include "hooks/setupapihook.h"
namespace games::gitadora {
@@ -17,6 +19,8 @@ namespace games::gitadora {
// settings
bool TWOCHANNEL = false;
std::optional<unsigned int> CAB_TYPE = std::nullopt;
bool P1_LEFTY = false;
bool P2_LEFTY = false;
/*
* Prevent GitaDora from creating folders on F drive
@@ -212,6 +216,16 @@ namespace games::gitadora {
log_fatal("gitadora", "Cabinet type 3 (SD2) not supported on XG series");
}
#endif
// arena model launches a tiny window yet backbuffer at 4k, resulting in unusable overlay
// force scaling to make things usable
if (!overlay::UI_SCALE_PERCENT.has_value() && is_arena_model() &&
GRAPHICS_WINDOWED && !cfg::CONFIGURATOR_STANDALONE) {
overlay::UI_SCALE_PERCENT = 250;
}
// for guitar wail SOCD cleaning
socd::ALGORITHM = socd::SocdAlgorithm::PreferRecent;
}
}
+10
View File
@@ -10,6 +10,8 @@ namespace games::gitadora {
// settings
extern bool TWOCHANNEL;
extern std::optional<unsigned int> CAB_TYPE;
extern bool P1_LEFTY;
extern bool P2_LEFTY;
class GitaDoraGame : public games::Game {
public:
@@ -38,4 +40,12 @@ namespace games::gitadora {
);
}
static inline bool is_player_lefty(size_t player) {
if (player == 0) {
return P1_LEFTY;
} else if (player == 1) {
return P2_LEFTY;
}
return false;
}
}
+45 -4
View File
@@ -107,9 +107,18 @@ std::string games::gitadora::get_buttons_help() {
// keep to max 100 characters wide
return
"guitar:\n"
"\n"
" < R G B Y P --- Pick ] \n"
"\n"
" If you hold your guitar left-handed (frets on the right hand):\n"
" * bind the buttons here as righty and use the in-game option\n"
" to turn on LEFT mode to flip the order of buttons.\n"
" * turn on GitaDora Lefty Guitar option in Options tab so that\n"
" wailing is recognized correctly.\n"
"\n"
"\n"
"drums:\n"
"\n"
" LeftCymbal RightCymbal\n"
" HiHat HiTom LowTom \n"
" Snare FloorTom \n"
@@ -117,10 +126,30 @@ std::string games::gitadora::get_buttons_help() {
" Left Bass\n"
" Pedal Pedal\n"
"\n"
"Drums are NOT velocity-sensitive!\n"
" Drums are NOT velocity-sensitive!\n"
"\n"
"For MIDI drums with Open/Closed HiHat configurations, bind variations below. "
"v2_drum algorithm might work better for those drums."
" For MIDI drums with Open/Closed HiHat configurations or pads with\n"
" multiple hit zones, ensure you bind all variation using the Pages\n"
" button at the bottom."
;
}
std::string games::gitadora::get_analogs_help() {
// keep to max 100 characters wide
return
"guitar:\n"
"\n"
"X axis: 0% when body is held facing monitor, 50% when facing flat on a table\n"
"Y axis: 50% when held horizontal, 0% when neck is raised, 100% pointing down\n"
"Z axis: 50% at rest, 100% when swinging neck toward monitor (only used in XG series)\n"
"\n"
"You need both X and Y axis for up/down wail to work correctly in-game.\n"
"If you only have Y axis, consider using digital wailing instead.\n"
"\n"
"Ensure you clear all analog bindings if you want to use digital wailing.\n"
"\n"
"If you hold your guitar left-handed (frets on the right hand),\n"
"analog bindings will likely not work as intended.\n"
"Consider using digital wailing in Buttons tab."
;
}
@@ -211,7 +240,19 @@ std::vector<Light> &games::gitadora::get_lights() {
"Guitar Right Speaker Mid Low Right B (DX)",
"Guitar Right Speaker Lower R (DX)",
"Guitar Right Speaker Lower G (DX)",
"Guitar Right Speaker Lower B (DX)"
"Guitar Right Speaker Lower B (DX)",
"Title Average R (Arena)",
"Title Average G (Arena)",
"Title Average B (Arena)",
"Woofer R (Arena)",
"Woofer G (Arena)",
"Woofer B (Arena)",
"Card Reader R (Arena)",
"Card Reader G (Arena)",
"Card Reader B (Arena)"
);
}
+13 -1
View File
@@ -191,13 +191,25 @@ namespace games::gitadora {
GuitarRightSpeakerMidLowRightB,
GuitarRightSpeakerLowerR,
GuitarRightSpeakerLowerG,
GuitarRightSpeakerLowerB
GuitarRightSpeakerLowerB,
// arena model (common for both guitar and drum)
ArenaTitleAvgR,
ArenaTitleAvgG,
ArenaTitleAvgB,
ArenaWooferR,
ArenaWooferG,
ArenaWooferB,
ArenaCardR,
ArenaCardG,
ArenaCardB,
} gitadora_lights_t;
}
// getters
std::vector<Button> &get_buttons();
std::string get_buttons_help();
std::string get_analogs_help();
std::vector<Analog> &get_analogs();
std::vector<Light> &get_lights();
}
+30 -9
View File
@@ -1,11 +1,12 @@

#include "misc/eamuse.h"
#include "util/utils.h"
#include "games/gitadora/gitadora.h"
#include "util/socd_cleaner.h"
#include "util/time.h"
#include "util/utils.h"
#include "io.h"
#include "j33i.h"
#define GUITAR_BTN_R 0x7
#define GUITAR_BTN_G 0x6
#define GUITAR_BTN_B 0x5
@@ -111,8 +112,8 @@ bool games::gitadora::J33ISerialDevice::parse_msg(
auto &analogs = get_analogs();
// get x,y,z analog values [-0.5f, 0.5f], centered at 0.f
float x = 0.f;
// get x,y,z analog values [-0.5f, 0.5f]
float x = is_player_lefty(0) ? 0.3f : -0.5f;
auto x_analog = analogs[Analogs::GuitarP1WailX];
if (x_analog.isSet()) {
x = GameAPI::Analogs::getState(RI_MGR, x_analog) - 0.5f;
@@ -123,11 +124,6 @@ bool games::gitadora::J33ISerialDevice::parse_msg(
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];
@@ -135,6 +131,31 @@ bool games::gitadora::J33ISerialDevice::parse_msg(
z = GameAPI::Analogs::getState(RI_MGR, z_analog) - 0.5f;
}
// handle digital wail up/down
const auto wail_up = GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::GuitarP1WailUp]);
const auto wail_down = GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::GuitarP1WailDown]);
const auto wail_result =
socd::get_guitar_wail(0, wail_up, wail_down, get_performance_milliseconds());
if (!is_player_lefty(0)) {
// righty
if (wail_result == socd::TiltUp) {
x = 0;
y = -0.5f;
} else if (wail_result == socd::TiltDown) {
x = -0.5f;
y = 0.5f;
}
} else {
// lefty
if (wail_result == socd::TiltUp) {
x = 0.f;
y = 0.f;
} else if (wail_result == socd::TiltDown) {
x = 0.5f;
y = 0.f;
}
}
// convert x,y,z to unsigned integer values that the game expects
const uint16_t x_int = x * ARENA_MODEL_WAIL_MAX * 2;
const uint16_t y_int = y * ARENA_MODEL_WAIL_MAX * 2;
+29 -12
View File
@@ -1,5 +1,7 @@
#include "bi2x_hook.h"
#if SPICE64
#include <cstdint>
#include "util/detour.h"
#include "util/logging.h"
@@ -24,17 +26,28 @@ namespace games::iidx {
uint8_t data[0x10];
};
struct AIO_NMGR_IOB2 {
uint8_t dummy0[0x50];
struct AIO_NMGR_IOB2_VTABLE {
// other functions here, but they never get called
uint8_t dummy[0x50];
void (__fastcall *pAIO_NMGR_IOB_BeginManage)(int64_t a1);
uint8_t dummy1[0x9A0];
};
struct AIO_NMGR_IOB2 {
AIO_NMGR_IOB2_VTABLE *vptr;
uint8_t dummy[0x9F0];
};
// confirmed 0x9F8 in iidx27, iidx32 aioNMgrIob2_Create
static_assert(sizeof(AIO_NMGR_IOB2) == 0x9F8);
struct AIO_IOB2_BI2X_TDJ {
// who knows
uint8_t data[0x13F8];
};
// confirmed 0x13F8 in iidx27, iidx32 in aioIob2Bi2xTDJ_Create
static_assert(sizeof(AIO_IOB2_BI2X_TDJ) == 0x13F8);
struct AIO_IOB2_BI2X_TDJ__DEVSTATUS {
// of course you could work with variables here
uint8_t buffer[0xCA];
@@ -78,7 +91,7 @@ namespace games::iidx {
// libaio-iob.dll
typedef AC_HNDLIF* (__fastcall *aioIob2Bi2x_OpenSciUsbCdc_t)(uint8_t device_num);
typedef int64_t (__fastcall *aioIob2Bi2x_WriteFirmGetState_t)(int64_t a1);
typedef AIO_NMGR_IOB2** (__fastcall *aioNMgrIob2_Create_t)(AC_HNDLIF *a1, unsigned int a2);
typedef AIO_NMGR_IOB2* (__fastcall *aioNMgrIob2_Create_t)(AC_HNDLIF *a1, unsigned int a2);
/*
* function pointers
@@ -443,7 +456,7 @@ namespace games::iidx {
}
}
static AC_HNDLIF* __fastcall aioIob2Bi2X_OpenSciUsbCdc(uint8_t device_num) {
static AC_HNDLIF* __fastcall aioIob2Bi2x_OpenSciUsbCdc(uint8_t device_num) {
if (acHndlif == nullptr) {
acHndlif = new AC_HNDLIF;
memset(acHndlif->data, 0x0, sizeof(acHndlif->data));
@@ -456,14 +469,16 @@ namespace games::iidx {
log_info("bi2x_hook", "AIO_NMGR_IOB::BeginManage");
};
static AIO_NMGR_IOB2** __fastcall aioNMgrIob2_Create(AC_HNDLIF *a1, unsigned int a2) {
static AIO_NMGR_IOB2* __fastcall aioNMgrIob2_Create(AC_HNDLIF *a1, unsigned int a2) {
if (aioNmgrIob2 == nullptr) {
aioNmgrIob2 = new AIO_NMGR_IOB2;
memset(aioNmgrIob2, 0x0, sizeof(AIO_NMGR_IOB2));
aioNmgrIob2->pAIO_NMGR_IOB_BeginManage = AIO_NMGR_IOB_BeginManageStub;
aioNmgrIob2 = new AIO_NMGR_IOB2{};
aioNmgrIob2->vptr = new AIO_NMGR_IOB2_VTABLE{};
aioNmgrIob2->vptr->pAIO_NMGR_IOB_BeginManage = AIO_NMGR_IOB_BeginManageStub;
}
log_info("bi2x_hook", "aioNMgrIob2_Create");
return &aioNmgrIob2;
log_info("bi2x_hook", "aioNMgrIob2_Create returned {}, size=0x{:x}, vptr @ {}",
fmt::ptr(aioNmgrIob2), sizeof(*aioNmgrIob2), fmt::ptr(aioNmgrIob2->vptr));
return aioNmgrIob2;
}
static int64_t __fastcall aioIob2Bi2x_WriteFirmGetState(int64_t a1) {
@@ -520,10 +535,12 @@ namespace games::iidx {
// hook IOB
const auto libaioIobDll = "libaio-iob.dll";
detour::trampoline_try(libaioIobDll, "aioIob2Bi2x_OpenSciUsbCdc",
aioIob2Bi2X_OpenSciUsbCdc, &aioIob2Bi2x_OpenSciUsbCdc_orig);
aioIob2Bi2x_OpenSciUsbCdc, &aioIob2Bi2x_OpenSciUsbCdc_orig);
detour::trampoline_try(libaioIobDll, "aioIob2Bi2x_WriteFirmGetState",
aioIob2Bi2x_WriteFirmGetState, &aioIob2Bi2x_WriteFirmGetState_orig);
detour::trampoline_try(libaioIobDll, "aioNMgrIob2_Create",
aioNMgrIob2_Create, &aioNMgrIob2_Create_orig);
}
}
#endif
+4
View File
@@ -1,6 +1,10 @@
#pragma once
#if SPICE64
namespace games::iidx {
void bi2x_hook_init();
}
#endif
+24 -5
View File
@@ -29,6 +29,8 @@
#include "util/libutils.h"
#include "util/memutils.h"
#include "util/sigscan.h"
#include "util/socd_cleaner.h"
#include "util/time.h"
#include "util/utils.h"
#include "launcher/signal.h"
@@ -251,6 +253,8 @@ namespace games::iidx {
}
}
#if SPICE64
typedef void* (*aioIob2Bi2x_CreateWriteFirmContext_t)(unsigned int, int);
static aioIob2Bi2x_CreateWriteFirmContext_t aioIob2Bi2x_CreateWriteFirmContext_orig = nullptr;
@@ -269,6 +273,8 @@ namespace games::iidx {
return nullptr;
}
#endif
IIDXGame::IIDXGame() : Game("Beatmania IIDX") {
logger::hook_add(log_hook, this);
}
@@ -319,6 +325,8 @@ namespace games::iidx {
devicehook_add(new IIDXFMSerialHandle());
}
#if SPICE64
// check for new I/O DLL
auto aio = libutils::try_library("libaio.dll");
if (aio != nullptr) {
@@ -384,6 +392,8 @@ namespace games::iidx {
}
}
#endif
if (hooks::audio::ENABLED) {
apply_audio_hacks();
} else {
@@ -499,6 +509,16 @@ namespace games::iidx {
" monitor in Windows settings before launching the game"
});
}
// socd
socd::ALGORITHM = socd::SocdAlgorithm::Neutral;
if (options->at(launcher::Options::IIDXDigitalTTSocd).is_active()) {
if (options->at(launcher::Options::IIDXDigitalTTSocd).value_text() == "last") {
socd::ALGORITHM = socd::SocdAlgorithm::PreferRecent;
} else if (options->at(launcher::Options::IIDXDigitalTTSocd).value_text() == "first") {
socd::ALGORITHM = socd::SocdAlgorithm::PreferFirst;
}
}
}
void IIDXGame::detach() {
@@ -722,13 +742,12 @@ namespace games::iidx {
bool ttpm_alt = GameAPI::Buttons::getState(RI_MGR, buttons.at(
player != 0 ? Buttons::P2_TTPlusMinusAlt : Buttons::P1_TTPlusMinusAlt));
// TT+
if (ttp)
const auto tt_socd = socd::socd_clean(player, ttm, ttp, get_performance_milliseconds());
if (tt_socd == socd::SocdCW) {
IIDXIO_TT_STATE[player] += change;
// TT-
if (ttm)
} else if (tt_socd == socd::SocdCCW) {
IIDXIO_TT_STATE[player] -= change;
}
// TT+/-
bool ttpm_rising_edge = !IIDXIO_TT_PRESSED[player] && ttpm;
+11
View File
@@ -47,6 +47,7 @@ namespace games {
static robin_hood::unordered_map<std::string, std::vector<Button>> buttons_keypads;
static robin_hood::unordered_map<std::string, std::vector<Button>> buttons_overlay;
static robin_hood::unordered_map<std::string, std::string> buttons_help;
static robin_hood::unordered_map<std::string, std::string> analogs_help;
static robin_hood::unordered_map<std::string, std::vector<Analog> &> analogs;
static robin_hood::unordered_map<std::string, std::vector<Light> &> lights;
static robin_hood::unordered_map<std::string, std::vector<Option>> options;
@@ -107,6 +108,7 @@ namespace games {
games.push_back(gitadora);
buttons.insert({ gitadora, gitadora::get_buttons() });
buttons_help.insert({ gitadora, gitadora::get_buttons_help() });
analogs_help.insert({ gitadora, gitadora::get_analogs_help() });
analogs.insert({ gitadora, gitadora::get_analogs() });
lights.insert({ gitadora, gitadora::get_lights() });
file_hints[gitadora].emplace_back("gdxg.dll");
@@ -349,6 +351,15 @@ namespace games {
return it->second;
}
std::string get_analogs_help(const std::string &game) {
initialize();
auto it = analogs_help.find(game);
if (it == analogs_help.end()) {
return "";
}
return it->second;
}
static std::vector<Button> gen_buttons_keypads(const std::string &game) {
auto buttons = GameAPI::Buttons::getButtons(game);
std::vector<std::string> names;
+1
View File
@@ -58,6 +58,7 @@ namespace games {
const std::vector<std::string> &get_games();
std::vector<Button> *get_buttons(const std::string &game);
std::string get_buttons_help(const std::string &game);
std::string get_analogs_help(const std::string &game);
std::vector<Button> *get_buttons_keypads(const std::string &game);
std::vector<Button> *get_buttons_overlay(const std::string &game);
std::vector<Analog> *get_analogs(const std::string &game);
+100 -36
View File
@@ -1,5 +1,7 @@
#include "bi2x_hook.h"
#if SPICE64
#include <cstdint>
#include "util/detour.h"
#include "util/logging.h"
@@ -10,12 +12,22 @@
#include "launcher/options.h"
#include "io.h"
#include "games/sdvx/sdvx.h"
#include "util/socd_cleaner.h"
#include "util/tapeled.h"
#include "util/time.h"
#include "acioemu/icca.h"
#define DEBUG_VERBOSE 0
#if DEBUG_VERBOSE
#define log_debug(module, format_str, ...) logger::push( \
LOG_FORMAT("M", module, format_str, ## __VA_ARGS__), logger::Style::GREY)
#else
#define log_debug(module, format_str, ...)
#endif
namespace games::sdvx {
constexpr bool BI2X_PASSTHROUGH = false;
bool BI2X_INITIALIZED = false;
/*
* class definitions
@@ -26,20 +38,54 @@ namespace games::sdvx {
uint8_t data[0x10];
};
struct AIO_NMGR_IOB2 {
uint8_t dummy0[0x50];
struct AIO_NMGR_IOB2_VTABLE {
// other functions here, but they never get called
uint8_t dummy[0x50];
void (__fastcall *pAIO_NMGR_IOB_BeginManage)(int64_t a1);
uint8_t dummy1[0x9A0];
};
struct AIO_NMGR_IOB2 {
AIO_NMGR_IOB2_VTABLE *vptr;
uint8_t dummy[0x9F0];
};
// confirmed in EG final in aioNMgrIob2_Create
static_assert(sizeof(AIO_NMGR_IOB2) == 0x9F8);
struct AIO_IOB2_BI2X_UFC {
// who knows
uint8_t data[0x13F8];
uint8_t data[0x39D8];
};
// confirmed in EG final in aioIob2Bi2xUFC_Create
static_assert(sizeof(AIO_IOB2_BI2X_UFC) == 0x39D8);
struct AIO_IOB2_BI2X_UFC__INPUT {
uint8_t DevIoCounter;
uint8_t bExIoAErr;
uint8_t bExIoBErr;
uint8_t bPcPowerOn;
uint8_t bPcPowerCheck;
uint8_t CoinCount;
uint8_t bTest;
uint8_t bService;
uint8_t bCoinSw;
uint8_t bCoinJam;
uint8_t bHPDetect;
};
struct AIO_IOB2_BI2X_UFC__DEVSTATUS {
// of course you could work with variables here
uint8_t buffer[0x19E];
uint8_t InputCounter;
uint8_t OutputCounter;
uint8_t IoResetCounter;
uint8_t TapeLedCounter;
uint8_t TapeLedRate[8];
AIO_IOB2_BI2X_UFC__INPUT Input;
uint8_t unk_1[289];
uint16_t AnalogLeft;
uint16_t AnalogRight;
uint8_t Buttons;
uint8_t unk_2[97];
};
/*
@@ -66,7 +112,7 @@ namespace games::sdvx {
// libaio-iob.dll
typedef AC_HNDLIF* (__fastcall *aioIob2Bi2x_OpenSciUsbCdc_t)(uint8_t device_num);
typedef int64_t (__fastcall *aioIob2Bi2x_WriteFirmGetState_t)(int64_t a1);
typedef AIO_NMGR_IOB2** (__fastcall *aioNMgrIob2_Create_t)(AC_HNDLIF *a1, unsigned int a2);
typedef AIO_NMGR_IOB2* (__fastcall *aioNMgrIob2_Create_t)(AC_HNDLIF *a1, unsigned int a2);
/*
* function pointers
@@ -133,8 +179,8 @@ namespace games::sdvx {
AIO_IOB2_BI2X_UFC__GetDeviceStatus_orig(This, status);
}
status->buffer[0] = count;
status->buffer[12] = count;
status->InputCounter = count;
status->Input.DevIoCounter = count;
count++;
// get buttons
@@ -142,41 +188,51 @@ namespace games::sdvx {
// control buttons
if (GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::Test]))
status->buffer[18] = 0x01;
status->Input.bTest = 0x01;
if (GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::Service]))
status->buffer[19] = 0x01;
status->Input.bService = 0x01;
if (GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::CoinMech]))
status->buffer[20] = 0x01;
status->Input.bCoinSw = 0x01;
if (GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::Start]))
status->buffer[316] |= 0x01;
status->Buttons |= 0x01;
if (GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::BT_A]))
status->buffer[316] |= 0x02;
status->Buttons |= 0x02;
if (GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::BT_B]))
status->buffer[316] |= 0x04;
status->Buttons |= 0x04;
if (GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::BT_C]))
status->buffer[316] |= 0x08;
status->Buttons |= 0x08;
if (GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::BT_D]))
status->buffer[316] |= 0x10;
status->Buttons |= 0x10;
if (GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::FX_L]))
status->buffer[316] |= 0x20;
status->Buttons |= 0x20;
if (GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::FX_R]))
status->buffer[316] |= 0x40;
status->Buttons |= 0x40;
if (GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::Headphone]))
status->buffer[22] = 0x01;
status->Input.bHPDetect = 0x01;
status->Input.CoinCount += eamuse_coin_get_stock();
const auto now = get_performance_milliseconds();
// volume left
if (GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::VOL_L_Left])) {
const auto vol_l_state = socd::socd_clean(0,
GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::VOL_L_Left]),
GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::VOL_L_Right]),
now);
if (vol_l_state == socd::SocdCCW) {
VOL_L -= ((uint16_t)DIGITAL_KNOB_SENS * 4);
}
if (GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::VOL_L_Right])) {
} else if (vol_l_state == socd::SocdCW) {
VOL_L += ((uint16_t)DIGITAL_KNOB_SENS * 4);
}
// volume right
if (GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::VOL_R_Left])) {
const auto vol_r_state = socd::socd_clean(1,
GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::VOL_R_Left]),
GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::VOL_R_Right]),
now);
if (vol_r_state == socd::SocdCCW) {
VOL_R -= ((uint16_t)DIGITAL_KNOB_SENS * 4);
}
if (GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::VOL_R_Right])) {
} else if (vol_r_state == socd::SocdCW) {
VOL_R += ((uint16_t)DIGITAL_KNOB_SENS * 4);
}
@@ -189,8 +245,14 @@ namespace games::sdvx {
vol_right += (uint16_t) (GameAPI::Analogs::getState(RI_MGR, analogs[Analogs::VOL_R]) * 65535);
}
*((uint16_t*) &status->buffer[312]) = vol_left;
*((uint16_t*) &status->buffer[314]) = vol_right;
status->AnalogLeft = vol_left;
status->AnalogRight = vol_right;
log_debug(
"bi2x_hook",
"knobs = {} {}",
status->AnalogLeft,
status->AnalogRight);
}
static void __fastcall AIO_IOB2_BI2X_UFC__IoReset(AIO_IOB2_BI2X_UFC *This,
@@ -345,21 +407,21 @@ namespace games::sdvx {
log_info("bi2x_hook", "AIO_NMGR_IOB::BeginManage");
}
static AIO_NMGR_IOB2** __fastcall aioNMgrIob2_Create(AC_HNDLIF *a1, unsigned int a2) {
static AIO_NMGR_IOB2* __fastcall aioNMgrIob2_Create(AC_HNDLIF *a1, unsigned int a2) {
if (aioNmgrIob2 == nullptr) {
aioNmgrIob2 = new AIO_NMGR_IOB2;
memset(aioNmgrIob2, 0x0, sizeof(AIO_NMGR_IOB2));
aioNmgrIob2->pAIO_NMGR_IOB_BeginManage = AIO_NMGR_IOB_BeginManageStub;
aioNmgrIob2 = new AIO_NMGR_IOB2{};
aioNmgrIob2->vptr = new AIO_NMGR_IOB2_VTABLE{};
aioNmgrIob2->vptr->pAIO_NMGR_IOB_BeginManage = AIO_NMGR_IOB_BeginManageStub;
}
log_info("bi2x_hook", "aioNMgrIob2_Create");
BI2X_INITIALIZED = true;
log_info("bi2x_hook", "aioNMgrIob2_Create returned {}, size=0x{:x}, vptr @ {}",
fmt::ptr(aioNmgrIob2), sizeof(*aioNmgrIob2), fmt::ptr(aioNmgrIob2->vptr));
// enable hack to make PIN pad work for KFC in BI2X mode
// this explicit check in the I/O init path is necessary
// (as opposed to just doing a check for "isValkyrieCabMode?")
// because there are hex edits that allow you to use legacy (KFC/BIO2) IO while in Valk mode
acioemu::ICCA_DEVICE_HACK = true;
return &aioNmgrIob2;
return aioNmgrIob2;
}
static int64_t __fastcall aioIob2Bi2x_WriteFirmGetState(int64_t a1) {
@@ -411,3 +473,5 @@ namespace games::sdvx {
aioNMgrIob2_Create, &aioNMgrIob2_Create_orig);
}
}
#endif
+4
View File
@@ -1,6 +1,10 @@
#pragma once
#if SPICE64
namespace games::sdvx {
void bi2x_hook_init();
}
#endif
+15 -5
View File
@@ -2,7 +2,6 @@
#include <external/robin_hood.h>
#include "avs/game.h"
#include "games/shared/lcdhandle.h"
#include "games/io.h"
#include "hooks/audio/audio.h"
@@ -19,6 +18,8 @@
#include "util/detour.h"
#include "util/logging.h"
#include "util/sigscan.h"
#include "util/socd_cleaner.h"
#include "util/time.h"
#include "util/libutils.h"
#include "misc/wintouchemu.h"
#include "misc/eamuse.h"
@@ -331,9 +332,8 @@ namespace games::sdvx {
}
#ifdef SPICE64 // SDVX5+ specific code
this->VALKYRIE_MODEL = avs::game::SPEC[0] == 'G' || avs::game::SPEC[0] == 'H';
// check -monitor + UFC mode
if (!GRAPHICS_WINDOWED && D3D9_ADAPTER.has_value() && this->VALKYRIE_MODEL) {
if (!GRAPHICS_WINDOWED && D3D9_ADAPTER.has_value() && is_valkyrie_model()) {
SHOW_VM_MONITOR_WARNING = true;
log_warning(
"sdvx",
@@ -362,6 +362,16 @@ namespace games::sdvx {
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() {
@@ -370,7 +380,7 @@ namespace games::sdvx {
#ifdef SPICE64 // SDVX5+ specific code
// LCD handle
if (!this->VALKYRIE_MODEL) {
if (!is_valkyrie_model()) {
devicehook_init();
devicehook_add(new games::shared::LCDHandle());
}
@@ -409,7 +419,7 @@ namespace games::sdvx {
nvapi_hook::initialize(avs::game::DLL_INSTANCE);
}
if (this->VALKYRIE_MODEL) {
if (is_valkyrie_model()) {
// hook touch window
// in windowed mode, game can accept mouse input on the second screen
if (!NATIVETOUCH && !GRAPHICS_WINDOWED) {
+8 -6
View File
@@ -4,6 +4,7 @@
#include <optional>
#include <cstdint>
#include "avs/game.h"
#include "games/game.h"
namespace games::sdvx {
@@ -20,12 +21,18 @@ namespace games::sdvx {
extern bool NATIVETOUCH;
extern uint8_t DIGITAL_KNOB_SENS;
extern std::optional<std::string> ASIO_DRIVER;
extern bool BI2X_INITIALIZED;
extern SdvxOverlayPosition OVERLAY_POS;
// states
extern bool SHOW_VM_MONITOR_WARNING;
static inline bool is_valkyrie_model() {
return (
avs::game::is_model("KFC") &&
(avs::game::SPEC[0] == 'G' || avs::game::SPEC[0] == 'H')
);
}
class SDVXGame : public games::Game {
public:
SDVXGame();
@@ -34,11 +41,6 @@ namespace games::sdvx {
virtual void post_attach() override;
virtual void detach() override;
private:
// don't be tempted to make this into a public or a global variable
// various modules need to check if VM mode is active and they should
// not depend on the fact that SDVX module is active (since it can be
// inactive on cabs or partial I/O setup)
bool VALKYRIE_MODEL = false;
void detect_sound_output_device();
};
}
@@ -82,7 +82,10 @@ HRESULT STDMETHODCALLTYPE WrappedIMMDevice::Activate(
return ret;
}
if (iid == IID_IAudioClient) {
// almost all games request IAudioClient
// so far we have not seen any games request IAudioClient2
// SDVX EG Final uses IID_IAudioClient3, but only when shared mode patch is on
if (iid == IID_IAudioClient || iid == IID_IAudioClient3) {
// prevent initialization recursion when using some ASIO backends that proxy to DirectSound, WASAPI, or WDM
// like ASIO4All or FlexASIO
@@ -92,23 +95,18 @@ HRESULT STDMETHODCALLTYPE WrappedIMMDevice::Activate(
}
std::lock_guard initialize_guard(hooks::audio::INITIALIZE_LOCK, std::adopt_lock);
auto client = reinterpret_cast<IAudioClient *>(*ppInterface);
// release old audio client if initialized
if (hooks::audio::CLIENT) {
hooks::audio::CLIENT->Release();
}
/*
ret = wrap_audio_client(pReal, dwClsCtx, pActivationParams, &client);
if (FAILED(ret)) {
return ret;
IAudioClient *client = nullptr;
if (iid == IID_IAudioClient) {
client = wrap_audio_client(reinterpret_cast<IAudioClient *>(*ppInterface));
} else { // IID_IAudioClient3
client = wrap_audio_client3(reinterpret_cast<IAudioClient3 *>(*ppInterface));
}
*/
client = wrap_audio_client(client);
*ppInterface = client;
// persist the audio client
hooks::audio::CLIENT = client;
hooks::audio::CLIENT->AddRef();
@@ -10,6 +10,7 @@
#include "hooks/audio/implementations/asio.h"
#include "hooks/audio/implementations/wave_out.h"
//#include "util/co_task_mem_ptr.h"
#include "util/utils.h"
#include "defs.h"
#include "dummy_audio_client.h"
@@ -40,114 +41,9 @@ static void fix_rec_format(WAVEFORMATEX *pFormat) {
pFormat->nAvgBytesPerSec = pFormat->nSamplesPerSec * pFormat->nBlockAlign;
}
// TODO(felix): is it appropriate to automatically switch to shared mode? should we do a
// `MessageBox` to notify the user?
/*
static bool check_for_exclusive_access(IAudioClient *client) {
static bool checked_once = false;
static bool previous_check_result = false;
CoTaskMemPtr<WAVEFORMATEX> mix_format;
REFERENCE_TIME requested_duration = 0;
if (checked_once) {
return previous_check_result;
}
if (audio::BACKEND.has_value()) {
return false;
}
// scope function so it has access to the local static variables
auto set_result = [](bool result) {
checked_once = true;
previous_check_result = result;
return result;
};
HRESULT ret = client->GetMixFormat(mix_format.ppv());
if (FAILED(ret)) {
PRINT_FAILED_RESULT("IAudioClient::GetMixFormat", ret);
return set_result(false);
}
log_info("audio::wasapi", "Mix Format:");
print_format(mix_format.data());
ret = client->IsFormatSupported(AUDCLNT_SHAREMODE_EXCLUSIVE, mix_format.data(), nullptr);
if (ret == AUDCLNT_E_UNSUPPORTED_FORMAT) {
auto mix_format_ex = reinterpret_cast<WAVEFORMATEXTENSIBLE *>(mix_format.data());
log_warning("audio::wasapi", "device does not natively support the mix format, converting to PCM");
if (mix_format->wFormatTag == WAVE_FORMAT_EXTENSIBLE &&
IsEqualGUID(GUID_KSDATAFORMAT_SUBTYPE_IEEE_FLOAT, mix_format_ex->SubFormat))
{
mix_format_ex->Format.wBitsPerSample = 16;
mix_format_ex->Format.nBlockAlign = mix_format_ex->Format.nChannels * (mix_format_ex->Format.wBitsPerSample / 8);
mix_format_ex->Format.nAvgBytesPerSec = mix_format_ex->Format.nSamplesPerSec * mix_format_ex->Format.nBlockAlign;
mix_format_ex->Samples.wValidBitsPerSample = 16;
mix_format_ex->SubFormat = GUID_KSDATAFORMAT_SUBTYPE_PCM;
} else if (mix_format->wFormatTag == WAVE_FORMAT_IEEE_FLOAT) {
mix_format->wBitsPerSample = 16;
mix_format->nBlockAlign = mix_format->nChannels * (mix_format->wBitsPerSample / 8);
mix_format->nAvgBytesPerSec = mix_format->nSamplesPerSec * mix_format->nBlockAlign;
mix_format->wFormatTag = WAVE_FORMAT_PCM;
} else {
log_warning("audio::wasapi", "mix format is not a floating point format");
return set_result(false);
}
ret = client->IsFormatSupported(AUDCLNT_SHAREMODE_EXCLUSIVE, mix_format.data(), nullptr);
if (FAILED(ret)) {
log_warning("audio::wasapi", "mix format is not supported");
return set_result(false);
}
}
ret = client->GetDevicePeriod(nullptr, &requested_duration);
if (FAILED(ret)) {
PRINT_FAILED_RESULT("IAudioClient::GetDevicePeriod", ret);
return false;
}
ret = client->Initialize(
AUDCLNT_SHAREMODE_EXCLUSIVE,
AUDCLNT_STREAMFLAGS_EVENTCALLBACK,
requested_duration,
requested_duration,
mix_format.data(),
nullptr);
if (ret == AUDCLNT_E_BUFFER_SIZE_NOT_ALIGNED || SUCCEEDED(ret)) {
log_info("audio::wasapi", "exclusive mode is available, disabling backend");
return set_result(true);
} else {
log_warning("audio::wasapi", "exclusive mode is not available, enabling backend, hr={}", FMT_HRESULT(ret));
}
return set_result(false);
}
HRESULT wrap_audio_client(
IMMDevice *device,
DWORD cls_ctx,
PROPVARIANT *activation_params,
IAudioClient **audio_client)
{
auto exclusive_available = check_for_exclusive_access(*audio_client);
(*audio_client)->Stop();
(*audio_client)->Reset();
(*audio_client)->Release();
*audio_client = nullptr;
SAFE_CALL("IMMDevice", "Activate", device->Activate(
IID_IAudioClient,
cls_ctx,
activation_params,
reinterpret_cast<void **>(audio_client)));
*/
IAudioClient *wrap_audio_client(IAudioClient *audio_client) {
log_misc("audio::wasapi", "wrapping IAudioClient");
AudioBackend *backend = nullptr;
bool requires_dummy = false;
@@ -164,9 +60,6 @@ IAudioClient *wrap_audio_client(IAudioClient *audio_client) {
break;
}
}
//} else if (!exclusive_available) {
// backend = new WaveOutBackend();
//}
IAudioClient *new_client;
@@ -182,6 +75,23 @@ IAudioClient *wrap_audio_client(IAudioClient *audio_client) {
return new_client;
}
IAudioClient3 *wrap_audio_client3(IAudioClient3 *audio_client) {
// TODO: ASIO backend for IAudioClient3, if there is a game that needs it
log_misc("audio::wasapi", "wrapping IAudioClient3");
if (hooks::audio::BACKEND.has_value()) {
log_fatal(
"audio::wasapi",
"IAudioClient3 does not currently support backends! clear -audiobackend and try again");
}
if (hooks::audio::USE_DUMMY) {
log_fatal(
"audio::wasapi",
"IAudioClient3 does not currently support dummy context, clear -audiodummy and try again");
}
return new WrappedIAudioClient(audio_client, nullptr);
}
// IUnknown
HRESULT STDMETHODCALLTYPE WrappedIAudioClient::QueryInterface(REFIID riid, void **ppvObj) {
@@ -190,8 +100,8 @@ HRESULT STDMETHODCALLTYPE WrappedIAudioClient::QueryInterface(REFIID riid, void
}
if (riid == IID_WrappedIAudioClient ||
riid == IID_IAudioClient)
{
riid == IID_IAudioClient ||
riid == IID_IAudioClient3) {
this->AddRef();
*ppvObj = this;
@@ -282,20 +192,6 @@ HRESULT STDMETHODCALLTYPE WrappedIAudioClient::Initialize(
}
log_info("audio::wasapi", "IAudioClient::Initialize success, hr={}", FMT_HRESULT(ret));
/*
if (ShareMode == AUDCLNT_SHAREMODE_SHARED) {
IAudioClockAdjustment *clock = nullptr;
SAFE_CALL("IAudioClient", "GetService", pReal->GetService(
IID_IAudioClockAdjustment,
reinterpret_cast<void **>(&clock)));
SAFE_CALL("IAudioClockAdjustment", "SetSampleRate", clock->SetSampleRate(
static_cast<float>(pFormat->nSamplesPerSec)));
}
*/
copy_wave_format(&hooks::audio::FORMAT, pFormat);
return ret;
@@ -482,3 +378,93 @@ HRESULT STDMETHODCALLTYPE WrappedIAudioClient::GetService(REFIID riid, void **pp
return ret;
}
HRESULT STDMETHODCALLTYPE WrappedIAudioClient::IsOffloadCapable(
AUDIO_STREAM_CATEGORY Category,
BOOL *pbOffloadCapable) {
WRAP_VERBOSE;
CHECK_RESULT(pReal3->IsOffloadCapable(Category, pbOffloadCapable));
}
HRESULT STDMETHODCALLTYPE WrappedIAudioClient::SetClientProperties(
const AudioClientProperties *pProperties) {
WRAP_VERBOSE;
CHECK_RESULT(pReal3->SetClientProperties(pProperties));
}
HRESULT STDMETHODCALLTYPE WrappedIAudioClient::GetBufferSizeLimits(
const WAVEFORMATEX *pFormat,
BOOL bEventDriven,
REFERENCE_TIME *phnsMinBufferDuration,
REFERENCE_TIME *phnsMaxBufferDuration) {
WRAP_VERBOSE;
CHECK_RESULT(pReal3->GetBufferSizeLimits(
pFormat,
bEventDriven,
phnsMinBufferDuration,
phnsMaxBufferDuration));
}
HRESULT STDMETHODCALLTYPE WrappedIAudioClient::GetSharedModeEnginePeriod(
const WAVEFORMATEX *pFormat,
UINT32 *pDefaultPeriodInFrames,
UINT32 *pFundamentalPeriodInFrames,
UINT32 *pMinPeriodInFrames,
UINT32 *pMaxPeriodInFrames) {
WRAP_VERBOSE;
CHECK_RESULT(pReal3->GetSharedModeEnginePeriod(
pFormat,
pDefaultPeriodInFrames,
pFundamentalPeriodInFrames,
pMinPeriodInFrames,
pMaxPeriodInFrames));
}
HRESULT STDMETHODCALLTYPE WrappedIAudioClient::GetCurrentSharedModeEnginePeriod(
WAVEFORMATEX **ppFormat,
UINT32 *pCurrentPeriodInFrames) {
WRAP_VERBOSE;
CHECK_RESULT(pReal3->GetCurrentSharedModeEnginePeriod(
ppFormat,
pCurrentPeriodInFrames));
}
HRESULT STDMETHODCALLTYPE WrappedIAudioClient::InitializeSharedAudioStream(
DWORD StreamFlags,
UINT32 PeriodInFrames,
const WAVEFORMATEX *pFormat,
LPCGUID AudioSessionGuid) {
if (!pFormat) {
return E_POINTER;
}
// verbose output
log_info("audio::wasapi", "IAudioClient3::InitializeSharedAudioStream hook hit");
log_info("audio::wasapi", "... ShareMode : {}", share_mode_str(AUDCLNT_SHAREMODE_SHARED));
log_info("audio::wasapi", "... StreamFlags : {}", stream_flags_str(StreamFlags));
log_info("audio::wasapi", "... PeriodInFrames : {}", PeriodInFrames);
print_format(pFormat);
// call next
HRESULT ret = pReal3->InitializeSharedAudioStream(
StreamFlags,
PeriodInFrames,
pFormat,
AudioSessionGuid);
// check for failure
if (FAILED(ret)) {
PRINT_FAILED_RESULT("IAudioClient3", "InitializeSharedAudioStream", ret);
return ret;
}
log_info("audio::wasapi", "IAudioClient3::InitializeSharedAudioStream success, hr={}", FMT_HRESULT(ret));
copy_wave_format(&hooks::audio::FORMAT, pFormat);
return ret;
}
@@ -16,9 +16,16 @@ static const GUID IID_WrappedIAudioClient = {
};
IAudioClient *wrap_audio_client(IAudioClient *client);
IAudioClient3 *wrap_audio_client3(IAudioClient3 *client);
struct WrappedIAudioClient : IAudioClient {
explicit WrappedIAudioClient(IAudioClient *orig, AudioBackend *backend) : pReal(orig), backend(backend) {
struct WrappedIAudioClient : IAudioClient3 {
explicit WrappedIAudioClient(IAudioClient3 *orig3, AudioBackend *backend) :
pReal(orig3), pReal3(orig3), backend(backend) {
}
explicit WrappedIAudioClient(IAudioClient *orig, AudioBackend *backend) :
pReal(orig), pReal3(nullptr), backend(backend) {
}
WrappedIAudioClient(const WrappedIAudioClient &) = delete;
@@ -47,9 +54,43 @@ struct WrappedIAudioClient : IAudioClient {
HRESULT STDMETHODCALLTYPE GetService(REFIID riid, void **ppv) override;
#pragma endregion
IAudioClient *const pReal;
AudioBackend *const backend;
#pragma region IAudioClient2
HRESULT STDMETHODCALLTYPE IsOffloadCapable(
AUDIO_STREAM_CATEGORY Category,
BOOL *pbOffloadCapable) override;
HRESULT STDMETHODCALLTYPE SetClientProperties(
const AudioClientProperties *pProperties) override;
HRESULT STDMETHODCALLTYPE GetBufferSizeLimits(
const WAVEFORMATEX *pFormat,
BOOL bEventDriven,
REFERENCE_TIME *phnsMinBufferDuration,
REFERENCE_TIME *phnsMaxBufferDuration) override;
#pragma endregion
#pragma region IAudioClient3
HRESULT STDMETHODCALLTYPE GetSharedModeEnginePeriod(
const WAVEFORMATEX *pFormat,
UINT32 *pDefaultPeriodInFrames,
UINT32 *pFundamentalPeriodInFrames,
UINT32 *pMinPeriodInFrames,
UINT32 *pMaxPeriodInFrames) override;
HRESULT STDMETHODCALLTYPE GetCurrentSharedModeEnginePeriod(
WAVEFORMATEX **ppFormat,
UINT32 *pCurrentPeriodInFrames) override;
HRESULT STDMETHODCALLTYPE InitializeSharedAudioStream(
DWORD StreamFlags,
UINT32 PeriodInFrames,
const WAVEFORMATEX *pFormat,
LPCGUID AudioSessionGuid) override;
#pragma endregion
IAudioClient *const pReal;
IAudioClient3 *const pReal3;
AudioBackend *const backend;
bool exclusive_mode = false;
int frame_size = 0;
};
@@ -149,5 +149,12 @@ LowLatencyAudioClient *LowLatencyAudioClient::Create(IMMDevice *device) {
log_info("audio::lowlatency", "... max buffer size : {} samples ({} ms)", maxPeriod, 1000.0f * maxPeriod / pFormat->nSamplesPerSec);
log_info("audio::lowlatency", "... default buffer size : {} samples ({} ms)", defaultPeriod, 1000.0f * defaultPeriod / pFormat->nSamplesPerSec);
log_info("audio::lowlatency", "... Windows will use minimum buffer size (instead of default) for shared mode audio clients from now on");
if (minPeriod < defaultPeriod) {
log_info("audio::lowlatency", "minimum period is less than default period, you will see latency improvement");
} else {
log_warning("audio::lowlatency", "minimum period is not less than default period, you will see NO latency improvement");
log_warning("audio::lowlatency", "this an audio driver / hardware limitation");
}
return new LowLatencyAudioClient(audioClient);
}
@@ -41,6 +41,15 @@
#undef min
#endif
#define D3D9_BACKEND_DEBUG 0
#if D3D9_BACKEND_DEBUG
#define log_debug(module, format_str, ...) logger::push( \
LOG_FORMAT("M", module, format_str, ## __VA_ARGS__), logger::Style::GREY)
#else
#define log_debug(module, format_str, ...)
#endif
#define CHECK_RESULT(x) \
do { \
HRESULT __ret = (x); \
@@ -514,7 +523,7 @@ HRESULT STDMETHODCALLTYPE WrappedIDirect3D9::EnumAdapterModes(
if (!modified && games::iidx::is_tdj_fhd() && refresh < 110) {
if ((width == 1920 && height == 1080) ||
(width == 1280 && height == 720)){
log_misc(
log_debug(
"graphics::d3d9", "removing mode {}, {}x{} @ {}Hz (for TDJ FHD)",
Mode, width, height, refresh);
memset(pMode, 0, sizeof(*pMode));
@@ -527,7 +536,7 @@ HRESULT STDMETHODCALLTYPE WrappedIDirect3D9::EnumAdapterModes(
// - skip 75 and 90 Hz entries because LDJ game timing is messed up with it
// (TDJ should be fine as it assumes a fixed 60 Hz timing)
if (!modified && (width == 1360 || width == 1366 || refresh == 75 || refresh == 90)) {
log_misc(
log_debug(
"graphics::d3d9", "removing mode {}, {}x{} @ {}Hz (for LDJ/TDJ)",
Mode, width, height, refresh);
memset(pMode, 0, sizeof(*pMode));
@@ -535,7 +544,7 @@ HRESULT STDMETHODCALLTYPE WrappedIDirect3D9::EnumAdapterModes(
}
if (!modified && !games::iidx::TDJ_MODE && games::iidx::FORCE_720P && (height > 720)) {
log_misc(
log_debug(
"graphics::d3d9", "removing mode {}, {}x{} @ {}Hz (-iidxforce720p)",
Mode, width, height, refresh);
memset(pMode, 0, sizeof(*pMode));
@@ -803,6 +812,14 @@ HRESULT STDMETHODCALLTYPE WrappedIDirect3D9::CreateDevice(
"either use -graphics-force-refresh option or change the desktop resolution beforehand.");
}
if (!GRAPHICS_WINDOWED && num_adapters >= 2 && GRAPHICS_FORCE_REFRESH_SUB.has_value()) {
log_info("graphics::d3d9", "force sub refresh rate: {} => {} Hz (-graphics-force-refresh-sub option)",
pPresentationParameters[1].FullScreen_RefreshRateInHz,
GRAPHICS_FORCE_REFRESH_SUB.value());
pPresentationParameters[1].FullScreen_RefreshRateInHz = GRAPHICS_FORCE_REFRESH_SUB.value();
}
// force single adapter
if (GRAPHICS_FORCE_SINGLE_ADAPTER) {
log_info("graphics::d3d9", "disabling adapter group device with force single adapter mode");
@@ -1022,6 +1039,17 @@ HRESULT STDMETHODCALLTYPE WrappedIDirect3D9::CreateDeviceEx(
}
}
if (!GRAPHICS_WINDOWED && num_adapters >= 2 && GRAPHICS_FORCE_REFRESH_SUB.has_value()) {
log_info("graphics::d3d9", "force sub refresh rate: {} => {} Hz (-graphics-force-refresh-sub option)",
pPresentationParameters[1].FullScreen_RefreshRateInHz,
GRAPHICS_FORCE_REFRESH_SUB.value());
pPresentationParameters[1].FullScreen_RefreshRateInHz = GRAPHICS_FORCE_REFRESH_SUB.value();
if (pFullscreenDisplayMode) {
pFullscreenDisplayMode[1].RefreshRate = GRAPHICS_FORCE_REFRESH_SUB.value();
}
}
// force single adapter
if (GRAPHICS_FORCE_SINGLE_ADAPTER) {
log_info("graphics::d3d9", "disabling adapter group device with force single adapter mode");
@@ -1353,9 +1381,7 @@ void graphics_d3d9_on_present(
}
// for IIDX TDJ / SDVX UFC, handle subscreen
const bool is_vm =
avs::game::is_model("KFC") &&
(avs::game::SPEC[0] == 'G' || avs::game::SPEC[0] == 'H');
const bool is_vm = games::sdvx::is_valkyrie_model();
const bool is_tdj = avs::game::is_model("LDJ") && games::iidx::TDJ_MODE;
if (is_vm || is_tdj) {
graphics_d3d9_ldj_on_present(wrapped_device);
@@ -742,14 +742,12 @@ void SurfaceHook(IDirect3DDevice9 *pReal) {
const int w = param.BackBufferWidth;
const int h = param.BackBufferHeight;
if (cfg::SCREENRESIZE->enable_screen_resize) {
RECT rect {
0,
0,
(LONG)topSurface_width,
(LONG)topSurface_height,
};
pReal->ColorFill(topSurface, &rect, D3DCOLOR_XRGB(0, 0, 0));
// this code used to clear the surface using ColorFill on every call, but
// this turned out to be very expensive, leading to major frame drops in
// SDVX Live2D scenario
if (cfg::SCREENRESIZE->need_surface_clean) {
pReal->ColorFill(topSurface, nullptr, D3DCOLOR_XRGB(0, 0, 0));
cfg::SCREENRESIZE->need_surface_clean = false;
}
D3DLOCKED_RECT rect;
+18
View File
@@ -61,7 +61,9 @@ bool GRAPHICS_SHOW_CURSOR = false;
graphics_orientation GRAPHICS_ADJUST_ORIENTATION = ORIENTATION_NORMAL;
bool GRAPHICS_WINDOWED = false;
std::vector<HWND> GRAPHICS_WINDOWS;
std::optional<HWND> GRAPHICS_WINDOW_MAIN;
UINT GRAPHICS_FORCE_REFRESH = 0;
std::optional<uint32_t> GRAPHICS_FORCE_REFRESH_SUB;
std::optional<int> GRAPHICS_FORCE_VSYNC_BUFFER;
bool GRAPHICS_FORCE_SINGLE_ADAPTER = false;
bool GRAPHICS_PREVENT_SECONDARY_WINDOW = false;
@@ -362,10 +364,12 @@ static HWND WINAPI CreateWindowExA_hook(DWORD dwExStyle, LPCSTR lpClassName, LPC
}
// gfdm
bool is_gitadora_main_window = false;
if (avs::game::is_model({"J32", "J33", "K32", "K33", "L32", "L33", "M32"})) {
// set window name
if (!lpWindowName) {
lpWindowName = "GITADORA";
is_gitadora_main_window = true;
}
}
@@ -408,6 +412,11 @@ static HWND WINAPI CreateWindowExA_hook(DWORD dwExStyle, LPCSTR lpClassName, LPC
hWndParent, hMenu, hInstance, lpParam);
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) {
// TDJ windowed mode: remember the subscreen window handle for later
TDJ_SUBSCREEN_WINDOW = result;
@@ -430,6 +439,7 @@ static HWND WINAPI CreateWindowExA_hook(DWORD dwExStyle, LPCSTR lpClassName, LPC
}
disable_touch_indicators(result);
log_misc("graphics", "CreateWindowExA returned {}", fmt::ptr(result));
return result;
}
@@ -496,6 +506,7 @@ static HWND WINAPI CreateWindowExW_hook(DWORD dwExStyle, LPCWSTR lpClassName, LP
hWndParent, hMenu, hInstance, lpParam);
GRAPHICS_WINDOWS.push_back(result);
log_misc("graphics", "CreateWindowExW returned {}", fmt::ptr(result));
disable_touch_indicators(result);
return result;
}
@@ -660,6 +671,13 @@ static BOOL WINAPI SetWindowPos_hook(HWND hWnd, HWND hWndInsertAfter,
return TRUE;
}
// prevent gitadora arena model from shifting windows around if the user has preferences
if (GRAPHICS_WINDOWED && games::gitadora::is_arena_model() &&
GRAPHICS_WINDOW_MAIN.has_value() && hWnd == GRAPHICS_WINDOW_MAIN.value() &&
cfg::SCREENRESIZE->enable_window_resize) {
return TRUE;
}
// call original
return SetWindowPos_orig(hWnd, hWndInsertAfter, X, Y, cx, cy, uFlags);
}
+2
View File
@@ -31,7 +31,9 @@ extern bool GRAPHICS_SHOW_CURSOR;
extern bool GRAPHICS_WINDOWED;
extern graphics_orientation GRAPHICS_ADJUST_ORIENTATION;
extern std::vector<HWND> GRAPHICS_WINDOWS;
extern std::optional<HWND> GRAPHICS_WINDOW_MAIN;
extern UINT GRAPHICS_FORCE_REFRESH;
extern std::optional<uint32_t> GRAPHICS_FORCE_REFRESH_SUB;
extern std::optional<int> GRAPHICS_FORCE_VSYNC_BUFFER;
extern bool GRAPHICS_FORCE_SINGLE_ADAPTER;
extern bool GRAPHICS_PREVENT_SECONDARY_WINDOW;
@@ -56,8 +56,12 @@ void graphics_capture_initial_window(HWND hWnd) {
graphics_load_windowed_parameters();
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;
if (!GetClientRect(hWnd, &rect)) {
@@ -372,13 +376,19 @@ void graphics_update_window_style(HWND hWnd) {
return;
}
log_debug("graphics-windowed", "graphics_update_window_style called");
log_debug(
"graphics-windowed",
"graphics_update_window_style called for hWnd={}",
fmt::ptr(hWnd));
// update frame style
auto style = cfg::SCREENRESIZE->init_window_style;
auto style_ex = cfg::SCREENRESIZE->init_window_style_ex;
switch (cfg::SCREENRESIZE->window_decoration) {
case cfg::WindowDecorationMode::Borderless:
style &= ~WS_OVERLAPPEDWINDOW;
style_ex &= ~WS_EX_CLIENTEDGE;
style_ex &= ~WS_EX_WINDOWEDGE;
break;
case cfg::WindowDecorationMode::ResizableFrame:
style |= WS_OVERLAPPEDWINDOW;
@@ -393,11 +403,26 @@ void graphics_update_window_style(HWND hWnd) {
"graphics_update_window_style - calling SetWindowLong with Mode {}, style 0x{:x}",
static_cast<int>(cfg::SCREENRESIZE->window_decoration),
style);
SetWindowLong(hWnd, GWL_STYLE, style);
const auto ret = SetWindowLong(hWnd, GWL_STYLE, style);
(void)ret;
log_debug(
"graphics-windowed",
"graphics_update_window_style - SetWindowLong(GWL_STYLE) returned {:x}, GLE={}",
ret,
GetLastError());
const auto ret2 = SetWindowLong(hWnd, GWL_EXSTYLE, style_ex);
(void)ret2;
log_debug(
"graphics-windowed",
"graphics_update_window_style SetWindowLong(GWL_EXSTYLE) returned {:x}, GLE={}",
ret2,
GetLastError());
// SetWindowPos must be called after SetWindowLong if the frame style changed
// this will be done in WM_STYLECHANGED handler
log_debug("graphics-windowed", "graphics_update_window_style returned");
}
void graphics_update_z_order(HWND hWnd, bool always_on_top) {
+2 -2
View File
@@ -10,6 +10,7 @@
#include "avs/game.h"
#include "games/iidx/iidx.h"
#include "games/gitadora/gitadora.h"
#include "games/sdvx/sdvx.h"
#include "util/detour.h"
#include "util/logging.h"
#include "util/utils.h"
@@ -220,8 +221,7 @@ void hooks::lang::early_init() {
detour::trampoline_try("kernel32.dll", "GetOEMCP", GetOEMCP_hook, &GetOEMCP_orig);
#ifdef SPICE64 // SDVX5+ specific code
bool isValkyrieCabinetMode = avs::game::SPEC[0] == 'G' || avs::game::SPEC[0] == 'H';
if (avs::game::is_model("KFC") && isValkyrieCabinetMode) {
if (games::sdvx::is_valkyrie_model()) {
log_info("hooks::lang", "hooking GetSystemDefaultLCID");
detour::trampoline_try(
"kernel32.dll",
+25
View File
@@ -107,6 +107,7 @@
#include "util/libutils.h"
#include "util/logging.h"
#include "util/peb.h"
#include "util/socd_cleaner.h"
#include "util/sysutils.h"
#include "util/tapeled.h"
#include "util/time.h"
@@ -326,6 +327,10 @@ int main_implementation(int argc, char *argv[]) {
if (options[launcher::Options::GraphicsForceRefresh].is_active()) {
GRAPHICS_FORCE_REFRESH = options[launcher::Options::GraphicsForceRefresh].value_uint32();
}
if (options[launcher::Options::FullscreenSubRefreshRate].is_active()) {
GRAPHICS_FORCE_REFRESH_SUB =
options[launcher::Options::FullscreenSubRefreshRate].value_uint32();
}
if (options[launcher::Options::GraphicsForceSingleAdapter].value_bool()) {
GRAPHICS_FORCE_SINGLE_ADAPTER = true;
}
@@ -584,6 +589,9 @@ int main_implementation(int argc, char *argv[]) {
if (options[launcher::Options::GitaDoraCabinetType].is_active()) {
games::gitadora::CAB_TYPE = options[launcher::Options::GitaDoraCabinetType].value_uint32();
}
if (options[launcher::Options::GitaDoraWailHold].is_active()) {
socd::TILT_HOLD_MS = options[launcher::Options::GitaDoraWailHold].value_uint32();
}
if (options[launcher::Options::LoadNostalgiaModule].value_bool()) {
attach_nostalgia = true;
}
@@ -605,6 +613,17 @@ int main_implementation(int argc, char *argv[]) {
if (options[launcher::Options::GitaDoraTwoChannelAudio].value_bool()) {
games::gitadora::TWOCHANNEL = true;
}
if (options[launcher::Options::GitaDoraLefty].is_active()) {
const auto text = options[launcher::Options::GitaDoraLefty].value_text();
if (text == "p1") {
games::gitadora::P1_LEFTY = true;
} else if (text == "p2") {
games::gitadora::P2_LEFTY = true;
} else if (text == "both") {
games::gitadora::P1_LEFTY = true;
games::gitadora::P2_LEFTY = true;
}
}
if (options[launcher::Options::LoadDDRModule].value_bool()) {
attach_ddr = true;
}
@@ -959,6 +978,12 @@ int main_implementation(int argc, char *argv[]) {
if (options[launcher::Options::DisableOverlay].value_bool()) {
overlay::ENABLED = false;
}
if (options[launcher::Options::OverlayScaling].is_active() && !cfg::CONFIGURATOR_STANDALONE && !cfg_run) {
const auto val = options[launcher::Options::OverlayScaling].value_uint32();
if (10 <= val && val <= 400 && val != 100) {
overlay::UI_SCALE_PERCENT = options[launcher::Options::OverlayScaling].value_uint32();
}
}
if (options[launcher::Options::DisableAudioHooks].value_bool()) {
hooks::audio::ENABLED = false;
deferredlogs::defer_error_messages({
+12 -6
View File
@@ -12,9 +12,11 @@
#include "launcher/launcher.h"
#include "util/utils.h"
#define FOREGROUND_GREY (8)
#define FOREGROUND_WHITE (FOREGROUND_RED | FOREGROUND_BLUE | FOREGROUND_GREEN)
#define FOREGROUND_YELLOW (FOREGROUND_RED | FOREGROUND_GREEN)
#define FOREGROUND_GREY (8)
#define FOREGROUND_WHITE (FOREGROUND_RED | FOREGROUND_BLUE | FOREGROUND_GREEN)
#define FOREGROUND_YELLOW (FOREGROUND_RED | FOREGROUND_GREEN)
#define FOREGROUND_CYAN (FOREGROUND_GREEN | FOREGROUND_BLUE)
#define FOREGROUND_MAGENTA (FOREGROUND_RED | FOREGROUND_BLUE)
namespace logger {
@@ -103,9 +105,6 @@ namespace logger {
last_style = content.second;
switch (content.second) {
case Style::DEFAULT:
set_console_color(hTerminal, FOREGROUND_WHITE);
break;
case Style::GREY:
set_console_color(hTerminal, FOREGROUND_GREY);
break;
@@ -115,6 +114,13 @@ namespace logger {
case Style::RED:
set_console_color(hTerminal, FOREGROUND_RED);
break;
case Style::SPECIAL:
set_console_color(hTerminal, FOREGROUND_CYAN);
break;
case Style::DEFAULT:
default:
set_console_color(hTerminal, FOREGROUND_WHITE);
break;
}
}
+2 -1
View File
@@ -12,7 +12,8 @@ namespace logger {
DEFAULT = 0,
GREY = 1,
YELLOW = 2,
RED = 3
RED = 3,
SPECIAL = 4,
};
void start();
+102 -8
View File
@@ -221,9 +221,11 @@ static const std::vector<OptionDefinition> OPTION_DEFINITIONS = {
.category = "Graphics (Full Screen)",
},
{
.title = "Force Refresh Rate",
.title = "Force Monitor Refresh Rate",
.name = "graphics-force-refresh",
.desc = "Force the refresh rate for the primary display adapter; works in both full screen and windowed modes",
.desc =
"Attempt to change the refresh rate for the primary monitor before the game boots; "
"works in both full screen and windowed modes, but known to fail for some games",
.type = OptionType::Integer,
.category = "Graphics (Common)",
},
@@ -242,21 +244,34 @@ static const std::vector<OptionDefinition> OPTION_DEFINITIONS = {
},
{
// FullscreenOrientationFlip
.title = "Full Screen Orientation Swap",
.title = "Full Screen Orientation Swap (EXPERIMENTAL)",
.name = "forceresswap",
.desc =
"Allows you to play portrait games in in landscape (and vice versa) by transposing resolution and applying image scaling.\n\n"
"Works great for some games, but can COMPLETELY BREAK other games - YMMV!\n\n"
"Strongly consider combining this with -forceres option to render at monitor native resolution\n\n"
"WARNING: for SDVX, this messes with camera angle for note lanes, causing it to be zoomed out!",
"WARNING: for SDVX, this messes with camera angle for note lanes, causing it to be zoomed out, and causes performance issues "
"with Live2D!",
.type = OptionType::Bool,
.hidden = true,
.category = "Graphics (Full Screen)"
},
{
// FullscreenSubRefreshRate
.title = "Force Submonitor Refresh Rate (EXPERIMENTAL)",
.name = "graphics-force-refresh-sub",
.desc =
"Override fullscreen refresh rate requested by the game for second monitor, "
"useful if you have a sub monitor that is not quite exactly 60Hz.\n\n"
"WARNING: experimental as we have not done extensive testing to see if this causes desyncs",
.type = OptionType::Integer,
.category = "Graphics (Full Screen)"
},
{
// Graphics9On12
.title = "DirectX 9 on 12 (DEPRECATED - use -dx9on12 instead)",
.name = "9on12",
.desc = "Use D3D9On12 wrapper library, requires Windows 10 Insider Preview 18956 or later. Deprecated - use -dx9on12 instead",
.desc = "Use D3D9On12 wrapper library, requires Windows 10. Deprecated - use -dx9on12 instead",
.type = OptionType::Bool,
.hidden = true,
.category = "Graphics (Common)",
@@ -267,7 +282,7 @@ static const std::vector<OptionDefinition> OPTION_DEFINITIONS = {
.name = "sp2x-dx9on12",
.display_name = "dx9on12",
.aliases= "dx9on12",
.desc = "Use D3D9On12 wrapper library, requires Windows 10 Insider Preview 18956 or later. Has no effect games on that don't use DX9. Can cause some games to crash.\n\n"
.desc = "Use D3D9On12 wrapper library, requires Windows 10. Has no effect games on that don't use DX9. Can cause some games to crash.\n\n"
"Default: auto (use DX9 for most games, but turn on 9on12 for games that require it on non-NVIDIA GPUs)",
.type = OptionType::Enum,
.category = "Graphics (Common)",
@@ -383,12 +398,24 @@ static const std::vector<OptionDefinition> OPTION_DEFINITIONS = {
.disabled = true,
},
{
// DisableOverlay
.title = "Disable Spice Overlay",
.name = "overlaydisable",
.desc = "Disables the in-game overlay",
.type = OptionType::Bool,
.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
.title = "Auto Show FPS/Clock",
@@ -592,7 +619,7 @@ static const std::vector<OptionDefinition> OPTION_DEFINITIONS = {
{
.title = "IIDX TDJ Mode (Lightning Model)",
.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,
.game_name = "Beatmania IIDX",
.category = "Game Options",
@@ -609,6 +636,23 @@ static const std::vector<OptionDefinition> OPTION_DEFINITIONS = {
.game_name = "Beatmania IIDX",
.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
.title = "IIDX LDJ Force 720p (HD)",
@@ -792,6 +836,23 @@ static const std::vector<OptionDefinition> OPTION_DEFINITIONS = {
.game_name = "Sound Voltex",
.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
.title = "SDVX ASIO driver",
@@ -917,6 +978,39 @@ static const std::vector<OptionDefinition> OPTION_DEFINITIONS = {
.category = "Game Options",
.elements = {{"1", "DX"}, {"2", "SD"}, {"3", "SD2 - white cab"}},
},
{
// GitaDoraLefty
.title = "GitaDora Lefty Guitar (for Digital Wailing)",
.name = "gdlefty",
.desc = "Enables lefty mode, flipping motion sensor directions. Default: off.\n\n"
"Without this option, enabling LEFT in the game option will continuously trigger UP wail.\n\n"
"Has no effect if you are using analog bindings for X/Y axis; expectation is that your controller "
"handles this correctly (most do not, however).\n\n"
"As always, remember to restart the game after changing options. If you need to change in the game, "
"use the I/O panel overlay window",
.type = OptionType::Enum,
.game_name = "GitaDora",
.category = "Game Options",
.elements = {
{"off", "both righty"},
{"p1", "p1 is lefty"},
{"p2", "p2 is lefty"},
{"both", "both lefty"},
},
},
{
// GitaDoraWailHold
.title = "GitaDora Digital Wail Hold",
.name = "gdwailhold",
.desc = "For digital wail input, how long (in milliseconds) to hold the wail state. Practically, this controls "
"how sensitive wailing is.\n\n"
"Default: 100 (ms)\n\n"
"If set to 0, no hold will occur, which means you need to press the wail button longer to register; "
"default value of 100ms will cause the game to trigger wail even on a light tap of the button",
.type = OptionType::Integer,
.game_name = "GitaDora",
.category = "Game Options",
},
{
.title = "Force Load Jubeat Module",
.name = "jb",
@@ -2179,7 +2273,7 @@ static const std::vector<OptionDefinition> OPTION_DEFINITIONS = {
.display_name = "tapeledalgo",
.aliases= "tapeledalgo",
.desc = "For games with light arrays, determine the algorithm that is used to translate them into a single light binding in Lights tab. "
"Only applies to IIDX, SDVX, and DDR for now. Default: mid",
"Default: mid",
.type = OptionType::Enum,
.category = "I/O Options",
.elements = {
+6
View File
@@ -31,6 +31,7 @@ namespace launcher {
GraphicsForceRefresh,
FullscreenResolution,
FullscreenOrientationFlip,
FullscreenSubRefreshRate,
Graphics9On12,
spice2x_Dx9On12,
NoLegacy,
@@ -47,6 +48,7 @@ namespace launcher {
SOFTID,
VREnable,
DisableOverlay,
OverlayScaling,
spice2x_FpsAutoShow,
spice2x_FpsOpposite,
spice2x_SubScreenAutoShow,
@@ -66,6 +68,7 @@ namespace launcher {
IIDXBIO2FW,
IIDXTDJMode,
spice2x_IIDXDigitalTTSensitivity,
IIDXDigitalTTSocd,
spice2x_IIDXLDJForce720p,
spice2x_IIDXTDJSubSize,
spice2x_IIDXLEDFontSize,
@@ -83,6 +86,7 @@ namespace launcher {
SDVXDisableCameras,
SDVXNativeTouch,
spice2x_SDVXDigitalKnobSensitivity,
SDVXDigitalKnobSocd,
spice2x_SDVXAsioDriver,
spice2x_SDVXSubPos,
spice2x_SDVXSubRedraw,
@@ -96,6 +100,8 @@ namespace launcher {
LoadGitaDoraModule,
GitaDoraTwoChannelAudio,
GitaDoraCabinetType,
GitaDoraLefty,
GitaDoraWailHold,
LoadJubeatModule,
LoadReflecBeatModule,
LoadShogikaiModule,
+88 -42
View File
@@ -7,6 +7,8 @@
#include "util/detour.h"
#include "util/libutils.h"
#include "util/circular_buffer.h"
#include "util/socd_cleaner.h"
#include "util/time.h"
#include "misc/eamuse.h"
#include "cfg/api.h"
#include "acio/icca/icca.h"
@@ -336,6 +338,9 @@ static int __cdecl gfdm_unit2_boot_initialize() {
return 1;
}
constexpr int LEFTY_X_CENTER = 2040;
// this seems to be only used in the I/O test menu, not in gameplay
static void *__cdecl gfdm_unit_get_button_p(void *a1, int a2, size_t player) {
memset(a1, 0, 64);
@@ -344,31 +349,61 @@ static void *__cdecl gfdm_unit_get_button_p(void *a1, int a2, size_t player) {
return a1;
}
// log_info("gitadora", "gfdm_unit_get_button_p: a2={}, player={}", a2, player);
// get buttons
auto &buttons = games::gitadora::get_buttons();
auto &analogs = games::gitadora::get_analogs();
const auto wail_up =
Buttons::getState(RI_MGR, buttons.at(gitadora_button_mapping[12 + 11 * (size_t) player]));
const auto wail_down =
Buttons::getState(RI_MGR, buttons.at(gitadora_button_mapping[13 + 11 * (size_t) player]));
const auto wail_result =
socd::get_guitar_wail(player, wail_up, wail_down, get_performance_milliseconds());
// X
((int *) a1)[4] = a2 == 1 ? 4080 : -4080;
if (analogs.at(player * 3 + 0).isSet())
((int *) a1)[4] = lroundf(Analogs::getState(
RI_MGR, analogs.at(gitadora_analog_mapping[player * 4 + 0])) * 8160.f) - 4080;
// wail X
((int *) a1)[4] = games::gitadora::is_player_lefty(player) ? LEFTY_X_CENTER : -4080;
auto &analog_x = analogs.at(gitadora_analog_mapping[player * 4 + 0]);
if (analog_x.isSet())
((int *) a1)[4] = lroundf(Analogs::getState(RI_MGR, analog_x) * 8160.f) - 4080;
// Y
// wail Y
((int *) a1)[5] = 0;
if (analogs.at(player * 3 + 1).isSet())
((int *) a1)[5] = lroundf(Analogs::getState(
RI_MGR, analogs.at(gitadora_analog_mapping[player * 4 + 1])) * 8160.f) - 4080;
if (Buttons::getState(RI_MGR, buttons.at(gitadora_button_mapping[12 + 11 * (size_t) player])))
((int *) a1)[5] = -4080;
if (Buttons::getState(RI_MGR, buttons.at(gitadora_button_mapping[13 + 11 * (size_t) player])))
((int *) a1)[5] = 4080;
auto &analog_y = analogs.at(gitadora_analog_mapping[player * 4 + 1]);
if (analog_y.isSet()) {
((int *) a1)[5] = lroundf(Analogs::getState(RI_MGR, analog_y) * 8160.f) - 4080;
}
// Z
// digital wailing
if (!games::gitadora::is_player_lefty(player)) {
// righty
if (wail_result == socd::TiltUp) {
// top center
((int *) a1)[4] = 0; // X
((int *) a1)[5] = -4080; // Y
} else if (wail_result == socd::TiltDown) {
// bottom left; for lefty, bottom right
((int *) a1)[4] = -4080; // X
((int *) a1)[5] = 4080; // Y
}
} else {
// lefty
if (wail_result == socd::TiltUp) {
// left
((int *) a1)[4] = -4080; // X
((int *) a1)[5] = 0; // Y
} else if (wail_result == socd::TiltDown) {
// right
((int *) a1)[4] = 4080; // X
((int *) a1)[5] = 0; // Y
}
}
// wail Z
((int *) a1)[6] = 0;
if (analogs.at(player * 3 + 2).isSet())
((int *) a1)[6] = lroundf(Analogs::getState(
RI_MGR, analogs.at(gitadora_analog_mapping[player * 4 + 2])) * 8160.f) - 4080;
auto &analog_z = analogs.at(gitadora_analog_mapping[player * 4 + 2]);
if (analog_z.isSet())
((int *) a1)[6] = lroundf(Analogs::getState(RI_MGR, analog_z) * 8160.f) - 4080;
// return the same buffer
return a1;
@@ -585,63 +620,74 @@ static long __cdecl gfdm_unit2_get_input(int device) {
return gfdm_unit_get_input_p(device, 1);
}
// this is used in gameplay
static long __cdecl gfdm_unit_get_sensor_gf_p(int a1, int a2, size_t player) {
// return if it's actually drum mania
if (games::gitadora::is_drum())
return 0;
// log_info("gitadora", "gfdm_unit_get_sensor_gf_p: a1={}, a2={}, player={}", a1, a2, player);
// get buttons and analogs
auto &buttons = games::gitadora::get_buttons();
auto &analogs = games::gitadora::get_analogs();
// X
// figure out digital wail Y
const size_t offset = (size_t) player * 11;
const auto wail_up = Buttons::getState(RI_MGR, buttons.at(gitadora_button_mapping[12 + offset]));
const auto wail_down = Buttons::getState(RI_MGR, buttons.at(gitadora_button_mapping[13 + offset]));
const auto wail_result = socd::get_guitar_wail(player, wail_up, wail_down, get_performance_milliseconds());
// wail X
if (a2 == 0) {
long ret = games::gitadora::is_player_lefty(player) ? LEFTY_X_CENTER : -4080;
// analog override
if (analogs.at(gitadora_analog_mapping[player * 3 + 0]).isSet()) {
return lroundf(Analogs::getState(
RI_MGR, analogs.at(gitadora_analog_mapping[player * 4 + 0])) * 8160.f) - 4080;
auto &analog_x = analogs.at(gitadora_analog_mapping[player * 4 + 0]);
if (analog_x.isSet()) {
ret = lroundf(Analogs::getState(RI_MGR, analog_x) * 8160.f) - 4080;
}
// digital wail up/down
if (wail_result == socd::TiltUp) {
ret = games::gitadora::is_player_lefty(player) ? -4080 : 0;
} else if (wail_result == socd::TiltDown) {
ret = games::gitadora::is_player_lefty(player) ? +4080 : -4080;
}
// default
return a1 == 1 ? 4080 : -4080;
return ret;
}
// Y
// wail Y
if (a2 == 1) {
long ret = 0;
// analog override
if (analogs.at(player * 3 + 1).isSet()) {
return lroundf(Analogs::getState(
RI_MGR, analogs.at(gitadora_analog_mapping[player * 4 + 1])) * 8160.f) - 4080;
auto &analog_y = analogs.at(gitadora_analog_mapping[player * 4 + 1]);
if (analog_y.isSet()) {
ret = lroundf(Analogs::getState(RI_MGR, analog_y) * 8160.f) - 4080;
}
// variables
long ret = 0;
size_t offset = (size_t) player * 11;
// wail up
if (Buttons::getState(RI_MGR, buttons.at(gitadora_button_mapping[12 + offset]))) {
ret -= 4080;
}
// wail down
if (Buttons::getState(RI_MGR, buttons.at(gitadora_button_mapping[13 + offset]))) {
ret += 4080;
// digital wail up/down
if (wail_result == socd::TiltUp) {
ret = games::gitadora::is_player_lefty(player) ? 0 : -4080;
} else if (wail_result == socd::TiltDown) {
ret = games::gitadora::is_player_lefty(player) ? 0 : 4080;
}
// return value
return ret;
}
// Z
// wail Z
if (a2 == 2) {
// analog override
if (analogs.at(gitadora_analog_mapping[player * 3 + 2]).isSet()) {
return lroundf(Analogs::getState(
RI_MGR, analogs.at(gitadora_analog_mapping[player * 4 + 2])) * 8160.f) - 4080;
auto &analog_z = analogs.at(gitadora_analog_mapping[player * 4 + 2]);
if (analog_z.isSet()) {
return lroundf(Analogs::getState(RI_MGR, analog_z) * 8160.f) - 4080;
}
// default
+21 -4
View File
@@ -3,6 +3,7 @@
#include "avs/game.h"
#include "cfg/configurator.h"
#include "games/io.h"
#include "games/gitadora/gitadora.h"
#include "games/iidx/iidx.h"
#include "hooks/graphics/graphics.h"
#include "misc/eamuse.h"
@@ -51,16 +52,26 @@ namespace overlay {
bool AUTO_SHOW_IOPANEL = false;
bool AUTO_SHOW_KEYPAD_P1 = false;
bool AUTO_SHOW_KEYPAD_P2 = false;
bool USE_WM_CHAR_FOR_IMGUI_CHAR_INPUT = false;
bool FPS_SHOULD_FLIP = false;
std::optional<uint32_t> UI_SCALE_PERCENT;
// global
std::mutex OVERLAY_MUTEX;
std::unique_ptr<overlay::SpiceOverlay> OVERLAY = nullptr;
ImFont* DSEG_FONT = nullptr;
bool SHOW_DEBUG_LOG_WINDOW = false;
ImVec2 apply_scaling_to_vector(float x, float y) {
if (!UI_SCALE_PERCENT.has_value()) {
return ImVec2(x, y);
}
return ImVec2(apply_scaling(x), apply_scaling(y));
}
ImVec2 apply_scaling_to_vector(const ImVec2& input) {
return apply_scaling_to_vector(input.x, input.y);
}
}
static void *ImGui_Alloc(size_t sz, void *user_data) {
@@ -170,6 +181,12 @@ void overlay::SpiceOverlay::init() {
style.WindowRounding = 0;
}
if (UI_SCALE_PERCENT.has_value()) {
const auto scale = static_cast<float>(UI_SCALE_PERCENT.value()) / 100.f;
ImGui::GetStyle().ScaleAllSizes(scale);
ImGui::GetIO().FontGlobalScale = scale;
}
// red theme based on:
// https://github.com/ocornut/imgui/issues/707#issuecomment-760220280
// r, g, b, a
@@ -245,7 +262,6 @@ void overlay::SpiceOverlay::init() {
if (!cfg::CONFIGURATOR_STANDALONE) {
io.ConfigFlags |= ImGuiConfigFlags_DockingEnable;
io.ConfigFlags |= ImGuiConfigFlags_ViewportsEnable;
}
if (is_touch_available("SpiceOverlay::init")) {
io.ConfigFlags |= ImGuiConfigFlags_IsTouchScreen;
@@ -366,7 +382,8 @@ void overlay::SpiceOverlay::init() {
window_iopanel = new overlay::windows::IIDXIOPanel(this);
} else if (avs::game::is_model("MDX")) {
window_iopanel = new overlay::windows::DDRIOPanel(this);
} else if (avs::game::is_model({"J32", "J33", "K32", "K33", "L32", "L33", "M32"})) {
} else if (avs::game::is_model({"J32", "J33", "K32", "K33", "L32", "L33", "M32"}) &&
!games::gitadora::is_arena_model()) {
window_iopanel = new overlay::windows::GitadoraIOPanel(this);
} else {
window_iopanel = new overlay::windows::IOPanel(this);
+13
View File
@@ -3,6 +3,7 @@
#include <memory>
#include <mutex>
#include <functional>
#include <optional>
#include <vector>
#include <windows.h>
#include <d3d9.h>
@@ -27,6 +28,18 @@ namespace overlay {
extern bool USE_WM_CHAR_FOR_IMGUI_CHAR_INPUT;
extern bool FPS_SHOULD_FLIP;
extern bool SHOW_DEBUG_LOG_WINDOW;
extern std::optional<uint32_t> UI_SCALE_PERCENT;
template <typename T>
float apply_scaling(T input) {
if (!UI_SCALE_PERCENT.has_value()) {
return input;
}
return static_cast<float>(input) * UI_SCALE_PERCENT.value() / 100.f;
}
ImVec2 apply_scaling_to_vector(const ImVec2& input);
ImVec2 apply_scaling_to_vector(float x, float y);
class SpiceOverlay {
public:
@@ -21,7 +21,7 @@ namespace overlay::windows {
CameraControl::CameraControl(SpiceOverlay *overlay) : Window(overlay) {
this->title = "IIDX Camera Control";
this->flags |= ImGuiWindowFlags_AlwaysAutoResize;
this->init_pos = ImVec2(40, 40);
this->init_pos = overlay::apply_scaling_to_vector(40, 40);
this->toggle_button = games::OverlayButtons::ToggleCameraControl;
}
+2 -2
View File
@@ -17,7 +17,7 @@ namespace overlay::windows {
CardManager::CardManager(SpiceOverlay *overlay) : Window(overlay) {
this->title = "Card Manager";
this->init_size = ImVec2(420, 420);
this->init_size = overlay::apply_scaling_to_vector(ImVec2(420, 420));
if (cfg::CONFIGURATOR_STANDALONE) {
this->init_pos = ImVec2(40, 40);
@@ -303,7 +303,7 @@ namespace overlay::windows {
//
// setting ImGuiInputTextFlags_CallbackCharFilter and pressing escape doesn't cause below
// to return true, making it necessary to provide a callback...
ImGui::SetNextItemWidth(240);
ImGui::SetNextItemWidth(overlay::apply_scaling(240));
if (ImGui::InputTextWithHint("", "Type here to search..", &this->search_filter)) {
this->current_card = nullptr;
this->search_filter_in_lower_case = strtolower(this->search_filter);
+33 -13
View File
@@ -42,8 +42,8 @@ namespace overlay::windows {
Config::Config(overlay::SpiceOverlay *overlay) : Window(overlay) {
this->title = "Configuration";
this->toggle_button = games::OverlayButtons::ToggleConfig;
this->init_size = ImVec2(800, 600);
this->size_min = ImVec2(100, 200);
this->init_size = overlay::apply_scaling_to_vector(ImVec2(800, 600));
this->size_min = overlay::apply_scaling_to_vector(ImVec2(100, 200));
this->init_pos = ImVec2(0, 0);
if (cfg::CONFIGURATOR_STANDALONE && cfg::CONFIGURATOR_TYPE == cfg::ConfigType::Config) {
this->active = true;
@@ -238,8 +238,8 @@ namespace overlay::windows {
// tab selection
auto tab_selected_new = ConfigTab::CONFIG_TAB_INVALID;
if (ImGui::BeginTabBar("Config Tabs", ImGuiTabBarFlags_NoCloseWithMiddleMouseButton)) {
const int page_offset = cfg::CONFIGURATOR_STANDALONE ? 88 : 110;
const int page_offset2 = cfg::CONFIGURATOR_STANDALONE ? 65 : 87;
const int page_offset = overlay::apply_scaling(cfg::CONFIGURATOR_STANDALONE ? 88 : 110);
const int page_offset2 = overlay::apply_scaling(cfg::CONFIGURATOR_STANDALONE ? 65 : 87);
if (ImGui::BeginTabItem("Buttons")) {
tab_selected_new = ConfigTab::CONFIG_TAB_BUTTONS;
@@ -249,7 +249,7 @@ namespace overlay::windows {
// help text for binding buttons, if the game has one
const auto help_text = games::get_buttons_help(this->games_selected_name);
if (!help_text.empty()) {
ImGui::TextColored(ImVec4(1.f, 0.7f, 0, 1), "Button Layout");
ImGui::TextColored(ImVec4(1.f, 0.7f, 0, 1), "Button Bindings");
ImGui::Spacing();
ImGui::TextWrapped("%s", help_text.c_str());
ImGui::TextUnformatted("");
@@ -299,8 +299,18 @@ namespace overlay::windows {
}
if (ImGui::BeginTabItem("Analogs")) {
tab_selected_new = ConfigTab::CONFIG_TAB_ANALOGS;
ImGui::BeginChild("Analogs", ImVec2(
0, ImGui::GetWindowContentRegionMax().y - page_offset2), false);
// help text for binding analog, if the game has one
const auto help_text = games::get_analogs_help(this->games_selected_name);
if (!help_text.empty()) {
ImGui::TextColored(ImVec4(1.f, 0.7f, 0, 1), "Analog Bindings");
ImGui::Spacing();
ImGui::TextWrapped("%s", help_text.c_str());
ImGui::TextUnformatted("");
}
this->build_analogs("Game", games::get_analogs(this->games_selected_name));
ImGui::EndChild();
ImGui::EndTabItem();
@@ -575,7 +585,7 @@ namespace overlay::windows {
if (ImGui::BeginTable("ButtonsTable", 3, ImGuiTableFlags_Resizable | ImGuiTableFlags_RowBg)) {
ImGui::TableSetupColumn("Name", 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
if (!buttons || buttons->empty()) {
@@ -1644,7 +1654,7 @@ namespace overlay::windows {
if (ImGui::BeginTable("AnalogsTable", 3, ImGuiTableFlags_Resizable | ImGuiTableFlags_RowBg)) {
ImGui::TableSetupColumn("Name", 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
if (!analogs || analogs->empty()) {
@@ -2086,7 +2096,7 @@ namespace overlay::windows {
if (ImGui::BeginTable("LightsTable", 3, ImGuiTableFlags_Resizable | ImGuiTableFlags_RowBg)) {
ImGui::TableSetupColumn("Name", 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
if (!lights || lights->empty()) {
@@ -2643,8 +2653,14 @@ namespace overlay::windows {
// tables must share the same ID to have synced column settings
if (ImGui::BeginTable("OptionsTable", 3, ImGuiTableFlags_Resizable | ImGuiTableFlags_RowBg)) {
ImGui::TableSetupColumn("Option", ImGuiTableColumnFlags_WidthStretch);
ImGui::TableSetupColumn("CMD Line Parameter", ImGuiTableColumnFlags_WidthFixed, 216);
ImGui::TableSetupColumn("Setting", ImGuiTableColumnFlags_WidthFixed, 240);
ImGui::TableSetupColumn(
"CMD Line Parameter",
ImGuiTableColumnFlags_WidthFixed,
overlay::apply_scaling(216));
ImGui::TableSetupColumn(
"Setting",
ImGuiTableColumnFlags_WidthFixed,
overlay::apply_scaling(240));
// iterate options
options_count = 0;
@@ -3056,7 +3072,11 @@ namespace overlay::windows {
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_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::PopItemWidth();
ImGui::PopStyleColor(3);
@@ -3080,8 +3100,8 @@ namespace overlay::windows {
// draw popups
{
const ImVec2 popup_size(
std::min(ImGui::GetIO().DisplaySize.x * 0.9f, 800.f),
std::min(ImGui::GetIO().DisplaySize.y * 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, overlay::apply_scaling(800.f)));
const ImVec2 popup_pos(
ImGui::GetIO().DisplaySize.x / 2 - popup_size.x / 2,
+2 -2
View File
@@ -10,10 +10,10 @@ namespace overlay::windows {
this->title = "spice2x";
this->init_size = ImVec2(
(ImGui::GetFontSize() * 14) + (ImGui::GetStyle().ItemSpacing.x * 2),
120);
overlay::apply_scaling(120));
this->init_pos = ImVec2(
ImGui::GetIO().DisplaySize.x / 2 - this->init_size.x / 2,
10);
overlay::apply_scaling(10));
this->flags = ImGuiWindowFlags_NoResize
| ImGuiWindowFlags_NoCollapse
+18 -13
View File
@@ -1,5 +1,6 @@
#include <iomanip>
#include <sstream>
#include "external/fmt/include/fmt/chrono.h"
#include "fps.h"
namespace overlay::windows {
@@ -15,13 +16,17 @@ namespace overlay::windows {
| ImGuiWindowFlags_NoNavInputs
| ImGuiWindowFlags_NoDocking;
this->bg_alpha = 0.5f;
this->start_time = std::chrono::system_clock::now();
this->start_time =
std::chrono::floor<std::chrono::seconds>(std::chrono::system_clock::now());
}
void FPS::calculate_initial_window() {
// width is 114x82 px with window decoration, 98x47 for the content
int pos_x = overlay::FPS_SHOULD_FLIP ? 8 : ImGui::GetIO().DisplaySize.x - 122;
this->init_pos = ImVec2(pos_x, 8);
int pos_x =
overlay::FPS_SHOULD_FLIP ?
overlay::apply_scaling(8) :
ImGui::GetIO().DisplaySize.x - overlay::apply_scaling(122);
this->init_pos = ImVec2(pos_x, overlay::apply_scaling(8));
}
void FPS::build_content() {
@@ -31,23 +36,23 @@ namespace overlay::windows {
ImGui::Text("FPS: %.1f", io.Framerate);
// ImGui::Text("FT: %.2fms", 1000 / io.Framerate);
auto now = std::chrono::system_clock::now();
const auto now = std::chrono::system_clock::now();
const auto now_s = std::chrono::floor<std::chrono::seconds>(now);
// current time
{
auto now_t = std::chrono::system_clock::to_time_t(now);
static CHAR buf[48];
std::strftime(buf, sizeof(buf), "Time: %H:%M:%S", std::localtime(&now_t));
ImGui::Text(buf);
const std::time_t tt = std::chrono::system_clock::to_time_t(now_s);
std::tm local_tm{};
localtime_s(&local_tm, &tt);
ImGui::TextUnformatted(fmt::format("Time: {:%H:%M:%S}", local_tm).c_str());
}
// elapsed time
{
auto d = now - this->start_time;
const auto h = std::chrono::duration_cast<std::chrono::hours>(d);
const auto m = std::chrono::duration_cast<std::chrono::minutes>(d - h);
const auto s = std::chrono::duration_cast<std::chrono::seconds>(d - h - m);
ImGui::Text("Up: %02d:%02d:%02d", (int)h.count(), (int)m.count(), (int)s.count());
const auto uptime = now_s - this->start_time;
ImGui::TextUnformatted(
fmt::format("Up: {:%H:%M:%S}",
std::chrono::floor<std::chrono::seconds>(uptime)).c_str());
}
}
}
+6 -2
View File
@@ -3,6 +3,7 @@
#include "launcher/launcher.h"
#include "games/io.h"
#include "misc/eamuse.h"
#include "overlay/imgui/extensions.h"
namespace overlay::windows {
@@ -11,7 +12,7 @@ namespace overlay::windows {
this->flags = ImGuiWindowFlags_AlwaysAutoResize;
this->init_size = ImVec2(
80,
overlay::apply_scaling(80),
ImGui::GetFrameHeight() +
ImGui::GetStyle().WindowPadding.y * 2 +
ImGui::GetFrameHeightWithSpacing() * 3 +
@@ -58,7 +59,7 @@ namespace overlay::windows {
void IOPanel::build_io_panel() {}
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 ImVec2 tall(
ImGui::GetFrameHeight(),
@@ -80,6 +81,9 @@ namespace overlay::windows {
{
ImGui::SetCursorPosY(ImGui::GetCursorPosY() - ImGui::GetFrameHeightWithSpacing());
this->build_button("+", tall, this->test_button, this->service_button);
if (ImGui::IsItemHovered()) {
ImGui::HelpTooltip("SERVICE + TEST");
}
}
ImGui::EndGroup();
+50 -15
View File
@@ -3,6 +3,7 @@
#include "games/io.h"
#include "games/gitadora/gitadora.h"
#include "games/gitadora/io.h"
#include "overlay/imgui/extensions.h"
#include "misc/eamuse.h"
#include "util/logging.h"
@@ -11,6 +12,7 @@ namespace overlay::windows {
GitadoraIOPanel::GitadoraIOPanel(SpiceOverlay *overlay) : IOPanel(overlay) {
this->title = "GITADORA IO Panel";
this->has_menu_controls = true;
// by default, make a safer assumption that there are two players
this->two_players = true;
// by default, enable the extra input only available on DX cabs...
@@ -33,6 +35,12 @@ namespace overlay::windows {
this->has_guitar_knobs = false;
}
if (games::gitadora::is_arena_model()) {
this->has_menu_controls = false;
this->two_players = false;
this->has_guitar_knobs = false;
}
find_gfdm_buttons();
}
@@ -68,25 +76,45 @@ namespace overlay::windows {
}
void GitadoraIOPanel::build_io_panel() {
ImGui::Dummy(ImVec2(12, 0));
ImGui::Dummy(overlay::apply_scaling_to_vector(12, 0));
ImGui::SameLine();
this->draw_buttons(0);
if (this->has_guitar_knobs) {
if (this->has_menu_controls) {
ImGui::SameLine();
this->draw_sliders(0);
}
// draw p2 only if guitar freaks
if (this->two_players) {
ImGui::SameLine();
ImGui::Dummy(ImVec2(12, 0));
ImGui::SameLine();
this->draw_buttons(1);
ImGui::PushID("P1");
this->draw_buttons(0);
if (this->has_guitar_knobs) {
ImGui::SameLine();
this->draw_sliders(1);
this->draw_sliders(0);
}
ImGui::PopID();
// draw p2 only if guitar freaks
if (this->two_players) {
ImGui::SameLine();
ImGui::Dummy(overlay::apply_scaling_to_vector(12, 0));
ImGui::SameLine();
ImGui::PushID("P2");
this->draw_buttons(1);
if (this->has_guitar_knobs) {
ImGui::SameLine();
this->draw_sliders(1);
}
ImGui::PopID();
}
}
if (games::gitadora::is_guitar()) {
ImGui::SameLine();
ImGui::Dummy(overlay::apply_scaling_to_vector(12, 0));
ImGui::SameLine();
ImGui::BeginGroup();
{
ImGui::Checkbox("P1 Lefty", &games::gitadora::P1_LEFTY);
if (this->two_players) {
ImGui::Checkbox("P2 Lefty", &games::gitadora::P2_LEFTY);
}
}
ImGui::EndGroup();
}
}
@@ -107,6 +135,10 @@ namespace overlay::windows {
{
ImGui::SetCursorPosY(ImGui::GetCursorPosY() + ImGui::GetFrameHeightWithSpacing());
this->build_button("<", leftright_size, this->left[p], nullptr, this->leftright_light[p]);
this->build_button("+", tiny_size, this->help[p], this->start[p], nullptr);
if (ImGui::IsItemHovered()) {
ImGui::HelpTooltip("HELP + START");
}
}
ImGui::EndGroup();
@@ -135,6 +167,9 @@ namespace overlay::windows {
ImGui::BeginGroup();
{
this->build_button("?", tiny_size, this->help[p], nullptr, this->help_light[p]);
if (ImGui::IsItemHovered()) {
ImGui::HelpTooltip("HELP");
}
this->build_button(">", leftright_size, this->right[p], nullptr, this->leftright_light[p]);
}
ImGui::EndGroup();
@@ -146,7 +181,7 @@ namespace overlay::windows {
games::gitadora::Analogs::GuitarP1Knob :
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
const auto analogs = games::get_analogs(eamuse_get_game());
@@ -18,6 +18,7 @@ namespace overlay::windows {
void draw_buttons(const int player);
void draw_sliders(const int player);
bool has_menu_controls;
bool two_players;
bool has_guitar_knobs;
+14 -6
View File
@@ -19,15 +19,15 @@ namespace overlay::windows {
case 0: {
this->toggle_button = games::OverlayButtons::ToggleVirtualKeypadP1;
this->init_pos = ImVec2(
26,
ImGui::GetIO().DisplaySize.y - 264);
overlay::apply_scaling(26),
ImGui::GetIO().DisplaySize.y - overlay::apply_scaling(264));
break;
}
case 1: {
this->toggle_button = games::OverlayButtons::ToggleVirtualKeypadP2;
this->init_pos = ImVec2(
ImGui::GetIO().DisplaySize.x - 220,
ImGui::GetIO().DisplaySize.y - 264);
ImGui::GetIO().DisplaySize.x - overlay::apply_scaling(220),
ImGui::GetIO().DisplaySize.y - overlay::apply_scaling(264));
break;
}
}
@@ -82,9 +82,17 @@ namespace overlay::windows {
// add selectable (fill last line)
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 {
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
+16 -6
View File
@@ -1,11 +1,13 @@
#include "patch_manager.h"
#include <chrono>
#include <thread>
#include <fstream>
#include <shellapi.h>
#include <winhttp.h>
#include <psapi.h>
#include <format>
#include "external/fmt/include/fmt/chrono.h"
#include "external/rapidjson/document.h"
#include "external/rapidjson/prettywriter.h"
#include "external/rapidjson/stringbuffer.h"
@@ -1150,7 +1152,7 @@ namespace overlay::windows {
}
}
std::string get_game_identifier(const std::filesystem::path& dll_path) {
std::string get_game_identifier(const std::filesystem::path& dll_path, bool print_info) {
uint32_t time_date_stamp = 0;
uint32_t address_of_entry_point = 0;
@@ -1159,7 +1161,7 @@ namespace overlay::windows {
if (!result) {
return "";
}
// concatenate TimeDateStamp and AddressOfEntryPoint
std::string identifier =
fmt::format(
@@ -1168,6 +1170,16 @@ namespace overlay::windows {
time_date_stamp,
address_of_entry_point);
if (print_info) {
const auto time = std::chrono::system_clock::from_time_t(time_date_stamp);
log_info(
"patchmanager",
"file: {}, patch id: {}, build timestamp of dll: {:%Y-%m-%d %H:%M}",
dll_path.has_filename() ? dll_path.filename().string() : dll_path.string(),
identifier,
time);
}
return identifier;
}
@@ -1657,14 +1669,12 @@ namespace overlay::windows {
ACTIVE_JSON_FILE = "";
std::string firstDll = avs::game::DLL_NAME;
std::string first_id = get_game_identifier(MODULE_PATH / firstDll);
std::string first_id = get_game_identifier(MODULE_PATH / firstDll, true);
std::filesystem::path firstPath = fmt::format("patches/{}.json", first_id);
log_misc("patchmanager", "patch identifier of {}: {}", firstDll, first_id);
auto extraDlls = getExtraDlls(firstDll);
std::erase_if(extraDlls, [](const std::string& dll) {
auto identifier = get_game_identifier(MODULE_PATH / dll);
auto identifier = get_game_identifier(MODULE_PATH / dll, true);
return identifier.empty() || !fileutils::file_exists(fmt::format("patches/{}.json", identifier));
});
+1 -1
View File
@@ -97,7 +97,7 @@ namespace overlay::windows {
extern std::optional<std::string> PATCH_MANAGER_CFG_PATH_OVERRIDE;
std::string get_game_identifier(const std::filesystem::path& dll_path);
std::string get_game_identifier(const std::filesystem::path& dll_path, bool print_info=false);
class PatchManager : public Window {
public:
+34 -5
View File
@@ -27,6 +27,11 @@ namespace overlay::windows {
}
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) {
return NULL;
}
@@ -95,6 +100,21 @@ namespace overlay::windows {
}
void ScreenResize::build_fullscreen_config() {
if (avs::game::is_model("KFC")) {
ImGui::TextColored(ImVec4(1, 0.5f, 0.5f, 1.f),
"Warning: Enabling Image Resize uses more GPU\n"
"resources and may significantly lower framerate\n"
"for songs with Live2D! Results may vary, use at\n"
"your own risk.");
} else {
ImGui::TextColored(ImVec4(1, 0.5f, 0.5f, 1.f),
"Warning: Enabling Image Resize uses more GPU\n"
"resources and may significantly lower framerate\n"
"in some situations! Results may vary, use at\n"
"your own risk.");
}
// enable checkbox
ImGui::Checkbox("Enable", &cfg::SCREENRESIZE->enable_screen_resize);
ImGui::SameLine();
@@ -136,22 +156,27 @@ namespace overlay::windows {
// aspect ratio
ImGui::Checkbox("Keep Aspect Ratio", &scene.keep_aspect_ratio);
if (scene.keep_aspect_ratio) {
if (ImGui::SliderFloat("Scale", &scene.scale_x, 0.5f, 2.5f)) {
// we want to avoid zooming out below 0.6 as it will cause StretchRect calls to
// go on a slow sync path with the GPU, leading to frame drops in some situations
// (e.g., SDVX Live2D)
if (ImGui::SliderFloat("Scale", &scene.scale_x, 0.6f, 2.5f)) {
scene.scale_y = scene.scale_x;
}
ImGui::SameLine();
ImGui::HelpMarker("Hint: ctrl + click on the slider to type in a numeric value.");
} else {
ImGui::SliderFloat("Width Scale", &scene.scale_x, 0.5f, 2.5f);
ImGui::SliderFloat("Width Scale", &scene.scale_x, 0.6f, 2.5f);
ImGui::SameLine();
ImGui::HelpMarker("Hint: ctrl + click on the slider to type in a numeric value.");
ImGui::SliderFloat("Height Scale", &scene.scale_y, 0.5f, 2.5f);
ImGui::SliderFloat("Height Scale", &scene.scale_y, 0.6f, 2.5f);
ImGui::SameLine();
ImGui::HelpMarker("Hint: ctrl + click on the slider to type in a numeric value.");
}
static const char* dupe_items[] = { "None", "Copy Left", "Copy Right" };
ImGui::Combo(
const bool duplicate_changed = ImGui::Combo(
"Duplicate",
reinterpret_cast<int *>(&scene.duplicate),
dupe_items,
@@ -162,6 +187,11 @@ namespace overlay::windows {
"wrap-around effect when an offset is set.");
ImGui::EndDisabled();
// tell d3d9 device to clear surface to get rid of ghost image
if (duplicate_changed) {
cfg::SCREENRESIZE->need_surface_clean = true;
}
}
void ScreenResize::build_windowed_config() {
@@ -171,7 +201,6 @@ namespace overlay::windows {
return;
}
ImGui::TextUnformatted("Warning: may cause some games to crash.");
ImGui::BeginDisabled(graphics_window_change_crashes_game());
if (ImGui::Combo(
"Window Style",
+1 -2
View File
@@ -10,8 +10,7 @@ namespace overlay::windows {
SDVXSubScreen::SDVXSubScreen(SpiceOverlay *overlay) : GenericSubScreen(overlay) {
this->title = "SDVX Subscreen";
bool isValkyrieCabinetMode = avs::game::SPEC[0] == 'G' || avs::game::SPEC[0] == 'H';
if (!isValkyrieCabinetMode) {
if (!games::sdvx::is_valkyrie_model()) {
this->disabled_message = "Valkyrie Model mode is not enabled!";
} else if (games::sdvx::SHOW_VM_MONITOR_WARNING) {
this->disabled_message = "VM mode subscreen overlay is not compatible with -monitor option";
+1 -1
View File
@@ -1986,7 +1986,7 @@ void CALLBACK rawinput::RawInputManager::input_midi_proc(HMIDIIN hMidiIn, UINT w
device.midiInfo->v2_last_off_time[midi_index] = get_performance_milliseconds();
device.updated = true;
}
// for v2_drum, NOTE ON is ignored
// for v2_drum, NOTE OFF is ignored
}
}
+14 -4
View File
@@ -32,6 +32,16 @@ namespace deferredlogs {
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) {
static std::once_flag printed;
std::call_once(printed, [is_crash]() {
@@ -76,8 +86,8 @@ namespace deferredlogs {
msg += "\n";
}
for (auto messages : errors) {
for (auto message : messages) {
for (const auto &messages : errors) {
for (const auto &message : messages) {
msg += " " + message + "\n";
}
msg += "\n";
@@ -85,13 +95,13 @@ namespace deferredlogs {
msg += " unsure what to do next?\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 += " https://github.com/spice2x/spice2x.github.io/wiki/Known-issues\n";
msg += "\n";
msg += "\\------------------------- spice2x auto-troubleshooter ------------------------/\n";
log_warning("troubleshooter", "{}", msg);
log_special("troubleshooter", "{}", msg);
});
}
}
+1
View File
@@ -10,6 +10,7 @@ namespace deferredlogs {
extern const std::initializer_list<std::string> SUPERSTEP_SOUND_ERROR_MESSAGE;
void set_softid(const std::string& softid);
void report_fatal_message();
void defer_error_messages(std::initializer_list<std::string> messages);
void dump_to_logger(bool is_crash=false);
}
+3 -2
View File
@@ -13,8 +13,9 @@ std::string_view log_get_datetime() {
std::string_view log_get_datetime(std::time_t now) {
static thread_local char buf[64];
// `localtime` on Windows is thread-safe
strftime(buf, sizeof(buf), "[%Y/%m/%d %X]", localtime(&now));
std::tm local_tm{};
localtime_s(&local_tm, &now);
strftime(buf, sizeof(buf), "[%Y/%m/%d %X]", &local_tm);
return buf;
}
+3
View File
@@ -87,6 +87,9 @@ void show_popup_for_fatal_error(std::string message);
#define log_warning(module, format_str, ...) logger::push( \
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, ...) { \
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__)); \
+146
View File
@@ -0,0 +1,146 @@
#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;
}
}
// first dimension: p1/p2
// second dimension: TiltUp / TiltDown
// value: last timestamp when it was on
static double most_recent_active[2][2] = {};
uint32_t TILT_HOLD_MS = 100;
TiltResult get_guitar_wail(uint8_t device, bool up, bool down, double time_now) {
if (device >= 2) {
log_fatal("socd", "invalid device index in socd_clean: {}", device);
}
const auto socd_result = socd_clean(device, up, down, time_now);
if (up) {
most_recent_active[device][TiltUp] = time_now;
}
if (down) {
most_recent_active[device][TiltDown] = time_now;
}
log_debug(
"socd",
"p{} wail up={}, down={}",
device + 1,
most_recent_active[device][TiltUp],
most_recent_active[device][TiltDown]);
const auto delta_up = time_now - most_recent_active[device][TiltUp];
const auto delta_down = time_now - most_recent_active[device][TiltDown];
const bool is_up = delta_up <= TILT_HOLD_MS;
const bool is_down = delta_down <= TILT_HOLD_MS;
auto result = TiltNone;
if (is_up && is_down) {
// both held recently: prefer more recently held using SOCD logic
if (socd_result == SocdCCW) {
result = TiltUp;
} else if (socd_result == SocdCW) {
result = TiltDown;
} else {
result = TiltUp;
}
} else if (is_up) {
result = TiltUp;
} else if (is_down) {
result = TiltDown;
}
// clear opposite direction being held
if (result == TiltUp) {
most_recent_active[device][TiltDown] = 0.f;
} else if (result == TiltDown) {
most_recent_active[device][TiltUp] = 0.f;
}
return result;
}
}
+36
View File
@@ -0,0 +1,36 @@
#pragma once
#include <cstdint>
namespace socd {
// SOCD for knobs / turntables
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);
// for guitar wail (up/down only)
extern uint32_t TILT_HOLD_MS;
typedef enum _TiltResult {
TiltUp = 0,
TiltDown = 1,
TiltNone = 2
} TiltResult;
TiltResult get_guitar_wail(uint8_t device, bool up, bool down, double time_now);
}
+6
View File
@@ -235,6 +235,12 @@ namespace sysutils {
} else {
log_misc("gpuinfo", "EnumDisplaySettingsA failed");
}
log_misc(
"gpuinfo", "{} {} is primary : {}",
prefix.c_str(),
index,
(adapter->StateFlags & DISPLAY_DEVICE_PRIMARY_DEVICE) ? "yes" : "no");
}
}