Compare commits

...

14 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
38 changed files with 744 additions and 282 deletions
+1 -1
View File
@@ -29,7 +29,7 @@ Therefore, when making code changes, please be extremely careful about containin
#### Config file compatibility #### 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 ### Code quality requirements
+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 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)); 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 // 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); STATUS_BUFFER[6] = (int) (51.f * pad1_vel + 0.5f);
} }
if (Buttons::getState(RI_MGR, buttons.at(games::ftt::Buttons::Pad2))) { 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 // process analogs
auto &analogs = games::ftt::get_analogs(); auto &analogs = games::ftt::get_analogs();
auto pad1_analog = analogs.at(games::ftt::Analogs::Pad1); auto &pad1_analog = analogs.at(games::ftt::Analogs::Pad1);
auto pad2_analog = analogs.at(games::ftt::Analogs::Pad2); auto &pad2_analog = analogs.at(games::ftt::Analogs::Pad2);
auto pad3_analog = analogs.at(games::ftt::Analogs::Pad3); auto &pad3_analog = analogs.at(games::ftt::Analogs::Pad3);
auto pad4_analog = analogs.at(games::ftt::Analogs::Pad4); auto &pad4_analog = analogs.at(games::ftt::Analogs::Pad4);
if (pad1_analog.isSet()) { if (pad1_analog.isSet()) {
auto val = (uint32_t) (51.f * Analogs::getState(RI_MGR, pad1_analog) + 0.5f); auto val = (uint32_t) (51.f * Analogs::getState(RI_MGR, pad1_analog) + 0.5f);
STATUS_BUFFER[6] = MAX(STATUS_BUFFER[6], val); STATUS_BUFFER[6] = MAX(STATUS_BUFFER[6], val);
+6
View File
@@ -1,9 +1,11 @@
#include "screen_resize.h" #include "screen_resize.h"
#include "avs/game.h"
#include "external/rapidjson/document.h" #include "external/rapidjson/document.h"
#include "external/rapidjson/pointer.h" #include "external/rapidjson/pointer.h"
#include "external/rapidjson/prettywriter.h" #include "external/rapidjson/prettywriter.h"
#include "misc/eamuse.h" #include "misc/eamuse.h"
#include "util/deferlog.h"
#include "util/utils.h" #include "util/utils.h"
#include "util/fileutils.h" #include "util/fileutils.h"
#include "hooks/graphics/graphics.h" #include "hooks/graphics/graphics.h"
@@ -78,6 +80,10 @@ namespace cfg {
root); root);
load_bool_value(doc, root + "enable_screen_resize", this->enable_screen_resize); 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); load_bool_value(doc, root + "enable_linear_filter", this->enable_linear_filter);
for (size_t i = 0; i < std::size(this->scene_settings); i++) { for (size_t i = 0; i < std::size(this->scene_settings); i++) {
auto& scene = this->scene_settings[i]; auto& scene = this->scene_settings[i];
+4
View File
@@ -1,5 +1,6 @@
#pragma once #pragma once
#include <atomic>
#include <memory> #include <memory>
#include <string> #include <string>
#include <optional> #include <optional>
@@ -51,6 +52,9 @@ namespace cfg {
bool enable_linear_filter = true; bool enable_linear_filter = true;
fullscreen_setting scene_settings[4]; fullscreen_setting scene_settings[4];
// state
std::atomic<bool> need_surface_clean = false;
// windowed mode sizing // windowed mode sizing
// Windows terminology: // Windows terminology:
// window = rectangle including the frame // window = rectangle including the frame
+17 -2
View File
@@ -8,6 +8,7 @@
#include "hooks/setupapihook.h" #include "hooks/setupapihook.h"
#include "hooks/sleephook.h" #include "hooks/sleephook.h"
#include "hooks/input/dinput8/hook.h" #include "hooks/input/dinput8/hook.h"
#include "util/deferlog.h"
#include "util/utils.h" #include "util/utils.h"
#include "util/libutils.h" #include "util/libutils.h"
#include "util/fileutils.h" #include "util/fileutils.h"
@@ -108,18 +109,32 @@ namespace games::ddr {
log_info("ddr", "`{}` returned {}", cmd, result); log_info("ddr", "`{}` returned {}", cmd, result);
} else { } else {
log_warning("ddr", "`{}` failed, returned {}", cmd, result); 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)});
} }
static std::once_flag printed;
std::call_once(printed, [file]() {
if (!contains_only_ascii(file.path().string())) { if (!contains_only_ascii(file.path().string())) {
log_warning( log_warning(
"ddr", "ddr",
"BAD PATH ERROR\n\n\n" "BAD PATH ERROR\n\n"
"!!! !!!\n" "!!! !!!\n"
"!!! filesystem path to codec contains non-ASCII characters! !!!\n" "!!! filesystem path to codec contains non-ASCII characters! !!!\n"
"!!! this may cause the game to crash! !!!\n" "!!! this may cause failures when loading codecs, and !!!\n"
"!!! potentially leading the game to crash! !!!\n"
"!!! !!!\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"});
} }
});
} }
} }
} }
+6
View File
@@ -11,6 +11,7 @@
#include "util/libutils.h" #include "util/libutils.h"
#include "util/logging.h" #include "util/logging.h"
#include "util/sigscan.h" #include "util/sigscan.h"
#include "util/socd_cleaner.h"
#include "hooks/setupapihook.h" #include "hooks/setupapihook.h"
namespace games::gitadora { namespace games::gitadora {
@@ -18,6 +19,8 @@ namespace games::gitadora {
// settings // settings
bool TWOCHANNEL = false; bool TWOCHANNEL = false;
std::optional<unsigned int> CAB_TYPE = std::nullopt; std::optional<unsigned int> CAB_TYPE = std::nullopt;
bool P1_LEFTY = false;
bool P2_LEFTY = false;
/* /*
* Prevent GitaDora from creating folders on F drive * Prevent GitaDora from creating folders on F drive
@@ -220,6 +223,9 @@ namespace games::gitadora {
GRAPHICS_WINDOWED && !cfg::CONFIGURATOR_STANDALONE) { GRAPHICS_WINDOWED && !cfg::CONFIGURATOR_STANDALONE) {
overlay::UI_SCALE_PERCENT = 250; 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 // settings
extern bool TWOCHANNEL; extern bool TWOCHANNEL;
extern std::optional<unsigned int> CAB_TYPE; extern std::optional<unsigned int> CAB_TYPE;
extern bool P1_LEFTY;
extern bool P2_LEFTY;
class GitaDoraGame : public games::Game { class GitaDoraGame : public games::Game {
public: 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;
}
} }
+32 -3
View File
@@ -107,9 +107,18 @@ std::string games::gitadora::get_buttons_help() {
// keep to max 100 characters wide // keep to max 100 characters wide
return return
"guitar:\n" "guitar:\n"
"\n"
" < R G B Y P --- Pick ] \n" " < R G B Y P --- Pick ] \n"
"\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" "drums:\n"
"\n"
" LeftCymbal RightCymbal\n" " LeftCymbal RightCymbal\n"
" HiHat HiTom LowTom \n" " HiHat HiTom LowTom \n"
" Snare FloorTom \n" " Snare FloorTom \n"
@@ -117,10 +126,30 @@ std::string games::gitadora::get_buttons_help() {
" Left Bass\n" " Left Bass\n"
" Pedal Pedal\n" " Pedal Pedal\n"
"\n" "\n"
"Drums are NOT velocity-sensitive!\n" " Drums are NOT velocity-sensitive!\n"
"\n" "\n"
"For MIDI drums with Open/Closed HiHat configurations, bind variations below. " " For MIDI drums with Open/Closed HiHat configurations or pads with\n"
"v2_drum algorithm might work better for those drums." " 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."
; ;
} }
+1
View File
@@ -209,6 +209,7 @@ namespace games::gitadora {
// getters // getters
std::vector<Button> &get_buttons(); std::vector<Button> &get_buttons();
std::string get_buttons_help(); std::string get_buttons_help();
std::string get_analogs_help();
std::vector<Analog> &get_analogs(); std::vector<Analog> &get_analogs();
std::vector<Light> &get_lights(); std::vector<Light> &get_lights();
} }
+30 -9
View File
@@ -1,11 +1,12 @@
#include "misc/eamuse.h" #include "misc/eamuse.h"
#include "util/utils.h" #include "games/gitadora/gitadora.h"
#include "util/socd_cleaner.h"
#include "util/time.h" #include "util/time.h"
#include "util/utils.h"
#include "io.h" #include "io.h"
#include "j33i.h" #include "j33i.h"
#define GUITAR_BTN_R 0x7 #define GUITAR_BTN_R 0x7
#define GUITAR_BTN_G 0x6 #define GUITAR_BTN_G 0x6
#define GUITAR_BTN_B 0x5 #define GUITAR_BTN_B 0x5
@@ -111,8 +112,8 @@ bool games::gitadora::J33ISerialDevice::parse_msg(
auto &analogs = get_analogs(); auto &analogs = get_analogs();
// get x,y,z analog values [-0.5f, 0.5f], centered at 0.f // get x,y,z analog values [-0.5f, 0.5f]
float x = 0.f; float x = is_player_lefty(0) ? 0.3f : -0.5f;
auto x_analog = analogs[Analogs::GuitarP1WailX]; auto x_analog = analogs[Analogs::GuitarP1WailX];
if (x_analog.isSet()) { if (x_analog.isSet()) {
x = GameAPI::Analogs::getState(RI_MGR, x_analog) - 0.5f; x = GameAPI::Analogs::getState(RI_MGR, x_analog) - 0.5f;
@@ -123,11 +124,6 @@ bool games::gitadora::J33ISerialDevice::parse_msg(
if (y_analog.isSet()) { if (y_analog.isSet()) {
y = GameAPI::Analogs::getState(RI_MGR, y_analog) - 0.5f; 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; float z = 0.f;
auto z_analog = analogs[Analogs::GuitarP1WailZ]; 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; 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 // 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 x_int = x * ARENA_MODEL_WAIL_MAX * 2;
const uint16_t y_int = y * ARENA_MODEL_WAIL_MAX * 2; const uint16_t y_int = y * ARENA_MODEL_WAIL_MAX * 2;
+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_keypads;
static robin_hood::unordered_map<std::string, std::vector<Button>> buttons_overlay; 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> 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<Analog> &> analogs;
static robin_hood::unordered_map<std::string, std::vector<Light> &> lights; static robin_hood::unordered_map<std::string, std::vector<Light> &> lights;
static robin_hood::unordered_map<std::string, std::vector<Option>> options; static robin_hood::unordered_map<std::string, std::vector<Option>> options;
@@ -107,6 +108,7 @@ namespace games {
games.push_back(gitadora); games.push_back(gitadora);
buttons.insert({ gitadora, gitadora::get_buttons() }); buttons.insert({ gitadora, gitadora::get_buttons() });
buttons_help.insert({ gitadora, gitadora::get_buttons_help() }); buttons_help.insert({ gitadora, gitadora::get_buttons_help() });
analogs_help.insert({ gitadora, gitadora::get_analogs_help() });
analogs.insert({ gitadora, gitadora::get_analogs() }); analogs.insert({ gitadora, gitadora::get_analogs() });
lights.insert({ gitadora, gitadora::get_lights() }); lights.insert({ gitadora, gitadora::get_lights() });
file_hints[gitadora].emplace_back("gdxg.dll"); file_hints[gitadora].emplace_back("gdxg.dll");
@@ -349,6 +351,15 @@ namespace games {
return it->second; 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) { static std::vector<Button> gen_buttons_keypads(const std::string &game) {
auto buttons = GameAPI::Buttons::getButtons(game); auto buttons = GameAPI::Buttons::getButtons(game);
std::vector<std::string> names; std::vector<std::string> names;
+1
View File
@@ -58,6 +58,7 @@ namespace games {
const std::vector<std::string> &get_games(); const std::vector<std::string> &get_games();
std::vector<Button> *get_buttons(const std::string &game); std::vector<Button> *get_buttons(const std::string &game);
std::string get_buttons_help(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_keypads(const std::string &game);
std::vector<Button> *get_buttons_overlay(const std::string &game); std::vector<Button> *get_buttons_overlay(const std::string &game);
std::vector<Analog> *get_analogs(const std::string &game); std::vector<Analog> *get_analogs(const std::string &game);
+44 -19
View File
@@ -60,9 +60,32 @@ namespace games::sdvx {
// confirmed in EG final in aioIob2Bi2xUFC_Create // confirmed in EG final in aioIob2Bi2xUFC_Create
static_assert(sizeof(AIO_IOB2_BI2X_UFC) == 0x39D8); 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 { struct AIO_IOB2_BI2X_UFC__DEVSTATUS {
// of course you could work with variables here uint8_t InputCounter;
uint8_t buffer[0x19E]; 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];
}; };
/* /*
@@ -156,8 +179,8 @@ namespace games::sdvx {
AIO_IOB2_BI2X_UFC__GetDeviceStatus_orig(This, status); AIO_IOB2_BI2X_UFC__GetDeviceStatus_orig(This, status);
} }
status->buffer[0] = count; status->InputCounter = count;
status->buffer[12] = count; status->Input.DevIoCounter = count;
count++; count++;
// get buttons // get buttons
@@ -165,27 +188,29 @@ namespace games::sdvx {
// control buttons // control buttons
if (GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::Test])) 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])) 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])) 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])) 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])) 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])) 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])) 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])) 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])) 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])) 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])) 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(); const auto now = get_performance_milliseconds();
@@ -220,14 +245,14 @@ namespace games::sdvx {
vol_right += (uint16_t) (GameAPI::Analogs::getState(RI_MGR, analogs[Analogs::VOL_R]) * 65535); vol_right += (uint16_t) (GameAPI::Analogs::getState(RI_MGR, analogs[Analogs::VOL_R]) * 65535);
} }
*((uint16_t*) &status->buffer[312]) = vol_left; status->AnalogLeft = vol_left;
*((uint16_t*) &status->buffer[314]) = vol_right; status->AnalogRight = vol_right;
log_debug( log_debug(
"bi2x_hook", "bi2x_hook",
"knobs = {} {}", "knobs = {} {}",
*((uint16_t*) &status->buffer[312]), status->AnalogLeft,
*((uint16_t*) &status->buffer[314])); status->AnalogRight);
} }
static void __fastcall AIO_IOB2_BI2X_UFC__IoReset(AIO_IOB2_BI2X_UFC *This, static void __fastcall AIO_IOB2_BI2X_UFC__IoReset(AIO_IOB2_BI2X_UFC *This,
@@ -82,7 +82,10 @@ HRESULT STDMETHODCALLTYPE WrappedIMMDevice::Activate(
return ret; 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 // prevent initialization recursion when using some ASIO backends that proxy to DirectSound, WASAPI, or WDM
// like ASIO4All or FlexASIO // like ASIO4All or FlexASIO
@@ -92,23 +95,18 @@ HRESULT STDMETHODCALLTYPE WrappedIMMDevice::Activate(
} }
std::lock_guard initialize_guard(hooks::audio::INITIALIZE_LOCK, std::adopt_lock); std::lock_guard initialize_guard(hooks::audio::INITIALIZE_LOCK, std::adopt_lock);
auto client = reinterpret_cast<IAudioClient *>(*ppInterface);
// release old audio client if initialized // release old audio client if initialized
if (hooks::audio::CLIENT) { if (hooks::audio::CLIENT) {
hooks::audio::CLIENT->Release(); hooks::audio::CLIENT->Release();
} }
/* IAudioClient *client = nullptr;
ret = wrap_audio_client(pReal, dwClsCtx, pActivationParams, &client); if (iid == IID_IAudioClient) {
if (FAILED(ret)) { client = wrap_audio_client(reinterpret_cast<IAudioClient *>(*ppInterface));
return ret; } else { // IID_IAudioClient3
client = wrap_audio_client3(reinterpret_cast<IAudioClient3 *>(*ppInterface));
} }
*/
client = wrap_audio_client(client);
*ppInterface = client; *ppInterface = client;
// persist the audio client // persist the audio client
hooks::audio::CLIENT = client; hooks::audio::CLIENT = client;
hooks::audio::CLIENT->AddRef(); hooks::audio::CLIENT->AddRef();
@@ -10,6 +10,7 @@
#include "hooks/audio/implementations/asio.h" #include "hooks/audio/implementations/asio.h"
#include "hooks/audio/implementations/wave_out.h" #include "hooks/audio/implementations/wave_out.h"
//#include "util/co_task_mem_ptr.h" //#include "util/co_task_mem_ptr.h"
#include "util/utils.h"
#include "defs.h" #include "defs.h"
#include "dummy_audio_client.h" #include "dummy_audio_client.h"
@@ -40,114 +41,9 @@ static void fix_rec_format(WAVEFORMATEX *pFormat) {
pFormat->nAvgBytesPerSec = pFormat->nSamplesPerSec * pFormat->nBlockAlign; 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) { IAudioClient *wrap_audio_client(IAudioClient *audio_client) {
log_misc("audio::wasapi", "wrapping IAudioClient");
AudioBackend *backend = nullptr; AudioBackend *backend = nullptr;
bool requires_dummy = false; bool requires_dummy = false;
@@ -164,9 +60,6 @@ IAudioClient *wrap_audio_client(IAudioClient *audio_client) {
break; break;
} }
} }
//} else if (!exclusive_available) {
// backend = new WaveOutBackend();
//}
IAudioClient *new_client; IAudioClient *new_client;
@@ -182,6 +75,23 @@ IAudioClient *wrap_audio_client(IAudioClient *audio_client) {
return new_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 // IUnknown
HRESULT STDMETHODCALLTYPE WrappedIAudioClient::QueryInterface(REFIID riid, void **ppvObj) { HRESULT STDMETHODCALLTYPE WrappedIAudioClient::QueryInterface(REFIID riid, void **ppvObj) {
@@ -190,8 +100,8 @@ HRESULT STDMETHODCALLTYPE WrappedIAudioClient::QueryInterface(REFIID riid, void
} }
if (riid == IID_WrappedIAudioClient || if (riid == IID_WrappedIAudioClient ||
riid == IID_IAudioClient) riid == IID_IAudioClient ||
{ riid == IID_IAudioClient3) {
this->AddRef(); this->AddRef();
*ppvObj = this; *ppvObj = this;
@@ -282,20 +192,6 @@ HRESULT STDMETHODCALLTYPE WrappedIAudioClient::Initialize(
} }
log_info("audio::wasapi", "IAudioClient::Initialize success, hr={}", FMT_HRESULT(ret)); 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); copy_wave_format(&hooks::audio::FORMAT, pFormat);
return ret; return ret;
@@ -482,3 +378,93 @@ HRESULT STDMETHODCALLTYPE WrappedIAudioClient::GetService(REFIID riid, void **pp
return ret; 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); IAudioClient *wrap_audio_client(IAudioClient *client);
IAudioClient3 *wrap_audio_client3(IAudioClient3 *client);
struct WrappedIAudioClient : IAudioClient { struct WrappedIAudioClient : IAudioClient3 {
explicit WrappedIAudioClient(IAudioClient *orig, AudioBackend *backend) : pReal(orig), backend(backend) {
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; WrappedIAudioClient(const WrappedIAudioClient &) = delete;
@@ -47,9 +54,43 @@ struct WrappedIAudioClient : IAudioClient {
HRESULT STDMETHODCALLTYPE GetService(REFIID riid, void **ppv) override; HRESULT STDMETHODCALLTYPE GetService(REFIID riid, void **ppv) override;
#pragma endregion #pragma endregion
IAudioClient *const pReal; #pragma region IAudioClient2
AudioBackend *const backend; 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; bool exclusive_mode = false;
int frame_size = 0; 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", "... 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", "... 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"); 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); return new LowLatencyAudioClient(audioClient);
} }
@@ -812,6 +812,14 @@ HRESULT STDMETHODCALLTYPE WrappedIDirect3D9::CreateDevice(
"either use -graphics-force-refresh option or change the desktop resolution beforehand."); "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 // force single adapter
if (GRAPHICS_FORCE_SINGLE_ADAPTER) { if (GRAPHICS_FORCE_SINGLE_ADAPTER) {
log_info("graphics::d3d9", "disabling adapter group device with force single adapter mode"); log_info("graphics::d3d9", "disabling adapter group device with force single adapter mode");
@@ -1031,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 // force single adapter
if (GRAPHICS_FORCE_SINGLE_ADAPTER) { if (GRAPHICS_FORCE_SINGLE_ADAPTER) {
log_info("graphics::d3d9", "disabling adapter group device with force single adapter mode"); log_info("graphics::d3d9", "disabling adapter group device with force single adapter mode");
@@ -742,14 +742,12 @@ void SurfaceHook(IDirect3DDevice9 *pReal) {
const int w = param.BackBufferWidth; const int w = param.BackBufferWidth;
const int h = param.BackBufferHeight; const int h = param.BackBufferHeight;
if (cfg::SCREENRESIZE->enable_screen_resize) { // this code used to clear the surface using ColorFill on every call, but
RECT rect { // this turned out to be very expensive, leading to major frame drops in
0, // SDVX Live2D scenario
0, if (cfg::SCREENRESIZE->need_surface_clean) {
(LONG)topSurface_width, pReal->ColorFill(topSurface, nullptr, D3DCOLOR_XRGB(0, 0, 0));
(LONG)topSurface_height, cfg::SCREENRESIZE->need_surface_clean = false;
};
pReal->ColorFill(topSurface, &rect, D3DCOLOR_XRGB(0, 0, 0));
} }
D3DLOCKED_RECT rect; D3DLOCKED_RECT rect;
+1
View File
@@ -63,6 +63,7 @@ bool GRAPHICS_WINDOWED = false;
std::vector<HWND> GRAPHICS_WINDOWS; std::vector<HWND> GRAPHICS_WINDOWS;
std::optional<HWND> GRAPHICS_WINDOW_MAIN; std::optional<HWND> GRAPHICS_WINDOW_MAIN;
UINT GRAPHICS_FORCE_REFRESH = 0; UINT GRAPHICS_FORCE_REFRESH = 0;
std::optional<uint32_t> GRAPHICS_FORCE_REFRESH_SUB;
std::optional<int> GRAPHICS_FORCE_VSYNC_BUFFER; std::optional<int> GRAPHICS_FORCE_VSYNC_BUFFER;
bool GRAPHICS_FORCE_SINGLE_ADAPTER = false; bool GRAPHICS_FORCE_SINGLE_ADAPTER = false;
bool GRAPHICS_PREVENT_SECONDARY_WINDOW = false; bool GRAPHICS_PREVENT_SECONDARY_WINDOW = false;
+1
View File
@@ -33,6 +33,7 @@ extern graphics_orientation GRAPHICS_ADJUST_ORIENTATION;
extern std::vector<HWND> GRAPHICS_WINDOWS; extern std::vector<HWND> GRAPHICS_WINDOWS;
extern std::optional<HWND> GRAPHICS_WINDOW_MAIN; extern std::optional<HWND> GRAPHICS_WINDOW_MAIN;
extern UINT GRAPHICS_FORCE_REFRESH; extern UINT GRAPHICS_FORCE_REFRESH;
extern std::optional<uint32_t> GRAPHICS_FORCE_REFRESH_SUB;
extern std::optional<int> GRAPHICS_FORCE_VSYNC_BUFFER; extern std::optional<int> GRAPHICS_FORCE_VSYNC_BUFFER;
extern bool GRAPHICS_FORCE_SINGLE_ADAPTER; extern bool GRAPHICS_FORCE_SINGLE_ADAPTER;
extern bool GRAPHICS_PREVENT_SECONDARY_WINDOW; extern bool GRAPHICS_PREVENT_SECONDARY_WINDOW;
+19
View File
@@ -107,6 +107,7 @@
#include "util/libutils.h" #include "util/libutils.h"
#include "util/logging.h" #include "util/logging.h"
#include "util/peb.h" #include "util/peb.h"
#include "util/socd_cleaner.h"
#include "util/sysutils.h" #include "util/sysutils.h"
#include "util/tapeled.h" #include "util/tapeled.h"
#include "util/time.h" #include "util/time.h"
@@ -326,6 +327,10 @@ int main_implementation(int argc, char *argv[]) {
if (options[launcher::Options::GraphicsForceRefresh].is_active()) { if (options[launcher::Options::GraphicsForceRefresh].is_active()) {
GRAPHICS_FORCE_REFRESH = options[launcher::Options::GraphicsForceRefresh].value_uint32(); 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()) { if (options[launcher::Options::GraphicsForceSingleAdapter].value_bool()) {
GRAPHICS_FORCE_SINGLE_ADAPTER = true; GRAPHICS_FORCE_SINGLE_ADAPTER = true;
} }
@@ -584,6 +589,9 @@ int main_implementation(int argc, char *argv[]) {
if (options[launcher::Options::GitaDoraCabinetType].is_active()) { if (options[launcher::Options::GitaDoraCabinetType].is_active()) {
games::gitadora::CAB_TYPE = options[launcher::Options::GitaDoraCabinetType].value_uint32(); 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()) { if (options[launcher::Options::LoadNostalgiaModule].value_bool()) {
attach_nostalgia = true; attach_nostalgia = true;
} }
@@ -605,6 +613,17 @@ int main_implementation(int argc, char *argv[]) {
if (options[launcher::Options::GitaDoraTwoChannelAudio].value_bool()) { if (options[launcher::Options::GitaDoraTwoChannelAudio].value_bool()) {
games::gitadora::TWOCHANNEL = true; 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()) { if (options[launcher::Options::LoadDDRModule].value_bool()) {
attach_ddr = true; attach_ddr = true;
} }
+55 -7
View File
@@ -221,9 +221,11 @@ static const std::vector<OptionDefinition> OPTION_DEFINITIONS = {
.category = "Graphics (Full Screen)", .category = "Graphics (Full Screen)",
}, },
{ {
.title = "Force Refresh Rate", .title = "Force Monitor Refresh Rate",
.name = "graphics-force-refresh", .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, .type = OptionType::Integer,
.category = "Graphics (Common)", .category = "Graphics (Common)",
}, },
@@ -242,21 +244,34 @@ static const std::vector<OptionDefinition> OPTION_DEFINITIONS = {
}, },
{ {
// FullscreenOrientationFlip // FullscreenOrientationFlip
.title = "Full Screen Orientation Swap", .title = "Full Screen Orientation Swap (EXPERIMENTAL)",
.name = "forceresswap", .name = "forceresswap",
.desc = .desc =
"Allows you to play portrait games in in landscape (and vice versa) by transposing resolution and applying image scaling.\n\n" "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" "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" "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, .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)" .category = "Graphics (Full Screen)"
}, },
{ {
// Graphics9On12 // Graphics9On12
.title = "DirectX 9 on 12 (DEPRECATED - use -dx9on12 instead)", .title = "DirectX 9 on 12 (DEPRECATED - use -dx9on12 instead)",
.name = "9on12", .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, .type = OptionType::Bool,
.hidden = true, .hidden = true,
.category = "Graphics (Common)", .category = "Graphics (Common)",
@@ -267,7 +282,7 @@ static const std::vector<OptionDefinition> OPTION_DEFINITIONS = {
.name = "sp2x-dx9on12", .name = "sp2x-dx9on12",
.display_name = "dx9on12", .display_name = "dx9on12",
.aliases= "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)", "Default: auto (use DX9 for most games, but turn on 9on12 for games that require it on non-NVIDIA GPUs)",
.type = OptionType::Enum, .type = OptionType::Enum,
.category = "Graphics (Common)", .category = "Graphics (Common)",
@@ -963,6 +978,39 @@ static const std::vector<OptionDefinition> OPTION_DEFINITIONS = {
.category = "Game Options", .category = "Game Options",
.elements = {{"1", "DX"}, {"2", "SD"}, {"3", "SD2 - white cab"}}, .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", .title = "Force Load Jubeat Module",
.name = "jb", .name = "jb",
@@ -2225,7 +2273,7 @@ static const std::vector<OptionDefinition> OPTION_DEFINITIONS = {
.display_name = "tapeledalgo", .display_name = "tapeledalgo",
.aliases= "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. " .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, .type = OptionType::Enum,
.category = "I/O Options", .category = "I/O Options",
.elements = { .elements = {
+3
View File
@@ -31,6 +31,7 @@ namespace launcher {
GraphicsForceRefresh, GraphicsForceRefresh,
FullscreenResolution, FullscreenResolution,
FullscreenOrientationFlip, FullscreenOrientationFlip,
FullscreenSubRefreshRate,
Graphics9On12, Graphics9On12,
spice2x_Dx9On12, spice2x_Dx9On12,
NoLegacy, NoLegacy,
@@ -99,6 +100,8 @@ namespace launcher {
LoadGitaDoraModule, LoadGitaDoraModule,
GitaDoraTwoChannelAudio, GitaDoraTwoChannelAudio,
GitaDoraCabinetType, GitaDoraCabinetType,
GitaDoraLefty,
GitaDoraWailHold,
LoadJubeatModule, LoadJubeatModule,
LoadReflecBeatModule, LoadReflecBeatModule,
LoadShogikaiModule, LoadShogikaiModule,
+88 -42
View File
@@ -7,6 +7,8 @@
#include "util/detour.h" #include "util/detour.h"
#include "util/libutils.h" #include "util/libutils.h"
#include "util/circular_buffer.h" #include "util/circular_buffer.h"
#include "util/socd_cleaner.h"
#include "util/time.h"
#include "misc/eamuse.h" #include "misc/eamuse.h"
#include "cfg/api.h" #include "cfg/api.h"
#include "acio/icca/icca.h" #include "acio/icca/icca.h"
@@ -336,6 +338,9 @@ static int __cdecl gfdm_unit2_boot_initialize() {
return 1; 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) { static void *__cdecl gfdm_unit_get_button_p(void *a1, int a2, size_t player) {
memset(a1, 0, 64); 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; return a1;
} }
// log_info("gitadora", "gfdm_unit_get_button_p: a2={}, player={}", a2, player);
// get buttons // get buttons
auto &buttons = games::gitadora::get_buttons(); auto &buttons = games::gitadora::get_buttons();
auto &analogs = games::gitadora::get_analogs(); 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 // wail X
((int *) a1)[4] = a2 == 1 ? 4080 : -4080; ((int *) a1)[4] = games::gitadora::is_player_lefty(player) ? LEFTY_X_CENTER : -4080;
if (analogs.at(player * 3 + 0).isSet()) auto &analog_x = analogs.at(gitadora_analog_mapping[player * 4 + 0]);
((int *) a1)[4] = lroundf(Analogs::getState( if (analog_x.isSet())
RI_MGR, analogs.at(gitadora_analog_mapping[player * 4 + 0])) * 8160.f) - 4080; ((int *) a1)[4] = lroundf(Analogs::getState(RI_MGR, analog_x) * 8160.f) - 4080;
// Y // wail Y
((int *) a1)[5] = 0; ((int *) a1)[5] = 0;
if (analogs.at(player * 3 + 1).isSet()) auto &analog_y = analogs.at(gitadora_analog_mapping[player * 4 + 1]);
((int *) a1)[5] = lroundf(Analogs::getState( if (analog_y.isSet()) {
RI_MGR, analogs.at(gitadora_analog_mapping[player * 4 + 1])) * 8160.f) - 4080; ((int *) a1)[5] = lroundf(Analogs::getState(RI_MGR, analog_y) * 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;
// 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; ((int *) a1)[6] = 0;
if (analogs.at(player * 3 + 2).isSet()) auto &analog_z = analogs.at(gitadora_analog_mapping[player * 4 + 2]);
((int *) a1)[6] = lroundf(Analogs::getState( if (analog_z.isSet())
RI_MGR, analogs.at(gitadora_analog_mapping[player * 4 + 2])) * 8160.f) - 4080; ((int *) a1)[6] = lroundf(Analogs::getState(RI_MGR, analog_z) * 8160.f) - 4080;
// return the same buffer // return the same buffer
return a1; return a1;
@@ -585,63 +620,74 @@ static long __cdecl gfdm_unit2_get_input(int device) {
return gfdm_unit_get_input_p(device, 1); 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) { static long __cdecl gfdm_unit_get_sensor_gf_p(int a1, int a2, size_t player) {
// return if it's actually drum mania // return if it's actually drum mania
if (games::gitadora::is_drum()) if (games::gitadora::is_drum())
return 0; return 0;
// log_info("gitadora", "gfdm_unit_get_sensor_gf_p: a1={}, a2={}, player={}", a1, a2, player);
// get buttons and analogs // get buttons and analogs
auto &buttons = games::gitadora::get_buttons(); auto &buttons = games::gitadora::get_buttons();
auto &analogs = games::gitadora::get_analogs(); 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) { if (a2 == 0) {
long ret = games::gitadora::is_player_lefty(player) ? LEFTY_X_CENTER : -4080;
// analog override // analog override
if (analogs.at(gitadora_analog_mapping[player * 3 + 0]).isSet()) { auto &analog_x = analogs.at(gitadora_analog_mapping[player * 4 + 0]);
return lroundf(Analogs::getState( if (analog_x.isSet()) {
RI_MGR, analogs.at(gitadora_analog_mapping[player * 4 + 0])) * 8160.f) - 4080; 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 // default
return a1 == 1 ? 4080 : -4080; return ret;
} }
// Y // wail Y
if (a2 == 1) { if (a2 == 1) {
long ret = 0;
// analog override // analog override
if (analogs.at(player * 3 + 1).isSet()) { auto &analog_y = analogs.at(gitadora_analog_mapping[player * 4 + 1]);
return lroundf(Analogs::getState( if (analog_y.isSet()) {
RI_MGR, analogs.at(gitadora_analog_mapping[player * 4 + 1])) * 8160.f) - 4080; ret = lroundf(Analogs::getState(RI_MGR, analog_y) * 8160.f) - 4080;
} }
// variables // digital wail up/down
long ret = 0; if (wail_result == socd::TiltUp) {
size_t offset = (size_t) player * 11; ret = games::gitadora::is_player_lefty(player) ? 0 : -4080;
} else if (wail_result == socd::TiltDown) {
// wail up ret = games::gitadora::is_player_lefty(player) ? 0 : 4080;
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;
} }
// return value // return value
return ret; return ret;
} }
// Z // wail Z
if (a2 == 2) { if (a2 == 2) {
// analog override // analog override
if (analogs.at(gitadora_analog_mapping[player * 3 + 2]).isSet()) { auto &analog_z = analogs.at(gitadora_analog_mapping[player * 4 + 2]);
return lroundf(Analogs::getState( if (analog_z.isSet()) {
RI_MGR, analogs.at(gitadora_analog_mapping[player * 4 + 2])) * 8160.f) - 4080; return lroundf(Analogs::getState(RI_MGR, analog_z) * 8160.f) - 4080;
} }
// default // default
+11 -1
View File
@@ -249,7 +249,7 @@ namespace overlay::windows {
// help text for binding buttons, if the game has one // help text for binding buttons, if the game has one
const auto help_text = games::get_buttons_help(this->games_selected_name); const auto help_text = games::get_buttons_help(this->games_selected_name);
if (!help_text.empty()) { 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::Spacing();
ImGui::TextWrapped("%s", help_text.c_str()); ImGui::TextWrapped("%s", help_text.c_str());
ImGui::TextUnformatted(""); ImGui::TextUnformatted("");
@@ -299,8 +299,18 @@ namespace overlay::windows {
} }
if (ImGui::BeginTabItem("Analogs")) { if (ImGui::BeginTabItem("Analogs")) {
tab_selected_new = ConfigTab::CONFIG_TAB_ANALOGS; tab_selected_new = ConfigTab::CONFIG_TAB_ANALOGS;
ImGui::BeginChild("Analogs", ImVec2( ImGui::BeginChild("Analogs", ImVec2(
0, ImGui::GetWindowContentRegionMax().y - page_offset2), false); 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)); this->build_analogs("Game", games::get_analogs(this->games_selected_name));
ImGui::EndChild(); ImGui::EndChild();
ImGui::EndTabItem(); ImGui::EndTabItem();
+13 -11
View File
@@ -1,5 +1,6 @@
#include <iomanip> #include <iomanip>
#include <sstream> #include <sstream>
#include "external/fmt/include/fmt/chrono.h"
#include "fps.h" #include "fps.h"
namespace overlay::windows { namespace overlay::windows {
@@ -15,7 +16,8 @@ namespace overlay::windows {
| ImGuiWindowFlags_NoNavInputs | ImGuiWindowFlags_NoNavInputs
| ImGuiWindowFlags_NoDocking; | ImGuiWindowFlags_NoDocking;
this->bg_alpha = 0.5f; 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() { void FPS::calculate_initial_window() {
@@ -34,23 +36,23 @@ namespace overlay::windows {
ImGui::Text("FPS: %.1f", io.Framerate); ImGui::Text("FPS: %.1f", io.Framerate);
// ImGui::Text("FT: %.2fms", 1000 / 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 // current time
{ {
auto now_t = std::chrono::system_clock::to_time_t(now); const std::time_t tt = std::chrono::system_clock::to_time_t(now_s);
static CHAR buf[48]; std::tm local_tm{};
std::strftime(buf, sizeof(buf), "Time: %H:%M:%S", std::localtime(&now_t)); localtime_s(&local_tm, &tt);
ImGui::Text(buf); ImGui::TextUnformatted(fmt::format("Time: {:%H:%M:%S}", local_tm).c_str());
} }
// elapsed time // elapsed time
{ {
auto d = now - this->start_time; const auto uptime = now_s - this->start_time;
const auto h = std::chrono::duration_cast<std::chrono::hours>(d); ImGui::TextUnformatted(
const auto m = std::chrono::duration_cast<std::chrono::minutes>(d - h); fmt::format("Up: {:%H:%M:%S}",
const auto s = std::chrono::duration_cast<std::chrono::seconds>(d - h - m); std::chrono::floor<std::chrono::seconds>(uptime)).c_str());
ImGui::Text("Up: %02d:%02d:%02d", (int)h.count(), (int)m.count(), (int)s.count());
} }
} }
} }
+4
View File
@@ -3,6 +3,7 @@
#include "launcher/launcher.h" #include "launcher/launcher.h"
#include "games/io.h" #include "games/io.h"
#include "misc/eamuse.h" #include "misc/eamuse.h"
#include "overlay/imgui/extensions.h"
namespace overlay::windows { namespace overlay::windows {
@@ -80,6 +81,9 @@ namespace overlay::windows {
{ {
ImGui::SetCursorPosY(ImGui::GetCursorPosY() - ImGui::GetFrameHeightWithSpacing()); ImGui::SetCursorPosY(ImGui::GetCursorPosY() - ImGui::GetFrameHeightWithSpacing());
this->build_button("+", tall, this->test_button, this->service_button); this->build_button("+", tall, this->test_button, this->service_button);
if (ImGui::IsItemHovered()) {
ImGui::HelpTooltip("SERVICE + TEST");
}
} }
ImGui::EndGroup(); ImGui::EndGroup();
@@ -3,6 +3,7 @@
#include "games/io.h" #include "games/io.h"
#include "games/gitadora/gitadora.h" #include "games/gitadora/gitadora.h"
#include "games/gitadora/io.h" #include "games/gitadora/io.h"
#include "overlay/imgui/extensions.h"
#include "misc/eamuse.h" #include "misc/eamuse.h"
#include "util/logging.h" #include "util/logging.h"
@@ -11,6 +12,7 @@ namespace overlay::windows {
GitadoraIOPanel::GitadoraIOPanel(SpiceOverlay *overlay) : IOPanel(overlay) { GitadoraIOPanel::GitadoraIOPanel(SpiceOverlay *overlay) : IOPanel(overlay) {
this->title = "GITADORA IO Panel"; this->title = "GITADORA IO Panel";
this->has_menu_controls = true;
// by default, make a safer assumption that there are two players // by default, make a safer assumption that there are two players
this->two_players = true; this->two_players = true;
// by default, enable the extra input only available on DX cabs... // by default, enable the extra input only available on DX cabs...
@@ -33,6 +35,12 @@ namespace overlay::windows {
this->has_guitar_knobs = false; 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(); find_gfdm_buttons();
} }
@@ -70,23 +78,43 @@ namespace overlay::windows {
void GitadoraIOPanel::build_io_panel() { void GitadoraIOPanel::build_io_panel() {
ImGui::Dummy(overlay::apply_scaling_to_vector(12, 0)); ImGui::Dummy(overlay::apply_scaling_to_vector(12, 0));
if (this->has_menu_controls) {
ImGui::SameLine(); ImGui::SameLine();
ImGui::PushID("P1");
this->draw_buttons(0); this->draw_buttons(0);
if (this->has_guitar_knobs) { if (this->has_guitar_knobs) {
ImGui::SameLine(); ImGui::SameLine();
this->draw_sliders(0); this->draw_sliders(0);
} }
ImGui::PopID();
// draw p2 only if guitar freaks // draw p2 only if guitar freaks
if (this->two_players) { if (this->two_players) {
ImGui::SameLine(); ImGui::SameLine();
ImGui::Dummy(overlay::apply_scaling_to_vector(12, 0)); ImGui::Dummy(overlay::apply_scaling_to_vector(12, 0));
ImGui::SameLine(); ImGui::SameLine();
ImGui::PushID("P2");
this->draw_buttons(1); this->draw_buttons(1);
if (this->has_guitar_knobs) { if (this->has_guitar_knobs) {
ImGui::SameLine(); ImGui::SameLine();
this->draw_sliders(1); 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()); ImGui::SetCursorPosY(ImGui::GetCursorPosY() + ImGui::GetFrameHeightWithSpacing());
this->build_button("<", leftright_size, this->left[p], nullptr, this->leftright_light[p]); 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(); ImGui::EndGroup();
@@ -135,6 +167,9 @@ namespace overlay::windows {
ImGui::BeginGroup(); ImGui::BeginGroup();
{ {
this->build_button("?", tiny_size, this->help[p], nullptr, this->help_light[p]); 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]); this->build_button(">", leftright_size, this->right[p], nullptr, this->leftright_light[p]);
} }
ImGui::EndGroup(); ImGui::EndGroup();
@@ -18,6 +18,7 @@ namespace overlay::windows {
void draw_buttons(const int player); void draw_buttons(const int player);
void draw_sliders(const int player); void draw_sliders(const int player);
bool has_menu_controls;
bool two_players; bool two_players;
bool has_guitar_knobs; bool has_guitar_knobs;
+15 -5
View File
@@ -1,11 +1,13 @@
#include "patch_manager.h" #include "patch_manager.h"
#include <chrono>
#include <thread> #include <thread>
#include <fstream> #include <fstream>
#include <shellapi.h> #include <shellapi.h>
#include <winhttp.h> #include <winhttp.h>
#include <psapi.h> #include <psapi.h>
#include <format> #include <format>
#include "external/fmt/include/fmt/chrono.h"
#include "external/rapidjson/document.h" #include "external/rapidjson/document.h"
#include "external/rapidjson/prettywriter.h" #include "external/rapidjson/prettywriter.h"
#include "external/rapidjson/stringbuffer.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 time_date_stamp = 0;
uint32_t address_of_entry_point = 0; uint32_t address_of_entry_point = 0;
@@ -1168,6 +1170,16 @@ namespace overlay::windows {
time_date_stamp, time_date_stamp,
address_of_entry_point); 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; return identifier;
} }
@@ -1657,14 +1669,12 @@ namespace overlay::windows {
ACTIVE_JSON_FILE = ""; ACTIVE_JSON_FILE = "";
std::string firstDll = avs::game::DLL_NAME; 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); std::filesystem::path firstPath = fmt::format("patches/{}.json", first_id);
log_misc("patchmanager", "patch identifier of {}: {}", firstDll, first_id);
auto extraDlls = getExtraDlls(firstDll); auto extraDlls = getExtraDlls(firstDll);
std::erase_if(extraDlls, [](const std::string& dll) { 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)); 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; 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 { class PatchManager : public Window {
public: public:
+29 -5
View File
@@ -100,6 +100,21 @@ namespace overlay::windows {
} }
void ScreenResize::build_fullscreen_config() { 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 // enable checkbox
ImGui::Checkbox("Enable", &cfg::SCREENRESIZE->enable_screen_resize); ImGui::Checkbox("Enable", &cfg::SCREENRESIZE->enable_screen_resize);
ImGui::SameLine(); ImGui::SameLine();
@@ -141,22 +156,27 @@ namespace overlay::windows {
// aspect ratio // aspect ratio
ImGui::Checkbox("Keep Aspect Ratio", &scene.keep_aspect_ratio); ImGui::Checkbox("Keep Aspect Ratio", &scene.keep_aspect_ratio);
if (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; scene.scale_y = scene.scale_x;
} }
ImGui::SameLine(); ImGui::SameLine();
ImGui::HelpMarker("Hint: ctrl + click on the slider to type in a numeric value."); ImGui::HelpMarker("Hint: ctrl + click on the slider to type in a numeric value.");
} else { } 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::SameLine();
ImGui::HelpMarker("Hint: ctrl + click on the slider to type in a numeric value."); 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::SameLine();
ImGui::HelpMarker("Hint: ctrl + click on the slider to type in a numeric value."); ImGui::HelpMarker("Hint: ctrl + click on the slider to type in a numeric value.");
} }
static const char* dupe_items[] = { "None", "Copy Left", "Copy Right" }; static const char* dupe_items[] = { "None", "Copy Left", "Copy Right" };
ImGui::Combo( const bool duplicate_changed = ImGui::Combo(
"Duplicate", "Duplicate",
reinterpret_cast<int *>(&scene.duplicate), reinterpret_cast<int *>(&scene.duplicate),
dupe_items, dupe_items,
@@ -167,6 +187,11 @@ namespace overlay::windows {
"wrap-around effect when an offset is set."); "wrap-around effect when an offset is set.");
ImGui::EndDisabled(); 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() { void ScreenResize::build_windowed_config() {
@@ -176,7 +201,6 @@ namespace overlay::windows {
return; return;
} }
ImGui::TextUnformatted("Warning: may cause some games to crash.");
ImGui::BeginDisabled(graphics_window_change_crashes_game()); ImGui::BeginDisabled(graphics_window_change_crashes_game());
if (ImGui::Combo( if (ImGui::Combo(
"Window Style", "Window Style",
+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.midiInfo->v2_last_off_time[midi_index] = get_performance_milliseconds();
device.updated = true; device.updated = true;
} }
// for v2_drum, NOTE ON is ignored // for v2_drum, NOTE OFF is ignored
} }
} }
+3 -2
View File
@@ -13,8 +13,9 @@ std::string_view log_get_datetime() {
std::string_view log_get_datetime(std::time_t now) { std::string_view log_get_datetime(std::time_t now) {
static thread_local char buf[64]; static thread_local char buf[64];
// `localtime` on Windows is thread-safe std::tm local_tm{};
strftime(buf, sizeof(buf), "[%Y/%m/%d %X]", localtime(&now)); localtime_s(&local_tm, &now);
strftime(buf, sizeof(buf), "[%Y/%m/%d %X]", &local_tm);
return buf; return buf;
} }
+59
View File
@@ -84,4 +84,63 @@ namespace socd {
return SocdNone; 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;
}
} }
+14
View File
@@ -4,6 +4,8 @@
namespace socd { namespace socd {
// SOCD for knobs / turntables
enum class SocdAlgorithm { enum class SocdAlgorithm {
Neutral, Neutral,
PreferRecent, PreferRecent,
@@ -19,4 +21,16 @@ namespace socd {
} SocdResult; } SocdResult;
SocdResult socd_clean(uint8_t device, bool ccw, bool cw, double time_now); 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 { } else {
log_misc("gpuinfo", "EnumDisplaySettingsA failed"); log_misc("gpuinfo", "EnumDisplaySettingsA failed");
} }
log_misc(
"gpuinfo", "{} {} is primary : {}",
prefix.c_str(),
index,
(adapter->StateFlags & DISPLAY_DEVICE_PRIMARY_DEVICE) ? "yes" : "no");
} }
} }