Compare commits

..

131 Commits

Author SHA1 Message Date
bicarus 220b27159b ea3: don't treat failure from dll_entry_init as fatal (#601)
## Link to GitHub Issue or related Pull Request, if one exists
fixes #600
2026-03-30 02:31:08 -07:00
bicarus b06a0a7d47 overlay: card override input should be disabled when overridden with cmd line (#599) 2026-03-29 20:36:42 -07:00
bicarus d66af8e16b overlay: options tab(s) clean up (#598)
## Description of change
Merge option name and command line parameter columns.
Expand the size of widget column.
In Search tab, add a new column to display the category.
Add a highlight effect to rows when hovered over - apply this to all
tables in the configurator UI.
2026-03-28 18:21:53 -07:00
bicarus-dev ad366ecdd0 update fatal crash message 2026-03-27 18:46:08 -07:00
llm96 22cb4d689c utils: print DLL info for early hooks (#597)
## Link to GitHub Issue or related Pull Request, if one exists
#596

## Description of change
Adds call to `libutils::print_dll_info` for early hooks too.

## Testing
```
[2026/03/27 09:51:16] I:launcher: SpiceTools Bootstrap (x64) (spice2x)
[2026/03/27 09:51:16] I:launcher: 1.0-V-2026-03-27T07:46:10
[2026/03/27 09:51:16] I:launcher: spice2x is free & open source; if you paid money for it, you got scammed
[2026/03/27 09:51:16] I:launcher: visit https://spice2x.github.io to download the latest version for free
[2026/03/27 09:51:16] I:launcher: arguments:
                                      [...]
                                      -z C:\Users\Noe\AppData\Local\Programs\SpiceTools\hooks\64\mempatcher64.dll
                                      [...]
[2026/03/27 09:51:16] I:launcher: loading early hook DLL C:\Users\Noe\AppData\Local\Programs\SpiceTools\hooks\64\mempatcher64.dll
[2026/03/27 09:51:16] I:superexit: enabled
[2026/03/27 09:51:16] I:libutils: DLL info for mempatcher64.dll: CompanyName = aixxe, ProductName = mempatcher, Version = 4975386c637a86e8daa89d479a0c67ab28d792ec
```
2026-03-27 12:19:32 -07:00
bicarus b38ea463aa utils: print out DLL metadata (#596)
## Link to GitHub Issue or related Pull Request, if one exists
#592 

## Description of change
* when custom DLLs are detected (`d3d9.dll`) print out some basic data
from the PE header
* for our own NVIDIA DLL stubs, add manifest files to identify ourselves
* as a bonus, when DLL hooks are loaded from `-k`, print DLL info there
as well

This is mainly to detect when DXVK is in use.
2026-03-27 02:34:56 -07:00
bicarus 056d1b2d90 ea3: warn about missing ea3 config (#595)
Show better warning message when `ea3-config.xml` / `eamuse-config.xml`
is missing. Right now we just say `failed to load prop/eamuseconfig.xml`
which is really confusing when people look for `eamuse-config.xml` that
never existed in the first place.
2026-03-27 01:56:32 -07:00
bicarus 37693916d9 overlay: show warning when binding analog axis as a button for TT/knobs (#594)
## Link to GitHub Issue or related Pull Request, if one exists
#345 

## Description of change
When X or Y axis is being bound to any button labeled `TT` or `Knob` or
`VOL-`, show a warning and ask if the user really meant to do that,
encouraging them to use the Analog tab instead.
2026-03-26 21:17:51 -07:00
bicarus 889e8b43eb overlay: redesign Cards tab (#593)
* add `-card0` `-card1` options at the top of Cards tab to clearly
indicate that they take precedence over card files
* reorganize UI / clean up
* remove ability to directly edit the contents of `card0.txt` /
`card1.txt` as that wasn't very intuitive; instead, encourage users to
use overrides directly or use an external text editor (`Edit` button
opens notepad)
2026-03-26 01:32:48 -07:00
bicarus 9db2db2340 presets: clear out applied status when switching profiles (#591)
## Link to GitHub Issue or related Pull Request, if one exists
#581 

## Description of change
Ensure "applied" status is not persisted through.
2026-03-24 22:46:38 -07:00
bicarus 1bb612f3c9 overlay: tweak imgui theme (#590)
## Link to GitHub Issue or related Pull Request, if one exists
#589
2026-03-24 08:57:58 -07:00
bicarus e886ab77af overlay: update imgui theme (#589)
Switch to CrimsonVesuvius theme from
`https://github.com/ocornut/imgui/issues/707#issuecomment-4107169777`
2026-03-24 01:59:13 -07:00
bicarus-dev fbb3c1d4c3 fix option 2026-03-24 00:42:50 -07:00
bicarus 73a69b2e8f touch: native touch hooks to honor Invert Touch option (#588)
## Link to GitHub Issue or related Pull Request, if one exists
For #587 

## Description of change
`Invert Touch` option is now honored by the IIDX/SDVX touch handler.
2026-03-23 20:42:55 -07:00
bicarus 63a0acac24 presets: minor UI fixes, add new controller profiles (#584)
## Link to GitHub Issue or related Pull Request, if one exists
#579 

## Description of change

* Fix profiles being loaded twice on tab switch
* Add more default profiles for some controllers
* Remove borders from tables since they look broken in software renderer
* Add check boxes to import/export only certain parts of of a profile
* Show device identifier string in drop down when applying profiles
since some controllers have multiple identically named interfaces
* Show completion message when action buttons are clicked (`Apply`
button, `Clear all bindings` button both become disabled & shows `done`
message)
2026-03-21 19:43:17 -07:00
ichijyo-hotaru 75ba49ff4a fix qma directory create CreateFileW_Hook to prevent recursive calls (#585)
## Link to GitHub Issue or related Pull Request, if one exists

N/A

## Description of change

Move `dirs_created` flag assignment before `dir_create_recursive` calls
in `CreateFileW_Hook` to prevent stack overflow. `dir_create_recursive`
calls `_wstat64` (ucrtbase), which internally calls `CreateFileW`,
re-entering the hook before the flag is set. GCC 13 preserved the source
order, but GCC 15 reorders the flag store after the function calls,
causing infinite recursion.

Note: I'm not fully confident the root cause is specific to GCC version
differences. Also, I cannot determine whether this directory creation
hook is truly necessary. Without these directories the game boots but
quizzes won't load, making gameplay impossible. It may be worth removing
this hook entirely, but for now this fix is needed to prevent the crash.

## Testing

Verified title screen transition on LMA-2019022700. Crashes on the
latest build without this fix.
2026-03-21 14:23:49 -07:00
ichijyo-hotaru b31ddb1ffc Add controller presets management (#581)
## Link to GitHub Issue or related Pull Request, if one exists
Implements #579.

## Description of change

### Controller Presets

Add a new "Presets" tab to spicecfg that allows users to save, load, and
manage controller binding presets.

#### Save Presets
- Capture all current button, analog, and light bindings as a preset
- "Assign Labels" dialog prompts the user to name each source device
(e.g. "Player 1", "Player 2") before saving
- Device IDs are replaced with these labels, making presets portable
across different machines
- Presets are stored in `%APPDATA%/spice2x/spicetools_presets.xml`

#### Load / Apply Presets
- Preset list shows name, type (Built-in / User), and binding counts
- Apply dialog maps each preset source label to a connected device or
keyboard
- Bindings are applied per-source, supporting multi-device setups (e.g.
two PHOENIXWAN controllers)

#### Edit / Delete Presets
- Rename source labels in saved presets (propagates to all bindings)
- Delete user presets

#### Built-in Presets
- PHOENIXWAN preset for Beatmania IIDX: P1/P2 buttons (1-7, Start,
EFFECT, VEFX), turntable analogs, and button lights

## Testing

* [x] Open spicecfg, go to Buttons tab, verify Presets section loads
* [x] Verify PHOENIXWAN builtin preset appears for Beatmania IIDX
* [x] Save a new preset: confirm "Assign Labels" dialog appears with
device descriptions as defaults
* [x] Apply a preset: confirm source column shows labels, popup does not
shrink
* [x] Edit labels on a saved preset: confirm rename propagates to all
bindings
* [x] Hover over a device source tooltip: confirm vKey shows as number
2026-03-20 16:46:17 -07:00
skogaby 458a1495c8 Fix stale frame returned by capture API on rapid successive calls (#583)
Fix stale frame returned by capture API on rapid successive calls

> [!NOTE]
> Before submitting code changes... 
> * Please do note that this is a GPL v3.0 open source project.
> * Please read the
[CONTRIBUTING](https://github.com/spice2x/spice2x.github.io/blob/main/CONTRIBUTING.md)
guide.
> * Maintainers reserve the right to reject or modify your submission
without reason.
> * No new compiler warnings must be introduced. Check the CI build
results.
> 
> Feel free to remove this section after you have read it.

## Link to GitHub Issue or related Pull Request, if one exists
https://github.com/spice2x/spice2x.github.io/issues/582

## Description of change
When using the SpiceAPI `capture` endpoint for taking screenshots, if
you call multiple times in a row in a short period, there's a good
chance for duplicate / stale data to be returned, and you end up with
multiple identical screenshots. This is due to a race condition in the
graphics capture pipeline because the capture data is never reset to
null between invocations.

`graphics_capture_receive_jpeg()` never resets the capture buffer's data
pointer to `nullptr` after consuming it. The condition variable wait
predicate checks `data != nullptr`, so subsequent calls find the
predicate already satisfied and return immediately with the previous
frame instead of waiting for the render thread to produce a fresh one.

Fix: null out `capture.data` after copying the shared_ptr (which keeps
the pixel data alive via refcount) but before releasing the lock.

## Testing
Tested this locally and invoked the capture API 10+ times in a row
without any duplicates. Previously, every other invocation would be a
duplicate, basically.
2026-03-20 14:43:20 -07:00
bicarus eb029faf2e Update CONTRIBUTING.md 2026-03-19 02:59:55 -07:00
bicarus d65eb6ed4f Update CONTRIBUTING.md 2026-03-19 02:59:23 -07:00
bicarus 97ec2e5d37 overlay: minor UI fixes for clear all / reset all (#580)
## Link to GitHub Issue or related Pull Request, if one exists
#578 

## Description of change
Use `Clear All` when there are no defaults, and `Reset All` when there
are defaults.

Clearing analogs should reset values to 0.5, not 0.
2026-03-18 02:51:02 -07:00
bicarus 97d65b137a overlay: "reset all" buttons for buttons/overlay/analog tabs (#578)
## Link to GitHub Issue or related Pull Request, if one exists
#576 

## Description of change
Add `Reset All` buttons to these tabs which clears out bindings to none
/ default.

For keypads, introduce a special `Use Preset` button which lets users
pick between numpad and top row number keys.

Update UI-visible string used for vKeys, especially the numpad ones
(e.g., `.` is now `Numpad .` to distinguish from the regular `.`
period).

Breaking change: if the user never had `P1 Keypad 00` set, the default
will change from `Enter` to `Numpad -` since both enter keys trigger the
same. This will affect a small number of popn/ddr players. They can just
change it back.

## Testing
*how was the code tested?*
2026-03-17 18:47:54 -07:00
bicarus a9f0e86aa3 overlay: categorize lights for different cab types (#576)
## Link to GitHub Issue or related Pull Request, if one exists
#574 

## Description of change
Refactor so that grouping of lights are in common I/O code. Add this to
IIDX/SDVX/DDR/GitaDora.

## Testing
2026-03-17 02:14:48 -07:00
bicarus 88679b7cb2 sdvx, troubleshooter: detect layer missing error (#577)
Detect `W:BM2D: CreateLayer() 指定したレイヤーは存在しません` and add to deferred log.
2026-03-16 11:22:08 -07:00
bicarus 9ec62a61ac overlay: small tweaks to auto light matching (#575)
## Link to GitHub Issue or related Pull Request, if one exists
#574 

## Description of change

* Add binds for Nostroller
* Update matching logic to ignore common device-side output names like
"Button" "Light" and "LED" (e.g., `Button 1` from a device now matches
on `P1 1`)
* For RGB matching, in addition to `Light R` on the device also try
`LightR` (Nostroller needs this)
* Add a confirmation dialog for `Clear All`
* Address minor bugs

## Testing
Seems to work on arcin, and Nostroller. Still need to retest Faucetwo.
Hopefully I didn't break Phoenixwan.
2026-03-15 19:04:36 -07:00
Horo 44befb7e9a cfg: auto light binding, sdvx light formatting, all light test/clear binds (#574)
<img width="784" height="561" alt="image"
src="https://github.com/user-attachments/assets/3ac6bf6d-2ca8-40e5-80ea-b0fd9e080d7b"
/>

## Link to GitHub Issue or related Pull Request, if one exists

n/a

## Description of change

I've always been annoyed with how long it takes to bind controller
lights, made a few improvements

1) added a button+window for automatically matching device lights to
game lights with the same name
2) added button(s) to cycle through all bound lights to visually confirm
location/function
3) added button to clear all bound lights
4) split SDVX lights into sections (Buttons, Valkyrie, Nemsys, Other)
with formatting, sorted based on current game spec

## Testing
Tested with multiple games and controllers (Faucetwo, custom con) and
matching always works if the descriptor strings are labeled correctly.

Not every controller labels their LEDs the same was as spice, but for
those that do this saves a ton of time clicking every box and testing
one by one.

The SDVX table split/formatting is important because myself (and several
friends) have tried binding to Wing/Controller lights and wondered why
they weren't working in game, when they are legacy Nemsys lights instead
of the strip lights handled by Valk/bi2x. I separated the sections
visually, with tooltips, plus sort the two sections based on active spec
to further minimize the chance of someone trying to use the wrong lights
for their game.
2026-03-15 02:19:24 -07:00
bicarus be388f7b49 otoca: add cam hook, fix printer rotation (#573)
## Link to GitHub Issue or related Pull Request, if one exists
related to #154 though this PR doesn't fix it

## Description of change

1. Add an option for cam hook so that the game can be launched without
any camera
1. Rotate image printed out by print-to-file so that it's right side up
(portrait orientation)

## Testing
Able to boot the game without any cameras, and tested printing in test
menu.

"Holo" printing remains broken.
2026-03-09 02:54:04 -07:00
bicarus 34ef034345 overlay: automatically hide mouse cursor when idle (#571)
## Link to GitHub Issue or related Pull Request, if one exists
n/a

## Description of change
Hide the ImGui-rendered cursor in the overlay when the mouse is idle for
more than 2 seconds.

## Testing
Tested windowed and fullscreen.
2026-03-08 20:06:18 -07:00
bicarus ba4623b009 patchmanager: various UI fixes (#572)
## Description of change
* Show more descriptive error when connection fails (for
`ERROR_WINHTTP_NAME_NOT_RESOLVED`)
* ensure patches are reloaded even if importing fails, since existing
checkboxes get reset
2026-03-08 19:43:15 -07:00
Will a0a7b86508 ddr p3io: fix neon pulse mapping (#566)
Logic was wrong; caught by compiling with a newer clang and it raising a
warning about the useless expression (since the second clause always
eval'd as true).

Changed to the intended functionality, but I have no ddr3 with p3io so I
can't test it. But it seems nobody ever really noticed this.
2026-03-06 19:05:44 -08:00
Will 6d7927927c Final changes for clang (#570)
- Allow overriding the toolchains so I can avoid changes in my working
tree
- Squash warnings that the deps use, can't just add them as PRIVATE
compile flags because it's in the headers :(
- Squash those d3d9 warnings with a suppression instead of trying to
rewrite 400 lines of code
2026-03-06 15:18:38 -08:00
Will 837c8a46d6 add some more _WIN32_WINNT defines to hook Win vista+ APIs when compiler is targeting XP (#568)
There's already a bunch of these in the codebase, this just catches a
few locations where they were missed when the compiler is targeting XP.
2026-03-06 14:21:55 -08:00
Will 22aeb64ff9 fix various minor issues causing compilation failure with clang (#567)
- Include order and forward decls were breaking button.h because a
declared but not defined struct can't be initiated (honestly this file
was cooked, crazy include order).
- `MAXINT` is GCC specific, `INT_MAX` is portable.
- InterlockedDecrement takes a LONG, not ULONG
- an imgui printf-style call using a direct string instead of %s (let's
ignore the fact that it's actually safe based on how the str is
constructed)
- missing `override` on a virtual subclass
- `CALLBACK` on a lambda threw me for a loop, but I believe moving it
after the arg list is the correct approach (see if it builds on gcc I
guess)
- `std::result_of` was removed in C++20, which the project is built with
2026-03-06 14:21:10 -08:00
bicarus e4b08064b7 rawinput: fix incorrect usage of HidP_GetUsages when descriptor is not range (#565)
## Link to GitHub Issue or related Pull Request, if one exists
Fixes #563 

## Description of change
When buttons are presented as NotRange and instead an array of single
buttons, we are calling `HidP_GetUsages` expecting at most one result
back, but in reality we need to expect all possible buttons on that
usage page since `HidP_GetUsages` returns all buttons in the usage page
+ link collection which would be the entire array of buttons - see
documentation for `HidP_GetUsages` on MSDN.

## Testing
Tested with my own controller modifying USB descriptors... more
controller testing is needed.
2026-03-05 20:41:47 -08:00
bicarus-dev 1e6b4c16cc asio bit difference message 2026-03-04 03:59:03 -08:00
bicarus-dev d6eb8f7125 fix duplicated text 2026-03-04 02:50:02 -08:00
bicarus dc6850e479 overlay: helper modal dialog for picking option values (#561)
## Link to GitHub Issue or related Pull Request, if one exists

## Description of change
Introduce a new modal dialog for picking out some of the options to
improve UX.

Implemented a few for now:

1. UI for picking ASIO drivers from a list (for -iidxasio, -sdvxasio,
-asioconvert, etc)
2. Button to generate card numbers for -card0 and card1
3. Processor Affinity selector (very similar to what's in Windows Task
Manager, list of checkboxes)
4. file selector (file open dialog for various file path overrides like
log file)
5. folder selector (folder override)

I wrote a selector for COM ports as well but how we use the value was
inconsistent (\\\\.\\COMx vs. COMx) and didn't see a huge point in it so
I didn't check it in.

## Testing
2026-03-02 12:08:20 -08:00
bicarus 2d623e179b audio: simplify ASIO backend conversion (#560)
## Link to GitHub Issue or related Pull Request, if one exists

## Description of change
Deprecate backend conversion options and replace with one unified option
for enabling WASAPI exclusive conversion to ASIO.

The new option takes in a string (ASIO driver name) and when filled out,
it automatically enables the ASIO backend.

This is to avoid confusion that was caused by the previous option - the
fact that it was split into two (one to enable the conversion, another
to optionally pick which ASIO driver to use) and that user needed to
specify an integer as opposed to the driver name.

## Testing
Tested on IIDX33. Old options should continue to work.
2026-03-01 13:06:18 -08:00
bicarus 884d665c1c sdvx, touch: account for 270 degree monitor rotation in wintouchemu (#559)
## Link to GitHub Issue or related Pull Request, if one exists
#558 

## Description of change
Same as #558 but this PR fixes the non-native touch cases (when
wintouchemu is active).

## Testing
Tested 90 and 270 degree rotation, 1080p and 1440p.
2026-02-27 17:17:00 -08:00
bicarus 2842fe7ec6 Update pull_request_template.md 2026-02-27 16:56:03 -08:00
bicarus 954e6022d9 sdvx, touch: hook Windows touch API when main monitor is rotated 270 degrees (portrait flipped) (#558)
## Link to GitHub Issue, if one exists
n/a

## Description of change
Game expects primary monitor to be in 90 deg rotation, and does the
touch calculation accordingly.

If the user does 270 deg rotation ("portrait, flipped") then the game's
touch calculation is flipped upside down. Detect this and provide the
fixed up values in the touch hook.

## Testing
Tested with SDVX in 90 deg, 270 deg rotation, and 1080p, forced 1440p.
2026-02-27 14:22:56 -08:00
Emma 955c50a9f3 api: fix for setting drs touch state (#557)
## Link to GitHub Issue, if one exists
N/A

## Description of change
Changes the array size used for collecting touch inputs from the API to
be dynamic according to the size of the input data, rather than fixed.
This fixes a crash.

## Testing
An API client was made sending touch states, both valid and invalid
(oversized). The valid states are handled identically in the test menu
while the oversized states no longer crash.
2026-02-26 10:47:08 -08:00
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
bicarus 8de7e5a9e4 sdvx: prevent the game from trying a random ASIO driver (#488)
## Link to GitHub Issue, if one exists
n/a

## Description of change
SDVX EG final started to pick an ASIO driver at random and tries to open
it, which causes crashes / hangs if the driver is not compatible
(ASIO4ALL in particular)

Only allow the game to access `HKLM\SOFTWARE\ASIO` if the user has a
Xonar AE, or if ASIO was configured using -sdvxasio.

## Testing
Tested year 1 data and EG final.
2025-12-30 17:58:28 -08:00
bicarus b8e99e75e5 gitadora: (arena model) prevent the small subscreen from being closed (#487)
## Link to GitHub Issue, if one exists
#477 

## Description of change
Ensure the small subscreen can't be resized or closed by accident.
2025-12-30 14:30:59 -08:00
bicarus 46dc22dd63 gitadora: (arena model) headphones i/o (#485)
## Link to GitHub Issue, if one exists
#477 

## Description of change
Plumb through headphone jack detection. Touch screen menu for headphone
volume lights up.

## Testing
Tested guitar.
2025-12-30 14:30:33 -08:00
oleg238948234 69d78e0a87 gitadora: (arena model) fix j32d encoding & use velocity (#483)
## Link to GitHub Issue, if one exists
N/A

## Description of change
in current implementation j32d analog values are not encoded properly,
values are supposed to go up to 1023, but they show up as 771 in game

## Testing
tested with M32-007-2025092400, used DM PAD CALIBRATION menu
2025-12-30 00:29:43 -08:00
oleg238948234 ab3d41432a gitadora: (arena model) F: drive fix (#484)
## Link to GitHub Issue, if one exists
N/A

## Description of change
prevents the game from creating folders on F: drive

## Testing
tested with M32-007-2025092400
2025-12-30 00:26:47 -08:00
bicarus 73e10cac0c gitadora: (arena model) text encoding hook (#482)
## Link to GitHub Issue, if one exists
#477 

## Description of change
Fix Japanese text broken in the game UI when system ACP is not
SHIFT-JIS.

## Testing
Checked song select and in-game title display.
2025-12-29 23:27:15 -08:00
bicarus c6242233a7 gitadora: (arena model) implement wailing, improve picking (#481)
## Link to GitHub Issue, if one exists
#477 

## Description of change
Add analog and digital wail for the arena model guitar.

Fix the picking code to be the same algorithm as extdev - detect rising
edges and insert 1 frame of input.

## Testing
Tested x/y/z and digital y up/down. Only Y gets used in game though.
2025-12-29 23:26:44 -08:00
bicarus 6b8335780f ddr: fix up init code when using old I/O with gold cab mode (#480)
## Link to GitHub Issue, if one exists
Fixes #479 

## Description of change
When p3io / p4io is in use with Gold Cab spec, use the old init code.

## Testing
Verified p3io / p4io / bio2 booting with 2024040200.
2025-12-29 16:26:30 -08:00
bicarus-dev 21f469b260 gitadora: arena cab volume change prevention (#478)
## Link to GitHub Issue, if one exists
#477 

## Description of change
Arena cab has two sound cards and it uses `WrappedIMMDeviceCollection`
to set the volume to 100% on boot on both, instead of the typical
`GetDefaultAudioEndpoint`.

## Testing
Testing Delta, testing other games as well as they are affected.
2025-12-29 03:23:58 -08:00
zxcwdta 07a0df5fdc gitadora: Added support for GITADORA Delta (#476)
## Link to GitHub Issue, if one exists
None

## Description of change
- Added bi2x_hook
- Added J32D (drum) and J33I (guitar) emulations
- AMI2000 card reader (this game uses this instead of ICCA)

## Testing
Played through a 15-minute session on Drummania.
Guitar mode works with keyboard input.
2025-12-28 16:17:06 -08:00
zxcwdta d4f1768bbd avs: Increased heap size for later gitadora games (#475)
## Link to GitHub Issue, if one exists
None

## Description of change
Increase the heap size for GITADORA games.

Later releases of GITADORA attempt to allocate a block of memory (size
of 0x7ECF78) for notes_info.xml but fail due to heap size being too
small, leading to HDD ERROR 5-1502-0008.

## Testing
Tested with current and previous GITADORA releases.
2025-12-26 23:00:57 -08:00
bicarus-dev 96e7e4949f iidx, linux: cache lstat result for movie thumbnails (#474)
## Link to GitHub Issue, if one exists
https://codeberg.org/nixac/spicetools/issues/4

## Description of change
Inspired by the Linux issue above in the fork, cache lstat calls for
thumbnail. IIDX 32 and 33 make a call for this every frame which can
cause significant overhead on Linux.

This is being enabled for all versions and not just the Linux variant of
the executables since it provides some marginal performance gain on
Windows.

## Testing
Tested IIDX 32/33, with the option on/off, and with AVS verbose logging
on/off.

Tested with ifs_layeredfs hook as well just to verify functionality.
2025-12-26 20:17:20 -08:00
bicarus-dev 24d3062c66 update superstep audio error troubleshooter message 2025-12-24 21:10:26 -08:00
bicarus-dev 6cd212aac2 iidx,sdvx: disable subscreen overlay if -monitor is in use (#471)
## Link to GitHub Issue, if one exists
#345 

## Description of change
If TDJ / VM mode is active AND user is enabled `-monitor` option (and
the value isn't 0, the primary monitor)

then disable the subscreen overlay, show an error message instead.

We're doing this because both games cannot accept emulated touch input
if `-monitor` option is in use. There is not much point in displaying
the second screen's image, other than to REALLY confuse people when
nothing happens when they click on it.

## Testing
Tested both games with `-monitor`.
2025-12-24 17:35:15 -08:00
bicarus-dev 064031bc12 Update CONTRIBUTING.md 2025-12-22 19:20:47 -08:00
bicarus-dev 4c8d4ebab6 iidx: detect ldj/tdj I/O mismatch (#470)
## Link to GitHub Issue, if one exists
#345 

## Description of change
For IIDX31+, detect if BI2A or BI2X I/O emulation becomes active, and
see if there is a mismatch between the -iidxtdj setting.

Right now a mismatch results in warning in log + deferred error log, but
in the future we could turn this into a fatal error and crash the game
to make it more obvious.

## Testing
Tested 4 combinations:

1. DLL in LDJ mode + -iidxtdj off (pass)
2. DLL in LDJ mode + -iidxtdj on (error)
3. DLL in TDJ mode + -iidxtdj off (error)
4. DLL in TDJ mode + -iidxtdj on (pass)
2025-12-22 18:52:37 -08:00
bicarus-dev 462a07680b Update CONTRIBUTING.md 2025-12-22 18:51:15 -08:00
bicarus-dev 081def30db overlay: clarify subscreen types (#469)
## Link to GitHub Issue, if one exists
#345 

## Description of change
In the main menu (Esc key), instead of just saying `Show subscreen`,
detect game and say `Show TDJ Subscreen` `Show LDJ LED Ticker` etc.

For the LED ticker window, show the title bar by default, and add an
option to hide it back.

This is to help with cases where people get stuck with being in LDJ mode
when they meant to turn on TDJ.

## Testing
Tested all configurations (IIDX LDJ/TDJ, SDVX UFC, DRS)
2025-12-22 15:59:31 -08:00
cchike 6776b08bce MDX: Refactored p4io.cpp (#467)
## Link to GitHub Issue, if one exists
#452 

## Description of change
Added `Sleep(4)` call to regulate how fast the `device_io` function will
call `getState` for each button. The panel buttons were commented out
because those are handled by the MDXF Control Status Buffer functions
anyways, so the work was redundant. It's also why the timing precision
from this function doesn't matter, so it can afford a longer sleep

## Testing
- Verified IO still functions properly
- Verified decrease in overall CPU usage
2025-12-21 20:49:39 -08:00
bicarus-dev 6cb507f4fa Update CONTRIBUTING.md 2025-12-21 18:57:42 -08:00
bicarus-dev de05af052f build: turn warnings into errors for gcc (#466)
for CI
2025-12-21 03:52:30 -08:00
164 changed files with 12066 additions and 3362 deletions
+13 -3
View File
@@ -1,8 +1,7 @@
## Contributing
Baseline rules for patch submissions are as follows. Any patches violating the rules below will not be accepted.
### New as of March 2025
Pull requests in GitHub are open. You don't need to bother with patch file submissions anymore. To contribute, fork the repo (just the main branch), make changes in your fork, and contribute to upstream by opening a pull request to the main repo.
To contribute, fork the repo (just the main branch), make changes in your fork, and contribute to upstream by opening a pull request to the main repo.
### Adding support for games
@@ -12,6 +11,14 @@ Pull requests in GitHub are open. You don't need to bother with patch file submi
* For a new game that was never supported (not a new version of a game, but rather a new series): please wait 1 year after official AC release in Japan.
* For new version of an already supported game: proceed with caution and use generally-accepted community guidelines.
### Forbidden "features" that will never be accepted
* Performing decryption of encrypted files
* Including (bundling), distributing, or linking to game data, or making it easier to find/download game data
* Any features that [phone home](https://en.wikipedia.org/wiki/Phoning_home)
* This includes automatic updaters.
* Exception: we have allowed the "patch import from URL" feature, but this has only been done under very careful considerations and plenty of warnings to the user.
### Avoiding regressions
The biggest risk in making code changes to spice is the risk of regressions. There is a **lot** of shared code used by many different games, reaching back game versions that are more than a decade old. It is practically impossible to test all supported games and versions.
@@ -22,7 +29,7 @@ Therefore, when making code changes, please be extremely careful about containin
#### Config file compatibility
Do not change the names of options, buttons binds, analogs, etc - since they are used as unique identifiers in config files. If they are changed, you will introduce an incompatibility with previous versions of config file.
Do not change the names of buttons binds, analogs, strings for command line parameters, etc - since they are used as unique identifiers in config files. If they are changed, you will introduce an incompatibility with previous versions of config file.
### Code quality requirements
@@ -49,6 +56,9 @@ Watch out for legacy OS compatibility. Currently, the minimum support floor is W
### Code style requirements
* Indents are four spaces.
* Try to keep under 100 characters per line. This includes comments and string literals.
* Avoid making unnecessary formatting or whitespace changes in a PR; e.g., watch out for text editor automatically stripping trailing whitespace. It pollutes the diff and slows down code review.
* For signed/unsigned integer types, prefer `cstdint` types; e.g., use `int32_t`, `uint64_t` and not `int` and `unsigned long long`.
* Always use \{ curly braces \} when appropriate; do not omit them even when it's optional; such as `for` `if` `else`, etc.
OK:
+1 -1
View File
@@ -7,7 +7,7 @@
>
> Feel free to remove this section after you have read it.
## Link to GitHub Issue, if one exists
## Link to GitHub Issue or related Pull Request, if one exists
#0
## Description of change
+32 -6
View File
@@ -109,6 +109,12 @@ elseif(CMAKE_CXX_COMPILER_ID STREQUAL "Clang")
# RapidJSON does this
add_compile_definitions(_SILENCE_CXX17_ITERATOR_BASE_CLASS_DEPRECATION_WARNING)
add_compile_options("-Wno-nontrivial-memcall") # imgui, rapidjson
add_compile_options("-Wno-deprecated-builtins") # robin_hood
add_compile_options("-Wno-deprecated-declarations") # rapidjson
add_compile_options("-Wno-inconsistent-missing-override") # headsocket
add_compile_options("-Wno-microsoft-exception-spec") # gcc/clang have different noexcept specifiers on d3d9 COM interfaces
# warnings
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wunknown-warning-option")
@@ -118,6 +124,9 @@ elseif(CMAKE_CXX_COMPILER_ID STREQUAL "Clang")
set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -static")
else()
# warnings as errors
set(CMAKE_COMPILE_WARNING_AS_ERROR ON)
# warnings
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wextra") # enable stuff
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-pointer-arith") # but we love pointer arithmetic :)
@@ -167,8 +176,8 @@ else()
set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -DNDEBUG")
# file prefix map for relative working directory
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_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}=.\"")
endif()
# release with debug info flags
@@ -257,6 +266,7 @@ set_source_files_properties(cfg/manifest.rc PROPERTIES LANGUAGE RC)
set_source_files_properties(cfg/icon.rc PROPERTIES LANGUAGE RC)
set_source_files_properties(cfg/Win32D.rc PROPERTIES LANGUAGE RC)
set_source_files_properties(build/manifest64.rc PROPERTIES LANGUAGE RC)
set_source_files_properties(stubs/manifest.rc PROPERTIES LANGUAGE RC)
# sources
#########
@@ -343,6 +353,7 @@ set(SOURCE_FILES ${SOURCE_FILES}
cfg/configurator.cpp
cfg/configurator_wnd.cpp
cfg/screen_resize.cpp
cfg/controller_presets.cpp
# easrv
easrv/easrv.cpp
@@ -398,6 +409,10 @@ set(SOURCE_FILES ${SOURCE_FILES}
games/nost/poke.cpp
games/gitadora/gitadora.cpp
games/gitadora/io.cpp
games/gitadora/handle.cpp
games/gitadora/j32d.cpp
games/gitadora/j33i.cpp
games/gitadora/bi2x_hook.cpp
games/mga/mga.cpp
games/mga/io.cpp
games/mga/gunio.cpp
@@ -434,6 +449,7 @@ set(SOURCE_FILES ${SOURCE_FILES}
games/scotto/io.cpp
games/drs/drs.cpp
games/drs/io.cpp
games/drs/rgb_cam.cpp
games/we/we.cpp
games/we/io.cpp
games/we/touchpanel.cpp
@@ -470,10 +486,12 @@ set(SOURCE_FILES ${SOURCE_FILES}
hooks/audio/acm.cpp
hooks/audio/audio.cpp
hooks/audio/buffer.cpp
hooks/audio/mme.cpp
hooks/audio/util.cpp
hooks/audio/backends/dsound/dsound_backend.cpp
hooks/audio/backends/mmdevice/audio_endpoint_volume.cpp
hooks/audio/backends/mmdevice/device.cpp
hooks/audio/backends/mmdevice/device_collection.cpp
hooks/audio/backends/mmdevice/device_enumerator.cpp
hooks/audio/backends/wasapi/audio_client.cpp
hooks/audio/backends/wasapi/audio_render_client.cpp
@@ -530,6 +548,7 @@ set(SOURCE_FILES ${SOURCE_FILES}
misc/sciunit.cpp
misc/sde.cpp
misc/wintouchemu.cpp
misc/ami2000.cpp
# nvapi
nvapi/nvapi.cpp
@@ -544,9 +563,11 @@ set(SOURCE_FILES ${SOURCE_FILES}
overlay/windows/camera_control.cpp
overlay/windows/card_manager.cpp
overlay/windows/drs_dancefloor.cpp
overlay/windows/gfdm_sub.cpp
overlay/windows/screen_resize.cpp
overlay/windows/sdvx_sub.cpp
overlay/windows/config.cpp
overlay/windows/controller_presets.cpp
overlay/windows/control.cpp
overlay/windows/eadev.cpp
overlay/windows/fps.cpp
@@ -614,6 +635,7 @@ set(SOURCE_FILES ${SOURCE_FILES}
util/execexe.cpp
util/dependencies.cpp
util/deferlog.cpp
util/socd_cleaner.cpp
)
source_group(TREE ${CMAKE_CURRENT_SOURCE_DIR} PREFIX "Source Files" FILES ${SOURCE_FILES})
@@ -792,7 +814,8 @@ endif()
# nvcuda.dll
set(SOURCE_FILES stubs/nvcuda.cpp)
add_library(spicetools_stubs_nvcuda SHARED ${SOURCE_FILES} stubs/nvcuda.def)
set(RESOURCE_FILES stubs/manifest.rc)
add_library(spicetools_stubs_nvcuda SHARED ${SOURCE_FILES} ${RESOURCE_FILES} stubs/nvcuda.def)
set_target_properties(spicetools_stubs_nvcuda PROPERTIES PREFIX "")
set_target_properties(spicetools_stubs_nvcuda PROPERTIES OUTPUT_NAME "nvcuda")
@@ -802,7 +825,8 @@ endif()
# nvcuvid.dll
set(SOURCE_FILES stubs/nvcuvid.cpp)
add_library(spicetools_stubs_nvcuvid SHARED ${SOURCE_FILES} stubs/nvcuvid.def)
set(RESOURCE_FILES stubs/manifest.rc)
add_library(spicetools_stubs_nvcuvid SHARED ${SOURCE_FILES} ${RESOURCE_FILES} stubs/nvcuvid.def)
set_target_properties(spicetools_stubs_nvcuvid PROPERTIES PREFIX "")
set_target_properties(spicetools_stubs_nvcuvid PROPERTIES OUTPUT_NAME "nvcuvid")
@@ -812,7 +836,8 @@ endif()
# nvEncodeAPI64.dll
set(SOURCE_FILES stubs/nvEncodeAPI64.cpp)
add_library(spicetools_stubs_nvEncodeAPI64 SHARED ${SOURCE_FILES} stubs/nvEncodeAPI64.def)
set(RESOURCE_FILES stubs/manifest.rc)
add_library(spicetools_stubs_nvEncodeAPI64 SHARED ${SOURCE_FILES} ${RESOURCE_FILES} stubs/nvEncodeAPI64.def)
set_target_properties(spicetools_stubs_nvEncodeAPI64 PROPERTIES PREFIX "")
set_target_properties(spicetools_stubs_nvEncodeAPI64 PROPERTIES OUTPUT_NAME "nvEncodeAPI64")
@@ -822,7 +847,8 @@ endif()
# cpusbxpkm.dll (32 bit)
set(SOURCE_FILES stubs/cpusbxpkm.cpp)
add_library(spicetools_stubs_cpusbxpkm SHARED ${SOURCE_FILES} stubs/cpusbxpkm.def)
set(RESOURCE_FILES stubs/manifest.rc)
add_library(spicetools_stubs_cpusbxpkm SHARED ${SOURCE_FILES} ${RESOURCE_FILES} stubs/cpusbxpkm.def)
set_target_properties(spicetools_stubs_cpusbxpkm PROPERTIES PREFIX "")
set_target_properties(spicetools_stubs_cpusbxpkm PROPERTIES OUTPUT_NAME "cpusbxpkm")
+30 -6
View File
@@ -9,11 +9,22 @@
#include "games/drs/drs.h"
#include "misc/eamuse.h"
#include "util/logging.h"
#include "util/socd_cleaner.h"
#include "util/time.h"
#include "util/utils.h"
#include "util/tapeled.h"
using namespace GameAPI;
#define DEBUG_VERBOSE 0
#if DEBUG_VERBOSE
#define log_debug(module, format_str, ...) logger::push( \
LOG_FORMAT("M", module, format_str, ## __VA_ARGS__), logger::Style::GREY)
#else
#define log_debug(module, format_str, ...)
#endif
// state
static uint8_t STATUS_BUFFER[272] {};
static bool STATUS_BUFFER_FREEZE = false;
@@ -94,18 +105,25 @@ static bool __cdecl ac_io_bi2a_update_control_status_buffer() {
}
// volume left
if (Buttons::getState(RI_MGR, buttons.at(games::sdvx::Buttons::VOL_L_Left))) {
const auto now = get_performance_milliseconds();
const auto vol_l_state = socd::socd_clean(0,
Buttons::getState(RI_MGR, buttons.at(games::sdvx::Buttons::VOL_L_Left)),
Buttons::getState(RI_MGR, buttons.at(games::sdvx::Buttons::VOL_L_Right)),
now);
if (vol_l_state == socd::SocdCCW) {
BI2A_VOLL = (BI2A_VOLL - games::sdvx::DIGITAL_KNOB_SENS) & 1023;
}
if (Buttons::getState(RI_MGR, buttons.at(games::sdvx::Buttons::VOL_L_Right))) {
} else if (vol_l_state == socd::SocdCW) {
BI2A_VOLL = (BI2A_VOLL + games::sdvx::DIGITAL_KNOB_SENS) & 1023;
}
// volume right
if (Buttons::getState(RI_MGR, buttons.at(games::sdvx::Buttons::VOL_R_Left))) {
const auto vol_r_state = socd::socd_clean(1,
Buttons::getState(RI_MGR, buttons.at(games::sdvx::Buttons::VOL_R_Left)),
Buttons::getState(RI_MGR, buttons.at(games::sdvx::Buttons::VOL_R_Right)),
now);
if (vol_r_state == socd::SocdCCW) {
BI2A_VOLR = (BI2A_VOLR - games::sdvx::DIGITAL_KNOB_SENS) & 1023;
}
if (Buttons::getState(RI_MGR, buttons.at(games::sdvx::Buttons::VOL_R_Right))) {
} else if (vol_r_state == socd::SocdCW) {
BI2A_VOLR = (BI2A_VOLR + games::sdvx::DIGITAL_KNOB_SENS) & 1023;
}
@@ -127,6 +145,12 @@ static bool __cdecl ac_io_bi2a_update_control_status_buffer() {
// save volumes in buffer
*((uint16_t*) &STATUS_BUFFER[17]) = (uint16_t) ((vol_left) << 2);
*((uint16_t*) &STATUS_BUFFER[19]) = (uint16_t) ((vol_right) << 2);
log_debug(
"bi2a",
"knobs = {} {}",
*((uint16_t*) &STATUS_BUFFER[17]),
*((uint16_t*) &STATUS_BUFFER[19]));
}
// DanceDanceRevolution
+5 -5
View File
@@ -52,7 +52,7 @@ static bool __cdecl ac_io_j32d_update_control_status_buffer(size_t a1) {
float pad3_vel = Buttons::getVelocity(RI_MGR, buttons.at(games::ftt::Buttons::Pad3));
float pad4_vel = Buttons::getVelocity(RI_MGR, buttons.at(games::ftt::Buttons::Pad4));
// FIXME(felix): this logic seems wrong for analog handling but correct for digital inputs
if (pad1_vel > 0.f) {
if (Buttons::getState(RI_MGR, buttons.at(games::ftt::Buttons::Pad1))) {
STATUS_BUFFER[6] = (int) (51.f * pad1_vel + 0.5f);
}
if (Buttons::getState(RI_MGR, buttons.at(games::ftt::Buttons::Pad2))) {
@@ -67,10 +67,10 @@ static bool __cdecl ac_io_j32d_update_control_status_buffer(size_t a1) {
// process analogs
auto &analogs = games::ftt::get_analogs();
auto pad1_analog = analogs.at(games::ftt::Analogs::Pad1);
auto pad2_analog = analogs.at(games::ftt::Analogs::Pad2);
auto pad3_analog = analogs.at(games::ftt::Analogs::Pad3);
auto pad4_analog = analogs.at(games::ftt::Analogs::Pad4);
auto &pad1_analog = analogs.at(games::ftt::Analogs::Pad1);
auto &pad2_analog = analogs.at(games::ftt::Analogs::Pad2);
auto &pad3_analog = analogs.at(games::ftt::Analogs::Pad3);
auto &pad4_analog = analogs.at(games::ftt::Analogs::Pad4);
if (pad1_analog.isSet()) {
auto val = (uint32_t) (51.f * Analogs::getState(RI_MGR, pad1_analog) + 0.5f);
STATUS_BUFFER[6] = MAX(STATUS_BUFFER[6], val);
+26 -6
View File
@@ -8,10 +8,21 @@
#include "games/sdvx/io.h"
#include "misc/eamuse.h"
#include "rawinput/rawinput.h"
#include "util/socd_cleaner.h"
#include "util/time.h"
#include "util/utils.h"
using namespace GameAPI;
#define DEBUG_VERBOSE 0
#if DEBUG_VERBOSE
#define log_debug(module, format_str, ...) logger::push( \
LOG_FORMAT("M", module, format_str, ## __VA_ARGS__), logger::Style::GREY)
#else
#define log_debug(module, format_str, ...)
#endif
// globals
uint8_t KFCA_VOL_SOUND = 96;
uint8_t KFCA_VOL_HEADPHONE = 96;
@@ -342,18 +353,25 @@ static char __cdecl ac_io_kfca_update_control_status_buffer() {
}
// volume left
if (Buttons::getState(RI_MGR, buttons.at(games::sdvx::Buttons::VOL_L_Left))) {
const auto now = get_performance_milliseconds();
const auto vol_l_state = socd::socd_clean(0,
Buttons::getState(RI_MGR, buttons.at(games::sdvx::Buttons::VOL_L_Left)),
Buttons::getState(RI_MGR, buttons.at(games::sdvx::Buttons::VOL_L_Right)),
now);
if (vol_l_state == socd::SocdCCW) {
KFCA_VOLL = (KFCA_VOLL - games::sdvx::DIGITAL_KNOB_SENS) & 1023;
}
if (Buttons::getState(RI_MGR, buttons.at(games::sdvx::Buttons::VOL_L_Right))) {
} else if (vol_l_state == socd::SocdCW) {
KFCA_VOLL = (KFCA_VOLL + games::sdvx::DIGITAL_KNOB_SENS) & 1023;
}
// volume right
if (Buttons::getState(RI_MGR, buttons.at(games::sdvx::Buttons::VOL_R_Left))) {
const auto vol_r_state = socd::socd_clean(1,
Buttons::getState(RI_MGR, buttons.at(games::sdvx::Buttons::VOL_R_Left)),
Buttons::getState(RI_MGR, buttons.at(games::sdvx::Buttons::VOL_R_Right)),
now);
if (vol_r_state == socd::SocdCCW) {
KFCA_VOLR = (KFCA_VOLR - games::sdvx::DIGITAL_KNOB_SENS) & 1023;
}
if (Buttons::getState(RI_MGR, buttons.at(games::sdvx::Buttons::VOL_R_Right))) {
} else if (vol_r_state == socd::SocdCW) {
KFCA_VOLR = (KFCA_VOLR + games::sdvx::DIGITAL_KNOB_SENS) & 1023;
}
@@ -371,6 +389,8 @@ static char __cdecl ac_io_kfca_update_control_status_buffer() {
// proper loops
vol_left %= 1024;
vol_right %= 1024;
log_debug("kfca", "knobs = {} {}", vol_left, vol_right);
// save volumes in buffer
STATUS_BUFFER[input_offset + 16 + 0] |= (unsigned char) ((vol_left << 6) & 0xFF);
+5 -11
View File
@@ -2,6 +2,7 @@
#include "acio/icca/icca.h"
#include "avs/game.h"
#include "games/sdvx/sdvx.h"
#include "misc/eamuse.h"
#include "util/logging.h"
#include "util/utils.h"
@@ -101,13 +102,10 @@ bool ICCADevice::parse_msg(MessageData *msg_in,
// send version data
auto msg = this->create_msg(msg_in, MSG_VERSION_SIZE);
if (
avs::game::is_model({"LDJ", "TBS", "UJK", "XIF"}) ||
// SDVX Valkyrie cabinet mode
(avs::game::is_model("KFC") && (avs::game::SPEC[0] == 'G' || avs::game::SPEC[0] == 'H'))
) {
if (avs::game::is_model({"LDJ", "TBS", "UJK", "XIF"}) ||
games::sdvx::is_valkyrie_model()) {
this->set_version(msg, 0x3, 0, 1, 7, 0, "ICCA");
} else if (avs::game::is_model({"VFG"})) {
} else if (avs::game::is_model("VFG")) {
this->set_version(msg, 0x3, 0, 1, 7, 0, "ICCB");
} else {
this->set_version(msg, 0x3, 0, 1, 6, 0, "ICCA");
@@ -520,11 +518,7 @@ void ICCADevice::update_status(int unit) {
buffer[1] = felica ? 0x01 : 0x00;
buffer[10] = felica ? 0x01 : 0x00;
} else if (
avs::game::is_model({"LDJ", "TBS", "XIF"}) ||
// SDVX Valkyrie cabinet mode
(avs::game::is_model("KFC") && (avs::game::SPEC[0] == 'G' || avs::game::SPEC[0] == 'H'))) {
} else if (avs::game::is_model({"LDJ", "TBS", "XIF"}) || games::sdvx::is_valkyrie_model()) {
// set status to 0 otherwise reader power on fails
buffer[0] = 0x00;
} else {
+2 -2
View File
@@ -34,7 +34,7 @@ namespace api::modules {
void DRS::touch_set(Request &req, Response &res) {
// get all touch points
games::drs::drs_touch_t touches[16];
auto touches = std::make_unique<games::drs::drs_touch_t[]>(req.params.Size());
size_t i = 0;
for (Value &param : req.params.GetArray()) {
@@ -86,6 +86,6 @@ namespace api::modules {
}
// apply touch points
games::drs::fire_touches(touches, i);
games::drs::fire_touches(touches.get(), i);
}
}
+4
View File
@@ -1108,6 +1108,8 @@ namespace avs {
// SOUND VOLTEX
{"soundvoltex.dll", 0x10000000},
// GITADORA
{"gdxg.dll", 0x2000000},
#endif
// jubeat
{"jubeat.dll", 0x2000000},
@@ -1232,6 +1234,7 @@ namespace avs {
break;
case 'F':
new_style = logger::Style::RED;
deferredlogs::report_fatal_message();
break;
default:
position = 0;
@@ -1300,6 +1303,7 @@ namespace avs {
break;
case 'F':
style = logger::Style::RED;
deferredlogs::report_fatal_message();
break;
case 'W':
style = logger::Style::YELLOW;
+54 -5
View File
@@ -5,8 +5,9 @@
#include "build/defs.h"
#include "easrv/easrv.h"
#include "easrv/smartea.h"
#include "games/mfc/mfc.h"
#include "games/gitadora/gitadora.h"
#include "hooks/avshook.h"
#include "hooks/graphics/graphics.h"
#include "util/detour.h"
#include "util/deferlog.h"
#include "util/fileutils.h"
@@ -216,7 +217,23 @@ namespace avs {
ea3_config_name = "prop/eamuse-config.xml";
}
log_info("avs-ea3", "booting (using {})", ea3_config_name);
if (avs::core::file_exists(ea3_config_name)) {
log_info("avs-ea3", "booting (using {})", ea3_config_name);
} else {
log_warning("avs-ea3", "looked for the following files in order:");
log_warning("avs-ea3", " * prop/ea3-config.xml");
log_warning("avs-ea3", " * prop/ea3-cfg.xml");
if (avs::game::DLL_NAME == "beatstream1.dll") {
log_warning("avs-ea3", " * prop/ea3-config-1.xml");
}
if (avs::game::DLL_NAME == "beatstream2.dll") {
log_warning("avs-ea3", " * prop/ea3-config-2.xml");
}
log_warning("avs-ea3", " * prop/eamuse-config.xml");
log_warning("avs-ea3", "none of the files above were found, game will fail to boot");
log_warning("avs-ea3", "usually, this means your game data is incomplete");
log_fatal("avs-ea3", "no ea3 config file found in prop directory; check log messages");
}
// remember new config path
CFG_PATH = ea3_config_name;
@@ -494,7 +511,9 @@ namespace avs {
std::ostringstream init_code;
init_code << EA3_MODEL;
init_code << EA3_DEST;
if (strcmp(EA3_MODEL, "MDX") == 0 && strcmp(EA3_SPEC, "I") == 0) {
if (avs::game::DLL_NAME == "arkmdxbio2.dll" &&
strcmp(EA3_MODEL, "MDX") == 0 && strcmp(EA3_SPEC, "I") == 0) {
// ddr gold cab
init_code << "G";
init_code << "B";
} else {
@@ -526,12 +545,42 @@ 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");
// call the game init
log_info("avs-ea3", "calling entry init");
log_info("avs-ea3", "calling dll_entry_init (init code = {})", init_code_str);
if (!avs::game::entry_init(init_code_str.data(), app_param)) {
log_fatal("avs-ea3", "entry init failed :(");
for (size_t i = 0; i < 255; i++) {
log_warning("avs-ea3", "dll_entry_init failed :(");
}
deferredlogs::defer_error_messages({
"dll_entry_init failed",
" init code: " + init_code_str,
" this is a fatal error that causes boot failure",
" most likely, your prop files have errors"
});
}
// accommodate changes to soft id code
+327
View File
@@ -0,0 +1,327 @@
<?xml version="1.0" encoding="UTF-8"?>
<templates>
<!-- PHOENIXWAN controller preset for Beatmania IIDX
Button indices 0-6: keys 1-7
Button indices 8-11: E1 (Start for Deluxe Model Style), E2 (Effect), E3 (VEFX), E4 (P1 Start for Lightning Model Style)
P1 Side P2 Side
8 9 10 11 8 9 10 11
2 4 6 2 4 6
TT 1 3 5 7 1 3 5 7 TT -->
<template name="DJ DAO PHOENIXWAN" game="Beatmania IIDX">
<buttons>
<button name="P1 1" vkey="0" analogtype="0" devid="Player 1" invert="false" debounce_up="0" debounce_down="0" velocity_threshold="0"/>
<button name="P1 2" vkey="1" analogtype="0" devid="Player 1" invert="false" debounce_up="0" debounce_down="0" velocity_threshold="0"/>
<button name="P1 3" vkey="2" analogtype="0" devid="Player 1" invert="false" debounce_up="0" debounce_down="0" velocity_threshold="0"/>
<button name="P1 4" vkey="3" analogtype="0" devid="Player 1" invert="false" debounce_up="0" debounce_down="0" velocity_threshold="0"/>
<button name="P1 5" vkey="4" analogtype="0" devid="Player 1" invert="false" debounce_up="0" debounce_down="0" velocity_threshold="0"/>
<button name="P1 6" vkey="5" analogtype="0" devid="Player 1" invert="false" debounce_up="0" debounce_down="0" velocity_threshold="0"/>
<button name="P1 7" vkey="6" analogtype="0" devid="Player 1" invert="false" debounce_up="0" debounce_down="0" velocity_threshold="0"/>
<button name="P2 1" vkey="0" analogtype="0" devid="Player 2" invert="false" debounce_up="0" debounce_down="0" velocity_threshold="0"/>
<button name="P2 2" vkey="1" analogtype="0" devid="Player 2" invert="false" debounce_up="0" debounce_down="0" velocity_threshold="0"/>
<button name="P2 3" vkey="2" analogtype="0" devid="Player 2" invert="false" debounce_up="0" debounce_down="0" velocity_threshold="0"/>
<button name="P2 4" vkey="3" analogtype="0" devid="Player 2" invert="false" debounce_up="0" debounce_down="0" velocity_threshold="0"/>
<button name="P2 5" vkey="4" analogtype="0" devid="Player 2" invert="false" debounce_up="0" debounce_down="0" velocity_threshold="0"/>
<button name="P2 6" vkey="5" analogtype="0" devid="Player 2" invert="false" debounce_up="0" debounce_down="0" velocity_threshold="0"/>
<button name="P2 7" vkey="6" analogtype="0" devid="Player 2" invert="false" debounce_up="0" debounce_down="0" velocity_threshold="0"/>
<!-- P1 Start: E1 primary (index 8), E4 alternative (index 11) -->
<button name="P1 Start" vkey="8" analogtype="0" devid="Player 1" invert="false" debounce_up="0" debounce_down="0" velocity_threshold="0"/>
<button name="P1 Start" vkey="11" analogtype="0" devid="Player 1" invert="false" debounce_up="0" debounce_down="0" velocity_threshold="0"/>
<!-- P2 Start: E1 primary (index 8), E4 alternative (index 11) -->
<button name="P2 Start" vkey="8" analogtype="0" devid="Player 2" invert="false" debounce_up="0" debounce_down="0" velocity_threshold="0"/>
<button name="P2 Start" vkey="11" analogtype="0" devid="Player 2" invert="false" debounce_up="0" debounce_down="0" velocity_threshold="0"/>
<!-- EFFECT: E2 (index 9), primary P2 side, alternative P1 side -->
<button name="EFFECT" vkey="9" analogtype="0" devid="Player 2" invert="false" debounce_up="0" debounce_down="0" velocity_threshold="0"/>
<button name="EFFECT" vkey="9" analogtype="0" devid="Player 1" invert="false" debounce_up="0" debounce_down="0" velocity_threshold="0"/>
<!-- VEFX: E3 (index 10), primary P2 side, alternative P1 side -->
<button name="VEFX" vkey="10" analogtype="0" devid="Player 2" invert="false" debounce_up="0" debounce_down="0" velocity_threshold="0"/>
<button name="VEFX" vkey="10" analogtype="0" devid="Player 1" invert="false" debounce_up="0" debounce_down="0" velocity_threshold="0"/>
</buttons>
<analogs>
<analog name="Turntable P1" devid="Player 1" index="4" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1" relative="false" delay="0"/>
<analog name="Turntable P2" devid="Player 2" index="4" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1" relative="false" delay="0"/>
</analogs>
<lights>
<light name="P1 1" devid="Player 1" index="0"/>
<light name="P1 2" devid="Player 1" index="1"/>
<light name="P1 3" devid="Player 1" index="2"/>
<light name="P1 4" devid="Player 1" index="3"/>
<light name="P1 5" devid="Player 1" index="4"/>
<light name="P1 6" devid="Player 1" index="5"/>
<light name="P1 7" devid="Player 1" index="6"/>
<light name="P2 1" devid="Player 2" index="0"/>
<light name="P2 2" devid="Player 2" index="1"/>
<light name="P2 3" devid="Player 2" index="2"/>
<light name="P2 4" devid="Player 2" index="3"/>
<light name="P2 5" devid="Player 2" index="4"/>
<light name="P2 6" devid="Player 2" index="5"/>
<light name="P2 7" devid="Player 2" index="6"/>
<!-- Start buttons bind both E1 (index 8) and E4 (index 11), but lights use
Lightning Model style only (one per side) to avoid both lighting up at once -->
<light name="P1 Start" devid="Player 1" index="10"/>
<light name="P2 Start" devid="Player 2" index="7"/>
<light name="VEFX" devid="Player 1" index="8"/>
<light name="VEFX" devid="Player 2" index="8"/>
<light name="Effect" devid="Player 1" index="9"/>
<light name="Effect" devid="Player 2" index="9"/>
</lights>
</template>
<!-- Common "double scratch" BMS playstyle: LSHIFT A S D SPACE L ; ' RSHIFT -->
<!-- Uses Up/Down arrows for turntable navigation -->
<template name="Keyboard (linear, double scratch, QWERTY)" game="Beatmania IIDX">
<buttons>
<button name="P1 1" vkey="65" analogtype="0" devid="" invert="false" debounce_up="0" debounce_down="0" velocity_threshold="0"/>
<button name="P1 2" vkey="83" analogtype="0" devid="" invert="false" debounce_up="0" debounce_down="0" velocity_threshold="0"/>
<button name="P1 3" vkey="68" analogtype="0" devid="" invert="false" debounce_up="0" debounce_down="0" velocity_threshold="0"/>
<button name="P1 4" vkey="32" analogtype="0" devid="" invert="false" debounce_up="0" debounce_down="0" velocity_threshold="0"/>
<button name="P1 5" vkey="76" analogtype="0" devid="" invert="false" debounce_up="0" debounce_down="0" velocity_threshold="0"/>
<button name="P1 6" vkey="186" analogtype="0" devid="" invert="false" debounce_up="0" debounce_down="0" velocity_threshold="0"/>
<button name="P1 7" vkey="222" analogtype="0" devid="" invert="false" debounce_up="0" debounce_down="0" velocity_threshold="0"/>
<button name="P1 TT+" vkey="40" analogtype="0" devid="" invert="false" debounce_up="0" debounce_down="0" velocity_threshold="0"/>
<button name="P1 TT-" vkey="38" analogtype="0" devid="" invert="false" debounce_up="0" debounce_down="0" velocity_threshold="0"/>
<button name="P1 TT+/-" vkey="160" analogtype="0" devid="" invert="false" debounce_up="0" debounce_down="0" velocity_threshold="0"/>
<button name="P1 TT+/- Alternate" vkey="161" analogtype="0" devid="" invert="false" debounce_up="0" debounce_down="0" velocity_threshold="0"/>
<button name="P1 Start" vkey="13" analogtype="0" devid="" invert="false" debounce_up="0" debounce_down="0" velocity_threshold="0"/>
<button name="P2 Start" vkey="69" analogtype="0" devid="" invert="false" debounce_up="0" debounce_down="0" velocity_threshold="0"/>
<button name="EFFECT" vkey="81" analogtype="0" devid="" invert="false" debounce_up="0" debounce_down="0" velocity_threshold="0"/>
<button name="VEFX" vkey="87" analogtype="0" devid="" invert="false" debounce_up="0" debounce_down="0" velocity_threshold="0"/>
</buttons>
<keypad_buttons/>
<analogs/>
<lights/>
</template>
<template name="arcin (arcin-infinitas)" game="Beatmania IIDX">
<buttons>
<button name="P1 1" vkey="0" analogtype="0" devid="arcin (1p)" invert="false" debounce_up="0" debounce_down="0" velocity_threshold="0"/>
<button name="P1 2" vkey="1" analogtype="0" devid="arcin (1p)" invert="false" debounce_up="0" debounce_down="0" velocity_threshold="0"/>
<button name="P1 3" vkey="2" analogtype="0" devid="arcin (1p)" invert="false" debounce_up="0" debounce_down="0" velocity_threshold="0"/>
<button name="P1 4" vkey="3" analogtype="0" devid="arcin (1p)" invert="false" debounce_up="0" debounce_down="0" velocity_threshold="0"/>
<button name="P1 5" vkey="4" analogtype="0" devid="arcin (1p)" invert="false" debounce_up="0" debounce_down="0" velocity_threshold="0"/>
<button name="P1 6" vkey="5" analogtype="0" devid="arcin (1p)" invert="false" debounce_up="0" debounce_down="0" velocity_threshold="0"/>
<button name="P1 7" vkey="6" analogtype="0" devid="arcin (1p)" invert="false" debounce_up="0" debounce_down="0" velocity_threshold="0"/>
<button name="P1 Start" vkey="9" analogtype="0" devid="arcin (1p)" invert="false" debounce_up="0" debounce_down="0" velocity_threshold="0"/>
<button name="P2 1" vkey="0" analogtype="0" devid="arcin (2p)" invert="false" debounce_up="0" debounce_down="0" velocity_threshold="0"/>
<button name="P2 2" vkey="1" analogtype="0" devid="arcin (2p)" invert="false" debounce_up="0" debounce_down="0" velocity_threshold="0"/>
<button name="P2 3" vkey="2" analogtype="0" devid="arcin (2p)" invert="false" debounce_up="0" debounce_down="0" velocity_threshold="0"/>
<button name="P2 4" vkey="3" analogtype="0" devid="arcin (2p)" invert="false" debounce_up="0" debounce_down="0" velocity_threshold="0"/>
<button name="P2 5" vkey="4" analogtype="0" devid="arcin (2p)" invert="false" debounce_up="0" debounce_down="0" velocity_threshold="0"/>
<button name="P2 6" vkey="5" analogtype="0" devid="arcin (2p)" invert="false" debounce_up="0" debounce_down="0" velocity_threshold="0"/>
<button name="P2 7" vkey="6" analogtype="0" devid="arcin (2p)" invert="false" debounce_up="0" debounce_down="0" velocity_threshold="0"/>
<button name="P2 Start" vkey="9" analogtype="0" devid="arcin (2p)" invert="false" debounce_up="0" debounce_down="0" velocity_threshold="0"/>
<button name="EFFECT" vkey="11" analogtype="0" devid="arcin (1p)" invert="false" debounce_up="0" debounce_down="0" velocity_threshold="0"/>
<button name="EFFECT" vkey="11" analogtype="0" devid="arcin (2p)" invert="false" debounce_up="0" debounce_down="0" velocity_threshold="0"/>
<button name="VEFX" vkey="10" analogtype="0" devid="arcin (1p)" invert="false" debounce_up="0" debounce_down="0" velocity_threshold="0"/>
<button name="VEFX" vkey="10" analogtype="0" devid="arcin (2p)" invert="false" debounce_up="0" debounce_down="0" velocity_threshold="0"/>
</buttons>
<keypad_buttons/>
<!-- Assumes analog mode. Need to invert direction since this FW is designed for INF. -->
<analogs>
<analog name="Turntable P1" devid="arcin (1p)" index="0" sensivity="1" deadzone="0" deadzone_mirror="false" invert="true" smoothing="false" multiplier="1" relative="false" delay="0"/>
<analog name="Turntable P2" devid="arcin (2p)" index="0" sensivity="1" deadzone="0" deadzone_mirror="false" invert="true" smoothing="false" multiplier="1" relative="false" delay="0"/>
</analogs>
<lights>
<light name="P1 1" devid="arcin (1p)" index="0"/>
<light name="P1 2" devid="arcin (1p)" index="1"/>
<light name="P1 3" devid="arcin (1p)" index="2"/>
<light name="P1 4" devid="arcin (1p)" index="3"/>
<light name="P1 5" devid="arcin (1p)" index="4"/>
<light name="P1 6" devid="arcin (1p)" index="5"/>
<light name="P1 7" devid="arcin (1p)" index="6"/>
<light name="P2 1" devid="arcin (2p)" index="0"/>
<light name="P2 2" devid="arcin (2p)" index="1"/>
<light name="P2 3" devid="arcin (2p)" index="2"/>
<light name="P2 4" devid="arcin (2p)" index="3"/>
<light name="P2 5" devid="arcin (2p)" index="4"/>
<light name="P2 6" devid="arcin (2p)" index="5"/>
<light name="P2 7" devid="arcin (2p)" index="6"/>
<light name="P1 Start" devid="arcin (1p)" index="9"/>
<light name="P2 Start" devid="arcin (2p)" index="9"/>
</lights>
</template>
<!-- This controller has many modes; here, we use HID Analog mode which is meant for data -->
<template name="DJ DAO Nostroller (Analog mode)" game="Nostalgia">
<buttons/>
<keypad_buttons/>
<analogs>
<analog name="Key 1" devid="Analogs key 01-28" index="4" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1" relative="false" delay="0"/>
<analog name="Key 2" devid="Analogs key 01-28" index="5" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1" relative="false" delay="0"/>
<analog name="Key 3" devid="Analogs key 01-28" index="6" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1" relative="false" delay="0"/>
<analog name="Key 4" devid="Analogs key 01-28" index="7" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1" relative="false" delay="0"/>
<analog name="Key 5" devid="Analogs key 01-28" index="8" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1" relative="false" delay="0"/>
<analog name="Key 6" devid="Analogs key 01-28" index="9" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1" relative="false" delay="0"/>
<analog name="Key 7" devid="Analogs key 01-28" index="10" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1" relative="false" delay="0"/>
<analog name="Key 8" devid="Analogs key 01-28" index="11" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1" relative="false" delay="0"/>
<analog name="Key 9" devid="Analogs key 01-28" index="12" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1" relative="false" delay="0"/>
<analog name="Key 10" devid="Analogs key 01-28" index="13" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1" relative="false" delay="0"/>
<analog name="Key 11" devid="Analogs key 01-28" index="14" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1" relative="false" delay="0"/>
<analog name="Key 12" devid="Analogs key 01-28" index="15" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1" relative="false" delay="0"/>
<analog name="Key 13" devid="Analogs key 01-28" index="16" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1" relative="false" delay="0"/>
<analog name="Key 14" devid="Analogs key 01-28" index="17" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1" relative="false" delay="0"/>
<analog name="Key 15" devid="Analogs key 01-28" index="18" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1" relative="false" delay="0"/>
<analog name="Key 16" devid="Analogs key 01-28" index="19" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1" relative="false" delay="0"/>
<analog name="Key 17" devid="Analogs key 01-28" index="20" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1" relative="false" delay="0"/>
<analog name="Key 18" devid="Analogs key 01-28" index="21" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1" relative="false" delay="0"/>
<analog name="Key 19" devid="Analogs key 01-28" index="22" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1" relative="false" delay="0"/>
<analog name="Key 20" devid="Analogs key 01-28" index="23" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1" relative="false" delay="0"/>
<analog name="Key 21" devid="Analogs key 01-28" index="24" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1" relative="false" delay="0"/>
<analog name="Key 22" devid="Analogs key 01-28" index="25" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1" relative="false" delay="0"/>
<analog name="Key 23" devid="Analogs key 01-28" index="26" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1" relative="false" delay="0"/>
<analog name="Key 24" devid="Analogs key 01-28" index="27" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1" relative="false" delay="0"/>
<analog name="Key 25" devid="Analogs key 01-28" index="28" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1" relative="false" delay="0"/>
<analog name="Key 26" devid="Analogs key 01-28" index="29" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1" relative="false" delay="0"/>
<analog name="Key 27" devid="Analogs key 01-28" index="30" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1" relative="false" delay="0"/>
<analog name="Key 28" devid="Analogs key 01-28" index="31" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1" relative="false" delay="0"/>
</analogs>
<lights>
<light name="Key 1 R" devid="Lights 01-14" index="0"/>
<light name="Key 1 G" devid="Lights 01-14" index="1"/>
<light name="Key 1 B" devid="Lights 01-14" index="2"/>
<light name="Key 2 R" devid="Lights 01-14" index="3"/>
<light name="Key 2 G" devid="Lights 01-14" index="4"/>
<light name="Key 2 B" devid="Lights 01-14" index="5"/>
<light name="Key 3 R" devid="Lights 01-14" index="6"/>
<light name="Key 3 G" devid="Lights 01-14" index="7"/>
<light name="Key 3 B" devid="Lights 01-14" index="8"/>
<light name="Key 4 R" devid="Lights 01-14" index="9"/>
<light name="Key 4 G" devid="Lights 01-14" index="10"/>
<light name="Key 4 B" devid="Lights 01-14" index="11"/>
<light name="Key 5 R" devid="Lights 01-14" index="12"/>
<light name="Key 5 G" devid="Lights 01-14" index="13"/>
<light name="Key 5 B" devid="Lights 01-14" index="14"/>
<light name="Key 6 R" devid="Lights 01-14" index="15"/>
<light name="Key 6 G" devid="Lights 01-14" index="16"/>
<light name="Key 6 B" devid="Lights 01-14" index="17"/>
<light name="Key 7 R" devid="Lights 01-14" index="18"/>
<light name="Key 7 G" devid="Lights 01-14" index="19"/>
<light name="Key 7 B" devid="Lights 01-14" index="20"/>
<light name="Key 8 R" devid="Lights 01-14" index="21"/>
<light name="Key 8 G" devid="Lights 01-14" index="22"/>
<light name="Key 8 B" devid="Lights 01-14" index="23"/>
<light name="Key 9 R" devid="Lights 01-14" index="24"/>
<light name="Key 9 G" devid="Lights 01-14" index="25"/>
<light name="Key 9 B" devid="Lights 01-14" index="26"/>
<light name="Key 10 R" devid="Lights 01-14" index="27"/>
<light name="Key 10 G" devid="Lights 01-14" index="28"/>
<light name="Key 10 B" devid="Lights 01-14" index="29"/>
<light name="Key 11 R" devid="Lights 01-14" index="30"/>
<light name="Key 11 G" devid="Lights 01-14" index="31"/>
<light name="Key 11 B" devid="Lights 01-14" index="32"/>
<light name="Key 12 R" devid="Lights 01-14" index="33"/>
<light name="Key 12 G" devid="Lights 01-14" index="34"/>
<light name="Key 12 B" devid="Lights 01-14" index="35"/>
<light name="Key 13 R" devid="Lights 01-14" index="36"/>
<light name="Key 13 G" devid="Lights 01-14" index="37"/>
<light name="Key 13 B" devid="Lights 01-14" index="38"/>
<light name="Key 14 R" devid="Lights 01-14" index="39"/>
<light name="Key 14 G" devid="Lights 01-14" index="40"/>
<light name="Key 14 B" devid="Lights 01-14" index="41"/>
<light name="Key 15 R" devid="Lights 15-28" index="0"/>
<light name="Key 15 G" devid="Lights 15-28" index="1"/>
<light name="Key 15 B" devid="Lights 15-28" index="2"/>
<light name="Key 16 R" devid="Lights 15-28" index="3"/>
<light name="Key 16 G" devid="Lights 15-28" index="4"/>
<light name="Key 16 B" devid="Lights 15-28" index="5"/>
<light name="Key 17 R" devid="Lights 15-28" index="6"/>
<light name="Key 17 G" devid="Lights 15-28" index="7"/>
<light name="Key 17 B" devid="Lights 15-28" index="8"/>
<light name="Key 18 R" devid="Lights 15-28" index="9"/>
<light name="Key 18 G" devid="Lights 15-28" index="10"/>
<light name="Key 18 B" devid="Lights 15-28" index="11"/>
<light name="Key 19 R" devid="Lights 15-28" index="12"/>
<light name="Key 19 G" devid="Lights 15-28" index="13"/>
<light name="Key 19 B" devid="Lights 15-28" index="14"/>
<light name="Key 20 R" devid="Lights 15-28" index="15"/>
<light name="Key 20 G" devid="Lights 15-28" index="16"/>
<light name="Key 20 B" devid="Lights 15-28" index="17"/>
<light name="Key 21 R" devid="Lights 15-28" index="18"/>
<light name="Key 21 G" devid="Lights 15-28" index="19"/>
<light name="Key 21 B" devid="Lights 15-28" index="20"/>
<light name="Key 22 R" devid="Lights 15-28" index="21"/>
<light name="Key 22 G" devid="Lights 15-28" index="22"/>
<light name="Key 22 B" devid="Lights 15-28" index="23"/>
<light name="Key 23 R" devid="Lights 15-28" index="24"/>
<light name="Key 23 G" devid="Lights 15-28" index="25"/>
<light name="Key 23 B" devid="Lights 15-28" index="26"/>
<light name="Key 24 R" devid="Lights 15-28" index="27"/>
<light name="Key 24 G" devid="Lights 15-28" index="28"/>
<light name="Key 24 B" devid="Lights 15-28" index="29"/>
<light name="Key 25 R" devid="Lights 15-28" index="30"/>
<light name="Key 25 G" devid="Lights 15-28" index="31"/>
<light name="Key 25 B" devid="Lights 15-28" index="32"/>
<light name="Key 26 R" devid="Lights 15-28" index="33"/>
<light name="Key 26 G" devid="Lights 15-28" index="34"/>
<light name="Key 26 B" devid="Lights 15-28" index="35"/>
<light name="Key 27 R" devid="Lights 15-28" index="36"/>
<light name="Key 27 G" devid="Lights 15-28" index="37"/>
<light name="Key 27 B" devid="Lights 15-28" index="38"/>
<light name="Key 28 R" devid="Lights 15-28" index="39"/>
<light name="Key 28 G" devid="Lights 15-28" index="40"/>
<light name="Key 28 B" devid="Lights 15-28" index="41"/>
</lights>
</template>
<!-- Gitaller has two modes - digital tilt and analog axis. This binds both of them at the same time. -->
<template name="DJ DAO Gitaller" game="GitaDora">
<buttons>
<button name="Guitar P1 Start" vkey="9" analogtype="0" devid="Gitaller P1" invert="false" debounce_up="0" debounce_down="0" velocity_threshold="0"/>
<button name="Guitar P1 Help" vkey="8" analogtype="0" devid="Gitaller P1" invert="false" debounce_up="0" debounce_down="0" velocity_threshold="0"/>
<button name="Guitar P1 Pick Up" vkey="5" analogtype="0" devid="Gitaller P1" invert="false" debounce_up="0" debounce_down="0" velocity_threshold="0"/>
<button name="Guitar P1 Pick Down" vkey="6" analogtype="0" devid="Gitaller P1" invert="false" debounce_up="0" debounce_down="0" velocity_threshold="0"/>
<button name="Guitar P1 R" vkey="0" analogtype="0" devid="Gitaller P1" invert="false" debounce_up="0" debounce_down="0" velocity_threshold="0"/>
<button name="Guitar P1 G" vkey="1" analogtype="0" devid="Gitaller P1" invert="false" debounce_up="0" debounce_down="0" velocity_threshold="0"/>
<button name="Guitar P1 B" vkey="2" analogtype="0" devid="Gitaller P1" invert="false" debounce_up="0" debounce_down="0" velocity_threshold="0"/>
<button name="Guitar P1 Y" vkey="3" analogtype="0" devid="Gitaller P1" invert="false" debounce_up="0" debounce_down="0" velocity_threshold="0"/>
<button name="Guitar P1 P" vkey="4" analogtype="0" devid="Gitaller P1" invert="false" debounce_up="0" debounce_down="0" velocity_threshold="0"/>
<button name="Guitar P1 Wail Up" vkey="10" analogtype="0" devid="Gitaller P1" invert="false" debounce_up="0" debounce_down="0" velocity_threshold="0"/>
<button name="Guitar P1 Wail Down" vkey="11" analogtype="0" devid="Gitaller P1" invert="false" debounce_up="0" debounce_down="0" velocity_threshold="0"/>
</buttons>
<keypad_buttons/>
<analogs>
<analog name="Guitar P1 Wail X" devid="Gitaller P1" index="1" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1" relative="false" delay="0"/>
<analog name="Guitar P1 Wail Y" devid="Gitaller P1" index="0" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1" relative="false" delay="0"/>
<analog name="Guitar P1 Wail Z" devid="Gitaller P1" index="5" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1" relative="false" delay="0"/>
</analogs>
<lights/>
<!-- Gen 1 FAUCETWO in HID mode; should also work for later gens -->
</template>
<template name="DJ DAO FAUCETWO" game="Sound Voltex">
<buttons>
<button name="BT-A" vkey="1" analogtype="0" devid="Buttons (MI_01)" invert="false" debounce_up="0" debounce_down="0" velocity_threshold="0"/>
<button name="BT-B" vkey="2" analogtype="0" devid="Buttons (MI_01)" invert="false" debounce_up="0" debounce_down="0" velocity_threshold="0"/>
<button name="BT-C" vkey="3" analogtype="0" devid="Buttons (MI_01)" invert="false" debounce_up="0" debounce_down="0" velocity_threshold="0"/>
<button name="BT-D" vkey="4" analogtype="0" devid="Buttons (MI_01)" invert="false" debounce_up="0" debounce_down="0" velocity_threshold="0"/>
<button name="FX-L" vkey="5" analogtype="0" devid="Buttons (MI_01)" invert="false" debounce_up="0" debounce_down="0" velocity_threshold="0"/>
<button name="FX-R" vkey="6" analogtype="0" devid="Buttons (MI_01)" invert="false" debounce_up="0" debounce_down="0" velocity_threshold="0"/>
<button name="Start" vkey="0" analogtype="0" devid="Buttons (MI_01)" invert="false" debounce_up="0" debounce_down="0" velocity_threshold="0"/>
</buttons>
<keypad_buttons/>
<analogs>
<analog name="VOL-L" devid="Buttons (MI_01)" index="1" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1" relative="false" delay="0"/>
<analog name="VOL-R" devid="Buttons (MI_01)" index="0" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1" relative="false" delay="0"/>
</analogs>
<lights>
<light name="BT-A" devid="Lights (MI_02)" index="24"/>
<light name="BT-B" devid="Lights (MI_02)" index="25"/>
<light name="BT-C" devid="Lights (MI_02)" index="26"/>
<light name="BT-D" devid="Lights (MI_02)" index="27"/>
<light name="FX-L" devid="Lights (MI_02)" index="28"/>
<light name="FX-R" devid="Lights (MI_02)" index="29"/>
<light name="Start" devid="Lights (MI_02)" index="30"/>
<light name="Upper Left Speaker Avg R" devid="Lights (MI_02)" index="0"/>
<light name="Upper Left Speaker Avg G" devid="Lights (MI_02)" index="1"/>
<light name="Upper Left Speaker Avg B" devid="Lights (MI_02)" index="2"/>
<light name="Upper Right Speaker Avg R" devid="Lights (MI_02)" index="9"/>
<light name="Upper Right Speaker Avg G" devid="Lights (MI_02)" index="10"/>
<light name="Upper Right Speaker Avg B" devid="Lights (MI_02)" index="11"/>
<light name="Lower Left Speaker Avg R" devid="Lights (MI_02)" index="3"/>
<light name="Lower Left Speaker Avg G" devid="Lights (MI_02)" index="4"/>
<light name="Lower Left Speaker Avg B" devid="Lights (MI_02)" index="5"/>
<light name="Lower Right Speaker Avg R" devid="Lights (MI_02)" index="6"/>
<light name="Lower Right Speaker Avg G" devid="Lights (MI_02)" index="7"/>
<light name="Lower Right Speaker Avg B" devid="Lights (MI_02)" index="8"/>
<light name="Control Panel Avg R" devid="Lights (MI_02)" index="15"/>
<light name="Control Panel Avg G" devid="Lights (MI_02)" index="16"/>
<light name="Control Panel Avg B" devid="Lights (MI_02)" index="17"/>
</lights>
</template>
</templates>
+9 -9
View File
@@ -41,8 +41,8 @@ done
# settings
GIT_BRANCH=$(git rev-parse --abbrev-ref HEAD 2> /dev/null || echo "none")
GIT_HEAD=$(git rev-parse HEAD || echo "none")
TOOLCHAIN_32="/usr/share/mingw/toolchain-i686-w64-mingw32.cmake"
TOOLCHAIN_64="/usr/share/mingw/toolchain-x86_64-w64-mingw32.cmake"
TOOLCHAIN_32="${TOOLCHAIN_32:-"/usr/share/mingw/toolchain-i686-w64-mingw32.cmake"}"
TOOLCHAIN_64="${TOOLCHAIN_64:-"/usr/share/mingw/toolchain-x86_64-w64-mingw32.cmake"}"
BUILDDIR_32_RELEASE="./cmake-build-release-32"
BUILDDIR_32_DEBUG="./cmake-build-debug-32"
BUILDDIR_64_RELEASE="./cmake-build-release-64"
@@ -74,7 +74,7 @@ then
BUILD_TYPE="Debug"
BUILDDIR_32=${BUILDDIR_32_DEBUG}
BUILDDIR_64=${BUILDDIR_64_DEBUG}
DIST_NAME=$(echo ${DIST_NAME} | sed 's/\.[^.]*$/-dbg&/')
DIST_NAME=$(echo "${DIST_NAME}" | sed 's/\.[^.]*$/-dbg&/')
fi
# determine number of cores
@@ -114,7 +114,7 @@ time (
fi
mkdir -p ${BUILDDIR_32}
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
# 64 bit
@@ -127,7 +127,7 @@ time (
fi
mkdir -p ${BUILDDIR_64}
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
echo ""
@@ -234,7 +234,7 @@ mkdir -p ${OUTDIR}/src
if ((INCLUDE_SRC > 0))
then
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?"
fi
@@ -249,11 +249,11 @@ if ((DIST_ENABLE > 0))
then
echo "Building dist..."
mkdir -p ${DIST_FOLDER}
rm -rf ${DIST_FOLDER}/${DIST_NAME}
rm -rf "${DIST_FOLDER}/${DIST_NAME}"
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..."
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
fi
+3 -3
View File
@@ -1,4 +1,4 @@
#!/bin/bash
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 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 build --pull "$PWD/external/docker" -t spicetools/deps --platform linux/x86_64
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 "$@"
+1
View File
@@ -13,5 +13,6 @@ IDR_LICENSES RCDATA "../licenses.txt"
IDR_PATCHES RCDATA "../build/patches.json"
IDR_README RCDATA "../readme.txt"
IDR_DSEGFONT RCDATA "../external/dseg/DSEG14Classic-Italic.ttf"
IDR_CONTROLLER_PRESETS_BUILTIN RCDATA "../build/controller_presets.xml"
#endif
+1
View File
@@ -91,6 +91,7 @@ public:
setMultiplier(1);
setRelativeMode(false);
setDelayBufferDepth(0);
setLastState(0.5f);
}
inline void clearBindings() {
+50 -5
View File
@@ -37,7 +37,11 @@ std::vector<Button> GameAPI::Buttons::sortButtons(
for (auto &bt : buttons) {
if (name == bt.getName()) {
button_found = true;
sorted.push_back(bt);
Button button_new = bt;
if (vkey_defaults) {
button_new.setVKeyDefault(vkey_defaults->at(index));
}
sorted.push_back(button_new);
break;
}
}
@@ -47,6 +51,7 @@ std::vector<Button> GameAPI::Buttons::sortButtons(
if (vkey_defaults) {
button.setVKey(vkey_defaults->at(index));
button.setVKeyDefault(vkey_defaults->at(index));
}
}
@@ -644,10 +649,12 @@ float GameAPI::Analogs::getState(rawinput::Device *device, Analog &analog) {
case rawinput::HID: {
// get value
if (inverted) {
value = 1.f - device->hidInfo->value_states[index];
} else {
value = device->hidInfo->value_states[index];
if (index < device->hidInfo->value_states.size()) {
if (inverted) {
value = 1.f - device->hidInfo->value_states[index];
} else {
value = device->hidInfo->value_states[index];
}
}
// deadzone
@@ -859,6 +866,44 @@ std::vector<Light> GameAPI::Lights::sortLights(
return sorted;
}
static std::vector<Light> sortLightsWithCategoryInternal(
const std::vector<Light> &lights,
const std::initializer_list<GameAPI::Lights::LightAndCategory> list)
{
std::vector<Light> sorted;
bool light_found;
for (auto &name : list) {
light_found = false;
for (auto &light : lights) {
if (name.light_name == light.getName()) {
light_found = true;
Light light_new = light;
light_new.setCategory(name.category_name);
sorted.push_back(light_new);
break;
}
}
if (!light_found) {
sorted.emplace_back(name.light_name, name.category_name);
}
}
return sorted;
}
void GameAPI::Lights::sortLightsWithCategory(
std::vector<Light> *lights,
const std::initializer_list<LightAndCategory> list) {
if (lights) {
*lights = sortLightsWithCategoryInternal(*lights, list);
}
}
void GameAPI::Lights::writeLight(rawinput::Device *device, int index, float value) {
// check device
+15 -21
View File
@@ -6,24 +6,20 @@
#include <windows.h>
#include "option.h"
#include "button.h"
#include "analog.h"
#include "light.h"
namespace rawinput {
class RawInputManager;
struct Device;
}
class Button;
class Analog;
class Light;
class Game;
class Option;
namespace GameAPI {
namespace Buttons {
enum State {
BUTTON_PRESSED = true,
BUTTON_NOT_PRESSED = false
};
/**
* Parses the config and returns the buttons set by the user.
*
@@ -113,21 +109,13 @@ namespace GameAPI {
}
namespace Lights {
std::vector<Light> getLights(const std::string &game_name);
std::vector<Light> sortLights(
const std::vector<Light> &lights,
const std::vector<std::string> &light_names);
template<typename T>
void sortLights(std::vector<Light> *lights, T t) {
const std::vector<std::string> light_names { t };
if (lights) {
*lights = GameAPI::Lights::sortLights(*lights, light_names);
}
}
template<typename T, typename... Rest>
void sortLights(std::vector<Light> *lights, T t, Rest... rest) {
const std::vector<std::string> light_names { t, rest... };
@@ -137,6 +125,16 @@ namespace GameAPI {
}
}
struct LightAndCategory {
const std::string category_name;
const std::string light_name;
LightAndCategory(std::string category_name, std::string light_name) :
category_name(std::move(category_name)), light_name(std::move(light_name)) {}
};
void sortLightsWithCategory(std::vector<Light> *lights, const std::initializer_list<LightAndCategory> list);
void writeLight(rawinput::Device *device, int index, float value);
void writeLight(rawinput::RawInputManager *manager, Light &light, float value);
void writeLight(std::unique_ptr<rawinput::RawInputManager> &manager, Light &light, float value);
@@ -152,7 +150,3 @@ namespace GameAPI {
void sortOptions(std::vector<Option> &, const std::vector<OptionDefinition> &);
}
}
#include "button.h"
#include "analog.h"
#include "light.h"
+50 -30
View File
@@ -44,7 +44,7 @@ std::string Button::getVKeyString() {
case 0x0C:
return "Clear";
case 0x0D:
return "Enter";
return "Enter/Return";
case 0x10:
return "Shift";
case 0x11:
@@ -58,8 +58,28 @@ std::string Button::getVKeyString() {
return "Pause";
case 0x14:
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:
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:
return "Space";
case 0x21:
@@ -163,37 +183,37 @@ std::string Button::getVKeyString() {
case 0x5D:
return "Apps";
case 0x60:
return "Num 0";
return "Numpad 0";
case 0x61:
return "Num 1";
return "Numpad 1";
case 0x62:
return "Num 2";
return "Numpad 2";
case 0x63:
return "Num 3";
return "Numpad 3";
case 0x64:
return "Num 4";
return "Numpad 4";
case 0x65:
return "Num 5";
return "Numpad 5";
case 0x66:
return "Num 6";
return "Numpad 6";
case 0x67:
return "Num 7";
return "Numpad 7";
case 0x68:
return "Num 8";
return "Numpad 8";
case 0x69:
return "Num 9";
return "Numpad 9";
case 0x6A:
return "*";
return "Numpad *";
case 0x6B:
return "+";
return "Numpad +";
case 0x6C:
return "Seperator";
return "Numpad Seperator";
case 0x6D:
return "-";
return "Numpad -";
case 0x6E:
return ".";
return "Numpad .";
case 0x6F:
return "/";
return "Numpad /";
case 0x70:
return "F1";
case 0x71:
@@ -255,9 +275,9 @@ std::string Button::getVKeyString() {
case 0xA3:
return "Right Control";
case 0xA4:
return "Left Menu";
return "Left Alt";
case 0xA5:
return "Right Menu";
return "Right Alt";
default:
// check win API
@@ -301,17 +321,17 @@ std::string Button::getDisplayString(rawinput::RawInputManager* manager) {
switch (device->type) {
case rawinput::MOUSE: {
const char *btn = "Unknown";
static const char *MOUSE_NAMES[] = {
"Left Mouse",
"Right Mouse",
"Middle Mouse",
"Mouse 1",
"Mouse 2",
"Mouse 3",
"Mouse 4",
"Mouse 5",
};
if (vKey < sizeof(MOUSE_NAMES)) {
static const std::array<const char *, 8>MOUSE_NAMES = {
"Left Mouse",
"Right Mouse",
"Middle Mouse",
"Mouse 1",
"Mouse 2",
"Mouse 3",
"Mouse 4",
"Mouse 5",
};
if (vKey < MOUSE_NAMES.size()) {
btn = MOUSE_NAMES[vKey];
}
return fmt::format("{} ({})", btn, device->desc);
+46 -3
View File
@@ -1,15 +1,23 @@
#pragma once
#include <cstdint>
#include <string>
#include <utility>
#include <vector>
#include "api.h"
namespace rawinput {
class RawInputManager;
}
namespace GameAPI {
namespace Buttons {
enum State {
BUTTON_PRESSED = true,
BUTTON_NOT_PRESSED = false
};
}
}
enum ButtonAnalogType {
BAT_NONE = 0,
BAT_POSITIVE = 1,
@@ -41,19 +49,24 @@ private:
std::string name;
std::string device_identifier = "";
unsigned short vKey = INVALID_VKEY;
// default bindings are always naive
unsigned short vKey_default = INVALID_VKEY;
ButtonAnalogType analog_type = BAT_NONE;
double debounce_up = 0.0;
double debounce_down = 0.0;
bool invert = false;
bool is_temporary = false;
GameAPI::Buttons::State last_state = GameAPI::Buttons::BUTTON_NOT_PRESSED;
float last_velocity = 0.f;
unsigned short velocity_threshold = 0;
std::string getVKeyString();
std::string getMidiNoteString();
public:
std::string getVKeyString();
// overrides
bool override_enabled = false;
@@ -116,6 +129,14 @@ public:
this->vKey = vKey;
}
inline unsigned short getVKeyDefault() const {
return this->vKey_default;
}
inline void setVKeyDefault(unsigned short vKey_default) {
this->vKey_default = vKey_default;
}
inline ButtonAnalogType getAnalogType() const {
return this->analog_type;
}
@@ -164,6 +185,14 @@ public:
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 {
return this->velocity_threshold;
}
@@ -172,6 +201,20 @@ public:
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 setMidiVKey(rawinput::RawInputManager* manager, bool is_note, int channel, int index);
+76 -1
View File
@@ -1,5 +1,6 @@
#include "config.h"
#include "util/logging.h"
#include "cfg/button.h"
/*
* This code absolutely sucks.
@@ -10,6 +11,14 @@
// settings
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 ///
@@ -510,8 +519,10 @@ bool Config::updateBinding(const Game &game, const Button &button, int alternati
return false;
}
tinyxml2::XMLElement *gameButtonNode = nullptr;
// iterate button nodes
tinyxml2::XMLElement *gameButtonNode = gameButtonsNode->FirstChildElement("button");
gameButtonNode = gameButtonsNode->FirstChildElement("button");
int button_count = 0;
while (gameButtonNode != nullptr) {
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
if (button_count == 0) {
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
if (light_count == 0) {
return false;
+460
View File
@@ -0,0 +1,460 @@
#include "overlay/windows/controller_presets.h"
#include <filesystem>
#include "cfg/resource.h"
#include "external/tinyxml2/tinyxml2.h"
#include "util/fileutils.h"
#include "util/logging.h"
#include "util/resutils.h"
namespace overlay::windows {
static std::filesystem::path get_templates_path() {
return fileutils::get_config_file_path("ControllerPresets", "spicetools_presets.xml");
}
static void write_button_entry(tinyxml2::XMLDocument &doc, tinyxml2::XMLElement *parent,
const std::string &name, const TemplateButtonEntry &entry) {
auto *el = doc.NewElement("button");
el->SetAttribute("name", name.c_str());
el->SetAttribute("vkey", entry.vKey);
el->SetAttribute("analogtype", (int)entry.analog_type);
el->SetAttribute("devid", entry.device_identifier.c_str());
el->SetAttribute("invert", entry.invert);
el->SetAttribute("debounce_up", entry.debounce_up);
el->SetAttribute("debounce_down", entry.debounce_down);
el->SetAttribute("velocity_threshold", entry.velocity_threshold);
parent->InsertEndChild(el);
}
static TemplateButtonEntry read_button_entry(tinyxml2::XMLElement *el) {
TemplateButtonEntry entry;
int vkey = INVALID_VKEY;
el->QueryIntAttribute("vkey", &vkey);
entry.vKey = (unsigned short)vkey;
int atype = 0;
el->QueryIntAttribute("analogtype", &atype);
entry.analog_type = (ButtonAnalogType)atype;
const char *devid = el->Attribute("devid");
entry.device_identifier = devid ? devid : "";
el->QueryBoolAttribute("invert", &entry.invert);
el->QueryDoubleAttribute("debounce_up", &entry.debounce_up);
el->QueryDoubleAttribute("debounce_down", &entry.debounce_down);
int vel = 0;
el->QueryIntAttribute("velocity_threshold", &vel);
entry.velocity_threshold = (unsigned short)vel;
return entry;
}
static void write_analog(tinyxml2::XMLDocument &doc, tinyxml2::XMLElement *parent,
const TemplateAnalogBinding &analog) {
auto *el = doc.NewElement("analog");
el->SetAttribute("name", analog.name.c_str());
el->SetAttribute("devid", analog.device_identifier.c_str());
el->SetAttribute("index", analog.index);
el->SetAttribute("sensivity", analog.sensitivity); // keep typo for compat
el->SetAttribute("deadzone", analog.deadzone);
el->SetAttribute("deadzone_mirror", analog.deadzone_mirror);
el->SetAttribute("invert", analog.invert);
el->SetAttribute("smoothing", analog.smoothing);
el->SetAttribute("multiplier", analog.multiplier);
el->SetAttribute("relative", analog.relative_mode);
el->SetAttribute("delay", analog.delay_buffer_depth);
parent->InsertEndChild(el);
}
static TemplateAnalogBinding read_analog(tinyxml2::XMLElement *el) {
TemplateAnalogBinding a;
const char *name = el->Attribute("name");
a.name = name ? name : "";
const char *devid = el->Attribute("devid");
a.device_identifier = devid ? devid : "";
int idx = 0xFF;
el->QueryIntAttribute("index", &idx);
a.index = (unsigned short)idx;
el->QueryFloatAttribute("sensivity", &a.sensitivity); // keep typo
el->QueryFloatAttribute("deadzone", &a.deadzone);
el->QueryBoolAttribute("deadzone_mirror", &a.deadzone_mirror);
el->QueryBoolAttribute("invert", &a.invert);
el->QueryBoolAttribute("smoothing", &a.smoothing);
el->QueryIntAttribute("multiplier", &a.multiplier);
el->QueryBoolAttribute("relative", &a.relative_mode);
el->QueryIntAttribute("delay", &a.delay_buffer_depth);
return a;
}
static void write_light_entry(tinyxml2::XMLDocument &doc, tinyxml2::XMLElement *parent,
const std::string &name, const TemplateLightEntry &entry) {
auto *el = doc.NewElement("light");
el->SetAttribute("name", name.c_str());
el->SetAttribute("devid", entry.device_identifier.c_str());
el->SetAttribute("index", entry.index);
parent->InsertEndChild(el);
}
static TemplateLightEntry read_light_entry(tinyxml2::XMLElement *el) {
TemplateLightEntry entry;
const char *devid = el->Attribute("devid");
entry.device_identifier = devid ? devid : "";
int idx = 0;
el->QueryIntAttribute("index", &idx);
entry.index = (unsigned int)idx;
return entry;
}
// Parse all templates from an already-loaded XMLDocument.
// is_builtin controls the is_builtin flag on each returned template.
static std::vector<ControllerTemplate> load_templates_from_doc(
tinyxml2::XMLDocument &doc, bool is_builtin) {
std::vector<ControllerTemplate> templates;
auto *root = doc.RootElement();
if (!root) {
return templates;
}
auto *tmpl_el = root->FirstChildElement("template");
while (tmpl_el) {
ControllerTemplate tmpl;
const char *name = tmpl_el->Attribute("name");
const char *game = tmpl_el->Attribute("game");
tmpl.name = name ? name : "";
tmpl.game_name = game ? game : "";
tmpl.is_builtin = is_builtin;
// buttons
auto *buttons_el = tmpl_el->FirstChildElement("buttons");
if (buttons_el) {
auto *btn_el = buttons_el->FirstChildElement("button");
while (btn_el) {
const char *btn_name = btn_el->Attribute("name");
std::string btn_name_str = btn_name ? btn_name : "";
TemplateButtonBinding *binding = nullptr;
for (auto &b : tmpl.buttons) {
if (b.name == btn_name_str) { binding = &b; break; }
}
if (!binding) {
tmpl.buttons.push_back({btn_name_str, read_button_entry(btn_el), {}});
} else {
binding->alternatives.push_back(read_button_entry(btn_el));
}
btn_el = btn_el->NextSiblingElement("button");
}
}
// keypad buttons
auto *kp_el = tmpl_el->FirstChildElement("keypad_buttons");
if (kp_el) {
auto *btn_el = kp_el->FirstChildElement("button");
while (btn_el) {
const char *btn_name = btn_el->Attribute("name");
std::string btn_name_str = btn_name ? btn_name : "";
TemplateButtonBinding *binding = nullptr;
for (auto &b : tmpl.keypad_buttons) {
if (b.name == btn_name_str) { binding = &b; break; }
}
if (!binding) {
tmpl.keypad_buttons.push_back({btn_name_str, read_button_entry(btn_el), {}});
} else {
binding->alternatives.push_back(read_button_entry(btn_el));
}
btn_el = btn_el->NextSiblingElement("button");
}
}
// analogs
auto *analogs_el = tmpl_el->FirstChildElement("analogs");
if (analogs_el) {
auto *analog_el = analogs_el->FirstChildElement("analog");
while (analog_el) {
tmpl.analogs.push_back(read_analog(analog_el));
analog_el = analog_el->NextSiblingElement("analog");
}
}
// lights
auto *lights_el = tmpl_el->FirstChildElement("lights");
if (lights_el) {
auto *light_el = lights_el->FirstChildElement("light");
while (light_el) {
const char *light_name = light_el->Attribute("name");
std::string light_name_str = light_name ? light_name : "";
TemplateLightBinding *binding = nullptr;
for (auto &l : tmpl.lights) {
if (l.name == light_name_str) { binding = &l; break; }
}
if (!binding) {
tmpl.lights.push_back({light_name_str, read_light_entry(light_el), {}});
} else {
binding->alternatives.push_back(read_light_entry(light_el));
}
light_el = light_el->NextSiblingElement("light");
}
}
templates.push_back(std::move(tmpl));
tmpl_el = tmpl_el->NextSiblingElement("template");
}
return templates;
}
static std::vector<ControllerTemplate> load_builtin_templates() {
auto xml = resutil::load_file_string(IDR_CONTROLLER_PRESETS_BUILTIN);
tinyxml2::XMLDocument doc;
auto err = doc.Parse(xml.c_str());
if (err != tinyxml2::XML_SUCCESS) {
log_warning("templates", "failed to parse builtin templates XML: {}", (int)err);
return {};
}
return load_templates_from_doc(doc, true);
}
std::vector<ControllerTemplate> load_user_templates() {
std::vector<ControllerTemplate> templates;
auto path = get_templates_path();
tinyxml2::XMLDocument doc;
if (doc.LoadFile(path.c_str()) != tinyxml2::XML_SUCCESS) {
return templates;
}
return load_templates_from_doc(doc, false);
}
bool save_user_template(
const ControllerTemplate &tmpl,
const bool save_buttons,
const bool save_keypads,
const bool save_analogs,
const bool save_lights) {
auto path = get_templates_path();
auto path_tmp = path;
path_tmp.replace_extension(L"tmp");
tinyxml2::XMLDocument doc;
if (doc.LoadFile(path.c_str()) != tinyxml2::XML_SUCCESS) {
auto *decl = doc.NewDeclaration();
doc.InsertFirstChild(decl);
auto *root = doc.NewElement("templates");
doc.InsertEndChild(root);
}
auto *root = doc.RootElement();
if (!root) {
root = doc.NewElement("templates");
doc.InsertEndChild(root);
}
// remove existing template with same name and game
auto *existing = root->FirstChildElement("template");
while (existing) {
auto *next = existing->NextSiblingElement("template");
const char *ename = existing->Attribute("name");
const char *egame = existing->Attribute("game");
if (ename && egame &&
std::string(ename) == tmpl.name &&
std::string(egame) == tmpl.game_name) {
root->DeleteChild(existing);
}
existing = next;
}
auto *tmpl_el = doc.NewElement("template");
tmpl_el->SetAttribute("name", tmpl.name.c_str());
tmpl_el->SetAttribute("game", tmpl.game_name.c_str());
// buttons — skip unbound entries
auto *buttons_el = doc.NewElement("buttons");
if (save_buttons) {
for (auto &btn : tmpl.buttons) {
if (!btn.primary.is_unbound()) {
write_button_entry(doc, buttons_el, btn.name, btn.primary);
}
for (auto &alt : btn.alternatives) {
if (!alt.is_unbound()) {
write_button_entry(doc, buttons_el, btn.name, alt);
}
}
}
}
tmpl_el->InsertEndChild(buttons_el);
// keypad buttons — skip unbound entries
if (!tmpl.keypad_buttons.empty()) {
auto *kp_el = doc.NewElement("keypad_buttons");
if (save_keypads) {
for (auto &btn : tmpl.keypad_buttons) {
if (!btn.primary.is_unbound()) {
write_button_entry(doc, kp_el, btn.name, btn.primary);
}
for (auto &alt : btn.alternatives) {
if (!alt.is_unbound()) {
write_button_entry(doc, kp_el, btn.name, alt);
}
}
}
}
tmpl_el->InsertEndChild(kp_el);
}
// analogs — skip unbound entries
auto *analogs_el = doc.NewElement("analogs");
if (save_analogs) {
for (auto &a : tmpl.analogs) {
if (!a.is_unbound()) {
write_analog(doc, analogs_el, a);
}
}
}
tmpl_el->InsertEndChild(analogs_el);
// lights — skip unbound entries
auto *lights_el = doc.NewElement("lights");
if (save_lights) {
for (auto &l : tmpl.lights) {
if (!l.primary.is_unbound()) {
write_light_entry(doc, lights_el, l.name, l.primary);
}
for (auto &alt : l.alternatives) {
if (!alt.is_unbound()) {
write_light_entry(doc, lights_el, l.name, alt);
}
}
}
}
tmpl_el->InsertEndChild(lights_el);
root->InsertEndChild(tmpl_el);
// ensure directory exists
if (!path.parent_path().empty() && !std::filesystem::exists(path.parent_path())) {
fileutils::dir_create_recursive(path.parent_path());
}
// save atomically via temp file
auto save_err = doc.SaveFile(path_tmp.c_str());
if (save_err != tinyxml2::XML_SUCCESS) {
log_warning("templates", "failed to save templates file: {}", (int)save_err);
return false;
}
std::error_code ec;
std::filesystem::rename(path_tmp, path, ec);
if (ec) {
log_warning("templates", "failed to rename templates file: {}", ec.message());
return false;
}
return true;
}
bool delete_user_template(const std::string &game_name, const std::string &template_name) {
auto path = get_templates_path();
tinyxml2::XMLDocument doc;
if (doc.LoadFile(path.c_str()) != tinyxml2::XML_SUCCESS) return false;
auto *root = doc.RootElement();
if (!root) return false;
bool found = false;
auto *tmpl_el = root->FirstChildElement("template");
while (tmpl_el) {
auto *next = tmpl_el->NextSiblingElement("template");
const char *ename = tmpl_el->Attribute("name");
const char *egame = tmpl_el->Attribute("game");
if (ename && egame &&
std::string(ename) == template_name &&
std::string(egame) == game_name) {
root->DeleteChild(tmpl_el);
found = true;
}
tmpl_el = next;
}
if (found) {
auto path_tmp = path;
path_tmp.replace_extension(L"tmp");
if (doc.SaveFile(path_tmp.c_str()) == tinyxml2::XML_SUCCESS) {
std::error_code ec;
std::filesystem::rename(path_tmp, path, ec);
}
}
return found;
}
bool rename_user_template(const std::string &game_name,
const std::string &old_name, const std::string &new_name) {
auto path = get_templates_path();
tinyxml2::XMLDocument doc;
if (doc.LoadFile(path.c_str()) != tinyxml2::XML_SUCCESS) return false;
auto *root = doc.RootElement();
if (!root) return false;
bool found = false;
auto *tmpl_el = root->FirstChildElement("template");
while (tmpl_el) {
const char *ename = tmpl_el->Attribute("name");
const char *egame = tmpl_el->Attribute("game");
if (ename && egame &&
std::string(ename) == old_name &&
std::string(egame) == game_name) {
tmpl_el->SetAttribute("name", new_name.c_str());
found = true;
break;
}
tmpl_el = tmpl_el->NextSiblingElement("template");
}
if (found) {
auto path_tmp = path;
path_tmp.replace_extension(L"tmp");
if (doc.SaveFile(path_tmp.c_str()) == tinyxml2::XML_SUCCESS) {
std::error_code ec;
std::filesystem::rename(path_tmp, path, ec);
}
}
return found;
}
std::vector<ControllerTemplate> get_templates_for_game(const std::string &game_name) {
std::vector<ControllerTemplate> result;
log_misc("templates", "loading built-in templates for {}", game_name);
for (auto &t : load_builtin_templates()) {
if (t.game_name == game_name) {
result.push_back(std::move(t));
}
}
log_misc("templates", "loading user templates for {}", game_name);
for (auto &t : load_user_templates()) {
if (t.game_name == game_name) {
result.push_back(std::move(t));
}
}
return result;
}
}
+34
View File
@@ -11,8 +11,10 @@ class Light {
private:
std::vector<Light> alternatives;
std::string lightName;
std::string lightCategory;
std::string deviceIdentifier = "";
unsigned int index = 0;
bool is_temporary = false;
public:
float last_state = 0.f;
@@ -22,6 +24,8 @@ public:
float override_state = 0.f;
explicit Light(std::string lightName) : lightName(std::move(lightName)) {};
explicit Light(std::string lightName, std::string lightCategory) :
lightName(std::move(lightName)), lightCategory(std::move(lightCategory)) {};
std::string getDisplayString(rawinput::RawInputManager* manager);
@@ -50,6 +54,14 @@ public:
return this->lightName;
}
inline const std::string &getCategory() const {
return this->lightCategory;
}
inline void setCategory(std::string category) {
this->lightCategory = std::move(category);
}
inline const std::string &getDeviceIdentifier() const {
return this->deviceIdentifier;
}
@@ -65,4 +77,26 @@ public:
inline void setIndex(unsigned int 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;
}
};
+13
View File
@@ -13,6 +13,15 @@ enum class OptionType {
Hex,
};
enum class OptionPickerType {
None,
AsioDriver,
EACard,
CpuAffinity,
FilePath,
DirectoryPath,
};
struct OptionDefinition {
std::string title;
// unique identifier used for flag matching but also stored in config files
@@ -32,6 +41,10 @@ struct OptionDefinition {
bool sensitive = false;
std::vector<std::pair<std::string, std::string>> elements = {};
bool disabled = false;
OptionPickerType picker = OptionPickerType::None;
// for OptionPickerType::FilePath
std::string file_extension = "";
};
class Option {
+1
View File
@@ -4,3 +4,4 @@
#define IDR_PATCHES 132
#define IDR_README 133
#define IDR_DSEGFONT 134
#define IDR_CONTROLLER_PRESETS_BUILTIN 135
+6
View File
@@ -1,9 +1,11 @@
#include "screen_resize.h"
#include "avs/game.h"
#include "external/rapidjson/document.h"
#include "external/rapidjson/pointer.h"
#include "external/rapidjson/prettywriter.h"
#include "misc/eamuse.h"
#include "util/deferlog.h"
#include "util/utils.h"
#include "util/fileutils.h"
#include "hooks/graphics/graphics.h"
@@ -78,6 +80,10 @@ namespace cfg {
root);
load_bool_value(doc, root + "enable_screen_resize", this->enable_screen_resize);
if (this->enable_screen_resize) {
log_misc("ScreenResize", "enabled by config file");
}
load_bool_value(doc, root + "enable_linear_filter", this->enable_linear_filter);
for (size_t i = 0; i < std::size(this->scene_settings); i++) {
auto& scene = this->scene_settings[i];
+5
View File
@@ -1,5 +1,6 @@
#pragma once
#include <atomic>
#include <memory>
#include <string>
#include <optional>
@@ -51,6 +52,9 @@ namespace cfg {
bool enable_linear_filter = true;
fullscreen_setting scene_settings[4];
// state
std::atomic<bool> need_surface_clean = false;
// windowed mode sizing
// Windows terminology:
// window = rectangle including the frame
@@ -69,6 +73,7 @@ namespace cfg {
uint32_t init_client_height = 0;
float init_client_aspect_ratio = 1.f;
uint32_t init_window_style = 0;
uint32_t init_window_style_ex = 0;
uint32_t window_deco_width = 0;
uint32_t window_deco_height = 0;
+15 -1
View File
@@ -1,5 +1,7 @@
#include "bi2x_hook.h"
#if SPICE64
#include <cstdint>
#include "util/detour.h"
#include "util/logging.h"
@@ -16,15 +18,19 @@ namespace games::ccj {
*/
struct AIO_SCI_COMM {
uint8_t data[0xf8];
};
struct AIO_NMGR_IOB2 {
uint8_t data[0xe30];
};
struct AIO_IOB2_BI2X_TBS {
uint8_t data[0x4680];
};
struct AIO_IOB2_BI2X_WRFIRM {
uint8_t data[0x20f48];
};
struct AIO_NMGR_IOB__NODEINFO {
@@ -72,6 +78,12 @@ namespace games::ccj {
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);
/*
@@ -621,4 +633,6 @@ namespace games::ccj {
detour::trampoline_try(libaioDll, "aioNodeCtl_IsError",
aioNodeCtl_IsError, &aioNodeCtl_IsError_orig);
}
}
}
#endif
+5 -1
View File
@@ -1,5 +1,9 @@
#pragma once
#if SPICE64
namespace games::ccj {
void bi2x_hook_init();
}
}
#endif
+4
View File
@@ -68,9 +68,13 @@ namespace games::ccj {
detour::trampoline_try("execexe.dll", MAKEINTRESOURCE(11),
(void*)execexe_CreateFileW_hook,(void**)&execexe_CreateFileW_orig);
#if SPICE64
// insert BI2X hooks
bi2x_hook_init();
#endif
// insert trackball hooks
trackball_hook_init();
+25 -10
View File
@@ -8,6 +8,7 @@
#include "hooks/setupapihook.h"
#include "hooks/sleephook.h"
#include "hooks/input/dinput8/hook.h"
#include "util/deferlog.h"
#include "util/utils.h"
#include "util/libutils.h"
#include "util/fileutils.h"
@@ -108,18 +109,32 @@ namespace games::ddr {
log_info("ddr", "`{}` returned {}", cmd, result);
} else {
log_warning("ddr", "`{}` failed, returned {}", cmd, result);
deferredlogs::defer_error_messages({
"DDR codec registration failure",
fmt::format(" {} failed to register with error {}", filename.string(), result)});
}
if (!contains_only_ascii(file.path().string())) {
log_warning(
"ddr",
"BAD PATH ERROR\n\n\n"
"!!! !!!\n"
"!!! filesystem path to codec contains non-ASCII characters! !!!\n"
"!!! this may cause the game to crash! !!!\n"
"!!! !!!\n"
);
}
static std::once_flag printed;
std::call_once(printed, [file]() {
if (!contains_only_ascii(file.path().string())) {
log_warning(
"ddr",
"BAD PATH ERROR\n\n"
"!!! !!!\n"
"!!! filesystem path to codec contains non-ASCII characters! !!!\n"
"!!! this may cause failures when loading codecs, and !!!\n"
"!!! potentially leading the game to crash! !!!\n"
"!!! !!!\n"
);
deferredlogs::defer_error_messages({
"path to DDR codecs contain non-Latin characters",
" this may lead to movies not playing, white screen issue,",
" game hanging, or potentially a crash",
" move the game to a directory that does not contain non-Latin characters",
" if you encountered any issues"});
}
});
}
}
}
+114 -110
View File
@@ -41,136 +41,140 @@ std::vector<Light> &games::ddr::get_lights() {
if (lights.empty()) {
lights = GameAPI::Lights::getLights("Dance Dance Revolution");
GameAPI::Lights::sortLights(
&lights,
"P1 Foot Left",
"P1 Foot Up",
"P1 Foot Right",
"P1 Foot Down",
"P2 Foot Left",
"P2 Foot Up",
"P2 Foot Right",
"P2 Foot Down",
"Spot Red",
"Spot Blue",
"Top Spot Red",
"Top Spot Blue",
"P1 Halogen Upper",
"P1 Halogen Lower",
"P2 Halogen Upper",
"P2 Halogen Lower",
"P1 Button",
"P2 Button",
"Neon",
"HD P1 Start",
"HD P1 Menu Left-Right",
"HD P1 Menu Up-Down",
"HD P2 Start",
"HD P2 Menu Left-Right",
"HD P2 Menu Up-Down",
"HD P1 Speaker F R",
"HD P1 Speaker F G",
"HD P1 Speaker F B",
"HD P1 Speaker W R",
"HD P1 Speaker W G",
"HD P1 Speaker W B",
"HD P2 Speaker F R",
"HD P2 Speaker F G",
"HD P2 Speaker F B",
"HD P2 Speaker W R",
"HD P2 Speaker W G",
"HD P2 Speaker W B",
GameAPI::Lights::sortLightsWithCategory(
&lights,
{
{"General", "P1 Foot Left"},
{"General", "P1 Foot Up"},
{"General", "P1 Foot Right"},
{"General", "P1 Foot Down"},
{"General", "P2 Foot Left"},
{"General", "P2 Foot Up"},
{"General", "P2 Foot Right"},
{"General", "P2 Foot Down"},
"WHITE Speaker Top R",
"WHITE Speaker Top G",
"WHITE Speaker Top B",
"WHITE Speaker Bottom R",
"WHITE Speaker Bottom G",
"WHITE Speaker Bottom B",
"WHITE Woofer R",
"WHITE Woofer G",
"WHITE Woofer B",
{"SD", "Spot Red"},
{"SD", "Spot Blue"},
{"SD", "Top Spot Red"},
{"SD", "Top Spot Blue"},
{"SD", "P1 Halogen Upper"},
{"SD", "P1 Halogen Lower"},
{"SD", "P2 Halogen Upper"},
{"SD", "P2 Halogen Lower"},
{"SD", "P1 Button"},
{"SD", "P2 Button"},
{"SD", "Neon"},
"GOLD P1 Menu Start",
"GOLD P1 Menu Up",
"GOLD P1 Menu Down",
"GOLD P1 Menu Left",
"GOLD P1 Menu Right",
{"HD", "HD P1 Start"},
{"HD", "HD P1 Menu Left-Right"},
{"HD", "HD P1 Menu Up-Down"},
{"HD", "HD P2 Start"},
{"HD", "HD P2 Menu Left-Right"},
{"HD", "HD P2 Menu Up-Down"},
{"HD", "HD P1 Speaker F R"},
{"HD", "HD P1 Speaker F G"},
{"HD", "HD P1 Speaker F B"},
{"HD", "HD P1 Speaker W R"},
{"HD", "HD P1 Speaker W G"},
{"HD", "HD P1 Speaker W B"},
{"HD", "HD P2 Speaker F R"},
{"HD", "HD P2 Speaker F G"},
{"HD", "HD P2 Speaker F B"},
{"HD", "HD P2 Speaker W R"},
{"HD", "HD P2 Speaker W G"},
{"HD", "HD P2 Speaker W B"},
"GOLD P2 Menu Start",
"GOLD P2 Menu Up",
"GOLD P2 Menu Down",
"GOLD P2 Menu Left",
"GOLD P2 Menu Right",
{"WHITE", "WHITE Speaker Top R"},
{"WHITE", "WHITE Speaker Top G"},
{"WHITE", "WHITE Speaker Top B"},
{"WHITE", "WHITE Speaker Bottom R"},
{"WHITE", "WHITE Speaker Bottom G"},
{"WHITE", "WHITE Speaker Bottom B"},
{"WHITE", "WHITE Woofer R"},
{"WHITE", "WHITE Woofer G"},
{"WHITE", "WHITE Woofer B"},
"GOLD Title Panel Left",
"GOLD Title Panel Center",
"GOLD Title Panel Right",
{"GOLD", "GOLD P1 Menu Start"},
{"GOLD", "GOLD P1 Menu Up"},
{"GOLD", "GOLD P1 Menu Down"},
{"GOLD", "GOLD P1 Menu Left"},
{"GOLD", "GOLD P1 Menu Right"},
"GOLD P1 Woofer Corner",
"GOLD P2 Woofer Corner",
{"GOLD", "GOLD P2 Menu Start"},
{"GOLD", "GOLD P2 Menu Up"},
{"GOLD", "GOLD P2 Menu Down"},
{"GOLD", "GOLD P2 Menu Left"},
{"GOLD", "GOLD P2 Menu Right"},
"GOLD P1 Card Unit R",
"GOLD P1 Card Unit G",
"GOLD P1 Card Unit B",
{"GOLD", "GOLD Title Panel Left"},
{"GOLD", "GOLD Title Panel Center"},
{"GOLD", "GOLD Title Panel Right"},
"GOLD P2 Card Unit R",
"GOLD P2 Card Unit G",
"GOLD P2 Card Unit B",
{"GOLD", "GOLD P1 Woofer Corner"},
{"GOLD", "GOLD P2 Woofer Corner"},
"GOLD Top Panel Avg R",
"GOLD Top Panel Avg G",
"GOLD Top Panel Avg B",
{"GOLD", "GOLD P1 Card Unit R"},
{"GOLD", "GOLD P1 Card Unit G"},
{"GOLD", "GOLD P1 Card Unit B"},
"GOLD Monitor Side Left Avg R",
"GOLD Monitor Side Left Avg G",
"GOLD Monitor Side Left Avg B",
{"GOLD", "GOLD P2 Card Unit R"},
{"GOLD", "GOLD P2 Card Unit G"},
{"GOLD", "GOLD P2 Card Unit B"},
"GOLD Monitor Side Right Avg R",
"GOLD Monitor Side Right Avg G",
"GOLD Monitor Side Right Avg B",
{"GOLD", "GOLD Top Panel Avg R"},
{"GOLD", "GOLD Top Panel Avg G"},
{"GOLD", "GOLD Top Panel Avg B"},
"GOLD P1 Foot Up Avg R",
"GOLD P1 Foot Up Avg G",
"GOLD P1 Foot Up Avg B",
{"GOLD", "GOLD Monitor Side Left Avg R"},
{"GOLD", "GOLD Monitor Side Left Avg G"},
{"GOLD", "GOLD Monitor Side Left Avg B"},
"GOLD P1 Foot Down Avg R",
"GOLD P1 Foot Down Avg G",
"GOLD P1 Foot Down Avg B",
{"GOLD", "GOLD Monitor Side Right Avg R"},
{"GOLD", "GOLD Monitor Side Right Avg G"},
{"GOLD", "GOLD Monitor Side Right Avg B"},
"GOLD P1 Foot Left Avg R",
"GOLD P1 Foot Left Avg G",
"GOLD P1 Foot Left Avg B",
{"GOLD", "GOLD P1 Foot Up Avg R"},
{"GOLD", "GOLD P1 Foot Up Avg G"},
{"GOLD", "GOLD P1 Foot Up Avg B"},
"GOLD P1 Foot Right Avg R",
"GOLD P1 Foot Right Avg G",
"GOLD P1 Foot Right Avg B",
{"GOLD", "GOLD P1 Foot Down Avg R"},
{"GOLD", "GOLD P1 Foot Down Avg G"},
{"GOLD", "GOLD P1 Foot Down Avg B"},
"GOLD P2 Foot Up Avg R",
"GOLD P2 Foot Up Avg G",
"GOLD P2 Foot Up Avg B",
{"GOLD", "GOLD P1 Foot Left Avg R"},
{"GOLD", "GOLD P1 Foot Left Avg G"},
{"GOLD", "GOLD P1 Foot Left Avg B"},
"GOLD P2 Foot Down Avg R",
"GOLD P2 Foot Down Avg G",
"GOLD P2 Foot Down Avg B",
{"GOLD", "GOLD P1 Foot Right Avg R"},
{"GOLD", "GOLD P1 Foot Right Avg G"},
{"GOLD", "GOLD P1 Foot Right Avg B"},
"GOLD P2 Foot Left Avg R",
"GOLD P2 Foot Left Avg G",
"GOLD P2 Foot Left Avg B",
{"GOLD", "GOLD P2 Foot Up Avg R"},
{"GOLD", "GOLD P2 Foot Up Avg G"},
{"GOLD", "GOLD P2 Foot Up Avg B"},
"GOLD P2 Foot Right Avg R",
"GOLD P2 Foot Right Avg G",
"GOLD P2 Foot Right Avg B",
{"GOLD", "GOLD P2 Foot Down Avg R"},
{"GOLD", "GOLD P2 Foot Down Avg G"},
{"GOLD", "GOLD P2 Foot Down Avg B"},
"GOLD P1 Stage Corner Up-Left",
"GOLD P1 Stage Corner Up-Right",
"GOLD P1 Stage Corner Down-Left",
"GOLD P1 Stage Corner Down-Right",
{"GOLD", "GOLD P2 Foot Left Avg R"},
{"GOLD", "GOLD P2 Foot Left Avg G"},
{"GOLD", "GOLD P2 Foot Left Avg B"},
"GOLD P2 Stage Corner Up-Left",
"GOLD P2 Stage Corner Up-Right",
"GOLD P2 Stage Corner Down-Left",
"GOLD P2 Stage Corner Down-Right"
{"GOLD", "GOLD P2 Foot Right Avg R"},
{"GOLD", "GOLD P2 Foot Right Avg G"},
{"GOLD", "GOLD P2 Foot Right Avg B"},
{"GOLD", "GOLD P1 Stage Corner Up-Left"},
{"GOLD", "GOLD P1 Stage Corner Up-Right"},
{"GOLD", "GOLD P1 Stage Corner Down-Left"},
{"GOLD", "GOLD P1 Stage Corner Down-Right"},
{"GOLD", "GOLD P2 Stage Corner Up-Left"},
{"GOLD", "GOLD P2 Stage Corner Up-Right"},
{"GOLD", "GOLD P2 Stage Corner Down-Left"},
{"GOLD", "GOLD P2 Stage Corner Down-Right"}
}
);
}
+1 -1
View File
@@ -282,7 +282,7 @@ int games::ddr::DDRP3IOHandle::write(LPCVOID lpBuffer, DWORD nNumberOfBytesToWri
}
// use both sat spots for a neon pulse
float value_neon = (light_bits & hd_mapping_bits[0] && hd_mapping_bits[1]) ? 1.f : 0.f;
float value_neon = (light_bits & hd_mapping_bits[0] && light_bits & hd_mapping_bits[1]) ? 1.f : 0.f;
GameAPI::Lights::writeLight(RI_MGR, lights[Lights::NEON], value_neon);
}
+5 -8
View File
@@ -253,28 +253,25 @@ namespace games::ddr {
if(((dwIoControlCode>>16)&0xFFFF) == FILE_DEVICE_UNKNOWN) {
switch((dwIoControlCode>>2)&0x3FFF) {
case P4IO_IOCTL_GET_INPUTS: {
// Prevents this function from being called at its normal 2000-2500 kHz cadence and overloading the CPU, instead reduces it to 250 Hz
Sleep(4);
memset(lpOutBuffer, 0, 16);
auto controls = (uint32_t*) lpOutBuffer;
const std::pair<size_t, uint8_t> buttonMapping[] =
{
// Panel input comes from MDXF Control Status Buffer functions
{Buttons::SERVICE, 25}, //Good
{Buttons::TEST, 28}, //Good
{Buttons::COIN_MECH, 24}, //Good
{Buttons::P1_START, 0}, //Good
{Buttons::P1_PANEL_UP, 5}, //Good
{Buttons::P1_PANEL_DOWN, 6}, //Good
{Buttons::P1_PANEL_LEFT, 7}, //Goood
{Buttons::P1_PANEL_RIGHT, 16}, //Good
{Buttons::P1_MENU_UP, 1}, //Good
{Buttons::P1_MENU_DOWN, 2}, //Good
{Buttons::P1_MENU_LEFT, 3}, //Good
{Buttons::P1_MENU_RIGHT, 4}, //Good
{Buttons::P2_START, 8}, //Good
{Buttons::P2_PANEL_UP, 13}, //Good
{Buttons::P2_PANEL_DOWN, 14}, //Good
{Buttons::P2_PANEL_LEFT, 15}, //Good
{Buttons::P2_PANEL_RIGHT, 20}, //Good
{Buttons::P2_MENU_UP, 9}, //Good
{Buttons::P2_MENU_DOWN, 10}, //Good
{Buttons::P2_MENU_LEFT, 11}, //Good
+6
View File
@@ -8,6 +8,7 @@
#include "util/detour.h"
#include "util/logging.h"
#include "util/memutils.h"
#include "rgb_cam.h"
#pragma pack(push)
@@ -150,6 +151,7 @@ namespace games::drs {
char DRS_TAPELED[DRS_TAPELED_COLS_TOTAL][3] {};
bool DISABLE_TOUCH = false;
bool TRANSPOSE_TOUCH = false;
bool RGB_CAMERA_HOOK = false;
std::vector<TouchEvent> TOUCH_EVENTS;
@@ -313,6 +315,10 @@ namespace games::drs {
} else {
log_info("drs", "no native input method detected");
}
if (RGB_CAMERA_HOOK) {
init_rgb_camera_hook();
}
}
void DRSGame::detach() {
+1
View File
@@ -31,6 +31,7 @@ namespace games::drs {
extern std::vector<TouchEvent> TOUCH_EVENTS;
extern bool DISABLE_TOUCH;
extern bool TRANSPOSE_TOUCH;
extern bool RGB_CAMERA_HOOK;
void fire_touches(drs_touch_t *events, size_t event_count);
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();
}
+722
View File
@@ -0,0 +1,722 @@
#include "bi2x_hook.h"
#if SPICE64
#include <optional>
#include <cstdint>
#include "util/detour.h"
#include "util/logging.h"
#include "rawinput/rawinput.h"
#include "misc/eamuse.h"
#include "games/io.h"
#include "io.h"
#include "util/tapeled.h"
#include <typeinfo>
namespace games::gitadora {
// /*
// * class definitions
// */
struct AIO_NMGR_IOB2 {
uint8_t data[0xe18];
};
struct AIO_IOB2_BI2X_AC1__SETTING {
};
struct AIO_NMGR_IOB5 {
uint8_t data[0xb10];
};
struct AIO_SCI_COMM {
uint8_t data[0x100];
};
struct AIO_IOB5_Y32D {
uint8_t data[0x2e8];
};
// struct AIO_IOB5_Y33I {
// };
struct AIO_IOB2_BI2X_AC1 {
uint8_t data[0x4570];
};
struct AIO_NMGR_IOB__NODEINFO {
uint8_t data[0xA8];
};
struct AIO_IOB2_BI2X_WRFIRM {
uint8_t data[0x20f48];
};
struct AIO_SCI_COMM_T {
uint64_t unk_0;
struct AIO_SCI_COMM *aioSciComm;
};
struct AIO_COMM_STATUS {
uint64_t unk[5];
};
struct AIO_IOB2_BI2X_AC1__INPUT {
uint8_t DevIoCounter;
uint8_t bExIoAErr;
uint8_t bExIoBErr;
uint8_t bPcPowerOn;
uint8_t bPcPowerCheck;
uint8_t bCoin1Jam;
uint8_t bCoin2Jam;
uint8_t bCoin3Jam;
uint8_t bCoin4Jam;
uint8_t Coin1Count;
uint8_t Coin2Count;
uint8_t Coin3Count;
uint8_t Coin4Count;
uint16_t AnalogCh1;
uint16_t AnalogCh2;
uint16_t AnalogCh3;
uint16_t AnalogCh4;
uint8_t CN8_8;
uint8_t CN8_9;
uint8_t CN8_10;
uint8_t CN9_8;
uint8_t CN9_9;
uint8_t CN9_10;
uint8_t CN11_11;
uint8_t CN11_12;
uint8_t CN11_13;
uint8_t CN11_14;
uint8_t CN11_15;
uint8_t CN11_16;
uint8_t CN11_17;
uint8_t CN11_18;
uint8_t CN11_19;
uint8_t CN11_20;
uint8_t CN12_11;
uint8_t CN12_12;
uint8_t CN12_13;
uint8_t CN12_14;
uint8_t CN12_15;
uint8_t CN12_16;
uint8_t CN12_17;
uint8_t CN12_18;
uint8_t CN12_19;
uint8_t CN12_20;
uint8_t CN12_21;
uint8_t CN12_22;
uint8_t CN12_23;
uint8_t CN12_24;
uint8_t CN15_3;
uint8_t CN15_4;
uint8_t CN15_5;
uint8_t CN15_6;
uint8_t CN15_7;
uint8_t CN15_8;
uint8_t CN15_9;
uint8_t CN15_10;
uint8_t CN15_11;
uint8_t CN15_12;
uint8_t CN15_13;
uint8_t CN15_14;
uint8_t CN15_15;
uint8_t CN15_16;
uint8_t CN15_17;
uint8_t CN15_18;
uint8_t CN15_19;
uint8_t CN15_20;
uint8_t CN19_8;
uint8_t CN19_9;
uint8_t CN19_10;
uint8_t CN19_11;
uint8_t CN19_12;
uint8_t CN19_13;
uint8_t CN19_14;
uint8_t CN19_15;
};
struct AIO_IOB2_BI2X_AC1__INPUTDATA {
uint8_t data[247];
};
struct AIO_IOB2_BI2X_AC1__OUTPUTDATA {
uint8_t data[48];
};
struct AIO_IOB2_BI2X_AC1__ICNPIN {
uint16_t Ain[4];
uint64_t CnPin;
};
struct AIO_IOB2_BI2X_AC1__DEVSTATUS {
uint8_t InputCounter;
uint8_t OutputCounter;
uint8_t IoResetCounter;
uint8_t TapeLedCounter;
uint8_t TapeLedRate[8];
AIO_IOB2_BI2X_AC1__INPUT Input;
AIO_IOB2_BI2X_AC1__INPUTDATA InputData;
AIO_IOB2_BI2X_AC1__OUTPUTDATA OutputData;
AIO_IOB2_BI2X_AC1__ICNPIN ICnPinHist[20];
};
struct AIO_IOB5_Y32D__DEVSTATUS {
uint8_t unk[0xB1];
};
// 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
*/
// libaio-iob2_video.dll
typedef AIO_IOB2_BI2X_AC1 *(__fastcall *aioIob2Bi2xAC1_Create_t)(AIO_NMGR_IOB2 *i_pNodeMgr, uint32_t i_DevId,
AIO_IOB2_BI2X_AC1__SETTING *i_Setting);
typedef void(__fastcall *aioIob2Bi2xAC1_GetDeviceStatus_t)(AIO_IOB2_BI2X_AC1 *i_pNodeCtl,
AIO_IOB2_BI2X_AC1__DEVSTATUS *o_DevStatus);
typedef void(__fastcall *aioIob2Bi2xAC1_SetWatchDogTimer_t)(AIO_IOB2_BI2X_AC1 *i_pNodeCtl, uint8_t i_Count);
typedef void(__fastcall *aioIob2Bi2xAC1_AddCounter_t)(AIO_IOB2_BI2X_AC1 *i_pNodeCtl, uint32_t i_Counter,
uint32_t i_Count);
typedef void(__fastcall *aioIob2Bi2xAC1_SetOutputData_t)(AIO_IOB2_BI2X_AC1 *i_pNodeCtl, uint32_t i_CnPin,
uint8_t i_Data);
typedef void(__fastcall *aioIob2Bi2xAC1_SetTapeLedDataPart_t)(AIO_IOB2_BI2X_AC1 *i_pNodeCtl, uint32_t i_TapeLedCh,
uint32_t i_Offset, uint8_t *i_pData,
uint32_t i_cntTapeLed, bool i_bReverse);
typedef void(__fastcall *aioIob2Bi2x_SetTapeLedDataLimit_t)(AIO_IOB2_BI2X_AC1 *i_pNodeCtl, uint32_t i_Channel,
uint8_t i_Scale, uint8_t i_Limit);
typedef AIO_IOB2_BI2X_WRFIRM *(__fastcall *aioIob2Bi2x_CreateWriteFirmContext_t)(uint32_t i_SerialNumber,
uint32_t i_bfIob);
typedef void(__fastcall *aioIob2Bi2x_DestroyWriteFirmContext_t)(AIO_IOB2_BI2X_WRFIRM *i_pWrFirm);
typedef int32_t(__fastcall *aioIob2Bi2x_WriteFirmGetState_t)(AIO_IOB2_BI2X_WRFIRM *i_pWrFirm);
typedef bool(__fastcall *aioIob2Bi2x_WriteFirmIsCompleted_t)(int32_t i_State);
// libaio-iob.dll
typedef AIO_NMGR_IOB2 *(__fastcall *aioNMgrIob2_Create_t)(AIO_SCI_COMM *i_pSci, uint32_t i_bfMode);
typedef void(__fastcall *aioNMgrIob_BeginManage_t)(AIO_NMGR_IOB2 *i_pNodeMgr);
typedef void(__fastcall *aioNCtlIob_GetNodeInfo_t)(AIO_IOB5_Y32D *i_pNodeCtl,
AIO_NMGR_IOB__NODEINFO *o_NodeInfo);
// libaio.dll
typedef AIO_SCI_COMM_T *(__fastcall *aioSciComm_Open_t)(AIO_SCI_COMM_T *unk);
typedef void(__fastcall *aioSci_Destroy_t)(AIO_SCI_COMM *i_pNodeMgr);
typedef void(__fastcall *aioSci_GetCommStatus_t)(AIO_SCI_COMM *i_pNodeMgr, AIO_COMM_STATUS *i_Status);
typedef void(__fastcall *aioNodeMgr_Destroy_t)(AIO_NMGR_IOB5 *i_pNodeMgr);
typedef int32_t(__fastcall *aioNodeMgr_GetState_t)(AIO_NMGR_IOB5 *i_pNodeMgr);
typedef bool(__fastcall *aioNodeMgr_IsReady_t)(AIO_NMGR_IOB5 *i_pNodeMgr, int32_t i_State);
typedef bool(__fastcall *aioNodeMgr_IsError_t)(AIO_NMGR_IOB5 *i_pNodeMgr, int32_t i_State);
typedef void(__fastcall *aioNodeCtl_Destroy_t)(AIO_IOB5_Y32D *i_pNodeCtl);
typedef int32_t(__fastcall *aioNodeCtl_GetState_t)(AIO_IOB5_Y32D *i_pNodeCtl);
typedef bool(__fastcall *aioNodeCtl_IsReady_t)(AIO_IOB5_Y32D *i_pNodeCtl, int32_t i_State);
typedef bool(__fastcall *aioNodeCtl_IsError_t)(AIO_IOB5_Y32D *i_pNodeCtl, int32_t i_State);
typedef void(__fastcall *aioNodeCtl_UpdateDevicesStatus_t)();
// libaio-iob5.dll
typedef AIO_NMGR_IOB5 *(__fastcall *aioNMgrIob5_Create_t)(AIO_SCI_COMM *i_pSci, uint32_t i_bfMode);
// libaio-iob5_y32.dll
typedef AIO_IOB5_Y32D *(__fastcall *aioIob5Y32d_Create_t)(AIO_NMGR_IOB5 *i_pSci, uint32_t i_bfMode);
// typedef AIO_IOB5_Y33I *(__fastcall *aioIob5Y33i_Create_t)(AIO_NMGR_IOB5 *i_pSci, uint32_t i_bfMode);
typedef void(__fastcall *aioIob5Y32d_GetDeviceStatus_t)(AIO_IOB5_Y32D *i_pNodeCtl,
AIO_IOB5_Y32D__DEVSTATUS *o_DevStatus);
// typedef void(__fastcall *aioIob5Y33i_GetDeviceStatus_t)(AIO_IOB5_Y33I *i_pNodeCtl,
// AIO_IOB2_BI2X_AC1__DEVSTATUS *o_DevStatus);
/*
* function pointers
*/
// libaio-iob2_video.dll
static aioIob2Bi2xAC1_Create_t aioIob2Bi2xAC1_Create_orig = nullptr;
static aioIob2Bi2xAC1_GetDeviceStatus_t aioIob2Bi2xAC1_GetDeviceStatus_orig = nullptr;
static aioIob2Bi2xAC1_SetWatchDogTimer_t aioIob2Bi2xAC1_SetWatchDogTimer_orig = nullptr;
static aioIob2Bi2xAC1_AddCounter_t aioIob2Bi2xAC1_AddCounter_orig = nullptr;
static aioIob2Bi2xAC1_SetOutputData_t aioIob2Bi2xAC1_SetOutputData_orig = nullptr;
static aioIob2Bi2xAC1_SetTapeLedDataPart_t aioIob2Bi2xAC1_SetTapeLedDataPart_orig = nullptr;
static aioIob2Bi2x_SetTapeLedDataLimit_t aioIob2Bi2x_SetTapeLedDataLimit_orig = nullptr;
static aioIob2Bi2x_CreateWriteFirmContext_t aioIob2Bi2x_CreateWriteFirmContext_orig = nullptr;
static aioIob2Bi2x_DestroyWriteFirmContext_t aioIob2Bi2x_DestroyWriteFirmContext_orig = nullptr;
static aioIob2Bi2x_WriteFirmGetState_t aioIob2Bi2x_WriteFirmGetState_orig = nullptr;
static aioIob2Bi2x_WriteFirmIsCompleted_t aioIob2Bi2x_WriteFirmIsCompleted_orig = nullptr;
// libaio.dll
static aioSciComm_Open_t aioSciComm_Open_orig = nullptr;
static aioSci_Destroy_t aioSci_Destroy_orig = nullptr;
static aioSci_GetCommStatus_t aioSci_GetCommStatus_orig = nullptr;
static aioNodeMgr_Destroy_t aioNodeMgr_Destroy_orig = nullptr;
static aioNodeMgr_GetState_t aioNodeMgr_GetState_orig = nullptr;
static aioNodeMgr_IsReady_t aioNodeMgr_IsReady_orig = nullptr;
static aioNodeMgr_IsError_t aioNodeMgr_IsError_orig = nullptr;
static aioNodeCtl_Destroy_t aioNodeCtl_Destroy_orig = nullptr;
static aioNodeCtl_GetState_t aioNodeCtl_GetState_orig = nullptr;
static aioNodeCtl_IsReady_t aioNodeCtl_IsReady_orig = nullptr;
static aioNodeCtl_IsError_t aioNodeCtl_IsError_orig = nullptr;
static aioNodeCtl_UpdateDevicesStatus_t aioNodeCtl_UpdateDevicesStatus_orig = nullptr;
// libaio-iob5.dll
static aioNMgrIob5_Create_t aioNMgrIob5_Create_orig = nullptr;
// libaio-iob.dll
static aioNMgrIob2_Create_t aioNMgrIob2_Create_orig = nullptr;
static aioNMgrIob_BeginManage_t aioNMgrIob_BeginManage_orig = nullptr;
static aioNCtlIob_GetNodeInfo_t aioNCtlIob_GetNodeInfo_orig = nullptr;
// libaio-iob5_y32.dll
static aioIob5Y32d_Create_t aioIob5Y32d_Create_orig = nullptr;
static aioIob5Y32d_GetDeviceStatus_t aioIob5Y32d_GetDeviceStatus_orig = nullptr;
/*
* variables
*/
static AIO_IOB2_BI2X_AC1 *aioIob2Bi2xAc1;
static AIO_NMGR_IOB2 *aioNmgrIob2;
static AIO_NMGR_IOB5 *aioNmgrIob5;
static AIO_IOB5_Y32D *aioIob5Y32d;
static AIO_SCI_COMM *aioSciComm;
static AIO_IOB2_BI2X_WRFIRM *aioIob2Bi2xWrfirm;
/*
* implementations
*/
// libaio-iob2_video.dll
static AIO_IOB2_BI2X_AC1 *__fastcall aioIob2Bi2xAC1_Create(AIO_NMGR_IOB2 *i_pNodeMgr, uint32_t i_DevId,
AIO_IOB2_BI2X_AC1__SETTING *i_Setting) {
if (i_pNodeMgr == aioNmgrIob2) {
aioIob2Bi2xAc1 = new AIO_IOB2_BI2X_AC1;
return aioIob2Bi2xAc1;
} else {
return aioIob2Bi2xAC1_Create_orig(i_pNodeMgr, i_DevId, i_Setting);
}
}
void __fastcall aioIob2Bi2xAC1_GetDeviceStatus(AIO_IOB2_BI2X_AC1 *i_pNodeCtl,
AIO_IOB2_BI2X_AC1__DEVSTATUS *o_DevStatus) {
RI_MGR->devices_flush_output();
if (i_pNodeCtl != aioIob2Bi2xAc1) {
return aioIob2Bi2xAC1_GetDeviceStatus_orig(i_pNodeCtl, o_DevStatus);
}
memset(o_DevStatus, 0x00, sizeof(AIO_IOB2_BI2X_AC1__DEVSTATUS));
auto &buttons = get_buttons();
// struct may be misaligned
o_DevStatus->Input.CN8_10 = GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::Test]) ? 0 : 0xFF;
o_DevStatus->Input.CN9_8 = GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::Service]) ? 0 : 0xFF;
o_DevStatus->Input.CN9_9 = GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::Coin]) ? 0 : 0xFF;
o_DevStatus->Input.CN12_14 = GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::Headphone]) ? 0xFF : 0;
// coin
o_DevStatus->Input.Coin1Count = eamuse_coin_get_stock();
}
void __fastcall aioIob2Bi2xAC1_SetWatchDogTimer(AIO_IOB2_BI2X_AC1 *i_pNodeCtl, uint8_t i_Count) {
if (i_pNodeCtl != aioIob2Bi2xAc1) {
return aioIob2Bi2xAC1_SetWatchDogTimer_orig(i_pNodeCtl, i_Count);
}
}
void __fastcall aioIob2Bi2xAC1_AddCounter(AIO_IOB2_BI2X_AC1 *i_pNodeCtl, uint32_t i_Counter, uint32_t i_Count) {
if (i_pNodeCtl == aioIob2Bi2xAc1 && i_Count == 0) {
eamuse_coin_set_stock((uint16_t)i_Count);
} else {
return aioIob2Bi2xAC1_AddCounter_orig(i_pNodeCtl, i_Counter, i_Count);
}
}
void __fastcall aioIob2Bi2xAC1_SetOutputData(AIO_IOB2_BI2X_AC1 *i_pNodeCtl, uint32_t i_CnPin, uint8_t i_Data) {
if (i_pNodeCtl != aioIob2Bi2xAc1) {
return aioIob2Bi2xAC1_SetOutputData_orig(i_pNodeCtl, i_CnPin, i_Data);
}
std::optional<Lights::gitadora_lights_t> light;
switch (i_CnPin) {
case 14:
light = Lights::ArenaWooferR;
break;
case 15:
light = Lights::ArenaWooferG;
break;
case 16:
light = Lights::ArenaWooferB;
break;
case 17:
light = Lights::ArenaCardR;
break;
case 18:
light = Lights::ArenaCardG;
break;
case 19:
light = Lights::ArenaCardB;
break;
default:
break;
}
if (light.has_value()) {
auto &lights = games::gitadora::get_lights();
GameAPI::Lights::writeLight(RI_MGR, lights.at(light.value()), i_Data / 255.f);
}
}
void __fastcall aioIob2Bi2xAC1_SetTapeLedDataPart(AIO_IOB2_BI2X_AC1 *i_pNodeCtl, uint32_t i_TapeLedCh,
uint32_t i_Offset, uint8_t *i_pData,
uint32_t i_cntTapeLed, bool i_bReverse) {
if (i_pNodeCtl != aioIob2Bi2xAc1) {
return aioIob2Bi2xAC1_SetTapeLedDataPart_orig(i_pNodeCtl, i_TapeLedCh, i_Offset, i_pData, i_cntTapeLed, i_bReverse);
}
if (tapeledutils::is_enabled() &&
i_TapeLedCh == 0 && i_Offset == 0 && i_cntTapeLed == 62) {
auto &lights = get_lights();
const auto rgb = tapeledutils::pick_color_from_led_tape(i_pData, i_cntTapeLed);
GameAPI::Lights::writeLight(RI_MGR, lights[Lights::ArenaTitleAvgR], rgb.r);
GameAPI::Lights::writeLight(RI_MGR, lights[Lights::ArenaTitleAvgG], rgb.g);
GameAPI::Lights::writeLight(RI_MGR, lights[Lights::ArenaTitleAvgB], rgb.b);
}
}
void __fastcall aioIob2Bi2x_SetTapeLedDataLimit(AIO_IOB2_BI2X_AC1 *i_pNodeCtl, uint32_t i_Channel,
uint8_t i_Scale, uint8_t i_Limit) {
if (i_pNodeCtl != aioIob2Bi2xAc1) {
return aioIob2Bi2x_SetTapeLedDataLimit_orig(i_pNodeCtl, i_Channel, i_Scale, i_Limit);
}
}
static AIO_IOB2_BI2X_WRFIRM *__fastcall aioIob2Bi2x_CreateWriteFirmContext(
uint32_t i_SerialNumber, uint32_t i_bfIob) {
aioIob2Bi2xWrfirm = new AIO_IOB2_BI2X_WRFIRM;
return aioIob2Bi2xWrfirm;
}
static void __fastcall aioIob2Bi2x_DestroyWriteFirmContext(AIO_IOB2_BI2X_WRFIRM *i_pWrFirm) {
if (i_pWrFirm == aioIob2Bi2xWrfirm) {
delete aioIob2Bi2xWrfirm;
aioIob2Bi2xWrfirm = nullptr;
} else {
return aioIob2Bi2x_DestroyWriteFirmContext_orig(i_pWrFirm);
}
}
static int32_t __fastcall aioIob2Bi2x_WriteFirmGetState(AIO_IOB2_BI2X_WRFIRM *i_pWrFirm) {
if (i_pWrFirm == aioIob2Bi2xWrfirm) {
return 8;
} else {
return aioIob2Bi2x_WriteFirmGetState_orig(i_pWrFirm);
}
}
static bool __fastcall aioIob2Bi2x_WriteFirmIsCompleted(int32_t i_State) {
if (aioIob2Bi2xWrfirm != nullptr) {
return true;
} else {
return aioIob2Bi2x_WriteFirmIsCompleted_orig(i_State);
}
}
// libaio-iob.dll
static AIO_NMGR_IOB2 *__fastcall aioNMgrIob2_Create(AIO_SCI_COMM *i_pSci, uint32_t i_bfMode) {
if (i_pSci == aioSciComm) {
aioNmgrIob2 = new AIO_NMGR_IOB2;
return aioNmgrIob2;
} else {
return aioNMgrIob2_Create_orig(i_pSci, i_bfMode);
}
}
static void __fastcall aioNMgrIob_BeginManage(AIO_NMGR_IOB2 *i_pNodeMgr) {
if (i_pNodeMgr == aioNmgrIob2) {
} else {
return aioNMgrIob_BeginManage_orig(i_pNodeMgr);
}
}
static void __fastcall aioNCtlIob_GetNodeInfo(
AIO_IOB5_Y32D *i_pNodeCtl, AIO_NMGR_IOB__NODEINFO *o_NodeInfo) {
if (i_pNodeCtl == aioIob5Y32d) {
memset(o_NodeInfo, 0, sizeof(AIO_NMGR_IOB__NODEINFO));
} else {
return aioNCtlIob_GetNodeInfo_orig(i_pNodeCtl, o_NodeInfo);
}
}
// libaio.dll
static AIO_SCI_COMM_T *__fastcall aioSciComm_Open(AIO_SCI_COMM_T *unk) {
aioSciComm = new AIO_SCI_COMM;
memset(unk, 0, sizeof(AIO_SCI_COMM_T));
unk->aioSciComm = aioSciComm;
return unk;
}
static void __fastcall aioSci_GetCommStatus(AIO_SCI_COMM *i_pNodeMgr, AIO_COMM_STATUS *i_Status) {
if (i_pNodeMgr == aioSciComm) {
memset(i_Status, 0, sizeof(AIO_COMM_STATUS));
} else {
return aioSci_GetCommStatus_orig(i_pNodeMgr, i_Status);
}
}
static void __fastcall aioSci_Destroy(AIO_SCI_COMM *i_pNodeMgr) {
if (i_pNodeMgr == aioSciComm) {
delete aioSciComm;
} else {
return aioSci_Destroy_orig(i_pNodeMgr);
}
}
static void __fastcall aioNodeMgr_Destroy(AIO_NMGR_IOB5 *i_pNodeMgr) {
if (i_pNodeMgr == aioNmgrIob5 ||
i_pNodeMgr == (AIO_NMGR_IOB5 *)aioIob2Bi2xAc1 ||
i_pNodeMgr == (AIO_NMGR_IOB5 *)aioNmgrIob2) {
delete aioNmgrIob5;
aioNmgrIob5 = nullptr;
} else {
return aioNodeMgr_Destroy_orig(i_pNodeMgr);
}
}
static int32_t __fastcall aioNodeMgr_GetState(AIO_NMGR_IOB5 *i_pNodeMgr) {
if (i_pNodeMgr == aioNmgrIob5 ||
i_pNodeMgr == (AIO_NMGR_IOB5 *)aioIob2Bi2xAc1 ||
i_pNodeMgr == (AIO_NMGR_IOB5 *)aioNmgrIob2) {
return 1;
} else {
return aioNodeMgr_GetState_orig(i_pNodeMgr);
}
}
static bool __fastcall aioNodeMgr_IsReady(AIO_NMGR_IOB5 *i_pNodeMgr, int32_t i_State) {
if (i_pNodeMgr == aioNmgrIob5 ||
i_pNodeMgr == (AIO_NMGR_IOB5 *)aioIob2Bi2xAc1 ||
i_pNodeMgr == (AIO_NMGR_IOB5 *)aioNmgrIob2) {
return true;
} else {
return aioNodeMgr_IsReady_orig(i_pNodeMgr, i_State);
}
}
static bool __fastcall aioNodeMgr_IsError(AIO_NMGR_IOB5 *i_pNodeMgr, int32_t i_State) {
if (i_pNodeMgr == aioNmgrIob5 ||
i_pNodeMgr == (AIO_NMGR_IOB5 *)aioIob2Bi2xAc1 ||
i_pNodeMgr == (AIO_NMGR_IOB5 *)aioNmgrIob2) {
return false;
} else {
return aioNodeMgr_IsError_orig(i_pNodeMgr, i_State);
}
}
static void __fastcall aioNodeCtl_Destroy(AIO_IOB5_Y32D *i_pNodeCtl) {
if (i_pNodeCtl == aioIob5Y32d ||
i_pNodeCtl == (AIO_IOB5_Y32D *)aioIob2Bi2xAc1 ||
i_pNodeCtl == (AIO_IOB5_Y32D *)aioNmgrIob2) {
delete aioIob5Y32d;
aioIob5Y32d = nullptr;
} else {
return aioNodeCtl_Destroy_orig(i_pNodeCtl);
}
}
static int32_t __fastcall aioNodeCtl_GetState(AIO_IOB5_Y32D *i_pNodeCtl) {
if (i_pNodeCtl == aioIob5Y32d ||
i_pNodeCtl == (AIO_IOB5_Y32D *)aioIob2Bi2xAc1 ||
i_pNodeCtl == (AIO_IOB5_Y32D *)aioNmgrIob2) {
return 1;
} else {
return aioNodeCtl_GetState_orig(i_pNodeCtl);
}
}
static bool __fastcall aioNodeCtl_IsReady(AIO_IOB5_Y32D *i_pNodeCtl, int32_t i_State) {
if (i_pNodeCtl == aioIob5Y32d ||
i_pNodeCtl == (AIO_IOB5_Y32D *)aioIob2Bi2xAc1 ||
i_pNodeCtl == (AIO_IOB5_Y32D *)aioNmgrIob2) {
return true;
} else {
return aioNodeCtl_IsReady_orig(i_pNodeCtl, i_State);
}
}
static bool __fastcall aioNodeCtl_IsError(AIO_IOB5_Y32D *i_pNodeCtl, int32_t i_State) {
if (i_pNodeCtl == aioIob5Y32d ||
i_pNodeCtl == (AIO_IOB5_Y32D *)aioIob2Bi2xAc1 ||
i_pNodeCtl == (AIO_IOB5_Y32D *)aioNmgrIob2) {
return false;
} else {
return aioNodeCtl_IsError_orig(i_pNodeCtl, i_State);
}
}
static void __fastcall aioNodeCtl_UpdateDevicesStatus() {
}
// libaio-iob5.dll
static AIO_NMGR_IOB5 *__fastcall aioNMgrIob5_Create(AIO_SCI_COMM *i_pSci, uint32_t i_bfMode) {
if (i_pSci == aioSciComm) {
aioNmgrIob5 = new AIO_NMGR_IOB5;
return aioNmgrIob5;
} else {
return aioNMgrIob5_Create_orig(i_pSci, i_bfMode);
}
}
// libaio-iob5_y32.dll
static AIO_IOB5_Y32D *__fastcall aioIob5Y32d_Create(AIO_NMGR_IOB5 *i_pSci, uint32_t i_bfMode) {
if (i_pSci == aioNmgrIob5) {
aioIob5Y32d = new AIO_IOB5_Y32D;
return aioIob5Y32d;
} else {
return aioIob5Y32d_Create_orig(i_pSci, i_bfMode);
}
}
void __fastcall aioIob5Y32d_GetDeviceStatus(AIO_IOB5_Y32D *i_pNodeCtl,
AIO_IOB5_Y32D__DEVSTATUS *o_DevStatus) {
if (i_pNodeCtl != aioIob5Y32d) {
return aioIob5Y32d_GetDeviceStatus_orig(i_pNodeCtl, o_DevStatus);
}
memset(o_DevStatus, 0, sizeof(AIO_IOB5_Y32D__DEVSTATUS));
}
void bi2x_hook_init() {
// avoid double init
static bool initialized = false;
if (initialized) {
return;
} else {
initialized = true;
}
// announce
log_info("bi2x_hook", "init");
// libaio-iob5_y32.dll
const auto libaioIob5Y32Dll = "libaio-iob5_y32.dll";
detour::trampoline_try(libaioIob5Y32Dll, "aioIob5Y32d_Create",
aioIob5Y32d_Create, &aioIob5Y32d_Create_orig);
detour::trampoline_try(libaioIob5Y32Dll, "aioIob5Y32i_Create",
aioIob5Y32d_Create, &aioIob5Y32d_Create_orig);
detour::trampoline_try(libaioIob5Y32Dll, "aioIob5Y32d_GetDeviceStatus",
aioIob5Y32d_GetDeviceStatus, &aioIob5Y32d_GetDeviceStatus_orig);
detour::trampoline_try(libaioIob5Y32Dll, "aioIob5Y32i_GetDeviceStatus",
aioIob5Y32d_GetDeviceStatus, &aioIob5Y32d_GetDeviceStatus_orig);
// libaio-iob5.dll
const auto libaioIob5Dll = "libaio-iob5.dll";
detour::trampoline_try(libaioIob5Dll, "aioNMgrIob5_Create",
aioNMgrIob5_Create, &aioNMgrIob5_Create_orig);
// libaio-iob2_video.dll
const auto libaioIob2VideoDll = "libaio-iob2_video.dll";
detour::trampoline_try(libaioIob2VideoDll, "aioIob2Bi2xAC1_Create",
aioIob2Bi2xAC1_Create, &aioIob2Bi2xAC1_Create_orig);
detour::trampoline_try(libaioIob2VideoDll, "aioIob2Bi2xAC1_GetDeviceStatus",
aioIob2Bi2xAC1_GetDeviceStatus, &aioIob2Bi2xAC1_GetDeviceStatus_orig);
detour::trampoline_try(libaioIob2VideoDll, "aioIob2Bi2xAC1_SetWatchDogTimer",
aioIob2Bi2xAC1_SetWatchDogTimer, &aioIob2Bi2xAC1_SetWatchDogTimer_orig);
detour::trampoline_try(libaioIob2VideoDll, "aioIob2Bi2xAC1_AddCounter",
aioIob2Bi2xAC1_AddCounter, &aioIob2Bi2xAC1_AddCounter_orig);
detour::trampoline_try(libaioIob2VideoDll, "aioIob2Bi2xAC1_SetOutputData",
aioIob2Bi2xAC1_SetOutputData, &aioIob2Bi2xAC1_SetOutputData_orig);
detour::trampoline_try(libaioIob2VideoDll, "aioIob2Bi2xAC1_SetTapeLedDataPart",
aioIob2Bi2xAC1_SetTapeLedDataPart, &aioIob2Bi2xAC1_SetTapeLedDataPart_orig);
detour::trampoline_try(libaioIob2VideoDll, "aioIob2Bi2x_SetTapeLedDataLimit",
aioIob2Bi2x_SetTapeLedDataLimit, &aioIob2Bi2x_SetTapeLedDataLimit_orig);
detour::trampoline_try(libaioIob2VideoDll, "?CreateWriteFirmContext@AIO_IOB2_BI2X@@SAPEAUWRFIRM@1@II@Z",
aioIob2Bi2x_CreateWriteFirmContext, &aioIob2Bi2x_CreateWriteFirmContext_orig);
detour::trampoline_try(libaioIob2VideoDll, "?DestroyWriteFirmContext@AIO_IOB2_BI2X@@SAXPEAUWRFIRM@1@@Z",
aioIob2Bi2x_DestroyWriteFirmContext, &aioIob2Bi2x_DestroyWriteFirmContext_orig);
detour::trampoline_try(libaioIob2VideoDll, "?WriteFirmGetState@AIO_IOB2_BI2X@@SAHPEAUWRFIRM@1@@Z",
aioIob2Bi2x_WriteFirmGetState, &aioIob2Bi2x_WriteFirmGetState_orig);
detour::trampoline_try(libaioIob2VideoDll, "?WriteFirmIsCompleted@AIO_IOB2_BI2X@@SA_NH@Z",
aioIob2Bi2x_WriteFirmIsCompleted, &aioIob2Bi2x_WriteFirmIsCompleted_orig);
// libaio-iob.dll
const auto libaioIobDll = "libaio-iob.dll";
detour::trampoline_try(libaioIobDll, "aioNMgrIob2_Create",
aioNMgrIob2_Create, &aioNMgrIob2_Create_orig);
detour::trampoline_try(libaioIobDll, "aioNMgrIob_BeginManage",
aioNMgrIob_BeginManage, &aioNMgrIob_BeginManage_orig);
detour::trampoline_try(libaioIobDll, "aioNCtlIob_GetNodeInfo",
aioNCtlIob_GetNodeInfo, &aioNCtlIob_GetNodeInfo_orig);
// libaio.dll
const auto libaioDll = "libaio.dll";
detour::trampoline_try(libaioDll, "?Open@AIO_SCI_COMM@@SA?AU?$AC_ERESULT_T@PEAVAIO_SCI_COMM@@@@PEBD0AEBUSETTING@1@@Z",
aioSciComm_Open, &aioSciComm_Open_orig);
detour::trampoline_try(libaioDll, "aioSci_GetCommStatus",
aioSci_GetCommStatus, &aioSci_GetCommStatus_orig);
detour::trampoline_try(libaioDll, "aioSci_Destroy",
aioSci_Destroy, &aioSci_Destroy_orig);
detour::trampoline_try(libaioDll, "aioNodeMgr_Destroy",
aioNodeMgr_Destroy, &aioNodeMgr_Destroy_orig);
detour::trampoline_try(libaioDll, "aioNodeMgr_GetState",
aioNodeMgr_GetState, &aioNodeMgr_GetState_orig);
detour::trampoline_try(libaioDll, "aioNodeMgr_IsReady",
aioNodeMgr_IsReady, &aioNodeMgr_IsReady_orig);
detour::trampoline_try(libaioDll, "aioNodeMgr_IsError",
aioNodeMgr_IsError, &aioNodeMgr_IsError_orig);
detour::trampoline_try(libaioDll, "aioNodeCtl_Destroy",
aioNodeCtl_Destroy, &aioNodeCtl_Destroy_orig);
detour::trampoline_try(libaioDll, "aioNodeCtl_GetState",
aioNodeCtl_GetState, &aioNodeCtl_GetState_orig);
detour::trampoline_try(libaioDll, "aioNodeCtl_IsReady",
aioNodeCtl_IsReady, &aioNodeCtl_IsReady_orig);
detour::trampoline_try(libaioDll, "aioNodeCtl_IsError",
aioNodeCtl_IsError, &aioNodeCtl_IsError_orig);
detour::trampoline_try(libaioDll, "aioNodeCtl_UpdateDevicesStatus",
aioNodeCtl_UpdateDevicesStatus, &aioNodeCtl_UpdateDevicesStatus_orig);
}
}
#endif
+9
View File
@@ -0,0 +1,9 @@
#pragma once
#if SPICE64
namespace games::gitadora {
void bi2x_hook_init();
}
#endif
+10
View File
@@ -0,0 +1,10 @@
#pragma once
#include "acioemu/acioemu.h"
namespace games::gitadora {
class GitadoraDevice : public acioemu::ACIODeviceEmu {
public:
bool is_ready = false;
};
}
+93 -2
View File
@@ -1,20 +1,56 @@
#include "gitadora.h"
#include "handle.h"
#include "bi2x_hook.h"
#include <unordered_map>
#include "cfg/configurator.h"
#include "hooks/audio/mme.h"
#include "hooks/graphics/graphics.h"
#include "misc/wintouchemu.h"
#include "overlay/overlay.h"
#include "util/cpuutils.h"
#include "util/detour.h"
#include "util/libutils.h"
#include "util/logging.h"
#include "util/sigscan.h"
#include "util/socd_cleaner.h"
#include "hooks/setupapihook.h"
namespace games::gitadora {
// settings
bool TWOCHANNEL = false;
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
*/
#ifdef SPICE64
static DWORD WINAPI GetLogicalDrives_hook() {
return GetLogicalDrives() | 0x20;
}
static UINT WINAPI GetDriveTypeA_hook(LPCSTR lpRootPathName) {
if (!strcmp(lpRootPathName, "F:\\")) {
return DRIVE_FIXED;
}
return GetDriveTypeA(lpRootPathName);
}
static BOOL WINAPI CreateDirectoryA_hook(LPCSTR lpPathName, LPSECURITY_ATTRIBUTES lpSecurityAttributes) {
if (!strncmp(lpPathName, "F:/", 3)) {
return TRUE;
}
return CreateDirectoryA(lpPathName, lpSecurityAttributes);
}
#endif
/*
* GitaDora checks if the IP address has changed, and if it has it throws 5-1506-0000 like jubeat.
@@ -184,6 +220,23 @@ namespace games::gitadora {
log_fatal("gitadora", "Cabinet type 3 (SD2) not supported on XG series");
}
#endif
// arena model launches a tiny window yet backbuffer at 4k, resulting in unusable overlay
// force scaling to make things usable
if (!overlay::UI_SCALE_PERCENT.has_value() && is_arena_model() &&
GRAPHICS_WINDOWED && !cfg::CONFIGURATOR_STANDALONE) {
overlay::UI_SCALE_PERCENT = 250;
}
// for guitar wail SOCD cleaning
socd::ALGORITHM = socd::SocdAlgorithm::PreferRecent;
// 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");
}
}
}
@@ -214,6 +267,44 @@ namespace games::gitadora {
system_module, "sys_debug_dip_set_param"));
#ifdef SPICE64
// gitadora arena model
auto aio = libutils::try_library("libaio.dll");
if (aio != nullptr) {
SETUPAPI_SETTINGS settings{};
settings.class_guid[0] = 0x86E0D1E0;
settings.class_guid[1] = 0x11D08089;
settings.class_guid[2] = 0x0008E49C;
settings.class_guid[3] = 0x731F303E;
const char property[] = "1CCF(8050)_000";
const char property_hardwareid[] = "USB\\VID_1CCF&PID_8050&MI_00\\000";
memcpy(settings.property_devicedesc, property, sizeof(property));
memcpy(settings.property_hardwareid, property_hardwareid, sizeof(property_hardwareid));
setupapihook_init(avs::game::DLL_INSTANCE);
setupapihook_add(settings);
// Gitadora IO(J32D/J33I) board emulation
devicehook_init(avs::game::DLL_INSTANCE);
devicehook_add(new GitaDoraSerialHandle());
// test/service/coin buttons
bi2x_hook_init();
// f: drive hook
detour::iat_try("GetLogicalDrives", GetLogicalDrives_hook, avs::game::DLL_INSTANCE);
detour::iat_try("GetDriveTypeA", GetDriveTypeA_hook, avs::game::DLL_INSTANCE);
detour::iat_try("CreateDirectoryA", CreateDirectoryA_hook, avs::game::DLL_INSTANCE);
// volume change prevention
hooks::audio::mme::init(avs::game::DLL_INSTANCE);
// touch hook
if (ARENA_SINGLE_WINDOW) {
wintouchemu::FORCE = true;
wintouchemu::INJECT_MOUSE_AS_WM_TOUCH = true;
wintouchemu::hook("GITADORA", avs::game::DLL_INSTANCE);
}
return;
}
HMODULE gdme_module = libutils::try_module("libgdme.dll");
+23 -2
View File
@@ -4,12 +4,18 @@
#include "avs/game.h"
#include "games/game.h"
#include "util/socd_cleaner.h"
namespace games::gitadora {
// settings
extern bool TWOCHANNEL;
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 {
public:
@@ -21,14 +27,29 @@ namespace games::gitadora {
static inline bool is_drum() {
return (
avs::game::is_model({ "J32", "K32", "L32" }) ||
(avs::game::is_model("M32") && avs::game::SPEC[0] == 'B')
(avs::game::is_model("M32") && (avs::game::SPEC[0] == 'B' || avs::game::SPEC[0] == 'D'))
);
}
static inline bool is_guitar() {
return (
avs::game::is_model({ "J33", "K33", "L33" }) ||
(avs::game::is_model("M32") && avs::game::SPEC[0] == 'A')
(avs::game::is_model("M32") && (avs::game::SPEC[0] == 'A' || avs::game::SPEC[0] == 'C'))
);
}
static inline bool is_arena_model() {
return (
avs::game::is_model("M32") &&
(avs::game::SPEC[0] == 'C' || avs::game::SPEC[0] == 'D')
);
}
static inline bool is_player_lefty(size_t player) {
if (player == 0) {
return P1_LEFTY;
} else if (player == 1) {
return P2_LEFTY;
}
return false;
}
}
+110
View File
@@ -0,0 +1,110 @@
#include "handle.h"
#include "misc/eamuse.h"
#include "rawinput/rawinput.h"
#include "util/utils.h"
#include "util/time.h"
#include "io.h"
#include "gitadora.h"
#include "j32d.h"
#include "j33i.h"
#include "device.h"
using namespace acioemu;
static games::gitadora::GitadoraDevice *device;
bool games::gitadora::GitaDoraSerialHandle::open(LPCWSTR lpFileName) {
if (wcscmp(lpFileName, L"COM1") != 0) {
return false;
}
bool is_drum = games::gitadora::is_drum();
if (is_drum) {
device = new J32DSerialDevice();
} else {
device = new J33ISerialDevice();
}
acio_emu.add_device(device);
log_info("gitadora", "Opened COM1 ({} DEVICE)", is_drum ? "J32D" : "J33I");
return true;
}
int games::gitadora::GitaDoraSerialHandle::read(LPVOID lpBuffer, DWORD nNumberOfBytesToRead) {
auto buffer = reinterpret_cast<uint8_t *>(lpBuffer);
// the game does not send cmd 0x12F to the IO
// but it is expected to receive 0x12F for user input
// so i manually created a payload so that parse_msg can be used
// to write to the response_buffer
if (device->is_ready) {
uint8_t chk = 0;
MessageData* msg = ACIODeviceEmu::create_msg(0x1, 0x12F, 0, 0);
msg->addr = 0x1;
auto write_buffer = reinterpret_cast<uint8_t *>(msg);
while (acio_emu.read() != std::nullopt); // there may be old stuffs in the buffer
acio_emu.write(0xAA);
acio_emu.write(0xAA);
for(DWORD i = 0; i < 5; i++) {
chk += write_buffer[i];
acio_emu.write(write_buffer[i]);
}
// direct copy and paste from write_msg
if (chk == ACIO_SOF || chk == ACIO_ESCAPE) {
acio_emu.write(ACIO_ESCAPE);
acio_emu.write(~chk);
} else {
acio_emu.write(chk);
}
}
// read from emu
DWORD bytes_read = 0;
while (bytes_read < nNumberOfBytesToRead) {
auto cur_byte = acio_emu.read();
if (cur_byte.has_value()) {
buffer[bytes_read++] = cur_byte.value();
} else {
break;
}
}
#ifdef ACIOEMU_LOG
log_info("gitadora", "Read IO Data: {}", bin2hex(buffer, bytes_read));
#endif
// return amount of bytes read
return (int)bytes_read;
}
int games::gitadora::GitaDoraSerialHandle::write(LPCVOID lpBuffer, DWORD nNumberOfBytesToWrite)
{
auto buffer = reinterpret_cast<const uint8_t *>(lpBuffer);
// write to emu
for (DWORD i = 0; i < nNumberOfBytesToWrite; i++) {
acio_emu.write(buffer[i]);
}
#ifdef ACIOEMU_LOG
log_info("gitadora", "Write IO Data: {}", bin2hex(buffer, nNumberOfBytesToWrite));
#endif
// return all data written
return (int)nNumberOfBytesToWrite;
}
int games::gitadora::GitaDoraSerialHandle::device_io(DWORD dwIoControlCode, LPVOID lpInBuffer, DWORD nInBufferSize,
LPVOID lpOutBuffer, DWORD nOutBufferSize) {
return -1;
}
bool games::gitadora::GitaDoraSerialHandle::close() {
log_info("gitadora", "Closed COM1");
return true;
}
+24
View File
@@ -0,0 +1,24 @@
#pragma once
#include "acioemu/acioemu.h"
#include "hooks/devicehook.h"
namespace games::gitadora {
class GitaDoraSerialHandle : public CustomHandle {
private:
acioemu::ACIOEmu acio_emu;
public:
bool open(LPCWSTR lpFileName) override;
int read(LPVOID lpBuffer, DWORD nNumberOfBytesToRead) override;
int write(LPCVOID lpBuffer, DWORD nNumberOfBytesToWrite) override;
int device_io(DWORD dwIoControlCode, LPVOID lpInBuffer, DWORD nInBufferSize, LPVOID lpOutBuffer,
DWORD nOutBufferSize) override;
bool close() override;
};
}
+125 -82
View File
@@ -10,6 +10,7 @@ std::vector<Button> &games::gitadora::get_buttons() {
"Service",
"Test",
"Coin",
"Headphone",
"Guitar P1 Start",
"Guitar P1 Up",
"Guitar P1 Down",
@@ -64,17 +65,18 @@ std::vector<Button> &games::gitadora::get_buttons() {
"Drum Help",
"Drum Button Extra 1",
"Drum Button Extra 2",
"Drum Left Cymbal",
"Drum Hi-Hat",
"Drum Hi-Hat Closed",
"Drum Hi-Hat Half-Open",
"Drum Left Pedal",
"Drum Snare",
"Drum Hi-Tom",
"Drum Low-Tom",
"Drum Right Cymbal",
"Drum Bass Pedal",
"Drum Left Cymbal",
"Drum Left Pedal",
"Drum Floor Tom"
"Drum Low-Tom",
"Drum Floor Tom",
"Drum Right Cymbal",
"Drum Hi-Hat Closed",
"Drum Hi-Hat Half-Open"
);
}
@@ -106,9 +108,18 @@ std::string games::gitadora::get_buttons_help() {
// keep to max 100 characters wide
return
"guitar:\n"
"\n"
" < R G B Y P --- Pick ] \n"
"\n"
" If you 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"
"\n"
" LeftCymbal RightCymbal\n"
" HiHat HiTom LowTom \n"
" Snare FloorTom \n"
@@ -116,10 +127,28 @@ std::string games::gitadora::get_buttons_help() {
" Left Bass\n"
" Pedal Pedal\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"
"For MIDI drums with Open/Closed HiHat configurations, bind variations below. "
"v2_drum algorithm might work better for those drums."
"X axis: 0% when body is held facing monitor, 50% when flat on a table\n"
"Y axis: 50% when held horizontal, 0% when neck is raised, 100% pointing down\n"
"Z axis: 50% at rest, 100% when swinging neck toward monitor (only used in XG series)\n"
"\n"
"You need both X and Y axis for up/down wail to work correctly in-game.\n"
"If you only have Y axis, consider using digital wailing instead.\n"
"\n"
"Ensure you clear all analog bindings if you want to use digital wailing.\n"
"\n"
"If you hold your guitar left-handed (frets on the right hand),\n"
"analog bindings will likely not work as intended.\n"
"Consider using digital wailing in Buttons tab."
;
}
@@ -129,88 +158,102 @@ std::vector<Light> &games::gitadora::get_lights() {
if (lights.empty()) {
lights = GameAPI::Lights::getLights("GitaDora");
GameAPI::Lights::sortLights(&lights,
"Guitar P1 Motor",
"Guitar P2 Motor",
GameAPI::Lights::sortLightsWithCategory(&lights,
{
{"Guitar", "Guitar P1 Motor"},
{"Guitar", "Guitar P2 Motor"},
"P1 Start",
"P1 Menu Up Down (DX)",
"P1 Menu Left Right (DX)",
"P1 Help (DX)",
{"Menu", "P1 Start"},
{"Menu", "P1 Menu Up Down (DX)"},
{"Menu", "P1 Menu Left Right (DX)"},
{"Menu", "P1 Help (DX)"},
"P2 Start",
"P2 Menu Up Down (DX)",
"P2 Menu Left Right (DX)",
"P2 Help (DX)",
{"Menu", "P2 Start"},
{"Menu", "P2 Menu Up Down (DX)"},
{"Menu", "P2 Menu Left Right (DX)"},
{"Menu", "P2 Help (DX)"},
"Drum Left Cymbal",
"Drum Hi-Hat",
"Drum Snare",
"Drum High Tom",
"Drum Low Tom",
"Drum Floor Tom",
"Drum Right Cymbal",
{"Drums", "Drum Left Cymbal"},
{"Drums", "Drum Hi-Hat"},
{"Drums", "Drum Snare"},
{"Drums", "Drum High Tom"},
{"Drums", "Drum Low Tom"},
{"Drums", "Drum Floor Tom"},
{"Drums", "Drum Right Cymbal"},
"Drum Woofer R",
"Drum Woofer G",
"Drum Woofer B",
{"DX/SD", "Drum Woofer R"},
{"DX/SD", "Drum Woofer G"},
{"DX/SD", "Drum Woofer B"},
"Drum Stage R (DX)",
"Drum Stage G (DX)",
"Drum Stage B (DX)",
{"DX/SD", "Drum Stage R (DX)"},
{"DX/SD", "Drum Stage G (DX)"},
{"DX/SD", "Drum Stage B (DX)"},
"Spot Left (DX)",
"Spot Right (DX)",
"Spot Center Left (DX)",
"Spot Center Right (DX)",
{"DX/SD", "Spot Left (DX)"},
{"DX/SD", "Spot Right (DX)"},
{"DX/SD", "Spot Center Left (DX)"},
{"DX/SD", "Spot Center Right (DX)"},
"Drum Spot Rear Left (DX)",
"Drum Spot Rear Right (DX)",
{"DX/SD", "Drum Spot Rear Left (DX)"},
{"DX/SD", "Drum Spot Rear Right (DX)"},
"Guitar Lower Left R (DX)",
"Guitar Lower Left G (DX)",
"Guitar Lower Left B (DX)",
"Guitar Lower Right R (DX)",
"Guitar Lower Right G (DX)",
"Guitar Lower Right B (DX)",
{"DX/SD", "Guitar Lower Left R (DX)"},
{"DX/SD", "Guitar Lower Left G (DX)"},
{"DX/SD", "Guitar Lower Left B (DX)"},
{"DX/SD", "Guitar Lower Right R (DX)"},
{"DX/SD", "Guitar Lower Right G (DX)"},
{"DX/SD", "Guitar Lower Right B (DX)"},
"Guitar Left Speaker Upper R (DX)",
"Guitar Left Speaker Upper G (DX)",
"Guitar Left Speaker Upper B (DX)",
"Guitar Left Speaker Mid Up Left R (DX)",
"Guitar Left Speaker Mid Up Left G (DX)",
"Guitar Left Speaker Mid Up Left B (DX)",
"Guitar Left Speaker Mid Up Right R (DX)",
"Guitar Left Speaker Mid Up Right G (DX)",
"Guitar Left Speaker Mid Up Right B (DX)",
"Guitar Left Speaker Mid Low Left R (DX)",
"Guitar Left Speaker Mid Low Left G (DX)",
"Guitar Left Speaker Mid Low Left B (DX)",
"Guitar Left Speaker Mid Low Right R (DX)",
"Guitar Left Speaker Mid Low Right G (DX)",
"Guitar Left Speaker Mid Low Right B (DX)",
"Guitar Left Speaker Lower R (DX)",
"Guitar Left Speaker Lower G (DX)",
"Guitar Left Speaker Lower B (DX)",
{"DX/SD", "Guitar Left Speaker Upper R (DX)"},
{"DX/SD", "Guitar Left Speaker Upper G (DX)"},
{"DX/SD", "Guitar Left Speaker Upper B (DX)"},
{"DX/SD", "Guitar Left Speaker Mid Up Left R (DX)"},
{"DX/SD", "Guitar Left Speaker Mid Up Left G (DX)"},
{"DX/SD", "Guitar Left Speaker Mid Up Left B (DX)"},
{"DX/SD", "Guitar Left Speaker Mid Up Right R (DX)"},
{"DX/SD", "Guitar Left Speaker Mid Up Right G (DX)"},
{"DX/SD", "Guitar Left Speaker Mid Up Right B (DX)"},
{"DX/SD", "Guitar Left Speaker Mid Low Left R (DX)"},
{"DX/SD", "Guitar Left Speaker Mid Low Left G (DX)"},
{"DX/SD", "Guitar Left Speaker Mid Low Left B (DX)"},
{"DX/SD", "Guitar Left Speaker Mid Low Right R (DX)"},
{"DX/SD", "Guitar Left Speaker Mid Low Right G (DX)"},
{"DX/SD", "Guitar Left Speaker Mid Low Right B (DX)"},
{"DX/SD", "Guitar Left Speaker Lower R (DX)"},
{"DX/SD", "Guitar Left Speaker Lower G (DX)"},
{"DX/SD", "Guitar Left Speaker Lower B (DX)"},
"Guitar Right Speaker Upper R (DX)",
"Guitar Right Speaker Upper G (DX)",
"Guitar Right Speaker Upper B (DX)",
"Guitar Right Speaker Mid Up Left R (DX)",
"Guitar Right Speaker Mid Up Left G (DX)",
"Guitar Right Speaker Mid Up Left B (DX)",
"Guitar Right Speaker Mid Up Right R (DX)",
"Guitar Right Speaker Mid Up Right G (DX)",
"Guitar Right Speaker Mid Up Right B (DX)",
"Guitar Right Speaker Mid Low Left R (DX)",
"Guitar Right Speaker Mid Low Left G (DX)",
"Guitar Right Speaker Mid Low Left B (DX)",
"Guitar Right Speaker Mid Low Right R (DX)",
"Guitar Right Speaker Mid Low Right G (DX)",
"Guitar Right Speaker Mid Low Right B (DX)",
"Guitar Right Speaker Lower R (DX)",
"Guitar Right Speaker Lower G (DX)",
"Guitar Right Speaker Lower B (DX)"
{"DX/SD", "Guitar Right Speaker Upper R (DX)"},
{"DX/SD", "Guitar Right Speaker Upper G (DX)"},
{"DX/SD", "Guitar Right Speaker Upper B (DX)"},
{"DX/SD", "Guitar Right Speaker Mid Up Left R (DX)"},
{"DX/SD", "Guitar Right Speaker Mid Up Left G (DX)"},
{"DX/SD", "Guitar Right Speaker Mid Up Left B (DX)"},
{"DX/SD", "Guitar Right Speaker Mid Up Right R (DX)"},
{"DX/SD", "Guitar Right Speaker Mid Up Right G (DX)"},
{"DX/SD", "Guitar Right Speaker Mid Up Right B (DX)"},
{"DX/SD", "Guitar Right Speaker Mid Low Left R (DX)"},
{"DX/SD", "Guitar Right Speaker Mid Low Left G (DX)"},
{"DX/SD", "Guitar Right Speaker Mid Low Left B (DX)"},
{"DX/SD", "Guitar Right Speaker Mid Low Right R (DX)"},
{"DX/SD", "Guitar Right Speaker Mid Low Right G (DX)"},
{"DX/SD", "Guitar Right Speaker Mid Low Right B (DX)"},
{"DX/SD", "Guitar Right Speaker Lower R (DX)"},
{"DX/SD", "Guitar Right Speaker Lower G (DX)"},
{"DX/SD", "Guitar Right Speaker Lower B (DX)"},
{"Arena Model", "Title Average R (Arena)"},
{"Arena Model", "Title Average G (Arena)"},
{"Arena Model", "Title Average B (Arena)"},
{"Arena Model", "Woofer R (Arena)"},
{"Arena Model", "Woofer G (Arena)"},
{"Arena Model", "Woofer B (Arena)"},
{"Arena Model", "Card Reader R (Arena)"},
{"Arena Model", "Card Reader G (Arena)"},
{"Arena Model", "Card Reader B (Arena)"}
}
);
}
+25 -9
View File
@@ -11,6 +11,7 @@ namespace games::gitadora {
Service,
Test,
Coin,
Headphone,
GuitarP1Start,
GuitarP1Up,
GuitarP1Down,
@@ -65,18 +66,21 @@ namespace games::gitadora {
DrumHelp,
DrumButtonExtra1,
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,
DrumHiHatClosed,
DrumHiHatHalfOpen,
DrumLeftPedal,
DrumSnare,
DrumHiTom,
DrumLowTom,
DrumRightCymbal,
DrumBassPedal,
DrumLeftCymbal,
DrumLeftPedal,
DrumFloorTom
DrumLowTom,
DrumFloorTom,
DrumRightCymbal,
// these only exist for back compat
// (created before it was possible to map multiple input to a button)
DrumHiHatClosed,
DrumHiHatHalfOpen
};
}
@@ -190,13 +194,25 @@ namespace games::gitadora {
GuitarRightSpeakerMidLowRightB,
GuitarRightSpeakerLowerR,
GuitarRightSpeakerLowerG,
GuitarRightSpeakerLowerB
GuitarRightSpeakerLowerB,
// arena model (common for both guitar and drum)
ArenaTitleAvgR,
ArenaTitleAvgG,
ArenaTitleAvgB,
ArenaWooferR,
ArenaWooferG,
ArenaWooferB,
ArenaCardR,
ArenaCardG,
ArenaCardB,
} gitadora_lights_t;
}
// getters
std::vector<Button> &get_buttons();
std::string get_buttons_help();
std::string get_analogs_help();
std::vector<Analog> &get_analogs();
std::vector<Light> &get_lights();
}
+100
View File
@@ -0,0 +1,100 @@

#include "misc/eamuse.h"
#include "util/utils.h"
#include "io.h"
#include "j32d.h"
using namespace acioemu;
games::gitadora::J32DSerialDevice::J32DSerialDevice() {
this->node_count = 1;
log_info("gitadora", "J32D device created.");
}
bool games::gitadora::J32DSerialDevice::parse_msg(
MessageData *msg_in,
circular_buffer<uint8_t> *response_buffer) {
#ifdef ACIOEMU_LOG
log_info("gitadora", "GTDR ADDR: {}, CMD: 0x{:04x}", msg_in->addr, msg_in->cmd.code);
#endif
static uint8_t pid = 0;
// check command
switch (msg_in->cmd.code) {
case ACIO_CMD_GET_VERSION: {
// send version data
auto msg = this->create_msg(msg_in, MSG_VERSION_SIZE);
this->set_version(msg, 0x20a, 0, 0, 1, 0, "J32D");
write_msg(msg, response_buffer);
delete msg;
break;
}
case ACIO_CMD_STARTUP:
case ACIO_CMD_CLEAR:
case 0x0120: {
if (msg_in->cmd.code == 0x0120) {
pid = msg_in->cmd.pid;
this->is_ready = true;
}
// send status 0
auto msg = this->create_msg_status(msg_in, 0);
write_msg(msg, response_buffer);
delete msg;
break;
}
// IO
case 0x012F: {
auto msg = this->create_msg(msg_in, 0x11);
msg->cmd.pid = pid;
// don't know if it's needed/correct,
// without it the game seems to work just fine
pid += 1;
J32D_IO_STATUS payload;
memset(&payload, 0, sizeof(J32D_IO_STATUS));
auto &buttons = get_buttons();
auto high_tom = (uint16_t) (GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::DrumHiTom]) * 1023);
auto low_tom = (uint16_t) (GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::DrumLowTom]) * 1023);
auto snare = (uint16_t) (GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::DrumSnare]) * 1023);
auto floor_tom = (uint16_t) (GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::DrumFloorTom]) * 1023);
auto left_cymbal = (uint16_t) (GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::DrumLeftCymbal]) * 1023);
auto right_cymbal = (uint16_t) (GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::DrumRightCymbal]) * 1023);
auto hihat = (uint16_t) ((GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::DrumHiHat]) ||
GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::DrumHiHatClosed]) ||
GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::DrumHiHatHalfOpen])) * 1023);
#define ENCODE_ANALOG(__val) (((__val & 0x3f) << 10) | ((__val & 0x3c0) >> 6))
payload.high_tom = ENCODE_ANALOG(high_tom);
payload.low_tom = ENCODE_ANALOG(low_tom);
payload.snare = ENCODE_ANALOG(snare);
payload.floor_tom = ENCODE_ANALOG(floor_tom);
payload.left_cymbal = ENCODE_ANALOG(left_cymbal);
payload.right_cymbal = ENCODE_ANALOG(right_cymbal);
payload.hihat = ENCODE_ANALOG(hihat);
#undef ENCODE_ANALOG
if (GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::DrumBassPedal])) {
payload.pedals |= 1;
}
if (GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::DrumLeftPedal])) {
payload.pedals |= 2;
}
memcpy(msg->cmd.raw, &payload, sizeof(J32D_IO_STATUS));
write_msg(msg, response_buffer);
delete msg;
break;
}
default:
#ifdef ACIOEMU_LOG
log_info("gitadora", "Unhandled cmd {} received.", msg_in->cmd.code);
#endif
return false;
}
// mark as handled
return true;
}
+25
View File
@@ -0,0 +1,25 @@
#pragma once
#include "acioemu/device.h"
#include "device.h"
namespace games::gitadora {
class J32DSerialDevice : public GitadoraDevice {
private:
uint8_t coin_counter = 0;
public:
J32DSerialDevice();
bool parse_msg(acioemu::MessageData *msg_in, circular_buffer<uint8_t> *response_buffer) override;
struct J32D_IO_STATUS {
uint16_t high_tom;
uint16_t low_tom;
uint16_t snare;
uint16_t floor_tom;
uint16_t left_cymbal;
uint16_t right_cymbal;
uint16_t hihat;
uint16_t unk7;
uint8_t pedals;
};
};
}
+203
View File
@@ -0,0 +1,203 @@

#include "misc/eamuse.h"
#include "games/gitadora/gitadora.h"
#include "util/socd_cleaner.h"
#include "util/time.h"
#include "util/utils.h"
#include "io.h"
#include "j33i.h"
#define GUITAR_BTN_R 0x7
#define GUITAR_BTN_G 0x6
#define GUITAR_BTN_B 0x5
#define GUITAR_BTN_Y 0x4
#define GUITAR_BTN_P 0x3
#define GUITAR_PICK_DOWN 0xC
#define GUITAR_PICK_UP 0xD
using namespace acioemu;
// why this number?? no clue, but this is what the test menu shows as the max
// value in the green box with the red dot; the value display itself can go
// higher but the red dot will reach the edge at +-189
#define ARENA_MODEL_WAIL_MAX 189
static bool GFDM_GF_PICK_STATE_UP;
static bool GFDM_GF_PICK_STATE_DOWN;
games::gitadora::J33ISerialDevice::J33ISerialDevice() {
this->node_count = 1;
log_info("gitadora", "J33I device created.");
}
bool games::gitadora::J33ISerialDevice::parse_msg(
MessageData *msg_in,
circular_buffer<uint8_t> *response_buffer) {
#ifdef ACIOEMU_LOG
log_info("gitadora", "GTDR ADDR: {}, CMD: 0x{:04x}", msg_in->addr, msg_in->cmd.code);
#endif
static uint8_t pid = 0;
// check command
switch (msg_in->cmd.code)
{
case ACIO_CMD_GET_VERSION: {
// send version data
auto msg = this->create_msg(msg_in, MSG_VERSION_SIZE);
this->set_version(msg, 0x10a, 0, 1, 0, 2, "J33I");
write_msg(msg, response_buffer);
delete msg;
break;
}
case ACIO_CMD_STARTUP:
case ACIO_CMD_CLEAR:
case 0x0120: {
if (msg_in->cmd.code == 0x0120) {
pid = msg_in->cmd.pid;
this->is_ready = true;
}
// send status 0
auto msg = this->create_msg_status(msg_in, 0);
write_msg(msg, response_buffer);
delete msg;
break;
}
// IO
case 0x012F: {
auto msg = this->create_msg(msg_in, 0x8);
msg->cmd.pid = pid;
pid += 1;
J33I_IO_STATUS payload;
memset(&payload, 0, sizeof(J33I_IO_STATUS));
auto &buttons = get_buttons();
if (GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::GuitarP1R])) {
payload.buttons |= 1 << GUITAR_BTN_R;
}
if (GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::GuitarP1G])) {
payload.buttons |= 1 << GUITAR_BTN_G;
}
if (GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::GuitarP1B])) {
payload.buttons |= 1 << GUITAR_BTN_B;
}
if (GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::GuitarP1Y])) {
payload.buttons |= 1 << GUITAR_BTN_Y;
}
if (GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::GuitarP1P])) {
payload.buttons |= 1 << GUITAR_BTN_P;
}
const auto pick_up = GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::GuitarP1PickUp]);
const auto pick_down = GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::GuitarP1PickDown]);
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 {
const auto socd = socd::socd_clean(
0,
pick_up,
pick_down,
get_performance_milliseconds(),
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;
}
}
auto &analogs = get_analogs();
// get x,y,z analog values [-0.5f, 0.5f]
float x = is_player_lefty(0) ? 0.3f : -0.5f;
auto x_analog = analogs[Analogs::GuitarP1WailX];
if (x_analog.isSet()) {
x = GameAPI::Analogs::getState(RI_MGR, x_analog) - 0.5f;
}
float y = 0.f;
auto y_analog = analogs[Analogs::GuitarP1WailY];
if (y_analog.isSet()) {
y = GameAPI::Analogs::getState(RI_MGR, y_analog) - 0.5f;
}
float z = 0.f;
auto z_analog = analogs[Analogs::GuitarP1WailZ];
if (z_analog.isSet()) {
z = GameAPI::Analogs::getState(RI_MGR, z_analog) - 0.5f;
}
// handle digital wail up/down
const auto wail_up = GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::GuitarP1WailUp]);
const auto wail_down = GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::GuitarP1WailDown]);
const auto wail_result =
socd::get_guitar_wail(0, wail_up, wail_down, get_performance_milliseconds());
if (!is_player_lefty(0)) {
// righty
if (wail_result == socd::TiltUp) {
x = 0;
y = -0.5f;
} else if (wail_result == socd::TiltDown) {
x = -0.5f;
y = 0.5f;
}
} else {
// lefty
if (wail_result == socd::TiltUp) {
x = 0.f;
y = 0.f;
} else if (wail_result == socd::TiltDown) {
x = 0.5f;
y = 0.f;
}
}
// convert x,y,z to unsigned integer values that the game expects
const uint16_t x_int = x * ARENA_MODEL_WAIL_MAX * 2;
const uint16_t y_int = y * ARENA_MODEL_WAIL_MAX * 2;
const uint16_t z_int = z * ARENA_MODEL_WAIL_MAX * 2;
payload.gyroscope_x = static_cast<uint16_t>(((x_int >> 3) & (0xFF)) | ((x_int & 0x07) << 12));
payload.gyroscope_y = static_cast<uint16_t>(((y_int >> 3) & (0xFF)) | ((y_int & 0x07) << 12));
payload.gyroscope_z = static_cast<uint16_t>(((z_int >> 3) & (0xFF)) | ((z_int & 0x07) << 12));
memcpy(msg->cmd.raw, &payload, sizeof(J33I_IO_STATUS));
write_msg(msg, response_buffer);
delete msg;
break;
}
default:
#ifdef ACIOEMU_LOG
log_info("gitadora", "Unhandled cmd {} received.", msg_in->cmd.code);
#endif
return false;
}
// mark as handled
return true;
}
+24
View File
@@ -0,0 +1,24 @@
#pragma once
#include "acioemu/device.h"
#include "device.h"
namespace games::gitadora {
class J33ISerialDevice : public GitadoraDevice {
private:
uint8_t coin_counter = 0;
public:
J33ISerialDevice();
bool parse_msg(acioemu::MessageData *msg_in, circular_buffer<uint8_t> *response_buffer) override;
// there may be more, but the game doesn't seem to care
// TODO: add support for gyroscope, knob and wail
struct J33I_IO_STATUS {
uint16_t gyroscope_x;
uint16_t gyroscope_y;
uint16_t gyroscope_z;
uint16_t buttons;
};
};
}
+3 -3
View File
@@ -195,11 +195,11 @@ bool games::iidx::IIDXFMSerialHandle::open(LPCWSTR lpFileName) {
return false;
}
log_info("iidx", "Opened COM2 (FM DEVICE)");
log_info("iidx", "Opened COM2 (FM DEVICE / bi2a)");
games::iidx::update_io_emulation_state(games::iidx::iidx_aio_emulation_state::bi2a_com2);
// ACIO device
acio_emu.add_device(new FMSerialDevice());
return true;
}
@@ -240,7 +240,7 @@ int games::iidx::IIDXFMSerialHandle::device_io(DWORD dwIoControlCode, LPVOID lpI
}
bool games::iidx::IIDXFMSerialHandle::close() {
log_info("iidx", "Closed COM2 (FM DEVICE)");
log_info("iidx", "Closed COM2 (FM DEVICE / bi2a)");
return true;
}
+34 -12
View File
@@ -1,5 +1,7 @@
#include "bi2x_hook.h"
#if SPICE64
#include <cstdint>
#include "util/detour.h"
#include "util/logging.h"
@@ -24,17 +26,28 @@ namespace games::iidx {
uint8_t data[0x10];
};
struct AIO_NMGR_IOB2 {
uint8_t dummy0[0x50];
struct AIO_NMGR_IOB2_VTABLE {
// other functions here, but they never get called
uint8_t dummy[0x50];
void (__fastcall *pAIO_NMGR_IOB_BeginManage)(int64_t a1);
uint8_t dummy1[0x9A0];
};
struct AIO_NMGR_IOB2 {
AIO_NMGR_IOB2_VTABLE *vptr;
uint8_t dummy[0x9F0];
};
// confirmed 0x9F8 in iidx27, iidx32 aioNMgrIob2_Create
static_assert(sizeof(AIO_NMGR_IOB2) == 0x9F8);
struct AIO_IOB2_BI2X_TDJ {
// who knows
uint8_t data[0x13F8];
};
// confirmed 0x13F8 in iidx27, iidx32 in aioIob2Bi2xTDJ_Create
static_assert(sizeof(AIO_IOB2_BI2X_TDJ) == 0x13F8);
struct AIO_IOB2_BI2X_TDJ__DEVSTATUS {
// of course you could work with variables here
uint8_t buffer[0xCA];
@@ -78,7 +91,7 @@ namespace games::iidx {
// libaio-iob.dll
typedef AC_HNDLIF* (__fastcall *aioIob2Bi2x_OpenSciUsbCdc_t)(uint8_t device_num);
typedef int64_t (__fastcall *aioIob2Bi2x_WriteFirmGetState_t)(int64_t a1);
typedef AIO_NMGR_IOB2** (__fastcall *aioNMgrIob2_Create_t)(AC_HNDLIF *a1, unsigned int a2);
typedef AIO_NMGR_IOB2* (__fastcall *aioNMgrIob2_Create_t)(AC_HNDLIF *a1, unsigned int a2);
/*
* function pointers
@@ -120,6 +133,11 @@ namespace games::iidx {
static AIO_IOB2_BI2X_TDJ* __fastcall aioIob2Bi2xTDJ_Create(AIO_NMGR_IOB2 *nmgr, int a2) {
if (!BI2X_PASSTHROUGH) {
log_info("bi2x_hook", "aioIob2Bi2xTDJ_Create called (TDJ I/O emulation active)");
games::iidx::update_io_emulation_state(
games::iidx::iidx_aio_emulation_state::bi2x_hook);
custom_node = new AIO_IOB2_BI2X_TDJ;
memset(&custom_node->data, 0, sizeof(custom_node->data));
@@ -438,7 +456,7 @@ namespace games::iidx {
}
}
static AC_HNDLIF* __fastcall aioIob2Bi2X_OpenSciUsbCdc(uint8_t device_num) {
static AC_HNDLIF* __fastcall aioIob2Bi2x_OpenSciUsbCdc(uint8_t device_num) {
if (acHndlif == nullptr) {
acHndlif = new AC_HNDLIF;
memset(acHndlif->data, 0x0, sizeof(acHndlif->data));
@@ -451,14 +469,16 @@ namespace games::iidx {
log_info("bi2x_hook", "AIO_NMGR_IOB::BeginManage");
};
static AIO_NMGR_IOB2** __fastcall aioNMgrIob2_Create(AC_HNDLIF *a1, unsigned int a2) {
static AIO_NMGR_IOB2* __fastcall aioNMgrIob2_Create(AC_HNDLIF *a1, unsigned int a2) {
if (aioNmgrIob2 == nullptr) {
aioNmgrIob2 = new AIO_NMGR_IOB2;
memset(aioNmgrIob2, 0x0, sizeof(AIO_NMGR_IOB2));
aioNmgrIob2->pAIO_NMGR_IOB_BeginManage = AIO_NMGR_IOB_BeginManageStub;
aioNmgrIob2 = new AIO_NMGR_IOB2{};
aioNmgrIob2->vptr = new AIO_NMGR_IOB2_VTABLE{};
aioNmgrIob2->vptr->pAIO_NMGR_IOB_BeginManage = AIO_NMGR_IOB_BeginManageStub;
}
log_info("bi2x_hook", "aioNMgrIob2_Create");
return &aioNmgrIob2;
log_info("bi2x_hook", "aioNMgrIob2_Create returned {}, size=0x{:x}, vptr @ {}",
fmt::ptr(aioNmgrIob2), sizeof(*aioNmgrIob2), fmt::ptr(aioNmgrIob2->vptr));
return aioNmgrIob2;
}
static int64_t __fastcall aioIob2Bi2x_WriteFirmGetState(int64_t a1) {
@@ -515,10 +535,12 @@ namespace games::iidx {
// hook IOB
const auto libaioIobDll = "libaio-iob.dll";
detour::trampoline_try(libaioIobDll, "aioIob2Bi2x_OpenSciUsbCdc",
aioIob2Bi2X_OpenSciUsbCdc, &aioIob2Bi2x_OpenSciUsbCdc_orig);
aioIob2Bi2x_OpenSciUsbCdc, &aioIob2Bi2x_OpenSciUsbCdc_orig);
detour::trampoline_try(libaioIobDll, "aioIob2Bi2x_WriteFirmGetState",
aioIob2Bi2x_WriteFirmGetState, &aioIob2Bi2x_WriteFirmGetState_orig);
detour::trampoline_try(libaioIobDll, "aioNMgrIob2_Create",
aioNMgrIob2_Create, &aioNMgrIob2_Create_orig);
}
}
#endif
+4
View File
@@ -1,6 +1,10 @@
#pragma once
#if SPICE64
namespace games::iidx {
void bi2x_hook_init();
}
#endif
+89 -15
View File
@@ -29,6 +29,8 @@
#include "util/libutils.h"
#include "util/memutils.h"
#include "util/sigscan.h"
#include "util/socd_cleaner.h"
#include "util/time.h"
#include "util/utils.h"
#include "launcher/signal.h"
@@ -79,6 +81,11 @@ namespace games::iidx {
char IIDXIO_LED_TICKER[10] = {' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', '\x00'};
bool IIDXIO_LED_TICKER_READONLY = false;
std::mutex IIDX_LED_TICKER_LOCK;
bool IIDX_TDJ_MONITOR_WARNING = false;
iidx_aio_emulation_state CURRENT_IO_EMULATION_STATE = iidx_aio_emulation_state::unknown;
// io
static bool HAS_LIBAIO; // this is how we detect iidx27+
tapeledutils::tape_led TAPELED_MAPPING[IIDX_TAPELED_TOTAL] = {
{ 19, Lights::StageLeftAvgR, Lights::StageLeftAvgG, Lights::StageLeftAvgB, "Stage Left" },
@@ -247,6 +254,8 @@ namespace games::iidx {
}
}
#if SPICE64
typedef void* (*aioIob2Bi2x_CreateWriteFirmContext_t)(unsigned int, int);
static aioIob2Bi2x_CreateWriteFirmContext_t aioIob2Bi2x_CreateWriteFirmContext_orig = nullptr;
@@ -265,6 +274,8 @@ namespace games::iidx {
return nullptr;
}
#endif
IIDXGame::IIDXGame() : Game("Beatmania IIDX") {
logger::hook_add(log_hook, this);
}
@@ -311,13 +322,16 @@ namespace games::iidx {
memcpy(settings2.interface_detail, interface_detail2, sizeof(interface_detail2));
setupapihook_add(settings2);
// IIDX 25-27 BIO2 BI2A input device
// IIDX 25+ BIO2 BI2A input device
devicehook_add(new IIDXFMSerialHandle());
}
#if SPICE64
// check for new I/O DLL
auto aio = libutils::try_library("libaio.dll");
if (aio != nullptr) {
HAS_LIBAIO = true;
// check TDJ mode
TDJ_MODE |= fileutils::text_read("C:\\000rom.txt") == "TDJ-JA";
@@ -379,6 +393,8 @@ namespace games::iidx {
}
}
#endif
if (hooks::audio::ENABLED) {
apply_audio_hacks();
} else {
@@ -438,7 +454,7 @@ namespace games::iidx {
}
// 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;
}
@@ -457,7 +473,7 @@ namespace games::iidx {
"!!! please do the following instead: !!!\n"
"!!! !!!\n"
"!!! Revert your changes to XML file so it says !!!\n"
"!!! <model __type=\"str\">LDJ</model> !!!\n"
"!!! <model __type=\"str\">LDJ</model> !!!\n"
"!!! !!!\n"
"!!! In SpiceCfg, enable 'IIDX TDJ Mode' or provide -iidxtdj flag !!!\n"
"!!! in command line !!!\n"
@@ -471,9 +487,8 @@ namespace games::iidx {
}
// check -monitor + TDJ mode
if (!GRAPHICS_WINDOWED &&
options->at(launcher::Options::DisplayAdapter).is_active() &&
TDJ_MODE) {
if (!GRAPHICS_WINDOWED && D3D9_ADAPTER.has_value() && TDJ_MODE) {
IIDX_TDJ_MONITOR_WARNING = true;
log_warning(
"iidx",
"\n\n!!! using -monitor option with TDJ is NOT recommended due to known !!!\n"
@@ -495,6 +510,16 @@ namespace games::iidx {
" monitor in Windows settings before launching the game"
});
}
// socd
socd::ALGORITHM = socd::SocdAlgorithm::Neutral;
if (options->at(launcher::Options::IIDXDigitalTTSocd).is_active()) {
if (options->at(launcher::Options::IIDXDigitalTTSocd).value_text() == "last") {
socd::ALGORITHM = socd::SocdAlgorithm::PreferRecent;
} else if (options->at(launcher::Options::IIDXDigitalTTSocd).value_text() == "first") {
socd::ALGORITHM = socd::SocdAlgorithm::PreferFirst;
}
}
}
void IIDXGame::detach() {
@@ -718,13 +743,12 @@ namespace games::iidx {
bool ttpm_alt = GameAPI::Buttons::getState(RI_MGR, buttons.at(
player != 0 ? Buttons::P2_TTPlusMinusAlt : Buttons::P1_TTPlusMinusAlt));
// TT+
if (ttp)
const auto tt_socd = socd::socd_clean(player, ttm, ttp, get_performance_milliseconds());
if (tt_socd == socd::SocdCW) {
IIDXIO_TT_STATE[player] += change;
// TT-
if (ttm)
} else if (tt_socd == socd::SocdCCW) {
IIDXIO_TT_STATE[player] -= change;
}
// TT+/-
bool ttpm_rising_edge = !IIDXIO_TT_PRESSED[player] && ttpm;
@@ -794,7 +818,7 @@ namespace games::iidx {
}
bool is_tdj_fhd() {
return TDJ_MODE && avs::game::is_ext(2022101900, MAXINT);
return TDJ_MODE && avs::game::is_ext(2022101900, INT_MAX);
}
void apply_audio_hacks() {
@@ -860,12 +884,12 @@ namespace games::iidx {
// patch iidx32+ for asio compatibility
// only do this if NOT wasapi (as opposed to checking if it's asio)
// the patch is only really needed for (some) non-XONAR devices but since people sometimes disguise
// the patch is only really needed for (some) non-XONAR devices but since people sometimes disguise
// other devices as a XONAR, don't check for the exact string (common ASIO workaround for INF)
if (avs::game::is_ext(2024090100, MAXINT) &&
if (avs::game::is_ext(2024090100, INT_MAX) &&
!(SOUND_OUTPUT_DEVICE_IN_EFFECT.has_value() &&
SOUND_OUTPUT_DEVICE_IN_EFFECT.value() == "wasapi")) {
// in iidx32 final:
// ff 50 08 call QWORD PTR [rax+0x8] ; ASIO instance AddRef
// 48 8b 4b 08 mov rcx,QWORD PTR [rbx+0x8]
@@ -895,4 +919,54 @@ namespace games::iidx {
#endif
}
void update_io_emulation_state(iidx_aio_emulation_state state) {
// <iidx27, don't care, since tdj wasn't a thing before
// this is also how we detect that iidx module is attached
if (!HAS_LIBAIO) {
return;
}
// in iidx27-30, most DLL types can handle both, caching things in nvram and possibly end
// up with wrong i/o (temporarily until nvram is initialized again)
// to avoid false positives, only do this for iidx31+, since we know for sure that iidx31
// has a clean 010/012 split
if (avs::game::is_ext(0, 2023101000)) {
return;
}
CURRENT_IO_EMULATION_STATE = state;
// log_warning below could be turned into log_fatal in the future once we gain confidence
// that this isn't triggering when it's not supposed to
if (state == iidx_aio_emulation_state::bi2a_com2 && TDJ_MODE) {
deferredlogs::defer_error_messages({
"IIDX I/O mode conflict",
" game is currently configured to use LDJ I/O;",
" however, you turned on IIDX TDJ Mode (-iidxtdj)",
" two options to fix this:",
" For TDJ mode (120Hz): import patches and enable Force TDJ Mode patch",
" For LDJ mode (60Hz): turn off IIDX TDJ Mode option",
});
log_warning(
"iidx",
"game is using LDJ I/O but spice TDJ mode is turned on! "
"enable Force TDJ I/O patch, OR turn off -iidxtdj");
} else if (state == iidx_aio_emulation_state::bi2x_hook && !TDJ_MODE) {
deferredlogs::defer_error_messages({
"IIDX I/O mode conflict",
" game is currently configured to use TDJ I/O;",
" however, you turned off IIDX TDJ Mode (-iidxtdj)",
" two options to fix this:",
" For TDJ mode (120Hz): turn on IIDX TDJ Mode option",
" For LDJ mode (60Hz): import patches and enable Force LDJ Mode patch",
});
log_warning(
"iidx",
"game is using TDJ I/O but spice TDJ mode is turned off! "
"turn on -iidxtdj, OR enable Force LDJ I/O patch");
} else {
log_misc("iidx", "i/o mode validation passed");
}
}
}
+10
View File
@@ -11,6 +11,12 @@
namespace games::iidx {
enum class iidx_aio_emulation_state {
unknown,
bi2a_com2,
bi2x_hook
};
// settings
extern bool FLIP_CAMS;
@@ -33,10 +39,12 @@ namespace games::iidx {
extern char IIDXIO_LED_TICKER[10];
extern bool IIDXIO_LED_TICKER_READONLY;
extern std::mutex IIDX_LED_TICKER_LOCK;
extern bool IIDX_TDJ_MONITOR_WARNING;
constexpr int IIDX_TAPELED_TOTAL = 17;
// data mapping
extern tapeledutils::tape_led TAPELED_MAPPING[IIDX_TAPELED_TOTAL];
extern iidx_aio_emulation_state CURRENT_IO_EMULATION_STATE;
class IIDXGame : public games::Game {
public:
@@ -62,4 +70,6 @@ namespace games::iidx {
const char* get_16seg();
bool is_tdj_fhd();
void apply_audio_hacks();
void update_io_emulation_state(iidx_aio_emulation_state state);
}
+98 -96
View File
@@ -83,103 +83,105 @@ std::vector<Light> &games::iidx::get_lights() {
if (lights.empty()) {
lights = GameAPI::Lights::getLights("Beatmania IIDX");
GameAPI::Lights::sortLights(
GameAPI::Lights::sortLightsWithCategory(
&lights,
"P1 1",
"P1 2",
"P1 3",
"P1 4",
"P1 5",
"P1 6",
"P1 7",
"P2 1",
"P2 2",
"P2 3",
"P2 4",
"P2 5",
"P2 6",
"P2 7",
"P1 Start",
"P2 Start",
"VEFX",
"Effect",
"Spot Light 1",
"Spot Light 2",
"Spot Light 3",
"Spot Light 4",
"Spot Light 5",
"Spot Light 6",
"Spot Light 7",
"Spot Light 8",
"Neon Lamp",
"Woofer R",
"Woofer G",
"Woofer B",
"IC Card Reader P1 R",
"IC Card Reader P1 G",
"IC Card Reader P1 B",
"IC Card Reader P2 R",
"IC Card Reader P2 G",
"IC Card Reader P2 B",
"Turntable P1 R",
"Turntable P1 G",
"Turntable P1 B",
"Turntable P2 R",
"Turntable P2 G",
"Turntable P2 B",
"Turntable P1 Resistance",
"Turntable P2 Resistance",
"Stage Left Avg R",
"Stage Left Avg G",
"Stage Left Avg B",
"Stage Right Avg R",
"Stage Right Avg G",
"Stage Right Avg B",
"Cabinet Left Avg R",
"Cabinet Left Avg G",
"Cabinet Left Avg B",
"Cabinet Right Avg R",
"Cabinet Right Avg G",
"Cabinet Right Avg B",
"Control Panel Under Avg R",
"Control Panel Under Avg G",
"Control Panel Under Avg B",
"Ceiling Left Avg R",
"Ceiling Left Avg G",
"Ceiling Left Avg B",
"Title Left Avg R",
"Title Left Avg G",
"Title Left Avg B",
"Title Right Avg R",
"Title Right Avg G",
"Title Right Avg B",
"Ceiling Right Avg R",
"Ceiling Right Avg G",
"Ceiling Right Avg B",
"Touch Panel Left Avg R",
"Touch Panel Left Avg G",
"Touch Panel Left Avg B",
"Touch Panel Right Avg R",
"Touch Panel Right Avg G",
"Touch Panel Right Avg B",
"Side Panel Left Inner Avg R",
"Side Panel Left Inner Avg G",
"Side Panel Left Inner Avg B",
"Side Panel Left Outer Avg R",
"Side Panel Left Outer Avg G",
"Side Panel Left Outer Avg B",
"Side Panel Left Avg R",
"Side Panel Left Avg G",
"Side Panel Left Avg B",
"Side Panel Right Outer Avg R",
"Side Panel Right Outer Avg G",
"Side Panel Right Outer Avg B",
"Side Panel Right Inner Avg R",
"Side Panel Right Inner Avg G",
"Side Panel Right Inner Avg B",
"Side Panel Right Avg R",
"Side Panel Right Avg G",
"Side Panel Right Avg B"
{
{"Common", "P1 1"},
{"Common", "P1 2"},
{"Common", "P1 3"},
{"Common", "P1 4"},
{"Common", "P1 5"},
{"Common", "P1 6"},
{"Common", "P1 7"},
{"Common", "P2 1"},
{"Common", "P2 2"},
{"Common", "P2 3"},
{"Common", "P2 4"},
{"Common", "P2 5"},
{"Common", "P2 6"},
{"Common", "P2 7"},
{"Common", "P1 Start"},
{"Common", "P2 Start"},
{"Common", "VEFX"},
{"Common", "Effect"},
{"DX", "Spot Light 1"},
{"DX", "Spot Light 2"},
{"DX", "Spot Light 3"},
{"DX", "Spot Light 4"},
{"DX", "Spot Light 5"},
{"DX", "Spot Light 6"},
{"DX", "Spot Light 7"},
{"DX", "Spot Light 8"},
{"DX", "Neon Lamp"},
{"Lightning Model", "Woofer R"},
{"Lightning Model", "Woofer G"},
{"Lightning Model", "Woofer B"},
{"Lightning Model", "IC Card Reader P1 R"},
{"Lightning Model", "IC Card Reader P1 G"},
{"Lightning Model", "IC Card Reader P1 B"},
{"Lightning Model", "IC Card Reader P2 R"},
{"Lightning Model", "IC Card Reader P2 G"},
{"Lightning Model", "IC Card Reader P2 B"},
{"Lightning Model", "Turntable P1 R"},
{"Lightning Model", "Turntable P1 G"},
{"Lightning Model", "Turntable P1 B"},
{"Lightning Model", "Turntable P2 R"},
{"Lightning Model", "Turntable P2 G"},
{"Lightning Model", "Turntable P2 B"},
{"Lightning Model", "Turntable P1 Resistance"},
{"Lightning Model", "Turntable P2 Resistance"},
{"Lightning Model", "Stage Left Avg R"},
{"Lightning Model", "Stage Left Avg G"},
{"Lightning Model", "Stage Left Avg B"},
{"Lightning Model", "Stage Right Avg R"},
{"Lightning Model", "Stage Right Avg G"},
{"Lightning Model", "Stage Right Avg B"},
{"Lightning Model", "Cabinet Left Avg R"},
{"Lightning Model", "Cabinet Left Avg G"},
{"Lightning Model", "Cabinet Left Avg B"},
{"Lightning Model", "Cabinet Right Avg R"},
{"Lightning Model", "Cabinet Right Avg G"},
{"Lightning Model", "Cabinet Right Avg B"},
{"Lightning Model", "Control Panel Under Avg R"},
{"Lightning Model", "Control Panel Under Avg G"},
{"Lightning Model", "Control Panel Under Avg B"},
{"Lightning Model", "Ceiling Left Avg R"},
{"Lightning Model", "Ceiling Left Avg G"},
{"Lightning Model", "Ceiling Left Avg B"},
{"Lightning Model", "Title Left Avg R"},
{"Lightning Model", "Title Left Avg G"},
{"Lightning Model", "Title Left Avg B"},
{"Lightning Model", "Title Right Avg R"},
{"Lightning Model", "Title Right Avg G"},
{"Lightning Model", "Title Right Avg B"},
{"Lightning Model", "Ceiling Right Avg R"},
{"Lightning Model", "Ceiling Right Avg G"},
{"Lightning Model", "Ceiling Right Avg B"},
{"Lightning Model", "Touch Panel Left Avg R"},
{"Lightning Model", "Touch Panel Left Avg G"},
{"Lightning Model", "Touch Panel Left Avg B"},
{"Lightning Model", "Touch Panel Right Avg R"},
{"Lightning Model", "Touch Panel Right Avg G"},
{"Lightning Model", "Touch Panel Right Avg B"},
{"Lightning Model", "Side Panel Left Inner Avg R"},
{"Lightning Model", "Side Panel Left Inner Avg G"},
{"Lightning Model", "Side Panel Left Inner Avg B"},
{"Lightning Model", "Side Panel Left Outer Avg R"},
{"Lightning Model", "Side Panel Left Outer Avg G"},
{"Lightning Model", "Side Panel Left Outer Avg B"},
{"Lightning Model", "Side Panel Left Avg R"},
{"Lightning Model", "Side Panel Left Avg G"},
{"Lightning Model", "Side Panel Left Avg B"},
{"Lightning Model", "Side Panel Right Outer Avg R"},
{"Lightning Model", "Side Panel Right Outer Avg G"},
{"Lightning Model", "Side Panel Right Outer Avg B"},
{"Lightning Model", "Side Panel Right Inner Avg R"},
{"Lightning Model", "Side Panel Right Inner Avg G"},
{"Lightning Model", "Side Panel Right Inner Avg B"},
{"Lightning Model", "Side Panel Right Avg R"},
{"Lightning Model", "Side Panel Right Avg G"},
{"Lightning Model", "Side Panel Right Avg B"}
}
);
}
+1 -1
View File
@@ -89,7 +89,7 @@ namespace games::iidx {
protected:
virtual ~IIDXLocalCamera() {};
ULONG m_nRefCount;
LONG m_nRefCount;
CRITICAL_SECTION m_critsec;
std::string m_name;
+15 -2
View File
@@ -47,6 +47,7 @@ namespace games {
static robin_hood::unordered_map<std::string, std::vector<Button>> buttons_keypads;
static robin_hood::unordered_map<std::string, std::vector<Button>> buttons_overlay;
static robin_hood::unordered_map<std::string, std::string> buttons_help;
static robin_hood::unordered_map<std::string, std::string> analogs_help;
static robin_hood::unordered_map<std::string, std::vector<Analog> &> analogs;
static robin_hood::unordered_map<std::string, std::vector<Light> &> lights;
static robin_hood::unordered_map<std::string, std::vector<Option>> options;
@@ -107,6 +108,7 @@ namespace games {
games.push_back(gitadora);
buttons.insert({ gitadora, gitadora::get_buttons() });
buttons_help.insert({ gitadora, gitadora::get_buttons_help() });
analogs_help.insert({ gitadora, gitadora::get_analogs_help() });
analogs.insert({ gitadora, gitadora::get_analogs() });
lights.insert({ gitadora, gitadora::get_lights() });
file_hints[gitadora].emplace_back("gdxg.dll");
@@ -254,6 +256,7 @@ namespace games {
const std::string otoca("Otoca D'or");
games.push_back(otoca);
buttons.insert({ otoca, otoca::get_buttons() });
buttons_help.insert({ otoca, otoca::get_buttons_help() });
file_hints[otoca].emplace_back("arkkep.dll");
// winning eleven
@@ -322,7 +325,7 @@ namespace games {
buttons_help.insert({ pc, pc::get_buttons_help() });
analogs.insert({ pc, pc::get_analogs() });
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() {
@@ -349,6 +352,15 @@ namespace games {
return it->second;
}
std::string get_analogs_help(const std::string &game) {
initialize();
auto it = analogs_help.find(game);
if (it == analogs_help.end()) {
return "";
}
return it->second;
}
static std::vector<Button> gen_buttons_keypads(const std::string &game) {
auto buttons = GameAPI::Buttons::getButtons(game);
std::vector<std::string> names;
@@ -389,7 +401,8 @@ namespace games {
vkey_defaults.push_back(unit == 0 ? VK_NUMPAD9 : 0xFF);
names.emplace_back(prefix + "00");
vkey_defaults.push_back(unit == 0 ? VK_RETURN : 0xFF);
// this used to be VK_RETURN, but that shares a value with normal Enter key
vkey_defaults.push_back(unit == 0 ? VK_SUBTRACT : 0xFF);
names.emplace_back(prefix + "Decimal");
vkey_defaults.push_back(unit == 0 ? VK_DECIMAL : 0xFF);
+1
View File
@@ -58,6 +58,7 @@ namespace games {
const std::vector<std::string> &get_games();
std::vector<Button> *get_buttons(const std::string &game);
std::string get_buttons_help(const std::string &game);
std::string get_analogs_help(const std::string &game);
std::vector<Button> *get_buttons_keypads(const std::string &game);
std::vector<Button> *get_buttons_overlay(const std::string &game);
std::vector<Analog> *get_analogs(const std::string &game);
+5
View File
@@ -23,6 +23,11 @@ std::vector<Button> &games::otoca::get_buttons() {
return buttons;
}
std::string games::otoca::get_buttons_help() {
// keep to max 100 characters wide
return " BT_Left Lever BT_Right";
}
std::vector<Light> &games::otoca::get_lights() {
static std::vector<Light> lights;
+1
View File
@@ -30,5 +30,6 @@ namespace games::otoca {
// getters
std::vector<Button> &get_buttons();
std::string get_buttons_help();
std::vector<Light> &get_lights();
}
+142
View File
@@ -1,16 +1,121 @@
#include "otoca.h"
#include "p4io.h"
#include "games/shared/printer.h"
#include "util/detour.h"
#include "util/libutils.h"
#include "util/logging.h"
#define OTOCA_DEBUG_VERBOSE 0
#if OTOCA_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::otoca {
bool BYPASS_CAMERA = false;
// for dev only
static bool CAMLIB_CALL_ORIGINAL = false;
OtocaGame::OtocaGame() : Game("Otoca D'or") {
}
OtocaGame::~OtocaGame() {
}
typedef void (__cdecl *LibCameraInit_t)();
static LibCameraInit_t LibCameraInit_orig = nullptr;
static void __cdecl LibCameraInit_hook() {
if (CAMLIB_CALL_ORIGINAL) {
LibCameraInit_orig();
log_debug("otoca::cam", "LibCameraInit returned");
}
}
typedef int (__cdecl *LibCameraOpen_t)(double, int, int);
static LibCameraOpen_t LibCameraOpen_orig = nullptr;
static int __cdecl LibCameraOpen_hook(double a1, int a2, int a3) {
int ret = 0;
if (CAMLIB_CALL_ORIGINAL) {
// with webcam, (15, 2, 0) returned 0
ret = LibCameraOpen_orig(a1, a2, a3);
log_debug("otoca::cam", "LibCameraOpen({}, {}, {}) returned {}", a1, a2, a3, ret);
}
return ret;
}
typedef int (__cdecl *LibCameraStop_t)(int);
static LibCameraStop_t LibCameraStop_orig = nullptr;
static int __cdecl LibCameraStop_hook(int a1) {
int ret = 0;
if (CAMLIB_CALL_ORIGINAL) {
// with webcam,
ret = LibCameraStop_orig(a1);
log_debug("otoca::cam", "LibCameraStop({}) returned {}", a1, ret);
}
return ret;
}
typedef void (__cdecl *LibCameraGetCameraNr_t)();
static LibCameraGetCameraNr_t LibCameraGetCameraNr_orig = nullptr;
static void __cdecl LibCameraGetCameraNr_hook() {
if (CAMLIB_CALL_ORIGINAL) {
LibCameraGetCameraNr_orig();
log_debug("otoca::cam", "LibCameraGetCameraNr returned");
}
}
typedef int (__cdecl *LibCameraRun_t)(int);
static LibCameraRun_t LibCameraRun_orig = nullptr;
static int __cdecl LibCameraRun_hook(int a1) {
int ret = 0;
if (CAMLIB_CALL_ORIGINAL) {
// with webcam, (0) returned 0
ret = LibCameraRun_orig(a1);
log_debug("otoca::cam", "LibCameraRun({}) returned {}", a1, ret);
}
return ret;
}
typedef int (__cdecl *LibCameraGetImage_t)(int, void *);
static LibCameraGetImage_t LibCameraGetImage_orig = nullptr;
static int __cdecl LibCameraGetImage_hook(int a1, void *a2) {
int ret = 0;
if (CAMLIB_CALL_ORIGINAL) {
// not sure if this is called
ret = LibCameraGetImage_orig(a1, a2);
log_debug("otoca::cam", "LibCameraGetImage({}, {}) returned {}", a1, fmt::ptr(a2), ret);
}
return ret;
}
typedef int (__cdecl *LibCameraGetBrightness_t)(int *, int);
static LibCameraGetBrightness_t LibCameraGetBrightness_orig = nullptr;
static int __cdecl LibCameraGetBrightness_hook(int *a1, int a2) {
int ret = 0;
if (CAMLIB_CALL_ORIGINAL) {
// with webcam, (ptr,) returned
ret = LibCameraGetBrightness_orig(a1, a2);
}
log_debug("otoca::cam", "LibCameraGetBrightness({}, {}) returned {}", fmt::ptr(a1), a2, ret);
return ret;
}
typedef int (__cdecl *LibCameraGetBrightnessRange_t)(int *, int *, int);
static LibCameraGetBrightnessRange_t LibCameraGetBrightnessRange_orig = nullptr;
static int __cdecl LibCameraGetBrightnessRange_hook(int *a1, int *a2, int a3) {
int ret = 0;
if (CAMLIB_CALL_ORIGINAL) {
// with webcam, (ptr, ptr, 0) returned 0
ret = LibCameraGetBrightnessRange_orig(a1, a2, a3);
log_debug("otoca::cam", "LibCameraGetBrightnessRange({}, {}, {}) returned {}", fmt::ptr(a1), fmt::ptr(a2), a3, ret);
}
return ret;
}
void OtocaGame::attach() {
Game::attach();
@@ -23,5 +128,42 @@ namespace games::otoca {
// initialize IO hooks
p4io_hook();
games::shared::printer_attach();
if (BYPASS_CAMERA) {
libutils::try_library("libcamera.dll");
const auto libcamera = "libcamera.dll";
detour::trampoline_try(
libcamera,
"?LibCameraInit@@YAXXZ",
LibCameraInit_hook, &LibCameraInit_orig);
detour::trampoline_try(
libcamera,
"?LibCameraOpen@@YA?AW4LIBCAMERA_STATUS@@NHPAUt_libcamera_open_param@@@Z",
LibCameraOpen_hook, &LibCameraOpen_orig);
detour::trampoline_try(
libcamera,
"?LibCameraStop@@YA?AW4LIBCAMERA_STATUS@@H@Z",
LibCameraStop_hook, &LibCameraStop_orig);
detour::trampoline_try(
libcamera,
"?LibCameraGetCameraNr@@YAHXZ",
LibCameraGetCameraNr_hook, &LibCameraGetCameraNr_orig);
detour::trampoline_try(
libcamera,
"?LibCameraRun@@YA?AW4LIBCAMERA_STATUS@@H@Z",
LibCameraRun_hook, &LibCameraRun_orig);
detour::trampoline_try(
libcamera,
"?LibCameraGetImage@@YA?AW4LIBCAMERA_STATUS@@HPAX@Z",
LibCameraGetImage_hook, &LibCameraGetImage_orig);
detour::trampoline_try(
libcamera,
"?LibCameraGetBrightness@@YA?AW4LIBCAMERA_STATUS@@PAJH@Z",
LibCameraGetBrightness_hook, &LibCameraGetBrightness_orig);
detour::trampoline_try(
libcamera,
"?LibCameraGetBrightnessRange@@YA?AW4LIBCAMERA_STATUS@@PAJ0H@Z",
LibCameraGetBrightnessRange_hook, &LibCameraGetBrightnessRange_orig);
}
}
}
+2
View File
@@ -4,6 +4,8 @@
namespace games::otoca {
extern bool BYPASS_CAMERA;
class OtocaGame : public games::Game {
public:
OtocaGame();
+38 -11
View File
@@ -1,5 +1,7 @@
#include "bi2x_hook.h"
#if SPICE64
#include "util/execexe.h"
#include "util/logging.h"
#include "rawinput/rawinput.h"
@@ -9,9 +11,10 @@
#include "util/tapeled.h"
#include "pc.h"
#include "util/utils.h"
#include "util/time.h"
#include "util/socd_cleaner.h"
namespace games::pc {
bool PC_KNOB_MODE = false;
constexpr float RELATIVE_DECAY = 0.02f;
struct FADER_RELATIVE {
@@ -75,18 +78,22 @@ namespace games::pc {
*/
struct AIO_SCI_COMM {
uint8_t data[0xf8];
};
struct AIO_NMGR_IOB2 {
uint8_t data[0xe30];
};
struct AIO_IOB2_BI2X_AC1 {
uint8_t data[0x4538];
};
struct AIO_IOB2_BI2X_AC1__SETTING {
};
struct AIO_IOB2_BI2X_WRFIRM {
uint8_t data[0x20f48];
};
struct AIO_IOB2_BI2X_AC1__INPUT
@@ -191,6 +198,12 @@ namespace games::pc {
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
*/
@@ -338,14 +351,21 @@ namespace games::pc {
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])) {
val = 0.f;
} else if (GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::FaderL_Left])) {
const auto now = get_performance_milliseconds();
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;
} else if (GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::FaderL_Right])) {
} else if (fader_l_socd == socd::SocdCW) {
val = +1.0f;
}
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_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;
}
}
if (GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::FaderR_Left]) &&
GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::FaderR_Right])) {
val = 0.f;
} else if (GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::FaderR_Left])) {
const auto fader_r_socd = socd::socd_clean(
1, // Fader-R
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;
} else if (GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::FaderR_Right])) {
} else if (fader_r_socd == socd::SocdCW) {
val = +1.0f;
}
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.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
#if SPICE64
namespace games::pc {
void bi2x_hook_init();
}
#endif
+20 -5
View File
@@ -1,3 +1,6 @@
// QueryDisplayConfig
#define _WIN32_WINNT 0x0601
#include "pc.h"
#include <format>
@@ -12,10 +15,12 @@
#include "misc/wintouchemu.h"
#include "hooks/graphics/graphics.h"
#include "rawinput/rawinput.h"
#include "util/socd_cleaner.h"
namespace games::pc {
std::string PC_INJECT_ARGS = "";
bool PC_NO_IO = false;
bool PC_KNOB_MODE = false;
static acioemu::ACIOHandle *acioHandle = nullptr;
static std::wstring portName = L"COM1";
@@ -30,10 +35,10 @@ namespace games::pc {
if (pRawInputDevices &&
(uiNumDevices > 0) &&
(pRawInputDevices[0].hwndTarget == RI_MGR->input_hwnd)) {
return RegisterRawInputDevices_orig(pRawInputDevices, uiNumDevices, cbSize);
}
// otherwise, it must be the game; prevent the game from registering for raw input
// and hijacking WM_INPUT messages.
SetLastError(0xDEADBEEF);
@@ -56,11 +61,16 @@ namespace games::pc {
}
LONG WINAPI QueryDisplayConfig_hook(UINT32, UINT32*, DISPLAYCONFIG_PATH_INFO*, UINT32*, DISPLAYCONFIG_MODE_INFO*, DISPLAYCONFIG_TOPOLOGY_ID*) {
// make unity fallback to EnumDisplaySettingsW as I don't
// make unity fallback to EnumDisplaySettingsW as I don't
// want to deal with this api which is way more complex
return ERROR_NOT_SUPPORTED;
}
void PCGame::pre_attach() {
// SOCD
socd::ALGORITHM = socd::SocdAlgorithm::PreferRecent;
}
void PCGame::attach() {
Game::attach();
@@ -78,17 +88,22 @@ namespace games::pc {
execexe::load_library("libaio-iob2_video.dll");
execexe::load_library("win10actlog.dll");
#if SPICE64
if (!PC_NO_IO) {
bi2x_hook_init();
}
#endif
});
const auto user32Dll = "user32.dll";
detour::trampoline_try(user32Dll, "RegisterRawInputDevices",
RegisterRawInputDevices_hook, &RegisterRawInputDevices_orig);
detour::trampoline_try(user32Dll, "QueryDisplayConfig",
detour::trampoline_try(user32Dll, "QueryDisplayConfig",
QueryDisplayConfig_hook, &QueryDisplayConfig_orig);
detour::trampoline_try(user32Dll, "EnumDisplaySettingsW",
detour::trampoline_try(user32Dll, "EnumDisplaySettingsW",
EnumDisplaySettingsW_hook, &EnumDisplaySettingsW_orig);
if (GRAPHICS_SHOW_CURSOR) {
+1
View File
@@ -11,6 +11,7 @@ namespace games::pc {
public:
PCGame() : Game("Polaris Chord") {}
virtual void pre_attach() override;
virtual void attach() override;
virtual void detach() override;
};
+15 -1
View File
@@ -1,5 +1,7 @@
#include "bi2x_hook.h"
#if SPICE64
#include "util/detour.h"
#include "util/logging.h"
#include "rawinput/rawinput.h"
@@ -15,15 +17,19 @@ namespace games::qks {
*/
struct AIO_SCI_COMM {
uint8_t data[0xf8];
};
struct AIO_NMGR_IOB {
uint8_t data[0xa00];
};
struct AIO_IOB2_BI2X_AC1 {
uint8_t data[0x3908];
};
struct AIO_IOB2_BI2X_WRFIRM {
uint8_t data[0x20f48];
};
struct AIO_NMGR_IOB__NODEINFO {
@@ -174,6 +180,12 @@ namespace games::qks {
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
*/
@@ -602,4 +614,6 @@ namespace games::qks {
detour::trampoline_try(libaioDll, "aioNodeCtl_IsError",
aioNodeCtl_IsError, &aioNodeCtl_IsError_orig);
}
}
}
#endif
+5 -1
View File
@@ -1,7 +1,11 @@
#pragma once
#if SPICE64
#include <stdint.h>
namespace games::qks {
void bi2x_hook_init();
}
}
#endif
+4
View File
@@ -86,9 +86,13 @@ namespace games::qks {
detour::trampoline_try("execexe.dll", MAKEINTRESOURCE(9),
execexe_CreateFileA_hook,&execexe_CreateFileA_orig);
#if SPICE64
// insert BI2X hooks
bi2x_hook_init();
#endif
// add card reader
acioHandle = new acioemu::ACIOHandle(portName.c_str());
devicehook_init_trampoline();
+1 -1
View File
@@ -88,10 +88,10 @@ namespace games::qma {
// create quiz directory
static bool dirs_created = false;
if (!dirs_created && _wcsnicmp(lpFileName, L"E:\\LMA", 6) == 0) {
dirs_created = true;
fileutils::dir_create_recursive("E:\\LMA\\quiz11");
fileutils::dir_create_recursive("E:\\LMA\\quiz13");
fileutils::dir_create_recursive("E:\\LMA\\quiz15");
dirs_created = true;
}
// return result
+100 -36
View File
@@ -1,5 +1,7 @@
#include "bi2x_hook.h"
#if SPICE64
#include <cstdint>
#include "util/detour.h"
#include "util/logging.h"
@@ -10,12 +12,22 @@
#include "launcher/options.h"
#include "io.h"
#include "games/sdvx/sdvx.h"
#include "util/socd_cleaner.h"
#include "util/tapeled.h"
#include "util/time.h"
#include "acioemu/icca.h"
#define DEBUG_VERBOSE 0
#if DEBUG_VERBOSE
#define log_debug(module, format_str, ...) logger::push( \
LOG_FORMAT("M", module, format_str, ## __VA_ARGS__), logger::Style::GREY)
#else
#define log_debug(module, format_str, ...)
#endif
namespace games::sdvx {
constexpr bool BI2X_PASSTHROUGH = false;
bool BI2X_INITIALIZED = false;
/*
* class definitions
@@ -26,20 +38,54 @@ namespace games::sdvx {
uint8_t data[0x10];
};
struct AIO_NMGR_IOB2 {
uint8_t dummy0[0x50];
struct AIO_NMGR_IOB2_VTABLE {
// other functions here, but they never get called
uint8_t dummy[0x50];
void (__fastcall *pAIO_NMGR_IOB_BeginManage)(int64_t a1);
uint8_t dummy1[0x9A0];
};
struct AIO_NMGR_IOB2 {
AIO_NMGR_IOB2_VTABLE *vptr;
uint8_t dummy[0x9F0];
};
// confirmed in EG final in aioNMgrIob2_Create
static_assert(sizeof(AIO_NMGR_IOB2) == 0x9F8);
struct AIO_IOB2_BI2X_UFC {
// who knows
uint8_t data[0x13F8];
uint8_t data[0x39D8];
};
// confirmed in EG final in aioIob2Bi2xUFC_Create
static_assert(sizeof(AIO_IOB2_BI2X_UFC) == 0x39D8);
struct AIO_IOB2_BI2X_UFC__INPUT {
uint8_t DevIoCounter;
uint8_t bExIoAErr;
uint8_t bExIoBErr;
uint8_t bPcPowerOn;
uint8_t bPcPowerCheck;
uint8_t CoinCount;
uint8_t bTest;
uint8_t bService;
uint8_t bCoinSw;
uint8_t bCoinJam;
uint8_t bHPDetect;
};
struct AIO_IOB2_BI2X_UFC__DEVSTATUS {
// of course you could work with variables here
uint8_t buffer[0x19E];
uint8_t InputCounter;
uint8_t OutputCounter;
uint8_t IoResetCounter;
uint8_t TapeLedCounter;
uint8_t TapeLedRate[8];
AIO_IOB2_BI2X_UFC__INPUT Input;
uint8_t unk_1[289];
uint16_t AnalogLeft;
uint16_t AnalogRight;
uint8_t Buttons;
uint8_t unk_2[97];
};
/*
@@ -66,7 +112,7 @@ namespace games::sdvx {
// libaio-iob.dll
typedef AC_HNDLIF* (__fastcall *aioIob2Bi2x_OpenSciUsbCdc_t)(uint8_t device_num);
typedef int64_t (__fastcall *aioIob2Bi2x_WriteFirmGetState_t)(int64_t a1);
typedef AIO_NMGR_IOB2** (__fastcall *aioNMgrIob2_Create_t)(AC_HNDLIF *a1, unsigned int a2);
typedef AIO_NMGR_IOB2* (__fastcall *aioNMgrIob2_Create_t)(AC_HNDLIF *a1, unsigned int a2);
/*
* function pointers
@@ -133,8 +179,8 @@ namespace games::sdvx {
AIO_IOB2_BI2X_UFC__GetDeviceStatus_orig(This, status);
}
status->buffer[0] = count;
status->buffer[12] = count;
status->InputCounter = count;
status->Input.DevIoCounter = count;
count++;
// get buttons
@@ -142,41 +188,51 @@ namespace games::sdvx {
// control buttons
if (GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::Test]))
status->buffer[18] = 0x01;
status->Input.bTest = 0x01;
if (GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::Service]))
status->buffer[19] = 0x01;
status->Input.bService = 0x01;
if (GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::CoinMech]))
status->buffer[20] = 0x01;
status->Input.bCoinSw = 0x01;
if (GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::Start]))
status->buffer[316] |= 0x01;
status->Buttons |= 0x01;
if (GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::BT_A]))
status->buffer[316] |= 0x02;
status->Buttons |= 0x02;
if (GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::BT_B]))
status->buffer[316] |= 0x04;
status->Buttons |= 0x04;
if (GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::BT_C]))
status->buffer[316] |= 0x08;
status->Buttons |= 0x08;
if (GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::BT_D]))
status->buffer[316] |= 0x10;
status->Buttons |= 0x10;
if (GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::FX_L]))
status->buffer[316] |= 0x20;
status->Buttons |= 0x20;
if (GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::FX_R]))
status->buffer[316] |= 0x40;
status->Buttons |= 0x40;
if (GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::Headphone]))
status->buffer[22] = 0x01;
status->Input.bHPDetect = 0x01;
status->Input.CoinCount += eamuse_coin_get_stock();
const auto now = get_performance_milliseconds();
// volume left
if (GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::VOL_L_Left])) {
const auto vol_l_state = socd::socd_clean(0,
GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::VOL_L_Left]),
GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::VOL_L_Right]),
now);
if (vol_l_state == socd::SocdCCW) {
VOL_L -= ((uint16_t)DIGITAL_KNOB_SENS * 4);
}
if (GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::VOL_L_Right])) {
} else if (vol_l_state == socd::SocdCW) {
VOL_L += ((uint16_t)DIGITAL_KNOB_SENS * 4);
}
// volume right
if (GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::VOL_R_Left])) {
const auto vol_r_state = socd::socd_clean(1,
GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::VOL_R_Left]),
GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::VOL_R_Right]),
now);
if (vol_r_state == socd::SocdCCW) {
VOL_R -= ((uint16_t)DIGITAL_KNOB_SENS * 4);
}
if (GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::VOL_R_Right])) {
} else if (vol_r_state == socd::SocdCW) {
VOL_R += ((uint16_t)DIGITAL_KNOB_SENS * 4);
}
@@ -189,8 +245,14 @@ namespace games::sdvx {
vol_right += (uint16_t) (GameAPI::Analogs::getState(RI_MGR, analogs[Analogs::VOL_R]) * 65535);
}
*((uint16_t*) &status->buffer[312]) = vol_left;
*((uint16_t*) &status->buffer[314]) = vol_right;
status->AnalogLeft = vol_left;
status->AnalogRight = vol_right;
log_debug(
"bi2x_hook",
"knobs = {} {}",
status->AnalogLeft,
status->AnalogRight);
}
static void __fastcall AIO_IOB2_BI2X_UFC__IoReset(AIO_IOB2_BI2X_UFC *This,
@@ -345,21 +407,21 @@ namespace games::sdvx {
log_info("bi2x_hook", "AIO_NMGR_IOB::BeginManage");
}
static AIO_NMGR_IOB2** __fastcall aioNMgrIob2_Create(AC_HNDLIF *a1, unsigned int a2) {
static AIO_NMGR_IOB2* __fastcall aioNMgrIob2_Create(AC_HNDLIF *a1, unsigned int a2) {
if (aioNmgrIob2 == nullptr) {
aioNmgrIob2 = new AIO_NMGR_IOB2;
memset(aioNmgrIob2, 0x0, sizeof(AIO_NMGR_IOB2));
aioNmgrIob2->pAIO_NMGR_IOB_BeginManage = AIO_NMGR_IOB_BeginManageStub;
aioNmgrIob2 = new AIO_NMGR_IOB2{};
aioNmgrIob2->vptr = new AIO_NMGR_IOB2_VTABLE{};
aioNmgrIob2->vptr->pAIO_NMGR_IOB_BeginManage = AIO_NMGR_IOB_BeginManageStub;
}
log_info("bi2x_hook", "aioNMgrIob2_Create");
BI2X_INITIALIZED = true;
log_info("bi2x_hook", "aioNMgrIob2_Create returned {}, size=0x{:x}, vptr @ {}",
fmt::ptr(aioNmgrIob2), sizeof(*aioNmgrIob2), fmt::ptr(aioNmgrIob2->vptr));
// enable hack to make PIN pad work for KFC in BI2X mode
// this explicit check in the I/O init path is necessary
// (as opposed to just doing a check for "isValkyrieCabMode?")
// because there are hex edits that allow you to use legacy (KFC/BIO2) IO while in Valk mode
acioemu::ICCA_DEVICE_HACK = true;
return &aioNmgrIob2;
return aioNmgrIob2;
}
static int64_t __fastcall aioIob2Bi2x_WriteFirmGetState(int64_t a1) {
@@ -411,3 +473,5 @@ namespace games::sdvx {
aioNMgrIob2_Create, &aioNMgrIob2_Create_orig);
}
}
#endif
+4
View File
@@ -1,6 +1,10 @@
#pragma once
#if SPICE64
namespace games::sdvx {
void bi2x_hook_init();
}
#endif
+71 -69
View File
@@ -65,76 +65,78 @@ std::vector<Light> &games::sdvx::get_lights() {
if (lights.empty()) {
lights = GameAPI::Lights::getLights("Sound Voltex");
GameAPI::Lights::sortLights(
GameAPI::Lights::sortLightsWithCategory(
&lights,
"BT-A",
"BT-B",
"BT-C",
"BT-D",
"FX-L",
"FX-R",
"Start",
"Wing Left Up R",
"Wing Left Up G",
"Wing Left Up B",
"Wing Right Up R",
"Wing Right Up G",
"Wing Right Up B",
"Wing Left Low R",
"Wing Left Low G",
"Wing Left Low B",
"Wing Right Low R",
"Wing Right Low G",
"Wing Right Low B",
"Woofer R",
"Woofer G",
"Woofer B",
"Controller R",
"Controller G",
"Controller B",
"Generator R",
"Generator G",
"Generator B",
"Pop",
"Title Left",
"Title Right",
"Volume Sound",
"Volume Headphone",
"Volume External",
"Volume Woofer",
"IC Card Reader R",
"IC Card Reader G",
"IC Card Reader B",
"Title Avg R",
"Title Avg G",
"Title Avg B",
"Upper Left Speaker Avg R",
"Upper Left Speaker Avg G",
"Upper Left Speaker Avg B",
"Upper Right Speaker Avg R",
"Upper Right Speaker Avg G",
"Upper Right Speaker Avg B",
"Left Wing Avg R",
"Left Wing Avg G",
"Left Wing Avg B",
"Right Wing Avg R",
"Right Wing Avg G",
"Right Wing Avg B",
"Lower Left Speaker Avg R",
"Lower Left Speaker Avg G",
"Lower Left Speaker Avg B",
"Lower Right Speaker Avg R",
"Lower Right Speaker Avg G",
"Lower Right Speaker Avg B",
"Control Panel Avg R",
"Control Panel Avg G",
"Control Panel Avg B",
"Woofer Avg R",
"Woofer Avg G",
"Woofer Avg B",
"V Unit Avg R",
"V Unit Avg G",
"V Unit Avg B"
{
{"Common", "BT-A"},
{"Common", "BT-B"},
{"Common", "BT-C"},
{"Common", "BT-D"},
{"Common", "FX-L"},
{"Common", "FX-R"},
{"Common", "Start"},
{"Nemsys", "Wing Left Up R"},
{"Nemsys", "Wing Left Up G"},
{"Nemsys", "Wing Left Up B"},
{"Nemsys", "Wing Right Up R"},
{"Nemsys", "Wing Right Up G"},
{"Nemsys", "Wing Right Up B"},
{"Nemsys", "Wing Left Low R"},
{"Nemsys", "Wing Left Low G"},
{"Nemsys", "Wing Left Low B"},
{"Nemsys", "Wing Right Low R"},
{"Nemsys", "Wing Right Low G"},
{"Nemsys", "Wing Right Low B"},
{"Nemsys", "Woofer R"},
{"Nemsys", "Woofer G"},
{"Nemsys", "Woofer B"},
{"Nemsys", "Controller R"},
{"Nemsys", "Controller G"},
{"Nemsys", "Controller B"},
{"Nemsys", "Generator R"},
{"Nemsys", "Generator G"},
{"Nemsys", "Generator B"},
{"Nemsys", "Pop"},
{"Nemsys", "Title Left"},
{"Nemsys", "Title Right"},
{"System", "Volume Sound"},
{"System", "Volume Headphone"},
{"System", "Volume External"},
{"System", "Volume Woofer"},
{"Valkyrie", "IC Card Reader R"},
{"Valkyrie", "IC Card Reader G"},
{"Valkyrie", "IC Card Reader B"},
{"Valkyrie", "Title Avg R"},
{"Valkyrie", "Title Avg G"},
{"Valkyrie", "Title Avg B"},
{"Valkyrie", "Upper Left Speaker Avg R"},
{"Valkyrie", "Upper Left Speaker Avg G"},
{"Valkyrie", "Upper Left Speaker Avg B"},
{"Valkyrie", "Upper Right Speaker Avg R"},
{"Valkyrie", "Upper Right Speaker Avg G"},
{"Valkyrie", "Upper Right Speaker Avg B"},
{"Valkyrie", "Left Wing Avg R"},
{"Valkyrie", "Left Wing Avg G"},
{"Valkyrie", "Left Wing Avg B"},
{"Valkyrie", "Right Wing Avg R"},
{"Valkyrie", "Right Wing Avg G"},
{"Valkyrie", "Right Wing Avg B"},
{"Valkyrie", "Lower Left Speaker Avg R"},
{"Valkyrie", "Lower Left Speaker Avg G"},
{"Valkyrie", "Lower Left Speaker Avg B"},
{"Valkyrie", "Lower Right Speaker Avg R"},
{"Valkyrie", "Lower Right Speaker Avg G"},
{"Valkyrie", "Lower Right Speaker Avg B"},
{"Valkyrie", "Control Panel Avg R"},
{"Valkyrie", "Control Panel Avg G"},
{"Valkyrie", "Control Panel Avg B"},
{"Valkyrie", "Woofer Avg R"},
{"Valkyrie", "Woofer Avg G"},
{"Valkyrie", "Woofer Avg B"},
{"Valkyrie", "V Unit Avg R"},
{"Valkyrie", "V Unit Avg G"},
{"Valkyrie", "V Unit Avg B"}
}
);
}
+88 -22
View File
@@ -2,7 +2,6 @@
#include <external/robin_hood.h>
#include "avs/game.h"
#include "games/shared/lcdhandle.h"
#include "games/io.h"
#include "hooks/audio/audio.h"
@@ -19,9 +18,12 @@
#include "util/detour.h"
#include "util/logging.h"
#include "util/sigscan.h"
#include "util/socd_cleaner.h"
#include "util/time.h"
#include "util/libutils.h"
#include "misc/wintouchemu.h"
#include "misc/eamuse.h"
#include "misc/nativetouchhook.h"
#include "misc/wintouchemu.h"
#include "bi2x_hook.h"
#include "camera.h"
#include "io.h"
@@ -48,21 +50,27 @@ namespace games::sdvx {
SdvxOverlayPosition OVERLAY_POS = SDVX_OVERLAY_BOTTOM;
bool ENABLE_COM_PORT_SCAN_HOOK = false;
std::optional<std::string> SOUND_OUTPUT_DEVICE = std::nullopt;
bool USE_ASIO = false;
std::optional<std::string> ASIO_DRIVER = std::nullopt;
// states
bool SHOW_VM_MONITOR_WARNING = false;
static HKEY real_asio_reg_handle = nullptr;
static HKEY real_asio_device_reg_handle = nullptr;
static LONG WINAPI RegOpenKeyA_hook(HKEY hKey, LPCSTR lpSubKey, PHKEY phkResult) {
if (lpSubKey != nullptr && phkResult != nullptr) {
if (hKey == HKEY_LOCAL_MACHINE &&
ASIO_DRIVER.has_value() &&
_stricmp(lpSubKey, "software\\asio") == 0)
{
*phkResult = PARENT_ASIO_REG_HANDLE;
if (lpSubKey != nullptr &&
phkResult != nullptr &&
hKey == HKEY_LOCAL_MACHINE &&
_stricmp(lpSubKey, "software\\asio") == 0) {
// prevent the game from randomly picking an ASIO device
if (!USE_ASIO) {
return ERROR_FILE_NOT_FOUND;
}
// convince the game to use our hook (so we can swap in the preferred device)
if (ASIO_DRIVER.has_value()) {
*phkResult = PARENT_ASIO_REG_HANDLE;
return RegOpenKeyA_orig(hKey, lpSubKey, &real_asio_reg_handle);
}
}
@@ -109,7 +117,7 @@ namespace games::sdvx {
lpSubKey != nullptr && phkResult != nullptr &&
_stricmp(lpSubKey, "HARDWARE\\DEVICEMAP\\SERIALCOMM") == 0) {
log_info("sdvx::io", "hook access to HKLM\\HARDWARE\\DEVICEMAP\\SERIALCOMM to force COM1 ICCA");
return 2; // ERROR_FILE_NOT_FOUND
return ERROR_FILE_NOT_FOUND;
}
return RegOpenKeyExA_orig(hKey, lpSubKey, ulOptions, samDesired, phkResult);
@@ -226,9 +234,56 @@ namespace games::sdvx {
});
return false;
}
static bool logged_missing_layer_error = false;
// this is raw SHIFT-JIS text for
// W:BM2D: CreateLayer() 指定したレイヤーは存在しません
if (!logged_missing_layer_error &&
data.find(
"\x57\x3A\x42\x4D\x32\x44\x3A\x20\x43\x72\x65\x61\x74\x65\x4C\x61\x79\x65\x72\x28\x29\x20"
"\x8E\x77\x92\xE8\x82\xB5\x82\xBD\x83\x8C\x83\x43\x83\x84\x81\x5B\x82\xCD\x91\xB6\x8D\xDD"
"\x82\xB5\x82\xDC\x82\xB9\x82\xF1") != std::string::npos) {
logged_missing_layer_error = true;
deferredlogs::defer_error_messages({
"sdvx game engine detected missing or corrupt file(s); raw SHIFT-JIS error was:",
std::string(" ") + data,
" this will almost certainly cause your game to crash",
" this is often caused by mismatched game DLLs or missing resources from game updates",
" double check the integrity of your game data and try again"
});
return false;
}
return false;
}
void SDVXGame::detect_sound_output_device() {
USE_ASIO = false;
// if the user specified a value for -sdvxasio, use asio
if (ASIO_DRIVER.has_value()) {
log_misc(
"sdvx",
"-sdvxasio is set to \"{}\"", ASIO_DRIVER.value());
USE_ASIO = true;
} else {
// see if XONAR AE is present; if so, use that
HKEY subkey;
LSTATUS result;
result = RegOpenKey(HKEY_LOCAL_MACHINE, "SOFTWARE\\ASIO\\XONAR SOUND CARD(64)", &subkey);
if (result == ERROR_SUCCESS) {
RegCloseKey(subkey);
USE_ASIO = true;
log_misc(
"sdvx",
"found HKLM\\SOFTWARE\\ASIO\\XONAR SOUND CARD(64)");
}
}
if (!USE_ASIO) {
log_misc("sdvx", "asio not configured");
}
}
typedef void **(__fastcall *volume_set_t)(uint64_t, uint64_t, uint64_t);
static volume_set_t volume_set_orig = nullptr;
static void **__fastcall volume_set_hook(uint64_t vol_sound, uint64_t vol_woofer, uint64_t vol_headphone) {
@@ -295,12 +350,9 @@ namespace games::sdvx {
}
#ifdef SPICE64 // SDVX5+ specific code
bool isValkyrieCabinetMode = avs::game::SPEC[0] == 'G' || avs::game::SPEC[0] == 'H';
auto options = games::get_options(eamuse_get_game());
// check -monitor + UFC mode
if (!GRAPHICS_WINDOWED &&
options->at(launcher::Options::DisplayAdapter).is_active() &&
isValkyrieCabinetMode) {
if (!GRAPHICS_WINDOWED && D3D9_ADAPTER.has_value() && is_valkyrie_model()) {
SHOW_VM_MONITOR_WARNING = true;
log_warning(
"sdvx",
"\n\n!!! using -monitor option with VM mode is NOT recommended due to !!!\n"
@@ -324,16 +376,29 @@ namespace games::sdvx {
}
#endif
#ifdef SPICE64
this->detect_sound_output_device();
#endif
// SOCD
auto options = games::get_options(eamuse_get_game());
socd::ALGORITHM = socd::SocdAlgorithm::PreferRecent;
if (options->at(launcher::Options::SDVXDigitalKnobSocd).is_active()) {
if (options->at(launcher::Options::SDVXDigitalKnobSocd).value_text() == "neutral") {
socd::ALGORITHM = socd::SocdAlgorithm::Neutral;
} else if (options->at(launcher::Options::SDVXDigitalKnobSocd).value_text() == "last") {
socd::ALGORITHM = socd::SocdAlgorithm::PreferRecent;
}
}
}
void SDVXGame::attach() {
Game::attach();
#ifdef SPICE64 // SDVX5+ specific code
bool isValkyrieCabinetMode = avs::game::SPEC[0] == 'G' || avs::game::SPEC[0] == 'H';
// LCD handle
if (!isValkyrieCabinetMode) {
if (!is_valkyrie_model()) {
devicehook_init();
devicehook_add(new games::shared::LCDHandle());
}
@@ -372,11 +437,12 @@ namespace games::sdvx {
nvapi_hook::initialize(avs::game::DLL_INSTANCE);
}
// check for Valkyrie cabinet mode
if (isValkyrieCabinetMode) {
// hook touch window
// in windowed mode, game can accept mouse input on the second screen
if (!NATIVETOUCH && !GRAPHICS_WINDOWED) {
if (is_valkyrie_model()) {
if (NATIVETOUCH) {
nativetouchhook::hook(avs::game::DLL_INSTANCE);
} else if (!NATIVETOUCH && !GRAPHICS_WINDOWED) {
// hook touch window
// in windowed mode, game can accept mouse input on the second screen
wintouchemu::FORCE = true;
wintouchemu::INJECT_MOUSE_AS_WM_TOUCH = true;
wintouchemu::hook_title_ends(
+13 -1
View File
@@ -4,6 +4,7 @@
#include <optional>
#include <cstdint>
#include "avs/game.h"
#include "games/game.h"
namespace games::sdvx {
@@ -20,9 +21,18 @@ namespace games::sdvx {
extern bool NATIVETOUCH;
extern uint8_t DIGITAL_KNOB_SENS;
extern std::optional<std::string> ASIO_DRIVER;
extern bool BI2X_INITIALIZED;
extern SdvxOverlayPosition OVERLAY_POS;
// states
extern bool SHOW_VM_MONITOR_WARNING;
static inline bool is_valkyrie_model() {
return (
avs::game::is_model("KFC") &&
(avs::game::SPEC[0] == 'G' || avs::game::SPEC[0] == 'H')
);
}
class SDVXGame : public games::Game {
public:
SDVXGame();
@@ -30,5 +40,7 @@ namespace games::sdvx {
virtual void attach() override;
virtual void post_attach() override;
virtual void detach() override;
private:
void detect_sound_output_device();
};
}
+1 -1
View File
@@ -334,7 +334,7 @@ namespace games::shared {
image_data[index + 2] = tmp;
}
if (avs::game::is_model({"KLP", "KFC"})) {
if (avs::game::is_model({"KLP", "KFC", "NCG"})) {
// rotate image clockwise
log_misc("printer", "rotate clockwise...");
auto rotated = new uint8_t[image_width * image_height * 3];
+2 -1
View File
@@ -42,7 +42,8 @@ namespace hooks::audio {
bool USE_DUMMY = false;
WAVEFORMATEXTENSIBLE FORMAT {};
std::optional<Backend> BACKEND = std::nullopt;
size_t ASIO_DRIVER_ID = 0;
std::optional<size_t> ASIO_DRIVER_ID = std::nullopt;
std::string ASIO_DRIVER_NAME = "";
bool ASIO_FORCE_UNLOAD_ON_STOP = false;
// private globals
+3 -1
View File
@@ -1,6 +1,7 @@
#pragma once
#include <optional>
#include <string>
#include <windows.h>
#include <mmreg.h>
@@ -20,7 +21,8 @@ namespace hooks::audio {
extern bool USE_DUMMY;
extern WAVEFORMATEXTENSIBLE FORMAT;
extern std::optional<Backend> BACKEND;
extern size_t ASIO_DRIVER_ID;
extern std::optional<size_t> ASIO_DRIVER_ID;
extern std::string ASIO_DRIVER_NAME;
extern bool ASIO_FORCE_UNLOAD_ON_STOP;
extern bool LOW_LATENCY_SHARED_WASAPI;
@@ -8,6 +8,7 @@
#include "hooks/audio/audio_private.h"
#include "hooks/audio/backends/mmdevice/audio_endpoint_volume.h"
#include "hooks/audio/backends/wasapi/audio_client.h"
#include "util/utils.h"
#define PRINT_FAILED_RESULT(name, ret) \
do { \
@@ -56,6 +57,7 @@ ULONG STDMETHODCALLTYPE WrappedIMMDevice::Release() {
ULONG refs = pReal != nullptr ? pReal->Release() : 0;
if (refs == 0) {
log_misc("audio::mmdevice", "WrappedIMMDevice::Release");
delete this;
}
@@ -69,7 +71,7 @@ HRESULT STDMETHODCALLTYPE WrappedIMMDevice::Activate(
PROPVARIANT *pActivationParams,
void **ppInterface)
{
log_misc("audio::mmdevice", "WrappedIMMDevice::Activate");
log_misc("audio::mmdevice", "WrappedIMMDevice::Activate {}", guid2s(iid));
// call original
HRESULT ret = pReal->Activate(iid, dwClsCtx, pActivationParams, ppInterface);
@@ -80,7 +82,10 @@ HRESULT STDMETHODCALLTYPE WrappedIMMDevice::Activate(
return ret;
}
if (iid == IID_IAudioClient) {
// almost all games request IAudioClient
// so far we have not seen any games request IAudioClient2
// SDVX EG Final uses IID_IAudioClient3, but only when shared mode patch is on
if (iid == IID_IAudioClient || iid == IID_IAudioClient3) {
// prevent initialization recursion when using some ASIO backends that proxy to DirectSound, WASAPI, or WDM
// like ASIO4All or FlexASIO
@@ -90,23 +95,18 @@ HRESULT STDMETHODCALLTYPE WrappedIMMDevice::Activate(
}
std::lock_guard initialize_guard(hooks::audio::INITIALIZE_LOCK, std::adopt_lock);
auto client = reinterpret_cast<IAudioClient *>(*ppInterface);
// release old audio client if initialized
if (hooks::audio::CLIENT) {
hooks::audio::CLIENT->Release();
}
/*
ret = wrap_audio_client(pReal, dwClsCtx, pActivationParams, &client);
if (FAILED(ret)) {
return ret;
IAudioClient *client = nullptr;
if (iid == IID_IAudioClient) {
client = wrap_audio_client(reinterpret_cast<IAudioClient *>(*ppInterface));
} else { // IID_IAudioClient3
client = wrap_audio_client3(reinterpret_cast<IAudioClient3 *>(*ppInterface));
}
*/
client = wrap_audio_client(client);
*ppInterface = client;
// persist the audio client
hooks::audio::CLIENT = client;
hooks::audio::CLIENT->AddRef();
@@ -0,0 +1,44 @@
#include "device_collection.h"
#include "device.h"
#include "util/utils.h"
#include "util/logging.h"
HRESULT STDMETHODCALLTYPE WrappedIMMDeviceCollection::QueryInterface(REFIID riid, void **ppvObj) {
if (ppvObj == nullptr) {
return E_POINTER;
}
if (riid == __uuidof(IMMDeviceCollection)) {
this->AddRef();
*ppvObj = this;
return S_OK;
}
return pReal->QueryInterface(riid, ppvObj);
}
ULONG STDMETHODCALLTYPE WrappedIMMDeviceCollection::AddRef() {
return pReal->AddRef();
}
ULONG STDMETHODCALLTYPE WrappedIMMDeviceCollection::Release() {
// get reference count of underlying interface
ULONG refs = pReal != nullptr ? pReal->Release() : 0;
if (refs == 0) {
delete this;
}
return refs;
}
HRESULT STDMETHODCALLTYPE WrappedIMMDeviceCollection::GetCount(UINT *pcDevices) {
return pReal->GetCount(pcDevices);
}
HRESULT STDMETHODCALLTYPE WrappedIMMDeviceCollection::Item(UINT nDevice, IMMDevice **ppDevice) {
log_info("audio", "WrappedIMMDeviceCollection::Item[{}]", nDevice);
// call original
const auto hr = pReal->Item(nDevice, ppDevice);
// wrap interface
*ppDevice = new WrappedIMMDevice(*ppDevice);
return hr;
}
@@ -0,0 +1,28 @@
#pragma once
#include <initguid.h>
#include <mmdeviceapi.h>
struct WrappedIMMDeviceCollection : IMMDeviceCollection {
explicit WrappedIMMDeviceCollection(IMMDeviceCollection *orig) : pReal(orig) {
}
WrappedIMMDeviceCollection(const WrappedIMMDeviceCollection &) = delete;
WrappedIMMDeviceCollection &operator=(const WrappedIMMDeviceCollection &) = delete;
virtual ~WrappedIMMDeviceCollection() = default;
#pragma region IUnknown
virtual HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, void **ppvObj) override;
virtual ULONG STDMETHODCALLTYPE AddRef() override;
virtual ULONG STDMETHODCALLTYPE Release() override;
#pragma endregion
#pragma region IMMDeviceCollection
virtual HRESULT STDMETHODCALLTYPE GetCount(UINT *pcDevices) override;
virtual HRESULT STDMETHODCALLTYPE Item(UINT nDevice, IMMDevice **ppDevice) override;
#pragma endregion
private:
IMMDeviceCollection *const pReal;
};
@@ -1,4 +1,5 @@
#include "device_enumerator.h"
#include "device_collection.h"
#include "device.h"
#include "util/utils.h"
@@ -42,7 +43,11 @@ HRESULT STDMETHODCALLTYPE WrappedIMMDeviceEnumerator::EnumAudioEndpoints(
DWORD dwStateMask,
IMMDeviceCollection **ppDevices)
{
return pReal->EnumAudioEndpoints(dataFlow, dwStateMask, ppDevices);
const auto hr = pReal->EnumAudioEndpoints(dataFlow, dwStateMask, ppDevices);
if (SUCCEEDED(hr) && (ppDevices != nullptr) && (*ppDevices != nullptr)) {
*ppDevices = new WrappedIMMDeviceCollection(*ppDevices);
}
return hr;
}
HRESULT STDMETHODCALLTYPE WrappedIMMDeviceEnumerator::GetDefaultAudioEndpoint(
@@ -10,6 +10,7 @@
#include "hooks/audio/implementations/asio.h"
#include "hooks/audio/implementations/wave_out.h"
//#include "util/co_task_mem_ptr.h"
#include "util/utils.h"
#include "defs.h"
#include "dummy_audio_client.h"
@@ -40,114 +41,9 @@ static void fix_rec_format(WAVEFORMATEX *pFormat) {
pFormat->nAvgBytesPerSec = pFormat->nSamplesPerSec * pFormat->nBlockAlign;
}
// TODO(felix): is it appropriate to automatically switch to shared mode? should we do a
// `MessageBox` to notify the user?
/*
static bool check_for_exclusive_access(IAudioClient *client) {
static bool checked_once = false;
static bool previous_check_result = false;
CoTaskMemPtr<WAVEFORMATEX> mix_format;
REFERENCE_TIME requested_duration = 0;
if (checked_once) {
return previous_check_result;
}
if (audio::BACKEND.has_value()) {
return false;
}
// scope function so it has access to the local static variables
auto set_result = [](bool result) {
checked_once = true;
previous_check_result = result;
return result;
};
HRESULT ret = client->GetMixFormat(mix_format.ppv());
if (FAILED(ret)) {
PRINT_FAILED_RESULT("IAudioClient::GetMixFormat", ret);
return set_result(false);
}
log_info("audio::wasapi", "Mix Format:");
print_format(mix_format.data());
ret = client->IsFormatSupported(AUDCLNT_SHAREMODE_EXCLUSIVE, mix_format.data(), nullptr);
if (ret == AUDCLNT_E_UNSUPPORTED_FORMAT) {
auto mix_format_ex = reinterpret_cast<WAVEFORMATEXTENSIBLE *>(mix_format.data());
log_warning("audio::wasapi", "device does not natively support the mix format, converting to PCM");
if (mix_format->wFormatTag == WAVE_FORMAT_EXTENSIBLE &&
IsEqualGUID(GUID_KSDATAFORMAT_SUBTYPE_IEEE_FLOAT, mix_format_ex->SubFormat))
{
mix_format_ex->Format.wBitsPerSample = 16;
mix_format_ex->Format.nBlockAlign = mix_format_ex->Format.nChannels * (mix_format_ex->Format.wBitsPerSample / 8);
mix_format_ex->Format.nAvgBytesPerSec = mix_format_ex->Format.nSamplesPerSec * mix_format_ex->Format.nBlockAlign;
mix_format_ex->Samples.wValidBitsPerSample = 16;
mix_format_ex->SubFormat = GUID_KSDATAFORMAT_SUBTYPE_PCM;
} else if (mix_format->wFormatTag == WAVE_FORMAT_IEEE_FLOAT) {
mix_format->wBitsPerSample = 16;
mix_format->nBlockAlign = mix_format->nChannels * (mix_format->wBitsPerSample / 8);
mix_format->nAvgBytesPerSec = mix_format->nSamplesPerSec * mix_format->nBlockAlign;
mix_format->wFormatTag = WAVE_FORMAT_PCM;
} else {
log_warning("audio::wasapi", "mix format is not a floating point format");
return set_result(false);
}
ret = client->IsFormatSupported(AUDCLNT_SHAREMODE_EXCLUSIVE, mix_format.data(), nullptr);
if (FAILED(ret)) {
log_warning("audio::wasapi", "mix format is not supported");
return set_result(false);
}
}
ret = client->GetDevicePeriod(nullptr, &requested_duration);
if (FAILED(ret)) {
PRINT_FAILED_RESULT("IAudioClient::GetDevicePeriod", ret);
return false;
}
ret = client->Initialize(
AUDCLNT_SHAREMODE_EXCLUSIVE,
AUDCLNT_STREAMFLAGS_EVENTCALLBACK,
requested_duration,
requested_duration,
mix_format.data(),
nullptr);
if (ret == AUDCLNT_E_BUFFER_SIZE_NOT_ALIGNED || SUCCEEDED(ret)) {
log_info("audio::wasapi", "exclusive mode is available, disabling backend");
return set_result(true);
} else {
log_warning("audio::wasapi", "exclusive mode is not available, enabling backend, hr={}", FMT_HRESULT(ret));
}
return set_result(false);
}
HRESULT wrap_audio_client(
IMMDevice *device,
DWORD cls_ctx,
PROPVARIANT *activation_params,
IAudioClient **audio_client)
{
auto exclusive_available = check_for_exclusive_access(*audio_client);
(*audio_client)->Stop();
(*audio_client)->Reset();
(*audio_client)->Release();
*audio_client = nullptr;
SAFE_CALL("IMMDevice", "Activate", device->Activate(
IID_IAudioClient,
cls_ctx,
activation_params,
reinterpret_cast<void **>(audio_client)));
*/
IAudioClient *wrap_audio_client(IAudioClient *audio_client) {
log_misc("audio::wasapi", "wrapping IAudioClient");
AudioBackend *backend = nullptr;
bool requires_dummy = false;
@@ -164,9 +60,6 @@ IAudioClient *wrap_audio_client(IAudioClient *audio_client) {
break;
}
}
//} else if (!exclusive_available) {
// backend = new WaveOutBackend();
//}
IAudioClient *new_client;
@@ -182,6 +75,23 @@ IAudioClient *wrap_audio_client(IAudioClient *audio_client) {
return new_client;
}
IAudioClient3 *wrap_audio_client3(IAudioClient3 *audio_client) {
// TODO: ASIO backend for IAudioClient3, if there is a game that needs it
log_misc("audio::wasapi", "wrapping IAudioClient3");
if (hooks::audio::BACKEND.has_value()) {
log_fatal(
"audio::wasapi",
"IAudioClient3 does not currently support backends! clear -audiobackend and try again");
}
if (hooks::audio::USE_DUMMY) {
log_fatal(
"audio::wasapi",
"IAudioClient3 does not currently support dummy context, clear -audiodummy and try again");
}
return new WrappedIAudioClient(audio_client, nullptr);
}
// IUnknown
HRESULT STDMETHODCALLTYPE WrappedIAudioClient::QueryInterface(REFIID riid, void **ppvObj) {
@@ -190,8 +100,8 @@ HRESULT STDMETHODCALLTYPE WrappedIAudioClient::QueryInterface(REFIID riid, void
}
if (riid == IID_WrappedIAudioClient ||
riid == IID_IAudioClient)
{
riid == IID_IAudioClient ||
riid == IID_IAudioClient3) {
this->AddRef();
*ppvObj = this;
@@ -282,20 +192,6 @@ HRESULT STDMETHODCALLTYPE WrappedIAudioClient::Initialize(
}
log_info("audio::wasapi", "IAudioClient::Initialize success, hr={}", FMT_HRESULT(ret));
/*
if (ShareMode == AUDCLNT_SHAREMODE_SHARED) {
IAudioClockAdjustment *clock = nullptr;
SAFE_CALL("IAudioClient", "GetService", pReal->GetService(
IID_IAudioClockAdjustment,
reinterpret_cast<void **>(&clock)));
SAFE_CALL("IAudioClockAdjustment", "SetSampleRate", clock->SetSampleRate(
static_cast<float>(pFormat->nSamplesPerSec)));
}
*/
copy_wave_format(&hooks::audio::FORMAT, pFormat);
return ret;
@@ -482,3 +378,93 @@ HRESULT STDMETHODCALLTYPE WrappedIAudioClient::GetService(REFIID riid, void **pp
return ret;
}
HRESULT STDMETHODCALLTYPE WrappedIAudioClient::IsOffloadCapable(
AUDIO_STREAM_CATEGORY Category,
BOOL *pbOffloadCapable) {
WRAP_VERBOSE;
CHECK_RESULT(pReal3->IsOffloadCapable(Category, pbOffloadCapable));
}
HRESULT STDMETHODCALLTYPE WrappedIAudioClient::SetClientProperties(
const AudioClientProperties *pProperties) {
WRAP_VERBOSE;
CHECK_RESULT(pReal3->SetClientProperties(pProperties));
}
HRESULT STDMETHODCALLTYPE WrappedIAudioClient::GetBufferSizeLimits(
const WAVEFORMATEX *pFormat,
BOOL bEventDriven,
REFERENCE_TIME *phnsMinBufferDuration,
REFERENCE_TIME *phnsMaxBufferDuration) {
WRAP_VERBOSE;
CHECK_RESULT(pReal3->GetBufferSizeLimits(
pFormat,
bEventDriven,
phnsMinBufferDuration,
phnsMaxBufferDuration));
}
HRESULT STDMETHODCALLTYPE WrappedIAudioClient::GetSharedModeEnginePeriod(
const WAVEFORMATEX *pFormat,
UINT32 *pDefaultPeriodInFrames,
UINT32 *pFundamentalPeriodInFrames,
UINT32 *pMinPeriodInFrames,
UINT32 *pMaxPeriodInFrames) {
WRAP_VERBOSE;
CHECK_RESULT(pReal3->GetSharedModeEnginePeriod(
pFormat,
pDefaultPeriodInFrames,
pFundamentalPeriodInFrames,
pMinPeriodInFrames,
pMaxPeriodInFrames));
}
HRESULT STDMETHODCALLTYPE WrappedIAudioClient::GetCurrentSharedModeEnginePeriod(
WAVEFORMATEX **ppFormat,
UINT32 *pCurrentPeriodInFrames) {
WRAP_VERBOSE;
CHECK_RESULT(pReal3->GetCurrentSharedModeEnginePeriod(
ppFormat,
pCurrentPeriodInFrames));
}
HRESULT STDMETHODCALLTYPE WrappedIAudioClient::InitializeSharedAudioStream(
DWORD StreamFlags,
UINT32 PeriodInFrames,
const WAVEFORMATEX *pFormat,
LPCGUID AudioSessionGuid) {
if (!pFormat) {
return E_POINTER;
}
// verbose output
log_info("audio::wasapi", "IAudioClient3::InitializeSharedAudioStream hook hit");
log_info("audio::wasapi", "... ShareMode : {}", share_mode_str(AUDCLNT_SHAREMODE_SHARED));
log_info("audio::wasapi", "... StreamFlags : {}", stream_flags_str(StreamFlags));
log_info("audio::wasapi", "... PeriodInFrames : {}", PeriodInFrames);
print_format(pFormat);
// call next
HRESULT ret = pReal3->InitializeSharedAudioStream(
StreamFlags,
PeriodInFrames,
pFormat,
AudioSessionGuid);
// check for failure
if (FAILED(ret)) {
PRINT_FAILED_RESULT("IAudioClient3", "InitializeSharedAudioStream", ret);
return ret;
}
log_info("audio::wasapi", "IAudioClient3::InitializeSharedAudioStream success, hr={}", FMT_HRESULT(ret));
copy_wave_format(&hooks::audio::FORMAT, pFormat);
return ret;
}
@@ -16,9 +16,16 @@ static const GUID IID_WrappedIAudioClient = {
};
IAudioClient *wrap_audio_client(IAudioClient *client);
IAudioClient3 *wrap_audio_client3(IAudioClient3 *client);
struct WrappedIAudioClient : IAudioClient {
explicit WrappedIAudioClient(IAudioClient *orig, AudioBackend *backend) : pReal(orig), backend(backend) {
struct WrappedIAudioClient : IAudioClient3 {
explicit WrappedIAudioClient(IAudioClient3 *orig3, AudioBackend *backend) :
pReal(orig3), pReal3(orig3), backend(backend) {
}
explicit WrappedIAudioClient(IAudioClient *orig, AudioBackend *backend) :
pReal(orig), pReal3(nullptr), backend(backend) {
}
WrappedIAudioClient(const WrappedIAudioClient &) = delete;
@@ -47,9 +54,43 @@ struct WrappedIAudioClient : IAudioClient {
HRESULT STDMETHODCALLTYPE GetService(REFIID riid, void **ppv) override;
#pragma endregion
IAudioClient *const pReal;
AudioBackend *const backend;
#pragma region IAudioClient2
HRESULT STDMETHODCALLTYPE IsOffloadCapable(
AUDIO_STREAM_CATEGORY Category,
BOOL *pbOffloadCapable) override;
HRESULT STDMETHODCALLTYPE SetClientProperties(
const AudioClientProperties *pProperties) override;
HRESULT STDMETHODCALLTYPE GetBufferSizeLimits(
const WAVEFORMATEX *pFormat,
BOOL bEventDriven,
REFERENCE_TIME *phnsMinBufferDuration,
REFERENCE_TIME *phnsMaxBufferDuration) override;
#pragma endregion
#pragma region IAudioClient3
HRESULT STDMETHODCALLTYPE GetSharedModeEnginePeriod(
const WAVEFORMATEX *pFormat,
UINT32 *pDefaultPeriodInFrames,
UINT32 *pFundamentalPeriodInFrames,
UINT32 *pMinPeriodInFrames,
UINT32 *pMaxPeriodInFrames) override;
HRESULT STDMETHODCALLTYPE GetCurrentSharedModeEnginePeriod(
WAVEFORMATEX **ppFormat,
UINT32 *pCurrentPeriodInFrames) override;
HRESULT STDMETHODCALLTYPE InitializeSharedAudioStream(
DWORD StreamFlags,
UINT32 PeriodInFrames,
const WAVEFORMATEX *pFormat,
LPCGUID AudioSessionGuid) override;
#pragma endregion
IAudioClient *const pReal;
IAudioClient3 *const pReal3;
AudioBackend *const backend;
bool exclusive_mode = false;
int frame_size = 0;
};
@@ -149,5 +149,12 @@ LowLatencyAudioClient *LowLatencyAudioClient::Create(IMMDevice *device) {
log_info("audio::lowlatency", "... max buffer size : {} samples ({} ms)", maxPeriod, 1000.0f * maxPeriod / pFormat->nSamplesPerSec);
log_info("audio::lowlatency", "... default buffer size : {} samples ({} ms)", defaultPeriod, 1000.0f * defaultPeriod / pFormat->nSamplesPerSec);
log_info("audio::lowlatency", "... Windows will use minimum buffer size (instead of default) for shared mode audio clients from now on");
if (minPeriod < defaultPeriod) {
log_info("audio::lowlatency", "minimum period is less than default period, you will see latency improvement");
} else {
log_warning("audio::lowlatency", "minimum period is not less than default period, you will see NO latency improvement");
log_warning("audio::lowlatency", "this an audio driver / hardware limitation");
}
return new LowLatencyAudioClient(audioClient);
}
@@ -218,13 +218,40 @@ void AsioBackend::set_thread_state(AsioThreadState state) {
bool AsioBackend::load_driver() {
AsioDriverList asio_driver_list;
for (const auto &driver : asio_driver_list.driver_list) {
log_info("audio::asio", "Driver {}", driver.id);
log_info("audio::asio", "... Name : {}", driver.name);
log_info("audio::asio", "... Path : {}", driver.dll_path);
size_t driver_id = 0;
std::string driver_name = "";
if (hooks::audio::ASIO_DRIVER_ID.has_value()) {
driver_id = hooks::audio::ASIO_DRIVER_ID.value();
}
const auto driver_id = hooks::audio::ASIO_DRIVER_ID;
for (const auto &driver : asio_driver_list.driver_list) {
log_info("audio::asio", " Driver ID : {}", driver.id);
log_info("audio::asio", " ... Name : {}", driver.name);
log_info("audio::asio", " ... Path : {}", driver.dll_path);
if (!hooks::audio::ASIO_DRIVER_NAME.empty()) {
// match on driver name
if (driver.name == hooks::audio::ASIO_DRIVER_NAME) {
driver_id = driver.id;
driver_name = driver.name;
}
} else if (driver.id == driver_id) {
// match on driver index (0 for default or user-specified)
driver_name = driver.name;
}
}
if (driver_name.empty()) {
if (!hooks::audio::ASIO_DRIVER_NAME.empty()) {
log_warning(
"audio::asio",
"tried to look for ASIO driver but failed: {}", hooks::audio::ASIO_DRIVER_NAME);
}
log_fatal(
"audio::asio",
"could not find ASIO driver to use for conversion, fix your audio options and try again");
}
log_info("audio::asio", "will attempt to use the following ASIO driver: ID = {}, Name = {}", driver_id, driver_name);
IAsio *driver = nullptr;
auto ret = asio_driver_list.open_driver(driver_id, reinterpret_cast<void **>(&driver));
+27
View File
@@ -0,0 +1,27 @@
#include "mme.h"
#include <mmeapi.h>
#include "util/detour.h"
#include "util/logging.h"
namespace hooks::audio::mme {
static decltype(mixerSetControlDetails) *mixerSetControlDetails_orig = nullptr;
MMRESULT
WINAPI
mixerSetControlDetails_hook(
HMIXEROBJ hmxobj, LPMIXERCONTROLDETAILS pmxcd, DWORD fdwDetails) {
log_misc("audio::mme", "mixerSetControlDetails_hook called; ignoring volume change");
return MMSYSERR_NOERROR;
}
void init(HINSTANCE module) {
mixerSetControlDetails_orig =
detour::iat_try("mixerSetControlDetails", mixerSetControlDetails_hook, module);
if (mixerSetControlDetails_orig != nullptr) {
log_misc("audio::mme", "mixerSetControlDetails hooked");
}
}
}
+7
View File
@@ -0,0 +1,7 @@
#pragma once
#include <windows.h>
namespace hooks::audio::mme {
void init(HINSTANCE module);
}
+81 -1
View File
@@ -1,7 +1,10 @@
#include "avshook.h"
#include <cassert>
#include <mutex>
#include <map>
#include <optional>
#include <external/robin_hood.h>
#include "avs/core.h"
#include "avs/ea3.h"
@@ -20,8 +23,12 @@ static bool FAKE_FILE_OPEN = false;
static std::map<std::string, std::string> ROM_FILE_MAP;
static std::string *ROM_FILE_CONTENTS = nullptr;
static std::mutex lstat_cache_mutex;
static robin_hood::unordered_map<std::string, std::pair<int, struct avs::core::avs_stat>> lstat_cache;
namespace hooks::avs::config {
bool DISABLE_VFS_DRIVE_REDIRECTION = false;
bool DISABLE_CACHE = false;
bool LOG = false;
};
@@ -79,6 +86,74 @@ static bool is_spam_file(const char *file) {
return false;
}
static bool is_ldj_cacheable_file(const char *file) {
static const char *spam_prefixes[] = {
// iidx33: profile background customization, spams on every frame during card entry
"/data/graphic/entry_card",
// iidx32+: movie thumbnails, spams on every frame during song select
"/data/graphic/thumbnail/",
"/data/graphic/movie_thumbnail/",
};
for (auto &spam : spam_prefixes) {
if (string_begins_with(file, spam)) {
return true;
}
}
return false;
}
static int cached_avs_fs_lstat(const char *name, struct avs::core::avs_stat *st) {
assert(name != nullptr);
assert(st != nullptr);
const std::string filename(name);
int value = 0;
std::lock_guard<std::mutex> lock(lstat_cache_mutex);
// if this was already seen, return cached result
if (lstat_cache.contains(filename)) {
const auto &result = lstat_cache.at(filename);
value = result.first;
memcpy(st, &result.second, sizeof(*st));
WRAP_DEBUG_FMT(
"(cached) file={}, a={}, m={}, c={}, size={}, u={}",
name,
st->st_atime,
st->st_mtime,
st->st_ctime,
st->filesize,
st->unk1);
return value;
}
// if not, call the original avs function
// this returns 1 on success with valid values in st
// return 0 on failure, with filesize set to 0
// maybe this could be used for future optimization
value = avs::core::avs_fs_lstat(name, st);
WRAP_DEBUG_FMT(
"file={} a={}, m={}, c={}, size={}, u={}",
name,
st->st_atime,
st->st_mtime,
st->st_ctime,
st->filesize,
st->unk1);
// and cache it if there is room (prevent unconstrained growth)
if (lstat_cache.size() <= 4096) {
// c++ was a mistake
lstat_cache.emplace(
std::piecewise_construct,
std::forward_as_tuple(filename),
std::forward_as_tuple(value, *st));
}
// done
return value;
}
static int avs_fs_fstat(avs::core::avs_file_t fd, struct avs::core::avs_stat *st) {
if (is_fake_fd(fd) && ROM_FILE_CONTENTS) {
if (st) {
@@ -104,7 +179,12 @@ static int avs_fs_lstat(const char *name, struct avs::core::avs_stat *st) {
return 1;
}
auto value = avs::core::avs_fs_lstat(name, st);
if (!hooks::avs::config::DISABLE_CACHE && (st != nullptr) &&
avs::game::is_model("LDJ") && is_ldj_cacheable_file(name)) {
return cached_avs_fs_lstat(name, st);
}
const auto value = avs::core::avs_fs_lstat(name, st);
if (!is_spam_file(name)) {
WRAP_DEBUG_FMT("name: {}", name);
}
+1
View File
@@ -9,6 +9,7 @@
namespace hooks::avs {
namespace config {
extern bool DISABLE_VFS_DRIVE_REDIRECTION;
extern bool DISABLE_CACHE;
extern bool LOG;
};
+7
View File
@@ -3,6 +3,7 @@
#include <vector>
#include "avs/game.h"
#include "games/gitadora/gitadora.h"
#include "util/detour.h"
#include "util/utils.h"
@@ -399,6 +400,12 @@ static BOOL WINAPI ClearCommError_hook(HANDLE hFile, LPDWORD lpErrors, LPCOMSTAT
lpStat->cbInQue = custom_handle->bytes_available();
}
// gitadora arena model needs this, or else
// the game will keep spamming 0xAA
if (games::gitadora::is_arena_model() && lpErrors) {
*lpErrors = 0;
}
return TRUE;
}
@@ -13,6 +13,7 @@
#include "avs/game.h"
#include "cfg/screen_resize.h"
#include "games/gitadora/gitadora.h"
#include "games/iidx/iidx.h"
#include "games/sdvx/sdvx.h"
#include "games/mfc/mfc.h"
@@ -41,6 +42,15 @@
#undef min
#endif
#define D3D9_BACKEND_DEBUG 0
#if D3D9_BACKEND_DEBUG
#define log_debug(module, format_str, ...) logger::push( \
LOG_FORMAT("M", module, format_str, ## __VA_ARGS__), logger::Style::GREY)
#else
#define log_debug(module, format_str, ...)
#endif
#define CHECK_RESULT(x) \
do { \
HRESULT __ret = (x); \
@@ -268,7 +278,7 @@ static bool is_dx9_on_12_enabled() {
}
if (GRAPHICS_9_ON_12_STATE == DX9ON12_FORCE_ON) {
if (avs::game::is_model("LDJ") && avs::game::is_ext(2023091500, MAXINT)) {
if (avs::game::is_model("LDJ") && avs::game::is_ext(2023091500, INT_MAX)) {
deferredlogs::defer_error_messages({
"dx9on12 was force enabled by user (-dx9on12)",
" IIDX31+ is known to be incompatible with DX 9on12, leading to blank screen or crashes",
@@ -514,7 +524,7 @@ HRESULT STDMETHODCALLTYPE WrappedIDirect3D9::EnumAdapterModes(
if (!modified && games::iidx::is_tdj_fhd() && refresh < 110) {
if ((width == 1920 && height == 1080) ||
(width == 1280 && height == 720)){
log_misc(
log_debug(
"graphics::d3d9", "removing mode {}, {}x{} @ {}Hz (for TDJ FHD)",
Mode, width, height, refresh);
memset(pMode, 0, sizeof(*pMode));
@@ -527,7 +537,7 @@ HRESULT STDMETHODCALLTYPE WrappedIDirect3D9::EnumAdapterModes(
// - skip 75 and 90 Hz entries because LDJ game timing is messed up with it
// (TDJ should be fine as it assumes a fixed 60 Hz timing)
if (!modified && (width == 1360 || width == 1366 || refresh == 75 || refresh == 90)) {
log_misc(
log_debug(
"graphics::d3d9", "removing mode {}, {}x{} @ {}Hz (for LDJ/TDJ)",
Mode, width, height, refresh);
memset(pMode, 0, sizeof(*pMode));
@@ -535,7 +545,7 @@ HRESULT STDMETHODCALLTYPE WrappedIDirect3D9::EnumAdapterModes(
}
if (!modified && !games::iidx::TDJ_MODE && games::iidx::FORCE_720P && (height > 720)) {
log_misc(
log_debug(
"graphics::d3d9", "removing mode {}, {}x{} @ {}Hz (-iidxforce720p)",
Mode, width, height, refresh);
memset(pMode, 0, sizeof(*pMode));
@@ -803,6 +813,14 @@ HRESULT STDMETHODCALLTYPE WrappedIDirect3D9::CreateDevice(
"either use -graphics-force-refresh option or change the desktop resolution beforehand.");
}
if (!GRAPHICS_WINDOWED && num_adapters >= 2 && GRAPHICS_FORCE_REFRESH_SUB.has_value()) {
log_info("graphics::d3d9", "force sub refresh rate: {} => {} Hz (-graphics-force-refresh-sub option)",
pPresentationParameters[1].FullScreen_RefreshRateInHz,
GRAPHICS_FORCE_REFRESH_SUB.value());
pPresentationParameters[1].FullScreen_RefreshRateInHz = GRAPHICS_FORCE_REFRESH_SUB.value();
}
// force single adapter
if (GRAPHICS_FORCE_SINGLE_ADAPTER) {
log_info("graphics::d3d9", "disabling adapter group device with force single adapter mode");
@@ -927,26 +945,37 @@ HRESULT STDMETHODCALLTYPE WrappedIDirect3D9::CreateDeviceEx(
for (size_t i = 0; i < num_adapters; i++) {
auto *params = &pPresentationParameters[i];
if (!GRAPHICS_WINDOWED && i == 0) {
GRAPHICS_FS_ORIGINAL_WIDTH = params->BackBufferWidth;
GRAPHICS_FS_ORIGINAL_HEIGHT = params->BackBufferHeight;
log_misc("graphics::d3d9", "original resolution: {}x{}", GRAPHICS_FS_ORIGINAL_WIDTH, GRAPHICS_FS_ORIGINAL_HEIGHT);
if (GRAPHICS_FS_CUSTOM_RESOLUTION.has_value()) {
if (!GRAPHICS_WINDOWED){
if (i == 0) {
GRAPHICS_FS_ORIGINAL_WIDTH = params->BackBufferWidth;
GRAPHICS_FS_ORIGINAL_HEIGHT = params->BackBufferHeight;
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(
"graphics::d3d9",
"use custom resolution {}x{} => {}x{}",
"use custom sub 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);
GRAPHICS_FS_CUSTOM_RESOLUTION_SUB.value().first,
GRAPHICS_FS_CUSTOM_RESOLUTION_SUB.value().second);
params->BackBufferWidth = GRAPHICS_FS_CUSTOM_RESOLUTION_SUB.value().first;
params->BackBufferHeight = GRAPHICS_FS_CUSTOM_RESOLUTION_SUB.value().second;
}
}
@@ -974,12 +1003,17 @@ HRESULT STDMETHODCALLTYPE WrappedIDirect3D9::CreateDeviceEx(
for (size_t i = 0; i < num_adapters; i++) {
auto *fullscreen_display_mode = &pFullscreenDisplayMode[i];
if (!GRAPHICS_WINDOWED && i == 0) {
if (GRAPHICS_FS_CUSTOM_RESOLUTION.has_value()) {
fullscreen_display_mode->Width = GRAPHICS_FS_CUSTOM_RESOLUTION.value().first;
fullscreen_display_mode->Height = GRAPHICS_FS_CUSTOM_RESOLUTION.value().second;
} else if (GRAPHICS_FS_ORIENTATION_SWAP) {
std::swap(fullscreen_display_mode->Width, fullscreen_display_mode->Height);
if (!GRAPHICS_WINDOWED) {
if (i == 0) {
if (GRAPHICS_FS_CUSTOM_RESOLUTION.has_value()) {
fullscreen_display_mode->Width = GRAPHICS_FS_CUSTOM_RESOLUTION.value().first;
fullscreen_display_mode->Height = GRAPHICS_FS_CUSTOM_RESOLUTION.value().second;
} 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
if (GRAPHICS_FORCE_SINGLE_ADAPTER) {
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) {
// iidx/sdvx
int swapchain = 1;
if (games::gitadora::is_arena_model()) {
swapchain = 3;
}
if (!ATTEMPTED_SUB_SWAP_CHAIN_ACQUIRE && SUB_SWAP_CHAIN == nullptr) {
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)) {
log_warning(
"graphics::d3d9",
@@ -1353,11 +1404,11 @@ void graphics_d3d9_on_present(
}
// for IIDX TDJ / SDVX UFC, handle subscreen
const bool is_vm =
avs::game::is_model("KFC") &&
(avs::game::SPEC[0] == 'G' || avs::game::SPEC[0] == 'H');
const bool is_vm = games::sdvx::is_valkyrie_model();
const bool is_tdj = avs::game::is_model("LDJ") && games::iidx::TDJ_MODE;
if (is_vm || is_tdj) {
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);
}
@@ -1519,4 +1570,4 @@ void update_backbuffer_dimensions(D3DPRESENT_PARAMETERS *params) {
params->BackBufferHeight);
return;
}
}
}
@@ -4,6 +4,7 @@
#include <mutex>
#include "avs/game.h"
#include "games/gitadora/gitadora.h"
#include "hooks/graphics/graphics.h"
#include "overlay/overlay.h"
#include "util/flags_helper.h"
@@ -249,8 +250,16 @@ HRESULT STDMETHODCALLTYPE WrappedIDirect3DDevice9::CreateAdditionalSwapChain(
WRAP_VERBOSE;
HRESULT hr = pReal->CreateAdditionalSwapChain(pPresentationParameters, ppSwapChain);
bool create_swap_chain = false;
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) {
sub_swapchain = new WrappedIDirect3DSwapChain9(this, *ppSwapChain);
sub_swapchain->should_run_hooks = false;
@@ -295,7 +304,17 @@ HRESULT STDMETHODCALLTYPE WrappedIDirect3DDevice9::GetSwapChain(
return D3D_OK;
}
bool swap = false;
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) {
sub_swapchain->AddRef();
*ppSwapChain = static_cast<IDirect3DSwapChain9 *>(sub_swapchain);
@@ -574,7 +593,7 @@ HRESULT STDMETHODCALLTYPE WrappedIDirect3DDevice9::UpdateTexture(
WRAP_DEBUG;
if (CUSTOM_RESET) {
#ifdef __GNUC__
#if defined(__GNUC__) && !defined(__clang__)
// Do a pointer compare rather than `QueryInterface` because it is a lot cheaper than incrementing
// the reference count
@@ -742,16 +761,14 @@ void SurfaceHook(IDirect3DDevice9 *pReal) {
const int w = param.BackBufferWidth;
const int h = param.BackBufferHeight;
if (cfg::SCREENRESIZE->enable_screen_resize) {
RECT rect {
0,
0,
(LONG)topSurface_width,
(LONG)topSurface_height,
};
pReal->ColorFill(topSurface, &rect, D3DCOLOR_XRGB(0, 0, 0));
// this code used to clear the surface using ColorFill on every call, but
// this turned out to be very expensive, leading to major frame drops in
// SDVX Live2D scenario
if (cfg::SCREENRESIZE->need_surface_clean) {
pReal->ColorFill(topSurface, nullptr, D3DCOLOR_XRGB(0, 0, 0));
cfg::SCREENRESIZE->need_surface_clean = false;
}
D3DLOCKED_RECT rect;
HRESULT hr;
topSurface->LockRect(&rect, NULL, D3DLOCK_DONOTWAIT);
+95 -7
View File
@@ -11,6 +11,7 @@
#include "cfg/icon.h"
#include "cfg/screen_resize.h"
#include "games/ddr/ddr.h"
#include "games/gitadora/gitadora.h"
#include "games/iidx/iidx.h"
#include "hooks/graphics/backends/d3d9/d3d9_backend.h"
#include "launcher/shutdown.h"
@@ -33,6 +34,7 @@ struct CaptureData {
HWND TDJ_SUBSCREEN_WINDOW = nullptr;
HWND SDVX_SUBSCREEN_WINDOW = nullptr;
HWND GFDM_SUBSCREEN_WINDOW = nullptr;
// icon
static HICON WINDOW_ICON = LoadIcon(GetModuleHandle(nullptr), MAKEINTRESOURCE(MAINICON));
@@ -60,6 +62,7 @@ graphics_orientation GRAPHICS_ADJUST_ORIENTATION = ORIENTATION_NORMAL;
bool GRAPHICS_WINDOWED = false;
std::vector<HWND> GRAPHICS_WINDOWS;
UINT GRAPHICS_FORCE_REFRESH = 0;
std::optional<uint32_t> GRAPHICS_FORCE_REFRESH_SUB;
std::optional<int> GRAPHICS_FORCE_VSYNC_BUFFER;
bool GRAPHICS_FORCE_SINGLE_ADAPTER = false;
bool GRAPHICS_PREVENT_SECONDARY_WINDOW = false;
@@ -68,6 +71,7 @@ bool GRAPHICS_9_ON_12_REQUESTED_BY_GAME = false;
bool SUBSCREEN_FORCE_REDRAW = 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_SUB;
bool GRAPHICS_FS_ORIENTATION_SWAP = false;
uint32_t GRAPHICS_FS_ORIGINAL_WIDTH = 0;
uint32_t GRAPHICS_FS_ORIGINAL_HEIGHT = 0;
@@ -94,6 +98,7 @@ static decltype(SetCursor) *SetCursor_orig = nullptr;
static decltype(SetWindowLongA) *SetWindowLongA_orig = nullptr;
static decltype(SetWindowLongW) *SetWindowLongW_orig = nullptr;
static decltype(SetWindowPos) *SetWindowPos_orig = nullptr;
static decltype(ShowWindow) *ShowWindow_orig = nullptr;
static void reset_window_hook(HWND hWnd) {
overlay::destroy(hWnd);
@@ -296,6 +301,30 @@ static BOOL WINAPI ClipCursor_hook(const RECT *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,
DWORD dwStyle, int x, int y, int nWidth, int nHeight,
HWND hWndParent, HMENU hMenu, HINSTANCE hInstance,
@@ -320,10 +349,19 @@ static HWND WINAPI CreateWindowExA_hook(DWORD dwExStyle, LPCSTR lpClassName, LPC
// set display orientation and/or refresh rate
// only set orientation when the target window is portrait
// (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_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) {
DEVMODE mode {};
@@ -333,11 +371,16 @@ static HWND WINAPI CreateWindowExA_hook(DWORD dwExStyle, LPCSTR lpClassName, LPC
DWORD orientation = GRAPHICS_ADJUST_ORIENTATION == ORIENTATION_CW ? DMDO_90 : DMDO_270;
log_misc(
"graphics",
"auto-rotate: call ChangeDisplaySettings and rotate display to DMDO_xx mode {}",
orientation);
"auto-rotate: call ChangeDisplaySettings and rotate display to DMDO_xx mode {}, w={}, h={}",
orientation, nWidth, nHeight);
mode.dmPelsWidth = nWidth;
mode.dmPelsHeight = nHeight;
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) {
@@ -352,7 +395,7 @@ static HWND WINAPI CreateWindowExA_hook(DWORD dwExStyle, LPCSTR lpClassName, LPC
const auto disp_res = ChangeDisplaySettings(&mode, CDS_FULLSCREEN);
if (disp_res != DISP_CHANGE_SUCCESSFUL) {
log_warning("graphics", "failed to change display settings: {}", disp_res);
log_warning("graphics", "ChangeDisplaySettings failed: {}", disp_res);
}
} else {
log_warning("graphics", "failed to get display settings");
@@ -369,11 +412,16 @@ static HWND WINAPI CreateWindowExA_hook(DWORD dwExStyle, LPCSTR lpClassName, LPC
bool is_tdj_sub_window = avs::game::is_model("LDJ") && window_name.ends_with(" sub");
bool is_sdvx_sub_window = avs::game::is_model("KFC") && window_name.ends_with(" Sub Screen");
bool is_gfdm_sub_window = games::gitadora::is_arena_model() && window_name.ends_with("SMALL");
// TDJ windowed mode with subscreen: hide maximize button
if ((is_tdj_sub_window && GRAPHICS_IIDX_WSUB) || is_sdvx_sub_window) {
// hide maximize button (prevent misaligned touches)
if ((is_tdj_sub_window && GRAPHICS_IIDX_WSUB) || is_sdvx_sub_window || is_gfdm_sub_window) {
dwStyle &= ~(WS_MAXIMIZEBOX);
}
// mouse clicks become misaligned when resized
if (is_gfdm_sub_window) {
dwStyle &= ~(WS_SIZEBOX);
}
if ((is_tdj_sub_window || is_sdvx_sub_window) && GRAPHICS_WSUB_BORDERLESS) {
dwStyle &= ~(WS_OVERLAPPEDWINDOW);
}
@@ -417,7 +465,19 @@ static HWND WINAPI CreateWindowExA_hook(DWORD dwExStyle, LPCSTR lpClassName, LPC
graphics_hook_subscreen_window(SDVX_SUBSCREEN_WINDOW);
}
// only hook touch window if multiple windows are allowed
if (is_gfdm_sub_window && !games::gitadora::ARENA_SINGLE_WINDOW) {
GFDM_SUBSCREEN_WINDOW = result;
graphics_hook_subscreen_window(GFDM_SUBSCREEN_WINDOW);
}
disable_touch_indicators(result);
log_misc(
"graphics",
"CreateWindowExA returned {}, {}",
fmt::ptr(result),
lpWindowName ? lpWindowName : "(null)");
return result;
}
@@ -449,7 +509,8 @@ static HWND WINAPI CreateWindowExW_hook(DWORD dwExStyle, LPCWSTR lpClassName, LP
}
// windowed mode adjustments
if (GRAPHICS_WINDOWED) {
// check the width to filter out invisible system-created windows
if (GRAPHICS_WINDOWED && nWidth > 0) {
// change window style
dwExStyle = 0;
@@ -484,6 +545,12 @@ static HWND WINAPI CreateWindowExW_hook(DWORD dwExStyle, LPCWSTR lpClassName, LP
hWndParent, hMenu, hInstance, lpParam);
GRAPHICS_WINDOWS.push_back(result);
log_misc(
"graphics",
"CreateWindowExW returned {}, {}",
fmt::ptr(result),
lpWindowName ? ws2s(lpWindowName) : "(null)");
disable_touch_indicators(result);
return result;
}
@@ -648,10 +715,29 @@ static BOOL WINAPI SetWindowPos_hook(HWND hWnd, HWND hWndInsertAfter,
return TRUE;
}
// prevent gitadora arena model from shifting windows around if the user has preferences
if (GRAPHICS_WINDOWED && games::gitadora::is_arena_model() &&
GRAPHICS_HOOKED_WINDOW.has_value() && hWnd == GRAPHICS_HOOKED_WINDOW.value() &&
cfg::SCREENRESIZE->enable_window_resize) {
return TRUE;
}
// call original
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) {
// check for null
@@ -794,6 +880,7 @@ void graphics_init() {
SetWindowLongA_orig = detour::iat_try("SetWindowLongA", SetWindowLongA_hook);
SetWindowLongW_orig = detour::iat_try("SetWindowLongW", SetWindowLongW_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("MessageBoxExA", MessageBoxExA_hook);
@@ -965,6 +1052,7 @@ bool graphics_capture_receive_jpeg(int screen, TooJpeg::WRITE_ONE_BYTE receiver,
auto capture_width = capture.width;
auto capture_height = capture.height;
auto capture_timestamp = capture.timestamp;
capture.data = nullptr;
lock.unlock();
// validate data
+8 -1
View File
@@ -32,10 +32,12 @@ extern bool GRAPHICS_WINDOWED;
extern graphics_orientation GRAPHICS_ADJUST_ORIENTATION;
extern std::vector<HWND> GRAPHICS_WINDOWS;
extern UINT GRAPHICS_FORCE_REFRESH;
extern std::optional<uint32_t> GRAPHICS_FORCE_REFRESH_SUB;
extern std::optional<int> GRAPHICS_FORCE_VSYNC_BUFFER;
extern bool GRAPHICS_FORCE_SINGLE_ADAPTER;
extern bool GRAPHICS_PREVENT_SECONDARY_WINDOW;
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 uint32_t GRAPHICS_FS_ORIGINAL_WIDTH;
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 bool GRAPHICS_WINDOW_ALWAYS_ON_TOP;
extern bool GRAPHICS_WINDOW_BACKBUFFER_SCALE;
extern std::optional<HWND> GRAPHICS_HOOKED_WINDOW;
extern bool GRAPHICS_IIDX_WSUB;
extern std::optional<std::string> GRAPHICS_WSUB_SIZE;
@@ -60,6 +63,7 @@ extern bool GRAPHICS_WSUB_BORDERLESS;
extern bool GRAPHICS_WSUB_ALWAYS_ON_TOP;
extern HWND TDJ_SUBSCREEN_WINDOW;
extern HWND SDVX_SUBSCREEN_WINDOW;
extern HWND GFDM_SUBSCREEN_WINDOW;
extern bool SUBSCREEN_FORCE_REDRAW;
@@ -98,5 +102,8 @@ void graphics_capture_initial_window(HWND hWnd);
void graphics_update_window_style(HWND hWnd);
void graphics_update_z_order(HWND hWnd, bool always_on_top);
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();
+108 -16
View File
@@ -1,6 +1,7 @@
#include "graphics.h"
#include "avs/game.h"
#include "games/gitadora/gitadora.h"
#include "cfg/screen_resize.h"
#include "overlay/overlay.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;
bool GRAPHICS_WINDOW_ALWAYS_ON_TOP = 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
bool GRAPHICS_IIDX_WSUB = false;
@@ -55,15 +57,22 @@ void graphics_capture_initial_window(HWND hWnd) {
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;
if (!GetClientRect(hWnd, &rect)) {
log_warning(
"graphics",
"graphics_capture_initial_window - GetClientRect failed, GLE: {}",
"graphics-windowed",
"[{}] graphics_capture_initial_window - GetClientRect failed, GLE: {}",
fmt::ptr(hWnd),
GetLastError());
return;
}
@@ -75,12 +84,22 @@ void graphics_capture_initial_window(HWND hWnd) {
cfg::SCREENRESIZE->init_client_aspect_ratio = (float)client_w / (float)client_h;
log_debug(
"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);
// ensure frame size is captured
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
// so that the next resize operation will work
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
if (cfg::SCREENRESIZE->enable_window_resize) {
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_y,
cfg::SCREENRESIZE->client_width,
@@ -104,12 +124,13 @@ void graphics_capture_initial_window(HWND hWnd) {
// ddr hangs when window frame doesn't have overlapped
if (cfg::SCREENRESIZE->window_decoration != cfg::WindowDecorationMode::Default) {
log_info(
"graphics-windowed", "change window style - decoration: {}",
"graphics-windowed", "[{}] change window style - decoration: {}",
fmt::ptr(hWnd),
cfg::SCREENRESIZE->window_decoration);
graphics_update_window_style(hWnd);
}
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);
}
@@ -152,7 +173,7 @@ void graphics_load_windowed_parameters() {
cfg::SCREENRESIZE->window_offset_x = result.first;
cfg::SCREENRESIZE->window_offset_y = result.second;
} 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_HEIGHT = result.second;
} 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_Y = result.second;
} 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) {
return;
}
if (graphics_window_change_crashes_game()) {
if (graphics_window_decoration_change_crashes_game()) {
return;
}
log_debug("graphics-windowed", "graphics_update_window_style called");
log_debug(
"graphics-windowed",
"graphics_update_window_style called for hWnd={}",
fmt::ptr(hWnd));
// update frame style
auto style = cfg::SCREENRESIZE->init_window_style;
auto style_ex = cfg::SCREENRESIZE->init_window_style_ex;
switch (cfg::SCREENRESIZE->window_decoration) {
case cfg::WindowDecorationMode::Borderless:
style &= ~WS_OVERLAPPEDWINDOW;
style_ex &= ~WS_EX_CLIENTEDGE;
style_ex &= ~WS_EX_WINDOWEDGE;
break;
case cfg::WindowDecorationMode::ResizableFrame:
style |= WS_OVERLAPPEDWINDOW;
if (graphics_window_resize_breaks_game()) {
style &= ~WS_SIZEBOX;
}
break;
case cfg::WindowDecorationMode::Default:
default:
@@ -393,11 +423,26 @@ void graphics_update_window_style(HWND hWnd) {
"graphics_update_window_style - calling SetWindowLong with Mode {}, style 0x{:x}",
static_cast<int>(cfg::SCREENRESIZE->window_decoration),
style);
SetWindowLong(hWnd, GWL_STYLE, style);
const auto ret = SetWindowLong(hWnd, GWL_STYLE, style);
(void)ret;
log_debug(
"graphics-windowed",
"graphics_update_window_style - SetWindowLong(GWL_STYLE) returned {:x}, GLE={}",
ret,
GetLastError());
const auto ret2 = SetWindowLong(hWnd, GWL_EXSTYLE, style_ex);
(void)ret2;
log_debug(
"graphics-windowed",
"graphics_update_window_style SetWindowLong(GWL_EXSTYLE) returned {:x}, GLE={}",
ret2,
GetLastError());
// SetWindowPos must be called after SetWindowLong if the frame style changed
// this will be done in WM_STYLECHANGED handler
log_debug("graphics-windowed", "graphics_update_window_style returned");
}
void graphics_update_z_order(HWND hWnd, bool always_on_top) {
@@ -454,12 +499,19 @@ void graphics_move_resize_window(HWND hWnd) {
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 bool result = false;
std::call_once(flag, []() {
// ddr crashes when frame style changes
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) {
log_warning(
"graphics-windowed",
@@ -468,3 +520,43 @@ bool graphics_window_change_crashes_game() {
});
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;
}
+51 -2
View File
@@ -1,3 +1,6 @@
// GetLocaleInfoEx
#define _WIN32_WINNT 0x0600
#include "lang.h"
#define WIN32_NO_STATUS
@@ -9,6 +12,8 @@
#include "avs/game.h"
#include "games/iidx/iidx.h"
#include "games/gitadora/gitadora.h"
#include "games/sdvx/sdvx.h"
#include "util/detour.h"
#include "util/logging.h"
#include "util/utils.h"
@@ -24,6 +29,7 @@ static decltype(GetLocaleInfoEx) *GetLocaleInfoEx_orig = nullptr;
#ifdef SPICE64
static decltype(GetSystemDefaultLCID) *GetSystemDefaultLCID_orig = nullptr;
static decltype(IsDBCSLeadByte) *IsDBCSLeadByte_orig = nullptr;
static decltype(WideCharToMultiByte) *WideCharToMultiByte_orig = nullptr;
#endif
static NTSTATUS NTAPI RtlMultiByteToUnicodeN_hook(
@@ -173,6 +179,41 @@ static BOOL WINAPI IsDBCSLeadByte_hook (
return IsDBCSLeadByteEx(CODEPAGE_SHIFT_JIS, TestChar);
}
static
int
WINAPI
WideCharToMultiByte_hook(
UINT CodePage,
DWORD dwFlags,
LPCWCH lpWideCharStr,
int cchWideChar,
LPSTR lpMultiByteStr,
int cbMultiByte,
LPCCH lpDefaultChar,
LPBOOL lpUsedDefaultChar
)
{
switch (CodePage) {
case CP_ACP:
case CP_THREAD_ACP:
SetThreadLocale(MAKELANGID(LANG_JAPANESE, SUBLANG_JAPANESE_JAPAN));
CodePage = CODEPAGE_SHIFT_JIS;
break;
default:
break;
}
return WideCharToMultiByte_orig(
CodePage,
dwFlags,
lpWideCharStr,
cchWideChar,
lpMultiByteStr,
cbMultiByte,
lpDefaultChar,
lpUsedDefaultChar);
}
#endif
void hooks::lang::early_init() {
@@ -183,8 +224,7 @@ void hooks::lang::early_init() {
detour::trampoline_try("kernel32.dll", "GetOEMCP", GetOEMCP_hook, &GetOEMCP_orig);
#ifdef SPICE64 // SDVX5+ specific code
bool isValkyrieCabinetMode = avs::game::SPEC[0] == 'G' || avs::game::SPEC[0] == 'H';
if (avs::game::is_model("KFC") && isValkyrieCabinetMode) {
if (games::sdvx::is_valkyrie_model()) {
log_info("hooks::lang", "hooking GetSystemDefaultLCID");
detour::trampoline_try(
"kernel32.dll",
@@ -213,6 +253,15 @@ void hooks::lang::early_init() {
IsDBCSLeadByte_hook,
&IsDBCSLeadByte_orig);
}
if (games::gitadora::is_arena_model()) {
log_info("hooks::lang", "hooking WideCharToMultiByte");
detour::trampoline_try(
"kernel32.dll",
"WideCharToMultiByte",
WideCharToMultiByte_hook,
&WideCharToMultiByte_orig);
}
#endif
}
+3
View File
@@ -1,3 +1,6 @@
// DisplayConfigSetDeviceInfo
#define _WIN32_WINNT 0x0601
#include "winuser.h"
#include "util/detour.h"
+108 -10
View File
@@ -88,6 +88,7 @@
#include "misc/device.h"
#include "misc/eamuse.h"
#include "misc/extdev.h"
#include "misc/ami2000.h"
#include "misc/sciunit.h"
#include "misc/sde.h"
#include "misc/wintouchemu.h"
@@ -106,6 +107,7 @@
#include "util/libutils.h"
#include "util/logging.h"
#include "util/peb.h"
#include "util/socd_cleaner.h"
#include "util/sysutils.h"
#include "util/tapeled.h"
#include "util/time.h"
@@ -195,6 +197,7 @@ int main_implementation(int argc, char *argv[]) {
bool attach_icca = false;
bool attach_device = false;
bool attach_extdev = false;
bool attach_ami2000 = false;
bool attach_sciunit = false;
bool attach_cpusbxpkm_printer = false;
bool attach_iidx = false;
@@ -324,6 +327,10 @@ int main_implementation(int argc, char *argv[]) {
if (options[launcher::Options::GraphicsForceRefresh].is_active()) {
GRAPHICS_FORCE_REFRESH = options[launcher::Options::GraphicsForceRefresh].value_uint32();
}
if (options[launcher::Options::FullscreenSubRefreshRate].is_active()) {
GRAPHICS_FORCE_REFRESH_SUB =
options[launcher::Options::FullscreenSubRefreshRate].value_uint32();
}
if (options[launcher::Options::GraphicsForceSingleAdapter].value_bool()) {
GRAPHICS_FORCE_SINGLE_ADAPTER = true;
}
@@ -344,8 +351,13 @@ int main_implementation(int argc, char *argv[]) {
GRAPHICS_FORCE_SINGLE_ADAPTER = true;
GRAPHICS_PREVENT_SECONDARY_WINDOW = true;
}
if (options[launcher::Options::DisplayAdapter].is_active()) {
if (options[launcher::Options::DisplayAdapter].is_active() &&
options[launcher::Options::DisplayAdapter].value_uint32() != D3DADAPTER_DEFAULT) {
D3D9_ADAPTER = options[launcher::Options::DisplayAdapter].value_uint32();
// when we fix up adapter numbers, we only fix the first adapter, and not any subsequent
// adapters, so we can't deal with multi-monitor games
GRAPHICS_FORCE_SINGLE_ADAPTER = true;
}
if (options[launcher::Options::CaptureCursor].value_bool()) {
GRAPHICS_CAPTURE_CURSOR = true;
@@ -423,8 +435,8 @@ int main_implementation(int argc, char *argv[]) {
if (options[launcher::Options::EnableEXTDEVModule].value_bool()) {
attach_extdev = true;
}
if (options[launcher::Options::EnableSCIUNITModule].value_bool()) {
attach_sciunit = true;
if (options[launcher::Options::EnableAMI2000Module].value_bool()) {
attach_ami2000 = true;
}
if (options[launcher::Options::EnableDevicePassthrough].value_bool()) {
hooks::device::ENABLE = false;
@@ -523,6 +535,9 @@ int main_implementation(int argc, char *argv[]) {
overlay::windows::IIDX_SEGMENT_LOCATION =
options[launcher::Options::spice2x_IIDXLEDPos].value_text();
}
if (options[launcher::Options::IIDXLEDBorderless].value_bool()) {
overlay::windows::IIDX_SEGMENT_BORDERLESS = true;
}
if (options[launcher::Options::spice2x_IIDXNoESpec].value_bool()) {
games::iidx::DISABLE_ESPEC_IO = true;
}
@@ -544,6 +559,10 @@ int main_implementation(int argc, char *argv[]) {
nvenc_hook::FORCE_DISABLE = true;
}
#endif
if (options[launcher::Options::OtocaCamHook].value_bool()) {
games::otoca::BYPASS_CAMERA = true;
}
if (options[launcher::Options::LoadJubeatModule].value_bool()) {
attach_jb = true;
}
@@ -574,6 +593,25 @@ int main_implementation(int argc, char *argv[]) {
if (options[launcher::Options::GitaDoraCabinetType].is_active()) {
games::gitadora::CAB_TYPE = options[launcher::Options::GitaDoraCabinetType].value_uint32();
}
if (options[launcher::Options::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()) {
attach_nostalgia = true;
}
@@ -595,6 +633,17 @@ int main_implementation(int argc, char *argv[]) {
if (options[launcher::Options::GitaDoraTwoChannelAudio].value_bool()) {
games::gitadora::TWOCHANNEL = true;
}
if (options[launcher::Options::GitaDoraLefty].is_active()) {
const auto text = options[launcher::Options::GitaDoraLefty].value_text();
if (text == "p1") {
games::gitadora::P1_LEFTY = true;
} else if (text == "p2") {
games::gitadora::P2_LEFTY = true;
} else if (text == "both") {
games::gitadora::P1_LEFTY = true;
games::gitadora::P2_LEFTY = true;
}
}
if (options[launcher::Options::LoadDDRModule].value_bool()) {
attach_ddr = true;
}
@@ -744,6 +793,9 @@ int main_implementation(int argc, char *argv[]) {
if (options[launcher::Options::DisableAvsVfsDriveMountRedirection].is_active()) {
hooks::avs::config::DISABLE_VFS_DRIVE_REDIRECTION = true;
}
if (options[launcher::Options::DisableAvsCache].is_active()) {
hooks::avs::config::DISABLE_CACHE = true;
}
if (options[launcher::Options::ScreenResizeConfigPath].is_active()) {
cfg::SCREEN_RESIZE_CFG_PATH_OVERRIDE =
options[launcher::Options::ScreenResizeConfigPath].value_text();
@@ -946,6 +998,12 @@ int main_implementation(int argc, char *argv[]) {
if (options[launcher::Options::DisableOverlay].value_bool()) {
overlay::ENABLED = false;
}
if (options[launcher::Options::OverlayScaling].is_active() && !cfg::CONFIGURATOR_STANDALONE && !cfg_run) {
const auto val = options[launcher::Options::OverlayScaling].value_uint32();
if (10 <= val && val <= 400 && val != 100) {
overlay::UI_SCALE_PERCENT = options[launcher::Options::OverlayScaling].value_uint32();
}
}
if (options[launcher::Options::DisableAudioHooks].value_bool()) {
hooks::audio::ENABLED = false;
deferredlogs::defer_error_messages({
@@ -957,18 +1015,26 @@ int main_implementation(int argc, char *argv[]) {
if (options[launcher::Options::spice2x_DisableVolumeHook].value_bool()) {
hooks::audio::VOLUME_HOOK_ENABLED = false;
}
if (options[launcher::Options::AudioBackend].is_active()) {
auto &name = options[launcher::Options::AudioBackend].value_text();
auto backend = hooks::audio::name_to_backend(name.c_str());
if (!backend.has_value() && !cfg::CONFIGURATOR_STANDALONE) {
log_fatal("launcher", "invalid audio backend: {}", name);
}
hooks::audio::BACKEND = backend;
}
if (options[launcher::Options::AsioDriverId].is_active()) {
hooks::audio::ASIO_DRIVER_ID = options[launcher::Options::AsioDriverId].value_uint32();
}
if (options[launcher::Options::AsioDriverName].is_active()) {
hooks::audio::BACKEND = hooks::audio::name_to_backend("asio");
hooks::audio::ASIO_DRIVER_NAME = options[launcher::Options::AsioDriverName].value_text();
// this new option overrides the old one
hooks::audio::ASIO_DRIVER_ID.reset();
}
if (options[launcher::Options::AudioDummy].value_bool()) {
hooks::audio::USE_DUMMY = true;
}
@@ -1029,8 +1095,8 @@ int main_implementation(int argc, char *argv[]) {
std::pair<uint32_t, uint32_t> result;
if (parse_width_height(options[launcher::Options::spice2x_WindowSize].value_text(), result)) {
GRAPHICS_WINDOW_SIZE = result;
} else {
log_warning("launcher", "failed to parse -windowsize");
} else if (!cfg_run && !cfg::CONFIGURATOR_STANDALONE) {
log_fatal("launcher", "failed to parse -windowsize");
}
}
if (options[launcher::Options::spice2x_WindowPosition].is_active()) {
@@ -1074,6 +1140,9 @@ int main_implementation(int argc, char *argv[]) {
if (options[launcher::Options::spice2x_DRSTransposeTouch].value_bool()) {
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()) {
const auto text = options[launcher::Options::spice2x_AutoCard].value_text();
if (text == "p1") {
@@ -1328,12 +1397,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()) {
std::pair<uint32_t, uint32_t> result;
if (parse_width_height(options[launcher::Options::FullscreenResolution].value_text(), result)) {
GRAPHICS_FS_CUSTOM_RESOLUTION = result;
} else {
log_warning("launcher", "failed to parse -forceres");
} else if (!cfg_run && !cfg::CONFIGURATOR_STANDALONE) {
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");
}
}
@@ -1417,6 +1507,7 @@ int main_implementation(int argc, char *argv[]) {
// early hooks
for (auto &hook : early_hooks) {
log_info("launcher", "loading early hook DLL {}", hook);
libutils::print_dll_info(hook);
HMODULE module;
if (!(module = libutils::try_library(hook))) {
log_warning("launcher", "failed to load early hook {}: {}", hook, get_last_error_string());
@@ -1629,6 +1720,7 @@ int main_implementation(int argc, char *argv[]) {
attach_io = true;
attach_device = true;
attach_extdev = true;
attach_ami2000 = true;
attach_gitadora = true;
break;
}
@@ -1809,7 +1901,7 @@ int main_implementation(int argc, char *argv[]) {
}
// 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";
attach_io = true;
attach_pc = true;
@@ -1848,7 +1940,7 @@ int main_implementation(int argc, char *argv[]) {
"This usually means one of the following:\n\n"
" 1. You put spice executables in the wrong place, 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");
@@ -2191,6 +2283,11 @@ int main_implementation(int argc, char *argv[]) {
extdev_attach();
}
// ami 2000 card reader attach
if (attach_io || attach_ami2000) {
ami2000_attach();
}
// sci unit attach
if (attach_io || attach_sciunit) {
sciunit_attach();
@@ -2211,6 +2308,7 @@ int main_implementation(int argc, char *argv[]) {
// load hooks
for (auto &hook : game_hooks) {
log_info("launcher", "loading hook DLL {}", hook);
libutils::print_dll_info(hook);
HMODULE module;
if (!(module = libutils::try_library(hook))) {
log_warning("launcher", "failed to load hook {}: {}", hook, get_last_error_string());

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