Compare commits

...

115 Commits

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

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

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

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

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

This needs a bit more customization since:

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

For XP builds, the call is compiled out to avoid linking against dwmapi
and windows_dll_compat_checker returns successfully when testing against
the XP Embedded image. I can't test it on XP, but it ran on Vista.
2026-04-15 18:51:53 -07:00
bicarus a8419bbaa0 grpahics: fix borderless for ddr/gitadora (#638)
## Link to GitHub Issue or related Pull Request, if one exists
n/a

## Description of change
Allow DDR to be launched in borderless window (honor the spicecfg
option).

Prevent Gitadora arena model from changing the window decoration once
launched as it hangs the game.

## Testing
Tested 32/64bit ddr, gitadora arena.
2026-04-14 18:11:42 -07:00
bicarus 0fc430ceb3 popn: add io emulation for more lights and coin (#637)
## Link to GitHub Issue or related Pull Request, if one exists
#618 

## Description of change
Wire up coin and more lights to bi3a_hook.

## Testing
Tested on U region
2026-04-14 09:46:13 -07:00
bicarus e59a50b64c popn: boot with 3 monitors (#636)
## Link to GitHub Issue or related Pull Request, if one exists
#618 

## Description of change
Take the monitor hooks added for IIDX/SDVX #612 and apply it to popn as
well now that popn has a subscreen.

There is a debug option in game that actually uses three monitors, which
can be activated via hex edits. This will prevent that from working.

## Testing
Tested with 3 monitors, fs and windowed.
2026-04-13 23:02:56 -07:00
bicarus 2042b629e9 popn: allow game to launch on PCs with only one monitor (#633)
## Link to GitHub Issue or related Pull Request, if one exists
#618 

## Description of change
The game insists on having two monitors. Hook various DX9 and Win32
monitor APIs to fake a monitor if the user only has one. This correctly
convinces the game that it needs to draw two screens instead of just
one.

## Testing
WIP
2026-04-13 03:00:47 -07:00
bicarus 440b9ed749 overlay: fix how input is handled when window loses focus (#632)
## Link to GitHub Issue or related Pull Request, if one exists
#286 

## Description of change
When we migrated to event-based I/O for ImGui, it introduced an issue
where a button would be held down even when the window is not in focus,
because we didn't notify ImGui that the button was released (after the
window lost focus).

Fix the ImGui custom I/O handler so that it immediately releases all
buttons when the window focus is lost.

## Testing
tested on 32-bit windowed mode
2026-04-12 04:44:46 -07:00
bicarus 9936088286 popn: subscreen overlay (#631)
## Link to GitHub Issue or related Pull Request, if one exists
#618 

## Description of change

Add overlay for subscreen.

Add an option for no subscreen window.

Use the same "force redraw subscreen" logic we used in SDVX to fix the
same issue.

Known issues:

* still crashing when running windowed if there is only one monitor
* having only one monitor causes sunscreen overlay to not work
(NumberOfAdaptersInGroup issue?)

## Testing
2026-04-12 02:11:24 -07:00
bicarus 4d8a6c1952 graphics: fix window position if out of bounds (#630)
## Link to GitHub Issue or related Pull Request, if one exists
#618 

## Description of change
Before calling `CreateWindowEx[A/W]`, check dimensions of the window
about to be created, and check if it will be out of bounds (completely
or partially).

If it's completely out of bounds, move it to `(0, 0)` to prevent crashes
or rendering issues.

## Testing
Tested popn and sdvx.
2026-04-11 20:12:43 -07:00
bicarus af69cf9251 popn: new cab lights (#629)
## Link to GitHub Issue or related Pull Request, if one exists
#618 

## Description of change
Basic support for lights - subwoofer RGB and button lights.

## Testing
Tested on title screen and in-game.

Tested beat pop as well since that lights up the colors in white/blue.
2026-04-11 18:08:45 -07:00
bicarus 1d5550b1e8 popn: implement bi3a-hook (bio3) (#626)
## Link to GitHub Issue or related Pull Request, if one exists
#618 

## Description of change
Turns out running this with bi2x doesn't process buttons properly, so we
have to use bi3a.

## Testing
Seems playable now.
2026-04-11 14:43:45 -07:00
bicarus ec3f6639b7 ccj: fix locale issues with carding in (#628)
## Link to GitHub Issue or related Pull Request, if one exists
#624 

## Description of change
See bug. Add locale hooks specifically for CCJ.

also, fix y axis direction for XInput, it was flipped because XInput
uses opposite values from what RawInput does

## Testing
Tested card in / play one round with Windows regional format set to
French (France) and other language set to English.
2026-04-11 14:42:51 -07:00
bicarus 727b99effd rawinput: fix ID for rawinput devices (#627)
## Link to GitHub Issue or related Pull Request, if one exists
#616 

## Description of change
The IDs don't really get used anywhere if it's not a real HID or MIDI
device, but putting them for completeness since they show up in Controls
overlay menu.

## Testing
2026-04-11 12:04:12 -07:00
bicarus 8afff3aaca popn: implement bi2x hook (#623)
## Link to GitHub Issue or related Pull Request, if one exists
#618 

## Description of change
Implement BI2X hook I/O for service/test/coin/headphones and the deka
pop-kuns.
2026-04-10 17:46:12 -07:00
bicarus b281517429 popn: hook DisplayConfigGetDeviceInfo to allow for any GPU port to be used for monitor, get past I/O check (#622)
## Link to GitHub Issue or related Pull Request, if one exists
#618 

## Description of change
The game expects the main display to be *not* HDMI, and at port 2.

Hooking DisplayConfigGetDeviceInfo allows us to boot the game on any
monitor.

Add basic I/O hook. It gets stuck in `USB I/O Checking...` for a couple
minutes but it'll eventually skip and boot to title screen.

## Testing
ugh.
2026-04-10 02:56:32 -07:00
bicarus 54ec9f272e popn: new cab windowed mode (#621)
## Link to GitHub Issue or related Pull Request, if one exists
#618 

## Description of change
Get windowed mode to work.

## Testing
tbd... i/o doesn't work right now but it launches two windows at
least...
2026-04-10 01:05:44 -07:00
bicarus a159691097 xinput: bind UI, vibration, hotplug (#620)
## Link to GitHub Issue or related Pull Request, if one exists
#616 

## Description of change
`Naive` button is now called `Naive/XInput` and detects XInput
controllers

Add vibration output as "Lights"

Add proper hotplug support and multiple controllers

Fix misc bugs on x86

## Testing
I tested with two xbox controllers.
2026-04-10 01:04:20 -07:00
bicarus ca70f7b19c popn: distinguish between hpm and new popn (#619)
## Link to GitHub Issue or related Pull Request, if one exists
#618

## Description of change
Enhance game detection logic.

Fix heap size.
2026-04-09 19:54:27 -07:00
bicarus 7b4227d2a9 rawinput: add basic support for xinput (#617)
## Link to GitHub Issue or related Pull Request, if one exists
#616 

## Description of change
Adds preliminary support for XInput controllers. Enough to manually bind
buttons and map analogs.
2026-04-09 02:49:20 -07:00
bicarus f89efa2558 build: flatten file structure in release archives (#615)
Before:

```
.\spice2x\spice.exe
.\spice2x\spicecfg.exe
.\spice2x\spice64.exe
.\spice2x\stubs\..
.\spice2x\extras\..
```

after:

```
.\spice.exe
.\spicecfg.exe
.\spice64.exe
.\stubs\..
.\extras\..
```
2026-04-07 16:48:31 -07:00
bicarus 6f4824b648 misc: improve monitor logging (#614) 2026-04-06 22:22:34 -07:00
bicarus 45debeb5c9 iidx, sdvx: booting fullscreen with three or more monitors (#612)
## Link to GitHub Issue or related Pull Request, if one exists
#345 

Hoping that #180 is also addressed by this change, but I can't test it.

## Description of change

IIDX (TDJ) and SDVX (Valk) will now be able to boot in full screen even
with 3 or more monitors attached.

spice will "hide" from the game any monitors other than the primary
monitor and one other monitor for subscreen, allowing it to boot without
crashing - accomplished by clearing `DISPLAY_DEVICE_ATTACHED_TO_DESKTOP`
flag when monitors are enumerated over `EnumDisplayDevicesA`, and
fortunately both games seem to respect that flag.

Add options (one for IIDX, another for SDVX) that lets you specify the
monitor ID of the second monitor.

When the option is set, user-specified monitor will be used as the
subscreen. When the option is not set, spice will just pick the
lowest-indexed monitor as the subscreen, not counting the primary one of
course.

## Testing
Tested both games with three monitors. Should probably work for four or
more.
2026-04-06 02:59:12 -07:00
bicarus-dev a01f452eba fix build script 2026-04-06 02:37:40 -07:00
Will e452734bc1 Build a WinXP compatible version of Spice (#611)
This was a lot more background work than expected, but that is the
nature of unnecessary hobbies I guess. Most things in here are obvious,
so here's the non-obvious thoughts and notes:

1. All the XP stuff is optional, if the toolchain isn't available it
won't even try
2. The docker build is the "known good" build and now has all the
required tools for XP and checking binaries
a. If you're OK with it, can add the XP_MUST_BUILD flag to
build_docker.sh
4. New `SPICE_XP` flag to comment out blocks
5. We could technically build using the Docker image I've made for my XP
compatible LLVM toolchain, but combining the binaries into a single zip
at the end of it is somewhat tedious, so I've chosen to grab the
binaries from my release
6. I also wrote a DLL checker to make sure the final binaries are
_actually_ XP compatible, which found some issues on x86_64 (I don't
know a single game that runs on 64 bit XP, but it wasn't too much effort
so whatever). This also won't run if missing, just to save the trouble.
7. The build script is still pretty shit, I've made some minor
improvements
  a. `/usr/bin/env bash` instead of `/bin/bash` as it's more correct
  b. Adding `-it` to the docker build, so Ctrl+C actually halts it
c. Main issue is that failures don't halt, because of the custom error
catching function. This has caused a lot of frustration, maybe we can
change that

## Testing
It runs on 32 and 64 bit installs of XP. My jubeat cab is on Win10 these
days and I haven't got around to testing an actual game, but I've
distributed test builds of this to people who *have* confirmed it works
as expected.
2026-04-06 01:14:29 -07:00
bicarus d1779b93fa overlay: remove tab stop from invisible selectable used in options tab (#613)
#598 added extra tab stops to the invisible Selectable widgets, which is
only used for detecting mouse hover
2026-04-06 01:02:56 -07:00
bicarus fe9aac7e29 overlay: fix cosmetic issue in options tab when there are no options for the game (#610)
Number of columns was modified in #598 but it didn't correctly account
for the case where a game doesn't have any game-specific options.
2026-04-05 19:01:09 -07:00
bicarus-dev f3d887ebcf update strings 2026-04-05 17:03:23 -07:00
bicarus 336500c8ae ccj: fix D3D10Warp.dll_unloaded issue (#609)
## Link to GitHub Issue or related Pull Request, if one exists
#0

## Description of change
Fix random crashes with CCJ.

This should remove the need to add `d3d10warp.dll` to `
preload_libraries` in `ee-config.xml`.

## Testing
Tried a couple games and didn't run into the crash..
2026-04-05 16:18:19 -07:00
bicarus ee3fa58bfa graphics: option to change primary monitor before launching game (#608)
## Description of change

Adds `-mainmonitor` option which changes the monitor layout before
launching the game. This is much more reliable than the old `-monitor`
option which operated at DX9 level. This works for TDJ/UFC subscreens,
for example.

Rename `-monitor` option to `-dx9mainadapter` to discourage use.
`-monitor` will continue to work, of course.

Changing of primary monitor happens first before any orientation changes
/ refresh rate changes, which means those options will result in the
logically correct configuration.

Create a new option category for `Monitor` (rotate, refresh rate, etc).

Add a monitor selection widget for `-mainmonitor` option in the
configurator.

Improve how we log names of monitors in the log during boot - should be
less of "Generic PnP Monitor" after this.
2026-04-04 23:26:38 -07:00
bicarus a4c3eddc2f graphics: rewrite auto-rotate and refresh rate options to be more reliable (#607)
## Description of change

Rotate monitor & change the refresh rate **well before** the game is
launched.

This is much faster and more reliable compared to what we do now, which
is trying to do monitor changes during CreateWindow hooks. For example,
SDVX makes a bunch of OS / NVAPI calls to figure out monitor geometry
while it's booting, and by the time CreateWindow is called, the game
already made up its mind, which leads to weird graphical issues.

This affects both windowed mode and full screen.

In addition, add options for normal orientation (landscape) and upside
down (landscape, flipped) options. This can be used if the monitor is
normally rotated to portrait, but the game needs to be in landscape.
2026-04-04 03:23:28 -07:00
bicarus e0f6cb2cbd api: dump key binding strings (#606)
Print out human readable text for vKey when dumping bindings.
2026-04-03 20:22:24 -07:00
bicarus 1ad6a9412e imgui: update imgui to v1.92.7, update dx9 backend to latest (#605) 2026-04-03 01:51:08 -07:00
bicarus 5af3e0d494 imgui: migrate to new event-based IO (#604)
## Link to GitHub Issue or related Pull Request, if one exists
#287

## Description of change
Switch over to new event-based I/O system in ImGui so that we can keep
up with ImGui updates.

Unlike #287, this change does not modify how mouse position is handled.
That continues to be supported by newer versions of ImGui.

## Testing
wip
2026-04-02 21:00:42 -07:00
bicarus-dev b1ee828457 add some logging to device reload 2026-04-01 02:51:03 -07:00
bicarus 0d60649715 overlay: small tweaks to cards tab (#602)
## Link to GitHub Issue or related Pull Request, if one exists
#593 

## Description of change
* Display a confirmation dialog when clicking on `Generate` for the
first time (to prevent accidental loss of card number)
* If `cardN.txt` contents are read back successfully, show card number
in green to indicate success, and red in case of failure.
2026-03-31 00:07:34 -07:00
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
141 changed files with 26036 additions and 10964 deletions
+1
View File
@@ -58,6 +58,7 @@ Watch out for legacy OS compatibility. Currently, the minimum support floor is W
* 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
+23 -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")
@@ -170,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
@@ -260,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
#########
@@ -346,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
@@ -375,6 +383,7 @@ set(SOURCE_FILES ${SOURCE_FILES}
games/shared/twtouch.cpp
games/popn/popn.cpp
games/popn/io.cpp
games/popn/bi3a_hook.cpp
games/bbc/bbc.cpp
games/bbc/io.cpp
games/hpm/hpm.cpp
@@ -559,6 +568,7 @@ set(SOURCE_FILES ${SOURCE_FILES}
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
@@ -573,6 +583,7 @@ set(SOURCE_FILES ${SOURCE_FILES}
overlay/windows/log.cpp
overlay/windows/midi.cpp
overlay/windows/patch_manager.cpp
overlay/windows/popn_sub.cpp
overlay/windows/wnd_manager.cpp
# rawinput
@@ -587,6 +598,8 @@ set(SOURCE_FILES ${SOURCE_FILES}
rawinput/smxstage.h
rawinput/smxdedicab.cpp
rawinput/smxdedicab.h
rawinput/xinput.cpp
rawinput/xinput.h
# reader
reader/reader.cpp
@@ -805,7 +818,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")
@@ -815,7 +829,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")
@@ -825,7 +840,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")
@@ -835,7 +851,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")
+122 -20
View File
@@ -35,24 +35,33 @@ struct ICCA_STATUS_LA9 {
static_assert(sizeof(struct ICCA_STATUS) == 24, "ICCA_STATUS must be 24 bytes");
enum ICCA_WORKFLOW {
STEP,
SLEEP,
START,
INIT,
READY,
GET_USERID,
ACTIVE,
EJECT,
EJECT_CHECK,
END,
CLOSE_EJECT,
CLOSE_E_CHK,
CLOSE_END,
ERR_GETUID = -2
STEP = 0,
SLEEP = 1,
START = 2,
INIT = 3,
READY = 4,
GET_USERID = 5,
ACTIVE = 6,
EJECT = 7,
EJECT_CHECK = 8,
END = 9,
CLOSE_EJECT = 10,
CLOSE_E_CHK = 11,
CLOSE_END = 12,
ERR_GETUID = -2,
ERR_REMOVED = -11
};
// used by RA as the status_code
enum ICCA_STATUS_CODE {
EMPTY = 1,
ENGAGED = 2
};
struct ICCA_UNIT {
struct ICCA_STATUS status {};
enum ICCA_WORKFLOW state = STEP;
enum ICCA_WORKFLOW state_jdz = SLEEP;
bool card_cmd_pressed = false;
bool card_in = false;
double card_in_time = 0.0;
@@ -75,6 +84,11 @@ static inline int icca_get_active_count() {
}
static inline int icca_get_unit_id(int unit_id) {
// on RA the unit_id is zero-indexed
if (avs::game::is_model("JDZ")) {
return unit_id;
}
if (icca_get_active_count() < 2)
return 1;
else {
@@ -122,9 +136,15 @@ static inline void update_card(int unit_id) {
IS_LAST_CARD_FELICA = is_card_uid_felica(unit->status.uid);
unit->state = acio::ICCA_COMPAT_ACTIVE ? START : ACTIVE;
unit->state_jdz = ACTIVE;
unit->status.error = 0;
unit->status.front_sensor = 1;
unit->status.rear_sensor = 1;
} else {
unit->state = ERR_GETUID;
unit->state_jdz = ERR_GETUID;
unit->status.front_sensor = 1;
unit->status.rear_sensor = 1;
memset(unit->status.uid, 0, 8);
}
unit->card_in = true;
@@ -132,10 +152,25 @@ static inline void update_card(int unit_id) {
} else if (unit->state == EJECT_CHECK) {
unit->state = SLEEP;
unit->card_in = false;
unit->status.front_sensor = 0;
unit->status.rear_sensor = 0;
} else if (unit->state_jdz == READY) {
unit->state = GET_USERID;
unit->state_jdz = GET_USERID;
unit->status.error = 0;
unit->status.front_sensor = 1;
unit->status.rear_sensor = 1;
unit->card_in = true;
unit->card_in_time = get_performance_seconds();
}
} else {
unit->state = acio::ICCA_COMPAT_ACTIVE ? START : ACTIVE;
if (unit->state_jdz == READY) {
unit->state_jdz = GET_USERID;
}
unit->status.error = 0;
unit->status.front_sensor = 1;
unit->status.rear_sensor = 1;
unit->card_in = true;
unit->card_in_time = get_performance_seconds();
}
@@ -144,6 +179,12 @@ static inline void update_card(int unit_id) {
unit->state = CLOSE_EJECT;
if (fabs(get_performance_seconds() - unit->card_in_time) > CARD_TIMEOUT) {
unit->card_in = false;
memset(unit->status.uid, 0, 8);
if (unit->state_jdz == ACTIVE || unit->state_jdz == GET_USERID) {
unit->state_jdz = ERR_REMOVED;
}
unit->status.front_sensor = 0;
unit->status.rear_sensor = 0;
}
}
@@ -302,7 +343,11 @@ static char __cdecl ac_io_icca_get_status(void *a1, void *a2) {
// copy state to output buffer
ICCA_UNIT *unit = &ICCA_UNITS[unit_id];
unit->status.status_code = unit->state;
if (avs::game::is_model("JDZ")) {
unit->status.status_code = unit->card_in ? ENGAGED : EMPTY;
} else {
unit->status.status_code = unit->state;
}
memcpy(status, &unit->status, sizeof(struct ICCA_STATUS));
// funny workaround
@@ -387,6 +432,11 @@ static char __cdecl ac_io_icca_req_uid(int unit_id) {
static int __cdecl ac_io_icca_req_uid_isfinished(int unit_id, DWORD *read_state) {
unit_id = icca_get_unit_id(unit_id);
ICCA_UNIT *unit = &ICCA_UNITS[unit_id];
if (avs::game::is_model("JDZ")) {
// hack for RA - only ever seen it as this
*read_state = 8;
return 1;
}
if (unit->card_in) {
if (fabs(get_performance_seconds() - unit->card_in_time) < CARD_TIMEOUT) {
unit->state = END;
@@ -403,16 +453,61 @@ static int __cdecl ac_io_icca_send_keep_alive_packet(int a1, int a2, int a3) {
return 0;
}
static int __cdecl ac_io_icca_workflow_jdz(int workflow, int unit_id) {
unit_id = icca_get_unit_id(unit_id);
ICCA_UNIT *unit = &ICCA_UNITS[unit_id];
switch (workflow) {
// RA uses "STEP" as more of a polling thing it seems
case STEP:
switch (unit->state_jdz) {
case CLOSE_EJECT:
unit->state_jdz = CLOSE_E_CHK;
break;
case CLOSE_E_CHK:
unit->state_jdz = CLOSE_END;
break;
case CLOSE_END:
unit->state_jdz = SLEEP;
break;
default:
break;
}
break;
case SLEEP:
unit->state = SLEEP;
break;
case START:
unit->state_jdz = INIT;
break;
case INIT:
case READY:
unit->state_jdz = READY;
break;
case CLOSE_EJECT:
unit->state_jdz = CLOSE_EJECT;
return CLOSE_E_CHK;
case CLOSE_END:
unit->state_jdz = SLEEP;
break;
default:
// should probably log a warning here
break;
}
return unit->state_jdz;
}
static int __cdecl ac_io_icca_workflow(int workflow, int unit_id) {
// RA uses a different workflow process for the slotted readers; treat it differently
if (avs::game::is_model("JDZ")) {
return ac_io_icca_workflow_jdz(workflow, unit_id);
}
unit_id = icca_get_unit_id(unit_id);
ICCA_UNIT *unit = &ICCA_UNITS[unit_id];
switch (workflow) {
case STEP:
if (avs::game::is_model("JDZ"))
unit->state = SLEEP;
else
unit->state = STEP;
unit->state = STEP;
break;
case SLEEP:
unit->state = SLEEP;
@@ -441,7 +536,6 @@ static int __cdecl ac_io_icca_workflow(int workflow, int unit_id) {
default:
break;
}
return unit->state;
}
@@ -467,6 +561,14 @@ acio::ICCAModule::ICCAModule(HMODULE module, acio::HookMode hookMode) : ACIOModu
void acio::ICCAModule::attach() {
ACIOModule::attach();
// workaround for RA; ac_io_icca_cardunit_init is never called so treat like both are initialized
if (avs::game::is_model("JDZ")) {
ICCA_UNITS[0].initialized = true;
ICCA_UNITS[1].initialized = true;
CARD_TIMEOUT = 10.0; // keep the card in the slot for longer
}
// hooks
ACIO_MODULE_HOOK(ac_io_icca_cardunit_init);
ACIO_MODULE_HOOK(ac_io_icca_cardunit_init_isfinished);
+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);
}
}
+10
View File
@@ -7,6 +7,7 @@
#include <stdint.h>
#include "external/robin_hood.h"
#include "games/popn/popn.h"
#include "launcher/logger.h"
#include "launcher/signal.h"
#include "util/deferlog.h"
@@ -1179,6 +1180,15 @@ namespace avs {
return;
}
// hack, both hello! popn and popn hc using popn.dll
if (games::popn::is_pikapika_model()) {
auto old_size = HEAP_SIZE;
HEAP_SIZE = 0x20000000;
DEFAULT_HEAP_SIZE_SET = true;
log_info("avs-core", "updated heap size: {} -> {}", old_size, avs::core::HEAP_SIZE);
return;
}
if (HEAP_SIZE_DEFAULTS.find(dll_name) != HEAP_SIZE_DEFAULTS.end()) {
auto old_size = HEAP_SIZE;
+29 -3
View File
@@ -217,7 +217,25 @@ namespace avs {
ea3_config_name = "prop/eamuse-config.xml";
}
log_info("avs-ea3", "booting (using {})", ea3_config_name);
if (fileutils::file_exists(ea3_config_name)) {
log_info("avs-ea3", "found {} on disk", ea3_config_name);
} else if (CFG_PATH.size()) {
log_fatal("avs-ea3", "user-specified ea3 config file is missing: {}", ea3_config_name);
} else {
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;
@@ -554,9 +572,17 @@ namespace avs {
auto app_param = avs::core::property_search_safe(app_config, nullptr, "/param");
// call the game init
log_info("avs-ea3", "calling entry init (init code = {})", init_code_str);
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>
+99 -11
View File
@@ -1,4 +1,4 @@
#!/bin/bash
#!/usr/bin/env bash
# set to EN-US for consistency
LANG=en_us_8859_1
@@ -41,12 +41,18 @@ 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"}"
TOOLCHAIN_WINXP_32="${TOOLCHAIN_WINXP_32:-"/opt/llvm-mingw-xp/toolchain-files/cmake/i686-mingw32-clang.cmake"}"
TOOLCHAIN_WINXP_64="${TOOLCHAIN_WINXP_64:-"/opt/llvm-mingw-xp/toolchain-files/cmake/x86_64-mingw32-clang.cmake"}"
BUILDDIR_32_RELEASE="./cmake-build-release-32"
BUILDDIR_32_DEBUG="./cmake-build-debug-32"
BUILDDIR_64_RELEASE="./cmake-build-release-64"
BUILDDIR_64_DEBUG="./cmake-build-debug-64"
BUILDDIR_WINXP_32_RELEASE="./cmake-build-release-winxp-32"
BUILDDIR_WINXP_32_DEBUG="./cmake-build-debug-winxp-32"
BUILDDIR_WINXP_64_RELEASE="./cmake-build-release-winxp-64"
BUILDDIR_WINXP_64_DEBUG="./cmake-build-debug-winxp-64"
DEBUG=0
OUTDIR="./bin/spice2x"
OUTDIR_EXTRAS="./bin/spice2x/extras"
@@ -64,17 +70,34 @@ DIST_NAME_EXTRAS="spice2x-$(date +%y)-$(date +%m)-$(date +%d)-full.zip"
DIST_COMMENT=${DIST_NAME}$'\n'"$GIT_BRANCH - $GIT_HEAD"$'\nThank you for playing.'
TARGETS_32="spicetools_stubs_kbt spicetools_stubs_kld spicetools_cfg spicetools_cfg_linux spicetools_spice spicetools_spice_laa spicetools_spice_linux spicetools_stubs_cpusbxpkm"
TARGETS_64="spicetools_stubs_kbt64 spicetools_stubs_kld64 spicetools_stubs_nvEncodeAPI64 spicetools_stubs_nvcuvid spicetools_stubs_nvcuda spicetools_spice64 spicetools_spice64_linux"
TARGETS_XP32="spicetools_cfg spicetools_spice"
TARGETS_XP64="spicetools_spice64"
# determine build type
BUILD_TYPE="Release"
BUILDDIR_32=${BUILDDIR_32_RELEASE}
BUILDDIR_64=${BUILDDIR_64_RELEASE}
BUILDDIR_WINXP_32=${BUILDDIR_WINXP_32_RELEASE}
BUILDDIR_WINXP_64=${BUILDDIR_WINXP_64_RELEASE}
if ((DEBUG > 0))
then
BUILD_TYPE="Debug"
BUILDDIR_32=${BUILDDIR_32_DEBUG}
BUILDDIR_64=${BUILDDIR_64_DEBUG}
DIST_NAME=$(echo ${DIST_NAME} | sed 's/\.[^.]*$/-dbg&/')
BUILDDIR_WINXP_32=${BUILDDIR_WINXP_32_DEBUG}
BUILDDIR_WINXP_64=${BUILDDIR_WINXP_64_DEBUG}
DIST_NAME=$(echo "${DIST_NAME}" | sed 's/\.[^.]*$/-dbg&/')
fi
# is the XP-compatible toolchain installed?
XP_MUST_BUILD=0
BUILD_XP=0
if [ -f "$TOOLCHAIN_WINXP_32" ] && [ -f "$TOOLCHAIN_WINXP_64" ]; then
BUILD_XP=1;
elif ((XP_MUST_BUILD > 0))
then
echo "WinXP toolchain not available, aborting"
exit 1
fi
# determine number of cores
@@ -90,6 +113,13 @@ echo "Git Branch: $GIT_BRANCH"
echo "Git Head: $GIT_HEAD"
echo "Toolchain for 32bit targets: $TOOLCHAIN_32"
echo "Toolchain for 64bit targets: $TOOLCHAIN_64"
if ((BUILD_XP > 0))
then
echo "Toolchain for WinXP 32bit targets: $TOOLCHAIN_WINXP_32"
echo "Toolchain for WinXP 64bit targets: $TOOLCHAIN_WINXP_64"
else
echo "WinXP toolchain not available, skipping WinXP builds"
fi
echo "Distribution Name: $DIST_NAME"
echo "Build Type: $BUILD_TYPE"
echo "Cores: $CORES"
@@ -114,7 +144,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,14 +157,62 @@ 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
if ((BUILD_XP > 0))
then
# 32 bit Windows XP
echo ""
echo "Building 32bit targets (WinXP toolchain)..."
echo "========================="
if ((CLEAN_BUILD > 0))
then
rm -rf ${BUILDDIR_WINXP_32}
fi
mkdir -p ${BUILDDIR_WINXP_32}
pushd ${BUILDDIR_WINXP_32} > /dev/null
CXXFLAGS="$CXXFLAGS -DSPICE_XP=1" cmake -G "Ninja" -DCMAKE_TOOLCHAIN_FILE=${TOOLCHAIN_WINXP_32} -DCMAKE_BUILD_TYPE=${BUILD_TYPE} "$OLDPWD" && ninja ${TARGETS_XP32}
popd > /dev/null
# 64 bit Windows XP
echo ""
echo "Building 64bit targets (WinXP toolchain)..."
echo "========================="
if ((CLEAN_BUILD > 0))
then
rm -rf ${BUILDDIR_WINXP_64}
fi
mkdir -p ${BUILDDIR_WINXP_64}
pushd ${BUILDDIR_WINXP_64} > /dev/null
CXXFLAGS="$CXXFLAGS -DSPICE_XP=1" cmake -G "Ninja" -DCMAKE_TOOLCHAIN_FILE=${TOOLCHAIN_WINXP_64} -DCMAKE_BUILD_TYPE=${BUILD_TYPE} "$OLDPWD" && ninja ${TARGETS_XP64}
popd > /dev/null
else
echo ""
echo "Skipping WinXP builds, toolchain not specified"
fi
echo ""
echo "Compilation process done :)"
echo "==========================="
)
if ((BUILD_XP > 0))
then
echo ""
echo "Checking XP compatibility..."
echo "============================="
if ! command -v windows_dll_compat_checker &> /dev/null; then
echo "WARNING: windows_dll_compat_checker not found, skipping XP compatibility check"
else
windows_dll_compat_checker -s PREMADE/winxp_x86_64.ini \
${BUILDDIR_WINXP_64}/spicetools/64/spice64.exe
windows_dll_compat_checker -s PREMADE/winxp_x86_64_32bit_dlls.ini \
${BUILDDIR_WINXP_32}/spicetools/spicecfg.exe \
${BUILDDIR_WINXP_32}/spicetools/32/spice.exe
fi
fi
# generate PDBs
if false # ((DEBUG > 0))
then
@@ -191,6 +269,10 @@ rm -rf ${OUTDIR_EXTRAS}
mkdir -p ${OUTDIR_EXTRAS}
mkdir -p ${OUTDIR_EXTRAS}/largeaddressaware
mkdir -p ${OUTDIR_EXTRAS}/linux
if ((BUILD_XP > 0))
then
mkdir -p ${OUTDIR_EXTRAS}/winxp
fi
if false # ((DEBUG > 0))
then
@@ -226,6 +308,12 @@ else
cp ${BUILDDIR_64}/spicetools/64/nvcuda.dll ${OUTDIR}/stubs/64 2>/dev/null
cp ${BUILDDIR_64}/spicetools/64/nvcuvid.dll ${OUTDIR}/stubs/64 2>/dev/null
cp ${BUILDDIR_32}/spicetools/32/cpusbxpkm.dll ${OUTDIR}/stubs/32 2>/dev/null
if ((BUILD_XP > 0))
then
cp ${BUILDDIR_WINXP_32}/spicetools/spicecfg.exe ${OUTDIR_EXTRAS}/winxp 2>/dev/null
cp ${BUILDDIR_WINXP_32}/spicetools/32/spice.exe ${OUTDIR_EXTRAS}/winxp 2>/dev/null
cp ${BUILDDIR_WINXP_64}/spicetools/64/spice64.exe ${OUTDIR_EXTRAS}/winxp 2>/dev/null
fi
fi
# pack source files to output directory
@@ -234,7 +322,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 +337,11 @@ if ((DIST_ENABLE > 0))
then
echo "Building dist..."
mkdir -p ${DIST_FOLDER}
rm -rf ${DIST_FOLDER}/${DIST_NAME}
pushd ${OUTDIR}/.. > /dev/null
zip -qrXT9 $OLDPWD/${DIST_FOLDER}/${DIST_NAME} spice2x -x "spice2x/extras/*" -z <<< "$DIST_COMMENT"
rm -rf "${DIST_FOLDER}/${DIST_NAME}"
pushd ${OUTDIR} > /dev/null
zip -qrXT9 "$OLDPWD/${DIST_FOLDER}/${DIST_NAME}" . -x "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}" . -z <<< "$DIST_COMMENT"
popd > /dev/null
fi
+12 -4
View File
@@ -1,4 +1,12 @@
#!/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 "$@"
#!/usr/bin/env bash
set -eu
docker build --pull "$PWD/external/docker" -t spicetools/deps --platform linux/x86_64
docker build --build-context gitroot="$PWD/../../.git" . -t spicetools/spice:latest
# Interactive TTY if available, so docker build can be Ctrl+C'd
DOCKER_FLAGS=""
[ -t 0 ] && DOCKER_FLAGS="-it"
docker run $DOCKER_FLAGS --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
+4
View File
@@ -90,6 +90,10 @@ std::string Analog::getDisplayString(rawinput::RawInputManager *manager) {
return "MIDI Unknown Index " + indexString + " (" + device->desc + ")";
}
}
case rawinput::XINPUT_GAMEPAD:
return fmt::format("{} ({})",
xinput::get_analog_string(static_cast<xinput::XInputAnalogEnum>(index)),
device->desc);
case rawinput::DESTROYED:
return "Device unplugged (" + indexString + ")";
default:
+1
View File
@@ -91,6 +91,7 @@ public:
setMultiplier(1);
setRelativeMode(false);
setDelayBufferDepth(0);
setLastState(0.5f);
}
inline void clearBindings() {
+102 -12
View File
@@ -1,5 +1,6 @@
#include "api.h"
#include <cassert>
#include <optional>
#include "launcher/superexit.h"
@@ -37,7 +38,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 +52,7 @@ std::vector<Button> GameAPI::Buttons::sortButtons(
if (vkey_defaults) {
button.setVKey(vkey_defaults->at(index));
button.setVKeyDefault(vkey_defaults->at(index));
}
}
@@ -193,9 +199,19 @@ GameAPI::Buttons::State GameAPI::Buttons::getState(rawinput::RawInputManager *ma
if (vKey < value_states->size()) {
auto value = value_states->at(vKey);
if (current_button->getAnalogType() == BAT_POSITIVE) {
state = value > 0.6f ? BUTTON_PRESSED : BUTTON_NOT_PRESSED;
float threshold = 0.6f;
if (current_button->getBatThreshold() > 0) {
threshold = std::clamp(current_button->getBatThreshold() / 100.f, 0.01f, 0.99f);
}
state = value > threshold ? BUTTON_PRESSED : BUTTON_NOT_PRESSED;
} else if (current_button->getAnalogType() == BAT_NEGATIVE) {
state = value < 0.4f ? BUTTON_PRESSED : BUTTON_NOT_PRESSED;
float threshold = 0.4f;
if (current_button->getBatThreshold() > 0) {
threshold = std::clamp((100 - current_button->getBatThreshold()) / 100.f, 0.01f, 0.99f);
}
state = value < threshold ? BUTTON_PRESSED : BUTTON_NOT_PRESSED;
} else {
state = value > 0.01f ? BUTTON_PRESSED : BUTTON_NOT_PRESSED;
}
@@ -384,6 +400,16 @@ GameAPI::Buttons::State GameAPI::Buttons::getState(rawinput::RawInputManager *ma
}
case rawinput::PIUIO_DEVICE: {
state = device->piuioDev->IsPressed(vKey) ? BUTTON_PRESSED : BUTTON_NOT_PRESSED;
break;
}
case rawinput::XINPUT_GAMEPAD: {
assert(reinterpret_cast<uintptr_t>(device->handle) < XUSER_MAX_COUNT);
const auto player = static_cast<uint8_t>(reinterpret_cast<uintptr_t>(device->handle));
state = manager->XINPUT_MGR->is_button_pressed(
player,
static_cast<xinput::XInputButtonEnum>(vKey)) ?
BUTTON_PRESSED : BUTTON_NOT_PRESSED;
break;
}
default:
break;
@@ -581,7 +607,7 @@ float Buttons::getVelocity(std::unique_ptr<rawinput::RawInputManager> &manager,
}
}
float GameAPI::Analogs::getState(rawinput::Device *device, Analog &analog) {
float GameAPI::Analogs::getState(rawinput::RawInputManager *manager, rawinput::Device *device, Analog &analog) {
float value = 0.5f;
if (!device) {
return value;
@@ -644,10 +670,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
@@ -760,6 +788,18 @@ float GameAPI::Analogs::getState(rawinput::Device *device, Analog &analog) {
}
break;
}
case rawinput::XINPUT_GAMEPAD: {
assert(reinterpret_cast<uintptr_t>(device->handle) < XUSER_MAX_COUNT);
const auto player = static_cast<uint8_t>(reinterpret_cast<uintptr_t>(device->handle));
value = manager->XINPUT_MGR->get_analog_state(
player,
static_cast<xinput::XInputAnalogEnum>(index));
// invert value
if (inverted) {
value = 1.f - value;
}
break;
}
default:
break;
}
@@ -815,7 +855,7 @@ float GameAPI::Analogs::getState(rawinput::RawInputManager *manager, Analog &ana
return analog.getLastState();
}
float state = getState(device, analog);
float state = getState(manager, device, analog);
analog.setLastState(state);
return state;
@@ -859,7 +899,45 @@ std::vector<Light> GameAPI::Lights::sortLights(
return sorted;
}
void GameAPI::Lights::writeLight(rawinput::Device *device, int index, float value) {
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::RawInputManager *manager, rawinput::Device *device, int index, float value) {
// check device
if (!device) {
@@ -946,6 +1024,18 @@ void GameAPI::Lights::writeLight(rawinput::Device *device, int index, float valu
}
break;
}
case rawinput::XINPUT_GAMEPAD: {
assert(reinterpret_cast<uintptr_t>(device->handle) < XUSER_MAX_COUNT);
const auto player = static_cast<uint8_t>(reinterpret_cast<uintptr_t>(device->handle));
if (index < static_cast<int>(xinput::XInputOutputEnum::COUNT)) {
manager->XINPUT_MGR->set_output_state(
player, static_cast<xinput::XInputOutputEnum>(index), value);
// no need to set output_pending; xinput output handled immediately
} else {
log_warning("api", "invalid xinput light index: {}", index);
}
break;
}
default:
break;
}
@@ -971,9 +1061,9 @@ void GameAPI::Lights::writeLight(rawinput::RawInputManager *manager, Light &ligh
// write state
if (light.override_enabled) {
writeLight(device, light.getIndex(), light.override_state);
writeLight(manager, device, light.getIndex(), light.override_state);
} else {
writeLight(device, light.getIndex(), value);
writeLight(manager, device, light.getIndex(), value);
}
}
+17 -23
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.
*
@@ -107,27 +103,19 @@ namespace GameAPI {
}
}
float getState(rawinput::Device *device, Analog &analog);
float getState(rawinput::RawInputManager *manager, rawinput::Device *device, Analog &analog);
float getState(rawinput::RawInputManager *manager, Analog &analog);
float getState(std::unique_ptr<rawinput::RawInputManager> &manager, Analog &analog);
}
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,7 +125,17 @@ namespace GameAPI {
}
}
void writeLight(rawinput::Device *device, int index, float value);
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::RawInputManager *manager, 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"
+56 -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,11 @@ 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";
case INVALID_VKEY:
return "Invalid";
default:
// check win API
@@ -301,17 +323,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);
@@ -395,6 +417,10 @@ std::string Button::getDisplayString(rawinput::RawInputManager* manager) {
}
case rawinput::PIUIO_DEVICE:
return "PIUIO " + vKeyString;
case rawinput::XINPUT_GAMEPAD:
return fmt::format("{} ({})",
xinput::get_button_string(static_cast<xinput::XInputButtonEnum>(vKey)),
device->desc);
case rawinput::DESTROYED:
return "Device unplugged (" + vKeyString + ")";
default:
+65 -4
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,25 @@ 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;
int bat_threshold = 0; // for positive/negative only, value in percentage, zero is default
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;
@@ -70,12 +84,20 @@ public:
if (this->override_enabled) {
return true;
}
// has a device
if (!this->device_identifier.empty()) {
return true;
}
// naive, and has a valid vKey
if (this->vKey != INVALID_VKEY) {
return true;
}
for (auto &alternative : this->alternatives) {
if (alternative.vKey != INVALID_VKEY) {
if (!alternative.getDeviceIdentifier().empty() ||
alternative.getVKey() != INVALID_VKEY) {
return true;
}
}
@@ -88,6 +110,7 @@ public:
alternatives.clear();
device_identifier = "";
analog_type = BAT_NONE;
bat_threshold = 0;
}
std::string getDisplayString(rawinput::RawInputManager* manager);
@@ -116,6 +139,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;
}
@@ -140,6 +171,14 @@ public:
this->debounce_down = debounce_time_down;
}
inline void setBatThreshold(int bat_threshold) {
this->bat_threshold = bat_threshold;
}
inline int getBatThreshold() const {
return this->bat_threshold;
}
inline bool getInvert() const {
return this->invert;
}
@@ -164,6 +203,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 +219,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);
+88 -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 ///
@@ -153,6 +162,7 @@ bool Config::addGame(Game &game) {
auto analogType = (int) BAT_NONE;
double debounce_up = 0.0;
double debounce_down = 0.0;
int bat_threshold = 0;
int velocity_threshold = 0;
bool invert = false;
tinyxml2::XMLError attrError = gameButtonNode->QueryIntAttribute("vkey", &vKey);
@@ -160,6 +170,7 @@ bool Config::addGame(Game &game) {
gameButtonNode->QueryIntAttribute("analogtype", &analogType);
gameButtonNode->QueryDoubleAttribute("debounce_up", &debounce_up);
gameButtonNode->QueryDoubleAttribute("debounce_down", &debounce_down);
gameButtonNode->QueryIntAttribute("bat_threshold", &bat_threshold);
gameButtonNode->QueryIntAttribute("velocity_threshold", &velocity_threshold);
gameButtonNode->QueryBoolAttribute("invert", &invert);
if (attrError != tinyxml2::XMLError::XML_SUCCESS) {
@@ -171,6 +182,7 @@ bool Config::addGame(Game &game) {
gameButtonNode->SetAttribute("devid", button->getDeviceIdentifier().c_str());
gameButtonNode->SetAttribute("debounce_up", debounce_up);
gameButtonNode->SetAttribute("debounce_down", debounce_down);
gameButtonNode->SetAttribute("bat_threshold", bat_threshold);
gameButtonNode->SetAttribute("velocity_threshold", velocity_threshold);
gameButtonNode->SetAttribute("invert", invert);
gameButtonsNode->InsertEndChild(gameButtonNode);
@@ -179,6 +191,7 @@ bool Config::addGame(Game &game) {
button->setAnalogType((ButtonAnalogType) analogType);
button->setDebounceUp(debounce_up);
button->setDebounceDown(debounce_down);
button->setBatThreshold(bat_threshold);
button->setVelocityThreshold(velocity_threshold);
button->setInvert(invert);
if (devid) {
@@ -198,6 +211,7 @@ bool Config::addGame(Game &game) {
gameButtonNode->SetAttribute("analogtype", (int) it.getAnalogType());
gameButtonNode->SetAttribute("debounce_up", it.getDebounceUp());
gameButtonNode->SetAttribute("debounce_down", it.getDebounceDown());
gameButtonNode->SetAttribute("bat_threshold", it.getBatThreshold());
gameButtonNode->SetAttribute("velocity_threshold", it.getVelocityThreshold());
gameButtonNode->SetAttribute("invert", it.getInvert());
gameButtonNode->SetAttribute("devid", it.getDeviceIdentifier().c_str());
@@ -423,6 +437,7 @@ bool Config::addGame(Game &game) {
gameButtonNode->SetAttribute("analogtype", it.getAnalogType());
gameButtonNode->SetAttribute("debounce_up", it.getDebounceUp());
gameButtonNode->SetAttribute("debounce_down", it.getDebounceDown());
gameButtonNode->SetAttribute("bat_threshold", it.getBatThreshold());
gameButtonNode->SetAttribute("velocity_threshold", it.getVelocityThreshold());
gameButtonNode->SetAttribute("invert", it.getInvert());
gameButtonNode->SetAttribute("devid", it.getDeviceIdentifier().c_str());
@@ -510,8 +525,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");
@@ -524,6 +541,7 @@ bool Config::updateBinding(const Game &game, const Button &button, int alternati
gameButtonNode->SetAttribute("analogtype", (int) button.getAnalogType());
gameButtonNode->SetAttribute("debounce_up", button.getDebounceUp());
gameButtonNode->SetAttribute("debounce_down", button.getDebounceDown());
gameButtonNode->SetAttribute("bat_threshold", button.getBatThreshold());
gameButtonNode->SetAttribute("velocity_threshold", button.getVelocityThreshold());
gameButtonNode->SetAttribute("invert", button.getInvert());
gameButtonNode->SetAttribute("devid", button.getDeviceIdentifier().c_str());
@@ -540,6 +558,7 @@ bool Config::updateBinding(const Game &game, const Button &button, int alternati
gameButtonNode->SetAttribute("analogtype", 0);
gameButtonNode->SetAttribute("debounce_up", 0.0);
gameButtonNode->SetAttribute("debounce_down", 0.0);
gameButtonNode->SetAttribute("bat_threshold", 0);
gameButtonNode->SetAttribute("velocity_threshold", 0);
gameButtonNode->SetAttribute("invert", false);
gameButtonNode->SetAttribute("devid", "");
@@ -574,6 +593,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 +812,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;
@@ -871,12 +954,14 @@ std::vector<Button> Config::getButtons(const std::string &gameName) {
auto analogType = (int) BAT_NONE;
double debounce_up = 0.0;
double debounce_down = 0.0;
int bat_threshold = 0;
int velocity_threshold = 0;
bool invert = false;
gameButtonNode->QueryIntAttribute("vkey", &vKey);
gameButtonNode->QueryIntAttribute("analogtype", &analogType);
gameButtonNode->QueryDoubleAttribute("debounce_up", &debounce_up);
gameButtonNode->QueryDoubleAttribute("debounce_down", &debounce_down);
gameButtonNode->QueryIntAttribute("bat_threshold", &bat_threshold);
gameButtonNode->QueryIntAttribute("velocity_threshold", &velocity_threshold);
gameButtonNode->QueryBoolAttribute("invert", &invert);
const char *devid = gameButtonNode->Attribute("devid");
@@ -891,6 +976,7 @@ std::vector<Button> Config::getButtons(const std::string &gameName) {
alt.setAnalogType((ButtonAnalogType) analogType);
alt.setDebounceUp(debounce_up);
alt.setDebounceDown(debounce_down);
alt.setBatThreshold(bat_threshold);
alt.setVelocityThreshold(velocity_threshold);
alt.setInvert(invert);
if (devid) {
@@ -909,6 +995,7 @@ std::vector<Button> Config::getButtons(const std::string &gameName) {
button.setAnalogType((ButtonAnalogType) analogType);
button.setDebounceUp(debounce_up);
button.setDebounceDown(debounce_down);
button.setBatThreshold(bat_threshold);
button.setVelocityThreshold(velocity_threshold);
button.setInvert(invert);
if (devid) {
+465
View File
@@ -0,0 +1,465 @@
#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("bat_threshold", entry.bat_threshold);
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;
int bat = 0;
el->QueryIntAttribute("bat_threshold", &bat);
entry.bat_threshold = bat;
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;
}
}
+9
View File
@@ -85,6 +85,15 @@ std::string Light::getDisplayString(rawinput::RawInputManager* manager) {
return "Invalid SMX Dedicab Light (" + index_string + ")";
}
case rawinput::XINPUT_GAMEPAD: {
if (index < static_cast<size_t>(xinput::XInputOutputEnum::COUNT)) {
return fmt::format("{} ({})",
xinput::get_output_string(static_cast<xinput::XInputOutputEnum>(index)),
device->desc);
}
return "Invalid XInput Gamepad Light (" + index_string + ")";
}
case rawinput::DESTROYED:
return "Unplugged device (" + index_string + ")";
default:
+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;
}
};
+14
View File
@@ -13,6 +13,16 @@ enum class OptionType {
Hex,
};
enum class OptionPickerType {
None,
AsioDriver,
EACard,
CpuAffinity,
FilePath,
DirectoryPath,
Monitor,
};
struct OptionDefinition {
std::string title;
// unique identifier used for flag matching but also stored in config files
@@ -32,6 +42,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
+1
View File
@@ -60,6 +60,7 @@ namespace cfg {
// window = rectangle including the frame
// client = just the content area without frames.
bool window_always_on_top = false;
bool window_disable_round_corners = false;
bool client_keep_aspect_ratio = true;
bool enable_window_resize = false;
int window_decoration = 0; // enum type WindowDecorationMode
+8
View File
@@ -15,3 +15,11 @@ RUN pacman --noconfirm -Syu git \
ccache
RUN useradd user -m && echo "ALL ALL=(ALL) NOPASSWD: ALL" >> /etc/sudoers
RUN su user -c "cd /tmp/ && git clone https://aur.archlinux.org/yay-bin.git --depth=1 && cd yay-bin && makepkg --noconfirm -si && yay --noconfirm -S mingw-w64-cmake"
RUN mkdir -p /opt/llvm-mingw-xp \
&& curl -fsSL "https://github.com/mon/llvm-mingw-xp/releases/download/llvm-mingw-xp-22.1.0/llvm-mingw-llvm-mingw-xp-22.1.0-msvcrt-ubuntu-22.04-x86_64.tar.xz" \
| tar -xJ --strip-components=1 -C /opt/llvm-mingw-xp
ENV PATH="$PATH:/opt/llvm-mingw-xp/bin"
RUN curl -fsSL "https://github.com/mon/windows-dll-compat-checker/releases/download/v1.3/windows_dll_compat_checker-linux-x86_64.tar.xz" \
| tar -xJ -C /usr/local/bin
+1 -1
View File
@@ -1,6 +1,6 @@
The MIT License (MIT)
Copyright (c) 2014-2023 Omar Cornut
Copyright (c) 2014-2026 Omar Cornut
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
+645 -269
View File
@@ -1,16 +1,37 @@
// dear imgui: Renderer for DirectX9
// This needs to be used along with a Platform Binding (e.g. Win32)
// dear imgui: Renderer Backend for DirectX9
// This needs to be used along with a Platform Backend (e.g. Win32)
// Implemented features:
// [X] Renderer: User texture binding. Use 'LPDIRECT3DTEXTURE9' as ImTextureID. Read the FAQ about ImTextureID in imgui.cpp.
// [X] Renderer: Support for large meshes (64k+ vertices) with 16-bits indices.
// [X] Renderer: User texture binding. Use 'LPDIRECT3DTEXTURE9' as texture identifier. Read the FAQ about ImTextureID/ImTextureRef!
// [X] Renderer: Large meshes support (64k+ vertices) even with 16-bit indices (ImGuiBackendFlags_RendererHasVtxOffset).
// [X] Renderer: Texture updates support for dynamic font atlas (ImGuiBackendFlags_RendererHasTextures).
// [X] Renderer: IMGUI_USE_BGRA_PACKED_COLOR support, as this is the optimal color encoding for DirectX9.
// [X] Renderer: Multi-viewport support (multiple windows). Enable with 'io.ConfigFlags |= ImGuiConfigFlags_ViewportsEnable'.
// You can copy and use unmodified imgui_impl_* files in your project. See main.cpp for an example of using this.
// If you are new to dear imgui, read examples/README.txt and read the documentation at the top of imgui.cpp.
// https://github.com/ocornut/imgui
// You can use unmodified imgui_impl_* files in your project. See examples/ folder for examples of using this.
// Prefer including the entire imgui/ repository into your project (either as a copy or as a submodule), and only build the backends you need.
// Learn about Dear ImGui:
// - FAQ https://dearimgui.com/faq
// - Getting Started https://dearimgui.com/getting-started
// - Documentation https://dearimgui.com/docs (same as your local docs/ folder).
// - Introduction, links and more at the top of imgui.cpp
// CHANGELOG
// (minor and older changes stripped away, please see git history for details)
// 2026-XX-XX: Platform: Added support for multiple windows via the ImGuiPlatformIO interface.
// 2026-03-19: Fixed issue in ImGui_ImplDX9_UpdateTexture() if ImTextureID_Invalid is defined to be != 0, which became the default since 2026-03-12. (#9295, #9310)
// 2025-09-18: Call platform_io.ClearRendererHandlers() on shutdown.
// 2025-06-11: DirectX9: Added support for ImGuiBackendFlags_RendererHasTextures, for dynamic font atlas.
// 2024-10-07: DirectX9: Changed default texture sampler to Clamp instead of Repeat/Wrap.
// 2024-02-12: DirectX9: Using RGBA format when supported by the driver to avoid CPU side conversion. (#6575)
// 2022-10-11: Using 'nullptr' instead of 'NULL' as per our switch to C++11.
// 2021-06-29: Reorganized backend to pull data from a single structure to facilitate usage with multiple-contexts (all g_XXXX access changed to bd->XXXX).
// 2021-06-25: DirectX9: Explicitly disable texture state stages after >= 1.
// 2021-05-19: DirectX9: Replaced direct access to ImDrawCmd::TextureId with a call to ImDrawCmd::GetTexID(). (will become a requirement)
// 2021-04-23: DirectX9: Explicitly setting up more graphics states to increase compatibility with unusual non-default states.
// 2021-03-18: DirectX9: Calling IDirect3DStateBlock9::Capture() after CreateStateBlock() as a workaround for state restoring issues (see #3857).
// 2021-03-03: DirectX9: Added support for IMGUI_USE_BGRA_PACKED_COLOR in user's imconfig file.
// 2021-02-18: DirectX9: Change blending equation to preserve alpha in output buffer.
// 2019-05-29: DirectX9: Added support for large mesh (64K+ vertices), enable ImGuiBackendFlags_RendererHasVtxOffset flag.
// 2019-04-30: DirectX9: Added support for special ImDrawCallback_ResetRenderState callback to reset render state.
// 2019-03-29: Misc: Fixed erroneous assert in ImGui_ImplDX9_InvalidateDeviceObjects().
@@ -22,160 +43,154 @@
// 2018-02-16: Misc: Obsoleted the io.RenderDrawListsFn callback and exposed ImGui_ImplDX9_RenderDrawData() in the .h file so you can call it yourself.
// 2018-02-06: Misc: Removed call to ImGui::Shutdown() which is not available from 1.60 WIP, user needs to call CreateContext/DestroyContext themselves.
#include "imgui_impl_dx9.h"
#include <algorithm>
// DirectX
#include <d3d9.h>
#include "imgui.h"
#ifndef IMGUI_DISABLE
#include "imgui_impl_dx9.h"
// allow std::min use
#ifdef min
#undef min
#endif
// DirectX data
static LPDIRECT3DDEVICE9 g_pd3dDevice = NULL;
static LPDIRECT3DVERTEXBUFFER9 g_pVB = NULL;
static LPDIRECT3DINDEXBUFFER9 g_pIB = NULL;
static LPDIRECT3DTEXTURE9 g_FontTexture = NULL;
static int g_VertexBufferSize = 5000, g_IndexBufferSize = 10000;
// DirectX
#include <d3d9.h>
// Clang/GCC warnings with -Weverything
#if defined(__clang__)
#pragma clang diagnostic ignored "-Wold-style-cast" // warning: use of old-style cast // yes, they are more terse.
#pragma clang diagnostic ignored "-Wsign-conversion" // warning: implicit conversion changes signedness
#endif
// DirectX data
struct ImGui_ImplDX9_Data
{
LPDIRECT3DDEVICE9 pd3dDevice;
LPDIRECT3DVERTEXBUFFER9 pVB;
LPDIRECT3DINDEXBUFFER9 pIB;
int VertexBufferSize;
int IndexBufferSize;
bool HasRgbaSupport;
ImGui_ImplDX9_Data() { memset((void*)this, 0, sizeof(*this)); VertexBufferSize = 5000; IndexBufferSize = 10000; }
};
struct CUSTOMVERTEX
{
float pos[3];
D3DCOLOR col;
float uv[2];
};
#define D3DFVF_CUSTOMVERTEX (D3DFVF_XYZ|D3DFVF_DIFFUSE|D3DFVF_TEX1)
// Render function.
// (this used to be set in io.RenderDrawListsFn and called by ImGui::Render(), but you can now call this directly from your main loop)
void ImGui_ImplDX9_RenderDrawData(ImDrawData *draw_data) {
#ifdef IMGUI_USE_BGRA_PACKED_COLOR
#define IMGUI_COL_TO_DX9_ARGB(_COL) (_COL)
#else
#define IMGUI_COL_TO_DX9_ARGB(_COL) (((_COL) & 0xFF00FF00) | (((_COL) & 0xFF0000) >> 16) | (((_COL) & 0xFF) << 16))
#endif
// Avoid rendering when minimized
if (draw_data->DisplaySize.x <= 0.0f || draw_data->DisplaySize.y <= 0.0f)
return;
// Backend data stored in io.BackendRendererUserData to allow support for multiple Dear ImGui contexts
// It is STRONGLY preferred that you use docking branch with multi-viewports (== single Dear ImGui context + multiple windows) instead of multiple Dear ImGui contexts.
static ImGui_ImplDX9_Data* ImGui_ImplDX9_GetBackendData()
{
return ImGui::GetCurrentContext() ? (ImGui_ImplDX9_Data*)ImGui::GetIO().BackendRendererUserData : nullptr;
}
// Create and grow buffers if needed
if (!g_pVB || g_VertexBufferSize < draw_data->TotalVtxCount) {
if (g_pVB) {
g_pVB->Release();
g_pVB = NULL;
}
g_VertexBufferSize = draw_data->TotalVtxCount + 5000;
if (g_pd3dDevice->CreateVertexBuffer(g_VertexBufferSize * sizeof(ImDrawVert),
D3DUSAGE_DYNAMIC | D3DUSAGE_WRITEONLY, D3DFVF_CUSTOMVERTEX,
D3DPOOL_DEFAULT, &g_pVB, NULL) < 0)
return;
}
if (!g_pIB || g_IndexBufferSize < draw_data->TotalIdxCount) {
if (g_pIB) {
g_pIB->Release();
g_pIB = NULL;
}
g_IndexBufferSize = draw_data->TotalIdxCount + 10000;
if (g_pd3dDevice->CreateIndexBuffer(g_IndexBufferSize * sizeof(ImDrawIdx),
D3DUSAGE_DYNAMIC | D3DUSAGE_WRITEONLY,
sizeof(ImDrawIdx) == 2 ? D3DFMT_INDEX16 : D3DFMT_INDEX32,
D3DPOOL_DEFAULT, &g_pIB, NULL) < 0)
return;
}
// Forward Declarations
static void ImGui_ImplDX9_InitMultiViewportSupport();
static void ImGui_ImplDX9_ShutdownMultiViewportSupport();
static void ImGui_ImplDX9_CreateDeviceObjectsForPlatformWindows();
static void ImGui_ImplDX9_InvalidateDeviceObjectsForPlatformWindows();
// Backup the DX9 state
IDirect3DStateBlock9 *d3d9_state_block = NULL;
if (g_pd3dDevice->CreateStateBlock(D3DSBT_ALL, &d3d9_state_block) < 0)
return;
struct render_state_t {
IDirect3DSurface9 *back_buffer;
IDirect3DSurface9 *depth_stencil;
IDirect3DSurface9 *render_targets[8];
D3DCAPS9 caps;
};
// Backup the DX9 transform (DX9 documentation suggests that it is included in the StateBlock but it doesn't appear to)
D3DMATRIX last_world, last_view, last_projection;
g_pd3dDevice->GetTransform(D3DTS_WORLD, &last_world);
g_pd3dDevice->GetTransform(D3DTS_VIEW, &last_view);
g_pd3dDevice->GetTransform(D3DTS_PROJECTION, &last_projection);
// Copy all vertices into a single contiguous buffer
ImDrawVert *vtx_dst;
ImDrawIdx *idx_dst;
if (g_pVB->Lock(0, (UINT) (draw_data->TotalVtxCount * sizeof(ImDrawVert)), (void **) &vtx_dst,
D3DLOCK_DISCARD) < 0)
return;
if (g_pIB->Lock(0, (UINT) (draw_data->TotalIdxCount * sizeof(ImDrawIdx)), (void **) &idx_dst,
D3DLOCK_DISCARD) < 0)
return;
for (int n = 0; n < draw_data->CmdListsCount; n++) {
const ImDrawList *cmd_list = draw_data->CmdLists[n];
memcpy(vtx_dst, cmd_list->VtxBuffer.Data, cmd_list->VtxBuffer.Size * sizeof(ImDrawVert));
memcpy(idx_dst, cmd_list->IdxBuffer.Data, cmd_list->IdxBuffer.Size * sizeof(ImDrawIdx));
vtx_dst += cmd_list->VtxBuffer.Size;
idx_dst += cmd_list->IdxBuffer.Size;
}
g_pVB->Unlock();
g_pIB->Unlock();
g_pd3dDevice->SetStreamSource(0, g_pVB, 0, sizeof(ImDrawVert));
g_pd3dDevice->SetIndices(g_pIB);
g_pd3dDevice->SetFVF(D3DFVF_CUSTOMVERTEX);
// Functions
static void ImGui_ImplDX9_SetupRenderState(ImDrawData* draw_data, struct render_state_t *render_state)
{
ImGui_ImplDX9_Data* bd = ImGui_ImplDX9_GetBackendData();
// Setup viewport
D3DVIEWPORT9 vp;
vp.X = vp.Y = 0;
vp.Width = (DWORD) draw_data->DisplaySize.x;
vp.Height = (DWORD) draw_data->DisplaySize.y;
vp.Width = (DWORD)draw_data->DisplaySize.x;
vp.Height = (DWORD)draw_data->DisplaySize.y;
vp.MinZ = 0.0f;
vp.MaxZ = 1.0f;
g_pd3dDevice->SetViewport(&vp);
g_pd3dDevice->SetPixelShader(nullptr);
g_pd3dDevice->SetVertexShader(nullptr);
LPDIRECT3DDEVICE9 device = bd->pd3dDevice;
device->SetViewport(&vp);
D3DCAPS9 caps {};
if (FAILED(g_pd3dDevice->GetDeviceCaps(&caps))) {
caps.NumSimultaneousRTs = 0UL;
}
// spice ldj
{
if (FAILED(device->GetDeviceCaps(&render_state->caps))) {
render_state->caps.NumSimultaneousRTs = 0UL;
}
IDirect3DSurface9 *back_buffer = nullptr;
IDirect3DSurface9 *depth_stencil = nullptr;
IDirect3DSurface9 *render_targets[8];
// save all previous render target state
for (size_t target = 0; target < std::min(8UL, render_state->caps.NumSimultaneousRTs); target++) {
if (FAILED(device->GetRenderTarget(target, &render_state->render_targets[target]))) {
render_state->render_targets[target] = nullptr;
}
}
// save all previous render target state
for (size_t target = 0; target < std::min(8UL, caps.NumSimultaneousRTs); target++) {
if (FAILED(g_pd3dDevice->GetRenderTarget(target, &render_targets[target]))) {
render_targets[target] = nullptr;
// get the previous depth stencil
if (FAILED(device->GetDepthStencilSurface(&render_state->depth_stencil))) {
render_state->depth_stencil = nullptr;
}
// set the back buffer as the current render target
if (SUCCEEDED(device->GetBackBuffer(0, 0, D3DBACKBUFFER_TYPE_MONO, &render_state->back_buffer))) {
device->SetRenderTarget(0, render_state->back_buffer);
device->SetDepthStencilSurface(nullptr);
for (size_t target = 1; target < std::min(8UL, render_state->caps.NumSimultaneousRTs); target++) {
device->SetRenderTarget(target, nullptr);
}
} else {
render_state->back_buffer = nullptr;
}
}
// get the previous depth stencil
if (FAILED(g_pd3dDevice->GetDepthStencilSurface(&depth_stencil))) {
depth_stencil = nullptr;
}
// set the back buffer as the current render target
if (SUCCEEDED(g_pd3dDevice->GetBackBuffer(0, 0, D3DBACKBUFFER_TYPE_MONO, &back_buffer))) {
g_pd3dDevice->SetRenderTarget(0, back_buffer);
g_pd3dDevice->SetDepthStencilSurface(nullptr);
for (size_t target = 1; target < std::min(8UL, caps.NumSimultaneousRTs); target++) {
g_pd3dDevice->SetRenderTarget(target, nullptr);
}
} else {
back_buffer = nullptr;
}
// Setup render state: fixed-pipeline, alpha-blending, no face culling, no depth testing, shade mode (for gradient)
g_pd3dDevice->SetRenderState(D3DRS_CULLMODE, D3DCULL_NONE);
g_pd3dDevice->SetRenderState(D3DRS_LIGHTING, false);
g_pd3dDevice->SetRenderState(D3DRS_ZENABLE, false);
g_pd3dDevice->SetRenderState(D3DRS_ALPHABLENDENABLE, true);
g_pd3dDevice->SetRenderState(D3DRS_ALPHATESTENABLE, false);
g_pd3dDevice->SetRenderState(D3DRS_BLENDOP, D3DBLENDOP_ADD);
g_pd3dDevice->SetRenderState(D3DRS_SRCBLEND, D3DBLEND_SRCALPHA);
g_pd3dDevice->SetRenderState(D3DRS_DESTBLEND, D3DBLEND_INVSRCALPHA);
g_pd3dDevice->SetRenderState(D3DRS_SCISSORTESTENABLE, true);
g_pd3dDevice->SetRenderState(D3DRS_SHADEMODE, D3DSHADE_GOURAUD);
g_pd3dDevice->SetRenderState(D3DRS_FOGENABLE, false);
g_pd3dDevice->SetTextureStageState(0, D3DTSS_COLOROP, D3DTOP_MODULATE);
g_pd3dDevice->SetTextureStageState(0, D3DTSS_COLORARG1, D3DTA_TEXTURE);
g_pd3dDevice->SetTextureStageState(0, D3DTSS_COLORARG2, D3DTA_DIFFUSE);
g_pd3dDevice->SetTextureStageState(0, D3DTSS_ALPHAOP, D3DTOP_MODULATE);
g_pd3dDevice->SetTextureStageState(0, D3DTSS_ALPHAARG1, D3DTA_TEXTURE);
g_pd3dDevice->SetTextureStageState(0, D3DTSS_ALPHAARG2, D3DTA_DIFFUSE);
g_pd3dDevice->SetSamplerState(0, D3DSAMP_MINFILTER, D3DTEXF_LINEAR);
g_pd3dDevice->SetSamplerState(0, D3DSAMP_MAGFILTER, D3DTEXF_LINEAR);
// Setup render state: fixed-pipeline, alpha-blending, no face culling, no depth testing, shade mode (for gradient), bilinear sampling.
device->SetPixelShader(nullptr);
device->SetVertexShader(nullptr);
device->SetRenderState(D3DRS_FILLMODE, D3DFILL_SOLID);
device->SetRenderState(D3DRS_SHADEMODE, D3DSHADE_GOURAUD);
device->SetRenderState(D3DRS_ZWRITEENABLE, FALSE);
device->SetRenderState(D3DRS_ALPHATESTENABLE, FALSE);
device->SetRenderState(D3DRS_CULLMODE, D3DCULL_NONE);
device->SetRenderState(D3DRS_ZENABLE, FALSE);
device->SetRenderState(D3DRS_ALPHABLENDENABLE, TRUE);
device->SetRenderState(D3DRS_BLENDOP, D3DBLENDOP_ADD);
device->SetRenderState(D3DRS_SRCBLEND, D3DBLEND_SRCALPHA);
device->SetRenderState(D3DRS_DESTBLEND, D3DBLEND_INVSRCALPHA);
device->SetRenderState(D3DRS_SEPARATEALPHABLENDENABLE, TRUE);
device->SetRenderState(D3DRS_SRCBLENDALPHA, D3DBLEND_ONE);
device->SetRenderState(D3DRS_DESTBLENDALPHA, D3DBLEND_INVSRCALPHA);
device->SetRenderState(D3DRS_SCISSORTESTENABLE, TRUE);
device->SetRenderState(D3DRS_FOGENABLE, FALSE);
device->SetRenderState(D3DRS_RANGEFOGENABLE, FALSE);
device->SetRenderState(D3DRS_SPECULARENABLE, FALSE);
device->SetRenderState(D3DRS_STENCILENABLE, FALSE);
device->SetRenderState(D3DRS_CLIPPING, TRUE);
device->SetRenderState(D3DRS_LIGHTING, FALSE);
device->SetTextureStageState(0, D3DTSS_COLOROP, D3DTOP_MODULATE);
device->SetTextureStageState(0, D3DTSS_COLORARG1, D3DTA_TEXTURE);
device->SetTextureStageState(0, D3DTSS_COLORARG2, D3DTA_DIFFUSE);
device->SetTextureStageState(0, D3DTSS_ALPHAOP, D3DTOP_MODULATE);
device->SetTextureStageState(0, D3DTSS_ALPHAARG1, D3DTA_TEXTURE);
device->SetTextureStageState(0, D3DTSS_ALPHAARG2, D3DTA_DIFFUSE);
device->SetTextureStageState(1, D3DTSS_COLOROP, D3DTOP_DISABLE);
device->SetTextureStageState(1, D3DTSS_ALPHAOP, D3DTOP_DISABLE);
device->SetSamplerState(0, D3DSAMP_MINFILTER, D3DTEXF_LINEAR);
device->SetSamplerState(0, D3DSAMP_MAGFILTER, D3DTEXF_LINEAR);
device->SetSamplerState(0, D3DSAMP_ADDRESSU, D3DTADDRESS_CLAMP);
device->SetSamplerState(0, D3DSAMP_ADDRESSV, D3DTADDRESS_CLAMP);
// Setup orthographic projection matrix
// Our visible imgui space lies from draw_data->DisplayPos (top left) to draw_data->DisplayPos+data_data->DisplaySize (bottom right). DisplayPos is (0,0) for single viewport apps.
@@ -185,176 +200,537 @@ void ImGui_ImplDX9_RenderDrawData(ImDrawData *draw_data) {
float R = draw_data->DisplayPos.x + draw_data->DisplaySize.x + 0.5f;
float T = draw_data->DisplayPos.y + 0.5f;
float B = draw_data->DisplayPos.y + draw_data->DisplaySize.y + 0.5f;
D3DMATRIX mat_identity = {{{1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f}}};
D3DMATRIX mat_identity = { { { 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f } } };
D3DMATRIX mat_projection =
{{{
2.0f / (R - L), 0.0f, 0.0f, 0.0f,
0.0f, 2.0f / (T - B), 0.0f, 0.0f,
0.0f, 0.0f, 0.5f, 0.0f,
(L + R) / (L - R), (T + B) / (B - T), 0.5f, 1.0f
}}};
g_pd3dDevice->SetTransform(D3DTS_WORLD, &mat_identity);
g_pd3dDevice->SetTransform(D3DTS_VIEW, &mat_identity);
g_pd3dDevice->SetTransform(D3DTS_PROJECTION, &mat_projection);
{ { {
2.0f/(R-L), 0.0f, 0.0f, 0.0f,
0.0f, 2.0f/(T-B), 0.0f, 0.0f,
0.0f, 0.0f, 0.5f, 0.0f,
(L+R)/(L-R), (T+B)/(B-T), 0.5f, 1.0f
} } };
device->SetTransform(D3DTS_WORLD, &mat_identity);
device->SetTransform(D3DTS_VIEW, &mat_identity);
device->SetTransform(D3DTS_PROJECTION, &mat_projection);
}
}
// Render function.
void ImGui_ImplDX9_RenderDrawData(ImDrawData* draw_data)
{
// Avoid rendering when minimized
if (draw_data->DisplaySize.x <= 0.0f || draw_data->DisplaySize.y <= 0.0f)
return;
ImGui_ImplDX9_Data* bd = ImGui_ImplDX9_GetBackendData();
LPDIRECT3DDEVICE9 device = bd->pd3dDevice;
// Catch up with texture updates. Most of the times, the list will have 1 element with an OK status, aka nothing to do.
// (This almost always points to ImGui::GetPlatformIO().Textures[] but is part of ImDrawData to allow overriding or disabling texture updates).
if (draw_data->Textures != nullptr)
for (ImTextureData* tex : *draw_data->Textures)
if (tex->Status != ImTextureStatus_OK)
ImGui_ImplDX9_UpdateTexture(tex);
// Create and grow buffers if needed
if (!bd->pVB || bd->VertexBufferSize < draw_data->TotalVtxCount)
{
if (bd->pVB) { bd->pVB->Release(); bd->pVB = nullptr; }
bd->VertexBufferSize = draw_data->TotalVtxCount + 5000;
if (device->CreateVertexBuffer(bd->VertexBufferSize * sizeof(CUSTOMVERTEX), D3DUSAGE_DYNAMIC | D3DUSAGE_WRITEONLY, D3DFVF_CUSTOMVERTEX, D3DPOOL_DEFAULT, &bd->pVB, nullptr) < 0)
return;
}
if (!bd->pIB || bd->IndexBufferSize < draw_data->TotalIdxCount)
{
if (bd->pIB) { bd->pIB->Release(); bd->pIB = nullptr; }
bd->IndexBufferSize = draw_data->TotalIdxCount + 10000;
if (device->CreateIndexBuffer(bd->IndexBufferSize * sizeof(ImDrawIdx), D3DUSAGE_DYNAMIC | D3DUSAGE_WRITEONLY, sizeof(ImDrawIdx) == 2 ? D3DFMT_INDEX16 : D3DFMT_INDEX32, D3DPOOL_DEFAULT, &bd->pIB, nullptr) < 0)
return;
}
// Backup the DX9 state
IDirect3DStateBlock9* state_block = nullptr;
if (device->CreateStateBlock(D3DSBT_ALL, &state_block) < 0)
return;
if (state_block->Capture() < 0)
{
state_block->Release();
return;
}
// Backup the DX9 transform (DX9 documentation suggests that it is included in the StateBlock but it doesn't appear to)
D3DMATRIX last_world, last_view, last_projection;
device->GetTransform(D3DTS_WORLD, &last_world);
device->GetTransform(D3DTS_VIEW, &last_view);
device->GetTransform(D3DTS_PROJECTION, &last_projection);
// Allocate buffers
CUSTOMVERTEX* vtx_dst;
ImDrawIdx* idx_dst;
if (bd->pVB->Lock(0, (UINT)(draw_data->TotalVtxCount * sizeof(CUSTOMVERTEX)), (void**)&vtx_dst, D3DLOCK_DISCARD) < 0)
{
state_block->Release();
return;
}
if (bd->pIB->Lock(0, (UINT)(draw_data->TotalIdxCount * sizeof(ImDrawIdx)), (void**)&idx_dst, D3DLOCK_DISCARD) < 0)
{
bd->pVB->Unlock();
state_block->Release();
return;
}
// Copy and convert all vertices into a single contiguous buffer, convert colors to DX9 default format.
// FIXME-OPT: This is a minor waste of resource, the ideal is to use imconfig.h and
// 1) to avoid repacking colors: #define IMGUI_USE_BGRA_PACKED_COLOR
// 2) to avoid repacking vertices: #define IMGUI_OVERRIDE_DRAWVERT_STRUCT_LAYOUT struct ImDrawVert { ImVec2 pos; float z; ImU32 col; ImVec2 uv; }
for (const ImDrawList* draw_list : draw_data->CmdLists)
{
const ImDrawVert* vtx_src = draw_list->VtxBuffer.Data;
for (int i = 0; i < draw_list->VtxBuffer.Size; i++)
{
vtx_dst->pos[0] = vtx_src->pos.x;
vtx_dst->pos[1] = vtx_src->pos.y;
vtx_dst->pos[2] = 0.0f;
vtx_dst->col = IMGUI_COL_TO_DX9_ARGB(vtx_src->col);
vtx_dst->uv[0] = vtx_src->uv.x;
vtx_dst->uv[1] = vtx_src->uv.y;
vtx_dst++;
vtx_src++;
}
memcpy(idx_dst, draw_list->IdxBuffer.Data, draw_list->IdxBuffer.Size * sizeof(ImDrawIdx));
idx_dst += draw_list->IdxBuffer.Size;
}
bd->pVB->Unlock();
bd->pIB->Unlock();
device->SetStreamSource(0, bd->pVB, 0, sizeof(CUSTOMVERTEX));
device->SetIndices(bd->pIB);
device->SetFVF(D3DFVF_CUSTOMVERTEX);
// Setup desired DX state
struct render_state_t render_state = {};
ImGui_ImplDX9_SetupRenderState(draw_data, &render_state);
// Render command lists
// (Because we merged all buffers into a single one, we maintain our own offset into them)
int global_vtx_offset = 0;
int global_idx_offset = 0;
ImVec2 clip_off = draw_data->DisplayPos;
for (int n = 0; n < draw_data->CmdListsCount; n++) {
const ImDrawList *cmd_list = draw_data->CmdLists[n];
for (int cmd_i = 0; cmd_i < cmd_list->CmdBuffer.Size; cmd_i++) {
const ImDrawCmd *pcmd = &cmd_list->CmdBuffer[cmd_i];
if (pcmd->UserCallback != NULL) {
pcmd->UserCallback(cmd_list, pcmd);
} else {
const RECT r = {(LONG) (pcmd->ClipRect.x - clip_off.x), (LONG) (pcmd->ClipRect.y - clip_off.y),
(LONG) (pcmd->ClipRect.z - clip_off.x), (LONG) (pcmd->ClipRect.w - clip_off.y)};
auto texture = reinterpret_cast<IDirect3DBaseTexture9 *>(pcmd->TextureId);
g_pd3dDevice->SetTexture(0, texture);
g_pd3dDevice->SetScissorRect(&r);
g_pd3dDevice->DrawIndexedPrimitive(D3DPT_TRIANGLELIST,
pcmd->VtxOffset + global_vtx_offset, 0,
(UINT) cmd_list->VtxBuffer.Size,
pcmd->IdxOffset + global_idx_offset, pcmd->ElemCount / 3);
for (const ImDrawList* draw_list : draw_data->CmdLists)
{
for (int cmd_i = 0; cmd_i < draw_list->CmdBuffer.Size; cmd_i++)
{
const ImDrawCmd* pcmd = &draw_list->CmdBuffer[cmd_i];
if (pcmd->UserCallback != nullptr)
{
// User callback, registered via ImDrawList::AddCallback()
// (ImDrawCallback_ResetRenderState is a special callback value used by the user to request the renderer to reset render state.)
if (pcmd->UserCallback == ImDrawCallback_ResetRenderState)
ImGui_ImplDX9_SetupRenderState(draw_data, &render_state);
else
pcmd->UserCallback(draw_list, pcmd);
}
else
{
// Project scissor/clipping rectangles into framebuffer space
ImVec2 clip_min(pcmd->ClipRect.x - clip_off.x, pcmd->ClipRect.y - clip_off.y);
ImVec2 clip_max(pcmd->ClipRect.z - clip_off.x, pcmd->ClipRect.w - clip_off.y);
if (clip_max.x <= clip_min.x || clip_max.y <= clip_min.y)
continue;
// Apply scissor/clipping rectangle
const RECT r = { (LONG)clip_min.x, (LONG)clip_min.y, (LONG)clip_max.x, (LONG)clip_max.y };
device->SetScissorRect(&r);
// Bind texture, Draw
const LPDIRECT3DTEXTURE9 texture = (LPDIRECT3DTEXTURE9)pcmd->GetTexID();
device->SetTexture(0, texture);
device->DrawIndexedPrimitive(D3DPT_TRIANGLELIST, pcmd->VtxOffset + global_vtx_offset, 0, (UINT)draw_list->VtxBuffer.Size, pcmd->IdxOffset + global_idx_offset, pcmd->ElemCount / 3);
}
}
global_idx_offset += cmd_list->IdxBuffer.Size;
global_vtx_offset += cmd_list->VtxBuffer.Size;
global_idx_offset += draw_list->IdxBuffer.Size;
global_vtx_offset += draw_list->VtxBuffer.Size;
}
if (back_buffer) {
back_buffer->Release();
back_buffer = nullptr;
}
// When using multi-viewports, it appears that there's an odd logic in DirectX9 which prevent subsequent windows
// from rendering until the first window submits at least one draw call, even once. That's our workaround. (see #2560)
if (global_vtx_offset == 0)
bd->pd3dDevice->DrawIndexedPrimitive(D3DPT_TRIANGLELIST, 0, 0, 0, 0, 0);
// restore previous depth stencil
if (depth_stencil) {
g_pd3dDevice->SetDepthStencilSurface(depth_stencil);
depth_stencil->Release();
depth_stencil = nullptr;
}
// spice ldj
{
if (render_state.back_buffer) {
render_state.back_buffer->Release();
render_state.back_buffer = nullptr;
}
// restore previous depth stencil
if (render_state.depth_stencil) {
device->SetDepthStencilSurface(render_state.depth_stencil);
render_state.depth_stencil->Release();
render_state.depth_stencil = nullptr;
}
// restore all render target state
for (size_t target = 0; target < std::min(8UL, render_state.caps.NumSimultaneousRTs); target++) {
auto render_target = render_state.render_targets[target];
// restore all render target state
for (size_t target = 0; target < std::min(8UL, caps.NumSimultaneousRTs); target++) {
auto render_target = render_targets[target];
if (render_target) {
g_pd3dDevice->SetRenderTarget(target, render_target);
render_target->Release();
if (render_target) {
device->SetRenderTarget(target, render_target);
render_target->Release();
}
}
}
// restore the DX9 transform
g_pd3dDevice->SetTransform(D3DTS_WORLD, &last_world);
g_pd3dDevice->SetTransform(D3DTS_VIEW, &last_view);
g_pd3dDevice->SetTransform(D3DTS_PROJECTION, &last_projection);
// Restore the DX9 transform
device->SetTransform(D3DTS_WORLD, &last_world);
device->SetTransform(D3DTS_VIEW, &last_view);
device->SetTransform(D3DTS_PROJECTION, &last_projection);
// restore the DX9 state
d3d9_state_block->Apply();
d3d9_state_block->Release();
// Restore the DX9 state
state_block->Apply();
state_block->Release();
}
bool ImGui_ImplDX9_Init(IDirect3DDevice9 *device) {
static bool ImGui_ImplDX9_CheckFormatSupport(LPDIRECT3DDEVICE9 pDevice, D3DFORMAT format)
{
LPDIRECT3D9 pd3d = nullptr;
if (pDevice->GetDirect3D(&pd3d) != D3D_OK)
return false;
D3DDEVICE_CREATION_PARAMETERS param = {};
D3DDISPLAYMODE mode = {};
if (pDevice->GetCreationParameters(&param) != D3D_OK || pDevice->GetDisplayMode(0, &mode) != D3D_OK)
{
pd3d->Release();
return false;
}
// Font texture should support linear filter, color blend and write to render-target
bool support = (pd3d->CheckDeviceFormat(param.AdapterOrdinal, param.DeviceType, mode.Format, D3DUSAGE_DYNAMIC | D3DUSAGE_QUERY_FILTER | D3DUSAGE_QUERY_POSTPIXELSHADER_BLENDING, D3DRTYPE_TEXTURE, format)) == D3D_OK;
pd3d->Release();
return support;
}
// Setup back-end capabilities flags
auto &io = ImGui::GetIO();
bool ImGui_ImplDX9_Init(IDirect3DDevice9* device)
{
ImGuiIO& io = ImGui::GetIO();
IMGUI_CHECKVERSION();
IM_ASSERT(io.BackendRendererUserData == nullptr && "Already initialized a renderer backend!");
// Setup backend capabilities flags
ImGui_ImplDX9_Data* bd = IM_NEW(ImGui_ImplDX9_Data)();
io.BackendRendererUserData = (void*)bd;
io.BackendRendererName = "imgui_impl_dx9";
io.BackendFlags |= ImGuiBackendFlags_RendererHasVtxOffset; // We can honor the ImDrawCmd::VtxOffset field, allowing for large meshes.
io.BackendFlags |= ImGuiBackendFlags_RendererHasTextures; // We can honor ImGuiPlatformIO::Textures[] requests during render.
io.BackendFlags |= ImGuiBackendFlags_RendererHasViewports; // We can create multi-viewports on the Renderer side (optional)
// We can honor the ImDrawCmd::VtxOffset field, allowing for large meshes.
io.BackendFlags |= ImGuiBackendFlags_RendererHasVtxOffset;
ImGuiPlatformIO& platform_io = ImGui::GetPlatformIO();
platform_io.Renderer_TextureMaxWidth = platform_io.Renderer_TextureMaxHeight = 4096;
bd->pd3dDevice = device;
bd->pd3dDevice->AddRef();
bd->HasRgbaSupport = ImGui_ImplDX9_CheckFormatSupport(bd->pd3dDevice, D3DFMT_A8B8G8R8);
ImGui_ImplDX9_InitMultiViewportSupport();
g_pd3dDevice = device;
g_pd3dDevice->AddRef();
return true;
}
void ImGui_ImplDX9_Shutdown() {
void ImGui_ImplDX9_Shutdown()
{
ImGui_ImplDX9_Data* bd = ImGui_ImplDX9_GetBackendData();
IM_ASSERT(bd != nullptr && "No renderer backend to shutdown, or already shutdown?");
ImGuiIO& io = ImGui::GetIO();
ImGuiPlatformIO& platform_io = ImGui::GetPlatformIO();
ImGui_ImplDX9_ShutdownMultiViewportSupport();
ImGui_ImplDX9_InvalidateDeviceObjects();
if (bd->pd3dDevice) { bd->pd3dDevice->Release(); }
if (g_pd3dDevice) {
g_pd3dDevice->Release();
g_pd3dDevice = NULL;
io.BackendRendererName = nullptr;
io.BackendRendererUserData = nullptr;
io.BackendFlags &= ~(ImGuiBackendFlags_RendererHasVtxOffset | ImGuiBackendFlags_RendererHasTextures | ImGuiBackendFlags_RendererHasViewports);
platform_io.ClearRendererHandlers();
IM_DELETE(bd);
}
// Convert RGBA32 to BGRA32 (because RGBA32 is not well supported by DX9 devices)
static void ImGui_ImplDX9_CopyTextureRegion(bool tex_use_colors, const ImU32* src, int src_pitch, ImU32* dst, int dst_pitch, int w, int h)
{
#ifndef IMGUI_USE_BGRA_PACKED_COLOR
ImGui_ImplDX9_Data* bd = ImGui_ImplDX9_GetBackendData();
const bool convert_rgba_to_bgra = (!bd->HasRgbaSupport && tex_use_colors);
#else
const bool convert_rgba_to_bgra = false;
IM_UNUSED(tex_use_colors);
#endif
for (int y = 0; y < h; y++)
{
const ImU32* src_p = (const ImU32*)(const void*)((const unsigned char*)src + src_pitch * y);
ImU32* dst_p = (ImU32*)(void*)((unsigned char*)dst + dst_pitch * y);
if (convert_rgba_to_bgra)
for (int x = w; x > 0; x--, src_p++, dst_p++) // Convert copy
*dst_p = IMGUI_COL_TO_DX9_ARGB(*src_p);
else
memcpy(dst_p, src_p, w * 4); // Raw copy
}
}
static bool ImGui_ImplDX9_CreateFontsTexture() {
// Build texture atlas
ImGuiIO &io = ImGui::GetIO();
unsigned char *pixels;
int width, height, bytes_per_pixel;
io.Fonts->GetTexDataAsRGBA32(&pixels, &width, &height, &bytes_per_pixel);
void ImGui_ImplDX9_UpdateTexture(ImTextureData* tex)
{
ImGui_ImplDX9_Data* bd = ImGui_ImplDX9_GetBackendData();
// Upload texture to graphics system
g_FontTexture = NULL;
if (g_pd3dDevice->CreateTexture(width, height, 1, D3DUSAGE_DYNAMIC, D3DFMT_A8R8G8B8,
D3DPOOL_DEFAULT, &g_FontTexture, NULL) < 0)
return false;
D3DLOCKED_RECT tex_locked_rect;
if (g_FontTexture->LockRect(0, &tex_locked_rect, NULL, 0) != D3D_OK)
return false;
for (int y = 0; y < height; y++)
memcpy((unsigned char *) tex_locked_rect.pBits + tex_locked_rect.Pitch * y,
pixels + (width * bytes_per_pixel) * y, (width * bytes_per_pixel));
g_FontTexture->UnlockRect(0);
// Store our identifier
io.Fonts->TexID = (ImTextureID) g_FontTexture;
return true;
}
bool ImGui_ImplDX9_CreateDeviceObjects() {
if (!g_pd3dDevice) {
return false;
}
return ImGui_ImplDX9_CreateFontsTexture();
}
void ImGui_ImplDX9_InvalidateDeviceObjects() {
if (!g_pd3dDevice)
return;
if (g_pVB) {
g_pVB->Release();
g_pVB = NULL;
}
if (g_pIB) {
g_pIB->Release();
g_pIB = NULL;
}
if (g_FontTexture) {
g_FontTexture->Release();
g_FontTexture = NULL;
ImGui::GetIO().Fonts->TexID = 0;
} // We copied g_pFontTextureView to io.Fonts->TexID so let's clear that as well.
}
void ImGui_ImplDX9_NewFrame() {
if (!g_FontTexture) {
ImGui_ImplDX9_CreateDeviceObjects();
}
IDirect3DSwapChain9 *swap_chain = nullptr;
if (SUCCEEDED(g_pd3dDevice->GetSwapChain(0, &swap_chain))) {
auto &io = ImGui::GetIO();
D3DPRESENT_PARAMETERS present_params {};
if (SUCCEEDED(swap_chain->GetPresentParameters(&present_params))) {
if (present_params.BackBufferWidth != 0 && present_params.BackBufferHeight != 0) {
io.DisplaySize.x = static_cast<float>(present_params.BackBufferWidth);
io.DisplaySize.y = static_cast<float>(present_params.BackBufferHeight);
} else {
RECT rect {};
GetClientRect(present_params.hDeviceWindow, &rect);
io.DisplaySize.x = static_cast<float>(rect.right - rect.left);
io.DisplaySize.y = static_cast<float>(rect.bottom - rect.top);
}
if (tex->Status == ImTextureStatus_WantCreate)
{
// Create and upload new texture to graphics system
//IMGUI_DEBUG_LOG("UpdateTexture #%03d: WantCreate %dx%d\n", tex->UniqueID, tex->Width, tex->Height);
IM_ASSERT(tex->TexID == ImTextureID_Invalid && tex->BackendUserData == nullptr);
IM_ASSERT(tex->Format == ImTextureFormat_RGBA32);
LPDIRECT3DTEXTURE9 dx_tex = nullptr;
HRESULT hr = bd->pd3dDevice->CreateTexture(tex->Width, tex->Height, 1, D3DUSAGE_DYNAMIC, bd->HasRgbaSupport ? D3DFMT_A8B8G8R8 : D3DFMT_A8R8G8B8, D3DPOOL_DEFAULT, &dx_tex, nullptr);
if (hr < 0)
{
IM_ASSERT(hr >= 0 && "Backend failed to create texture!");
return;
}
swap_chain->Release();
D3DLOCKED_RECT locked_rect;
if (dx_tex->LockRect(0, &locked_rect, nullptr, 0) == D3D_OK)
{
ImGui_ImplDX9_CopyTextureRegion(tex->UseColors, (ImU32*)tex->GetPixels(), tex->Width * 4, (ImU32*)locked_rect.pBits, (ImU32)locked_rect.Pitch, tex->Width, tex->Height);
dx_tex->UnlockRect(0);
}
// Store identifiers
tex->SetTexID((ImTextureID)(intptr_t)dx_tex);
tex->SetStatus(ImTextureStatus_OK);
}
else if (tex->Status == ImTextureStatus_WantUpdates)
{
// Update selected blocks. We only ever write to textures regions which have never been used before!
// This backend choose to use tex->Updates[] but you can use tex->UpdateRect to upload a single region.
LPDIRECT3DTEXTURE9 backend_tex = (LPDIRECT3DTEXTURE9)(intptr_t)tex->TexID;
RECT update_rect = { (LONG)tex->UpdateRect.x, (LONG)tex->UpdateRect.y, (LONG)(tex->UpdateRect.x + tex->UpdateRect.w), (LONG)(tex->UpdateRect.y + tex->UpdateRect.h) };
D3DLOCKED_RECT locked_rect;
if (backend_tex->LockRect(0, &locked_rect, &update_rect, 0) == D3D_OK)
for (ImTextureRect& r : tex->Updates)
ImGui_ImplDX9_CopyTextureRegion(tex->UseColors, (ImU32*)tex->GetPixelsAt(r.x, r.y), tex->Width * 4,
(ImU32*)locked_rect.pBits + (r.x - update_rect.left) + (r.y - update_rect.top) * (locked_rect.Pitch / 4), (int)locked_rect.Pitch, r.w, r.h);
backend_tex->UnlockRect(0);
tex->SetStatus(ImTextureStatus_OK);
}
else if (tex->Status == ImTextureStatus_WantDestroy)
{
if (tex->TexID != ImTextureID_Invalid)
if (LPDIRECT3DTEXTURE9 backend_tex = (LPDIRECT3DTEXTURE9)tex->TexID)
{
IM_ASSERT(tex->TexID == (ImTextureID)(intptr_t)backend_tex);
backend_tex->Release();
// Clear identifiers and mark as destroyed (in order to allow e.g. calling InvalidateDeviceObjects while running)
tex->SetTexID(ImTextureID_Invalid);
}
tex->SetStatus(ImTextureStatus_Destroyed);
}
}
bool ImGui_ImplDX9_CreateDeviceObjects()
{
ImGui_ImplDX9_Data* bd = ImGui_ImplDX9_GetBackendData();
if (!bd || !bd->pd3dDevice)
return false;
ImGui_ImplDX9_CreateDeviceObjectsForPlatformWindows();
return true;
}
void ImGui_ImplDX9_InvalidateDeviceObjects()
{
ImGui_ImplDX9_Data* bd = ImGui_ImplDX9_GetBackendData();
if (!bd || !bd->pd3dDevice)
return;
// Destroy all textures
for (ImTextureData* tex : ImGui::GetPlatformIO().Textures)
if (tex->RefCount == 1)
{
tex->SetStatus(ImTextureStatus_WantDestroy);
ImGui_ImplDX9_UpdateTexture(tex);
}
if (bd->pVB) { bd->pVB->Release(); bd->pVB = nullptr; }
if (bd->pIB) { bd->pIB->Release(); bd->pIB = nullptr; }
ImGui_ImplDX9_InvalidateDeviceObjectsForPlatformWindows();
}
void ImGui_ImplDX9_NewFrame()
{
ImGui_ImplDX9_Data* bd = ImGui_ImplDX9_GetBackendData();
IM_ASSERT(bd != nullptr && "Context or backend not initialized! Did you call ImGui_ImplDX9_Init()?");
// IM_UNUSED(bd);
// spice ldj
{
IDirect3DSwapChain9 *swap_chain = nullptr;
if (SUCCEEDED(bd->pd3dDevice->GetSwapChain(0, &swap_chain))) {
auto &io = ImGui::GetIO();
D3DPRESENT_PARAMETERS present_params {};
if (SUCCEEDED(swap_chain->GetPresentParameters(&present_params))) {
if (present_params.BackBufferWidth != 0 && present_params.BackBufferHeight != 0) {
io.DisplaySize.x = static_cast<float>(present_params.BackBufferWidth);
io.DisplaySize.y = static_cast<float>(present_params.BackBufferHeight);
} else {
RECT rect {};
GetClientRect(present_params.hDeviceWindow, &rect);
io.DisplaySize.x = static_cast<float>(rect.right - rect.left);
io.DisplaySize.y = static_cast<float>(rect.bottom - rect.top);
}
}
swap_chain->Release();
}
}
}
//--------------------------------------------------------------------------------------------------------
// MULTI-VIEWPORT / PLATFORM INTERFACE SUPPORT
// This is an _advanced_ and _optional_ feature, allowing the backend to create and handle multiple viewports simultaneously.
// If you are new to dear imgui or creating a new binding for dear imgui, it is recommended that you completely ignore this section first..
//--------------------------------------------------------------------------------------------------------
// Helper structure we store in the void* RendererUserData field of each ImGuiViewport to easily retrieve our backend data.
struct ImGui_ImplDX9_ViewportData
{
IDirect3DSwapChain9* SwapChain;
D3DPRESENT_PARAMETERS d3dpp;
ImGui_ImplDX9_ViewportData() { SwapChain = nullptr; ZeroMemory(&d3dpp, sizeof(D3DPRESENT_PARAMETERS)); }
~ImGui_ImplDX9_ViewportData() { IM_ASSERT(SwapChain == nullptr); }
};
static void ImGui_ImplDX9_CreateWindow(ImGuiViewport* viewport)
{
ImGui_ImplDX9_Data* bd = ImGui_ImplDX9_GetBackendData();
ImGui_ImplDX9_ViewportData* vd = IM_NEW(ImGui_ImplDX9_ViewportData)();
viewport->RendererUserData = vd;
// PlatformHandleRaw should always be a HWND, whereas PlatformHandle might be a higher-level handle (e.g. GLFWWindow*, SDL's WindowID).
// Some backends will leave PlatformHandleRaw == 0, in which case we assume PlatformHandle will contain the HWND.
HWND hwnd = viewport->PlatformHandleRaw ? (HWND)viewport->PlatformHandleRaw : (HWND)viewport->PlatformHandle;
IM_ASSERT(hwnd != 0);
ZeroMemory(&vd->d3dpp, sizeof(D3DPRESENT_PARAMETERS));
vd->d3dpp.Windowed = TRUE;
vd->d3dpp.SwapEffect = D3DSWAPEFFECT_DISCARD;
vd->d3dpp.BackBufferWidth = (UINT)viewport->Size.x;
vd->d3dpp.BackBufferHeight = (UINT)viewport->Size.y;
vd->d3dpp.BackBufferFormat = D3DFMT_UNKNOWN;
vd->d3dpp.hDeviceWindow = hwnd;
vd->d3dpp.EnableAutoDepthStencil = FALSE;
vd->d3dpp.AutoDepthStencilFormat = D3DFMT_D16;
vd->d3dpp.PresentationInterval = D3DPRESENT_INTERVAL_IMMEDIATE; // Present without vsync
HRESULT hr = bd->pd3dDevice->CreateAdditionalSwapChain(&vd->d3dpp, &vd->SwapChain); IM_UNUSED(hr);
IM_ASSERT(hr == D3D_OK);
IM_ASSERT(vd->SwapChain != nullptr);
}
static void ImGui_ImplDX9_DestroyWindow(ImGuiViewport* viewport)
{
// The main viewport (owned by the application) will always have RendererUserData == 0 since we didn't create the data for it.
if (ImGui_ImplDX9_ViewportData* vd = (ImGui_ImplDX9_ViewportData*)viewport->RendererUserData)
{
if (vd->SwapChain)
vd->SwapChain->Release();
vd->SwapChain = nullptr;
ZeroMemory(&vd->d3dpp, sizeof(D3DPRESENT_PARAMETERS));
IM_DELETE(vd);
}
viewport->RendererUserData = nullptr;
}
static void ImGui_ImplDX9_SetWindowSize(ImGuiViewport* viewport, ImVec2 size)
{
ImGui_ImplDX9_Data* bd = ImGui_ImplDX9_GetBackendData();
ImGui_ImplDX9_ViewportData* vd = (ImGui_ImplDX9_ViewportData*)viewport->RendererUserData;
if (vd->SwapChain)
{
vd->SwapChain->Release();
vd->SwapChain = nullptr;
vd->d3dpp.BackBufferWidth = (UINT)size.x;
vd->d3dpp.BackBufferHeight = (UINT)size.y;
HRESULT hr = bd->pd3dDevice->CreateAdditionalSwapChain(&vd->d3dpp, &vd->SwapChain); IM_UNUSED(hr);
IM_ASSERT(hr == D3D_OK);
}
}
static void ImGui_ImplDX9_RenderWindow(ImGuiViewport* viewport, void*)
{
ImGui_ImplDX9_Data* bd = ImGui_ImplDX9_GetBackendData();
ImGui_ImplDX9_ViewportData* vd = (ImGui_ImplDX9_ViewportData*)viewport->RendererUserData;
ImVec4 clear_color = ImVec4(0.0f, 0.0f, 0.0f, 1.0f);
LPDIRECT3DSURFACE9 render_target = nullptr;
LPDIRECT3DSURFACE9 last_render_target = nullptr;
LPDIRECT3DSURFACE9 last_depth_stencil = nullptr;
vd->SwapChain->GetBackBuffer(0, D3DBACKBUFFER_TYPE_MONO, &render_target);
bd->pd3dDevice->GetRenderTarget(0, &last_render_target);
bd->pd3dDevice->GetDepthStencilSurface(&last_depth_stencil);
bd->pd3dDevice->SetRenderTarget(0, render_target);
bd->pd3dDevice->SetDepthStencilSurface(nullptr);
if (!(viewport->Flags & ImGuiViewportFlags_NoRendererClear))
{
D3DCOLOR clear_col_dx = D3DCOLOR_RGBA((int)(clear_color.x*255.0f), (int)(clear_color.y*255.0f), (int)(clear_color.z*255.0f), (int)(clear_color.w*255.0f));
bd->pd3dDevice->Clear(0, nullptr, D3DCLEAR_TARGET, clear_col_dx, 1.0f, 0);
}
ImGui_ImplDX9_RenderDrawData(viewport->DrawData);
// Restore render target
bd->pd3dDevice->SetRenderTarget(0, last_render_target);
bd->pd3dDevice->SetDepthStencilSurface(last_depth_stencil);
render_target->Release();
last_render_target->Release();
if (last_depth_stencil) last_depth_stencil->Release();
}
static void ImGui_ImplDX9_SwapBuffers(ImGuiViewport* viewport, void*)
{
ImGui_ImplDX9_ViewportData* vd = (ImGui_ImplDX9_ViewportData*)viewport->RendererUserData;
HRESULT hr = vd->SwapChain->Present(nullptr, nullptr, vd->d3dpp.hDeviceWindow, nullptr, 0);
// Let main application handle D3DERR_DEVICELOST by resetting the device.
IM_ASSERT(SUCCEEDED(hr) || hr == D3DERR_DEVICELOST);
}
static void ImGui_ImplDX9_InitMultiViewportSupport()
{
ImGuiPlatformIO& platform_io = ImGui::GetPlatformIO();
platform_io.Renderer_CreateWindow = ImGui_ImplDX9_CreateWindow;
platform_io.Renderer_DestroyWindow = ImGui_ImplDX9_DestroyWindow;
platform_io.Renderer_SetWindowSize = ImGui_ImplDX9_SetWindowSize;
platform_io.Renderer_RenderWindow = ImGui_ImplDX9_RenderWindow;
platform_io.Renderer_SwapBuffers = ImGui_ImplDX9_SwapBuffers;
}
static void ImGui_ImplDX9_ShutdownMultiViewportSupport()
{
ImGui::DestroyPlatformWindows();
}
static void ImGui_ImplDX9_CreateDeviceObjectsForPlatformWindows()
{
ImGuiPlatformIO& platform_io = ImGui::GetPlatformIO();
for (int i = 1; i < platform_io.Viewports.Size; i++)
if (!platform_io.Viewports[i]->RendererUserData)
ImGui_ImplDX9_CreateWindow(platform_io.Viewports[i]);
}
static void ImGui_ImplDX9_InvalidateDeviceObjectsForPlatformWindows()
{
ImGuiPlatformIO& platform_io = ImGui::GetPlatformIO();
for (int i = 1; i < platform_io.Viewports.Size; i++)
if (platform_io.Viewports[i]->RendererUserData)
ImGui_ImplDX9_DestroyWindow(platform_io.Viewports[i]);
}
//-----------------------------------------------------------------------------
#endif // #ifndef IMGUI_DISABLE
+25 -12
View File
@@ -1,25 +1,38 @@
// dear imgui: Renderer for DirectX9
// This needs to be used along with a Platform Binding (e.g. Win32)
// dear imgui: Renderer Backend for DirectX9
// This needs to be used along with a Platform Backend (e.g. Win32)
// Implemented features:
// [X] Renderer: User texture binding. Use 'LPDIRECT3DTEXTURE9' as ImTextureID. Read the FAQ about ImTextureID in imgui.cpp.
// [X] Renderer: Support for large meshes (64k+ vertices) with 16-bits indices.
// [X] Renderer: User texture binding. Use 'LPDIRECT3DTEXTURE9' as texture identifier. Read the FAQ about ImTextureID/ImTextureRef!
// [X] Renderer: Large meshes support (64k+ vertices) even with 16-bit indices (ImGuiBackendFlags_RendererHasVtxOffset).
// [X] Renderer: Texture updates support for dynamic font atlas (ImGuiBackendFlags_RendererHasTextures).
// [X] Renderer: IMGUI_USE_BGRA_PACKED_COLOR support, as this is the optimal color encoding for DirectX9.
// [X] Renderer: Multi-viewport support (multiple windows). Enable with 'io.ConfigFlags |= ImGuiConfigFlags_ViewportsEnable'.
// You can copy and use unmodified imgui_impl_* files in your project. See main.cpp for an example of using this.
// If you are new to dear imgui, read examples/README.txt and read the documentation at the top of imgui.cpp.
// https://github.com/ocornut/imgui
// You can use unmodified imgui_impl_* files in your project. See examples/ folder for examples of using this.
// Prefer including the entire imgui/ repository into your project (either as a copy or as a submodule), and only build the backends you need.
// Learn about Dear ImGui:
// - FAQ https://dearimgui.com/faq
// - Getting Started https://dearimgui.com/getting-started
// - Documentation https://dearimgui.com/docs (same as your local docs/ folder).
// - Introduction, links and more at the top of imgui.cpp
#pragma once
#include "imgui.h"
#include "imgui.h" // IMGUI_IMPL_API
#ifndef IMGUI_DISABLE
struct IDirect3DDevice9;
IMGUI_IMPL_API bool ImGui_ImplDX9_Init(IDirect3DDevice9 *device);
// Follow "Getting Started" link and check examples/ folder to learn about using backends!
IMGUI_IMPL_API bool ImGui_ImplDX9_Init(IDirect3DDevice9* device);
IMGUI_IMPL_API void ImGui_ImplDX9_Shutdown();
IMGUI_IMPL_API void ImGui_ImplDX9_NewFrame();
IMGUI_IMPL_API void ImGui_ImplDX9_RenderDrawData(ImDrawData *draw_data);
IMGUI_IMPL_API void ImGui_ImplDX9_RenderDrawData(ImDrawData* draw_data);
// Use if you want to reset your rendering device without losing ImGui state.
// Use if you want to reset your rendering device without losing Dear ImGui state.
IMGUI_IMPL_API bool ImGui_ImplDX9_CreateDeviceObjects();
IMGUI_IMPL_API void ImGui_ImplDX9_InvalidateDeviceObjects();
// (Advanced) Use e.g. if you need to precisely control the timing of texture updates (e.g. for staged rendering), by setting ImDrawData::Textures = nullptr to handle this manually.
IMGUI_IMPL_API void ImGui_ImplDX9_UpdateTexture(ImTextureData* tex);
#endif // #ifndef IMGUI_DISABLE
+13 -5
View File
@@ -15,7 +15,8 @@
#pragma once
//---- Define assertion handler. Defaults to calling assert().
// If your macro uses multiple statements, make sure is enclosed in a 'do { .. } while (0)' block so it can be used as a single statement.
// - If your macro uses multiple statements, make sure is enclosed in a 'do { .. } while (0)' block so it can be used as a single statement.
// - Compiling with NDEBUG will usually strip out assert() to nothing, which is NOT recommended because we use asserts to notify of programmer mistakes.
//#define IM_ASSERT(_EXPR) MyAssert(_EXPR)
#define IM_ASSERT(_EXPR) ((void)(_EXPR)) // Disable asserts
@@ -29,7 +30,6 @@
//---- Don't define obsolete functions/enums/behaviors. Consider enabling from time to time after updating to clean your code of obsolete function/names.
//#define IMGUI_DISABLE_OBSOLETE_FUNCTIONS
//#define IMGUI_DISABLE_OBSOLETE_KEYIO // 1.87+ disable legacy io.KeyMap[]+io.KeysDown[] in favor io.AddKeyEvent(). This is automatically done by IMGUI_DISABLE_OBSOLETE_FUNCTIONS.
//---- Disable all of Dear ImGui or don't implement standard windows/tools.
// It is very strongly recommended to NOT disable the demo windows and debug tool during development. They are extremely useful in day to day work. Please read comments in imgui_demo.cpp.
@@ -49,6 +49,7 @@
//#define IMGUI_DISABLE_FILE_FUNCTIONS // Don't implement ImFileOpen/ImFileClose/ImFileRead/ImFileWrite and ImFileHandle at all (replace them with dummies)
//#define IMGUI_DISABLE_DEFAULT_FILE_FUNCTIONS // Don't implement ImFileOpen/ImFileClose/ImFileRead/ImFileWrite and ImFileHandle so you can implement them yourself if you don't want to link with fopen/fclose/fread/fwrite. This will also disable the LogToTTY() function.
#define IMGUI_DISABLE_DEFAULT_ALLOCATORS // Don't implement default allocators calling malloc()/free() to avoid linking with them. You will need to call ImGui::SetAllocatorFunctions().
//#define IMGUI_DISABLE_DEFAULT_FONT // Disable default embedded fonts (ProggyClean/ProggyForever), remove ~9 KB + ~14 KB from output binary. AddFontDefaultXXX() functions will assert.
//#define IMGUI_DISABLE_SSE // Disable use of SSE intrinsics even if available
//---- Enable Test Engine / Automation features.
@@ -59,12 +60,15 @@
//#define IMGUI_INCLUDE_IMGUI_USER_H
//#define IMGUI_USER_H_FILENAME "my_folder/my_imgui_user.h"
//---- Pack colors to BGRA8 instead of RGBA8 (to avoid converting from one to another)
//---- Pack vertex colors as BGRA8 instead of RGBA8 (to avoid converting from one to another). Need dedicated backend support.
#define IMGUI_USE_BGRA_PACKED_COLOR
//---- Performance optimization for DX9
#define IMGUI_OVERRIDE_DRAWVERT_STRUCT_LAYOUT struct ImDrawVert { ImVec2 pos; float z; ImU32 col; ImVec2 uv; }
//---- Use legacy CRC32-adler tables (used before 1.91.6), in order to preserve old .ini data that you cannot afford to invalidate.
//#define IMGUI_USE_LEGACY_CRC32_ADLER
//---- Use 32-bit for ImWchar (default is 16-bit) to support Unicode planes 1-16. (e.g. point beyond 0xFFFF like emoticons, dingbats, symbols, shapes, ancient languages, etc...)
//#define IMGUI_USE_WCHAR32
@@ -83,13 +87,13 @@
//---- Use FreeType to build and rasterize the font atlas (instead of stb_truetype which is embedded by default in Dear ImGui)
// Requires FreeType headers to be available in the include path. Requires program to be compiled with 'misc/freetype/imgui_freetype.cpp' (in this repository) + the FreeType library (not provided).
// Note that imgui_freetype.cpp may be used _without_ this define, if you manually call ImFontAtlas::SetFontLoader(). The define is simply a convenience.
// On Windows you may use vcpkg with 'vcpkg install freetype --triplet=x64-windows' + 'vcpkg integrate install'.
//#define IMGUI_ENABLE_FREETYPE
//---- Use FreeType + plutosvg or lunasvg to render OpenType SVG fonts (SVGinOT)
// Only works in combination with IMGUI_ENABLE_FREETYPE.
// - lunasvg is currently easier to acquire/install, as e.g. it is part of vcpkg.
// - plutosvg will support more fonts and may load them faster. It currently requires to be built manually but it is fairly easy. See misc/freetype/README for instructions.
// - plutosvg is currently easier to install, as e.g. it is part of vcpkg. It will support more fonts and may load them faster. See misc/freetype/README for instructions.
// - Both require headers to be available in the include path + program to be linked with the library code (not provided).
// - (note: lunasvg implementation is based on Freetype's rsvg-port.c which is licensed under CeCILL-C Free Software License Agreement)
//#define IMGUI_ENABLE_FREETYPE_PLUTOSVG
@@ -130,6 +134,10 @@
//#define IM_DEBUG_BREAK IM_ASSERT(0)
//#define IM_DEBUG_BREAK __debugbreak()
//---- Debug Tools: Enable highlight ID conflicts _before_ hovering items. When io.ConfigDebugHighlightIdConflicts is set.
// (THIS WILL SLOW DOWN DEAR IMGUI. Only use occasionally and disable after use)
//#define IMGUI_DEBUG_HIGHLIGHT_ALL_ID_CONFLICTS
//---- Debug Tools: Enable slower asserts
//#define IMGUI_DEBUG_PARANOID
+3439 -1953
View File
File diff suppressed because it is too large Load Diff
+1023 -517
View File
File diff suppressed because it is too large Load Diff
+2825 -2079
View File
File diff suppressed because it is too large Load Diff
+3216 -1153
View File
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+258 -164
View File
@@ -1,6 +1,7 @@
// Mini memory editor for Dear ImGui (to embed in your game/tools)
// Get latest version at http://www.github.com/ocornut/imgui_club
//
// Licensed under The MIT License (MIT)
// Right-click anywhere to access the Options menu!
// You can adjust the keyboard repeat delay/rate in ImGuiIO.
// The code assume a mono-space font for simplicity!
@@ -39,10 +40,22 @@
// - v0.43 (2021/03/12): added OptFooterExtraHeight to allow for custom drawing at the bottom of the editor [@leiradel]
// - v0.44 (2021/03/12): use ImGuiInputTextFlags_AlwaysOverwrite in 1.82 + fix hardcoded width.
// - v0.50 (2021/11/12): various fixes for recent dear imgui versions (fixed misuse of clipper, relying on SetKeyboardFocusHere() handling scrolling from 1.85). added default size.
// - v0.51 (2024/02/22): fix for layout change in 1.89 when using IMGUI_DISABLE_OBSOLETE_FUNCTIONS. (#34)
// - v0.52 (2024/03/08): removed unnecessary GetKeyIndex() calls, they are a no-op since 1.87.
// - v0.53 (2024/05/27): fixed right-click popup from not appearing when using DrawContents(). warning fixes. (#35)
// - v0.54 (2024/07/29): allow ReadOnly mode to still select and preview data. (#46) [@DeltaGW2])
// - v0.55 (2024/08/19): added BgColorFn to allow setting background colors independently from highlighted selection. (#27) [@StrikerX3]
// added MouseHoveredAddr public readable field. (#47, #27) [@StrikerX3]
// fixed a data preview crash with 1.91.0 WIP. fixed contiguous highlight color when using data preview.
// *BREAKING* added UserData field passed to all optional function handlers: ReadFn, WriteFn, HighlightFn, BgColorFn. (#50) [@silverweed]
// - v0.56 (2024/11/04): fixed MouseHovered, MouseHoveredAddr not being set when hovering a byte being edited. (#54)
// - v0.57 (2025/03/26): fixed warnings. using ImGui's ImSXX/ImUXX types instead of e.g. int32_t/uint32_t. (#56)
// - v0.58 (2025/03/31): fixed extraneous footer spacing (added in 0.51) breaking vertical auto-resize. (#53)
// - v0.59 (2025/04/08): fixed GotoAddrAndHighlight() not working if OptShowOptions is disabled.
//
// Todo/Bugs:
// TODO:
// - This is generally old/crappy code, it should work but isn't very good.. to be rewritten some day.
// - PageUp/PageDown are supported because we use _NoNav. This is a good test scenario for working out idioms of how to mix natural nav and our own...
// - PageUp/PageDown are not supported because we use _NoNav. This is a good test scenario for working out idioms of how to mix natural nav and our own...
// - Arrows are being sent to the InputText() about to disappear which for LeftArrow makes the text cursor appear at position 1 for one frame.
// - Using InputText() is awkward and maybe overkill here, consider implementing something custom.
@@ -51,15 +64,18 @@
#include <stdio.h> // sprintf, scanf
#include <stdint.h> // uint8_t, etc.
#ifdef _MSC_VER
#define _PRISizeT "I"
#if defined(_MSC_VER) && !defined(snprintf)
#define ImSnprintf _snprintf
#else
#define _PRISizeT "z"
#define ImSnprintf snprintf
#endif
#if defined(_MSC_VER) && !defined(__clang__)
#define _PRISizeT "I"
#else
#define _PRISizeT "z"
#endif
#ifdef _MSC_VER
#if defined(_MSC_VER) || defined(_UCRT)
#pragma warning (push)
#pragma warning (disable: 4996) // warning C4996: 'sprintf': This function or variable may be unsafe.
#endif
@@ -88,9 +104,17 @@ struct MemoryEditor
int OptAddrDigitsCount; // = 0 // number of addr digits to display (default calculated based on maximum displayed addr).
float OptFooterExtraHeight; // = 0 // space to reserve at the bottom of the widget to add custom widgets
ImU32 HighlightColor; // // background color of highlighted bytes.
ImU8 (*ReadFn)(const ImU8* data, size_t off); // = 0 // optional handler to read bytes.
void (*WriteFn)(ImU8* data, size_t off, ImU8 d); // = 0 // optional handler to write bytes.
bool (*HighlightFn)(const ImU8* data, size_t off);//= 0 // optional handler to return Highlight property (to support non-contiguous highlighting).
// Function handlers
ImU8 (*ReadFn)(const ImU8* mem, size_t off, void* user_data); // = 0 // optional handler to read bytes.
void (*WriteFn)(ImU8* mem, size_t off, ImU8 d, void* user_data); // = 0 // optional handler to write bytes.
bool (*HighlightFn)(const ImU8* mem, size_t off, void* user_data); // = 0 // optional handler to return Highlight property (to support non-contiguous highlighting).
ImU32 (*BgColorFn)(const ImU8* mem, size_t off, void* user_data); // = 0 // optional handler to return custom background color of individual bytes.
void* UserData; // = NULL // user data forwarded to the function handlers
// Public read-only data
bool MouseHovered; // set when mouse is hovering a value.
size_t MouseHoveredAddr; // the address currently being hovered if MouseHovered is set.
// [Internal State]
bool ContentsWidthChanged;
@@ -101,7 +125,7 @@ struct MemoryEditor
char AddrInputBuf[32];
size_t GotoAddr;
size_t HighlightMin, HighlightMax;
int PreviewEndianess;
int PreviewEndianness;
ImGuiDataType PreviewDataType;
MemoryEditor()
@@ -120,9 +144,11 @@ struct MemoryEditor
OptAddrDigitsCount = 0;
OptFooterExtraHeight = 0.0f;
HighlightColor = IM_COL32(255, 255, 255, 50);
ReadFn = NULL;
WriteFn = NULL;
HighlightFn = NULL;
ReadFn = nullptr;
WriteFn = nullptr;
HighlightFn = nullptr;
BgColorFn = nullptr;
UserData = nullptr;
// State/Internals
ContentsWidthChanged = false;
@@ -131,8 +157,10 @@ struct MemoryEditor
memset(DataInputBuf, 0, sizeof(DataInputBuf));
memset(AddrInputBuf, 0, sizeof(AddrInputBuf));
GotoAddr = (size_t)-1;
MouseHovered = false;
MouseHoveredAddr = 0;
HighlightMin = HighlightMax = (size_t)-1;
PreviewEndianess = 0;
PreviewEndianness = 0;
PreviewDataType = ImGuiDataType_S32;
}
@@ -145,16 +173,16 @@ struct MemoryEditor
struct Sizes
{
int AddrDigitsCount;
float LineHeight;
float GlyphWidth;
float HexCellWidth;
float SpacingBetweenMidCols;
float PosHexStart;
float PosHexEnd;
float PosAsciiStart;
float PosAsciiEnd;
float WindowWidth;
int AddrDigitsCount; // Number of digits required to represent maximum address.
float LineHeight; // Height of each line (no spacing).
float GlyphWidth; // Glyph width (assume mono-space).
float HexCellWidth; // Width of a hex edit cell ~2.5f * GlypHWidth.
float SpacingBetweenMidCols; // Spacing between each columns section (OptMidColsCount).
float OffsetHexMinX;
float OffsetHexMaxX;
float OffsetAsciiMinX;
float OffsetAsciiMaxX;
float WindowWidth; // Ideal window width.
Sizes() { memset(this, 0, sizeof(*this)); }
};
@@ -170,17 +198,17 @@ struct MemoryEditor
s.GlyphWidth = ImGui::CalcTextSize("F").x + 1; // We assume the font is mono-space
s.HexCellWidth = (float)(int)(s.GlyphWidth * 2.5f); // "FF " we include trailing space in the width to easily catch clicks everywhere
s.SpacingBetweenMidCols = (float)(int)(s.HexCellWidth * 0.25f); // Every OptMidColsCount columns we add a bit of extra spacing
s.PosHexStart = (s.AddrDigitsCount + 2) * s.GlyphWidth;
s.PosHexEnd = s.PosHexStart + (s.HexCellWidth * Cols);
s.PosAsciiStart = s.PosAsciiEnd = s.PosHexEnd;
s.OffsetHexMinX = (s.AddrDigitsCount + 2) * s.GlyphWidth;
s.OffsetHexMaxX = s.OffsetHexMinX + (s.HexCellWidth * Cols);
s.OffsetAsciiMinX = s.OffsetAsciiMaxX = s.OffsetHexMaxX;
if (OptShowAscii)
{
s.PosAsciiStart = s.PosHexEnd + s.GlyphWidth * 1;
s.OffsetAsciiMinX = s.OffsetHexMaxX + s.GlyphWidth * 1;
if (OptMidColsCount > 0)
s.PosAsciiStart += (float)((Cols + OptMidColsCount - 1) / OptMidColsCount) * s.SpacingBetweenMidCols;
s.PosAsciiEnd = s.PosAsciiStart + Cols * s.GlyphWidth;
s.OffsetAsciiMinX += (float)((Cols + OptMidColsCount - 1) / OptMidColsCount) * s.SpacingBetweenMidCols;
s.OffsetAsciiMaxX = s.OffsetAsciiMinX + Cols * s.GlyphWidth;
}
s.WindowWidth = s.PosAsciiEnd + style.ScrollbarSize + style.WindowPadding.x * 2 + s.GlyphWidth;
s.WindowWidth = s.OffsetAsciiMaxX + style.ScrollbarSize + style.WindowPadding.x * 2 + s.GlyphWidth;
}
// Standalone Memory Editor window
@@ -194,8 +222,6 @@ struct MemoryEditor
Open = true;
if (ImGui::Begin(title, &Open, ImGuiWindowFlags_NoScrollbar))
{
if (ImGui::IsWindowHovered(ImGuiHoveredFlags_RootAndChildWindows) && ImGui::IsMouseReleased(ImGuiMouseButton_Right))
ImGui::OpenPopup("context");
DrawContents(mem_data, mem_size, base_display_addr);
if (ContentsWidthChanged)
{
@@ -217,6 +243,8 @@ struct MemoryEditor
CalcSizes(s, mem_size, base_display_addr);
ImGuiStyle& style = ImGui::GetStyle();
const ImVec2 contents_pos_start = ImGui::GetCursorScreenPos();
// We begin into our scrolling region with the 'ImGuiWindowFlags_NoMove' in order to prevent click from moving the window.
// This is used as a facility since our main click detection code doesn't assign an ActiveId so the click would normally be caught as a window-move.
const float height_separator = style.ItemSpacing.y;
@@ -225,20 +253,21 @@ struct MemoryEditor
footer_height += height_separator + ImGui::GetFrameHeightWithSpacing() * 1;
if (OptShowDataPreview)
footer_height += height_separator + ImGui::GetFrameHeightWithSpacing() * 1 + ImGui::GetTextLineHeightWithSpacing() * 3;
ImGui::BeginChild("##scrolling", ImVec2(0, -footer_height), false, ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoNav);
ImGui::BeginChild("##scrolling", ImVec2(-FLT_MIN, -footer_height), ImGuiChildFlags_None, ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoNav);
ImDrawList* draw_list = ImGui::GetWindowDrawList();
ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(0, 0));
ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(0, 0));
// We are not really using the clipper API correctly here, because we rely on visible_start_addr/visible_end_addr for our scrolling function.
const ImVec2 avail_size = ImGui::GetContentRegionAvail();
const int line_total_count = (int)((mem_size + Cols - 1) / Cols);
ImGuiListClipper clipper;
clipper.Begin(line_total_count, s.LineHeight);
bool data_next = false;
if (ReadOnly || DataEditingAddr >= mem_size)
if (DataEditingAddr >= mem_size)
DataEditingAddr = (size_t)-1;
if (DataPreviewAddr >= mem_size)
DataPreviewAddr = (size_t)-1;
@@ -249,16 +278,16 @@ struct MemoryEditor
if (DataEditingAddr != (size_t)-1)
{
// Move cursor but only apply on next frame so scrolling with be synchronized (because currently we can't change the scrolling while the window is being rendered)
if (ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_UpArrow)) && (ptrdiff_t)DataEditingAddr >= (ptrdiff_t)Cols) { data_editing_addr_next = DataEditingAddr - Cols; }
else if (ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_DownArrow)) && (ptrdiff_t)DataEditingAddr < (ptrdiff_t)mem_size - Cols) { data_editing_addr_next = DataEditingAddr + Cols; }
else if (ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_LeftArrow)) && (ptrdiff_t)DataEditingAddr > (ptrdiff_t)0) { data_editing_addr_next = DataEditingAddr - 1; }
else if (ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_RightArrow)) && (ptrdiff_t)DataEditingAddr < (ptrdiff_t)mem_size - 1) { data_editing_addr_next = DataEditingAddr + 1; }
if (ImGui::IsKeyPressed(ImGuiKey_UpArrow) && (ptrdiff_t)DataEditingAddr >= (ptrdiff_t)Cols) { data_editing_addr_next = DataEditingAddr - Cols; }
else if (ImGui::IsKeyPressed(ImGuiKey_DownArrow) && (ptrdiff_t)DataEditingAddr < (ptrdiff_t)mem_size - Cols){ data_editing_addr_next = DataEditingAddr + Cols; }
else if (ImGui::IsKeyPressed(ImGuiKey_LeftArrow) && (ptrdiff_t)DataEditingAddr > (ptrdiff_t)0) { data_editing_addr_next = DataEditingAddr - 1; }
else if (ImGui::IsKeyPressed(ImGuiKey_RightArrow) && (ptrdiff_t)DataEditingAddr < (ptrdiff_t)mem_size - 1) { data_editing_addr_next = DataEditingAddr + 1; }
}
// Draw vertical separator
ImVec2 window_pos = ImGui::GetWindowPos();
if (OptShowAscii)
draw_list->AddLine(ImVec2(window_pos.x + s.PosAsciiStart - s.GlyphWidth, window_pos.y), ImVec2(window_pos.x + s.PosAsciiStart - s.GlyphWidth, window_pos.y + 9999), ImGui::GetColorU32(ImGuiCol_Border));
draw_list->AddLine(ImVec2(window_pos.x + s.OffsetAsciiMinX - s.GlyphWidth, window_pos.y), ImVec2(window_pos.x + s.OffsetAsciiMinX - s.GlyphWidth, window_pos.y + 9999), ImGui::GetColorU32(ImGuiCol_Border));
const ImU32 color_text = ImGui::GetColorU32(ImGuiCol_Text);
const ImU32 color_disabled = OptGreyOutZeroes ? ImGui::GetColorU32(ImGuiCol_TextDisabled) : color_text;
@@ -268,36 +297,51 @@ struct MemoryEditor
const char* format_byte = OptUpperCaseHex ? "%02X" : "%02x";
const char* format_byte_space = OptUpperCaseHex ? "%02X " : "%02x ";
MouseHovered = false;
MouseHoveredAddr = 0;
while (clipper.Step())
for (int line_i = clipper.DisplayStart; line_i < clipper.DisplayEnd; line_i++) // display only visible lines
{
size_t addr = (size_t)(line_i * Cols);
size_t addr = (size_t)line_i * Cols;
ImGui::Text(format_address, s.AddrDigitsCount, base_display_addr + addr);
// Draw Hexadecimal
for (int n = 0; n < Cols && addr < mem_size; n++, addr++)
{
float byte_pos_x = s.PosHexStart + s.HexCellWidth * n;
float byte_pos_x = s.OffsetHexMinX + s.HexCellWidth * n;
if (OptMidColsCount > 0)
byte_pos_x += (float)(n / OptMidColsCount) * s.SpacingBetweenMidCols;
ImGui::SameLine(byte_pos_x);
// Draw highlight
bool is_highlight_from_user_range = (addr >= HighlightMin && addr < HighlightMax);
bool is_highlight_from_user_func = (HighlightFn && HighlightFn(mem_data, addr));
bool is_highlight_from_preview = (addr >= DataPreviewAddr && addr < DataPreviewAddr + preview_data_type_size);
// Draw highlight or custom background color
const bool is_highlight_from_user_range = (addr >= HighlightMin && addr < HighlightMax);
const bool is_highlight_from_user_func = (HighlightFn && HighlightFn(mem_data, addr, UserData));
const bool is_highlight_from_preview = (addr >= DataPreviewAddr && addr < DataPreviewAddr + preview_data_type_size);
ImU32 bg_color = 0;
bool is_next_byte_highlighted = false;
if (is_highlight_from_user_range || is_highlight_from_user_func || is_highlight_from_preview)
{
ImVec2 pos = ImGui::GetCursorScreenPos();
float highlight_width = s.GlyphWidth * 2;
bool is_next_byte_highlighted = (addr + 1 < mem_size) && ((HighlightMax != (size_t)-1 && addr + 1 < HighlightMax) || (HighlightFn && HighlightFn(mem_data, addr + 1)));
is_next_byte_highlighted = (addr + 1 < mem_size) && ((HighlightMax != (size_t)-1 && addr + 1 < HighlightMax) || (HighlightFn && HighlightFn(mem_data, addr + 1, UserData)) || (addr + 1 < DataPreviewAddr + preview_data_type_size));
bg_color = HighlightColor;
}
else if (BgColorFn != nullptr)
{
is_next_byte_highlighted = (addr + 1 < mem_size) && ((BgColorFn(mem_data, addr + 1, UserData) & IM_COL32_A_MASK) != 0);
bg_color = BgColorFn(mem_data, addr, UserData);
}
if (bg_color != 0)
{
float bg_width = s.GlyphWidth * 2;
if (is_next_byte_highlighted || (n + 1 == Cols))
{
highlight_width = s.HexCellWidth;
bg_width = s.HexCellWidth;
if (OptMidColsCount > 0 && n > 0 && (n + 1) < Cols && ((n + 1) % OptMidColsCount) == 0)
highlight_width += s.SpacingBetweenMidCols;
bg_width += s.SpacingBetweenMidCols;
}
draw_list->AddRectFilled(pos, ImVec2(pos.x + highlight_width, pos.y + s.LineHeight), HighlightColor);
ImVec2 pos = ImGui::GetCursorScreenPos();
draw_list->AddRectFilled(pos, ImVec2(pos.x + bg_width, pos.y + s.LineHeight), bg_color);
}
if (DataEditingAddr == addr)
@@ -308,17 +352,21 @@ struct MemoryEditor
if (DataEditingTakeFocus)
{
ImGui::SetKeyboardFocusHere(0);
sprintf(AddrInputBuf, format_data, s.AddrDigitsCount, base_display_addr + addr);
sprintf(DataInputBuf, format_byte, ReadFn ? ReadFn(mem_data, addr) : mem_data[addr]);
ImSnprintf(AddrInputBuf, 32, format_data, s.AddrDigitsCount, base_display_addr + addr);
ImSnprintf(DataInputBuf, 32, format_byte, ReadFn ? ReadFn(mem_data, addr, UserData) : mem_data[addr]);
}
struct UserData
struct InputTextUserData
{
// FIXME: We should have a way to retrieve the text edit cursor position more easily in the API, this is rather tedious. This is such a ugly mess we may be better off not using InputText() at all here.
static int Callback(ImGuiInputTextCallbackData* data)
{
UserData* user_data = (UserData*)data->UserData;
InputTextUserData* user_data = (InputTextUserData*)data->UserData;
if (!data->HasSelection())
user_data->CursorPos = data->CursorPos;
#if IMGUI_VERSION_NUM < 19102
if (data->Flags & ImGuiInputTextFlags_ReadOnly)
return 0;
#endif
if (data->SelectionStart == 0 && data->SelectionEnd == data->BufTextLen)
{
// When not editing a byte, always refresh its InputText content pulled from underlying memory data
@@ -334,39 +382,42 @@ struct MemoryEditor
char CurrentBufOverwrite[3]; // Input
int CursorPos; // Output
};
UserData user_data;
user_data.CursorPos = -1;
sprintf(user_data.CurrentBufOverwrite, format_byte, ReadFn ? ReadFn(mem_data, addr) : mem_data[addr]);
InputTextUserData input_text_user_data;
input_text_user_data.CursorPos = -1;
ImSnprintf(input_text_user_data.CurrentBufOverwrite, 3, format_byte, ReadFn ? ReadFn(mem_data, addr, UserData) : mem_data[addr]);
ImGuiInputTextFlags flags = ImGuiInputTextFlags_CharsHexadecimal | ImGuiInputTextFlags_EnterReturnsTrue | ImGuiInputTextFlags_AutoSelectAll | ImGuiInputTextFlags_NoHorizontalScroll | ImGuiInputTextFlags_CallbackAlways;
#if IMGUI_VERSION_NUM >= 18104
flags |= ImGuiInputTextFlags_AlwaysOverwrite;
#else
flags |= ImGuiInputTextFlags_AlwaysInsertMode;
#endif
if (ReadOnly)
flags |= ImGuiInputTextFlags_ReadOnly;
flags |= ImGuiInputTextFlags_AlwaysOverwrite; // was ImGuiInputTextFlags_AlwaysInsertMode
ImGui::SetNextItemWidth(s.GlyphWidth * 2);
if (ImGui::InputText("##data", DataInputBuf, IM_ARRAYSIZE(DataInputBuf), flags, UserData::Callback, &user_data))
if (ImGui::InputText("##data", DataInputBuf, IM_ARRAYSIZE(DataInputBuf), flags, InputTextUserData::Callback, &input_text_user_data))
data_write = data_next = true;
else if (!DataEditingTakeFocus && !ImGui::IsItemActive())
DataEditingAddr = data_editing_addr_next = (size_t)-1;
DataEditingTakeFocus = false;
if (user_data.CursorPos >= 2)
if (input_text_user_data.CursorPos >= 2)
data_write = data_next = true;
if (data_editing_addr_next != (size_t)-1)
data_write = data_next = false;
unsigned int data_input_value = 0;
if (data_write && sscanf(DataInputBuf, "%X", &data_input_value) == 1)
if (!ReadOnly && data_write && sscanf(DataInputBuf, "%X", &data_input_value) == 1)
{
if (WriteFn)
WriteFn(mem_data, addr, (ImU8)data_input_value);
WriteFn(mem_data, addr, (ImU8)data_input_value, UserData);
else
mem_data[addr] = (ImU8)data_input_value;
}
if (ImGui::IsItemHovered())
{
MouseHovered = true;
MouseHoveredAddr = addr;
}
ImGui::PopID();
}
else
{
// NB: The trailing space is not visible but ensure there's no gap that the mouse cannot click on.
ImU8 b = ReadFn ? ReadFn(mem_data, addr) : mem_data[addr];
ImU8 b = ReadFn ? ReadFn(mem_data, addr, UserData) : mem_data[addr];
if (OptShowHexII)
{
@@ -386,10 +437,15 @@ struct MemoryEditor
else
ImGui::Text(format_byte_space, b);
}
if (!ReadOnly && ImGui::IsItemHovered() && ImGui::IsMouseClicked(0))
if (ImGui::IsItemHovered())
{
DataEditingTakeFocus = true;
data_editing_addr_next = addr;
MouseHovered = true;
MouseHoveredAddr = addr;
if (ImGui::IsMouseClicked(0))
{
DataEditingTakeFocus = true;
data_editing_addr_next = addr;
}
}
}
}
@@ -397,15 +453,24 @@ struct MemoryEditor
if (OptShowAscii)
{
// Draw ASCII values
ImGui::SameLine(s.PosAsciiStart);
ImGui::SameLine(s.OffsetAsciiMinX);
ImVec2 pos = ImGui::GetCursorScreenPos();
addr = line_i * Cols;
addr = (size_t)line_i * Cols;
const float mouse_off_x = ImGui::GetIO().MousePos.x - pos.x;
const size_t mouse_addr = (mouse_off_x >= 0.0f && mouse_off_x < s.OffsetAsciiMaxX - s.OffsetAsciiMinX) ? addr + (size_t)(mouse_off_x / s.GlyphWidth) : (size_t)-1;
ImGui::PushID(line_i);
if (ImGui::InvisibleButton("ascii", ImVec2(s.PosAsciiEnd - s.PosAsciiStart, s.LineHeight)))
if (ImGui::InvisibleButton("ascii", ImVec2(s.OffsetAsciiMaxX - s.OffsetAsciiMinX, s.LineHeight)))
{
DataEditingAddr = DataPreviewAddr = addr + (size_t)((ImGui::GetIO().MousePos.x - pos.x) / s.GlyphWidth);
DataEditingAddr = DataPreviewAddr = mouse_addr;
DataEditingTakeFocus = true;
}
if (ImGui::IsItemHovered())
{
MouseHovered = true;
MouseHoveredAddr = mouse_addr;
}
ImGui::PopID();
for (int n = 0; n < Cols && addr < mem_size; n++, addr++)
{
@@ -414,7 +479,11 @@ struct MemoryEditor
draw_list->AddRectFilled(pos, ImVec2(pos.x + s.GlyphWidth, pos.y + s.LineHeight), ImGui::GetColorU32(ImGuiCol_FrameBg));
draw_list->AddRectFilled(pos, ImVec2(pos.x + s.GlyphWidth, pos.y + s.LineHeight), ImGui::GetColorU32(ImGuiCol_TextSelectedBg));
}
unsigned char c = ReadFn ? ReadFn(mem_data, addr) : mem_data[addr];
else if (BgColorFn)
{
draw_list->AddRectFilled(pos, ImVec2(pos.x + s.GlyphWidth, pos.y + s.LineHeight), BgColorFn(mem_data, addr, UserData));
}
unsigned char c = ReadFn ? ReadFn(mem_data, addr, UserData) : mem_data[addr];
char display_c = (c < 32 || c >= 128) ? '.' : c;
draw_list->AddText(pos, (display_c == c) ? color_text : color_disabled, &display_c, &display_c + 1);
pos.x += s.GlyphWidth;
@@ -422,10 +491,14 @@ struct MemoryEditor
}
}
ImGui::PopStyleVar(2);
const float child_width = ImGui::GetWindowSize().x;
ImGui::EndChild();
// Notify the main window of our ideal child content size (FIXME: we are missing an API to get the contents size from the child)
ImVec2 backup_pos = ImGui::GetCursorScreenPos();
ImGui::SetCursorPosX(s.WindowWidth);
ImGui::Dummy(ImVec2(0.0f, 0.0f));
ImGui::SetCursorScreenPos(backup_pos);
if (data_next && DataEditingAddr + 1 < mem_size)
{
@@ -450,18 +523,28 @@ struct MemoryEditor
ImGui::Separator();
DrawPreviewLine(s, mem_data, mem_size, base_display_addr);
}
}
void DrawOptionsLine(const Sizes& s, void* mem_data, size_t mem_size, size_t base_display_addr)
{
IM_UNUSED(mem_data);
ImGuiStyle& style = ImGui::GetStyle();
const char* format_range = OptUpperCaseHex ? "Range %0*" _PRISizeT "X..%0*" _PRISizeT "X" : "Range %0*" _PRISizeT "x..%0*" _PRISizeT "x";
if (GotoAddr != (size_t)-1)
{
if (GotoAddr < mem_size)
{
ImGui::BeginChild("##scrolling");
ImGui::SetScrollY((GotoAddr / Cols) * ImGui::GetTextLineHeight() - avail_size.y * 0.5f);
ImGui::EndChild();
DataEditingAddr = DataPreviewAddr = GotoAddr;
DataEditingTakeFocus = true;
}
GotoAddr = (size_t)-1;
}
// Options menu
if (ImGui::Button("Options"))
ImGui::OpenPopup("context");
if (ImGui::BeginPopup("context"))
const ImVec2 contents_pos_end(contents_pos_start.x + child_width, ImGui::GetCursorScreenPos().y);
//ImGui::GetForegroundDrawList()->AddRect(contents_pos_start, contents_pos_end, IM_COL32(255, 0, 0, 255));
if (OptShowOptions)
if (ImGui::IsMouseHoveringRect(contents_pos_start, contents_pos_end))
if (ImGui::IsWindowHovered(ImGuiHoveredFlags_ChildWindows) && ImGui::IsMouseReleased(ImGuiMouseButton_Right))
ImGui::OpenPopup("OptionsPopup");
if (ImGui::BeginPopup("OptionsPopup"))
{
ImGui::SetNextItemWidth(s.GlyphWidth * 7 + style.FramePadding.x * 2.0f);
if (ImGui::DragInt("##cols", &Cols, 0.2f, 4, 32, "%d cols")) { ContentsWidthChanged = true; if (Cols < 1) Cols = 1; }
@@ -473,6 +556,17 @@ struct MemoryEditor
ImGui::EndPopup();
}
}
void DrawOptionsLine(const Sizes& s, void* mem_data, size_t mem_size, size_t base_display_addr)
{
IM_UNUSED(mem_data);
ImGuiStyle& style = ImGui::GetStyle();
const char* format_range = OptUpperCaseHex ? "Range %0*" _PRISizeT "X..%0*" _PRISizeT "X" : "Range %0*" _PRISizeT "x..%0*" _PRISizeT "x";
// Options menu
if (ImGui::Button("Options"))
ImGui::OpenPopup("OptionsPopup");
ImGui::SameLine();
ImGui::Text(format_range, s.AddrDigitsCount, base_display_addr, s.AddrDigitsCount, base_display_addr + mem_size - 1);
@@ -488,18 +582,11 @@ struct MemoryEditor
}
}
if (GotoAddr != (size_t)-1)
{
if (GotoAddr < mem_size)
{
ImGui::BeginChild("##scrolling");
ImGui::SetScrollFromPosY(ImGui::GetCursorStartPos().y + (GotoAddr / Cols) * ImGui::GetTextLineHeight());
ImGui::EndChild();
DataEditingAddr = DataPreviewAddr = GotoAddr;
DataEditingTakeFocus = true;
}
GotoAddr = (size_t)-1;
}
//if (MouseHovered)
//{
// ImGui::SameLine();
// ImGui::Text("Hovered: %p", MouseHoveredAddr);
//}
}
void DrawPreviewLine(const Sizes& s, void* mem_data_void, size_t mem_size, size_t base_display_addr)
@@ -511,16 +598,21 @@ struct MemoryEditor
ImGui::Text("Preview as:");
ImGui::SameLine();
ImGui::SetNextItemWidth((s.GlyphWidth * 10.0f) + style.FramePadding.x * 2.0f + style.ItemInnerSpacing.x);
static const ImGuiDataType supported_data_types[] = { ImGuiDataType_S8, ImGuiDataType_U8, ImGuiDataType_S16, ImGuiDataType_U16, ImGuiDataType_S32, ImGuiDataType_U32, ImGuiDataType_S64, ImGuiDataType_U64, ImGuiDataType_Float, ImGuiDataType_Double };
if (ImGui::BeginCombo("##combo_type", DataTypeGetDesc(PreviewDataType), ImGuiComboFlags_HeightLargest))
{
for (int n = 0; n < ImGuiDataType_COUNT; n++)
if (ImGui::Selectable(DataTypeGetDesc((ImGuiDataType)n), PreviewDataType == n))
PreviewDataType = (ImGuiDataType)n;
for (int n = 0; n < IM_ARRAYSIZE(supported_data_types); n++)
{
ImGuiDataType data_type = supported_data_types[n];
if (ImGui::Selectable(DataTypeGetDesc(data_type), PreviewDataType == data_type))
PreviewDataType = data_type;
}
ImGui::EndCombo();
}
ImGui::SameLine();
ImGui::SetNextItemWidth((s.GlyphWidth * 6.0f) + style.FramePadding.x * 2.0f + style.ItemInnerSpacing.x);
ImGui::Combo("##combo_endianess", &PreviewEndianess, "LE\0BE\0\0");
ImGui::Combo("##combo_endianness", &PreviewEndianness, "LE\0BE\0\0");
char buf[128] = "";
float x = s.GlyphWidth * 6.0f;
@@ -537,18 +629,19 @@ struct MemoryEditor
ImGui::Text("Bin"); ImGui::SameLine(x); ImGui::TextUnformatted(has_value ? buf : "N/A");
}
// Utilities for Data Preview
// Utilities for Data Preview (since we don't access imgui_internal.h)
// FIXME: This technically depends on ImGuiDataType order.
const char* DataTypeGetDesc(ImGuiDataType data_type) const
{
const char* descs[] = { "Int8", "Uint8", "Int16", "Uint16", "Int32", "Uint32", "Int64", "Uint64", "Float", "Double" };
IM_ASSERT(data_type >= 0 && data_type < ImGuiDataType_COUNT);
IM_ASSERT(data_type >= 0 && data_type < IM_ARRAYSIZE(descs));
return descs[data_type];
}
size_t DataTypeGetSize(ImGuiDataType data_type) const
{
const size_t sizes[] = { 1, 1, 2, 2, 4, 4, 8, 8, sizeof(float), sizeof(double) };
IM_ASSERT(data_type >= 0 && data_type < ImGuiDataType_COUNT);
IM_ASSERT(data_type >= 0 && data_type < IM_ARRAYSIZE(sizes));
return sizes[data_type];
}
@@ -561,18 +654,18 @@ struct MemoryEditor
bool IsBigEndian() const
{
uint16_t x = 1;
ImU16 x = 1;
char c[2];
memcpy(c, &x, 2);
return c[0] != 0;
}
static void* EndianessCopyBigEndian(void* _dst, void* _src, size_t s, int is_little_endian)
static void* EndiannessCopyBigEndian(void* _dst, void* _src, size_t s, int is_little_endian)
{
if (is_little_endian)
{
uint8_t* dst = (uint8_t*)_dst;
uint8_t* src = (uint8_t*)_src + s - 1;
ImU8* dst = (ImU8*)_dst;
ImU8* src = (ImU8*)_src + s - 1;
for (int i = 0, n = (int)s; i < n; ++i)
memcpy(dst++, src--, 1);
return _dst;
@@ -583,7 +676,7 @@ struct MemoryEditor
}
}
static void* EndianessCopyLittleEndian(void* _dst, void* _src, size_t s, int is_little_endian)
static void* EndiannessCopyLittleEndian(void* _dst, void* _src, size_t s, int is_little_endian)
{
if (is_little_endian)
{
@@ -591,23 +684,23 @@ struct MemoryEditor
}
else
{
uint8_t* dst = (uint8_t*)_dst;
uint8_t* src = (uint8_t*)_src + s - 1;
ImU8* dst = (ImU8*)_dst;
ImU8* src = (ImU8*)_src + s - 1;
for (int i = 0, n = (int)s; i < n; ++i)
memcpy(dst++, src--, 1);
return _dst;
}
}
void* EndianessCopy(void* dst, void* src, size_t size) const
void* EndiannessCopy(void* dst, void* src, size_t size) const
{
static void* (*fp)(void*, void*, size_t, int) = NULL;
if (fp == NULL)
fp = IsBigEndian() ? EndianessCopyBigEndian : EndianessCopyLittleEndian;
return fp(dst, src, size, PreviewEndianess);
static void* (*fp)(void*, void*, size_t, int) = nullptr;
if (fp == nullptr)
fp = IsBigEndian() ? EndiannessCopyBigEndian : EndiannessCopyLittleEndian;
return fp(dst, src, size, PreviewEndianness);
}
const char* FormatBinary(const uint8_t* buf, int width) const
const char* FormatBinary(const ImU8* buf, int width) const
{
IM_ASSERT(width <= 64);
size_t out_n = 0;
@@ -627,19 +720,19 @@ struct MemoryEditor
// [Internal]
void DrawPreviewData(size_t addr, const ImU8* mem_data, size_t mem_size, ImGuiDataType data_type, DataFormat data_format, char* out_buf, size_t out_buf_size) const
{
uint8_t buf[8];
ImU8 buf[8];
size_t elem_size = DataTypeGetSize(data_type);
size_t size = addr + elem_size > mem_size ? mem_size - addr : elem_size;
if (ReadFn)
for (int i = 0, n = (int)size; i < n; ++i)
buf[i] = ReadFn(mem_data, addr + i);
buf[i] = ReadFn(mem_data, addr + i, UserData);
else
memcpy(buf, mem_data + addr, size);
if (data_format == DataFormat_Bin)
{
uint8_t binbuf[8];
EndianessCopy(binbuf, buf, size);
ImU8 binbuf[8];
EndiannessCopy(binbuf, buf, size);
ImSnprintf(out_buf, out_buf_size, "%s", FormatBinary(binbuf, (int)size * 8));
return;
}
@@ -649,84 +742,85 @@ struct MemoryEditor
{
case ImGuiDataType_S8:
{
int8_t int8 = 0;
EndianessCopy(&int8, buf, size);
if (data_format == DataFormat_Dec) { ImSnprintf(out_buf, out_buf_size, "%hhd", int8); return; }
if (data_format == DataFormat_Hex) { ImSnprintf(out_buf, out_buf_size, "0x%02x", int8 & 0xFF); return; }
ImS8 data = 0;
EndiannessCopy(&data, buf, size);
if (data_format == DataFormat_Dec) { ImSnprintf(out_buf, out_buf_size, "%hhd", data); return; }
if (data_format == DataFormat_Hex) { ImSnprintf(out_buf, out_buf_size, "0x%02x", data & 0xFF); return; }
break;
}
case ImGuiDataType_U8:
{
uint8_t uint8 = 0;
EndianessCopy(&uint8, buf, size);
if (data_format == DataFormat_Dec) { ImSnprintf(out_buf, out_buf_size, "%hhu", uint8); return; }
if (data_format == DataFormat_Hex) { ImSnprintf(out_buf, out_buf_size, "0x%02x", uint8 & 0XFF); return; }
ImU8 data = 0;
EndiannessCopy(&data, buf, size);
if (data_format == DataFormat_Dec) { ImSnprintf(out_buf, out_buf_size, "%hhu", data); return; }
if (data_format == DataFormat_Hex) { ImSnprintf(out_buf, out_buf_size, "0x%02x", data & 0XFF); return; }
break;
}
case ImGuiDataType_S16:
{
int16_t int16 = 0;
EndianessCopy(&int16, buf, size);
if (data_format == DataFormat_Dec) { ImSnprintf(out_buf, out_buf_size, "%hd", int16); return; }
if (data_format == DataFormat_Hex) { ImSnprintf(out_buf, out_buf_size, "0x%04x", int16 & 0xFFFF); return; }
ImS16 data = 0;
EndiannessCopy(&data, buf, size);
if (data_format == DataFormat_Dec) { ImSnprintf(out_buf, out_buf_size, "%hd", data); return; }
if (data_format == DataFormat_Hex) { ImSnprintf(out_buf, out_buf_size, "0x%04x", data & 0xFFFF); return; }
break;
}
case ImGuiDataType_U16:
{
uint16_t uint16 = 0;
EndianessCopy(&uint16, buf, size);
if (data_format == DataFormat_Dec) { ImSnprintf(out_buf, out_buf_size, "%hu", uint16); return; }
if (data_format == DataFormat_Hex) { ImSnprintf(out_buf, out_buf_size, "0x%04x", uint16 & 0xFFFF); return; }
ImU16 data = 0;
EndiannessCopy(&data, buf, size);
if (data_format == DataFormat_Dec) { ImSnprintf(out_buf, out_buf_size, "%hu", data); return; }
if (data_format == DataFormat_Hex) { ImSnprintf(out_buf, out_buf_size, "0x%04x", data & 0xFFFF); return; }
break;
}
case ImGuiDataType_S32:
{
int32_t int32 = 0;
EndianessCopy(&int32, buf, size);
if (data_format == DataFormat_Dec) { ImSnprintf(out_buf, out_buf_size, "%d", int32); return; }
if (data_format == DataFormat_Hex) { ImSnprintf(out_buf, out_buf_size, "0x%08x", int32); return; }
ImS32 data = 0;
EndiannessCopy(&data, buf, size);
if (data_format == DataFormat_Dec) { ImSnprintf(out_buf, out_buf_size, "%d", data); return; }
if (data_format == DataFormat_Hex) { ImSnprintf(out_buf, out_buf_size, "0x%08x", data); return; }
break;
}
case ImGuiDataType_U32:
{
uint32_t uint32 = 0;
EndianessCopy(&uint32, buf, size);
if (data_format == DataFormat_Dec) { ImSnprintf(out_buf, out_buf_size, "%u", uint32); return; }
if (data_format == DataFormat_Hex) { ImSnprintf(out_buf, out_buf_size, "0x%08x", uint32); return; }
ImU32 data = 0;
EndiannessCopy(&data, buf, size);
if (data_format == DataFormat_Dec) { ImSnprintf(out_buf, out_buf_size, "%u", data); return; }
if (data_format == DataFormat_Hex) { ImSnprintf(out_buf, out_buf_size, "0x%08x", data); return; }
break;
}
case ImGuiDataType_S64:
{
int64_t int64 = 0;
EndianessCopy(&int64, buf, size);
if (data_format == DataFormat_Dec) { ImSnprintf(out_buf, out_buf_size, "%lld", (long long)int64); return; }
if (data_format == DataFormat_Hex) { ImSnprintf(out_buf, out_buf_size, "0x%016llx", (long long)int64); return; }
ImS64 data = 0;
EndiannessCopy(&data, buf, size);
if (data_format == DataFormat_Dec) { ImSnprintf(out_buf, out_buf_size, "%lld", (long long)data); return; }
if (data_format == DataFormat_Hex) { ImSnprintf(out_buf, out_buf_size, "0x%016llx", (long long)data); return; }
break;
}
case ImGuiDataType_U64:
{
uint64_t uint64 = 0;
EndianessCopy(&uint64, buf, size);
if (data_format == DataFormat_Dec) { ImSnprintf(out_buf, out_buf_size, "%llu", (long long)uint64); return; }
if (data_format == DataFormat_Hex) { ImSnprintf(out_buf, out_buf_size, "0x%016llx", (long long)uint64); return; }
ImU64 data = 0;
EndiannessCopy(&data, buf, size);
if (data_format == DataFormat_Dec) { ImSnprintf(out_buf, out_buf_size, "%llu", (long long)data); return; }
if (data_format == DataFormat_Hex) { ImSnprintf(out_buf, out_buf_size, "0x%016llx", (long long)data); return; }
break;
}
case ImGuiDataType_Float:
{
float float32 = 0.0f;
EndianessCopy(&float32, buf, size);
if (data_format == DataFormat_Dec) { ImSnprintf(out_buf, out_buf_size, "%f", float32); return; }
if (data_format == DataFormat_Hex) { ImSnprintf(out_buf, out_buf_size, "%a", float32); return; }
float data = 0.0f;
EndiannessCopy(&data, buf, size);
if (data_format == DataFormat_Dec) { ImSnprintf(out_buf, out_buf_size, "%f", data); return; }
if (data_format == DataFormat_Hex) { ImSnprintf(out_buf, out_buf_size, "%a", data); return; }
break;
}
case ImGuiDataType_Double:
{
double float64 = 0.0;
EndianessCopy(&float64, buf, size);
if (data_format == DataFormat_Dec) { ImSnprintf(out_buf, out_buf_size, "%f", float64); return; }
if (data_format == DataFormat_Hex) { ImSnprintf(out_buf, out_buf_size, "%a", float64); return; }
double data = 0.0;
EndiannessCopy(&data, buf, size);
if (data_format == DataFormat_Dec) { ImSnprintf(out_buf, out_buf_size, "%f", data); return; }
if (data_format == DataFormat_Hex) { ImSnprintf(out_buf, out_buf_size, "%a", data); return; }
break;
}
default:
case ImGuiDataType_COUNT:
break;
} // Switch
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+2 -2
View File
@@ -315,7 +315,7 @@ static int stbrp__skyline_find_min_y(stbrp_context *c, stbrp_node *first, int x0
if (node->y > min_y) {
// raise min_y higher.
// we've accounted for all waste up to min_y,
// but we'll now add more waste for everything we've visted
// but we'll now add more waste for everything we've visited
waste_area += visited_width * (node->y - min_y);
min_y = node->y;
// the first time through, visited_width might be reduced
@@ -470,7 +470,7 @@ static stbrp__findresult stbrp__skyline_pack_rectangle(stbrp_context *context, i
// insert the new node into the right starting point, and
// let 'cur' point to the remaining nodes needing to be
// stiched back in
// stitched back in
cur = *res.prev_link;
if (cur->x < res.x) {
+113 -55
View File
@@ -5,7 +5,8 @@
// - Fix in stb_textedit_find_charpos to handle last line (see https://github.com/ocornut/imgui/issues/6000 + #6783)
// - Added name to struct or it may be forward declared in our code.
// - Added UTF-8 support (see https://github.com/nothings/stb/issues/188 + https://github.com/ocornut/imgui/pull/7925)
// Grep for [DEAR IMGUI] to find the changes.
// - Changed STB_TEXTEDIT_INSERTCHARS() to return inserted count (instead of 0/1 bool), allowing partial insertion.
// Grep for [DEAR IMGUI] to find some changes.
// - Also renamed macros used or defined outside of IMSTB_TEXTEDIT_IMPLEMENTATION block from STB_TEXTEDIT_* to IMSTB_TEXTEDIT_*
// stb_textedit.h - v1.14 - public domain - Sean Barrett
@@ -39,6 +40,7 @@
//
// VERSION HISTORY
//
// !!!! (2025-10-23) changed STB_TEXTEDIT_INSERTCHARS() to return inserted count (instead of 0/1 bool), allowing partial insertion.
// 1.14 (2021-07-11) page up/down, various fixes
// 1.13 (2019-02-07) fix bug in undo size management
// 1.12 (2018-01-29) user can change STB_TEXTEDIT_KEYTYPE, fix redo to avoid crash
@@ -141,12 +143,14 @@
// with previous char)
// STB_TEXTEDIT_KEYTOTEXT(k) maps a keyboard input to an insertable character
// (return type is int, -1 means not valid to insert)
// (not supported if you want to use UTF-8, see below)
// STB_TEXTEDIT_GETCHAR(obj,i) returns the i'th character of obj, 0-based
// STB_TEXTEDIT_NEWLINE the character returned by _GETCHAR() we recognize
// as manually wordwrapping for end-of-line positioning
//
// STB_TEXTEDIT_DELETECHARS(obj,i,n) delete n characters starting at i
// STB_TEXTEDIT_INSERTCHARS(obj,i,c*,n) insert n characters at i (pointed to by STB_TEXTEDIT_CHARTYPE*)
// STB_TEXTEDIT_INSERTCHARS(obj,i,c*,n) try to insert n characters at i (pointed to by STB_TEXTEDIT_CHARTYPE*)
// returns number of characters actually inserted. [DEAR IMGUI]
//
// STB_TEXTEDIT_K_SHIFT a power of two that is or'd in to a keyboard input to represent the shift key
//
@@ -178,6 +182,13 @@
// STB_TEXTEDIT_K_TEXTSTART2 secondary keyboard input to move cursor to start of text
// STB_TEXTEDIT_K_TEXTEND2 secondary keyboard input to move cursor to end of text
//
// To support UTF-8:
//
// STB_TEXTEDIT_GETPREVCHARINDEX returns index of previous character
// STB_TEXTEDIT_GETNEXTCHARINDEX returns index of next character
// Do NOT define STB_TEXTEDIT_KEYTOTEXT.
// Instead, call stb_textedit_text() directly for text contents.
//
// Keyboard input must be encoded as a single integer value; e.g. a character code
// and some bitflags that represent shift states. to simplify the interface, SHIFT must
// be a bitflag, so we can test the shifted state of cursor movements to allow selection,
@@ -250,8 +261,10 @@
// if the STB_TEXTEDIT_KEYTOTEXT function is defined, selected keys are
// transformed into text and stb_textedit_text() is automatically called.
//
// text: [DEAR IMGUI] added 2024-09
// call this to text inputs sent to the textfield.
// text: (added 2025)
// call this to directly send text input the textfield, which is required
// for UTF-8 support, because stb_textedit_key() + STB_TEXTEDIT_KEYTOTEXT()
// cannot infer text length.
//
//
// When rendering, you can read the cursor position and selection state from
@@ -400,6 +413,16 @@ typedef struct
#define IMSTB_TEXTEDIT_memmove memmove
#endif
// [DEAR IMGUI]
// Functions must be implemented for UTF8 support
// Code in this file that uses those functions is modified for [DEAR IMGUI] and deviates from the original stb_textedit.
// There is not necessarily a '[DEAR IMGUI]' at the usage sites.
#ifndef IMSTB_TEXTEDIT_GETPREVCHARINDEX
#define IMSTB_TEXTEDIT_GETPREVCHARINDEX(OBJ, IDX) ((IDX) - 1)
#endif
#ifndef IMSTB_TEXTEDIT_GETNEXTCHARINDEX
#define IMSTB_TEXTEDIT_GETNEXTCHARINDEX(OBJ, IDX) ((IDX) + 1)
#endif
/////////////////////////////////////////////////////////////////////////////
//
@@ -407,7 +430,7 @@ typedef struct
//
// traverse the layout to locate the nearest character to a display position
static int stb_text_locate_coord(IMSTB_TEXTEDIT_STRING *str, float x, float y)
static int stb_text_locate_coord(IMSTB_TEXTEDIT_STRING *str, float x, float y, int* out_side_on_line)
{
StbTexteditRow r;
int n = STB_TEXTEDIT_STRINGLEN(str);
@@ -417,6 +440,7 @@ static int stb_text_locate_coord(IMSTB_TEXTEDIT_STRING *str, float x, float y)
r.x0 = r.x1 = 0;
r.ymin = r.ymax = 0;
r.num_chars = 0;
*out_side_on_line = 0;
// search rows to find one that straddles 'y'
while (i < n) {
@@ -436,7 +460,10 @@ static int stb_text_locate_coord(IMSTB_TEXTEDIT_STRING *str, float x, float y)
// below all text, return 'after' last character
if (i >= n)
{
*out_side_on_line = 1;
return n;
}
// check if it's before the beginning of the line
if (x < r.x0)
@@ -449,6 +476,7 @@ static int stb_text_locate_coord(IMSTB_TEXTEDIT_STRING *str, float x, float y)
for (k=0; k < r.num_chars; k = IMSTB_TEXTEDIT_GETNEXTCHARINDEX(str, i + k) - i) {
float w = STB_TEXTEDIT_GETWIDTH(str, i, k);
if (x < prev_x+w) {
*out_side_on_line = (k == 0) ? 0 : 1;
if (x < prev_x+w/2)
return k+i;
else
@@ -460,6 +488,7 @@ static int stb_text_locate_coord(IMSTB_TEXTEDIT_STRING *str, float x, float y)
}
// if the last character is a newline, return that. otherwise return 'after' the last character
*out_side_on_line = 1;
if (STB_TEXTEDIT_GETCHAR(str, i+r.num_chars-1) == STB_TEXTEDIT_NEWLINE)
return i+r.num_chars-1;
else
@@ -471,6 +500,7 @@ static void stb_textedit_click(IMSTB_TEXTEDIT_STRING *str, STB_TexteditState *st
{
// In single-line mode, just always make y = 0. This lets the drag keep working if the mouse
// goes off the top or bottom of the text
int side_on_line;
if( state->single_line )
{
StbTexteditRow r;
@@ -478,16 +508,18 @@ static void stb_textedit_click(IMSTB_TEXTEDIT_STRING *str, STB_TexteditState *st
y = r.ymin;
}
state->cursor = stb_text_locate_coord(str, x, y);
state->cursor = stb_text_locate_coord(str, x, y, &side_on_line);
state->select_start = state->cursor;
state->select_end = state->cursor;
state->has_preferred_x = 0;
str->LastMoveDirectionLR = (ImS8)(side_on_line ? ImGuiDir_Right : ImGuiDir_Left);
}
// API drag: on mouse drag, move the cursor and selection endpoint to the clicked location
static void stb_textedit_drag(IMSTB_TEXTEDIT_STRING *str, STB_TexteditState *state, float x, float y)
{
int p = 0;
int side_on_line;
// In single-line mode, just always make y = 0. This lets the drag keep working if the mouse
// goes off the top or bottom of the text
@@ -501,8 +533,9 @@ static void stb_textedit_drag(IMSTB_TEXTEDIT_STRING *str, STB_TexteditState *sta
if (state->select_start == state->select_end)
state->select_start = state->cursor;
p = stb_text_locate_coord(str, x, y);
p = stb_text_locate_coord(str, x, y, &side_on_line);
state->cursor = state->select_end = p;
str->LastMoveDirectionLR = (ImS8)(side_on_line ? ImGuiDir_Right : ImGuiDir_Left);
}
/////////////////////////////////////////////////////////////////////////////
@@ -552,6 +585,8 @@ static void stb_textedit_find_charpos(StbFindState *find, IMSTB_TEXTEDIT_STRING
STB_TEXTEDIT_LAYOUTROW(&r, str, i);
if (n < i + r.num_chars)
break;
if (str->LastMoveDirectionLR == ImGuiDir_Right && str->Stb->cursor > 0 && str->Stb->cursor == i + r.num_chars && STB_TEXTEDIT_GETCHAR(str, i + r.num_chars - 1) != STB_TEXTEDIT_NEWLINE) // [DEAR IMGUI] Wrapping point handling
break;
if (i + r.num_chars == z && z > 0 && STB_TEXTEDIT_GETCHAR(str, z - 1) != STB_TEXTEDIT_NEWLINE) // [DEAR IMGUI] special handling for last line
break; // [DEAR IMGUI]
prev_start = i;
@@ -648,15 +683,33 @@ static void stb_textedit_move_to_last(IMSTB_TEXTEDIT_STRING *str, STB_TexteditSt
}
}
// [DEAR IMGUI]
// Functions must be implemented for UTF8 support
// Code in this file that uses those functions is modified for [DEAR IMGUI] and deviates from the original stb_textedit.
// There is not necessarily a '[DEAR IMGUI]' at the usage sites.
#ifndef IMSTB_TEXTEDIT_GETPREVCHARINDEX
#define IMSTB_TEXTEDIT_GETPREVCHARINDEX(obj, idx) (idx - 1)
// [DEAR IMGUI] Extracted this function so we can more easily add support for word-wrapping.
#ifndef STB_TEXTEDIT_MOVELINESTART
static int stb_textedit_move_line_start(IMSTB_TEXTEDIT_STRING *str, STB_TexteditState *state, int cursor)
{
if (state->single_line)
return 0;
while (cursor > 0) {
int prev = IMSTB_TEXTEDIT_GETPREVCHARINDEX(str, cursor);
if (STB_TEXTEDIT_GETCHAR(str, prev) == STB_TEXTEDIT_NEWLINE)
break;
cursor = prev;
}
return cursor;
}
#define STB_TEXTEDIT_MOVELINESTART stb_textedit_move_line_start
#endif
#ifndef IMSTB_TEXTEDIT_GETNEXTCHARINDEX
#define IMSTB_TEXTEDIT_GETNEXTCHARINDEX(obj, idx) (idx + 1)
#ifndef STB_TEXTEDIT_MOVELINEEND
static int stb_textedit_move_line_end(IMSTB_TEXTEDIT_STRING *str, STB_TexteditState *state, int cursor)
{
int n = STB_TEXTEDIT_STRINGLEN(str);
if (state->single_line)
return n;
while (cursor < n && STB_TEXTEDIT_GETCHAR(str, cursor) != STB_TEXTEDIT_NEWLINE)
cursor = IMSTB_TEXTEDIT_GETNEXTCHARINDEX(str, cursor);
return cursor;
}
#define STB_TEXTEDIT_MOVELINEEND stb_textedit_move_line_end
#endif
#ifdef STB_TEXTEDIT_IS_SPACE
@@ -668,9 +721,9 @@ static int is_word_boundary( IMSTB_TEXTEDIT_STRING *str, int idx )
#ifndef STB_TEXTEDIT_MOVEWORDLEFT
static int stb_textedit_move_to_word_previous( IMSTB_TEXTEDIT_STRING *str, int c )
{
--c; // always move at least one character
while( c >= 0 && !is_word_boundary( str, c ) )
--c;
c = IMSTB_TEXTEDIT_GETPREVCHARINDEX( str, c ); // always move at least one character
while (c >= 0 && !is_word_boundary(str, c))
c = IMSTB_TEXTEDIT_GETPREVCHARINDEX(str, c);
if( c < 0 )
c = 0;
@@ -684,9 +737,9 @@ static int stb_textedit_move_to_word_previous( IMSTB_TEXTEDIT_STRING *str, int c
static int stb_textedit_move_to_word_next( IMSTB_TEXTEDIT_STRING *str, int c )
{
const int len = STB_TEXTEDIT_STRINGLEN(str);
++c; // always move at least one character
c = IMSTB_TEXTEDIT_GETNEXTCHARINDEX(str, c); // always move at least one character
while( c < len && !is_word_boundary( str, c ) )
++c;
c = IMSTB_TEXTEDIT_GETNEXTCHARINDEX(str, c);
if( c > len )
c = len;
@@ -725,7 +778,8 @@ static int stb_textedit_paste_internal(IMSTB_TEXTEDIT_STRING *str, STB_TexteditS
stb_textedit_clamp(str, state);
stb_textedit_delete_selection(str,state);
// try to insert the characters
if (STB_TEXTEDIT_INSERTCHARS(str, state->cursor, text, len)) {
len = STB_TEXTEDIT_INSERTCHARS(str, state->cursor, text, len);
if (len) {
stb_text_makeundo_insert(state, state->cursor, len);
state->cursor += len;
state->has_preferred_x = 0;
@@ -739,6 +793,7 @@ static int stb_textedit_paste_internal(IMSTB_TEXTEDIT_STRING *str, STB_TexteditS
#define STB_TEXTEDIT_KEYTYPE int
#endif
// API key: process text input
// [DEAR IMGUI] Added stb_textedit_text(), extracted out and called by stb_textedit_key() for backward compatibility.
static void stb_textedit_text(IMSTB_TEXTEDIT_STRING* str, STB_TexteditState* state, const IMSTB_TEXTEDIT_CHARTYPE* text, int text_len)
{
@@ -749,14 +804,15 @@ static void stb_textedit_text(IMSTB_TEXTEDIT_STRING* str, STB_TexteditState* sta
if (state->insert_mode && !STB_TEXT_HAS_SELECTION(state) && state->cursor < STB_TEXTEDIT_STRINGLEN(str)) {
stb_text_makeundo_replace(str, state, state->cursor, 1, 1);
STB_TEXTEDIT_DELETECHARS(str, state->cursor, 1);
if (STB_TEXTEDIT_INSERTCHARS(str, state->cursor, text, text_len)) {
text_len = STB_TEXTEDIT_INSERTCHARS(str, state->cursor, text, text_len);
if (text_len) {
state->cursor += text_len;
state->has_preferred_x = 0;
}
}
else {
} else {
stb_textedit_delete_selection(str, state); // implicitly clamps
if (STB_TEXTEDIT_INSERTCHARS(str, state->cursor, text, text_len)) {
text_len = STB_TEXTEDIT_INSERTCHARS(str, state->cursor, text, text_len);
if (text_len) {
stb_text_makeundo_insert(state, state->cursor, text_len);
state->cursor += text_len;
state->has_preferred_x = 0;
@@ -771,6 +827,7 @@ retry:
switch (key) {
default: {
#ifdef STB_TEXTEDIT_KEYTOTEXT
// This is not suitable for UTF-8 support.
int c = STB_TEXTEDIT_KEYTOTEXT(key);
if (c > 0) {
IMSTB_TEXTEDIT_CHARTYPE ch = (IMSTB_TEXTEDIT_CHARTYPE)c;
@@ -911,15 +968,16 @@ retry:
// [DEAR IMGUI]
// going down while being on the last line shouldn't bring us to that line end
if (STB_TEXTEDIT_GETCHAR(str, find.first_char + find.length - 1) != STB_TEXTEDIT_NEWLINE)
break;
//if (STB_TEXTEDIT_GETCHAR(str, find.first_char + find.length - 1) != STB_TEXTEDIT_NEWLINE)
// break;
// now find character position down a row
state->cursor = start;
STB_TEXTEDIT_LAYOUTROW(&row, str, state->cursor);
x = row.x0;
for (i=0; i < row.num_chars; ++i) {
for (i=0; i < row.num_chars; ) {
float dx = STB_TEXTEDIT_GETWIDTH(str, start, i);
int next = IMSTB_TEXTEDIT_GETNEXTCHARINDEX(str, state->cursor);
#ifdef IMSTB_TEXTEDIT_GETWIDTH_NEWLINE
if (dx == IMSTB_TEXTEDIT_GETWIDTH_NEWLINE)
break;
@@ -927,10 +985,13 @@ retry:
x += dx;
if (x > goal_x)
break;
state->cursor = IMSTB_TEXTEDIT_GETNEXTCHARINDEX(str, state->cursor);
i += next - state->cursor;
state->cursor = next;
}
stb_textedit_clamp(str, state);
if (state->cursor == find.first_char + find.length)
str->LastMoveDirectionLR = ImGuiDir_Left;
state->has_preferred_x = 1;
state->preferred_x = goal_x;
@@ -980,8 +1041,9 @@ retry:
state->cursor = find.prev_first;
STB_TEXTEDIT_LAYOUTROW(&row, str, state->cursor);
x = row.x0;
for (i=0; i < row.num_chars; ++i) {
for (i=0; i < row.num_chars; ) {
float dx = STB_TEXTEDIT_GETWIDTH(str, find.prev_first, i);
int next = IMSTB_TEXTEDIT_GETNEXTCHARINDEX(str, state->cursor);
#ifdef IMSTB_TEXTEDIT_GETWIDTH_NEWLINE
if (dx == IMSTB_TEXTEDIT_GETWIDTH_NEWLINE)
break;
@@ -989,10 +1051,15 @@ retry:
x += dx;
if (x > goal_x)
break;
state->cursor = IMSTB_TEXTEDIT_GETNEXTCHARINDEX(str, state->cursor);
i += next - state->cursor;
state->cursor = next;
}
stb_textedit_clamp(str, state);
if (state->cursor == find.first_char)
str->LastMoveDirectionLR = ImGuiDir_Right;
else if (state->cursor == find.prev_first)
str->LastMoveDirectionLR = ImGuiDir_Left;
state->has_preferred_x = 1;
state->preferred_x = goal_x;
@@ -1002,10 +1069,15 @@ retry:
// go to previous line
// (we need to scan previous line the hard way. maybe we could expose this as a new API function?)
prev_scan = find.prev_first > 0 ? find.prev_first - 1 : 0;
while (prev_scan > 0 && STB_TEXTEDIT_GETCHAR(str, prev_scan - 1) != STB_TEXTEDIT_NEWLINE)
--prev_scan;
while (prev_scan > 0)
{
int prev = IMSTB_TEXTEDIT_GETPREVCHARINDEX(str, prev_scan);
if (STB_TEXTEDIT_GETCHAR(str, prev) == STB_TEXTEDIT_NEWLINE)
break;
prev_scan = prev;
}
find.first_char = find.prev_first;
find.prev_first = prev_scan;
find.prev_first = STB_TEXTEDIT_MOVELINESTART(str, state, prev_scan);
}
break;
}
@@ -1079,10 +1151,7 @@ retry:
case STB_TEXTEDIT_K_LINESTART:
stb_textedit_clamp(str, state);
stb_textedit_move_to_first(state);
if (state->single_line)
state->cursor = 0;
else while (state->cursor > 0 && STB_TEXTEDIT_GETCHAR(str, state->cursor-1) != STB_TEXTEDIT_NEWLINE)
--state->cursor;
state->cursor = STB_TEXTEDIT_MOVELINESTART(str, state, state->cursor);
state->has_preferred_x = 0;
break;
@@ -1090,13 +1159,9 @@ retry:
case STB_TEXTEDIT_K_LINEEND2:
#endif
case STB_TEXTEDIT_K_LINEEND: {
int n = STB_TEXTEDIT_STRINGLEN(str);
stb_textedit_clamp(str, state);
stb_textedit_move_to_first(state);
if (state->single_line)
state->cursor = n;
else while (state->cursor < n && STB_TEXTEDIT_GETCHAR(str, state->cursor) != STB_TEXTEDIT_NEWLINE)
++state->cursor;
stb_textedit_move_to_last(str, state);
state->cursor = STB_TEXTEDIT_MOVELINEEND(str, state, state->cursor);
state->has_preferred_x = 0;
break;
}
@@ -1107,10 +1172,7 @@ retry:
case STB_TEXTEDIT_K_LINESTART | STB_TEXTEDIT_K_SHIFT:
stb_textedit_clamp(str, state);
stb_textedit_prep_selection_at_cursor(state);
if (state->single_line)
state->cursor = 0;
else while (state->cursor > 0 && STB_TEXTEDIT_GETCHAR(str, state->cursor-1) != STB_TEXTEDIT_NEWLINE)
--state->cursor;
state->cursor = STB_TEXTEDIT_MOVELINESTART(str, state, state->cursor);
state->select_end = state->cursor;
state->has_preferred_x = 0;
break;
@@ -1119,13 +1181,9 @@ retry:
case STB_TEXTEDIT_K_LINEEND2 | STB_TEXTEDIT_K_SHIFT:
#endif
case STB_TEXTEDIT_K_LINEEND | STB_TEXTEDIT_K_SHIFT: {
int n = STB_TEXTEDIT_STRINGLEN(str);
stb_textedit_clamp(str, state);
stb_textedit_prep_selection_at_cursor(state);
if (state->single_line)
state->cursor = n;
else while (state->cursor < n && STB_TEXTEDIT_GETCHAR(str, state->cursor) != STB_TEXTEDIT_NEWLINE)
++state->cursor;
state->cursor = STB_TEXTEDIT_MOVELINEEND(str, state, state->cursor);
state->select_end = state->cursor;
state->has_preferred_x = 0;
break;
@@ -1300,7 +1358,7 @@ static void stb_text_undo(IMSTB_TEXTEDIT_STRING *str, STB_TexteditState *state)
// check type of recorded action:
if (u.insert_length) {
// easy case: was a deletion, so we need to insert n characters
STB_TEXTEDIT_INSERTCHARS(str, u.where, &s->undo_char[u.char_storage], u.insert_length);
u.insert_length = STB_TEXTEDIT_INSERTCHARS(str, u.where, &s->undo_char[u.char_storage], u.insert_length);
s->undo_char_point -= u.insert_length;
}
@@ -1351,7 +1409,7 @@ static void stb_text_redo(IMSTB_TEXTEDIT_STRING *str, STB_TexteditState *state)
if (r.insert_length) {
// easy case: need to insert n characters
STB_TEXTEDIT_INSERTCHARS(str, r.where, &s->undo_char[r.char_storage], r.insert_length);
r.insert_length = STB_TEXTEDIT_INSERTCHARS(str, r.where, &s->undo_char[r.char_storage], r.insert_length);
s->redo_char_point += r.insert_length;
}
+3 -3
View File
@@ -886,7 +886,7 @@ STBTT_DEF unsigned char *stbtt_GetCodepointBitmap(const stbtt_fontinfo *info, fl
// xoff/yoff are the offset it pixel space from the glyph origin to the top-left of the bitmap
STBTT_DEF unsigned char *stbtt_GetCodepointBitmapSubpixel(const stbtt_fontinfo *info, float scale_x, float scale_y, float shift_x, float shift_y, int codepoint, int *width, int *height, int *xoff, int *yoff);
// the same as stbtt_GetCodepoitnBitmap, but you can specify a subpixel
// the same as stbtt_GetCodepointBitmap, but you can specify a subpixel
// shift for the character
STBTT_DEF void stbtt_MakeCodepointBitmap(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, int codepoint);
@@ -4516,8 +4516,8 @@ static int stbtt__compute_crossings_x(float x, float y, int nverts, stbtt_vertex
q2[0] = (float)x2;
q2[1] = (float)y2;
if (equal(q0,q1) || equal(q1,q2)) {
x0 = (int)verts[i-1].x;
y0 = (int)verts[i-1].y;
x0 = (int)verts[i-1].x; //-V1048
y0 = (int)verts[i-1].y; //-V1048
x1 = (int)verts[i ].x;
y1 = (int)verts[i ].y;
if (y > STBTT_min(y0,y1) && y < STBTT_max(y0,y1) && x > STBTT_min(x0,x1)) {
+4
View File
@@ -65,6 +65,10 @@ namespace games::ccj {
libutils::load_library("libaio-iob2_video.dll");
libutils::load_library("win10actlog.dll");
// we do this to prevent this unexpectedly being unloaded
// which leads to D3D10Warp.dll_unloaded crash
libutils::load_library("d3d10warp.dll");
detour::trampoline_try("execexe.dll", MAKEINTRESOURCE(11),
(void*)execexe_CreateFileW_hook,(void**)&execexe_CreateFileW_orig);
+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);
}
+1
View File
@@ -174,6 +174,7 @@ namespace games::gitadora {
static_assert(sizeof(AIO_IOB5_Y32D) == 0x2e8);
static_assert(sizeof(AIO_IOB2_BI2X_AC1) == 0x4570);
static_assert(sizeof(AIO_IOB2_BI2X_WRFIRM) == 0x20f48);
static_assert(sizeof(AIO_IOB2_BI2X_AC1__DEVSTATUS) == 0x2C8);
/*
* typedefs
+83 -81
View File
@@ -158,100 +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)"},
"Title Average R (Arena)",
"Title Average G (Arena)",
"Title Average B (Arena)",
{"Arena Model", "Title Average R (Arena)"},
{"Arena Model", "Title Average G (Arena)"},
{"Arena Model", "Title Average B (Arena)"},
"Woofer R (Arena)",
"Woofer G (Arena)",
"Woofer B (Arena)",
{"Arena Model", "Woofer R (Arena)"},
{"Arena Model", "Woofer G (Arena)"},
{"Arena Model", "Woofer B (Arena)"},
"Card Reader R (Arena)",
"Card Reader G (Arena)",
"Card Reader B (Arena)"
{"Arena Model", "Card Reader R (Arena)"},
{"Arena Model", "Card Reader G (Arena)"},
{"Arena Model", "Card Reader B (Arena)"}
}
);
}
+7 -7
View File
@@ -1,6 +1,6 @@
#include "camera.h"
#if SPICE64
#if SPICE64 && !SPICE_XP
#include <d3d9.h>
#include "mf_wrappers.h"
@@ -84,7 +84,7 @@ namespace games::iidx {
bool find_camera_hooks() {
std::string dll_name = avs::game::DLL_NAME;
dll_path = MODULE_PATH / dll_name;
dll_path = MODULE_PATH / dll_name;
iidx_module = libutils::try_module(dll_path);
if (!iidx_module) {
@@ -149,7 +149,7 @@ namespace games::iidx {
"E800000000488BC8488BD34883C420",
"X????XXXXXXXXXX",
0, 0));
if (addr_textures_ptr == nullptr) {
log_warning("iidx:camhook", "failed to find hook: addr_textures (part 1)");
return FALSE;
@@ -232,7 +232,7 @@ namespace games::iidx {
}
static void **__fastcall camera_hook_a(PBYTE a1) {
std::call_once(hook_a_init, [&]{
std::call_once(hook_a_init, [&]{
device = *reinterpret_cast<LPDIRECT3DDEVICE9EX*>(a1 + addr_device_offset);
auto const preview = *reinterpret_cast<LPDIRECT3DTEXTURE9**>((uint8_t*)iidx_module + addr_textures);
auto const manager = reinterpret_cast<Camera::CCameraManager2*>((uint8_t*)iidx_module + addr_camera_manager);
@@ -298,11 +298,11 @@ namespace games::iidx {
// Ask for source type = video capture devices.
if (SUCCEEDED(hr)) {
hr = pAttributes->SetGUID(
MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE,
MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE,
MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE_VIDCAP_GUID
);
}
// Enumerate devices.
if (SUCCEEDED(hr)) {
hr = WrappedMFEnumDeviceSources(pAttributes, &ppDevices, &numDevices);
@@ -478,7 +478,7 @@ namespace games::iidx {
init_mf_library();
}
}
return result;
}
+1 -1
View File
@@ -1,6 +1,6 @@
#pragma once
#if SPICE64
#if SPICE64 && !SPICE_XP
#include <vector>
#include "games/iidx/local_camera.h"
+27 -16
View File
@@ -30,6 +30,7 @@
#include "util/memutils.h"
#include "util/sigscan.h"
#include "util/socd_cleaner.h"
#include "util/sysutils.h"
#include "util/time.h"
#include "util/utils.h"
#include "launcher/signal.h"
@@ -82,6 +83,7 @@ namespace games::iidx {
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+
@@ -422,6 +424,14 @@ namespace games::iidx {
init_legacy_camera_hook(FLIP_CAMS);
}
#if SPICE64
if (TDJ_MODE) {
sysutils::hook_EnumDisplayDevicesA();
}
#endif
// init cfgmgr32 hooks
cfgmgr32hook_init(avs::game::DLL_INSTANCE);
}
@@ -453,7 +463,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;
}
@@ -472,7 +482,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"
@@ -485,28 +495,28 @@ namespace games::iidx {
log_fatal("iidx", "BAD MODEL NAME ERROR - TDJ specified, must be LDJ instead");
}
// check -monitor + TDJ mode
// check -dxmainadapter + 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"
"!!! compatibility issues with the game !!!\n"
"\n\n"
"!!! using -dxmainadapter option with TDJ is NOT recommended !!!\n"
"!!! due to known compatibility issues with the game !!!\n"
"!!! !!!\n"
"!!! * game may launch in wrong resolution or refresh rate !!!\n"
"!!! * touch / mouse input may stop working in subscreen / overlay !!!\n"
"!!! !!!\n"
"!!! recommendation is to NOT use -monitor and instead set the !!!\n"
"!!! primary monitor in Windows settings before launching the game !!!\n\n"
"!!! recommendation is to use the Change Main Monitor (-mainmonitor) !!!\n"
"!!! option instead of -dxmainadapter !!!\n\n"
);
deferredlogs::defer_error_messages({
"-monitor option is NOT recommended when running with TDJ mode",
"-dxmainadapter option is NOT recommended when running with TDJ mode",
" due to known compatibility issues with the game:",
" * game may launch in wrong resolution or refresh rate",
" * touch / mouse input may stop working in subscreen / overlay",
" recommended fix is to NOT use -monitor and instead set the primary",
" monitor in Windows settings before launching the game"
" use Change Main Monitor (-mainmonitor) option instead"
});
}
@@ -817,7 +827,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() {
@@ -883,12 +893,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]
@@ -905,8 +915,7 @@ namespace games::iidx {
log_warning(
"iidx",
"Failed to apply ASIO compatibility fix for iidx32+. "
"Unless patches are applied, your ASIO device ({}) may hang or fail to work",
ASIO_DRIVER->c_str());
"Unless patches are applied, your ASIO device may hang or fail to work");
} else {
log_info(
"iidx",
@@ -934,6 +943,8 @@ namespace games::iidx {
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) {
+7 -4
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;
@@ -38,6 +44,7 @@ namespace games::iidx {
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:
@@ -64,9 +71,5 @@ namespace games::iidx {
bool is_tdj_fhd();
void apply_audio_hacks();
enum class iidx_aio_emulation_state {
bi2a_com2,
bi2x_hook
};
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"}
}
);
}
+31 -31
View File
@@ -1,6 +1,6 @@
#include "games/iidx/local_camera.h"
#if SPICE64
#if SPICE64 && !SPICE_XP
#include "util/logging.h"
#include "util/utils.h"
@@ -128,7 +128,7 @@ namespace games::iidx {
// TODO: Color space conversion
// if (SUCCEEDED(hr)) {
// hr = pAttributes->SetUINT32(MF_SOURCE_READER_ENABLE_ADVANCED_VIDEO_PROCESSING, TRUE);
// }
// }
// if (SUCCEEDED(hr)) {
// hr = pAttributes->SetUINT32(MF_READWRITE_DISABLE_CONVERTERS, FALSE);
// }
@@ -189,11 +189,11 @@ namespace games::iidx {
&pType
);
if (FAILED(hr)) {
if (FAILED(hr)) {
if (hr != MF_E_NO_MORE_TYPES) {
log_warning("iidx:camhook", "[{}] Cannot get media type {} {:#x}", m_name, i, (ULONG)hr);
}
break;
break;
}
hr = TryMediaType(pType, &bestWidth, &bestFrameRate);
@@ -246,7 +246,7 @@ namespace games::iidx {
pSelectedMediaType = m_pAutoMediaType;
}
if (SUCCEEDED(hr)) {
if (SUCCEEDED(hr)) {
hr = ChangeMediaType(pSelectedMediaType);
}
@@ -281,7 +281,7 @@ namespace games::iidx {
m_cameraHeight = info.height;
UpdateDrawRect();
}
return hr;
}
@@ -395,7 +395,7 @@ namespace games::iidx {
m_device->ColorFill(m_pDestSurf, &targetRect, D3DCOLOR_XRGB(0, 0, 0));
}
void IIDXLocalCamera::CreateThread() {
// Create thread
m_drawThread = new std::thread([this]() {
@@ -411,7 +411,7 @@ namespace games::iidx {
if (accumulator > 1.0) {
accumulator -= 1.0;
floorFrameTimeMicroSec += 1;
}
}
std::this_thread::sleep_for(std::chrono::microseconds(floorFrameTimeMicroSec));
}
});
@@ -430,12 +430,12 @@ namespace games::iidx {
log_misc("iidx:camhook", "[{}] Init camera control", m_name);
hr = m_pSource->QueryInterface(IID_IAMCameraControl, (void**)&m_pCameraControl);
if (FAILED(hr)) {
// The device does not support IAMCameraControl
hr = m_pSource->QueryInterface(IID_IAMCameraControl, (void**)&m_pCameraControl);
if (FAILED(hr)) {
// The device does not support IAMCameraControl
log_warning("iidx:camhook", "[{}] Camera control not supported", m_name);
return E_FAIL;
}
}
for (size_t i = 0; i < CAMERA_CONTROL_PROP_SIZE; i++) {
long minValue = 0;
@@ -472,9 +472,9 @@ namespace games::iidx {
CameraControlProp prop = m_controlProps.at(i);
log_misc(
"iidx:camhook", "[{}] >> {} range=({}, {}) default={} delta={} dFlags={} value={} vFlags={}",
"iidx:camhook", "[{}] >> {} range=({}, {}) default={} delta={} dFlags={} value={} vFlags={}",
m_name,
CAMERA_CONTROL_LABELS[i],
CAMERA_CONTROL_LABELS[i],
prop.minValue, prop.maxValue,
prop.defaultValue,
prop.delta,
@@ -570,9 +570,9 @@ namespace games::iidx {
// Get frame rate
hr = MFGetAttributeRatio(
pType,
MF_MT_FRAME_RATE,
(UINT32*)&frameRate.Numerator,
pType,
MF_MT_FRAME_RATE,
(UINT32*)&frameRate.Numerator,
(UINT32*)&frameRate.Denominator
);
if (FAILED(hr)) { goto done; }
@@ -580,7 +580,7 @@ namespace games::iidx {
info.frameRate = frameRate.Numerator / frameRate.Denominator;
info.description = fmt::format(
"{}x{} @{}FPS {}",
"{}x{} @{}FPS {}",
info.width,
info.height,
(int)info.frameRate,
@@ -606,7 +606,7 @@ namespace games::iidx {
return "Unknown";
}
/**
/**
* Return values:
* S_OK: this is a "better" media type than the existing one
* S_FALSE: valid media type, but not "better"
@@ -620,17 +620,17 @@ namespace games::iidx {
hr = pType->GetGUID(MF_MT_SUBTYPE, &subtype);
if (FAILED(hr)) {
if (FAILED(hr)) {
log_warning("iidx:camhook", "[{}] Failed to get subtype: {:#x}", m_name, (ULONG)hr);
return hr;
return hr;
}
hr = MFGetAttributeSize(pType, MF_MT_FRAME_SIZE, &width, &height);
if (FAILED(hr)) {
if (FAILED(hr)) {
log_warning("iidx:camhook", "[{}] Failed to get frame size: {:#x}", m_name, (ULONG)hr);
return hr;
}
// Only support format with converters
// TODO: verify conversion support with DXVA
if (subtype != MFVideoFormat_YUY2 && subtype != MFVideoFormat_NV12) {
@@ -639,12 +639,12 @@ namespace games::iidx {
// Frame rate
hr = MFGetAttributeRatio(
pType,
MF_MT_FRAME_RATE,
(UINT32*)&frameRate.Numerator,
pType,
MF_MT_FRAME_RATE,
(UINT32*)&frameRate.Numerator,
(UINT32*)&frameRate.Denominator
);
if (FAILED(hr)) {
if (FAILED(hr)) {
log_warning("iidx:camhook", "[{}] Failed to get frame rate: {:#x}", m_name, (ULONG)hr);
return hr;
}
@@ -667,7 +667,7 @@ namespace games::iidx {
// Check if this format has better resolution / frame rate
if ((width > *pBestWidth) || (width >= (UINT32)TARGET_SURFACE_WIDTH && frameRateValue >= *pBestFrameRate)) {
// log_misc(
// "iidx:camhook", "Better media type {} ({}x{}) @({} FPS)",
// "iidx:camhook", "Better media type {} ({}x{}) @({} FPS)",
// GetVideoFormatName(subtype),
// width,
// height,
@@ -730,7 +730,7 @@ namespace games::iidx {
HRESULT IIDXLocalCamera::FlushDrawCommands() {
IDirect3DQuery9* pEventQuery = nullptr;
// It is necessary to flush the command queue
// It is necessary to flush the command queue
// or the data is not ready for the receiver to read.
// Adapted from : https://msdn.microsoft.com/en-us/library/windows/desktop/bb172234%28v=vs.85%29.aspx
// Also see : http://www.ogre3d.org/forums/viewtopic.php?f=5&t=50486
@@ -784,8 +784,8 @@ namespace games::iidx {
for (int y = 0; y < TARGET_SURFACE_HEIGHT; y++) {
for (int x = 0; x < TARGET_SURFACE_WIDTH; x++) {
memcpy(
pDest + x * pixelSize,
pSrc + (flip_h ? (TARGET_SURFACE_WIDTH - x - 1) : x) * pixelSize,
pDest + x * pixelSize,
pSrc + (flip_h ? (TARGET_SURFACE_WIDTH - x - 1) : x) * pixelSize,
pixelSize
);
}
+2 -2
View File
@@ -1,6 +1,6 @@
#pragma once
#if SPICE64
#if SPICE64 && !SPICE_XP
#include <d3d9.h>
#include <dxva2api.h>
@@ -89,7 +89,7 @@ namespace games::iidx {
protected:
virtual ~IIDXLocalCamera() {};
ULONG m_nRefCount;
LONG m_nRefCount;
CRITICAL_SECTION m_critsec;
std::string m_name;
+50 -47
View File
@@ -51,7 +51,7 @@ namespace games {
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;
static robin_hood::unordered_map<std::string, std::vector<std::string>> file_hints;
static robin_hood::unordered_map<std::string, std::vector<std::vector<std::string>>> file_hints;
static void initialize() {
@@ -68,7 +68,7 @@ namespace games {
buttons_help.insert({ bbc, bbc::get_buttons_help() });
analogs.insert({ bbc, bbc::get_analogs() });
lights.insert({ bbc, bbc::get_lights() });
file_hints[bbc].emplace_back("bsch.dll");
file_hints[bbc].push_back({"bsch.dll"});
// hpm
const std::string hpm("HELLO! Pop'n Music");
@@ -76,32 +76,32 @@ namespace games {
buttons.insert({ hpm, hpm::get_buttons() });
buttons_help.insert({ hpm, hpm::get_buttons_help() });
lights.insert({ hpm, hpm::get_lights() });
file_hints[hpm].emplace_back("popn.dll");
file_hints[hpm].push_back({"popn.dll", "pdh.dll"});
// bs
const std::string bs("Beatstream");
games.push_back(bs);
buttons.insert({ bs, bs::get_buttons() });
lights.insert({ bs, bs::get_lights() });
file_hints[bs].emplace_back("beatstream.dll");
file_hints[bs].emplace_back("beatstream1.dll");
file_hints[bs].emplace_back("beatstream2.dll");
file_hints[bs].push_back({"beatstream.dll"});
file_hints[bs].push_back({"beatstream1.dll"});
file_hints[bs].push_back({"beatstream2.dll"});
// ddr
const std::string ddr("Dance Dance Revolution");
games.push_back(ddr);
buttons.insert({ ddr, ddr::get_buttons() });
lights.insert({ ddr, ddr::get_lights() });
file_hints[ddr].emplace_back("ddr.dll");
file_hints[ddr].emplace_back("mdxja_945.dll");
file_hints[ddr].emplace_back("arkmdxp3.dll");
file_hints[ddr].push_back({"ddr.dll"});
file_hints[ddr].push_back({"mdxja_945.dll"});
file_hints[ddr].push_back({"arkmdxp3.dll"});
// dea
const std::string dea("Dance Evolution");
games.push_back(dea);
buttons.insert({ dea, dea::get_buttons() });
lights.insert({ dea, dea::get_lights() });
file_hints[dea].emplace_back("arkkdm.dll");
file_hints[dea].push_back({"arkkdm.dll"});
// gitadora
const std::string gitadora("GitaDora");
@@ -111,7 +111,7 @@ namespace games {
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");
file_hints[gitadora].push_back({"gdxg.dll"});
// iidx
const std::string iidx("Beatmania IIDX");
@@ -120,7 +120,7 @@ namespace games {
buttons_help.insert({ iidx, iidx::get_buttons_help() });
analogs.insert({ iidx, iidx::get_analogs() });
lights.insert({ iidx, iidx::get_lights() });
file_hints[iidx].emplace_back("bm2dx.dll");
file_hints[iidx].push_back({"bm2dx.dll"});
// jb
const std::string jb("Jubeat");
@@ -128,7 +128,7 @@ namespace games {
buttons.insert({ jb, jb::get_buttons() });
buttons_help.insert({ jb, jb::get_buttons_help() });
lights.insert({ jb, jb::get_lights() });
file_hints[jb].emplace_back("jubeat.dll");
file_hints[jb].push_back({"jubeat.dll"});
// mga
const std::string mga("Metal Gear");
@@ -136,7 +136,7 @@ namespace games {
buttons.insert({ mga, mga::get_buttons() });
analogs.insert({ mga, mga::get_analogs() });
lights.insert({ mga, mga::get_lights() });
file_hints[mga].emplace_back("launch.dll");
file_hints[mga].push_back({"launch.dll"});
// museca
const std::string museca("Museca");
@@ -145,7 +145,7 @@ namespace games {
buttons_help.insert({ museca, museca::get_buttons_help() });
analogs.insert({ museca, museca::get_analogs() });
lights.insert({ museca, museca::get_lights() });
file_hints[museca].emplace_back("museca.dll");
file_hints[museca].push_back({"museca.dll"});
// nost
const std::string nost("Nostalgia");
@@ -154,7 +154,7 @@ namespace games {
buttons_help.insert({ nost, nost::get_buttons_help() });
analogs.insert({ nost, nost::get_analogs() });
lights.insert({ nost, nost::get_lights() });
file_hints[nost].emplace_back("nostalgia.dll");
file_hints[nost].push_back({"nostalgia.dll"});
// popn
const std::string popn("Pop'n Music");
@@ -162,41 +162,42 @@ namespace games {
buttons.insert({ popn, popn::get_buttons() });
buttons_help.insert({ popn, popn::get_buttons_help() });
lights.insert({ popn, popn::get_lights() });
file_hints[popn].emplace_back("popn19.dll");
file_hints[popn].emplace_back("popn20.dll");
file_hints[popn].emplace_back("popn21.dll");
file_hints[popn].emplace_back("popn22.dll");
file_hints[popn].emplace_back("popn23.dll");
file_hints[popn].emplace_back("popn24.dll");
file_hints[popn].emplace_back("popn25.dll");
file_hints[popn].push_back({"popn19.dll"});
file_hints[popn].push_back({"popn20.dll"});
file_hints[popn].push_back({"popn21.dll"});
file_hints[popn].push_back({"popn22.dll"});
file_hints[popn].push_back({"popn23.dll"});
file_hints[popn].push_back({"popn24.dll"});
file_hints[popn].push_back({"popn25.dll"});
file_hints[popn].push_back({"popn.dll", "libaio-iob2_video.dll"});
// qma
const std::string qma("Quiz Magic Academy");
games.push_back(qma);
buttons.insert({ qma, qma::get_buttons() });
lights.insert({ qma, qma::get_lights() });
file_hints[qma].emplace_back("client.dll");
file_hints[qma].push_back({"client.dll"});
// rb
const std::string rb("Reflec Beat");
games.push_back(rb);
buttons.insert({ rb, rb::get_buttons() });
lights.insert({ rb, rb::get_lights() });
file_hints[rb].emplace_back("reflecbeat.dll");
file_hints[rb].push_back({"reflecbeat.dll"});
// shogikai
std::string shogikai("Tenkaichi Shogikai");
games.push_back(shogikai);
buttons.insert({ shogikai, shogikai::get_buttons() });
lights.insert({ shogikai, shogikai::get_lights() });
file_hints[shogikai].emplace_back("shogi_engine.dll");
file_hints[shogikai].push_back({"shogi_engine.dll"});
// rf3d
const std::string rf3d("Road Fighters 3D");
games.push_back(rf3d);
buttons.insert({ rf3d, rf3d::get_buttons() });
analogs.insert({ rf3d, rf3d::get_analogs() });
file_hints[rf3d].emplace_back("jgt.dll");
file_hints[rf3d].push_back({"jgt.dll"});
// sc
const std::string sc("Steel Chronicle");
@@ -204,7 +205,7 @@ namespace games {
buttons.insert({ sc, sc::get_buttons() });
analogs.insert({ sc, sc::get_analogs() });
lights.insert({ sc, sc::get_lights() });
file_hints[sc].emplace_back("gamekgg.dll");
file_hints[sc].push_back({"gamekgg.dll"});
// sdvx
const std::string sdvx("Sound Voltex");
@@ -213,13 +214,13 @@ namespace games {
buttons_help.insert({ sdvx, sdvx::get_buttons_help() });
analogs.insert({ sdvx, sdvx::get_analogs() });
lights.insert({ sdvx, sdvx::get_lights() });
file_hints[sdvx].emplace_back("soundvoltex.dll");
file_hints[sdvx].push_back({"soundvoltex.dll"});
// mfc
const std::string mfc("Mahjong Fight Club");
games.push_back(mfc);
buttons.insert({ mfc, mfc::get_buttons() });
file_hints[mfc].emplace_back("allinone.dll");
file_hints[mfc].push_back({"allinone.dll"});
// ftt
const std::string ftt("FutureTomTom");
@@ -228,21 +229,21 @@ namespace games {
buttons_help.insert({ ftt, ftt::get_buttons_help() });
analogs.insert({ ftt, ftt::get_analogs() });
lights.insert({ ftt, ftt::get_lights() });
file_hints[ftt].emplace_back("arkmmd.dll");
file_hints[ftt].push_back({"arkmmd.dll"});
// loveplus
const std::string loveplus("LovePlus");
games.push_back(loveplus);
buttons.insert({ loveplus, loveplus::get_buttons() });
lights.insert({ loveplus, loveplus::get_lights() });
file_hints[loveplus].emplace_back("arkklp.dll");
file_hints[loveplus].push_back({"arkklp.dll"});
// scotto
const std::string scotto("Scotto");
games.push_back(scotto);
buttons.insert({ scotto, scotto::get_buttons() });
lights.insert({ scotto, scotto::get_lights() });
file_hints[scotto].emplace_back("scotto.dll");
file_hints[scotto].push_back({"scotto.dll"});
// drs
const std::string drs("DANCERUSH");
@@ -250,13 +251,14 @@ namespace games {
buttons.insert({ drs, drs::get_buttons() });
buttons_help.insert({ drs, drs::get_buttons_help() });
lights.insert({ drs, drs::get_lights() });
file_hints[drs].emplace_back("superstep.dll");
file_hints[drs].push_back({"superstep.dll"});
// otoca
const std::string otoca("Otoca D'or");
games.push_back(otoca);
buttons.insert({ otoca, otoca::get_buttons() });
file_hints[otoca].emplace_back("arkkep.dll");
buttons_help.insert({ otoca, otoca::get_buttons_help() });
file_hints[otoca].push_back({"arkkep.dll"});
// winning eleven
const std::string we("Winning Eleven");
@@ -264,27 +266,27 @@ namespace games {
buttons.insert({ we, we::get_buttons() });
analogs.insert({ we, we::get_analogs() });
lights.insert({ we, we::get_lights() });
file_hints[we].emplace_back("weac12_bootstrap_release.dll");
file_hints[we].emplace_back("arknck.dll");
file_hints[we].push_back({"weac12_bootstrap_release.dll"});
file_hints[we].push_back({"arknck.dll"});
// silent scope: bone eater
const std::string silentscope("Silent Scope: Bone Eater");
games.push_back(silentscope);
buttons.insert({ silentscope, silentscope::get_buttons() });
analogs.insert({ silentscope, silentscope::get_analogs() });
file_hints[silentscope].emplace_back("arkndd.dll");
file_hints[silentscope].push_back({"arkndd.dll"});
// charge machine
const std::string pcm("Charge Machine");
games.push_back(pcm);
buttons.insert({ pcm, pcm::get_buttons() });
file_hints[pcm].emplace_back("launch.dll");
file_hints[pcm].push_back({"launch.dll"});
// ongaku paradise
const std::string op("Ongaku Paradise");
games.push_back(op);
buttons.insert({ op, onpara::get_buttons() });
file_hints[op].emplace_back("arkjc9.dll");
file_hints[op].push_back({"arkjc9.dll"});
// bc
const std::string bc("Busou Shinki: Armored Princess Battle Conductor");
@@ -292,7 +294,7 @@ namespace games {
buttons.insert({ bc, bc::get_buttons() });
buttons_help.insert({ bc, bc::get_buttons_help() });
analogs.insert({ bc, bc::get_analogs() });
file_hints[bc].emplace_back("game/bsac_app.exe");
file_hints[bc].push_back({"game/bsac_app.exe"});
// ccj
const std::string ccj("Chase Chase Jokers");
@@ -301,21 +303,21 @@ namespace games {
buttons_help.insert({ ccj, ccj::get_buttons_help() });
analogs.insert({ ccj, ccj::get_analogs() });
lights.insert({ ccj, ccj::get_lights() });
file_hints[ccj].emplace_back("game/chaseproject.exe");
file_hints[ccj].push_back({"game/chaseproject.exe"});
// QuizKnock STADIUM
const std::string qks("QuizKnock STADIUM");
games.push_back(qks);
buttons.insert({ qks, qks::get_buttons() });
buttons_help.insert({ qks, qks::get_buttons_help() });
file_hints[qks].emplace_back("game/uks.exe");
file_hints[qks].push_back({"game/uks.exe"});
// Mahjong Fight Girl
const std::string mfg("Mahjong Fight Girl");
games.push_back(mfg);
buttons.insert({ mfg, mfg::get_buttons() });
buttons_help.insert({ mfg, mfg::get_buttons_help() });
file_hints[mfg].emplace_back("game/MFGClient_Data");
file_hints[mfg].push_back({"game/MFGClient_Data"});
// Polaris Chord
const std::string pc("Polaris Chord");
@@ -324,7 +326,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].push_back({"game/svm.exe"});
}
const std::vector<std::string> &get_games() {
@@ -400,7 +402,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);
@@ -557,7 +560,7 @@ namespace games {
return &it->second;
}
std::vector<std::string> *get_game_file_hints(const std::string &game) {
std::vector<std::vector<std::string>> *get_game_file_hints(const std::string &game) {
initialize();
auto it = file_hints.find(game);
if (it == file_hints.end()) {
+1 -1
View File
@@ -64,5 +64,5 @@ namespace games {
std::vector<Analog> *get_analogs(const std::string &game);
std::vector<Light> *get_lights(const std::string &game);
std::vector<Option> *get_options(const std::string &game);
std::vector<std::string> *get_game_file_hints(const std::string &game);
std::vector<std::vector<std::string>> *get_game_file_hints(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();
+8 -5
View File
@@ -1,3 +1,6 @@
// QueryDisplayConfig
#define _WIN32_WINNT 0x0601
#include "pc.h"
#include <format>
@@ -32,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);
@@ -58,7 +61,7 @@ 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;
}
@@ -98,9 +101,9 @@ namespace games::pc {
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) {
+552
View File
@@ -0,0 +1,552 @@
#include "bi3a_hook.h"
#if SPICE64 && !SPICE_XP
#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 "popn.h"
#include "io.h"
#include "util/tapeled.h"
#include <typeinfo>
#ifdef min
#undef min
#endif
#ifdef max
#undef max
#endif
namespace games::popn {
/*
* class definitions
*/
struct AIO_NMGR_IOB2 {
uint8_t data[0xe18];
};
struct AIO_NMGR_IOB5 {
uint8_t data[0xb10];
};
struct AIO_SCI_COMM {
uint8_t data[0x108];
};
struct AIO_SCI_COMM_T {
uint64_t unk_0;
struct AIO_SCI_COMM *aioSciComm;
};
struct AIO_COMM_STATUS {
uint64_t unk[5];
};
struct AIO_IOB5_BI3A {
uint8_t data[0x4FF0];
};
union AIO_IOB5_BI3A__DEVSTATUS {
uint8_t RawBytes[648];
struct {
uint8_t Reserved0[27]; // 0-26
uint8_t Coin0; // 27
uint8_t Coin1; // 28
uint8_t Reserved1[15]; // 29-43
uint8_t TestButton; // 44, io code offset rcx+0xe4
uint8_t ServiceButton; // 45, io code offset rcx+0xe5
uint8_t CoinMech; // 46, io code offset rcx+0xe6
uint8_t Reserved2[19]; // 47-65
uint8_t JOYL_SW; // 66
uint8_t Reserved3[4]; // 67-70
uint8_t JOYR_SW; // 71
uint8_t Headphones; // 72
uint8_t Recorder; // 73, unused
uint8_t Reserved4[295]; // 74-368
uint8_t Buttons_0_8; // 369
uint8_t Button_9; // 370
} Input;
};
// verified with M39-004-2025121500
static_assert(sizeof(AIO_NMGR_IOB2) == 0xe18);
static_assert(sizeof(AIO_NMGR_IOB5) == 0xb10);
static_assert(sizeof(AIO_SCI_COMM) == 0x108);
static_assert(sizeof(AIO_IOB5_BI3A) == 0x4FF0);
static_assert(sizeof(AIO_IOB5_BI3A__DEVSTATUS) == 0x288);
/*
* typedefs
*/
// 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_BI3A *i_pNodeCtl);
typedef int32_t (__fastcall *aioNodeCtl_GetState_t)(AIO_IOB5_BI3A *i_pNodeCtl);
typedef bool (__fastcall *aioNodeCtl_IsReady_t)(AIO_IOB5_BI3A *i_pNodeCtl, int32_t i_State);
typedef bool (__fastcall *aioNodeCtl_IsError_t)(AIO_IOB5_BI3A *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);
typedef AIO_IOB5_BI3A *(__fastcall *aioIob5Bi3a_Create_t)(AIO_NMGR_IOB5 *i_pNodeMgr, uint8_t i, void *p);
typedef void(__fastcall *aioIob5Bi3a_GetDeviceStatus_t)(AIO_IOB5_BI3A *i_pNodeCtl, AIO_IOB5_BI3A__DEVSTATUS *o_DevStatus);
typedef void(__fastcall *aioIob5Bi3a_SetOutputData_t)(AIO_IOB5_BI3A *a1, unsigned int a2, unsigned __int8 a3);
typedef void(__fastcall *aioIob5Bi3a_SetTapeLedDataPart_t)(AIO_IOB5_BI3A *a1, unsigned int a2, char a3, const void *a4, unsigned int a5, bool a6);
typedef int64_t (__fastcall *aioIob5Bi3a_SetTapeLedDataLimit_t)(AIO_IOB5_BI3A *i_pNodeCtl, uint32_t i_Channel, uint8_t i_Scale, uint8_t i_Limit);
/*
* function pointers
*/
// 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;
static aioIob5Bi3a_Create_t aioIob5Bi3a_Create_orig = nullptr;
static aioIob5Bi3a_GetDeviceStatus_t aioIob5Bi3a_GetDeviceStatus_orig = nullptr;
static aioIob5Bi3a_SetOutputData_t aioIob5Bi3a_SetOutputData_orig = nullptr;
static aioIob5Bi3a_SetTapeLedDataPart_t aioIob5Bi3a_SetTapeLedDataPart_orig = nullptr;
static aioIob5Bi3a_SetTapeLedDataLimit_t aioIob5Bi3a_SetTapeLedDataLimit_orig = nullptr;
/*
* variables
*/
static AIO_NMGR_IOB2 *aioNmgrIob2;
static AIO_NMGR_IOB5 *aioNmgrIob5;
static AIO_SCI_COMM *aioSciComm;
static AIO_IOB5_BI3A *aioIob5Bi3a;
/*
* implementations
*/
// 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 *)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 *)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 *)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 *)aioNmgrIob2) {
return false;
} else {
return aioNodeMgr_IsError_orig(i_pNodeMgr, i_State);
}
}
static void __fastcall aioNodeCtl_Destroy(AIO_IOB5_BI3A *i_pNodeCtl) {
if (i_pNodeCtl != aioIob5Bi3a) {
return aioNodeCtl_Destroy_orig(i_pNodeCtl);
}
delete aioIob5Bi3a;
aioIob5Bi3a = nullptr;
}
static int32_t __fastcall aioNodeCtl_GetState(AIO_IOB5_BI3A *i_pNodeCtl) {
if (i_pNodeCtl == aioIob5Bi3a) {
return 1;
}
return aioNodeCtl_GetState_orig(i_pNodeCtl);
}
static bool __fastcall aioNodeCtl_IsReady(AIO_IOB5_BI3A *i_pNodeCtl, int32_t i_State) {
if (i_pNodeCtl == aioIob5Bi3a) {
return true;
}
return aioNodeCtl_IsReady_orig(i_pNodeCtl, i_State);
}
static bool __fastcall aioNodeCtl_IsError(AIO_IOB5_BI3A *i_pNodeCtl, int32_t i_State) {
if (i_pNodeCtl == aioIob5Bi3a) {
return false;
}
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;
log_info("bi3a_hook", "aioNMgrIob5_Create called, returning custom AIO_NMGR_IOB5: {}", fmt::ptr(aioNmgrIob5));
return aioNmgrIob5;
} else {
return aioNMgrIob5_Create_orig(i_pSci, i_bfMode);
}
}
static AIO_IOB5_BI3A *__fastcall aioIob5Bi3a_Create(AIO_NMGR_IOB5 *i_pNodeMgr, uint8_t i, void *p) {
log_info("bi3a_hook", "aioIob5Bi3a_Create called with i_pNodeMgr={}, i={}, p={}", fmt::ptr(i_pNodeMgr), i, fmt::ptr(p));
if (i_pNodeMgr == aioNmgrIob5) {
aioIob5Bi3a = new AIO_IOB5_BI3A;
log_info("bi3a_hook", "aioIob5Bi3a_Create: returning custom AIO_IOB5_BI3A: {}", fmt::ptr(aioIob5Bi3a));
return aioIob5Bi3a;
} else {
return aioIob5Bi3a_Create_orig(i_pNodeMgr, i, p);
}
}
static void __fastcall aioIob5Bi3a_GetDeviceStatus(AIO_IOB5_BI3A *i_pNodeCtl, AIO_IOB5_BI3A__DEVSTATUS *o_DevStatus) {
RI_MGR->devices_flush_output();
if (i_pNodeCtl != aioIob5Bi3a) {
return aioIob5Bi3a_GetDeviceStatus_orig(i_pNodeCtl, o_DevStatus);
}
memset(o_DevStatus, 0, sizeof(*o_DevStatus));
auto &buttons = get_buttons();
// operator
if (GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::Test])) {
o_DevStatus->Input.TestButton = 1;
}
if (GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::Service])) {
o_DevStatus->Input.ServiceButton = 1;
}
if (GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::CoinMech])) {
o_DevStatus->Input.CoinMech = 1;
}
// buttons 1-9
if (GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::Button1])) {
o_DevStatus->Input.Buttons_0_8 |= (1 << 0);
}
if (GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::Button2])) {
o_DevStatus->Input.Buttons_0_8 |= (1 << 1);
}
if (GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::Button3])) {
o_DevStatus->Input.Buttons_0_8 |= (1 << 2);
}
if (GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::Button4])) {
o_DevStatus->Input.Buttons_0_8 |= (1 << 3);
}
if (GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::Button5])) {
o_DevStatus->Input.Buttons_0_8 |= (1 << 4);
}
if (GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::Button6])) {
o_DevStatus->Input.Buttons_0_8 |= (1 << 5);
}
if (GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::Button7])) {
o_DevStatus->Input.Buttons_0_8 |= (1 << 6);
}
if (GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::Button8])) {
o_DevStatus->Input.Buttons_0_8 |= (1 << 7);
}
if (GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::Button9])) {
o_DevStatus->Input.Button_9 = 1;
}
// deka pop-kuns
if (GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::RedPop])) {
o_DevStatus->Input.JOYL_SW = 1;
}
if (GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::BluePop])) {
o_DevStatus->Input.JOYR_SW = 1;
}
// audio
if (GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::Headphone])) {
o_DevStatus->Input.Headphones = 1;
}
// coin
o_DevStatus->Input.Coin0 += eamuse_coin_get_stock();
}
static void __fastcall aioIob5Bi3a_SetOutputData(AIO_IOB5_BI3A *i_pNodeCtl, uint32_t i_CnPin, uint8_t i_Data) {
if (i_pNodeCtl != aioIob5Bi3a) {
return aioIob5Bi3a_SetOutputData_orig(i_pNodeCtl, i_CnPin, i_Data);
}
// 7 = IC_CARD_R
// 8 = IC_CARD_G
// 9 = IC_CARD_B
// 10 = WOOFER_R
// 11 = WOOFER_G
// 12 = WOOFER_B
// values = 0 to 255 in test menu
std::optional<Lights::popn_lights_t> light;
switch (i_CnPin) {
case 7:
light = Lights::IC_Card_R;
break;
case 8:
light = Lights::IC_Card_G;
break;
case 9:
light = Lights::IC_Card_B;
break;
case 10:
light = Lights::WooferLED_R;
break;
case 11:
light = Lights::WooferLED_G;
break;
case 12:
light = Lights::WooferLED_B;
break;
default:
break;
}
if (light.has_value()) {
auto &lights = get_lights();
GameAPI::Lights::writeLight(RI_MGR, lights.at(light.value()), i_Data / 255.f);
}
return;
}
static int64_t __fastcall aioIob5Bi3a_SetTapeLedDataLimit(
AIO_IOB5_BI3A *i_pNodeCtl, uint32_t i_Channel, uint8_t i_Scale, uint8_t i_Limit) {
if (i_pNodeCtl != aioIob5Bi3a) {
return aioIob5Bi3a_SetTapeLedDataLimit_orig(i_pNodeCtl, i_Channel, i_Scale, i_Limit);
}
return 0;
}
static void __fastcall aioIob5Bi3a_SetTapeLedDataPart(
AIO_IOB5_BI3A *i_pNodeCtl, uint32_t i_CnPin, char i_LedType, const void *i_pData, uint32_t i_DataSize, bool i_bIsLast) {
if (i_pNodeCtl != aioIob5Bi3a) {
return aioIob5Bi3a_SetTapeLedDataPart_orig(i_pNodeCtl, i_CnPin, i_LedType, i_pData, i_DataSize, i_bIsLast);
}
auto number_of_leds = i_DataSize;
// buttons 1-9, but game passes in wrong length; fix it up
if (i_CnPin == 0 && i_DataSize == 9) {
number_of_leds = i_DataSize * 3;
}
if (FALSE) {
std::string data;
for (uint32_t i = 0; i < number_of_leds; i++) {
auto dataByte = ((uint8_t *)i_pData)[i];
data += fmt::format("{:02X} ", dataByte);
}
log_info(
"bi3a_hook",
"aioIob5Bi3a_SetTapeLedDataPart Pin={}, Size={}, Type={}, data@{} = {}",
i_CnPin, number_of_leds, (uint8_t)i_LedType, fmt::ptr(i_pData), data);
}
auto &lights = get_lights();
if (i_CnPin == 0 && number_of_leds == 9 * 3) {
// special handling converting to non-RGB lights
// take the max(R, G, B) and use it to write the light value
uint8_t light_value[9] = { 0 };
for (uint32_t i = 0; i < number_of_leds; i += 3) {
light_value[i / 3] =
std::max({ ((uint8_t *)i_pData)[i], ((uint8_t *)i_pData)[i + 1], ((uint8_t *)i_pData)[i + 2] });
}
constexpr Lights::popn_lights_t legacy_button_lights[] = {
Lights::popn_lights_t::Button1,
Lights::popn_lights_t::Button2,
Lights::popn_lights_t::Button3,
Lights::popn_lights_t::Button4,
Lights::popn_lights_t::Button5,
Lights::popn_lights_t::Button6,
Lights::popn_lights_t::Button7,
Lights::popn_lights_t::Button8,
Lights::popn_lights_t::Button9
};
static_assert(std::size(legacy_button_lights) == 9);
for (size_t light = 0; light < 9; light++) {
// on the new cab, buttons are colorless plastic and they rely on RGB to be lit at all times, even when "off"
// when translating to legacy on/off lights, treat anything above ~60% brightness as fully on,
// otherwise off, to avoid dimly lit "off" state
GameAPI::Lights::writeLight(
RI_MGR,
lights.at(legacy_button_lights[light]),
light_value[light] > 150 ? 1.f : 0.f);
}
// color buttons
constexpr Lights::popn_lights_t button_lights[] = {
Lights::popn_lights_t::PikaButton1_R, Lights::popn_lights_t::PikaButton1_G, Lights::popn_lights_t::PikaButton1_B,
Lights::popn_lights_t::PikaButton2_R, Lights::popn_lights_t::PikaButton2_G, Lights::popn_lights_t::PikaButton2_B,
Lights::popn_lights_t::PikaButton3_R, Lights::popn_lights_t::PikaButton3_G, Lights::popn_lights_t::PikaButton3_B,
Lights::popn_lights_t::PikaButton4_R, Lights::popn_lights_t::PikaButton4_G, Lights::popn_lights_t::PikaButton4_B,
Lights::popn_lights_t::PikaButton5_R, Lights::popn_lights_t::PikaButton5_G, Lights::popn_lights_t::PikaButton5_B,
Lights::popn_lights_t::PikaButton6_R, Lights::popn_lights_t::PikaButton6_G, Lights::popn_lights_t::PikaButton6_B,
Lights::popn_lights_t::PikaButton7_R, Lights::popn_lights_t::PikaButton7_G, Lights::popn_lights_t::PikaButton7_B,
Lights::popn_lights_t::PikaButton8_R, Lights::popn_lights_t::PikaButton8_G, Lights::popn_lights_t::PikaButton8_B,
Lights::popn_lights_t::PikaButton9_R, Lights::popn_lights_t::PikaButton9_G, Lights::popn_lights_t::PikaButton9_B
};
static_assert(std::size(button_lights) == 9 * 3);
for (size_t light = 0; light < number_of_leds; light++) {
GameAPI::Lights::writeLight(
RI_MGR,
lights.at(button_lights[light]),
((uint8_t *)i_pData)[light] / 255.f);
}
}
if (tapeledutils::is_enabled() && i_CnPin > 0 && i_CnPin < std::size(TAPELED_MAPPING)) {
auto &map = TAPELED_MAPPING[i_CnPin];
const auto data_size = std::min(map.data.capacity(), (size_t)number_of_leds / 3);
// pick a color to use
const auto rgb = tapeledutils::pick_color_from_led_tape(map, (uint8_t *)i_pData, data_size);
// program the lights into API
auto &lights = get_lights();
GameAPI::Lights::writeLight(RI_MGR, lights[map.index_r], rgb.r);
GameAPI::Lights::writeLight(RI_MGR, lights[map.index_g], rgb.g);
GameAPI::Lights::writeLight(RI_MGR, lights[map.index_b], rgb.b);
// tape LED output over API not implemented
// for (size_t i = 0; i < data_size; i++) {
// map.data[i].r = ((uint8_t *)i_pData)[i * 3];
// map.data[i].g = ((uint8_t *)i_pData)[i * 3 + 1];
// map.data[i].b = ((uint8_t *)i_pData)[i * 3 + 2];
// }
}
}
void bi3a_hook_init() {
// avoid double init
static bool initialized = false;
if (initialized) {
return;
} else {
initialized = true;
}
// announce
log_info("bi3a_hook", "init");
// libaio-iob5.dll
const auto libaioIob5Dll = "libaio-iob5.dll";
detour::trampoline_try(libaioIob5Dll, "aioNMgrIob5_Create",
aioNMgrIob5_Create, &aioNMgrIob5_Create_orig);
detour::trampoline_try(libaioIob5Dll, "aioIob5Bi3a_Create",
aioIob5Bi3a_Create, &aioIob5Bi3a_Create_orig);
detour::trampoline_try(libaioIob5Dll, "aioIob5Bi3a_GetDeviceStatus",
aioIob5Bi3a_GetDeviceStatus, &aioIob5Bi3a_GetDeviceStatus_orig);
detour::trampoline_try(libaioIob5Dll, "aioIob5Bi3a_SetOutputData",
aioIob5Bi3a_SetOutputData, &aioIob5Bi3a_SetOutputData_orig);
detour::trampoline_try(libaioIob5Dll, "aioIob5Bi3a_SetTapeLedDataPart",
aioIob5Bi3a_SetTapeLedDataPart, &aioIob5Bi3a_SetTapeLedDataPart_orig);
detour::trampoline_try(libaioIob5Dll, "aioIob5Bi3a_SetTapeLedDataLimit",
aioIob5Bi3a_SetTapeLedDataLimit, &aioIob5Bi3a_SetTapeLedDataLimit_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@I@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 && !SPICE_XP
namespace games::popn {
void bi3a_hook_init();
}
#endif
+128 -78
View File
@@ -11,37 +11,7 @@ std::vector<Button> &games::popn::get_buttons() {
"Service",
"Test",
"Coin Mech",
"Button 1",
"Button 2",
"Button 3",
"Button 4",
"Button 5",
"Button 6",
"Button 7",
"Button 8",
"Button 9"
);
}
return buttons;
}
std::string games::popn::get_buttons_help() {
// keep to max 100 characters wide
return
" 2 4 6 8 \n"
" 1 3 5 7 9 "
;
}
std::vector<Light> &games::popn::get_lights() {
static std::vector<Light> lights;
if (lights.empty()) {
lights = GameAPI::Lights::getLights("Pop'n Music");
GameAPI::Lights::sortLights(
&lights,
"Headphones",
"Button 1",
"Button 2",
"Button 3",
@@ -51,55 +21,135 @@ std::vector<Light> &games::popn::get_lights() {
"Button 7",
"Button 8",
"Button 9",
"Top LED 1",
"Top LED 2",
"Top LED 3",
"Top LED 4",
"Top LED 5",
"Top LED 6",
"Top LED 7",
"Top LED 8",
"Top LED 9",
"Top LED 10",
"Top LED 11",
"Top LED 12",
"Top LED 13",
"Top LED 14",
"Top LED 15",
"Top LED 16",
"Top LED 17",
"Top LED 18",
"Top LED 19",
"Top LED 20",
"Top LED 21",
"Top LED 22",
"Top LED 23",
"Top LED 24",
"Top LED 25",
"Top LED 26",
"Top LED 27",
"Top LED 28",
"Top LED 29",
"Top LED 30",
"Top LED 31",
"Top LED 32",
"Top LED R",
"Top LED G",
"Top LED B",
"Hi Lamp 1",
"Hi Lamp 2",
"Hi Lamp 3",
"Hi Lamp 4",
"Hi Lamp 5",
"Left Lamp 1",
"Left Lamp 2",
"Right Lamp 1",
"Right Lamp 2",
"Woofer LED R",
"Woofer LED G",
"Woofer LED B"
"Red Pop-Kun",
"Blue Pop-Kun"
);
}
return buttons;
}
std::string games::popn::get_buttons_help() {
// keep to max 100 characters wide
return
"RED 2 4 6 8 BLUE\n"
" 1 3 5 7 9"
;
}
std::vector<Light> &games::popn::get_lights() {
static std::vector<Light> lights;
if (lights.empty()) {
lights = GameAPI::Lights::getLights("Pop'n Music");
GameAPI::Lights::sortLightsWithCategory(&lights, {
{"Common", "Button 1"},
{"Common", "Button 2"},
{"Common", "Button 3"},
{"Common", "Button 4"},
{"Common", "Button 5"},
{"Common", "Button 6"},
{"Common", "Button 7"},
{"Common", "Button 8"},
{"Common", "Button 9"},
{"SD", "Hi Lamp 1"},
{"SD", "Hi Lamp 2"},
{"SD", "Hi Lamp 3"},
{"SD", "Hi Lamp 4"},
{"SD", "Hi Lamp 5"},
{"SD", "Left Lamp 1"},
{"SD", "Left Lamp 2"},
{"SD", "Right Lamp 1"},
{"SD", "Right Lamp 2"},
{"HD", "Top LED 1"},
{"HD", "Top LED 2"},
{"HD", "Top LED 3"},
{"HD", "Top LED 4"},
{"HD", "Top LED 5"},
{"HD", "Top LED 6"},
{"HD", "Top LED 7"},
{"HD", "Top LED 8"},
{"HD", "Top LED 9"},
{"HD", "Top LED 10"},
{"HD", "Top LED 11"},
{"HD", "Top LED 12"},
{"HD", "Top LED 13"},
{"HD", "Top LED 14"},
{"HD", "Top LED 15"},
{"HD", "Top LED 16"},
{"HD", "Top LED 17"},
{"HD", "Top LED 18"},
{"HD", "Top LED 19"},
{"HD", "Top LED 20"},
{"HD", "Top LED 21"},
{"HD", "Top LED 22"},
{"HD", "Top LED 23"},
{"HD", "Top LED 24"},
{"HD", "Top LED 25"},
{"HD", "Top LED 26"},
{"HD", "Top LED 27"},
{"HD", "Top LED 28"},
{"HD", "Top LED 29"},
{"HD", "Top LED 30"},
{"HD", "Top LED 31"},
{"HD", "Top LED 32"},
{"HD/Pika", "Top LED R"},
{"HD/Pika", "Top LED G"},
{"HD/Pika", "Top LED B"},
{"HD/Pika", "Woofer LED R"},
{"HD/Pika", "Woofer LED G"},
{"HD/Pika", "Woofer LED B"},
{"Pika", "Button 1 R"},
{"Pika", "Button 1 G"},
{"Pika", "Button 1 B"},
{"Pika", "Button 2 R"},
{"Pika", "Button 2 G"},
{"Pika", "Button 2 B"},
{"Pika", "Button 3 R"},
{"Pika", "Button 3 G"},
{"Pika", "Button 3 B"},
{"Pika", "Button 4 R"},
{"Pika", "Button 4 G"},
{"Pika", "Button 4 B"},
{"Pika", "Button 5 R"},
{"Pika", "Button 5 G"},
{"Pika", "Button 5 B"},
{"Pika", "Button 6 R"},
{"Pika", "Button 6 G"},
{"Pika", "Button 6 B"},
{"Pika", "Button 7 R"},
{"Pika", "Button 7 G"},
{"Pika", "Button 7 B"},
{"Pika", "Button 8 R"},
{"Pika", "Button 8 G"},
{"Pika", "Button 8 B"},
{"Pika", "Button 9 R"},
{"Pika", "Button 9 G"},
{"Pika", "Button 9 B"},
{"Pika", "Red Pop-Kun R"},
{"Pika", "Red Pop-Kun G"},
{"Pika", "Red Pop-Kun B"},
{"Pika", "Blue Pop-Kun R"},
{"Pika", "Blue Pop-Kun G"},
{"Pika", "Blue Pop-Kun B"},
{"Pika", "IC Card R"},
{"Pika", "IC Card G"},
{"Pika", "IC Card B"},
{"Pika", "Speakers R"},
{"Pika", "Speakers G"},
{"Pika", "Speakers B"},
{"Pika", "Monitor R"},
{"Pika", "Monitor G"},
{"Pika", "Monitor B"},
{"Pika", "Control Panel R"},
{"Pika", "Control Panel G"},
{"Pika", "Control Panel B"},
{"Pika", "Cabinet R"},
{"Pika", "Cabinet G"},
{"Pika", "Cabinet B"},
});
}
return lights;
}
+62 -11
View File
@@ -11,6 +11,7 @@ namespace games::popn {
Service,
Test,
CoinMech,
Headphone,
Button1,
Button2,
Button3,
@@ -20,12 +21,14 @@ namespace games::popn {
Button7,
Button8,
Button9,
RedPop,
BluePop
};
}
// all lights in correct order
namespace Lights {
enum {
typedef enum {
Button1,
Button2,
Button3,
@@ -35,6 +38,15 @@ namespace games::popn {
Button7,
Button8,
Button9,
HiLamp1,
HiLamp2,
HiLamp3,
HiLamp4,
HiLamp5,
LeftLamp1,
LeftLamp2,
RightLamp1,
RightLamp2,
TopLED1,
TopLED2,
TopLED3,
@@ -70,19 +82,58 @@ namespace games::popn {
TopLED_R,
TopLED_G,
TopLED_B,
HiLamp1,
HiLamp2,
HiLamp3,
HiLamp4,
HiLamp5,
LeftLamp1,
LeftLamp2,
RightLamp1,
RightLamp2,
WooferLED_R,
WooferLED_G,
WooferLED_B,
};
PikaButton1_R,
PikaButton1_G,
PikaButton1_B,
PikaButton2_R,
PikaButton2_G,
PikaButton2_B,
PikaButton3_R,
PikaButton3_G,
PikaButton3_B,
PikaButton4_R,
PikaButton4_G,
PikaButton4_B,
PikaButton5_R,
PikaButton5_G,
PikaButton5_B,
PikaButton6_R,
PikaButton6_G,
PikaButton6_B,
PikaButton7_R,
PikaButton7_G,
PikaButton7_B,
PikaButton8_R,
PikaButton8_G,
PikaButton8_B,
PikaButton9_R,
PikaButton9_G,
PikaButton9_B,
RedPopKun_R,
RedPopKun_G,
RedPopKun_B,
BluePopKun_R,
BluePopKun_G,
BluePopKun_B,
IC_Card_R,
IC_Card_G,
IC_Card_B,
Speaker_R,
Speaker_G,
Speaker_B,
Monitor_R,
Monitor_G,
Monitor_B,
ControlPanel_R,
ControlPanel_G,
ControlPanel_B,
Cabinet_R,
Cabinet_G,
Cabinet_B,
} popn_lights_t;
}
// getters
+409
View File
@@ -1,7 +1,9 @@
#include "avs/game.h"
#include "popn.h"
#include "bi3a_hook.h"
#include <cstdint>
#include <cstring>
#include "hooks/graphics/graphics.h"
#include "rawinput/rawinput.h"
#include "util/detour.h"
#include "util/fileutils.h"
@@ -9,14 +11,327 @@
#include "util/utils.h"
#include "cfg/button.h"
#include "cfg/api.h"
#include "hooks/setupapihook.h"
#include "hooks/sleephook.h"
#include "launcher/launcher.h"
#include "launcher/logger.h"
#include "misc/eamuse.h"
#include "util/sysutils.h"
#include "io.h"
#include "util/deferlog.h"
#include "misc/nativetouchhook.h"
#include "misc/wintouchemu.h"
namespace games::popn {
bool SHOW_PIKA_MONITOR_WARNING = false;
bool NATIVE_TOUCH = false;
#if SPICE64 && !SPICE_XP
const HMONITOR FAKE_MONITOR_HMONITOR = (HMONITOR)-1;
constexpr LONG FAKE_MONITOR_OFFSET_X_Y = -999999;
constexpr LONG FAKE_MONITOR_WIDTH = 1280;
constexpr LONG FAKE_MONITOR_HEIGHT = 800;
// pin 0 = buttons 1-3, size 9 (but it's actually triple that in reality - game passes in wrong length), values 0-0xFF
// pin 1 = left pop (red pop kun), size 16, values 0-0xFF
// pin 2 = right pop (blue pop kun), size 16, values 0-0xFF
// pin 3 = title, size 44, values 0-0xFF
// pin 4 = speaker, size 40, values 0-0x7F - U region only
// pin 5 = monitor, size 18, values 0-0x7F - U region only
// pin 6 = control panel, size 28, values 0-0x7F - U region only
// pin 7 = cabinet, size 22, values 0-0x7F - U region only
tapeledutils::tape_led TAPELED_MAPPING[POPN_TAPELED_TOTAL] = {
{
// [0] button lights: these are button lights and handled specially
0,
0,
0,
0,
0,
"Invalid"
},
{
// [1] red pop-kun
5, // 5*3; game reports 16 but make it 15
Lights::popn_lights_t::RedPopKun_R,
Lights::popn_lights_t::RedPopKun_G,
Lights::popn_lights_t::RedPopKun_B,
0xFF,
"Red Pop-Kun"
},
{
// [2] blue pop-kun
5, // 5*3; game reports 16 but make it 15
Lights::popn_lights_t::BluePopKun_R,
Lights::popn_lights_t::BluePopKun_G,
Lights::popn_lights_t::BluePopKun_B,
0xFF,
"Blue Pop-Kun"
},
{
// [3] top LED
14, // 14*3; game reports 44 but make it 42
Lights::popn_lights_t::TopLED_R,
Lights::popn_lights_t::TopLED_G,
Lights::popn_lights_t::TopLED_B,
0xFF,
"Top"
},
{
// [4] speakers
13, // 13*3; game reports 40 but make it 39
Lights::popn_lights_t::Speaker_G,
Lights::popn_lights_t::Speaker_R,
Lights::popn_lights_t::Speaker_B,
0x7F,
"Speakers"
},
{
// [5] monitor
6,
Lights::popn_lights_t::Monitor_G,
Lights::popn_lights_t::Monitor_R,
Lights::popn_lights_t::Monitor_B,
0x7F,
"Monitor"
},
{
// [6] control panel
9, // 9*3; game reports 28
Lights::popn_lights_t::ControlPanel_G,
Lights::popn_lights_t::ControlPanel_R,
Lights::popn_lights_t::ControlPanel_B,
0x7F,
"Control Panel"
},
{
// [7] Cabinet
7, // 7*3; game reports 22
Lights::popn_lights_t::Cabinet_G,
Lights::popn_lights_t::Cabinet_R,
Lights::popn_lights_t::Cabinet_B,
0x7F,
"Cabinet"
},
};
static decltype(GetDisplayConfigBufferSizes) *GetDisplayConfigBufferSizes_orig = nullptr;
static decltype(QueryDisplayConfig) *QueryDisplayConfig_orig = nullptr;
static decltype(DisplayConfigGetDeviceInfo) *DisplayConfigGetDeviceInfo_orig = nullptr;
static UINT32 pNumPathArrayElements_original = 0;
static UINT32 pNumModeInfoArrayElements_original = 0;
static
LONG
WINAPI
GetDisplayConfigBufferSizes_hook(
UINT32 Flags,
UINT32 *pNumPathArrayElements,
UINT32 *pNumModeInfoArrayElements)
{
const auto ret = GetDisplayConfigBufferSizes_orig(Flags, pNumPathArrayElements, pNumModeInfoArrayElements);
if (FAKE_SUBSCREEN_ADAPTER) {
log_misc("popn", "GetDisplayConfigBufferSizes returning fake monitor paths and modes");
pNumPathArrayElements_original = *pNumPathArrayElements;
pNumModeInfoArrayElements_original = *pNumModeInfoArrayElements;
*pNumPathArrayElements += 1;
*pNumModeInfoArrayElements += 2;
}
return ret;
}
static
LONG
WINAPI
QueryDisplayConfig_hook(
UINT32 flags,
UINT32* numPathArrayElements,
DISPLAYCONFIG_PATH_INFO* pathArray,
UINT32* numModeInfoArrayElements,
DISPLAYCONFIG_MODE_INFO* modeInfoArray,
DISPLAYCONFIG_TOPOLOGY_ID* currentTopologyId)
{
if (!FAKE_SUBSCREEN_ADAPTER) {
return QueryDisplayConfig_orig(
flags,
numPathArrayElements, pathArray,
numModeInfoArrayElements, modeInfoArray,
currentTopologyId);
}
// call original to fill in real monitor info
UINT32 num_paths = pNumPathArrayElements_original;
UINT32 num_modes = pNumModeInfoArrayElements_original;
const auto ret = QueryDisplayConfig_orig(
flags,
&num_paths, pathArray,
&num_modes, modeInfoArray,
currentTopologyId);
if (ret != ERROR_SUCCESS) {
log_warning("popn", "QueryDisplayConfig failed with error code {}", ret);
return ret;
}
log_misc("popn", "QueryDisplayConfig returning fake monitor paths and modes");
// insert a fake path
DISPLAYCONFIG_PATH_INFO *path = &pathArray[pNumPathArrayElements_original];
*path = {};
path->flags = DISPLAYCONFIG_PATH_ACTIVE;
path->sourceInfo.adapterId.HighPart = -1;
path->sourceInfo.adapterId.LowPart = -1;
path->sourceInfo.id = -1;
path->sourceInfo.modeInfoIdx = pNumModeInfoArrayElements_original;
path->sourceInfo.statusFlags = DISPLAYCONFIG_SOURCE_IN_USE;
path->targetInfo.adapterId.HighPart = -1;
path->targetInfo.adapterId.LowPart = -1;
path->targetInfo.id = -2;
path->targetInfo.modeInfoIdx = pNumModeInfoArrayElements_original + 1;
path->targetInfo.outputTechnology = DISPLAYCONFIG_OUTPUT_TECHNOLOGY_HDMI;
path->targetInfo.rotation = DISPLAYCONFIG_ROTATION_IDENTITY;
path->targetInfo.scaling = DISPLAYCONFIG_SCALING_IDENTITY;
path->targetInfo.refreshRate.Numerator = 60000;
path->targetInfo.refreshRate.Denominator = 1000;
path->targetInfo.scanLineOrdering = DISPLAYCONFIG_SCANLINE_ORDERING_PROGRESSIVE;
path->targetInfo.targetAvailable = true;
path->targetInfo.statusFlags = DISPLAYCONFIG_TARGET_IN_USE;
// insert fake mode source
DISPLAYCONFIG_MODE_INFO *mode_source = &modeInfoArray[pNumModeInfoArrayElements_original];
*mode_source = {};
mode_source->infoType = DISPLAYCONFIG_MODE_INFO_TYPE_SOURCE;
mode_source->id = -1;
mode_source->adapterId.HighPart = -1;
mode_source->adapterId.LowPart = -1;
mode_source->sourceMode.width = FAKE_MONITOR_WIDTH;
mode_source->sourceMode.height = FAKE_MONITOR_HEIGHT;
mode_source->sourceMode.pixelFormat = DISPLAYCONFIG_PIXELFORMAT_32BPP;
mode_source->sourceMode.position.x = FAKE_MONITOR_OFFSET_X_Y;
mode_source->sourceMode.position.y = FAKE_MONITOR_OFFSET_X_Y;
// insert fake mode target
DISPLAYCONFIG_MODE_INFO *mode_target = &modeInfoArray[pNumModeInfoArrayElements_original+1];
*mode_target = {};
mode_target->infoType = DISPLAYCONFIG_MODE_INFO_TYPE_TARGET;
mode_target->id = -2;
mode_target->adapterId.HighPart = -1;
mode_target->adapterId.LowPart = -1;
return ret;
}
static
LONG
WINAPI
DisplayConfigGetDeviceInfo_hook(DISPLAYCONFIG_DEVICE_INFO_HEADER* requestPacket)
{
if (requestPacket == nullptr) {
return DisplayConfigGetDeviceInfo_orig(requestPacket);
}
// fake monitor
if (FAKE_SUBSCREEN_ADAPTER &&
(requestPacket->id == static_cast<UINT32>(-1) ||
requestPacket->id == static_cast<UINT32>(-2)) &&
requestPacket->adapterId.HighPart == static_cast<LONG>(-1) &&
requestPacket->adapterId.LowPart == static_cast<DWORD>(-1)) {
log_misc(
"popn",
"DisplayConfigGetDeviceInfo hook hit for fake monitor, type={}, size={}, id={}, luid={}/{}",
static_cast<int>(requestPacket->type),
requestPacket->size,
requestPacket->id,
requestPacket->adapterId.HighPart,
requestPacket->adapterId.LowPart);
if (requestPacket->type == DISPLAYCONFIG_DEVICE_INFO_GET_TARGET_NAME) {
const auto target = reinterpret_cast<DISPLAYCONFIG_TARGET_DEVICE_NAME*>(requestPacket);
target->flags.value = 0;
target->outputTechnology = DISPLAYCONFIG_OUTPUT_TECHNOLOGY_HDMI;
target->connectorInstance = 0;
wcscpy(target->monitorFriendlyDeviceName, L"Spice Fake Monitor");
wcscpy(target->monitorDevicePath, L"\\\\?\\SpiceFakeMonitor");
} else if (requestPacket->type == DISPLAYCONFIG_DEVICE_INFO_GET_SOURCE_NAME) {
const auto source = reinterpret_cast<DISPLAYCONFIG_SOURCE_DEVICE_NAME*>(requestPacket);
// value must match WrappedIDirect3D9::GetAdapterIdentifier
wcscpy(source->viewGdiDeviceName, L"\\\\.\\DISPLAY_SPICE_FAKE");
} else {
log_fatal(
"popn",
"unexpected device info type {} for fake monitor",
static_cast<int>(requestPacket->type));
}
return ERROR_SUCCESS;
}
const auto ret = DisplayConfigGetDeviceInfo_orig(requestPacket);
log_misc(
"popn",
"DisplayConfigGetDeviceInfo returned {}, type={}, size={}, id={}, luid={}/{}",
ret,
static_cast<int>(requestPacket->type),
requestPacket->size,
requestPacket->id,
requestPacket->adapterId.HighPart,
requestPacket->adapterId.LowPart);
if (ret == ERROR_SUCCESS) {
if (requestPacket->type == DISPLAYCONFIG_DEVICE_INFO_GET_SOURCE_NAME) {
const auto sourceName = reinterpret_cast<DISPLAYCONFIG_SOURCE_DEVICE_NAME*>(requestPacket);
log_misc(
"popn",
"... name={}",
ws2s(sourceName->viewGdiDeviceName));
} else if (requestPacket->type == DISPLAYCONFIG_DEVICE_INFO_GET_TARGET_NAME) {
const auto targetName = reinterpret_cast<DISPLAYCONFIG_TARGET_DEVICE_NAME*>(requestPacket);
log_misc(
"popn",
"... flags={}, outputTechnology: {}, connectorInstance={}, friendlyname={} path={}",
targetName->flags.value,
static_cast<int>(targetName->outputTechnology),
targetName->connectorInstance,
ws2s(targetName->monitorFriendlyDeviceName),
ws2s(targetName->monitorDevicePath));
// need to fix up some values for the primary monitor...
const auto monitors = sysutils::enumerate_monitors();
for (const auto& monitor : monitors) {
if (monitor.id == targetName->header.id &&
monitor.adapter_id_HighPart == targetName->header.adapterId.HighPart &&
monitor.adapter_id_LowPart == targetName->header.adapterId.LowPart) {
if (monitor.is_primary) {
targetName->outputTechnology = DISPLAYCONFIG_OUTPUT_TECHNOLOGY_DISPLAYPORT_EXTERNAL;
targetName->connectorInstance = 2;
log_info(
"popn",
"... overriding primary monitor ({}) to pretend to be DP port #2",
monitor.display_name);
} else {
// TODO: is this what the game expects for subscreen?
// TODO: what if there are 3+ monitors?
targetName->outputTechnology = DISPLAYCONFIG_OUTPUT_TECHNOLOGY_HDMI;
targetName->connectorInstance = 0;
}
break;
}
}
}
}
return ret;
}
#endif
#if !SPICE64
static int __cdecl usbCheckAlive() {
return 1;
}
@@ -227,6 +542,8 @@ namespace games::popn {
return MMSYSERR_NOERROR;
}
#endif
static bool log_hook(void *user, const std::string &data, logger::Style style, std::string &out) {
// get rid of the log spam
@@ -249,6 +566,8 @@ namespace games::popn {
void POPNGame::pre_attach() {
Game::pre_attach();
#if !SPICE64
// only needed for older versions (19-21, inclusive) but create them anyway since 21 and above are all M39
fileutils::dir_create_recursive_log("popn", "dev\\raw\\bookkeeping");
fileutils::dir_create_recursive_log("popn", "dev\\raw\\ranking");
@@ -257,11 +576,15 @@ namespace games::popn {
// load without resolving references
// makes game not trigger DLLMain which results in an error due to missing EZUSB device
LoadLibraryExW((MODULE_PATH / "ezusb.dll").c_str(), nullptr, DONT_RESOLVE_DLL_REFERENCES);
#endif
}
void POPNGame::attach() {
Game::attach();
#if !SPICE64
/*
* Fast Boot (TM) Patch
* Game tries to create some directories and if it fails it will sleep for 1 second
@@ -320,5 +643,91 @@ namespace games::popn {
detour::inline_hook((void *) usbWdtStartDone,
libutils::try_proc(ezusb, "?usbWdtStartDone@@YAHXZ"));
}
#endif
#if SPICE64 && !SPICE_XP
if (!GRAPHICS_WINDOWED && D3D9_ADAPTER.has_value()) {
SHOW_PIKA_MONITOR_WARNING = true;
log_warning(
"popn",
"\n\n"
"!!! using -dxmainadapter option is NOT recommended due to known !!!\n"
"!!! compatibility issues with the game !!!\n"
"!!! !!!\n"
"!!! * game may launch in wrong resolution or refresh rate !!!\n"
"!!! * touch / mouse input may stop working in subscreen / overlay !!!\n"
"!!! !!!\n"
"!!! recommendation is to use the Change Main Monitor (-mainmonitor) !!!\n"
"!!! option instead of -dxmainadapter !!!\n\n"
);
deferredlogs::defer_error_messages({
"-dxmainadapter option is NOT recommended due to known compatibility ",
" issues with the game!",
" * game may launch in wrong resolution or refresh rate",
" * touch / mouse input may stop working in subscreen / overlay",
" use Change Main Monitor (-mainmonitor) option instead"
});
}
// monitor hook
GetDisplayConfigBufferSizes_orig =
detour::iat_try("GetDisplayConfigBufferSizes",
GetDisplayConfigBufferSizes_hook, avs::game::DLL_INSTANCE);
QueryDisplayConfig_orig =
detour::iat_try("QueryDisplayConfig",
QueryDisplayConfig_hook, avs::game::DLL_INSTANCE);
DisplayConfigGetDeviceInfo_orig =
detour::iat_try("DisplayConfigGetDeviceInfo",
DisplayConfigGetDeviceInfo_hook, avs::game::DLL_INSTANCE);
// GUID_DEVCLASS_USB = {86E0D1E0-11D0-89B0-00A0C9054129}
SETUPAPI_SETTINGS settings{};
settings.class_guid[0] = 0x86E0D1E0;
settings.class_guid[1] = 0x11D08089;
settings.class_guid[2] = 0x0008E49C;
settings.class_guid[3] = 0x731F303E;
// BIO3 - 8058 (maybe U region cabs with more lights?)
// BIO3 - 8054 (probably J region?)
// BIO2 - 8050 (need bi2x_hook, everything will work except buttons 1-9)
const char property[] = "1CCF(8058)_000";
const char property_hardwareid[] = "USB\\VID_1CCF&PID_8058&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);
bi3a_hook_init();
// note: if for whatever reason you want to use BIO2, the game needs to be patched
// check popn.dll and look for static table that looks like this:
// 00000001 00000003 00000001 (button 1)
// 00000002 00000004 00000001 (button 2)
// 00000004 00000005 00000001 (button 3)
// 00000008 00000006 00000001 (button 4)
// 00000010 00000007 00000001 (button 5)
// 00000020 00000008 00000001 (button 6)
// 00000040 00000009 00000001 (button 7)
// 00000080 0000000A 00000001 (button 8)
// 00000100 0000000B 00000001 (button 9)
// set third column to 0 and it will work with BIO2
if (!GRAPHICS_WINDOWED) {
if (NATIVE_TOUCH) {
nativetouchhook::hook(avs::game::DLL_INSTANCE);
} else {
wintouchemu::FORCE = true;
wintouchemu::INJECT_MOUSE_AS_WM_TOUCH = true;
wintouchemu::hook_title_ends("", "Main Screen", avs::game::DLL_INSTANCE);
}
}
sysutils::hook_EnumDisplayDevicesA();
#endif
}
}
+20
View File
@@ -1,9 +1,29 @@
#pragma once
#include "avs/game.h"
#include "games/game.h"
#include "util/tapeled.h"
namespace games::popn {
static inline bool is_pikapika_model() {
#if SPICE64
return (avs::game::is_model("M39") && avs::game::SPEC[0] == 'D');
#else
return false;
#endif
}
#if SPICE64
constexpr int POPN_TAPELED_TOTAL = 8;
extern tapeledutils::tape_led TAPELED_MAPPING[POPN_TAPELED_TOTAL];
#endif
extern bool SHOW_PIKA_MONITOR_WARNING;
extern bool NATIVE_TOUCH;
class POPNGame : public games::Game {
public:
POPNGame();
+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
+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"}
}
);
}
+42 -13
View File
@@ -21,8 +21,10 @@
#include "util/socd_cleaner.h"
#include "util/time.h"
#include "util/libutils.h"
#include "misc/wintouchemu.h"
#include "util/sysutils.h"
#include "misc/eamuse.h"
#include "misc/nativetouchhook.h"
#include "misc/wintouchemu.h"
#include "bi2x_hook.h"
#include "camera.h"
#include "io.h"
@@ -233,6 +235,27 @@ 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;
}
@@ -328,28 +351,28 @@ namespace games::sdvx {
}
#ifdef SPICE64 // SDVX5+ specific code
// check -monitor + UFC mode
// check -dxmainadapter + UFC mode
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"
"!!! known compatibility issues with the game !!!\n"
"\n\n"
"!!! using -dxmainadapter option with Valkyrie mode is NOT !!!\n"
"!!! recommended due to known compatibility issues with the game !!!\n"
"!!! !!!\n"
"!!! * game may launch in wrong resolution or refresh rate !!!\n"
"!!! * touch / mouse input may stop working in subscreen / overlay !!!\n"
"!!! !!!\n"
"!!! recommendation is to NOT use -monitor and instead set the !!!\n"
"!!! primary monitor in Windows settings before launching the game !!!\n\n"
"!!! recommendation is to use the Change Main Monitor (-mainmonitor) !!!\n"
"!!! option instead of -dxmainadapter !!!\n\n"
);
deferredlogs::defer_error_messages({
"-monitor option is NOT recommended when running with Valkyrie mode",
"-dxmainadapter option is NOT recommended when running with Valkyrie mode",
" due to known compatibility issues with the game:",
" * game may launch in wrong resolution or refresh rate",
" * touch / mouse input may stop working in subscreen / overlay",
" recommended fix is to NOT use -monitor and instead set the primary",
" monitor in Windows settings before launching the game"
" use Change Main Monitor (-mainmonitor) option instead"
});
}
#endif
@@ -369,7 +392,7 @@ namespace games::sdvx {
}
}
}
void SDVXGame::attach() {
Game::attach();
@@ -416,9 +439,11 @@ namespace games::sdvx {
}
if (is_valkyrie_model()) {
// hook touch window
// in windowed mode, game can accept mouse input on the second screen
if (!NATIVETOUCH && !GRAPHICS_WINDOWED) {
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(
@@ -442,6 +467,7 @@ namespace games::sdvx {
ENABLE_COM_PORT_SCAN_HOOK = true;
}
}
#endif
// ASIO device hook
@@ -477,6 +503,9 @@ namespace games::sdvx {
// remove log spam
logger::hook_add(sdvx64_spam_remover, nullptr);
if (is_valkyrie_model()) {
sysutils::hook_EnumDisplayDevicesA();
}
#endif
}
+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];
+12 -9
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
@@ -65,14 +66,16 @@ static HRESULT STDAPICALLTYPE CoCreateInstance_hook(
if (FAILED(ret)) {
if (IsEqualCLSID(rclsid, CLSID_MMDeviceEnumerator)) {
log_warning("audio", "CoCreateInstance failed for CLSID_MMDeviceEnumerator, hr={}", FMT_HRESULT(ret));
}
// Windows 11 KB5052093 issue - reverb DMO went missing from dsdmo.dll (fails with 0x80040154)
if (IsEqualCLSID(rclsid, CLSID_DirectSoundI3DL2ReverbDMO) && ret == (HRESULT)0x80040154) {
log_warning(
"audio",
"CoCreateInstance for CLSID_DirectSoundI3DL2ReverbDMO failed with 0x80040154, swap with CLSID_DirectSoundWavesReverbDMO "
"(workaround for Windows 11 KB5052093 issue); REVERB EX replaced with REVERB");
} else if (IsEqualCLSID(rclsid, CLSID_DirectSoundI3DL2ReverbDMO)) {
// deal with reverb DMO missing from dsdmo.dll
// it usually fails with 0x80040154 (REGDB_E_CLASSNOTREG), but we have also seen 0x8007007e (ERROR_MOD_NOT_FOUND)
static std::once_flag printed;
std::call_once(printed, [ret]() {
log_warning(
"audio",
"CoCreateInstance for CLSID_DirectSoundI3DL2ReverbDMO failed with {}, swap with CLSID_DirectSoundWavesReverbDMO...",
FMT_HRESULT(ret));
});
ret = CoCreateInstance_orig(CLSID_DirectSoundWavesReverbDMO, pUnkOuter, dwClsContext, riid, ppv);
if (FAILED(ret)) {
log_warning("audio", "CoCreateInstance(CLSID_DirectSoundWavesReverbDMO...) failed, hr={}", FMT_HRESULT(ret));
+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;
@@ -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));
@@ -15,6 +15,7 @@
#include "cfg/screen_resize.h"
#include "games/gitadora/gitadora.h"
#include "games/iidx/iidx.h"
#include "games/popn/popn.h"
#include "games/sdvx/sdvx.h"
#include "games/mfc/mfc.h"
#include "games/io.h"
@@ -278,7 +279,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",
@@ -462,7 +463,20 @@ HRESULT STDMETHODCALLTYPE WrappedIDirect3D9::RegisterSoftwareDevice(void *pIniti
}
UINT STDMETHODCALLTYPE WrappedIDirect3D9::GetAdapterCount() {
return pReal->GetAdapterCount();
UINT result = pReal->GetAdapterCount();
if (!FAKE_SUBSCREEN_ADAPTER && games::popn::is_pikapika_model() && result == 1) {
FAKE_SUBSCREEN_ADAPTER = true;
log_info(
"graphics::d3d9",
"GetAdapterCount returned 1, popn needs 2 adapters - enabliing fake submonitor mode");
}
if (FAKE_SUBSCREEN_ADAPTER) {
return 2;
}
return result;
}
HRESULT STDMETHODCALLTYPE WrappedIDirect3D9::GetAdapterIdentifier(
@@ -470,10 +484,26 @@ HRESULT STDMETHODCALLTYPE WrappedIDirect3D9::GetAdapterIdentifier(
DWORD Flags,
D3DADAPTER_IDENTIFIER9 *pIdentifier)
{
if (FAKE_SUBSCREEN_ADAPTER && Adapter == 1 && pIdentifier) {
*pIdentifier = {};
strcpy(pIdentifier->DeviceName, "\\\\.\\DISPLAY_SPICE_FAKE");
log_misc(
"graphics::d3d9",
"GetAdapterIdentifier called for fake subscreen adapter 1: {}",
pIdentifier->DeviceName);
return S_OK;
}
CHECK_RESULT(pReal->GetAdapterIdentifier(Adapter, Flags, pIdentifier));
}
UINT STDMETHODCALLTYPE WrappedIDirect3D9::GetAdapterModeCount(UINT Adapter, D3DFORMAT Format) {
if (FAKE_SUBSCREEN_ADAPTER && Adapter == 1) {
log_misc("graphics::d3d9", "GetAdapterModeCount called for fake subscreen adapter");
return 1;
}
return pReal->GetAdapterModeCount(Adapter, Format);
}
@@ -483,6 +513,19 @@ HRESULT STDMETHODCALLTYPE WrappedIDirect3D9::EnumAdapterModes(
UINT Mode,
D3DDISPLAYMODE *pMode)
{
if (FAKE_SUBSCREEN_ADAPTER && Adapter == 1) {
log_misc("graphics::d3d9", "EnumAdapterModes called for fake subscreen adapter");
if (Mode == 0 && pMode) {
pMode->Width = 1280;
pMode->Height = 800;
pMode->RefreshRate = 60;
pMode->Format = D3DFMT_X8R8G8B8;
return S_OK;
} else {
return D3DERR_INVALIDCALL;
}
}
HRESULT ret = pReal->EnumAdapterModes(Adapter, Format, Mode, pMode);
if (SUCCEEDED(ret) && pMode) {
@@ -552,18 +595,6 @@ HRESULT STDMETHODCALLTYPE WrappedIDirect3D9::EnumAdapterModes(
modified = true;
}
}
if (!modified &&
(width > height) &&
(GRAPHICS_ADJUST_ORIENTATION == ORIENTATION_CW ||
GRAPHICS_ADJUST_ORIENTATION == ORIENTATION_CCW)) {
log_misc(
"graphics::d3d9", "swapping width and height for mode {}, {}x{} @ {}Hz (-autoorientation)",
Mode, width, height, refresh);
pMode->Height = width;
pMode->Width = height;
modified = true;
}
}
CHECK_RESULT(ret);
@@ -591,6 +622,11 @@ HRESULT STDMETHODCALLTYPE WrappedIDirect3D9::CheckDeviceFormat(
D3DRESOURCETYPE RType,
D3DFORMAT CheckFormat)
{
if (FAKE_SUBSCREEN_ADAPTER && Adapter == 1) {
log_misc("graphics::d3d9", "CheckDeviceFormat called for fake subscreen adapter");
return D3D_OK;
}
CHECK_RESULT(pReal->CheckDeviceFormat(Adapter, DeviceType, AdapterFormat, Usage, RType, CheckFormat));
}
@@ -672,7 +708,7 @@ HRESULT STDMETHODCALLTYPE WrappedIDirect3D9::GetDeviceCaps(UINT Adapter, D3DDEVT
}
// in windowed mode, LDJ will always launch two windows, no special handling needed here
} else if (avs::game::is_model("KFC")) {
if (GRAPHICS_WINDOWED & GRAPHICS_PREVENT_SECONDARY_WINDOW) {
if (GRAPHICS_WINDOWED && GRAPHICS_PREVENT_SECONDARY_WINDOW) {
// user wants windowed mode but does not want subscreen at all
pCaps->NumberOfAdaptersInGroup = 1;
} else {
@@ -687,6 +723,8 @@ HRESULT STDMETHODCALLTYPE WrappedIDirect3D9::GetDeviceCaps(UINT Adapter, D3DDEVT
} else if (GRAPHICS_FORCE_SINGLE_ADAPTER) {
pCaps->NumberOfAdaptersInGroup = 1;
}
} else if (games::popn::is_pikapika_model()) {
pCaps->NumberOfAdaptersInGroup = 2;
}
return ret;
@@ -945,26 +983,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;
}
}
@@ -992,12 +1041,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;
}
}
@@ -1017,7 +1071,8 @@ HRESULT STDMETHODCALLTYPE WrappedIDirect3D9::CreateDeviceEx(
//
// note from MSDN: `pFullscreenDisplayMode` must be NULL for windowed mode.
if (GRAPHICS_WINDOWED) {
if (avs::game::is_model({"LDJ", "KFC"}) && (BehaviorFlags & D3DCREATE_ADAPTERGROUP_DEVICE)) {
if (avs::game::is_model({"LDJ", "KFC", "M39"}) &&
(BehaviorFlags & D3DCREATE_ADAPTERGROUP_DEVICE)) {
log_misc("graphics::d3d9", "disabling adapter group device in windowed mode");
D3D9_BEHAVIOR_DISABLE |= D3DCREATE_ADAPTERGROUP_DEVICE;
@@ -1089,7 +1144,8 @@ HRESULT STDMETHODCALLTYPE WrappedIDirect3D9::CreateDeviceEx(
*ppReturnedDeviceInterface = new WrappedIDirect3DDevice9(hFocusWindow, *ppReturnedDeviceInterface);
// initialize sub screen if IIDX/SDVX requested a multi-head context
if (avs::game::is_model({"LDJ", "KFC"}) && (orig_behavior_flags & D3DCREATE_ADAPTERGROUP_DEVICE)) {
if (avs::game::is_model({"LDJ", "KFC", "M39"}) &&
(orig_behavior_flags & D3DCREATE_ADAPTERGROUP_DEVICE)) {
graphics_d3d9_ldj_init_sub_screen(*ppReturnedDeviceInterface, &pPresentationParameters[1]);
}
}
@@ -1128,8 +1184,6 @@ static void graphics_d3d9_ldj_init_sub_screen(IDirect3DDevice9Ex *device, D3DPRE
*/
if (GRAPHICS_WINDOWED) {
log_info("graphics::d3d9", "creating additional swap chain");
present_params->Windowed = true;
present_params->FullScreen_RefreshRateInHz = 0;
@@ -1138,18 +1192,23 @@ static void graphics_d3d9_ldj_init_sub_screen(IDirect3DDevice9Ex *device, D3DPRE
hr = device->CreateAdditionalSwapChain(present_params, &SUB_SWAP_CHAIN);
if (FAILED(hr)) {
log_warning("graphics::d3d9", "failed to create additional swap chain, hr={}", FMT_HRESULT(hr));
} else {
log_info("graphics::d3d9", "created additional swap chain for windowed mode");
}
} else {
hr = device->GetSwapChain(1, &SUB_SWAP_CHAIN);
if (FAILED(hr)) {
log_warning("graphics::d3d9", "failed to acquire fullscreen sub swap chain, hr={}", FMT_HRESULT(hr));
} else {
log_info("graphics::d3d9", "acquired fullscreen sub swap chain");
return;
}
hr = device->CreateAdditionalSwapChain(present_params, &SUB_SWAP_CHAIN);
if (FAILED(hr)) {
log_warning("graphics::d3d9", "failed to get additional swap chain, hr={}", FMT_HRESULT(hr));
} else {
log_info("graphics::d3d9", "created additional swap chain for fullscreen mode");
}
}
}
@@ -1192,7 +1251,10 @@ static void graphics_d3d9_ldj_on_present(IDirect3DDevice9 *wrapped_device) {
if (SUB_SWAP_CHAIN != nullptr) {
wintouchemu::update();
if (GRAPHICS_WINDOWED || SUBSCREEN_FORCE_REDRAW) {
// newer versions of exceed gear needs SUBSCREEN_FORCE_REDRAW
// (when enabled on older versions of EG, you end up with graphical glitches on the subscreen
// early versions of popn HC needs this as well, otherwise the subscreen doesn't update at all
if (GRAPHICS_WINDOWED || SUBSCREEN_FORCE_REDRAW || games::popn::is_pikapika_model()) {
SUB_SWAP_CHAIN->Present(nullptr, nullptr, nullptr, nullptr, 0);
}
}
@@ -1392,7 +1454,8 @@ void graphics_d3d9_on_present(
const bool is_tdj = avs::game::is_model("LDJ") && games::iidx::TDJ_MODE;
const bool is_gfdm_arena =
games::gitadora::is_arena_model() && games::gitadora::ARENA_SINGLE_WINDOW;
if (is_vm || is_tdj || is_gfdm_arena) {
const bool is_pika = games::popn::is_pikapika_model();
if (is_vm || is_tdj || is_gfdm_arena || is_pika) {
graphics_d3d9_ldj_on_present(wrapped_device);
}
@@ -1554,4 +1617,4 @@ void update_backbuffer_dimensions(D3DPRESENT_PARAMETERS *params) {
params->BackBufferHeight);
return;
}
}
}
@@ -251,7 +251,7 @@ HRESULT STDMETHODCALLTYPE WrappedIDirect3DDevice9::CreateAdditionalSwapChain(
HRESULT hr = pReal->CreateAdditionalSwapChain(pPresentationParameters, ppSwapChain);
bool create_swap_chain = false;
if (avs::game::is_model({"LDJ", "KFC"})) {
if (avs::game::is_model({"LDJ", "KFC", "M39"})) {
create_swap_chain = true;
} else if (games::gitadora::is_arena_model() &&
games::gitadora::ARENA_SINGLE_WINDOW &&
@@ -305,7 +305,7 @@ HRESULT STDMETHODCALLTYPE WrappedIDirect3DDevice9::GetSwapChain(
}
bool swap = false;
if (iSwapChain == 1 && avs::game::is_model({"LDJ", "KFC"})) {
if (iSwapChain == 1 && avs::game::is_model({"LDJ", "KFC", "M39"})) {
swap = true;
}
if (games::gitadora::is_arena_model() &&
@@ -344,7 +344,7 @@ UINT STDMETHODCALLTYPE WrappedIDirect3DDevice9::GetNumberOfSwapChains() {
UINT n = pReal->GetNumberOfSwapChains();
if (sub_swapchain && avs::game::is_model({"LDJ", "KFC"})) {
if (sub_swapchain && avs::game::is_model({"LDJ", "KFC", "M39"})) {
n += 1;
}
@@ -593,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
@@ -768,7 +768,7 @@ void SurfaceHook(IDirect3DDevice9 *pReal) {
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);
+304 -87
View File
@@ -1,3 +1,6 @@
// GetDisplayConfigBufferSizes etc is Vista+
#define _WIN32_WINNT 0x0601
#include <initguid.h>
#include "graphics.h"
@@ -13,6 +16,7 @@
#include "games/ddr/ddr.h"
#include "games/gitadora/gitadora.h"
#include "games/iidx/iidx.h"
#include "games/popn/popn.h"
#include "hooks/graphics/backends/d3d9/d3d9_backend.h"
#include "launcher/shutdown.h"
#include "overlay/overlay.h"
@@ -35,6 +39,8 @@ struct CaptureData {
HWND TDJ_SUBSCREEN_WINDOW = nullptr;
HWND SDVX_SUBSCREEN_WINDOW = nullptr;
HWND GFDM_SUBSCREEN_WINDOW = nullptr;
HWND POPN_SUBSCREEN_WINDOW = nullptr;
bool FAKE_SUBSCREEN_ADAPTER = false;
// icon
static HICON WINDOW_ICON = LoadIcon(GetModuleHandle(nullptr), MAKEINTRESOURCE(MAINICON));
@@ -58,7 +64,6 @@ bool GRAPHICS_CAPTURE_CURSOR = false;
bool GRAPHICS_LOG_HRESULT = false;
bool GRAPHICS_SDVX_FORCE_720 = false;
bool GRAPHICS_SHOW_CURSOR = false;
graphics_orientation GRAPHICS_ADJUST_ORIENTATION = ORIENTATION_NORMAL;
bool GRAPHICS_WINDOWED = false;
std::vector<HWND> GRAPHICS_WINDOWS;
UINT GRAPHICS_FORCE_REFRESH = 0;
@@ -71,6 +76,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;
@@ -232,9 +238,9 @@ static LRESULT CALLBACK WindowProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM l
// This allows touches received on subscreen window to be translated correctly.
update_spicetouch_window_dimensions(hWnd);
// log_misc(
// "graphics", "detected window change ({}x{} @ {}, {}), updating touch coord-space to match",
// "graphics", "detected window change ({}x{} @ {}, {}), updating touch coord-space to match",
// SPICETOUCH_TOUCH_WIDTH, SPICETOUCH_TOUCH_HEIGHT, SPICETOUCH_TOUCH_X, SPICETOUCH_TOUCH_Y);
// Update SPICETOUCH window if present
if (SPICETOUCH_TOUCH_HWND) {
SetWindowPos(
@@ -300,30 +306,6 @@ 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,
@@ -345,62 +327,6 @@ static HWND WINAPI CreateWindowExA_hook(DWORD dwExStyle, LPCSTR lpClassName, LPC
fmt::ptr(hInstance),
fmt::ptr(lpParam));
// 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)
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 {};
// get display settings
if (EnumDisplaySettings(nullptr, ENUM_CURRENT_SETTINGS, &mode)) {
if (adjust_orientation) {
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 {}, 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) {
log_info(
"graphics",
"call ChangeDisplaySettings to force refresh rate: {} => {} Hz (-graphics-force-refresh)",
mode.dmDisplayFrequency,
GRAPHICS_FORCE_REFRESH);
mode.dmDisplayFrequency = GRAPHICS_FORCE_REFRESH;
}
const auto disp_res = ChangeDisplaySettings(&mode, CDS_FULLSCREEN);
if (disp_res != DISP_CHANGE_SUCCESSFUL) {
log_warning("graphics", "ChangeDisplaySettings failed: {}", disp_res);
}
} else {
log_warning("graphics", "failed to get display settings");
}
}
// gfdm
if (avs::game::is_model({"J32", "J33", "K32", "K33", "L32", "L33", "M32"})) {
// set window name
@@ -411,17 +337,18 @@ 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_popn_sub_window = avs::game::is_model("M39") && window_name.ends_with("Sub Screen");
bool is_gfdm_sub_window = games::gitadora::is_arena_model() && window_name.ends_with("SMALL");
// hide maximize button (prevent misaligned touches)
if ((is_tdj_sub_window && GRAPHICS_IIDX_WSUB) || is_sdvx_sub_window || is_gfdm_sub_window) {
if ((is_tdj_sub_window && GRAPHICS_IIDX_WSUB) || is_sdvx_sub_window || is_gfdm_sub_window || is_popn_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) {
if ((is_tdj_sub_window || is_sdvx_sub_window || is_popn_sub_window) && GRAPHICS_WSUB_BORDERLESS) {
dwStyle &= ~(WS_OVERLAPPEDWINDOW);
}
@@ -443,6 +370,10 @@ static HWND WINAPI CreateWindowExA_hook(DWORD dwExStyle, LPCSTR lpClassName, LPC
}
}
if (GRAPHICS_WINDOWED) {
graphics_window_check_bounds_before_creation(x, y, nWidth, nHeight);
}
// call original
HWND result = CreateWindowExA_orig(dwExStyle, lpClassName, lpWindowName, dwStyle, x, y, nWidth, nHeight,
hWndParent, hMenu, hInstance, lpParam);
@@ -451,7 +382,7 @@ static HWND WINAPI CreateWindowExA_hook(DWORD dwExStyle, LPCSTR lpClassName, LPC
if (is_tdj_sub_window) {
// TDJ windowed mode: remember the subscreen window handle for later
TDJ_SUBSCREEN_WINDOW = result;
// hook for preventing the closing of subscreen window
if (GRAPHICS_IIDX_WSUB) {
graphics_hook_subscreen_window(TDJ_SUBSCREEN_WINDOW);
@@ -470,6 +401,13 @@ static HWND WINAPI CreateWindowExA_hook(DWORD dwExStyle, LPCSTR lpClassName, LPC
graphics_hook_subscreen_window(GFDM_SUBSCREEN_WINDOW);
}
if (is_popn_sub_window) {
POPN_SUBSCREEN_WINDOW = result;
if (!GRAPHICS_PREVENT_SECONDARY_WINDOW) {
graphics_hook_subscreen_window(POPN_SUBSCREEN_WINDOW);
}
}
disable_touch_indicators(result);
log_misc(
"graphics",
@@ -538,6 +476,18 @@ static HWND WINAPI CreateWindowExW_hook(DWORD dwExStyle, LPCWSTR lpClassName, LP
}
}
// FTT
if (avs::game::is_model("MMD")) {
// set window name
if (!lpWindowName) {
lpWindowName = L"Future TomTom";
}
}
if (GRAPHICS_WINDOWED) {
graphics_window_check_bounds_before_creation(x, y, nWidth, nHeight);
}
// call original
HWND result = CreateWindowExW_orig(
dwExStyle, lpClassName, lpWindowName, dwStyle, x, y, nWidth, nHeight,
@@ -610,7 +560,7 @@ static BOOL WINAPI MoveWindow_hook(HWND hWnd, int X, int Y, int nWidth, int nHei
if (GRAPHICS_IIDX_WSUB) {
// (Experimental) Show subscreen in windowed mode
graphics_load_windowed_subscreen_parameters();
RECT rect {};
DWORD dwStyle;
@@ -733,6 +683,13 @@ static BOOL WINAPI ShowWindow_hook(HWND hWnd, int nCmdShow) {
return true;
}
if (games::popn::is_pikapika_model() &&
GRAPHICS_PREVENT_SECONDARY_WINDOW &&
hWnd == POPN_SUBSCREEN_WINDOW) {
log_info("graphics", "ShowWindow_hook - hiding sub window {}", fmt::ptr(hWnd));
return true;
}
// call original
return ShowWindow_orig(hWnd, nCmdShow);
}
@@ -975,6 +932,9 @@ void graphics_hook_subscreen_window(HWND hWnd) {
if (GRAPHICS_WSUB_ALWAYS_ON_TOP) {
graphics_update_z_order(hWnd, true);
}
if (GRAPHICS_WINDOW_DISABLE_ROUNDED_CORNERS) {
graphics_set_corner_preference(hWnd, true);
}
}
void graphics_screens_register(int screen) {
@@ -1051,6 +1011,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
@@ -1147,3 +1108,259 @@ std::string graphics_screenshot_genpath() {
id++;
}
}
static std::string get_dmdo_string(DWORD dmdo) {
switch (dmdo) {
case DMDO_DEFAULT:
return "DMDO_DEFAULT";
case DMDO_90:
return "DMDO_90";
case DMDO_180:
return "DMDO_180";
case DMDO_270:
return "DMDO_270";
default:
return fmt::format("Unknown ({})", dmdo);
}
}
void change_primary_monitor(const std::string &monitor_name) {
log_misc("graphics", "try changing primary monitor to {}...", monitor_name);
// for WinXP, since these are Vista+ or 7+ APIs
const auto user32 = LoadLibraryA("user32.dll");
if (!user32) {
log_warning("graphics", "can't find user32.dll???");
return;
}
const auto GetDisplayConfigBufferSizes_addr =
reinterpret_cast<decltype(GetDisplayConfigBufferSizes) *>(
GetProcAddress(user32, "GetDisplayConfigBufferSizes"));
const auto QueryDisplayConfig_addr =
reinterpret_cast<decltype(QueryDisplayConfig) *>(
GetProcAddress(user32, "QueryDisplayConfig"));
const auto DisplayConfigGetDeviceInfo_addr =
reinterpret_cast<decltype(DisplayConfigGetDeviceInfo) *>(
GetProcAddress(user32, "DisplayConfigGetDeviceInfo"));
const auto SetDisplayConfig_addr =
reinterpret_cast<decltype(SetDisplayConfig) *>(
GetProcAddress(user32, "SetDisplayConfig"));
if (GetDisplayConfigBufferSizes_addr == nullptr || QueryDisplayConfig_addr == nullptr ||
DisplayConfigGetDeviceInfo_addr == nullptr || SetDisplayConfig_addr == nullptr) {
log_warning("graphics", "cannot change primary monitor, OS does not support required APIs)");
return;
}
UINT32 path_count = 0;
UINT32 mode_count = 0;
std::vector<DISPLAYCONFIG_PATH_INFO> paths;
std::vector<DISPLAYCONFIG_MODE_INFO> modes;
bool succeeded = false;
// in a retry loop, try to query for display config
// retry loop is needed because it can fail with ERROR_INSUFFICIENT_BUFFER
for (int attempt = 0; attempt < 5; ++attempt) {
auto status = GetDisplayConfigBufferSizes_addr(QDC_DATABASE_CURRENT, &path_count, &mode_count);
if (status != ERROR_SUCCESS) {
log_warning("graphics", "GetDisplayConfigBufferSizes failed: {}", status);
return;
}
paths.resize(path_count);
modes.resize(mode_count);
DISPLAYCONFIG_TOPOLOGY_ID topology_id = DISPLAYCONFIG_TOPOLOGY_INTERNAL;
status = QueryDisplayConfig_addr(
QDC_DATABASE_CURRENT,
&path_count,
paths.data(),
&mode_count,
modes.data(),
&topology_id);
if (status == ERROR_SUCCESS) {
// Shrink to actual returned counts
paths.resize(path_count);
modes.resize(mode_count);
succeeded = true;
break;
}
if (status != ERROR_INSUFFICIENT_BUFFER) {
log_warning("graphics", "QueryDisplayConfig failed: {}", status);
return;
}
Sleep(500);
}
if (!succeeded) {
log_warning("graphics", "QueryDisplayConfig failed after reaching max retries");
return;
}
LONG x = 0;
LONG y = 0;
bool found = false;
// find the new main monitor
for (auto& mode : modes) {
if (mode.infoType != DISPLAYCONFIG_MODE_INFO_TYPE_SOURCE) {
continue;
}
DISPLAYCONFIG_SOURCE_DEVICE_NAME name = {};
name.header.type = DISPLAYCONFIG_DEVICE_INFO_GET_SOURCE_NAME;
name.header.size = sizeof(name);
name.header.adapterId = mode.adapterId;
name.header.id = mode.id;
if (DisplayConfigGetDeviceInfo_addr(&name.header) != ERROR_SUCCESS) {
continue;
}
const auto device_name = std::string(ws2s(name.viewGdiDeviceName));
if (monitor_name == device_name) {
x = mode.sourceMode.position.x;
y = mode.sourceMode.position.y;
found = true;
log_info(
"graphics",
"new main monitor target found: {}, old position: ({}, {})",
monitor_name,
x, y);
break;
}
}
if (!found) {
log_fatal(
"graphics",
"new main monitor target not found, check -mainmonitor option: {}",
monitor_name);
return;
}
// update monitor positions so that the new monitor is at (0, 0)
for (auto& mode : modes) {
if (mode.infoType == DISPLAYCONFIG_MODE_INFO_TYPE_SOURCE) {
mode.sourceMode.position.x -= x;
mode.sourceMode.position.y -= y;
}
}
// finally, commit the new display config
const auto status = SetDisplayConfig_addr(
path_count,
paths.data(),
mode_count,
modes.data(),
SDC_APPLY | SDC_USE_SUPPLIED_DISPLAY_CONFIG);
if (status != ERROR_SUCCESS) {
log_fatal("graphics", "SetDisplayConfig failed, check -mainmonitor option: {}", status);
}
// a little extra time for windows to settle and redraw things
Sleep(2000);
}
void update_monitor_on_boot(std::optional<graphics_orientation> target_orientation, UINT target_refresh_rate) {
// note: all of this is only being done for the primary motnior
// get current settings
DEVMODEA dm = {};
dm.dmSize = sizeof(dm);
if (!EnumDisplaySettingsExA(NULL, ENUM_CURRENT_SETTINGS, &dm, 0)) {
log_warning("graphics", "EnumDisplaySettingsExa failed {}", get_last_error_string());
return;
}
bool needs_update = false;
if (target_orientation.has_value()) {
// convert orientation values, and figure out if resolution needs to be swapped
bool rotate_resolution = false;
DWORD orientation = DMDO_DEFAULT;
switch (target_orientation.value()) {
case ORIENTATION_CW:
orientation = DMDO_90;
if (dm.dmDisplayOrientation == DMDO_DEFAULT || dm.dmDisplayOrientation == DMDO_180) {
rotate_resolution = true;
}
break;
case ORIENTATION_CCW:
orientation = DMDO_270;
if (dm.dmDisplayOrientation == DMDO_DEFAULT || dm.dmDisplayOrientation == DMDO_180) {
rotate_resolution = true;
}
break;
case ORIENTATION_FLIPPED:
orientation = DMDO_180;
if (dm.dmDisplayOrientation == DMDO_90 || dm.dmDisplayOrientation == DMDO_270) {
rotate_resolution = true;
}
break;
default:
orientation = DMDO_DEFAULT;
if (dm.dmDisplayOrientation == DMDO_90 || dm.dmDisplayOrientation == DMDO_270) {
rotate_resolution = true;
}
break;
}
// update orientation (and resolution if it must be swapped)
if (dm.dmDisplayOrientation != orientation) {
log_misc("graphics",
"current orientation {} => desired orientation {}",
get_dmdo_string(dm.dmDisplayOrientation), get_dmdo_string(orientation));
const DWORD originalWidth = dm.dmPelsWidth;
const DWORD originalHeight = dm.dmPelsHeight;
// change orientation
dm.dmDisplayOrientation = orientation;
dm.dmFields |= DM_DISPLAYORIENTATION;
// rotate resolution
if (rotate_resolution) {
dm.dmPelsWidth = originalHeight;
dm.dmPelsHeight = originalWidth;
dm.dmFields |= DM_PELSHEIGHT | DM_PELSWIDTH;
}
needs_update = true;
}
}
// update refresh rate
if (target_refresh_rate > 0) {
log_misc("graphics",
"current refresh rate {} => desired refresh rate {}",
dm.dmDisplayFrequency, target_refresh_rate);
dm.dmDisplayFrequency = target_refresh_rate;
dm.dmFields |= DM_DISPLAYFREQUENCY;
needs_update = true;
}
if (!needs_update) {
// nothing to do
return;
}
const auto result = ChangeDisplaySettings(&dm, CDS_FULLSCREEN);
if (result != DISP_CHANGE_SUCCESSFUL) {
log_fatal(
"graphics",
"failed to update display settings ({}px x {}px @ {}Hz): error {}, double check options",
dm.dmPelsWidth,
dm.dmPelsHeight,
dm.dmDisplayFrequency,
result);
} else {
log_info("graphics", "display settings updated successfully ({}px x {}px @ {}Hz)",
dm.dmPelsWidth,
dm.dmPelsHeight,
dm.dmDisplayFrequency);
}
// sleep for a little bit after changing monitor settings to delay game launch
Sleep(1000);
}
+14 -2
View File
@@ -7,6 +7,9 @@
#include <windows.h>
#include <d3d9.h>
#if !SPICE_XP
#include <dwmapi.h>
#endif
#include "external/toojpeg/toojpeg.h"
@@ -15,6 +18,7 @@ enum graphics_orientation {
ORIENTATION_CW = 0,
ORIENTATION_CCW = 1,
ORIENTATION_NORMAL = 2,
ORIENTATION_FLIPPED = 3
};
enum graphics_dx9on12_state {
@@ -29,7 +33,6 @@ extern bool GRAPHICS_LOG_HRESULT;
extern bool GRAPHICS_SDVX_FORCE_720;
extern bool GRAPHICS_SHOW_CURSOR;
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;
@@ -37,6 +40,7 @@ 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;
@@ -49,6 +53,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 bool GRAPHICS_WINDOW_DISABLE_ROUNDED_CORNERS;
extern std::optional<HWND> GRAPHICS_HOOKED_WINDOW;
extern bool GRAPHICS_IIDX_WSUB;
@@ -62,9 +67,11 @@ extern bool GRAPHICS_WSUB_BORDERLESS;
extern bool GRAPHICS_WSUB_ALWAYS_ON_TOP;
extern HWND TDJ_SUBSCREEN_WINDOW;
extern HWND SDVX_SUBSCREEN_WINDOW;
extern HWND POPN_SUBSCREEN_WINDOW;
extern HWND GFDM_SUBSCREEN_WINDOW;
extern bool SUBSCREEN_FORCE_REDRAW;
extern bool FAKE_SUBSCREEN_ADAPTER;
// settings
extern std::string GRAPHICS_DEVICEID;
@@ -98,11 +105,16 @@ std::string graphics_screenshot_genpath();
// graphics_windowed.cpp
void graphics_windowed_wndproc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam);
void graphics_capture_initial_window(HWND hWnd);
void graphics_update_window_style(HWND hWnd);
void graphics_update_window_style(HWND hWnd, bool force_update=false);
void graphics_update_z_order(HWND hWnd, bool always_on_top);
void graphics_set_corner_preference(HWND hWnd, bool disable);
void graphics_move_resize_window(HWND hWnd);
bool graphics_window_options_breaks_game();
bool graphics_window_decoration_change_crashes_game();
bool graphics_window_resize_breaks_game();
bool graphics_window_move_and_resize_breaks_game();
void graphics_load_windowed_subscreen_parameters();
void graphics_window_check_bounds_before_creation(int &x, int &y, const int width, const int height);
void change_primary_monitor(const std::string &monitor_name);
void update_monitor_on_boot(std::optional<graphics_orientation> target_orientation, UINT target_refresh_rate);
@@ -24,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;
bool GRAPHICS_WINDOW_DISABLE_ROUNDED_CORNERS = false;
std::optional<HWND> GRAPHICS_HOOKED_WINDOW;
// IIDX Windowed Subscreen - starts out as false, enabled by IIDX module on pre-attach as needed
@@ -121,18 +122,21 @@ void graphics_capture_initial_window(HWND hWnd) {
cfg::SCREENRESIZE->client_height);
graphics_move_resize_window(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: {}",
fmt::ptr(hWnd),
cfg::SCREENRESIZE->window_decoration);
graphics_update_window_style(hWnd);
graphics_update_window_style(hWnd, true); // force
}
if (cfg::SCREENRESIZE->window_always_on_top) {
log_info("graphics-windowed", "[{}] change window z-order - always on top", fmt::ptr(hWnd));
graphics_update_z_order(hWnd, true);
}
if (cfg::SCREENRESIZE->window_disable_round_corners) {
log_info("graphics-windowed", "[{}] disabling rounded corners", fmt::ptr(hWnd));
graphics_set_corner_preference(hWnd, true);
}
// ensure spicetouch coordinates are initialized
update_spicetouch_window_dimensions(hWnd);
@@ -173,7 +177,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");
}
}
@@ -182,6 +186,10 @@ void graphics_load_windowed_parameters() {
cfg::SCREENRESIZE->window_always_on_top = true;
}
if (GRAPHICS_WINDOW_DISABLE_ROUNDED_CORNERS) {
cfg::SCREENRESIZE->window_disable_round_corners = true;
}
log_debug("graphics-windowed", "graphics_load_windowed_parameters returned");
}
@@ -202,7 +210,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");
}
}
@@ -216,7 +224,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");
}
}
}
@@ -385,11 +393,11 @@ void graphics_wm_sizing_aspect_ratio(int edge, RECT& rect) {
log_debug("graphics-windowed", "graphics_wm_sizing_aspect_ratio returned");
}
void graphics_update_window_style(HWND hWnd) {
void graphics_update_window_style(HWND hWnd, bool force_update) {
if (!GRAPHICS_WINDOWED) {
return;
}
if (graphics_window_decoration_change_crashes_game()) {
if (!force_update && graphics_window_decoration_change_crashes_game()) {
return;
}
@@ -470,6 +478,27 @@ void graphics_update_z_order(HWND hWnd, bool always_on_top) {
log_debug("graphics-windowed", "graphics_update_z_order returned");
}
void graphics_set_corner_preference(HWND hWnd, bool disable) {
if (!GRAPHICS_WINDOWED) {
return;
}
#if !SPICE_XP
log_debug("graphics-windowed", "graphics_set_corner_preference called");
DWM_WINDOW_CORNER_PREFERENCE corner_preference = disable ? DWMWCP_DONOTROUND : DWMWCP_DEFAULT;
DwmSetWindowAttribute(
hWnd,
DWMWA_WINDOW_CORNER_PREFERENCE,
&corner_preference, sizeof(corner_preference));
log_debug("graphics-windowed", "graphics_set_corner_preference returned");
#else
// don't link against DwmSetWindowAttribute on XP
log_debug("graphics-windowed", "graphics_set_corner_preference called; ignoring");
#endif
}
void graphics_move_resize_window(HWND hWnd) {
if (!GRAPHICS_WINDOWED) {
return;
@@ -503,8 +532,15 @@ 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");
// ddr and ftt crash when frame style changes
if (avs::game::is_model("MDX") || avs::game::is_model("MMD")) {
result = true;
}
// arena model hangs immediately when decoration changes
if (games::gitadora::is_arena_model()) {
result = true;
}
// changing window decoration also changes dimensions and position
// so this must be prevented
@@ -560,3 +596,56 @@ bool graphics_window_resize_breaks_game() {
return result;
}
void graphics_window_check_bounds_before_creation(int &x, int &y, const int width, const int height) {
RECT rect = {
.left = x,
.top = y,
.right = x + width,
.bottom = y + height
};
HMONITOR mon = MonitorFromRect(&rect, MONITOR_DEFAULTTONULL);
if (mon != nullptr) {
return;
}
log_warning(
"graphics-windowed",
"window is completely out of bounds, resetting position: ({}, {}) => (0, 0)",
x, y);
x = 0;
y = 0;
rect = {
.left = x,
.top = y,
.right = x + width,
.bottom = y + height
};
mon = MonitorFromRect(&rect, MONITOR_DEFAULTTONEAREST);
MONITORINFO mi = {};
mi.cbSize = sizeof(mi);
if (!GetMonitorInfo(mon, &mi)) {
log_warning(
"graphics-windowed",
"failed to get monitor info for out of bounds window");
return;
}
log_misc(
"graphics-windowed",
"nearest monitor for this window has work area ({}, {}),({}, {})",
mi.rcWork.left, mi.rcWork.top, mi.rcWork.right, mi.rcWork.bottom);
RECT bounds = mi.rcWork;
const bool fully_visible =
(rect.left >= bounds.left &&
rect.top >= bounds.top &&
rect.right <= bounds.right &&
rect.bottom <= bounds.bottom);
if (!fully_visible) {
log_warning("graphics-windowed", "window will not be fully visible on monitor");
}
}
+55
View File
@@ -1,3 +1,6 @@
// GetLocaleInfoEx
#define _WIN32_WINNT 0x0600
#include "lang.h"
#define WIN32_NO_STATUS
@@ -27,6 +30,8 @@ static decltype(GetLocaleInfoEx) *GetLocaleInfoEx_orig = nullptr;
static decltype(GetSystemDefaultLCID) *GetSystemDefaultLCID_orig = nullptr;
static decltype(IsDBCSLeadByte) *IsDBCSLeadByte_orig = nullptr;
static decltype(WideCharToMultiByte) *WideCharToMultiByte_orig = nullptr;
static decltype(GetLocaleInfoA) *GetLocaleInfoA_orig = nullptr;
static decltype(GetThreadLocale) *GetThreadLocale_orig = nullptr;
#endif
static NTSTATUS NTAPI RtlMultiByteToUnicodeN_hook(
@@ -91,6 +96,10 @@ static LCID WINAPI GetSystemDefaultLCID_hook() {
return 0x411;
}
static LCID WINAPI GetThreadLocale_hook() {
return 0x411;
}
#endif
static int WINAPI MultiByteToWideChar_hook(
@@ -211,6 +220,31 @@ WideCharToMultiByte_hook(
lpUsedDefaultChar);
}
int
WINAPI
GetLocaleInfoA_hook(
LCID Locale,
LCTYPE LCType,
LPSTR lpLCData,
int cchData) {
if (LCType == LOCALE_SISO639LANGNAME && lpLCData != NULL && cchData >= 3) {
log_misc("hooks::lang", "GetLocaleInfoA_hook hit ({:#x}, LOCALE_SISO639LANGNAME), return `ja`", Locale);
strcpy(lpLCData, "ja");
return 3;
}
if (LCType == LOCALE_SISO3166CTRYNAME && lpLCData != NULL && cchData >= 3) {
log_misc("hooks::lang",
"GetLocaleInfoA_hook hit ({:#x}, LOCALE_SISO3166CTRYNAME), return `JP`", Locale);
strcpy(lpLCData, "JP");
return 3;
}
log_misc("hooks::lang", "GetLocaleInfoA_hook hit, {:#x}, {:#x}", Locale, LCType);
return GetLocaleInfoA_orig(Locale, LCType, lpLCData, cchData);
}
#endif
void hooks::lang::early_init() {
@@ -241,6 +275,27 @@ void hooks::lang::early_init() {
}
#ifdef SPICE64
if (avs::game::is_model("UJK")) {
// CCJ build of Unity calls GetThreadLocale,
// and then GetLocaleInfoA to figure out the locale; eventually this is
// used to figure out the decimal separator, which is then used in parsing
// Double values - which fails carding in if it isn't "."
log_info("hooks::lang", "hooking GetThreadLocale");
detour::trampoline_try(
"kernel32.dll",
"GetThreadLocale",
GetThreadLocale_hook,
&GetThreadLocale_orig);
log_info("hooks::lang", "hooking GetLocaleInfoA");
detour::trampoline_try(
"kernel32.dll",
"GetLocaleInfoA",
GetLocaleInfoA_hook,
&GetLocaleInfoA_orig);
}
// for TDJ subscreen search keyboard
if (avs::game::is_model("LDJ") && games::iidx::TDJ_MODE) {
log_info("hooks::lang", "hooking IsDBCSLeadByte");
+3
View File
@@ -1,3 +1,6 @@
// DisplayConfigSetDeviceInfo
#define _WIN32_WINNT 0x0601
#include "winuser.h"
#include "util/detour.h"
+126 -41
View File
@@ -351,9 +351,9 @@ int main_implementation(int argc, char *argv[]) {
GRAPHICS_FORCE_SINGLE_ADAPTER = true;
GRAPHICS_PREVENT_SECONDARY_WINDOW = true;
}
if (options[launcher::Options::DisplayAdapter].is_active() &&
options[launcher::Options::DisplayAdapter].value_uint32() != D3DADAPTER_DEFAULT) {
D3D9_ADAPTER = options[launcher::Options::DisplayAdapter].value_uint32();
if (options[launcher::Options::DXDisplayAdapter].is_active() &&
options[launcher::Options::DXDisplayAdapter].value_uint32() != D3DADAPTER_DEFAULT) {
D3D9_ADAPTER = options[launcher::Options::DXDisplayAdapter].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
@@ -374,12 +374,15 @@ int main_implementation(int argc, char *argv[]) {
if (options[launcher::Options::AllowEA3Verbose].value_bool()) {
avs::ea3::EA3_DEBUG_VERBOSE = true;
}
std::optional<graphics_orientation> monitor_orientation;
if (options[launcher::Options::spice2x_AutoOrientation].is_active()) {
GRAPHICS_ADJUST_ORIENTATION =
monitor_orientation =
(graphics_orientation)options[launcher::Options::spice2x_AutoOrientation].value_uint32();
} else if (options[launcher::Options::AdjustOrientation].value_bool()) {
GRAPHICS_ADJUST_ORIENTATION = ORIENTATION_CW;
monitor_orientation = ORIENTATION_CW;
}
if (options[launcher::Options::spice2x_NoD3D9DeviceHook].value_bool()) {
D3D9_DEVICE_HOOK_DISABLE = true;
// touch emulation gets disabled, might as well turn these on
@@ -470,6 +473,9 @@ int main_implementation(int argc, char *argv[]) {
if (options[launcher::Options::spice2x_SDVXSubRedraw].value_bool()) {
SUBSCREEN_FORCE_REDRAW = true;
}
if (options[launcher::Options::SDVXSubMonitorOverride].is_active()) {
sysutils::SECOND_MONITOR_OVERRIDE = options[launcher::Options::SDVXSubMonitorOverride].value_text();
}
if (options[launcher::Options::LoadIIDXModule].value_bool()) {
attach_iidx = true;
}
@@ -559,6 +565,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;
}
@@ -580,6 +590,16 @@ int main_implementation(int argc, char *argv[]) {
if (options[launcher::Options::PopnMusicForceSDMode].value_bool()) {
avs::ea3::PCB_TYPE = 0;
}
if (options[launcher::Options::PopnNoSub].value_bool()) {
GRAPHICS_FORCE_SINGLE_ADAPTER = true;
GRAPHICS_PREVENT_SECONDARY_WINDOW = true;
}
if (options[launcher::Options::PopnSubMonitorOverride].is_active()) {
sysutils::SECOND_MONITOR_OVERRIDE = options[launcher::Options::PopnSubMonitorOverride].value_text();
}
if (options[launcher::Options::PopnNativeTouch].value_bool()) {
games::popn::NATIVE_TOUCH = true;
}
if (options[launcher::Options::LoadMetalGearArcadeModule].value_bool()) {
attach_mga = true;
}
@@ -976,7 +996,7 @@ int main_implementation(int argc, char *argv[]) {
// do not explicitly set GRAPHICS_9_ON_12_STATE to default here; must respect
// legacy Graphics9On12 option from above if set
}
if (options[launcher::Options::NoLegacy].value_bool() && !cfg::CONFIGURATOR_STANDALONE) {
if (options[launcher::Options::NoLegacy].value_bool() && !cfg_run) {
rawinput::NOLEGACY = true;
}
if (options[launcher::Options::RichPresence].value_bool()) {
@@ -1011,18 +1031,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;
}
@@ -1083,8 +1111,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()) {
@@ -1092,6 +1120,7 @@ int main_implementation(int argc, char *argv[]) {
}
GRAPHICS_WINDOW_ALWAYS_ON_TOP = options[launcher::Options::spice2x_WindowAlwaysOnTop].value_bool();
GRAPHICS_WINDOW_BACKBUFFER_SCALE = options[launcher::Options::WindowForceScaling].value_bool();
GRAPHICS_WINDOW_DISABLE_ROUNDED_CORNERS = options[launcher::Options::WindowDisableRoundedCorners].value_bool();
// IIDX/SDVX Windowed Subscreen
if (options[launcher::Options::spice2x_IIDXWindowedSubscreenSize].is_active()) {
@@ -1112,6 +1141,9 @@ int main_implementation(int argc, char *argv[]) {
options[launcher::Options::SDVXWindowedSubscreenAlwaysOnTop].value_bool()) {
GRAPHICS_WSUB_ALWAYS_ON_TOP = true;
}
if (options[launcher::Options::IIDXSubMonitorOverride].is_active()) {
sysutils::SECOND_MONITOR_OVERRIDE = options[launcher::Options::IIDXSubMonitorOverride].value_text();
}
if (options[launcher::Options::spice2x_JubeatLegacyTouch].value_bool()) {
games::jb::TOUCH_LEGACY_BOX = true;
@@ -1212,7 +1244,7 @@ int main_implementation(int argc, char *argv[]) {
acio::MDXF_BUFFER_FILL_MODE = acio::MDXFBufferFillMode::THREAD_MODE;
} else if (options[launcher::Options::DDRP4IOBufferMode].value_text() == "backfill") {
acio::MDXF_BUFFER_FILL_MODE = acio::MDXFBufferFillMode::BACKFILL_MODE;
}
}
}
if (options[launcher::Options::MidiAlgoVer].is_active()) {
@@ -1279,7 +1311,19 @@ int main_implementation(int argc, char *argv[]) {
}
// log
#ifndef SPICE_LINUX
#if SPICE_LINUX
#ifdef SPICE64
log_info("launcher", "SpiceTools Bootstrap (x64) (spice2x) for Linux");
#else
log_info("launcher", "SpiceTools Bootstrap (x32) (spice2x) for Linux");
#endif
#elif SPICE_XP
#ifdef SPICE64
log_info("launcher", "SpiceTools Bootstrap (x64) (spice2x) for WinXP");
#else
log_info("launcher", "SpiceTools Bootstrap (x32) (spice2x) for WinXP");
#endif
#else
#ifdef SPICE64
log_info("launcher", "SpiceTools Bootstrap (x64) (spice2x)");
#elif SPICE32_LARGE_ADDRESS_AWARE
@@ -1287,12 +1331,6 @@ int main_implementation(int argc, char *argv[]) {
#else
log_info("launcher", "SpiceTools Bootstrap (x32) (spice2x)");
#endif
#else
#ifdef SPICE64
log_info("launcher", "SpiceTools Bootstrap (x64) (spice2x) for Linux");
#else
log_info("launcher", "SpiceTools Bootstrap (x32) (spice2x) for Linux");
#endif
#endif
log_info("launcher", "{}", VERSION_STRING);
@@ -1349,7 +1387,7 @@ int main_implementation(int argc, char *argv[]) {
log_warning(
"launcher",
"multiple values for -{}, command line args take precedence: {}",
option.get_definition().name,
option.get_definition().name,
value);
} else {
log_warning(
@@ -1364,7 +1402,7 @@ int main_implementation(int argc, char *argv[]) {
if (launcher::USE_CMD_OVERRIDE) {
log_info(
"launcher",
"user specified -cmdoverride, therefore command line args took precedence over spicecfg");
"user specified -cmdoverride, therefore command line args took precedence over spicecfg");
} else {
log_warning(
"launcher",
@@ -1385,12 +1423,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");
}
}
@@ -1474,6 +1533,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());
@@ -1708,11 +1768,21 @@ int main_implementation(int argc, char *argv[]) {
break;
}
// HELLO! Pop'n Music
if (check_dll("popn.dll")) {
avs::game::DLL_NAME = "popn.dll";
attach_io = true;
attach_hpm = true;
if (check_dll("libaio-iob2_video.dll")) {
// pop'n music (pika cabinet)
attach_popn = true;
// automatically show cursor in windowed mode to interact with second window (sub)
if (GRAPHICS_WINDOWED) {
GRAPHICS_SHOW_CURSOR = true;
}
} else {
// HELLO! Pop'n Music
attach_hpm = true;
}
break;
}
@@ -1765,9 +1835,7 @@ int main_implementation(int argc, char *argv[]) {
attach_ftt = true;
// the game is windowed by default unless we set the env
if (GRAPHICS_WINDOWED) {
GRAPHICS_WINDOWED = false;
} else {
if (!GRAPHICS_WINDOWED) {
SetEnvironmentVariable("DAMAC_VIEWER_FULLSCREEN", "0");
}
@@ -1867,7 +1935,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;
@@ -2099,6 +2167,12 @@ int main_implementation(int argc, char *argv[]) {
sysutils::print_gpus();
}
// fix up monitor
if (options[launcher::Options::PrimaryMonitor].is_active()) {
change_primary_monitor(options[launcher::Options::PrimaryMonitor].value_text());
}
update_monitor_on_boot(monitor_orientation, GRAPHICS_FORCE_REFRESH);
// initialize raw input
RI_MGR = std::make_unique<rawinput::RawInputManager>();
for (const auto &device : sextet_devices) {
@@ -2183,7 +2257,7 @@ int main_implementation(int argc, char *argv[]) {
game->attach();
}
#ifdef SPICE64
#if SPICE64 && !SPICE_XP
if (!cfg::CONFIGURATOR_STANDALONE) {
if (games::iidx::TDJ_CAMERA) {
games::iidx::init_camera_hooks();
@@ -2274,6 +2348,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());
@@ -2289,9 +2364,9 @@ int main_implementation(int argc, char *argv[]) {
// layeredfs
if (fileutils::dir_exists("data_mods") &&
GetModuleHandleA("ifs_hook.dll") == nullptr) {
log_warning("launcher", "data_mods directory found, but ifs_hook.dll is not present; mods will not load");
log_warning("launcher", "data_mods directory found, but ifs_hook.dll is not loaded; mods will not load");
deferredlogs::defer_error_messages({
"data_mods directory was found, but ifs_hook.dll is not present",
"data_mods directory was found, but ifs_hook.dll is not loaded",
" your mods will not load",
" to fix this, download ifs_layeredfs and add it as a DLL hook (-k)",
" https://github.com/mon/ifs_layeredfs"
@@ -2463,7 +2538,7 @@ int main_implementation(int argc, char *argv[]) {
// disable poke
games::iidx::poke::disable();
#ifdef SPICE64
#if SPICE64 && !SPICE_XP
games::iidx::camera_release();
#endif
@@ -2534,9 +2609,10 @@ void dump_button_bindings(std::vector<Button> *buttons) {
if (button->isNaive()) {
log_misc(
"rawinput", " [{}] dev=Naive, vkey={}",
"rawinput", " [{}] dev=Naive, vkey={} ({})",
button->getName(),
button->getVKey()
button->getVKey(),
button->getVKeyString()
);
} else {
log_misc(
@@ -2549,16 +2625,25 @@ void dump_button_bindings(std::vector<Button> *buttons) {
}
for (auto& alt : button->getAlternatives()) {
if (alt.getVKey() == INVALID_VKEY) {
if (!alt.isValid()) {
continue;
}
log_misc(
"rawinput", " [{}] (alt) dev={}, vkey={}, analogtype={}",
button->getName(),
alt.isNaive() ? "Naive" : alt.getDeviceIdentifier(),
alt.getVKey(),
static_cast<uint32_t>(alt.getAnalogType())
);
if (alt.isNaive()) {
log_misc(
"rawinput", " [{}] (alt) dev=Naive, vkey={} ({})",
alt.getName(),
alt.getVKey(),
alt.getVKeyString()
);
} else {
log_misc(
"rawinput", " [{}] (alt) dev={}, vkey={}, analogtype={}",
alt.getName(),
alt.getDeviceIdentifier(),
alt.getVKey(),
static_cast<uint32_t>(alt.getAnalogType())
);
}
}
}
}
+202 -58
View File
@@ -23,6 +23,7 @@ static const std::vector<std::string> CATEGORY_ORDER_BASIC = {
"Game Options",
"Common",
"Network",
"Monitor",
"Graphics (Common)",
"Graphics (Full Screen)",
"Graphics (Windowed)",
@@ -31,6 +32,7 @@ static const std::vector<std::string> CATEGORY_ORDER_BASIC = {
static const std::vector<std::string> CATEGORY_ORDER_ADVANCED = {
"Game Options (Advanced)",
"Game Options (Peripherals)",
"Overlay",
"Network (Advanced)",
"Performance",
@@ -71,6 +73,7 @@ static const std::vector<OptionDefinition> OPTION_DEFINITIONS = {
.type = OptionType::Text,
.setting_name = "*.dll",
.category = "Path Overrides",
// intentionally not setting a file picker here to discourage people setting this without a good reason
},
{
.title = "Open Configurator",
@@ -121,6 +124,7 @@ static const std::vector<OptionDefinition> OPTION_DEFINITIONS = {
.setting_name = "E004010000000000",
.category = "Network",
.sensitive = true,
.picker = OptionPickerType::EACard,
},
{
.title = "Player 2 Card",
@@ -130,6 +134,7 @@ static const std::vector<OptionDefinition> OPTION_DEFINITIONS = {
.setting_name = "E004010000000000",
.category = "Network",
.sensitive = true,
.picker = OptionPickerType::EACard,
},
{
// Player1PinMacro
@@ -203,9 +208,24 @@ static const std::vector<OptionDefinition> OPTION_DEFINITIONS = {
.category = "Common",
},
{
.title = "Monitor",
// PrimaryMonitor
.title = "Change Main Monitor",
.name = "mainmonitor",
.desc = "Changes the primary monitor before launching the game. It will be restored on exit.\n\n"
"This may fail to work properly on hybrid laptops with iGPU + dGPU.",
.type = OptionType::Text,
.setting_name = "\\\\.\\DISPLAY2",
.category = "Monitor",
.picker = OptionPickerType::Monitor,
},
{
// DXDisplayAdapter
.title = "DX Primary Display Adapter Override",
.name = "monitor",
.desc = "Sets the display that the game will be opened in, for multiple monitors.\n\n"
.display_name = "dxmainadapter",
.aliases = "dxmainadapter",
.desc = "Prefer to use Change Main Monitor option instead of this one.\n\n"
"Sets the display that the game will be opened in, for multiple monitors.\n\n"
"0 is the primary monitor, 1 is the second monitor, and so on.\n\n"
"Not all games will respect this. Not recommended for multi-monitor games like Lightning Model / Valkyrie Model modes. "
"Disable Full Screen Optimizations for best results.",
@@ -213,21 +233,19 @@ static const std::vector<OptionDefinition> OPTION_DEFINITIONS = {
.category = "Graphics (Full Screen)",
},
{
.title = "Only Use One Monitor",
.title = "Only Use Main Monitor For Full Screen",
.name = "graphics-force-single-adapter",
.desc = "Force the graphics device to be opened utilizing only one adapter in multi-monitor systems.\n\n"
"May cause unstable framerate and desyncs, especially if monitors have different refresh rates!",
.type = OptionType::Bool,
.category = "Graphics (Full Screen)",
.category = "Monitor",
},
{
.title = "Force Monitor Refresh Rate",
.title = "Monitor Refresh Rate",
.name = "graphics-force-refresh",
.desc =
"Attempt to change the refresh rate for the primary monitor before the game boots; "
"works in both full screen and windowed modes, but known to fail for some games.",
.desc = "Change the refresh rate for the primary monitor before launching the game. It will be restored on exit.",
.type = OptionType::Integer,
.category = "Graphics (Common)",
.category = "Monitor",
},
{
// FullscreenResolution
@@ -256,9 +274,21 @@ static const std::vector<OptionDefinition> OPTION_DEFINITIONS = {
.hidden = true,
.category = "Graphics (Full Screen)"
},
{
// FullscreenSubResolution
.title = "Force FS Subscreen Resolution (EXPERIMENTAL)",
.name = "forceressub",
.desc =
"Override fullscreen resolution requested by the game for second monitor, "
"useful if you have a sub monitor that is not quite exactly what the game expects.\n\n"
"WARNING: experimental as we have not done extensive testing to see if this causes desyncs.",
.type = OptionType::Text,
.setting_name = "1280,720",
.category = "Graphics (Full Screen)"
},
{
// FullscreenSubRefreshRate
.title = "Force Submonitor Refresh Rate (EXPERIMENTAL)",
.title = "Force FS Subscreen Refresh Rate (EXPERIMENTAL)",
.name = "graphics-force-refresh-sub",
.desc =
"Override fullscreen refresh rate requested by the game for second monitor, "
@@ -484,7 +514,7 @@ static const std::vector<OptionDefinition> OPTION_DEFINITIONS = {
.desc = "Flip the camera order.",
.type = OptionType::Bool,
.game_name = "Beatmania IIDX",
.category = "Game Options (Advanced)",
.category = "Game Options (Peripherals)",
},
{
// IIDXDisableCameras
@@ -494,7 +524,7 @@ static const std::vector<OptionDefinition> OPTION_DEFINITIONS = {
.type = OptionType::Bool,
.hidden = true,
.game_name = "Beatmania IIDX",
.category = "Game Options",
.category = "Game Options (Peripherals)",
},
{
// IIDXCabCamAccess
@@ -504,7 +534,7 @@ static const std::vector<OptionDefinition> OPTION_DEFINITIONS = {
"Only turn this on if you have OFFICIAL arcade cameras connected to the correct USB ports. Default: auto.",
.type = OptionType::Enum,
.game_name = "Beatmania IIDX",
.category = "Game Options (Advanced)",
.category = "Game Options (Peripherals)",
.elements = {
{"auto", ""},
{"off", ""},
@@ -520,7 +550,7 @@ static const std::vector<OptionDefinition> OPTION_DEFINITIONS = {
.desc = "Experimental camera support for IIDX 27+ via texture hooking. Requires camera(s) that support YUY2 or NV12 encoding.",
.type = OptionType::Bool,
.game_name = "Beatmania IIDX",
.category = "Game Options (Advanced)",
.category = "Game Options (Peripherals)",
},
{
// IIDXCamHookRatio
@@ -532,7 +562,7 @@ static const std::vector<OptionDefinition> OPTION_DEFINITIONS = {
.type = OptionType::Enum,
.hidden = true,
.game_name = "Beatmania IIDX",
.category = "Game Options (Advanced)",
.category = "Game Options (Peripherals)",
.elements = {
{"43", "4:3"},
{"169", "16:9"},
@@ -550,7 +580,7 @@ static const std::vector<OptionDefinition> OPTION_DEFINITIONS = {
.type = OptionType::Text,
.setting_name = "0x5817a0,0x6fffbd8,0xbbae40,0xe0,0x30",
.game_name = "Beatmania IIDX",
.category = "Game Options (Advanced)",
.category = "Game Options (Peripherals)",
},
{
// IIDXCamHookTopId
@@ -565,7 +595,7 @@ static const std::vector<OptionDefinition> OPTION_DEFINITIONS = {
.type = OptionType::Text,
.setting_name = "vid_1234&pid_5678",
.game_name = "Beatmania IIDX",
.category = "Game Options (Advanced)",
.category = "Game Options (Peripherals)",
},
{
// IIDXCamHookFrontId
@@ -580,7 +610,7 @@ static const std::vector<OptionDefinition> OPTION_DEFINITIONS = {
.type = OptionType::Text,
.setting_name = "vid_90ab&pid_cdef",
.game_name = "Beatmania IIDX",
.category = "Game Options (Advanced)",
.category = "Game Options (Peripherals)",
},
{
.title = "IIDX Sound Output Device",
@@ -606,6 +636,7 @@ static const std::vector<OptionDefinition> OPTION_DEFINITIONS = {
.type = OptionType::Text,
.game_name = "Beatmania IIDX",
.category = "Game Options",
.picker = OptionPickerType::AsioDriver,
},
{
.title = "IIDX BIO2 Firmware Update",
@@ -742,11 +773,11 @@ static const std::vector<OptionDefinition> OPTION_DEFINITIONS = {
.category = "Game Options (Advanced)",
},
{
.title = "SDVX Force 720p",
.title = "SDVX Vivid Wave Force 720p Window (DEPRECATED - use -windowsize instead)",
.name = "sdvx720",
.desc = "Force Sound Voltex 720p display mode, used by older games. "
"For newer games, use window resize function instead.",
.desc = "Old & deprecated option for launching Vivid Wave in 720p when using windowed mode.",
.type = OptionType::Bool,
.hidden = true,
.game_name = "Sound Voltex",
.category = "Game Options (Advanced)",
},
@@ -756,7 +787,7 @@ static const std::vector<OptionDefinition> OPTION_DEFINITIONS = {
.desc = "Enable Sound Voltex printer emulation.",
.type = OptionType::Bool,
.game_name = "Sound Voltex",
.category = "Game Options (Advanced)",
.category = "Game Options (Peripherals)",
},
{
// SDVXPrinterOutputPath
@@ -765,7 +796,8 @@ static const std::vector<OptionDefinition> OPTION_DEFINITIONS = {
.desc = "Path to folder where images will be stored.",
.type = OptionType::Text,
.game_name = "Sound Voltex",
.category = "Game Options (Advanced)",
.category = "Game Options (Peripherals)",
.picker = OptionPickerType::DirectoryPath,
},
{
.title = "SDVX Printer Output Clear",
@@ -773,7 +805,7 @@ static const std::vector<OptionDefinition> OPTION_DEFINITIONS = {
.desc = "Clean up saved images in the output directory on startup.",
.type = OptionType::Bool,
.game_name = "Sound Voltex",
.category = "Game Options (Advanced)",
.category = "Game Options (Peripherals)",
},
{
.title = "SDVX Printer Output Overwrite",
@@ -781,7 +813,7 @@ static const std::vector<OptionDefinition> OPTION_DEFINITIONS = {
.desc = "Always overwrite the same file in output directory.",
.type = OptionType::Bool,
.game_name = "Sound Voltex",
.category = "Game Options (Advanced)",
.category = "Game Options (Peripherals)",
},
{
// SDVXPrinterOutputFormat
@@ -791,7 +823,7 @@ static const std::vector<OptionDefinition> OPTION_DEFINITIONS = {
.type = OptionType::Text,
.setting_name = "(png/bmp/tga/jpg)",
.game_name = "Sound Voltex",
.category = "Game Options (Advanced)",
.category = "Game Options (Peripherals)",
},
{
.title = "SDVX Printer JPG Quality",
@@ -800,7 +832,7 @@ static const std::vector<OptionDefinition> OPTION_DEFINITIONS = {
.type = OptionType::Integer,
.setting_name = "(0-100)",
.game_name = "Sound Voltex",
.category = "Game Options (Advanced)",
.category = "Game Options (Peripherals)",
},
{
.title = "SDVX Disable Cameras (DEPRECATED - no longer needed)",
@@ -811,10 +843,10 @@ static const std::vector<OptionDefinition> OPTION_DEFINITIONS = {
.type = OptionType::Bool,
.hidden = true,
.game_name = "Sound Voltex",
.category = "Game Options",
.category = "Game Options (Peripherals)",
},
{
.title = "SDVX Native Touch Handling",
.title = "SDVX FS Subscreen Native Touch Handling",
.name = "sdvxnativetouch",
.desc = "Disables touch hooks and lets the game access a touch screen directly. "
"Requires a touch screen to be connected as a secondary monitor. "
@@ -824,6 +856,18 @@ static const std::vector<OptionDefinition> OPTION_DEFINITIONS = {
.game_name = "Sound Voltex",
.category = "Game Options (Advanced)",
},
{
// spice2x_SDVXSubRedraw
.title = "SDVX FS Subscreen Force Redraw",
.name = "sp2x-sdvxsubredraw",
.display_name = "sdvxsubredraw",
.aliases= "sdvxsubredraw",
.desc = "Check if submonitor in fullscreen mode doesn't update every frame; "
"this option forces subscreen to redraw every frame.",
.type = OptionType::Bool,
.game_name = "Sound Voltex",
.category = "Game Options (Advanced)",
},
{
// spice2x_SDVXDigitalKnobSensitivity
.title = "SDVX Digital Knob Sensitivity",
@@ -860,11 +904,12 @@ static const std::vector<OptionDefinition> OPTION_DEFINITIONS = {
.display_name = "sdvxasio",
.aliases= "sdvxasio",
.desc = "ASIO driver name to use, replacing XONAR SOUND CARD(64). "
"String should match registry key under HLKM\\SOFTWARE\\ASIO\\ \n\n"
"String should match registry key under HKLM\\SOFTWARE\\ASIO\\ \n\n"
"SDVX is EXTREMELY picky about ASIO devices it can support!",
.type = OptionType::Text,
.game_name = "Sound Voltex",
.category = "Game Options",
.picker = OptionPickerType::AsioDriver,
},
{
// spice2x_SDVXSubPos
@@ -885,16 +930,15 @@ static const std::vector<OptionDefinition> OPTION_DEFINITIONS = {
},
},
{
// spice2x_SDVXSubRedraw
.title = "SDVX Subscreen Force Redraw",
.name = "sp2x-sdvxsubredraw",
.display_name = "sdvxsubredraw",
.aliases= "sdvxsubredraw",
.desc = "Check if second monitor for subscreen doesn't update every frame; "
"forces subscreen to redraw every frame, only needed for newer EG.",
.type = OptionType::Bool,
// SDVXSubMonitorOverride
.title = "SDVX Subscreen Monitor Override",
.name = "sdvxsubmonitor",
.desc = "If you have three or more monitors, this option can be set to tell the game which monitor is the subscreen.",
.type = OptionType::Text,
.setting_name = "\\\\.\\DISPLAY3",
.game_name = "Sound Voltex",
.category = "Game Options (Advanced)",
.category = "Monitor",
.picker = OptionPickerType::Monitor,
},
{
.title = "Force Load DDR Module",
@@ -945,6 +989,38 @@ static const std::vector<OptionDefinition> OPTION_DEFINITIONS = {
.game_name = "Pop'n Music",
.category = "Game Options",
},
{
// PopnNoSub
.title = "Pop'n Music PikaPika Subscreen Disable",
.name = "popnnosub",
.desc = "Prevents PikaPika model subscreen from launching a separate window.",
.type = OptionType::Bool,
.game_name = "Pop'n Music",
.category = "Game Options",
},
{
// PopnSubMonitorOverride
.title = "Pop'n Music PikaPika Subscreen Monitor Override",
.name = "popnsubmonitor",
.desc = "If you have three or more monitors, this option can be set to tell the game which monitor is the subscreen.",
.type = OptionType::Text,
.setting_name = "\\\\.\\DISPLAY3",
.game_name = "Pop'n Music",
.category = "Monitor",
.picker = OptionPickerType::Monitor,
},
{
// PopnNativeTouch
.title = "Pop'n Music PikaPika Native Touch Handling",
.name = "popnnativetouch",
.desc = "Disables touch hooks and lets the game access a touch screen directly. "
"Requires a touch screen to be connected as a secondary monitor. "
"Touch input must be routed to the primary screen via Windows Tablet PC settings. "
"Enable this when you get duplicate touch inputs from an actual touch screen.",
.type = OptionType::Bool,
.game_name = "Pop'n Music",
.category = "Game Options (Advanced)",
},
{
.title = "Force Load HELLO! Pop'n Music Module",
.name = "hpm",
@@ -1272,6 +1348,7 @@ static const std::vector<OptionDefinition> OPTION_DEFINITIONS = {
.desc = "Sets a custom path to the modules folder.",
.type = OptionType::Text,
.category = "Path Overrides",
// intentionally not setting a folder picker here to discourage people setting this without a good reason
},
{
.title = "Screenshot Folder Override",
@@ -1279,6 +1356,7 @@ static const std::vector<OptionDefinition> OPTION_DEFINITIONS = {
.desc = "Sets a custom path to the screenshots folder.",
.type = OptionType::Text,
.category = "Path Overrides",
.picker = OptionPickerType::DirectoryPath,
},
{
.title = "Configuration Path Override",
@@ -1298,6 +1376,8 @@ static const std::vector<OptionDefinition> OPTION_DEFINITIONS = {
"If left empty, %appdata%\\spice2x\\spicetools_screen_resize.json will be used.",
.type = OptionType::Text,
.category = "Path Overrides",
.picker = OptionPickerType::FilePath,
.file_extension = "JSON",
},
{
// PatchManagerConfigPath
@@ -1307,6 +1387,8 @@ static const std::vector<OptionDefinition> OPTION_DEFINITIONS = {
"If left empty, %appdata%\\spice2x\\spicetools_patch_manager.json will be used.",
.type = OptionType::Text,
.category = "Path Overrides",
.picker = OptionPickerType::FilePath,
.file_extension = "JSON",
},
{
.title = "Intel SDE",
@@ -1314,6 +1396,7 @@ static const std::vector<OptionDefinition> OPTION_DEFINITIONS = {
.desc = "Path to Intel SDE kit path for automatic attaching.",
.type = OptionType::Text,
.category = "Development",
.picker = OptionPickerType::DirectoryPath
},
{
.title = "ea3-config.xml Override",
@@ -1321,6 +1404,8 @@ static const std::vector<OptionDefinition> OPTION_DEFINITIONS = {
.desc = "Sets a custom path to ea3-config.xml.",
.type = OptionType::Text,
.category = "Path Overrides",
.picker = OptionPickerType::FilePath,
.file_extension = "XML",
},
{
.title = "app-config.xml Override",
@@ -1328,6 +1413,8 @@ static const std::vector<OptionDefinition> OPTION_DEFINITIONS = {
.desc = "Sets a custom path to app-config.xml.",
.type = OptionType::Text,
.category = "Path Overrides",
.picker = OptionPickerType::FilePath,
.file_extension = "XML",
},
{
.title = "avs-config.xml Override",
@@ -1335,6 +1422,8 @@ static const std::vector<OptionDefinition> OPTION_DEFINITIONS = {
.desc = "Sets a custom path to avs-config.xml.",
.type = OptionType::Text,
.category = "Path Overrides",
.picker = OptionPickerType::FilePath,
.file_extension = "XML",
},
{
.title = "bootstrap.xml Override",
@@ -1342,6 +1431,8 @@ static const std::vector<OptionDefinition> OPTION_DEFINITIONS = {
.desc = "Sets a custom path to bootstrap.xml.",
.type = OptionType::Text,
.category = "Path Overrides",
.picker = OptionPickerType::FilePath,
.file_extension = "XML",
},
{
.title = "log.txt Override",
@@ -1349,6 +1440,8 @@ static const std::vector<OptionDefinition> OPTION_DEFINITIONS = {
.desc = "Sets a custom path to log.txt.",
.type = OptionType::Text,
.category = "Path Overrides",
.picker = OptionPickerType::FilePath,
.file_extension = "TXT",
},
{
.title = "API TCP Port",
@@ -1497,9 +1590,9 @@ static const std::vector<OptionDefinition> OPTION_DEFINITIONS = {
.category = "Touch Parameters",
},
{
.title = "Invert Raw Input Touch",
.title = "Invert Touch",
.name = "touchinvert",
.desc = "Inverts raw touch coordinates; only works for default raw input handler, "
.desc = "Inverts touch coordinates; only works for default raw input handler and IIDX/SDVX native touch handler, "
"and not Windows Touch API.",
.type = OptionType::Bool,
.category = "Touch Parameters",
@@ -1658,6 +1751,7 @@ static const std::vector<OptionDefinition> OPTION_DEFINITIONS = {
"Must provide a hexadecimal mask (e.g., 0x1ff00).",
.type = OptionType::Hex,
.category = "Performance",
.picker = OptionPickerType::CpuAffinity,
},
{
// spice2x_ProcessorEfficiencyClass
@@ -1730,11 +1824,13 @@ static const std::vector<OptionDefinition> OPTION_DEFINITIONS = {
.category = "Audio (Hacks)",
},
{
.title = "Spice Audio Hook Backend",
// AudioBackend
.title = "Spice Audio Hook Backend (DEPRECATED - use -asioconvert instead)",
.name = "audiobackend",
.desc = "Selects the audio backend to use when spice audio hook is enabled, overriding exclusive WASAPI. "
"Does nothing for games that do not output to exclusive WASAPI.",
.type = OptionType::Enum,
.hidden = true,
.category = "Audio",
.elements = {
{"asio", "ASIO"},
@@ -1742,12 +1838,26 @@ static const std::vector<OptionDefinition> OPTION_DEFINITIONS = {
},
},
{
.title = "Spice Audio Hook ASIO Driver ID",
// AsioDriverId
.title = "Spice Audio Hook ASIO Driver ID (DEPRECATED - use -asioconvert instead)",
.name = "asiodriverid",
.desc = "Selects the ASIO driver id to use when Spice Audio Backend is set to ASIO.",
.type = OptionType::Integer,
.hidden = true,
.category = "Audio",
},
{
// AsioDriverName
.title = "WASAPI Exclusive to ASIO Conversion",
.name = "asioconvert",
.desc = "Converts WASAPI Exclusive audio output to ASIO. Value here should match registry key under HKLM\\SOFTWARE\\ASIO\\\n\n"
"Use this if the game is configured to use WASAPI Exclusive but you want to use your ASIO driver instead.\n\n"
"This should only be used as last resort if your audio device does not support WASAPI Exclusive.\n\n"
"Does nothing for games that do not output to exclusive WASAPI.",
.type = OptionType::Text,
.category = "Audio",
.picker = OptionPickerType::AsioDriver,
},
{
.title = "WASAPI Dummy Context",
.name = "audiodummy",
@@ -1795,17 +1905,20 @@ static const std::vector<OptionDefinition> OPTION_DEFINITIONS = {
},
{
// spice2x_AutoOrientation
.title = "Auto-rotate Display",
.title = "Rotate Monitor",
.name = "sp2x-autoorientation",
.display_name = "autoorientation",
.aliases= "autoorientation",
.desc = "Automatically adjust the orientation of your display when launched. "
"WARNING: game may launch at incorrect refresh rate! Use in combination with "
"-graphics-force-refresh and potentially either -9on12 or full-screen optimizations (FSO) to fix.",
.desc = "Change the orientation of the primary display before launching the game. It will be restored on exit",
.type = OptionType::Enum,
.category = "Graphics (Common)",
// match graphics_orientation
.elements = {{"0", "90 (CW)"}, {"1", "270 (CCW)"}},
.category = "Monitor",
// match graphics_orientation enum
.elements = {
{"0", "Portrait"},
{"1", "Portrait, Flipped"},
{"2", "Landscape"},
{"3", "Landscape, Flipped"}
},
},
{
.title = "AVS Log Level",
@@ -2118,6 +2231,14 @@ static const std::vector<OptionDefinition> OPTION_DEFINITIONS = {
.type = OptionType::Bool,
.category = "Graphics (Windowed)",
},
{
// WindowDisableRoundedCorners
.title = "Disable Round Window Corners",
.name = "windownoroundcorners",
.desc = "Windows 11 and above only: Disables rounded corners on the game window(s).",
.type = OptionType::Bool,
.category = "Graphics (Windowed)",
},
{
// spice2x_IIDXWindowedSubscreenSize
.title = "IIDX Windowed Subscreen Size",
@@ -2247,11 +2368,11 @@ static const std::vector<OptionDefinition> OPTION_DEFINITIONS = {
"Note: this is NOT for the motion camera - you still need a real RealSense camera for that!",
.type = OptionType::Bool,
.game_name = "DANCERUSH",
.category = "Game Options",
.category = "Game Options (Peripherals)",
},
{
// spice2x_IIDXNativeTouch
.title = "IIDX Native Touch Handling",
.title = "IIDX TDJ Subscreen Native Touch Handling",
.name = "sp2x-iidxnativetouch",
.display_name = "iidxnativetouch",
.aliases= "iidxnativetouch",
@@ -2274,6 +2395,17 @@ static const std::vector<OptionDefinition> OPTION_DEFINITIONS = {
.game_name = "Beatmania IIDX",
.category = "Game Options",
},
{
// IIDXSubMonitorOverride
.title = "IIDX TDJ Subscreen Monitor Override",
.name = "iidxsubmonitor",
.desc = "If you have three or more monitors, this option can be set to tell the game which monitor is the subscreen.",
.type = OptionType::Text,
.setting_name = "\\\\.\\DISPLAY3",
.game_name = "Beatmania IIDX",
.category = "Monitor",
.picker = OptionPickerType::Monitor,
},
{
// spice2x_IIDXEmulateSubscreenKeypadTouch
.title = "IIDX TDJ Subscreen Keypad Touch Emulation",
@@ -2570,7 +2702,7 @@ static const std::vector<OptionDefinition> OPTION_DEFINITIONS = {
.desc = "Allow game to access camera; camera must be compatible with game.",
.type = OptionType::Bool,
.game_name = "LovePlus",
.category = "Game Options",
.category = "Game Options (Peripherals)",
},
{
// LovePlusPrinterOutputPath
@@ -2579,7 +2711,8 @@ static const std::vector<OptionDefinition> OPTION_DEFINITIONS = {
.desc = "Path to folder where images will be stored.",
.type = OptionType::Text,
.game_name = "LovePlus",
.category = "Game Options (Advanced)",
.category = "Game Options (Peripherals)",
.picker = OptionPickerType::DirectoryPath,
},
{
.title = "LovePlus Printer Output Clear",
@@ -2587,7 +2720,7 @@ static const std::vector<OptionDefinition> OPTION_DEFINITIONS = {
.desc = "Clean up saved images in the output directory on startup.",
.type = OptionType::Bool,
.game_name = "LovePlus",
.category = "Game Options (Advanced)",
.category = "Game Options (Peripherals)",
},
{
.title = "LovePlus Printer Output Overwrite",
@@ -2595,7 +2728,7 @@ static const std::vector<OptionDefinition> OPTION_DEFINITIONS = {
.desc = "Always overwrite the same file in output directory.",
.type = OptionType::Bool,
.game_name = "LovePlus",
.category = "Game Options (Advanced)",
.category = "Game Options (Peripherals)",
},
{
// LovePlusPrinterOutputFormat
@@ -2605,7 +2738,7 @@ static const std::vector<OptionDefinition> OPTION_DEFINITIONS = {
.type = OptionType::Text,
.setting_name = "(png/bmp/tga/jpg)",
.game_name = "LovePlus",
.category = "Game Options (Advanced)",
.category = "Game Options (Peripherals)",
},
{
.title = "LovePlus Printer JPG Quality",
@@ -2614,7 +2747,7 @@ static const std::vector<OptionDefinition> OPTION_DEFINITIONS = {
.type = OptionType::Integer,
.setting_name = "(0-100)",
.game_name = "LovePlus",
.category = "Game Options (Advanced)",
.category = "Game Options (Peripherals)",
},
{
// OptionConflictResolution
@@ -2625,6 +2758,17 @@ static const std::vector<OptionDefinition> OPTION_DEFINITIONS = {
.type = OptionType::Bool,
.category = "Development",
.disabled = true,
},
{
// OtocaCamHook
.title = "Otoca Camera Check Bypass",
.name = "otocacamhook",
.desc =
"This game REQUIRES a (compatible) webcam to save progress. If you don't have any, check this "
"option to bypass camera error during boot, allowing you to try out the game.",
.type = OptionType::Bool,
.game_name = "Otoca D'or",
.category = "Game Options (Peripherals)",
}
};
+12 -2
View File
@@ -26,11 +26,13 @@ namespace launcher {
ExecuteScript,
CaptureCursor,
ShowCursor,
DisplayAdapter,
PrimaryMonitor,
DXDisplayAdapter,
GraphicsForceSingleAdapter,
GraphicsForceRefresh,
FullscreenResolution,
FullscreenOrientationFlip,
FullscreenSubResolution,
FullscreenSubRefreshRate,
Graphics9On12,
spice2x_Dx9On12,
@@ -85,17 +87,21 @@ namespace launcher {
SDVXPrinterJPGQuality,
SDVXDisableCameras,
SDVXNativeTouch,
spice2x_SDVXSubRedraw,
spice2x_SDVXDigitalKnobSensitivity,
SDVXDigitalKnobSocd,
spice2x_SDVXAsioDriver,
spice2x_SDVXSubPos,
spice2x_SDVXSubRedraw,
SDVXSubMonitorOverride,
LoadDDRModule,
DDR43Mode,
DDRSkipCodecRegisteration,
LoadPopnMusicModule,
PopnMusicForceHDMode,
PopnMusicForceSDMode,
PopnNoSub,
PopnSubMonitorOverride,
PopnNativeTouch,
LoadHelloPopnMusicModule,
LoadGitaDoraModule,
GitaDoraTwoChannelAudio,
@@ -186,6 +192,7 @@ namespace launcher {
spice2x_DisableVolumeHook,
AudioBackend,
AsioDriverId,
AsioDriverName,
AudioDummy,
DelayBy5Seconds,
spice2x_DelayByNSeconds,
@@ -226,6 +233,7 @@ namespace launcher {
spice2x_WindowPosition,
spice2x_WindowAlwaysOnTop,
WindowForceScaling,
WindowDisableRoundedCorners,
spice2x_IIDXWindowedSubscreenSize,
spice2x_IIDXWindowedSubscreenPosition,
IIDXWindowedSubscreenBorderless,
@@ -240,6 +248,7 @@ namespace launcher {
DRSRGBCameraHook,
spice2x_IIDXNativeTouch,
spice2x_IIDXNoSub,
IIDXSubMonitorOverride,
spice2x_IIDXEmulateSubscreenKeypadTouch,
spice2x_AutoCard,
spice2x_LowLatencySharedAudio,
@@ -269,6 +278,7 @@ namespace launcher {
LovePlusPrinterOutputFormat,
LovePlusPrinterJPGQuality,
OptionConflictResolution,
OtocaCamHook,
};
enum class OptionsCategory {
+3 -4
View File
@@ -86,13 +86,12 @@ namespace launcher {
if (LAUNCHER_ARGC > 0) {
// build cmd line
std::stringstream cmd_line;
cmd_line << "START \"\" ";
std::string cmd_line = "START \"\" ";
for (int i = 0; i < LAUNCHER_ARGC; i++)
cmd_line << " \"" << LAUNCHER_ARGV[i] << "\"";
cmd_line += " \"" + std::string(LAUNCHER_ARGV[i]) + "\"";
// run command
system(cmd_line.str().c_str());
system(cmd_line.c_str());
}
}
+14 -5
View File
@@ -185,21 +185,30 @@ static LONG WINAPI TopLevelExceptionFilter(struct _EXCEPTION_POINTERS *Exception
}
static BOOL WINAPI SetConsoleCtrlHandler_hook(PHANDLER_ROUTINE pHandlerRoutine, BOOL Add) {
log_misc("signal", "SetConsoleCtrlHandler hook hit");
static std::once_flag printed;
std::call_once(printed, []() {
log_misc("signal", "SetConsoleCtrlHandler hook hit (printing once)");
});
return TRUE;
}
static LPTOP_LEVEL_EXCEPTION_FILTER WINAPI SetUnhandledExceptionFilter_hook(
LPTOP_LEVEL_EXCEPTION_FILTER lpTopLevelExceptionFilter)
{
log_info("signal", "SetUnhandledExceptionFilter hook hit");
LPTOP_LEVEL_EXCEPTION_FILTER lpTopLevelExceptionFilter) {
static std::once_flag printed;
std::call_once(printed, []() {
log_info("signal", "SetUnhandledExceptionFilter hook hit (printing once)");
});
return nullptr;
}
static PVOID WINAPI AddVectoredExceptionHandler_hook(ULONG First, PVECTORED_EXCEPTION_HANDLER Handler) {
log_info("signal", "AddVectoredExceptionHandler hook hit");
static std::once_flag printed;
std::call_once(printed, []() {
log_info("signal", "AddVectoredExceptionHandler hook hit (printing once)");
});
return launcher::signal::USE_VEH_WORKAROUND ? INVALID_HANDLE_VALUE : nullptr;
}
+52 -20
View File
@@ -119,6 +119,7 @@ static std::string EAMIO_DLL_NAME = "eamio.dll";
static robin_hood::unordered_map<int, std::thread *> BT5API_THREADS;
static robin_hood::unordered_map<int, int> BT5API_THREAD_RESULTS;
static uint8_t BT5API_CARD_STATES[] = {0, 0};
static uint16_t BT5API_KEYPAD_STATES[] = {0, 0};
static void bt5api_log(const char *bt5_module, const char *fmt, ...) {
@@ -240,46 +241,77 @@ void bt5api_hook(HINSTANCE module) {
void bt5api_poll_reader_card(uint8_t unit_no) {
// check if initialized
if (!EAMIO_DLL) {
// check if initialized or out of bounds
if (!EAMIO_DLL || unit_no >= eamuse_get_game_keypads()) {
return;
}
// poll
if (!eam_io_poll(unit_no)) {
log_warning("bt5api", "polling bt5api reader {} returned failure",
log_warning("bt5api", "polling bt5api reader card {} returned failure",
static_cast<int>(unit_no));
return;
}
// get sensor state
uint8_t sensor_state = eam_io_get_sensor_state(unit_no);
// if we have a change in state.
if(sensor_state != BT5API_CARD_STATES[unit_no]) {
// check for card in
if (sensor_state > BT5API_CARD_STATES[unit_no]) {
uint8_t card_id[8];
eam_io_read_card(unit_no, card_id, 8);
eamuse_card_insert(unit_no, card_id);
// card inserted into the back.
if(sensor_state & (1 << EAM_IO_SENSOR_BACK)) {
uint8_t card_id[8];
log_info("bt5api", "card unit {} inserted", static_cast<int>(unit_no));
// do the bt5 dance
eam_io_card_slot_cmd(unit_no, EAM_IO_CARD_SLOT_CMD_CLOSE);
eam_io_poll(unit_no);
eam_io_card_slot_cmd(unit_no, EAM_IO_CARD_SLOT_CMD_READ);
eam_io_poll(unit_no);
// ask the api for the card, then insert it into our engine.
eam_io_read_card(unit_no, card_id, 8);
eamuse_card_insert(unit_no, card_id);
}
// if card removed entirely, do another bt5 dance!
if(sensor_state == 0) {
eam_io_card_slot_cmd(unit_no, EAM_IO_CARD_SLOT_CMD_CLOSE);
eam_io_poll(unit_no);
eam_io_card_slot_cmd(unit_no, EAM_IO_CARD_SLOT_CMD_OPEN);
log_info("bt5api", "card unit {} removed", static_cast<int>(unit_no));
}
}
// save state
// save prev state
BT5API_CARD_STATES[unit_no] = sensor_state;
}
void bt5api_poll_reader_keypad(uint8_t unit_no) {
// check if initialized
if (!EAMIO_DLL) {
// check if initialized or out of bounds
if (!EAMIO_DLL || unit_no >= eamuse_get_game_keypads()) {
return;
}
// poll
if (!eam_io_poll(unit_no)) {
log_warning("bt5api", "polling bt5api reader {} returned failure",
static_cast<int>(unit_no));
uint16_t keypad_current = eam_io_get_keypad_state(unit_no);
uint16_t keypad_changes = ~BT5API_KEYPAD_STATES[unit_no] & keypad_current;
// if user has pressed decimal for the first time, eject the slotted reader.
if(keypad_changes & (1 << EAM_IO_KEYPAD_DECIMAL)) {
log_info("bt5api", "card unit {} ejecting", static_cast<int>(unit_no));
eam_io_card_slot_cmd(unit_no, EAM_IO_CARD_SLOT_CMD_EJECT);
}
// save the last pressed.
BT5API_KEYPAD_STATES[unit_no] = keypad_current;
// get keypad
eamuse_set_keypad_overrides_bt5(unit_no, eam_io_get_keypad_state(unit_no));
// send off the state to the main engine.
eamuse_set_keypad_overrides_bt5(unit_no, keypad_current);
}
void bt5api_dispose() {
+8 -1
View File
@@ -202,7 +202,14 @@ bool eamuse_card_insert_consume(int active_count, int unit_id) {
// bt5api
if (BT5API_ENABLED) {
bt5api_poll_reader_card((uint8_t) index);
// some bt5api want to be polled in order.
if (eamuse_get_game_keypads() > 1) {
bt5api_poll_reader_card(1);
bt5api_poll_reader_card(0);
}
else {
bt5api_poll_reader_card((uint8_t) index);
}
}
// auto insert card
+32 -1
View File
@@ -2,7 +2,10 @@
// mingw otherwise doesn't load touch stuff
#define _WIN32_WINNT 0x0601
#include "avs/game.h"
#include "wintouchemu.h"
#include "rawinput/touch.h"
#include "hooks/graphics/graphics.h"
#include "util/detour.h"
#include "util/logging.h"
@@ -63,6 +66,11 @@ namespace nativetouchhook {
point->cyContact = 0;
}
static void flip_touch_points(PTOUCHINPUT point) {
point->x = rawinput::touch::DISPLAY_SIZE_X * 100 - point->x;
point->y = rawinput::touch::DISPLAY_SIZE_Y * 100 - point->y;
}
static BOOL WINAPI GetTouchInputInfoHook(
HTOUCHINPUT hTouchInput, UINT cInputs, PTOUCHINPUT pInputs, int cbSize) {
@@ -72,9 +80,32 @@ namespace nativetouchhook {
return result;
}
bool flip_values = false;
if (avs::game::is_model("KFC") && rawinput::touch::DISPLAY_INITIALIZED) {
log_debug(
"touch::native", "DISPLAY_ORIENTATION = {}, DISPLAY_SIZE_X = {}, DISPLAY_SIZE_Y = {}",
rawinput::touch::DISPLAY_ORIENTATION,
rawinput::touch::DISPLAY_SIZE_X,
rawinput::touch::DISPLAY_SIZE_Y);
if (rawinput::touch::DISPLAY_ORIENTATION == DMDO_270) {
flip_values = true;
}
}
if (rawinput::touch::INVERTED) {
flip_values = !flip_values;
}
for (size_t i = 0; i < cInputs; i++) {
PTOUCHINPUT point = &pInputs[i];
strip_contact_size(point);
if (avs::game::is_model("LDJ")) {
strip_contact_size(point);
}
if (flip_values) {
flip_touch_points(point);
}
}
return result;
+17
View File
@@ -12,6 +12,7 @@
#include "cfg/screen_resize.h"
#include "games/gitadora/gitadora.h"
#include "games/iidx/iidx.h"
#include "games/popn/popn.h"
#include "hooks/graphics/graphics.h"
#include "overlay/overlay.h"
#include "overlay/windows/generic_sub.h"
@@ -20,6 +21,7 @@
#include "util/logging.h"
#include "util/time.h"
#include "util/utils.h"
#include "rawinput/touch.h"
#include "avs/game.h"
@@ -126,6 +128,11 @@ namespace wintouchemu {
WINDOW_TITLE_END = window_title_end;
}
static void flip_touch_points(PTOUCHINPUT point) {
point->x = rawinput::touch::DISPLAY_SIZE_X * 100 - point->x;
point->y = rawinput::touch::DISPLAY_SIZE_Y * 100 - point->y;
}
static BOOL WINAPI GetTouchInputInfoHook(HANDLE hTouchInput, UINT cInputs, PTOUCHINPUT pInputs, int cbSize) {
// check if original should be called
@@ -220,6 +227,12 @@ namespace wintouchemu {
touch_input->cxContact = 0;
touch_input->cyContact = 0;
if (avs::game::is_model("KFC") &&
rawinput::touch::DISPLAY_INITIALIZED &&
rawinput::touch::DISPLAY_ORIENTATION == DMDO_270) {
flip_touch_points(touch_input);
}
} else if (USE_MOUSE && !mouse_used) {
// disable further mouse inputs this call
@@ -393,6 +406,10 @@ namespace wintouchemu {
// use mouse position as ImGui overlay will block the touch window
log_info("wintouchemu", "use mouse cursor API for ldj overlay subscreen");
USE_MOUSE = true;
} else if (games::popn::is_pikapika_model()) {
// same as iidx case above
log_info("wintouchemu", "use mouse cursor API for popn overlay subscreen");
USE_MOUSE = true;
} else if (games::gitadora::is_arena_model() && games::gitadora::ARENA_SINGLE_WINDOW) {
log_info("wintouchemu", "use mouse cursor API for gitadora overlay subscreen");
USE_MOUSE = true;
+88 -2
View File
@@ -26,7 +26,7 @@ namespace ImGui {
void HelpMarker(const char* desc) {
ImGui::TextDisabled("(?)");
if (ImGui::IsItemHovered()) {
if (ImGui::IsItemHovered(TOOLTIP_FLAGS)) {
HelpTooltip(desc);
}
}
@@ -59,7 +59,7 @@ namespace ImGui {
ImGui::PushStyleColor(ImGuiCol_TextDisabled, ImVec4(1.f, 1.f, 0.f, 1.f));
ImGui::TextDisabled("(!)");
ImGui::PopStyleColor();
if (ImGui::IsItemHovered()) {
if (ImGui::IsItemHovered(TOOLTIP_FLAGS)) {
WarnTooltip(desc, warn);
}
}
@@ -92,4 +92,90 @@ namespace ImGui {
ImGui::GetColorU32(ImGuiCol_PlotHistogram),
thickness);
}
std::string TruncateText(const std::string& p_text, float p_truncated_width, bool &truncated) {
std::string truncated_text = p_text;
const float text_width =
ImGui::CalcTextSize(p_text.c_str(), nullptr, true).x;
if (text_width > p_truncated_width) {
truncated = true;
constexpr const char* ELLIPSIS = " ...";
const float ellipsis_size = ImGui::CalcTextSize(ELLIPSIS).x;
int visible_chars = 0;
for (size_t i = 0; i < p_text.size(); i++) {
const float current_width = ImGui::CalcTextSize(
p_text.substr(0, i).c_str(), nullptr, true)
.x;
if (current_width + ellipsis_size > p_truncated_width) {
break;
}
visible_chars = i;
}
truncated_text = (p_text.substr(0, visible_chars) + ELLIPSIS).c_str();
}
return truncated_text;
}
void TextTruncated(const std::string& p_text, float p_truncated_width) {
if (p_text.empty()) {
ImGui::TextDisabled("-");
return;
}
bool truncated = false;
ImGui::TextUnformatted(TruncateText(p_text, p_truncated_width, truncated).c_str());
if (truncated && ImGui::IsItemHovered(TOOLTIP_FLAGS)) {
ImGui::HelpTooltip(p_text.c_str());
}
}
bool DeleteButton(const std::string& tooltip) {
ImGui::PushID(tooltip.c_str());
ImGui::PushStyleColor(ImGuiCol_Button, ImGui::GetColorU32(ImGuiCol_Button, 0.7f));
ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(3.f, 2.f));
bool clicked = ImGui::SmallButton("\u00D7"); // multiplication sign (×)
ImGui::PopStyleVar();
ImGui::PopStyleColor();
if (!tooltip.empty() && ImGui::IsItemHovered(TOOLTIP_FLAGS)) {
ImGui::HelpTooltip(tooltip.c_str());
}
ImGui::PopID();
return clicked;
}
bool ClearButton(const std::string& tooltip) {
ImGui::PushID(tooltip.c_str());
// same colors as a checkbox
ImGui::PushStyleColor(ImGuiCol_Button, ImGui::GetColorU32(ImGuiCol_FrameBg));
ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImGui::GetColorU32(ImGuiCol_FrameBgHovered));
ImGui::PushStyleColor(ImGuiCol_ButtonActive, ImGui::GetColorU32(ImGuiCol_FrameBgActive));
ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(0.97f, 0.97f, 0.97f, 1.00f));
bool clicked = ImGui::Button("\u00D7"); // multiplication sign (×)
ImGui::PopStyleColor(4);
if (!tooltip.empty() && ImGui::IsItemHovered(TOOLTIP_FLAGS)) {
ImGui::HelpTooltip(tooltip.c_str());
}
ImGui::PopID();
return clicked;
}
void InvisibleTableRowSelectable() {
ImGui::TableSetColumnIndex(0);
ImGui::PushStyleColor(ImGuiCol_Header, 0);
ImGui::PushStyleColor(ImGuiCol_HeaderHovered, 0);
ImGui::PushStyleColor(ImGuiCol_HeaderActive, 0);
ImGui::PushTabStop(false); // prevent tab navigation
ImGui::Selectable("##row", false,
ImGuiSelectableFlags_SpanAllColumns | ImGuiSelectableFlags_AllowOverlap);
ImGui::PopTabStop();
ImGui::PopStyleColor(3);
if (ImGui::IsItemHovered()) {
ImGui::TableSetBgColor(ImGuiTableBgTarget_RowBg1, IM_COL32(200, 200, 200, 24));
}
}
}
+12
View File
@@ -1,12 +1,24 @@
#pragma once
#include <string>
#include "external/imgui/imgui.h"
namespace ImGui {
constexpr ImGuiHoveredFlags TOOLTIP_FLAGS =
(ImGuiHoveredFlags_ForTooltip | ImGuiHoveredFlags_AllowWhenDisabled);
void HelpTooltip(const char* desc);
void HelpMarker(const char* desc);
void WarnTooltip(const char* desc, const char* warn);
void WarnMarker(const char* desc, const char* warn);
void DummyMarker();
void Knob(float fraction, float size, float thickness = 2.f,
float pos_x = -1.f, float pos_y = -1.f);
void TextTruncated(const std::string& p_text, float p_truncated_width);
bool DeleteButton(const std::string& tooltip);
bool ClearButton(const std::string& tooltip);
void InvisibleTableRowSelectable();
}
+205 -86
View File
@@ -11,9 +11,19 @@
#include "rawinput/rawinput.h"
#include "touch/touch.h"
#include "util/logging.h"
#include "util/time.h"
#include "util/utils.h"
#include "hooks/graphics/graphics.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
#if !defined(IMGUI_ENABLE_WIN32_DEFAULT_IME_FUNCTIONS) || \
!defined(IMGUI_DISABLE_DEFAULT_ALLOCATORS) || \
!defined(IMGUI_USE_BGRA_PACKED_COLOR) || \
@@ -29,6 +39,97 @@ static HWND g_hWnd = nullptr;
static INT64 g_Time = 0;
static INT64 g_TicksPerSecond = 0;
static ImGuiMouseCursor g_LastMouseCursor = ImGuiMouseCursor_COUNT;
static double g_LastMouseMovement = 0.f;
static bool g_MouseCursorAutoHide = false;
constexpr size_t VKEY_MAX = 255;
static std::array<bool, VKEY_MAX> g_KeysDown;
static std::array<bool, ImGuiMouseButton_COUNT> g_MouseDown;
static ImGuiKey get_imgui_key(int vkey) {
switch (vkey) {
case VK_TAB:
return ImGuiKey_Tab;
case VK_LEFT:
return ImGuiKey_LeftArrow;
case VK_RIGHT:
return ImGuiKey_RightArrow;
case VK_UP:
return ImGuiKey_UpArrow;
case VK_DOWN:
return ImGuiKey_DownArrow;
case VK_PRIOR:
return ImGuiKey_PageUp;
case VK_NEXT:
return ImGuiKey_PageDown;
case VK_HOME:
return ImGuiKey_Home;
case VK_END:
return ImGuiKey_End;
case VK_INSERT:
return ImGuiKey_Insert;
case VK_DELETE:
return ImGuiKey_Delete;
case VK_BACK:
return ImGuiKey_Backspace;
case VK_SPACE:
return ImGuiKey_Space;
case VK_RETURN:
return ImGuiKey_Enter;
case VK_ESCAPE:
return ImGuiKey_Escape;
case VK_SHIFT:
case VK_LSHIFT:
return ImGuiKey_LeftShift;
case VK_RSHIFT:
return ImGuiKey_RightShift;
case VK_CONTROL:
case VK_LCONTROL:
return ImGuiKey_LeftCtrl;
case VK_RCONTROL:
return ImGuiKey_RightCtrl;
case 'A':
case 'a':
return ImGuiKey_A;
case 'C':
case 'c':
return ImGuiKey_C;
case 'V':
case 'v':
return ImGuiKey_V;
case 'X':
case 'x':
return ImGuiKey_X;
case 'Y':
case 'y':
return ImGuiKey_Y;
case 'Z':
case 'z':
return ImGuiKey_Z;
default:
return ImGuiKey_None;
}
}
static ImGuiKey get_imgui_mod_key(int vkey) {
switch (vkey) {
case VK_SHIFT:
case VK_LSHIFT:
case VK_RSHIFT:
return ImGuiMod_Shift;
return ImGuiMod_Shift;
case VK_CONTROL:
case VK_LCONTROL:
case VK_RCONTROL:
return ImGuiMod_Ctrl;
case VK_MENU:
case VK_LMENU:
case VK_RMENU:
return ImGuiMod_Alt;
default:
return ImGuiMod_None;
}
}
bool ImGui_ImplSpice_Init(HWND hWnd) {
log_misc("imgui_impl_spice", "init");
@@ -56,33 +157,15 @@ bool ImGui_ImplSpice_Init(HWND hWnd) {
io.BackendPlatformName = "imgui_impl_spice";
io.ConfigErrorRecoveryEnableTooltip = true;
// keyboard mapping
io.KeyMap[ImGuiKey_Tab] = VK_TAB;
io.KeyMap[ImGuiKey_LeftArrow] = VK_LEFT;
io.KeyMap[ImGuiKey_RightArrow] = VK_RIGHT;
io.KeyMap[ImGuiKey_UpArrow] = VK_UP;
io.KeyMap[ImGuiKey_DownArrow] = VK_DOWN;
io.KeyMap[ImGuiKey_PageUp] = VK_PRIOR;
io.KeyMap[ImGuiKey_PageDown] = VK_NEXT;
io.KeyMap[ImGuiKey_Home] = VK_HOME;
io.KeyMap[ImGuiKey_End] = VK_END;
io.KeyMap[ImGuiKey_Insert] = VK_INSERT;
io.KeyMap[ImGuiKey_Delete] = VK_DELETE;
io.KeyMap[ImGuiKey_Backspace] = VK_BACK;
io.KeyMap[ImGuiKey_Space] = VK_SPACE;
io.KeyMap[ImGuiKey_Enter] = VK_RETURN;
io.KeyMap[ImGuiKey_Escape] = VK_ESCAPE;
io.KeyMap[ImGuiKey_KeypadEnter] = VK_RETURN;
io.KeyMap[ImGuiKey_A] = 'A';
io.KeyMap[ImGuiKey_C] = 'C';
io.KeyMap[ImGuiKey_V] = 'V';
io.KeyMap[ImGuiKey_X] = 'X';
io.KeyMap[ImGuiKey_Y] = 'Y';
io.KeyMap[ImGuiKey_Z] = 'Z';
// get display size
ImGui_ImplSpice_UpdateDisplaySize();
// only enable mouse cursor auto hiding if we want ImGui to draw the cursor
// (if OS is drawing it, we don't auto-hide)
if (io.MouseDrawCursor && !cfg::CONFIGURATOR_STANDALONE) {
g_MouseCursorAutoHide = true;
}
// return success
return true;
}
@@ -182,7 +265,8 @@ static void ImGui_ImplSpice_UpdateMousePos() {
// trigger any overlay, don't process anything else
const auto is_windowed_subscreen =
(GRAPHICS_IIDX_WSUB && active_window == TDJ_SUBSCREEN_WINDOW) ||
(active_window == SDVX_SUBSCREEN_WINDOW);
(active_window == SDVX_SUBSCREEN_WINDOW) ||
(active_window == POPN_SUBSCREEN_WINDOW);
if (is_windowed_subscreen) {
io.MousePos = ImVec2(-FLT_MAX, -FLT_MAX);
return;
@@ -239,7 +323,7 @@ static void ImGui_ImplSpice_UpdateMousePos() {
}
// delay press
io.MouseDown[0] = delay_touch++ >= delay_touch_target && last_touch_id == tp.id;
g_MouseDown[ImGuiMouseButton_Left] = delay_touch++ >= delay_touch_target && last_touch_id == tp.id;
if (last_touch_id == ~0u) {
last_touch_id = tp.id;
}
@@ -265,44 +349,31 @@ void ImGui_ImplSpice_NewFrame() {
io.DeltaTime = (float) (current_time - g_Time) / g_TicksPerSecond;
g_Time = current_time;
// only process new input from devices if window is in focus
// (when not in focus, all mouse/keyboard events are treated as released)
const auto accept_new_input = superexit::has_focus() && !rawinput::OS_WINDOW_ACTIVE;
// remember old state
BYTE KeysDownOld[sizeof(io.KeysDown)];
for (size_t i = 0; i < sizeof(io.KeysDown); i++) {
KeysDownOld[i] = io.KeysDown[i] ? ~0 : 0;
std::array<BYTE, VKEY_MAX> KeysDownOld;
for (size_t i = 0; i < VKEY_MAX; i++) {
KeysDownOld[i] = g_KeysDown[i] ? ~0 : 0;
}
KeysDownOld[VK_SHIFT] |= KeysDownOld[VK_LSHIFT];
KeysDownOld[VK_SHIFT] |= KeysDownOld[VK_RSHIFT];
const auto MouseDownOld = g_MouseDown;
// reset keys state
io.MouseWheel = 0;
io.KeyCtrl = false;
io.KeyShift = false;
io.KeyAlt = false;
io.KeySuper = false;
memset(io.KeysDown, false, sizeof(io.KeysDown));
memset(io.MouseDown, false, sizeof(io.MouseDown));
// early quit if window not in focus
if (!superexit::has_focus()) {
return;
}
// read keyboard modifiers inputs
io.KeyCtrl = (::GetKeyState(VK_CONTROL) & 0x8000) != 0;
io.KeyShift = (::GetKeyState(VK_SHIFT) & 0x8000) != 0;
io.KeyAlt = (::GetKeyState(VK_MENU) & 0x8000) != 0;
io.KeySuper = (::GetKeyState(VK_LWIN) & 0x8000) != 0;
io.KeySuper |= (::GetKeyState(VK_RWIN) & 0x8000) != 0;
g_MouseDown.fill(false);
g_KeysDown.fill(false);
// apply windows mouse buttons
io.MouseDown[0] |= (get_async_primary_mouse()) != 0;
io.MouseDown[1] |= (get_async_secondary_mouse()) != 0;
io.MouseDown[2] |= (GetAsyncKeyState(VK_MBUTTON)) != 0;
g_MouseDown[ImGuiMouseButton_Left] |= (get_async_primary_mouse()) != 0;
g_MouseDown[ImGuiMouseButton_Right] |= (get_async_secondary_mouse()) != 0;
g_MouseDown[ImGuiMouseButton_Middle] |= (GetAsyncKeyState(VK_MBUTTON)) != 0;
// read new keys state
static long mouse_wheel_last = 0;
long mouse_wheel = 0;
if (RI_MGR != nullptr) {
if (RI_MGR != nullptr && accept_new_input) {
auto devices = RI_MGR->devices_get();
for (auto &device : devices) {
switch (device.type) {
@@ -312,21 +383,21 @@ void ImGui_ImplSpice_NewFrame() {
// mouse button triggers
if (GetSystemMetrics(SM_SWAPBUTTON)) {
if (mouse->key_states[rawinput::MOUSEBTN_RIGHT]) {
io.MouseDown[0] = true;
g_MouseDown[ImGuiMouseButton_Left] = true;
}
if (mouse->key_states[rawinput::MOUSEBTN_LEFT]) {
io.MouseDown[1] = true;
g_MouseDown[ImGuiMouseButton_Right] = true;
}
} else {
if (mouse->key_states[rawinput::MOUSEBTN_LEFT]) {
io.MouseDown[0] = true;
g_MouseDown[ImGuiMouseButton_Left] = true;
}
if (mouse->key_states[rawinput::MOUSEBTN_RIGHT]) {
io.MouseDown[1] = true;
g_MouseDown[ImGuiMouseButton_Right] = true;
}
}
if (mouse->key_states[rawinput::MOUSEBTN_MIDDLE]) {
io.MouseDown[2] = true;
g_MouseDown[ImGuiMouseButton_Middle] = true;
}
// final mouse wheel value should be all devices combined
@@ -337,33 +408,11 @@ void ImGui_ImplSpice_NewFrame() {
case rawinput::KEYBOARD: {
// iterate all virtual key codes
for (size_t vKey = 0; vKey < 256; vKey++) {
for (size_t vKey = 0; vKey < VKEY_MAX; vKey++) {
// get state (combined from all pages)
auto &key_states = device.keyboardInfo->key_states;
bool state = false;
for (size_t page_index = 0; page_index < 1024; page_index += 256) {
state |= key_states[page_index + vKey];
}
// trigger
io.KeysDown[vKey] |= state;
// generate character input, but only if WM_CHAR didn't take over the
// functionality
if (!overlay::USE_WM_CHAR_FOR_IMGUI_CHAR_INPUT && !KeysDownOld[vKey] && state) {
UCHAR buf[2];
auto ret = ToAscii(
static_cast<UINT>(vKey),
0,
static_cast<const BYTE *>(KeysDownOld),
reinterpret_cast<LPWORD>(buf),
0);
if (ret > 0) {
for (int i = 0; i < ret; i++) {
overlay::OVERLAY->input_char(buf[i]);
}
}
g_KeysDown[vKey] |= key_states[page_index + vKey];
}
}
break;
@@ -374,13 +423,83 @@ void ImGui_ImplSpice_NewFrame() {
}
}
// process keyboard input from all keyboard collapsed into one state (g_KeysDown)
for (size_t vKey = 0; vKey < VKEY_MAX; vKey++) {
const bool state = g_KeysDown[vKey];
const auto imgui_key = get_imgui_key(vKey);
const auto changed =
(state && !KeysDownOld[vKey]) ||
(!state && KeysDownOld[vKey]);
if (imgui_key != ImGuiKey_None && changed) {
io.AddKeyEvent(imgui_key, state);
log_debug("imgui_impl_spice", "vkey {:#x} added as navigation event, state: {}", static_cast<uint64_t>(vKey), state);
// mod key must also be processed separately
const auto imgui_mod_key = get_imgui_mod_key(vKey);
if (imgui_mod_key != ImGuiMod_None) {
io.AddKeyEvent(imgui_mod_key, state);
}
}
// generate character input, but only if WM_CHAR didn't take over the functionality
// only detecting rising edges here - this means holding a key won't work
// (it's better than repeating a character input every frame - cost we pay for providing input
// on top of rawinput instead of WM_CHAR)
if (!overlay::USE_WM_CHAR_FOR_IMGUI_CHAR_INPUT && !KeysDownOld[vKey] && state) {
UCHAR buf[2];
auto ret = ToAscii(
static_cast<UINT>(vKey),
0,
static_cast<const BYTE *>(KeysDownOld.data()),
reinterpret_cast<LPWORD>(buf),
0);
if (ret > 0) {
for (int i = 0; i < ret; i++) {
overlay::OVERLAY->input_char(buf[i]);
log_debug("imgui_impl_spice", "vkey {:#x} inputted as character", vKey);
}
}
}
}
// set mouse wheel
auto mouse_diff = mouse_wheel - mouse_wheel_last;
auto wheel_diff = mouse_wheel - mouse_wheel_last;
mouse_wheel_last = mouse_wheel;
io.MouseWheel = mouse_diff;
io.AddMouseWheelEvent(0, wheel_diff);
// update OS mouse position
ImGui_ImplSpice_UpdateMousePos();
const auto old_mouse_pos = io.MousePos;
if (accept_new_input) {
ImGui_ImplSpice_UpdateMousePos();
}
const auto new_mouse_pos = io.MousePos;
// update mouse buttons
// doing this after ImGui_ImplSpice_UpdateMousePos since it can set g_MouseDown for touch input
for (size_t i = 0; i < g_MouseDown.size(); i++) {
if (MouseDownOld[i] != g_MouseDown[i]) {
io.AddMouseButtonEvent(i, g_MouseDown[i]);
log_debug("imgui_impl_spice", "mouse button {} event", g_MouseDown[i]);
}
}
// automatically hide cursor
if (g_MouseCursorAutoHide) {
if (old_mouse_pos.x != new_mouse_pos.x ||
old_mouse_pos.y != new_mouse_pos.y ||
wheel_diff != 0 ||
g_MouseDown[ImGuiMouseButton_Left] || g_MouseDown[ImGuiMouseButton_Right] || g_MouseDown[ImGuiMouseButton_Middle]) {
// mouse moved, update time and show the cursor
g_LastMouseMovement = get_performance_milliseconds();
io.MouseDrawCursor = true;
} else if ((get_performance_milliseconds() - g_LastMouseMovement) > 2000) {
// mouse idle for more than 2 seconds, hide the cursor
io.MouseDrawCursor = false;
}
}
if (cfg::CONFIGURATOR_STANDALONE) {
// if cursor is inside the client area, always set the OS cursor to what ImGui wants
+3 -3
View File
@@ -501,7 +501,7 @@ void paint_draw_cmd(
const SwOptions& options,
Stats* stats)
{
const auto texture = reinterpret_cast<const Texture*>(pcmd.TextureId);
const auto texture = reinterpret_cast<const Texture*>(pcmd.GetTexID());
const auto offset = pcmd.IdxOffset;
// ImGui uses the first pixel for "white".
@@ -678,7 +678,7 @@ void bind_imgui_painting()
int font_width, font_height;
io.Fonts->GetTexDataAsAlpha8(&tex_data, &font_width, &font_height);
const auto texture = new Texture{tex_data, font_width, font_height};
io.Fonts->TexID = reinterpret_cast<ImTextureID>(texture);
io.Fonts->SetTexID(reinterpret_cast<ImTextureID>(texture));
}
static Stats s_stats; // TODO: pass as an argument?
@@ -700,7 +700,7 @@ void paint_imgui(uint32_t* pixels, int width_pixels, int height_pixels, const Sw
void unbind_imgui_painting()
{
ImGuiIO& io = ImGui::GetIO();
delete reinterpret_cast<Texture*>(io.Fonts->TexID);
delete reinterpret_cast<Texture*>(io.Fonts->TexID.GetTexID());
io.Fonts = nullptr;
}
+67 -61
View File
@@ -5,6 +5,7 @@
#include "games/io.h"
#include "games/gitadora/gitadora.h"
#include "games/iidx/iidx.h"
#include "games/popn/popn.h"
#include "hooks/graphics/graphics.h"
#include "misc/eamuse.h"
#include "touch/touch.h"
@@ -35,6 +36,7 @@
#include "windows/iopanel_ddr.h"
#include "windows/iopanel_gfdm.h"
#include "windows/iopanel_iidx.h"
#include "windows/popn_sub.h"
#include "windows/sdvx_sub.h"
#include "windows/keypad.h"
#include "windows/log.h"
@@ -69,7 +71,7 @@ namespace overlay {
}
return ImVec2(apply_scaling(x), apply_scaling(y));
}
ImVec2 apply_scaling_to_vector(const ImVec2& input) {
return apply_scaling_to_vector(input.x, input.y);
}
@@ -185,74 +187,76 @@ void overlay::SpiceOverlay::init() {
if (UI_SCALE_PERCENT.has_value()) {
const auto scale = static_cast<float>(UI_SCALE_PERCENT.value()) / 100.f;
ImGui::GetStyle().ScaleAllSizes(scale);
ImGui::GetIO().FontGlobalScale = scale;
ImGui::GetStyle().FontScaleMain = scale;
}
// red theme based on:
// https://github.com/ocornut/imgui/issues/707#issuecomment-760220280
// r, g, b, a
// based on CrimsonVesuvius theme from
// https://github.com/ocornut/imgui/issues/707#issuecomment-4107169777
ImVec4* colors = ImGui::GetStyle().Colors;
// note: also see bg_alpha value in each overlay window
colors[ImGuiCol_WindowBg] = ImVec4(0.06f, 0.06f, 0.06f, 1.0f);
colors[ImGuiCol_PopupBg] = ImVec4(0.08f, 0.08f, 0.08f, 1.0f);
// Text
colors[ImGuiCol_Text] = ImVec4(1.00f, 0.90f, 0.90f, 1.00f); // Slight pinkish tint to off-white
colors[ImGuiCol_TextDisabled] = ImVec4(0.35f, 0.26f, 0.26f, 1.00f);
colors[ImGuiCol_Border] = ImVec4(0.00f, 0.00f, 0.00f, 0.50f);
colors[ImGuiCol_BorderShadow] = ImVec4(0.00f, 0.00f, 0.00f, 0.00f);
// Backgrounds
colors[ImGuiCol_WindowBg] = ImVec4(0.08f, 0.07f, 0.07f, 1.00f); // Deep charcoal
colors[ImGuiCol_ChildBg] = ImVec4(0.10f, 0.09f, 0.09f, 1.00f);
colors[ImGuiCol_PopupBg] = ImVec4(0.08f, 0.07f, 0.07f, 0.96f);
colors[ImGuiCol_FrameBg] = ImVec4(0.18f, 0.18f, 0.18f, 0.54f);
colors[ImGuiCol_FrameBgHovered] = ImVec4(0.18f, 0.18f, 0.18f, 0.80f);
colors[ImGuiCol_FrameBgActive] = ImVec4(0.24f, 0.24f, 0.24f, 0.80f);
// Borders
colors[ImGuiCol_Border] = ImVec4(0.25f, 0.15f, 0.15f, 0.80f);
colors[ImGuiCol_BorderShadow] = ImVec4(0.00f, 0.00f, 0.00f, 0.00f);
colors[ImGuiCol_TitleBg] = ImVec4(0.04f, 0.04f, 0.04f, 1.00f);
colors[ImGuiCol_TitleBgActive] = ImVec4(0.48f, 0.16f, 0.16f, 1.00f);
colors[ImGuiCol_TitleBgCollapsed] = ImVec4(0.48f, 0.16f, 0.16f, 1.00f);
colors[ImGuiCol_MenuBarBg] = ImVec4(0.14f, 0.14f, 0.14f, 1.00f);
colors[ImGuiCol_ScrollbarBg] = ImVec4(0.02f, 0.02f, 0.02f, 0.53f);
colors[ImGuiCol_ScrollbarGrab] = ImVec4(0.31f, 0.31f, 0.31f, 1.00f);
colors[ImGuiCol_ScrollbarGrabHovered] = ImVec4(0.41f, 0.41f, 0.41f, 1.00f);
colors[ImGuiCol_ScrollbarGrabActive] = ImVec4(0.51f, 0.51f, 0.51f, 1.00f);
colors[ImGuiCol_CheckMark] = ImVec4(0.89f, 0.00f, 0.19f, 1.00f);
colors[ImGuiCol_SliderGrab] = ImVec4(1.00f, 0.19f, 0.19f, 0.40f);
colors[ImGuiCol_SliderGrabActive] = ImVec4(0.89f, 0.00f, 0.19f, 1.00f);
// Frames
colors[ImGuiCol_FrameBg] = ImVec4(0.15f, 0.10f, 0.10f, 1.00f);
colors[ImGuiCol_FrameBgHovered] = ImVec4(0.25f, 0.15f, 0.15f, 1.00f);
colors[ImGuiCol_FrameBgActive] = ImVec4(0.35f, 0.20f, 0.20f, 1.00f);
colors[ImGuiCol_Button] = ImVec4(1.00f, 0.19f, 0.19f, 0.40f);
colors[ImGuiCol_ButtonHovered] = ImVec4(0.80f, 0.17f, 0.00f, 1.00f);
colors[ImGuiCol_ButtonActive] = ImVec4(0.80f, 0.17f, 0.00f, 0.90f);
// Title Bars
colors[ImGuiCol_TitleBg] = ImVec4(0.12f, 0.08f, 0.08f, 1.00f);
colors[ImGuiCol_TitleBgActive] = ImVec4(0.25f, 0.10f, 0.10f, 1.00f);
colors[ImGuiCol_TitleBgCollapsed] = ImVec4(0.05f, 0.05f, 0.05f, 1.00f);
colors[ImGuiCol_Header] = ImVec4(0.33f, 0.35f, 0.36f, 0.53f);
colors[ImGuiCol_HeaderHovered] = ImVec4(0.44f, 0.44f, 0.44f, 0.67f);
colors[ImGuiCol_HeaderActive] = ImVec4(0.47f, 0.47f, 0.47f, 0.67f);
colors[ImGuiCol_Separator] = ImVec4(0.32f, 0.32f, 0.32f, 1.00f);
colors[ImGuiCol_SeparatorHovered] = ImVec4(0.32f, 0.32f, 0.32f, 1.00f);
colors[ImGuiCol_SeparatorActive] = ImVec4(0.32f, 0.32f, 0.32f, 1.00f);
colors[ImGuiCol_ResizeGrip] = ImVec4(1.00f, 1.00f, 1.00f, 0.85f);
colors[ImGuiCol_ResizeGripHovered] = ImVec4(1.00f, 1.00f, 1.00f, 0.60f);
colors[ImGuiCol_ResizeGripActive] = ImVec4(1.00f, 1.00f, 1.00f, 0.90f);
// Menus
colors[ImGuiCol_MenuBarBg] = ImVec4(0.12f, 0.08f, 0.08f, 1.00f);
colors[ImGuiCol_Tab] = colors[ImGuiCol_Button];
colors[ImGuiCol_TabHovered] = colors[ImGuiCol_ButtonHovered];
colors[ImGuiCol_TabActive] = colors[ImGuiCol_ButtonActive];
colors[ImGuiCol_TabUnfocused] = colors[ImGuiCol_Tab] * ImVec4(1.0f, 1.0f, 1.0f, 0.6f);
colors[ImGuiCol_TabUnfocusedActive] = colors[ImGuiCol_TabActive] * ImVec4(1.0f, 1.0f, 1.0f, 0.6f);
// Scrollbars
colors[ImGuiCol_ScrollbarBg] = ImVec4(0.05f, 0.05f, 0.05f, 1.00f);
colors[ImGuiCol_ScrollbarGrab] = ImVec4(0.25f, 0.12f, 0.12f, 1.00f);
colors[ImGuiCol_ScrollbarGrabHovered] = ImVec4(0.35f, 0.15f, 0.15f, 1.00f);
colors[ImGuiCol_ScrollbarGrabActive] = ImVec4(0.45f, 0.20f, 0.20f, 1.00f);
colors[ImGuiCol_DockingPreview] = ImVec4(0.47f, 0.47f, 0.47f, 0.47f);
colors[ImGuiCol_DockingEmptyBg] = ImVec4(0.20f, 0.20f, 0.20f, 1.00f);
colors[ImGuiCol_PlotLines] = ImVec4(0.61f, 0.61f, 0.61f, 1.00f);
colors[ImGuiCol_PlotLinesHovered] = ImVec4(1.00f, 0.43f, 0.35f, 1.00f);
colors[ImGuiCol_PlotHistogram] = ImVec4(0.90f, 0.70f, 0.00f, 1.00f);
colors[ImGuiCol_PlotHistogramHovered] = ImVec4(1.00f, 0.60f, 0.00f, 1.00f);
colors[ImGuiCol_TableHeaderBg] = ImVec4(0.19f, 0.19f, 0.20f, 1.00f);
colors[ImGuiCol_TableBorderStrong] = ImVec4(0.31f, 0.31f, 0.35f, 1.00f);
colors[ImGuiCol_TableBorderLight] = ImVec4(0.23f, 0.23f, 0.25f, 1.00f);
colors[ImGuiCol_TableRowBg] = ImVec4(0.00f, 0.00f, 0.00f, 0.00f);
colors[ImGuiCol_TableRowBgAlt] = ImVec4(1.00f, 1.00f, 1.00f, 0.02f);
colors[ImGuiCol_TextSelectedBg] = ImVec4(0.26f, 0.59f, 0.98f, 0.35f);
colors[ImGuiCol_DragDropTarget] = ImVec4(1.00f, 1.00f, 0.00f, 0.90f);
colors[ImGuiCol_NavHighlight] = ImVec4(0.26f, 0.59f, 0.98f, 1.00f);
colors[ImGuiCol_NavWindowingHighlight] = ImVec4(1.00f, 1.00f, 1.00f, 0.70f);
colors[ImGuiCol_NavWindowingDimBg] = ImVec4(0.80f, 0.80f, 0.80f, 0.20f);
colors[ImGuiCol_ModalWindowDimBg] = ImVec4(0.80f, 0.80f, 0.80f, 0.35f);
// Interactables (The High-Intensity Red)
colors[ImGuiCol_CheckMark] = ImVec4(0.85f, 0.15f, 0.15f, 1.00f); // Sharp Red
colors[ImGuiCol_SliderGrab] = ImVec4(0.60f, 0.12f, 0.12f, 1.00f);
colors[ImGuiCol_SliderGrabActive] = ImVec4(0.85f, 0.15f, 0.15f, 1.00f);
colors[ImGuiCol_Button] = ImVec4(0.30f, 0.12f, 0.12f, 1.00f);
colors[ImGuiCol_ButtonHovered] = ImVec4(0.50f, 0.18f, 0.18f, 1.00f);
colors[ImGuiCol_ButtonActive] = ImVec4(0.70f, 0.25f, 0.25f, 1.00f);
colors[ImGuiCol_Header] = ImVec4(0.30f, 0.12f, 0.12f, 1.00f);
colors[ImGuiCol_HeaderHovered] = ImVec4(0.50f, 0.18f, 0.18f, 1.00f);
colors[ImGuiCol_HeaderActive] = ImVec4(0.70f, 0.25f, 0.25f, 1.00f);
// Tabs
colors[ImGuiCol_Tab] = ImVec4(0.15f, 0.10f, 0.10f, 1.00f);
colors[ImGuiCol_TabHovered] = ImVec4(0.50f, 0.18f, 0.18f, 1.00f);
colors[ImGuiCol_TabActive] = ImVec4(0.35f, 0.12f, 0.12f, 1.00f);
// Misc
colors[ImGuiCol_PlotLines] = ImVec4(0.85f, 0.20f, 0.20f, 1.00f);
colors[ImGuiCol_TextSelectedBg] = ImVec4(0.85f, 0.15f, 0.15f, 0.35f);
colors[ImGuiCol_NavHighlight] = ImVec4(0.85f, 0.15f, 0.15f, 1.00f);
// spice custom
colors[ImGuiCol_TableRowBgAlt] = ImVec4(1.00f, 1.00f, 1.00f, 0.025f);
colors[ImGuiCol_Separator] = ImVec4(0.32f, 0.22f, 0.22f, 1.00f);
colors[ImGuiCol_SeparatorHovered] = ImVec4(0.42f, 0.22f, 0.22f, 1.00f);
colors[ImGuiCol_SeparatorActive] = ImVec4(0.52f, 0.22f, 0.22f, 1.00f);
#ifdef IMGUI_HAS_DOCK
colors[ImGuiCol_DockingPreview] = ImVec4(0.85f, 0.15f, 0.15f, 0.40f);
colors[ImGuiCol_DockingEmptyBg] = ImVec4(0.08f, 0.07f, 0.07f, 1.00f);
#endif
// configure IO
auto &io = ImGui::GetIO();
@@ -281,7 +285,7 @@ void overlay::SpiceOverlay::init() {
io.FontAllowUserScaling = true;
// add default font
io.Fonts->AddFontDefault();
io.Fonts->AddFontDefaultBitmap();
// add fallback fonts for missing glyph ranges
ImFontConfig config {};
@@ -348,7 +352,7 @@ void overlay::SpiceOverlay::init() {
this->window_add(window_config = new overlay::windows::Config(this));
this->window_add(window_control = new overlay::windows::Control(this));
this->window_add(window_log = new overlay::windows::Log(this));
#ifdef SPICE64
#if SPICE64 && !SPICE_XP
if (avs::game::is_model("LDJ")) {
this->window_add(window_camera = new overlay::windows::CameraControl(this));
}
@@ -413,6 +417,8 @@ void overlay::SpiceOverlay::init() {
window_sub = new overlay::windows::SDVXSubScreen(this);
} else if (games::gitadora::is_arena_model() && games::gitadora::ARENA_SINGLE_WINDOW) {
window_sub = new overlay::windows::GitaDoraSubScreen(this);
} else if (games::popn::is_pikapika_model()) {
window_sub = new overlay::windows::PopnSubScreen(this);
}
if (window_sub) {
@@ -736,4 +742,4 @@ void overlay::SpiceOverlay::add_font(const char* font, ImFontConfig* config, con
} else {
log_misc("overlay", "font not found: {}", full_path.string());
}
}
}
@@ -1,6 +1,6 @@
#include "camera_control.h"
#if SPICE64
#if SPICE64 && !SPICE_XP
#include <games/io.h>
#include <strmif.h>
@@ -193,7 +193,7 @@ namespace overlay::windows {
}
ImGui::Separator();
// reset button
if (ImGui::Button("Reset")) {
selectedCamera->ResetCameraControlProps();
+1 -1
View File
@@ -1,6 +1,6 @@
#pragma once
#if SPICE64
#if SPICE64 && !SPICE_XP
#include "overlay/window.h"
#include <strmif.h>
+7 -7
View File
@@ -144,7 +144,7 @@ namespace overlay::windows {
" xxxx xxxx xxxx xxxx ");
ImGui::EndDisabled();
}
ImGui::PopID();
return clicked;
}
@@ -152,8 +152,8 @@ namespace overlay::windows {
void CardManager::open_card_editor() {
if (this->current_card) {
const auto card = this->current_card;
strcpy_s(this->name_buffer, std::size(this->name_buffer), card->name.c_str());
strcpy_s(this->card_buffer, std::size(this->card_buffer), card->id.c_str());
snprintf(this->name_buffer, std::size(this->name_buffer), "%s", card->name.c_str());
snprintf(this->card_buffer, std::size(this->card_buffer), "%s", card->id.c_str());
this->color_buffer[0] = card->color[0];
this->color_buffer[1] = card->color[1];
this->color_buffer[2] = card->color[2];
@@ -227,7 +227,7 @@ namespace overlay::windows {
};
generate_search_string(&card);
this->cards.emplace_back(card);
// mark this card as the selected one
this->current_card = &this->cards.back();
}
@@ -301,7 +301,7 @@ namespace overlay::windows {
// search for card
//
// setting ImGuiInputTextFlags_CallbackCharFilter and pressing escape doesn't cause below
// setting ImGuiInputTextFlags_CallbackCharFilter and pressing escape doesn't cause below
// to return true, making it necessary to provide a callback...
ImGui::SetNextItemWidth(overlay::apply_scaling(240));
if (ImGui::InputTextWithHint("", "Type here to search..", &this->search_filter)) {
@@ -399,11 +399,11 @@ namespace overlay::windows {
// cards from card manager JSON
for (auto &card : this->cards) {
if (!this->search_filter_in_lower_case.empty() && !card.search_string.empty()) {
const bool matched =
card.search_string.find(this->search_filter_in_lower_case) != std::string::npos;
if (!matched) {
continue;
}
File diff suppressed because it is too large Load Diff
+129 -17
View File
@@ -1,5 +1,6 @@
#pragma once
#include <chrono>
#include <optional>
#include "cfg/game.h"
@@ -7,6 +8,7 @@
#include "rawinput/device.h"
#include "external/imgui/imgui_filebrowser.h"
#include "patch_manager.h"
#include "controller_presets.h"
namespace overlay::windows {
@@ -22,9 +24,18 @@ namespace overlay::windows {
CONFIG_TAB_OPTIONS,
CONFIG_TAB_ADVANCED,
CONFIG_TAB_DEV,
CONFIG_TAB_PRESETS,
CONFIG_TAB_SEARCH,
};
struct MatchEntry {
std::string game_name;
std::string device_name;
int light_index;
int control_index;
bool soft;
};
class Config : public Window {
private:
@@ -38,7 +49,6 @@ namespace overlay::windows {
ConfigTab tab_selected = ConfigTab::CONFIG_TAB_INVALID;
// buttons tab
int buttons_page = 0;
bool buttons_keyboard_state[0xFF];
bool buttons_bind_active = false;
bool buttons_many_active = false;
@@ -55,18 +65,64 @@ namespace overlay::windows {
int analogs_devices_control_selected = -1;
// lights tab
int lights_page = 0;
std::vector<rawinput::Device *> lights_devices;
int lights_devices_selected = -1;
int lights_devices_control_selected = -1;
// keypads tab
// lights test all
bool lights_testing = false;
int lights_test_current = -1;
std::chrono::steady_clock::time_point lights_test_time;
// lights auto match
std::vector<rawinput::Device *> auto_match_devices;
int auto_match_device_selected = -1;
bool auto_match_testing = false;
int auto_match_test_current = -1;
std::chrono::steady_clock::time_point auto_match_test_time;
std::string auto_match_test_device;
unsigned int auto_match_test_control = 0;
std::chrono::steady_clock::time_point auto_match_copy_time;
bool auto_match_copied = false;
bool auto_match_soft_enabled = true;
bool auto_match_p2 = false;
// cards tab
int keypads_selected[2] {};
char keypads_card_path[2][1024] {};
std::thread *keypads_card_select = nullptr;
bool keypads_card_select_done = false;
ImGui::FileBrowser keypads_card_select_browser[2];
char keypads_card_number[2][18] {};
bool keypads_card_override_valid[2] = { false, false };
bool keypads_card_file_contents_valid[2] = { false, false };
bool keypads_card_overwrite_confirmed[2];
// presets tab
std::vector<ControllerTemplate> templates_cache;
bool templates_cache_dirty = true;
int templates_selected = -1;
char template_save_name[256] = {};
// per-source target mapping: source index -> selected target device index
std::vector<int> template_target_selection;
// deferred popup flags (to avoid ImGui ID stack mismatch inside tables)
bool template_apply_open = false;
bool template_edit_open = false;
bool template_save_labels_open = false;
bool template_delete_open = false;
ControllerTemplate template_pending_save;
std::vector<std::string> template_save_sources;
std::vector<std::string> template_save_labels;
bool apply_buttons = true;
bool apply_keypads = true;
bool apply_analogs = true;
bool apply_lights = true;
bool save_buttons = true;
bool save_keypads = true;
bool save_analogs = true;
bool save_lights = true;
bool all_cleared = false;
std::vector<bool> template_is_applied;
// patches tab
std::unique_ptr<PatchManager> patch_manager;
@@ -78,26 +134,82 @@ namespace overlay::windows {
std::string search_filter = "";
std::string search_filter_in_lower_case = "";
std::filesystem::path file_picker_path;
std::thread *file_picker_thread = nullptr;
bool file_picker_done = false;
std::pair<std::string, int> analog_as_button_warning_show_next_frame = { "", 0 };
void build_buttons(const std::string &name, std::vector<Button> *buttons, int min = 0, int max = -1);
void build_button(
const std::string &name,
Button &primary_button,
Button *button,
const int button_it,
const int button_it_max,
const int alt_index);
void bind_button_popup(const std::string &bind_name, Button *button, const int button_it_max, const int alt_index);
void naive_button_popup(const std::string &naive_string, Button *button, const int button_it_max, const int alt_index);
void edit_button_popup(
const std::string &edit_name,
const std::string &button_display,
Button *button,
const float button_velocity,
const int alt_index);
void clear_button(Button *button, const int alt_index, std::optional<unsigned short> vKey_default = std::nullopt);
void reset_button_to_default(Button *button, unsigned short vKey_default);
unsigned int get_keypad_top_row(const Button &button);
void build_analogs(const std::string &name, std::vector<Analog> *analogs);
void edit_analog_popup(Analog &analog);
void update() override;
void stop_lights_test();
void build_lights(const std::string &name, std::vector<Light> *lights);
void build_light(Light &primary_light, Light *light, const int light_index, const int alt_index);
void clear_light(Light *light, const int alt_index);
void edit_light_popup(Light &primary_light, Light *light, const int alt_index);
void auto_match_lights_popup(std::vector<Light> *lights);
std::string match_lights(
rawinput::Device *device,
std::vector<Light> *lights,
std::vector<std::string> &raw_names,
std::vector<MatchEntry> &matched,
std::vector<MatchEntry> &unmatched);
void build_presets();
void clear_all_bindings();
void apply_template_source(const ControllerTemplate &tmpl,
const std::string &source_filter,
const std::string &target_device);
ControllerTemplate capture_current_bindings(const std::string &name);
void build_cards();
std::string build_option_value_picker_title(const OptionDefinition& option);
void build_option_value_picker(Option& option);
void build_options(
std::vector<Option> *options, const std::string &category, const std::string *filter=nullptr);
void build_about();
void build_launcher();
void build_keypad_warning();
void launch_shell(LPCSTR app, LPCSTR file=nullptr);
void build_menu(int *game_selected);
void shutdown_system(bool force, bool reboot_instead);
void set_alternating_row_colors(const int row_index);
bool validate_ea_card(char card_number[16]);
public:
Config(SpiceOverlay *overlay);
~Config() override;
void read_card(int player = -1);
void write_card(int player);
void build_content() override;
void build_buttons(const std::string &name, std::vector<Button> *buttons,
int min = 0, int max = -1);
void build_analogs(const std::string &name, std::vector<Analog> *analogs);
void build_lights(const std::string &name, std::vector<Light> *lights);
void build_cards();
void build_options(
std::vector<Option> *options, const std::string &category, const std::string *filter=nullptr);
void build_about();
void build_launcher();
void launch_shell(LPCSTR app, LPCSTR file=nullptr);
static void build_page_selector(int *page);
void build_menu(int *game_selected);
void shutdown_system(bool force, bool reboot_instead);
};
}
+13 -10
View File
@@ -628,13 +628,12 @@ namespace overlay::windows {
ImGui::Text("Wheel: %ld", mouse->pos_wheel);
// keys
std::stringstream keys;
keys << "[";
std::string keys = "[";
for (auto key : mouse->key_states) {
keys << (key ? "1," : "0,");
keys += (key ? "1," : "0,");
}
keys << "]";
ImGui::Text("Keys: %s", keys.str().c_str());
keys += "]";
ImGui::Text("Keys: %s", keys.c_str());
break;
}
@@ -643,15 +642,14 @@ namespace overlay::windows {
ImGui::Text("Type: Keyboard");
// keys
std::stringstream keys;
keys << "[";
std::string keys = "[";
for (size_t i = 0; i < std::size(keyboard->key_states); i++) {
if (keyboard->key_states[i]) {
keys << i << ",";
keys += std::to_string(i) + ",";
}
}
keys << "]";
ImGui::Text("Keys: %s", keys.str().c_str());
keys += "]";
ImGui::Text("Keys: %s", keys.c_str());
break;
}
@@ -732,6 +730,10 @@ namespace overlay::windows {
ImGui::Text("Type: PIUIO");
break;
}
case rawinput::XINPUT_GAMEPAD: {
ImGui::Text("Type: XInput");
break;
}
case rawinput::DESTROYED: {
ImGui::Text("Disconnected.");
break;
@@ -739,6 +741,7 @@ namespace overlay::windows {
case rawinput::UNKNOWN:
default:
ImGui::Text("Type: Unknown");
break;
}
ImGui::TreePop();
}
@@ -0,0 +1,242 @@
#include "controller_presets.h"
#include <algorithm>
#include "cfg/button.h"
namespace overlay::windows {
// helpers - iterate both buttons and keypad_buttons
static void count_buttons_in(const std::vector<TemplateButtonBinding> &btns, int &naive, int &device) {
for (auto &btn : btns) {
if (btn.primary.is_naive()) naive++;
if (btn.primary.is_device()) device++;
for (auto &alt : btn.alternatives) {
if (alt.is_naive()) naive++;
if (alt.is_device()) device++;
}
}
}
int ControllerTemplate::count_naive_buttons() const {
int naive = 0, device = 0;
count_buttons_in(buttons, naive, device);
count_buttons_in(keypad_buttons, naive, device);
return naive;
}
int ControllerTemplate::count_device_buttons() const {
int naive = 0, device = 0;
count_buttons_in(buttons, naive, device);
count_buttons_in(keypad_buttons, naive, device);
return device;
}
int ControllerTemplate::count_device_analogs() const {
int count = 0;
for (auto &a : analogs) {
if (a.is_device()) count++;
}
return count;
}
int ControllerTemplate::count_device_lights() const {
int count = 0;
for (auto &l : lights) {
if (l.primary.is_device()) count++;
for (auto &alt : l.alternatives) {
if (alt.is_device()) count++;
}
}
return count;
}
static void collect_devices_from(const std::vector<TemplateButtonBinding> &btns,
std::set<std::string> &devices) {
for (auto &btn : btns) {
if (btn.primary.is_device()) devices.insert(btn.primary.device_identifier);
for (auto &alt : btn.alternatives) {
if (alt.is_device()) devices.insert(alt.device_identifier);
}
}
}
std::set<std::string> ControllerTemplate::get_used_devices() const {
std::set<std::string> devices;
collect_devices_from(buttons, devices);
collect_devices_from(keypad_buttons, devices);
for (auto &a : analogs) {
if (a.is_device()) devices.insert(a.device_identifier);
}
for (auto &l : lights) {
if (l.primary.is_device()) devices.insert(l.primary.device_identifier);
for (auto &alt : l.alternatives) {
if (alt.is_device()) devices.insert(alt.device_identifier);
}
}
return devices;
}
static void collect_sources_from(const std::vector<TemplateButtonBinding> &btns,
std::set<std::string> &sources_set, bool &has_naive) {
for (auto &btn : btns) {
if (btn.primary.is_naive()) has_naive = true;
if (btn.primary.is_device()) sources_set.insert(btn.primary.device_identifier);
for (auto &alt : btn.alternatives) {
if (alt.is_naive()) has_naive = true;
if (alt.is_device()) sources_set.insert(alt.device_identifier);
}
}
}
std::vector<std::string> ControllerTemplate::get_binding_sources() const {
std::set<std::string> sources_set;
bool has_naive = false;
collect_sources_from(buttons, sources_set, has_naive);
collect_sources_from(keypad_buttons, sources_set, has_naive);
for (auto &a : analogs) {
if (a.is_device()) {
sources_set.insert(a.device_identifier);
}
}
for (auto &l : lights) {
if (l.primary.is_device()) sources_set.insert(l.primary.device_identifier);
for (auto &alt : l.alternatives) {
if (alt.is_device()) sources_set.insert(alt.device_identifier);
}
}
std::vector<std::string> result;
if (has_naive) {
result.emplace_back("Naive");
}
for (auto &s : sources_set) {
result.push_back(s);
}
// sort alphabetically
std::sort(result.begin(), result.end());
return result;
}
void ControllerTemplate::rename_source(const std::string &old_id, const std::string &new_id) {
auto rename_in_buttons = [&](std::vector<TemplateButtonBinding> &btns) {
for (auto &btn : btns) {
if (btn.primary.device_identifier == old_id) btn.primary.device_identifier = new_id;
for (auto &alt : btn.alternatives) {
if (alt.device_identifier == old_id) alt.device_identifier = new_id;
}
}
};
rename_in_buttons(buttons);
rename_in_buttons(keypad_buttons);
for (auto &a : analogs) {
if (a.device_identifier == old_id) a.device_identifier = new_id;
}
for (auto &l : lights) {
if (l.primary.device_identifier == old_id) l.primary.device_identifier = new_id;
for (auto &alt : l.alternatives) {
if (alt.device_identifier == old_id) alt.device_identifier = new_id;
}
}
}
std::string ControllerTemplate::get_source_summary(const std::string &source) const {
bool is_naive = (source == "Naive");
std::string result;
// collect button lines from a button vector
auto collect_btn_lines = [&](const std::vector<TemplateButtonBinding> &btns,
std::vector<std::string> &lines) {
for (auto &tb : btns) {
auto check_entry = [&](const TemplateButtonEntry &e) {
if (is_naive && e.is_naive()) {
Button b("");
b.setVKey(e.vKey);
lines.push_back(tb.name + " = " + (e.vKey == INVALID_VKEY ? "None" : b.getVKeyString()));
} else if (!is_naive && e.is_device() && e.device_identifier == source) {
std::string desc = std::to_string(e.vKey);
if (e.analog_type != BAT_NONE) {
desc += " (analog:" + std::to_string((int)e.analog_type) + ")";
}
lines.push_back(tb.name + " = " + desc);
}
};
check_entry(tb.primary);
for (auto &alt : tb.alternatives) {
check_entry(alt);
}
}
};
// buttons
std::vector<std::string> btn_lines;
collect_btn_lines(buttons, btn_lines);
if (!btn_lines.empty()) {
result += "Buttons:\n";
for (auto &line : btn_lines) {
result += " " + line + "\n";
}
}
// keypad buttons
std::vector<std::string> kp_lines;
collect_btn_lines(keypad_buttons, kp_lines);
if (!kp_lines.empty()) {
result += "Keypad:\n";
for (auto &line : kp_lines) {
result += " " + line + "\n";
}
}
// analogs
if (!is_naive) {
std::vector<std::string> analog_lines;
for (auto &a : analogs) {
if (a.is_device() && a.device_identifier == source) {
analog_lines.push_back(a.name + " (idx:" + std::to_string(a.index) + ")");
}
}
if (!analog_lines.empty()) {
result += "Analogs:\n";
for (auto &line : analog_lines) {
result += " " + line + "\n";
}
}
}
// lights
if (!is_naive) {
std::vector<std::string> light_lines;
for (auto &l : lights) {
auto check_light = [&](const TemplateLightEntry &e) {
if (e.is_device() && e.device_identifier == source) {
light_lines.push_back(l.name + " (idx:" + std::to_string(e.index) + ")");
}
};
check_light(l.primary);
for (auto &alt : l.alternatives) {
check_light(alt);
}
}
if (!light_lines.empty()) {
result += "Lights:\n";
for (auto &line : light_lines) {
result += " " + line + "\n";
}
}
}
if (result.empty()) {
result = "(no bindings)";
}
// trim trailing newline
while (!result.empty() && result.back() == '\n') {
result.pop_back();
}
return result;
}
}

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