Compare commits

...

35 Commits

Author SHA1 Message Date
bicarus 5f94ef4478 overlay: fix scroll bar jumping around when regaining focus (#673)
## Link to GitHub Issue or related Pull Request, if one exists
Regression caused by #604, probably

## Description of change
When spicecfg or the game loses focus and regains it, scroll bar jumps
around. This was because we weren't keeping proper track of mouse wheel
position while not in focus, so when we gain it back, it's as if the
scroll bar was moved a huge distance.

## Testing
Tested spicecfg and in overlay.
2026-05-03 03:49:52 -07:00
bicarus c4193eddc5 iidx: millisecond-based delay option (#672)
## Link to GitHub Issue or related Pull Request, if one exists
#181 

## Description of change
Use millisecond-based calculation to add artificial delay to the TT
input.

## Testing
Tested 64 bit TDJ and LDJ.
2026-05-03 01:23:45 -07:00
bicarus c8962a0e77 overlay: setting button as always-on (#671)
## Link to GitHub Issue or related Pull Request, if one exists
n/a

## Description of change

Add a checkbox to the buttons edit dialog that lets you set a device as
"always on", accomplished by setting the device to `Naive`, vkey to
`0xff` (invalid), and `Invert`.

No backend rawinput changes.

## Testing
2026-05-02 22:42:36 -07:00
din b6a749e5db add snek to lights match map (#670)
## Description of change
add snek board to lights mapping

## Testing
with a snek board


<img width="490" height="477"
alt="DeepinScreenshot_select-area_20260502164249"
src="https://github.com/user-attachments/assets/a86affff-74c6-4d5d-b657-fab12c40aaa4"
/>
2026-05-02 15:49:19 -07:00
bicarus-dev 7dbbe9809e update iidx tt delay option text 2026-05-02 02:50:29 -07:00
bicarus 79a3e32d5d iidx: delay logic for iidx tt (#669)
## Link to GitHub Issue or related Pull Request, if one exists
Re-implements fix for #181 as it was removed by #668

## Description of change
Add two options to add artificial delay to turntable input. These
operate directly on the input logic (`get_tt`) which is called by I/O
emulation code.

Functionally, it's **identical** to the `Delay (experimental)` option we
had in the analog tab, just moved to Options tab.

## Testing
64 bit tdj: ok
64 bit ldj bi2a: ok
32 bit ldj ezusb: ok

Worth noting that TDJ bi2x_hook polls at 120Hz, bi2a at ~500Hz, and
ezusb at ~170Hz
2026-05-02 02:17:51 -07:00
bicarus c5a4e954f9 rawinput: remove experimental analog features (delay and relative axis mode) (#668)
## Link to GitHub Issue or related Pull Request, if one exists
Related to #181 

## Description of change
This PR removes two features marked as "experimental" for analog axis -
`Delay` and `Relative Axis`.

They have been in spice as experimental option for almost 2 years.. but
they have a fundamental problem of being tied to how often the analog is
polled. Not just how often the game's I/O emulation code polls it, but
literally any source (spice API, overlay, etc).

For `Delay`, the only real usage I'm aware of is for #181 which is
delaying IIDX turntable input. For this, I will follow up with a
separate PR that adds back the delay option specifically for IIDX.

For `Relative Axis` - I'm not quite sure if any one actually uses this.
This could be implemented per-game if some game demands it. Polaris
Chord already has one, for example.

## Testing
2026-05-02 00:51:21 -07:00
bicarus 7b862768b0 overlay: show name of analog control in popup title (#667) 2026-05-02 00:01:33 -07:00
bicarus af8d8dae9f rawinput: distinguish between circular and linear analog input (#665)
## Link to GitHub Issue or related Pull Request, if one exists
n/a

## Description of change
Currently, all analogs are treated as circular values that wrap around.
This works for knobs and turntables, but can get weird for linear values
(like tilt sensors and sliders) especially once analog options are
enabled, such as sensitivity.

This PR groups analogs into 3 buckets: circular, linear (centered), and
linear (positive). Linear types get different algorithms applied to it
when options are set (e.g., sensitivity caps out at 0, 1 instead of
wrapping around).

In linear mode:
* Sensitivity value applies an exponential response curve (power curve)
to the original value.
* Multiplier/divisor serves as a linear cut-off.
* Relative mode works as you expect, but of course, values don't wrap
around.
* Invert and deadzone work as you expect (since they applied to the
controller input anyway)
* Smoothing is removed from the UI and has no effect.
* Delay is unaffected.

Also, fix a small bug with -/+ button not working for analog `Delay`
option.

## Testing
WIP
2026-05-01 19:02:32 -07:00
llm96 d9d5823fdb rawinput: add missing nullptr check (#666)
## Link to GitHub Issue or related Pull Request, if one exists
N/A

## Description of change

Fixes a crash when opening the properties modal on a Naive button bind.


https://github.com/spice2x/spice2x.github.io/blob/82dda3380039bbaa995ee99e2d3effebbcb58082/src/spice2x/overlay/windows/config.cpp#L2081

Binds:
<img width="800" height="600" alt="image"
src="https://github.com/user-attachments/assets/18d1fcfb-085f-4a2b-8fce-36809a4bbb5a"
/>

Stack trace from Debug build:
```
overlay::windows::Config::edit_button_popup(const std::string &, const std::string &, Button *, float, int) config.cpp:2081
overlay::windows::Config::build_button(const std::string &, Button &, Button *, int, int, int) config.cpp:1050
overlay::windows::Config::build_buttons(const std::string &, std::vector<…> *, int, int) config.cpp:718
overlay::windows::Config::build_content() config.cpp:283
overlay::Window::build() window.cpp:96
overlay::SpiceOverlay::new_frame() overlay.cpp:486
graphics_d3d9_on_present(HWND__ *, IDirect3DDevice9 *, IDirect3DDevice9 *) d3d9_backend.cpp:1447
__ZN23WrappedIDirect3DDevice97PresentEPK7tagRECTS2_P6HWND__PK8_RGNDATA@20 d3d9_device.cpp:396
main_implementation(int, char **) launcher.cpp:2453
main launcher.cpp:2671
```

Relevant snippet from error log:
```
[2026/05/01 18:50:17] M:config: tab selection changed from 0 to 1
[2026/05/01 18:50:48] W:signal: exception raised: EXCEPTION_ACCESS_VIOLATION
[2026/05/01 18:50:48] W:troubleshooter: 

/-------------------------- spice2x auto-troubleshooter -----------------------\

  spice2x version: 2026-04-23
  game version: LDJ:J:A:A:2013090900

  the game has crashed
    * share this entire log file with someone for troubleshooting (log.txt)
    * spice will also attempt to create a minidump (minidump.dmp)
        minidump should only be shared with people you trust as it may contain
        sensitive data (PCBID, card ID, etc)

  log level is set to `disable` (either in avs-config.xml or using -loglevel)
      this log file may have omitted important error messages from the game
      if you are troubleshooting crashes or failures, it is recommended that you
      set AVS Log Level (-loglevel) option to `all`

  unsure what to do next?
    * update to the latest version:
        https://github.com/spice2x/spice2x.github.io/releases
    * check the FAQ:
        https://github.com/spice2x/spice2x.github.io/wiki/Known-issues

\------------------------- spice2x auto-troubleshooter ------------------------/

[2026/05/01 18:50:48] I:signal: printing callstack
[2026/05/01 18:50:48] I:stackwalker: 006A7001 (spice): (unknown): (unknown)
[2026/05/01 18:50:48] I:stackwalker: 0068DF8C (spice): (unknown): (unknown)
[2026/05/01 18:50:48] I:stackwalker: 0069A7C1 (spice): (unknown): (unknown)
[2026/05/01 18:50:48] I:stackwalker: 0069B857 (spice): (unknown): (unknown)
[2026/05/01 18:50:48] I:stackwalker: 0066461F (spice): (unknown): (unknown)
[2026/05/01 18:50:48] I:stackwalker: 00663B7A (spice): (unknown): (unknown)
[2026/05/01 18:50:48] I:stackwalker: 0045B548 (spice): (unknown): (unknown)
[2026/05/01 18:50:48] I:stackwalker: 004FE1CE (spice): (unknown): (unknown)
[2026/05/01 18:50:48] I:stackwalker: 1008AD13 (bm2dx): (unknown): dll_entry_main
[2026/05/01 18:50:48] I:stackwalker: 100934A7 (bm2dx): (unknown): dll_entry_main
[2026/05/01 18:50:48] I:stackwalker: 10088D24 (bm2dx): (unknown): dll_entry_main
[2026/05/01 18:50:48] I:stackwalker: 0044B8A6 (spice): (unknown): (unknown)
[2026/05/01 18:50:48] I:stackwalker: 77290CB7 (ntdll): (unknown): RtlGetSystemTimeAndBias
[2026/05/01 18:50:53] F:signal: end
[2026/05/01 18:50:53] I:launcher: stopping subsystems
[2026/05/01 18:50:53] I:logger: stop
[2026/05/01 18:50:53] I:audio: stopping
[2026/05/01 18:50:54] I:rawinput: disposing devices
[2026/05/01 18:50:54] I:xinput: destroyed
```

## Testing
Replicated on upstream build from `spice2x-26-04-23-full.zip`
2026-05-01 14:42:45 -07:00
bicarus 82dda33800 graphics: restore monitor settings when fullscreen is lost then restored (#664)
## Link to GitHub Issue or related Pull Request, if one exists
Fixes #663 

## Description of change
Apply monitor rotation and refresh rate again when fullscreen focus is
regained.

## Testing
Tested with sdvx7
2026-04-28 16:04:52 -07:00
bicarus 3876e38636 xinput: small fixes (#661)
## Link to GitHub Issue or related Pull Request, if one exists
#616 

## Description of change
Fix UI labels being wrong for analog stick up/down (they were reversed).

Adjust deadzone so that when sticks are bound as digital buttons, they
are not as sensitive.

Small refactoring of code.

## Testing
Tested via spicecfg using my xbox one controller.
2026-04-27 22:19:23 -07:00
bicarus 6bc1357d6a ddr: add support for analog axis-only pads and handle left+right / up+down input (#659)
## Link to GitHub Issue or related Pull Request, if one exists
n/a

## Description of change
Some crappy DDR soft mats (either USB-based or console pads with crappy
adapters that lack a dedicated "DDR pad mode") use X and Y analog axis
for arrows, which means they cannot cleanly report Up+Down or Left+Right
input.

According to some ancient Stepmania code, a lot of these report a middle
value (value between center and right, for example, for left+right).
Borrow this hack from Stepmania and implement it in spice as analog
input for DDR.

Also, for rawinput/xinput automatic bind scenario, update the logic so
that face buttons are always detected before analog axis / dpads for
consistency. This would help with binding any DDR pads that
simultaneously input face buttons / dpad / analog axis, so that we can
prefer face buttons when detected.

## Testing
I don't have any soft mats that have this, but it was tested with an
xbox controller.
2026-04-27 20:17:54 -07:00
Will 37218e7fe0 Try and preserve the wideness of std::filesystem::path more (#660)
As noted in #567, a filesystem path that contains non-ascii will break a
lot if using a clang toolchain.

Luckily, fmtlib has a lossy utf8 convert when you use it to print a path
(after including `fmt/std.h`). The vast majority of this diff is just
removing `.string()` from paths inside loggings calls.

There are some callsites I _didn't_ touch, mainly the options, because
it would be an ABI break to change those to be wide strings and I cbf
looking into settings upgrades. There are also some spots (avs mountpath
remapping, for example) where the path is guaranteed to be ascii, so I
didn't modify them.

ImGui doesn't appear to easily support wide strings (I mean, surely it
does, but I'm not gonna look too far into it) so I mostly just left
those alone too, with a few spots modified to re-use fmtlib's lossy
utf8.

Some of the changes are basically never gonna be hit IRL, like who would
put a file with a non-ascii _extension_ along with their modules? But
the diff is (I hope) pretty easy to validate as OK.

Testing has been somewhat minimal, I fired up the GCC build of spice2x
in a dodgy folder name, got mojibake (running via wine in linux so take
that as you will), ran the unmodified clang spice and crashed the same
way the reporter did. After modification, I get the exact same mojibake
so I assume if the terminal enjoys utf8 it'll display OK.

Claude (only used for review) thinks the commit is fine but is annoyed
that I use `fmt::detail` in the appdata censoring, which is part of the
private API; personally I don't care because it's pretty stable.
2026-04-27 20:07:15 -07:00
bicarus 678e11eade rawinput: fix up devices that report invalid max values (#658)
## Link to GitHub Issue or related Pull Request, if one exists
regression caused by #653

## Description of change
Some controllers - like the Xbox One controller I have - reports
0xffffffff as the max range value for analog, despite reporting 16-bit
width. This causes us to consider 0xffffffff as -1 so the range became
[0, -1] which is invalid.

Our automatic calibration does eventually fix this (-1 will fix itself
to become the highest actual value reported, which would be 0xffff).

This seems to be a common issue with XInput-based controllers in
general: see `Correct_Axis_Max` in
https://github.com/argonlefou/DemulShooter/blob/master/DsCore/RawInput/RawInputController.cs

#653 removed the fix that was masking this issue. Put in a new fix that
fixes these up.

## Testing
Tested with Xbox One controller.
2026-04-27 01:41:44 -07:00
bicarus f72313fe45 sdvx: work around bad logger configuration (#657)
## Link to GitHub Issue or related Pull Request, if one exists
#345 

## Description of change
SDVX4 data from certain private forum has bad logger configuration and
crashes on boot. Add an explicit workaround to fix up that broken
`avs-config.xml` file, scoped to `soundvoltex.dll` + 32-bit.

## Testing
Tested HH.
2026-04-23 01:33:49 -07:00
bicarus-dev 06374ef78a cosmetic fix to build script 2026-04-22 23:14:03 -07:00
bicarus-dev bdde1ec6b8 update ifs_hook missing warning message 2026-04-22 22:56:55 -07:00
bicarus-dev d2176fe4f0 clear out button properties when switching devices in edit dialog 2026-04-22 22:48:29 -07:00
bicarus fb9eef904a popn: block keypad overlay window in new cab i/o (#656)
Redirect users to the subscreen overlay when keypad window is open.
2026-04-22 22:21:18 -07:00
bicarus 48903998a5 ea3: fix boot failure when -e is specified to override ea3-config path (#655)
## Link to GitHub Issue or related Pull Request, if one exists
Regression introduced by #595 

## Description of change
`avs::core::file_exists` fails if you use `\` as path separator... what
I meant to use was `fileutils::file_exists`.

This would have caused failures - mostly for gitadora players - if they
used `prop\ea3-config2.xml` for example.

## Testing
2026-04-22 01:55:13 -07:00
bicarus f2dca0265f rawinput: custom threshold for BAT (button-analog type) (#654)
## Link to GitHub Issue or related Pull Request, if one exists
#653 

## Description of change
By default, BAT Positive / Negative binds trigger when the stick is
tilted 10% off the center.

This needs a bit more customization since:

1. User may want to customize the center point (e.g., guitar controller
neutral position)
2. User may want to adjust the sensitivity (e.g., guitar controller tilt
angle to trigger wailing).

## Testing
WIP
2026-04-22 01:14:15 -07:00
bicarus 1147ed9858 rawinput: fix signed range values (#653)
## Link to GitHub Issue or related Pull Request, if one exists
n/a

## Description of change
Official GuitarFreaks controller has the motion sensor values with
`valuemin=-127` to `valuemax=127` and we don't deal with negative values
correctly.

## Testing
tbd
2026-04-21 22:56:27 -07:00
bicarus 19d93f4ffd audio: deal with missing DirectSoundI3DL2ReverbDMO better (#652)
## Link to GitHub Issue or related Pull Request, if one exists
n/a

## Description of change
On some Win11 systems,
`CoCreateInstance(CLSID_DirectSoundI3DL2ReverbDMO)` started to fail with
`ERROR_MOD_NOT_FOUND` instead of `REGDB_E_CLASSNOTREG` for some reason.

## Testing
User tested.
2026-04-21 18:42:21 -07:00
bicarus-dev 15bfffa1d3 Merge branch 'main' of https://github.com/spice2x/spice2x.github.io 2026-04-18 23:07:10 -07:00
bicarus-dev 18a9a47c33 remove dead code 2026-04-18 23:07:05 -07:00
bicarus 6838e69ebe popn: rest of the new cab lights (#651)
## Link to GitHub Issue or related Pull Request, if one exists
#618 

## Description of change
Add rest of the LED strip lights.

## Testing
Tested U region.
2026-04-18 22:17:20 -07:00
bicarus 26b3b64794 rawinput, touch: discard invalid touch points from device in hybrid mode (#649)
## Link to GitHub Issue or related Pull Request, if one exists
Fixes #648

## Description of change
Deal with devices in hybrid packet mode that send garbage data in second
packet when it's out of bounds (outside the number of contact points).
MSDN says they're supposed to be (0, 0), but some devices don't follow
the spec.

## Testing
I don't have a device that reports garbage values but this should fix it
in theory.

I tested on my Dell touch monitor which is in hybrid mode & sends 5
touches at a time, but can split across two packets to send up to 10
touches. It sends (0, 0) for the remaining slots when there are 6-9
touch points, but confirmed that there was no regression.
2026-04-18 16:22:00 -07:00
bicarus aadd2bdd59 cfg: deprecate -sdvx720, add new category for camera/printer options (#650) 2026-04-18 15:56:15 -07:00
Emma d0e24e31ca ftt: allow windowed mode resize options, add title (#646)
## Description of change
This code adds a window title to FutureTomTom when running in windowed
mode and allows the windowed graphics hooks to take effect.

## Testing
I ran the game and made sure fullscreen mode still worked as well as all
window move/resize functionality.
2026-04-16 14:32:06 -07:00
bicarus 4fb7f20c7b iidx: fix crash when ASIO compatibility fix patching fails (#645)
Logging method was crashing when the patching failed.
2026-04-16 00:42:40 -07:00
bicarus 6ddae70c5a util: fix dangling pointer issues with improper std::stringstream usage (#644)
## Description of change
`std::ostringstream.str()` returns a temporary string so it's invalid to
obtain c_str from it and pass it around.

## Testing
wip
2026-04-15 23:25:38 -07:00
bicarus b38160d6c2 popn: native touch option, keypad warning (#640)
#639 

the usual
2026-04-15 19:31:26 -07:00
Emma 231c998cc0 acio: implement ICCA slotted reader emulation for iidx18 (#642)
## Link to GitHub Issue or related Pull Request, if one exists
#121

## Description of change
This implements a seperate ICCA card workflow for the slotted readers
acting how Resort Anthem expects, enabling cards to be inserted.
Additionally this fixes a bug where both card units would be treated the
same (for keypad reads) under Resort Anthem.

The slotted reader workflow was implemented as a seperate function for
two reasons - it would be nice to not mess with any existing code to
avoid breaking all the functional games, and the way the slotted readers
are handled by RA seemed different enough to how everything was already
designed to where it didn't make sense to keep adding special edge cases
for RA.

## Testing
IIDX18 was tested and cards could be inserted on both P1 and P2 side.
IIDX19 through 22 was tested to make sure nothing had broken for
wavepass emulation.

## Known Issues
In IIDX18, when a card isn't set but the insert key is pressed, after
the card timeout the game will error out with an invalid state error.
2026-04-15 18:53:21 -07:00
Emma 127d31a85c graphics: add option to disable win11 rounded corners on windows (#641)
## Description of change
This adds a toggleable option to remove the rounded window corners when
running a game, or a subscreen, in a window on Windows 11 and upwards.

## Testing
On Windows 11: Resort Anthem, MÚSECA and EPOLIS were tested and the
window corners were removed from both main windows and the TDJ subscreen
when running in windowed mode, with no change in fullscreen mode.

On Windows 7: IIDX18 was tested with no change, the call to
`DwmSetWindowAttribute` is ignored as `DWMWA_WINDOW_CORNER_PREFERENCE`
doesn't exist.

For XP builds, the call is compiled out to avoid linking against dwmapi
and windows_dll_compat_checker returns successfully when testing against
the XP Embedded image. I can't test it on XP, but it ran on Vista.
2026-04-15 18:51:53 -07:00
76 changed files with 1693 additions and 842 deletions
+122 -20
View File
@@ -35,24 +35,33 @@ struct ICCA_STATUS_LA9 {
static_assert(sizeof(struct ICCA_STATUS) == 24, "ICCA_STATUS must be 24 bytes"); static_assert(sizeof(struct ICCA_STATUS) == 24, "ICCA_STATUS must be 24 bytes");
enum ICCA_WORKFLOW { enum ICCA_WORKFLOW {
STEP, STEP = 0,
SLEEP, SLEEP = 1,
START, START = 2,
INIT, INIT = 3,
READY, READY = 4,
GET_USERID, GET_USERID = 5,
ACTIVE, ACTIVE = 6,
EJECT, EJECT = 7,
EJECT_CHECK, EJECT_CHECK = 8,
END, END = 9,
CLOSE_EJECT, CLOSE_EJECT = 10,
CLOSE_E_CHK, CLOSE_E_CHK = 11,
CLOSE_END, CLOSE_END = 12,
ERR_GETUID = -2 ERR_GETUID = -2,
ERR_REMOVED = -11
}; };
// used by RA as the status_code
enum ICCA_STATUS_CODE {
EMPTY = 1,
ENGAGED = 2
};
struct ICCA_UNIT { struct ICCA_UNIT {
struct ICCA_STATUS status {}; struct ICCA_STATUS status {};
enum ICCA_WORKFLOW state = STEP; enum ICCA_WORKFLOW state = STEP;
enum ICCA_WORKFLOW state_jdz = SLEEP;
bool card_cmd_pressed = false; bool card_cmd_pressed = false;
bool card_in = false; bool card_in = false;
double card_in_time = 0.0; double card_in_time = 0.0;
@@ -75,6 +84,11 @@ static inline int icca_get_active_count() {
} }
static inline int icca_get_unit_id(int unit_id) { static inline int icca_get_unit_id(int unit_id) {
// on RA the unit_id is zero-indexed
if (avs::game::is_model("JDZ")) {
return unit_id;
}
if (icca_get_active_count() < 2) if (icca_get_active_count() < 2)
return 1; return 1;
else { else {
@@ -122,9 +136,15 @@ static inline void update_card(int unit_id) {
IS_LAST_CARD_FELICA = is_card_uid_felica(unit->status.uid); IS_LAST_CARD_FELICA = is_card_uid_felica(unit->status.uid);
unit->state = acio::ICCA_COMPAT_ACTIVE ? START : ACTIVE; unit->state = acio::ICCA_COMPAT_ACTIVE ? START : ACTIVE;
unit->state_jdz = ACTIVE;
unit->status.error = 0; unit->status.error = 0;
unit->status.front_sensor = 1;
unit->status.rear_sensor = 1;
} else { } else {
unit->state = ERR_GETUID; unit->state = ERR_GETUID;
unit->state_jdz = ERR_GETUID;
unit->status.front_sensor = 1;
unit->status.rear_sensor = 1;
memset(unit->status.uid, 0, 8); memset(unit->status.uid, 0, 8);
} }
unit->card_in = true; unit->card_in = true;
@@ -132,10 +152,25 @@ static inline void update_card(int unit_id) {
} else if (unit->state == EJECT_CHECK) { } else if (unit->state == EJECT_CHECK) {
unit->state = SLEEP; unit->state = SLEEP;
unit->card_in = false; unit->card_in = false;
unit->status.front_sensor = 0;
unit->status.rear_sensor = 0;
} else if (unit->state_jdz == READY) {
unit->state = GET_USERID;
unit->state_jdz = GET_USERID;
unit->status.error = 0;
unit->status.front_sensor = 1;
unit->status.rear_sensor = 1;
unit->card_in = true;
unit->card_in_time = get_performance_seconds();
} }
} else { } else {
unit->state = acio::ICCA_COMPAT_ACTIVE ? START : ACTIVE; unit->state = acio::ICCA_COMPAT_ACTIVE ? START : ACTIVE;
if (unit->state_jdz == READY) {
unit->state_jdz = GET_USERID;
}
unit->status.error = 0; unit->status.error = 0;
unit->status.front_sensor = 1;
unit->status.rear_sensor = 1;
unit->card_in = true; unit->card_in = true;
unit->card_in_time = get_performance_seconds(); unit->card_in_time = get_performance_seconds();
} }
@@ -144,6 +179,12 @@ static inline void update_card(int unit_id) {
unit->state = CLOSE_EJECT; unit->state = CLOSE_EJECT;
if (fabs(get_performance_seconds() - unit->card_in_time) > CARD_TIMEOUT) { if (fabs(get_performance_seconds() - unit->card_in_time) > CARD_TIMEOUT) {
unit->card_in = false; unit->card_in = false;
memset(unit->status.uid, 0, 8);
if (unit->state_jdz == ACTIVE || unit->state_jdz == GET_USERID) {
unit->state_jdz = ERR_REMOVED;
}
unit->status.front_sensor = 0;
unit->status.rear_sensor = 0;
} }
} }
@@ -302,7 +343,11 @@ static char __cdecl ac_io_icca_get_status(void *a1, void *a2) {
// copy state to output buffer // copy state to output buffer
ICCA_UNIT *unit = &ICCA_UNITS[unit_id]; ICCA_UNIT *unit = &ICCA_UNITS[unit_id];
unit->status.status_code = unit->state; if (avs::game::is_model("JDZ")) {
unit->status.status_code = unit->card_in ? ENGAGED : EMPTY;
} else {
unit->status.status_code = unit->state;
}
memcpy(status, &unit->status, sizeof(struct ICCA_STATUS)); memcpy(status, &unit->status, sizeof(struct ICCA_STATUS));
// funny workaround // funny workaround
@@ -387,6 +432,11 @@ static char __cdecl ac_io_icca_req_uid(int unit_id) {
static int __cdecl ac_io_icca_req_uid_isfinished(int unit_id, DWORD *read_state) { static int __cdecl ac_io_icca_req_uid_isfinished(int unit_id, DWORD *read_state) {
unit_id = icca_get_unit_id(unit_id); unit_id = icca_get_unit_id(unit_id);
ICCA_UNIT *unit = &ICCA_UNITS[unit_id]; ICCA_UNIT *unit = &ICCA_UNITS[unit_id];
if (avs::game::is_model("JDZ")) {
// hack for RA - only ever seen it as this
*read_state = 8;
return 1;
}
if (unit->card_in) { if (unit->card_in) {
if (fabs(get_performance_seconds() - unit->card_in_time) < CARD_TIMEOUT) { if (fabs(get_performance_seconds() - unit->card_in_time) < CARD_TIMEOUT) {
unit->state = END; unit->state = END;
@@ -403,16 +453,61 @@ static int __cdecl ac_io_icca_send_keep_alive_packet(int a1, int a2, int a3) {
return 0; return 0;
} }
static int __cdecl ac_io_icca_workflow_jdz(int workflow, int unit_id) {
unit_id = icca_get_unit_id(unit_id);
ICCA_UNIT *unit = &ICCA_UNITS[unit_id];
switch (workflow) {
// RA uses "STEP" as more of a polling thing it seems
case STEP:
switch (unit->state_jdz) {
case CLOSE_EJECT:
unit->state_jdz = CLOSE_E_CHK;
break;
case CLOSE_E_CHK:
unit->state_jdz = CLOSE_END;
break;
case CLOSE_END:
unit->state_jdz = SLEEP;
break;
default:
break;
}
break;
case SLEEP:
unit->state = SLEEP;
break;
case START:
unit->state_jdz = INIT;
break;
case INIT:
case READY:
unit->state_jdz = READY;
break;
case CLOSE_EJECT:
unit->state_jdz = CLOSE_EJECT;
return CLOSE_E_CHK;
case CLOSE_END:
unit->state_jdz = SLEEP;
break;
default:
// should probably log a warning here
break;
}
return unit->state_jdz;
}
static int __cdecl ac_io_icca_workflow(int workflow, int unit_id) { static int __cdecl ac_io_icca_workflow(int workflow, int unit_id) {
// RA uses a different workflow process for the slotted readers; treat it differently
if (avs::game::is_model("JDZ")) {
return ac_io_icca_workflow_jdz(workflow, unit_id);
}
unit_id = icca_get_unit_id(unit_id); unit_id = icca_get_unit_id(unit_id);
ICCA_UNIT *unit = &ICCA_UNITS[unit_id]; ICCA_UNIT *unit = &ICCA_UNITS[unit_id];
switch (workflow) { switch (workflow) {
case STEP: case STEP:
if (avs::game::is_model("JDZ")) unit->state = STEP;
unit->state = SLEEP;
else
unit->state = STEP;
break; break;
case SLEEP: case SLEEP:
unit->state = SLEEP; unit->state = SLEEP;
@@ -441,7 +536,6 @@ static int __cdecl ac_io_icca_workflow(int workflow, int unit_id) {
default: default:
break; break;
} }
return unit->state; return unit->state;
} }
@@ -467,6 +561,14 @@ acio::ICCAModule::ICCAModule(HMODULE module, acio::HookMode hookMode) : ACIOModu
void acio::ICCAModule::attach() { void acio::ICCAModule::attach() {
ACIOModule::attach(); ACIOModule::attach();
// workaround for RA; ac_io_icca_cardunit_init is never called so treat like both are initialized
if (avs::game::is_model("JDZ")) {
ICCA_UNITS[0].initialized = true;
ICCA_UNITS[1].initialized = true;
CARD_TIMEOUT = 10.0; // keep the card in the slot for longer
}
// hooks // hooks
ACIO_MODULE_HOOK(ac_io_icca_cardunit_init); ACIO_MODULE_HOOK(ac_io_icca_cardunit_init);
ACIO_MODULE_HOOK(ac_io_icca_cardunit_init_isfinished); ACIO_MODULE_HOOK(ac_io_icca_cardunit_init_isfinished);
+17 -5
View File
@@ -2,6 +2,7 @@
#include "mdxf_poll.h" #include "mdxf_poll.h"
#include "avs/game.h" #include "avs/game.h"
#include "games/ddr/ddr.h"
#include "games/ddr/io.h" #include "games/ddr/io.h"
#include "launcher/launcher.h" #include "launcher/launcher.h"
#include "rawinput/rawinput.h" #include "rawinput/rawinput.h"
@@ -357,14 +358,17 @@ static bool __cdecl ac_io_mdxf_update_control_status_buffer_impl(int node, MDXFP
// decide on button map // decide on button map
const size_t *button_map = nullptr; const size_t *button_map = nullptr;
int player = 0;
switch (node) { switch (node) {
case 17: case 17:
case 25: case 25:
button_map = &buttons_p1[0]; button_map = &buttons_p1[0];
player = 1;
break; break;
case 18: case 18:
case 26: case 26:
button_map = &buttons_p2[0]; button_map = &buttons_p2[0];
player = 2;
break; break;
} }
@@ -374,19 +378,27 @@ static bool __cdecl ac_io_mdxf_update_control_status_buffer_impl(int node, MDXFP
if (source == EXTERNAL_POLL) { if (source == EXTERNAL_POLL) {
// get buttons // get buttons
auto &buttons = games::ddr::get_buttons(); auto &buttons = games::ddr::get_buttons();
// get analogs
bool analog_left = false;
bool analog_right = false;
games::ddr::get_analog_x_axis(player, analog_left, analog_right);
bool analog_up = false;
bool analog_down = false;
games::ddr::get_analog_y_axis(player, analog_up, analog_down);
uint8_t up_down = 0; uint8_t up_down = 0;
uint8_t left_right = 0; uint8_t left_right = 0;
if (GameAPI::Buttons::getState(RI_MGR, buttons.at(button_map[0])) || analog_up) {
if (GameAPI::Buttons::getState(RI_MGR, buttons.at(button_map[0]))) {
up_down |= 0xF0; up_down |= 0xF0;
} }
if (GameAPI::Buttons::getState(RI_MGR, buttons.at(button_map[1]))) { if (GameAPI::Buttons::getState(RI_MGR, buttons.at(button_map[1])) || analog_down) {
up_down |= 0x0F; up_down |= 0x0F;
} }
if (GameAPI::Buttons::getState(RI_MGR, buttons.at(button_map[2]))) { if (GameAPI::Buttons::getState(RI_MGR, buttons.at(button_map[2])) || analog_left) {
left_right |= 0xF0; left_right |= 0xF0;
} }
if (GameAPI::Buttons::getState(RI_MGR, buttons.at(button_map[3]))) { if (GameAPI::Buttons::getState(RI_MGR, buttons.at(button_map[3])) || analog_right) {
left_right |= 0x0F; left_right |= 0x0F;
} }
current_state = (uint16_t(up_down) << 8) | left_right; current_state = (uint16_t(up_down) << 8) | left_right;
+3 -3
View File
@@ -58,7 +58,7 @@ namespace api::modules {
// check if file exists in modules // check if file exists in modules
if (!fileutils::file_exists(dll_path)) { if (!fileutils::file_exists(dll_path)) {
return error(res, "Couldn't find " + dll_path.string()); return error(res, fmt::format("Couldn't find {}", dll_path));
} }
// get module // get module
@@ -118,7 +118,7 @@ namespace api::modules {
// check if file exists in modules // check if file exists in modules
if (!fileutils::file_exists(dll_path)) { if (!fileutils::file_exists(dll_path)) {
return error(res, "Couldn't find " + dll_path.string()); return error(res, fmt::format("Couldn't find {}", dll_path));
} }
// get module // get module
@@ -196,7 +196,7 @@ namespace api::modules {
// check if file exists in modules // check if file exists in modules
if (!fileutils::file_exists(dll_path)) { if (!fileutils::file_exists(dll_path)) {
return error(res, "Couldn't find " + dll_path.string()); return error(res, fmt::format("Couldn't find {}", dll_path));
} }
// get module // get module
+16 -7
View File
@@ -1168,7 +1168,7 @@ namespace avs {
// Ongaku Paradise // Ongaku Paradise
{"arkjc9.dll", 0xA00000}, {"arkjc9.dll", 0xA00000},
// KAMUNITY // KAMUNITY
{"kamunity.dll", 33554432}, {"kamunity.dll", 33554432},
}; };
@@ -1516,7 +1516,7 @@ namespace avs {
" * It's also possible that you have incomplete game data\n" " * It's also possible that you have incomplete game data\n"
" * Do NOT copy over random DLLs from another game installation; DLL must match game version\n" " * Do NOT copy over random DLLs from another game installation; DLL must match game version\n"
"\n" "\n"
, DLL_NAME, MODULE_PATH.string()) }; , DLL_NAME, MODULE_PATH) };
log_warning("avs-ea3", "{}", info_str); log_warning("avs-ea3", "{}", info_str);
log_fatal("avs-ea3", "Failed to find critical avs DLL on disk (avs2-core.dll OR {})", DLL_NAME); log_fatal("avs-ea3", "Failed to find critical avs DLL on disk (avs2-core.dll OR {})", DLL_NAME);
} }
@@ -1757,9 +1757,9 @@ namespace avs {
{ {
deferredlogs::defer_error_messages({ deferredlogs::defer_error_messages({
"AVS filesystem initialization failure was previously detected during boot!", "AVS filesystem initialization failure was previously detected during boot!",
fmt::format(" ERROR: directory could not be created: {}", src_path.string().c_str()), fmt::format(" ERROR: directory could not be created: {}", src_path),
fmt::format(" if you see a crash, it may have been caused by bad <mounttable> contents in {}", avs::core::CFG_PATH.c_str()), fmt::format(" if you see a crash, it may have been caused by bad <mounttable> contents in {}", avs::core::CFG_PATH),
" fix the XML file and try again" " fix the XML file and try again"
}); });
} }
@@ -1780,14 +1780,14 @@ namespace avs {
auto created = std::filesystem::create_directories(real_path, err); auto created = std::filesystem::create_directories(real_path, err);
if (created) { if (created) {
log_info("avs-core", "created '{}' at '{}'", avs_path, real_path.string()); log_info("avs-core", "created '{}' at '{}'", avs_path, real_path);
} }
if (err) { if (err) {
avs_dir_err(real_path); avs_dir_err(real_path);
log_warning("avs-core", "failed to create '{}' folder at '{}': {}", log_warning("avs-core", "failed to create '{}' folder at '{}': {}",
avs_path, avs_path,
real_path.string(), real_path,
err.message()); err.message());
} }
} }
@@ -1921,6 +1921,15 @@ namespace avs {
// create nvram and raw directories if possible for mounttable configurations // create nvram and raw directories if possible for mounttable configurations
create_avs_config_fs_table(config, config_node); create_avs_config_fs_table(config, config_node);
#if !SPICE64
// sdvx4 bad log config fix
if (avs::game::DLL_NAME == "soundvoltex.dll" && // it's too early for avs::game::is_model
property_search_safe(config, config_node, "/log/enable_console")) {
log_info("avs-core", "applying SDVX4 avs-config.xml fix for <log><enable_console>");
property_search_remove_safe(config, config_node, "/log/enable_console");
}
#endif
// set log level // set log level
if (!LOG_LEVEL_CUSTOM.empty()) { if (!LOG_LEVEL_CUSTOM.empty()) {
property_search_remove_safe(config, config_node, "/log/level"); property_search_remove_safe(config, config_node, "/log/level");
+6 -4
View File
@@ -151,8 +151,8 @@ namespace avs {
" * It's also possible that you have incomplete game data\n" " * It's also possible that you have incomplete game data\n"
" * Do NOT copy over random DLLs from another game installation; DLL must match game version\n" " * Do NOT copy over random DLLs from another game installation; DLL must match game version\n"
"\n" "\n"
, DLL_NAME, MODULE_PATH.string()) }; , DLL_NAME, MODULE_PATH) };
log_warning("avs-ea3", "{}", info_str); log_warning("avs-ea3", "{}", info_str);
log_fatal("avs-ea3", "Failed to find critical ea3 DLL on disk (avs2-ea3.dll OR {})", DLL_NAME); log_fatal("avs-ea3", "Failed to find critical ea3 DLL on disk (avs2-ea3.dll OR {})", DLL_NAME);
} }
@@ -217,8 +217,10 @@ namespace avs {
ea3_config_name = "prop/eamuse-config.xml"; ea3_config_name = "prop/eamuse-config.xml";
} }
if (avs::core::file_exists(ea3_config_name)) { if (fileutils::file_exists(ea3_config_name)) {
log_info("avs-ea3", "booting (using {})", ea3_config_name); log_info("avs-ea3", "found {} on disk", ea3_config_name);
} else if (CFG_PATH.size()) {
log_fatal("avs-ea3", "user-specified ea3 config file is missing: {}", ea3_config_name);
} else { } else {
log_warning("avs-ea3", "looked for the following files in order:"); log_warning("avs-ea3", "looked for the following files in order:");
log_warning("avs-ea3", " * prop/ea3-config.xml"); log_warning("avs-ea3", " * prop/ea3-config.xml");
+4 -5
View File
@@ -73,11 +73,10 @@ namespace avs {
// load game instance // load game instance
const auto dll_path = MODULE_PATH / DLL_NAME; const auto dll_path = MODULE_PATH / DLL_NAME;
const auto dll_path_s = dll_path.string(); log_info("avs-game", "DLL path: {}", dll_path);
log_info("avs-game", "DLL path: {}", dll_path_s.c_str());
// MAX_PATH is 260 // MAX_PATH is 260
if (130 <= dll_path_s.length()) { if (const auto dll_path_len = dll_path.wstring().length(); 130 <= dll_path_len) {
log_warning( log_warning(
"avs-game", "avs-game",
"PATH TOO LONG WARNING\n\n" "PATH TOO LONG WARNING\n\n"
@@ -93,7 +92,7 @@ namespace avs {
"long, often resulting in random crashes. Move the game contents to\n" "long, often resulting in random crashes. Move the game contents to\n"
"a directory with shorter path.\n" "a directory with shorter path.\n"
"-------------------------------------------------------------------\n\n", "-------------------------------------------------------------------\n\n",
dll_path_s, dll_path_s.length()); dll_path, dll_path_len);
} }
// ddr gamemdx.dll user error // ddr gamemdx.dll user error
@@ -112,7 +111,7 @@ namespace avs {
// file not found on disk // file not found on disk
if (!fileutils::file_exists(dll_path)) { if (!fileutils::file_exists(dll_path)) {
log_warning("avs-game", "game DLL could not be found on disk: {}", dll_path.string().c_str()); log_warning("avs-game", "game DLL could not be found on disk: {}", dll_path);
log_warning("avs-game", "double check -exec and -modules parameters; unless you know what you're doing, leave them blank"); log_warning("avs-game", "double check -exec and -modules parameters; unless you know what you're doing, leave them blank");
} }
+37 -37
View File
@@ -39,8 +39,8 @@
<button name="VEFX" vkey="10" analogtype="0" devid="Player 1" invert="false" debounce_up="0" debounce_down="0" velocity_threshold="0"/> <button name="VEFX" vkey="10" analogtype="0" devid="Player 1" invert="false" debounce_up="0" debounce_down="0" velocity_threshold="0"/>
</buttons> </buttons>
<analogs> <analogs>
<analog name="Turntable P1" devid="Player 1" index="4" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1" relative="false" delay="0"/> <analog name="Turntable P1" devid="Player 1" index="4" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1"/>
<analog name="Turntable P2" devid="Player 2" index="4" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1" relative="false" delay="0"/> <analog name="Turntable P2" devid="Player 2" index="4" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1"/>
</analogs> </analogs>
<lights> <lights>
<light name="P1 1" devid="Player 1" index="0"/> <light name="P1 1" devid="Player 1" index="0"/>
@@ -117,8 +117,8 @@
<keypad_buttons/> <keypad_buttons/>
<!-- Assumes analog mode. Need to invert direction since this FW is designed for INF. --> <!-- Assumes analog mode. Need to invert direction since this FW is designed for INF. -->
<analogs> <analogs>
<analog name="Turntable P1" devid="arcin (1p)" index="0" sensivity="1" deadzone="0" deadzone_mirror="false" invert="true" smoothing="false" multiplier="1" relative="false" delay="0"/> <analog name="Turntable P1" devid="arcin (1p)" index="0" sensivity="1" deadzone="0" deadzone_mirror="false" invert="true" smoothing="false" multiplier="1"/>
<analog name="Turntable P2" devid="arcin (2p)" index="0" sensivity="1" deadzone="0" deadzone_mirror="false" invert="true" smoothing="false" multiplier="1" relative="false" delay="0"/> <analog name="Turntable P2" devid="arcin (2p)" index="0" sensivity="1" deadzone="0" deadzone_mirror="false" invert="true" smoothing="false" multiplier="1"/>
</analogs> </analogs>
<lights> <lights>
<light name="P1 1" devid="arcin (1p)" index="0"/> <light name="P1 1" devid="arcin (1p)" index="0"/>
@@ -144,34 +144,34 @@
<buttons/> <buttons/>
<keypad_buttons/> <keypad_buttons/>
<analogs> <analogs>
<analog name="Key 1" devid="Analogs key 01-28" index="4" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1" relative="false" delay="0"/> <analog name="Key 1" devid="Analogs key 01-28" index="4" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1"/>
<analog name="Key 2" devid="Analogs key 01-28" index="5" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1" relative="false" delay="0"/> <analog name="Key 2" devid="Analogs key 01-28" index="5" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1"/>
<analog name="Key 3" devid="Analogs key 01-28" index="6" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1" relative="false" delay="0"/> <analog name="Key 3" devid="Analogs key 01-28" index="6" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1"/>
<analog name="Key 4" devid="Analogs key 01-28" index="7" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1" relative="false" delay="0"/> <analog name="Key 4" devid="Analogs key 01-28" index="7" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1"/>
<analog name="Key 5" devid="Analogs key 01-28" index="8" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1" relative="false" delay="0"/> <analog name="Key 5" devid="Analogs key 01-28" index="8" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1"/>
<analog name="Key 6" devid="Analogs key 01-28" index="9" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1" relative="false" delay="0"/> <analog name="Key 6" devid="Analogs key 01-28" index="9" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1"/>
<analog name="Key 7" devid="Analogs key 01-28" index="10" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1" relative="false" delay="0"/> <analog name="Key 7" devid="Analogs key 01-28" index="10" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1"/>
<analog name="Key 8" devid="Analogs key 01-28" index="11" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1" relative="false" delay="0"/> <analog name="Key 8" devid="Analogs key 01-28" index="11" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1"/>
<analog name="Key 9" devid="Analogs key 01-28" index="12" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1" relative="false" delay="0"/> <analog name="Key 9" devid="Analogs key 01-28" index="12" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1"/>
<analog name="Key 10" devid="Analogs key 01-28" index="13" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1" relative="false" delay="0"/> <analog name="Key 10" devid="Analogs key 01-28" index="13" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1"/>
<analog name="Key 11" devid="Analogs key 01-28" index="14" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1" relative="false" delay="0"/> <analog name="Key 11" devid="Analogs key 01-28" index="14" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1"/>
<analog name="Key 12" devid="Analogs key 01-28" index="15" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1" relative="false" delay="0"/> <analog name="Key 12" devid="Analogs key 01-28" index="15" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1"/>
<analog name="Key 13" devid="Analogs key 01-28" index="16" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1" relative="false" delay="0"/> <analog name="Key 13" devid="Analogs key 01-28" index="16" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1"/>
<analog name="Key 14" devid="Analogs key 01-28" index="17" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1" relative="false" delay="0"/> <analog name="Key 14" devid="Analogs key 01-28" index="17" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1"/>
<analog name="Key 15" devid="Analogs key 01-28" index="18" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1" relative="false" delay="0"/> <analog name="Key 15" devid="Analogs key 01-28" index="18" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1"/>
<analog name="Key 16" devid="Analogs key 01-28" index="19" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1" relative="false" delay="0"/> <analog name="Key 16" devid="Analogs key 01-28" index="19" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1"/>
<analog name="Key 17" devid="Analogs key 01-28" index="20" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1" relative="false" delay="0"/> <analog name="Key 17" devid="Analogs key 01-28" index="20" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1"/>
<analog name="Key 18" devid="Analogs key 01-28" index="21" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1" relative="false" delay="0"/> <analog name="Key 18" devid="Analogs key 01-28" index="21" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1"/>
<analog name="Key 19" devid="Analogs key 01-28" index="22" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1" relative="false" delay="0"/> <analog name="Key 19" devid="Analogs key 01-28" index="22" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1"/>
<analog name="Key 20" devid="Analogs key 01-28" index="23" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1" relative="false" delay="0"/> <analog name="Key 20" devid="Analogs key 01-28" index="23" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1"/>
<analog name="Key 21" devid="Analogs key 01-28" index="24" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1" relative="false" delay="0"/> <analog name="Key 21" devid="Analogs key 01-28" index="24" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1"/>
<analog name="Key 22" devid="Analogs key 01-28" index="25" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1" relative="false" delay="0"/> <analog name="Key 22" devid="Analogs key 01-28" index="25" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1"/>
<analog name="Key 23" devid="Analogs key 01-28" index="26" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1" relative="false" delay="0"/> <analog name="Key 23" devid="Analogs key 01-28" index="26" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1"/>
<analog name="Key 24" devid="Analogs key 01-28" index="27" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1" relative="false" delay="0"/> <analog name="Key 24" devid="Analogs key 01-28" index="27" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1"/>
<analog name="Key 25" devid="Analogs key 01-28" index="28" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1" relative="false" delay="0"/> <analog name="Key 25" devid="Analogs key 01-28" index="28" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1"/>
<analog name="Key 26" devid="Analogs key 01-28" index="29" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1" relative="false" delay="0"/> <analog name="Key 26" devid="Analogs key 01-28" index="29" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1"/>
<analog name="Key 27" devid="Analogs key 01-28" index="30" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1" relative="false" delay="0"/> <analog name="Key 27" devid="Analogs key 01-28" index="30" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1"/>
<analog name="Key 28" devid="Analogs key 01-28" index="31" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1" relative="false" delay="0"/> <analog name="Key 28" devid="Analogs key 01-28" index="31" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1"/>
</analogs> </analogs>
<lights> <lights>
<light name="Key 1 R" devid="Lights 01-14" index="0"/> <light name="Key 1 R" devid="Lights 01-14" index="0"/>
@@ -277,9 +277,9 @@
</buttons> </buttons>
<keypad_buttons/> <keypad_buttons/>
<analogs> <analogs>
<analog name="Guitar P1 Wail X" devid="Gitaller P1" index="1" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1" relative="false" delay="0"/> <analog name="Guitar P1 Wail X" devid="Gitaller P1" index="1" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1"/>
<analog name="Guitar P1 Wail Y" devid="Gitaller P1" index="0" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1" relative="false" delay="0"/> <analog name="Guitar P1 Wail Y" devid="Gitaller P1" index="0" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1"/>
<analog name="Guitar P1 Wail Z" devid="Gitaller P1" index="5" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1" relative="false" delay="0"/> <analog name="Guitar P1 Wail Z" devid="Gitaller P1" index="5" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1"/>
</analogs> </analogs>
<lights/> <lights/>
<!-- Gen 1 FAUCETWO in HID mode; should also work for later gens --> <!-- Gen 1 FAUCETWO in HID mode; should also work for later gens -->
@@ -296,8 +296,8 @@
</buttons> </buttons>
<keypad_buttons/> <keypad_buttons/>
<analogs> <analogs>
<analog name="VOL-L" devid="Buttons (MI_01)" index="1" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1" relative="false" delay="0"/> <analog name="VOL-L" devid="Buttons (MI_01)" index="1" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1"/>
<analog name="VOL-R" devid="Buttons (MI_01)" index="0" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1" relative="false" delay="0"/> <analog name="VOL-R" devid="Buttons (MI_01)" index="0" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1"/>
</analogs> </analogs>
<lights> <lights>
<light name="BT-A" devid="Lights (MI_02)" index="24"/> <light name="BT-A" devid="Lights (MI_02)" index="24"/>
+2
View File
@@ -163,6 +163,7 @@ time (
if ((BUILD_XP > 0)) if ((BUILD_XP > 0))
then then
# 32 bit Windows XP # 32 bit Windows XP
echo ""
echo "Building 32bit targets (WinXP toolchain)..." echo "Building 32bit targets (WinXP toolchain)..."
echo "=========================" echo "========================="
if ((CLEAN_BUILD > 0)) if ((CLEAN_BUILD > 0))
@@ -187,6 +188,7 @@ time (
CXXFLAGS="$CXXFLAGS -DSPICE_XP=1" cmake -G "Ninja" -DCMAKE_TOOLCHAIN_FILE=${TOOLCHAIN_WINXP_64} -DCMAKE_BUILD_TYPE=${BUILD_TYPE} "$OLDPWD" && ninja ${TARGETS_XP64} CXXFLAGS="$CXXFLAGS -DSPICE_XP=1" cmake -G "Ninja" -DCMAKE_TOOLCHAIN_FILE=${TOOLCHAIN_WINXP_64} -DCMAKE_BUILD_TYPE=${BUILD_TYPE} "$OLDPWD" && ninja ${TARGETS_XP64}
popd > /dev/null popd > /dev/null
else else
echo ""
echo "Skipping WinXP builds, toolchain not specified" echo "Skipping WinXP builds, toolchain not specified"
fi fi
+29 -14
View File
@@ -207,22 +207,37 @@ float Analog::applyMultiplier(float value) {
} }
float Analog::normalizeAnalogValue(float value) { float Analog::normalizeAnalogValue(float value) {
// effectively the same as fmodf(value, 1.f) if (getType() == GameAPI::Analogs::AnalogType::Circular) {
// for small values, this is MUCH faster than fmodf. // effectively the same as fmodf(value, 1.f)
float new_value = value; // for small values, this is MUCH faster than fmodf.
while (new_value > 1.f) { float new_value = value;
new_value -= 1.f; while (new_value > 1.f) {
new_value -= 1.f;
}
while (new_value < 0.f) {
new_value += 1.f;
}
return new_value;
} else {
// clamp to [0, 1] range
return std::clamp(value, 0.f, 1.f);
} }
while (new_value < 0.f) {
new_value += 1.f;
}
return new_value;
} }
float Analog::applyDeadzone(float raw_value) { float Analog::applyDeadzone(float raw_value) {
float value = raw_value; float value = raw_value;
const auto deadzone = this->getDeadzone(); auto deadzone = this->getDeadzone();
if (deadzone > 0) {
// in the past, positive deadzone applied in the center, negative deadzone applied to 0
// after each analog value received a type (circular/linear) this has been simpliifed to
// positive values only since we can figure out where the rest value is
// for back compat, treat negative value as positive
if (deadzone < 0.f) {
deadzone = -deadzone;
}
if (getType() != GameAPI::Analogs::AnalogType::LinearPositive) {
// calculate values // calculate values
const auto delta = value - 0.5f; const auto delta = value - 0.5f;
@@ -255,7 +270,7 @@ float Analog::applyDeadzone(float raw_value) {
} }
} }
} else if (deadzone < 0) { } else {
// invert for mirror // invert for mirror
if (this->getDeadzoneMirror()) { if (this->getDeadzoneMirror()) {
@@ -263,8 +278,8 @@ float Analog::applyDeadzone(float raw_value) {
} }
// deadzone from minimum value // deadzone from minimum value
if (deadzone > -1 && value > -deadzone) { if (deadzone < 1.f && deadzone < value) {
value = std::min(1.f, (value + deadzone) / (1.f + deadzone)); value = std::max(0.f, (value - deadzone) / (1.f - deadzone));
} else { } else {
value = 0.f; value = 0.f;
} }
+23 -35
View File
@@ -13,6 +13,21 @@ namespace rawinput {
class RawInputManager; class RawInputManager;
} }
namespace GameAPI::Analogs {
enum class AnalogType {
// default; values wrap around (below 0 turns into 1, over 1 is 0)
// knobs, turntables
Circular = 0,
// typical joystick that rests at the center and caps at [0, 1]
LinearCentered = 1,
// one-directional value (sliders and instruments like piano/drum velocity)
// starts at 0 and goes up to 1
LinearPositive = 2,
};
}
struct AnalogMovingAverage { struct AnalogMovingAverage {
double time_in_ms; double time_in_ms;
float sine; float sine;
@@ -31,10 +46,11 @@ private:
float last_state = 0.5f; float last_state = 0.5f;
bool sensitivity_set = false; bool sensitivity_set = false;
bool deadzone_set = false; bool deadzone_set = false;
GameAPI::Analogs::AnalogType type = GameAPI::Analogs::AnalogType::Circular;
// smoothing function // smoothing function
bool smoothing = false; bool smoothing = false;
std::array<AnalogMovingAverage, ANALOG_HISTORY_CNT> vector_history; std::array<AnalogMovingAverage, ANALOG_HISTORY_CNT> vector_history = {};
int vector_history_index = 0; int vector_history_index = 0;
float smoothed_last_state = 0.f; float smoothed_last_state = 0.f;
@@ -47,14 +63,6 @@ private:
float divisor_previous_value = 0.5f; float divisor_previous_value = 0.5f;
unsigned short divisor_region = 0; unsigned short divisor_region = 0;
// relative input mode
float absolute_value_for_rel_mode = 0.5f;
bool relative_mode = false;
// circular buffer (delayed input)
int delay_buffer_depth = 0;
std::queue<float> delay_buffer;
float calculateAngularDifference(float old_rads, float new_rads); float calculateAngularDifference(float old_rads, float new_rads);
float normalizeAngle(float rads); float normalizeAngle(float rads);
float normalizeAnalogValue(float value); float normalizeAnalogValue(float value);
@@ -66,7 +74,8 @@ public:
float override_state = 0.5f; float override_state = 0.5f;
explicit Analog(std::string name) : name(std::move(name)) { explicit Analog(std::string name) : name(std::move(name)) {
vector_history.fill({0.0, 0.f, 0.f}); };
explicit Analog(std::string name, GameAPI::Analogs::AnalogType type) : name(std::move(name)), type(type) {
}; };
std::string getDisplayString(rawinput::RawInputManager* manager); std::string getDisplayString(rawinput::RawInputManager* manager);
@@ -89,8 +98,6 @@ public:
smoothing = false; smoothing = false;
deadzone_mirror = false; deadzone_mirror = false;
setMultiplier(1); setMultiplier(1);
setRelativeMode(false);
setDelayBufferDepth(0);
setLastState(0.5f); setLastState(0.5f);
} }
@@ -188,30 +195,11 @@ public:
this->last_state = last_state; this->last_state = last_state;
} }
inline bool isRelativeMode() const { inline GameAPI::Analogs::AnalogType getType() const {
return this->relative_mode; return this->type;
} }
inline void setRelativeMode(bool relative_mode) { inline void setType(GameAPI::Analogs::AnalogType type) {
this->relative_mode = relative_mode; this->type = type;
this->absolute_value_for_rel_mode = 0.5f;
}
inline float getAbsoluteValue(float relative_delta) {
this->absolute_value_for_rel_mode =
normalizeAnalogValue(this->absolute_value_for_rel_mode + relative_delta);
return this->absolute_value_for_rel_mode;
}
inline int getDelayBufferDepth() const {
return this->delay_buffer_depth;
}
inline void setDelayBufferDepth(int depth) {
this->delay_buffer_depth = depth;
}
inline std::queue<float> &getDelayBuffer() {
return this->delay_buffer;
} }
}; };
+92 -42
View File
@@ -199,9 +199,19 @@ GameAPI::Buttons::State GameAPI::Buttons::getState(rawinput::RawInputManager *ma
if (vKey < value_states->size()) { if (vKey < value_states->size()) {
auto value = value_states->at(vKey); auto value = value_states->at(vKey);
if (current_button->getAnalogType() == BAT_POSITIVE) { if (current_button->getAnalogType() == BAT_POSITIVE) {
state = value > 0.6f ? BUTTON_PRESSED : BUTTON_NOT_PRESSED; float threshold = 0.6f;
if (current_button->getBatThreshold() > 0) {
threshold = std::clamp(current_button->getBatThreshold() / 100.f, 0.01f, 0.99f);
}
state = value > threshold ? BUTTON_PRESSED : BUTTON_NOT_PRESSED;
} else if (current_button->getAnalogType() == BAT_NEGATIVE) { } else if (current_button->getAnalogType() == BAT_NEGATIVE) {
state = value < 0.4f ? BUTTON_PRESSED : BUTTON_NOT_PRESSED; float threshold = 0.4f;
if (current_button->getBatThreshold() > 0) {
threshold = std::clamp((100 - current_button->getBatThreshold()) / 100.f, 0.01f, 0.99f);
}
state = value < threshold ? BUTTON_PRESSED : BUTTON_NOT_PRESSED;
} else { } else {
state = value > 0.01f ? BUTTON_PRESSED : BUTTON_NOT_PRESSED; state = value > 0.01f ? BUTTON_PRESSED : BUTTON_NOT_PRESSED;
} }
@@ -669,32 +679,12 @@ float GameAPI::Analogs::getState(rawinput::RawInputManager *manager, rawinput::D
} }
// deadzone // deadzone
if (analog.isDeadzoneSet()) { // do not apply deadzone to circular analogs since it doesn't make sense
if (analog.isDeadzoneSet() && analog.getType() != AnalogType::Circular) {
value = analog.applyDeadzone(value); value = analog.applyDeadzone(value);
} }
if (analog.isRelativeMode()) { if (analog.getType() == AnalogType::Circular) {
float relative_delta = value - 0.5f;
// built-in scaling to make values reasonable
relative_delta /= 80.f;
// integer multiplier/divisor
const auto mult = analog.getMultiplier();
if (mult < -1) {
relative_delta /= -mult;
} else if (1 < mult) {
relative_delta *= mult;
}
// sensitivity (ranges from 0.0 to 4.0)
if (analog.isSensitivitySet()) {
relative_delta *= analog.getSensitivity();
}
// translate relative movement to absolute value
value = analog.getAbsoluteValue(relative_delta);
} else {
// integer multiplier // integer multiplier
value = analog.applyMultiplier(value); value = analog.applyMultiplier(value);
@@ -722,30 +712,54 @@ float GameAPI::Analogs::getState(rawinput::RawInputManager *manager, rawinput::D
// apply to value // apply to value
value = rads * (float) M_1_TAU; value = rads * (float) M_1_TAU;
} }
} } else {
// sensitivity
if (analog.isSensitivitySet()) {
// adjust curve
// values < 1.f : less sensitive around neutral
// values > 1.f : more sensitive around neutral
float curve = analog.getSensitivity();
if (curve <= 0.f) {
curve = 0.01f;
}
curve = 1.f / curve;
// delay if (analog.getType() == AnalogType::LinearCentered) {
if (0 < analog.getDelayBufferDepth()) { // convert 0.0..1.0 to -1.0..+1.0
auto& queue = analog.getDelayBuffer(); float signed_raw = (value - 0.5f) * 2.0f;
// apply curve
float sign = signed_raw < 0.0f ? -1.0f : 1.0f;
float magnitude = fabsf(signed_raw);
float curved = sign * powf(magnitude, curve);
// convert back to 0.0..1.0
value = curved * 0.5f + 0.5f;
} else {
value = powf(value, curve);
}
// ensure the queue isn't too long; drop old values value = std::clamp(value, 0.f, 1.f);
while (analog.getDelayBufferDepth() <= (int)queue.size()) {
queue.pop();
} }
// always push new value // multiplier / divisor
queue.push(value); if (analog.getMultiplier() < -1) {
if (analog.getType() == AnalogType::LinearCentered) {
value = (value - 0.5f) / (-analog.getMultiplier()) + 0.5f;
} else {
value /= -analog.getMultiplier();
}
// get a new value to return value = std::clamp(value, 0.f, 1.f);
if ((int)queue.size() < analog.getDelayBufferDepth()) {
// not enough in the queue, stall for now, shouldn't happen often } else if (analog.getMultiplier() > 1) {
value = analog.getLastState(); if (analog.getType() == AnalogType::LinearCentered) {
} else { value = (value - 0.5f) * analog.getMultiplier() + 0.5f;
value = queue.front(); } else {
queue.pop(); value *= analog.getMultiplier();
}
value = std::clamp(value, 0.f, 1.f);
} }
} }
break; break;
} }
case rawinput::MIDI: { case rawinput::MIDI: {
@@ -829,6 +843,42 @@ std::vector<Analog> GameAPI::Analogs::sortAnalogs(
return sorted; return sorted;
} }
static std::vector<Analog> sortAnalogsWithTypeInternal(
std::vector<Analog> &analogs,
const std::initializer_list<GameAPI::Analogs::AnalogWithType> list) {
std::vector<Analog> sorted;
bool analog_found;
for (auto &a : list) {
analog_found = false;
for (auto &analog : analogs) {
if (a.name == analog.getName()) {
analog_found = true;
analog.setType(a.type);
sorted.push_back(analog);
break;
}
}
if (!analog_found) {
sorted.emplace_back(a.name, a.type);
}
}
return sorted;
}
void GameAPI::Analogs::sortAnalogsWithType(
std::vector<Analog> *analogs,
const std::initializer_list<AnalogWithType> list) {
if (analogs) {
*analogs = sortAnalogsWithTypeInternal(*analogs, list);
}
}
float GameAPI::Analogs::getState(rawinput::RawInputManager *manager, Analog &analog) { float GameAPI::Analogs::getState(rawinput::RawInputManager *manager, Analog &analog) {
// check override // check override
+8 -7
View File
@@ -85,14 +85,15 @@ namespace GameAPI {
const std::vector<Analog> &analogs, const std::vector<Analog> &analogs,
const std::vector<std::string> &analog_names); const std::vector<std::string> &analog_names);
template<typename T> struct AnalogWithType {
void sortAnalogs(std::vector<Analog> *analogs, T t) { const std::string name;
const std::vector<std::string> analog_names { t }; const AnalogType type;
if (analogs) { AnalogWithType(std::string name, AnalogType type) :
*analogs = GameAPI::Analogs::sortAnalogs(*analogs, analog_names); name(std::move(name)), type(type) {}
} };
}
void sortAnalogsWithType(std::vector<Analog> *analogs, const std::initializer_list<AnalogWithType> list);
template<typename T, typename... Rest> template<typename T, typename... Rest>
void sortAnalogs(std::vector<Analog> *analogs, T t, Rest... rest) { void sortAnalogs(std::vector<Analog> *analogs, T t, Rest... rest) {
+4 -1
View File
@@ -306,7 +306,10 @@ std::string Button::getDisplayString(rawinput::RawInputManager* manager) {
std::string vKeyString = fmt::format("{:#x}", vKey); std::string vKeyString = fmt::format("{:#x}", vKey);
// device must be existing // device must be existing
if (this->device_identifier.empty() && vKey == INVALID_VKEY) { if (this->isNaive() && vKey == INVALID_VKEY) {
if (this->getInvert()) {
return "(always on)";
}
return ""; return "";
} }
+10
View File
@@ -56,6 +56,7 @@ private:
ButtonAnalogType analog_type = BAT_NONE; ButtonAnalogType analog_type = BAT_NONE;
double debounce_up = 0.0; double debounce_up = 0.0;
double debounce_down = 0.0; double debounce_down = 0.0;
int bat_threshold = 0; // for positive/negative only, value in percentage, zero is default
bool invert = false; bool invert = false;
bool is_temporary = false; bool is_temporary = false;
@@ -109,6 +110,7 @@ public:
alternatives.clear(); alternatives.clear();
device_identifier = ""; device_identifier = "";
analog_type = BAT_NONE; analog_type = BAT_NONE;
bat_threshold = 0;
} }
std::string getDisplayString(rawinput::RawInputManager* manager); std::string getDisplayString(rawinput::RawInputManager* manager);
@@ -169,6 +171,14 @@ public:
this->debounce_down = debounce_time_down; this->debounce_down = debounce_time_down;
} }
inline void setBatThreshold(int bat_threshold) {
this->bat_threshold = bat_threshold;
}
inline int getBatThreshold() const {
return this->bat_threshold;
}
inline bool getInvert() const { inline bool getInvert() const {
return this->invert; return this->invert;
} }
+17 -27
View File
@@ -28,7 +28,7 @@ Config::Config() {
this->status = false; this->status = false;
if (CONFIG_PATH_OVERRIDE.length() > 0) { if (CONFIG_PATH_OVERRIDE.length() > 0) {
this->configLocation = CONFIG_PATH_OVERRIDE; this->configLocation = CONFIG_PATH_OVERRIDE;
log_info("cfg", "using custom config file: {}", this->configLocation.string()); log_info("cfg", "using custom config file: {}", this->configLocation);
} else { } else {
this->configLocation = std::filesystem::path(_wgetenv(L"APPDATA")) / L"spicetools.xml"; this->configLocation = std::filesystem::path(_wgetenv(L"APPDATA")) / L"spicetools.xml";
// avoids logging the expanded appdata path as it contains user name // avoids logging the expanded appdata path as it contains user name
@@ -57,7 +57,7 @@ Config::Config() {
this->firstFillConfigFile(); this->firstFillConfigFile();
break; break;
case tinyxml2::XMLError::XML_ERROR_FILE_COULD_NOT_BE_OPENED: case tinyxml2::XMLError::XML_ERROR_FILE_COULD_NOT_BE_OPENED:
log_fatal("cfg", "could not open config file: {}", this->configLocation.string()); log_fatal("cfg", "could not open config file: {}", this->configLocation);
break; break;
case tinyxml2::XMLError::XML_ERROR_FILE_NOT_FOUND: case tinyxml2::XMLError::XML_ERROR_FILE_NOT_FOUND:
this->createConfigFile(); this->createConfigFile();
@@ -72,7 +72,7 @@ Config::Config() {
case tinyxml2::XMLError::XML_ERROR_PARSING_UNKNOWN: case tinyxml2::XMLError::XML_ERROR_PARSING_UNKNOWN:
case tinyxml2::XMLError::XML_ERROR_MISMATCHED_ELEMENT: case tinyxml2::XMLError::XML_ERROR_MISMATCHED_ELEMENT:
case tinyxml2::XMLError::XML_ERROR_PARSING: case tinyxml2::XMLError::XML_ERROR_PARSING:
log_warning("cfg", "Couldn't read config file: {}", this->configLocation.string()); log_warning("cfg", "Couldn't read config file: {}", this->configLocation);
this->createConfigFile(); this->createConfigFile();
this->firstFillConfigFile(); this->firstFillConfigFile();
break; break;
@@ -162,6 +162,7 @@ bool Config::addGame(Game &game) {
auto analogType = (int) BAT_NONE; auto analogType = (int) BAT_NONE;
double debounce_up = 0.0; double debounce_up = 0.0;
double debounce_down = 0.0; double debounce_down = 0.0;
int bat_threshold = 0;
int velocity_threshold = 0; int velocity_threshold = 0;
bool invert = false; bool invert = false;
tinyxml2::XMLError attrError = gameButtonNode->QueryIntAttribute("vkey", &vKey); tinyxml2::XMLError attrError = gameButtonNode->QueryIntAttribute("vkey", &vKey);
@@ -169,6 +170,7 @@ bool Config::addGame(Game &game) {
gameButtonNode->QueryIntAttribute("analogtype", &analogType); gameButtonNode->QueryIntAttribute("analogtype", &analogType);
gameButtonNode->QueryDoubleAttribute("debounce_up", &debounce_up); gameButtonNode->QueryDoubleAttribute("debounce_up", &debounce_up);
gameButtonNode->QueryDoubleAttribute("debounce_down", &debounce_down); gameButtonNode->QueryDoubleAttribute("debounce_down", &debounce_down);
gameButtonNode->QueryIntAttribute("bat_threshold", &bat_threshold);
gameButtonNode->QueryIntAttribute("velocity_threshold", &velocity_threshold); gameButtonNode->QueryIntAttribute("velocity_threshold", &velocity_threshold);
gameButtonNode->QueryBoolAttribute("invert", &invert); gameButtonNode->QueryBoolAttribute("invert", &invert);
if (attrError != tinyxml2::XMLError::XML_SUCCESS) { if (attrError != tinyxml2::XMLError::XML_SUCCESS) {
@@ -180,6 +182,7 @@ bool Config::addGame(Game &game) {
gameButtonNode->SetAttribute("devid", button->getDeviceIdentifier().c_str()); gameButtonNode->SetAttribute("devid", button->getDeviceIdentifier().c_str());
gameButtonNode->SetAttribute("debounce_up", debounce_up); gameButtonNode->SetAttribute("debounce_up", debounce_up);
gameButtonNode->SetAttribute("debounce_down", debounce_down); gameButtonNode->SetAttribute("debounce_down", debounce_down);
gameButtonNode->SetAttribute("bat_threshold", bat_threshold);
gameButtonNode->SetAttribute("velocity_threshold", velocity_threshold); gameButtonNode->SetAttribute("velocity_threshold", velocity_threshold);
gameButtonNode->SetAttribute("invert", invert); gameButtonNode->SetAttribute("invert", invert);
gameButtonsNode->InsertEndChild(gameButtonNode); gameButtonsNode->InsertEndChild(gameButtonNode);
@@ -188,6 +191,7 @@ bool Config::addGame(Game &game) {
button->setAnalogType((ButtonAnalogType) analogType); button->setAnalogType((ButtonAnalogType) analogType);
button->setDebounceUp(debounce_up); button->setDebounceUp(debounce_up);
button->setDebounceDown(debounce_down); button->setDebounceDown(debounce_down);
button->setBatThreshold(bat_threshold);
button->setVelocityThreshold(velocity_threshold); button->setVelocityThreshold(velocity_threshold);
button->setInvert(invert); button->setInvert(invert);
if (devid) { if (devid) {
@@ -207,6 +211,7 @@ bool Config::addGame(Game &game) {
gameButtonNode->SetAttribute("analogtype", (int) it.getAnalogType()); gameButtonNode->SetAttribute("analogtype", (int) it.getAnalogType());
gameButtonNode->SetAttribute("debounce_up", it.getDebounceUp()); gameButtonNode->SetAttribute("debounce_up", it.getDebounceUp());
gameButtonNode->SetAttribute("debounce_down", it.getDebounceDown()); gameButtonNode->SetAttribute("debounce_down", it.getDebounceDown());
gameButtonNode->SetAttribute("bat_threshold", it.getBatThreshold());
gameButtonNode->SetAttribute("velocity_threshold", it.getVelocityThreshold()); gameButtonNode->SetAttribute("velocity_threshold", it.getVelocityThreshold());
gameButtonNode->SetAttribute("invert", it.getInvert()); gameButtonNode->SetAttribute("invert", it.getInvert());
gameButtonNode->SetAttribute("devid", it.getDeviceIdentifier().c_str()); gameButtonNode->SetAttribute("devid", it.getDeviceIdentifier().c_str());
@@ -247,8 +252,6 @@ bool Config::addGame(Game &game) {
bool invert = false; bool invert = false;
bool smoothing = false; bool smoothing = false;
int multiplier = 1; int multiplier = 1;
bool relative_mode = false;
int delay_buffer_depth = 0;
tinyxml2::XMLError err1 = gameAnalogNode->QueryIntAttribute("index", &index); tinyxml2::XMLError err1 = gameAnalogNode->QueryIntAttribute("index", &index);
gameAnalogNode->QueryFloatAttribute("sensivity", &sensitivity); gameAnalogNode->QueryFloatAttribute("sensivity", &sensitivity);
gameAnalogNode->QueryFloatAttribute("deadzone", &deadzone); gameAnalogNode->QueryFloatAttribute("deadzone", &deadzone);
@@ -256,8 +259,6 @@ bool Config::addGame(Game &game) {
gameAnalogNode->QueryBoolAttribute("invert", &invert); gameAnalogNode->QueryBoolAttribute("invert", &invert);
gameAnalogNode->QueryBoolAttribute("smoothing", &smoothing); gameAnalogNode->QueryBoolAttribute("smoothing", &smoothing);
gameAnalogNode->QueryIntAttribute("multiplier", &multiplier); gameAnalogNode->QueryIntAttribute("multiplier", &multiplier);
gameAnalogNode->QueryBoolAttribute("relative", &relative_mode);
gameAnalogNode->QueryIntAttribute("delay", &delay_buffer_depth);
const char *devid = gameAnalogNode->Attribute("devid"); const char *devid = gameAnalogNode->Attribute("devid");
if (err1 != tinyxml2::XMLError::XML_SUCCESS || !devid) { if (err1 != tinyxml2::XMLError::XML_SUCCESS || !devid) {
@@ -272,8 +273,6 @@ bool Config::addGame(Game &game) {
gameAnalogNode->SetAttribute("invert", it.getInvert()); gameAnalogNode->SetAttribute("invert", it.getInvert());
gameAnalogNode->SetAttribute("smoothing", it.getSmoothing()); gameAnalogNode->SetAttribute("smoothing", it.getSmoothing());
gameAnalogNode->SetAttribute("multiplier", it.getMultiplier()); gameAnalogNode->SetAttribute("multiplier", it.getMultiplier());
gameAnalogNode->SetAttribute("relative", it.isRelativeMode());
gameAnalogNode->SetAttribute("delay", it.getDelayBufferDepth());
gameAnalogsNode->InsertEndChild(gameAnalogNode); gameAnalogsNode->InsertEndChild(gameAnalogNode);
} else { } else {
it.setIndex(static_cast<unsigned short int>(index)); it.setIndex(static_cast<unsigned short int>(index));
@@ -284,8 +283,6 @@ bool Config::addGame(Game &game) {
it.setInvert(invert); it.setInvert(invert);
it.setSmoothing(smoothing); it.setSmoothing(smoothing);
it.setMultiplier(multiplier); it.setMultiplier(multiplier);
it.setRelativeMode(relative_mode);
it.setDelayBufferDepth(delay_buffer_depth);
} }
} else { } else {
gameAnalogNode = this->configFile.NewElement("analog"); gameAnalogNode = this->configFile.NewElement("analog");
@@ -297,8 +294,6 @@ bool Config::addGame(Game &game) {
gameAnalogNode->SetAttribute("invert", it.getInvert()); gameAnalogNode->SetAttribute("invert", it.getInvert());
gameAnalogNode->SetAttribute("smoothing", it.getSmoothing()); gameAnalogNode->SetAttribute("smoothing", it.getSmoothing());
gameAnalogNode->SetAttribute("multiplier", it.getMultiplier()); gameAnalogNode->SetAttribute("multiplier", it.getMultiplier());
gameAnalogNode->SetAttribute("relative", it.isRelativeMode());
gameAnalogNode->SetAttribute("delay", it.getDelayBufferDepth());
gameAnalogNode->SetAttribute("devid", it.getDeviceIdentifier().c_str()); gameAnalogNode->SetAttribute("devid", it.getDeviceIdentifier().c_str());
gameAnalogsNode->InsertEndChild(gameAnalogNode); gameAnalogsNode->InsertEndChild(gameAnalogNode);
} }
@@ -432,6 +427,7 @@ bool Config::addGame(Game &game) {
gameButtonNode->SetAttribute("analogtype", it.getAnalogType()); gameButtonNode->SetAttribute("analogtype", it.getAnalogType());
gameButtonNode->SetAttribute("debounce_up", it.getDebounceUp()); gameButtonNode->SetAttribute("debounce_up", it.getDebounceUp());
gameButtonNode->SetAttribute("debounce_down", it.getDebounceDown()); gameButtonNode->SetAttribute("debounce_down", it.getDebounceDown());
gameButtonNode->SetAttribute("bat_threshold", it.getBatThreshold());
gameButtonNode->SetAttribute("velocity_threshold", it.getVelocityThreshold()); gameButtonNode->SetAttribute("velocity_threshold", it.getVelocityThreshold());
gameButtonNode->SetAttribute("invert", it.getInvert()); gameButtonNode->SetAttribute("invert", it.getInvert());
gameButtonNode->SetAttribute("devid", it.getDeviceIdentifier().c_str()); gameButtonNode->SetAttribute("devid", it.getDeviceIdentifier().c_str());
@@ -451,8 +447,6 @@ bool Config::addGame(Game &game) {
gameAnalogNode->SetAttribute("invert", it.getInvert()); gameAnalogNode->SetAttribute("invert", it.getInvert());
gameAnalogNode->SetAttribute("smoothing", it.getSmoothing()); gameAnalogNode->SetAttribute("smoothing", it.getSmoothing());
gameAnalogNode->SetAttribute("multiplier", it.getMultiplier()); gameAnalogNode->SetAttribute("multiplier", it.getMultiplier());
gameAnalogNode->SetAttribute("relative", it.isRelativeMode());
gameAnalogNode->SetAttribute("delay", it.getDelayBufferDepth());
gameAnalogsNode->InsertEndChild(gameAnalogNode); gameAnalogsNode->InsertEndChild(gameAnalogNode);
} }
@@ -535,6 +529,7 @@ bool Config::updateBinding(const Game &game, const Button &button, int alternati
gameButtonNode->SetAttribute("analogtype", (int) button.getAnalogType()); gameButtonNode->SetAttribute("analogtype", (int) button.getAnalogType());
gameButtonNode->SetAttribute("debounce_up", button.getDebounceUp()); gameButtonNode->SetAttribute("debounce_up", button.getDebounceUp());
gameButtonNode->SetAttribute("debounce_down", button.getDebounceDown()); gameButtonNode->SetAttribute("debounce_down", button.getDebounceDown());
gameButtonNode->SetAttribute("bat_threshold", button.getBatThreshold());
gameButtonNode->SetAttribute("velocity_threshold", button.getVelocityThreshold()); gameButtonNode->SetAttribute("velocity_threshold", button.getVelocityThreshold());
gameButtonNode->SetAttribute("invert", button.getInvert()); gameButtonNode->SetAttribute("invert", button.getInvert());
gameButtonNode->SetAttribute("devid", button.getDeviceIdentifier().c_str()); gameButtonNode->SetAttribute("devid", button.getDeviceIdentifier().c_str());
@@ -551,6 +546,7 @@ bool Config::updateBinding(const Game &game, const Button &button, int alternati
gameButtonNode->SetAttribute("analogtype", 0); gameButtonNode->SetAttribute("analogtype", 0);
gameButtonNode->SetAttribute("debounce_up", 0.0); gameButtonNode->SetAttribute("debounce_up", 0.0);
gameButtonNode->SetAttribute("debounce_down", 0.0); gameButtonNode->SetAttribute("debounce_down", 0.0);
gameButtonNode->SetAttribute("bat_threshold", 0);
gameButtonNode->SetAttribute("velocity_threshold", 0); gameButtonNode->SetAttribute("velocity_threshold", 0);
gameButtonNode->SetAttribute("invert", false); gameButtonNode->SetAttribute("invert", false);
gameButtonNode->SetAttribute("devid", ""); gameButtonNode->SetAttribute("devid", "");
@@ -683,8 +679,6 @@ bool Config::updateBinding(const Game &game, const Analog &analog) {
gameAnalogNode->SetAttribute("invert", analog.getInvert()); gameAnalogNode->SetAttribute("invert", analog.getInvert());
gameAnalogNode->SetAttribute("smoothing", analog.getSmoothing()); gameAnalogNode->SetAttribute("smoothing", analog.getSmoothing());
gameAnalogNode->SetAttribute("multiplier", analog.getMultiplier()); gameAnalogNode->SetAttribute("multiplier", analog.getMultiplier());
gameAnalogNode->SetAttribute("relative", analog.isRelativeMode());
gameAnalogNode->SetAttribute("delay", analog.getDelayBufferDepth());
gameAnalogNode->SetAttribute("devid", analog.getDeviceIdentifier().c_str()); gameAnalogNode->SetAttribute("devid", analog.getDeviceIdentifier().c_str());
} else { } else {
gameAnalogNode = this->configFile.NewElement("analog"); gameAnalogNode = this->configFile.NewElement("analog");
@@ -695,8 +689,6 @@ bool Config::updateBinding(const Game &game, const Analog &analog) {
gameAnalogNode->SetAttribute("invert", analog.getInvert()); gameAnalogNode->SetAttribute("invert", analog.getInvert());
gameAnalogNode->SetAttribute("smoothing", analog.getSmoothing()); gameAnalogNode->SetAttribute("smoothing", analog.getSmoothing());
gameAnalogNode->SetAttribute("multiplier", analog.getMultiplier()); gameAnalogNode->SetAttribute("multiplier", analog.getMultiplier());
gameAnalogNode->SetAttribute("relative", analog.isRelativeMode());
gameAnalogNode->SetAttribute("delay", analog.getDelayBufferDepth());
gameAnalogNode->SetAttribute("devid", analog.getDeviceIdentifier().c_str()); gameAnalogNode->SetAttribute("devid", analog.getDeviceIdentifier().c_str());
gameAnalogsNode->InsertEndChild(gameAnalogNode); gameAnalogsNode->InsertEndChild(gameAnalogNode);
} }
@@ -946,12 +938,14 @@ std::vector<Button> Config::getButtons(const std::string &gameName) {
auto analogType = (int) BAT_NONE; auto analogType = (int) BAT_NONE;
double debounce_up = 0.0; double debounce_up = 0.0;
double debounce_down = 0.0; double debounce_down = 0.0;
int bat_threshold = 0;
int velocity_threshold = 0; int velocity_threshold = 0;
bool invert = false; bool invert = false;
gameButtonNode->QueryIntAttribute("vkey", &vKey); gameButtonNode->QueryIntAttribute("vkey", &vKey);
gameButtonNode->QueryIntAttribute("analogtype", &analogType); gameButtonNode->QueryIntAttribute("analogtype", &analogType);
gameButtonNode->QueryDoubleAttribute("debounce_up", &debounce_up); gameButtonNode->QueryDoubleAttribute("debounce_up", &debounce_up);
gameButtonNode->QueryDoubleAttribute("debounce_down", &debounce_down); gameButtonNode->QueryDoubleAttribute("debounce_down", &debounce_down);
gameButtonNode->QueryIntAttribute("bat_threshold", &bat_threshold);
gameButtonNode->QueryIntAttribute("velocity_threshold", &velocity_threshold); gameButtonNode->QueryIntAttribute("velocity_threshold", &velocity_threshold);
gameButtonNode->QueryBoolAttribute("invert", &invert); gameButtonNode->QueryBoolAttribute("invert", &invert);
const char *devid = gameButtonNode->Attribute("devid"); const char *devid = gameButtonNode->Attribute("devid");
@@ -966,6 +960,7 @@ std::vector<Button> Config::getButtons(const std::string &gameName) {
alt.setAnalogType((ButtonAnalogType) analogType); alt.setAnalogType((ButtonAnalogType) analogType);
alt.setDebounceUp(debounce_up); alt.setDebounceUp(debounce_up);
alt.setDebounceDown(debounce_down); alt.setDebounceDown(debounce_down);
alt.setBatThreshold(bat_threshold);
alt.setVelocityThreshold(velocity_threshold); alt.setVelocityThreshold(velocity_threshold);
alt.setInvert(invert); alt.setInvert(invert);
if (devid) { if (devid) {
@@ -984,6 +979,7 @@ std::vector<Button> Config::getButtons(const std::string &gameName) {
button.setAnalogType((ButtonAnalogType) analogType); button.setAnalogType((ButtonAnalogType) analogType);
button.setDebounceUp(debounce_up); button.setDebounceUp(debounce_up);
button.setDebounceDown(debounce_down); button.setDebounceDown(debounce_down);
button.setBatThreshold(bat_threshold);
button.setVelocityThreshold(velocity_threshold); button.setVelocityThreshold(velocity_threshold);
button.setInvert(invert); button.setInvert(invert);
if (devid) { if (devid) {
@@ -1129,8 +1125,6 @@ std::vector<Analog> Config::getAnalogs(const std::string &gameName) {
bool invert = false; bool invert = false;
bool smoothing = false; bool smoothing = false;
int multiplier = 1; int multiplier = 1;
bool relative_mode = false;
int delay_buffer_depth = 0;
gameAnalogNode->QueryIntAttribute("index", &index); gameAnalogNode->QueryIntAttribute("index", &index);
gameAnalogNode->QueryFloatAttribute("sensivity", &sensitivity); gameAnalogNode->QueryFloatAttribute("sensivity", &sensitivity);
gameAnalogNode->QueryFloatAttribute("deadzone", &deadzone); gameAnalogNode->QueryFloatAttribute("deadzone", &deadzone);
@@ -1138,8 +1132,6 @@ std::vector<Analog> Config::getAnalogs(const std::string &gameName) {
gameAnalogNode->QueryBoolAttribute("invert", &invert); gameAnalogNode->QueryBoolAttribute("invert", &invert);
gameAnalogNode->QueryBoolAttribute("smoothing", &smoothing); gameAnalogNode->QueryBoolAttribute("smoothing", &smoothing);
gameAnalogNode->QueryIntAttribute("multiplier", &multiplier); gameAnalogNode->QueryIntAttribute("multiplier", &multiplier);
gameAnalogNode->QueryBoolAttribute("relative", &relative_mode);
gameAnalogNode->QueryIntAttribute("delay", &delay_buffer_depth);
const char *devid = gameAnalogNode->Attribute("devid"); const char *devid = gameAnalogNode->Attribute("devid");
// create analog and add to list // create analog and add to list
@@ -1151,8 +1143,6 @@ std::vector<Analog> Config::getAnalogs(const std::string &gameName) {
analog.setInvert(invert); analog.setInvert(invert);
analog.setSmoothing(smoothing); analog.setSmoothing(smoothing);
analog.setMultiplier(multiplier); analog.setMultiplier(multiplier);
analog.setRelativeMode(relative_mode);
analog.setDelayBufferDepth(delay_buffer_depth);
if (devid) { if (devid) {
analog.setDeviceIdentifier(devid); analog.setDeviceIdentifier(devid);
} }
@@ -1315,7 +1305,7 @@ void Config::saveConfigFile() {
// create a .tmp file and write to it... // create a .tmp file and write to it...
const auto xml_result = this->configFile.SaveFile(this->configLocationTemp.c_str(), false); const auto xml_result = this->configFile.SaveFile(this->configLocationTemp.c_str(), false);
if (xml_result != tinyxml2::XMLError::XML_SUCCESS) { if (xml_result != tinyxml2::XMLError::XML_SUCCESS) {
log_info("cfg", "failed to write file: {}", this->configLocationTemp.string()); log_info("cfg", "failed to write file: {}", this->configLocationTemp);
return; return;
} }
// copy the .tmp file to the main file... // copy the .tmp file to the main file...
@@ -1325,4 +1315,4 @@ void Config::saveConfigFile() {
} }
// delete the .tmp file (not critical if this fails) // delete the .tmp file (not critical if this fails)
DeleteFileW(this->configLocationTemp.c_str()); DeleteFileW(this->configLocationTemp.c_str());
} }
+5 -4
View File
@@ -24,6 +24,7 @@ namespace overlay::windows {
el->SetAttribute("invert", entry.invert); el->SetAttribute("invert", entry.invert);
el->SetAttribute("debounce_up", entry.debounce_up); el->SetAttribute("debounce_up", entry.debounce_up);
el->SetAttribute("debounce_down", entry.debounce_down); el->SetAttribute("debounce_down", entry.debounce_down);
el->SetAttribute("bat_threshold", entry.bat_threshold);
el->SetAttribute("velocity_threshold", entry.velocity_threshold); el->SetAttribute("velocity_threshold", entry.velocity_threshold);
parent->InsertEndChild(el); parent->InsertEndChild(el);
} }
@@ -49,6 +50,10 @@ namespace overlay::windows {
el->QueryIntAttribute("velocity_threshold", &vel); el->QueryIntAttribute("velocity_threshold", &vel);
entry.velocity_threshold = (unsigned short)vel; entry.velocity_threshold = (unsigned short)vel;
int bat = 0;
el->QueryIntAttribute("bat_threshold", &bat);
entry.bat_threshold = bat;
return entry; return entry;
} }
@@ -64,8 +69,6 @@ namespace overlay::windows {
el->SetAttribute("invert", analog.invert); el->SetAttribute("invert", analog.invert);
el->SetAttribute("smoothing", analog.smoothing); el->SetAttribute("smoothing", analog.smoothing);
el->SetAttribute("multiplier", analog.multiplier); el->SetAttribute("multiplier", analog.multiplier);
el->SetAttribute("relative", analog.relative_mode);
el->SetAttribute("delay", analog.delay_buffer_depth);
parent->InsertEndChild(el); parent->InsertEndChild(el);
} }
@@ -87,8 +90,6 @@ namespace overlay::windows {
el->QueryBoolAttribute("invert", &a.invert); el->QueryBoolAttribute("invert", &a.invert);
el->QueryBoolAttribute("smoothing", &a.smoothing); el->QueryBoolAttribute("smoothing", &a.smoothing);
el->QueryIntAttribute("multiplier", &a.multiplier); el->QueryIntAttribute("multiplier", &a.multiplier);
el->QueryBoolAttribute("relative", &a.relative_mode);
el->QueryIntAttribute("delay", &a.delay_buffer_depth);
return a; return a;
} }
+3 -3
View File
@@ -21,7 +21,7 @@ namespace cfg {
if (SCREEN_RESIZE_CFG_PATH_OVERRIDE.has_value()) { if (SCREEN_RESIZE_CFG_PATH_OVERRIDE.has_value()) {
this->config_path = SCREEN_RESIZE_CFG_PATH_OVERRIDE.value(); this->config_path = SCREEN_RESIZE_CFG_PATH_OVERRIDE.value();
if (fileutils::file_exists(this->config_path)) { if (fileutils::file_exists(this->config_path)) {
log_info("ScreenResize", "loading config from: {}", this->config_path.string()); log_info("ScreenResize", "loading config from: {}", this->config_path);
file_exists = true; file_exists = true;
} }
} else { } else {
@@ -35,7 +35,7 @@ namespace cfg {
ScreenResize::~ScreenResize() { ScreenResize::~ScreenResize() {
} }
void ScreenResize::config_load() { void ScreenResize::config_load() {
std::string config = fileutils::text_read(this->config_path); std::string config = fileutils::text_read(this->config_path);
if (config.empty()) { if (config.empty()) {
@@ -78,7 +78,7 @@ namespace cfg {
eamuse_get_game(), eamuse_get_game(),
use_game_setting, use_game_setting,
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) { if (this->enable_screen_resize) {
log_misc("ScreenResize", "enabled by config file"); log_misc("ScreenResize", "enabled by config file");
+1
View File
@@ -60,6 +60,7 @@ namespace cfg {
// window = rectangle including the frame // window = rectangle including the frame
// client = just the content area without frames. // client = just the content area without frames.
bool window_always_on_top = false; bool window_always_on_top = false;
bool window_disable_round_corners = false;
bool client_keep_aspect_ratio = true; bool client_keep_aspect_ratio = true;
bool enable_window_resize = false; bool enable_window_resize = false;
int window_decoration = 0; // enum type WindowDecorationMode int window_decoration = 0; // enum type WindowDecorationMode
+6 -5
View File
@@ -38,11 +38,12 @@ std::vector<Analog> &games::bc::get_analogs() {
if (analogs.empty()) { if (analogs.empty()) {
analogs = GameAPI::Analogs::getAnalogs("Busou Shinki: Armored Princess Battle Conductor"); analogs = GameAPI::Analogs::getAnalogs("Busou Shinki: Armored Princess Battle Conductor");
GameAPI::Analogs::sortAnalogs( using GameAPI::Analogs::AnalogType;
&analogs,
"Stick X", GameAPI::Analogs::sortAnalogsWithType(&analogs, {
"Stick Y" { "Stick X", AnalogType::LinearCentered },
); { "Stick Y", AnalogType::LinearCentered }
});
} }
return analogs; return analogs;
+8 -7
View File
@@ -45,13 +45,14 @@ std::vector<Analog> &games::ccj::get_analogs() {
if (analogs.empty()) { if (analogs.empty()) {
analogs = GameAPI::Analogs::getAnalogs("Chase Chase Jokers"); analogs = GameAPI::Analogs::getAnalogs("Chase Chase Jokers");
GameAPI::Analogs::sortAnalogs( using GameAPI::Analogs::AnalogType;
&analogs,
"Joystick X", GameAPI::Analogs::sortAnalogsWithType(&analogs, {
"Joystick Y", { "Joystick X", AnalogType::LinearCentered },
"Trackball DX", { "Joystick Y", AnalogType::LinearCentered },
"Trackball DY" { "Trackball DX", AnalogType::LinearCentered },
); { "Trackball DY", AnalogType::LinearCentered }
});
} }
return analogs; return analogs;
+58 -11
View File
@@ -23,6 +23,7 @@
#include "p3io/usbmem.h" #include "p3io/usbmem.h"
#include "p4io/p4io.h" #include "p4io/p4io.h"
#include <cstring>
using namespace acioemu; using namespace acioemu;
@@ -53,9 +54,9 @@ namespace games::ddr {
return SendMessage_real(hWnd, Msg, wParam, lParam); return SendMessage_real(hWnd, Msg, wParam, lParam);
} }
bool contains_only_ascii(const std::string& str) { bool contains_only_ascii(const std::wstring& str) {
for (auto c: str) { for (auto c: str) {
if (static_cast<unsigned char>(c) > 127) { if (c > 127) {
return false; return false;
} }
} }
@@ -80,24 +81,24 @@ namespace games::ddr {
} }
if (fileutils::dir_exists(dir)) { if (fileutils::dir_exists(dir)) {
log_info("ddr", "looking for codecs in this directory: {}", dir.string()); log_info("ddr", "looking for codecs in this directory: {}", dir);
} else { } else {
log_info("ddr", "codecs directory not found: {}", dir.string()); log_info("ddr", "codecs directory not found: {}", dir);
return; return;
} }
for (const auto &file : std::filesystem::directory_iterator(dir)) { for (const auto &file : std::filesystem::directory_iterator(dir)) {
const auto &filename = file.path().filename(); const auto &filename = file.path().filename();
const auto extension = strtolower(filename.extension().string()); const auto extension = filename.extension().wstring();
if (extension != ".dll") { if (wcsicmp(extension.c_str(), L".dll") != 0) {
continue; continue;
} }
log_info("ddr", "found DLL: {}, size: {} bytes", filename.string(), file.file_size()); log_info("ddr", "found DLL: {}, size: {} bytes", filename, file.file_size());
if (filename == "k-clvsd.dll" || filename.string().find("xactengine") == 0) { if (filename == "k-clvsd.dll" || filename.wstring().find(L"xactengine") == 0) {
const std::wstring wcmd = L"regsvr32.exe /s \"" + file.path().wstring() + L"\""; const std::wstring wcmd = L"regsvr32.exe /s \"" + file.path().wstring() + L"\"";
const std::string cmd = "regsvr32.exe /s \"" + file.path().string() + "\""; const std::string cmd = fmt::format("regsvr32.exe /s \"{}\"", file.path());
int result = 0; int result = 0;
std::thread t([wcmd, &result]() { std::thread t([wcmd, &result]() {
@@ -111,12 +112,12 @@ namespace games::ddr {
log_warning("ddr", "`{}` failed, returned {}", cmd, result); log_warning("ddr", "`{}` failed, returned {}", cmd, result);
deferredlogs::defer_error_messages({ deferredlogs::defer_error_messages({
"DDR codec registration failure", "DDR codec registration failure",
fmt::format(" {} failed to register with error {}", filename.string(), result)}); fmt::format(" {} failed to register with error {}", filename, result)});
} }
static std::once_flag printed; static std::once_flag printed;
std::call_once(printed, [file]() { std::call_once(printed, [file]() {
if (!contains_only_ascii(file.path().string())) { if (!contains_only_ascii(file.path().wstring())) {
log_warning( log_warning(
"ddr", "ddr",
"BAD PATH ERROR\n\n" "BAD PATH ERROR\n\n"
@@ -290,4 +291,50 @@ namespace games::ddr {
// dispose device hook // dispose device hook
devicehook_dispose(); devicehook_dispose();
} }
static void get_analog_xy_axis(Analog &analog, bool &less, bool &more) {
if (!analog.isSet()) {
return;
}
const auto value = GameAPI::Analogs::getState(RI_MGR, analog);
// stepmania linux source:
// https://github.com/stepmania/stepmania/blob/d55acb1ba26f1c5b5e3048d6d6c0bd116625216f/src/arch/InputHandler/InputHandler_Linux_Event.cpp#L410
// for example, value cap with range [0, 255]:
// 127 = 0.498, neutral
// 128 = 0.502, both
// apparently some adapters report values like above; this obviously makes a lot of
// assumptions about bit width of the HID value cap and how the adapter reports
// neutral/both values, but this is good enough for parity with stepmania and its many forks
if (0.5001f < value && value < 0.75f) {
less |= true;
more |= true;
} else if (value <= 0.25f) {
less |= true;
} else if (value >= 0.75f) {
more |= true;
}
}
void get_analog_x_axis(int player, bool &left, bool &right) {
if (player != 1 && player != 2) {
log_fatal("ddr", "invalid player number: {}, expected 1 or 2", player);
return;
}
auto &analog = get_analogs().at(player == 1 ? Analogs::P1_LEFT_RIGHT : Analogs::P2_LEFT_RIGHT);
get_analog_xy_axis(analog, left, right);
}
void get_analog_y_axis(int player, bool &up, bool &down) {
if (player != 1 && player != 2) {
log_fatal("ddr", "invalid player number: {}, expected 1 or 2", player);
return;
}
auto &analog = get_analogs().at(player == 1 ? Analogs::P1_UP_DOWN : Analogs::P2_UP_DOWN);
get_analog_xy_axis(analog, up, down);
}
} }
+3
View File
@@ -23,4 +23,7 @@ namespace games::ddr {
private: private:
void register_codecs(); void register_codecs();
}; };
void get_analog_x_axis(int player, bool &left, bool &right);
void get_analog_y_axis(int player, bool &up, bool &down);
} }
+44
View File
@@ -35,6 +35,50 @@ std::vector<Button> &games::ddr::get_buttons() {
return analogs; return analogs;
} }
std::string games::ddr::get_buttons_help() {
// keep to max 100 characters wide
return
"For DDR pad arrows, double check that simultaneous Left+Right and Up+Down can be detected.\n"
"You should boot the game, enter test menu, and use Foot Panel Check.\n\n"
"When mapping arrows, try the following in order:\n\n"
" 1. If your controller uses face buttons (A/B/X/Y), bind them here.\n"
" 2. If your controller supports XInput, use that.\n"
" 3. If your controller uses analog axis for arrows, try Analogs tab.\n"
" 4. Otherwise, you will need to use remapping software or get a better adapter."
;
}
std::vector<Analog> &games::ddr::get_analogs() {
static std::vector<Analog> analogs;
if (analogs.empty()) {
analogs = GameAPI::Analogs::getAnalogs("Dance Dance Revolution");
using namespace GameAPI::Analogs;
GameAPI::Analogs::sortAnalogsWithType(&analogs, {
{ "P1 Left-Right (Axis Fix)", AnalogType::LinearCentered },
{ "P1 Up-Down (Axis Fix)", AnalogType::LinearCentered },
{ "P2 Left-Right (Axis Fix)", AnalogType::LinearCentered },
{ "P2 Up-Down (Axis Fix)", AnalogType::LinearCentered }
});
}
return analogs;
}
std::string games::ddr::get_analogs_help() {
// keep to max 100 characters wide
return
"Only use this if your DDR pad outputs analog axis for arrows.\n\n"
"If the pad uses face buttons (A/B/X/Y), use Buttons tab instead.\n\n"
"Spice will treat values <=25% as Left, >=75% as Right, ~=50% as neutral,\n"
"and value between 50% and 75% as both arrows.\n\n"
"This is the classic Stepmania \"Axis Fix\" which may or may not work with\n"
"your dance pad or your adapter."
;
}
std::vector<Light> &games::ddr::get_lights() { std::vector<Light> &games::ddr::get_lights() {
static std::vector<Light> lights; static std::vector<Light> lights;
+13
View File
@@ -33,6 +33,16 @@ namespace games::ddr {
}; };
} }
// all analogs in correct order
namespace Analogs {
enum {
P1_LEFT_RIGHT,
P1_UP_DOWN,
P2_LEFT_RIGHT,
P2_UP_DOWN
};
}
// all lights in correct order // all lights in correct order
namespace Lights { namespace Lights {
enum { enum {
@@ -177,5 +187,8 @@ namespace games::ddr {
// getters // getters
std::vector<Button> &get_buttons(); 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(); std::vector<Light> &get_lights();
} }
+49 -1
View File
@@ -498,32 +498,80 @@ int games::ddr::DDRP3IOHandle::device_io(
// shift table // shift table
static size_t shift_table[] = { static size_t shift_table[] = {
30, 28, 29, 8, 9, 10, 11, 12, 24, 25, 14, 15, 16, 17, 18, 19, 20, 26, 27, 22, 23 30, 28, 29, // service, test, coin
8, // p1 start
9, 10, 11, 12, // p1 panel
24, 25, 14, 15, // p1 menu
16, // p2 start
17, 18, 19, 20, // p2 panel
26, 27, 22, 23 // p2 menu
}; };
static size_t button_table[] = { static size_t button_table[] = {
Buttons::SERVICE, Buttons::SERVICE,
Buttons::TEST, Buttons::TEST,
Buttons::COIN_MECH, Buttons::COIN_MECH,
Buttons::P1_START, Buttons::P1_START,
Buttons::P1_PANEL_UP, Buttons::P1_PANEL_UP,
Buttons::P1_PANEL_DOWN, Buttons::P1_PANEL_DOWN,
Buttons::P1_PANEL_LEFT, Buttons::P1_PANEL_LEFT,
Buttons::P1_PANEL_RIGHT, Buttons::P1_PANEL_RIGHT,
Buttons::P1_MENU_UP, Buttons::P1_MENU_UP,
Buttons::P1_MENU_DOWN, Buttons::P1_MENU_DOWN,
Buttons::P1_MENU_LEFT, Buttons::P1_MENU_LEFT,
Buttons::P1_MENU_RIGHT, Buttons::P1_MENU_RIGHT,
Buttons::P2_START, Buttons::P2_START,
Buttons::P2_PANEL_UP, Buttons::P2_PANEL_UP,
Buttons::P2_PANEL_DOWN, Buttons::P2_PANEL_DOWN,
Buttons::P2_PANEL_LEFT, Buttons::P2_PANEL_LEFT,
Buttons::P2_PANEL_RIGHT, Buttons::P2_PANEL_RIGHT,
Buttons::P2_MENU_UP, Buttons::P2_MENU_UP,
Buttons::P2_MENU_DOWN, Buttons::P2_MENU_DOWN,
Buttons::P2_MENU_LEFT, Buttons::P2_MENU_LEFT,
Buttons::P2_MENU_RIGHT, Buttons::P2_MENU_RIGHT,
}; };
// get analogs
struct {
bool up;
bool down;
bool left;
bool right;
} analog_values[2] = {};
games::ddr::get_analog_x_axis(1, analog_values[0].left, analog_values[0].right);
games::ddr::get_analog_y_axis(1, analog_values[0].up, analog_values[0].down);
games::ddr::get_analog_x_axis(2, analog_values[1].left, analog_values[1].right);
games::ddr::get_analog_y_axis(2, analog_values[1].up, analog_values[1].down);
if (analog_values[0].up) {
controls |= 1 << 9;
}
if (analog_values[0].down) {
controls |= 1 << 10;
}
if (analog_values[0].left) {
controls |= 1 << 11;
}
if (analog_values[0].right) {
controls |= 1 << 12;
}
if (analog_values[1].up) {
controls |= 1 << 17;
}
if (analog_values[1].down) {
controls |= 1 << 18;
}
if (analog_values[1].left) {
controls |= 1 << 19;
}
if (analog_values[1].right) {
controls |= 1 << 20;
}
// update states // update states
auto &buttons = get_buttons(); auto &buttons = get_buttons();
size_t count = 0; size_t count = 0;
+8 -7
View File
@@ -35,13 +35,14 @@ std::vector<Analog> &games::ftt::get_analogs() {
if (analogs.empty()) { if (analogs.empty()) {
analogs = GameAPI::Analogs::getAnalogs("FutureTomTom"); analogs = GameAPI::Analogs::getAnalogs("FutureTomTom");
GameAPI::Analogs::sortAnalogs( using GameAPI::Analogs::AnalogType;
&analogs,
"Pad 1", GameAPI::Analogs::sortAnalogsWithType(&analogs, {
"Pad 2", { "Pad 1", AnalogType::LinearPositive },
"Pad 3", { "Pad 2", AnalogType::LinearPositive },
"Pad 4" { "Pad 3", AnalogType::LinearPositive },
); { "Pad 4", AnalogType::LinearPositive }
});
} }
return analogs; return analogs;
+12 -10
View File
@@ -86,19 +86,21 @@ std::vector<Button> &games::gitadora::get_buttons() {
std::vector<Analog> &games::gitadora::get_analogs() { std::vector<Analog> &games::gitadora::get_analogs() {
static std::vector<Analog> analogs; static std::vector<Analog> analogs;
using namespace GameAPI::Analogs;
if (analogs.empty()) { if (analogs.empty()) {
analogs = GameAPI::Analogs::getAnalogs("GitaDora"); analogs = GameAPI::Analogs::getAnalogs("GitaDora");
GameAPI::Analogs::sortAnalogs(&analogs, GameAPI::Analogs::sortAnalogsWithType(&analogs, {
"Guitar P1 Wail X", {"Guitar P1 Wail X", AnalogType::LinearCentered},
"Guitar P1 Wail Y", {"Guitar P1 Wail Y", AnalogType::LinearCentered},
"Guitar P1 Wail Z", {"Guitar P1 Wail Z", AnalogType::LinearCentered},
"Guitar P1 Knob", {"Guitar P1 Knob", AnalogType::Circular},
"Guitar P2 Wail X", {"Guitar P2 Wail X", AnalogType::LinearCentered},
"Guitar P2 Wail Y", {"Guitar P2 Wail Y", AnalogType::LinearCentered},
"Guitar P2 Wail Z", {"Guitar P2 Wail Z", AnalogType::LinearCentered},
"Guitar P2 Knob" {"Guitar P2 Knob", AnalogType::Circular}
); });
} }
return analogs; return analogs;
+39 -3
View File
@@ -67,6 +67,8 @@ namespace games::iidx {
std::optional<std::string> SOUND_OUTPUT_DEVICE = std::nullopt; std::optional<std::string> SOUND_OUTPUT_DEVICE = std::nullopt;
std::optional<std::string> SOUND_OUTPUT_DEVICE_IN_EFFECT = std::nullopt; std::optional<std::string> SOUND_OUTPUT_DEVICE_IN_EFFECT = std::nullopt;
std::optional<std::string> ASIO_DRIVER = std::nullopt; std::optional<std::string> ASIO_DRIVER = std::nullopt;
uint32_t TT_DELAY_P1 = 0;
uint32_t TT_DELAY_P2 = 0;
uint8_t DIGITAL_TT_SENS = 4; uint8_t DIGITAL_TT_SENS = 4;
std::optional<std::string> SUBSCREEN_OVERLAY_SIZE = std::nullopt; std::optional<std::string> SUBSCREEN_OVERLAY_SIZE = std::nullopt;
std::optional<std::string> SCREEN_MODE = std::nullopt; std::optional<std::string> SCREEN_MODE = std::nullopt;
@@ -781,7 +783,42 @@ namespace games::iidx {
} }
// return higher 8 bit // return higher 8 bit
return (uint8_t) (ret_value >> 2); uint8_t result = (uint8_t) (ret_value >> 2);
// delay
if ((player == 0 && TT_DELAY_P1 > 0) ||
(player == 1 && TT_DELAY_P2 > 0)) {
static std::queue<std::pair<double, uint8_t>> delay_queue[2];
auto &queue = delay_queue[player];
const auto max_delta_ms =
static_cast<double>((player == 0) ? TT_DELAY_P1 : TT_DELAY_P2);
// always push a new value
const auto now = get_performance_milliseconds();
queue.push(std::make_pair(now, result));
// drain the queue down to reasonable length to prevent unconstrained growth
// this would accommodate 1 second at ~1000Hz
// (in reality all three iidx I/O emulation runs well under 500Hz)
while (queue.size() > 1024) {
queue.pop();
}
// pop until we find one that falls just under the time threshold
while (!queue.empty()) {
const auto delta_t = now - queue.front().first;
if (delta_t <= max_delta_ms) {
break;
}
queue.pop();
}
result = queue.front().second;
}
return result;
} }
unsigned char get_slider(uint8_t slider) { unsigned char get_slider(uint8_t slider) {
@@ -915,8 +952,7 @@ namespace games::iidx {
log_warning( log_warning(
"iidx", "iidx",
"Failed to apply ASIO compatibility fix for iidx32+. " "Failed to apply ASIO compatibility fix for iidx32+. "
"Unless patches are applied, your ASIO device ({}) may hang or fail to work", "Unless patches are applied, your ASIO device may hang or fail to work");
ASIO_DRIVER->c_str());
} else { } else {
log_info( log_info(
"iidx", "iidx",
+2
View File
@@ -31,6 +31,8 @@ namespace games::iidx {
extern bool NATIVE_TOUCH; extern bool NATIVE_TOUCH;
extern std::optional<std::string> SOUND_OUTPUT_DEVICE; extern std::optional<std::string> SOUND_OUTPUT_DEVICE;
extern std::optional<std::string> ASIO_DRIVER; extern std::optional<std::string> ASIO_DRIVER;
extern uint32_t TT_DELAY_P1;
extern uint32_t TT_DELAY_P2;
extern uint8_t DIGITAL_TT_SENS; extern uint8_t DIGITAL_TT_SENS;
extern std::optional<std::string> SUBSCREEN_OVERLAY_SIZE; extern std::optional<std::string> SUBSCREEN_OVERLAY_SIZE;
extern std::optional<std::string> SCREEN_MODE; extern std::optional<std::string> SCREEN_MODE;
+11 -10
View File
@@ -63,16 +63,17 @@ std::vector<Analog> &games::iidx::get_analogs() {
if (analogs.empty()) { if (analogs.empty()) {
analogs = GameAPI::Analogs::getAnalogs("Beatmania IIDX"); analogs = GameAPI::Analogs::getAnalogs("Beatmania IIDX");
GameAPI::Analogs::sortAnalogs( using namespace GameAPI::Analogs;
&analogs,
"Turntable P1", GameAPI::Analogs::sortAnalogsWithType(&analogs, {
"Turntable P2", { "Turntable P1", AnalogType::Circular },
"VEFX", { "Turntable P2", AnalogType::Circular },
"Low-EQ", { "VEFX", AnalogType::LinearPositive },
"Hi-EQ", { "Low-EQ", AnalogType::LinearPositive },
"Filter", { "Hi-EQ", AnalogType::LinearPositive },
"Play Volume" { "Filter", AnalogType::LinearPositive },
); { "Play Volume", AnalogType::LinearPositive }
});
} }
return analogs; return analogs;
} }
+3
View File
@@ -92,6 +92,9 @@ namespace games {
games.push_back(ddr); games.push_back(ddr);
buttons.insert({ ddr, ddr::get_buttons() }); buttons.insert({ ddr, ddr::get_buttons() });
lights.insert({ ddr, ddr::get_lights() }); lights.insert({ ddr, ddr::get_lights() });
buttons_help.insert({ ddr, ddr::get_buttons_help() });
analogs_help.insert({ ddr, ddr::get_analogs_help() });
analogs.insert({ ddr, ddr::get_analogs() });
file_hints[ddr].push_back({"ddr.dll"}); file_hints[ddr].push_back({"ddr.dll"});
file_hints[ddr].push_back({"mdxja_945.dll"}); file_hints[ddr].push_back({"mdxja_945.dll"});
file_hints[ddr].push_back({"arkmdxp3.dll"}); file_hints[ddr].push_back({"arkmdxp3.dll"});
+2 -2
View File
@@ -167,7 +167,7 @@ namespace games::jb {
void JBGame::pre_attach() { void JBGame::pre_attach() {
if (!cfg::CONFIGURATOR_STANDALONE) { if (!cfg::CONFIGURATOR_STANDALONE) {
const auto current_path = std::filesystem::current_path(); const auto current_path = std::filesystem::current_path();
log_misc("jubeat", "current working directory: {}", current_path.string()); log_misc("jubeat", "current working directory: {}", current_path);
if (current_path.parent_path() == current_path.root_path()) { if (current_path.parent_path() == current_path.root_path()) {
log_warning( log_warning(
"jubeat", "jubeat",
@@ -178,7 +178,7 @@ namespace games::jb {
" c:\\jubeat\\contents\\spice.exe <- OK\n\n" " c:\\jubeat\\contents\\spice.exe <- OK\n\n"
"To fix this, create a new directory and move ALL game files there.\n\n" "To fix this, create a new directory and move ALL game files there.\n\n"
"Your current working directory: {}\n", "Your current working directory: {}\n",
current_path.string()); current_path);
log_fatal( log_fatal(
"jubeat", "jubeat",
+5 -5
View File
@@ -36,11 +36,11 @@ std::vector<Analog> &games::mga::get_analogs() {
if (analogs.empty()) { if (analogs.empty()) {
analogs = GameAPI::Analogs::getAnalogs("Metal Gear"); analogs = GameAPI::Analogs::getAnalogs("Metal Gear");
GameAPI::Analogs::sortAnalogs( using namespace GameAPI::Analogs;
&analogs, GameAPI::Analogs::sortAnalogsWithType(&analogs, {
"Joy X", { "Joy X", AnalogType::LinearCentered },
"Joy Y" { "Joy Y", AnalogType::LinearCentered }
); });
} }
return analogs; return analogs;
+32 -31
View File
@@ -165,37 +165,38 @@ std::vector<Analog> &games::nost::get_analogs() {
if (analogs.empty()) { if (analogs.empty()) {
analogs = GameAPI::Analogs::getAnalogs("Nostalgia"); analogs = GameAPI::Analogs::getAnalogs("Nostalgia");
GameAPI::Analogs::sortAnalogs( using namespace GameAPI::Analogs;
&analogs,
"Key 1", GameAPI::Analogs::sortAnalogsWithType(&analogs, {
"Key 2", { "Key 1", AnalogType::LinearPositive },
"Key 3", { "Key 2", AnalogType::LinearPositive },
"Key 4", { "Key 3", AnalogType::LinearPositive },
"Key 5", { "Key 4", AnalogType::LinearPositive },
"Key 6", { "Key 5", AnalogType::LinearPositive },
"Key 7", { "Key 6", AnalogType::LinearPositive },
"Key 8", { "Key 7", AnalogType::LinearPositive },
"Key 9", { "Key 8", AnalogType::LinearPositive },
"Key 10", { "Key 9", AnalogType::LinearPositive },
"Key 11", { "Key 10", AnalogType::LinearPositive },
"Key 12", { "Key 11", AnalogType::LinearPositive },
"Key 13", { "Key 12", AnalogType::LinearPositive },
"Key 14", { "Key 13", AnalogType::LinearPositive },
"Key 15", { "Key 14", AnalogType::LinearPositive },
"Key 16", { "Key 15", AnalogType::LinearPositive },
"Key 17", { "Key 16", AnalogType::LinearPositive },
"Key 18", { "Key 17", AnalogType::LinearPositive },
"Key 19", { "Key 18", AnalogType::LinearPositive },
"Key 20", { "Key 19", AnalogType::LinearPositive },
"Key 21", { "Key 20", AnalogType::LinearPositive },
"Key 22", { "Key 21", AnalogType::LinearPositive },
"Key 23", { "Key 22", AnalogType::LinearPositive },
"Key 24", { "Key 23", AnalogType::LinearPositive },
"Key 25", { "Key 24", AnalogType::LinearPositive },
"Key 26", { "Key 25", AnalogType::LinearPositive },
"Key 27", { "Key 26", AnalogType::LinearPositive },
"Key 28" { "Key 27", AnalogType::LinearPositive },
); { "Key 28", AnalogType::LinearPositive }
});
} }
return analogs; return analogs;
} }
+6 -5
View File
@@ -51,11 +51,12 @@ std::vector<Analog> &games::pc::get_analogs() {
if (analogs.empty()) { if (analogs.empty()) {
analogs = GameAPI::Analogs::getAnalogs("Polaris Chord"); analogs = GameAPI::Analogs::getAnalogs("Polaris Chord");
GameAPI::Analogs::sortAnalogs( using GameAPI::Analogs::AnalogType;
&analogs,
"Fader-L", GameAPI::Analogs::sortAnalogsWithType(&analogs, {
"Fader-R" { "Fader-L", AnalogType::LinearCentered },
); { "Fader-R", AnalogType::LinearCentered }
});
} }
return analogs; return analogs;
+1 -10
View File
@@ -387,15 +387,6 @@ namespace games::popn {
return 0; return 0;
} }
struct PopnLight {
int data_index;
Lights::popn_lights_t light;
uint8_t size;
PopnLight(
int data_index, Lights::popn_lights_t light, uint8_t size) :
data_index(data_index), light(light), size(size) {}
};
static void __fastcall aioIob5Bi3a_SetTapeLedDataPart( static void __fastcall aioIob5Bi3a_SetTapeLedDataPart(
AIO_IOB5_BI3A *i_pNodeCtl, uint32_t i_CnPin, char i_LedType, const void *i_pData, uint32_t i_DataSize, bool i_bIsLast) { AIO_IOB5_BI3A *i_pNodeCtl, uint32_t i_CnPin, char i_LedType, const void *i_pData, uint32_t i_DataSize, bool i_bIsLast) {
@@ -484,7 +475,7 @@ namespace games::popn {
const auto data_size = std::min(map.data.capacity(), (size_t)number_of_leds / 3); const auto data_size = std::min(map.data.capacity(), (size_t)number_of_leds / 3);
// pick a color to use // pick a color to use
const auto rgb = tapeledutils::pick_color_from_led_tape((uint8_t *)i_pData, data_size); const auto rgb = tapeledutils::pick_color_from_led_tape(map, (uint8_t *)i_pData, data_size);
// program the lights into API // program the lights into API
auto &lights = get_lights(); auto &lights = get_lights();
+12
View File
@@ -136,6 +136,18 @@ std::vector<Light> &games::popn::get_lights() {
{"Pika", "IC Card R"}, {"Pika", "IC Card R"},
{"Pika", "IC Card G"}, {"Pika", "IC Card G"},
{"Pika", "IC Card B"}, {"Pika", "IC Card B"},
{"Pika", "Speakers R"},
{"Pika", "Speakers G"},
{"Pika", "Speakers B"},
{"Pika", "Monitor R"},
{"Pika", "Monitor G"},
{"Pika", "Monitor B"},
{"Pika", "Control Panel R"},
{"Pika", "Control Panel G"},
{"Pika", "Control Panel B"},
{"Pika", "Cabinet R"},
{"Pika", "Cabinet G"},
{"Pika", "Cabinet B"},
}); });
} }
+12
View File
@@ -121,6 +121,18 @@ namespace games::popn {
IC_Card_R, IC_Card_R,
IC_Card_G, IC_Card_G,
IC_Card_B, IC_Card_B,
Speaker_R,
Speaker_G,
Speaker_B,
Monitor_R,
Monitor_G,
Monitor_B,
ControlPanel_R,
ControlPanel_G,
ControlPanel_B,
Cabinet_R,
Cabinet_G,
Cabinet_B,
} popn_lights_t; } popn_lights_t;
} }
+56 -6
View File
@@ -19,11 +19,13 @@
#include "util/sysutils.h" #include "util/sysutils.h"
#include "io.h" #include "io.h"
#include "util/deferlog.h" #include "util/deferlog.h"
#include "misc/nativetouchhook.h"
#include "misc/wintouchemu.h" #include "misc/wintouchemu.h"
namespace games::popn { namespace games::popn {
bool SHOW_PIKA_MONITOR_WARNING = false; bool SHOW_PIKA_MONITOR_WARNING = false;
bool NATIVE_TOUCH = false;
#if SPICE64 && !SPICE_XP #if SPICE64 && !SPICE_XP
@@ -36,39 +38,83 @@ namespace games::popn {
// pin 1 = left pop (red pop kun), size 16, values 0-0xFF // pin 1 = left pop (red pop kun), size 16, values 0-0xFF
// pin 2 = right pop (blue pop kun), size 16, values 0-0xFF // pin 2 = right pop (blue pop kun), size 16, values 0-0xFF
// pin 3 = title, size 44, values 0-0xFF // pin 3 = title, size 44, values 0-0xFF
// pin 4 = speaker, size 40, values 0-0xFF - U region only // pin 4 = speaker, size 40, values 0-0x7F - U region only
// pin 5 = monitor, size 18, values 0-0x7F - U region only // pin 5 = monitor, size 18, values 0-0x7F - U region only
// pin 6 = control panel, size 28, values 0-0x7F - U region only // pin 6 = control panel, size 28, values 0-0x7F - U region only
// pin 7 = cabinet, size 22, values 0-0x7F - U region only // pin 7 = cabinet, size 22, values 0-0x7F - U region only
tapeledutils::tape_led TAPELED_MAPPING[POPN_TAPELED_TOTAL] = { tapeledutils::tape_led TAPELED_MAPPING[POPN_TAPELED_TOTAL] = {
{ {
0, // these are button lights and handled specially // [0] button lights: these are button lights and handled specially
0,
0,
0, 0,
0, 0,
0, 0,
"Invalid" "Invalid"
}, },
{ {
// [1] red pop-kun
5, // 5*3; game reports 16 but make it 15 5, // 5*3; game reports 16 but make it 15
Lights::popn_lights_t::RedPopKun_R, Lights::popn_lights_t::RedPopKun_R,
Lights::popn_lights_t::RedPopKun_G, Lights::popn_lights_t::RedPopKun_G,
Lights::popn_lights_t::RedPopKun_B, Lights::popn_lights_t::RedPopKun_B,
0xFF,
"Red Pop-Kun" "Red Pop-Kun"
}, },
{ {
// [2] blue pop-kun
5, // 5*3; game reports 16 but make it 15 5, // 5*3; game reports 16 but make it 15
Lights::popn_lights_t::BluePopKun_R, Lights::popn_lights_t::BluePopKun_R,
Lights::popn_lights_t::BluePopKun_G, Lights::popn_lights_t::BluePopKun_G,
Lights::popn_lights_t::BluePopKun_B, Lights::popn_lights_t::BluePopKun_B,
0xFF,
"Blue Pop-Kun" "Blue Pop-Kun"
}, },
{ {
14, // 14*3; game reports 44 but make it 42; these are lights above the banner // [3] top LED
14, // 14*3; game reports 44 but make it 42
Lights::popn_lights_t::TopLED_R, Lights::popn_lights_t::TopLED_R,
Lights::popn_lights_t::TopLED_G, Lights::popn_lights_t::TopLED_G,
Lights::popn_lights_t::TopLED_B, Lights::popn_lights_t::TopLED_B,
0xFF,
"Top" "Top"
}, },
{
// [4] speakers
13, // 13*3; game reports 40 but make it 39
Lights::popn_lights_t::Speaker_G,
Lights::popn_lights_t::Speaker_R,
Lights::popn_lights_t::Speaker_B,
0x7F,
"Speakers"
},
{
// [5] monitor
6,
Lights::popn_lights_t::Monitor_G,
Lights::popn_lights_t::Monitor_R,
Lights::popn_lights_t::Monitor_B,
0x7F,
"Monitor"
},
{
// [6] control panel
9, // 9*3; game reports 28
Lights::popn_lights_t::ControlPanel_G,
Lights::popn_lights_t::ControlPanel_R,
Lights::popn_lights_t::ControlPanel_B,
0x7F,
"Control Panel"
},
{
// [7] Cabinet
7, // 7*3; game reports 22
Lights::popn_lights_t::Cabinet_G,
Lights::popn_lights_t::Cabinet_R,
Lights::popn_lights_t::Cabinet_B,
0x7F,
"Cabinet"
},
}; };
@@ -670,9 +716,13 @@ namespace games::popn {
// set third column to 0 and it will work with BIO2 // set third column to 0 and it will work with BIO2
if (!GRAPHICS_WINDOWED) { if (!GRAPHICS_WINDOWED) {
wintouchemu::FORCE = true; if (NATIVE_TOUCH) {
wintouchemu::INJECT_MOUSE_AS_WM_TOUCH = true; nativetouchhook::hook(avs::game::DLL_INSTANCE);
wintouchemu::hook_title_ends("", "Main Screen", avs::game::DLL_INSTANCE); } else {
wintouchemu::FORCE = true;
wintouchemu::INJECT_MOUSE_AS_WM_TOUCH = true;
wintouchemu::hook_title_ends("", "Main Screen", avs::game::DLL_INSTANCE);
}
} }
sysutils::hook_EnumDisplayDevicesA(); sysutils::hook_EnumDisplayDevicesA();
+2 -1
View File
@@ -17,11 +17,12 @@ namespace games::popn {
} }
#if SPICE64 #if SPICE64
constexpr int POPN_TAPELED_TOTAL = 4; constexpr int POPN_TAPELED_TOTAL = 8;
extern tapeledutils::tape_led TAPELED_MAPPING[POPN_TAPELED_TOTAL]; extern tapeledutils::tape_led TAPELED_MAPPING[POPN_TAPELED_TOTAL];
#endif #endif
extern bool SHOW_PIKA_MONITOR_WARNING; extern bool SHOW_PIKA_MONITOR_WARNING;
extern bool NATIVE_TOUCH;
class POPNGame : public games::Game { class POPNGame : public games::Game {
public: public:
+7 -6
View File
@@ -35,12 +35,13 @@ std::vector<Analog> &games::rf3d::get_analogs() {
if (analogs.empty()) { if (analogs.empty()) {
analogs = GameAPI::Analogs::getAnalogs("Road Fighters 3D"); analogs = GameAPI::Analogs::getAnalogs("Road Fighters 3D");
GameAPI::Analogs::sortAnalogs( using GameAPI::Analogs::AnalogType;
&analogs,
"Wheel", GameAPI::Analogs::sortAnalogsWithType(&analogs, {
"Accelerate", { "Wheel", AnalogType::LinearCentered },
"Brake" { "Accelerate", AnalogType::LinearPositive },
); { "Brake", AnalogType::LinearPositive }
});
} }
return analogs; return analogs;
+8 -7
View File
@@ -31,13 +31,14 @@ std::vector<Analog> &games::sc::get_analogs() {
if (analogs.empty()) { if (analogs.empty()) {
analogs = GameAPI::Analogs::getAnalogs("Steel Chronicle"); analogs = GameAPI::Analogs::getAnalogs("Steel Chronicle");
GameAPI::Analogs::sortAnalogs( using GameAPI::Analogs::AnalogType;
&analogs,
"Left Stick X", GameAPI::Analogs::sortAnalogsWithType(&analogs, {
"Left Stick Y", { "Left Stick X", AnalogType::LinearCentered },
"Right Stick X", { "Left Stick Y", AnalogType::LinearCentered },
"Right Stick Y" { "Right Stick X", AnalogType::LinearCentered },
); { "Right Stick Y", AnalogType::LinearCentered }
});
} }
return analogs; return analogs;
+6 -5
View File
@@ -31,11 +31,12 @@ std::vector<Analog> &games::silentscope::get_analogs() {
if (analogs.empty()) { if (analogs.empty()) {
analogs = GameAPI::Analogs::getAnalogs("Silent Scope: Bone Eater"); analogs = GameAPI::Analogs::getAnalogs("Silent Scope: Bone Eater");
GameAPI::Analogs::sortAnalogs( using GameAPI::Analogs::AnalogType;
&analogs,
"Gun X", GameAPI::Analogs::sortAnalogsWithType(&analogs, {
"Gun Y" { "Gun X", AnalogType::LinearCentered },
); { "Gun Y", AnalogType::LinearCentered }
});
} }
return analogs; return analogs;
+8 -7
View File
@@ -50,13 +50,14 @@ std::vector<Analog> &games::we::get_analogs() {
if (analogs.empty()) { if (analogs.empty()) {
analogs = GameAPI::Analogs::getAnalogs("Winning Eleven"); analogs = GameAPI::Analogs::getAnalogs("Winning Eleven");
GameAPI::Analogs::sortAnalogs( using GameAPI::Analogs::AnalogType;
&analogs,
"Pad Stick Left X", GameAPI::Analogs::sortAnalogsWithType(&analogs, {
"Pad Stick Left Y", { "Pad Stick Left X", AnalogType::LinearCentered },
"Pad Stick Right X", { "Pad Stick Left Y", AnalogType::LinearCentered },
"Pad Stick Right Y" { "Pad Stick Right X", AnalogType::LinearCentered },
); { "Pad Stick Right Y", AnalogType::LinearCentered }
});
} }
return analogs; return analogs;
+10 -8
View File
@@ -66,14 +66,16 @@ static HRESULT STDAPICALLTYPE CoCreateInstance_hook(
if (FAILED(ret)) { if (FAILED(ret)) {
if (IsEqualCLSID(rclsid, CLSID_MMDeviceEnumerator)) { if (IsEqualCLSID(rclsid, CLSID_MMDeviceEnumerator)) {
log_warning("audio", "CoCreateInstance failed for CLSID_MMDeviceEnumerator, hr={}", FMT_HRESULT(ret)); log_warning("audio", "CoCreateInstance failed for CLSID_MMDeviceEnumerator, hr={}", FMT_HRESULT(ret));
} } else if (IsEqualCLSID(rclsid, CLSID_DirectSoundI3DL2ReverbDMO)) {
// deal with reverb DMO missing from dsdmo.dll
// Windows 11 KB5052093 issue - reverb DMO went missing from dsdmo.dll (fails with 0x80040154) // it usually fails with 0x80040154 (REGDB_E_CLASSNOTREG), but we have also seen 0x8007007e (ERROR_MOD_NOT_FOUND)
if (IsEqualCLSID(rclsid, CLSID_DirectSoundI3DL2ReverbDMO) && ret == (HRESULT)0x80040154) { static std::once_flag printed;
log_warning( std::call_once(printed, [ret]() {
"audio", log_warning(
"CoCreateInstance for CLSID_DirectSoundI3DL2ReverbDMO failed with 0x80040154, swap with CLSID_DirectSoundWavesReverbDMO " "audio",
"(workaround for Windows 11 KB5052093 issue); REVERB EX replaced with REVERB"); "CoCreateInstance for CLSID_DirectSoundI3DL2ReverbDMO failed with {}, swap with CLSID_DirectSoundWavesReverbDMO...",
FMT_HRESULT(ret));
});
ret = CoCreateInstance_orig(CLSID_DirectSoundWavesReverbDMO, pUnkOuter, dwClsContext, riid, ppv); ret = CoCreateInstance_orig(CLSID_DirectSoundWavesReverbDMO, pUnkOuter, dwClsContext, riid, ppv);
if (FAILED(ret)) { if (FAILED(ret)) {
log_warning("audio", "CoCreateInstance(CLSID_DirectSoundWavesReverbDMO...) failed, hr={}", FMT_HRESULT(ret)); log_warning("audio", "CoCreateInstance(CLSID_DirectSoundWavesReverbDMO...) failed, hr={}", FMT_HRESULT(ret));
+2 -2
View File
@@ -291,7 +291,7 @@ static int avs_fs_mount(const char *mountpoint, const char *fsroot, const char *
std::filesystem::create_directories(mapped_path, err); std::filesystem::create_directories(mapped_path, err);
if (err) { if (err) {
log_warning("hooks::avs", "failed to create '{}': {}", mapped_path.string(), err.message()); log_warning("hooks::avs", "failed to create '{}': {}", mapped_path, err.message());
} else { } else {
// if this is the `e:\`, then create the special directories // if this is the `e:\`, then create the special directories
@@ -305,7 +305,7 @@ static int avs_fs_mount(const char *mountpoint, const char *fsroot, const char *
log_misc("hooks::avs", "source directory '{}' remapped to '{}'", log_misc("hooks::avs", "source directory '{}' remapped to '{}'",
fsroot, fsroot,
mapped_path.string()); mapped_path);
} }
new_fs_root = mapped_path.string(); new_fs_root = mapped_path.string();
+94 -15
View File
@@ -59,6 +59,11 @@ static CaptureData GRAPHICS_CAPTURE_BUFFER[GRAPHICS_CAPTURE_SCREEN_NO] {};
static std::mutex GRAPHICS_CAPTURE_BUFFER_M[GRAPHICS_CAPTURE_SCREEN_NO] {}; static std::mutex GRAPHICS_CAPTURE_BUFFER_M[GRAPHICS_CAPTURE_SCREEN_NO] {};
static std::condition_variable GRAPHICS_CAPTURE_CV[GRAPHICS_CAPTURE_SCREEN_NO] {}; static std::condition_variable GRAPHICS_CAPTURE_CV[GRAPHICS_CAPTURE_SCREEN_NO] {};
static std::optional<graphics_orientation> target_orientation_on_boot;
static UINT target_refresh_rate_on_boot = 0;
static bool monitor_settings_changed = false;
static bool monitor_layout_needs_reset = false;
// flag settings // flag settings
bool GRAPHICS_CAPTURE_CURSOR = false; bool GRAPHICS_CAPTURE_CURSOR = false;
bool GRAPHICS_LOG_HRESULT = false; bool GRAPHICS_LOG_HRESULT = false;
@@ -104,6 +109,7 @@ static decltype(SetWindowLongA) *SetWindowLongA_orig = nullptr;
static decltype(SetWindowLongW) *SetWindowLongW_orig = nullptr; static decltype(SetWindowLongW) *SetWindowLongW_orig = nullptr;
static decltype(SetWindowPos) *SetWindowPos_orig = nullptr; static decltype(SetWindowPos) *SetWindowPos_orig = nullptr;
static decltype(ShowWindow) *ShowWindow_orig = nullptr; static decltype(ShowWindow) *ShowWindow_orig = nullptr;
static decltype(SetDisplayConfig) *SetDisplayConfig_addr = nullptr;
static void reset_window_hook(HWND hWnd) { static void reset_window_hook(HWND hWnd) {
overlay::destroy(hWnd); overlay::destroy(hWnd);
@@ -232,7 +238,7 @@ static LRESULT CALLBACK WindowProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM l
switch (uMsg) { switch (uMsg) {
case WM_MOVE: case WM_MOVE:
case WM_SIZE: { case WM_SIZE:
// Update SPICETOUCH space when the main window changes size or moves. // Update SPICETOUCH space when the main window changes size or moves.
// The update happens regardless of whether the "fake" spicetouch window is present or not. // The update happens regardless of whether the "fake" spicetouch window is present or not.
// This allows touches received on subscreen window to be translated correctly. // This allows touches received on subscreen window to be translated correctly.
@@ -249,7 +255,16 @@ static LRESULT CALLBACK WindowProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM l
SPICETOUCH_TOUCH_WIDTH, SPICETOUCH_TOUCH_HEIGHT, SPICETOUCH_TOUCH_WIDTH, SPICETOUCH_TOUCH_HEIGHT,
SWP_NOZORDER | SWP_NOREDRAW | SWP_NOREPOSITION | SWP_NOACTIVATE); SWP_NOZORDER | SWP_NOREDRAW | SWP_NOREPOSITION | SWP_NOACTIVATE);
} }
} break;
case WM_ACTIVATEAPP:
if (wParam) {
// regained focus
// this *can* get called twice in a row when restoring, but update_monitor_at_runtime
// is idempotent (checks current display settings to see if changes are needed),
// so it shouldn't cause any issues
update_monitor_at_runtime();
}
break;
default: default:
break; break;
} }
@@ -476,6 +491,14 @@ static HWND WINAPI CreateWindowExW_hook(DWORD dwExStyle, LPCWSTR lpClassName, LP
} }
} }
// FTT
if (avs::game::is_model("MMD")) {
// set window name
if (!lpWindowName) {
lpWindowName = L"Future TomTom";
}
}
if (GRAPHICS_WINDOWED) { if (GRAPHICS_WINDOWED) {
graphics_window_check_bounds_before_creation(x, y, nWidth, nHeight); graphics_window_check_bounds_before_creation(x, y, nWidth, nHeight);
} }
@@ -924,6 +947,9 @@ void graphics_hook_subscreen_window(HWND hWnd) {
if (GRAPHICS_WSUB_ALWAYS_ON_TOP) { if (GRAPHICS_WSUB_ALWAYS_ON_TOP) {
graphics_update_z_order(hWnd, true); graphics_update_z_order(hWnd, true);
} }
if (GRAPHICS_WINDOW_DISABLE_ROUNDED_CORNERS) {
graphics_set_corner_preference(hWnd, true);
}
} }
void graphics_screens_register(int screen) { void graphics_screens_register(int screen) {
@@ -1131,7 +1157,7 @@ void change_primary_monitor(const std::string &monitor_name) {
const auto DisplayConfigGetDeviceInfo_addr = const auto DisplayConfigGetDeviceInfo_addr =
reinterpret_cast<decltype(DisplayConfigGetDeviceInfo) *>( reinterpret_cast<decltype(DisplayConfigGetDeviceInfo) *>(
GetProcAddress(user32, "DisplayConfigGetDeviceInfo")); GetProcAddress(user32, "DisplayConfigGetDeviceInfo"));
const auto SetDisplayConfig_addr = SetDisplayConfig_addr =
reinterpret_cast<decltype(SetDisplayConfig) *>( reinterpret_cast<decltype(SetDisplayConfig) *>(
GetProcAddress(user32, "SetDisplayConfig")); GetProcAddress(user32, "SetDisplayConfig"));
if (GetDisplayConfigBufferSizes_addr == nullptr || QueryDisplayConfig_addr == nullptr || if (GetDisplayConfigBufferSizes_addr == nullptr || QueryDisplayConfig_addr == nullptr ||
@@ -1246,11 +1272,13 @@ void change_primary_monitor(const std::string &monitor_name) {
log_fatal("graphics", "SetDisplayConfig failed, check -mainmonitor option: {}", status); log_fatal("graphics", "SetDisplayConfig failed, check -mainmonitor option: {}", status);
} }
monitor_layout_needs_reset = true;
// a little extra time for windows to settle and redraw things // a little extra time for windows to settle and redraw things
Sleep(2000); Sleep(2000);
} }
void update_monitor_on_boot(std::optional<graphics_orientation> target_orientation, UINT target_refresh_rate) { void update_monitor(bool is_boot, std::optional<graphics_orientation> target_orientation, UINT target_refresh_rate) {
// note: all of this is only being done for the primary motnior // note: all of this is only being done for the primary motnior
// get current settings // get current settings
@@ -1319,7 +1347,7 @@ void update_monitor_on_boot(std::optional<graphics_orientation> target_orientati
} }
// update refresh rate // update refresh rate
if (target_refresh_rate > 0) { if (target_refresh_rate > 0 && target_refresh_rate != dm.dmDisplayFrequency) {
log_misc("graphics", log_misc("graphics",
"current refresh rate {} => desired refresh rate {}", "current refresh rate {} => desired refresh rate {}",
dm.dmDisplayFrequency, target_refresh_rate); dm.dmDisplayFrequency, target_refresh_rate);
@@ -1331,25 +1359,76 @@ void update_monitor_on_boot(std::optional<graphics_orientation> target_orientati
if (!needs_update) { if (!needs_update) {
// nothing to do // nothing to do
log_misc("graphics", "display settings are already up to date, no changes needed");
return; return;
} }
const auto result = ChangeDisplaySettings(&dm, CDS_FULLSCREEN); const auto result = ChangeDisplaySettings(&dm, CDS_FULLSCREEN);
if (result != DISP_CHANGE_SUCCESSFUL) { if (result != DISP_CHANGE_SUCCESSFUL) {
log_fatal( if (is_boot) {
"graphics", log_fatal(
"failed to update display settings ({}px x {}px @ {}Hz): error {}, double check options", "graphics",
dm.dmPelsWidth, "failed to update display settings ({}px x {}px @ {}Hz): error {}, double check options",
dm.dmPelsHeight, dm.dmPelsWidth,
dm.dmDisplayFrequency, dm.dmPelsHeight,
result); dm.dmDisplayFrequency,
result);
} else {
log_warning(
"graphics",
"failed to update display settings ({}px x {}px @ {}Hz): error {}, double check options",
dm.dmPelsWidth,
dm.dmPelsHeight,
dm.dmDisplayFrequency,
result);
}
} else { } else {
monitor_settings_changed = true;
// sleep for a little bit after changing monitor settings to delay game launch/resume
Sleep(1000);
log_info("graphics", "display settings updated successfully ({}px x {}px @ {}Hz)", log_info("graphics", "display settings updated successfully ({}px x {}px @ {}Hz)",
dm.dmPelsWidth, dm.dmPelsWidth,
dm.dmPelsHeight, dm.dmPelsHeight,
dm.dmDisplayFrequency); dm.dmDisplayFrequency);
} }
}
// sleep for a little bit after changing monitor settings to delay game launch
Sleep(1000); void update_monitor_on_boot(std::optional<graphics_orientation> target_orientation, UINT target_refresh_rate) {
target_orientation_on_boot = target_orientation;
target_refresh_rate_on_boot = target_refresh_rate;
log_misc("graphics", "applying monitor updates at boot...");
update_monitor(true, target_orientation, target_refresh_rate);
}
void update_monitor_at_runtime() {
if (monitor_settings_changed) {
log_misc("graphics", "applying monitor updates at runtime as window regained focus...");
update_monitor(false, target_orientation_on_boot, target_refresh_rate_on_boot);
}
}
void reset_monitor_on_exit() {
// while CDS_FULLSCREEN is *supposed* to be temporary & the OS attempts to
// restore the original settings on exit, it can sometimes fail to do that;
// therefore, we try our best to clean things up on the way out
if (monitor_settings_changed) {
monitor_settings_changed = false;
log_misc("graphics", "resetting monitor settings on exit...");
ChangeDisplaySettingsW(nullptr, 0);
}
// same for this one.
if (monitor_layout_needs_reset) {
monitor_layout_needs_reset = false;
log_misc("graphics", "restoring primary monitor on exit...");
if (SetDisplayConfig_addr != nullptr) {
SetDisplayConfig_addr(
0,
nullptr,
0,
nullptr,
SDC_APPLY | SDC_USE_DATABASE_CURRENT);
}
}
} }
+8 -1
View File
@@ -7,6 +7,9 @@
#include <windows.h> #include <windows.h>
#include <d3d9.h> #include <d3d9.h>
#if !SPICE_XP
#include <dwmapi.h>
#endif
#include "external/toojpeg/toojpeg.h" #include "external/toojpeg/toojpeg.h"
@@ -50,6 +53,7 @@ extern std::optional<std::pair<uint32_t, uint32_t>> GRAPHICS_WINDOW_SIZE;
extern std::optional<std::string> GRAPHICS_WINDOW_POS; extern std::optional<std::string> GRAPHICS_WINDOW_POS;
extern bool GRAPHICS_WINDOW_ALWAYS_ON_TOP; extern bool GRAPHICS_WINDOW_ALWAYS_ON_TOP;
extern bool GRAPHICS_WINDOW_BACKBUFFER_SCALE; extern bool GRAPHICS_WINDOW_BACKBUFFER_SCALE;
extern bool GRAPHICS_WINDOW_DISABLE_ROUNDED_CORNERS;
extern std::optional<HWND> GRAPHICS_HOOKED_WINDOW; extern std::optional<HWND> GRAPHICS_HOOKED_WINDOW;
extern bool GRAPHICS_IIDX_WSUB; extern bool GRAPHICS_IIDX_WSUB;
@@ -103,6 +107,7 @@ void graphics_windowed_wndproc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lPara
void graphics_capture_initial_window(HWND hWnd); void graphics_capture_initial_window(HWND hWnd);
void graphics_update_window_style(HWND hWnd, bool force_update=false); void graphics_update_window_style(HWND hWnd, bool force_update=false);
void graphics_update_z_order(HWND hWnd, bool always_on_top); void graphics_update_z_order(HWND hWnd, bool always_on_top);
void graphics_set_corner_preference(HWND hWnd, bool disable);
void graphics_move_resize_window(HWND hWnd); void graphics_move_resize_window(HWND hWnd);
bool graphics_window_options_breaks_game(); bool graphics_window_options_breaks_game();
bool graphics_window_decoration_change_crashes_game(); bool graphics_window_decoration_change_crashes_game();
@@ -112,4 +117,6 @@ void graphics_load_windowed_subscreen_parameters();
void graphics_window_check_bounds_before_creation(int &x, int &y, const int width, const int height); void graphics_window_check_bounds_before_creation(int &x, int &y, const int width, const int height);
void change_primary_monitor(const std::string &monitor_name); void change_primary_monitor(const std::string &monitor_name);
void update_monitor_on_boot(std::optional<graphics_orientation> target_orientation, UINT target_refresh_rate); void update_monitor_on_boot(std::optional<graphics_orientation> target_orientation, UINT target_refresh_rate);
void update_monitor_at_runtime();
void reset_monitor_on_exit();
@@ -24,6 +24,7 @@ std::optional<std::pair<uint32_t, uint32_t>> GRAPHICS_WINDOW_SIZE;
std::optional<std::string> GRAPHICS_WINDOW_POS; std::optional<std::string> GRAPHICS_WINDOW_POS;
bool GRAPHICS_WINDOW_ALWAYS_ON_TOP = false; bool GRAPHICS_WINDOW_ALWAYS_ON_TOP = false;
bool GRAPHICS_WINDOW_BACKBUFFER_SCALE = false; bool GRAPHICS_WINDOW_BACKBUFFER_SCALE = false;
bool GRAPHICS_WINDOW_DISABLE_ROUNDED_CORNERS = false;
std::optional<HWND> GRAPHICS_HOOKED_WINDOW; std::optional<HWND> GRAPHICS_HOOKED_WINDOW;
// IIDX Windowed Subscreen - starts out as false, enabled by IIDX module on pre-attach as needed // IIDX Windowed Subscreen - starts out as false, enabled by IIDX module on pre-attach as needed
@@ -132,6 +133,10 @@ void graphics_capture_initial_window(HWND hWnd) {
log_info("graphics-windowed", "[{}] change window z-order - always on top", fmt::ptr(hWnd)); log_info("graphics-windowed", "[{}] change window z-order - always on top", fmt::ptr(hWnd));
graphics_update_z_order(hWnd, true); graphics_update_z_order(hWnd, true);
} }
if (cfg::SCREENRESIZE->window_disable_round_corners) {
log_info("graphics-windowed", "[{}] disabling rounded corners", fmt::ptr(hWnd));
graphics_set_corner_preference(hWnd, true);
}
// ensure spicetouch coordinates are initialized // ensure spicetouch coordinates are initialized
update_spicetouch_window_dimensions(hWnd); update_spicetouch_window_dimensions(hWnd);
@@ -181,6 +186,10 @@ void graphics_load_windowed_parameters() {
cfg::SCREENRESIZE->window_always_on_top = true; cfg::SCREENRESIZE->window_always_on_top = true;
} }
if (GRAPHICS_WINDOW_DISABLE_ROUNDED_CORNERS) {
cfg::SCREENRESIZE->window_disable_round_corners = true;
}
log_debug("graphics-windowed", "graphics_load_windowed_parameters returned"); log_debug("graphics-windowed", "graphics_load_windowed_parameters returned");
} }
@@ -469,6 +478,27 @@ void graphics_update_z_order(HWND hWnd, bool always_on_top) {
log_debug("graphics-windowed", "graphics_update_z_order returned"); log_debug("graphics-windowed", "graphics_update_z_order returned");
} }
void graphics_set_corner_preference(HWND hWnd, bool disable) {
if (!GRAPHICS_WINDOWED) {
return;
}
#if !SPICE_XP
log_debug("graphics-windowed", "graphics_set_corner_preference called");
DWM_WINDOW_CORNER_PREFERENCE corner_preference = disable ? DWMWCP_DONOTROUND : DWMWCP_DEFAULT;
DwmSetWindowAttribute(
hWnd,
DWMWA_WINDOW_CORNER_PREFERENCE,
&corner_preference, sizeof(corner_preference));
log_debug("graphics-windowed", "graphics_set_corner_preference returned");
#else
// don't link against DwmSetWindowAttribute on XP
log_debug("graphics-windowed", "graphics_set_corner_preference called; ignoring");
#endif
}
void graphics_move_resize_window(HWND hWnd) { void graphics_move_resize_window(HWND hWnd) {
if (!GRAPHICS_WINDOWED) { if (!GRAPHICS_WINDOWED) {
return; return;
@@ -502,8 +532,8 @@ bool graphics_window_decoration_change_crashes_game() {
static std::once_flag flag; static std::once_flag flag;
static bool result = false; static bool result = false;
std::call_once(flag, []() { std::call_once(flag, []() {
// ddr crashes when frame style changes // ddr and ftt crash when frame style changes
if (avs::game::is_model("MDX")) { if (avs::game::is_model("MDX") || avs::game::is_model("MMD")) {
result = true; result = true;
} }
+13 -5
View File
@@ -518,6 +518,12 @@ int main_implementation(int argc, char *argv[]) {
if (options[launcher::Options::IIDXTDJMode].value_bool()) { if (options[launcher::Options::IIDXTDJMode].value_bool()) {
games::iidx::TDJ_MODE = true; games::iidx::TDJ_MODE = true;
} }
if (options[launcher::Options::IIDXTTDelayP1].is_active()) {
games::iidx::TT_DELAY_P1 = options[launcher::Options::IIDXTTDelayP1].value_uint32();
}
if (options[launcher::Options::IIDXTTDelayP2].is_active()) {
games::iidx::TT_DELAY_P2 = options[launcher::Options::IIDXTTDelayP2].value_uint32();
}
if (options[launcher::Options::spice2x_IIDXDigitalTTSensitivity].is_active()) { if (options[launcher::Options::spice2x_IIDXDigitalTTSensitivity].is_active()) {
games::iidx::DIGITAL_TT_SENS = (uint8_t) games::iidx::DIGITAL_TT_SENS = (uint8_t)
options[launcher::Options::spice2x_IIDXDigitalTTSensitivity].value_uint32(); options[launcher::Options::spice2x_IIDXDigitalTTSensitivity].value_uint32();
@@ -597,6 +603,9 @@ int main_implementation(int argc, char *argv[]) {
if (options[launcher::Options::PopnSubMonitorOverride].is_active()) { if (options[launcher::Options::PopnSubMonitorOverride].is_active()) {
sysutils::SECOND_MONITOR_OVERRIDE = options[launcher::Options::PopnSubMonitorOverride].value_text(); sysutils::SECOND_MONITOR_OVERRIDE = options[launcher::Options::PopnSubMonitorOverride].value_text();
} }
if (options[launcher::Options::PopnNativeTouch].value_bool()) {
games::popn::NATIVE_TOUCH = true;
}
if (options[launcher::Options::LoadMetalGearArcadeModule].value_bool()) { if (options[launcher::Options::LoadMetalGearArcadeModule].value_bool()) {
attach_mga = true; attach_mga = true;
} }
@@ -1117,6 +1126,7 @@ int main_implementation(int argc, char *argv[]) {
} }
GRAPHICS_WINDOW_ALWAYS_ON_TOP = options[launcher::Options::spice2x_WindowAlwaysOnTop].value_bool(); GRAPHICS_WINDOW_ALWAYS_ON_TOP = options[launcher::Options::spice2x_WindowAlwaysOnTop].value_bool();
GRAPHICS_WINDOW_BACKBUFFER_SCALE = options[launcher::Options::WindowForceScaling].value_bool(); GRAPHICS_WINDOW_BACKBUFFER_SCALE = options[launcher::Options::WindowForceScaling].value_bool();
GRAPHICS_WINDOW_DISABLE_ROUNDED_CORNERS = options[launcher::Options::WindowDisableRoundedCorners].value_bool();
// IIDX/SDVX Windowed Subscreen // IIDX/SDVX Windowed Subscreen
if (options[launcher::Options::spice2x_IIDXWindowedSubscreenSize].is_active()) { if (options[launcher::Options::spice2x_IIDXWindowedSubscreenSize].is_active()) {
@@ -1831,9 +1841,7 @@ int main_implementation(int argc, char *argv[]) {
attach_ftt = true; attach_ftt = true;
// the game is windowed by default unless we set the env // the game is windowed by default unless we set the env
if (GRAPHICS_WINDOWED) { if (!GRAPHICS_WINDOWED) {
GRAPHICS_WINDOWED = false;
} else {
SetEnvironmentVariable("DAMAC_VIEWER_FULLSCREEN", "0"); SetEnvironmentVariable("DAMAC_VIEWER_FULLSCREEN", "0");
} }
@@ -2362,9 +2370,9 @@ int main_implementation(int argc, char *argv[]) {
// layeredfs // layeredfs
if (fileutils::dir_exists("data_mods") && if (fileutils::dir_exists("data_mods") &&
GetModuleHandleA("ifs_hook.dll") == nullptr) { GetModuleHandleA("ifs_hook.dll") == nullptr) {
log_warning("launcher", "data_mods directory found, but ifs_hook.dll is not present; mods will not load"); log_warning("launcher", "data_mods directory found, but ifs_hook.dll is not loaded; mods will not load");
deferredlogs::defer_error_messages({ deferredlogs::defer_error_messages({
"data_mods directory was found, but ifs_hook.dll is not present", "data_mods directory was found, but ifs_hook.dll is not loaded",
" your mods will not load", " your mods will not load",
" to fix this, download ifs_layeredfs and add it as a DLL hook (-k)", " to fix this, download ifs_layeredfs and add it as a DLL hook (-k)",
" https://github.com/mon/ifs_layeredfs" " https://github.com/mon/ifs_layeredfs"
+71 -26
View File
@@ -32,6 +32,7 @@ static const std::vector<std::string> CATEGORY_ORDER_BASIC = {
static const std::vector<std::string> CATEGORY_ORDER_ADVANCED = { static const std::vector<std::string> CATEGORY_ORDER_ADVANCED = {
"Game Options (Advanced)", "Game Options (Advanced)",
"Game Options (Peripherals)",
"Overlay", "Overlay",
"Network (Advanced)", "Network (Advanced)",
"Performance", "Performance",
@@ -513,7 +514,7 @@ static const std::vector<OptionDefinition> OPTION_DEFINITIONS = {
.desc = "Flip the camera order.", .desc = "Flip the camera order.",
.type = OptionType::Bool, .type = OptionType::Bool,
.game_name = "Beatmania IIDX", .game_name = "Beatmania IIDX",
.category = "Game Options (Advanced)", .category = "Game Options (Peripherals)",
}, },
{ {
// IIDXDisableCameras // IIDXDisableCameras
@@ -523,7 +524,7 @@ static const std::vector<OptionDefinition> OPTION_DEFINITIONS = {
.type = OptionType::Bool, .type = OptionType::Bool,
.hidden = true, .hidden = true,
.game_name = "Beatmania IIDX", .game_name = "Beatmania IIDX",
.category = "Game Options", .category = "Game Options (Peripherals)",
}, },
{ {
// IIDXCabCamAccess // IIDXCabCamAccess
@@ -533,7 +534,7 @@ static const std::vector<OptionDefinition> OPTION_DEFINITIONS = {
"Only turn this on if you have OFFICIAL arcade cameras connected to the correct USB ports. Default: auto.", "Only turn this on if you have OFFICIAL arcade cameras connected to the correct USB ports. Default: auto.",
.type = OptionType::Enum, .type = OptionType::Enum,
.game_name = "Beatmania IIDX", .game_name = "Beatmania IIDX",
.category = "Game Options (Advanced)", .category = "Game Options (Peripherals)",
.elements = { .elements = {
{"auto", ""}, {"auto", ""},
{"off", ""}, {"off", ""},
@@ -549,7 +550,7 @@ static const std::vector<OptionDefinition> OPTION_DEFINITIONS = {
.desc = "Experimental camera support for IIDX 27+ via texture hooking. Requires camera(s) that support YUY2 or NV12 encoding.", .desc = "Experimental camera support for IIDX 27+ via texture hooking. Requires camera(s) that support YUY2 or NV12 encoding.",
.type = OptionType::Bool, .type = OptionType::Bool,
.game_name = "Beatmania IIDX", .game_name = "Beatmania IIDX",
.category = "Game Options (Advanced)", .category = "Game Options (Peripherals)",
}, },
{ {
// IIDXCamHookRatio // IIDXCamHookRatio
@@ -561,7 +562,7 @@ static const std::vector<OptionDefinition> OPTION_DEFINITIONS = {
.type = OptionType::Enum, .type = OptionType::Enum,
.hidden = true, .hidden = true,
.game_name = "Beatmania IIDX", .game_name = "Beatmania IIDX",
.category = "Game Options (Advanced)", .category = "Game Options (Peripherals)",
.elements = { .elements = {
{"43", "4:3"}, {"43", "4:3"},
{"169", "16:9"}, {"169", "16:9"},
@@ -579,7 +580,7 @@ static const std::vector<OptionDefinition> OPTION_DEFINITIONS = {
.type = OptionType::Text, .type = OptionType::Text,
.setting_name = "0x5817a0,0x6fffbd8,0xbbae40,0xe0,0x30", .setting_name = "0x5817a0,0x6fffbd8,0xbbae40,0xe0,0x30",
.game_name = "Beatmania IIDX", .game_name = "Beatmania IIDX",
.category = "Game Options (Advanced)", .category = "Game Options (Peripherals)",
}, },
{ {
// IIDXCamHookTopId // IIDXCamHookTopId
@@ -594,7 +595,7 @@ static const std::vector<OptionDefinition> OPTION_DEFINITIONS = {
.type = OptionType::Text, .type = OptionType::Text,
.setting_name = "vid_1234&pid_5678", .setting_name = "vid_1234&pid_5678",
.game_name = "Beatmania IIDX", .game_name = "Beatmania IIDX",
.category = "Game Options (Advanced)", .category = "Game Options (Peripherals)",
}, },
{ {
// IIDXCamHookFrontId // IIDXCamHookFrontId
@@ -609,7 +610,7 @@ static const std::vector<OptionDefinition> OPTION_DEFINITIONS = {
.type = OptionType::Text, .type = OptionType::Text,
.setting_name = "vid_90ab&pid_cdef", .setting_name = "vid_90ab&pid_cdef",
.game_name = "Beatmania IIDX", .game_name = "Beatmania IIDX",
.category = "Game Options (Advanced)", .category = "Game Options (Peripherals)",
}, },
{ {
.title = "IIDX Sound Output Device", .title = "IIDX Sound Output Device",
@@ -654,6 +655,30 @@ static const std::vector<OptionDefinition> OPTION_DEFINITIONS = {
.game_name = "Beatmania IIDX", .game_name = "Beatmania IIDX",
.category = "Game Options", .category = "Game Options",
}, },
{
// IIDXTTDelayP1
.title = "IIDX TT Delay ms (Player 1)",
.name = "iidxttdelayp1",
.desc = "Delays turntable by number of milliseconds. "
"Delay will be *at most* the specified period; maximum error is dependent on the game's poll rate. "
"As usual, changing any option requires a restart. Default: 0 (no delay).",
.type = OptionType::Integer,
.setting_name = "(0-500)",
.game_name = "Beatmania IIDX",
.category = "Game Options (Advanced)",
},
{
// IIDXTTDelayP2
.title = "IIDX TT Delay ms (Player 2)",
.name = "iidxttdelayp2",
.desc = "Delays turntable by number of milliseconds. "
"Delay will be *at most* the specified period; maximum error is dependent on the game's poll rate. "
"As usual, changing any option requires a restart. Default: 0 (no delay).",
.type = OptionType::Integer,
.setting_name = "(0-500)",
.game_name = "Beatmania IIDX",
.category = "Game Options (Advanced)",
},
{ {
// spice2x_IIDXDigitalTTSensitivity // spice2x_IIDXDigitalTTSensitivity
.title = "IIDX Digital TT Sensitivity", .title = "IIDX Digital TT Sensitivity",
@@ -772,11 +797,11 @@ static const std::vector<OptionDefinition> OPTION_DEFINITIONS = {
.category = "Game Options (Advanced)", .category = "Game Options (Advanced)",
}, },
{ {
.title = "SDVX Force 720p", .title = "SDVX Vivid Wave Force 720p Window (DEPRECATED - use -windowsize instead)",
.name = "sdvx720", .name = "sdvx720",
.desc = "Force Sound Voltex 720p display mode, used by older games. " .desc = "Old & deprecated option for launching Vivid Wave in 720p when using windowed mode.",
"For newer games, use window resize function instead.",
.type = OptionType::Bool, .type = OptionType::Bool,
.hidden = true,
.game_name = "Sound Voltex", .game_name = "Sound Voltex",
.category = "Game Options (Advanced)", .category = "Game Options (Advanced)",
}, },
@@ -786,7 +811,7 @@ static const std::vector<OptionDefinition> OPTION_DEFINITIONS = {
.desc = "Enable Sound Voltex printer emulation.", .desc = "Enable Sound Voltex printer emulation.",
.type = OptionType::Bool, .type = OptionType::Bool,
.game_name = "Sound Voltex", .game_name = "Sound Voltex",
.category = "Game Options (Advanced)", .category = "Game Options (Peripherals)",
}, },
{ {
// SDVXPrinterOutputPath // SDVXPrinterOutputPath
@@ -795,7 +820,7 @@ static const std::vector<OptionDefinition> OPTION_DEFINITIONS = {
.desc = "Path to folder where images will be stored.", .desc = "Path to folder where images will be stored.",
.type = OptionType::Text, .type = OptionType::Text,
.game_name = "Sound Voltex", .game_name = "Sound Voltex",
.category = "Game Options (Advanced)", .category = "Game Options (Peripherals)",
.picker = OptionPickerType::DirectoryPath, .picker = OptionPickerType::DirectoryPath,
}, },
{ {
@@ -804,7 +829,7 @@ static const std::vector<OptionDefinition> OPTION_DEFINITIONS = {
.desc = "Clean up saved images in the output directory on startup.", .desc = "Clean up saved images in the output directory on startup.",
.type = OptionType::Bool, .type = OptionType::Bool,
.game_name = "Sound Voltex", .game_name = "Sound Voltex",
.category = "Game Options (Advanced)", .category = "Game Options (Peripherals)",
}, },
{ {
.title = "SDVX Printer Output Overwrite", .title = "SDVX Printer Output Overwrite",
@@ -812,7 +837,7 @@ static const std::vector<OptionDefinition> OPTION_DEFINITIONS = {
.desc = "Always overwrite the same file in output directory.", .desc = "Always overwrite the same file in output directory.",
.type = OptionType::Bool, .type = OptionType::Bool,
.game_name = "Sound Voltex", .game_name = "Sound Voltex",
.category = "Game Options (Advanced)", .category = "Game Options (Peripherals)",
}, },
{ {
// SDVXPrinterOutputFormat // SDVXPrinterOutputFormat
@@ -822,7 +847,7 @@ static const std::vector<OptionDefinition> OPTION_DEFINITIONS = {
.type = OptionType::Text, .type = OptionType::Text,
.setting_name = "(png/bmp/tga/jpg)", .setting_name = "(png/bmp/tga/jpg)",
.game_name = "Sound Voltex", .game_name = "Sound Voltex",
.category = "Game Options (Advanced)", .category = "Game Options (Peripherals)",
}, },
{ {
.title = "SDVX Printer JPG Quality", .title = "SDVX Printer JPG Quality",
@@ -831,7 +856,7 @@ static const std::vector<OptionDefinition> OPTION_DEFINITIONS = {
.type = OptionType::Integer, .type = OptionType::Integer,
.setting_name = "(0-100)", .setting_name = "(0-100)",
.game_name = "Sound Voltex", .game_name = "Sound Voltex",
.category = "Game Options (Advanced)", .category = "Game Options (Peripherals)",
}, },
{ {
.title = "SDVX Disable Cameras (DEPRECATED - no longer needed)", .title = "SDVX Disable Cameras (DEPRECATED - no longer needed)",
@@ -842,7 +867,7 @@ static const std::vector<OptionDefinition> OPTION_DEFINITIONS = {
.type = OptionType::Bool, .type = OptionType::Bool,
.hidden = true, .hidden = true,
.game_name = "Sound Voltex", .game_name = "Sound Voltex",
.category = "Game Options", .category = "Game Options (Peripherals)",
}, },
{ {
.title = "SDVX FS Subscreen Native Touch Handling", .title = "SDVX FS Subscreen Native Touch Handling",
@@ -1008,6 +1033,18 @@ static const std::vector<OptionDefinition> OPTION_DEFINITIONS = {
.category = "Monitor", .category = "Monitor",
.picker = OptionPickerType::Monitor, .picker = OptionPickerType::Monitor,
}, },
{
// PopnNativeTouch
.title = "Pop'n Music PikaPika Native Touch Handling",
.name = "popnnativetouch",
.desc = "Disables touch hooks and lets the game access a touch screen directly. "
"Requires a touch screen to be connected as a secondary monitor. "
"Touch input must be routed to the primary screen via Windows Tablet PC settings. "
"Enable this when you get duplicate touch inputs from an actual touch screen.",
.type = OptionType::Bool,
.game_name = "Pop'n Music",
.category = "Game Options (Advanced)",
},
{ {
.title = "Force Load HELLO! Pop'n Music Module", .title = "Force Load HELLO! Pop'n Music Module",
.name = "hpm", .name = "hpm",
@@ -2218,6 +2255,14 @@ static const std::vector<OptionDefinition> OPTION_DEFINITIONS = {
.type = OptionType::Bool, .type = OptionType::Bool,
.category = "Graphics (Windowed)", .category = "Graphics (Windowed)",
}, },
{
// WindowDisableRoundedCorners
.title = "Disable Round Window Corners",
.name = "windownoroundcorners",
.desc = "Windows 11 and above only: Disables rounded corners on the game window(s).",
.type = OptionType::Bool,
.category = "Graphics (Windowed)",
},
{ {
// spice2x_IIDXWindowedSubscreenSize // spice2x_IIDXWindowedSubscreenSize
.title = "IIDX Windowed Subscreen Size", .title = "IIDX Windowed Subscreen Size",
@@ -2347,7 +2392,7 @@ static const std::vector<OptionDefinition> OPTION_DEFINITIONS = {
"Note: this is NOT for the motion camera - you still need a real RealSense camera for that!", "Note: this is NOT for the motion camera - you still need a real RealSense camera for that!",
.type = OptionType::Bool, .type = OptionType::Bool,
.game_name = "DANCERUSH", .game_name = "DANCERUSH",
.category = "Game Options", .category = "Game Options (Peripherals)",
}, },
{ {
// spice2x_IIDXNativeTouch // spice2x_IIDXNativeTouch
@@ -2681,7 +2726,7 @@ static const std::vector<OptionDefinition> OPTION_DEFINITIONS = {
.desc = "Allow game to access camera; camera must be compatible with game.", .desc = "Allow game to access camera; camera must be compatible with game.",
.type = OptionType::Bool, .type = OptionType::Bool,
.game_name = "LovePlus", .game_name = "LovePlus",
.category = "Game Options", .category = "Game Options (Peripherals)",
}, },
{ {
// LovePlusPrinterOutputPath // LovePlusPrinterOutputPath
@@ -2690,7 +2735,7 @@ static const std::vector<OptionDefinition> OPTION_DEFINITIONS = {
.desc = "Path to folder where images will be stored.", .desc = "Path to folder where images will be stored.",
.type = OptionType::Text, .type = OptionType::Text,
.game_name = "LovePlus", .game_name = "LovePlus",
.category = "Game Options (Advanced)", .category = "Game Options (Peripherals)",
.picker = OptionPickerType::DirectoryPath, .picker = OptionPickerType::DirectoryPath,
}, },
{ {
@@ -2699,7 +2744,7 @@ static const std::vector<OptionDefinition> OPTION_DEFINITIONS = {
.desc = "Clean up saved images in the output directory on startup.", .desc = "Clean up saved images in the output directory on startup.",
.type = OptionType::Bool, .type = OptionType::Bool,
.game_name = "LovePlus", .game_name = "LovePlus",
.category = "Game Options (Advanced)", .category = "Game Options (Peripherals)",
}, },
{ {
.title = "LovePlus Printer Output Overwrite", .title = "LovePlus Printer Output Overwrite",
@@ -2707,7 +2752,7 @@ static const std::vector<OptionDefinition> OPTION_DEFINITIONS = {
.desc = "Always overwrite the same file in output directory.", .desc = "Always overwrite the same file in output directory.",
.type = OptionType::Bool, .type = OptionType::Bool,
.game_name = "LovePlus", .game_name = "LovePlus",
.category = "Game Options (Advanced)", .category = "Game Options (Peripherals)",
}, },
{ {
// LovePlusPrinterOutputFormat // LovePlusPrinterOutputFormat
@@ -2717,7 +2762,7 @@ static const std::vector<OptionDefinition> OPTION_DEFINITIONS = {
.type = OptionType::Text, .type = OptionType::Text,
.setting_name = "(png/bmp/tga/jpg)", .setting_name = "(png/bmp/tga/jpg)",
.game_name = "LovePlus", .game_name = "LovePlus",
.category = "Game Options (Advanced)", .category = "Game Options (Peripherals)",
}, },
{ {
.title = "LovePlus Printer JPG Quality", .title = "LovePlus Printer JPG Quality",
@@ -2726,7 +2771,7 @@ static const std::vector<OptionDefinition> OPTION_DEFINITIONS = {
.type = OptionType::Integer, .type = OptionType::Integer,
.setting_name = "(0-100)", .setting_name = "(0-100)",
.game_name = "LovePlus", .game_name = "LovePlus",
.category = "Game Options (Advanced)", .category = "Game Options (Peripherals)",
}, },
{ {
// OptionConflictResolution // OptionConflictResolution
@@ -2747,7 +2792,7 @@ static const std::vector<OptionDefinition> OPTION_DEFINITIONS = {
"option to bypass camera error during boot, allowing you to try out the game.", "option to bypass camera error during boot, allowing you to try out the game.",
.type = OptionType::Bool, .type = OptionType::Bool,
.game_name = "Otoca D'or", .game_name = "Otoca D'or",
.category = "Game Options", .category = "Game Options (Peripherals)",
} }
}; };
+4
View File
@@ -69,6 +69,8 @@ namespace launcher {
IIDXAsioDriver, IIDXAsioDriver,
IIDXBIO2FW, IIDXBIO2FW,
IIDXTDJMode, IIDXTDJMode,
IIDXTTDelayP1,
IIDXTTDelayP2,
spice2x_IIDXDigitalTTSensitivity, spice2x_IIDXDigitalTTSensitivity,
IIDXDigitalTTSocd, IIDXDigitalTTSocd,
spice2x_IIDXLDJForce720p, spice2x_IIDXLDJForce720p,
@@ -101,6 +103,7 @@ namespace launcher {
PopnMusicForceSDMode, PopnMusicForceSDMode,
PopnNoSub, PopnNoSub,
PopnSubMonitorOverride, PopnSubMonitorOverride,
PopnNativeTouch,
LoadHelloPopnMusicModule, LoadHelloPopnMusicModule,
LoadGitaDoraModule, LoadGitaDoraModule,
GitaDoraTwoChannelAudio, GitaDoraTwoChannelAudio,
@@ -232,6 +235,7 @@ namespace launcher {
spice2x_WindowPosition, spice2x_WindowPosition,
spice2x_WindowAlwaysOnTop, spice2x_WindowAlwaysOnTop,
WindowForceScaling, WindowForceScaling,
WindowDisableRoundedCorners,
spice2x_IIDXWindowedSubscreenSize, spice2x_IIDXWindowedSubscreenSize,
spice2x_IIDXWindowedSubscreenPosition, spice2x_IIDXWindowedSubscreenPosition,
IIDXWindowedSubscreenBorderless, IIDXWindowedSubscreenBorderless,
+7 -4
View File
@@ -7,6 +7,7 @@
#include "easrv/easrv.h" #include "easrv/easrv.h"
#include "rawinput/rawinput.h" #include "rawinput/rawinput.h"
#include "hooks/audio/audio.h" #include "hooks/audio/audio.h"
#include "hooks/graphics/graphics.h"
#include "util/deferlog.h" #include "util/deferlog.h"
#include "util/logging.h" #include "util/logging.h"
@@ -22,6 +23,9 @@ namespace launcher {
// therefore, subsystems need to be guarded against multiple unload attempts // therefore, subsystems need to be guarded against multiple unload attempts
log_info("launcher", "stopping subsystems"); log_info("launcher", "stopping subsystems");
// reset monitor settings
reset_monitor_on_exit();
// before shutting down logger, dump any deferred log messages // before shutting down logger, dump any deferred log messages
deferredlogs::dump_to_logger(); deferredlogs::dump_to_logger();
@@ -86,13 +90,12 @@ namespace launcher {
if (LAUNCHER_ARGC > 0) { if (LAUNCHER_ARGC > 0) {
// build cmd line // build cmd line
std::stringstream cmd_line; std::string cmd_line = "START \"\" ";
cmd_line << "START \"\" ";
for (int i = 0; i < LAUNCHER_ARGC; i++) for (int i = 0; i < LAUNCHER_ARGC; i++)
cmd_line << " \"" << LAUNCHER_ARGV[i] << "\""; cmd_line += " \"" + std::string(LAUNCHER_ARGV[i]) + "\"";
// run command // run command
system(cmd_line.str().c_str()); system(cmd_line.c_str());
} }
} }
+6 -6
View File
@@ -87,7 +87,7 @@ bool eamuse_get_card(const std::filesystem::path &path, uint8_t *card, int index
std::unique_lock<std::mutex> lock(CARD_OVERRIDES_LOCK); std::unique_lock<std::mutex> lock(CARD_OVERRIDES_LOCK);
const auto card_override = CARD_OVERRIDES[index]; const auto card_override = CARD_OVERRIDES[index];
lock.unlock(); lock.unlock();
// Check if card overrides are present // Check if card overrides are present
if (!card_override.empty()) { if (!card_override.empty()) {
@@ -131,7 +131,7 @@ bool eamuse_get_card_from_file(const std::filesystem::path &path, uint8_t *card,
// open file // open file
std::ifstream f(path); std::ifstream f(path);
if (!f) { if (!f) {
log_warning("eamuse", "{} can not be opened!", path.string()); log_warning("eamuse", "{} can not be opened!", path);
return false; return false;
} }
@@ -142,7 +142,7 @@ bool eamuse_get_card_from_file(const std::filesystem::path &path, uint8_t *card,
// check size // check size
if (length < 16) { if (length < 16) {
log_warning("eamuse", "{} is too small (must be at least 16 characters)", path.string()); log_warning("eamuse", "{} is too small (must be at least 16 characters)", path);
return false; return false;
} }
@@ -160,14 +160,14 @@ bool eamuse_get_card_from_file(const std::filesystem::path &path, uint8_t *card,
if (!digit && !character_big && !character_small) { if (!digit && !character_big && !character_small) {
log_warning("eamuse", log_warning("eamuse",
"{} contains an invalid character sequence at byte {} (16 characters, 0-9/A-F only)", "{} contains an invalid character sequence at byte {} (16 characters, 0-9/A-F only)",
path.string(), n); path, n);
return false; return false;
} }
} }
// info // info
log_info("eamuse", "[P{}] Inserted {}: {}", index+1, path.string(), buffer); log_info("eamuse", "[P{}] Inserted {}: {}", index+1, path, buffer);
// convert hex to bytes // convert hex to bytes
hex2bin(buffer, card); hex2bin(buffer, card);
@@ -454,7 +454,7 @@ uint16_t eamuse_get_keypad_state(size_t unit) {
if (unit >= std::size(KEYPAD_STATE)) { if (unit >= std::size(KEYPAD_STATE)) {
return 0; return 0;
} }
// reset // reset
KEYPAD_STATE[unit] = KEYPAD_STATE_OVERRIDES[unit]; KEYPAD_STATE[unit] = KEYPAD_STATE_OVERRIDES[unit];
KEYPAD_STATE[unit] |= KEYPAD_STATE_OVERRIDES_BT5[unit]; KEYPAD_STATE[unit] |= KEYPAD_STATE_OVERRIDES_BT5[unit];
+33 -25
View File
@@ -373,7 +373,7 @@ void ImGui_ImplSpice_NewFrame() {
// read new keys state // read new keys state
static long mouse_wheel_last = 0; static long mouse_wheel_last = 0;
long mouse_wheel = 0; long mouse_wheel = 0;
if (RI_MGR != nullptr && accept_new_input) { if (RI_MGR != nullptr) {
auto devices = RI_MGR->devices_get(); auto devices = RI_MGR->devices_get();
for (auto &device : devices) { for (auto &device : devices) {
switch (device.type) { switch (device.type) {
@@ -381,40 +381,46 @@ void ImGui_ImplSpice_NewFrame() {
auto &mouse = device.mouseInfo; auto &mouse = device.mouseInfo;
// mouse button triggers // mouse button triggers
if (GetSystemMetrics(SM_SWAPBUTTON)) { if (accept_new_input) {
if (mouse->key_states[rawinput::MOUSEBTN_RIGHT]) { if (GetSystemMetrics(SM_SWAPBUTTON)) {
g_MouseDown[ImGuiMouseButton_Left] = true; if (mouse->key_states[rawinput::MOUSEBTN_RIGHT]) {
g_MouseDown[ImGuiMouseButton_Left] = true;
}
if (mouse->key_states[rawinput::MOUSEBTN_LEFT]) {
g_MouseDown[ImGuiMouseButton_Right] = true;
}
} else {
if (mouse->key_states[rawinput::MOUSEBTN_LEFT]) {
g_MouseDown[ImGuiMouseButton_Left] = true;
}
if (mouse->key_states[rawinput::MOUSEBTN_RIGHT]) {
g_MouseDown[ImGuiMouseButton_Right] = true;
}
} }
if (mouse->key_states[rawinput::MOUSEBTN_LEFT]) { if (mouse->key_states[rawinput::MOUSEBTN_MIDDLE]) {
g_MouseDown[ImGuiMouseButton_Right] = true; g_MouseDown[ImGuiMouseButton_Middle] = true;
} }
} else {
if (mouse->key_states[rawinput::MOUSEBTN_LEFT]) {
g_MouseDown[ImGuiMouseButton_Left] = true;
}
if (mouse->key_states[rawinput::MOUSEBTN_RIGHT]) {
g_MouseDown[ImGuiMouseButton_Right] = true;
}
}
if (mouse->key_states[rawinput::MOUSEBTN_MIDDLE]) {
g_MouseDown[ImGuiMouseButton_Middle] = true;
} }
// final mouse wheel value should be all devices combined // final mouse wheel value should be all devices combined
// need to continuously calculate mouse wheel position even when not in focus
// to avoid values jumping (since mouse wheel values are absolute not relative)
mouse_wheel += mouse->pos_wheel; mouse_wheel += mouse->pos_wheel;
break; break;
} }
case rawinput::KEYBOARD: { case rawinput::KEYBOARD: {
if (accept_new_input) {
// iterate all virtual key codes // iterate all virtual key codes
for (size_t vKey = 0; vKey < VKEY_MAX; vKey++) { for (size_t vKey = 0; vKey < VKEY_MAX; vKey++) {
// get state (combined from all pages) // get state (combined from all pages)
auto &key_states = device.keyboardInfo->key_states; auto &key_states = device.keyboardInfo->key_states;
for (size_t page_index = 0; page_index < 1024; page_index += 256) { for (size_t page_index = 0; page_index < 1024; page_index += 256) {
g_KeysDown[vKey] |= key_states[page_index + vKey]; g_KeysDown[vKey] |= key_states[page_index + vKey];
}
} }
} }
break; break;
} }
default: default:
@@ -464,9 +470,11 @@ void ImGui_ImplSpice_NewFrame() {
} }
// set mouse wheel // set mouse wheel
auto wheel_diff = mouse_wheel - mouse_wheel_last; long wheel_diff = mouse_wheel - mouse_wheel_last;
mouse_wheel_last = mouse_wheel; mouse_wheel_last = mouse_wheel;
io.AddMouseWheelEvent(0, wheel_diff); if (wheel_diff != 0 && accept_new_input) {
io.AddMouseWheelEvent(0, wheel_diff);
}
// update OS mouse position // update OS mouse position
const auto old_mouse_pos = io.MousePos; const auto old_mouse_pos = io.MousePos;
+2 -2
View File
@@ -733,13 +733,13 @@ void overlay::SpiceOverlay::add_font(const char* font, ImFontConfig* config, con
full_path += font; full_path += font;
if (fileutils::file_exists(full_path)) { if (fileutils::file_exists(full_path)) {
log_misc("overlay", "loading font: {}", full_path.string()); log_misc("overlay", "loading font: {}", full_path);
ImGui::GetIO().Fonts->AddFontFromFileTTF( ImGui::GetIO().Fonts->AddFontFromFileTTF(
full_path.string().c_str(), full_path.string().c_str(),
13.0f, 13.0f,
config, config,
glyphs); glyphs);
} else { } else {
log_misc("overlay", "font not found: {}", full_path.string()); log_misc("overlay", "font not found: {}", full_path);
} }
} }
+250 -165
View File
@@ -17,6 +17,7 @@
#include "external/imgui/misc/cpp/imgui_stdlib.h" #include "external/imgui/misc/cpp/imgui_stdlib.h"
#include "games/io.h" #include "games/io.h"
#include "games/sdvx/sdvx.h" #include "games/sdvx/sdvx.h"
#include "games/popn/popn.h"
#include "avs/core.h" #include "avs/core.h"
#include "avs/ea3.h" #include "avs/ea3.h"
#include "avs/game.h" #include "avs/game.h"
@@ -283,18 +284,8 @@ namespace overlay::windows {
// keypad buttons // keypad buttons
ImGui::TextUnformatted(""); ImGui::TextUnformatted("");
if (this->games_selected_name == "Beatmania IIDX") { this->build_keypad_warning();
ImGui::Indent(INDENT);
ImGui::TextColored(
ImVec4(1, 0.5f, 0.5f, 1.f),
"WARNING: Lightning Model (TDJ) I/O will ignore the keypad!");
ImGui::TextWrapped(
"Use Toggle Sub Screen button to show the overlay and use your mouse, "
"connect using SpiceCompanion app, or connect a touch screen to enter "
"the PIN.");
ImGui::Unindent(INDENT);
ImGui::TextUnformatted("");
}
auto keypad_buttons = games::get_buttons_keypads(this->games_selected_name); auto keypad_buttons = games::get_buttons_keypads(this->games_selected_name);
auto keypad_count = eamuse_get_game_keypads_name(); auto keypad_count = eamuse_get_game_keypads_name();
if (keypad_count == 1) { if (keypad_count == 1) {
@@ -602,6 +593,34 @@ namespace overlay::windows {
} }
} }
void Config::build_keypad_warning() {
// not checking the -iidxtdj option here, we could do that in the future
const bool is_tdj = this->games_selected_name == "Beatmania IIDX";
// not using games::popn::is_pikapika_model() here since that always returns false on 32-bit
const bool is_popn = this->games_selected_name == "Pop'n Music" && avs::game::SPEC[0] == 'D';
if (!is_tdj && !is_popn) {
return;
}
ImGui::Indent(INDENT);
if (is_tdj) {
ImGui::TextColored(
ImVec4(1, 0.5f, 0.5f, 1.f),
"WARNING: Lightning Model (TDJ) I/O will ignore keypad number input!");
} else if (is_popn) {
ImGui::TextColored(
ImVec4(1, 0.5f, 0.5f, 1.f),
"WARNING: PikaPika Pop-Kun model will ignore keypad number input!");
}
ImGui::TextWrapped(
"Use Toggle Sub Screen button (Overlay tab) to show the overlay and use your mouse, "
"connect using SpiceCompanion app, or connect a touch screen to enter "
"the PIN.");
ImGui::Unindent(INDENT);
ImGui::TextUnformatted("");
}
void Config::build_buttons(const std::string &name, std::vector<Button> *buttons, int min, int max) { void Config::build_buttons(const std::string &name, std::vector<Button> *buttons, int min, int max) {
ImGui::AlignTextToFramePadding(); ImGui::AlignTextToFramePadding();
ImGui::TextColored(ImVec4(1.f, 0.7f, 0, 1), "%s Buttons", name.c_str()); ImGui::TextColored(ImVec4(1.f, 0.7f, 0, 1), "%s Buttons", name.c_str());
@@ -1044,6 +1063,7 @@ namespace overlay::windows {
button->setAnalogType(BAT_NONE); button->setAnalogType(BAT_NONE);
button->setDebounceUp(0.0); button->setDebounceUp(0.0);
button->setDebounceDown(0.0); button->setDebounceDown(0.0);
button->setBatThreshold(0);
button->setVelocityThreshold(0); button->setVelocityThreshold(0);
button->setInvert(false); button->setInvert(false);
button->setLastState(GameAPI::Buttons::BUTTON_NOT_PRESSED); button->setLastState(GameAPI::Buttons::BUTTON_NOT_PRESSED);
@@ -1186,6 +1206,16 @@ namespace overlay::windows {
case rawinput::HID: { case rawinput::HID: {
auto hid = device->hidInfo; auto hid = device->hidInfo;
struct temp_button {
std::string device_name;
unsigned short vkey;
ButtonAnalogType analog_type;
};
// use this to prioritize button binding over value types
// (for controllers that output both)
std::optional<temp_button> button_to_bind;
// ignore touchscreen and digitizer button inputs // ignore touchscreen and digitizer button inputs
// digitizer has funky stuff like "Touch Valid" "Data Valid" always held high // digitizer has funky stuff like "Touch Valid" "Data Valid" always held high
if (!rawinput::touch::is_touchscreen(device) && if (!rawinput::touch::is_touchscreen(device) &&
@@ -1199,18 +1229,11 @@ namespace overlay::windows {
// check if button is down // check if button is down
if (button_states[i]) { if (button_states[i]) {
button_to_bind = {
// bind key device->name,
button->setDeviceIdentifier(device->name); static_cast<unsigned short>(button_index + i),
button->setVKey(static_cast<unsigned short>(button_index + i)); BAT_NONE
button->setAnalogType(BAT_NONE); };
::Config::getInstance().updateBinding(
games_list[games_selected], *button,
alt_index - 1);
ImGui::CloseCurrentPopup();
buttons_bind_active = false;
inc_buttons_many_index(button_it_max);
RI_MGR->devices_midi_freeze(false);
break; break;
} }
} }
@@ -1219,93 +1242,100 @@ namespace overlay::windows {
} }
// value caps // value caps
auto value_states = &hid->value_states; if (!button_to_bind.has_value()) {
auto bind_value_states = &hid->bind_value_states; auto value_states = &hid->value_states;
auto value_names = &hid->value_caps_names; auto bind_value_states = &hid->bind_value_states;
for (size_t i = 0; i < value_states->size(); i++) { auto value_names = &hid->value_caps_names;
auto &state = value_states->at(i); for (size_t i = 0; i < value_states->size(); i++) {
auto &bind_state = bind_value_states->at(i); auto &state = value_states->at(i);
auto &value_name = value_names->at(i); auto &bind_state = bind_value_states->at(i);
auto &value_name = value_names->at(i);
// check for valid axis names // check for valid axis names
if (value_name == "X" || if (value_name == "X" ||
value_name == "Y" || value_name == "Y" ||
value_name == "Rx" || value_name == "Rx" ||
value_name == "Ry" || value_name == "Ry" ||
value_name == "Z") value_name == "Z")
{ {
// check if axis is in activation area // check if axis is in activation area
float normalized = (state - 0.5f) * 2.f; float normalized = (state - 0.5f) * 2.f;
float diff = std::fabs(state - bind_state); float diff = std::fabs(state - bind_state);
if (std::fabs(normalized) > 0.9f && diff > 0.1f) { if (std::fabs(normalized) > 0.9f && diff > 0.1f) {
auto bat = normalized > 0 ? BAT_POSITIVE : BAT_NEGATIVE; auto bat = normalized > 0 ? BAT_POSITIVE : BAT_NEGATIVE;
// bind value // bind value
button->setDeviceIdentifier(device->name); button_to_bind = {
button->setVKey(static_cast<unsigned short>(i)); device->name,
button->setAnalogType(bat); static_cast<unsigned short>(i),
button->setDebounceUp(0.0); bat
button->setDebounceDown(0.0); };
button->setVelocityThreshold(0);
::Config::getInstance().updateBinding(
games_list[games_selected], *button,
alt_index - 1);
ImGui::CloseCurrentPopup();
buttons_bind_active = false;
inc_buttons_many_index(button_it_max);
RI_MGR->devices_midi_freeze(false);
// usually, turntables are X, knobs are X and Y // usually, turntables are X, knobs are X and Y
// gamepad triggers are Z, and Rx/Ry are right thumb stick // gamepad triggers are Z, and Rx/Ry are right thumb stick
// one day we will label all I/O modules and flag which one are button-as-analog // one day we will label all I/O modules and flag which one are button-as-analog
// so that we don't have to do string comparions like below // so that we don't have to do string comparions like below
if ((value_name == "X" || value_name == "Y") && if ((value_name == "X" || value_name == "Y") &&
(button->getName().find("Press") == std::string::npos) && // museca (button->getName().find("Press") == std::string::npos) && // museca
(button->getName().find("Slowdown") == std::string::npos) && // bishibashi (button->getName().find("Slowdown") == std::string::npos) && // bishibashi
(button->getName().find("TT+") != std::string::npos || (button->getName().find("TT+") != std::string::npos ||
button->getName().find("TT-") != std::string::npos || // iidx button->getName().find("TT-") != std::string::npos || // iidx
button->getName().find("Knob") != std::string::npos || // gitadora guitar button->getName().find("Knob") != std::string::npos || // gitadora guitar
button->getName().find("Disk") != std::string::npos || // museca, bishibashi button->getName().find("Disk") != std::string::npos || // museca, bishibashi
button->getName().find("VOL-") != std::string::npos)) { // sdvx button->getName().find("P1 Panel") != std::string::npos || // ddr
this->analog_as_button_warning_show_next_frame = button->getName().find("P2 Panel") != std::string::npos || // ddr
std::make_pair(button->getName(), alt_index); button->getName().find("VOL-") != std::string::npos)) { // sdvx
this->analog_as_button_warning_show_next_frame =
std::make_pair(button->getName(), alt_index);
}
break;
} else if (diff > 0.3f) {
bind_state = state;
} }
break;
} else if (diff > 0.3f) {
bind_state = state;
} }
}
// hat switch // hat switch
if (value_name == "Hat switch") { if (value_name == "Hat switch") {
// get hat switch values // get hat switch values
ButtonAnalogType buffer[3], buffer_bind[3]; ButtonAnalogType buffer[3], buffer_bind[3];
Button::getHatSwitchValues(state, buffer); Button::getHatSwitchValues(state, buffer);
Button::getHatSwitchValues(bind_state, buffer_bind); Button::getHatSwitchValues(bind_state, buffer_bind);
// check the first entry only // check the first entry only
if (buffer[0] != BAT_NONE && buffer[0] != buffer_bind[0]) { if (buffer[0] != BAT_NONE && buffer[0] != buffer_bind[0]) {
// bind value // bind value
button->setDeviceIdentifier(device->name); button_to_bind = {
button->setVKey(static_cast<unsigned short>(i)); device->name,
button->setAnalogType(buffer[0]); static_cast<unsigned short>(i),
button->setDebounceUp(0.0); buffer[0]
button->setDebounceDown(0.0); };
button->setVelocityThreshold(0); break;
::Config::getInstance().updateBinding( }
games_list[games_selected], *button,
alt_index - 1);
ImGui::CloseCurrentPopup();
buttons_bind_active = false;
inc_buttons_many_index(button_it_max);
RI_MGR->devices_midi_freeze(false);
break;
} }
} }
} }
if (button_to_bind.has_value()) {
const auto &b = button_to_bind.value();
button->setDeviceIdentifier(b.device_name);
button->setVKey(b.vkey);
button->setAnalogType(b.analog_type);
button->setDebounceUp(0.0);
button->setDebounceDown(0.0);
button->setBatThreshold(0);
button->setVelocityThreshold(0);
::Config::getInstance().updateBinding(
games_list[games_selected], *button,
alt_index - 1);
ImGui::CloseCurrentPopup();
buttons_bind_active = false;
inc_buttons_many_index(button_it_max);
RI_MGR->devices_midi_freeze(false);
}
break; break;
} }
case rawinput::MIDI: { case rawinput::MIDI: {
@@ -1341,6 +1371,7 @@ namespace overlay::windows {
button->setAnalogType(BAT_NONE); button->setAnalogType(BAT_NONE);
button->setDebounceUp(0.0); button->setDebounceUp(0.0);
button->setDebounceDown(0.0); button->setDebounceDown(0.0);
button->setBatThreshold(0);
// same idea as setMidiVKey - keep velocity threshold consistent // same idea as setMidiVKey - keep velocity threshold consistent
button->setVelocityThreshold( button->setVelocityThreshold(
device->midiInfo->v2_velocity_threshold[button->getVKey()]); device->midiInfo->v2_velocity_threshold[button->getVKey()]);
@@ -1366,6 +1397,7 @@ namespace overlay::windows {
button->setAnalogType(BAT_MIDI_CTRL_PRECISION); button->setAnalogType(BAT_MIDI_CTRL_PRECISION);
button->setDebounceUp(0.0); button->setDebounceUp(0.0);
button->setDebounceDown(0.0); button->setDebounceDown(0.0);
button->setBatThreshold(0);
button->setVelocityThreshold(0); button->setVelocityThreshold(0);
::Config::getInstance().updateBinding( ::Config::getInstance().updateBinding(
games_list[games_selected], *button, games_list[games_selected], *button,
@@ -1392,6 +1424,7 @@ namespace overlay::windows {
button->setAnalogType(BAT_MIDI_CTRL_SINGLE); button->setAnalogType(BAT_MIDI_CTRL_SINGLE);
button->setDebounceUp(0.0); button->setDebounceUp(0.0);
button->setDebounceDown(0.0); button->setDebounceDown(0.0);
button->setBatThreshold(0);
button->setVelocityThreshold(0); button->setVelocityThreshold(0);
::Config::getInstance().updateBinding( ::Config::getInstance().updateBinding(
games_list[games_selected], *button, games_list[games_selected], *button,
@@ -1418,6 +1451,7 @@ namespace overlay::windows {
button->setAnalogType(BAT_MIDI_CTRL_ONOFF); button->setAnalogType(BAT_MIDI_CTRL_ONOFF);
button->setDebounceUp(0.0); button->setDebounceUp(0.0);
button->setDebounceDown(0.0); button->setDebounceDown(0.0);
button->setBatThreshold(0);
button->setVelocityThreshold(0); button->setVelocityThreshold(0);
::Config::getInstance().updateBinding( ::Config::getInstance().updateBinding(
games_list[games_selected], *button, games_list[games_selected], *button,
@@ -1443,6 +1477,7 @@ namespace overlay::windows {
button->setAnalogType(BAT_MIDI_PITCH_DOWN); button->setAnalogType(BAT_MIDI_PITCH_DOWN);
button->setDebounceUp(0.0); button->setDebounceUp(0.0);
button->setDebounceDown(0.0); button->setDebounceDown(0.0);
button->setBatThreshold(0);
button->setVelocityThreshold(0); button->setVelocityThreshold(0);
::Config::getInstance().updateBinding( ::Config::getInstance().updateBinding(
games_list[games_selected], *button, games_list[games_selected], *button,
@@ -1463,6 +1498,7 @@ namespace overlay::windows {
button->setAnalogType(BAT_MIDI_PITCH_UP); button->setAnalogType(BAT_MIDI_PITCH_UP);
button->setDebounceUp(0.0); button->setDebounceUp(0.0);
button->setDebounceDown(0.0); button->setDebounceDown(0.0);
button->setBatThreshold(0);
button->setVelocityThreshold(0); button->setVelocityThreshold(0);
::Config::getInstance().updateBinding( ::Config::getInstance().updateBinding(
games_list[games_selected], *button, games_list[games_selected], *button,
@@ -1492,6 +1528,7 @@ namespace overlay::windows {
button->setAnalogType(BAT_NONE); button->setAnalogType(BAT_NONE);
button->setDebounceUp(0.0); button->setDebounceUp(0.0);
button->setDebounceDown(0.0); button->setDebounceDown(0.0);
button->setBatThreshold(0);
button->setVelocityThreshold(0); button->setVelocityThreshold(0);
::Config::getInstance().updateBinding( ::Config::getInstance().updateBinding(
games_list[games_selected], *button, games_list[games_selected], *button,
@@ -1616,6 +1653,7 @@ namespace overlay::windows {
button->setVKey(vKey); button->setVKey(vKey);
button->setDebounceUp(0.0); button->setDebounceUp(0.0);
button->setDebounceDown(0.0); button->setDebounceDown(0.0);
button->setBatThreshold(0);
button->setVelocityThreshold(0); button->setVelocityThreshold(0);
::Config::getInstance().updateBinding( ::Config::getInstance().updateBinding(
games_list[games_selected], *button, games_list[games_selected], *button,
@@ -1667,6 +1705,9 @@ namespace overlay::windows {
button->setDeviceIdentifier(device.name); button->setDeviceIdentifier(device.name);
// reset controls when switching devices // reset controls when switching devices
button->setAnalogType(ButtonAnalogType::BAT_NONE); button->setAnalogType(ButtonAnalogType::BAT_NONE);
button->setDebounceUp(0.0);
button->setDebounceDown(0.0);
button->setBatThreshold(0);
button->setVelocityThreshold(0); button->setVelocityThreshold(0);
button->setVKey(0); button->setVKey(0);
button->setInvert(false); button->setInvert(false);
@@ -1962,6 +2003,7 @@ namespace overlay::windows {
int vKey = button->getVKey(); int vKey = button->getVKey();
if (ImGui::InputInt(button->isNaive() ? "Virtual Key" : "Index", &vKey, 1, 1)) { if (ImGui::InputInt(button->isNaive() ? "Virtual Key" : "Index", &vKey, 1, 1)) {
button->setVKey(vKey); button->setVKey(vKey);
button->setInvert(false);
} }
if (ImGui::IsItemDeactivatedAfterEdit()) { if (ImGui::IsItemDeactivatedAfterEdit()) {
dirty = true; dirty = true;
@@ -2029,11 +2071,64 @@ namespace overlay::windows {
"This setting will add noticable input lag."); "This setting will add noticable input lag.");
} }
// invert // invert (widget hidden for naive + 0xff)
bool invert = button->getInvert(); if (!(button->isNaive() && button->getVKey() == INVALID_VKEY)) {
if (ImGui::Checkbox("Invert", &invert)) { bool invert = button->getInvert();
button->setInvert(invert); if (ImGui::Checkbox("Invert Resulting Value", &invert)) {
dirty = true; button->setInvert(invert);
dirty = true;
}
}
// always on (naive + vkey 0xff + invert)
if (button->isNaive() && alt_index == 0) {
bool always_on = button->isNaive() && button->getVKey() == INVALID_VKEY && button->getInvert();
if (ImGui::Checkbox("Always On", &always_on)) {
if (always_on) {
button->setVKey(INVALID_VKEY);
button->setInvert(true);
} else {
button->setInvert(false);
}
dirty = true;
}
}
// bat threshold
if (device != nullptr && device->type == rawinput::HID &&
(button->getAnalogType() == BAT_POSITIVE || button->getAnalogType() == BAT_NEGATIVE)) {
int bat_threshold = button->getBatThreshold();
bool is_active = (bat_threshold > 0);
if (ImGui::Checkbox("Custom Analog Threshold", &is_active)) {
if (!is_active) {
button->setBatThreshold(0);
} else if (bat_threshold == 0) {
button->setBatThreshold(60);
}
dirty = true;
}
ImGui::SameLine();
ImGui::HelpMarker(
"Assuming that 50% is the center, value exceeding 60% triggers the button by default. "
"With this option, you can customize the trigger threshold.\n\n"
"Setting this to 51% means tiny movements will trigger the button (small deadzone), "
"while setting this to 99% means the button will only trigger at maximum value (large deadzone).\n\n"
"Setting this to below 50% adjusts the neutral / center position.");
if (is_active) {
if (ImGui::SliderInt(
"Analog Threshold",
&bat_threshold,
1, 99,
"%d%%",
ImGuiSliderFlags_AlwaysClamp)) {
button->setBatThreshold(bat_threshold);
}
if (ImGui::IsItemDeactivatedAfterEdit()) {
dirty = true;
}
}
} }
// state display // state display
@@ -2114,9 +2209,9 @@ namespace overlay::windows {
ImGui::Separator(); ImGui::Separator();
if (ImGui::BeginTable("AnalogsTable", 3, ImGuiTableFlags_Resizable | ImGuiTableFlags_RowBg)) { if (ImGui::BeginTable("AnalogsTable", 3, ImGuiTableFlags_Resizable | ImGuiTableFlags_RowBg)) {
ImGui::TableSetupColumn("Name", ImGuiTableColumnFlags_WidthFixed, overlay::apply_scaling(220)); ImGui::TableSetupColumn("Name", ImGuiTableColumnFlags_WidthFixed, overlay::apply_scaling(240));
ImGui::TableSetupColumn("Binding", ImGuiTableColumnFlags_WidthStretch); ImGui::TableSetupColumn("Binding", ImGuiTableColumnFlags_WidthStretch);
ImGui::TableSetupColumn("Actions", ImGuiTableColumnFlags_WidthFixed, overlay::apply_scaling(100)); ImGui::TableSetupColumn("Actions", ImGuiTableColumnFlags_WidthFixed, overlay::apply_scaling(80));
// check if empty // check if empty
if (!analogs || analogs->empty()) { if (!analogs || analogs->empty()) {
@@ -2175,8 +2270,9 @@ namespace overlay::windows {
} }
// analog binding // analog binding
const std::string title = "Analog Binding (" + analog.getName() + ")";
if (ImGui::Button("Set")) { if (ImGui::Button("Set")) {
ImGui::OpenPopup("Analog Binding"); ImGui::OpenPopup(title.c_str());
// get devices // get devices
this->analogs_devices.clear(); this->analogs_devices.clear();
@@ -2206,7 +2302,7 @@ namespace overlay::windows {
} }
} }
edit_analog_popup(analog); edit_analog_popup(analog, title);
// row hover detection (invisible selectable that spans entire row) // row hover detection (invisible selectable that spans entire row)
ImGui::InvisibleTableRowSelectable(); ImGui::InvisibleTableRowSelectable();
@@ -2220,8 +2316,8 @@ namespace overlay::windows {
} }
} }
void Config::edit_analog_popup(Analog &analog) { void Config::edit_analog_popup(Analog &analog, std::string title) {
if (ImGui::BeginPopupModal("Analog Binding", NULL, ImGuiWindowFlags_AlwaysAutoResize)) { if (ImGui::BeginPopupModal(title.c_str(), NULL, ImGuiWindowFlags_AlwaysAutoResize)) {
// device selector // device selector
auto analog_device_changed = ImGui::Combo( auto analog_device_changed = ImGui::Combo(
@@ -2428,31 +2524,45 @@ namespace overlay::windows {
const bool value_changed = const bool value_changed =
ImGui::SliderFloat("Sensitivity", &sensitivity, 0.f, 2.f, "%.3f"); ImGui::SliderFloat("Sensitivity", &sensitivity, 0.f, 2.f, "%.3f");
ImGui::SameLine(); ImGui::SameLine();
ImGui::HelpMarker( if (device->type == rawinput::HID && analog.getType() != GameAPI::Analogs::AnalogType::Circular) {
"Adjust floating point multiplier to relative movement.\n\n" ImGui::HelpMarker(
"Value is squared before being multiplied (e.g., 1.44 is 2x sensitivity, 2.00 is 4x).\n\n" "Adjust the analog sensitivity curve; "
"Dependent on how often the game polls for input. Intended for angular input (knobs, turntables)"); "values <1.0 are less sensitive around neutral, "
"values >1.0 are more sensitive.");
} else {
ImGui::HelpMarker(
"Adjust floating point multiplier to relative movement.\n\n"
"Value is squared before being multiplied (e.g., 1.44 is 2x sensitivity, 2.00 is 4x).");
}
if (value_changed) { if (value_changed) {
analog.setSensitivity(sensitivity * sensitivity); analog.setSensitivity(sensitivity * sensitivity);
} }
} }
if (device->type == rawinput::HID || device->type == rawinput::MIDI) {
// hide deadzone for circular analog since it doesn't make any sense
if ((device->type == rawinput::HID || device->type == rawinput::MIDI) &&
analog.getType() != GameAPI::Analogs::AnalogType::Circular) {
auto deadzone = analog.getDeadzone(); auto deadzone = analog.getDeadzone();
// for back compat (before each analog had a type)
if (deadzone < 0.f) {
deadzone = -deadzone;
analog.setDeadzone(deadzone);
}
const bool value_changed = const bool value_changed =
ImGui::SliderFloat("Deadzone", &deadzone, -0.999f, 0.999f, "%.3f"); ImGui::SliderFloat("Deadzone", &deadzone, 0.f, 0.999f, "%.3f");
if (value_changed) { if (value_changed) {
analog.setDeadzone(deadzone); analog.setDeadzone(deadzone);
} }
ImGui::SameLine(); ImGui::SameLine();
ImGui::HelpMarker("Positive values specify a deadzone around the middle.\n" ImGui::HelpMarker("Specify the deadzone that gets applied to at-rest (neutral) value.");
"Negative values specify a deadzone from the minimum value.");
// deadzone mirror // deadzone mirror
bool deadzone_mirror = analog.getDeadzoneMirror(); bool deadzone_mirror = analog.getDeadzoneMirror();
ImGui::Checkbox("Deadzone Mirror", &deadzone_mirror); ImGui::Checkbox("Deadzone Mirror", &deadzone_mirror);
ImGui::SameLine(); ImGui::SameLine();
ImGui::HelpMarker("Positive deadzone values cut off at edges instead.\n" ImGui::HelpMarker("Apply deadzone to extreme value(s) instead of neutral.");
"Negative deadzone values cut off at maximum value instead.");
if (deadzone_mirror != analog.getDeadzoneMirror()) { if (deadzone_mirror != analog.getDeadzoneMirror()) {
analog.setDeadzoneMirror(deadzone_mirror); analog.setDeadzoneMirror(deadzone_mirror);
} }
@@ -2471,47 +2581,20 @@ namespace overlay::windows {
if (this->analogs_devices_selected >= 0) { if (this->analogs_devices_selected >= 0) {
const auto device = this->analogs_devices.at(this->analogs_devices_selected); const auto device = this->analogs_devices.at(this->analogs_devices_selected);
if (device->type == rawinput::HID) { if (device->type == rawinput::HID) {
// smoothing
bool smoothing = analog.getSmoothing();
ImGui::BeginDisabled(analog.isRelativeMode());
ImGui::Checkbox("Smooth Axis (adds latency)", &smoothing);
ImGui::SameLine();
ImGui::HelpMarker(
"Apply a moving average algorithm; intended for angular input (knobs, turntables). "
"Adds a slight bit of latency to input as the algorithm averages out recent input. "
"Only use in dire situations where the input is too jittery for the game.");
ImGui::EndDisabled();
if (smoothing != analog.getSmoothing()) {
analog.setSmoothing(smoothing);
}
// relative input mode if (analog.getType() == GameAPI::Analogs::AnalogType::Circular) {
bool relative_analog = analog.isRelativeMode(); // smoothing
ImGui::Checkbox("Relative Axis (experimental)", &relative_analog); bool smoothing = analog.getSmoothing();
ImGui::SameLine(); ImGui::Checkbox("Smooth Axis (adds latency)", &smoothing);
ImGui::HelpMarker( ImGui::SameLine();
"Use relative directional input instead of positional values.\n\n" ImGui::HelpMarker(
"Can be used to translate analog sticks to knob input, for example.\n\n" "Apply a moving average algorithm; intended for angular input (knobs, turntables). "
"WARNING: speed depends on how often the game polls for input! " "Adds a slight bit of latency to input as the algorithm averages out recent input. "
"Strongly recommended that you go into the game's test menu instead " "Only use in dire situations where the input is too jittery for the game.");
"of adjusting in spicecfg."); if (smoothing != analog.getSmoothing()) {
if (relative_analog != analog.isRelativeMode()) { analog.setSmoothing(smoothing);
analog.setRelativeMode(relative_analog); }
} }
// delay buffer
int delay = analog.getDelayBufferDepth();
ImGui::InputInt("Delay (experimental)", &delay, 1, 10);
if (ImGui::IsItemDeactivatedAfterEdit()) {
delay = CLAMP(delay, 0, 256);
analog.setDelayBufferDepth(delay);
}
ImGui::SameLine();
ImGui::HelpMarker(
"Adds a delay to input. This is poll-based, not time-based.\n\n"
"WARNING: delay depends on how often the game polls for input! "
"Strongly recommended that you go into the game's test menu instead "
"of adjusting in spicecfg.");
} }
} }
@@ -2522,10 +2605,12 @@ namespace overlay::windows {
ImGui::ProgressBar(value); ImGui::ProgressBar(value);
// centered knob preview // centered knob preview
const float knob_size = 64.f; if (analog.getType() == GameAPI::Analogs::AnalogType::Circular) {
auto width = ImGui::GetContentRegionAvail().x - knob_size; const float knob_size = 64.f;
ImGui::SetCursorPosX(ImGui::GetCursorPosX() + (width / 2)); auto width = ImGui::GetContentRegionAvail().x - knob_size;
ImGui::Knob(value, knob_size); ImGui::SetCursorPosX(ImGui::GetCursorPosX() + (width / 2));
ImGui::Knob(value, knob_size);
}
// update analog // update analog
if (analogs_devices_selected >= 0 && analogs_devices_selected < (int) analogs_devices.size()) { if (analogs_devices_selected >= 0 && analogs_devices_selected < (int) analogs_devices.size()) {
@@ -5264,6 +5349,7 @@ namespace overlay::windows {
btn.setInvert(entry->invert); btn.setInvert(entry->invert);
btn.setDebounceUp(entry->debounce_up); btn.setDebounceUp(entry->debounce_up);
btn.setDebounceDown(entry->debounce_down); btn.setDebounceDown(entry->debounce_down);
btn.setBatThreshold(entry->bat_threshold);
btn.setVelocityThreshold(entry->velocity_threshold); btn.setVelocityThreshold(entry->velocity_threshold);
::Config::getInstance().updateBinding(game, btn, -1); ::Config::getInstance().updateBinding(game, btn, -1);
} else { } else {
@@ -5274,6 +5360,7 @@ namespace overlay::windows {
alt_btn.setInvert(entry->invert); alt_btn.setInvert(entry->invert);
alt_btn.setDebounceUp(entry->debounce_up); alt_btn.setDebounceUp(entry->debounce_up);
alt_btn.setDebounceDown(entry->debounce_down); alt_btn.setDebounceDown(entry->debounce_down);
alt_btn.setBatThreshold(entry->bat_threshold);
alt_btn.setVelocityThreshold(entry->velocity_threshold); alt_btn.setVelocityThreshold(entry->velocity_threshold);
alt_btn.setTemporary(true); alt_btn.setTemporary(true);
btn.getAlternatives().push_back(alt_btn); btn.getAlternatives().push_back(alt_btn);
@@ -5319,8 +5406,6 @@ namespace overlay::windows {
a.setInvert(ta.invert); a.setInvert(ta.invert);
a.setSmoothing(ta.smoothing); a.setSmoothing(ta.smoothing);
a.setMultiplier(ta.multiplier); a.setMultiplier(ta.multiplier);
a.setRelativeMode(ta.relative_mode);
a.setDelayBufferDepth(ta.delay_buffer_depth);
::Config::getInstance().updateBinding(game, a); ::Config::getInstance().updateBinding(game, a);
break; break;
} }
+6 -4
View File
@@ -162,7 +162,7 @@ namespace overlay::windows {
unsigned int get_keypad_top_row(const Button &button); unsigned int get_keypad_top_row(const Button &button);
void build_analogs(const std::string &name, std::vector<Analog> *analogs); void build_analogs(const std::string &name, std::vector<Analog> *analogs);
void edit_analog_popup(Analog &analog); void edit_analog_popup(Analog &analog, std::string title);
void update() override; void update() override;
void stop_lights_test(); void stop_lights_test();
@@ -193,13 +193,15 @@ namespace overlay::windows {
std::vector<Option> *options, const std::string &category, const std::string *filter=nullptr); std::vector<Option> *options, const std::string &category, const std::string *filter=nullptr);
void build_about(); void build_about();
void build_launcher(); void build_launcher();
void launch_shell(LPCSTR app, LPCSTR file=nullptr); void build_keypad_warning();
void launch_shell(LPCSTR app, LPCSTR file=nullptr);
void build_menu(int *game_selected); void build_menu(int *game_selected);
void shutdown_system(bool force, bool reboot_instead); void shutdown_system(bool force, bool reboot_instead);
void set_alternating_row_colors(const int row_index); void set_alternating_row_colors(const int row_index);
bool validate_ea_card(char card_number[16]); bool validate_ea_card(char card_number[16]);
public: public:
+8 -10
View File
@@ -628,13 +628,12 @@ namespace overlay::windows {
ImGui::Text("Wheel: %ld", mouse->pos_wheel); ImGui::Text("Wheel: %ld", mouse->pos_wheel);
// keys // keys
std::stringstream keys; std::string keys = "[";
keys << "[";
for (auto key : mouse->key_states) { for (auto key : mouse->key_states) {
keys << (key ? "1," : "0,"); keys += (key ? "1," : "0,");
} }
keys << "]"; keys += "]";
ImGui::Text("Keys: %s", keys.str().c_str()); ImGui::Text("Keys: %s", keys.c_str());
break; break;
} }
@@ -643,15 +642,14 @@ namespace overlay::windows {
ImGui::Text("Type: Keyboard"); ImGui::Text("Type: Keyboard");
// keys // keys
std::stringstream keys; std::string keys = "[";
keys << "[";
for (size_t i = 0; i < std::size(keyboard->key_states); i++) { for (size_t i = 0; i < std::size(keyboard->key_states); i++) {
if (keyboard->key_states[i]) { if (keyboard->key_states[i]) {
keys << i << ","; keys += std::to_string(i) + ",";
} }
} }
keys << "]"; keys += "]";
ImGui::Text("Keys: %s", keys.str().c_str()); ImGui::Text("Keys: %s", keys.c_str());
break; break;
} }
@@ -17,6 +17,7 @@ namespace overlay::windows {
bool invert = false; bool invert = false;
double debounce_up = 0.0; double debounce_up = 0.0;
double debounce_down = 0.0; double debounce_down = 0.0;
int bat_threshold = 0;
unsigned short velocity_threshold = 0; unsigned short velocity_threshold = 0;
bool is_naive() const { return device_identifier.empty() && vKey != INVALID_VKEY; } bool is_naive() const { return device_identifier.empty() && vKey != INVALID_VKEY; }
@@ -31,6 +32,7 @@ namespace overlay::windows {
e.invert = btn.getInvert(); e.invert = btn.getInvert();
e.debounce_up = btn.getDebounceUp(); e.debounce_up = btn.getDebounceUp();
e.debounce_down = btn.getDebounceDown(); e.debounce_down = btn.getDebounceDown();
e.bat_threshold = btn.getBatThreshold();
e.velocity_threshold = btn.getVelocityThreshold(); e.velocity_threshold = btn.getVelocityThreshold();
return e; return e;
} }
@@ -69,8 +71,6 @@ namespace overlay::windows {
bool invert = false; bool invert = false;
bool smoothing = false; bool smoothing = false;
int multiplier = 1; int multiplier = 1;
bool relative_mode = false;
int delay_buffer_depth = 0;
bool is_device() const { return !device_identifier.empty(); } bool is_device() const { return !device_identifier.empty(); }
bool is_unbound() const { return device_identifier.empty() && index == 0xFF; } bool is_unbound() const { return device_identifier.empty() && index == 0xFF; }
@@ -87,8 +87,6 @@ namespace overlay::windows {
invert = a.getInvert(); invert = a.getInvert();
smoothing = a.getSmoothing(); smoothing = a.getSmoothing();
multiplier = a.getMultiplier(); multiplier = a.getMultiplier();
relative_mode = a.isRelativeMode();
delay_buffer_depth = a.getDelayBufferDepth();
} }
}; };
+25
View File
@@ -3,6 +3,7 @@
#include "avs/game.h" #include "avs/game.h"
#include "games/iidx/iidx.h" #include "games/iidx/iidx.h"
#include "games/popn/popn.h"
#include "misc/eamuse.h" #include "misc/eamuse.h"
#include "util/logging.h" #include "util/logging.h"
#include "overlay/imgui/extensions.h" #include "overlay/imgui/extensions.h"
@@ -42,6 +43,8 @@ namespace overlay::windows {
void Keypad::build_content() { void Keypad::build_content() {
if (avs::game::is_model("LDJ") && games::iidx::TDJ_MODE) { if (avs::game::is_model("LDJ") && games::iidx::TDJ_MODE) {
build_tdj_keypad(); build_tdj_keypad();
} else if (games::popn::is_pikapika_model()) {
build_popn_pika_keypad();
} else { } else {
build_keypad(); build_keypad();
} }
@@ -132,4 +135,26 @@ namespace overlay::windows {
eamuse_set_keypad_overrides_overlay(this->unit, 0); eamuse_set_keypad_overrides_overlay(this->unit, 0);
} }
} }
void Keypad::build_popn_pika_keypad() {
ImGui::AlignTextToFramePadding();
ImGui::TextDisabled("Keypad disabled in Pop'n Pikapika model!\nUse subscreen overlay.");
ImGui::SameLine();
ImGui::WarnMarker(
nullptr,
"Pop'n Music Pikapika cabinets do not have any keypads; they use the subscreen.\n\n"
"Fullscreen mode: bind a key in Overlay tab, and press it in game to show the subscreen, "
"then use your mouse to click. Page Up button is the default binding.\n\n"
"Windowed mode: look for the second window in the taskbar (or ALT+TAB).\n\n"
"Windowed mode with -popnnosub: bring up the subscreen overlay (default Page Up).\n\n"
);
ImGui::Spacing();
if (ImGui::Button("Insert Card")) {
eamuse_set_keypad_overrides_overlay(this->unit, 1 << EAM_IO_INSERT);
} else {
eamuse_set_keypad_overrides_overlay(this->unit, 0);
}
}
} }
+1
View File
@@ -10,6 +10,7 @@ namespace overlay::windows {
size_t unit = 0; size_t unit = 0;
void build_keypad(); void build_keypad();
void build_tdj_keypad(); void build_tdj_keypad();
void build_popn_pika_keypad();
public: public:
@@ -74,6 +74,23 @@ namespace overlay::windows {
{"Nostroller (Lights 15-28)", "Nostalgia", 0, 0, "LED_27", "", "", true, "Key 27", ""}, {"Nostroller (Lights 15-28)", "Nostalgia", 0, 0, "LED_27", "", "", true, "Key 27", ""},
{"Nostroller (Lights 15-28)", "Nostalgia", 0, 0, "LED_28", "", "", true, "Key 28", ""}, {"Nostroller (Lights 15-28)", "Nostalgia", 0, 0, "LED_28", "", "", true, "Key 28", ""},
// icedragon.io snek board, ddr mode
{"snek lights", "Dance Dance Revolution", 0x2e8a, 0x10a8, "neon", "", "", false, "Neon", ""},
{"snek lights", "Dance Dance Revolution", 0x2e8a, 0x10a8, "mar p1 upper", "", "", false, "P1 Halogen Upper", ""},
{"snek lights", "Dance Dance Revolution", 0x2e8a, 0x10a8, "mar p1 lower", "", "", false, "P1 Halogen Lower", ""},
{"snek lights", "Dance Dance Revolution", 0x2e8a, 0x10a8, "mar p2 upper", "", "", false, "P2 Halogen Upper", ""},
{"snek lights", "Dance Dance Revolution", 0x2e8a, 0x10a8, "mar p2 lower", "", "", false, "P2 Halogen Lower", ""},
{"snek lights", "Dance Dance Revolution", 0x2e8a, 0x10a8, "p1 buttons", "", "", false, "P1 Button", ""},
{"snek lights", "Dance Dance Revolution", 0x2e8a, 0x10a8, "p2 buttons", "", "", false, "P2 Button", ""},
{"snek lights", "Dance Dance Revolution", 0x2e8a, 0x10a8, "p1 up", "", "", false, "P1 Foot Up", ""},
{"snek lights", "Dance Dance Revolution", 0x2e8a, 0x10a8, "p1 down", "", "", false, "P1 Foot Down", ""},
{"snek lights", "Dance Dance Revolution", 0x2e8a, 0x10a8, "p1 left", "", "", false, "P1 Foot Left", ""},
{"snek lights", "Dance Dance Revolution", 0x2e8a, 0x10a8, "p1 right", "", "", false, "P1 Foot Right", ""},
{"snek lights", "Dance Dance Revolution", 0x2e8a, 0x10a8, "p2 up", "", "", false, "P2 Foot Up", ""},
{"snek lights", "Dance Dance Revolution", 0x2e8a, 0x10a8, "p2 down", "", "", false, "P2 Foot Down", ""},
{"snek lights", "Dance Dance Revolution", 0x2e8a, 0x10a8, "p2 left", "", "", false, "P2 Foot Left", ""},
{"snek lights", "Dance Dance Revolution", 0x2e8a, 0x10a8, "p2 right", "", "", false, "P2 Foot Right", ""},
}; };
static const int LIGHT_MATCH_MAP_COUNT = static const int LIGHT_MATCH_MAP_COUNT =
+29 -24
View File
@@ -95,6 +95,10 @@ namespace overlay::windows {
} }
// utility // utility
std::string displayPath(const std::filesystem::path &path) {
return fmt::format(FMT_STRING("{}"), path);
}
std::string getFromUrl(const std::string& dll_name, const std::string& url) { std::string getFromUrl(const std::string& dll_name, const std::string& url) {
log_info("patchmanager", "getting patches from URL: {}, for file: {}", url, dll_name); log_info("patchmanager", "getting patches from URL: {}, for file: {}", url, dll_name);
std::string result; std::string result;
@@ -226,7 +230,7 @@ namespace overlay::windows {
if (PATCH_MANAGER_CFG_PATH_OVERRIDE.has_value()) { if (PATCH_MANAGER_CFG_PATH_OVERRIDE.has_value()) {
this->config_path = PATCH_MANAGER_CFG_PATH_OVERRIDE.value(); this->config_path = PATCH_MANAGER_CFG_PATH_OVERRIDE.value();
log_info("patchmanager", "using custom config file path: {}", this->config_path.string().c_str()); log_info("patchmanager", "using custom config file path: {}", this->config_path);
} else { } else {
this->config_path = this->config_path =
fileutils::get_config_file_path("patchmanager", "spicetools_patch_manager.json"); fileutils::get_config_file_path("patchmanager", "spicetools_patch_manager.json");
@@ -330,7 +334,7 @@ namespace overlay::windows {
"Wrong path? Run spicecfg from the correct directory, or fix your modules parameter before launching spicecfg.\n" "Wrong path? Run spicecfg from the correct directory, or fix your modules parameter before launching spicecfg.\n"
"Make sure you're not using a different one when launching the game."); "Make sure you're not using a different one when launching the game.");
ImGui::SameLine(); ImGui::SameLine();
ImGui::Text("Modules Path: %s", MODULE_PATH.string().c_str()); ImGui::Text("Modules Path: %s", displayPath(MODULE_PATH).c_str());
ImGui::AlignTextToFramePadding(); ImGui::AlignTextToFramePadding();
ImGui::DummyMarker(); ImGui::DummyMarker();
@@ -878,7 +882,7 @@ namespace overlay::windows {
} }
void PatchManager::hard_apply_patches() { void PatchManager::hard_apply_patches() {
std::vector<std::string> written_list; std::vector<std::filesystem::path> written_list;
for (auto& patch : patches) { for (auto& patch : patches) {
switch (patch.type) { switch (patch.type) {
case PatchType::Memory: case PatchType::Memory:
@@ -1218,7 +1222,7 @@ namespace overlay::windows {
log_info( log_info(
"patchmanager", "patchmanager",
"file: {}, patch id: {}, build timestamp of dll: {:%Y-%m-%d %H:%M}", "file: {}, patch id: {}, build timestamp of dll: {:%Y-%m-%d %H:%M}",
dll_path.has_filename() ? dll_path.filename().string() : dll_path.string(), dll_path.has_filename() ? dll_path.filename() : dll_path,
identifier, identifier,
time); time);
} }
@@ -1623,7 +1627,7 @@ namespace overlay::windows {
// save to file // save to file
std::filesystem::path save_path = LOCAL_PATCHES_PATH / (identifier + ".json"); std::filesystem::path save_path = LOCAL_PATCHES_PATH / (identifier + ".json");
fileutils::text_write(save_path, patches_json); fileutils::text_write(save_path, patches_json);
log_info("patchmanager", "remotely fetched JSON saved to: {}", save_path.string()); log_info("patchmanager", "remotely fetched JSON saved to: {}", save_path);
return true; return true;
} else { } else {
log_warning("patchmanager", "failed to fetch patches JSON for {}", dll_name); log_warning("patchmanager", "failed to fetch patches JSON for {}", dll_name);
@@ -1675,19 +1679,19 @@ namespace overlay::windows {
const size_t patches_size_previous = patches.size(); const size_t patches_size_previous = patches.size();
for (const std::filesystem::path& patches_json_path: LOCAL_PATCHES_JSON_PATHS) { for (const std::filesystem::path& patches_json_path: LOCAL_PATCHES_JSON_PATHS) {
if (!fileutils::file_exists(patches_json_path)) { if (!fileutils::file_exists(patches_json_path)) {
log_misc("patchmanager", "file does not exist, skipping: {}", patches_json_path.string()); log_misc("patchmanager", "file does not exist, skipping: {}", patches_json_path);
continue; continue;
} }
log_misc("patchmanager", "reading from patches.json: {}", patches_json_path.string()); log_misc("patchmanager", "reading from patches.json: {}", patches_json_path);
std::string content = fileutils::text_read(patches_json_path); std::string content = fileutils::text_read(patches_json_path);
append_patches(content, apply_patches, filter); append_patches(content, apply_patches, filter);
const auto new_patches = patches.size() - patches_size_previous; const auto new_patches = patches.size() - patches_size_previous;
log_info("patchmanager", "loaded {} patches from: {}", new_patches, patches_json_path.string()); log_info("patchmanager", "loaded {} patches from: {}", new_patches, patches_json_path);
if (0 < new_patches) { if (0 < new_patches) {
ret = true; ret = true;
ACTIVE_JSON_FILE = patches_json_path.string(); ACTIVE_JSON_FILE = displayPath(patches_json_path);
break; break;
} }
} }
@@ -1723,21 +1727,21 @@ namespace overlay::windows {
if (fileutils::file_exists(firstPath) || !extraDlls.empty()) { if (fileutils::file_exists(firstPath) || !extraDlls.empty()) {
if (fileutils::file_exists(firstPath)) { if (fileutils::file_exists(firstPath)) {
log_info("patchmanager", "loaded patches for {} from {}", firstDll, firstPath.string()); log_info("patchmanager", "loaded patches for {} from {}", firstDll, firstPath);
std::string content = fileutils::text_read(firstPath); std::string content = fileutils::text_read(firstPath);
append_patches(content, apply_patches, nullptr, first_id); append_patches(content, apply_patches, nullptr, first_id);
ACTIVE_JSON_FILE = firstPath.string(); ACTIVE_JSON_FILE = displayPath(firstPath);
} }
for (const std::string& dll : extraDlls) { for (const std::string& dll : extraDlls) {
auto extraId = get_game_identifier(MODULE_PATH / dll); auto extraId = get_game_identifier(MODULE_PATH / dll);
auto extraPath = std::filesystem::path(fmt::format("patches/{}.json", extraId)); auto extraPath = std::filesystem::path(fmt::format("patches/{}.json", extraId));
log_info("patchmanager", "loaded patches for {} from {}", dll, extraPath.string()); log_info("patchmanager", "loaded patches for {} from {}", dll, extraPath);
std::string content = fileutils::text_read(extraPath); std::string content = fileutils::text_read(extraPath);
append_patches(content, apply_patches, nullptr, extraId); append_patches(content, apply_patches, nullptr, extraId);
if (ACTIVE_JSON_FILE.empty()) { if (ACTIVE_JSON_FILE.empty()) {
ACTIVE_JSON_FILE = extraPath.string(); ACTIVE_JSON_FILE = displayPath(extraPath);
} else { } else {
ACTIVE_JSON_FILE += ", " + extraPath.string(); ACTIVE_JSON_FILE += ", " + displayPath(extraPath);
} }
} }
} else { } else {
@@ -3024,22 +3028,23 @@ namespace overlay::windows {
} }
void create_dll_backup( void create_dll_backup(
std::vector<std::string>& written_list, const std::filesystem::path& dll_path) { std::vector<std::filesystem::path>& written_list, const std::filesystem::path& dll_path) {
// if dll_path is not in written_list, create a file backup. // if dll_path is not in written_list, create a file backup.
if (std::find(written_list.begin(), written_list.end(), dll_path.string()) == written_list.end()) { if (std::find(written_list.begin(), written_list.end(), dll_path) == written_list.end()) {
written_list.push_back(dll_path.string()); written_list.push_back(dll_path);
auto dll_bak_path = std::filesystem::path(dll_path.string() + ".bak"); auto dll_bak_path = dll_path;
dll_bak_path += ".bak";
try { try {
if (!fileutils::file_exists(dll_bak_path)) { if (!fileutils::file_exists(dll_bak_path)) {
std::filesystem::copy(dll_path, dll_bak_path); std::filesystem::copy(dll_path, dll_bak_path);
} }
log_info("patchmanager", "created DLL backup for: {}", dll_path.string()); log_info("patchmanager", "created DLL backup for: {}", dll_path);
} catch (const std::filesystem::filesystem_error& e) { } catch (const std::filesystem::filesystem_error& e) {
log_warning( log_warning(
"patchmanager", "patchmanager",
"filesystem error while creating DLL backup for {}, error: {}", "filesystem error while creating DLL backup for {}, error: {}",
dll_path.string(), e.what()); dll_path, e.what());
} }
} }
} }
@@ -3065,14 +3070,14 @@ namespace overlay::windows {
/// check if file exists /// check if file exists
auto dll_path = MODULE_PATH / dll_name; auto dll_path = MODULE_PATH / dll_name;
if (!fileutils::file_exists(dll_path)) { if (!fileutils::file_exists(dll_path)) {
log_warning("patchmanager", "{} does not exist", dll_path.string()); log_warning("patchmanager", "{} does not exist", dll_path);
return nullptr; return nullptr;
} }
// get module // get module
auto module = libutils::try_module(dll_path); auto module = libutils::try_module(dll_path);
if (!module) { if (!module) {
log_warning("patchmanager", "cannot get module: {}", dll_path.string()); log_warning("patchmanager", "cannot get module: {}", dll_path);
return nullptr; return nullptr;
} }
@@ -3081,7 +3086,7 @@ namespace overlay::windows {
if (offset == -1) { if (offset == -1) {
log_warning( log_warning(
"patchmanager", "cannot convert offset to RVA: {}, {}", "patchmanager", "cannot convert offset to RVA: {}, {}",
dll_path.string(), data_offset); dll_path, data_offset);
return nullptr; return nullptr;
} }
@@ -3095,7 +3100,7 @@ namespace overlay::windows {
log_warning( log_warning(
"patchmanager", "GetModuleInformation failed for {}, gle: {}", "patchmanager", "GetModuleInformation failed for {}, gle: {}",
dll_path.string(), GetLastError()); dll_path, GetLastError());
return nullptr; return nullptr;
} }
+2 -2
View File
@@ -162,11 +162,11 @@ namespace overlay::windows {
uint8_t* destination, const std::string& dll_name, size_t offset, size_t size); uint8_t* destination, const std::string& dll_name, size_t offset, size_t size);
void create_dll_backup( void create_dll_backup(
std::vector<std::string>& written_list, const std::filesystem::path& dll_path); std::vector<std::filesystem::path>& written_list, const std::filesystem::path& dll_path);
std::string fix_up_dll_name(const std::string& dll_name); std::string fix_up_dll_name(const std::string& dll_name);
uint8_t* get_dll_offset_for_patch_apply( uint8_t* get_dll_offset_for_patch_apply(
const std::string& dll_name, const uint64_t data_offset, const size_t size_in_bytes); const std::string& dll_name, const uint64_t data_offset, const size_t size_in_bytes);
uint64_t parse_json_data_offset( uint64_t parse_json_data_offset(
const std::string &patch_name, const rapidjson::Value &value); const std::string &patch_name, const rapidjson::Value &value);
+26 -17
View File
@@ -605,14 +605,22 @@ void rawinput::RawInputManager::devices_scan_rawinput(RAWINPUTDEVICELIST *device
value_caps.LogicalMax = 255; value_caps.LogicalMax = 255;
} }
// fix min and max values // fix up invalid max values (seen on xbox controllers where max is 0xffffffff despite being 16-bit)
if (value_caps.BitSize > 0 && value_caps.BitSize <= sizeof(value_caps.LogicalMin) * 8) { if (value_caps.LogicalMin == 0 && value_caps.BitSize > 0 && value_caps.BitSize < 32) {
auto shift_size = sizeof(value_caps.LogicalMin) * 8 - value_caps.BitSize + 1; const uint32_t field_max = (1u << value_caps.BitSize) - 1u;
auto mask = ((uint64_t) 1 << value_caps.BitSize) - 1; const uint32_t logical_max = static_cast<uint32_t>(value_caps.LogicalMax);
value_caps.LogicalMin &= mask;
value_caps.LogicalMin <<= shift_size; if (logical_max > field_max) {
value_caps.LogicalMin >>= shift_size; log_info(
value_caps.LogicalMax &= mask; "rawinput",
"value cap {} LogicalMax exceeds bit width, fixing it up: {} -> {}",
value_cap_num,
value_caps.LogicalMax,
field_max
);
value_caps.LogicalMax = static_cast<LONG>(field_max);
}
} }
// fix up hat switch to initially report as neutral position // fix up hat switch to initially report as neutral position
@@ -1914,15 +1922,6 @@ LRESULT CALLBACK rawinput::RawInputManager::input_wnd_proc(
LONG value_min = value_caps.LogicalMin; LONG value_min = value_caps.LogicalMin;
LONG value_max = value_caps.LogicalMax; LONG value_max = value_caps.LogicalMax;
// fix sign bits for signed values
if (value_caps.LogicalMin < 0 &&
value_caps.BitSize > 0 &&
value_caps.BitSize <= sizeof(value_caps.LogicalMin) * 8) {
auto shift_size = sizeof(value_caps.LogicalMin) * 8 - value_caps.BitSize + 1;
value_raw <<= shift_size;
value_raw >>= shift_size;
}
float value; float value;
// 0x1 == generic desktop, 0x39 == hat switch // 0x1 == generic desktop, 0x39 == hat switch
if (value_caps.UsagePage == 0x1 && value_caps.Range.UsageMin == 0x39) { if (value_caps.UsagePage == 0x1 && value_caps.Range.UsageMin == 0x39) {
@@ -1935,6 +1934,16 @@ LRESULT CALLBACK rawinput::RawInputManager::input_wnd_proc(
value = -1.f; value = -1.f;
} }
} else { } else {
// fix sign bits for signed values
if (value_caps.LogicalMin < 0 &&
0 < value_caps.BitSize && value_caps.BitSize < 32) {
ULONG raw = static_cast<ULONG>(value_raw) & ((1u << value_caps.BitSize) - 1u);
const ULONG sign_bit = 1u << (value_caps.BitSize - 1);
value_raw = static_cast<LONG>((raw ^ sign_bit) - sign_bit);
}
// automatic calibration // automatic calibration
if (value_raw < value_min) { if (value_raw < value_min) {
value_caps.LogicalMin = value_raw; value_caps.LogicalMin = value_raw;
+9 -12
View File
@@ -99,7 +99,7 @@ namespace rawinput::touch {
// check type // check type
if (device->type != HID) { if (device->type != HID) {
log_fatal("rawinput", "touch update called on non HID device"); log_warning("rawinput", "touch update called on non HID device");
return; return;
} }
@@ -192,8 +192,10 @@ namespace rawinput::touch {
contact_count = std::max(contact_count, (size_t) hid->value_states_raw[index]); contact_count = std::max(contact_count, (size_t) hid->value_states_raw[index]);
} }
// https://learn.microsoft.com/en-us/windows-hardware/design/component-guidelines/touchscreen-packet-reporting-modes
// hybrid mode devices will report a contact count of 0 for subsequent reports that are // hybrid mode devices will report a contact count of 0 for subsequent reports that are
// part of the same initial frame // part of the same initial frame
// most laptops and touchscreens are like this - they have the contact count set to 5 but can actually report up to 10 fingers
if (contact_count > 0) { if (contact_count > 0) {
if (contact_count > touch_report_count) { if (contact_count > touch_report_count) {
touch.remaining_contact_count = contact_count - touch_report_count; touch.remaining_contact_count = contact_count - touch_report_count;
@@ -216,6 +218,12 @@ namespace rawinput::touch {
touch_points.reserve(touch.elements_x.size()); touch_points.reserve(touch.elements_x.size());
for (size_t i = 0; i < touch.elements_x.size(); i++) { for (size_t i = 0; i < touch.elements_x.size(); i++) {
// if there are no more touch events to handle, exit out early
if (touch_report_count == 0) {
break;
}
touch_report_count--;
// build touch point // build touch point
HIDTouchPoint hid_tp{}; HIDTouchPoint hid_tp{};
auto pos_x = hid->value_states[touch.elements_x[i]]; auto pos_x = hid->value_states[touch.elements_x[i]];
@@ -247,17 +255,6 @@ namespace rawinput::touch {
hid_tp.y = 1.f - hid_tp.y; hid_tp.y = 1.f - hid_tp.y;
} }
// check if this touch point should be considered valid
//
// If "Contact count" reports there are no touch reports remaining and the X and Y
// coordinates of this touch point are zero, then this report element should be
// skipped.
if (touch_report_count == 0 && hid_tp.x == 0.f && hid_tp.y == 0.f) {
continue;
} else if (touch_report_count > 0) {
touch_report_count--;
}
// generate ID (hopefully unique) // generate ID (hopefully unique)
hid_tp.id = (DWORD) hid->value_states_raw[touch.elements_contact_identifier[i]]; hid_tp.id = (DWORD) hid->value_states_raw[touch.elements_contact_identifier[i]];
hid_tp.id += (DWORD) (0xFFFFFF + device->id * 512); hid_tp.id += (DWORD) (0xFFFFFF + device->id * 512);
+133 -75
View File
@@ -1,6 +1,11 @@
#include "rawinput/xinput.h" #include "rawinput/xinput.h"
#include "util/logging.h" #include "util/logging.h"
// std::min
#ifdef min
#undef min
#endif
namespace xinput { namespace xinput {
// this is all we need to emulate xinput.h which we avoid including here... // this is all we need to emulate xinput.h which we avoid including here...
@@ -24,6 +29,10 @@ namespace xinput {
#define XINPUT_GAMEPAD_X 0x4000 #define XINPUT_GAMEPAD_X 0x4000
#define XINPUT_GAMEPAD_Y 0x8000 #define XINPUT_GAMEPAD_Y 0x8000
// custom
static constexpr float GAMEPAD_THUMB_DIGITAL_THRESHOLD = 0.2f;
typedef struct { typedef struct {
uint32_t dwPacketNumber; uint32_t dwPacketNumber;
XINPUT_GAMEPAD_STATE Gamepad; XINPUT_GAMEPAD_STATE Gamepad;
@@ -153,7 +162,7 @@ XInputSetState(
float XInputManager::get_analog_state(uint8_t player, XInputAnalogEnum analog) { float XInputManager::get_analog_state(uint8_t player, XInputAnalogEnum analog) {
return 0.5f; return 0.5f;
} }
bool XInputManager::is_button_pressed(uint8_t player, XInputButtonEnum button) { bool XInputManager::is_button_pressed(uint8_t player, XInputButtonEnum button, XINPUT_GAMEPAD_STATE_NORMALIZED *state_in) {
return false; return false;
} }
bool XInputManager::get_any_button_pressed(XINPUT_NEW_BUTTON &button) { bool XInputManager::get_any_button_pressed(XINPUT_NEW_BUTTON &button) {
@@ -223,6 +232,40 @@ XInputSetState(
return players; return players;
} }
static void normalize_stick(
SHORT raw_x,
SHORT raw_y,
float deadzone,
float &out_x,
float &out_y) {
const int x_i = static_cast<int>(raw_x);
const int y_i = -static_cast<int>(raw_y);
const float x_f = static_cast<float>(x_i);
const float y_f = static_cast<float>(y_i);
const float magnitude = std::sqrt(x_f * x_f + y_f * y_f);
// within deadzone; ignore
if (magnitude <= deadzone) {
out_x = 0.5f;
out_y = 0.5f;
return;
}
// scale value starting with 0 from deadzone border
const float magnitude_clamped = std::min(magnitude, 32767.0f);
const float scaled = (magnitude_clamped - deadzone) / (32767.0f - deadzone);
// normalize
const float normalized_x = (x_f / magnitude) * scaled;
const float normalized_y = (y_f / magnitude) * scaled;
// convert range to [0, 1] with 0.5 as center
out_x = std::clamp((normalized_x + 1.f) / 2.f, 0.f, 1.f);
out_y = std::clamp((normalized_y + 1.f) / 2.f, 0.f, 1.f);
}
void XInputManager::get_state(uint8_t player, XINPUT_GAMEPAD_STATE_NORMALIZED &state) { void XInputManager::get_state(uint8_t player, XINPUT_GAMEPAD_STATE_NORMALIZED &state) {
state = {}; state = {};
state.sThumbLX = 0.5f; state.sThumbLX = 0.5f;
@@ -233,7 +276,7 @@ XInputSetState(
return; return;
} }
XINPUT_STATE x; XINPUT_STATE x = {};
if (XInputGetState_addr(player, &x) != ERROR_SUCCESS) { if (XInputGetState_addr(player, &x) != ERROR_SUCCESS) {
return; return;
} }
@@ -242,106 +285,83 @@ XInputSetState(
state.bLeftTrigger = x.Gamepad.bLeftTrigger / 255.0f; state.bLeftTrigger = x.Gamepad.bLeftTrigger / 255.0f;
state.bRightTrigger = x.Gamepad.bRightTrigger / 255.0f; state.bRightTrigger = x.Gamepad.bRightTrigger / 255.0f;
// left stick circular dead zone // apply circular deadzone logic to left stick
{ normalize_stick(
const auto x_raw = x.Gamepad.sThumbLX; x.Gamepad.sThumbLX,
const auto y_raw = -x.Gamepad.sThumbLY; // flip to make [down = positive] x.Gamepad.sThumbLY,
const float magnitude = sqrtf(x_raw * x_raw + y_raw * y_raw); static_cast<float>(XINPUT_GAMEPAD_LEFT_THUMB_DEADZONE),
if (magnitude > XINPUT_GAMEPAD_LEFT_THUMB_DEADZONE) { state.sThumbLX,
const float scaled = state.sThumbLY);
(magnitude - XINPUT_GAMEPAD_LEFT_THUMB_DEADZONE) /
(32767.0f - XINPUT_GAMEPAD_LEFT_THUMB_DEADZONE);
state.sThumbLX = (x_raw / magnitude) * scaled; // apply circular deadzone logic to right stick
state.sThumbLY = (y_raw / magnitude) * scaled; normalize_stick(
x.Gamepad.sThumbRX,
// normalize to [0, 1] x.Gamepad.sThumbRY,
state.sThumbLX = std::clamp((state.sThumbLX + 1.f) / 2.f, 0.f, 1.f); static_cast<float>(XINPUT_GAMEPAD_RIGHT_THUMB_DEADZONE),
state.sThumbLY = std::clamp((state.sThumbLY + 1.f) / 2.f, 0.f, 1.f); state.sThumbRX,
} else { state.sThumbRY);
// within deadzone
state.sThumbLX = 0.5f;
state.sThumbLY = 0.5f;
}
}
// right stick circular dead zone
{
const auto x_raw = x.Gamepad.sThumbRX;
const auto y_raw = -x.Gamepad.sThumbRY; // flip to make [down = positive]
const float magnitude = sqrtf(x_raw * x_raw + y_raw * y_raw);
if (magnitude > XINPUT_GAMEPAD_RIGHT_THUMB_DEADZONE) {
const float scaled =
(magnitude - XINPUT_GAMEPAD_RIGHT_THUMB_DEADZONE) /
(32767.0f - XINPUT_GAMEPAD_RIGHT_THUMB_DEADZONE);
state.sThumbRX = (x_raw / magnitude) * scaled;
state.sThumbRY = (y_raw / magnitude) * scaled;
// normalize to [0, 1]
state.sThumbRX = std::clamp((state.sThumbRX + 1.f) / 2.f, 0.f, 1.f);
state.sThumbRY = std::clamp((state.sThumbRY + 1.f) / 2.f, 0.f, 1.f);
} else {
// within deadzone
state.sThumbRX = 0.5f;
state.sThumbRY = 0.5f;
}
}
} }
bool XInputManager::is_button_pressed(uint8_t player, XInputButtonEnum button) { bool XInputManager::is_button_pressed(uint8_t player, XInputButtonEnum button, XINPUT_GAMEPAD_STATE_NORMALIZED *state_in) {
XINPUT_GAMEPAD_STATE_NORMALIZED state; XINPUT_GAMEPAD_STATE_NORMALIZED state_on_stack;
XINPUT_GAMEPAD_STATE_NORMALIZED *state;
get_state(player, state); if (state_in) {
state = state_in;
} else {
state = &state_on_stack;
get_state(player, *state);
}
switch (button) { switch (button) {
case XInputButtonEnum::DPAD_UP: case XInputButtonEnum::DPAD_UP:
return (state.wButtons & XINPUT_GAMEPAD_DPAD_UP) != 0; return (state->wButtons & XINPUT_GAMEPAD_DPAD_UP) != 0;
case XInputButtonEnum::DPAD_DOWN: case XInputButtonEnum::DPAD_DOWN:
return (state.wButtons & XINPUT_GAMEPAD_DPAD_DOWN) != 0; return (state->wButtons & XINPUT_GAMEPAD_DPAD_DOWN) != 0;
case XInputButtonEnum::DPAD_LEFT: case XInputButtonEnum::DPAD_LEFT:
return (state.wButtons & XINPUT_GAMEPAD_DPAD_LEFT) != 0; return (state->wButtons & XINPUT_GAMEPAD_DPAD_LEFT) != 0;
case XInputButtonEnum::DPAD_RIGHT: case XInputButtonEnum::DPAD_RIGHT:
return (state.wButtons & XINPUT_GAMEPAD_DPAD_RIGHT) != 0; return (state->wButtons & XINPUT_GAMEPAD_DPAD_RIGHT) != 0;
case XInputButtonEnum::START: case XInputButtonEnum::START:
return (state.wButtons & XINPUT_GAMEPAD_START) != 0; return (state->wButtons & XINPUT_GAMEPAD_START) != 0;
case XInputButtonEnum::BACK: case XInputButtonEnum::BACK:
return (state.wButtons & XINPUT_GAMEPAD_BACK) != 0; return (state->wButtons & XINPUT_GAMEPAD_BACK) != 0;
case XInputButtonEnum::LEFT_STICK: case XInputButtonEnum::LEFT_STICK:
return (state.wButtons & XINPUT_GAMEPAD_LEFT_THUMB) != 0; return (state->wButtons & XINPUT_GAMEPAD_LEFT_THUMB) != 0;
case XInputButtonEnum::RIGHT_STICK: case XInputButtonEnum::RIGHT_STICK:
return (state.wButtons & XINPUT_GAMEPAD_RIGHT_THUMB) != 0; return (state->wButtons & XINPUT_GAMEPAD_RIGHT_THUMB) != 0;
case XInputButtonEnum::LEFT_SHOULDER: case XInputButtonEnum::LEFT_SHOULDER:
return (state.wButtons & XINPUT_GAMEPAD_LEFT_SHOULDER) != 0; return (state->wButtons & XINPUT_GAMEPAD_LEFT_SHOULDER) != 0;
case XInputButtonEnum::RIGHT_SHOULDER: case XInputButtonEnum::RIGHT_SHOULDER:
return (state.wButtons & XINPUT_GAMEPAD_RIGHT_SHOULDER) != 0; return (state->wButtons & XINPUT_GAMEPAD_RIGHT_SHOULDER) != 0;
case XInputButtonEnum::BUTTON_A: case XInputButtonEnum::BUTTON_A:
return (state.wButtons & XINPUT_GAMEPAD_A) != 0; return (state->wButtons & XINPUT_GAMEPAD_A) != 0;
case XInputButtonEnum::BUTTON_B: case XInputButtonEnum::BUTTON_B:
return (state.wButtons & XINPUT_GAMEPAD_B) != 0; return (state->wButtons & XINPUT_GAMEPAD_B) != 0;
case XInputButtonEnum::BUTTON_X: case XInputButtonEnum::BUTTON_X:
return (state.wButtons & XINPUT_GAMEPAD_X) != 0; return (state->wButtons & XINPUT_GAMEPAD_X) != 0;
case XInputButtonEnum::BUTTON_Y: case XInputButtonEnum::BUTTON_Y:
return (state.wButtons & XINPUT_GAMEPAD_Y) != 0; return (state->wButtons & XINPUT_GAMEPAD_Y) != 0;
case XInputButtonEnum::LEFT_TRIGGER: case XInputButtonEnum::LEFT_TRIGGER:
return state.bLeftTrigger >= XINPUT_GAMEPAD_TRIGGER_THRESHOLD; return state->bLeftTrigger >= XINPUT_GAMEPAD_TRIGGER_THRESHOLD;
case XInputButtonEnum::RIGHT_TRIGGER: case XInputButtonEnum::RIGHT_TRIGGER:
return state.bRightTrigger >= XINPUT_GAMEPAD_TRIGGER_THRESHOLD; return state->bRightTrigger >= XINPUT_GAMEPAD_TRIGGER_THRESHOLD;
case XInputButtonEnum::LEFT_STICK_UP: case XInputButtonEnum::LEFT_STICK_UP:
return state.sThumbLY > 0.6f; return state->sThumbLY < (0.5f - GAMEPAD_THUMB_DIGITAL_THRESHOLD);
case XInputButtonEnum::LEFT_STICK_DOWN: case XInputButtonEnum::LEFT_STICK_DOWN:
return state.sThumbLY < 0.4f; return state->sThumbLY > (0.5f + GAMEPAD_THUMB_DIGITAL_THRESHOLD);
case XInputButtonEnum::LEFT_STICK_LEFT: case XInputButtonEnum::LEFT_STICK_LEFT:
return state.sThumbLX < 0.4f; return state->sThumbLX < (0.5f - GAMEPAD_THUMB_DIGITAL_THRESHOLD);
case XInputButtonEnum::LEFT_STICK_RIGHT: case XInputButtonEnum::LEFT_STICK_RIGHT:
return state.sThumbLX > 0.6f; return state->sThumbLX > (0.5f + GAMEPAD_THUMB_DIGITAL_THRESHOLD);
case XInputButtonEnum::RIGHT_STICK_UP: case XInputButtonEnum::RIGHT_STICK_UP:
return state.sThumbRY > 0.6f; return state->sThumbRY < (0.5f - GAMEPAD_THUMB_DIGITAL_THRESHOLD);
case XInputButtonEnum::RIGHT_STICK_DOWN: case XInputButtonEnum::RIGHT_STICK_DOWN:
return state.sThumbRY < 0.4f; return state->sThumbRY > (0.5f + GAMEPAD_THUMB_DIGITAL_THRESHOLD);
case XInputButtonEnum::RIGHT_STICK_LEFT: case XInputButtonEnum::RIGHT_STICK_LEFT:
return state.sThumbRX < 0.4f; return state->sThumbRX < (0.5f - GAMEPAD_THUMB_DIGITAL_THRESHOLD);
case XInputButtonEnum::RIGHT_STICK_RIGHT: case XInputButtonEnum::RIGHT_STICK_RIGHT:
return state.sThumbRX > 0.6f; return state->sThumbRX > (0.5f + GAMEPAD_THUMB_DIGITAL_THRESHOLD);
default: default:
break; break;
} }
@@ -374,11 +394,49 @@ XInputSetState(
} }
bool XInputManager::get_any_button_pressed(XINPUT_NEW_BUTTON &button) { bool XInputManager::get_any_button_pressed(XINPUT_NEW_BUTTON &button) {
constexpr std::array<XInputButtonEnum, static_cast<size_t>(XInputButtonEnum::COUNT)> button_priority = {
// the ordering here is important; we want to check buttons first, then dpad, then analog
// this is to help with cases like DDR pads that output multiple at the same time for arrows
// actual buttons
XInputButtonEnum::BUTTON_A,
XInputButtonEnum::BUTTON_B,
XInputButtonEnum::BUTTON_X,
XInputButtonEnum::BUTTON_Y,
XInputButtonEnum::START,
XInputButtonEnum::BACK,
XInputButtonEnum::LEFT_STICK,
XInputButtonEnum::RIGHT_STICK,
XInputButtonEnum::LEFT_SHOULDER,
XInputButtonEnum::RIGHT_SHOULDER,
// dpad
XInputButtonEnum::DPAD_UP,
XInputButtonEnum::DPAD_DOWN,
XInputButtonEnum::DPAD_LEFT,
XInputButtonEnum::DPAD_RIGHT,
// analog values that can be used as buttons
XInputButtonEnum::LEFT_TRIGGER,
XInputButtonEnum::RIGHT_TRIGGER,
XInputButtonEnum::LEFT_STICK_UP,
XInputButtonEnum::LEFT_STICK_DOWN,
XInputButtonEnum::LEFT_STICK_LEFT,
XInputButtonEnum::LEFT_STICK_RIGHT,
XInputButtonEnum::RIGHT_STICK_UP,
XInputButtonEnum::RIGHT_STICK_DOWN,
XInputButtonEnum::RIGHT_STICK_LEFT,
XInputButtonEnum::RIGHT_STICK_RIGHT,
};
for (uint8_t player = 0; player < XUSER_MAX_COUNT; player++) { for (uint8_t player = 0; player < XUSER_MAX_COUNT; player++) {
XINPUT_GAMEPAD_STATE_NORMALIZED state;
get_state(player, state);
for (uint16_t b = 0; b < static_cast<uint16_t>(XInputButtonEnum::COUNT); b++) { for (uint16_t b = 0; b < static_cast<uint16_t>(XInputButtonEnum::COUNT); b++) {
if (is_button_pressed(player, static_cast<XInputButtonEnum>(b))) { if (is_button_pressed(player, button_priority[b], &state)) {
button.player = player; button.player = player;
button.button = static_cast<XInputButtonEnum>(b); button.button = button_priority[b];
return true; return true;
} }
} }
+3 -1
View File
@@ -31,6 +31,8 @@ namespace xinput {
float sThumbRY; float sThumbRY;
}; };
// the order of this enum is used for vkey mapping (saved in config file)
// therefore it can never be changed
enum class XInputButtonEnum : uint16_t { enum class XInputButtonEnum : uint16_t {
// actual buttons // actual buttons
DPAD_UP, DPAD_UP,
@@ -102,7 +104,7 @@ namespace xinput {
~XInputManager(); ~XInputManager();
void stop(); void stop();
std::vector<uint8_t> get_available_players(); std::vector<uint8_t> get_available_players();
bool is_button_pressed(uint8_t player, XInputButtonEnum button); bool is_button_pressed(uint8_t player, XInputButtonEnum button, XINPUT_GAMEPAD_STATE_NORMALIZED *state_in=nullptr);
float get_analog_state(uint8_t player, XInputAnalogEnum analog); float get_analog_state(uint8_t player, XInputAnalogEnum analog);
bool get_any_button_pressed(XINPUT_NEW_BUTTON &button); bool get_any_button_pressed(XINPUT_NEW_BUTTON &button);
void set_output_state(uint8_t player, XInputOutputEnum output, float value); void set_output_state(uint8_t player, XInputOutputEnum output, float value);
+14 -37
View File
@@ -82,14 +82,14 @@ bool fileutils::verify_header_pe(const std::filesystem::path &file_path) {
if (!valid) { if (!valid) {
log_fatal("fileutils", log_fatal("fileutils",
"{} (32 bit) can't be loaded using spice64.exe - please use spice.exe for this game.", "{} (32 bit) can't be loaded using spice64.exe - please use spice.exe for this game.",
file_path.string()); file_path);
} }
#else #else
valid = dll_file_header->Machine == IMAGE_FILE_MACHINE_I386; valid = dll_file_header->Machine == IMAGE_FILE_MACHINE_I386;
if (!valid) { if (!valid) {
log_fatal("fileutils", log_fatal("fileutils",
"{} (64 bit) can't be loaded using spice.exe - please use spice64.exe for this game.", "{} (64 bit) can't be loaded using spice.exe - please use spice64.exe for this game.",
file_path.string()); file_path);
} }
#endif #endif
} }
@@ -156,9 +156,9 @@ bool fileutils::dir_create_log(const std::string_view &module, const std::filesy
auto ret = std::filesystem::create_directory(dir_path, err); auto ret = std::filesystem::create_directory(dir_path, err);
if (err) { if (err) {
log_warning(module, "failed to create directory '{}': {}", dir_path.string(), err.message()); log_warning(module, "failed to create directory '{}': {}", dir_path, err.message());
} else if (ret) { } else if (ret) {
log_misc(module, "created directory '{}'", dir_path.string()); log_misc(module, "created directory '{}'", dir_path);
} }
return ret && !err; return ret && !err;
@@ -178,39 +178,14 @@ bool fileutils::dir_create_recursive_log(const std::string_view &module, const s
auto ret = std::filesystem::create_directories(dir_path, err); auto ret = std::filesystem::create_directories(dir_path, err);
if (err) { if (err) {
log_warning(module, "failed to create directory (recursive) '{}': {}", dir_path.string(), err.message()); log_warning(module, "failed to create directory (recursive) '{}': {}", dir_path, err.message());
} else if (ret) { } else if (ret) {
log_misc(module, "created directory (recursive) '{}'", dir_path.string()); log_misc(module, "created directory (recursive) '{}'", dir_path);
} }
return ret && !err; return ret && !err;
} }
void fileutils::dir_scan(const std::string &path, std::vector<std::string> &vec, bool recursive) {
// check directory
if (std::filesystem::exists(path) && std::filesystem::is_directory(path)) {
if (recursive) {
for (const auto &entry : std::filesystem::recursive_directory_iterator(path)) {
if (!std::filesystem::is_directory(entry)) {
auto path = entry.path().string();
vec.emplace_back(std::move(path));
}
}
} else {
for (const auto &entry : std::filesystem::directory_iterator(path)) {
if (!std::filesystem::is_directory(entry)) {
auto path = entry.path().string();
vec.emplace_back(std::move(path));
}
}
}
}
// determinism
std::sort(vec.begin(), vec.end());
}
bool fileutils::text_write(const std::filesystem::path &file_path, std::string text) { bool fileutils::text_write(const std::filesystem::path &file_path, std::string text) {
std::ofstream out(file_path, std::ios::out | std::ios::binary); std::ofstream out(file_path, std::ios::out | std::ios::binary);
if (out) { if (out) {
@@ -296,22 +271,24 @@ std::filesystem::path fileutils::get_config_file_path(const std::string module,
bool fileutils::write_config_file(const std::string_view &module, const std::filesystem::path path, std::string text) { bool fileutils::write_config_file(const std::string_view &module, const std::filesystem::path path, std::string text) {
// attempt to undo %appdata% expansion to hide user name // attempt to undo %appdata% expansion to hide user name
const auto appdata = std::filesystem::path(_wgetenv(L"APPDATA")).string(); const auto appdata = std::filesystem::path(_wgetenv(L"APPDATA")).wstring();
auto censored = path.string(); auto censored = path.wstring();
const auto substr_offset = censored.find(appdata); const auto substr_offset = censored.find(appdata);
if (substr_offset != std::string::npos) { if (substr_offset != std::string::npos) {
censored.replace(substr_offset, appdata.length(), "%appdata%"); censored.replace(substr_offset, appdata.length(), L"%appdata%");
} }
auto censored_display = fmt::detail::to_utf8<wchar_t>(censored, fmt::detail::to_utf8_error_policy::replace);
// create directory path up to where the config file lives // create directory path up to where the config file lives
if (!path.parent_path().empty() && !std::filesystem::exists(path.parent_path())) { if (!path.parent_path().empty() && !std::filesystem::exists(path.parent_path())) {
log_misc(module, "creating directory path to config file: {}", censored); log_misc(module, "creating directory path to config file: {}", censored_display);
if (!fileutils::dir_create_recursive(path.parent_path())) { if (!fileutils::dir_create_recursive(path.parent_path())) {
return false; return false;
} }
} }
// save file // save file
log_info(module, "saving config file: {}", censored); log_info(module, "saving config file: {}", censored_display);
return fileutils::text_write(path, text); return fileutils::text_write(path, text);
} }
-1
View File
@@ -26,7 +26,6 @@ namespace fileutils {
bool dir_create_log(const std::string_view &module, const std::filesystem::path &dir_path); bool dir_create_log(const std::string_view &module, const std::filesystem::path &dir_path);
bool dir_create_recursive(const std::filesystem::path &dir_path); bool dir_create_recursive(const std::filesystem::path &dir_path);
bool dir_create_recursive_log(const std::string_view &module, const std::filesystem::path &dir_path); bool dir_create_recursive_log(const std::string_view &module, const std::filesystem::path &dir_path);
void dir_scan(const std::string &path, std::vector<std::string> &vec, bool recursive);
// IO // IO
bool text_write(const std::filesystem::path &file_path, std::string text); bool text_write(const std::filesystem::path &file_path, std::string text);
+21 -20
View File
@@ -1,5 +1,6 @@
#include "libutils.h" #include "libutils.h"
#include <cstring>
#include <windows.h> #include <windows.h>
#include <psapi.h> #include <psapi.h>
#include <shlwapi.h> #include <shlwapi.h>
@@ -38,7 +39,7 @@ std::filesystem::path libutils::module_file_name(HMODULE module) {
return std::filesystem::path(std::move(buf)); return std::filesystem::path(std::move(buf));
} }
static inline void load_library_fail(const std::string &file_name, bool fatal) { static inline void load_library_fail(const std::filesystem::path &file_name, bool fatal) {
std::string info_str { fmt::format( std::string info_str { fmt::format(
"DLL failed to load - this is a common error. Please carefully read ALL of the following steps for a fix:\n" "DLL failed to load - this is a common error. Please carefully read ALL of the following steps for a fix:\n"
" 1. Confirm if the file ({}) exists on the disk and check the file permissions.\n" " 1. Confirm if the file ({}) exists on the disk and check the file permissions.\n"
@@ -76,9 +77,9 @@ HMODULE libutils::load_library(const std::filesystem::path &path, bool fatal) {
HMODULE module = LoadLibraryW(path.c_str()); HMODULE module = LoadLibraryW(path.c_str());
if (!module) { if (!module) {
log_warning("libutils", "'{}' couldn't be loaded: {}", path.string(), get_last_error_string()); log_warning("libutils", "'{}' couldn't be loaded: {}", path, get_last_error_string());
dependencies::walk(path); dependencies::walk(path);
load_library_fail(path.filename().string(), fatal); load_library_fail(path.filename(), fatal);
} }
return module; return module;
@@ -236,7 +237,7 @@ intptr_t libutils::rva2offset(const std::filesystem::path &path, intptr_t rva) {
HANDLE dll_mapping = CreateFileMappingW(dll_file, nullptr, PAGE_READONLY, 0, 0, nullptr); HANDLE dll_mapping = CreateFileMappingW(dll_file, nullptr, PAGE_READONLY, 0, 0, nullptr);
if (!dll_mapping) { if (!dll_mapping) {
CloseHandle(dll_file); CloseHandle(dll_file);
log_warning("libutils", "could not create file mapping for {}", path.string()); log_warning("libutils", "could not create file mapping for {}", path);
return -1; return -1;
} }
@@ -245,7 +246,7 @@ intptr_t libutils::rva2offset(const std::filesystem::path &path, intptr_t rva) {
if (!dll_file_base) { if (!dll_file_base) {
CloseHandle(dll_file); CloseHandle(dll_file);
CloseHandle(dll_mapping); CloseHandle(dll_mapping);
log_warning("libutils", "could not map view of file for {}", path.string()); log_warning("libutils", "could not map view of file for {}", path);
return -1; return -1;
} }
@@ -306,7 +307,7 @@ intptr_t libutils::offset2rva(const std::filesystem::path &path, intptr_t offset
// create file mapping // create file mapping
HANDLE dll_mapping = CreateFileMappingW(dll_file, nullptr, PAGE_READONLY, 0, 0, nullptr); HANDLE dll_mapping = CreateFileMappingW(dll_file, nullptr, PAGE_READONLY, 0, 0, nullptr);
if (!dll_mapping) { if (!dll_mapping) {
log_warning("libutils", "could not create file mapping for {}: {}", path.string(), get_last_error_string()); log_warning("libutils", "could not create file mapping for {}: {}", path, get_last_error_string());
CloseHandle(dll_file); CloseHandle(dll_file);
return -1; return -1;
} }
@@ -314,7 +315,7 @@ intptr_t libutils::offset2rva(const std::filesystem::path &path, intptr_t offset
// map view of file // map view of file
LPVOID dll_file_base = MapViewOfFile(dll_mapping, FILE_MAP_READ, 0, 0, 0); LPVOID dll_file_base = MapViewOfFile(dll_mapping, FILE_MAP_READ, 0, 0, 0);
if (!dll_file_base) { if (!dll_file_base) {
log_warning("libutils", "could not map view of file for {}: {}", path.string(), get_last_error_string()); log_warning("libutils", "could not map view of file for {}: {}", path, get_last_error_string());
CloseHandle(dll_file); CloseHandle(dll_file);
CloseHandle(dll_mapping); CloseHandle(dll_mapping);
return -1; return -1;
@@ -344,9 +345,9 @@ void libutils::check_duplicate_dlls() {
for (const auto &file : std::filesystem::directory_iterator(MODULE_PATH)) { for (const auto &file : std::filesystem::directory_iterator(MODULE_PATH)) {
const auto &filename = file.path().filename(); const auto &filename = file.path().filename();
const auto extension = strtolower(filename.extension().string()); const auto extension = filename.extension().wstring();
if (extension == ".dll" && if (wcsicmp(extension.c_str(), L".dll") == 0 &&
fileutils::file_exists(spice_bin_path / filename)) { fileutils::file_exists(spice_bin_path / filename)) {
log_warning( log_warning(
"libutils", "libutils",
@@ -361,9 +362,9 @@ void libutils::check_duplicate_dlls() {
"this has unintended consequences and may crash your game!\n" "this has unintended consequences and may crash your game!\n"
"resolve the conflict by deleting the stale copy of the DLL\n" "resolve the conflict by deleting the stale copy of the DLL\n"
"-------------------------------------------------------------------\n\n\n", "-------------------------------------------------------------------\n\n\n",
filename.string(), filename,
(spice_bin_path / filename).string(), (spice_bin_path / filename),
(MODULE_PATH / filename).string()); (MODULE_PATH / filename));
} }
} }
} }
@@ -384,22 +385,22 @@ void libutils::warn_if_dll_exists(const std::string &file_name) {
void libutils::print_dll_info(std::filesystem::path filename) { void libutils::print_dll_info(std::filesystem::path filename) {
DWORD handle; DWORD handle;
const auto size = GetFileVersionInfoSizeA(filename.string().c_str(), &handle); const auto size = GetFileVersionInfoSizeW(filename.wstring().c_str(), &handle);
if (size == 0) { if (size == 0) {
log_debug( log_debug(
"libutils", "libutils",
"GetFileVersionInfoSizeA failed for {}: {}", "GetFileVersionInfoSizeA failed for {}: {}",
filename.filename().string(), filename.filename(),
get_last_error_string()); get_last_error_string());
return; return;
} }
auto data = util::make_unique_plain<VOID>(size); auto data = util::make_unique_plain<VOID>(size);
if (!GetFileVersionInfoA(filename.string().c_str(), handle, size, data.get())) { if (!GetFileVersionInfoW(filename.wstring().c_str(), handle, size, data.get())) {
log_debug( log_debug(
"libutils", "libutils",
"GetFileVersionInfoA failed for {}: {}", "GetFileVersionInfoA failed for {}: {}",
filename.filename().string(), filename.filename(),
get_last_error_string()); get_last_error_string());
return; return;
} }
@@ -414,7 +415,7 @@ void libutils::print_dll_info(std::filesystem::path filename) {
log_debug( log_debug(
"libutils", "libutils",
"VerQueryValueA failed for {}: {}", "VerQueryValueA failed for {}: {}",
filename.filename().string(), filename.filename(),
get_last_error_string()); get_last_error_string());
return; return;
} }
@@ -422,7 +423,7 @@ void libutils::print_dll_info(std::filesystem::path filename) {
log_debug( log_debug(
"libutils", "libutils",
"VerQueryValueA returned invalid results for {}", "VerQueryValueA returned invalid results for {}",
filename.filename().string()); filename.filename());
return; return;
} }
@@ -457,7 +458,7 @@ void libutils::print_dll_info(std::filesystem::path filename) {
"libutils", "libutils",
"VerQueryValueA({}) failed for {}: {}", "VerQueryValueA({}) failed for {}: {}",
subBlock, subBlock,
filename.filename().string(), filename.filename(),
get_last_error_string()); get_last_error_string());
return ""; return "";
}; };
@@ -469,7 +470,7 @@ void libutils::print_dll_info(std::filesystem::path filename) {
log_info( log_info(
"libutils", "libutils",
"DLL info for {}: CompanyName = {}, ProductName = {}, Version = {}", "DLL info for {}: CompanyName = {}, ProductName = {}, Version = {}",
filename.filename().string(), filename.filename(),
company_name.empty() ? "?" : company_name, company_name.empty() ? "?" : company_name,
product_name.empty() ? "?" : product_name, product_name.empty() ? "?" : product_name,
version_str.empty() ? "?" : version_str); version_str.empty() ? "?" : version_str);
+2 -1
View File
@@ -10,6 +10,7 @@
#include "external/fmt/include/fmt/format.h" #include "external/fmt/include/fmt/format.h"
#include "external/fmt/include/fmt/compile.h" #include "external/fmt/include/fmt/compile.h"
#include "external/fmt/include/fmt/std.h"
#include "launcher/launcher.h" #include "launcher/launcher.h"
#include "launcher/logger.h" #include "launcher/logger.h"
@@ -75,7 +76,7 @@ void show_popup_for_fatal_error(std::string message);
// misc log // misc log
#define LOG_FORMAT(level, module, fmt_str, ...) fmt::format(FMT_COMPILE("{}" fmt_str "\n"), \ #define LOG_FORMAT(level, module, fmt_str, ...) fmt::format(FMT_COMPILE("{}" fmt_str "\n"), \
fmt_log { std::time(nullptr), level, module }, ## __VA_ARGS__) fmt_log { std::time(nullptr), level, module }, ## __VA_ARGS__)
#define LOG_FORMAT_POPUP(module, fmt_str, ...) fmt::format(FMT_COMPILE("{}: " fmt_str "\n"), module, ## __VA_ARGS__) #define LOG_FORMAT_POPUP(module, fmt_str, ...) fmt::format(FMT_COMPILE("{}: " fmt_str "\n"), module, ## __VA_ARGS__)
#define log_misc(module, format_str, ...) logger::push( \ #define log_misc(module, format_str, ...) logger::push( \
+17 -15
View File
@@ -219,20 +219,21 @@ intptr_t replace_pattern(HMODULE module, const std::string &signature,
strreplace(pattern_str, "??", "00"); strreplace(pattern_str, "??", "00");
auto pattern_bin = std::make_unique<uint8_t[]>(signature.length() / 2); auto pattern_bin = std::make_unique<uint8_t[]>(signature.length() / 2);
if (!hex2bin(pattern_str.c_str(), pattern_bin.get())) { if (!hex2bin(pattern_str.c_str(), pattern_bin.get())) {
return false; return 0;
} }
// build signature mask // build signature mask
std::ostringstream signature_mask; std::string signature_mask;
signature_mask.reserve(signature.size() / 2);
for (size_t i = 0; i < signature.length(); i += 2) { for (size_t i = 0; i < signature.length(); i += 2) {
if (signature[i] == '?') { if (signature[i] == '?') {
if (signature[i + 1] == '?') { if (signature[i + 1] == '?') {
signature_mask << '?'; signature_mask += '?';
} else { } else {
return false; return 0;
} }
} else { } else {
signature_mask << 'X'; signature_mask += 'X';
} }
} }
@@ -241,20 +242,21 @@ intptr_t replace_pattern(HMODULE module, const std::string &signature,
strreplace(replace_data_str, "??", "00"); strreplace(replace_data_str, "??", "00");
auto replace_data_bin = std::make_unique<uint8_t[]>(replacement.length() / 2); auto replace_data_bin = std::make_unique<uint8_t[]>(replacement.length() / 2);
if (!hex2bin(replace_data_str.c_str(), replace_data_bin.get())) { if (!hex2bin(replace_data_str.c_str(), replace_data_bin.get())) {
return false; return 0;
} }
// build replace mask // build replace mask
std::ostringstream replace_mask; std::string replace_mask;
replace_mask.reserve(replacement.size() / 2);
for (size_t i = 0; i < replacement.length(); i += 2) { for (size_t i = 0; i < replacement.length(); i += 2) {
if (replacement[i] == '?') { if (replacement[i] == '?') {
if (replacement[i + 1] == '?') { if (replacement[i + 1] == '?') {
replace_mask << '?'; replace_mask += '?';
} else { } else {
return false; return 0;
} }
} else { } else {
replace_mask << 'X'; replace_mask += 'X';
} }
} }
@@ -262,18 +264,18 @@ intptr_t replace_pattern(HMODULE module, const std::string &signature,
return replace_pattern( return replace_pattern(
module, module,
pattern_bin.get(), pattern_bin.get(),
signature_mask.str().c_str(), signature_mask.c_str(),
offset, offset,
usage, usage,
replace_data_bin.get(), replace_data_bin.get(),
replace_mask.str().c_str() replace_mask.c_str()
); );
} }
bool get_pe_identifier(const std::filesystem::path& dll_path, uint32_t* time_date_stamp, uint32_t* address_of_entry_point) { bool get_pe_identifier(const std::filesystem::path& dll_path, uint32_t* time_date_stamp, uint32_t* address_of_entry_point) {
std::ifstream file(dll_path, std::ios::binary); std::ifstream file(dll_path, std::ios::binary);
if (!file) { if (!file) {
log_warning("sigscan", "Failed to open file: {}", dll_path.string().c_str()); log_warning("sigscan", "Failed to open file: {}", dll_path);
return false; return false;
} }
@@ -281,7 +283,7 @@ bool get_pe_identifier(const std::filesystem::path& dll_path, uint32_t* time_dat
IMAGE_DOS_HEADER dos_header; IMAGE_DOS_HEADER dos_header;
file.read(reinterpret_cast<char*>(&dos_header), sizeof(dos_header)); file.read(reinterpret_cast<char*>(&dos_header), sizeof(dos_header));
if (dos_header.e_magic != IMAGE_DOS_SIGNATURE) { if (dos_header.e_magic != IMAGE_DOS_SIGNATURE) {
log_warning("sigscan", "Invalid DOS signature: {}", dll_path.string().c_str()); log_warning("sigscan", "Invalid DOS signature: {}", dll_path);
return false; return false;
} }
@@ -292,7 +294,7 @@ bool get_pe_identifier(const std::filesystem::path& dll_path, uint32_t* time_dat
IMAGE_NT_HEADERS nt_headers; IMAGE_NT_HEADERS nt_headers;
file.read(reinterpret_cast<char*>(&nt_headers), sizeof(nt_headers)); file.read(reinterpret_cast<char*>(&nt_headers), sizeof(nt_headers));
if (nt_headers.Signature != IMAGE_NT_SIGNATURE) { if (nt_headers.Signature != IMAGE_NT_SIGNATURE) {
log_warning("sigscan", "Invalid NT signature: {}", dll_path.string().c_str()); log_warning("sigscan", "Invalid NT signature: {}", dll_path);
return false; return false;
} }
+12 -3
View File
@@ -9,7 +9,7 @@ namespace tapeledutils {
} }
// for bi2x-style byte array of all colors and LEDs at once // for bi2x-style byte array of all colors and LEDs at once
rgb_float3_t pick_color_from_led_tape(uint8_t *data, size_t data_size) { static rgb_float3_t pick_color_from_led_tape_internal(uint8_t *data, size_t data_size, uint8_t divisor) {
rgb_float3_t result = {0.f, 0.f, 0.f}; rgb_float3_t result = {0.f, 0.f, 0.f};
if (TAPE_LED_ALGORITHM == TAPE_LED_USE_AVERAGE) { if (TAPE_LED_ALGORITHM == TAPE_LED_USE_AVERAGE) {
@@ -25,7 +25,7 @@ namespace tapeledutils {
} }
// normalize // normalize
const float avg_mult = 1.f / (data_size * 255); const float avg_mult = 1.f / (data_size * divisor);
result.r = avg_ri * avg_mult; result.r = avg_ri * avg_mult;
result.g = avg_gi * avg_mult; result.g = avg_gi * avg_mult;
result.b = avg_bi * avg_mult; result.b = avg_bi * avg_mult;
@@ -50,7 +50,7 @@ namespace tapeledutils {
} }
// normalize // normalize
const float single_mult = 1.f / 255; const float single_mult = 1.f / divisor;
result.r = color[0] * single_mult; result.r = color[0] * single_mult;
result.g = color[1] * single_mult; result.g = color[1] * single_mult;
result.b = color[2] * single_mult; result.b = color[2] * single_mult;
@@ -58,6 +58,15 @@ namespace tapeledutils {
return result; return result;
} }
rgb_float3_t pick_color_from_led_tape(tape_led &light, uint8_t *data, size_t data_size) {
const auto max_value = (light.max_value > 0) ? light.max_value : 0xFF;
return pick_color_from_led_tape_internal(data, data_size, max_value);
}
rgb_float3_t pick_color_from_led_tape(uint8_t *data, size_t data_size) {
return pick_color_from_led_tape_internal(data, data_size, 0xFF);
}
// for bi2a-style that calls for each individual LED // for bi2a-style that calls for each individual LED
size_t get_led_index_using_avg_algo(size_t data_size) { size_t get_led_index_using_avg_algo(size_t data_size) {
size_t index_to_use; size_t index_to_use;
+22 -2
View File
@@ -25,13 +25,33 @@ namespace tapeledutils {
struct tape_led { struct tape_led {
std::vector<rgb_float3_t> data; std::vector<rgb_float3_t> data;
int index_r, index_g, index_b; // Averaged RGB light output indexes int index_r, index_g, index_b; // Averaged RGB light output indexes
uint8_t max_value;
std::string lightName; std::string lightName;
tape_led(size_t data_size, int index_r, int index_g, int index_b, std::string lightName) tape_led(
: data(std::vector<rgb_float3_t>(data_size)), index_r(index_r), index_g(index_g), index_b(index_b), lightName(std::move(lightName)) {} size_t data_size,
int index_r, int index_g, int index_b,
std::string lightName)
: data(
std::vector<rgb_float3_t>(data_size)),
index_r(index_r), index_g(index_g), index_b(index_b),
max_value(0),
lightName(std::move(lightName)) {}
tape_led(
size_t data_size,
int index_r, int index_g, int index_b,
uint8_t max_value,
std::string lightName)
: data(
std::vector<rgb_float3_t>(data_size)),
index_r(index_r), index_g(index_g), index_b(index_b),
max_value(max_value),
lightName(std::move(lightName)) {}
}; };
bool is_enabled(); bool is_enabled();
rgb_float3_t pick_color_from_led_tape(tape_led &light, uint8_t *data, size_t data_size);
rgb_float3_t pick_color_from_led_tape(uint8_t *data, size_t data_size); rgb_float3_t pick_color_from_led_tape(uint8_t *data, size_t data_size);
size_t get_led_index_using_avg_algo(size_t data_size); size_t get_led_index_using_avg_algo(size_t data_size);
} }