Compare commits

...

68 Commits

Author SHA1 Message Date
bicarus dc82c980b1 iidx: show error message for emulated I/O mismatch in LDJ/TDJ sub overlay (#555)
## Link to GitHub Issue, if one exists
#345 

## Description of change
When DLL is configured to use TDJ I/O but spice is in LDJ mode, show an
error in LED ticker suggesting the user to turn on `-iidxtdj`.

When DLL is configured to use LDJ I/O but spice is in TDJ mode, show an
error in TDJ overaly suggesting the user to fix the DLL.

Fix a small bug that caused a white window to be created when `-w` is
enabled even when `-iidxtdj` is not, but DLL is using TDJ I/O.
2026-02-15 19:49:07 -08:00
bicarus 408ed17521 cfg: turn a handful of parameter parsing errors into fatal (#554)
For parameters that require two values (`width, height` or `x, y`), if
the string fails to parse, turn into runtime failure.
2026-02-13 03:22:20 -08:00
bicarus f4050e9adb graphics: custom resolution for subscreen (#553)
## Link to GitHub Issue, if one exists
n/a

## Description of change
Allow user to specify a custom resolution for the sub monitor, in
addition to the existing refresh rate option

This would allow practically any touch monitor (including ones with
weird resolution / aspect ratio / refresh rate) to be used with
IIDX/SDVX.

## Testing
Tested with various resolution / refresh rate combinations on IIDX /
SDVX.
2026-02-12 17:51:38 -08:00
din badae01357 bt5api fix (#552)
## Description of change
use case: user with two ICCA readers in a daisy chain configuration (as
is wired in og IIDX/DDR) wanted to play DDR using their existing
equipment.

Existing `reader/` implementation is only designed for a single reader
per com port vs two readers on one daisy chained com port. This can be
implemented and improved at a future date.

Due to time constraints `eamio-icca.dll` from btools did support both
wavepass and slotted in a daisy chain configuration, and spice does
support bt5api calls.

However, the implementation was not sufficient to allow use of both
modules correctly, often stalling, not did it allow the ability to eject
the card.

I have cleaned up the implementation such that it matches the btools
`eamiotest` implementation and allows the user to use decimal to eject
the card as desired.

As well as properly staging the requests for state of the two individual
readers, like `eamio-icca` expects.

## Testing
Tested with both wavepass and slotted (ICCA and ICCB) modules and in
combination with released `eamio-icca.dll` binaries from btools 5.49 as
released by their github using `MDX` as a test subject.

Works as expected and allows end users to use real readers (if they have
them) as virtual readers in spice's emulation layer.
2026-02-04 19:34:25 -08:00
bicarus 38d9939eb7 cfg: fix early game detection for Polaris Chord and MFG (#551)
## Link to GitHub Issue, if one exists


## Description of change
Some parts of the UI (e.g., Card tab) rely on early auto detection of
the game, which relies on file hints and not the game code. Two games
(PC and MFG) rely on directory presence and not file presence.
2026-02-04 11:40:07 -08:00
bicarus f5a3caf536 signal: avoid spamming log with hook hit messages (#550)
## Link to GitHub Issue, if one exists
n/a

## Description of change
Some people have weird DLLs that keep calling
`AddVectoredExceptionHandler` which ends up spamming the log.

## Testing
Tested IIDX with error + signaldisable to confirm the warning message
being shown, ran Polaris Chord once to check "hook hit" message.
2026-02-03 20:08:15 -08:00
bicarus 027445afb1 overlay: add context menu for overflow operations (#547)
Remove the green + button, remove the bottom checkbox row, and instead
put extra operations like `Add alternate` and `Bind many` to a context
menu.
2026-01-31 23:57:08 -08:00
bicarus 63a3900b79 overlay: add skip button to bind many (#546) 2026-01-31 20:58:15 -08:00
bicarus f542a78347 overlay: add hover delay to tooltip (#545)
Clean up flags used for tooltip display, which adds a tiny delay by
default.
2026-01-31 15:47:23 -08:00
bicarus 4d32dde83e overlay: remove multiple pages from Buttons/Overlay/Lights tabs, replace with tree layout (#541)
The old pages UI was not intuitive and often led to questions like:

* How do I bind multiple controllers to a single input?? (failure to
discover)
* I have a ghost input that won't go away, how do I fix this? Is this a
bug? (non-intuitive UI - there are too many pages and finding out where
each binding is potentially requires scrolling through 100 pages)
* Potential perf issue - user binds too many by accident, or puts
bindings all the way in page 99, requiring us to walk each element in
the vector, wasting precious CPU cycles during an input poll.

This PR removes the multiple pages, and puts everything on a single
page, showing multiple binds in a tree structure.

It also puts a hard limit on how many alternate bindings can be made (8
for buttons, 16 for lights), though if the user has configured more in
older version they will be respected.
2026-01-31 15:16:19 -08:00
bicarus 927170ce73 api: fix unbounded access in getting analog state (#544)
## Link to GitHub Issue, if one exists
#0

## Description of change
Occasionally we would read a bad "vKey" for an analog axis and end up
accessing invalid memory (beyond the size of a vector) and potentially
crash or read junk values.

## Testing
Fixed based on minidump, this was a rare repro though.
2026-01-30 20:53:13 -08:00
bicarus 875a0f0765 overlay: address small polish issues (#543)
* Make a custom widget for truncated text that shows a tooltip only when
truncated
* Use the above widget more consistently in various places
* Ensure tooltips still show even when the preceding widget is disabled
2026-01-30 19:17:44 -08:00
bicarus 3ad88ef15c overlay: fixes for naive binding (#542)
## Description of change
Three bugs:

* Some keys are IME-dependent. Right Alt can be IME mode switch for
Japanese / Korean, and Right Ctrl is often the Kanji button.
* If you press modifiers like Ctrl during bind-many-naive, it will skip
over a button, because of broken for loop logic that ends up calling
`inc_buttons_many_index` too many times
* trying to cancel out of bind-many-naive with left and right mouse
buttons swap doesn't work

## Testing
Validating various IME. I don't have a keyboard in another language
though..
2026-01-30 13:16:14 -08:00
bicarus 7a6cadb176 overlay: refactor code for lights (#539)
No functional change.
2026-01-29 16:13:56 -08:00
Will 4feca23240 Build scripts: support paths with spaces (#540)
Doing dumb things and building inside a path that contains spaces.
Quoted the relevant bits in the build script (and a couple more
shellcheck brought up that realistically won't ever be an issue, but the
squigglies annoy me.
2026-01-29 16:12:23 -08:00
bicarus 2e52bebd3d overlay: bring back "-" placeholder for unbound io, clean up code (#538) 2026-01-29 11:53:16 -08:00
bicarus aa218d5be7 overlay: fix colors of new extension widgets (#537) 2026-01-29 01:58:43 -08:00
bicarus 94f009991a overlay: simplify various input controls by introducing new custom ImGUI widgets (#536)
## Description of change

* Add a red `x` button to replace `Clear` button in binding tabs
(Buttons/Analog/Lights).
* Add a gray `x` button to replace `Clear` button in options tabs next
to input widgets
* Add helper for truncating text and adding ellipsis (...) to deal with
text overflow in tables.
* Reword some UI text for clarity
2026-01-28 02:17:40 -08:00
bicarus c3b9ce3fdc cfg: fix mouse names out of bounds (#535)
## Link to GitHub Issue, if one exists
n/a

## Description of change
Improper bounds check resulting in crash when binding mouse buttons
beyond Mouse 5.

## Testing
Tested with `Edit` function.
2026-01-26 23:42:12 -08:00
bicarus 1517cf9a7c overlay: refactor UI code for I/O binding tabs (#533)
No functional changes, refactor of code only.
2026-01-26 22:24:14 -08:00
bicarus f0f46296f2 cfg: clean up stale buttons and lights alt bindings on save (#534)
## Link to GitHub Issue, if one exists
n/a

## Description of change
For buttons and lights, it's possible to have multiple alternatives.

When you create alternatives and then clear then, the config files
continue to have stale entries. This is because alternatives are stored
in a vector and elements are never deleted when unbound; they get
written out to the XML on modification.

These stale XML entries live forever, and get loaded on next startup of
spice.

This PR fixes that by deleting stale entries - basically, XML nodes will
be kept only until the last valid node (properly bound button or light),
trimming the trailing invalid entries.

## Testing
Manual testing, validating XML.
2026-01-26 22:12:04 -08:00
bicarus 1d1981fcde rb: prevent window moves and resizes in windowed mode (#532)
## Link to GitHub Issue, if one exists
n/a

## Description of change
Disable all window options that change dimensions or position as they
cause problems with touch overlay in windowed mode. Always On Top is the
only thing that's still possible.

## Testing
2026-01-24 18:49:16 -08:00
bicarus 2f538fe2e4 gitadora: fix order of drums in Buttons tab (#531)
## Link to GitHub Issue, if one exists
n/a

## Description of change
Before this PR, drums were in the order they appear in test menu.

This is really confusing. Change it so that they appear in the order
they do in default drummania layout.

## Testing
validated manually in gw
2026-01-23 10:22:24 -08:00
bicarus-dev 8e01b2e5c8 update gitadora help text 2026-01-23 03:03:18 -08:00
bicarus cd14ca800e graphics: fix window resize not working properly in some games (#530)
## Link to GitHub Issue, if one exists
n/a

## Description of change
Need to filter out system-created windows (such as `CicMarshalWnd`) when
looking up which window to mess with when user interacts with F11 menu.

While I'm here, clean up the hack for gfdm arena model that remembers
the main window.

## Testing
Tested:

DDR
gfdm arena 4 / 1 window modes
iidx (windowed mode, with/without nosub option)
sdvx with sub windows

Need to check:
full screen games
2026-01-22 19:06:31 -08:00
bicarus cb59fe14e9 gitadora: prevent window resize (#529)
## Link to GitHub Issue, if one exists
n/a

## Description of change
Gitadora (pre-Delta) tends to soft lock, have graphical issues (menu bg
render as black), or take a long time (minutes) between scene
transitions when window is resized.
2026-01-22 00:51:12 -08:00
bicarus 9fd9290cd3 gitadora: option for guitar picking algorithm (#528)
## Link to GitHub Issue, if one exists
n/a

## Description of change
Adds an option to pick from four algorithms for guitar picking input.

The default behavior is being switched from legacy (rising edge only,
always 1 frame of input) to SOCD "prefer recent" algorithm. This makes
it slightly easier to navigate the menu with the guitar.

## Testing
Validated gw, gwdelta, and sdvx just in case.
2026-01-21 21:14:28 -08:00
bicarus-dev 1dd99aac74 update error message for module auto detection failure 2026-01-20 18:04:54 -08:00
bicarus 9e376f373e patcher: load auto-enable settings even if dest/spec/rev don't match (#527)
## Link to GitHub Issue, if one exists
n/a

## Description of change
Before this change, if user changes `dest`/`spec`/`rev`, then the "auto
enable patches" settings doesn't get loaded. This is because the patch
manager config file relies on having an exact match of
`model+dest+spec+rev+ext`.

This causes a lot of confusion for:

* people who edit prop files - e.g., to switch between sdvx nemsys/valk
cab modes - and forget to re-enable patches
* people who use batch scripts to change xml files on launch - e.g., for
gitadora, omnimix, etc - even if they checked auto-apply in spicecfg,
when they use the batch script to launch the game, the setting won't be
loaded

This PR solves this by just ignoring the `dest`/`spec`/`rev` fields when
checking if patches should be auto applied on launch. Yes, this may
cause false positive cases, but probably very unlikely, and it's pretty
harmless anyway.

For future consideration: should we fix up gitadora XG disparity (K32 vs
K33 etc)?

## Testing
Manually validated
2026-01-20 04:00:15 -08:00
bicarus d4e51b77d1 overlay: options tab tooltip formatting consistency (#526) 2026-01-20 00:24:40 -08:00
bicarus 61181836b0 nvapi: set refresh rate / framerate settings (#525) 2026-01-20 00:23:14 -08:00
bicarus 4e26c8afa5 drs: rgb camera hook (#524)
## Link to GitHub Issue, if one exists
n/a

## Description of change
Add plug&play hooks for RGB camera in DRS.

## Testing
Seems to work with my crappy Logitech C270

Camera options (brightness etc) seem to work in test menu as well).

Tested by another user with realsense camera + logitech camera, both
cams were detected correctly.
2026-01-19 02:54:01 -08:00
bicarus 7684028f99 io: fix bi2x_hook implementation in various games (#522)
## Link to GitHub Issue, if one exists
similar issue as #499

## Description of change
Fix broken bi2x_hook implementations that did not properly allocate I/O
buffers with correct sizes.

People keep copy-pasting other implementation that allocate 0 bytes of
memory, but we run the risk of memory corruption or/or game crashes if
we keep doing this.

We've seen some mystery crashes with CCJ for example, maybe this will
fix that.

While I'm here, implement SOCD cleaner for Polaris Chord.

## Testing
Tested I/O menu with CCJ, play through tutorial (didn't test matches)
Tested GWDelta (test menu and a few songs in guitar)
Tested PC (played a credit)
Tested QKS (still can't get past the title screen)
2026-01-18 18:06:15 -08:00
bicarus-dev 1d6be8fe4b fix print message 2026-01-17 20:24:18 -08:00
bicarus c70cf40af3 graphics: fix monitor auto-rotate for reflec beat (#521)
## Link to GitHub Issue, if one exists
Fixes #341

## Description of change
fix monitor auto-rotate for reflec beat

## Testing
Tested KBR, LBR, MBR (volzza2), MBR (reflesia)
2026-01-17 19:57:47 -08:00
bicarus 5715d4e605 gitadora: (arena model) override app-config (#520)
## Link to GitHub Issue, if one exists
#477 

## Description of change
Just like the old `sys_code_get_cmdline`, override command line
parameters in `app-config*` so that we can control windowed mode / full
screen.

## Testing
Tested both GF and DM.

Technically both games can boot into the correct mode using `-d -DM` but
we'll try to use the right one just in case.
2026-01-17 17:49:50 -08:00
bicarus 821a705a7d gitadora: (arena model) single window mode + subscreen overlay (#486)
## Link to GitHub Issue, if one exists
#477 

## Description of change
This is a work in progress.

What works:
- [x] subscreen overlay shows the touch screen image (in windowed mode)
- [x] touch works in the overlay
- [x] only the main window is shown, other windows are not launched

What doesn't:
- [ ] game occasionally crashes when entering test menu?
- [ ] full screen mode is not tested at all due to monitor requirements
- [ ] need more polish (various options to control the behavior)
2026-01-17 02:17:33 -08:00
bicarus-dev 86f2a1fd39 remove asio failure message 2026-01-14 10:05:54 -08:00
bicarus-dev 86f218329a fix typo 2026-01-14 01:07:46 -08:00
Isaac Drew 66094de38a Add coin support for SDVX BI2X (#518)
## Description of change
Add coin handling to SDVX BI2X. I also tried to populate the structs as
best I could.

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

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

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

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

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

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

## Description of change
Implement `WrappedIAudioClient3`.

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

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

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

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

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

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

## Description of change
Use fmt library for formatting time

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

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

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

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

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

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

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

The warning label will be kept with slightly reworded text.

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

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

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

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

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

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

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

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

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

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

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

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

## Description of change
Two issues:

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

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

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

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

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

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

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

Gitadora Arena will receive 250% by default.

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

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

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

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

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

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

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

## Testing
Verified in terminal.
2025-12-31 01:15:19 -08:00
103 changed files with 4998 additions and 2609 deletions
+10 -3
View File
@@ -1,8 +1,7 @@
## Contributing ## Contributing
Baseline rules for patch submissions are as follows. Any patches violating the rules below will not be accepted. Baseline rules for patch submissions are as follows. Any patches violating the rules below will not be accepted.
### New as of March 2025 To contribute, fork the repo (just the main branch), make changes in your fork, and contribute to upstream by opening a pull request to the main repo.
Pull requests in GitHub are open. You don't need to bother with patch file submissions anymore. To contribute, fork the repo (just the main branch), make changes in your fork, and contribute to upstream by opening a pull request to the main repo.
### Adding support for games ### Adding support for games
@@ -12,6 +11,14 @@ Pull requests in GitHub are open. You don't need to bother with patch file submi
* For a new game that was never supported (not a new version of a game, but rather a new series): please wait 1 year after official AC release in Japan. * For a new game that was never supported (not a new version of a game, but rather a new series): please wait 1 year after official AC release in Japan.
* For new version of an already supported game: proceed with caution and use generally-accepted community guidelines. * For new version of an already supported game: proceed with caution and use generally-accepted community guidelines.
### Forbidden "features" that will never be accepted
* Performing decryption of encrypted files
* Including (bundling), distributing, or linking to game data, or making it easier to find/download game data
* Any features that [phone home](https://en.wikipedia.org/wiki/Phoning_home)
* This includes automatic updaters.
* Exception: we have allowed the "patch import from URL" feature, but this has only been done under very careful considerations and plenty of warnings to the user.
### Avoiding regressions ### Avoiding regressions
The biggest risk in making code changes to spice is the risk of regressions. There is a **lot** of shared code used by many different games, reaching back game versions that are more than a decade old. It is practically impossible to test all supported games and versions. The biggest risk in making code changes to spice is the risk of regressions. There is a **lot** of shared code used by many different games, reaching back game versions that are more than a decade old. It is practically impossible to test all supported games and versions.
@@ -22,7 +29,7 @@ Therefore, when making code changes, please be extremely careful about containin
#### Config file compatibility #### Config file compatibility
Do not change the names of options, buttons binds, analogs, etc - since they are used as unique identifiers in config files. If they are changed, you will introduce an incompatibility with previous versions of config file. Do not change the names of buttons binds, analogs, strings for command line parameters, etc - since they are used as unique identifiers in config files. If they are changed, you will introduce an incompatibility with previous versions of config file.
### Code quality requirements ### Code quality requirements
+5 -2
View File
@@ -170,8 +170,8 @@ else()
set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -DNDEBUG") set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -DNDEBUG")
# file prefix map for relative working directory # file prefix map for relative working directory
set(CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE} -ffile-prefix-map=${CMAKE_SOURCE_DIR}=.") set(CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE} -ffile-prefix-map=\"${CMAKE_SOURCE_DIR}=.\"")
set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -ffile-prefix-map=${CMAKE_SOURCE_DIR}=.") set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -ffile-prefix-map=\"${CMAKE_SOURCE_DIR}=.\"")
endif() endif()
# release with debug info flags # release with debug info flags
@@ -441,6 +441,7 @@ set(SOURCE_FILES ${SOURCE_FILES}
games/scotto/io.cpp games/scotto/io.cpp
games/drs/drs.cpp games/drs/drs.cpp
games/drs/io.cpp games/drs/io.cpp
games/drs/rgb_cam.cpp
games/we/we.cpp games/we/we.cpp
games/we/io.cpp games/we/io.cpp
games/we/touchpanel.cpp games/we/touchpanel.cpp
@@ -554,6 +555,7 @@ set(SOURCE_FILES ${SOURCE_FILES}
overlay/windows/camera_control.cpp overlay/windows/camera_control.cpp
overlay/windows/card_manager.cpp overlay/windows/card_manager.cpp
overlay/windows/drs_dancefloor.cpp overlay/windows/drs_dancefloor.cpp
overlay/windows/gfdm_sub.cpp
overlay/windows/screen_resize.cpp overlay/windows/screen_resize.cpp
overlay/windows/sdvx_sub.cpp overlay/windows/sdvx_sub.cpp
overlay/windows/config.cpp overlay/windows/config.cpp
@@ -624,6 +626,7 @@ set(SOURCE_FILES ${SOURCE_FILES}
util/execexe.cpp util/execexe.cpp
util/dependencies.cpp util/dependencies.cpp
util/deferlog.cpp util/deferlog.cpp
util/socd_cleaner.cpp
) )
source_group(TREE ${CMAKE_CURRENT_SOURCE_DIR} PREFIX "Source Files" FILES ${SOURCE_FILES}) source_group(TREE ${CMAKE_CURRENT_SOURCE_DIR} PREFIX "Source Files" FILES ${SOURCE_FILES})
+30 -6
View File
@@ -9,11 +9,22 @@
#include "games/drs/drs.h" #include "games/drs/drs.h"
#include "misc/eamuse.h" #include "misc/eamuse.h"
#include "util/logging.h" #include "util/logging.h"
#include "util/socd_cleaner.h"
#include "util/time.h"
#include "util/utils.h" #include "util/utils.h"
#include "util/tapeled.h" #include "util/tapeled.h"
using namespace GameAPI; using namespace GameAPI;
#define DEBUG_VERBOSE 0
#if DEBUG_VERBOSE
#define log_debug(module, format_str, ...) logger::push( \
LOG_FORMAT("M", module, format_str, ## __VA_ARGS__), logger::Style::GREY)
#else
#define log_debug(module, format_str, ...)
#endif
// state // state
static uint8_t STATUS_BUFFER[272] {}; static uint8_t STATUS_BUFFER[272] {};
static bool STATUS_BUFFER_FREEZE = false; static bool STATUS_BUFFER_FREEZE = false;
@@ -94,18 +105,25 @@ static bool __cdecl ac_io_bi2a_update_control_status_buffer() {
} }
// volume left // volume left
if (Buttons::getState(RI_MGR, buttons.at(games::sdvx::Buttons::VOL_L_Left))) { const auto now = get_performance_milliseconds();
const auto vol_l_state = socd::socd_clean(0,
Buttons::getState(RI_MGR, buttons.at(games::sdvx::Buttons::VOL_L_Left)),
Buttons::getState(RI_MGR, buttons.at(games::sdvx::Buttons::VOL_L_Right)),
now);
if (vol_l_state == socd::SocdCCW) {
BI2A_VOLL = (BI2A_VOLL - games::sdvx::DIGITAL_KNOB_SENS) & 1023; BI2A_VOLL = (BI2A_VOLL - games::sdvx::DIGITAL_KNOB_SENS) & 1023;
} } else if (vol_l_state == socd::SocdCW) {
if (Buttons::getState(RI_MGR, buttons.at(games::sdvx::Buttons::VOL_L_Right))) {
BI2A_VOLL = (BI2A_VOLL + games::sdvx::DIGITAL_KNOB_SENS) & 1023; BI2A_VOLL = (BI2A_VOLL + games::sdvx::DIGITAL_KNOB_SENS) & 1023;
} }
// volume right // volume right
if (Buttons::getState(RI_MGR, buttons.at(games::sdvx::Buttons::VOL_R_Left))) { const auto vol_r_state = socd::socd_clean(1,
Buttons::getState(RI_MGR, buttons.at(games::sdvx::Buttons::VOL_R_Left)),
Buttons::getState(RI_MGR, buttons.at(games::sdvx::Buttons::VOL_R_Right)),
now);
if (vol_r_state == socd::SocdCCW) {
BI2A_VOLR = (BI2A_VOLR - games::sdvx::DIGITAL_KNOB_SENS) & 1023; BI2A_VOLR = (BI2A_VOLR - games::sdvx::DIGITAL_KNOB_SENS) & 1023;
} } else if (vol_r_state == socd::SocdCW) {
if (Buttons::getState(RI_MGR, buttons.at(games::sdvx::Buttons::VOL_R_Right))) {
BI2A_VOLR = (BI2A_VOLR + games::sdvx::DIGITAL_KNOB_SENS) & 1023; BI2A_VOLR = (BI2A_VOLR + games::sdvx::DIGITAL_KNOB_SENS) & 1023;
} }
@@ -127,6 +145,12 @@ static bool __cdecl ac_io_bi2a_update_control_status_buffer() {
// save volumes in buffer // save volumes in buffer
*((uint16_t*) &STATUS_BUFFER[17]) = (uint16_t) ((vol_left) << 2); *((uint16_t*) &STATUS_BUFFER[17]) = (uint16_t) ((vol_left) << 2);
*((uint16_t*) &STATUS_BUFFER[19]) = (uint16_t) ((vol_right) << 2); *((uint16_t*) &STATUS_BUFFER[19]) = (uint16_t) ((vol_right) << 2);
log_debug(
"bi2a",
"knobs = {} {}",
*((uint16_t*) &STATUS_BUFFER[17]),
*((uint16_t*) &STATUS_BUFFER[19]));
} }
// DanceDanceRevolution // DanceDanceRevolution
+5 -5
View File
@@ -52,7 +52,7 @@ static bool __cdecl ac_io_j32d_update_control_status_buffer(size_t a1) {
float pad3_vel = Buttons::getVelocity(RI_MGR, buttons.at(games::ftt::Buttons::Pad3)); float pad3_vel = Buttons::getVelocity(RI_MGR, buttons.at(games::ftt::Buttons::Pad3));
float pad4_vel = Buttons::getVelocity(RI_MGR, buttons.at(games::ftt::Buttons::Pad4)); float pad4_vel = Buttons::getVelocity(RI_MGR, buttons.at(games::ftt::Buttons::Pad4));
// FIXME(felix): this logic seems wrong for analog handling but correct for digital inputs // FIXME(felix): this logic seems wrong for analog handling but correct for digital inputs
if (pad1_vel > 0.f) { if (Buttons::getState(RI_MGR, buttons.at(games::ftt::Buttons::Pad1))) {
STATUS_BUFFER[6] = (int) (51.f * pad1_vel + 0.5f); STATUS_BUFFER[6] = (int) (51.f * pad1_vel + 0.5f);
} }
if (Buttons::getState(RI_MGR, buttons.at(games::ftt::Buttons::Pad2))) { if (Buttons::getState(RI_MGR, buttons.at(games::ftt::Buttons::Pad2))) {
@@ -67,10 +67,10 @@ static bool __cdecl ac_io_j32d_update_control_status_buffer(size_t a1) {
// process analogs // process analogs
auto &analogs = games::ftt::get_analogs(); auto &analogs = games::ftt::get_analogs();
auto pad1_analog = analogs.at(games::ftt::Analogs::Pad1); auto &pad1_analog = analogs.at(games::ftt::Analogs::Pad1);
auto pad2_analog = analogs.at(games::ftt::Analogs::Pad2); auto &pad2_analog = analogs.at(games::ftt::Analogs::Pad2);
auto pad3_analog = analogs.at(games::ftt::Analogs::Pad3); auto &pad3_analog = analogs.at(games::ftt::Analogs::Pad3);
auto pad4_analog = analogs.at(games::ftt::Analogs::Pad4); auto &pad4_analog = analogs.at(games::ftt::Analogs::Pad4);
if (pad1_analog.isSet()) { if (pad1_analog.isSet()) {
auto val = (uint32_t) (51.f * Analogs::getState(RI_MGR, pad1_analog) + 0.5f); auto val = (uint32_t) (51.f * Analogs::getState(RI_MGR, pad1_analog) + 0.5f);
STATUS_BUFFER[6] = MAX(STATUS_BUFFER[6], val); STATUS_BUFFER[6] = MAX(STATUS_BUFFER[6], val);
+26 -6
View File
@@ -8,10 +8,21 @@
#include "games/sdvx/io.h" #include "games/sdvx/io.h"
#include "misc/eamuse.h" #include "misc/eamuse.h"
#include "rawinput/rawinput.h" #include "rawinput/rawinput.h"
#include "util/socd_cleaner.h"
#include "util/time.h"
#include "util/utils.h" #include "util/utils.h"
using namespace GameAPI; using namespace GameAPI;
#define DEBUG_VERBOSE 0
#if DEBUG_VERBOSE
#define log_debug(module, format_str, ...) logger::push( \
LOG_FORMAT("M", module, format_str, ## __VA_ARGS__), logger::Style::GREY)
#else
#define log_debug(module, format_str, ...)
#endif
// globals // globals
uint8_t KFCA_VOL_SOUND = 96; uint8_t KFCA_VOL_SOUND = 96;
uint8_t KFCA_VOL_HEADPHONE = 96; uint8_t KFCA_VOL_HEADPHONE = 96;
@@ -342,18 +353,25 @@ static char __cdecl ac_io_kfca_update_control_status_buffer() {
} }
// volume left // volume left
if (Buttons::getState(RI_MGR, buttons.at(games::sdvx::Buttons::VOL_L_Left))) { const auto now = get_performance_milliseconds();
const auto vol_l_state = socd::socd_clean(0,
Buttons::getState(RI_MGR, buttons.at(games::sdvx::Buttons::VOL_L_Left)),
Buttons::getState(RI_MGR, buttons.at(games::sdvx::Buttons::VOL_L_Right)),
now);
if (vol_l_state == socd::SocdCCW) {
KFCA_VOLL = (KFCA_VOLL - games::sdvx::DIGITAL_KNOB_SENS) & 1023; KFCA_VOLL = (KFCA_VOLL - games::sdvx::DIGITAL_KNOB_SENS) & 1023;
} } else if (vol_l_state == socd::SocdCW) {
if (Buttons::getState(RI_MGR, buttons.at(games::sdvx::Buttons::VOL_L_Right))) {
KFCA_VOLL = (KFCA_VOLL + games::sdvx::DIGITAL_KNOB_SENS) & 1023; KFCA_VOLL = (KFCA_VOLL + games::sdvx::DIGITAL_KNOB_SENS) & 1023;
} }
// volume right // volume right
if (Buttons::getState(RI_MGR, buttons.at(games::sdvx::Buttons::VOL_R_Left))) { const auto vol_r_state = socd::socd_clean(1,
Buttons::getState(RI_MGR, buttons.at(games::sdvx::Buttons::VOL_R_Left)),
Buttons::getState(RI_MGR, buttons.at(games::sdvx::Buttons::VOL_R_Right)),
now);
if (vol_r_state == socd::SocdCCW) {
KFCA_VOLR = (KFCA_VOLR - games::sdvx::DIGITAL_KNOB_SENS) & 1023; KFCA_VOLR = (KFCA_VOLR - games::sdvx::DIGITAL_KNOB_SENS) & 1023;
} } else if (vol_r_state == socd::SocdCW) {
if (Buttons::getState(RI_MGR, buttons.at(games::sdvx::Buttons::VOL_R_Right))) {
KFCA_VOLR = (KFCA_VOLR + games::sdvx::DIGITAL_KNOB_SENS) & 1023; KFCA_VOLR = (KFCA_VOLR + games::sdvx::DIGITAL_KNOB_SENS) & 1023;
} }
@@ -372,6 +390,8 @@ static char __cdecl ac_io_kfca_update_control_status_buffer() {
vol_left %= 1024; vol_left %= 1024;
vol_right %= 1024; vol_right %= 1024;
log_debug("kfca", "knobs = {} {}", vol_left, vol_right);
// save volumes in buffer // save volumes in buffer
STATUS_BUFFER[input_offset + 16 + 0] |= (unsigned char) ((vol_left << 6) & 0xFF); STATUS_BUFFER[input_offset + 16 + 0] |= (unsigned char) ((vol_left << 6) & 0xFF);
STATUS_BUFFER[input_offset + 16 + 1] |= (unsigned char) ((vol_left >> 2) & 0xFF); STATUS_BUFFER[input_offset + 16 + 1] |= (unsigned char) ((vol_left >> 2) & 0xFF);
+5 -11
View File
@@ -2,6 +2,7 @@
#include "acio/icca/icca.h" #include "acio/icca/icca.h"
#include "avs/game.h" #include "avs/game.h"
#include "games/sdvx/sdvx.h"
#include "misc/eamuse.h" #include "misc/eamuse.h"
#include "util/logging.h" #include "util/logging.h"
#include "util/utils.h" #include "util/utils.h"
@@ -101,13 +102,10 @@ bool ICCADevice::parse_msg(MessageData *msg_in,
// send version data // send version data
auto msg = this->create_msg(msg_in, MSG_VERSION_SIZE); auto msg = this->create_msg(msg_in, MSG_VERSION_SIZE);
if ( if (avs::game::is_model({"LDJ", "TBS", "UJK", "XIF"}) ||
avs::game::is_model({"LDJ", "TBS", "UJK", "XIF"}) || games::sdvx::is_valkyrie_model()) {
// SDVX Valkyrie cabinet mode
(avs::game::is_model("KFC") && (avs::game::SPEC[0] == 'G' || avs::game::SPEC[0] == 'H'))
) {
this->set_version(msg, 0x3, 0, 1, 7, 0, "ICCA"); this->set_version(msg, 0x3, 0, 1, 7, 0, "ICCA");
} else if (avs::game::is_model({"VFG"})) { } else if (avs::game::is_model("VFG")) {
this->set_version(msg, 0x3, 0, 1, 7, 0, "ICCB"); this->set_version(msg, 0x3, 0, 1, 7, 0, "ICCB");
} else { } else {
this->set_version(msg, 0x3, 0, 1, 6, 0, "ICCA"); this->set_version(msg, 0x3, 0, 1, 6, 0, "ICCA");
@@ -520,11 +518,7 @@ void ICCADevice::update_status(int unit) {
buffer[1] = felica ? 0x01 : 0x00; buffer[1] = felica ? 0x01 : 0x00;
buffer[10] = felica ? 0x01 : 0x00; buffer[10] = felica ? 0x01 : 0x00;
} else if ( } else if (avs::game::is_model({"LDJ", "TBS", "XIF"}) || games::sdvx::is_valkyrie_model()) {
avs::game::is_model({"LDJ", "TBS", "XIF"}) ||
// SDVX Valkyrie cabinet mode
(avs::game::is_model("KFC") && (avs::game::SPEC[0] == 'G' || avs::game::SPEC[0] == 'H'))) {
// set status to 0 otherwise reader power on fails // set status to 0 otherwise reader power on fails
buffer[0] = 0x00; buffer[0] = 0x00;
} else { } else {
+2
View File
@@ -1234,6 +1234,7 @@ namespace avs {
break; break;
case 'F': case 'F':
new_style = logger::Style::RED; new_style = logger::Style::RED;
deferredlogs::report_fatal_message();
break; break;
default: default:
position = 0; position = 0;
@@ -1302,6 +1303,7 @@ namespace avs {
break; break;
case 'F': case 'F':
style = logger::Style::RED; style = logger::Style::RED;
deferredlogs::report_fatal_message();
break; break;
case 'W': case 'W':
style = logger::Style::YELLOW; style = logger::Style::YELLOW;
+24 -1
View File
@@ -5,8 +5,9 @@
#include "build/defs.h" #include "build/defs.h"
#include "easrv/easrv.h" #include "easrv/easrv.h"
#include "easrv/smartea.h" #include "easrv/smartea.h"
#include "games/mfc/mfc.h" #include "games/gitadora/gitadora.h"
#include "hooks/avshook.h" #include "hooks/avshook.h"
#include "hooks/graphics/graphics.h"
#include "util/detour.h" #include "util/detour.h"
#include "util/deferlog.h" #include "util/deferlog.h"
#include "util/fileutils.h" #include "util/fileutils.h"
@@ -528,6 +529,28 @@ namespace avs {
} }
} }
// update -WINDOW in app-config
if (games::gitadora::is_arena_model()) {
avs::core::property_search_remove_safe(app_config, nullptr, "/param/cmdline");
std::string cmdline{};
if (games::gitadora::is_drum()) {
if (GRAPHICS_WINDOWED) {
cmdline = "-d -DM -WINDOW";
} else {
cmdline = "-d -DM";
}
} else {
if (GRAPHICS_WINDOWED) {
cmdline = "-g -GF -WINDOW";
} else {
cmdline = "-g -GF";
}
}
log_info("avs-ea3", "using cmdline: `{}`", cmdline);
avs::core::property_node_create(app_config, nullptr,
avs::core::NODE_TYPE_str, "/param/cmdline", cmdline.c_str());
}
auto app_param = avs::core::property_search_safe(app_config, nullptr, "/param"); auto app_param = avs::core::property_search_safe(app_config, nullptr, "/param");
// call the game init // call the game init
+7 -7
View File
@@ -74,7 +74,7 @@ then
BUILD_TYPE="Debug" BUILD_TYPE="Debug"
BUILDDIR_32=${BUILDDIR_32_DEBUG} BUILDDIR_32=${BUILDDIR_32_DEBUG}
BUILDDIR_64=${BUILDDIR_64_DEBUG} BUILDDIR_64=${BUILDDIR_64_DEBUG}
DIST_NAME=$(echo ${DIST_NAME} | sed 's/\.[^.]*$/-dbg&/') DIST_NAME=$(echo "${DIST_NAME}" | sed 's/\.[^.]*$/-dbg&/')
fi fi
# determine number of cores # determine number of cores
@@ -114,7 +114,7 @@ time (
fi fi
mkdir -p ${BUILDDIR_32} mkdir -p ${BUILDDIR_32}
pushd ${BUILDDIR_32} > /dev/null pushd ${BUILDDIR_32} > /dev/null
cmake -G "Ninja" -DCMAKE_TOOLCHAIN_FILE=${TOOLCHAIN_32} -DCMAKE_BUILD_TYPE=${BUILD_TYPE} $OLDPWD && ninja ${TARGETS_32} cmake -G "Ninja" -DCMAKE_TOOLCHAIN_FILE=${TOOLCHAIN_32} -DCMAKE_BUILD_TYPE=${BUILD_TYPE} "$OLDPWD" && ninja ${TARGETS_32}
popd > /dev/null popd > /dev/null
# 64 bit # 64 bit
@@ -127,7 +127,7 @@ time (
fi fi
mkdir -p ${BUILDDIR_64} mkdir -p ${BUILDDIR_64}
pushd ${BUILDDIR_64} > /dev/null pushd ${BUILDDIR_64} > /dev/null
cmake -G "Ninja" -DCMAKE_TOOLCHAIN_FILE=${TOOLCHAIN_64} -DCMAKE_BUILD_TYPE=${BUILD_TYPE} $OLDPWD && ninja ${TARGETS_64} cmake -G "Ninja" -DCMAKE_TOOLCHAIN_FILE=${TOOLCHAIN_64} -DCMAKE_BUILD_TYPE=${BUILD_TYPE} "$OLDPWD" && ninja ${TARGETS_64}
popd > /dev/null popd > /dev/null
echo "" echo ""
@@ -234,7 +234,7 @@ mkdir -p ${OUTDIR}/src
if ((INCLUDE_SRC > 0)) if ((INCLUDE_SRC > 0))
then then
echo "Generating source file archive..." echo "Generating source file archive..."
git archive --format tar.gz --prefix=spice2x/ HEAD ./ > ${OUTDIR}/src/spice2x-${GIT_BRANCH}.tar.gz 2>/dev/null || \ git archive --format tar.gz --prefix=spice2x/ HEAD ./ > "${OUTDIR}/src/spice2x-${GIT_BRANCH}.tar.gz" 2>/dev/null || \
echo "WARNING: Couldn't get git to create the archive. Is this a git repository?" echo "WARNING: Couldn't get git to create the archive. Is this a git repository?"
fi fi
@@ -249,11 +249,11 @@ if ((DIST_ENABLE > 0))
then then
echo "Building dist..." echo "Building dist..."
mkdir -p ${DIST_FOLDER} mkdir -p ${DIST_FOLDER}
rm -rf ${DIST_FOLDER}/${DIST_NAME} rm -rf "${DIST_FOLDER}/${DIST_NAME}"
pushd ${OUTDIR}/.. > /dev/null pushd ${OUTDIR}/.. > /dev/null
zip -qrXT9 $OLDPWD/${DIST_FOLDER}/${DIST_NAME} spice2x -x "spice2x/extras/*" -z <<< "$DIST_COMMENT" zip -qrXT9 "$OLDPWD/${DIST_FOLDER}/${DIST_NAME}" spice2x -x "spice2x/extras/*" -z <<< "$DIST_COMMENT"
echo "Building extras..." echo "Building extras..."
zip -qrXT9 $OLDPWD/${DIST_FOLDER}/${DIST_NAME_EXTRAS} spice2x -z <<< "$DIST_COMMENT" zip -qrXT9 "$OLDPWD/${DIST_FOLDER}/${DIST_NAME_EXTRAS}" spice2x -z <<< "$DIST_COMMENT"
popd > /dev/null popd > /dev/null
fi fi
+3 -3
View File
@@ -1,4 +1,4 @@
#!/bin/bash #!/bin/bash
docker build --pull $PWD/external/docker -t spicetools/deps --platform linux/x86_64 docker build --pull "$PWD/external/docker" -t spicetools/deps --platform linux/x86_64
docker build --build-context gitroot=$PWD/../../.git . -t spicetools/spice:latest docker build --build-context gitroot="$PWD/../../.git" . -t spicetools/spice:latest
docker run --rm -v $PWD/dist:/src/src/spice2x/dist -v $PWD/bin:/src/src/spice2x/bin -v $PWD/.ccache:/src/src/spice2x/.ccache spicetools/spice "$@" docker run --rm -v "$PWD/dist:/src/src/spice2x/dist" -v "$PWD/bin:/src/src/spice2x/bin" -v "$PWD/.ccache:/src/src/spice2x/.ccache" spicetools/spice "$@"
+6 -4
View File
@@ -644,10 +644,12 @@ float GameAPI::Analogs::getState(rawinput::Device *device, Analog &analog) {
case rawinput::HID: { case rawinput::HID: {
// get value // get value
if (inverted) { if (index < device->hidInfo->value_states.size()) {
value = 1.f - device->hidInfo->value_states[index]; if (inverted) {
} else { value = 1.f - device->hidInfo->value_states[index];
value = device->hidInfo->value_states[index]; } else {
value = device->hidInfo->value_states[index];
}
} }
// deadzone // deadzone
+33 -13
View File
@@ -58,8 +58,28 @@ std::string Button::getVKeyString() {
return "Pause"; return "Pause";
case 0x14: case 0x14:
return "Caps Lock"; return "Caps Lock";
case 0x15:
return "IME Kana/Hangul";
case 0x16:
return "IME On";
case 0x17:
return "IME Junja";
case 0x18:
return "IME Final";
case 0x19:
return "IME Hanja/Kanji";
case 0x1A:
return "IME Off";
case 0x1B: case 0x1B:
return "Escape"; return "Escape";
case 0x1C:
return "IME Convert";
case 0x1D:
return "IME Nonconvert";
case 0x1E:
return "IME Accept";
case 0x1F:
return "IME Mode Change";
case 0x20: case 0x20:
return "Space"; return "Space";
case 0x21: case 0x21:
@@ -255,9 +275,9 @@ std::string Button::getVKeyString() {
case 0xA3: case 0xA3:
return "Right Control"; return "Right Control";
case 0xA4: case 0xA4:
return "Left Menu"; return "Left Alt";
case 0xA5: case 0xA5:
return "Right Menu"; return "Right Alt";
default: default:
// check win API // check win API
@@ -301,17 +321,17 @@ std::string Button::getDisplayString(rawinput::RawInputManager* manager) {
switch (device->type) { switch (device->type) {
case rawinput::MOUSE: { case rawinput::MOUSE: {
const char *btn = "Unknown"; const char *btn = "Unknown";
static const char *MOUSE_NAMES[] = { static const std::array<const char *, 8>MOUSE_NAMES = {
"Left Mouse", "Left Mouse",
"Right Mouse", "Right Mouse",
"Middle Mouse", "Middle Mouse",
"Mouse 1", "Mouse 1",
"Mouse 2", "Mouse 2",
"Mouse 3", "Mouse 3",
"Mouse 4", "Mouse 4",
"Mouse 5", "Mouse 5",
}; };
if (vKey < sizeof(MOUSE_NAMES)) { if (vKey < MOUSE_NAMES.size()) {
btn = MOUSE_NAMES[vKey]; btn = MOUSE_NAMES[vKey];
} }
return fmt::format("{} ({})", btn, device->desc); return fmt::format("{} ({})", btn, device->desc);
+23
View File
@@ -45,6 +45,7 @@ private:
double debounce_up = 0.0; double debounce_up = 0.0;
double debounce_down = 0.0; double debounce_down = 0.0;
bool invert = false; bool invert = false;
bool is_temporary = false;
GameAPI::Buttons::State last_state = GameAPI::Buttons::BUTTON_NOT_PRESSED; GameAPI::Buttons::State last_state = GameAPI::Buttons::BUTTON_NOT_PRESSED;
float last_velocity = 0.f; float last_velocity = 0.f;
@@ -164,6 +165,14 @@ public:
this->last_velocity = last_velocity; this->last_velocity = last_velocity;
} }
inline bool isTemporary() const {
return this->is_temporary;
}
inline void setTemporary(bool is_temporary) {
this->is_temporary = is_temporary;
}
inline unsigned short getVelocityThreshold() const { inline unsigned short getVelocityThreshold() const {
return this->velocity_threshold; return this->velocity_threshold;
} }
@@ -172,6 +181,20 @@ public:
this->velocity_threshold = velocity_threshold; this->velocity_threshold = velocity_threshold;
} }
inline bool isValid() {
// temporarily created by UI
if (isTemporary()) {
return true;
}
// nothing is set
if (getDeviceIdentifier().empty() && getVKey() == INVALID_VKEY) {
return false;
}
return true;
}
void getMidiVKey(int& channel, int& index); void getMidiVKey(int& channel, int& index);
void setMidiVKey(rawinput::RawInputManager* manager, bool is_note, int channel, int index); void setMidiVKey(rawinput::RawInputManager* manager, bool is_note, int channel, int index);
+76 -1
View File
@@ -1,5 +1,6 @@
#include "config.h" #include "config.h"
#include "util/logging.h" #include "util/logging.h"
#include "cfg/button.h"
/* /*
* This code absolutely sucks. * This code absolutely sucks.
@@ -10,6 +11,14 @@
// settings // settings
std::string CONFIG_PATH_OVERRIDE = ""; std::string CONFIG_PATH_OVERRIDE = "";
#define CFG_DEBUG_VERBOSE 0
#if CFG_DEBUG_VERBOSE
#define log_debug(module, format_str, ...) logger::push( \
LOG_FORMAT("M", module, format_str, ## __VA_ARGS__), logger::Style::GREY)
#else
#define log_debug(module, format_str, ...)
#endif
/////////////////// ///////////////////
/// Constructor /// /// Constructor ///
@@ -510,8 +519,10 @@ bool Config::updateBinding(const Game &game, const Button &button, int alternati
return false; return false;
} }
tinyxml2::XMLElement *gameButtonNode = nullptr;
// iterate button nodes // iterate button nodes
tinyxml2::XMLElement *gameButtonNode = gameButtonsNode->FirstChildElement("button"); gameButtonNode = gameButtonsNode->FirstChildElement("button");
int button_count = 0; int button_count = 0;
while (gameButtonNode != nullptr) { while (gameButtonNode != nullptr) {
const char *buttonNodeName = gameButtonNode->Attribute("name"); const char *buttonNodeName = gameButtonNode->Attribute("name");
@@ -574,6 +585,39 @@ bool Config::updateBinding(const Game &game, const Button &button, int alternati
} }
} }
// trim the trailing invalid (unbound) alternatives
tinyxml2::XMLElement *to_delete = nullptr;
gameButtonNode = gameButtonsNode->LastChildElement("button");
while (gameButtonNode != nullptr) {
const char *name = gameButtonNode->Attribute("name");
const char *devid = gameButtonNode->Attribute("devid");
int vkey = INVALID_VKEY;
gameButtonNode->QueryIntAttribute("vkey", &vkey);
// wrong button, ignore and keep iterating backwards
if (name == nullptr || std::string(name) != button.getName()) {
gameButtonNode = gameButtonNode->PreviousSiblingElement("button");
continue;
}
// found a node for this button, which means to_delete is not the first one
// delete the node for to_delete
if (to_delete != nullptr) {
log_debug("cfg", "deleting excessive button binding for {}", name);
gameButtonsNode->DeleteChild(to_delete);
to_delete = nullptr;
}
if ((devid == nullptr || strlen(devid) == 0) && vkey == INVALID_VKEY) {
// unbound button with no device ID, make a note of it so it can be deleted later
to_delete = gameButtonNode;
gameButtonNode = gameButtonNode->PreviousSiblingElement("button");
} else {
// valid binding found; stop iterating
break;
}
}
// check if button was not found // check if button was not found
if (button_count == 0) { if (button_count == 0) {
return false; return false;
@@ -760,6 +804,37 @@ bool Config::updateBinding(const Game &game, const Light &light, int alternative
} }
} }
// trim the trailing invalid (unbound) alternatives
tinyxml2::XMLElement *to_delete = nullptr;
gameLightNode = gameLightsNode->LastChildElement("light");
while (gameLightNode != nullptr) {
const char *name = gameLightNode->Attribute("name");
const char *devid = gameLightNode->Attribute("devid");
// wrong light, ignore and keep iterating backwards
if (name == nullptr || std::string(name) != light.getName()) {
gameLightNode = gameLightNode->PreviousSiblingElement("light");
continue;
}
// found a node for this button, which means to_delete is not the first one
// delete the node for to_delete
if (to_delete != nullptr) {
log_debug("cfg", "deleting excessive light binding for {}", name);
gameLightsNode->DeleteChild(to_delete);
to_delete = nullptr;
}
if (devid == nullptr || strlen(devid) == 0) {
// unbound light, make a note of it so it can be deleted later
to_delete = gameLightNode;
gameLightNode = gameLightNode->PreviousSiblingElement("light");
} else {
// valid binding found; stop iterating
break;
}
}
// check if light was not found // check if light was not found
if (light_count == 0) { if (light_count == 0) {
return false; return false;
+23
View File
@@ -13,6 +13,7 @@ private:
std::string lightName; std::string lightName;
std::string deviceIdentifier = ""; std::string deviceIdentifier = "";
unsigned int index = 0; unsigned int index = 0;
bool is_temporary = false;
public: public:
float last_state = 0.f; float last_state = 0.f;
@@ -65,4 +66,26 @@ public:
inline void setIndex(unsigned int index) { inline void setIndex(unsigned int index) {
this->index = index; this->index = index;
} }
inline bool isTemporary() const {
return this->is_temporary;
}
inline void setTemporary(bool is_temporary) {
this->is_temporary = is_temporary;
}
inline bool isValid() {
// temporarily created by UI
if (isTemporary()) {
return true;
}
// nothing is set
if (getDeviceIdentifier().empty()) {
return false;
}
return true;
}
}; };
+6
View File
@@ -1,9 +1,11 @@
#include "screen_resize.h" #include "screen_resize.h"
#include "avs/game.h"
#include "external/rapidjson/document.h" #include "external/rapidjson/document.h"
#include "external/rapidjson/pointer.h" #include "external/rapidjson/pointer.h"
#include "external/rapidjson/prettywriter.h" #include "external/rapidjson/prettywriter.h"
#include "misc/eamuse.h" #include "misc/eamuse.h"
#include "util/deferlog.h"
#include "util/utils.h" #include "util/utils.h"
#include "util/fileutils.h" #include "util/fileutils.h"
#include "hooks/graphics/graphics.h" #include "hooks/graphics/graphics.h"
@@ -78,6 +80,10 @@ namespace cfg {
root); root);
load_bool_value(doc, root + "enable_screen_resize", this->enable_screen_resize); load_bool_value(doc, root + "enable_screen_resize", this->enable_screen_resize);
if (this->enable_screen_resize) {
log_misc("ScreenResize", "enabled by config file");
}
load_bool_value(doc, root + "enable_linear_filter", this->enable_linear_filter); load_bool_value(doc, root + "enable_linear_filter", this->enable_linear_filter);
for (size_t i = 0; i < std::size(this->scene_settings); i++) { for (size_t i = 0; i < std::size(this->scene_settings); i++) {
auto& scene = this->scene_settings[i]; auto& scene = this->scene_settings[i];
+5
View File
@@ -1,5 +1,6 @@
#pragma once #pragma once
#include <atomic>
#include <memory> #include <memory>
#include <string> #include <string>
#include <optional> #include <optional>
@@ -51,6 +52,9 @@ namespace cfg {
bool enable_linear_filter = true; bool enable_linear_filter = true;
fullscreen_setting scene_settings[4]; fullscreen_setting scene_settings[4];
// state
std::atomic<bool> need_surface_clean = false;
// windowed mode sizing // windowed mode sizing
// Windows terminology: // Windows terminology:
// window = rectangle including the frame // window = rectangle including the frame
@@ -69,6 +73,7 @@ namespace cfg {
uint32_t init_client_height = 0; uint32_t init_client_height = 0;
float init_client_aspect_ratio = 1.f; float init_client_aspect_ratio = 1.f;
uint32_t init_window_style = 0; uint32_t init_window_style = 0;
uint32_t init_window_style_ex = 0;
uint32_t window_deco_width = 0; uint32_t window_deco_width = 0;
uint32_t window_deco_height = 0; uint32_t window_deco_height = 0;
+14
View File
@@ -1,5 +1,7 @@
#include "bi2x_hook.h" #include "bi2x_hook.h"
#if SPICE64
#include <cstdint> #include <cstdint>
#include "util/detour.h" #include "util/detour.h"
#include "util/logging.h" #include "util/logging.h"
@@ -16,15 +18,19 @@ namespace games::ccj {
*/ */
struct AIO_SCI_COMM { struct AIO_SCI_COMM {
uint8_t data[0xf8];
}; };
struct AIO_NMGR_IOB2 { struct AIO_NMGR_IOB2 {
uint8_t data[0xe30];
}; };
struct AIO_IOB2_BI2X_TBS { struct AIO_IOB2_BI2X_TBS {
uint8_t data[0x4680];
}; };
struct AIO_IOB2_BI2X_WRFIRM { struct AIO_IOB2_BI2X_WRFIRM {
uint8_t data[0x20f48];
}; };
struct AIO_NMGR_IOB__NODEINFO { struct AIO_NMGR_IOB__NODEINFO {
@@ -72,6 +78,12 @@ namespace games::ccj {
AIO_IOB2_BI2X_AC1__OUTPUTDATA OutputData; AIO_IOB2_BI2X_AC1__OUTPUTDATA OutputData;
}; };
// checked with UJK-001-2024060400
static_assert(sizeof(AIO_SCI_COMM) == 0xf8);
static_assert(sizeof(AIO_NMGR_IOB2) == 0xe30);
static_assert(sizeof(AIO_IOB2_BI2X_TBS) == 0x4680);
static_assert(sizeof(AIO_IOB2_BI2X_WRFIRM) == 0x20f48);
static void write_iccr_led(Lights::ccj_lights_t light, uint8_t value); static void write_iccr_led(Lights::ccj_lights_t light, uint8_t value);
/* /*
@@ -622,3 +634,5 @@ namespace games::ccj {
aioNodeCtl_IsError, &aioNodeCtl_IsError_orig); aioNodeCtl_IsError, &aioNodeCtl_IsError_orig);
} }
} }
#endif
+4
View File
@@ -1,5 +1,9 @@
#pragma once #pragma once
#if SPICE64
namespace games::ccj { namespace games::ccj {
void bi2x_hook_init(); void bi2x_hook_init();
} }
#endif
+4
View File
@@ -68,9 +68,13 @@ namespace games::ccj {
detour::trampoline_try("execexe.dll", MAKEINTRESOURCE(11), detour::trampoline_try("execexe.dll", MAKEINTRESOURCE(11),
(void*)execexe_CreateFileW_hook,(void**)&execexe_CreateFileW_orig); (void*)execexe_CreateFileW_hook,(void**)&execexe_CreateFileW_orig);
#if SPICE64
// insert BI2X hooks // insert BI2X hooks
bi2x_hook_init(); bi2x_hook_init();
#endif
// insert trackball hooks // insert trackball hooks
trackball_hook_init(); trackball_hook_init();
+25 -10
View File
@@ -8,6 +8,7 @@
#include "hooks/setupapihook.h" #include "hooks/setupapihook.h"
#include "hooks/sleephook.h" #include "hooks/sleephook.h"
#include "hooks/input/dinput8/hook.h" #include "hooks/input/dinput8/hook.h"
#include "util/deferlog.h"
#include "util/utils.h" #include "util/utils.h"
#include "util/libutils.h" #include "util/libutils.h"
#include "util/fileutils.h" #include "util/fileutils.h"
@@ -108,18 +109,32 @@ namespace games::ddr {
log_info("ddr", "`{}` returned {}", cmd, result); log_info("ddr", "`{}` returned {}", cmd, result);
} else { } else {
log_warning("ddr", "`{}` failed, returned {}", cmd, result); log_warning("ddr", "`{}` failed, returned {}", cmd, result);
deferredlogs::defer_error_messages({
"DDR codec registration failure",
fmt::format(" {} failed to register with error {}", filename.string(), result)});
} }
if (!contains_only_ascii(file.path().string())) { static std::once_flag printed;
log_warning( std::call_once(printed, [file]() {
"ddr", if (!contains_only_ascii(file.path().string())) {
"BAD PATH ERROR\n\n\n" log_warning(
"!!! !!!\n" "ddr",
"!!! filesystem path to codec contains non-ASCII characters! !!!\n" "BAD PATH ERROR\n\n"
"!!! this may cause the game to crash! !!!\n" "!!! !!!\n"
"!!! !!!\n" "!!! filesystem path to codec contains non-ASCII characters! !!!\n"
); "!!! this may cause failures when loading codecs, and !!!\n"
} "!!! potentially leading the game to crash! !!!\n"
"!!! !!!\n"
);
deferredlogs::defer_error_messages({
"path to DDR codecs contain non-Latin characters",
" this may lead to movies not playing, white screen issue,",
" game hanging, or potentially a crash",
" move the game to a directory that does not contain non-Latin characters",
" if you encountered any issues"});
}
});
} }
} }
} }
+6
View File
@@ -8,6 +8,7 @@
#include "util/detour.h" #include "util/detour.h"
#include "util/logging.h" #include "util/logging.h"
#include "util/memutils.h" #include "util/memutils.h"
#include "rgb_cam.h"
#pragma pack(push) #pragma pack(push)
@@ -150,6 +151,7 @@ namespace games::drs {
char DRS_TAPELED[DRS_TAPELED_COLS_TOTAL][3] {}; char DRS_TAPELED[DRS_TAPELED_COLS_TOTAL][3] {};
bool DISABLE_TOUCH = false; bool DISABLE_TOUCH = false;
bool TRANSPOSE_TOUCH = false; bool TRANSPOSE_TOUCH = false;
bool RGB_CAMERA_HOOK = false;
std::vector<TouchEvent> TOUCH_EVENTS; std::vector<TouchEvent> TOUCH_EVENTS;
@@ -313,6 +315,10 @@ namespace games::drs {
} else { } else {
log_info("drs", "no native input method detected"); log_info("drs", "no native input method detected");
} }
if (RGB_CAMERA_HOOK) {
init_rgb_camera_hook();
}
} }
void DRSGame::detach() { void DRSGame::detach() {
+1
View File
@@ -31,6 +31,7 @@ namespace games::drs {
extern std::vector<TouchEvent> TOUCH_EVENTS; extern std::vector<TouchEvent> TOUCH_EVENTS;
extern bool DISABLE_TOUCH; extern bool DISABLE_TOUCH;
extern bool TRANSPOSE_TOUCH; extern bool TRANSPOSE_TOUCH;
extern bool RGB_CAMERA_HOOK;
void fire_touches(drs_touch_t *events, size_t event_count); void fire_touches(drs_touch_t *events, size_t event_count);
void start_touch(); void start_touch();
+170
View File
@@ -0,0 +1,170 @@
#include "games/drs/rgb_cam.h"
// set version to Windows 7 to enable Media Foundation functions
#ifdef _WIN32_WINNT
#undef _WIN32_WINNT
#endif
#define _WIN32_WINNT 0x0601
// turn on C-style COM objects
#define CINTERFACE
#include <mfobjects.h>
#include <mfidl.h>
#include <string>
#include "avs/game.h"
#include "cfg/configurator.h"
#include "hooks/cfgmgr32hook.h"
#include "util/detour.h"
#include "util/memutils.h"
#include "util/logging.h"
#include "util/utils.h"
static VTBL_TYPE(IMFActivate, GetAllocatedString) GetAllocatedString_orig = nullptr;
static decltype(MFEnumDeviceSources) *MFEnumDeviceSources_orig = nullptr;
namespace games::drs {
/*
* Camera related stuff
*/
static std::wstring CAMERA0_ID;
static HRESULT WINAPI GetAllocatedString_hook(IMFActivate* This, REFGUID guidKey, LPWSTR *ppwszValue,
UINT32 *pcchLength)
{
// call the original
HRESULT result = GetAllocatedString_orig(This, guidKey, ppwszValue, pcchLength);
// log the cam name
log_misc("drs::rgbcam", "obtained {}", ws2s(*ppwszValue));
// try first camera
wchar_t *pwc = nullptr;
if (CAMERA0_ID.length() == 23) {
pwc = wcsstr(*ppwszValue, CAMERA0_ID.c_str());
}
// check if camera could be identified
if (pwc) {
// fake the USB IDs
pwc[4] = L'2';
pwc[5] = L'8';
pwc[6] = L'8';
pwc[7] = L'c';
pwc[13] = L'0';
pwc[14] = L'0';
pwc[15] = L'0';
pwc[16] = L'2';
pwc[21] = L'0';
pwc[22] = L'0';
log_misc("drs::rgbcam", "replaced {}", ws2s(*ppwszValue));
}
// return original result
return result;
}
static void hook_camera(std::wstring camera_id, std::string camera_instance) {
// don't hook if camera 0 is already hooked
if (CAMERA0_ID.length() > 0) {
return;
}
// save the camera ID
CAMERA0_ID = camera_id;
// cfgmgr hook
CFGMGR32_HOOK_SETTING camera_setting;
camera_setting.device_instance = 0xDEADBEEF;
camera_setting.parent_instance = ~camera_setting.device_instance;
camera_setting.device_id = "USB\\VEN_8086&DEV_8C2D";
camera_setting.device_node_id = "USB\\VID_288C&PID_0002&MI_00\\?&????????&?&????";
if (camera_instance.length() == 17) {
for (int i = 0; i < 17; i++) {
camera_setting.device_node_id[28 + i] = camera_instance[i];
}
}
cfgmgr32hook_add(camera_setting);
log_info("drs::rgbcam", "hooked camera @ {}", ws2s(camera_id));
}
static void hook_camera_vtable(IMFActivate *camera) {
// hook allocated string method for camera identification
memutils::VProtectGuard camera_guard(camera->lpVtbl);
camera->lpVtbl->GetAllocatedString = GetAllocatedString_hook;
}
static HRESULT WINAPI MFEnumDeviceSources_hook(IMFAttributes *pAttributes, IMFActivate ***pppSourceActivate,
UINT32 *pcSourceActivate) {
// call original function
HRESULT result_orig = MFEnumDeviceSources_orig(pAttributes, pppSourceActivate, pcSourceActivate);
// check for capture devices
if (FAILED(result_orig) || !*pcSourceActivate) {
return result_orig;
}
// get camera
IMFActivate *camera = **pppSourceActivate;
// save original method for later use
if (GetAllocatedString_orig == nullptr) {
GetAllocatedString_orig = camera->lpVtbl->GetAllocatedString;
}
// hook allocated string method for camera identification
hook_camera_vtable(camera);
// get camera link
LPWSTR camera_link_lpwstr;
UINT32 camera_link_length;
if (SUCCEEDED(GetAllocatedString_orig(
camera,
MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE_VIDCAP_SYMBOLIC_LINK,
&camera_link_lpwstr,
&camera_link_length))) {
// cut name to make ID
std::wstring camera_link_ws = std::wstring(camera_link_lpwstr);
std::wstring camera_id = camera_link_ws.substr(8, 23);
log_info("drs::rgbcam", "found video capture device: {}", ws2s(camera_link_ws));
// only support cameras that start with \\?\usb
if (!string_begins_with(camera_link_ws, L"\\\\?\\usb")) {
return result_orig;
}
// get camera instance
std::string camera_link = ws2s(camera_link_ws);
std::string camera_instance = camera_link.substr(32, 17);
// hook the camera
hook_camera(camera_id, camera_instance);
} else {
log_warning("drs::rgbcam", "failed to open camera");
}
// return result
return result_orig;
}
void init_rgb_camera_hook() {
log_info("drs::rgbcam", "initializing camera hook");
cfgmgr32hook_init(avs::game::DLL_INSTANCE);
// camera media framework hook
MFEnumDeviceSources_orig = detour::iat_try(
"MFEnumDeviceSources", MFEnumDeviceSources_hook, avs::game::DLL_INSTANCE);
}
}
+5
View File
@@ -0,0 +1,5 @@
#pragma once
namespace games::drs {
void init_rgb_camera_hook();
}
+58
View File
@@ -1,5 +1,8 @@
#include "bi2x_hook.h" #include "bi2x_hook.h"
#if SPICE64
#include <optional>
#include <cstdint> #include <cstdint>
#include "util/detour.h" #include "util/detour.h"
#include "util/logging.h" #include "util/logging.h"
@@ -17,24 +20,29 @@ namespace games::gitadora {
// */ // */
struct AIO_NMGR_IOB2 { struct AIO_NMGR_IOB2 {
uint8_t data[0xe18];
}; };
struct AIO_IOB2_BI2X_AC1__SETTING { struct AIO_IOB2_BI2X_AC1__SETTING {
}; };
struct AIO_NMGR_IOB5 { struct AIO_NMGR_IOB5 {
uint8_t data[0xb10];
}; };
struct AIO_SCI_COMM { struct AIO_SCI_COMM {
uint8_t data[0x100];
}; };
struct AIO_IOB5_Y32D { struct AIO_IOB5_Y32D {
uint8_t data[0x2e8];
}; };
// struct AIO_IOB5_Y33I { // struct AIO_IOB5_Y33I {
// }; // };
struct AIO_IOB2_BI2X_AC1 { struct AIO_IOB2_BI2X_AC1 {
uint8_t data[0x4570];
}; };
struct AIO_NMGR_IOB__NODEINFO { struct AIO_NMGR_IOB__NODEINFO {
@@ -42,6 +50,7 @@ namespace games::gitadora {
}; };
struct AIO_IOB2_BI2X_WRFIRM { struct AIO_IOB2_BI2X_WRFIRM {
uint8_t data[0x20f48];
}; };
struct AIO_SCI_COMM_T { struct AIO_SCI_COMM_T {
@@ -158,6 +167,14 @@ namespace games::gitadora {
uint8_t unk[0xB1]; uint8_t unk[0xB1];
}; };
// verified with M32-007-2025092400
static_assert(sizeof(AIO_NMGR_IOB2) == 0xe18);
static_assert(sizeof(AIO_NMGR_IOB5) == 0xb10);
static_assert(sizeof(AIO_SCI_COMM) == 0x100);
static_assert(sizeof(AIO_IOB5_Y32D) == 0x2e8);
static_assert(sizeof(AIO_IOB2_BI2X_AC1) == 0x4570);
static_assert(sizeof(AIO_IOB2_BI2X_WRFIRM) == 0x20f48);
/* /*
* typedefs * typedefs
*/ */
@@ -327,6 +344,34 @@ namespace games::gitadora {
if (i_pNodeCtl != aioIob2Bi2xAc1) { if (i_pNodeCtl != aioIob2Bi2xAc1) {
return aioIob2Bi2xAC1_SetOutputData_orig(i_pNodeCtl, i_CnPin, i_Data); return aioIob2Bi2xAC1_SetOutputData_orig(i_pNodeCtl, i_CnPin, i_Data);
} }
std::optional<Lights::gitadora_lights_t> light;
switch (i_CnPin) {
case 14:
light = Lights::ArenaWooferR;
break;
case 15:
light = Lights::ArenaWooferG;
break;
case 16:
light = Lights::ArenaWooferB;
break;
case 17:
light = Lights::ArenaCardR;
break;
case 18:
light = Lights::ArenaCardG;
break;
case 19:
light = Lights::ArenaCardB;
break;
default:
break;
}
if (light.has_value()) {
auto &lights = games::gitadora::get_lights();
GameAPI::Lights::writeLight(RI_MGR, lights.at(light.value()), i_Data / 255.f);
}
} }
void __fastcall aioIob2Bi2xAC1_SetTapeLedDataPart(AIO_IOB2_BI2X_AC1 *i_pNodeCtl, uint32_t i_TapeLedCh, void __fastcall aioIob2Bi2xAC1_SetTapeLedDataPart(AIO_IOB2_BI2X_AC1 *i_pNodeCtl, uint32_t i_TapeLedCh,
@@ -336,6 +381,16 @@ namespace games::gitadora {
if (i_pNodeCtl != aioIob2Bi2xAc1) { if (i_pNodeCtl != aioIob2Bi2xAc1) {
return aioIob2Bi2xAC1_SetTapeLedDataPart_orig(i_pNodeCtl, i_TapeLedCh, i_Offset, i_pData, i_cntTapeLed, i_bReverse); return aioIob2Bi2xAC1_SetTapeLedDataPart_orig(i_pNodeCtl, i_TapeLedCh, i_Offset, i_pData, i_cntTapeLed, i_bReverse);
} }
if (tapeledutils::is_enabled() &&
i_TapeLedCh == 0 && i_Offset == 0 && i_cntTapeLed == 62) {
auto &lights = get_lights();
const auto rgb = tapeledutils::pick_color_from_led_tape(i_pData, i_cntTapeLed);
GameAPI::Lights::writeLight(RI_MGR, lights[Lights::ArenaTitleAvgR], rgb.r);
GameAPI::Lights::writeLight(RI_MGR, lights[Lights::ArenaTitleAvgG], rgb.g);
GameAPI::Lights::writeLight(RI_MGR, lights[Lights::ArenaTitleAvgB], rgb.b);
}
} }
void __fastcall aioIob2Bi2x_SetTapeLedDataLimit(AIO_IOB2_BI2X_AC1 *i_pNodeCtl, uint32_t i_Channel, void __fastcall aioIob2Bi2x_SetTapeLedDataLimit(AIO_IOB2_BI2X_AC1 *i_pNodeCtl, uint32_t i_Channel,
@@ -551,6 +606,7 @@ namespace games::gitadora {
} }
// libaio-iob5_y32.dll // libaio-iob5_y32.dll
static AIO_IOB5_Y32D *__fastcall aioIob5Y32d_Create(AIO_NMGR_IOB5 *i_pSci, uint32_t i_bfMode) { static AIO_IOB5_Y32D *__fastcall aioIob5Y32d_Create(AIO_NMGR_IOB5 *i_pSci, uint32_t i_bfMode) {
if (i_pSci == aioNmgrIob5) { if (i_pSci == aioNmgrIob5) {
@@ -662,3 +718,5 @@ namespace games::gitadora {
aioNodeCtl_UpdateDevicesStatus, &aioNodeCtl_UpdateDevicesStatus_orig); aioNodeCtl_UpdateDevicesStatus, &aioNodeCtl_UpdateDevicesStatus_orig);
} }
} }
#endif
+4
View File
@@ -1,5 +1,9 @@
#pragma once #pragma once
#if SPICE64
namespace games::gitadora { namespace games::gitadora {
void bi2x_hook_init(); void bi2x_hook_init();
} }
#endif
+32
View File
@@ -5,11 +5,14 @@
#include "cfg/configurator.h" #include "cfg/configurator.h"
#include "hooks/audio/mme.h" #include "hooks/audio/mme.h"
#include "hooks/graphics/graphics.h" #include "hooks/graphics/graphics.h"
#include "misc/wintouchemu.h"
#include "overlay/overlay.h"
#include "util/cpuutils.h" #include "util/cpuutils.h"
#include "util/detour.h" #include "util/detour.h"
#include "util/libutils.h" #include "util/libutils.h"
#include "util/logging.h" #include "util/logging.h"
#include "util/sigscan.h" #include "util/sigscan.h"
#include "util/socd_cleaner.h"
#include "hooks/setupapihook.h" #include "hooks/setupapihook.h"
namespace games::gitadora { namespace games::gitadora {
@@ -17,6 +20,11 @@ namespace games::gitadora {
// settings // settings
bool TWOCHANNEL = false; bool TWOCHANNEL = false;
std::optional<unsigned int> CAB_TYPE = std::nullopt; std::optional<unsigned int> CAB_TYPE = std::nullopt;
bool ARENA_SINGLE_WINDOW = false;
bool P1_LEFTY = false;
bool P2_LEFTY = false;
std::optional<std::string> SUBSCREEN_OVERLAY_SIZE;
std::optional<socd::SocdAlgorithm> PICK_ALGO = socd::SocdAlgorithm::PreferRecent;
/* /*
* Prevent GitaDora from creating folders on F drive * Prevent GitaDora from creating folders on F drive
@@ -212,6 +220,23 @@ namespace games::gitadora {
log_fatal("gitadora", "Cabinet type 3 (SD2) not supported on XG series"); log_fatal("gitadora", "Cabinet type 3 (SD2) not supported on XG series");
} }
#endif #endif
// arena model launches a tiny window yet backbuffer at 4k, resulting in unusable overlay
// force scaling to make things usable
if (!overlay::UI_SCALE_PERCENT.has_value() && is_arena_model() &&
GRAPHICS_WINDOWED && !cfg::CONFIGURATOR_STANDALONE) {
overlay::UI_SCALE_PERCENT = 250;
}
// for guitar wail SOCD cleaning
socd::ALGORITHM = socd::SocdAlgorithm::PreferRecent;
// for guitar picking
if (PICK_ALGO.has_value()) {
log_info("gitadora", "guitar pick SOCD algorithm: {}", static_cast<int>(PICK_ALGO.value()));
} else {
log_info("gitadora", "guitar pick SOCD algorithm: legacy");
}
} }
} }
@@ -271,6 +296,13 @@ namespace games::gitadora {
// volume change prevention // volume change prevention
hooks::audio::mme::init(avs::game::DLL_INSTANCE); hooks::audio::mme::init(avs::game::DLL_INSTANCE);
// touch hook
if (ARENA_SINGLE_WINDOW) {
wintouchemu::FORCE = true;
wintouchemu::INJECT_MOUSE_AS_WM_TOUCH = true;
wintouchemu::hook("GITADORA", avs::game::DLL_INSTANCE);
}
return; return;
} }
+14
View File
@@ -4,12 +4,18 @@
#include "avs/game.h" #include "avs/game.h"
#include "games/game.h" #include "games/game.h"
#include "util/socd_cleaner.h"
namespace games::gitadora { namespace games::gitadora {
// settings // settings
extern bool TWOCHANNEL; extern bool TWOCHANNEL;
extern std::optional<unsigned int> CAB_TYPE; extern std::optional<unsigned int> CAB_TYPE;
extern bool ARENA_SINGLE_WINDOW;
extern bool P1_LEFTY;
extern bool P2_LEFTY;
extern std::optional<std::string> SUBSCREEN_OVERLAY_SIZE;
extern std::optional<socd::SocdAlgorithm> PICK_ALGO;
class GitaDoraGame : public games::Game { class GitaDoraGame : public games::Game {
public: public:
@@ -38,4 +44,12 @@ namespace games::gitadora {
); );
} }
static inline bool is_player_lefty(size_t player) {
if (player == 0) {
return P1_LEFTY;
} else if (player == 1) {
return P2_LEFTY;
}
return false;
}
} }
+51 -11
View File
@@ -65,17 +65,18 @@ std::vector<Button> &games::gitadora::get_buttons() {
"Drum Help", "Drum Help",
"Drum Button Extra 1", "Drum Button Extra 1",
"Drum Button Extra 2", "Drum Button Extra 2",
"Drum Left Cymbal",
"Drum Hi-Hat", "Drum Hi-Hat",
"Drum Hi-Hat Closed", "Drum Left Pedal",
"Drum Hi-Hat Half-Open",
"Drum Snare", "Drum Snare",
"Drum Hi-Tom", "Drum Hi-Tom",
"Drum Low-Tom",
"Drum Right Cymbal",
"Drum Bass Pedal", "Drum Bass Pedal",
"Drum Left Cymbal", "Drum Low-Tom",
"Drum Left Pedal", "Drum Floor Tom",
"Drum Floor Tom" "Drum Right Cymbal",
"Drum Hi-Hat Closed",
"Drum Hi-Hat Half-Open"
); );
} }
@@ -107,9 +108,18 @@ std::string games::gitadora::get_buttons_help() {
// keep to max 100 characters wide // keep to max 100 characters wide
return return
"guitar:\n" "guitar:\n"
"\n"
" < R G B Y P --- Pick ] \n" " < R G B Y P --- Pick ] \n"
"\n" "\n"
" If you normally hold your guitar left-handed (frets on right hand):\n"
" * here, hold the guitar as right-handed (frets on left hand),\n"
" and bind the buttons, pick, and wail controls\n"
" * in Options tab, turn on GitaDora Lefty Guitar\n"
" * finally, use the in-game option to turn on LEFT mode\n"
"\n"
"\n"
"drums:\n" "drums:\n"
"\n"
" LeftCymbal RightCymbal\n" " LeftCymbal RightCymbal\n"
" HiHat HiTom LowTom \n" " HiHat HiTom LowTom \n"
" Snare FloorTom \n" " Snare FloorTom \n"
@@ -117,10 +127,28 @@ std::string games::gitadora::get_buttons_help() {
" Left Bass\n" " Left Bass\n"
" Pedal Pedal\n" " Pedal Pedal\n"
"\n" "\n"
"Drums are NOT velocity-sensitive!\n" " For MIDI drums with Open/Closed HiHat configurations or pads with\n"
" multiple hit zones, ensure you bind all variation using the Pages\n"
" button at the bottom."
;
}
std::string games::gitadora::get_analogs_help() {
// keep to max 100 characters wide
return
"guitar:\n"
"\n" "\n"
"For MIDI drums with Open/Closed HiHat configurations, bind variations below. " "X axis: 0% when body is held facing monitor, 50% when flat on a table\n"
"v2_drum algorithm might work better for those drums." "Y axis: 50% when held horizontal, 0% when neck is raised, 100% pointing down\n"
"Z axis: 50% at rest, 100% when swinging neck toward monitor (only used in XG series)\n"
"\n"
"You need both X and Y axis for up/down wail to work correctly in-game.\n"
"If you only have Y axis, consider using digital wailing instead.\n"
"\n"
"Ensure you clear all analog bindings if you want to use digital wailing.\n"
"\n"
"If you hold your guitar left-handed (frets on the right hand),\n"
"analog bindings will likely not work as intended.\n"
"Consider using digital wailing in Buttons tab."
; ;
} }
@@ -211,7 +239,19 @@ std::vector<Light> &games::gitadora::get_lights() {
"Guitar Right Speaker Mid Low Right B (DX)", "Guitar Right Speaker Mid Low Right B (DX)",
"Guitar Right Speaker Lower R (DX)", "Guitar Right Speaker Lower R (DX)",
"Guitar Right Speaker Lower G (DX)", "Guitar Right Speaker Lower G (DX)",
"Guitar Right Speaker Lower B (DX)" "Guitar Right Speaker Lower B (DX)",
"Title Average R (Arena)",
"Title Average G (Arena)",
"Title Average B (Arena)",
"Woofer R (Arena)",
"Woofer G (Arena)",
"Woofer B (Arena)",
"Card Reader R (Arena)",
"Card Reader G (Arena)",
"Card Reader B (Arena)"
); );
} }
+24 -9
View File
@@ -66,18 +66,21 @@ namespace games::gitadora {
DrumHelp, DrumHelp,
DrumButtonExtra1, DrumButtonExtra1,
DrumButtonExtra2, DrumButtonExtra2,
// note: this is the order they appear in the test menu // note: this is the order of drums in the default layout
DrumLeftCymbal,
DrumHiHat, DrumHiHat,
DrumHiHatClosed, DrumLeftPedal,
DrumHiHatHalfOpen,
DrumSnare, DrumSnare,
DrumHiTom, DrumHiTom,
DrumLowTom,
DrumRightCymbal,
DrumBassPedal, DrumBassPedal,
DrumLeftCymbal, DrumLowTom,
DrumLeftPedal, DrumFloorTom,
DrumFloorTom DrumRightCymbal,
// these only exist for back compat
// (created before it was possible to map multiple input to a button)
DrumHiHatClosed,
DrumHiHatHalfOpen
}; };
} }
@@ -191,13 +194,25 @@ namespace games::gitadora {
GuitarRightSpeakerMidLowRightB, GuitarRightSpeakerMidLowRightB,
GuitarRightSpeakerLowerR, GuitarRightSpeakerLowerR,
GuitarRightSpeakerLowerG, GuitarRightSpeakerLowerG,
GuitarRightSpeakerLowerB GuitarRightSpeakerLowerB,
// arena model (common for both guitar and drum)
ArenaTitleAvgR,
ArenaTitleAvgG,
ArenaTitleAvgB,
ArenaWooferR,
ArenaWooferG,
ArenaWooferB,
ArenaCardR,
ArenaCardG,
ArenaCardB,
} gitadora_lights_t; } gitadora_lights_t;
} }
// getters // getters
std::vector<Button> &get_buttons(); std::vector<Button> &get_buttons();
std::string get_buttons_help(); std::string get_buttons_help();
std::string get_analogs_help();
std::vector<Analog> &get_analogs(); std::vector<Analog> &get_analogs();
std::vector<Light> &get_lights(); std::vector<Light> &get_lights();
} }
+64 -21
View File
@@ -1,11 +1,12 @@
#include "misc/eamuse.h" #include "misc/eamuse.h"
#include "util/utils.h" #include "games/gitadora/gitadora.h"
#include "util/socd_cleaner.h"
#include "util/time.h" #include "util/time.h"
#include "util/utils.h"
#include "io.h" #include "io.h"
#include "j33i.h" #include "j33i.h"
#define GUITAR_BTN_R 0x7 #define GUITAR_BTN_R 0x7
#define GUITAR_BTN_G 0x6 #define GUITAR_BTN_G 0x6
#define GUITAR_BTN_B 0x5 #define GUITAR_BTN_B 0x5
@@ -91,28 +92,50 @@ bool games::gitadora::J33ISerialDevice::parse_msg(
payload.buttons |= 1 << GUITAR_BTN_P; payload.buttons |= 1 << GUITAR_BTN_P;
} }
if (GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::GuitarP1PickUp])) {
if (!GFDM_GF_PICK_STATE_UP) { const auto pick_up = GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::GuitarP1PickUp]);
GFDM_GF_PICK_STATE_UP = true; const auto pick_down = GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::GuitarP1PickDown]);
payload.buttons |= 1 << GUITAR_PICK_UP; if (!games::gitadora::PICK_ALGO.has_value()) {
// legacy behavior (detect rising edges only, input for 1 frame)
if (pick_up) {
if (!GFDM_GF_PICK_STATE_UP) {
GFDM_GF_PICK_STATE_UP = true;
payload.buttons |= 1 << GUITAR_PICK_UP;
}
} else {
GFDM_GF_PICK_STATE_UP = false;
}
if (pick_down) {
if (!GFDM_GF_PICK_STATE_DOWN) {
GFDM_GF_PICK_STATE_DOWN = true;
payload.buttons |= 1 << GUITAR_PICK_DOWN;
}
} else {
GFDM_GF_PICK_STATE_DOWN = false;
} }
} else { } else {
GFDM_GF_PICK_STATE_UP = false; const auto socd = socd::socd_clean(
} 0,
pick_up,
if (GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::GuitarP1PickDown])) { pick_down,
if (!GFDM_GF_PICK_STATE_DOWN) { get_performance_milliseconds(),
GFDM_GF_PICK_STATE_DOWN = true; games::gitadora::PICK_ALGO.value()
);
if (socd == socd::SocdCCW) {
payload.buttons |= 1 << GUITAR_PICK_UP;
} else if (socd == socd::SocdCW) {
payload.buttons |= 1 << GUITAR_PICK_DOWN;
} else if (socd == socd::SocdBoth) {
payload.buttons |= 1 << GUITAR_PICK_UP;
payload.buttons |= 1 << GUITAR_PICK_DOWN; payload.buttons |= 1 << GUITAR_PICK_DOWN;
} }
} else {
GFDM_GF_PICK_STATE_DOWN = false;
} }
auto &analogs = get_analogs(); auto &analogs = get_analogs();
// get x,y,z analog values [-0.5f, 0.5f], centered at 0.f // get x,y,z analog values [-0.5f, 0.5f]
float x = 0.f; float x = is_player_lefty(0) ? 0.3f : -0.5f;
auto x_analog = analogs[Analogs::GuitarP1WailX]; auto x_analog = analogs[Analogs::GuitarP1WailX];
if (x_analog.isSet()) { if (x_analog.isSet()) {
x = GameAPI::Analogs::getState(RI_MGR, x_analog) - 0.5f; x = GameAPI::Analogs::getState(RI_MGR, x_analog) - 0.5f;
@@ -123,11 +146,6 @@ bool games::gitadora::J33ISerialDevice::parse_msg(
if (y_analog.isSet()) { if (y_analog.isSet()) {
y = GameAPI::Analogs::getState(RI_MGR, y_analog) - 0.5f; y = GameAPI::Analogs::getState(RI_MGR, y_analog) - 0.5f;
} }
if (GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::GuitarP1WailUp])) {
y = -0.5f;
} else if (GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::GuitarP1WailDown])) {
y = 0.5f;
}
float z = 0.f; float z = 0.f;
auto z_analog = analogs[Analogs::GuitarP1WailZ]; auto z_analog = analogs[Analogs::GuitarP1WailZ];
@@ -135,6 +153,31 @@ bool games::gitadora::J33ISerialDevice::parse_msg(
z = GameAPI::Analogs::getState(RI_MGR, z_analog) - 0.5f; z = GameAPI::Analogs::getState(RI_MGR, z_analog) - 0.5f;
} }
// handle digital wail up/down
const auto wail_up = GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::GuitarP1WailUp]);
const auto wail_down = GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::GuitarP1WailDown]);
const auto wail_result =
socd::get_guitar_wail(0, wail_up, wail_down, get_performance_milliseconds());
if (!is_player_lefty(0)) {
// righty
if (wail_result == socd::TiltUp) {
x = 0;
y = -0.5f;
} else if (wail_result == socd::TiltDown) {
x = -0.5f;
y = 0.5f;
}
} else {
// lefty
if (wail_result == socd::TiltUp) {
x = 0.f;
y = 0.f;
} else if (wail_result == socd::TiltDown) {
x = 0.5f;
y = 0.f;
}
}
// convert x,y,z to unsigned integer values that the game expects // convert x,y,z to unsigned integer values that the game expects
const uint16_t x_int = x * ARENA_MODEL_WAIL_MAX * 2; const uint16_t x_int = x * ARENA_MODEL_WAIL_MAX * 2;
const uint16_t y_int = y * ARENA_MODEL_WAIL_MAX * 2; const uint16_t y_int = y * ARENA_MODEL_WAIL_MAX * 2;
+29 -12
View File
@@ -1,5 +1,7 @@
#include "bi2x_hook.h" #include "bi2x_hook.h"
#if SPICE64
#include <cstdint> #include <cstdint>
#include "util/detour.h" #include "util/detour.h"
#include "util/logging.h" #include "util/logging.h"
@@ -24,17 +26,28 @@ namespace games::iidx {
uint8_t data[0x10]; uint8_t data[0x10];
}; };
struct AIO_NMGR_IOB2 { struct AIO_NMGR_IOB2_VTABLE {
uint8_t dummy0[0x50]; // other functions here, but they never get called
uint8_t dummy[0x50];
void (__fastcall *pAIO_NMGR_IOB_BeginManage)(int64_t a1); void (__fastcall *pAIO_NMGR_IOB_BeginManage)(int64_t a1);
uint8_t dummy1[0x9A0];
}; };
struct AIO_NMGR_IOB2 {
AIO_NMGR_IOB2_VTABLE *vptr;
uint8_t dummy[0x9F0];
};
// confirmed 0x9F8 in iidx27, iidx32 aioNMgrIob2_Create
static_assert(sizeof(AIO_NMGR_IOB2) == 0x9F8);
struct AIO_IOB2_BI2X_TDJ { struct AIO_IOB2_BI2X_TDJ {
// who knows // who knows
uint8_t data[0x13F8]; uint8_t data[0x13F8];
}; };
// confirmed 0x13F8 in iidx27, iidx32 in aioIob2Bi2xTDJ_Create
static_assert(sizeof(AIO_IOB2_BI2X_TDJ) == 0x13F8);
struct AIO_IOB2_BI2X_TDJ__DEVSTATUS { struct AIO_IOB2_BI2X_TDJ__DEVSTATUS {
// of course you could work with variables here // of course you could work with variables here
uint8_t buffer[0xCA]; uint8_t buffer[0xCA];
@@ -78,7 +91,7 @@ namespace games::iidx {
// libaio-iob.dll // libaio-iob.dll
typedef AC_HNDLIF* (__fastcall *aioIob2Bi2x_OpenSciUsbCdc_t)(uint8_t device_num); typedef AC_HNDLIF* (__fastcall *aioIob2Bi2x_OpenSciUsbCdc_t)(uint8_t device_num);
typedef int64_t (__fastcall *aioIob2Bi2x_WriteFirmGetState_t)(int64_t a1); typedef int64_t (__fastcall *aioIob2Bi2x_WriteFirmGetState_t)(int64_t a1);
typedef AIO_NMGR_IOB2** (__fastcall *aioNMgrIob2_Create_t)(AC_HNDLIF *a1, unsigned int a2); typedef AIO_NMGR_IOB2* (__fastcall *aioNMgrIob2_Create_t)(AC_HNDLIF *a1, unsigned int a2);
/* /*
* function pointers * function pointers
@@ -443,7 +456,7 @@ namespace games::iidx {
} }
} }
static AC_HNDLIF* __fastcall aioIob2Bi2X_OpenSciUsbCdc(uint8_t device_num) { static AC_HNDLIF* __fastcall aioIob2Bi2x_OpenSciUsbCdc(uint8_t device_num) {
if (acHndlif == nullptr) { if (acHndlif == nullptr) {
acHndlif = new AC_HNDLIF; acHndlif = new AC_HNDLIF;
memset(acHndlif->data, 0x0, sizeof(acHndlif->data)); memset(acHndlif->data, 0x0, sizeof(acHndlif->data));
@@ -456,14 +469,16 @@ namespace games::iidx {
log_info("bi2x_hook", "AIO_NMGR_IOB::BeginManage"); log_info("bi2x_hook", "AIO_NMGR_IOB::BeginManage");
}; };
static AIO_NMGR_IOB2** __fastcall aioNMgrIob2_Create(AC_HNDLIF *a1, unsigned int a2) { static AIO_NMGR_IOB2* __fastcall aioNMgrIob2_Create(AC_HNDLIF *a1, unsigned int a2) {
if (aioNmgrIob2 == nullptr) { if (aioNmgrIob2 == nullptr) {
aioNmgrIob2 = new AIO_NMGR_IOB2; aioNmgrIob2 = new AIO_NMGR_IOB2{};
memset(aioNmgrIob2, 0x0, sizeof(AIO_NMGR_IOB2)); aioNmgrIob2->vptr = new AIO_NMGR_IOB2_VTABLE{};
aioNmgrIob2->pAIO_NMGR_IOB_BeginManage = AIO_NMGR_IOB_BeginManageStub; aioNmgrIob2->vptr->pAIO_NMGR_IOB_BeginManage = AIO_NMGR_IOB_BeginManageStub;
} }
log_info("bi2x_hook", "aioNMgrIob2_Create"); log_info("bi2x_hook", "aioNMgrIob2_Create returned {}, size=0x{:x}, vptr @ {}",
return &aioNmgrIob2; fmt::ptr(aioNmgrIob2), sizeof(*aioNmgrIob2), fmt::ptr(aioNmgrIob2->vptr));
return aioNmgrIob2;
} }
static int64_t __fastcall aioIob2Bi2x_WriteFirmGetState(int64_t a1) { static int64_t __fastcall aioIob2Bi2x_WriteFirmGetState(int64_t a1) {
@@ -520,10 +535,12 @@ namespace games::iidx {
// hook IOB // hook IOB
const auto libaioIobDll = "libaio-iob.dll"; const auto libaioIobDll = "libaio-iob.dll";
detour::trampoline_try(libaioIobDll, "aioIob2Bi2x_OpenSciUsbCdc", detour::trampoline_try(libaioIobDll, "aioIob2Bi2x_OpenSciUsbCdc",
aioIob2Bi2X_OpenSciUsbCdc, &aioIob2Bi2x_OpenSciUsbCdc_orig); aioIob2Bi2x_OpenSciUsbCdc, &aioIob2Bi2x_OpenSciUsbCdc_orig);
detour::trampoline_try(libaioIobDll, "aioIob2Bi2x_WriteFirmGetState", detour::trampoline_try(libaioIobDll, "aioIob2Bi2x_WriteFirmGetState",
aioIob2Bi2x_WriteFirmGetState, &aioIob2Bi2x_WriteFirmGetState_orig); aioIob2Bi2x_WriteFirmGetState, &aioIob2Bi2x_WriteFirmGetState_orig);
detour::trampoline_try(libaioIobDll, "aioNMgrIob2_Create", detour::trampoline_try(libaioIobDll, "aioNMgrIob2_Create",
aioNMgrIob2_Create, &aioNMgrIob2_Create_orig); aioNMgrIob2_Create, &aioNMgrIob2_Create_orig);
} }
} }
#endif
+4
View File
@@ -1,6 +1,10 @@
#pragma once #pragma once
#if SPICE64
namespace games::iidx { namespace games::iidx {
void bi2x_hook_init(); void bi2x_hook_init();
} }
#endif
+28 -6
View File
@@ -29,6 +29,8 @@
#include "util/libutils.h" #include "util/libutils.h"
#include "util/memutils.h" #include "util/memutils.h"
#include "util/sigscan.h" #include "util/sigscan.h"
#include "util/socd_cleaner.h"
#include "util/time.h"
#include "util/utils.h" #include "util/utils.h"
#include "launcher/signal.h" #include "launcher/signal.h"
@@ -80,6 +82,7 @@ namespace games::iidx {
bool IIDXIO_LED_TICKER_READONLY = false; bool IIDXIO_LED_TICKER_READONLY = false;
std::mutex IIDX_LED_TICKER_LOCK; std::mutex IIDX_LED_TICKER_LOCK;
bool IIDX_TDJ_MONITOR_WARNING = false; bool IIDX_TDJ_MONITOR_WARNING = false;
iidx_aio_emulation_state CURRENT_IO_EMULATION_STATE = iidx_aio_emulation_state::unknown;
// io // io
static bool HAS_LIBAIO; // this is how we detect iidx27+ static bool HAS_LIBAIO; // this is how we detect iidx27+
@@ -251,6 +254,8 @@ namespace games::iidx {
} }
} }
#if SPICE64
typedef void* (*aioIob2Bi2x_CreateWriteFirmContext_t)(unsigned int, int); typedef void* (*aioIob2Bi2x_CreateWriteFirmContext_t)(unsigned int, int);
static aioIob2Bi2x_CreateWriteFirmContext_t aioIob2Bi2x_CreateWriteFirmContext_orig = nullptr; static aioIob2Bi2x_CreateWriteFirmContext_t aioIob2Bi2x_CreateWriteFirmContext_orig = nullptr;
@@ -269,6 +274,8 @@ namespace games::iidx {
return nullptr; return nullptr;
} }
#endif
IIDXGame::IIDXGame() : Game("Beatmania IIDX") { IIDXGame::IIDXGame() : Game("Beatmania IIDX") {
logger::hook_add(log_hook, this); logger::hook_add(log_hook, this);
} }
@@ -319,6 +326,8 @@ namespace games::iidx {
devicehook_add(new IIDXFMSerialHandle()); devicehook_add(new IIDXFMSerialHandle());
} }
#if SPICE64
// check for new I/O DLL // check for new I/O DLL
auto aio = libutils::try_library("libaio.dll"); auto aio = libutils::try_library("libaio.dll");
if (aio != nullptr) { if (aio != nullptr) {
@@ -384,6 +393,8 @@ namespace games::iidx {
} }
} }
#endif
if (hooks::audio::ENABLED) { if (hooks::audio::ENABLED) {
apply_audio_hacks(); apply_audio_hacks();
} else { } else {
@@ -443,7 +454,7 @@ namespace games::iidx {
} }
// windowed subscreen, enabled by default, unless turned off by user // windowed subscreen, enabled by default, unless turned off by user
if (GRAPHICS_WINDOWED && !options->at(launcher::Options::spice2x_IIDXNoSub).value_bool()) { if (GRAPHICS_WINDOWED && !options->at(launcher::Options::spice2x_IIDXNoSub).value_bool() && TDJ_MODE) {
GRAPHICS_IIDX_WSUB = true; GRAPHICS_IIDX_WSUB = true;
} }
@@ -499,6 +510,16 @@ namespace games::iidx {
" monitor in Windows settings before launching the game" " monitor in Windows settings before launching the game"
}); });
} }
// socd
socd::ALGORITHM = socd::SocdAlgorithm::Neutral;
if (options->at(launcher::Options::IIDXDigitalTTSocd).is_active()) {
if (options->at(launcher::Options::IIDXDigitalTTSocd).value_text() == "last") {
socd::ALGORITHM = socd::SocdAlgorithm::PreferRecent;
} else if (options->at(launcher::Options::IIDXDigitalTTSocd).value_text() == "first") {
socd::ALGORITHM = socd::SocdAlgorithm::PreferFirst;
}
}
} }
void IIDXGame::detach() { void IIDXGame::detach() {
@@ -722,13 +743,12 @@ namespace games::iidx {
bool ttpm_alt = GameAPI::Buttons::getState(RI_MGR, buttons.at( bool ttpm_alt = GameAPI::Buttons::getState(RI_MGR, buttons.at(
player != 0 ? Buttons::P2_TTPlusMinusAlt : Buttons::P1_TTPlusMinusAlt)); player != 0 ? Buttons::P2_TTPlusMinusAlt : Buttons::P1_TTPlusMinusAlt));
// TT+ const auto tt_socd = socd::socd_clean(player, ttm, ttp, get_performance_milliseconds());
if (ttp) if (tt_socd == socd::SocdCW) {
IIDXIO_TT_STATE[player] += change; IIDXIO_TT_STATE[player] += change;
} else if (tt_socd == socd::SocdCCW) {
// TT-
if (ttm)
IIDXIO_TT_STATE[player] -= change; IIDXIO_TT_STATE[player] -= change;
}
// TT+/- // TT+/-
bool ttpm_rising_edge = !IIDXIO_TT_PRESSED[player] && ttpm; bool ttpm_rising_edge = !IIDXIO_TT_PRESSED[player] && ttpm;
@@ -915,6 +935,8 @@ namespace games::iidx {
return; return;
} }
CURRENT_IO_EMULATION_STATE = state;
// log_warning below could be turned into log_fatal in the future once we gain confidence // log_warning below could be turned into log_fatal in the future once we gain confidence
// that this isn't triggering when it's not supposed to // that this isn't triggering when it's not supposed to
if (state == iidx_aio_emulation_state::bi2a_com2 && TDJ_MODE) { if (state == iidx_aio_emulation_state::bi2a_com2 && TDJ_MODE) {
+7 -4
View File
@@ -11,6 +11,12 @@
namespace games::iidx { namespace games::iidx {
enum class iidx_aio_emulation_state {
unknown,
bi2a_com2,
bi2x_hook
};
// settings // settings
extern bool FLIP_CAMS; extern bool FLIP_CAMS;
@@ -38,6 +44,7 @@ namespace games::iidx {
constexpr int IIDX_TAPELED_TOTAL = 17; constexpr int IIDX_TAPELED_TOTAL = 17;
// data mapping // data mapping
extern tapeledutils::tape_led TAPELED_MAPPING[IIDX_TAPELED_TOTAL]; extern tapeledutils::tape_led TAPELED_MAPPING[IIDX_TAPELED_TOTAL];
extern iidx_aio_emulation_state CURRENT_IO_EMULATION_STATE;
class IIDXGame : public games::Game { class IIDXGame : public games::Game {
public: public:
@@ -64,9 +71,5 @@ namespace games::iidx {
bool is_tdj_fhd(); bool is_tdj_fhd();
void apply_audio_hacks(); void apply_audio_hacks();
enum class iidx_aio_emulation_state {
bi2a_com2,
bi2x_hook
};
void update_io_emulation_state(iidx_aio_emulation_state state); void update_io_emulation_state(iidx_aio_emulation_state state);
} }
+12 -1
View File
@@ -47,6 +47,7 @@ namespace games {
static robin_hood::unordered_map<std::string, std::vector<Button>> buttons_keypads; static robin_hood::unordered_map<std::string, std::vector<Button>> buttons_keypads;
static robin_hood::unordered_map<std::string, std::vector<Button>> buttons_overlay; static robin_hood::unordered_map<std::string, std::vector<Button>> buttons_overlay;
static robin_hood::unordered_map<std::string, std::string> buttons_help; static robin_hood::unordered_map<std::string, std::string> buttons_help;
static robin_hood::unordered_map<std::string, std::string> analogs_help;
static robin_hood::unordered_map<std::string, std::vector<Analog> &> analogs; static robin_hood::unordered_map<std::string, std::vector<Analog> &> analogs;
static robin_hood::unordered_map<std::string, std::vector<Light> &> lights; static robin_hood::unordered_map<std::string, std::vector<Light> &> lights;
static robin_hood::unordered_map<std::string, std::vector<Option>> options; static robin_hood::unordered_map<std::string, std::vector<Option>> options;
@@ -107,6 +108,7 @@ namespace games {
games.push_back(gitadora); games.push_back(gitadora);
buttons.insert({ gitadora, gitadora::get_buttons() }); buttons.insert({ gitadora, gitadora::get_buttons() });
buttons_help.insert({ gitadora, gitadora::get_buttons_help() }); buttons_help.insert({ gitadora, gitadora::get_buttons_help() });
analogs_help.insert({ gitadora, gitadora::get_analogs_help() });
analogs.insert({ gitadora, gitadora::get_analogs() }); analogs.insert({ gitadora, gitadora::get_analogs() });
lights.insert({ gitadora, gitadora::get_lights() }); lights.insert({ gitadora, gitadora::get_lights() });
file_hints[gitadora].emplace_back("gdxg.dll"); file_hints[gitadora].emplace_back("gdxg.dll");
@@ -322,7 +324,7 @@ namespace games {
buttons_help.insert({ pc, pc::get_buttons_help() }); buttons_help.insert({ pc, pc::get_buttons_help() });
analogs.insert({ pc, pc::get_analogs() }); analogs.insert({ pc, pc::get_analogs() });
lights.insert({ pc, pc::get_lights() }); lights.insert({ pc, pc::get_lights() });
file_hints[pc].emplace_back("game/svm_Data"); file_hints[pc].emplace_back("game/svm.exe");
} }
const std::vector<std::string> &get_games() { const std::vector<std::string> &get_games() {
@@ -349,6 +351,15 @@ namespace games {
return it->second; return it->second;
} }
std::string get_analogs_help(const std::string &game) {
initialize();
auto it = analogs_help.find(game);
if (it == analogs_help.end()) {
return "";
}
return it->second;
}
static std::vector<Button> gen_buttons_keypads(const std::string &game) { static std::vector<Button> gen_buttons_keypads(const std::string &game) {
auto buttons = GameAPI::Buttons::getButtons(game); auto buttons = GameAPI::Buttons::getButtons(game);
std::vector<std::string> names; std::vector<std::string> names;
+1
View File
@@ -58,6 +58,7 @@ namespace games {
const std::vector<std::string> &get_games(); const std::vector<std::string> &get_games();
std::vector<Button> *get_buttons(const std::string &game); std::vector<Button> *get_buttons(const std::string &game);
std::string get_buttons_help(const std::string &game); std::string get_buttons_help(const std::string &game);
std::string get_analogs_help(const std::string &game);
std::vector<Button> *get_buttons_keypads(const std::string &game); std::vector<Button> *get_buttons_keypads(const std::string &game);
std::vector<Button> *get_buttons_overlay(const std::string &game); std::vector<Button> *get_buttons_overlay(const std::string &game);
std::vector<Analog> *get_analogs(const std::string &game); std::vector<Analog> *get_analogs(const std::string &game);
+38 -11
View File
@@ -1,5 +1,7 @@
#include "bi2x_hook.h" #include "bi2x_hook.h"
#if SPICE64
#include "util/execexe.h" #include "util/execexe.h"
#include "util/logging.h" #include "util/logging.h"
#include "rawinput/rawinput.h" #include "rawinput/rawinput.h"
@@ -9,9 +11,10 @@
#include "util/tapeled.h" #include "util/tapeled.h"
#include "pc.h" #include "pc.h"
#include "util/utils.h" #include "util/utils.h"
#include "util/time.h"
#include "util/socd_cleaner.h"
namespace games::pc { namespace games::pc {
bool PC_KNOB_MODE = false;
constexpr float RELATIVE_DECAY = 0.02f; constexpr float RELATIVE_DECAY = 0.02f;
struct FADER_RELATIVE { struct FADER_RELATIVE {
@@ -75,18 +78,22 @@ namespace games::pc {
*/ */
struct AIO_SCI_COMM { struct AIO_SCI_COMM {
uint8_t data[0xf8];
}; };
struct AIO_NMGR_IOB2 { struct AIO_NMGR_IOB2 {
uint8_t data[0xe30];
}; };
struct AIO_IOB2_BI2X_AC1 { struct AIO_IOB2_BI2X_AC1 {
uint8_t data[0x4538];
}; };
struct AIO_IOB2_BI2X_AC1__SETTING { struct AIO_IOB2_BI2X_AC1__SETTING {
}; };
struct AIO_IOB2_BI2X_WRFIRM { struct AIO_IOB2_BI2X_WRFIRM {
uint8_t data[0x20f48];
}; };
struct AIO_IOB2_BI2X_AC1__INPUT struct AIO_IOB2_BI2X_AC1__INPUT
@@ -191,6 +198,12 @@ namespace games::pc {
AIO_IOB2_BI2X_AC1__ICNPIN ICnPinHist[20]; AIO_IOB2_BI2X_AC1__ICNPIN ICnPinHist[20];
}; };
// verified with XIF-2024122300
static_assert(sizeof(AIO_SCI_COMM) == 0xf8);
static_assert(sizeof(AIO_NMGR_IOB2) == 0xe30);
static_assert(sizeof(AIO_IOB2_BI2X_AC1) == 0x4538);
static_assert(sizeof(AIO_IOB2_BI2X_WRFIRM) == 0x20f48);
/* /*
* typedefs * typedefs
*/ */
@@ -338,14 +351,21 @@ namespace games::pc {
val = (GameAPI::Analogs::getState(RI_MGR, analogs[Analogs::FaderL]) - 0.5f) * 2; val = (GameAPI::Analogs::getState(RI_MGR, analogs[Analogs::FaderL]) - 0.5f) * 2;
} }
} }
if (GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::FaderL_Left]) &&
GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::FaderL_Right])) { const auto now = get_performance_milliseconds();
val = 0.f;
} else if (GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::FaderL_Left])) { const auto fader_l_socd = socd::socd_clean(
0, // Fader-L
GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::FaderL_Left]),
GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::FaderL_Right]),
now);
if (fader_l_socd == socd::SocdCCW) {
val = -1.0f; val = -1.0f;
} else if (GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::FaderL_Right])) { } else if (fader_l_socd == socd::SocdCW) {
val = +1.0f; val = +1.0f;
} }
o_DevStatus->Input.CN15_7 = (val < 0.2f) ? 0xFF : 0; o_DevStatus->Input.CN15_7 = (val < 0.2f) ? 0xFF : 0;
o_DevStatus->Input.CN15_6 = (val > -0.85f && val < 0.35f) ? 0xFF : 0; o_DevStatus->Input.CN15_6 = (val > -0.85f && val < 0.35f) ? 0xFF : 0;
o_DevStatus->Input.CN15_5 = (val > -0.6f && val < 0.6f) ? 0xFF : 0; o_DevStatus->Input.CN15_5 = (val > -0.6f && val < 0.6f) ? 0xFF : 0;
@@ -363,14 +383,19 @@ namespace games::pc {
val = (GameAPI::Analogs::getState(RI_MGR, analogs[Analogs::FaderR]) - 0.5f) * 2; val = (GameAPI::Analogs::getState(RI_MGR, analogs[Analogs::FaderR]) - 0.5f) * 2;
} }
} }
if (GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::FaderR_Left]) &&
GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::FaderR_Right])) { const auto fader_r_socd = socd::socd_clean(
val = 0.f; 1, // Fader-R
} else if (GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::FaderR_Left])) { GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::FaderR_Left]),
GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::FaderR_Right]),
now);
if (fader_r_socd == socd::SocdCCW) {
val = -1.0f; val = -1.0f;
} else if (GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::FaderR_Right])) { } else if (fader_r_socd == socd::SocdCW) {
val = +1.0f; val = +1.0f;
} }
o_DevStatus->Input.CN11_20 = (val < 0.2f) ? 0xFF : 0; o_DevStatus->Input.CN11_20 = (val < 0.2f) ? 0xFF : 0;
o_DevStatus->Input.CN11_19 = (val > -0.85f && val < 0.35f) ? 0xFF : 0; o_DevStatus->Input.CN11_19 = (val > -0.85f && val < 0.35f) ? 0xFF : 0;
o_DevStatus->Input.CN9_10 = (val > -0.6f && val < 0.6f) ? 0xFF : 0; o_DevStatus->Input.CN9_10 = (val > -0.6f && val < 0.6f) ? 0xFF : 0;
@@ -737,3 +762,5 @@ namespace games::pc {
} }
} }
#endif
+4
View File
@@ -1,5 +1,9 @@
#pragma once #pragma once
#if SPICE64
namespace games::pc { namespace games::pc {
void bi2x_hook_init(); void bi2x_hook_init();
} }
#endif
+12
View File
@@ -12,10 +12,12 @@
#include "misc/wintouchemu.h" #include "misc/wintouchemu.h"
#include "hooks/graphics/graphics.h" #include "hooks/graphics/graphics.h"
#include "rawinput/rawinput.h" #include "rawinput/rawinput.h"
#include "util/socd_cleaner.h"
namespace games::pc { namespace games::pc {
std::string PC_INJECT_ARGS = ""; std::string PC_INJECT_ARGS = "";
bool PC_NO_IO = false; bool PC_NO_IO = false;
bool PC_KNOB_MODE = false;
static acioemu::ACIOHandle *acioHandle = nullptr; static acioemu::ACIOHandle *acioHandle = nullptr;
static std::wstring portName = L"COM1"; static std::wstring portName = L"COM1";
@@ -61,6 +63,11 @@ namespace games::pc {
return ERROR_NOT_SUPPORTED; return ERROR_NOT_SUPPORTED;
} }
void PCGame::pre_attach() {
// SOCD
socd::ALGORITHM = socd::SocdAlgorithm::PreferRecent;
}
void PCGame::attach() { void PCGame::attach() {
Game::attach(); Game::attach();
@@ -78,9 +85,14 @@ namespace games::pc {
execexe::load_library("libaio-iob2_video.dll"); execexe::load_library("libaio-iob2_video.dll");
execexe::load_library("win10actlog.dll"); execexe::load_library("win10actlog.dll");
#if SPICE64
if (!PC_NO_IO) { if (!PC_NO_IO) {
bi2x_hook_init(); bi2x_hook_init();
} }
#endif
}); });
const auto user32Dll = "user32.dll"; const auto user32Dll = "user32.dll";
+1
View File
@@ -11,6 +11,7 @@ namespace games::pc {
public: public:
PCGame() : Game("Polaris Chord") {} PCGame() : Game("Polaris Chord") {}
virtual void pre_attach() override;
virtual void attach() override; virtual void attach() override;
virtual void detach() override; virtual void detach() override;
}; };
+14
View File
@@ -1,5 +1,7 @@
#include "bi2x_hook.h" #include "bi2x_hook.h"
#if SPICE64
#include "util/detour.h" #include "util/detour.h"
#include "util/logging.h" #include "util/logging.h"
#include "rawinput/rawinput.h" #include "rawinput/rawinput.h"
@@ -15,15 +17,19 @@ namespace games::qks {
*/ */
struct AIO_SCI_COMM { struct AIO_SCI_COMM {
uint8_t data[0xf8];
}; };
struct AIO_NMGR_IOB { struct AIO_NMGR_IOB {
uint8_t data[0xa00];
}; };
struct AIO_IOB2_BI2X_AC1 { struct AIO_IOB2_BI2X_AC1 {
uint8_t data[0x3908];
}; };
struct AIO_IOB2_BI2X_WRFIRM { struct AIO_IOB2_BI2X_WRFIRM {
uint8_t data[0x20f48];
}; };
struct AIO_NMGR_IOB__NODEINFO { struct AIO_NMGR_IOB__NODEINFO {
@@ -174,6 +180,12 @@ namespace games::qks {
AIO_IOB2_BI2X_AC1__SETTING Setting; AIO_IOB2_BI2X_AC1__SETTING Setting;
}; };
// verified with UKS-001-2024030600
static_assert(sizeof(AIO_IOB2_BI2X_AC1) == 0x3908);
static_assert(sizeof(AIO_SCI_COMM) == 0xf8);
static_assert(sizeof(AIO_NMGR_IOB) == 0xa00);
static_assert(sizeof(AIO_IOB2_BI2X_WRFIRM) == 0x20f48);
/* /*
* typedefs * typedefs
*/ */
@@ -603,3 +615,5 @@ namespace games::qks {
aioNodeCtl_IsError, &aioNodeCtl_IsError_orig); aioNodeCtl_IsError, &aioNodeCtl_IsError_orig);
} }
} }
#endif
+4
View File
@@ -1,7 +1,11 @@
#pragma once #pragma once
#if SPICE64
#include <stdint.h> #include <stdint.h>
namespace games::qks { namespace games::qks {
void bi2x_hook_init(); void bi2x_hook_init();
} }
#endif
+4
View File
@@ -86,9 +86,13 @@ namespace games::qks {
detour::trampoline_try("execexe.dll", MAKEINTRESOURCE(9), detour::trampoline_try("execexe.dll", MAKEINTRESOURCE(9),
execexe_CreateFileA_hook,&execexe_CreateFileA_orig); execexe_CreateFileA_hook,&execexe_CreateFileA_orig);
#if SPICE64
// insert BI2X hooks // insert BI2X hooks
bi2x_hook_init(); bi2x_hook_init();
#endif
// add card reader // add card reader
acioHandle = new acioemu::ACIOHandle(portName.c_str()); acioHandle = new acioemu::ACIOHandle(portName.c_str());
devicehook_init_trampoline(); devicehook_init_trampoline();
+100 -36
View File
@@ -1,5 +1,7 @@
#include "bi2x_hook.h" #include "bi2x_hook.h"
#if SPICE64
#include <cstdint> #include <cstdint>
#include "util/detour.h" #include "util/detour.h"
#include "util/logging.h" #include "util/logging.h"
@@ -10,12 +12,22 @@
#include "launcher/options.h" #include "launcher/options.h"
#include "io.h" #include "io.h"
#include "games/sdvx/sdvx.h" #include "games/sdvx/sdvx.h"
#include "util/socd_cleaner.h"
#include "util/tapeled.h" #include "util/tapeled.h"
#include "util/time.h"
#include "acioemu/icca.h" #include "acioemu/icca.h"
#define DEBUG_VERBOSE 0
#if DEBUG_VERBOSE
#define log_debug(module, format_str, ...) logger::push( \
LOG_FORMAT("M", module, format_str, ## __VA_ARGS__), logger::Style::GREY)
#else
#define log_debug(module, format_str, ...)
#endif
namespace games::sdvx { namespace games::sdvx {
constexpr bool BI2X_PASSTHROUGH = false; constexpr bool BI2X_PASSTHROUGH = false;
bool BI2X_INITIALIZED = false;
/* /*
* class definitions * class definitions
@@ -26,20 +38,54 @@ namespace games::sdvx {
uint8_t data[0x10]; uint8_t data[0x10];
}; };
struct AIO_NMGR_IOB2 { struct AIO_NMGR_IOB2_VTABLE {
uint8_t dummy0[0x50]; // other functions here, but they never get called
uint8_t dummy[0x50];
void (__fastcall *pAIO_NMGR_IOB_BeginManage)(int64_t a1); void (__fastcall *pAIO_NMGR_IOB_BeginManage)(int64_t a1);
uint8_t dummy1[0x9A0];
}; };
struct AIO_NMGR_IOB2 {
AIO_NMGR_IOB2_VTABLE *vptr;
uint8_t dummy[0x9F0];
};
// confirmed in EG final in aioNMgrIob2_Create
static_assert(sizeof(AIO_NMGR_IOB2) == 0x9F8);
struct AIO_IOB2_BI2X_UFC { struct AIO_IOB2_BI2X_UFC {
// who knows // who knows
uint8_t data[0x13F8]; uint8_t data[0x39D8];
};
// confirmed in EG final in aioIob2Bi2xUFC_Create
static_assert(sizeof(AIO_IOB2_BI2X_UFC) == 0x39D8);
struct AIO_IOB2_BI2X_UFC__INPUT {
uint8_t DevIoCounter;
uint8_t bExIoAErr;
uint8_t bExIoBErr;
uint8_t bPcPowerOn;
uint8_t bPcPowerCheck;
uint8_t CoinCount;
uint8_t bTest;
uint8_t bService;
uint8_t bCoinSw;
uint8_t bCoinJam;
uint8_t bHPDetect;
}; };
struct AIO_IOB2_BI2X_UFC__DEVSTATUS { struct AIO_IOB2_BI2X_UFC__DEVSTATUS {
// of course you could work with variables here uint8_t InputCounter;
uint8_t buffer[0x19E]; uint8_t OutputCounter;
uint8_t IoResetCounter;
uint8_t TapeLedCounter;
uint8_t TapeLedRate[8];
AIO_IOB2_BI2X_UFC__INPUT Input;
uint8_t unk_1[289];
uint16_t AnalogLeft;
uint16_t AnalogRight;
uint8_t Buttons;
uint8_t unk_2[97];
}; };
/* /*
@@ -66,7 +112,7 @@ namespace games::sdvx {
// libaio-iob.dll // libaio-iob.dll
typedef AC_HNDLIF* (__fastcall *aioIob2Bi2x_OpenSciUsbCdc_t)(uint8_t device_num); typedef AC_HNDLIF* (__fastcall *aioIob2Bi2x_OpenSciUsbCdc_t)(uint8_t device_num);
typedef int64_t (__fastcall *aioIob2Bi2x_WriteFirmGetState_t)(int64_t a1); typedef int64_t (__fastcall *aioIob2Bi2x_WriteFirmGetState_t)(int64_t a1);
typedef AIO_NMGR_IOB2** (__fastcall *aioNMgrIob2_Create_t)(AC_HNDLIF *a1, unsigned int a2); typedef AIO_NMGR_IOB2* (__fastcall *aioNMgrIob2_Create_t)(AC_HNDLIF *a1, unsigned int a2);
/* /*
* function pointers * function pointers
@@ -133,8 +179,8 @@ namespace games::sdvx {
AIO_IOB2_BI2X_UFC__GetDeviceStatus_orig(This, status); AIO_IOB2_BI2X_UFC__GetDeviceStatus_orig(This, status);
} }
status->buffer[0] = count; status->InputCounter = count;
status->buffer[12] = count; status->Input.DevIoCounter = count;
count++; count++;
// get buttons // get buttons
@@ -142,41 +188,51 @@ namespace games::sdvx {
// control buttons // control buttons
if (GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::Test])) if (GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::Test]))
status->buffer[18] = 0x01; status->Input.bTest = 0x01;
if (GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::Service])) if (GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::Service]))
status->buffer[19] = 0x01; status->Input.bService = 0x01;
if (GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::CoinMech])) if (GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::CoinMech]))
status->buffer[20] = 0x01; status->Input.bCoinSw = 0x01;
if (GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::Start])) if (GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::Start]))
status->buffer[316] |= 0x01; status->Buttons |= 0x01;
if (GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::BT_A])) if (GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::BT_A]))
status->buffer[316] |= 0x02; status->Buttons |= 0x02;
if (GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::BT_B])) if (GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::BT_B]))
status->buffer[316] |= 0x04; status->Buttons |= 0x04;
if (GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::BT_C])) if (GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::BT_C]))
status->buffer[316] |= 0x08; status->Buttons |= 0x08;
if (GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::BT_D])) if (GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::BT_D]))
status->buffer[316] |= 0x10; status->Buttons |= 0x10;
if (GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::FX_L])) if (GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::FX_L]))
status->buffer[316] |= 0x20; status->Buttons |= 0x20;
if (GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::FX_R])) if (GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::FX_R]))
status->buffer[316] |= 0x40; status->Buttons |= 0x40;
if (GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::Headphone])) if (GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::Headphone]))
status->buffer[22] = 0x01; status->Input.bHPDetect = 0x01;
status->Input.CoinCount += eamuse_coin_get_stock();
const auto now = get_performance_milliseconds();
// volume left // volume left
if (GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::VOL_L_Left])) { const auto vol_l_state = socd::socd_clean(0,
GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::VOL_L_Left]),
GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::VOL_L_Right]),
now);
if (vol_l_state == socd::SocdCCW) {
VOL_L -= ((uint16_t)DIGITAL_KNOB_SENS * 4); VOL_L -= ((uint16_t)DIGITAL_KNOB_SENS * 4);
} } else if (vol_l_state == socd::SocdCW) {
if (GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::VOL_L_Right])) {
VOL_L += ((uint16_t)DIGITAL_KNOB_SENS * 4); VOL_L += ((uint16_t)DIGITAL_KNOB_SENS * 4);
} }
// volume right // volume right
if (GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::VOL_R_Left])) { const auto vol_r_state = socd::socd_clean(1,
GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::VOL_R_Left]),
GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::VOL_R_Right]),
now);
if (vol_r_state == socd::SocdCCW) {
VOL_R -= ((uint16_t)DIGITAL_KNOB_SENS * 4); VOL_R -= ((uint16_t)DIGITAL_KNOB_SENS * 4);
} } else if (vol_r_state == socd::SocdCW) {
if (GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::VOL_R_Right])) {
VOL_R += ((uint16_t)DIGITAL_KNOB_SENS * 4); VOL_R += ((uint16_t)DIGITAL_KNOB_SENS * 4);
} }
@@ -189,8 +245,14 @@ namespace games::sdvx {
vol_right += (uint16_t) (GameAPI::Analogs::getState(RI_MGR, analogs[Analogs::VOL_R]) * 65535); vol_right += (uint16_t) (GameAPI::Analogs::getState(RI_MGR, analogs[Analogs::VOL_R]) * 65535);
} }
*((uint16_t*) &status->buffer[312]) = vol_left; status->AnalogLeft = vol_left;
*((uint16_t*) &status->buffer[314]) = vol_right; status->AnalogRight = vol_right;
log_debug(
"bi2x_hook",
"knobs = {} {}",
status->AnalogLeft,
status->AnalogRight);
} }
static void __fastcall AIO_IOB2_BI2X_UFC__IoReset(AIO_IOB2_BI2X_UFC *This, static void __fastcall AIO_IOB2_BI2X_UFC__IoReset(AIO_IOB2_BI2X_UFC *This,
@@ -345,21 +407,21 @@ namespace games::sdvx {
log_info("bi2x_hook", "AIO_NMGR_IOB::BeginManage"); log_info("bi2x_hook", "AIO_NMGR_IOB::BeginManage");
} }
static AIO_NMGR_IOB2** __fastcall aioNMgrIob2_Create(AC_HNDLIF *a1, unsigned int a2) { static AIO_NMGR_IOB2* __fastcall aioNMgrIob2_Create(AC_HNDLIF *a1, unsigned int a2) {
if (aioNmgrIob2 == nullptr) { if (aioNmgrIob2 == nullptr) {
aioNmgrIob2 = new AIO_NMGR_IOB2; aioNmgrIob2 = new AIO_NMGR_IOB2{};
memset(aioNmgrIob2, 0x0, sizeof(AIO_NMGR_IOB2)); aioNmgrIob2->vptr = new AIO_NMGR_IOB2_VTABLE{};
aioNmgrIob2->pAIO_NMGR_IOB_BeginManage = AIO_NMGR_IOB_BeginManageStub; aioNmgrIob2->vptr->pAIO_NMGR_IOB_BeginManage = AIO_NMGR_IOB_BeginManageStub;
} }
log_info("bi2x_hook", "aioNMgrIob2_Create"); log_info("bi2x_hook", "aioNMgrIob2_Create returned {}, size=0x{:x}, vptr @ {}",
BI2X_INITIALIZED = true; fmt::ptr(aioNmgrIob2), sizeof(*aioNmgrIob2), fmt::ptr(aioNmgrIob2->vptr));
// enable hack to make PIN pad work for KFC in BI2X mode // enable hack to make PIN pad work for KFC in BI2X mode
// this explicit check in the I/O init path is necessary // this explicit check in the I/O init path is necessary
// (as opposed to just doing a check for "isValkyrieCabMode?") // (as opposed to just doing a check for "isValkyrieCabMode?")
// because there are hex edits that allow you to use legacy (KFC/BIO2) IO while in Valk mode // because there are hex edits that allow you to use legacy (KFC/BIO2) IO while in Valk mode
acioemu::ICCA_DEVICE_HACK = true; acioemu::ICCA_DEVICE_HACK = true;
return &aioNmgrIob2; return aioNmgrIob2;
} }
static int64_t __fastcall aioIob2Bi2x_WriteFirmGetState(int64_t a1) { static int64_t __fastcall aioIob2Bi2x_WriteFirmGetState(int64_t a1) {
@@ -411,3 +473,5 @@ namespace games::sdvx {
aioNMgrIob2_Create, &aioNMgrIob2_Create_orig); aioNMgrIob2_Create, &aioNMgrIob2_Create_orig);
} }
} }
#endif
+4
View File
@@ -1,6 +1,10 @@
#pragma once #pragma once
#if SPICE64
namespace games::sdvx { namespace games::sdvx {
void bi2x_hook_init(); void bi2x_hook_init();
} }
#endif
+15 -9
View File
@@ -2,7 +2,6 @@
#include <external/robin_hood.h> #include <external/robin_hood.h>
#include "avs/game.h"
#include "games/shared/lcdhandle.h" #include "games/shared/lcdhandle.h"
#include "games/io.h" #include "games/io.h"
#include "hooks/audio/audio.h" #include "hooks/audio/audio.h"
@@ -19,6 +18,8 @@
#include "util/detour.h" #include "util/detour.h"
#include "util/logging.h" #include "util/logging.h"
#include "util/sigscan.h" #include "util/sigscan.h"
#include "util/socd_cleaner.h"
#include "util/time.h"
#include "util/libutils.h" #include "util/libutils.h"
#include "misc/wintouchemu.h" #include "misc/wintouchemu.h"
#include "misc/eamuse.h" #include "misc/eamuse.h"
@@ -64,10 +65,6 @@ namespace games::sdvx {
// prevent the game from randomly picking an ASIO device // prevent the game from randomly picking an ASIO device
if (!USE_ASIO) { if (!USE_ASIO) {
log_info(
"sdvx::asio",
"game tried to access HKLM\\SOFTWARE\\ASIO but ASIO is disabled; "
"access prevented, ASIO init will fail");
return ERROR_FILE_NOT_FOUND; return ERROR_FILE_NOT_FOUND;
} }
// convince the game to use our hook (so we can swap in the preferred device) // convince the game to use our hook (so we can swap in the preferred device)
@@ -331,9 +328,8 @@ namespace games::sdvx {
} }
#ifdef SPICE64 // SDVX5+ specific code #ifdef SPICE64 // SDVX5+ specific code
this->VALKYRIE_MODEL = avs::game::SPEC[0] == 'G' || avs::game::SPEC[0] == 'H';
// check -monitor + UFC mode // check -monitor + UFC mode
if (!GRAPHICS_WINDOWED && D3D9_ADAPTER.has_value() && this->VALKYRIE_MODEL) { if (!GRAPHICS_WINDOWED && D3D9_ADAPTER.has_value() && is_valkyrie_model()) {
SHOW_VM_MONITOR_WARNING = true; SHOW_VM_MONITOR_WARNING = true;
log_warning( log_warning(
"sdvx", "sdvx",
@@ -362,6 +358,16 @@ namespace games::sdvx {
this->detect_sound_output_device(); this->detect_sound_output_device();
#endif #endif
// SOCD
auto options = games::get_options(eamuse_get_game());
socd::ALGORITHM = socd::SocdAlgorithm::PreferRecent;
if (options->at(launcher::Options::SDVXDigitalKnobSocd).is_active()) {
if (options->at(launcher::Options::SDVXDigitalKnobSocd).value_text() == "neutral") {
socd::ALGORITHM = socd::SocdAlgorithm::Neutral;
} else if (options->at(launcher::Options::SDVXDigitalKnobSocd).value_text() == "last") {
socd::ALGORITHM = socd::SocdAlgorithm::PreferRecent;
}
}
} }
void SDVXGame::attach() { void SDVXGame::attach() {
@@ -370,7 +376,7 @@ namespace games::sdvx {
#ifdef SPICE64 // SDVX5+ specific code #ifdef SPICE64 // SDVX5+ specific code
// LCD handle // LCD handle
if (!this->VALKYRIE_MODEL) { if (!is_valkyrie_model()) {
devicehook_init(); devicehook_init();
devicehook_add(new games::shared::LCDHandle()); devicehook_add(new games::shared::LCDHandle());
} }
@@ -409,7 +415,7 @@ namespace games::sdvx {
nvapi_hook::initialize(avs::game::DLL_INSTANCE); nvapi_hook::initialize(avs::game::DLL_INSTANCE);
} }
if (this->VALKYRIE_MODEL) { if (is_valkyrie_model()) {
// hook touch window // hook touch window
// in windowed mode, game can accept mouse input on the second screen // in windowed mode, game can accept mouse input on the second screen
if (!NATIVETOUCH && !GRAPHICS_WINDOWED) { if (!NATIVETOUCH && !GRAPHICS_WINDOWED) {
+8 -6
View File
@@ -4,6 +4,7 @@
#include <optional> #include <optional>
#include <cstdint> #include <cstdint>
#include "avs/game.h"
#include "games/game.h" #include "games/game.h"
namespace games::sdvx { namespace games::sdvx {
@@ -20,12 +21,18 @@ namespace games::sdvx {
extern bool NATIVETOUCH; extern bool NATIVETOUCH;
extern uint8_t DIGITAL_KNOB_SENS; extern uint8_t DIGITAL_KNOB_SENS;
extern std::optional<std::string> ASIO_DRIVER; extern std::optional<std::string> ASIO_DRIVER;
extern bool BI2X_INITIALIZED;
extern SdvxOverlayPosition OVERLAY_POS; extern SdvxOverlayPosition OVERLAY_POS;
// states // states
extern bool SHOW_VM_MONITOR_WARNING; extern bool SHOW_VM_MONITOR_WARNING;
static inline bool is_valkyrie_model() {
return (
avs::game::is_model("KFC") &&
(avs::game::SPEC[0] == 'G' || avs::game::SPEC[0] == 'H')
);
}
class SDVXGame : public games::Game { class SDVXGame : public games::Game {
public: public:
SDVXGame(); SDVXGame();
@@ -34,11 +41,6 @@ namespace games::sdvx {
virtual void post_attach() override; virtual void post_attach() override;
virtual void detach() override; virtual void detach() override;
private: private:
// don't be tempted to make this into a public or a global variable
// various modules need to check if VM mode is active and they should
// not depend on the fact that SDVX module is active (since it can be
// inactive on cabs or partial I/O setup)
bool VALKYRIE_MODEL = false;
void detect_sound_output_device(); void detect_sound_output_device();
}; };
} }
@@ -82,7 +82,10 @@ HRESULT STDMETHODCALLTYPE WrappedIMMDevice::Activate(
return ret; return ret;
} }
if (iid == IID_IAudioClient) { // almost all games request IAudioClient
// so far we have not seen any games request IAudioClient2
// SDVX EG Final uses IID_IAudioClient3, but only when shared mode patch is on
if (iid == IID_IAudioClient || iid == IID_IAudioClient3) {
// prevent initialization recursion when using some ASIO backends that proxy to DirectSound, WASAPI, or WDM // prevent initialization recursion when using some ASIO backends that proxy to DirectSound, WASAPI, or WDM
// like ASIO4All or FlexASIO // like ASIO4All or FlexASIO
@@ -92,23 +95,18 @@ HRESULT STDMETHODCALLTYPE WrappedIMMDevice::Activate(
} }
std::lock_guard initialize_guard(hooks::audio::INITIALIZE_LOCK, std::adopt_lock); std::lock_guard initialize_guard(hooks::audio::INITIALIZE_LOCK, std::adopt_lock);
auto client = reinterpret_cast<IAudioClient *>(*ppInterface);
// release old audio client if initialized // release old audio client if initialized
if (hooks::audio::CLIENT) { if (hooks::audio::CLIENT) {
hooks::audio::CLIENT->Release(); hooks::audio::CLIENT->Release();
} }
/* IAudioClient *client = nullptr;
ret = wrap_audio_client(pReal, dwClsCtx, pActivationParams, &client); if (iid == IID_IAudioClient) {
if (FAILED(ret)) { client = wrap_audio_client(reinterpret_cast<IAudioClient *>(*ppInterface));
return ret; } else { // IID_IAudioClient3
client = wrap_audio_client3(reinterpret_cast<IAudioClient3 *>(*ppInterface));
} }
*/
client = wrap_audio_client(client);
*ppInterface = client; *ppInterface = client;
// persist the audio client // persist the audio client
hooks::audio::CLIENT = client; hooks::audio::CLIENT = client;
hooks::audio::CLIENT->AddRef(); hooks::audio::CLIENT->AddRef();
@@ -10,6 +10,7 @@
#include "hooks/audio/implementations/asio.h" #include "hooks/audio/implementations/asio.h"
#include "hooks/audio/implementations/wave_out.h" #include "hooks/audio/implementations/wave_out.h"
//#include "util/co_task_mem_ptr.h" //#include "util/co_task_mem_ptr.h"
#include "util/utils.h"
#include "defs.h" #include "defs.h"
#include "dummy_audio_client.h" #include "dummy_audio_client.h"
@@ -40,114 +41,9 @@ static void fix_rec_format(WAVEFORMATEX *pFormat) {
pFormat->nAvgBytesPerSec = pFormat->nSamplesPerSec * pFormat->nBlockAlign; pFormat->nAvgBytesPerSec = pFormat->nSamplesPerSec * pFormat->nBlockAlign;
} }
// TODO(felix): is it appropriate to automatically switch to shared mode? should we do a
// `MessageBox` to notify the user?
/*
static bool check_for_exclusive_access(IAudioClient *client) {
static bool checked_once = false;
static bool previous_check_result = false;
CoTaskMemPtr<WAVEFORMATEX> mix_format;
REFERENCE_TIME requested_duration = 0;
if (checked_once) {
return previous_check_result;
}
if (audio::BACKEND.has_value()) {
return false;
}
// scope function so it has access to the local static variables
auto set_result = [](bool result) {
checked_once = true;
previous_check_result = result;
return result;
};
HRESULT ret = client->GetMixFormat(mix_format.ppv());
if (FAILED(ret)) {
PRINT_FAILED_RESULT("IAudioClient::GetMixFormat", ret);
return set_result(false);
}
log_info("audio::wasapi", "Mix Format:");
print_format(mix_format.data());
ret = client->IsFormatSupported(AUDCLNT_SHAREMODE_EXCLUSIVE, mix_format.data(), nullptr);
if (ret == AUDCLNT_E_UNSUPPORTED_FORMAT) {
auto mix_format_ex = reinterpret_cast<WAVEFORMATEXTENSIBLE *>(mix_format.data());
log_warning("audio::wasapi", "device does not natively support the mix format, converting to PCM");
if (mix_format->wFormatTag == WAVE_FORMAT_EXTENSIBLE &&
IsEqualGUID(GUID_KSDATAFORMAT_SUBTYPE_IEEE_FLOAT, mix_format_ex->SubFormat))
{
mix_format_ex->Format.wBitsPerSample = 16;
mix_format_ex->Format.nBlockAlign = mix_format_ex->Format.nChannels * (mix_format_ex->Format.wBitsPerSample / 8);
mix_format_ex->Format.nAvgBytesPerSec = mix_format_ex->Format.nSamplesPerSec * mix_format_ex->Format.nBlockAlign;
mix_format_ex->Samples.wValidBitsPerSample = 16;
mix_format_ex->SubFormat = GUID_KSDATAFORMAT_SUBTYPE_PCM;
} else if (mix_format->wFormatTag == WAVE_FORMAT_IEEE_FLOAT) {
mix_format->wBitsPerSample = 16;
mix_format->nBlockAlign = mix_format->nChannels * (mix_format->wBitsPerSample / 8);
mix_format->nAvgBytesPerSec = mix_format->nSamplesPerSec * mix_format->nBlockAlign;
mix_format->wFormatTag = WAVE_FORMAT_PCM;
} else {
log_warning("audio::wasapi", "mix format is not a floating point format");
return set_result(false);
}
ret = client->IsFormatSupported(AUDCLNT_SHAREMODE_EXCLUSIVE, mix_format.data(), nullptr);
if (FAILED(ret)) {
log_warning("audio::wasapi", "mix format is not supported");
return set_result(false);
}
}
ret = client->GetDevicePeriod(nullptr, &requested_duration);
if (FAILED(ret)) {
PRINT_FAILED_RESULT("IAudioClient::GetDevicePeriod", ret);
return false;
}
ret = client->Initialize(
AUDCLNT_SHAREMODE_EXCLUSIVE,
AUDCLNT_STREAMFLAGS_EVENTCALLBACK,
requested_duration,
requested_duration,
mix_format.data(),
nullptr);
if (ret == AUDCLNT_E_BUFFER_SIZE_NOT_ALIGNED || SUCCEEDED(ret)) {
log_info("audio::wasapi", "exclusive mode is available, disabling backend");
return set_result(true);
} else {
log_warning("audio::wasapi", "exclusive mode is not available, enabling backend, hr={}", FMT_HRESULT(ret));
}
return set_result(false);
}
HRESULT wrap_audio_client(
IMMDevice *device,
DWORD cls_ctx,
PROPVARIANT *activation_params,
IAudioClient **audio_client)
{
auto exclusive_available = check_for_exclusive_access(*audio_client);
(*audio_client)->Stop();
(*audio_client)->Reset();
(*audio_client)->Release();
*audio_client = nullptr;
SAFE_CALL("IMMDevice", "Activate", device->Activate(
IID_IAudioClient,
cls_ctx,
activation_params,
reinterpret_cast<void **>(audio_client)));
*/
IAudioClient *wrap_audio_client(IAudioClient *audio_client) { IAudioClient *wrap_audio_client(IAudioClient *audio_client) {
log_misc("audio::wasapi", "wrapping IAudioClient");
AudioBackend *backend = nullptr; AudioBackend *backend = nullptr;
bool requires_dummy = false; bool requires_dummy = false;
@@ -164,9 +60,6 @@ IAudioClient *wrap_audio_client(IAudioClient *audio_client) {
break; break;
} }
} }
//} else if (!exclusive_available) {
// backend = new WaveOutBackend();
//}
IAudioClient *new_client; IAudioClient *new_client;
@@ -182,6 +75,23 @@ IAudioClient *wrap_audio_client(IAudioClient *audio_client) {
return new_client; return new_client;
} }
IAudioClient3 *wrap_audio_client3(IAudioClient3 *audio_client) {
// TODO: ASIO backend for IAudioClient3, if there is a game that needs it
log_misc("audio::wasapi", "wrapping IAudioClient3");
if (hooks::audio::BACKEND.has_value()) {
log_fatal(
"audio::wasapi",
"IAudioClient3 does not currently support backends! clear -audiobackend and try again");
}
if (hooks::audio::USE_DUMMY) {
log_fatal(
"audio::wasapi",
"IAudioClient3 does not currently support dummy context, clear -audiodummy and try again");
}
return new WrappedIAudioClient(audio_client, nullptr);
}
// IUnknown // IUnknown
HRESULT STDMETHODCALLTYPE WrappedIAudioClient::QueryInterface(REFIID riid, void **ppvObj) { HRESULT STDMETHODCALLTYPE WrappedIAudioClient::QueryInterface(REFIID riid, void **ppvObj) {
@@ -190,8 +100,8 @@ HRESULT STDMETHODCALLTYPE WrappedIAudioClient::QueryInterface(REFIID riid, void
} }
if (riid == IID_WrappedIAudioClient || if (riid == IID_WrappedIAudioClient ||
riid == IID_IAudioClient) riid == IID_IAudioClient ||
{ riid == IID_IAudioClient3) {
this->AddRef(); this->AddRef();
*ppvObj = this; *ppvObj = this;
@@ -282,20 +192,6 @@ HRESULT STDMETHODCALLTYPE WrappedIAudioClient::Initialize(
} }
log_info("audio::wasapi", "IAudioClient::Initialize success, hr={}", FMT_HRESULT(ret)); log_info("audio::wasapi", "IAudioClient::Initialize success, hr={}", FMT_HRESULT(ret));
/*
if (ShareMode == AUDCLNT_SHAREMODE_SHARED) {
IAudioClockAdjustment *clock = nullptr;
SAFE_CALL("IAudioClient", "GetService", pReal->GetService(
IID_IAudioClockAdjustment,
reinterpret_cast<void **>(&clock)));
SAFE_CALL("IAudioClockAdjustment", "SetSampleRate", clock->SetSampleRate(
static_cast<float>(pFormat->nSamplesPerSec)));
}
*/
copy_wave_format(&hooks::audio::FORMAT, pFormat); copy_wave_format(&hooks::audio::FORMAT, pFormat);
return ret; return ret;
@@ -482,3 +378,93 @@ HRESULT STDMETHODCALLTYPE WrappedIAudioClient::GetService(REFIID riid, void **pp
return ret; return ret;
} }
HRESULT STDMETHODCALLTYPE WrappedIAudioClient::IsOffloadCapable(
AUDIO_STREAM_CATEGORY Category,
BOOL *pbOffloadCapable) {
WRAP_VERBOSE;
CHECK_RESULT(pReal3->IsOffloadCapable(Category, pbOffloadCapable));
}
HRESULT STDMETHODCALLTYPE WrappedIAudioClient::SetClientProperties(
const AudioClientProperties *pProperties) {
WRAP_VERBOSE;
CHECK_RESULT(pReal3->SetClientProperties(pProperties));
}
HRESULT STDMETHODCALLTYPE WrappedIAudioClient::GetBufferSizeLimits(
const WAVEFORMATEX *pFormat,
BOOL bEventDriven,
REFERENCE_TIME *phnsMinBufferDuration,
REFERENCE_TIME *phnsMaxBufferDuration) {
WRAP_VERBOSE;
CHECK_RESULT(pReal3->GetBufferSizeLimits(
pFormat,
bEventDriven,
phnsMinBufferDuration,
phnsMaxBufferDuration));
}
HRESULT STDMETHODCALLTYPE WrappedIAudioClient::GetSharedModeEnginePeriod(
const WAVEFORMATEX *pFormat,
UINT32 *pDefaultPeriodInFrames,
UINT32 *pFundamentalPeriodInFrames,
UINT32 *pMinPeriodInFrames,
UINT32 *pMaxPeriodInFrames) {
WRAP_VERBOSE;
CHECK_RESULT(pReal3->GetSharedModeEnginePeriod(
pFormat,
pDefaultPeriodInFrames,
pFundamentalPeriodInFrames,
pMinPeriodInFrames,
pMaxPeriodInFrames));
}
HRESULT STDMETHODCALLTYPE WrappedIAudioClient::GetCurrentSharedModeEnginePeriod(
WAVEFORMATEX **ppFormat,
UINT32 *pCurrentPeriodInFrames) {
WRAP_VERBOSE;
CHECK_RESULT(pReal3->GetCurrentSharedModeEnginePeriod(
ppFormat,
pCurrentPeriodInFrames));
}
HRESULT STDMETHODCALLTYPE WrappedIAudioClient::InitializeSharedAudioStream(
DWORD StreamFlags,
UINT32 PeriodInFrames,
const WAVEFORMATEX *pFormat,
LPCGUID AudioSessionGuid) {
if (!pFormat) {
return E_POINTER;
}
// verbose output
log_info("audio::wasapi", "IAudioClient3::InitializeSharedAudioStream hook hit");
log_info("audio::wasapi", "... ShareMode : {}", share_mode_str(AUDCLNT_SHAREMODE_SHARED));
log_info("audio::wasapi", "... StreamFlags : {}", stream_flags_str(StreamFlags));
log_info("audio::wasapi", "... PeriodInFrames : {}", PeriodInFrames);
print_format(pFormat);
// call next
HRESULT ret = pReal3->InitializeSharedAudioStream(
StreamFlags,
PeriodInFrames,
pFormat,
AudioSessionGuid);
// check for failure
if (FAILED(ret)) {
PRINT_FAILED_RESULT("IAudioClient3", "InitializeSharedAudioStream", ret);
return ret;
}
log_info("audio::wasapi", "IAudioClient3::InitializeSharedAudioStream success, hr={}", FMT_HRESULT(ret));
copy_wave_format(&hooks::audio::FORMAT, pFormat);
return ret;
}
@@ -16,9 +16,16 @@ static const GUID IID_WrappedIAudioClient = {
}; };
IAudioClient *wrap_audio_client(IAudioClient *client); IAudioClient *wrap_audio_client(IAudioClient *client);
IAudioClient3 *wrap_audio_client3(IAudioClient3 *client);
struct WrappedIAudioClient : IAudioClient { struct WrappedIAudioClient : IAudioClient3 {
explicit WrappedIAudioClient(IAudioClient *orig, AudioBackend *backend) : pReal(orig), backend(backend) {
explicit WrappedIAudioClient(IAudioClient3 *orig3, AudioBackend *backend) :
pReal(orig3), pReal3(orig3), backend(backend) {
}
explicit WrappedIAudioClient(IAudioClient *orig, AudioBackend *backend) :
pReal(orig), pReal3(nullptr), backend(backend) {
} }
WrappedIAudioClient(const WrappedIAudioClient &) = delete; WrappedIAudioClient(const WrappedIAudioClient &) = delete;
@@ -47,9 +54,43 @@ struct WrappedIAudioClient : IAudioClient {
HRESULT STDMETHODCALLTYPE GetService(REFIID riid, void **ppv) override; HRESULT STDMETHODCALLTYPE GetService(REFIID riid, void **ppv) override;
#pragma endregion #pragma endregion
IAudioClient *const pReal; #pragma region IAudioClient2
AudioBackend *const backend; HRESULT STDMETHODCALLTYPE IsOffloadCapable(
AUDIO_STREAM_CATEGORY Category,
BOOL *pbOffloadCapable) override;
HRESULT STDMETHODCALLTYPE SetClientProperties(
const AudioClientProperties *pProperties) override;
HRESULT STDMETHODCALLTYPE GetBufferSizeLimits(
const WAVEFORMATEX *pFormat,
BOOL bEventDriven,
REFERENCE_TIME *phnsMinBufferDuration,
REFERENCE_TIME *phnsMaxBufferDuration) override;
#pragma endregion
#pragma region IAudioClient3
HRESULT STDMETHODCALLTYPE GetSharedModeEnginePeriod(
const WAVEFORMATEX *pFormat,
UINT32 *pDefaultPeriodInFrames,
UINT32 *pFundamentalPeriodInFrames,
UINT32 *pMinPeriodInFrames,
UINT32 *pMaxPeriodInFrames) override;
HRESULT STDMETHODCALLTYPE GetCurrentSharedModeEnginePeriod(
WAVEFORMATEX **ppFormat,
UINT32 *pCurrentPeriodInFrames) override;
HRESULT STDMETHODCALLTYPE InitializeSharedAudioStream(
DWORD StreamFlags,
UINT32 PeriodInFrames,
const WAVEFORMATEX *pFormat,
LPCGUID AudioSessionGuid) override;
#pragma endregion
IAudioClient *const pReal;
IAudioClient3 *const pReal3;
AudioBackend *const backend;
bool exclusive_mode = false; bool exclusive_mode = false;
int frame_size = 0; int frame_size = 0;
}; };
@@ -149,5 +149,12 @@ LowLatencyAudioClient *LowLatencyAudioClient::Create(IMMDevice *device) {
log_info("audio::lowlatency", "... max buffer size : {} samples ({} ms)", maxPeriod, 1000.0f * maxPeriod / pFormat->nSamplesPerSec); log_info("audio::lowlatency", "... max buffer size : {} samples ({} ms)", maxPeriod, 1000.0f * maxPeriod / pFormat->nSamplesPerSec);
log_info("audio::lowlatency", "... default buffer size : {} samples ({} ms)", defaultPeriod, 1000.0f * defaultPeriod / pFormat->nSamplesPerSec); log_info("audio::lowlatency", "... default buffer size : {} samples ({} ms)", defaultPeriod, 1000.0f * defaultPeriod / pFormat->nSamplesPerSec);
log_info("audio::lowlatency", "... Windows will use minimum buffer size (instead of default) for shared mode audio clients from now on"); log_info("audio::lowlatency", "... Windows will use minimum buffer size (instead of default) for shared mode audio clients from now on");
if (minPeriod < defaultPeriod) {
log_info("audio::lowlatency", "minimum period is less than default period, you will see latency improvement");
} else {
log_warning("audio::lowlatency", "minimum period is not less than default period, you will see NO latency improvement");
log_warning("audio::lowlatency", "this an audio driver / hardware limitation");
}
return new LowLatencyAudioClient(audioClient); return new LowLatencyAudioClient(audioClient);
} }
@@ -13,6 +13,7 @@
#include "avs/game.h" #include "avs/game.h"
#include "cfg/screen_resize.h" #include "cfg/screen_resize.h"
#include "games/gitadora/gitadora.h"
#include "games/iidx/iidx.h" #include "games/iidx/iidx.h"
#include "games/sdvx/sdvx.h" #include "games/sdvx/sdvx.h"
#include "games/mfc/mfc.h" #include "games/mfc/mfc.h"
@@ -41,6 +42,15 @@
#undef min #undef min
#endif #endif
#define D3D9_BACKEND_DEBUG 0
#if D3D9_BACKEND_DEBUG
#define log_debug(module, format_str, ...) logger::push( \
LOG_FORMAT("M", module, format_str, ## __VA_ARGS__), logger::Style::GREY)
#else
#define log_debug(module, format_str, ...)
#endif
#define CHECK_RESULT(x) \ #define CHECK_RESULT(x) \
do { \ do { \
HRESULT __ret = (x); \ HRESULT __ret = (x); \
@@ -514,7 +524,7 @@ HRESULT STDMETHODCALLTYPE WrappedIDirect3D9::EnumAdapterModes(
if (!modified && games::iidx::is_tdj_fhd() && refresh < 110) { if (!modified && games::iidx::is_tdj_fhd() && refresh < 110) {
if ((width == 1920 && height == 1080) || if ((width == 1920 && height == 1080) ||
(width == 1280 && height == 720)){ (width == 1280 && height == 720)){
log_misc( log_debug(
"graphics::d3d9", "removing mode {}, {}x{} @ {}Hz (for TDJ FHD)", "graphics::d3d9", "removing mode {}, {}x{} @ {}Hz (for TDJ FHD)",
Mode, width, height, refresh); Mode, width, height, refresh);
memset(pMode, 0, sizeof(*pMode)); memset(pMode, 0, sizeof(*pMode));
@@ -527,7 +537,7 @@ HRESULT STDMETHODCALLTYPE WrappedIDirect3D9::EnumAdapterModes(
// - skip 75 and 90 Hz entries because LDJ game timing is messed up with it // - skip 75 and 90 Hz entries because LDJ game timing is messed up with it
// (TDJ should be fine as it assumes a fixed 60 Hz timing) // (TDJ should be fine as it assumes a fixed 60 Hz timing)
if (!modified && (width == 1360 || width == 1366 || refresh == 75 || refresh == 90)) { if (!modified && (width == 1360 || width == 1366 || refresh == 75 || refresh == 90)) {
log_misc( log_debug(
"graphics::d3d9", "removing mode {}, {}x{} @ {}Hz (for LDJ/TDJ)", "graphics::d3d9", "removing mode {}, {}x{} @ {}Hz (for LDJ/TDJ)",
Mode, width, height, refresh); Mode, width, height, refresh);
memset(pMode, 0, sizeof(*pMode)); memset(pMode, 0, sizeof(*pMode));
@@ -535,7 +545,7 @@ HRESULT STDMETHODCALLTYPE WrappedIDirect3D9::EnumAdapterModes(
} }
if (!modified && !games::iidx::TDJ_MODE && games::iidx::FORCE_720P && (height > 720)) { if (!modified && !games::iidx::TDJ_MODE && games::iidx::FORCE_720P && (height > 720)) {
log_misc( log_debug(
"graphics::d3d9", "removing mode {}, {}x{} @ {}Hz (-iidxforce720p)", "graphics::d3d9", "removing mode {}, {}x{} @ {}Hz (-iidxforce720p)",
Mode, width, height, refresh); Mode, width, height, refresh);
memset(pMode, 0, sizeof(*pMode)); memset(pMode, 0, sizeof(*pMode));
@@ -803,6 +813,14 @@ HRESULT STDMETHODCALLTYPE WrappedIDirect3D9::CreateDevice(
"either use -graphics-force-refresh option or change the desktop resolution beforehand."); "either use -graphics-force-refresh option or change the desktop resolution beforehand.");
} }
if (!GRAPHICS_WINDOWED && num_adapters >= 2 && GRAPHICS_FORCE_REFRESH_SUB.has_value()) {
log_info("graphics::d3d9", "force sub refresh rate: {} => {} Hz (-graphics-force-refresh-sub option)",
pPresentationParameters[1].FullScreen_RefreshRateInHz,
GRAPHICS_FORCE_REFRESH_SUB.value());
pPresentationParameters[1].FullScreen_RefreshRateInHz = GRAPHICS_FORCE_REFRESH_SUB.value();
}
// force single adapter // force single adapter
if (GRAPHICS_FORCE_SINGLE_ADAPTER) { if (GRAPHICS_FORCE_SINGLE_ADAPTER) {
log_info("graphics::d3d9", "disabling adapter group device with force single adapter mode"); log_info("graphics::d3d9", "disabling adapter group device with force single adapter mode");
@@ -927,26 +945,37 @@ HRESULT STDMETHODCALLTYPE WrappedIDirect3D9::CreateDeviceEx(
for (size_t i = 0; i < num_adapters; i++) { for (size_t i = 0; i < num_adapters; i++) {
auto *params = &pPresentationParameters[i]; auto *params = &pPresentationParameters[i];
if (!GRAPHICS_WINDOWED && i == 0) { if (!GRAPHICS_WINDOWED){
GRAPHICS_FS_ORIGINAL_WIDTH = params->BackBufferWidth; if (i == 0) {
GRAPHICS_FS_ORIGINAL_HEIGHT = params->BackBufferHeight; GRAPHICS_FS_ORIGINAL_WIDTH = params->BackBufferWidth;
log_misc("graphics::d3d9", "original resolution: {}x{}", GRAPHICS_FS_ORIGINAL_WIDTH, GRAPHICS_FS_ORIGINAL_HEIGHT); GRAPHICS_FS_ORIGINAL_HEIGHT = params->BackBufferHeight;
if (GRAPHICS_FS_CUSTOM_RESOLUTION.has_value()) { log_misc("graphics::d3d9", "original resolution: {}x{}", GRAPHICS_FS_ORIGINAL_WIDTH, GRAPHICS_FS_ORIGINAL_HEIGHT);
if (GRAPHICS_FS_CUSTOM_RESOLUTION.has_value()) {
log_misc(
"graphics::d3d9",
"use custom resolution {}x{} => {}x{}",
params->BackBufferWidth, params->BackBufferHeight,
GRAPHICS_FS_CUSTOM_RESOLUTION.value().first,
GRAPHICS_FS_CUSTOM_RESOLUTION.value().second);
params->BackBufferWidth = GRAPHICS_FS_CUSTOM_RESOLUTION.value().first;
params->BackBufferHeight = GRAPHICS_FS_CUSTOM_RESOLUTION.value().second;
} else if (GRAPHICS_FS_ORIENTATION_SWAP) {
log_misc(
"graphics::d3d9",
"swap full orientation {}x{} => {}x{}",
params->BackBufferWidth, params->BackBufferHeight,
params->BackBufferHeight, params->BackBufferWidth);
std::swap(params->BackBufferWidth, params->BackBufferHeight);
}
} else if (i == 1 && GRAPHICS_FS_CUSTOM_RESOLUTION_SUB.has_value()) {
log_misc( log_misc(
"graphics::d3d9", "graphics::d3d9",
"use custom resolution {}x{} => {}x{}", "use custom sub resolution {}x{} => {}x{}",
params->BackBufferWidth, params->BackBufferHeight, params->BackBufferWidth, params->BackBufferHeight,
GRAPHICS_FS_CUSTOM_RESOLUTION.value().first, GRAPHICS_FS_CUSTOM_RESOLUTION_SUB.value().first,
GRAPHICS_FS_CUSTOM_RESOLUTION.value().second); GRAPHICS_FS_CUSTOM_RESOLUTION_SUB.value().second);
params->BackBufferWidth = GRAPHICS_FS_CUSTOM_RESOLUTION.value().first; params->BackBufferWidth = GRAPHICS_FS_CUSTOM_RESOLUTION_SUB.value().first;
params->BackBufferHeight = GRAPHICS_FS_CUSTOM_RESOLUTION.value().second; params->BackBufferHeight = GRAPHICS_FS_CUSTOM_RESOLUTION_SUB.value().second;
} else if (GRAPHICS_FS_ORIENTATION_SWAP) {
log_misc(
"graphics::d3d9",
"swap full orientation {}x{} => {}x{}",
params->BackBufferWidth, params->BackBufferHeight,
params->BackBufferHeight, params->BackBufferWidth);
std::swap(params->BackBufferWidth, params->BackBufferHeight);
} }
} }
@@ -974,12 +1003,17 @@ HRESULT STDMETHODCALLTYPE WrappedIDirect3D9::CreateDeviceEx(
for (size_t i = 0; i < num_adapters; i++) { for (size_t i = 0; i < num_adapters; i++) {
auto *fullscreen_display_mode = &pFullscreenDisplayMode[i]; auto *fullscreen_display_mode = &pFullscreenDisplayMode[i];
if (!GRAPHICS_WINDOWED && i == 0) { if (!GRAPHICS_WINDOWED) {
if (GRAPHICS_FS_CUSTOM_RESOLUTION.has_value()) { if (i == 0) {
fullscreen_display_mode->Width = GRAPHICS_FS_CUSTOM_RESOLUTION.value().first; if (GRAPHICS_FS_CUSTOM_RESOLUTION.has_value()) {
fullscreen_display_mode->Height = GRAPHICS_FS_CUSTOM_RESOLUTION.value().second; fullscreen_display_mode->Width = GRAPHICS_FS_CUSTOM_RESOLUTION.value().first;
} else if (GRAPHICS_FS_ORIENTATION_SWAP) { fullscreen_display_mode->Height = GRAPHICS_FS_CUSTOM_RESOLUTION.value().second;
std::swap(fullscreen_display_mode->Width, fullscreen_display_mode->Height); } else if (GRAPHICS_FS_ORIENTATION_SWAP) {
std::swap(fullscreen_display_mode->Width, fullscreen_display_mode->Height);
}
} else if (i == 1 && GRAPHICS_FS_CUSTOM_RESOLUTION_SUB.has_value()) {
fullscreen_display_mode->Width = GRAPHICS_FS_CUSTOM_RESOLUTION_SUB.value().first;
fullscreen_display_mode->Height = GRAPHICS_FS_CUSTOM_RESOLUTION_SUB.value().second;
} }
} }
@@ -1022,6 +1056,17 @@ HRESULT STDMETHODCALLTYPE WrappedIDirect3D9::CreateDeviceEx(
} }
} }
if (!GRAPHICS_WINDOWED && num_adapters >= 2 && GRAPHICS_FORCE_REFRESH_SUB.has_value()) {
log_info("graphics::d3d9", "force sub refresh rate: {} => {} Hz (-graphics-force-refresh-sub option)",
pPresentationParameters[1].FullScreen_RefreshRateInHz,
GRAPHICS_FORCE_REFRESH_SUB.value());
pPresentationParameters[1].FullScreen_RefreshRateInHz = GRAPHICS_FORCE_REFRESH_SUB.value();
if (pFullscreenDisplayMode) {
pFullscreenDisplayMode[1].RefreshRate = GRAPHICS_FORCE_REFRESH_SUB.value();
}
}
// force single adapter // force single adapter
if (GRAPHICS_FORCE_SINGLE_ADAPTER) { if (GRAPHICS_FORCE_SINGLE_ADAPTER) {
log_info("graphics::d3d9", "disabling adapter group device with force single adapter mode"); log_info("graphics::d3d9", "disabling adapter group device with force single adapter mode");
@@ -1142,9 +1187,15 @@ IDirect3DSurface9 *graphics_d3d9_ldj_get_sub_screen() {
} }
static void graphics_d3d9_ldj_on_present(IDirect3DDevice9 *wrapped_device) { static void graphics_d3d9_ldj_on_present(IDirect3DDevice9 *wrapped_device) {
// iidx/sdvx
int swapchain = 1;
if (games::gitadora::is_arena_model()) {
swapchain = 3;
}
if (!ATTEMPTED_SUB_SWAP_CHAIN_ACQUIRE && SUB_SWAP_CHAIN == nullptr) { if (!ATTEMPTED_SUB_SWAP_CHAIN_ACQUIRE && SUB_SWAP_CHAIN == nullptr) {
ATTEMPTED_SUB_SWAP_CHAIN_ACQUIRE = true; ATTEMPTED_SUB_SWAP_CHAIN_ACQUIRE = true;
HRESULT hr = wrapped_device->GetSwapChain(1, &SUB_SWAP_CHAIN); HRESULT hr = wrapped_device->GetSwapChain(swapchain, &SUB_SWAP_CHAIN);
if (FAILED(hr)) { if (FAILED(hr)) {
log_warning( log_warning(
"graphics::d3d9", "graphics::d3d9",
@@ -1353,11 +1404,11 @@ void graphics_d3d9_on_present(
} }
// for IIDX TDJ / SDVX UFC, handle subscreen // for IIDX TDJ / SDVX UFC, handle subscreen
const bool is_vm = const bool is_vm = games::sdvx::is_valkyrie_model();
avs::game::is_model("KFC") &&
(avs::game::SPEC[0] == 'G' || avs::game::SPEC[0] == 'H');
const bool is_tdj = avs::game::is_model("LDJ") && games::iidx::TDJ_MODE; const bool is_tdj = avs::game::is_model("LDJ") && games::iidx::TDJ_MODE;
if (is_vm || is_tdj) { const bool is_gfdm_arena =
games::gitadora::is_arena_model() && games::gitadora::ARENA_SINGLE_WINDOW;
if (is_vm || is_tdj || is_gfdm_arena) {
graphics_d3d9_ldj_on_present(wrapped_device); graphics_d3d9_ldj_on_present(wrapped_device);
} }
@@ -4,6 +4,7 @@
#include <mutex> #include <mutex>
#include "avs/game.h" #include "avs/game.h"
#include "games/gitadora/gitadora.h"
#include "hooks/graphics/graphics.h" #include "hooks/graphics/graphics.h"
#include "overlay/overlay.h" #include "overlay/overlay.h"
#include "util/flags_helper.h" #include "util/flags_helper.h"
@@ -249,8 +250,16 @@ HRESULT STDMETHODCALLTYPE WrappedIDirect3DDevice9::CreateAdditionalSwapChain(
WRAP_VERBOSE; WRAP_VERBOSE;
HRESULT hr = pReal->CreateAdditionalSwapChain(pPresentationParameters, ppSwapChain); HRESULT hr = pReal->CreateAdditionalSwapChain(pPresentationParameters, ppSwapChain);
bool create_swap_chain = false;
if (avs::game::is_model({"LDJ", "KFC"})) { if (avs::game::is_model({"LDJ", "KFC"})) {
create_swap_chain = true;
} else if (games::gitadora::is_arena_model() &&
games::gitadora::ARENA_SINGLE_WINDOW &&
pPresentationParameters->BackBufferWidth == 800) {
create_swap_chain = true;
}
if (create_swap_chain) {
if (SUCCEEDED(hr) && !sub_swapchain) { if (SUCCEEDED(hr) && !sub_swapchain) {
sub_swapchain = new WrappedIDirect3DSwapChain9(this, *ppSwapChain); sub_swapchain = new WrappedIDirect3DSwapChain9(this, *ppSwapChain);
sub_swapchain->should_run_hooks = false; sub_swapchain->should_run_hooks = false;
@@ -295,7 +304,17 @@ HRESULT STDMETHODCALLTYPE WrappedIDirect3DDevice9::GetSwapChain(
return D3D_OK; return D3D_OK;
} }
bool swap = false;
if (iSwapChain == 1 && avs::game::is_model({"LDJ", "KFC"})) { if (iSwapChain == 1 && avs::game::is_model({"LDJ", "KFC"})) {
swap = true;
}
if (games::gitadora::is_arena_model() &&
games::gitadora::ARENA_SINGLE_WINDOW &&
iSwapChain == 3) {
swap = true;
}
if (swap) {
if (sub_swapchain) { if (sub_swapchain) {
sub_swapchain->AddRef(); sub_swapchain->AddRef();
*ppSwapChain = static_cast<IDirect3DSwapChain9 *>(sub_swapchain); *ppSwapChain = static_cast<IDirect3DSwapChain9 *>(sub_swapchain);
@@ -742,14 +761,12 @@ void SurfaceHook(IDirect3DDevice9 *pReal) {
const int w = param.BackBufferWidth; const int w = param.BackBufferWidth;
const int h = param.BackBufferHeight; const int h = param.BackBufferHeight;
if (cfg::SCREENRESIZE->enable_screen_resize) { // this code used to clear the surface using ColorFill on every call, but
RECT rect { // this turned out to be very expensive, leading to major frame drops in
0, // SDVX Live2D scenario
0, if (cfg::SCREENRESIZE->need_surface_clean) {
(LONG)topSurface_width, pReal->ColorFill(topSurface, nullptr, D3DCOLOR_XRGB(0, 0, 0));
(LONG)topSurface_height, cfg::SCREENRESIZE->need_surface_clean = false;
};
pReal->ColorFill(topSurface, &rect, D3DCOLOR_XRGB(0, 0, 0));
} }
D3DLOCKED_RECT rect; D3DLOCKED_RECT rect;
+83 -8
View File
@@ -62,6 +62,7 @@ graphics_orientation GRAPHICS_ADJUST_ORIENTATION = ORIENTATION_NORMAL;
bool GRAPHICS_WINDOWED = false; bool GRAPHICS_WINDOWED = false;
std::vector<HWND> GRAPHICS_WINDOWS; std::vector<HWND> GRAPHICS_WINDOWS;
UINT GRAPHICS_FORCE_REFRESH = 0; UINT GRAPHICS_FORCE_REFRESH = 0;
std::optional<uint32_t> GRAPHICS_FORCE_REFRESH_SUB;
std::optional<int> GRAPHICS_FORCE_VSYNC_BUFFER; std::optional<int> GRAPHICS_FORCE_VSYNC_BUFFER;
bool GRAPHICS_FORCE_SINGLE_ADAPTER = false; bool GRAPHICS_FORCE_SINGLE_ADAPTER = false;
bool GRAPHICS_PREVENT_SECONDARY_WINDOW = false; bool GRAPHICS_PREVENT_SECONDARY_WINDOW = false;
@@ -70,6 +71,7 @@ bool GRAPHICS_9_ON_12_REQUESTED_BY_GAME = false;
bool SUBSCREEN_FORCE_REDRAW = false; bool SUBSCREEN_FORCE_REDRAW = false;
bool D3D9_DEVICE_HOOK_DISABLE = false; bool D3D9_DEVICE_HOOK_DISABLE = false;
std::optional<std::pair<uint32_t, uint32_t>> GRAPHICS_FS_CUSTOM_RESOLUTION; std::optional<std::pair<uint32_t, uint32_t>> GRAPHICS_FS_CUSTOM_RESOLUTION;
std::optional<std::pair<uint32_t, uint32_t>> GRAPHICS_FS_CUSTOM_RESOLUTION_SUB;
bool GRAPHICS_FS_ORIENTATION_SWAP = false; bool GRAPHICS_FS_ORIENTATION_SWAP = false;
uint32_t GRAPHICS_FS_ORIGINAL_WIDTH = 0; uint32_t GRAPHICS_FS_ORIGINAL_WIDTH = 0;
uint32_t GRAPHICS_FS_ORIGINAL_HEIGHT = 0; uint32_t GRAPHICS_FS_ORIGINAL_HEIGHT = 0;
@@ -96,6 +98,7 @@ static decltype(SetCursor) *SetCursor_orig = nullptr;
static decltype(SetWindowLongA) *SetWindowLongA_orig = nullptr; 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 void reset_window_hook(HWND hWnd) { static void reset_window_hook(HWND hWnd) {
overlay::destroy(hWnd); overlay::destroy(hWnd);
@@ -298,6 +301,30 @@ static BOOL WINAPI ClipCursor_hook(const RECT *lpRect) {
return ClipCursor_orig(lpRect); return ClipCursor_orig(lpRect);
} }
static void fix_up_rb_auto_rotate_dimensions(DWORD &w, DWORD &h) {
// reflec beat
// for whatever reason, rb creates the first window it weird dimensions (774x1389 for reflesia, for example)
// so doing ChangeDisplaySettings as-is ends up with DISP_CHANGE_BADMODE
// while a second window (with title D3DProxyWindow) does get created with correct dimensions, it's
// too late to rotate the window then; as a workaround force the display change now with patched up dimensions
bool changed = false;
if (w == 774) {
// LBR, MBR reflesia
w = 768;
h = 1360;
changed = true;
} else if (w == 1086) {
// MBR (before reflesia)
w = 1080;
h = 1920;
changed = true;
}
if (changed) {
log_misc("graphics", "fix auto-rotate dimensions for reflec beat: w={}, h={}", w, h);
}
}
static HWND WINAPI CreateWindowExA_hook(DWORD dwExStyle, LPCSTR lpClassName, LPCSTR lpWindowName, static HWND WINAPI CreateWindowExA_hook(DWORD dwExStyle, LPCSTR lpClassName, LPCSTR lpWindowName,
DWORD dwStyle, int x, int y, int nWidth, int nHeight, DWORD dwStyle, int x, int y, int nWidth, int nHeight,
HWND hWndParent, HMENU hMenu, HINSTANCE hInstance, HWND hWndParent, HMENU hMenu, HINSTANCE hInstance,
@@ -322,10 +349,19 @@ static HWND WINAPI CreateWindowExA_hook(DWORD dwExStyle, LPCSTR lpClassName, LPC
// set display orientation and/or refresh rate // set display orientation and/or refresh rate
// only set orientation when the target window is portrait // only set orientation when the target window is portrait
// (avoid doing this for SDVX subscreen which is in landscape, for example) // (avoid doing this for SDVX subscreen which is in landscape, for example)
const auto adjust_orientation = auto adjust_orientation =
(GRAPHICS_ADJUST_ORIENTATION == ORIENTATION_CW || (GRAPHICS_ADJUST_ORIENTATION == ORIENTATION_CW ||
GRAPHICS_ADJUST_ORIENTATION == ORIENTATION_CCW); GRAPHICS_ADJUST_ORIENTATION == ORIENTATION_CCW);
// reflec beat
if (avs::game::is_model("KBR")) {
// KBR (rb1) launches landscape and draws rotated by itself, don't do any rotation
adjust_orientation = false;
} else if (avs::game::is_model({"LBR", "MBR"}) && window_name == "D3DProxyWindow") {
// do not auto-rotate for second window (D3DProxyWindow)
adjust_orientation = false;
}
if ((nHeight > nWidth && adjust_orientation) || GRAPHICS_FORCE_REFRESH > 0) { if ((nHeight > nWidth && adjust_orientation) || GRAPHICS_FORCE_REFRESH > 0) {
DEVMODE mode {}; DEVMODE mode {};
@@ -335,11 +371,16 @@ static HWND WINAPI CreateWindowExA_hook(DWORD dwExStyle, LPCSTR lpClassName, LPC
DWORD orientation = GRAPHICS_ADJUST_ORIENTATION == ORIENTATION_CW ? DMDO_90 : DMDO_270; DWORD orientation = GRAPHICS_ADJUST_ORIENTATION == ORIENTATION_CW ? DMDO_90 : DMDO_270;
log_misc( log_misc(
"graphics", "graphics",
"auto-rotate: call ChangeDisplaySettings and rotate display to DMDO_xx mode {}", "auto-rotate: call ChangeDisplaySettings and rotate display to DMDO_xx mode {}, w={}, h={}",
orientation); orientation, nWidth, nHeight);
mode.dmPelsWidth = nWidth; mode.dmPelsWidth = nWidth;
mode.dmPelsHeight = nHeight; mode.dmPelsHeight = nHeight;
mode.dmDisplayOrientation = orientation; mode.dmDisplayOrientation = orientation;
// reflec beat
if (avs::game::is_model({"LBR", "MBR"})) {
fix_up_rb_auto_rotate_dimensions(mode.dmPelsWidth, mode.dmPelsHeight);
}
} }
if (GRAPHICS_FORCE_REFRESH > 0) { if (GRAPHICS_FORCE_REFRESH > 0) {
@@ -354,7 +395,7 @@ static HWND WINAPI CreateWindowExA_hook(DWORD dwExStyle, LPCSTR lpClassName, LPC
const auto disp_res = ChangeDisplaySettings(&mode, CDS_FULLSCREEN); const auto disp_res = ChangeDisplaySettings(&mode, CDS_FULLSCREEN);
if (disp_res != DISP_CHANGE_SUCCESSFUL) { if (disp_res != DISP_CHANGE_SUCCESSFUL) {
log_warning("graphics", "failed to change display settings: {}", disp_res); log_warning("graphics", "ChangeDisplaySettings failed: {}", disp_res);
} }
} else { } else {
log_warning("graphics", "failed to get display settings"); log_warning("graphics", "failed to get display settings");
@@ -373,7 +414,7 @@ static HWND WINAPI CreateWindowExA_hook(DWORD dwExStyle, LPCSTR lpClassName, LPC
bool is_sdvx_sub_window = avs::game::is_model("KFC") && window_name.ends_with(" Sub Screen"); bool is_sdvx_sub_window = avs::game::is_model("KFC") && window_name.ends_with(" Sub Screen");
bool is_gfdm_sub_window = games::gitadora::is_arena_model() && window_name.ends_with("SMALL"); bool is_gfdm_sub_window = games::gitadora::is_arena_model() && window_name.ends_with("SMALL");
// TDJ windowed mode with subscreen: hide maximize button // hide maximize button (prevent misaligned touches)
if ((is_tdj_sub_window && GRAPHICS_IIDX_WSUB) || is_sdvx_sub_window || is_gfdm_sub_window) { if ((is_tdj_sub_window && GRAPHICS_IIDX_WSUB) || is_sdvx_sub_window || is_gfdm_sub_window) {
dwStyle &= ~(WS_MAXIMIZEBOX); dwStyle &= ~(WS_MAXIMIZEBOX);
} }
@@ -381,7 +422,7 @@ static HWND WINAPI CreateWindowExA_hook(DWORD dwExStyle, LPCSTR lpClassName, LPC
if (is_gfdm_sub_window) { if (is_gfdm_sub_window) {
dwStyle &= ~(WS_SIZEBOX); dwStyle &= ~(WS_SIZEBOX);
} }
if ((is_tdj_sub_window || is_sdvx_sub_window || is_gfdm_sub_window) && GRAPHICS_WSUB_BORDERLESS) { if ((is_tdj_sub_window || is_sdvx_sub_window) && GRAPHICS_WSUB_BORDERLESS) {
dwStyle &= ~(WS_OVERLAPPEDWINDOW); dwStyle &= ~(WS_OVERLAPPEDWINDOW);
} }
@@ -424,12 +465,19 @@ static HWND WINAPI CreateWindowExA_hook(DWORD dwExStyle, LPCSTR lpClassName, LPC
graphics_hook_subscreen_window(SDVX_SUBSCREEN_WINDOW); graphics_hook_subscreen_window(SDVX_SUBSCREEN_WINDOW);
} }
if (is_gfdm_sub_window) { // only hook touch window if multiple windows are allowed
if (is_gfdm_sub_window && !games::gitadora::ARENA_SINGLE_WINDOW) {
GFDM_SUBSCREEN_WINDOW = result; GFDM_SUBSCREEN_WINDOW = result;
graphics_hook_subscreen_window(GFDM_SUBSCREEN_WINDOW); graphics_hook_subscreen_window(GFDM_SUBSCREEN_WINDOW);
} }
disable_touch_indicators(result); disable_touch_indicators(result);
log_misc(
"graphics",
"CreateWindowExA returned {}, {}",
fmt::ptr(result),
lpWindowName ? lpWindowName : "(null)");
return result; return result;
} }
@@ -461,7 +509,8 @@ static HWND WINAPI CreateWindowExW_hook(DWORD dwExStyle, LPCWSTR lpClassName, LP
} }
// windowed mode adjustments // windowed mode adjustments
if (GRAPHICS_WINDOWED) { // check the width to filter out invisible system-created windows
if (GRAPHICS_WINDOWED && nWidth > 0) {
// change window style // change window style
dwExStyle = 0; dwExStyle = 0;
@@ -496,6 +545,12 @@ static HWND WINAPI CreateWindowExW_hook(DWORD dwExStyle, LPCWSTR lpClassName, LP
hWndParent, hMenu, hInstance, lpParam); hWndParent, hMenu, hInstance, lpParam);
GRAPHICS_WINDOWS.push_back(result); GRAPHICS_WINDOWS.push_back(result);
log_misc(
"graphics",
"CreateWindowExW returned {}, {}",
fmt::ptr(result),
lpWindowName ? ws2s(lpWindowName) : "(null)");
disable_touch_indicators(result); disable_touch_indicators(result);
return result; return result;
} }
@@ -660,10 +715,29 @@ static BOOL WINAPI SetWindowPos_hook(HWND hWnd, HWND hWndInsertAfter,
return TRUE; return TRUE;
} }
// prevent gitadora arena model from shifting windows around if the user has preferences
if (GRAPHICS_WINDOWED && games::gitadora::is_arena_model() &&
GRAPHICS_HOOKED_WINDOW.has_value() && hWnd == GRAPHICS_HOOKED_WINDOW.value() &&
cfg::SCREENRESIZE->enable_window_resize) {
return TRUE;
}
// call original // call original
return SetWindowPos_orig(hWnd, hWndInsertAfter, X, Y, cx, cy, uFlags); return SetWindowPos_orig(hWnd, hWndInsertAfter, X, Y, cx, cy, uFlags);
} }
static BOOL WINAPI ShowWindow_hook(HWND hWnd, int nCmdShow) {
if (games::gitadora::is_arena_model() &&
games::gitadora::ARENA_SINGLE_WINDOW &&
hWnd != GRAPHICS_HOOKED_WINDOW) {
log_info("graphics", "ShowWindow_hook - hiding sub window {}", fmt::ptr(hWnd));
return true;
}
// call original
return ShowWindow_orig(hWnd, nCmdShow);
}
static ATOM WINAPI RegisterClassA_hook(const WNDCLASSA *lpWndClass) { static ATOM WINAPI RegisterClassA_hook(const WNDCLASSA *lpWndClass) {
// check for null // check for null
@@ -806,6 +880,7 @@ void graphics_init() {
SetWindowLongA_orig = detour::iat_try("SetWindowLongA", SetWindowLongA_hook); SetWindowLongA_orig = detour::iat_try("SetWindowLongA", SetWindowLongA_hook);
SetWindowLongW_orig = detour::iat_try("SetWindowLongW", SetWindowLongW_hook); SetWindowLongW_orig = detour::iat_try("SetWindowLongW", SetWindowLongW_hook);
SetWindowPos_orig = detour::iat_try("SetWindowPos", SetWindowPos_hook); SetWindowPos_orig = detour::iat_try("SetWindowPos", SetWindowPos_hook);
ShowWindow_orig = detour::iat_try("ShowWindow", ShowWindow_hook);
detour::iat_try("MessageBoxA", MessageBoxA_hook); detour::iat_try("MessageBoxA", MessageBoxA_hook);
detour::iat_try("MessageBoxExA", MessageBoxExA_hook); detour::iat_try("MessageBoxExA", MessageBoxExA_hook);
+7 -1
View File
@@ -32,10 +32,12 @@ extern bool GRAPHICS_WINDOWED;
extern graphics_orientation GRAPHICS_ADJUST_ORIENTATION; extern graphics_orientation GRAPHICS_ADJUST_ORIENTATION;
extern std::vector<HWND> GRAPHICS_WINDOWS; extern std::vector<HWND> GRAPHICS_WINDOWS;
extern UINT GRAPHICS_FORCE_REFRESH; extern UINT GRAPHICS_FORCE_REFRESH;
extern std::optional<uint32_t> GRAPHICS_FORCE_REFRESH_SUB;
extern std::optional<int> GRAPHICS_FORCE_VSYNC_BUFFER; extern std::optional<int> GRAPHICS_FORCE_VSYNC_BUFFER;
extern bool GRAPHICS_FORCE_SINGLE_ADAPTER; extern bool GRAPHICS_FORCE_SINGLE_ADAPTER;
extern bool GRAPHICS_PREVENT_SECONDARY_WINDOW; extern bool GRAPHICS_PREVENT_SECONDARY_WINDOW;
extern std::optional<std::pair<uint32_t, uint32_t>> GRAPHICS_FS_CUSTOM_RESOLUTION; extern std::optional<std::pair<uint32_t, uint32_t>> GRAPHICS_FS_CUSTOM_RESOLUTION;
extern std::optional<std::pair<uint32_t, uint32_t>> GRAPHICS_FS_CUSTOM_RESOLUTION_SUB;
extern bool GRAPHICS_FS_ORIENTATION_SWAP; extern bool GRAPHICS_FS_ORIENTATION_SWAP;
extern uint32_t GRAPHICS_FS_ORIGINAL_WIDTH; extern uint32_t GRAPHICS_FS_ORIGINAL_WIDTH;
extern uint32_t GRAPHICS_FS_ORIGINAL_HEIGHT; extern uint32_t GRAPHICS_FS_ORIGINAL_HEIGHT;
@@ -48,6 +50,7 @@ extern std::optional<std::pair<uint32_t, uint32_t>> GRAPHICS_WINDOW_SIZE;
extern std::optional<std::string> GRAPHICS_WINDOW_POS; extern 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 std::optional<HWND> GRAPHICS_HOOKED_WINDOW;
extern bool GRAPHICS_IIDX_WSUB; extern bool GRAPHICS_IIDX_WSUB;
extern std::optional<std::string> GRAPHICS_WSUB_SIZE; extern std::optional<std::string> GRAPHICS_WSUB_SIZE;
@@ -99,5 +102,8 @@ void graphics_capture_initial_window(HWND hWnd);
void graphics_update_window_style(HWND hWnd); void graphics_update_window_style(HWND hWnd);
void graphics_update_z_order(HWND hWnd, bool always_on_top); void graphics_update_z_order(HWND hWnd, bool always_on_top);
void graphics_move_resize_window(HWND hWnd); void graphics_move_resize_window(HWND hWnd);
bool graphics_window_change_crashes_game(); bool graphics_window_options_breaks_game();
bool graphics_window_decoration_change_crashes_game();
bool graphics_window_resize_breaks_game();
bool graphics_window_move_and_resize_breaks_game();
void graphics_load_windowed_subscreen_parameters(); void graphics_load_windowed_subscreen_parameters();
+108 -16
View File
@@ -1,6 +1,7 @@
#include "graphics.h" #include "graphics.h"
#include "avs/game.h" #include "avs/game.h"
#include "games/gitadora/gitadora.h"
#include "cfg/screen_resize.h" #include "cfg/screen_resize.h"
#include "overlay/overlay.h" #include "overlay/overlay.h"
#include "util/logging.h" #include "util/logging.h"
@@ -23,6 +24,7 @@ std::optional<std::pair<uint32_t, uint32_t>> GRAPHICS_WINDOW_SIZE;
std::optional<std::string> GRAPHICS_WINDOW_POS; 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;
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
bool GRAPHICS_IIDX_WSUB = false; bool GRAPHICS_IIDX_WSUB = false;
@@ -55,15 +57,22 @@ void graphics_capture_initial_window(HWND hWnd) {
graphics_load_windowed_parameters(); graphics_load_windowed_parameters();
cfg::SCREENRESIZE->init_window_style = GetWindowLong(hWnd, GWL_STYLE); GRAPHICS_HOOKED_WINDOW = hWnd;
log_debug("graphics-windowed", "graphics_capture_initial_window called"); cfg::SCREENRESIZE->init_window_style = GetWindowLong(hWnd, GWL_STYLE);
cfg::SCREENRESIZE->init_window_style_ex = GetWindowLong(hWnd, GWL_EXSTYLE);
log_debug(
"graphics-windowed",
"graphics_capture_initial_window called, hWnd={}",
fmt::ptr(hWnd));
RECT rect; RECT rect;
if (!GetClientRect(hWnd, &rect)) { if (!GetClientRect(hWnd, &rect)) {
log_warning( log_warning(
"graphics", "graphics-windowed",
"graphics_capture_initial_window - GetClientRect failed, GLE: {}", "[{}] graphics_capture_initial_window - GetClientRect failed, GLE: {}",
fmt::ptr(hWnd),
GetLastError()); GetLastError());
return; return;
} }
@@ -75,12 +84,22 @@ void graphics_capture_initial_window(HWND hWnd) {
cfg::SCREENRESIZE->init_client_aspect_ratio = (float)client_w / (float)client_h; cfg::SCREENRESIZE->init_client_aspect_ratio = (float)client_w / (float)client_h;
log_debug( log_debug(
"graphics-windowed", "graphics-windowed",
"graphics_capture_initial_window initial window size {}x{}, ratio {}", "[{}] graphics_capture_initial_window initial window size {}x{}, ratio {}",
fmt::ptr(hWnd),
client_w, client_h, cfg::SCREENRESIZE->init_client_aspect_ratio); client_w, client_h, cfg::SCREENRESIZE->init_client_aspect_ratio);
// ensure frame size is captured // ensure frame size is captured
graphics_wm_style_changed(hWnd, false); graphics_wm_style_changed(hWnd, false);
// if the game is not compatible, ignore user-supplied values and keep the current size
if (graphics_window_resize_breaks_game()) {
cfg::SCREENRESIZE->client_width = client_w;
cfg::SCREENRESIZE->client_height = client_h;
}
if (graphics_window_move_and_resize_breaks_game()) {
cfg::SCREENRESIZE->enable_window_resize = false;
}
// if there was no user-supplied dimension, seed it with the current size // if there was no user-supplied dimension, seed it with the current size
// so that the next resize operation will work // so that the next resize operation will work
if (cfg::SCREENRESIZE->client_width == 0) { if (cfg::SCREENRESIZE->client_width == 0) {
@@ -94,7 +113,8 @@ void graphics_capture_initial_window(HWND hWnd) {
// resize must be done before applying the border // resize must be done before applying the border
if (cfg::SCREENRESIZE->enable_window_resize) { if (cfg::SCREENRESIZE->enable_window_resize) {
log_info( log_info(
"graphics-windowed", "change window rect - window offset: {}x{}, client size: {}x{}", "graphics-windowed", "[{}] change window rect - window offset: {}x{}, client size: {}x{}",
fmt::ptr(hWnd),
cfg::SCREENRESIZE->window_offset_x, cfg::SCREENRESIZE->window_offset_x,
cfg::SCREENRESIZE->window_offset_y, cfg::SCREENRESIZE->window_offset_y,
cfg::SCREENRESIZE->client_width, cfg::SCREENRESIZE->client_width,
@@ -104,12 +124,13 @@ void graphics_capture_initial_window(HWND hWnd) {
// ddr hangs when window frame doesn't have overlapped // ddr hangs when window frame doesn't have overlapped
if (cfg::SCREENRESIZE->window_decoration != cfg::WindowDecorationMode::Default) { if (cfg::SCREENRESIZE->window_decoration != cfg::WindowDecorationMode::Default) {
log_info( log_info(
"graphics-windowed", "change window style - decoration: {}", "graphics-windowed", "[{}] change window style - decoration: {}",
fmt::ptr(hWnd),
cfg::SCREENRESIZE->window_decoration); cfg::SCREENRESIZE->window_decoration);
graphics_update_window_style(hWnd); graphics_update_window_style(hWnd);
} }
if (cfg::SCREENRESIZE->window_always_on_top) { if (cfg::SCREENRESIZE->window_always_on_top) {
log_info("graphics-windowed", "change window z-order - always on top"); log_info("graphics-windowed", "[{}] change window z-order - always on top", fmt::ptr(hWnd));
graphics_update_z_order(hWnd, true); graphics_update_z_order(hWnd, true);
} }
@@ -152,7 +173,7 @@ void graphics_load_windowed_parameters() {
cfg::SCREENRESIZE->window_offset_x = result.first; cfg::SCREENRESIZE->window_offset_x = result.first;
cfg::SCREENRESIZE->window_offset_y = result.second; cfg::SCREENRESIZE->window_offset_y = result.second;
} else { } else {
log_warning("graphics-windowed", "failed to parse -windowpos"); log_fatal("graphics-windowed", "failed to parse -windowpos");
} }
} }
@@ -181,7 +202,7 @@ void graphics_load_windowed_subscreen_parameters() {
GRAPHICS_WSUB_WIDTH = result.first; GRAPHICS_WSUB_WIDTH = result.first;
GRAPHICS_WSUB_HEIGHT = result.second; GRAPHICS_WSUB_HEIGHT = result.second;
} else { } else {
log_warning("graphics-windowed", "failed to parse -wsubsize"); log_fatal("graphics-windowed", "failed to parse -iidxwsubsize / -sdvxwsubsize");
} }
} }
@@ -195,7 +216,7 @@ void graphics_load_windowed_subscreen_parameters() {
GRAPHICS_WSUB_X = result.first; GRAPHICS_WSUB_X = result.first;
GRAPHICS_WSUB_Y = result.second; GRAPHICS_WSUB_Y = result.second;
} else { } else {
log_warning("graphics-windowed", "failed to parse -wsubpos"); log_fatal("graphics-windowed", "failed to parse -iidxwsubpos / -sdvxwsubpos");
} }
} }
} }
@@ -368,20 +389,29 @@ void graphics_update_window_style(HWND hWnd) {
if (!GRAPHICS_WINDOWED) { if (!GRAPHICS_WINDOWED) {
return; return;
} }
if (graphics_window_change_crashes_game()) { if (graphics_window_decoration_change_crashes_game()) {
return; return;
} }
log_debug("graphics-windowed", "graphics_update_window_style called"); log_debug(
"graphics-windowed",
"graphics_update_window_style called for hWnd={}",
fmt::ptr(hWnd));
// update frame style // update frame style
auto style = cfg::SCREENRESIZE->init_window_style; auto style = cfg::SCREENRESIZE->init_window_style;
auto style_ex = cfg::SCREENRESIZE->init_window_style_ex;
switch (cfg::SCREENRESIZE->window_decoration) { switch (cfg::SCREENRESIZE->window_decoration) {
case cfg::WindowDecorationMode::Borderless: case cfg::WindowDecorationMode::Borderless:
style &= ~WS_OVERLAPPEDWINDOW; style &= ~WS_OVERLAPPEDWINDOW;
style_ex &= ~WS_EX_CLIENTEDGE;
style_ex &= ~WS_EX_WINDOWEDGE;
break; break;
case cfg::WindowDecorationMode::ResizableFrame: case cfg::WindowDecorationMode::ResizableFrame:
style |= WS_OVERLAPPEDWINDOW; style |= WS_OVERLAPPEDWINDOW;
if (graphics_window_resize_breaks_game()) {
style &= ~WS_SIZEBOX;
}
break; break;
case cfg::WindowDecorationMode::Default: case cfg::WindowDecorationMode::Default:
default: default:
@@ -393,11 +423,26 @@ void graphics_update_window_style(HWND hWnd) {
"graphics_update_window_style - calling SetWindowLong with Mode {}, style 0x{:x}", "graphics_update_window_style - calling SetWindowLong with Mode {}, style 0x{:x}",
static_cast<int>(cfg::SCREENRESIZE->window_decoration), static_cast<int>(cfg::SCREENRESIZE->window_decoration),
style); style);
SetWindowLong(hWnd, GWL_STYLE, style);
const auto ret = SetWindowLong(hWnd, GWL_STYLE, style);
(void)ret;
log_debug(
"graphics-windowed",
"graphics_update_window_style - SetWindowLong(GWL_STYLE) returned {:x}, GLE={}",
ret,
GetLastError());
const auto ret2 = SetWindowLong(hWnd, GWL_EXSTYLE, style_ex);
(void)ret2;
log_debug(
"graphics-windowed",
"graphics_update_window_style SetWindowLong(GWL_EXSTYLE) returned {:x}, GLE={}",
ret2,
GetLastError());
// SetWindowPos must be called after SetWindowLong if the frame style changed // SetWindowPos must be called after SetWindowLong if the frame style changed
// this will be done in WM_STYLECHANGED handler // this will be done in WM_STYLECHANGED handler
log_debug("graphics-windowed", "graphics_update_window_style returned");
} }
void graphics_update_z_order(HWND hWnd, bool always_on_top) { void graphics_update_z_order(HWND hWnd, bool always_on_top) {
@@ -454,12 +499,19 @@ void graphics_move_resize_window(HWND hWnd) {
log_debug("graphics-windowed", "graphics_move_resize_window returned"); log_debug("graphics-windowed", "graphics_move_resize_window returned");
} }
bool graphics_window_change_crashes_game() { bool graphics_window_decoration_change_crashes_game() {
static std::once_flag flag; static 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 crashes when frame style changes
result = avs::game::is_model("MDX"); result = avs::game::is_model("MDX");
// changing window decoration also changes dimensions and position
// so this must be prevented
if (graphics_window_move_and_resize_breaks_game()) {
result = true;
}
if (result) { if (result) {
log_warning( log_warning(
"graphics-windowed", "graphics-windowed",
@@ -468,3 +520,43 @@ bool graphics_window_change_crashes_game() {
}); });
return result; return result;
} }
bool graphics_window_move_and_resize_breaks_game() {
static std::once_flag flag;
static bool result = false;
std::call_once(flag, []() {
// games like reflec beat in use spice touch overlay for windowed mode
// and mouse input breaks if window is moved as the overlay does not
// currently support window moves/resizes
result = avs::game::is_model({"KBR", "LBR", "MBR"});
if (result) {
log_warning(
"graphics-windowed",
"ignoring changes to window properties due to incompatibility with this game");
}
});
return result;
}
bool graphics_window_resize_breaks_game() {
// for whatever reason, gitadora games on dx/sd/sd2 cabs behave poorly when window is resized
// (eihter at launch or while it's running)
// usually results in: soft lock during scene transtions or really long transitions,
// backgrounds of menus not rendering, etc.
bool is_gitadora = avs::game::is_model({ "J32", "J33", "K32", "K33", "L32", "L33", "M32" });
bool is_arena = games::gitadora::is_arena_model();
bool result = graphics_window_move_and_resize_breaks_game() || (is_gitadora && !is_arena);
static std::once_flag flag;
std::call_once(flag, [&result]() {
if (result) {
log_warning(
"graphics-windowed",
"ignoring changes to window size due to incompatibility with this game");
}
});
return result;
}
+2 -2
View File
@@ -10,6 +10,7 @@
#include "avs/game.h" #include "avs/game.h"
#include "games/iidx/iidx.h" #include "games/iidx/iidx.h"
#include "games/gitadora/gitadora.h" #include "games/gitadora/gitadora.h"
#include "games/sdvx/sdvx.h"
#include "util/detour.h" #include "util/detour.h"
#include "util/logging.h" #include "util/logging.h"
#include "util/utils.h" #include "util/utils.h"
@@ -220,8 +221,7 @@ void hooks::lang::early_init() {
detour::trampoline_try("kernel32.dll", "GetOEMCP", GetOEMCP_hook, &GetOEMCP_orig); detour::trampoline_try("kernel32.dll", "GetOEMCP", GetOEMCP_hook, &GetOEMCP_orig);
#ifdef SPICE64 // SDVX5+ specific code #ifdef SPICE64 // SDVX5+ specific code
bool isValkyrieCabinetMode = avs::game::SPEC[0] == 'G' || avs::game::SPEC[0] == 'H'; if (games::sdvx::is_valkyrie_model()) {
if (avs::game::is_model("KFC") && isValkyrieCabinetMode) {
log_info("hooks::lang", "hooking GetSystemDefaultLCID"); log_info("hooks::lang", "hooking GetSystemDefaultLCID");
detour::trampoline_try( detour::trampoline_try(
"kernel32.dll", "kernel32.dll",
+71 -6
View File
@@ -107,6 +107,7 @@
#include "util/libutils.h" #include "util/libutils.h"
#include "util/logging.h" #include "util/logging.h"
#include "util/peb.h" #include "util/peb.h"
#include "util/socd_cleaner.h"
#include "util/sysutils.h" #include "util/sysutils.h"
#include "util/tapeled.h" #include "util/tapeled.h"
#include "util/time.h" #include "util/time.h"
@@ -326,6 +327,10 @@ int main_implementation(int argc, char *argv[]) {
if (options[launcher::Options::GraphicsForceRefresh].is_active()) { if (options[launcher::Options::GraphicsForceRefresh].is_active()) {
GRAPHICS_FORCE_REFRESH = options[launcher::Options::GraphicsForceRefresh].value_uint32(); GRAPHICS_FORCE_REFRESH = options[launcher::Options::GraphicsForceRefresh].value_uint32();
} }
if (options[launcher::Options::FullscreenSubRefreshRate].is_active()) {
GRAPHICS_FORCE_REFRESH_SUB =
options[launcher::Options::FullscreenSubRefreshRate].value_uint32();
}
if (options[launcher::Options::GraphicsForceSingleAdapter].value_bool()) { if (options[launcher::Options::GraphicsForceSingleAdapter].value_bool()) {
GRAPHICS_FORCE_SINGLE_ADAPTER = true; GRAPHICS_FORCE_SINGLE_ADAPTER = true;
} }
@@ -584,6 +589,25 @@ int main_implementation(int argc, char *argv[]) {
if (options[launcher::Options::GitaDoraCabinetType].is_active()) { if (options[launcher::Options::GitaDoraCabinetType].is_active()) {
games::gitadora::CAB_TYPE = options[launcher::Options::GitaDoraCabinetType].value_uint32(); games::gitadora::CAB_TYPE = options[launcher::Options::GitaDoraCabinetType].value_uint32();
} }
if (options[launcher::Options::GitaDoraArenaSingleWindow].value_bool() && GRAPHICS_WINDOWED) {
games::gitadora::ARENA_SINGLE_WINDOW = true;
}
if (options[launcher::Options::GitaDoraWailHold].is_active()) {
socd::TILT_HOLD_MS = options[launcher::Options::GitaDoraWailHold].value_uint32();
}
if (options[launcher::Options::GitaDoraPickAlgo].is_active()) {
const auto text = options[launcher::Options::GitaDoraPickAlgo].value_text();
if (text == "legacy") {
games::gitadora::PICK_ALGO.reset();
} else if (text == "neutral") {
games::gitadora::PICK_ALGO = socd::SocdAlgorithm::Neutral;
} else if (text == "raw") {
games::gitadora::PICK_ALGO = socd::SocdAlgorithm::None;
}
}
if (options[launcher::Options::GitaDoraSubOverlaySize].is_active()) {
games::gitadora::SUBSCREEN_OVERLAY_SIZE = options[launcher::Options::GitaDoraSubOverlaySize].value_text();
}
if (options[launcher::Options::LoadNostalgiaModule].value_bool()) { if (options[launcher::Options::LoadNostalgiaModule].value_bool()) {
attach_nostalgia = true; attach_nostalgia = true;
} }
@@ -605,6 +629,17 @@ int main_implementation(int argc, char *argv[]) {
if (options[launcher::Options::GitaDoraTwoChannelAudio].value_bool()) { if (options[launcher::Options::GitaDoraTwoChannelAudio].value_bool()) {
games::gitadora::TWOCHANNEL = true; games::gitadora::TWOCHANNEL = true;
} }
if (options[launcher::Options::GitaDoraLefty].is_active()) {
const auto text = options[launcher::Options::GitaDoraLefty].value_text();
if (text == "p1") {
games::gitadora::P1_LEFTY = true;
} else if (text == "p2") {
games::gitadora::P2_LEFTY = true;
} else if (text == "both") {
games::gitadora::P1_LEFTY = true;
games::gitadora::P2_LEFTY = true;
}
}
if (options[launcher::Options::LoadDDRModule].value_bool()) { if (options[launcher::Options::LoadDDRModule].value_bool()) {
attach_ddr = true; attach_ddr = true;
} }
@@ -959,6 +994,12 @@ int main_implementation(int argc, char *argv[]) {
if (options[launcher::Options::DisableOverlay].value_bool()) { if (options[launcher::Options::DisableOverlay].value_bool()) {
overlay::ENABLED = false; overlay::ENABLED = false;
} }
if (options[launcher::Options::OverlayScaling].is_active() && !cfg::CONFIGURATOR_STANDALONE && !cfg_run) {
const auto val = options[launcher::Options::OverlayScaling].value_uint32();
if (10 <= val && val <= 400 && val != 100) {
overlay::UI_SCALE_PERCENT = options[launcher::Options::OverlayScaling].value_uint32();
}
}
if (options[launcher::Options::DisableAudioHooks].value_bool()) { if (options[launcher::Options::DisableAudioHooks].value_bool()) {
hooks::audio::ENABLED = false; hooks::audio::ENABLED = false;
deferredlogs::defer_error_messages({ deferredlogs::defer_error_messages({
@@ -1042,8 +1083,8 @@ int main_implementation(int argc, char *argv[]) {
std::pair<uint32_t, uint32_t> result; std::pair<uint32_t, uint32_t> result;
if (parse_width_height(options[launcher::Options::spice2x_WindowSize].value_text(), result)) { if (parse_width_height(options[launcher::Options::spice2x_WindowSize].value_text(), result)) {
GRAPHICS_WINDOW_SIZE = result; GRAPHICS_WINDOW_SIZE = result;
} else { } else if (!cfg_run && !cfg::CONFIGURATOR_STANDALONE) {
log_warning("launcher", "failed to parse -windowsize"); log_fatal("launcher", "failed to parse -windowsize");
} }
} }
if (options[launcher::Options::spice2x_WindowPosition].is_active()) { if (options[launcher::Options::spice2x_WindowPosition].is_active()) {
@@ -1087,6 +1128,9 @@ int main_implementation(int argc, char *argv[]) {
if (options[launcher::Options::spice2x_DRSTransposeTouch].value_bool()) { if (options[launcher::Options::spice2x_DRSTransposeTouch].value_bool()) {
games::drs::TRANSPOSE_TOUCH = true; games::drs::TRANSPOSE_TOUCH = true;
} }
if (options[launcher::Options::DRSRGBCameraHook].value_bool()) {
games::drs::RGB_CAMERA_HOOK = true;
}
if (options[launcher::Options::spice2x_AutoCard].is_active()) { if (options[launcher::Options::spice2x_AutoCard].is_active()) {
const auto text = options[launcher::Options::spice2x_AutoCard].value_text(); const auto text = options[launcher::Options::spice2x_AutoCard].value_text();
if (text == "p1") { if (text == "p1") {
@@ -1341,12 +1385,33 @@ int main_implementation(int argc, char *argv[]) {
); );
} }
if (launcher::signal::DISABLE && !cfg::CONFIGURATOR_STANDALONE) {
log_warning(
"launcher",
"WARNING - user specified -signaldisable option\n\n\n"
"!!! !!!\n"
"!!! Using -signaldisable option disables all exception handling, !!!\n"
"!!! which means when the game crashes it will likely just vanish !!!\n"
"!!! without troubleshooting information in the logs. !!!\n"
"!!! !!!\n"
);
}
if (options[launcher::Options::FullscreenResolution].is_active()) { if (options[launcher::Options::FullscreenResolution].is_active()) {
std::pair<uint32_t, uint32_t> result; std::pair<uint32_t, uint32_t> result;
if (parse_width_height(options[launcher::Options::FullscreenResolution].value_text(), result)) { if (parse_width_height(options[launcher::Options::FullscreenResolution].value_text(), result)) {
GRAPHICS_FS_CUSTOM_RESOLUTION = result; GRAPHICS_FS_CUSTOM_RESOLUTION = result;
} else { } else if (!cfg_run && !cfg::CONFIGURATOR_STANDALONE) {
log_warning("launcher", "failed to parse -forceres"); log_fatal("launcher", "failed to parse -forceres");
}
}
if (options[launcher::Options::FullscreenSubResolution].is_active()) {
std::pair<uint32_t, uint32_t> result;
if (parse_width_height(options[launcher::Options::FullscreenSubResolution].value_text(), result)) {
GRAPHICS_FS_CUSTOM_RESOLUTION_SUB = result;
} else if (!cfg_run && !cfg::CONFIGURATOR_STANDALONE) {
log_fatal("launcher", "failed to parse -forceressub");
} }
} }
@@ -1823,7 +1888,7 @@ int main_implementation(int argc, char *argv[]) {
} }
// Polaris Chord // Polaris Chord
if (check_dll("kamunity.dll") && fileutils::dir_exists("game/svm_Data")) { if (check_dll("kamunity.dll") && fileutils::file_exists("game/svm.exe")) {
avs::game::DLL_NAME = "kamunity.dll"; avs::game::DLL_NAME = "kamunity.dll";
attach_io = true; attach_io = true;
attach_pc = true; attach_pc = true;
@@ -1862,7 +1927,7 @@ int main_implementation(int argc, char *argv[]) {
"This usually means one of the following:\n\n" "This usually means one of the following:\n\n"
" 1. You put spice executables in the wrong place, or\n" " 1. You put spice executables in the wrong place, or\n"
" 2. XML files in prop directory are malformed or missing, or\n" " 2. XML files in prop directory are malformed or missing, or\n"
" 3. You passed in invalid options (usually, path overrides like -exec)\n\n" " 3. You passed in invalid options (do not specify -exec or -modules if you don't know what you're doing)\n\n"
); );
log_fatal("launcher", "module auto detection failed, see log.txt for troubleshooting"); log_fatal("launcher", "module auto detection failed, see log.txt for troubleshooting");
+12 -6
View File
@@ -12,9 +12,11 @@
#include "launcher/launcher.h" #include "launcher/launcher.h"
#include "util/utils.h" #include "util/utils.h"
#define FOREGROUND_GREY (8) #define FOREGROUND_GREY (8)
#define FOREGROUND_WHITE (FOREGROUND_RED | FOREGROUND_BLUE | FOREGROUND_GREEN) #define FOREGROUND_WHITE (FOREGROUND_RED | FOREGROUND_BLUE | FOREGROUND_GREEN)
#define FOREGROUND_YELLOW (FOREGROUND_RED | FOREGROUND_GREEN) #define FOREGROUND_YELLOW (FOREGROUND_RED | FOREGROUND_GREEN)
#define FOREGROUND_CYAN (FOREGROUND_GREEN | FOREGROUND_BLUE)
#define FOREGROUND_MAGENTA (FOREGROUND_RED | FOREGROUND_BLUE)
namespace logger { namespace logger {
@@ -103,9 +105,6 @@ namespace logger {
last_style = content.second; last_style = content.second;
switch (content.second) { switch (content.second) {
case Style::DEFAULT:
set_console_color(hTerminal, FOREGROUND_WHITE);
break;
case Style::GREY: case Style::GREY:
set_console_color(hTerminal, FOREGROUND_GREY); set_console_color(hTerminal, FOREGROUND_GREY);
break; break;
@@ -115,6 +114,13 @@ namespace logger {
case Style::RED: case Style::RED:
set_console_color(hTerminal, FOREGROUND_RED); set_console_color(hTerminal, FOREGROUND_RED);
break; break;
case Style::SPECIAL:
set_console_color(hTerminal, FOREGROUND_CYAN);
break;
case Style::DEFAULT:
default:
set_console_color(hTerminal, FOREGROUND_WHITE);
break;
} }
} }
+2 -1
View File
@@ -12,7 +12,8 @@ namespace logger {
DEFAULT = 0, DEFAULT = 0,
GREY = 1, GREY = 1,
YELLOW = 2, YELLOW = 2,
RED = 3 RED = 3,
SPECIAL = 4,
}; };
void start(); void start();
File diff suppressed because it is too large Load Diff
+12 -1
View File
@@ -31,6 +31,8 @@ namespace launcher {
GraphicsForceRefresh, GraphicsForceRefresh,
FullscreenResolution, FullscreenResolution,
FullscreenOrientationFlip, FullscreenOrientationFlip,
FullscreenSubResolution,
FullscreenSubRefreshRate,
Graphics9On12, Graphics9On12,
spice2x_Dx9On12, spice2x_Dx9On12,
NoLegacy, NoLegacy,
@@ -47,6 +49,7 @@ namespace launcher {
SOFTID, SOFTID,
VREnable, VREnable,
DisableOverlay, DisableOverlay,
OverlayScaling,
spice2x_FpsAutoShow, spice2x_FpsAutoShow,
spice2x_FpsOpposite, spice2x_FpsOpposite,
spice2x_SubScreenAutoShow, spice2x_SubScreenAutoShow,
@@ -66,6 +69,7 @@ namespace launcher {
IIDXBIO2FW, IIDXBIO2FW,
IIDXTDJMode, IIDXTDJMode,
spice2x_IIDXDigitalTTSensitivity, spice2x_IIDXDigitalTTSensitivity,
IIDXDigitalTTSocd,
spice2x_IIDXLDJForce720p, spice2x_IIDXLDJForce720p,
spice2x_IIDXTDJSubSize, spice2x_IIDXTDJSubSize,
spice2x_IIDXLEDFontSize, spice2x_IIDXLEDFontSize,
@@ -82,10 +86,11 @@ namespace launcher {
SDVXPrinterJPGQuality, SDVXPrinterJPGQuality,
SDVXDisableCameras, SDVXDisableCameras,
SDVXNativeTouch, SDVXNativeTouch,
spice2x_SDVXSubRedraw,
spice2x_SDVXDigitalKnobSensitivity, spice2x_SDVXDigitalKnobSensitivity,
SDVXDigitalKnobSocd,
spice2x_SDVXAsioDriver, spice2x_SDVXAsioDriver,
spice2x_SDVXSubPos, spice2x_SDVXSubPos,
spice2x_SDVXSubRedraw,
LoadDDRModule, LoadDDRModule,
DDR43Mode, DDR43Mode,
DDRSkipCodecRegisteration, DDRSkipCodecRegisteration,
@@ -96,6 +101,11 @@ namespace launcher {
LoadGitaDoraModule, LoadGitaDoraModule,
GitaDoraTwoChannelAudio, GitaDoraTwoChannelAudio,
GitaDoraCabinetType, GitaDoraCabinetType,
GitaDoraArenaSingleWindow,
GitaDoraLefty,
GitaDoraWailHold,
GitaDoraPickAlgo,
GitaDoraSubOverlaySize,
LoadJubeatModule, LoadJubeatModule,
LoadReflecBeatModule, LoadReflecBeatModule,
LoadShogikaiModule, LoadShogikaiModule,
@@ -228,6 +238,7 @@ namespace launcher {
spice2x_IIDXWindowedTDJ, spice2x_IIDXWindowedTDJ,
spice2x_DRSDisableTouch, spice2x_DRSDisableTouch,
spice2x_DRSTransposeTouch, spice2x_DRSTransposeTouch,
DRSRGBCameraHook,
spice2x_IIDXNativeTouch, spice2x_IIDXNativeTouch,
spice2x_IIDXNoSub, spice2x_IIDXNoSub,
spice2x_IIDXEmulateSubscreenKeypadTouch, spice2x_IIDXEmulateSubscreenKeypadTouch,
+14 -5
View File
@@ -185,21 +185,30 @@ static LONG WINAPI TopLevelExceptionFilter(struct _EXCEPTION_POINTERS *Exception
} }
static BOOL WINAPI SetConsoleCtrlHandler_hook(PHANDLER_ROUTINE pHandlerRoutine, BOOL Add) { static BOOL WINAPI SetConsoleCtrlHandler_hook(PHANDLER_ROUTINE pHandlerRoutine, BOOL Add) {
log_misc("signal", "SetConsoleCtrlHandler hook hit"); static std::once_flag printed;
std::call_once(printed, []() {
log_misc("signal", "SetConsoleCtrlHandler hook hit (printing once)");
});
return TRUE; return TRUE;
} }
static LPTOP_LEVEL_EXCEPTION_FILTER WINAPI SetUnhandledExceptionFilter_hook( static LPTOP_LEVEL_EXCEPTION_FILTER WINAPI SetUnhandledExceptionFilter_hook(
LPTOP_LEVEL_EXCEPTION_FILTER lpTopLevelExceptionFilter) LPTOP_LEVEL_EXCEPTION_FILTER lpTopLevelExceptionFilter) {
{
log_info("signal", "SetUnhandledExceptionFilter hook hit"); static std::once_flag printed;
std::call_once(printed, []() {
log_info("signal", "SetUnhandledExceptionFilter hook hit (printing once)");
});
return nullptr; return nullptr;
} }
static PVOID WINAPI AddVectoredExceptionHandler_hook(ULONG First, PVECTORED_EXCEPTION_HANDLER Handler) { static PVOID WINAPI AddVectoredExceptionHandler_hook(ULONG First, PVECTORED_EXCEPTION_HANDLER Handler) {
log_info("signal", "AddVectoredExceptionHandler hook hit"); static std::once_flag printed;
std::call_once(printed, []() {
log_info("signal", "AddVectoredExceptionHandler hook hit (printing once)");
});
return launcher::signal::USE_VEH_WORKAROUND ? INVALID_HANDLE_VALUE : nullptr; return launcher::signal::USE_VEH_WORKAROUND ? INVALID_HANDLE_VALUE : nullptr;
} }
+49 -17
View File
@@ -119,6 +119,7 @@ static std::string EAMIO_DLL_NAME = "eamio.dll";
static robin_hood::unordered_map<int, std::thread *> BT5API_THREADS; static robin_hood::unordered_map<int, std::thread *> BT5API_THREADS;
static robin_hood::unordered_map<int, int> BT5API_THREAD_RESULTS; static robin_hood::unordered_map<int, int> BT5API_THREAD_RESULTS;
static uint8_t BT5API_CARD_STATES[] = {0, 0}; static uint8_t BT5API_CARD_STATES[] = {0, 0};
static uint16_t BT5API_KEYPAD_STATES[] = {0, 0};
static void bt5api_log(const char *bt5_module, const char *fmt, ...) { static void bt5api_log(const char *bt5_module, const char *fmt, ...) {
@@ -240,46 +241,77 @@ void bt5api_hook(HINSTANCE module) {
void bt5api_poll_reader_card(uint8_t unit_no) { void bt5api_poll_reader_card(uint8_t unit_no) {
// check if initialized // check if initialized or out of bounds
if (!EAMIO_DLL) { if (!EAMIO_DLL || unit_no >= eamuse_get_game_keypads()) {
return; return;
} }
// poll // poll
if (!eam_io_poll(unit_no)) { if (!eam_io_poll(unit_no)) {
log_warning("bt5api", "polling bt5api reader {} returned failure", log_warning("bt5api", "polling bt5api reader card {} returned failure",
static_cast<int>(unit_no)); static_cast<int>(unit_no));
return;
} }
// get sensor state // get sensor state
uint8_t sensor_state = eam_io_get_sensor_state(unit_no); uint8_t sensor_state = eam_io_get_sensor_state(unit_no);
// check for card in // if we have a change in state.
if (sensor_state > BT5API_CARD_STATES[unit_no]) { if(sensor_state != BT5API_CARD_STATES[unit_no]) {
uint8_t card_id[8];
eam_io_read_card(unit_no, card_id, 8); // card inserted into the back.
eamuse_card_insert(unit_no, card_id); if(sensor_state & (1 << EAM_IO_SENSOR_BACK)) {
uint8_t card_id[8];
log_info("bt5api", "card unit {} inserted", static_cast<int>(unit_no));
// do the bt5 dance
eam_io_card_slot_cmd(unit_no, EAM_IO_CARD_SLOT_CMD_CLOSE);
eam_io_poll(unit_no);
eam_io_card_slot_cmd(unit_no, EAM_IO_CARD_SLOT_CMD_READ);
eam_io_poll(unit_no);
// ask the api for the card, then insert it into our engine.
eam_io_read_card(unit_no, card_id, 8);
eamuse_card_insert(unit_no, card_id);
}
// if card removed entirely, do another bt5 dance!
if(sensor_state == 0) {
eam_io_card_slot_cmd(unit_no, EAM_IO_CARD_SLOT_CMD_CLOSE);
eam_io_poll(unit_no);
eam_io_card_slot_cmd(unit_no, EAM_IO_CARD_SLOT_CMD_OPEN);
log_info("bt5api", "card unit {} removed", static_cast<int>(unit_no));
}
} }
// save state // save prev state
BT5API_CARD_STATES[unit_no] = sensor_state; BT5API_CARD_STATES[unit_no] = sensor_state;
} }
void bt5api_poll_reader_keypad(uint8_t unit_no) { void bt5api_poll_reader_keypad(uint8_t unit_no) {
// check if initialized // check if initialized or out of bounds
if (!EAMIO_DLL) { if (!EAMIO_DLL || unit_no >= eamuse_get_game_keypads()) {
return; return;
} }
// poll uint16_t keypad_current = eam_io_get_keypad_state(unit_no);
if (!eam_io_poll(unit_no)) { uint16_t keypad_changes = ~BT5API_KEYPAD_STATES[unit_no] & keypad_current;
log_warning("bt5api", "polling bt5api reader {} returned failure",
static_cast<int>(unit_no)); // if user has pressed decimal for the first time, eject the slotted reader.
if(keypad_changes & (1 << EAM_IO_KEYPAD_DECIMAL)) {
log_info("bt5api", "card unit {} ejecting", static_cast<int>(unit_no));
eam_io_card_slot_cmd(unit_no, EAM_IO_CARD_SLOT_CMD_EJECT);
} }
// get keypad // save the last pressed.
eamuse_set_keypad_overrides_bt5(unit_no, eam_io_get_keypad_state(unit_no)); BT5API_KEYPAD_STATES[unit_no] = keypad_current;
// send off the state to the main engine.
eamuse_set_keypad_overrides_bt5(unit_no, keypad_current);
} }
void bt5api_dispose() { void bt5api_dispose() {
+8 -1
View File
@@ -202,7 +202,14 @@ bool eamuse_card_insert_consume(int active_count, int unit_id) {
// bt5api // bt5api
if (BT5API_ENABLED) { if (BT5API_ENABLED) {
bt5api_poll_reader_card((uint8_t) index); // some bt5api want to be polled in order.
if (eamuse_get_game_keypads() > 1) {
bt5api_poll_reader_card(1);
bt5api_poll_reader_card(0);
}
else {
bt5api_poll_reader_card((uint8_t) index);
}
} }
// auto insert card // auto insert card
+120 -56
View File
@@ -7,6 +7,8 @@
#include "util/detour.h" #include "util/detour.h"
#include "util/libutils.h" #include "util/libutils.h"
#include "util/circular_buffer.h" #include "util/circular_buffer.h"
#include "util/socd_cleaner.h"
#include "util/time.h"
#include "misc/eamuse.h" #include "misc/eamuse.h"
#include "cfg/api.h" #include "cfg/api.h"
#include "acio/icca/icca.h" #include "acio/icca/icca.h"
@@ -336,6 +338,9 @@ static int __cdecl gfdm_unit2_boot_initialize() {
return 1; return 1;
} }
constexpr int LEFTY_X_CENTER = 2040;
// this seems to be only used in the I/O test menu, not in gameplay
static void *__cdecl gfdm_unit_get_button_p(void *a1, int a2, size_t player) { static void *__cdecl gfdm_unit_get_button_p(void *a1, int a2, size_t player) {
memset(a1, 0, 64); memset(a1, 0, 64);
@@ -344,31 +349,61 @@ static void *__cdecl gfdm_unit_get_button_p(void *a1, int a2, size_t player) {
return a1; return a1;
} }
// log_info("gitadora", "gfdm_unit_get_button_p: a2={}, player={}", a2, player);
// get buttons // get buttons
auto &buttons = games::gitadora::get_buttons(); auto &buttons = games::gitadora::get_buttons();
auto &analogs = games::gitadora::get_analogs(); auto &analogs = games::gitadora::get_analogs();
const auto wail_up =
Buttons::getState(RI_MGR, buttons.at(gitadora_button_mapping[12 + 11 * (size_t) player]));
const auto wail_down =
Buttons::getState(RI_MGR, buttons.at(gitadora_button_mapping[13 + 11 * (size_t) player]));
const auto wail_result =
socd::get_guitar_wail(player, wail_up, wail_down, get_performance_milliseconds());
// X // wail X
((int *) a1)[4] = a2 == 1 ? 4080 : -4080; ((int *) a1)[4] = games::gitadora::is_player_lefty(player) ? LEFTY_X_CENTER : -4080;
if (analogs.at(player * 3 + 0).isSet()) auto &analog_x = analogs.at(gitadora_analog_mapping[player * 4 + 0]);
((int *) a1)[4] = lroundf(Analogs::getState( if (analog_x.isSet())
RI_MGR, analogs.at(gitadora_analog_mapping[player * 4 + 0])) * 8160.f) - 4080; ((int *) a1)[4] = lroundf(Analogs::getState(RI_MGR, analog_x) * 8160.f) - 4080;
// Y // wail Y
((int *) a1)[5] = 0; ((int *) a1)[5] = 0;
if (analogs.at(player * 3 + 1).isSet()) auto &analog_y = analogs.at(gitadora_analog_mapping[player * 4 + 1]);
((int *) a1)[5] = lroundf(Analogs::getState( if (analog_y.isSet()) {
RI_MGR, analogs.at(gitadora_analog_mapping[player * 4 + 1])) * 8160.f) - 4080; ((int *) a1)[5] = lroundf(Analogs::getState(RI_MGR, analog_y) * 8160.f) - 4080;
if (Buttons::getState(RI_MGR, buttons.at(gitadora_button_mapping[12 + 11 * (size_t) player]))) }
((int *) a1)[5] = -4080;
if (Buttons::getState(RI_MGR, buttons.at(gitadora_button_mapping[13 + 11 * (size_t) player])))
((int *) a1)[5] = 4080;
// Z // digital wailing
if (!games::gitadora::is_player_lefty(player)) {
// righty
if (wail_result == socd::TiltUp) {
// top center
((int *) a1)[4] = 0; // X
((int *) a1)[5] = -4080; // Y
} else if (wail_result == socd::TiltDown) {
// bottom left; for lefty, bottom right
((int *) a1)[4] = -4080; // X
((int *) a1)[5] = 4080; // Y
}
} else {
// lefty
if (wail_result == socd::TiltUp) {
// left
((int *) a1)[4] = -4080; // X
((int *) a1)[5] = 0; // Y
} else if (wail_result == socd::TiltDown) {
// right
((int *) a1)[4] = 4080; // X
((int *) a1)[5] = 0; // Y
}
}
// wail Z
((int *) a1)[6] = 0; ((int *) a1)[6] = 0;
if (analogs.at(player * 3 + 2).isSet()) auto &analog_z = analogs.at(gitadora_analog_mapping[player * 4 + 2]);
((int *) a1)[6] = lroundf(Analogs::getState( if (analog_z.isSet())
RI_MGR, analogs.at(gitadora_analog_mapping[player * 4 + 2])) * 8160.f) - 4080; ((int *) a1)[6] = lroundf(Analogs::getState(RI_MGR, analog_z) * 8160.f) - 4080;
// return the same buffer // return the same buffer
return a1; return a1;
@@ -487,24 +522,42 @@ static long __cdecl gfdm_unit_get_input_p(int device, size_t player) {
if (games::gitadora::is_guitar()) { if (games::gitadora::is_guitar()) {
auto offset = player * 11; auto offset = player * 11;
// pick up const auto pick_up = Buttons::getState(RI_MGR, buttons.at(gitadora_button_mapping[3 + offset]));
if (Buttons::getState(RI_MGR, buttons.at(gitadora_button_mapping[3 + offset]))) { const auto pick_down = Buttons::getState(RI_MGR, buttons.at(gitadora_button_mapping[4 + offset]));
if (!GFDM_GF_PICK_STATE_UP[player]) { if (!games::gitadora::PICK_ALGO.has_value()) {
GFDM_GF_PICK_STATE_UP[player] = true; // legacy behavior (detect rising edges only, input for 1 frame)
ret |= 0x80 | 0x20; if (pick_up) {
if (!GFDM_GF_PICK_STATE_UP[player]) {
GFDM_GF_PICK_STATE_UP[player] = true;
ret |= 0x80 | 0x20;
}
} else {
GFDM_GF_PICK_STATE_UP[player] = false;
} }
} else {
GFDM_GF_PICK_STATE_UP[player] = false;
}
// pick down if (pick_down) {
if (Buttons::getState(RI_MGR, buttons.at(gitadora_button_mapping[4 + offset]))) { if (!GFDM_GF_PICK_STATE_DOWN[player]) {
if (!GFDM_GF_PICK_STATE_DOWN[player]) { GFDM_GF_PICK_STATE_DOWN[player] = true;
GFDM_GF_PICK_STATE_DOWN[player] = true; ret |= 0x100 | 0x20;
ret |= 0x100 | 0x20; }
} else {
GFDM_GF_PICK_STATE_DOWN[player] = false;
} }
} else { } else {
GFDM_GF_PICK_STATE_DOWN[player] = false; const auto socd = socd::socd_clean(
player,
pick_up,
pick_down,
get_performance_milliseconds(),
games::gitadora::PICK_ALGO.value()
);
if (socd == socd::SocdCCW) {
ret |= 0x80 | 0x20; // pick up
} else if (socd == socd::SocdCW) {
ret |= 0x100 | 0x20; // pick down
} else if (socd == socd::SocdBoth) {
ret |= 0x100 | 0x80 | 0x20; // up and down
}
} }
// button R // button R
@@ -585,63 +638,74 @@ static long __cdecl gfdm_unit2_get_input(int device) {
return gfdm_unit_get_input_p(device, 1); return gfdm_unit_get_input_p(device, 1);
} }
// this is used in gameplay
static long __cdecl gfdm_unit_get_sensor_gf_p(int a1, int a2, size_t player) { static long __cdecl gfdm_unit_get_sensor_gf_p(int a1, int a2, size_t player) {
// return if it's actually drum mania // return if it's actually drum mania
if (games::gitadora::is_drum()) if (games::gitadora::is_drum())
return 0; return 0;
// log_info("gitadora", "gfdm_unit_get_sensor_gf_p: a1={}, a2={}, player={}", a1, a2, player);
// get buttons and analogs // get buttons and analogs
auto &buttons = games::gitadora::get_buttons(); auto &buttons = games::gitadora::get_buttons();
auto &analogs = games::gitadora::get_analogs(); auto &analogs = games::gitadora::get_analogs();
// X // figure out digital wail Y
const size_t offset = (size_t) player * 11;
const auto wail_up = Buttons::getState(RI_MGR, buttons.at(gitadora_button_mapping[12 + offset]));
const auto wail_down = Buttons::getState(RI_MGR, buttons.at(gitadora_button_mapping[13 + offset]));
const auto wail_result = socd::get_guitar_wail(player, wail_up, wail_down, get_performance_milliseconds());
// wail X
if (a2 == 0) { if (a2 == 0) {
long ret = games::gitadora::is_player_lefty(player) ? LEFTY_X_CENTER : -4080;
// analog override // analog override
if (analogs.at(gitadora_analog_mapping[player * 3 + 0]).isSet()) { auto &analog_x = analogs.at(gitadora_analog_mapping[player * 4 + 0]);
return lroundf(Analogs::getState( if (analog_x.isSet()) {
RI_MGR, analogs.at(gitadora_analog_mapping[player * 4 + 0])) * 8160.f) - 4080; ret = lroundf(Analogs::getState(RI_MGR, analog_x) * 8160.f) - 4080;
}
// digital wail up/down
if (wail_result == socd::TiltUp) {
ret = games::gitadora::is_player_lefty(player) ? -4080 : 0;
} else if (wail_result == socd::TiltDown) {
ret = games::gitadora::is_player_lefty(player) ? +4080 : -4080;
} }
// default // default
return a1 == 1 ? 4080 : -4080; return ret;
} }
// Y // wail Y
if (a2 == 1) { if (a2 == 1) {
long ret = 0;
// analog override // analog override
if (analogs.at(player * 3 + 1).isSet()) { auto &analog_y = analogs.at(gitadora_analog_mapping[player * 4 + 1]);
return lroundf(Analogs::getState( if (analog_y.isSet()) {
RI_MGR, analogs.at(gitadora_analog_mapping[player * 4 + 1])) * 8160.f) - 4080; ret = lroundf(Analogs::getState(RI_MGR, analog_y) * 8160.f) - 4080;
} }
// variables // digital wail up/down
long ret = 0; if (wail_result == socd::TiltUp) {
size_t offset = (size_t) player * 11; ret = games::gitadora::is_player_lefty(player) ? 0 : -4080;
} else if (wail_result == socd::TiltDown) {
// wail up ret = games::gitadora::is_player_lefty(player) ? 0 : 4080;
if (Buttons::getState(RI_MGR, buttons.at(gitadora_button_mapping[12 + offset]))) {
ret -= 4080;
}
// wail down
if (Buttons::getState(RI_MGR, buttons.at(gitadora_button_mapping[13 + offset]))) {
ret += 4080;
} }
// return value // return value
return ret; return ret;
} }
// Z // wail Z
if (a2 == 2) { if (a2 == 2) {
// analog override // analog override
if (analogs.at(gitadora_analog_mapping[player * 3 + 2]).isSet()) { auto &analog_z = analogs.at(gitadora_analog_mapping[player * 4 + 2]);
return lroundf(Analogs::getState( if (analog_z.isSet()) {
RI_MGR, analogs.at(gitadora_analog_mapping[player * 4 + 2])) * 8160.f) - 4080; return lroundf(Analogs::getState(RI_MGR, analog_z) * 8160.f) - 4080;
} }
// default // default
+5 -1
View File
@@ -10,6 +10,7 @@
#include <thread> #include <thread>
#include "cfg/screen_resize.h" #include "cfg/screen_resize.h"
#include "games/gitadora/gitadora.h"
#include "games/iidx/iidx.h" #include "games/iidx/iidx.h"
#include "hooks/graphics/graphics.h" #include "hooks/graphics/graphics.h"
#include "overlay/overlay.h" #include "overlay/overlay.h"
@@ -390,7 +391,10 @@ namespace wintouchemu {
} else if (avs::game::is_model("LDJ") && !GENERIC_SUB_WINDOW_FULLSIZE) { } else if (avs::game::is_model("LDJ") && !GENERIC_SUB_WINDOW_FULLSIZE) {
// overlay subscreen in IIDX // overlay subscreen in IIDX
// use mouse position as ImGui overlay will block the touch window // use mouse position as ImGui overlay will block the touch window
log_info("wintouchemu", "use mouse cursor API for overlay subscreen"); log_info("wintouchemu", "use mouse cursor API for ldj overlay subscreen");
USE_MOUSE = true;
} else if (games::gitadora::is_arena_model() && games::gitadora::ARENA_SINGLE_WINDOW) {
log_info("wintouchemu", "use mouse cursor API for gitadora overlay subscreen");
USE_MOUSE = true; USE_MOUSE = true;
} else { } else {
// create touch window - create overlay if not yet existing at this point // create touch window - create overlay if not yet existing at this point
+30 -6
View File
@@ -236,6 +236,26 @@ namespace nvapi {
return NvAPI_DRS_SetSetting(h_session, h_profile, &drs_setting); return NvAPI_DRS_SetSetting(h_session, h_profile, &drs_setting);
} }
NvAPI_Status set_refresh_rate(NvDRSSessionHandle h_session, NvDRSProfileHandle h_profile) {
auto drs_setting = NVDRS_SETTING {};
drs_setting.version = NVDRS_SETTING_VER;
drs_setting.settingId = REFRESH_RATE_OVERRIDE_ID;
drs_setting.settingType = NVDRS_DWORD_TYPE;
drs_setting.u32PredefinedValue = REFRESH_RATE_OVERRIDE_APPLICATION_CONTROLLED;
drs_setting.u32CurrentValue = REFRESH_RATE_OVERRIDE_APPLICATION_CONTROLLED;
return NvAPI_DRS_SetSetting(h_session, h_profile, &drs_setting);
}
NvAPI_Status set_max_fps(NvDRSSessionHandle h_session, NvDRSProfileHandle h_profile) {
auto drs_setting = NVDRS_SETTING {};
drs_setting.version = NVDRS_SETTING_VER;
drs_setting.settingId = FRL_FPS_ID;
drs_setting.settingType = NVDRS_DWORD_TYPE;
drs_setting.u32PredefinedValue = FRL_FPS_DISABLED;
drs_setting.u32CurrentValue = FRL_FPS_DISABLED;
return NvAPI_DRS_SetSetting(h_session, h_profile, &drs_setting);
}
void set_profile_settings() { void set_profile_settings() {
if (!NVAPI_MODULE_LOAD_SUCCEEDED) { if (!NVAPI_MODULE_LOAD_SUCCEEDED) {
return; return;
@@ -266,22 +286,26 @@ namespace nvapi {
log_info("nvapi", "setting VSync to Application Controlled..."); log_info("nvapi", "setting VSync to Application Controlled...");
if ((status = set_vsync_mode(h_session, h_profile)) != NVAPI_OK) { if ((status = set_vsync_mode(h_session, h_profile)) != NVAPI_OK) {
log_warning("nvapi", "could not set VSync mode: {}", get_error_message(status)); log_warning("nvapi", "could not set VSync mode: {}", get_error_message(status));
NvAPI_DRS_DestroySession(h_session);
return;
} }
log_info("nvapi", "applying preferred PState to Maximum Performance..."); log_info("nvapi", "applying preferred PState to Maximum Performance...");
if ((status = set_gpu_power_state(h_session, h_profile)) != NVAPI_OK) { if ((status = set_gpu_power_state(h_session, h_profile)) != NVAPI_OK) {
log_warning("nvapi", "could not set preferred PState: {}", get_error_message(status)); log_warning("nvapi", "could not set preferred PState: {}", get_error_message(status));
NvAPI_DRS_DestroySession(h_session);
return;
} }
log_info("nvapi", "disabling G-SYNC..."); log_info("nvapi", "disabling G-SYNC...");
if ((status = set_gsync_mode(h_session, h_profile)) != NVAPI_OK) { if ((status = set_gsync_mode(h_session, h_profile)) != NVAPI_OK) {
log_warning("nvapi", "could not set G-Sync mode: {}", get_error_message(status)); log_warning("nvapi", "could not set G-Sync mode: {}", get_error_message(status));
NvAPI_DRS_DestroySession(h_session); }
return;
log_info("nvapi", "setting preferred refresh rate to Application Controlled...");
if ((status = set_refresh_rate(h_session, h_profile)) != NVAPI_OK) {
log_warning("nvapi", "could not set preferred refresh rate: {}", get_error_message(status));
}
log_info("nvapi", "disabling max framerate...");
if ((status = set_max_fps(h_session, h_profile)) != NVAPI_OK) {
log_warning("nvapi", "could not disable max framerate: {}", get_error_message(status));
} }
log_info("nvapi", "saving settings for DRS session..."); log_info("nvapi", "saving settings for DRS session...");
+75 -2
View File
@@ -26,7 +26,7 @@ namespace ImGui {
void HelpMarker(const char* desc) { void HelpMarker(const char* desc) {
ImGui::TextDisabled("(?)"); ImGui::TextDisabled("(?)");
if (ImGui::IsItemHovered()) { if (ImGui::IsItemHovered(TOOLTIP_FLAGS)) {
HelpTooltip(desc); HelpTooltip(desc);
} }
} }
@@ -59,7 +59,7 @@ namespace ImGui {
ImGui::PushStyleColor(ImGuiCol_TextDisabled, ImVec4(1.f, 1.f, 0.f, 1.f)); ImGui::PushStyleColor(ImGuiCol_TextDisabled, ImVec4(1.f, 1.f, 0.f, 1.f));
ImGui::TextDisabled("(!)"); ImGui::TextDisabled("(!)");
ImGui::PopStyleColor(); ImGui::PopStyleColor();
if (ImGui::IsItemHovered()) { if (ImGui::IsItemHovered(TOOLTIP_FLAGS)) {
WarnTooltip(desc, warn); WarnTooltip(desc, warn);
} }
} }
@@ -92,4 +92,77 @@ namespace ImGui {
ImGui::GetColorU32(ImGuiCol_PlotHistogram), ImGui::GetColorU32(ImGuiCol_PlotHistogram),
thickness); thickness);
} }
std::string TruncateText(const std::string& p_text, float p_truncated_width, bool &truncated) {
std::string truncated_text = p_text;
const float text_width =
ImGui::CalcTextSize(p_text.c_str(), nullptr, true).x;
if (text_width > p_truncated_width) {
truncated = true;
constexpr const char* ELLIPSIS = " ...";
const float ellipsis_size = ImGui::CalcTextSize(ELLIPSIS).x;
int visible_chars = 0;
for (size_t i = 0; i < p_text.size(); i++) {
const float current_width = ImGui::CalcTextSize(
p_text.substr(0, i).c_str(), nullptr, true)
.x;
if (current_width + ellipsis_size > p_truncated_width) {
break;
}
visible_chars = i;
}
truncated_text = (p_text.substr(0, visible_chars) + ELLIPSIS).c_str();
}
return truncated_text;
}
void TextTruncated(const std::string& p_text, float p_truncated_width) {
if (p_text.empty()) {
ImGui::TextDisabled("-");
return;
}
bool truncated = false;
ImGui::TextUnformatted(TruncateText(p_text, p_truncated_width, truncated).c_str());
if (truncated && ImGui::IsItemHovered(TOOLTIP_FLAGS)) {
ImGui::HelpTooltip(p_text.c_str());
}
}
bool DeleteButton(const std::string& tooltip) {
ImGui::PushID(tooltip.c_str());
ImGui::PushStyleColor(ImGuiCol_Button, ImGui::GetColorU32(ImGuiCol_Button, 0.7f));
ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(3.f, 2.f));
bool clicked = ImGui::SmallButton("\u00D7"); // multiplication sign (×)
ImGui::PopStyleVar();
ImGui::PopStyleColor();
if (!tooltip.empty() && ImGui::IsItemHovered(TOOLTIP_FLAGS)) {
ImGui::SameLine();
ImGui::HelpTooltip(tooltip.c_str());
}
ImGui::PopID();
return clicked;
}
bool ClearButton(const std::string& tooltip) {
ImGui::PushID(tooltip.c_str());
// same colors as a checkbox
ImGui::PushStyleColor(ImGuiCol_Button, ImGui::GetColorU32(ImGuiCol_FrameBg));
ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImGui::GetColorU32(ImGuiCol_FrameBgHovered));
ImGui::PushStyleColor(ImGuiCol_ButtonActive, ImGui::GetColorU32(ImGuiCol_FrameBgActive));
ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(0.97f, 0.97f, 0.97f, 1.00f));
bool clicked = ImGui::Button("\u00D7"); // multiplication sign (×)
ImGui::PopStyleColor(4);
if (!tooltip.empty() && ImGui::IsItemHovered(TOOLTIP_FLAGS)) {
ImGui::SameLine();
ImGui::HelpTooltip(tooltip.c_str());
}
ImGui::PopID();
return clicked;
}
} }
+11
View File
@@ -1,12 +1,23 @@
#pragma once #pragma once
#include <string>
#include "external/imgui/imgui.h"
namespace ImGui { namespace ImGui {
constexpr ImGuiHoveredFlags TOOLTIP_FLAGS =
(ImGuiHoveredFlags_ForTooltip | ImGuiHoveredFlags_AllowWhenDisabled);
void HelpTooltip(const char* desc); void HelpTooltip(const char* desc);
void HelpMarker(const char* desc); void HelpMarker(const char* desc);
void WarnTooltip(const char* desc, const char* warn); void WarnTooltip(const char* desc, const char* warn);
void WarnMarker(const char* desc, const char* warn); void WarnMarker(const char* desc, const char* warn);
void DummyMarker(); void DummyMarker();
void Knob(float fraction, float size, float thickness = 2.f, void Knob(float fraction, float size, float thickness = 2.f,
float pos_x = -1.f, float pos_y = -1.f); float pos_x = -1.f, float pos_y = -1.f);
void TextTruncated(const std::string& p_text, float p_truncated_width);
bool DeleteButton(const std::string& tooltip);
bool ClearButton(const std::string& tooltip);
} }
+25 -4
View File
@@ -3,6 +3,7 @@
#include "avs/game.h" #include "avs/game.h"
#include "cfg/configurator.h" #include "cfg/configurator.h"
#include "games/io.h" #include "games/io.h"
#include "games/gitadora/gitadora.h"
#include "games/iidx/iidx.h" #include "games/iidx/iidx.h"
#include "hooks/graphics/graphics.h" #include "hooks/graphics/graphics.h"
#include "misc/eamuse.h" #include "misc/eamuse.h"
@@ -28,6 +29,7 @@
#include "windows/generic_sub.h" #include "windows/generic_sub.h"
#include "windows/iidx_seg.h" #include "windows/iidx_seg.h"
#include "windows/iidx_sub.h" #include "windows/iidx_sub.h"
#include "windows/gfdm_sub.h"
#include "windows/drs_dancefloor.h" #include "windows/drs_dancefloor.h"
#include "windows/iopanel.h" #include "windows/iopanel.h"
#include "windows/iopanel_ddr.h" #include "windows/iopanel_ddr.h"
@@ -51,16 +53,26 @@ namespace overlay {
bool AUTO_SHOW_IOPANEL = false; bool AUTO_SHOW_IOPANEL = false;
bool AUTO_SHOW_KEYPAD_P1 = false; bool AUTO_SHOW_KEYPAD_P1 = false;
bool AUTO_SHOW_KEYPAD_P2 = false; bool AUTO_SHOW_KEYPAD_P2 = false;
bool USE_WM_CHAR_FOR_IMGUI_CHAR_INPUT = false; bool USE_WM_CHAR_FOR_IMGUI_CHAR_INPUT = false;
bool FPS_SHOULD_FLIP = false; bool FPS_SHOULD_FLIP = false;
std::optional<uint32_t> UI_SCALE_PERCENT;
// global // global
std::mutex OVERLAY_MUTEX; std::mutex OVERLAY_MUTEX;
std::unique_ptr<overlay::SpiceOverlay> OVERLAY = nullptr; std::unique_ptr<overlay::SpiceOverlay> OVERLAY = nullptr;
ImFont* DSEG_FONT = nullptr; ImFont* DSEG_FONT = nullptr;
bool SHOW_DEBUG_LOG_WINDOW = false; bool SHOW_DEBUG_LOG_WINDOW = false;
ImVec2 apply_scaling_to_vector(float x, float y) {
if (!UI_SCALE_PERCENT.has_value()) {
return ImVec2(x, y);
}
return ImVec2(apply_scaling(x), apply_scaling(y));
}
ImVec2 apply_scaling_to_vector(const ImVec2& input) {
return apply_scaling_to_vector(input.x, input.y);
}
} }
static void *ImGui_Alloc(size_t sz, void *user_data) { static void *ImGui_Alloc(size_t sz, void *user_data) {
@@ -170,6 +182,12 @@ void overlay::SpiceOverlay::init() {
style.WindowRounding = 0; style.WindowRounding = 0;
} }
if (UI_SCALE_PERCENT.has_value()) {
const auto scale = static_cast<float>(UI_SCALE_PERCENT.value()) / 100.f;
ImGui::GetStyle().ScaleAllSizes(scale);
ImGui::GetIO().FontGlobalScale = scale;
}
// red theme based on: // red theme based on:
// https://github.com/ocornut/imgui/issues/707#issuecomment-760220280 // https://github.com/ocornut/imgui/issues/707#issuecomment-760220280
// r, g, b, a // r, g, b, a
@@ -245,7 +263,6 @@ void overlay::SpiceOverlay::init() {
if (!cfg::CONFIGURATOR_STANDALONE) { if (!cfg::CONFIGURATOR_STANDALONE) {
io.ConfigFlags |= ImGuiConfigFlags_DockingEnable; io.ConfigFlags |= ImGuiConfigFlags_DockingEnable;
io.ConfigFlags |= ImGuiConfigFlags_ViewportsEnable;
} }
if (is_touch_available("SpiceOverlay::init")) { if (is_touch_available("SpiceOverlay::init")) {
io.ConfigFlags |= ImGuiConfigFlags_IsTouchScreen; io.ConfigFlags |= ImGuiConfigFlags_IsTouchScreen;
@@ -366,7 +383,8 @@ void overlay::SpiceOverlay::init() {
window_iopanel = new overlay::windows::IIDXIOPanel(this); window_iopanel = new overlay::windows::IIDXIOPanel(this);
} else if (avs::game::is_model("MDX")) { } else if (avs::game::is_model("MDX")) {
window_iopanel = new overlay::windows::DDRIOPanel(this); window_iopanel = new overlay::windows::DDRIOPanel(this);
} else if (avs::game::is_model({"J32", "J33", "K32", "K33", "L32", "L33", "M32"})) { } else if (avs::game::is_model({"J32", "J33", "K32", "K33", "L32", "L33", "M32"}) &&
!games::gitadora::is_arena_model()) {
window_iopanel = new overlay::windows::GitadoraIOPanel(this); window_iopanel = new overlay::windows::GitadoraIOPanel(this);
} else { } else {
window_iopanel = new overlay::windows::IOPanel(this); window_iopanel = new overlay::windows::IOPanel(this);
@@ -393,7 +411,10 @@ void overlay::SpiceOverlay::init() {
window_sub = new overlay::windows::DRSDanceFloorDisplay(this); window_sub = new overlay::windows::DRSDanceFloorDisplay(this);
} else if (avs::game::is_model("KFC")) { } else if (avs::game::is_model("KFC")) {
window_sub = new overlay::windows::SDVXSubScreen(this); window_sub = new overlay::windows::SDVXSubScreen(this);
} else if (games::gitadora::is_arena_model() && games::gitadora::ARENA_SINGLE_WINDOW) {
window_sub = new overlay::windows::GitaDoraSubScreen(this);
} }
if (window_sub) { if (window_sub) {
this->window_add(window_sub); this->window_add(window_sub);
if (AUTO_SHOW_SUBSCREEN) { if (AUTO_SHOW_SUBSCREEN) {
+13
View File
@@ -3,6 +3,7 @@
#include <memory> #include <memory>
#include <mutex> #include <mutex>
#include <functional> #include <functional>
#include <optional>
#include <vector> #include <vector>
#include <windows.h> #include <windows.h>
#include <d3d9.h> #include <d3d9.h>
@@ -27,6 +28,18 @@ namespace overlay {
extern bool USE_WM_CHAR_FOR_IMGUI_CHAR_INPUT; extern bool USE_WM_CHAR_FOR_IMGUI_CHAR_INPUT;
extern bool FPS_SHOULD_FLIP; extern bool FPS_SHOULD_FLIP;
extern bool SHOW_DEBUG_LOG_WINDOW; extern bool SHOW_DEBUG_LOG_WINDOW;
extern std::optional<uint32_t> UI_SCALE_PERCENT;
template <typename T>
float apply_scaling(T input) {
if (!UI_SCALE_PERCENT.has_value()) {
return input;
}
return static_cast<float>(input) * UI_SCALE_PERCENT.value() / 100.f;
}
ImVec2 apply_scaling_to_vector(const ImVec2& input);
ImVec2 apply_scaling_to_vector(float x, float y);
class SpiceOverlay { class SpiceOverlay {
public: public:
@@ -21,7 +21,7 @@ namespace overlay::windows {
CameraControl::CameraControl(SpiceOverlay *overlay) : Window(overlay) { CameraControl::CameraControl(SpiceOverlay *overlay) : Window(overlay) {
this->title = "IIDX Camera Control"; this->title = "IIDX Camera Control";
this->flags |= ImGuiWindowFlags_AlwaysAutoResize; this->flags |= ImGuiWindowFlags_AlwaysAutoResize;
this->init_pos = ImVec2(40, 40); this->init_pos = overlay::apply_scaling_to_vector(40, 40);
this->toggle_button = games::OverlayButtons::ToggleCameraControl; this->toggle_button = games::OverlayButtons::ToggleCameraControl;
} }
+2 -2
View File
@@ -17,7 +17,7 @@ namespace overlay::windows {
CardManager::CardManager(SpiceOverlay *overlay) : Window(overlay) { CardManager::CardManager(SpiceOverlay *overlay) : Window(overlay) {
this->title = "Card Manager"; this->title = "Card Manager";
this->init_size = ImVec2(420, 420); this->init_size = overlay::apply_scaling_to_vector(ImVec2(420, 420));
if (cfg::CONFIGURATOR_STANDALONE) { if (cfg::CONFIGURATOR_STANDALONE) {
this->init_pos = ImVec2(40, 40); this->init_pos = ImVec2(40, 40);
@@ -303,7 +303,7 @@ namespace overlay::windows {
// //
// setting ImGuiInputTextFlags_CallbackCharFilter and pressing escape doesn't cause below // setting ImGuiInputTextFlags_CallbackCharFilter and pressing escape doesn't cause below
// to return true, making it necessary to provide a callback... // to return true, making it necessary to provide a callback...
ImGui::SetNextItemWidth(240); ImGui::SetNextItemWidth(overlay::apply_scaling(240));
if (ImGui::InputTextWithHint("", "Type here to search..", &this->search_filter)) { if (ImGui::InputTextWithHint("", "Type here to search..", &this->search_filter)) {
this->current_card = nullptr; this->current_card = nullptr;
this->search_filter_in_lower_case = strtolower(this->search_filter); this->search_filter_in_lower_case = strtolower(this->search_filter);
File diff suppressed because it is too large Load Diff
+35 -11
View File
@@ -38,7 +38,6 @@ namespace overlay::windows {
ConfigTab tab_selected = ConfigTab::CONFIG_TAB_INVALID; ConfigTab tab_selected = ConfigTab::CONFIG_TAB_INVALID;
// buttons tab // buttons tab
int buttons_page = 0;
bool buttons_keyboard_state[0xFF]; bool buttons_keyboard_state[0xFF];
bool buttons_bind_active = false; bool buttons_bind_active = false;
bool buttons_many_active = false; bool buttons_many_active = false;
@@ -55,7 +54,6 @@ namespace overlay::windows {
int analogs_devices_control_selected = -1; int analogs_devices_control_selected = -1;
// lights tab // lights tab
int lights_page = 0;
std::vector<rawinput::Device *> lights_devices; std::vector<rawinput::Device *> lights_devices;
int lights_devices_selected = -1; int lights_devices_selected = -1;
int lights_devices_control_selected = -1; int lights_devices_control_selected = -1;
@@ -78,17 +76,33 @@ namespace overlay::windows {
std::string search_filter = ""; std::string search_filter = "";
std::string search_filter_in_lower_case = ""; std::string search_filter_in_lower_case = "";
public: void build_buttons(const std::string &name, std::vector<Button> *buttons, int min = 0, int max = -1);
Config(SpiceOverlay *overlay); void build_button(
~Config() override; const std::string &name,
Button &primary_button,
Button *button,
const int button_it,
const int button_it_max,
const int alt_index);
void bind_button_popup(const std::string &bind_name, Button *button, const int button_it_max, const int alt_index);
void naive_button_popup(const std::string &naive_string, Button *button, const int button_it_max, const int alt_index);
void edit_button_popup(
const std::string &edit_name,
const std::string &button_display,
Button *button,
const float button_velocity,
const int alt_index);
void clear_button(Button *button, const int alt_index);
void read_card(int player = -1);
void write_card(int player);
void build_content() override;
void build_buttons(const std::string &name, std::vector<Button> *buttons,
int min = 0, int max = -1);
void build_analogs(const std::string &name, std::vector<Analog> *analogs); void build_analogs(const std::string &name, std::vector<Analog> *analogs);
void edit_analog_popup(Analog &analog);
void build_lights(const std::string &name, std::vector<Light> *lights); void build_lights(const std::string &name, std::vector<Light> *lights);
void build_light(Light &primary_light, Light *light, const int light_index, const int alt_index);
void clear_light(Light *light, const int alt_index);
void edit_light_popup(Light &primary_light, Light *light, const int alt_index);
void build_cards(); void build_cards();
void build_options( void build_options(
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);
@@ -96,8 +110,18 @@ namespace overlay::windows {
void build_launcher(); void build_launcher();
void launch_shell(LPCSTR app, LPCSTR file=nullptr); void launch_shell(LPCSTR app, LPCSTR file=nullptr);
static void build_page_selector(int *page);
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);
public:
Config(SpiceOverlay *overlay);
~Config() override;
void read_card(int player = -1);
void write_card(int player);
void build_content() override;
}; };
} }
+5 -2
View File
@@ -3,6 +3,7 @@
#include "misc/eamuse.h" #include "misc/eamuse.h"
#include "util/logging.h" #include "util/logging.h"
#include "games/iidx/iidx.h" #include "games/iidx/iidx.h"
#include "games/gitadora/gitadora.h"
namespace overlay::windows { namespace overlay::windows {
@@ -10,10 +11,10 @@ namespace overlay::windows {
this->title = "spice2x"; this->title = "spice2x";
this->init_size = ImVec2( this->init_size = ImVec2(
(ImGui::GetFontSize() * 14) + (ImGui::GetStyle().ItemSpacing.x * 2), (ImGui::GetFontSize() * 14) + (ImGui::GetStyle().ItemSpacing.x * 2),
120); overlay::apply_scaling(120));
this->init_pos = ImVec2( this->init_pos = ImVec2(
ImGui::GetIO().DisplaySize.x / 2 - this->init_size.x / 2, ImGui::GetIO().DisplaySize.x / 2 - this->init_size.x / 2,
10); overlay::apply_scaling(10));
this->flags = ImGuiWindowFlags_NoResize this->flags = ImGuiWindowFlags_NoResize
| ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoCollapse
@@ -69,6 +70,8 @@ namespace overlay::windows {
sub = "Show Valkyrie Subscreen"; sub = "Show Valkyrie Subscreen";
} else if (avs::game::is_model("REC")) { } else if (avs::game::is_model("REC")) {
sub = "Show DRS Dance Floor"; sub = "Show DRS Dance Floor";
} else if (games::gitadora::is_arena_model() && games::gitadora::ARENA_SINGLE_WINDOW) {
sub = "Show GITADORA Subscreen";
} }
build_button(this->overlay->window_sub, sub, size, NextItem::NEW_LINE, false); build_button(this->overlay->window_sub, sub, size, NextItem::NEW_LINE, false);
+18 -13
View File
@@ -1,5 +1,6 @@
#include <iomanip> #include <iomanip>
#include <sstream> #include <sstream>
#include "external/fmt/include/fmt/chrono.h"
#include "fps.h" #include "fps.h"
namespace overlay::windows { namespace overlay::windows {
@@ -15,13 +16,17 @@ namespace overlay::windows {
| ImGuiWindowFlags_NoNavInputs | ImGuiWindowFlags_NoNavInputs
| ImGuiWindowFlags_NoDocking; | ImGuiWindowFlags_NoDocking;
this->bg_alpha = 0.5f; this->bg_alpha = 0.5f;
this->start_time = std::chrono::system_clock::now(); this->start_time =
std::chrono::floor<std::chrono::seconds>(std::chrono::system_clock::now());
} }
void FPS::calculate_initial_window() { void FPS::calculate_initial_window() {
// width is 114x82 px with window decoration, 98x47 for the content // width is 114x82 px with window decoration, 98x47 for the content
int pos_x = overlay::FPS_SHOULD_FLIP ? 8 : ImGui::GetIO().DisplaySize.x - 122; int pos_x =
this->init_pos = ImVec2(pos_x, 8); overlay::FPS_SHOULD_FLIP ?
overlay::apply_scaling(8) :
ImGui::GetIO().DisplaySize.x - overlay::apply_scaling(122);
this->init_pos = ImVec2(pos_x, overlay::apply_scaling(8));
} }
void FPS::build_content() { void FPS::build_content() {
@@ -31,23 +36,23 @@ namespace overlay::windows {
ImGui::Text("FPS: %.1f", io.Framerate); ImGui::Text("FPS: %.1f", io.Framerate);
// ImGui::Text("FT: %.2fms", 1000 / io.Framerate); // ImGui::Text("FT: %.2fms", 1000 / io.Framerate);
auto now = std::chrono::system_clock::now(); const auto now = std::chrono::system_clock::now();
const auto now_s = std::chrono::floor<std::chrono::seconds>(now);
// current time // current time
{ {
auto now_t = std::chrono::system_clock::to_time_t(now); const std::time_t tt = std::chrono::system_clock::to_time_t(now_s);
static CHAR buf[48]; std::tm local_tm{};
std::strftime(buf, sizeof(buf), "Time: %H:%M:%S", std::localtime(&now_t)); localtime_s(&local_tm, &tt);
ImGui::Text(buf); ImGui::TextUnformatted(fmt::format("Time: {:%H:%M:%S}", local_tm).c_str());
} }
// elapsed time // elapsed time
{ {
auto d = now - this->start_time; const auto uptime = now_s - this->start_time;
const auto h = std::chrono::duration_cast<std::chrono::hours>(d); ImGui::TextUnformatted(
const auto m = std::chrono::duration_cast<std::chrono::minutes>(d - h); fmt::format("Up: {:%H:%M:%S}",
const auto s = std::chrono::duration_cast<std::chrono::seconds>(d - h - m); std::chrono::floor<std::chrono::seconds>(uptime)).c_str());
ImGui::Text("Up: %02d:%02d:%02d", (int)h.count(), (int)m.count(), (int)s.count());
} }
} }
} }
@@ -85,10 +85,12 @@ namespace overlay::windows {
} }
void GenericSubScreen::touch_transform(const ImVec2 xy_in, LONG *x_out, LONG *y_out) {} void GenericSubScreen::touch_transform(const ImVec2 xy_in, LONG *x_out, LONG *y_out) {}
void GenericSubScreen::check_for_errors() {}
void GenericSubScreen::build_content() { void GenericSubScreen::build_content() {
this->flags |= ImGuiWindowFlags_NoBackground; this->flags |= ImGuiWindowFlags_NoBackground;
check_for_errors();
if (this->disabled_message.has_value()) { if (this->disabled_message.has_value()) {
this->draws_window = true; this->draws_window = true;
this->flags &= ~ImGuiWindowFlags_NoBackground; this->flags &= ~ImGuiWindowFlags_NoBackground;
@@ -31,6 +31,7 @@ namespace overlay::windows {
protected: protected:
virtual void touch_transform(const ImVec2 xy_in, LONG *x_out, LONG *y_out); virtual void touch_transform(const ImVec2 xy_in, LONG *x_out, LONG *y_out);
virtual void check_for_errors();
ImVec2 overlay_content_top_left; ImVec2 overlay_content_top_left;
ImVec2 overlay_content_size; ImVec2 overlay_content_size;
std::optional<std::string> disabled_message = std::nullopt; std::optional<std::string> disabled_message = std::nullopt;
+54
View File
@@ -0,0 +1,54 @@
#include "avs/game.h"
#include "gfdm_sub.h"
#include "games/gitadora/gitadora.h"
#include "hooks/graphics/graphics.h"
#include "touch/touch.h"
namespace overlay::windows {
GitaDoraSubScreen::GitaDoraSubScreen(SpiceOverlay *overlay) : GenericSubScreen(overlay) {
this->title = "GITADORA Subscreen";
if (!games::gitadora::is_arena_model()) {
this->disabled_message = "Requires Arena Model!";
}
this->resize_callback = keep_10_by_16;
// medium size - perfect for "layout B" in GW Delta, though in drums it covers the pacemaker
float size = 0.64f;
if (games::gitadora::SUBSCREEN_OVERLAY_SIZE.has_value()) {
if (games::gitadora::SUBSCREEN_OVERLAY_SIZE.value() == "large") {
// enough to not cover the FPS overlay
size = 0.88f;
} else if (games::gitadora::SUBSCREEN_OVERLAY_SIZE.value() == "small") {
size = 0.48f;
}
}
const float height = ImGui::GetIO().DisplaySize.y * size;
const float width = height * 10 / 16;
this->init_size = ImVec2(width, height + ImGui::GetFrameHeight());
// bottom right
this->init_pos = ImVec2(
ImGui::GetIO().DisplaySize.x - ImGui::GetFrameHeight() / 4 - this->init_size.x,
ImGui::GetIO().DisplaySize.y - this->init_size.y - (ImGui::GetFrameHeight() / 4));
}
void GitaDoraSubScreen::touch_transform(const ImVec2 xy_in, LONG *x_out, LONG *y_out) {
if (!this->get_active()) {
return;
}
if (GRAPHICS_WINDOWED) {
// Touch needs to be registered on global coords
*x_out = SPICETOUCH_TOUCH_X + xy_in.x * SPICETOUCH_TOUCH_WIDTH;
*y_out = SPICETOUCH_TOUCH_Y + xy_in.y * SPICETOUCH_TOUCH_HEIGHT;
} else {
// Fullscreen mode, scale to game coords
*x_out = xy_in.x * ImGui::GetIO().DisplaySize.x;
*y_out = xy_in.y * ImGui::GetIO().DisplaySize.y;
}
}
}
+20
View File
@@ -0,0 +1,20 @@
#pragma once
#include "overlay/window.h"
#include "overlay/windows/generic_sub.h"
namespace overlay::windows {
class GitaDoraSubScreen : public GenericSubScreen {
public:
GitaDoraSubScreen(SpiceOverlay *overlay);
protected:
void touch_transform(const ImVec2 xy_in, LONG *x_out, LONG *y_out) override;
private:
static void keep_10_by_16(ImGuiSizeCallbackData* data) {
data->DesiredSize.y = (data->DesiredSize.x * 16.f / 10.f) + ImGui::GetFrameHeight();
}
};
}
+18
View File
@@ -14,6 +14,7 @@ namespace overlay::windows {
static const size_t TICKER_SIZE = 9; static const size_t TICKER_SIZE = 9;
static const ImVec4 DARK_GRAY(0.1f, 0.1f, 0.1f, 1.f); static const ImVec4 DARK_GRAY(0.1f, 0.1f, 0.1f, 1.f);
static const ImVec4 YELLOW(1.f, 1.f, 0.f, 1.f);
static const ImVec4 RED(1.f, 0.f, 0.f, 1.f); static const ImVec4 RED(1.f, 0.f, 0.f, 1.f);
static const float PADDING_Y = 8.f; static const float PADDING_Y = 8.f;
static const float PADDING_X = 4.f; static const float PADDING_X = 4.f;
@@ -91,6 +92,23 @@ namespace overlay::windows {
} }
void IIDXSegmentDisplay::build_content() { void IIDXSegmentDisplay::build_content() {
// explicitly check if TDJ I/O is enabled to account for the fact that I/O emulation may be
// disabled
if (games::iidx::CURRENT_IO_EMULATION_STATE == games::iidx::iidx_aio_emulation_state::bi2x_hook) {
ImGui::TextUnformatted("");
ImGui::TextColored(
YELLOW,
"%s",
"Invalid I/O mode; DLL is configured for TDJ I/O!\n"
"Enable -iidxtdj if you want TDJ subscreen overlay.");
ImGui::TextUnformatted("");
if (ImGui::Button("Close")) {
this->set_active(false);
}
return;
}
char input_ticker[TICKER_SIZE]; char input_ticker[TICKER_SIZE];
// get ticker content from game // get ticker content from game
+14 -4
View File
@@ -12,11 +12,9 @@ namespace overlay::windows {
this->title = "IIDX TDJ Subscreen"; this->title = "IIDX TDJ Subscreen";
if (GRAPHICS_IIDX_WSUB) { if (GRAPHICS_IIDX_WSUB) {
this->disabled_message = this->disabled_message = "Close this overlay and use the second window. (try ALT+TAB)";
"Close this overlay and use the second window.\n"
"If you don't see the window, double check your DLL type and apply TDJ I/O patches as needed.";
} else if (games::iidx::IIDX_TDJ_MONITOR_WARNING) { } else if (games::iidx::IIDX_TDJ_MONITOR_WARNING) {
this->disabled_message = "TDJ mode subscreen overlay is not compatible with -monitor option"; this->disabled_message = "TDJ mode subscreen overlay is not compatible with -monitor option.";
} }
float size = 0.5f; float size = 0.5f;
@@ -44,6 +42,18 @@ namespace overlay::windows {
ImGui::GetIO().DisplaySize.y - this->init_size.y - (ImGui::GetFrameHeight() / 2)); ImGui::GetIO().DisplaySize.y - this->init_size.y - (ImGui::GetFrameHeight() / 2));
} }
void IIDXSubScreen::check_for_errors() {
if (games::iidx::CURRENT_IO_EMULATION_STATE ==
games::iidx::iidx_aio_emulation_state::bi2a_com2) {
// explicitly check if LDJ I/O is enabled (as opposed to checking if NOT TDJ I/O)
// to account for the fact that I/O emulation may be disabled
this->disabled_message =
"LDJ I/O detected; TDJ mode subscreen overlay requires TDJ I/O.\n"
"Ensure you apply applicable TDJ I/O patches, or run with TDJ DLL.";
}
}
void IIDXSubScreen::touch_transform(const ImVec2 xy_in, LONG *x_out, LONG *y_out) { void IIDXSubScreen::touch_transform(const ImVec2 xy_in, LONG *x_out, LONG *y_out) {
if (!this->get_active()) { if (!this->get_active()) {
return; return;
+1
View File
@@ -12,6 +12,7 @@ namespace overlay::windows {
protected: protected:
void touch_transform(const ImVec2 xy_in, LONG *x_out, LONG *y_out) override; void touch_transform(const ImVec2 xy_in, LONG *x_out, LONG *y_out) override;
void check_for_errors() override;
}; };
} }
+6 -2
View File
@@ -3,6 +3,7 @@
#include "launcher/launcher.h" #include "launcher/launcher.h"
#include "games/io.h" #include "games/io.h"
#include "misc/eamuse.h" #include "misc/eamuse.h"
#include "overlay/imgui/extensions.h"
namespace overlay::windows { namespace overlay::windows {
@@ -11,7 +12,7 @@ namespace overlay::windows {
this->flags = ImGuiWindowFlags_AlwaysAutoResize; this->flags = ImGuiWindowFlags_AlwaysAutoResize;
this->init_size = ImVec2( this->init_size = ImVec2(
80, overlay::apply_scaling(80),
ImGui::GetFrameHeight() + ImGui::GetFrameHeight() +
ImGui::GetStyle().WindowPadding.y * 2 + ImGui::GetStyle().WindowPadding.y * 2 +
ImGui::GetFrameHeightWithSpacing() * 3 + ImGui::GetFrameHeightWithSpacing() * 3 +
@@ -58,7 +59,7 @@ namespace overlay::windows {
void IOPanel::build_io_panel() {} void IOPanel::build_io_panel() {}
void IOPanel::build_operator_menu() { void IOPanel::build_operator_menu() {
const ImVec2 wide(60, 0); const ImVec2 wide = overlay::apply_scaling_to_vector(60, 0);
const float spacing_x = ImGui::GetStyle().ItemSpacing.y; const float spacing_x = ImGui::GetStyle().ItemSpacing.y;
const ImVec2 tall( const ImVec2 tall(
ImGui::GetFrameHeight(), ImGui::GetFrameHeight(),
@@ -80,6 +81,9 @@ namespace overlay::windows {
{ {
ImGui::SetCursorPosY(ImGui::GetCursorPosY() - ImGui::GetFrameHeightWithSpacing()); ImGui::SetCursorPosY(ImGui::GetCursorPosY() - ImGui::GetFrameHeightWithSpacing());
this->build_button("+", tall, this->test_button, this->service_button); this->build_button("+", tall, this->test_button, this->service_button);
if (ImGui::IsItemHovered(ImGui::TOOLTIP_FLAGS)) {
ImGui::HelpTooltip("SERVICE + TEST");
}
} }
ImGui::EndGroup(); ImGui::EndGroup();
+50 -15
View File
@@ -3,6 +3,7 @@
#include "games/io.h" #include "games/io.h"
#include "games/gitadora/gitadora.h" #include "games/gitadora/gitadora.h"
#include "games/gitadora/io.h" #include "games/gitadora/io.h"
#include "overlay/imgui/extensions.h"
#include "misc/eamuse.h" #include "misc/eamuse.h"
#include "util/logging.h" #include "util/logging.h"
@@ -11,6 +12,7 @@ namespace overlay::windows {
GitadoraIOPanel::GitadoraIOPanel(SpiceOverlay *overlay) : IOPanel(overlay) { GitadoraIOPanel::GitadoraIOPanel(SpiceOverlay *overlay) : IOPanel(overlay) {
this->title = "GITADORA IO Panel"; this->title = "GITADORA IO Panel";
this->has_menu_controls = true;
// by default, make a safer assumption that there are two players // by default, make a safer assumption that there are two players
this->two_players = true; this->two_players = true;
// by default, enable the extra input only available on DX cabs... // by default, enable the extra input only available on DX cabs...
@@ -33,6 +35,12 @@ namespace overlay::windows {
this->has_guitar_knobs = false; this->has_guitar_knobs = false;
} }
if (games::gitadora::is_arena_model()) {
this->has_menu_controls = false;
this->two_players = false;
this->has_guitar_knobs = false;
}
find_gfdm_buttons(); find_gfdm_buttons();
} }
@@ -68,25 +76,45 @@ namespace overlay::windows {
} }
void GitadoraIOPanel::build_io_panel() { void GitadoraIOPanel::build_io_panel() {
ImGui::Dummy(ImVec2(12, 0)); ImGui::Dummy(overlay::apply_scaling_to_vector(12, 0));
ImGui::SameLine(); if (this->has_menu_controls) {
this->draw_buttons(0);
if (this->has_guitar_knobs) {
ImGui::SameLine(); ImGui::SameLine();
this->draw_sliders(0); ImGui::PushID("P1");
} this->draw_buttons(0);
// draw p2 only if guitar freaks
if (this->two_players) {
ImGui::SameLine();
ImGui::Dummy(ImVec2(12, 0));
ImGui::SameLine();
this->draw_buttons(1);
if (this->has_guitar_knobs) { if (this->has_guitar_knobs) {
ImGui::SameLine(); ImGui::SameLine();
this->draw_sliders(1); this->draw_sliders(0);
} }
ImGui::PopID();
// draw p2 only if guitar freaks
if (this->two_players) {
ImGui::SameLine();
ImGui::Dummy(overlay::apply_scaling_to_vector(12, 0));
ImGui::SameLine();
ImGui::PushID("P2");
this->draw_buttons(1);
if (this->has_guitar_knobs) {
ImGui::SameLine();
this->draw_sliders(1);
}
ImGui::PopID();
}
}
if (games::gitadora::is_guitar()) {
ImGui::SameLine();
ImGui::Dummy(overlay::apply_scaling_to_vector(12, 0));
ImGui::SameLine();
ImGui::BeginGroup();
{
ImGui::Checkbox("P1 Lefty", &games::gitadora::P1_LEFTY);
if (this->two_players) {
ImGui::Checkbox("P2 Lefty", &games::gitadora::P2_LEFTY);
}
}
ImGui::EndGroup();
} }
} }
@@ -107,6 +135,10 @@ namespace overlay::windows {
{ {
ImGui::SetCursorPosY(ImGui::GetCursorPosY() + ImGui::GetFrameHeightWithSpacing()); ImGui::SetCursorPosY(ImGui::GetCursorPosY() + ImGui::GetFrameHeightWithSpacing());
this->build_button("<", leftright_size, this->left[p], nullptr, this->leftright_light[p]); this->build_button("<", leftright_size, this->left[p], nullptr, this->leftright_light[p]);
this->build_button("+", tiny_size, this->help[p], this->start[p], nullptr);
if (ImGui::IsItemHovered(ImGui::TOOLTIP_FLAGS)) {
ImGui::HelpTooltip("HELP + START");
}
} }
ImGui::EndGroup(); ImGui::EndGroup();
@@ -135,6 +167,9 @@ namespace overlay::windows {
ImGui::BeginGroup(); ImGui::BeginGroup();
{ {
this->build_button("?", tiny_size, this->help[p], nullptr, this->help_light[p]); this->build_button("?", tiny_size, this->help[p], nullptr, this->help_light[p]);
if (ImGui::IsItemHovered(ImGui::TOOLTIP_FLAGS)) {
ImGui::HelpTooltip("HELP");
}
this->build_button(">", leftright_size, this->right[p], nullptr, this->leftright_light[p]); this->build_button(">", leftright_size, this->right[p], nullptr, this->leftright_light[p]);
} }
ImGui::EndGroup(); ImGui::EndGroup();
@@ -146,7 +181,7 @@ namespace overlay::windows {
games::gitadora::Analogs::GuitarP1Knob : games::gitadora::Analogs::GuitarP1Knob :
games::gitadora::Analogs::GuitarP2Knob; games::gitadora::Analogs::GuitarP2Knob;
const ImVec2 slider_size(32, get_suggested_height()); const ImVec2 slider_size(overlay::apply_scaling(32), get_suggested_height());
// get analog // get analog
const auto analogs = games::get_analogs(eamuse_get_game()); const auto analogs = games::get_analogs(eamuse_get_game());
@@ -18,6 +18,7 @@ namespace overlay::windows {
void draw_buttons(const int player); void draw_buttons(const int player);
void draw_sliders(const int player); void draw_sliders(const int player);
bool has_menu_controls;
bool two_players; bool two_players;
bool has_guitar_knobs; bool has_guitar_knobs;
+15 -7
View File
@@ -19,15 +19,15 @@ namespace overlay::windows {
case 0: { case 0: {
this->toggle_button = games::OverlayButtons::ToggleVirtualKeypadP1; this->toggle_button = games::OverlayButtons::ToggleVirtualKeypadP1;
this->init_pos = ImVec2( this->init_pos = ImVec2(
26, overlay::apply_scaling(26),
ImGui::GetIO().DisplaySize.y - 264); ImGui::GetIO().DisplaySize.y - overlay::apply_scaling(264));
break; break;
} }
case 1: { case 1: {
this->toggle_button = games::OverlayButtons::ToggleVirtualKeypadP2; this->toggle_button = games::OverlayButtons::ToggleVirtualKeypadP2;
this->init_pos = ImVec2( this->init_pos = ImVec2(
ImGui::GetIO().DisplaySize.x - 220, ImGui::GetIO().DisplaySize.x - overlay::apply_scaling(220),
ImGui::GetIO().DisplaySize.y - 264); ImGui::GetIO().DisplaySize.y - overlay::apply_scaling(264));
break; break;
} }
} }
@@ -82,9 +82,17 @@ namespace overlay::windows {
// add selectable (fill last line) // add selectable (fill last line)
if (i == std::size(BUTTONS) - 1) { if (i == std::size(BUTTONS) - 1) {
ImGui::Selectable(button.text, false, 0, ImVec2(112, 32)); ImGui::Selectable(
button.text,
false,
0,
overlay::apply_scaling_to_vector(ImVec2(112, 32)));
} else { } else {
ImGui::Selectable(button.text, false, 0, ImVec2(32, 32)); ImGui::Selectable(
button.text,
false,
0,
overlay::apply_scaling_to_vector(ImVec2(32, 32)));
} }
// mouse down handler // mouse down handler
@@ -112,7 +120,7 @@ namespace overlay::windows {
"Lightning Model cabinets (TDJ) do not have any keypads; they use the subscreen.\n\n" "Lightning Model cabinets (TDJ) 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, " "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" "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.\n\n" "Windowed mode: look for the second window in the taskbar (or ALT+TAB).\n\n"
"Windowed mode with -iidxnosub: bring up the subscreen overlay (default Page Up).\n\n" "Windowed mode with -iidxnosub: bring up the subscreen overlay (default Page Up).\n\n"
); );
+54 -15
View File
@@ -1,11 +1,13 @@
#include "patch_manager.h" #include "patch_manager.h"
#include <chrono>
#include <thread> #include <thread>
#include <fstream> #include <fstream>
#include <shellapi.h> #include <shellapi.h>
#include <winhttp.h> #include <winhttp.h>
#include <psapi.h> #include <psapi.h>
#include <format> #include <format>
#include "external/fmt/include/fmt/chrono.h"
#include "external/rapidjson/document.h" #include "external/rapidjson/document.h"
#include "external/rapidjson/prettywriter.h" #include "external/rapidjson/prettywriter.h"
#include "external/rapidjson/stringbuffer.h" #include "external/rapidjson/stringbuffer.h"
@@ -719,7 +721,7 @@ namespace overlay::windows {
if (style_color_pushed) { if (style_color_pushed) {
ImGui::PopStyleColor(style_color_pushed); ImGui::PopStyleColor(style_color_pushed);
} }
if (ImGui::IsItemHovered()) { if (ImGui::IsItemHovered(ImGui::TOOLTIP_FLAGS)) {
show_patch_tooltip(patch); show_patch_tooltip(patch);
} }
@@ -760,7 +762,7 @@ namespace overlay::windows {
patch.last_status = is_patch_active(patch); patch.last_status = is_patch_active(patch);
} }
ImGui::EndDisabled(); ImGui::EndDisabled();
if (ImGui::IsItemHovered()) { if (ImGui::IsItemHovered(ImGui::TOOLTIP_FLAGS)) {
show_patch_tooltip(patch); show_patch_tooltip(patch);
} }
@@ -789,7 +791,7 @@ namespace overlay::windows {
} }
ImGui::EndCombo(); ImGui::EndCombo();
} }
if (ImGui::IsItemHovered()) { if (ImGui::IsItemHovered(ImGui::TOOLTIP_FLAGS)) {
show_patch_tooltip(patch); show_patch_tooltip(patch);
} }
} else if (patch.type == PatchType::Integer) { } else if (patch.type == PatchType::Integer) {
@@ -805,7 +807,7 @@ namespace overlay::windows {
apply_patch(patch, true); apply_patch(patch, true);
config_dirty = true; config_dirty = true;
} }
if (ImGui::IsItemHovered()) { if (ImGui::IsItemHovered(ImGui::TOOLTIP_FLAGS)) {
show_patch_tooltip(patch); show_patch_tooltip(patch);
} }
} }
@@ -822,7 +824,7 @@ namespace overlay::windows {
ImGui::InputInt("##dummy_int_input", &patch.patch_number.value); ImGui::InputInt("##dummy_int_input", &patch.patch_number.value);
} }
ImGui::EndDisabled(); ImGui::EndDisabled();
if (ImGui::IsItemHovered()) { if (ImGui::IsItemHovered(ImGui::TOOLTIP_FLAGS)) {
show_patch_tooltip(patch); show_patch_tooltip(patch);
} }
} }
@@ -928,6 +930,13 @@ namespace overlay::windows {
} }
} }
// match on model and ext, ignoring dest/spec/rev
// sample: LDJ:J:E:A:2025011400
bool PatchManager::is_game_id_wildcard_matched(const std::string& id_from_config) {
return ((id_from_config.compare(0, 3, avs::game::MODEL) == 0) &&
(id_from_config.compare(10, 10, avs::game::EXT) == 0));
}
void PatchManager::config_load() { void PatchManager::config_load() {
log_info("patchmanager", "loading config"); log_info("patchmanager", "loading config");
@@ -953,7 +962,7 @@ namespace overlay::windows {
if (auto_apply != doc.MemberEnd() && auto_apply->value.IsArray()) { if (auto_apply != doc.MemberEnd() && auto_apply->value.IsArray()) {
// get game id // get game id
auto game_id = avs::game::get_identifier(); const auto game_id = avs::game::get_identifier();
// iterate entries // iterate entries
setting_auto_apply = false; setting_auto_apply = false;
@@ -962,15 +971,37 @@ namespace overlay::windows {
if (entry.IsString()) { if (entry.IsString()) {
// check if this is our game identifier // check if this is our game identifier
std::string entry_id = entry.GetString(); const std::string entry_id = entry.GetString();
if (game_id == entry_id) {
setting_auto_apply = true; if (!setting_auto_apply) {
if (game_id == entry_id) {
// exact match
setting_auto_apply = true;
log_misc(
"patchmanager",
"matched auto apply entry by full game identifier: {}",
entry_id);
} else if (is_game_id_wildcard_matched(entry_id)) {
// match on model and ext, ignoring dest/spec/rev
// sample: LDJ:J:E:A:2025011400
setting_auto_apply = true;
log_misc(
"patchmanager",
"matched auto apply entry by partial game identifier: {}:?:?:?:{}",
avs::game::MODEL, avs::game::EXT);
}
} }
// move to list // move to list
setting_auto_apply_list.emplace_back(entry_id); setting_auto_apply_list.emplace_back(entry_id);
} }
} }
if (!setting_auto_apply) {
log_misc(
"patchmanager",
"no auto apply entry matched, patches will not load automatically");
}
} }
// read enabled patches // read enabled patches
@@ -1058,7 +1089,7 @@ namespace overlay::windows {
auto game_id = avs::game::get_identifier(); auto game_id = avs::game::get_identifier();
bool game_id_added = false; bool game_id_added = false;
for (auto &entry : setting_auto_apply_list) { for (auto &entry : setting_auto_apply_list) {
if (entry == game_id) { if (entry == game_id || is_game_id_wildcard_matched(entry)) {
if (!setting_auto_apply) { if (!setting_auto_apply) {
continue; continue;
} }
@@ -1150,7 +1181,7 @@ namespace overlay::windows {
} }
} }
std::string get_game_identifier(const std::filesystem::path& dll_path) { std::string get_game_identifier(const std::filesystem::path& dll_path, bool print_info) {
uint32_t time_date_stamp = 0; uint32_t time_date_stamp = 0;
uint32_t address_of_entry_point = 0; uint32_t address_of_entry_point = 0;
@@ -1168,6 +1199,16 @@ namespace overlay::windows {
time_date_stamp, time_date_stamp,
address_of_entry_point); address_of_entry_point);
if (print_info) {
const auto time = std::chrono::system_clock::from_time_t(time_date_stamp);
log_info(
"patchmanager",
"file: {}, patch id: {}, build timestamp of dll: {:%Y-%m-%d %H:%M}",
dll_path.has_filename() ? dll_path.filename().string() : dll_path.string(),
identifier,
time);
}
return identifier; return identifier;
} }
@@ -1657,14 +1698,12 @@ namespace overlay::windows {
ACTIVE_JSON_FILE = ""; ACTIVE_JSON_FILE = "";
std::string firstDll = avs::game::DLL_NAME; std::string firstDll = avs::game::DLL_NAME;
std::string first_id = get_game_identifier(MODULE_PATH / firstDll); std::string first_id = get_game_identifier(MODULE_PATH / firstDll, true);
std::filesystem::path firstPath = fmt::format("patches/{}.json", first_id); std::filesystem::path firstPath = fmt::format("patches/{}.json", first_id);
log_misc("patchmanager", "patch identifier of {}: {}", firstDll, first_id);
auto extraDlls = getExtraDlls(firstDll); auto extraDlls = getExtraDlls(firstDll);
std::erase_if(extraDlls, [](const std::string& dll) { std::erase_if(extraDlls, [](const std::string& dll) {
auto identifier = get_game_identifier(MODULE_PATH / dll); auto identifier = get_game_identifier(MODULE_PATH / dll, true);
return identifier.empty() || !fileutils::file_exists(fmt::format("patches/{}.json", identifier)); return identifier.empty() || !fileutils::file_exists(fmt::format("patches/{}.json", identifier));
}); });
+3 -1
View File
@@ -97,7 +97,7 @@ namespace overlay::windows {
extern std::optional<std::string> PATCH_MANAGER_CFG_PATH_OVERRIDE; extern std::optional<std::string> PATCH_MANAGER_CFG_PATH_OVERRIDE;
std::string get_game_identifier(const std::filesystem::path& dll_path); std::string get_game_identifier(const std::filesystem::path& dll_path, bool print_info=false);
class PatchManager : public Window { class PatchManager : public Window {
public: public:
@@ -143,6 +143,8 @@ namespace overlay::windows {
std::string pe_identifier_for_patch = ""); std::string pe_identifier_for_patch = "");
void show_patch_tooltip(const PatchData& patch); void show_patch_tooltip(const PatchData& patch);
bool is_game_id_wildcard_matched(const std::string& id_from_config);
}; };
PatchStatus is_patch_active(PatchData &patch); PatchStatus is_patch_active(PatchData &patch);
+81 -34
View File
@@ -27,6 +27,12 @@ namespace overlay::windows {
} }
HWND ScreenResize::get_first_window() { HWND ScreenResize::get_first_window() {
// this is ideal
if (GRAPHICS_HOOKED_WINDOW.has_value()) {
return GRAPHICS_HOOKED_WINDOW.value();
}
// we typically do not want to check GRAPHICS_WINDOWS because it may include
// system windows (e.g., CicMarshalWnd)
if (GRAPHICS_WINDOWS.size() == 0) { if (GRAPHICS_WINDOWS.size() == 0) {
return NULL; return NULL;
} }
@@ -95,6 +101,21 @@ namespace overlay::windows {
} }
void ScreenResize::build_fullscreen_config() { void ScreenResize::build_fullscreen_config() {
if (avs::game::is_model("KFC")) {
ImGui::TextColored(ImVec4(1, 0.5f, 0.5f, 1.f),
"Warning: Enabling Image Resize uses more GPU\n"
"resources and may significantly lower framerate\n"
"for songs with Live2D! Results may vary, use at\n"
"your own risk.");
} else {
ImGui::TextColored(ImVec4(1, 0.5f, 0.5f, 1.f),
"Warning: Enabling Image Resize uses more GPU\n"
"resources and may significantly lower framerate\n"
"in some situations! Results may vary, use at\n"
"your own risk.");
}
// enable checkbox // enable checkbox
ImGui::Checkbox("Enable", &cfg::SCREENRESIZE->enable_screen_resize); ImGui::Checkbox("Enable", &cfg::SCREENRESIZE->enable_screen_resize);
ImGui::SameLine(); ImGui::SameLine();
@@ -136,22 +157,27 @@ namespace overlay::windows {
// aspect ratio // aspect ratio
ImGui::Checkbox("Keep Aspect Ratio", &scene.keep_aspect_ratio); ImGui::Checkbox("Keep Aspect Ratio", &scene.keep_aspect_ratio);
if (scene.keep_aspect_ratio) { if (scene.keep_aspect_ratio) {
if (ImGui::SliderFloat("Scale", &scene.scale_x, 0.5f, 2.5f)) {
// we want to avoid zooming out below 0.6 as it will cause StretchRect calls to
// go on a slow sync path with the GPU, leading to frame drops in some situations
// (e.g., SDVX Live2D)
if (ImGui::SliderFloat("Scale", &scene.scale_x, 0.6f, 2.5f)) {
scene.scale_y = scene.scale_x; scene.scale_y = scene.scale_x;
} }
ImGui::SameLine(); ImGui::SameLine();
ImGui::HelpMarker("Hint: ctrl + click on the slider to type in a numeric value."); ImGui::HelpMarker("Hint: ctrl + click on the slider to type in a numeric value.");
} else { } else {
ImGui::SliderFloat("Width Scale", &scene.scale_x, 0.5f, 2.5f); ImGui::SliderFloat("Width Scale", &scene.scale_x, 0.6f, 2.5f);
ImGui::SameLine(); ImGui::SameLine();
ImGui::HelpMarker("Hint: ctrl + click on the slider to type in a numeric value."); ImGui::HelpMarker("Hint: ctrl + click on the slider to type in a numeric value.");
ImGui::SliderFloat("Height Scale", &scene.scale_y, 0.5f, 2.5f); ImGui::SliderFloat("Height Scale", &scene.scale_y, 0.6f, 2.5f);
ImGui::SameLine(); ImGui::SameLine();
ImGui::HelpMarker("Hint: ctrl + click on the slider to type in a numeric value."); ImGui::HelpMarker("Hint: ctrl + click on the slider to type in a numeric value.");
} }
static const char* dupe_items[] = { "None", "Copy Left", "Copy Right" }; static const char* dupe_items[] = { "None", "Copy Left", "Copy Right" };
ImGui::Combo( const bool duplicate_changed = ImGui::Combo(
"Duplicate", "Duplicate",
reinterpret_cast<int *>(&scene.duplicate), reinterpret_cast<int *>(&scene.duplicate),
dupe_items, dupe_items,
@@ -162,6 +188,11 @@ namespace overlay::windows {
"wrap-around effect when an offset is set."); "wrap-around effect when an offset is set.");
ImGui::EndDisabled(); ImGui::EndDisabled();
// tell d3d9 device to clear surface to get rid of ghost image
if (duplicate_changed) {
cfg::SCREENRESIZE->need_surface_clean = true;
}
} }
void ScreenResize::build_windowed_config() { void ScreenResize::build_windowed_config() {
@@ -171,8 +202,7 @@ namespace overlay::windows {
return; return;
} }
ImGui::TextUnformatted("Warning: may cause some games to crash."); ImGui::BeginDisabled(graphics_window_decoration_change_crashes_game());
ImGui::BeginDisabled(graphics_window_change_crashes_game());
if (ImGui::Combo( if (ImGui::Combo(
"Window Style", "Window Style",
&cfg::SCREENRESIZE->window_decoration, &cfg::SCREENRESIZE->window_decoration,
@@ -197,42 +227,59 @@ namespace overlay::windows {
"Reduces pixelated scaling artifacts. Works great on some games, but completely broken on others.\n\n" "Reduces pixelated scaling artifacts. Works great on some games, but completely broken on others.\n\n"
"This can't be changed in-game; instead, set -windowscale option in spicecfg and restart."); "This can't be changed in-game; instead, set -windowscale option in spicecfg and restart.");
ImGui::Checkbox("Keep Aspect Ratio", &cfg::SCREENRESIZE->client_keep_aspect_ratio);
ImGui::Checkbox("Manual window move/resize", &cfg::SCREENRESIZE->enable_window_resize); ImGui::BeginDisabled(graphics_window_move_and_resize_breaks_game());
{
ImGui::Checkbox("Keep Aspect Ratio", &cfg::SCREENRESIZE->client_keep_aspect_ratio);
ImGui::Checkbox("Manual window move/resize", &cfg::SCREENRESIZE->enable_window_resize);
}
ImGui::EndDisabled();
ImGui::BeginDisabled(!cfg::SCREENRESIZE->enable_window_resize); ImGui::BeginDisabled(!cfg::SCREENRESIZE->enable_window_resize);
bool changed = false; bool changed = false;
const uint32_t step = 1; const uint32_t step = 1;
const uint32_t step_fast = 10; const uint32_t step_fast = 10;
ImGui::BeginDisabled(cfg::SCREENRESIZE->client_keep_aspect_ratio);
ImGui::InputScalar( ImGui::BeginDisabled(graphics_window_resize_breaks_game());
"Width", {
ImGuiDataType_U32, ImGui::BeginDisabled(cfg::SCREENRESIZE->client_keep_aspect_ratio);
&cfg::SCREENRESIZE->client_width, {
&step, &step_fast, nullptr); ImGui::InputScalar(
changed |= ImGui::IsItemDeactivatedAfterEdit(); "Width",
ImGuiDataType_U32,
&cfg::SCREENRESIZE->client_width,
&step, &step_fast, nullptr);
changed |= ImGui::IsItemDeactivatedAfterEdit();
}
ImGui::EndDisabled();
ImGui::InputScalar(
"Height",
ImGuiDataType_U32,
&cfg::SCREENRESIZE->client_height,
&step, &step_fast, nullptr);
changed |= ImGui::IsItemDeactivatedAfterEdit();
}
ImGui::EndDisabled(); ImGui::EndDisabled();
ImGui::InputScalar( ImGui::BeginDisabled(graphics_window_move_and_resize_breaks_game());
"Height", {
ImGuiDataType_U32, ImGui::InputScalar(
&cfg::SCREENRESIZE->client_height, "X Offset",
&step, &step_fast, nullptr); ImGuiDataType_S32,
changed |= ImGui::IsItemDeactivatedAfterEdit(); &cfg::SCREENRESIZE->window_offset_x,
&step, &step_fast, nullptr);
changed |= ImGui::IsItemDeactivatedAfterEdit();
ImGui::InputScalar( ImGui::InputScalar(
"X Offset", "Y Offset",
ImGuiDataType_S32, ImGuiDataType_S32,
&cfg::SCREENRESIZE->window_offset_x, &cfg::SCREENRESIZE->window_offset_y,
&step, &step_fast, nullptr); &step, &step_fast, nullptr);
changed |= ImGui::IsItemDeactivatedAfterEdit(); changed |= ImGui::IsItemDeactivatedAfterEdit();
}
ImGui::InputScalar( ImGui::EndDisabled();
"Y Offset",
ImGuiDataType_S32,
&cfg::SCREENRESIZE->window_offset_y,
&step, &step_fast, nullptr);
changed |= ImGui::IsItemDeactivatedAfterEdit();
if (changed) { if (changed) {
if (cfg::SCREENRESIZE->client_keep_aspect_ratio) { if (cfg::SCREENRESIZE->client_keep_aspect_ratio) {
+2 -3
View File
@@ -10,8 +10,7 @@ namespace overlay::windows {
SDVXSubScreen::SDVXSubScreen(SpiceOverlay *overlay) : GenericSubScreen(overlay) { SDVXSubScreen::SDVXSubScreen(SpiceOverlay *overlay) : GenericSubScreen(overlay) {
this->title = "SDVX Subscreen"; this->title = "SDVX Subscreen";
bool isValkyrieCabinetMode = avs::game::SPEC[0] == 'G' || avs::game::SPEC[0] == 'H'; if (!games::sdvx::is_valkyrie_model()) {
if (!isValkyrieCabinetMode) {
this->disabled_message = "Valkyrie Model mode is not enabled!"; this->disabled_message = "Valkyrie Model mode is not enabled!";
} else if (games::sdvx::SHOW_VM_MONITOR_WARNING) { } else if (games::sdvx::SHOW_VM_MONITOR_WARNING) {
this->disabled_message = "VM mode subscreen overlay is not compatible with -monitor option"; this->disabled_message = "VM mode subscreen overlay is not compatible with -monitor option";
@@ -19,7 +18,7 @@ namespace overlay::windows {
if (GRAPHICS_PREVENT_SECONDARY_WINDOW) { if (GRAPHICS_PREVENT_SECONDARY_WINDOW) {
this->disabled_message = "Subscreen has been disabled by the user (-sdvxnosub)."; this->disabled_message = "Subscreen has been disabled by the user (-sdvxnosub).";
} else { } else {
this->disabled_message = "Overlay unavailable in windowed mode! Use the second window instead."; this->disabled_message = "Overlay unavailable in windowed mode! Use the second window instead. (ALT+TAB)";
} }
} }
+1 -1
View File
@@ -1986,7 +1986,7 @@ void CALLBACK rawinput::RawInputManager::input_midi_proc(HMIDIIN hMidiIn, UINT w
device.midiInfo->v2_last_off_time[midi_index] = get_performance_milliseconds(); device.midiInfo->v2_last_off_time[midi_index] = get_performance_milliseconds();
device.updated = true; device.updated = true;
} }
// for v2_drum, NOTE ON is ignored // for v2_drum, NOTE OFF is ignored
} }
} }
+14 -4
View File
@@ -32,6 +32,16 @@ namespace deferredlogs {
deferred_errors.emplace_back(messages); deferred_errors.emplace_back(messages);
} }
void report_fatal_message() {
static std::once_flag printed;
std::call_once(printed, []() {
deferredlogs::defer_error_messages({
"game reported FATAL error(s)",
" look for messages containing F: in the log",
});
});
}
void dump_to_logger(bool is_crash) { void dump_to_logger(bool is_crash) {
static std::once_flag printed; static std::once_flag printed;
std::call_once(printed, [is_crash]() { std::call_once(printed, [is_crash]() {
@@ -76,8 +86,8 @@ namespace deferredlogs {
msg += "\n"; msg += "\n";
} }
for (auto messages : errors) { for (const auto &messages : errors) {
for (auto message : messages) { for (const auto &message : messages) {
msg += " " + message + "\n"; msg += " " + message + "\n";
} }
msg += "\n"; msg += "\n";
@@ -85,13 +95,13 @@ namespace deferredlogs {
msg += " unsure what to do next?\n"; msg += " unsure what to do next?\n";
msg += " * update to the latest version:\n"; msg += " * update to the latest version:\n";
msg += " https://github.com/spice2x/spice2x.github.io/releases/latest\n"; msg += " https://github.com/spice2x/spice2x.github.io/releases\n";
msg += " * check the FAQ:\n"; msg += " * check the FAQ:\n";
msg += " https://github.com/spice2x/spice2x.github.io/wiki/Known-issues\n"; msg += " https://github.com/spice2x/spice2x.github.io/wiki/Known-issues\n";
msg += "\n"; msg += "\n";
msg += "\\------------------------- spice2x auto-troubleshooter ------------------------/\n"; msg += "\\------------------------- spice2x auto-troubleshooter ------------------------/\n";
log_warning("troubleshooter", "{}", msg); log_special("troubleshooter", "{}", msg);
}); });
} }
} }
+1
View File
@@ -10,6 +10,7 @@ namespace deferredlogs {
extern const std::initializer_list<std::string> SUPERSTEP_SOUND_ERROR_MESSAGE; extern const std::initializer_list<std::string> SUPERSTEP_SOUND_ERROR_MESSAGE;
void set_softid(const std::string& softid); void set_softid(const std::string& softid);
void report_fatal_message();
void defer_error_messages(std::initializer_list<std::string> messages); void defer_error_messages(std::initializer_list<std::string> messages);
void dump_to_logger(bool is_crash=false); void dump_to_logger(bool is_crash=false);
} }
+3 -2
View File
@@ -13,8 +13,9 @@ std::string_view log_get_datetime() {
std::string_view log_get_datetime(std::time_t now) { std::string_view log_get_datetime(std::time_t now) {
static thread_local char buf[64]; static thread_local char buf[64];
// `localtime` on Windows is thread-safe std::tm local_tm{};
strftime(buf, sizeof(buf), "[%Y/%m/%d %X]", localtime(&now)); localtime_s(&local_tm, &now);
strftime(buf, sizeof(buf), "[%Y/%m/%d %X]", &local_tm);
return buf; return buf;
} }
+3
View File
@@ -87,6 +87,9 @@ void show_popup_for_fatal_error(std::string message);
#define log_warning(module, format_str, ...) logger::push( \ #define log_warning(module, format_str, ...) logger::push( \
LOG_FORMAT("W", module, format_str, ## __VA_ARGS__), logger::Style::YELLOW) LOG_FORMAT("W", module, format_str, ## __VA_ARGS__), logger::Style::YELLOW)
#define log_special(module, format_str, ...) logger::push( \
LOG_FORMAT("W", module, format_str, ## __VA_ARGS__), logger::Style::SPECIAL)
#define log_fatal(module, format_str, ...) { \ #define log_fatal(module, format_str, ...) { \
logger::push(LOG_FORMAT("F", module, format_str, ## __VA_ARGS__), logger::Style::RED); \ logger::push(LOG_FORMAT("F", module, format_str, ## __VA_ARGS__), logger::Style::RED); \
show_popup_for_fatal_error(LOG_FORMAT_POPUP(module, format_str, ## __VA_ARGS__)); \ show_popup_for_fatal_error(LOG_FORMAT_POPUP(module, format_str, ## __VA_ARGS__)); \

Some files were not shown because too many files have changed in this diff Show More