Compare commits

...

68 Commits

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

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

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

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

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

## Description of change

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

No backend rawinput changes.

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

## Testing
with a snek board


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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

## Description of change

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


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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Small refactoring of code.

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

This needs a bit more customization since:

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

For XP builds, the call is compiled out to avoid linking against dwmapi
and windows_dll_compat_checker returns successfully when testing against
the XP Embedded image. I can't test it on XP, but it ran on Vista.
2026-04-15 18:51:53 -07:00
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
128 changed files with 19346 additions and 9006 deletions
+4
View File
@@ -383,6 +383,7 @@ set(SOURCE_FILES ${SOURCE_FILES}
games/shared/twtouch.cpp games/shared/twtouch.cpp
games/popn/popn.cpp games/popn/popn.cpp
games/popn/io.cpp games/popn/io.cpp
games/popn/bi3a_hook.cpp
games/bbc/bbc.cpp games/bbc/bbc.cpp
games/bbc/io.cpp games/bbc/io.cpp
games/hpm/hpm.cpp games/hpm/hpm.cpp
@@ -582,6 +583,7 @@ set(SOURCE_FILES ${SOURCE_FILES}
overlay/windows/log.cpp overlay/windows/log.cpp
overlay/windows/midi.cpp overlay/windows/midi.cpp
overlay/windows/patch_manager.cpp overlay/windows/patch_manager.cpp
overlay/windows/popn_sub.cpp
overlay/windows/wnd_manager.cpp overlay/windows/wnd_manager.cpp
# rawinput # rawinput
@@ -596,6 +598,8 @@ set(SOURCE_FILES ${SOURCE_FILES}
rawinput/smxstage.h rawinput/smxstage.h
rawinput/smxdedicab.cpp rawinput/smxdedicab.cpp
rawinput/smxdedicab.h rawinput/smxdedicab.h
rawinput/xinput.cpp
rawinput/xinput.h
# reader # reader
reader/reader.cpp reader/reader.cpp
+120 -18
View File
@@ -35,24 +35,33 @@ struct ICCA_STATUS_LA9 {
static_assert(sizeof(struct ICCA_STATUS) == 24, "ICCA_STATUS must be 24 bytes"); static_assert(sizeof(struct ICCA_STATUS) == 24, "ICCA_STATUS must be 24 bytes");
enum ICCA_WORKFLOW { enum ICCA_WORKFLOW {
STEP, STEP = 0,
SLEEP, SLEEP = 1,
START, START = 2,
INIT, INIT = 3,
READY, READY = 4,
GET_USERID, GET_USERID = 5,
ACTIVE, ACTIVE = 6,
EJECT, EJECT = 7,
EJECT_CHECK, EJECT_CHECK = 8,
END, END = 9,
CLOSE_EJECT, CLOSE_EJECT = 10,
CLOSE_E_CHK, CLOSE_E_CHK = 11,
CLOSE_END, CLOSE_END = 12,
ERR_GETUID = -2 ERR_GETUID = -2,
ERR_REMOVED = -11
}; };
// used by RA as the status_code
enum ICCA_STATUS_CODE {
EMPTY = 1,
ENGAGED = 2
};
struct ICCA_UNIT { struct ICCA_UNIT {
struct ICCA_STATUS status {}; struct ICCA_STATUS status {};
enum ICCA_WORKFLOW state = STEP; enum ICCA_WORKFLOW state = STEP;
enum ICCA_WORKFLOW state_jdz = SLEEP;
bool card_cmd_pressed = false; bool card_cmd_pressed = false;
bool card_in = false; bool card_in = false;
double card_in_time = 0.0; double card_in_time = 0.0;
@@ -75,6 +84,11 @@ static inline int icca_get_active_count() {
} }
static inline int icca_get_unit_id(int unit_id) { static inline int icca_get_unit_id(int unit_id) {
// on RA the unit_id is zero-indexed
if (avs::game::is_model("JDZ")) {
return unit_id;
}
if (icca_get_active_count() < 2) if (icca_get_active_count() < 2)
return 1; return 1;
else { else {
@@ -122,9 +136,15 @@ static inline void update_card(int unit_id) {
IS_LAST_CARD_FELICA = is_card_uid_felica(unit->status.uid); IS_LAST_CARD_FELICA = is_card_uid_felica(unit->status.uid);
unit->state = acio::ICCA_COMPAT_ACTIVE ? START : ACTIVE; unit->state = acio::ICCA_COMPAT_ACTIVE ? START : ACTIVE;
unit->state_jdz = ACTIVE;
unit->status.error = 0; unit->status.error = 0;
unit->status.front_sensor = 1;
unit->status.rear_sensor = 1;
} else { } else {
unit->state = ERR_GETUID; unit->state = ERR_GETUID;
unit->state_jdz = ERR_GETUID;
unit->status.front_sensor = 1;
unit->status.rear_sensor = 1;
memset(unit->status.uid, 0, 8); memset(unit->status.uid, 0, 8);
} }
unit->card_in = true; unit->card_in = true;
@@ -132,10 +152,25 @@ static inline void update_card(int unit_id) {
} else if (unit->state == EJECT_CHECK) { } else if (unit->state == EJECT_CHECK) {
unit->state = SLEEP; unit->state = SLEEP;
unit->card_in = false; unit->card_in = false;
unit->status.front_sensor = 0;
unit->status.rear_sensor = 0;
} else if (unit->state_jdz == READY) {
unit->state = GET_USERID;
unit->state_jdz = GET_USERID;
unit->status.error = 0;
unit->status.front_sensor = 1;
unit->status.rear_sensor = 1;
unit->card_in = true;
unit->card_in_time = get_performance_seconds();
} }
} else { } else {
unit->state = acio::ICCA_COMPAT_ACTIVE ? START : ACTIVE; unit->state = acio::ICCA_COMPAT_ACTIVE ? START : ACTIVE;
if (unit->state_jdz == READY) {
unit->state_jdz = GET_USERID;
}
unit->status.error = 0; unit->status.error = 0;
unit->status.front_sensor = 1;
unit->status.rear_sensor = 1;
unit->card_in = true; unit->card_in = true;
unit->card_in_time = get_performance_seconds(); unit->card_in_time = get_performance_seconds();
} }
@@ -144,6 +179,12 @@ static inline void update_card(int unit_id) {
unit->state = CLOSE_EJECT; unit->state = CLOSE_EJECT;
if (fabs(get_performance_seconds() - unit->card_in_time) > CARD_TIMEOUT) { if (fabs(get_performance_seconds() - unit->card_in_time) > CARD_TIMEOUT) {
unit->card_in = false; unit->card_in = false;
memset(unit->status.uid, 0, 8);
if (unit->state_jdz == ACTIVE || unit->state_jdz == GET_USERID) {
unit->state_jdz = ERR_REMOVED;
}
unit->status.front_sensor = 0;
unit->status.rear_sensor = 0;
} }
} }
@@ -302,7 +343,11 @@ static char __cdecl ac_io_icca_get_status(void *a1, void *a2) {
// copy state to output buffer // copy state to output buffer
ICCA_UNIT *unit = &ICCA_UNITS[unit_id]; ICCA_UNIT *unit = &ICCA_UNITS[unit_id];
if (avs::game::is_model("JDZ")) {
unit->status.status_code = unit->card_in ? ENGAGED : EMPTY;
} else {
unit->status.status_code = unit->state; unit->status.status_code = unit->state;
}
memcpy(status, &unit->status, sizeof(struct ICCA_STATUS)); memcpy(status, &unit->status, sizeof(struct ICCA_STATUS));
// funny workaround // funny workaround
@@ -387,6 +432,11 @@ static char __cdecl ac_io_icca_req_uid(int unit_id) {
static int __cdecl ac_io_icca_req_uid_isfinished(int unit_id, DWORD *read_state) { static int __cdecl ac_io_icca_req_uid_isfinished(int unit_id, DWORD *read_state) {
unit_id = icca_get_unit_id(unit_id); unit_id = icca_get_unit_id(unit_id);
ICCA_UNIT *unit = &ICCA_UNITS[unit_id]; ICCA_UNIT *unit = &ICCA_UNITS[unit_id];
if (avs::game::is_model("JDZ")) {
// hack for RA - only ever seen it as this
*read_state = 8;
return 1;
}
if (unit->card_in) { if (unit->card_in) {
if (fabs(get_performance_seconds() - unit->card_in_time) < CARD_TIMEOUT) { if (fabs(get_performance_seconds() - unit->card_in_time) < CARD_TIMEOUT) {
unit->state = END; unit->state = END;
@@ -403,15 +453,60 @@ static int __cdecl ac_io_icca_send_keep_alive_packet(int a1, int a2, int a3) {
return 0; return 0;
} }
static int __cdecl ac_io_icca_workflow_jdz(int workflow, int unit_id) {
unit_id = icca_get_unit_id(unit_id);
ICCA_UNIT *unit = &ICCA_UNITS[unit_id];
switch (workflow) {
// RA uses "STEP" as more of a polling thing it seems
case STEP:
switch (unit->state_jdz) {
case CLOSE_EJECT:
unit->state_jdz = CLOSE_E_CHK;
break;
case CLOSE_E_CHK:
unit->state_jdz = CLOSE_END;
break;
case CLOSE_END:
unit->state_jdz = SLEEP;
break;
default:
break;
}
break;
case SLEEP:
unit->state = SLEEP;
break;
case START:
unit->state_jdz = INIT;
break;
case INIT:
case READY:
unit->state_jdz = READY;
break;
case CLOSE_EJECT:
unit->state_jdz = CLOSE_EJECT;
return CLOSE_E_CHK;
case CLOSE_END:
unit->state_jdz = SLEEP;
break;
default:
// should probably log a warning here
break;
}
return unit->state_jdz;
}
static int __cdecl ac_io_icca_workflow(int workflow, int unit_id) { static int __cdecl ac_io_icca_workflow(int workflow, int unit_id) {
// RA uses a different workflow process for the slotted readers; treat it differently
if (avs::game::is_model("JDZ")) {
return ac_io_icca_workflow_jdz(workflow, unit_id);
}
unit_id = icca_get_unit_id(unit_id); unit_id = icca_get_unit_id(unit_id);
ICCA_UNIT *unit = &ICCA_UNITS[unit_id]; ICCA_UNIT *unit = &ICCA_UNITS[unit_id];
switch (workflow) { switch (workflow) {
case STEP: case STEP:
if (avs::game::is_model("JDZ"))
unit->state = SLEEP;
else
unit->state = STEP; unit->state = STEP;
break; break;
case SLEEP: case SLEEP:
@@ -441,7 +536,6 @@ static int __cdecl ac_io_icca_workflow(int workflow, int unit_id) {
default: default:
break; break;
} }
return unit->state; return unit->state;
} }
@@ -467,6 +561,14 @@ acio::ICCAModule::ICCAModule(HMODULE module, acio::HookMode hookMode) : ACIOModu
void acio::ICCAModule::attach() { void acio::ICCAModule::attach() {
ACIOModule::attach(); ACIOModule::attach();
// workaround for RA; ac_io_icca_cardunit_init is never called so treat like both are initialized
if (avs::game::is_model("JDZ")) {
ICCA_UNITS[0].initialized = true;
ICCA_UNITS[1].initialized = true;
CARD_TIMEOUT = 10.0; // keep the card in the slot for longer
}
// hooks // hooks
ACIO_MODULE_HOOK(ac_io_icca_cardunit_init); ACIO_MODULE_HOOK(ac_io_icca_cardunit_init);
ACIO_MODULE_HOOK(ac_io_icca_cardunit_init_isfinished); ACIO_MODULE_HOOK(ac_io_icca_cardunit_init_isfinished);
+17 -5
View File
@@ -2,6 +2,7 @@
#include "mdxf_poll.h" #include "mdxf_poll.h"
#include "avs/game.h" #include "avs/game.h"
#include "games/ddr/ddr.h"
#include "games/ddr/io.h" #include "games/ddr/io.h"
#include "launcher/launcher.h" #include "launcher/launcher.h"
#include "rawinput/rawinput.h" #include "rawinput/rawinput.h"
@@ -357,14 +358,17 @@ static bool __cdecl ac_io_mdxf_update_control_status_buffer_impl(int node, MDXFP
// decide on button map // decide on button map
const size_t *button_map = nullptr; const size_t *button_map = nullptr;
int player = 0;
switch (node) { switch (node) {
case 17: case 17:
case 25: case 25:
button_map = &buttons_p1[0]; button_map = &buttons_p1[0];
player = 1;
break; break;
case 18: case 18:
case 26: case 26:
button_map = &buttons_p2[0]; button_map = &buttons_p2[0];
player = 2;
break; break;
} }
@@ -374,19 +378,27 @@ static bool __cdecl ac_io_mdxf_update_control_status_buffer_impl(int node, MDXFP
if (source == EXTERNAL_POLL) { if (source == EXTERNAL_POLL) {
// get buttons // get buttons
auto &buttons = games::ddr::get_buttons(); auto &buttons = games::ddr::get_buttons();
// get analogs
bool analog_left = false;
bool analog_right = false;
games::ddr::get_analog_x_axis(player, analog_left, analog_right);
bool analog_up = false;
bool analog_down = false;
games::ddr::get_analog_y_axis(player, analog_up, analog_down);
uint8_t up_down = 0; uint8_t up_down = 0;
uint8_t left_right = 0; uint8_t left_right = 0;
if (GameAPI::Buttons::getState(RI_MGR, buttons.at(button_map[0])) || analog_up) {
if (GameAPI::Buttons::getState(RI_MGR, buttons.at(button_map[0]))) {
up_down |= 0xF0; up_down |= 0xF0;
} }
if (GameAPI::Buttons::getState(RI_MGR, buttons.at(button_map[1]))) { if (GameAPI::Buttons::getState(RI_MGR, buttons.at(button_map[1])) || analog_down) {
up_down |= 0x0F; up_down |= 0x0F;
} }
if (GameAPI::Buttons::getState(RI_MGR, buttons.at(button_map[2]))) { if (GameAPI::Buttons::getState(RI_MGR, buttons.at(button_map[2])) || analog_left) {
left_right |= 0xF0; left_right |= 0xF0;
} }
if (GameAPI::Buttons::getState(RI_MGR, buttons.at(button_map[3]))) { if (GameAPI::Buttons::getState(RI_MGR, buttons.at(button_map[3])) || analog_right) {
left_right |= 0x0F; left_right |= 0x0F;
} }
current_state = (uint16_t(up_down) << 8) | left_right; current_state = (uint16_t(up_down) << 8) | left_right;
+3 -3
View File
@@ -58,7 +58,7 @@ namespace api::modules {
// check if file exists in modules // check if file exists in modules
if (!fileutils::file_exists(dll_path)) { if (!fileutils::file_exists(dll_path)) {
return error(res, "Couldn't find " + dll_path.string()); return error(res, fmt::format("Couldn't find {}", dll_path));
} }
// get module // get module
@@ -118,7 +118,7 @@ namespace api::modules {
// check if file exists in modules // check if file exists in modules
if (!fileutils::file_exists(dll_path)) { if (!fileutils::file_exists(dll_path)) {
return error(res, "Couldn't find " + dll_path.string()); return error(res, fmt::format("Couldn't find {}", dll_path));
} }
// get module // get module
@@ -196,7 +196,7 @@ namespace api::modules {
// check if file exists in modules // check if file exists in modules
if (!fileutils::file_exists(dll_path)) { if (!fileutils::file_exists(dll_path)) {
return error(res, "Couldn't find " + dll_path.string()); return error(res, fmt::format("Couldn't find {}", dll_path));
} }
// get module // get module
+24 -5
View File
@@ -7,6 +7,7 @@
#include <stdint.h> #include <stdint.h>
#include "external/robin_hood.h" #include "external/robin_hood.h"
#include "games/popn/popn.h"
#include "launcher/logger.h" #include "launcher/logger.h"
#include "launcher/signal.h" #include "launcher/signal.h"
#include "util/deferlog.h" #include "util/deferlog.h"
@@ -1179,6 +1180,15 @@ namespace avs {
return; 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()) { if (HEAP_SIZE_DEFAULTS.find(dll_name) != HEAP_SIZE_DEFAULTS.end()) {
auto old_size = HEAP_SIZE; auto old_size = HEAP_SIZE;
@@ -1506,7 +1516,7 @@ namespace avs {
" * It's also possible that you have incomplete game data\n" " * It's also possible that you have incomplete game data\n"
" * Do NOT copy over random DLLs from another game installation; DLL must match game version\n" " * Do NOT copy over random DLLs from another game installation; DLL must match game version\n"
"\n" "\n"
, DLL_NAME, MODULE_PATH.string()) }; , DLL_NAME, MODULE_PATH) };
log_warning("avs-ea3", "{}", info_str); log_warning("avs-ea3", "{}", info_str);
log_fatal("avs-ea3", "Failed to find critical avs DLL on disk (avs2-core.dll OR {})", DLL_NAME); log_fatal("avs-ea3", "Failed to find critical avs DLL on disk (avs2-core.dll OR {})", DLL_NAME);
} }
@@ -1747,8 +1757,8 @@ namespace avs {
{ {
deferredlogs::defer_error_messages({ deferredlogs::defer_error_messages({
"AVS filesystem initialization failure was previously detected during boot!", "AVS filesystem initialization failure was previously detected during boot!",
fmt::format(" ERROR: directory could not be created: {}", src_path.string().c_str()), fmt::format(" ERROR: directory could not be created: {}", src_path),
fmt::format(" if you see a crash, it may have been caused by bad <mounttable> contents in {}", avs::core::CFG_PATH.c_str()), fmt::format(" if you see a crash, it may have been caused by bad <mounttable> contents in {}", avs::core::CFG_PATH),
" fix the XML file and try again" " fix the XML file and try again"
}); });
} }
@@ -1770,14 +1780,14 @@ namespace avs {
auto created = std::filesystem::create_directories(real_path, err); auto created = std::filesystem::create_directories(real_path, err);
if (created) { if (created) {
log_info("avs-core", "created '{}' at '{}'", avs_path, real_path.string()); log_info("avs-core", "created '{}' at '{}'", avs_path, real_path);
} }
if (err) { if (err) {
avs_dir_err(real_path); avs_dir_err(real_path);
log_warning("avs-core", "failed to create '{}' folder at '{}': {}", log_warning("avs-core", "failed to create '{}' folder at '{}': {}",
avs_path, avs_path,
real_path.string(), real_path,
err.message()); err.message());
} }
} }
@@ -1911,6 +1921,15 @@ namespace avs {
// create nvram and raw directories if possible for mounttable configurations // create nvram and raw directories if possible for mounttable configurations
create_avs_config_fs_table(config, config_node); create_avs_config_fs_table(config, config_node);
#if !SPICE64
// sdvx4 bad log config fix
if (avs::game::DLL_NAME == "soundvoltex.dll" && // it's too early for avs::game::is_model
property_search_safe(config, config_node, "/log/enable_console")) {
log_info("avs-core", "applying SDVX4 avs-config.xml fix for <log><enable_console>");
property_search_remove_safe(config, config_node, "/log/enable_console");
}
#endif
// set log level // set log level
if (!LOG_LEVEL_CUSTOM.empty()) { if (!LOG_LEVEL_CUSTOM.empty()) {
property_search_remove_safe(config, config_node, "/log/level"); property_search_remove_safe(config, config_node, "/log/level");
+5 -3
View File
@@ -151,7 +151,7 @@ namespace avs {
" * It's also possible that you have incomplete game data\n" " * It's also possible that you have incomplete game data\n"
" * Do NOT copy over random DLLs from another game installation; DLL must match game version\n" " * Do NOT copy over random DLLs from another game installation; DLL must match game version\n"
"\n" "\n"
, DLL_NAME, MODULE_PATH.string()) }; , DLL_NAME, MODULE_PATH) };
log_warning("avs-ea3", "{}", info_str); log_warning("avs-ea3", "{}", info_str);
log_fatal("avs-ea3", "Failed to find critical ea3 DLL on disk (avs2-ea3.dll OR {})", DLL_NAME); log_fatal("avs-ea3", "Failed to find critical ea3 DLL on disk (avs2-ea3.dll OR {})", DLL_NAME);
@@ -217,8 +217,10 @@ namespace avs {
ea3_config_name = "prop/eamuse-config.xml"; ea3_config_name = "prop/eamuse-config.xml";
} }
if (avs::core::file_exists(ea3_config_name)) { if (fileutils::file_exists(ea3_config_name)) {
log_info("avs-ea3", "booting (using {})", ea3_config_name); log_info("avs-ea3", "found {} on disk", ea3_config_name);
} else if (CFG_PATH.size()) {
log_fatal("avs-ea3", "user-specified ea3 config file is missing: {}", ea3_config_name);
} else { } else {
log_warning("avs-ea3", "looked for the following files in order:"); log_warning("avs-ea3", "looked for the following files in order:");
log_warning("avs-ea3", " * prop/ea3-config.xml"); log_warning("avs-ea3", " * prop/ea3-config.xml");
+4 -5
View File
@@ -73,11 +73,10 @@ namespace avs {
// load game instance // load game instance
const auto dll_path = MODULE_PATH / DLL_NAME; const auto dll_path = MODULE_PATH / DLL_NAME;
const auto dll_path_s = dll_path.string(); log_info("avs-game", "DLL path: {}", dll_path);
log_info("avs-game", "DLL path: {}", dll_path_s.c_str());
// MAX_PATH is 260 // MAX_PATH is 260
if (130 <= dll_path_s.length()) { if (const auto dll_path_len = dll_path.wstring().length(); 130 <= dll_path_len) {
log_warning( log_warning(
"avs-game", "avs-game",
"PATH TOO LONG WARNING\n\n" "PATH TOO LONG WARNING\n\n"
@@ -93,7 +92,7 @@ namespace avs {
"long, often resulting in random crashes. Move the game contents to\n" "long, often resulting in random crashes. Move the game contents to\n"
"a directory with shorter path.\n" "a directory with shorter path.\n"
"-------------------------------------------------------------------\n\n", "-------------------------------------------------------------------\n\n",
dll_path_s, dll_path_s.length()); dll_path, dll_path_len);
} }
// ddr gamemdx.dll user error // ddr gamemdx.dll user error
@@ -112,7 +111,7 @@ namespace avs {
// file not found on disk // file not found on disk
if (!fileutils::file_exists(dll_path)) { if (!fileutils::file_exists(dll_path)) {
log_warning("avs-game", "game DLL could not be found on disk: {}", dll_path.string().c_str()); log_warning("avs-game", "game DLL could not be found on disk: {}", dll_path);
log_warning("avs-game", "double check -exec and -modules parameters; unless you know what you're doing, leave them blank"); log_warning("avs-game", "double check -exec and -modules parameters; unless you know what you're doing, leave them blank");
} }
+37 -37
View File
@@ -39,8 +39,8 @@
<button name="VEFX" vkey="10" analogtype="0" devid="Player 1" invert="false" debounce_up="0" debounce_down="0" velocity_threshold="0"/> <button name="VEFX" vkey="10" analogtype="0" devid="Player 1" invert="false" debounce_up="0" debounce_down="0" velocity_threshold="0"/>
</buttons> </buttons>
<analogs> <analogs>
<analog name="Turntable P1" devid="Player 1" index="4" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1" relative="false" delay="0"/> <analog name="Turntable P1" devid="Player 1" index="4" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1"/>
<analog name="Turntable P2" devid="Player 2" index="4" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1" relative="false" delay="0"/> <analog name="Turntable P2" devid="Player 2" index="4" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1"/>
</analogs> </analogs>
<lights> <lights>
<light name="P1 1" devid="Player 1" index="0"/> <light name="P1 1" devid="Player 1" index="0"/>
@@ -117,8 +117,8 @@
<keypad_buttons/> <keypad_buttons/>
<!-- Assumes analog mode. Need to invert direction since this FW is designed for INF. --> <!-- Assumes analog mode. Need to invert direction since this FW is designed for INF. -->
<analogs> <analogs>
<analog name="Turntable P1" devid="arcin (1p)" index="0" sensivity="1" deadzone="0" deadzone_mirror="false" invert="true" smoothing="false" multiplier="1" relative="false" delay="0"/> <analog name="Turntable P1" devid="arcin (1p)" index="0" sensivity="1" deadzone="0" deadzone_mirror="false" invert="true" smoothing="false" multiplier="1"/>
<analog name="Turntable P2" devid="arcin (2p)" index="0" sensivity="1" deadzone="0" deadzone_mirror="false" invert="true" smoothing="false" multiplier="1" relative="false" delay="0"/> <analog name="Turntable P2" devid="arcin (2p)" index="0" sensivity="1" deadzone="0" deadzone_mirror="false" invert="true" smoothing="false" multiplier="1"/>
</analogs> </analogs>
<lights> <lights>
<light name="P1 1" devid="arcin (1p)" index="0"/> <light name="P1 1" devid="arcin (1p)" index="0"/>
@@ -144,34 +144,34 @@
<buttons/> <buttons/>
<keypad_buttons/> <keypad_buttons/>
<analogs> <analogs>
<analog name="Key 1" devid="Analogs key 01-28" index="4" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1" relative="false" delay="0"/> <analog name="Key 1" devid="Analogs key 01-28" index="4" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1"/>
<analog name="Key 2" devid="Analogs key 01-28" index="5" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1" relative="false" delay="0"/> <analog name="Key 2" devid="Analogs key 01-28" index="5" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1"/>
<analog name="Key 3" devid="Analogs key 01-28" index="6" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1" relative="false" delay="0"/> <analog name="Key 3" devid="Analogs key 01-28" index="6" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1"/>
<analog name="Key 4" devid="Analogs key 01-28" index="7" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1" relative="false" delay="0"/> <analog name="Key 4" devid="Analogs key 01-28" index="7" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1"/>
<analog name="Key 5" devid="Analogs key 01-28" index="8" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1" relative="false" delay="0"/> <analog name="Key 5" devid="Analogs key 01-28" index="8" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1"/>
<analog name="Key 6" devid="Analogs key 01-28" index="9" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1" relative="false" delay="0"/> <analog name="Key 6" devid="Analogs key 01-28" index="9" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1"/>
<analog name="Key 7" devid="Analogs key 01-28" index="10" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1" relative="false" delay="0"/> <analog name="Key 7" devid="Analogs key 01-28" index="10" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1"/>
<analog name="Key 8" devid="Analogs key 01-28" index="11" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1" relative="false" delay="0"/> <analog name="Key 8" devid="Analogs key 01-28" index="11" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1"/>
<analog name="Key 9" devid="Analogs key 01-28" index="12" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1" relative="false" delay="0"/> <analog name="Key 9" devid="Analogs key 01-28" index="12" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1"/>
<analog name="Key 10" devid="Analogs key 01-28" index="13" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1" relative="false" delay="0"/> <analog name="Key 10" devid="Analogs key 01-28" index="13" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1"/>
<analog name="Key 11" devid="Analogs key 01-28" index="14" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1" relative="false" delay="0"/> <analog name="Key 11" devid="Analogs key 01-28" index="14" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1"/>
<analog name="Key 12" devid="Analogs key 01-28" index="15" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1" relative="false" delay="0"/> <analog name="Key 12" devid="Analogs key 01-28" index="15" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1"/>
<analog name="Key 13" devid="Analogs key 01-28" index="16" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1" relative="false" delay="0"/> <analog name="Key 13" devid="Analogs key 01-28" index="16" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1"/>
<analog name="Key 14" devid="Analogs key 01-28" index="17" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1" relative="false" delay="0"/> <analog name="Key 14" devid="Analogs key 01-28" index="17" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1"/>
<analog name="Key 15" devid="Analogs key 01-28" index="18" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1" relative="false" delay="0"/> <analog name="Key 15" devid="Analogs key 01-28" index="18" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1"/>
<analog name="Key 16" devid="Analogs key 01-28" index="19" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1" relative="false" delay="0"/> <analog name="Key 16" devid="Analogs key 01-28" index="19" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1"/>
<analog name="Key 17" devid="Analogs key 01-28" index="20" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1" relative="false" delay="0"/> <analog name="Key 17" devid="Analogs key 01-28" index="20" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1"/>
<analog name="Key 18" devid="Analogs key 01-28" index="21" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1" relative="false" delay="0"/> <analog name="Key 18" devid="Analogs key 01-28" index="21" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1"/>
<analog name="Key 19" devid="Analogs key 01-28" index="22" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1" relative="false" delay="0"/> <analog name="Key 19" devid="Analogs key 01-28" index="22" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1"/>
<analog name="Key 20" devid="Analogs key 01-28" index="23" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1" relative="false" delay="0"/> <analog name="Key 20" devid="Analogs key 01-28" index="23" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1"/>
<analog name="Key 21" devid="Analogs key 01-28" index="24" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1" relative="false" delay="0"/> <analog name="Key 21" devid="Analogs key 01-28" index="24" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1"/>
<analog name="Key 22" devid="Analogs key 01-28" index="25" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1" relative="false" delay="0"/> <analog name="Key 22" devid="Analogs key 01-28" index="25" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1"/>
<analog name="Key 23" devid="Analogs key 01-28" index="26" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1" relative="false" delay="0"/> <analog name="Key 23" devid="Analogs key 01-28" index="26" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1"/>
<analog name="Key 24" devid="Analogs key 01-28" index="27" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1" relative="false" delay="0"/> <analog name="Key 24" devid="Analogs key 01-28" index="27" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1"/>
<analog name="Key 25" devid="Analogs key 01-28" index="28" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1" relative="false" delay="0"/> <analog name="Key 25" devid="Analogs key 01-28" index="28" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1"/>
<analog name="Key 26" devid="Analogs key 01-28" index="29" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1" relative="false" delay="0"/> <analog name="Key 26" devid="Analogs key 01-28" index="29" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1"/>
<analog name="Key 27" devid="Analogs key 01-28" index="30" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1" relative="false" delay="0"/> <analog name="Key 27" devid="Analogs key 01-28" index="30" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1"/>
<analog name="Key 28" devid="Analogs key 01-28" index="31" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1" relative="false" delay="0"/> <analog name="Key 28" devid="Analogs key 01-28" index="31" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1"/>
</analogs> </analogs>
<lights> <lights>
<light name="Key 1 R" devid="Lights 01-14" index="0"/> <light name="Key 1 R" devid="Lights 01-14" index="0"/>
@@ -277,9 +277,9 @@
</buttons> </buttons>
<keypad_buttons/> <keypad_buttons/>
<analogs> <analogs>
<analog name="Guitar P1 Wail X" devid="Gitaller P1" index="1" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1" relative="false" delay="0"/> <analog name="Guitar P1 Wail X" devid="Gitaller P1" index="1" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1"/>
<analog name="Guitar P1 Wail Y" devid="Gitaller P1" index="0" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1" relative="false" delay="0"/> <analog name="Guitar P1 Wail Y" devid="Gitaller P1" index="0" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1"/>
<analog name="Guitar P1 Wail Z" devid="Gitaller P1" index="5" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1" relative="false" delay="0"/> <analog name="Guitar P1 Wail Z" devid="Gitaller P1" index="5" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1"/>
</analogs> </analogs>
<lights/> <lights/>
<!-- Gen 1 FAUCETWO in HID mode; should also work for later gens --> <!-- Gen 1 FAUCETWO in HID mode; should also work for later gens -->
@@ -296,8 +296,8 @@
</buttons> </buttons>
<keypad_buttons/> <keypad_buttons/>
<analogs> <analogs>
<analog name="VOL-L" devid="Buttons (MI_01)" index="1" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1" relative="false" delay="0"/> <analog name="VOL-L" devid="Buttons (MI_01)" index="1" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1"/>
<analog name="VOL-R" devid="Buttons (MI_01)" index="0" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1" relative="false" delay="0"/> <analog name="VOL-R" devid="Buttons (MI_01)" index="0" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1"/>
</analogs> </analogs>
<lights> <lights>
<light name="BT-A" devid="Lights (MI_02)" index="24"/> <light name="BT-A" devid="Lights (MI_02)" index="24"/>
+92 -4
View File
@@ -1,4 +1,4 @@
#!/bin/bash #!/usr/bin/env bash
# set to EN-US for consistency # set to EN-US for consistency
LANG=en_us_8859_1 LANG=en_us_8859_1
@@ -43,10 +43,16 @@ GIT_BRANCH=$(git rev-parse --abbrev-ref HEAD 2> /dev/null || echo "none")
GIT_HEAD=$(git rev-parse HEAD || echo "none") GIT_HEAD=$(git rev-parse HEAD || echo "none")
TOOLCHAIN_32="${TOOLCHAIN_32:-"/usr/share/mingw/toolchain-i686-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_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_RELEASE="./cmake-build-release-32"
BUILDDIR_32_DEBUG="./cmake-build-debug-32" BUILDDIR_32_DEBUG="./cmake-build-debug-32"
BUILDDIR_64_RELEASE="./cmake-build-release-64" BUILDDIR_64_RELEASE="./cmake-build-release-64"
BUILDDIR_64_DEBUG="./cmake-build-debug-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 DEBUG=0
OUTDIR="./bin/spice2x" OUTDIR="./bin/spice2x"
OUTDIR_EXTRAS="./bin/spice2x/extras" OUTDIR_EXTRAS="./bin/spice2x/extras"
@@ -64,19 +70,36 @@ 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.' 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_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_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 # determine build type
BUILD_TYPE="Release" BUILD_TYPE="Release"
BUILDDIR_32=${BUILDDIR_32_RELEASE} BUILDDIR_32=${BUILDDIR_32_RELEASE}
BUILDDIR_64=${BUILDDIR_64_RELEASE} BUILDDIR_64=${BUILDDIR_64_RELEASE}
BUILDDIR_WINXP_32=${BUILDDIR_WINXP_32_RELEASE}
BUILDDIR_WINXP_64=${BUILDDIR_WINXP_64_RELEASE}
if ((DEBUG > 0)) if ((DEBUG > 0))
then then
BUILD_TYPE="Debug" BUILD_TYPE="Debug"
BUILDDIR_32=${BUILDDIR_32_DEBUG} BUILDDIR_32=${BUILDDIR_32_DEBUG}
BUILDDIR_64=${BUILDDIR_64_DEBUG} BUILDDIR_64=${BUILDDIR_64_DEBUG}
BUILDDIR_WINXP_32=${BUILDDIR_WINXP_32_DEBUG}
BUILDDIR_WINXP_64=${BUILDDIR_WINXP_64_DEBUG}
DIST_NAME=$(echo "${DIST_NAME}" | sed 's/\.[^.]*$/-dbg&/') DIST_NAME=$(echo "${DIST_NAME}" | sed 's/\.[^.]*$/-dbg&/')
fi 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 # determine number of cores
CORES=$(nproc) CORES=$(nproc)
@@ -90,6 +113,13 @@ echo "Git Branch: $GIT_BRANCH"
echo "Git Head: $GIT_HEAD" echo "Git Head: $GIT_HEAD"
echo "Toolchain for 32bit targets: $TOOLCHAIN_32" echo "Toolchain for 32bit targets: $TOOLCHAIN_32"
echo "Toolchain for 64bit targets: $TOOLCHAIN_64" 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 "Distribution Name: $DIST_NAME"
echo "Build Type: $BUILD_TYPE" echo "Build Type: $BUILD_TYPE"
echo "Cores: $CORES" echo "Cores: $CORES"
@@ -130,11 +160,59 @@ time (
cmake -G "Ninja" -DCMAKE_TOOLCHAIN_FILE=${TOOLCHAIN_64} -DCMAKE_BUILD_TYPE=${BUILD_TYPE} "$OLDPWD" && ninja ${TARGETS_64} cmake -G "Ninja" -DCMAKE_TOOLCHAIN_FILE=${TOOLCHAIN_64} -DCMAKE_BUILD_TYPE=${BUILD_TYPE} "$OLDPWD" && ninja ${TARGETS_64}
popd > /dev/null popd > /dev/null
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 ""
echo "Compilation process done :)" echo "Compilation process done :)"
echo "===========================" 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 # generate PDBs
if false # ((DEBUG > 0)) if false # ((DEBUG > 0))
then then
@@ -191,6 +269,10 @@ rm -rf ${OUTDIR_EXTRAS}
mkdir -p ${OUTDIR_EXTRAS} mkdir -p ${OUTDIR_EXTRAS}
mkdir -p ${OUTDIR_EXTRAS}/largeaddressaware mkdir -p ${OUTDIR_EXTRAS}/largeaddressaware
mkdir -p ${OUTDIR_EXTRAS}/linux mkdir -p ${OUTDIR_EXTRAS}/linux
if ((BUILD_XP > 0))
then
mkdir -p ${OUTDIR_EXTRAS}/winxp
fi
if false # ((DEBUG > 0)) if false # ((DEBUG > 0))
then then
@@ -226,6 +308,12 @@ else
cp ${BUILDDIR_64}/spicetools/64/nvcuda.dll ${OUTDIR}/stubs/64 2>/dev/null 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_64}/spicetools/64/nvcuvid.dll ${OUTDIR}/stubs/64 2>/dev/null
cp ${BUILDDIR_32}/spicetools/32/cpusbxpkm.dll ${OUTDIR}/stubs/32 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 fi
# pack source files to output directory # pack source files to output directory
@@ -250,10 +338,10 @@ then
echo "Building dist..." echo "Building dist..."
mkdir -p ${DIST_FOLDER} mkdir -p ${DIST_FOLDER}
rm -rf "${DIST_FOLDER}/${DIST_NAME}" rm -rf "${DIST_FOLDER}/${DIST_NAME}"
pushd ${OUTDIR}/.. > /dev/null pushd ${OUTDIR} > /dev/null
zip -qrXT9 "$OLDPWD/${DIST_FOLDER}/${DIST_NAME}" spice2x -x "spice2x/extras/*" -z <<< "$DIST_COMMENT" zip -qrXT9 "$OLDPWD/${DIST_FOLDER}/${DIST_NAME}" . -x "extras/*" -z <<< "$DIST_COMMENT"
echo "Building extras..." echo "Building extras..."
zip -qrXT9 "$OLDPWD/${DIST_FOLDER}/${DIST_NAME_EXTRAS}" spice2x -z <<< "$DIST_COMMENT" zip -qrXT9 "$OLDPWD/${DIST_FOLDER}/${DIST_NAME_EXTRAS}" . -z <<< "$DIST_COMMENT"
popd > /dev/null popd > /dev/null
fi fi
+10 -2
View File
@@ -1,4 +1,12 @@
#!/bin/bash #!/usr/bin/env bash
set -eu
docker build --pull "$PWD/external/docker" -t spicetools/deps --platform linux/x86_64 docker build --pull "$PWD/external/docker" -t spicetools/deps --platform linux/x86_64
docker build --build-context gitroot="$PWD/../../.git" . -t spicetools/spice:latest docker build --build-context gitroot="$PWD/../../.git" . -t spicetools/spice:latest
docker run --rm -v "$PWD/dist:/src/src/spice2x/dist" -v "$PWD/bin:/src/src/spice2x/bin" -v "$PWD/.ccache:/src/src/spice2x/.ccache" spicetools/spice "$@"
# 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 "$@"
+24 -5
View File
@@ -90,6 +90,10 @@ std::string Analog::getDisplayString(rawinput::RawInputManager *manager) {
return "MIDI Unknown Index " + indexString + " (" + device->desc + ")"; 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: case rawinput::DESTROYED:
return "Device unplugged (" + indexString + ")"; return "Device unplugged (" + indexString + ")";
default: default:
@@ -203,6 +207,7 @@ float Analog::applyMultiplier(float value) {
} }
float Analog::normalizeAnalogValue(float value) { float Analog::normalizeAnalogValue(float value) {
if (getType() == GameAPI::Analogs::AnalogType::Circular) {
// effectively the same as fmodf(value, 1.f) // effectively the same as fmodf(value, 1.f)
// for small values, this is MUCH faster than fmodf. // for small values, this is MUCH faster than fmodf.
float new_value = value; float new_value = value;
@@ -213,12 +218,26 @@ float Analog::normalizeAnalogValue(float value) {
new_value += 1.f; new_value += 1.f;
} }
return new_value; return new_value;
} else {
// clamp to [0, 1] range
return std::clamp(value, 0.f, 1.f);
}
} }
float Analog::applyDeadzone(float raw_value) { float Analog::applyDeadzone(float raw_value) {
float value = raw_value; float value = raw_value;
const auto deadzone = this->getDeadzone(); auto deadzone = this->getDeadzone();
if (deadzone > 0) {
// in the past, positive deadzone applied in the center, negative deadzone applied to 0
// after each analog value received a type (circular/linear) this has been simpliifed to
// positive values only since we can figure out where the rest value is
// for back compat, treat negative value as positive
if (deadzone < 0.f) {
deadzone = -deadzone;
}
if (getType() != GameAPI::Analogs::AnalogType::LinearPositive) {
// calculate values // calculate values
const auto delta = value - 0.5f; const auto delta = value - 0.5f;
@@ -251,7 +270,7 @@ float Analog::applyDeadzone(float raw_value) {
} }
} }
} else if (deadzone < 0) { } else {
// invert for mirror // invert for mirror
if (this->getDeadzoneMirror()) { if (this->getDeadzoneMirror()) {
@@ -259,8 +278,8 @@ float Analog::applyDeadzone(float raw_value) {
} }
// deadzone from minimum value // deadzone from minimum value
if (deadzone > -1 && value > -deadzone) { if (deadzone < 1.f && deadzone < value) {
value = std::min(1.f, (value + deadzone) / (1.f + deadzone)); value = std::max(0.f, (value - deadzone) / (1.f - deadzone));
} else { } else {
value = 0.f; value = 0.f;
} }
+23 -35
View File
@@ -13,6 +13,21 @@ namespace rawinput {
class RawInputManager; class RawInputManager;
} }
namespace GameAPI::Analogs {
enum class AnalogType {
// default; values wrap around (below 0 turns into 1, over 1 is 0)
// knobs, turntables
Circular = 0,
// typical joystick that rests at the center and caps at [0, 1]
LinearCentered = 1,
// one-directional value (sliders and instruments like piano/drum velocity)
// starts at 0 and goes up to 1
LinearPositive = 2,
};
}
struct AnalogMovingAverage { struct AnalogMovingAverage {
double time_in_ms; double time_in_ms;
float sine; float sine;
@@ -31,10 +46,11 @@ private:
float last_state = 0.5f; float last_state = 0.5f;
bool sensitivity_set = false; bool sensitivity_set = false;
bool deadzone_set = false; bool deadzone_set = false;
GameAPI::Analogs::AnalogType type = GameAPI::Analogs::AnalogType::Circular;
// smoothing function // smoothing function
bool smoothing = false; bool smoothing = false;
std::array<AnalogMovingAverage, ANALOG_HISTORY_CNT> vector_history; std::array<AnalogMovingAverage, ANALOG_HISTORY_CNT> vector_history = {};
int vector_history_index = 0; int vector_history_index = 0;
float smoothed_last_state = 0.f; float smoothed_last_state = 0.f;
@@ -47,14 +63,6 @@ private:
float divisor_previous_value = 0.5f; float divisor_previous_value = 0.5f;
unsigned short divisor_region = 0; unsigned short divisor_region = 0;
// relative input mode
float absolute_value_for_rel_mode = 0.5f;
bool relative_mode = false;
// circular buffer (delayed input)
int delay_buffer_depth = 0;
std::queue<float> delay_buffer;
float calculateAngularDifference(float old_rads, float new_rads); float calculateAngularDifference(float old_rads, float new_rads);
float normalizeAngle(float rads); float normalizeAngle(float rads);
float normalizeAnalogValue(float value); float normalizeAnalogValue(float value);
@@ -66,7 +74,8 @@ public:
float override_state = 0.5f; float override_state = 0.5f;
explicit Analog(std::string name) : name(std::move(name)) { explicit Analog(std::string name) : name(std::move(name)) {
vector_history.fill({0.0, 0.f, 0.f}); };
explicit Analog(std::string name, GameAPI::Analogs::AnalogType type) : name(std::move(name)), type(type) {
}; };
std::string getDisplayString(rawinput::RawInputManager* manager); std::string getDisplayString(rawinput::RawInputManager* manager);
@@ -89,8 +98,6 @@ public:
smoothing = false; smoothing = false;
deadzone_mirror = false; deadzone_mirror = false;
setMultiplier(1); setMultiplier(1);
setRelativeMode(false);
setDelayBufferDepth(0);
setLastState(0.5f); setLastState(0.5f);
} }
@@ -188,30 +195,11 @@ public:
this->last_state = last_state; this->last_state = last_state;
} }
inline bool isRelativeMode() const { inline GameAPI::Analogs::AnalogType getType() const {
return this->relative_mode; return this->type;
} }
inline void setRelativeMode(bool relative_mode) { inline void setType(GameAPI::Analogs::AnalogType type) {
this->relative_mode = relative_mode; this->type = type;
this->absolute_value_for_rel_mode = 0.5f;
}
inline float getAbsoluteValue(float relative_delta) {
this->absolute_value_for_rel_mode =
normalizeAnalogValue(this->absolute_value_for_rel_mode + relative_delta);
return this->absolute_value_for_rel_mode;
}
inline int getDelayBufferDepth() const {
return this->delay_buffer_depth;
}
inline void setDelayBufferDepth(int depth) {
this->delay_buffer_depth = depth;
}
inline std::queue<float> &getDelayBuffer() {
return this->delay_buffer;
} }
}; };
+135 -50
View File
@@ -1,5 +1,6 @@
#include "api.h" #include "api.h"
#include <cassert>
#include <optional> #include <optional>
#include "launcher/superexit.h" #include "launcher/superexit.h"
@@ -198,9 +199,19 @@ GameAPI::Buttons::State GameAPI::Buttons::getState(rawinput::RawInputManager *ma
if (vKey < value_states->size()) { if (vKey < value_states->size()) {
auto value = value_states->at(vKey); auto value = value_states->at(vKey);
if (current_button->getAnalogType() == BAT_POSITIVE) { if (current_button->getAnalogType() == BAT_POSITIVE) {
state = value > 0.6f ? BUTTON_PRESSED : BUTTON_NOT_PRESSED; float threshold = 0.6f;
if (current_button->getBatThreshold() > 0) {
threshold = std::clamp(current_button->getBatThreshold() / 100.f, 0.01f, 0.99f);
}
state = value > threshold ? BUTTON_PRESSED : BUTTON_NOT_PRESSED;
} else if (current_button->getAnalogType() == BAT_NEGATIVE) { } else if (current_button->getAnalogType() == BAT_NEGATIVE) {
state = value < 0.4f ? BUTTON_PRESSED : BUTTON_NOT_PRESSED; float threshold = 0.4f;
if (current_button->getBatThreshold() > 0) {
threshold = std::clamp((100 - current_button->getBatThreshold()) / 100.f, 0.01f, 0.99f);
}
state = value < threshold ? BUTTON_PRESSED : BUTTON_NOT_PRESSED;
} else { } else {
state = value > 0.01f ? BUTTON_PRESSED : BUTTON_NOT_PRESSED; state = value > 0.01f ? BUTTON_PRESSED : BUTTON_NOT_PRESSED;
} }
@@ -389,6 +400,16 @@ GameAPI::Buttons::State GameAPI::Buttons::getState(rawinput::RawInputManager *ma
} }
case rawinput::PIUIO_DEVICE: { case rawinput::PIUIO_DEVICE: {
state = device->piuioDev->IsPressed(vKey) ? BUTTON_PRESSED : BUTTON_NOT_PRESSED; 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: default:
break; break;
@@ -586,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; float value = 0.5f;
if (!device) { if (!device) {
return value; return value;
@@ -658,32 +679,12 @@ float GameAPI::Analogs::getState(rawinput::Device *device, Analog &analog) {
} }
// deadzone // deadzone
if (analog.isDeadzoneSet()) { // do not apply deadzone to circular analogs since it doesn't make sense
if (analog.isDeadzoneSet() && analog.getType() != AnalogType::Circular) {
value = analog.applyDeadzone(value); value = analog.applyDeadzone(value);
} }
if (analog.isRelativeMode()) { if (analog.getType() == AnalogType::Circular) {
float relative_delta = value - 0.5f;
// built-in scaling to make values reasonable
relative_delta /= 80.f;
// integer multiplier/divisor
const auto mult = analog.getMultiplier();
if (mult < -1) {
relative_delta /= -mult;
} else if (1 < mult) {
relative_delta *= mult;
}
// sensitivity (ranges from 0.0 to 4.0)
if (analog.isSensitivitySet()) {
relative_delta *= analog.getSensitivity();
}
// translate relative movement to absolute value
value = analog.getAbsoluteValue(relative_delta);
} else {
// integer multiplier // integer multiplier
value = analog.applyMultiplier(value); value = analog.applyMultiplier(value);
@@ -711,30 +712,54 @@ float GameAPI::Analogs::getState(rawinput::Device *device, Analog &analog) {
// apply to value // apply to value
value = rads * (float) M_1_TAU; value = rads * (float) M_1_TAU;
} }
}
// delay
if (0 < analog.getDelayBufferDepth()) {
auto& queue = analog.getDelayBuffer();
// ensure the queue isn't too long; drop old values
while (analog.getDelayBufferDepth() <= (int)queue.size()) {
queue.pop();
}
// always push new value
queue.push(value);
// get a new value to return
if ((int)queue.size() < analog.getDelayBufferDepth()) {
// not enough in the queue, stall for now, shouldn't happen often
value = analog.getLastState();
} else { } else {
value = queue.front(); // sensitivity
queue.pop(); if (analog.isSensitivitySet()) {
// adjust curve
// values < 1.f : less sensitive around neutral
// values > 1.f : more sensitive around neutral
float curve = analog.getSensitivity();
if (curve <= 0.f) {
curve = 0.01f;
} }
curve = 1.f / curve;
if (analog.getType() == AnalogType::LinearCentered) {
// convert 0.0..1.0 to -1.0..+1.0
float signed_raw = (value - 0.5f) * 2.0f;
// apply curve
float sign = signed_raw < 0.0f ? -1.0f : 1.0f;
float magnitude = fabsf(signed_raw);
float curved = sign * powf(magnitude, curve);
// convert back to 0.0..1.0
value = curved * 0.5f + 0.5f;
} else {
value = powf(value, curve);
} }
value = std::clamp(value, 0.f, 1.f);
}
// multiplier / divisor
if (analog.getMultiplier() < -1) {
if (analog.getType() == AnalogType::LinearCentered) {
value = (value - 0.5f) / (-analog.getMultiplier()) + 0.5f;
} else {
value /= -analog.getMultiplier();
}
value = std::clamp(value, 0.f, 1.f);
} else if (analog.getMultiplier() > 1) {
if (analog.getType() == AnalogType::LinearCentered) {
value = (value - 0.5f) * analog.getMultiplier() + 0.5f;
} else {
value *= analog.getMultiplier();
}
value = std::clamp(value, 0.f, 1.f);
}
}
break; break;
} }
case rawinput::MIDI: { case rawinput::MIDI: {
@@ -767,6 +792,18 @@ float GameAPI::Analogs::getState(rawinput::Device *device, Analog &analog) {
} }
break; 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: default:
break; break;
} }
@@ -806,6 +843,42 @@ std::vector<Analog> GameAPI::Analogs::sortAnalogs(
return sorted; return sorted;
} }
static std::vector<Analog> sortAnalogsWithTypeInternal(
std::vector<Analog> &analogs,
const std::initializer_list<GameAPI::Analogs::AnalogWithType> list) {
std::vector<Analog> sorted;
bool analog_found;
for (auto &a : list) {
analog_found = false;
for (auto &analog : analogs) {
if (a.name == analog.getName()) {
analog_found = true;
analog.setType(a.type);
sorted.push_back(analog);
break;
}
}
if (!analog_found) {
sorted.emplace_back(a.name, a.type);
}
}
return sorted;
}
void GameAPI::Analogs::sortAnalogsWithType(
std::vector<Analog> *analogs,
const std::initializer_list<AnalogWithType> list) {
if (analogs) {
*analogs = sortAnalogsWithTypeInternal(*analogs, list);
}
}
float GameAPI::Analogs::getState(rawinput::RawInputManager *manager, Analog &analog) { float GameAPI::Analogs::getState(rawinput::RawInputManager *manager, Analog &analog) {
// check override // check override
@@ -822,7 +895,7 @@ float GameAPI::Analogs::getState(rawinput::RawInputManager *manager, Analog &ana
return analog.getLastState(); return analog.getLastState();
} }
float state = getState(device, analog); float state = getState(manager, device, analog);
analog.setLastState(state); analog.setLastState(state);
return state; return state;
@@ -904,7 +977,7 @@ void GameAPI::Lights::sortLightsWithCategory(
} }
void GameAPI::Lights::writeLight(rawinput::Device *device, int index, float value) { void GameAPI::Lights::writeLight(rawinput::RawInputManager *manager, rawinput::Device *device, int index, float value) {
// check device // check device
if (!device) { if (!device) {
@@ -991,6 +1064,18 @@ void GameAPI::Lights::writeLight(rawinput::Device *device, int index, float valu
} }
break; 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: default:
break; break;
} }
@@ -1016,9 +1101,9 @@ void GameAPI::Lights::writeLight(rawinput::RawInputManager *manager, Light &ligh
// write state // write state
if (light.override_enabled) { if (light.override_enabled) {
writeLight(device, light.getIndex(), light.override_state); writeLight(manager, device, light.getIndex(), light.override_state);
} else { } else {
writeLight(device, light.getIndex(), value); writeLight(manager, device, light.getIndex(), value);
} }
} }
+10 -9
View File
@@ -85,14 +85,15 @@ namespace GameAPI {
const std::vector<Analog> &analogs, const std::vector<Analog> &analogs,
const std::vector<std::string> &analog_names); const std::vector<std::string> &analog_names);
template<typename T> struct AnalogWithType {
void sortAnalogs(std::vector<Analog> *analogs, T t) { const std::string name;
const std::vector<std::string> analog_names { t }; const AnalogType type;
if (analogs) { AnalogWithType(std::string name, AnalogType type) :
*analogs = GameAPI::Analogs::sortAnalogs(*analogs, analog_names); name(std::move(name)), type(type) {}
} };
}
void sortAnalogsWithType(std::vector<Analog> *analogs, const std::initializer_list<AnalogWithType> list);
template<typename T, typename... Rest> template<typename T, typename... Rest>
void sortAnalogs(std::vector<Analog> *analogs, T t, Rest... rest) { void sortAnalogs(std::vector<Analog> *analogs, T t, Rest... rest) {
@@ -103,7 +104,7 @@ 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(rawinput::RawInputManager *manager, Analog &analog);
float getState(std::unique_ptr<rawinput::RawInputManager> &manager, Analog &analog); float getState(std::unique_ptr<rawinput::RawInputManager> &manager, Analog &analog);
} }
@@ -135,7 +136,7 @@ namespace GameAPI {
void sortLightsWithCategory(std::vector<Light> *lights, const std::initializer_list<LightAndCategory> list); void sortLightsWithCategory(std::vector<Light> *lights, const std::initializer_list<LightAndCategory> list);
void writeLight(rawinput::Device *device, int index, float value); void writeLight(rawinput::RawInputManager *manager, rawinput::Device *device, int index, float value);
void writeLight(rawinput::RawInputManager *manager, Light &light, float value); void writeLight(rawinput::RawInputManager *manager, Light &light, float value);
void writeLight(std::unique_ptr<rawinput::RawInputManager> &manager, Light &light, float value); void writeLight(std::unique_ptr<rawinput::RawInputManager> &manager, Light &light, float value);
+10 -1
View File
@@ -278,6 +278,8 @@ std::string Button::getVKeyString() {
return "Left Alt"; return "Left Alt";
case 0xA5: case 0xA5:
return "Right Alt"; return "Right Alt";
case INVALID_VKEY:
return "Invalid";
default: default:
// check win API // check win API
@@ -304,7 +306,10 @@ std::string Button::getDisplayString(rawinput::RawInputManager* manager) {
std::string vKeyString = fmt::format("{:#x}", vKey); std::string vKeyString = fmt::format("{:#x}", vKey);
// device must be existing // device must be existing
if (this->device_identifier.empty() && vKey == INVALID_VKEY) { if (this->isNaive() && vKey == INVALID_VKEY) {
if (this->getInvert()) {
return "(always on)";
}
return ""; return "";
} }
@@ -415,6 +420,10 @@ std::string Button::getDisplayString(rawinput::RawInputManager* manager) {
} }
case rawinput::PIUIO_DEVICE: case rawinput::PIUIO_DEVICE:
return "PIUIO " + vKeyString; return "PIUIO " + vKeyString;
case rawinput::XINPUT_GAMEPAD:
return fmt::format("{} ({})",
xinput::get_button_string(static_cast<xinput::XInputButtonEnum>(vKey)),
device->desc);
case rawinput::DESTROYED: case rawinput::DESTROYED:
return "Device unplugged (" + vKeyString + ")"; return "Device unplugged (" + vKeyString + ")";
default: default:
+19 -1
View File
@@ -56,6 +56,7 @@ private:
ButtonAnalogType analog_type = BAT_NONE; ButtonAnalogType analog_type = BAT_NONE;
double debounce_up = 0.0; double debounce_up = 0.0;
double debounce_down = 0.0; double debounce_down = 0.0;
int bat_threshold = 0; // for positive/negative only, value in percentage, zero is default
bool invert = false; bool invert = false;
bool is_temporary = false; bool is_temporary = false;
@@ -83,12 +84,20 @@ public:
if (this->override_enabled) { if (this->override_enabled) {
return true; return true;
} }
// has a device
if (!this->device_identifier.empty()) {
return true;
}
// naive, and has a valid vKey
if (this->vKey != INVALID_VKEY) { if (this->vKey != INVALID_VKEY) {
return true; return true;
} }
for (auto &alternative : this->alternatives) { for (auto &alternative : this->alternatives) {
if (alternative.vKey != INVALID_VKEY) { if (!alternative.getDeviceIdentifier().empty() ||
alternative.getVKey() != INVALID_VKEY) {
return true; return true;
} }
} }
@@ -101,6 +110,7 @@ public:
alternatives.clear(); alternatives.clear();
device_identifier = ""; device_identifier = "";
analog_type = BAT_NONE; analog_type = BAT_NONE;
bat_threshold = 0;
} }
std::string getDisplayString(rawinput::RawInputManager* manager); std::string getDisplayString(rawinput::RawInputManager* manager);
@@ -161,6 +171,14 @@ public:
this->debounce_down = debounce_time_down; this->debounce_down = debounce_time_down;
} }
inline void setBatThreshold(int bat_threshold) {
this->bat_threshold = bat_threshold;
}
inline int getBatThreshold() const {
return this->bat_threshold;
}
inline bool getInvert() const { inline bool getInvert() const {
return this->invert; return this->invert;
} }
+16 -26
View File
@@ -28,7 +28,7 @@ Config::Config() {
this->status = false; this->status = false;
if (CONFIG_PATH_OVERRIDE.length() > 0) { if (CONFIG_PATH_OVERRIDE.length() > 0) {
this->configLocation = CONFIG_PATH_OVERRIDE; this->configLocation = CONFIG_PATH_OVERRIDE;
log_info("cfg", "using custom config file: {}", this->configLocation.string()); log_info("cfg", "using custom config file: {}", this->configLocation);
} else { } else {
this->configLocation = std::filesystem::path(_wgetenv(L"APPDATA")) / L"spicetools.xml"; this->configLocation = std::filesystem::path(_wgetenv(L"APPDATA")) / L"spicetools.xml";
// avoids logging the expanded appdata path as it contains user name // avoids logging the expanded appdata path as it contains user name
@@ -57,7 +57,7 @@ Config::Config() {
this->firstFillConfigFile(); this->firstFillConfigFile();
break; break;
case tinyxml2::XMLError::XML_ERROR_FILE_COULD_NOT_BE_OPENED: case tinyxml2::XMLError::XML_ERROR_FILE_COULD_NOT_BE_OPENED:
log_fatal("cfg", "could not open config file: {}", this->configLocation.string()); log_fatal("cfg", "could not open config file: {}", this->configLocation);
break; break;
case tinyxml2::XMLError::XML_ERROR_FILE_NOT_FOUND: case tinyxml2::XMLError::XML_ERROR_FILE_NOT_FOUND:
this->createConfigFile(); this->createConfigFile();
@@ -72,7 +72,7 @@ Config::Config() {
case tinyxml2::XMLError::XML_ERROR_PARSING_UNKNOWN: case tinyxml2::XMLError::XML_ERROR_PARSING_UNKNOWN:
case tinyxml2::XMLError::XML_ERROR_MISMATCHED_ELEMENT: case tinyxml2::XMLError::XML_ERROR_MISMATCHED_ELEMENT:
case tinyxml2::XMLError::XML_ERROR_PARSING: case tinyxml2::XMLError::XML_ERROR_PARSING:
log_warning("cfg", "Couldn't read config file: {}", this->configLocation.string()); log_warning("cfg", "Couldn't read config file: {}", this->configLocation);
this->createConfigFile(); this->createConfigFile();
this->firstFillConfigFile(); this->firstFillConfigFile();
break; break;
@@ -162,6 +162,7 @@ bool Config::addGame(Game &game) {
auto analogType = (int) BAT_NONE; auto analogType = (int) BAT_NONE;
double debounce_up = 0.0; double debounce_up = 0.0;
double debounce_down = 0.0; double debounce_down = 0.0;
int bat_threshold = 0;
int velocity_threshold = 0; int velocity_threshold = 0;
bool invert = false; bool invert = false;
tinyxml2::XMLError attrError = gameButtonNode->QueryIntAttribute("vkey", &vKey); tinyxml2::XMLError attrError = gameButtonNode->QueryIntAttribute("vkey", &vKey);
@@ -169,6 +170,7 @@ bool Config::addGame(Game &game) {
gameButtonNode->QueryIntAttribute("analogtype", &analogType); gameButtonNode->QueryIntAttribute("analogtype", &analogType);
gameButtonNode->QueryDoubleAttribute("debounce_up", &debounce_up); gameButtonNode->QueryDoubleAttribute("debounce_up", &debounce_up);
gameButtonNode->QueryDoubleAttribute("debounce_down", &debounce_down); gameButtonNode->QueryDoubleAttribute("debounce_down", &debounce_down);
gameButtonNode->QueryIntAttribute("bat_threshold", &bat_threshold);
gameButtonNode->QueryIntAttribute("velocity_threshold", &velocity_threshold); gameButtonNode->QueryIntAttribute("velocity_threshold", &velocity_threshold);
gameButtonNode->QueryBoolAttribute("invert", &invert); gameButtonNode->QueryBoolAttribute("invert", &invert);
if (attrError != tinyxml2::XMLError::XML_SUCCESS) { if (attrError != tinyxml2::XMLError::XML_SUCCESS) {
@@ -180,6 +182,7 @@ bool Config::addGame(Game &game) {
gameButtonNode->SetAttribute("devid", button->getDeviceIdentifier().c_str()); gameButtonNode->SetAttribute("devid", button->getDeviceIdentifier().c_str());
gameButtonNode->SetAttribute("debounce_up", debounce_up); gameButtonNode->SetAttribute("debounce_up", debounce_up);
gameButtonNode->SetAttribute("debounce_down", debounce_down); gameButtonNode->SetAttribute("debounce_down", debounce_down);
gameButtonNode->SetAttribute("bat_threshold", bat_threshold);
gameButtonNode->SetAttribute("velocity_threshold", velocity_threshold); gameButtonNode->SetAttribute("velocity_threshold", velocity_threshold);
gameButtonNode->SetAttribute("invert", invert); gameButtonNode->SetAttribute("invert", invert);
gameButtonsNode->InsertEndChild(gameButtonNode); gameButtonsNode->InsertEndChild(gameButtonNode);
@@ -188,6 +191,7 @@ bool Config::addGame(Game &game) {
button->setAnalogType((ButtonAnalogType) analogType); button->setAnalogType((ButtonAnalogType) analogType);
button->setDebounceUp(debounce_up); button->setDebounceUp(debounce_up);
button->setDebounceDown(debounce_down); button->setDebounceDown(debounce_down);
button->setBatThreshold(bat_threshold);
button->setVelocityThreshold(velocity_threshold); button->setVelocityThreshold(velocity_threshold);
button->setInvert(invert); button->setInvert(invert);
if (devid) { if (devid) {
@@ -207,6 +211,7 @@ bool Config::addGame(Game &game) {
gameButtonNode->SetAttribute("analogtype", (int) it.getAnalogType()); gameButtonNode->SetAttribute("analogtype", (int) it.getAnalogType());
gameButtonNode->SetAttribute("debounce_up", it.getDebounceUp()); gameButtonNode->SetAttribute("debounce_up", it.getDebounceUp());
gameButtonNode->SetAttribute("debounce_down", it.getDebounceDown()); gameButtonNode->SetAttribute("debounce_down", it.getDebounceDown());
gameButtonNode->SetAttribute("bat_threshold", it.getBatThreshold());
gameButtonNode->SetAttribute("velocity_threshold", it.getVelocityThreshold()); gameButtonNode->SetAttribute("velocity_threshold", it.getVelocityThreshold());
gameButtonNode->SetAttribute("invert", it.getInvert()); gameButtonNode->SetAttribute("invert", it.getInvert());
gameButtonNode->SetAttribute("devid", it.getDeviceIdentifier().c_str()); gameButtonNode->SetAttribute("devid", it.getDeviceIdentifier().c_str());
@@ -247,8 +252,6 @@ bool Config::addGame(Game &game) {
bool invert = false; bool invert = false;
bool smoothing = false; bool smoothing = false;
int multiplier = 1; int multiplier = 1;
bool relative_mode = false;
int delay_buffer_depth = 0;
tinyxml2::XMLError err1 = gameAnalogNode->QueryIntAttribute("index", &index); tinyxml2::XMLError err1 = gameAnalogNode->QueryIntAttribute("index", &index);
gameAnalogNode->QueryFloatAttribute("sensivity", &sensitivity); gameAnalogNode->QueryFloatAttribute("sensivity", &sensitivity);
gameAnalogNode->QueryFloatAttribute("deadzone", &deadzone); gameAnalogNode->QueryFloatAttribute("deadzone", &deadzone);
@@ -256,8 +259,6 @@ bool Config::addGame(Game &game) {
gameAnalogNode->QueryBoolAttribute("invert", &invert); gameAnalogNode->QueryBoolAttribute("invert", &invert);
gameAnalogNode->QueryBoolAttribute("smoothing", &smoothing); gameAnalogNode->QueryBoolAttribute("smoothing", &smoothing);
gameAnalogNode->QueryIntAttribute("multiplier", &multiplier); gameAnalogNode->QueryIntAttribute("multiplier", &multiplier);
gameAnalogNode->QueryBoolAttribute("relative", &relative_mode);
gameAnalogNode->QueryIntAttribute("delay", &delay_buffer_depth);
const char *devid = gameAnalogNode->Attribute("devid"); const char *devid = gameAnalogNode->Attribute("devid");
if (err1 != tinyxml2::XMLError::XML_SUCCESS || !devid) { if (err1 != tinyxml2::XMLError::XML_SUCCESS || !devid) {
@@ -272,8 +273,6 @@ bool Config::addGame(Game &game) {
gameAnalogNode->SetAttribute("invert", it.getInvert()); gameAnalogNode->SetAttribute("invert", it.getInvert());
gameAnalogNode->SetAttribute("smoothing", it.getSmoothing()); gameAnalogNode->SetAttribute("smoothing", it.getSmoothing());
gameAnalogNode->SetAttribute("multiplier", it.getMultiplier()); gameAnalogNode->SetAttribute("multiplier", it.getMultiplier());
gameAnalogNode->SetAttribute("relative", it.isRelativeMode());
gameAnalogNode->SetAttribute("delay", it.getDelayBufferDepth());
gameAnalogsNode->InsertEndChild(gameAnalogNode); gameAnalogsNode->InsertEndChild(gameAnalogNode);
} else { } else {
it.setIndex(static_cast<unsigned short int>(index)); it.setIndex(static_cast<unsigned short int>(index));
@@ -284,8 +283,6 @@ bool Config::addGame(Game &game) {
it.setInvert(invert); it.setInvert(invert);
it.setSmoothing(smoothing); it.setSmoothing(smoothing);
it.setMultiplier(multiplier); it.setMultiplier(multiplier);
it.setRelativeMode(relative_mode);
it.setDelayBufferDepth(delay_buffer_depth);
} }
} else { } else {
gameAnalogNode = this->configFile.NewElement("analog"); gameAnalogNode = this->configFile.NewElement("analog");
@@ -297,8 +294,6 @@ bool Config::addGame(Game &game) {
gameAnalogNode->SetAttribute("invert", it.getInvert()); gameAnalogNode->SetAttribute("invert", it.getInvert());
gameAnalogNode->SetAttribute("smoothing", it.getSmoothing()); gameAnalogNode->SetAttribute("smoothing", it.getSmoothing());
gameAnalogNode->SetAttribute("multiplier", it.getMultiplier()); gameAnalogNode->SetAttribute("multiplier", it.getMultiplier());
gameAnalogNode->SetAttribute("relative", it.isRelativeMode());
gameAnalogNode->SetAttribute("delay", it.getDelayBufferDepth());
gameAnalogNode->SetAttribute("devid", it.getDeviceIdentifier().c_str()); gameAnalogNode->SetAttribute("devid", it.getDeviceIdentifier().c_str());
gameAnalogsNode->InsertEndChild(gameAnalogNode); gameAnalogsNode->InsertEndChild(gameAnalogNode);
} }
@@ -432,6 +427,7 @@ bool Config::addGame(Game &game) {
gameButtonNode->SetAttribute("analogtype", it.getAnalogType()); gameButtonNode->SetAttribute("analogtype", it.getAnalogType());
gameButtonNode->SetAttribute("debounce_up", it.getDebounceUp()); gameButtonNode->SetAttribute("debounce_up", it.getDebounceUp());
gameButtonNode->SetAttribute("debounce_down", it.getDebounceDown()); gameButtonNode->SetAttribute("debounce_down", it.getDebounceDown());
gameButtonNode->SetAttribute("bat_threshold", it.getBatThreshold());
gameButtonNode->SetAttribute("velocity_threshold", it.getVelocityThreshold()); gameButtonNode->SetAttribute("velocity_threshold", it.getVelocityThreshold());
gameButtonNode->SetAttribute("invert", it.getInvert()); gameButtonNode->SetAttribute("invert", it.getInvert());
gameButtonNode->SetAttribute("devid", it.getDeviceIdentifier().c_str()); gameButtonNode->SetAttribute("devid", it.getDeviceIdentifier().c_str());
@@ -451,8 +447,6 @@ bool Config::addGame(Game &game) {
gameAnalogNode->SetAttribute("invert", it.getInvert()); gameAnalogNode->SetAttribute("invert", it.getInvert());
gameAnalogNode->SetAttribute("smoothing", it.getSmoothing()); gameAnalogNode->SetAttribute("smoothing", it.getSmoothing());
gameAnalogNode->SetAttribute("multiplier", it.getMultiplier()); gameAnalogNode->SetAttribute("multiplier", it.getMultiplier());
gameAnalogNode->SetAttribute("relative", it.isRelativeMode());
gameAnalogNode->SetAttribute("delay", it.getDelayBufferDepth());
gameAnalogsNode->InsertEndChild(gameAnalogNode); gameAnalogsNode->InsertEndChild(gameAnalogNode);
} }
@@ -535,6 +529,7 @@ bool Config::updateBinding(const Game &game, const Button &button, int alternati
gameButtonNode->SetAttribute("analogtype", (int) button.getAnalogType()); gameButtonNode->SetAttribute("analogtype", (int) button.getAnalogType());
gameButtonNode->SetAttribute("debounce_up", button.getDebounceUp()); gameButtonNode->SetAttribute("debounce_up", button.getDebounceUp());
gameButtonNode->SetAttribute("debounce_down", button.getDebounceDown()); gameButtonNode->SetAttribute("debounce_down", button.getDebounceDown());
gameButtonNode->SetAttribute("bat_threshold", button.getBatThreshold());
gameButtonNode->SetAttribute("velocity_threshold", button.getVelocityThreshold()); gameButtonNode->SetAttribute("velocity_threshold", button.getVelocityThreshold());
gameButtonNode->SetAttribute("invert", button.getInvert()); gameButtonNode->SetAttribute("invert", button.getInvert());
gameButtonNode->SetAttribute("devid", button.getDeviceIdentifier().c_str()); gameButtonNode->SetAttribute("devid", button.getDeviceIdentifier().c_str());
@@ -551,6 +546,7 @@ bool Config::updateBinding(const Game &game, const Button &button, int alternati
gameButtonNode->SetAttribute("analogtype", 0); gameButtonNode->SetAttribute("analogtype", 0);
gameButtonNode->SetAttribute("debounce_up", 0.0); gameButtonNode->SetAttribute("debounce_up", 0.0);
gameButtonNode->SetAttribute("debounce_down", 0.0); gameButtonNode->SetAttribute("debounce_down", 0.0);
gameButtonNode->SetAttribute("bat_threshold", 0);
gameButtonNode->SetAttribute("velocity_threshold", 0); gameButtonNode->SetAttribute("velocity_threshold", 0);
gameButtonNode->SetAttribute("invert", false); gameButtonNode->SetAttribute("invert", false);
gameButtonNode->SetAttribute("devid", ""); gameButtonNode->SetAttribute("devid", "");
@@ -683,8 +679,6 @@ bool Config::updateBinding(const Game &game, const Analog &analog) {
gameAnalogNode->SetAttribute("invert", analog.getInvert()); gameAnalogNode->SetAttribute("invert", analog.getInvert());
gameAnalogNode->SetAttribute("smoothing", analog.getSmoothing()); gameAnalogNode->SetAttribute("smoothing", analog.getSmoothing());
gameAnalogNode->SetAttribute("multiplier", analog.getMultiplier()); gameAnalogNode->SetAttribute("multiplier", analog.getMultiplier());
gameAnalogNode->SetAttribute("relative", analog.isRelativeMode());
gameAnalogNode->SetAttribute("delay", analog.getDelayBufferDepth());
gameAnalogNode->SetAttribute("devid", analog.getDeviceIdentifier().c_str()); gameAnalogNode->SetAttribute("devid", analog.getDeviceIdentifier().c_str());
} else { } else {
gameAnalogNode = this->configFile.NewElement("analog"); gameAnalogNode = this->configFile.NewElement("analog");
@@ -695,8 +689,6 @@ bool Config::updateBinding(const Game &game, const Analog &analog) {
gameAnalogNode->SetAttribute("invert", analog.getInvert()); gameAnalogNode->SetAttribute("invert", analog.getInvert());
gameAnalogNode->SetAttribute("smoothing", analog.getSmoothing()); gameAnalogNode->SetAttribute("smoothing", analog.getSmoothing());
gameAnalogNode->SetAttribute("multiplier", analog.getMultiplier()); gameAnalogNode->SetAttribute("multiplier", analog.getMultiplier());
gameAnalogNode->SetAttribute("relative", analog.isRelativeMode());
gameAnalogNode->SetAttribute("delay", analog.getDelayBufferDepth());
gameAnalogNode->SetAttribute("devid", analog.getDeviceIdentifier().c_str()); gameAnalogNode->SetAttribute("devid", analog.getDeviceIdentifier().c_str());
gameAnalogsNode->InsertEndChild(gameAnalogNode); gameAnalogsNode->InsertEndChild(gameAnalogNode);
} }
@@ -946,12 +938,14 @@ std::vector<Button> Config::getButtons(const std::string &gameName) {
auto analogType = (int) BAT_NONE; auto analogType = (int) BAT_NONE;
double debounce_up = 0.0; double debounce_up = 0.0;
double debounce_down = 0.0; double debounce_down = 0.0;
int bat_threshold = 0;
int velocity_threshold = 0; int velocity_threshold = 0;
bool invert = false; bool invert = false;
gameButtonNode->QueryIntAttribute("vkey", &vKey); gameButtonNode->QueryIntAttribute("vkey", &vKey);
gameButtonNode->QueryIntAttribute("analogtype", &analogType); gameButtonNode->QueryIntAttribute("analogtype", &analogType);
gameButtonNode->QueryDoubleAttribute("debounce_up", &debounce_up); gameButtonNode->QueryDoubleAttribute("debounce_up", &debounce_up);
gameButtonNode->QueryDoubleAttribute("debounce_down", &debounce_down); gameButtonNode->QueryDoubleAttribute("debounce_down", &debounce_down);
gameButtonNode->QueryIntAttribute("bat_threshold", &bat_threshold);
gameButtonNode->QueryIntAttribute("velocity_threshold", &velocity_threshold); gameButtonNode->QueryIntAttribute("velocity_threshold", &velocity_threshold);
gameButtonNode->QueryBoolAttribute("invert", &invert); gameButtonNode->QueryBoolAttribute("invert", &invert);
const char *devid = gameButtonNode->Attribute("devid"); const char *devid = gameButtonNode->Attribute("devid");
@@ -966,6 +960,7 @@ std::vector<Button> Config::getButtons(const std::string &gameName) {
alt.setAnalogType((ButtonAnalogType) analogType); alt.setAnalogType((ButtonAnalogType) analogType);
alt.setDebounceUp(debounce_up); alt.setDebounceUp(debounce_up);
alt.setDebounceDown(debounce_down); alt.setDebounceDown(debounce_down);
alt.setBatThreshold(bat_threshold);
alt.setVelocityThreshold(velocity_threshold); alt.setVelocityThreshold(velocity_threshold);
alt.setInvert(invert); alt.setInvert(invert);
if (devid) { if (devid) {
@@ -984,6 +979,7 @@ std::vector<Button> Config::getButtons(const std::string &gameName) {
button.setAnalogType((ButtonAnalogType) analogType); button.setAnalogType((ButtonAnalogType) analogType);
button.setDebounceUp(debounce_up); button.setDebounceUp(debounce_up);
button.setDebounceDown(debounce_down); button.setDebounceDown(debounce_down);
button.setBatThreshold(bat_threshold);
button.setVelocityThreshold(velocity_threshold); button.setVelocityThreshold(velocity_threshold);
button.setInvert(invert); button.setInvert(invert);
if (devid) { if (devid) {
@@ -1129,8 +1125,6 @@ std::vector<Analog> Config::getAnalogs(const std::string &gameName) {
bool invert = false; bool invert = false;
bool smoothing = false; bool smoothing = false;
int multiplier = 1; int multiplier = 1;
bool relative_mode = false;
int delay_buffer_depth = 0;
gameAnalogNode->QueryIntAttribute("index", &index); gameAnalogNode->QueryIntAttribute("index", &index);
gameAnalogNode->QueryFloatAttribute("sensivity", &sensitivity); gameAnalogNode->QueryFloatAttribute("sensivity", &sensitivity);
gameAnalogNode->QueryFloatAttribute("deadzone", &deadzone); gameAnalogNode->QueryFloatAttribute("deadzone", &deadzone);
@@ -1138,8 +1132,6 @@ std::vector<Analog> Config::getAnalogs(const std::string &gameName) {
gameAnalogNode->QueryBoolAttribute("invert", &invert); gameAnalogNode->QueryBoolAttribute("invert", &invert);
gameAnalogNode->QueryBoolAttribute("smoothing", &smoothing); gameAnalogNode->QueryBoolAttribute("smoothing", &smoothing);
gameAnalogNode->QueryIntAttribute("multiplier", &multiplier); gameAnalogNode->QueryIntAttribute("multiplier", &multiplier);
gameAnalogNode->QueryBoolAttribute("relative", &relative_mode);
gameAnalogNode->QueryIntAttribute("delay", &delay_buffer_depth);
const char *devid = gameAnalogNode->Attribute("devid"); const char *devid = gameAnalogNode->Attribute("devid");
// create analog and add to list // create analog and add to list
@@ -1151,8 +1143,6 @@ std::vector<Analog> Config::getAnalogs(const std::string &gameName) {
analog.setInvert(invert); analog.setInvert(invert);
analog.setSmoothing(smoothing); analog.setSmoothing(smoothing);
analog.setMultiplier(multiplier); analog.setMultiplier(multiplier);
analog.setRelativeMode(relative_mode);
analog.setDelayBufferDepth(delay_buffer_depth);
if (devid) { if (devid) {
analog.setDeviceIdentifier(devid); analog.setDeviceIdentifier(devid);
} }
@@ -1315,7 +1305,7 @@ void Config::saveConfigFile() {
// create a .tmp file and write to it... // create a .tmp file and write to it...
const auto xml_result = this->configFile.SaveFile(this->configLocationTemp.c_str(), false); const auto xml_result = this->configFile.SaveFile(this->configLocationTemp.c_str(), false);
if (xml_result != tinyxml2::XMLError::XML_SUCCESS) { if (xml_result != tinyxml2::XMLError::XML_SUCCESS) {
log_info("cfg", "failed to write file: {}", this->configLocationTemp.string()); log_info("cfg", "failed to write file: {}", this->configLocationTemp);
return; return;
} }
// copy the .tmp file to the main file... // copy the .tmp file to the main file...
+5 -4
View File
@@ -24,6 +24,7 @@ namespace overlay::windows {
el->SetAttribute("invert", entry.invert); el->SetAttribute("invert", entry.invert);
el->SetAttribute("debounce_up", entry.debounce_up); el->SetAttribute("debounce_up", entry.debounce_up);
el->SetAttribute("debounce_down", entry.debounce_down); el->SetAttribute("debounce_down", entry.debounce_down);
el->SetAttribute("bat_threshold", entry.bat_threshold);
el->SetAttribute("velocity_threshold", entry.velocity_threshold); el->SetAttribute("velocity_threshold", entry.velocity_threshold);
parent->InsertEndChild(el); parent->InsertEndChild(el);
} }
@@ -49,6 +50,10 @@ namespace overlay::windows {
el->QueryIntAttribute("velocity_threshold", &vel); el->QueryIntAttribute("velocity_threshold", &vel);
entry.velocity_threshold = (unsigned short)vel; entry.velocity_threshold = (unsigned short)vel;
int bat = 0;
el->QueryIntAttribute("bat_threshold", &bat);
entry.bat_threshold = bat;
return entry; return entry;
} }
@@ -64,8 +69,6 @@ namespace overlay::windows {
el->SetAttribute("invert", analog.invert); el->SetAttribute("invert", analog.invert);
el->SetAttribute("smoothing", analog.smoothing); el->SetAttribute("smoothing", analog.smoothing);
el->SetAttribute("multiplier", analog.multiplier); el->SetAttribute("multiplier", analog.multiplier);
el->SetAttribute("relative", analog.relative_mode);
el->SetAttribute("delay", analog.delay_buffer_depth);
parent->InsertEndChild(el); parent->InsertEndChild(el);
} }
@@ -87,8 +90,6 @@ namespace overlay::windows {
el->QueryBoolAttribute("invert", &a.invert); el->QueryBoolAttribute("invert", &a.invert);
el->QueryBoolAttribute("smoothing", &a.smoothing); el->QueryBoolAttribute("smoothing", &a.smoothing);
el->QueryIntAttribute("multiplier", &a.multiplier); el->QueryIntAttribute("multiplier", &a.multiplier);
el->QueryBoolAttribute("relative", &a.relative_mode);
el->QueryIntAttribute("delay", &a.delay_buffer_depth);
return a; return a;
} }
+9
View File
@@ -85,6 +85,15 @@ std::string Light::getDisplayString(rawinput::RawInputManager* manager) {
return "Invalid SMX Dedicab Light (" + index_string + ")"; 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: case rawinput::DESTROYED:
return "Unplugged device (" + index_string + ")"; return "Unplugged device (" + index_string + ")";
default: default:
+1
View File
@@ -20,6 +20,7 @@ enum class OptionPickerType {
CpuAffinity, CpuAffinity,
FilePath, FilePath,
DirectoryPath, DirectoryPath,
Monitor,
}; };
struct OptionDefinition { struct OptionDefinition {
+1 -1
View File
@@ -21,7 +21,7 @@ namespace cfg {
if (SCREEN_RESIZE_CFG_PATH_OVERRIDE.has_value()) { if (SCREEN_RESIZE_CFG_PATH_OVERRIDE.has_value()) {
this->config_path = SCREEN_RESIZE_CFG_PATH_OVERRIDE.value(); this->config_path = SCREEN_RESIZE_CFG_PATH_OVERRIDE.value();
if (fileutils::file_exists(this->config_path)) { if (fileutils::file_exists(this->config_path)) {
log_info("ScreenResize", "loading config from: {}", this->config_path.string()); log_info("ScreenResize", "loading config from: {}", this->config_path);
file_exists = true; file_exists = true;
} }
} else { } else {
+1
View File
@@ -60,6 +60,7 @@ namespace cfg {
// window = rectangle including the frame // window = rectangle including the frame
// client = just the content area without frames. // client = just the content area without frames.
bool window_always_on_top = false; bool window_always_on_top = false;
bool window_disable_round_corners = false;
bool client_keep_aspect_ratio = true; bool client_keep_aspect_ratio = true;
bool enable_window_resize = false; bool enable_window_resize = false;
int window_decoration = 0; // enum type WindowDecorationMode int window_decoration = 0; // enum type WindowDecorationMode
+8
View File
@@ -15,3 +15,11 @@ RUN pacman --noconfirm -Syu git \
ccache ccache
RUN useradd user -m && echo "ALL ALL=(ALL) NOPASSWD: ALL" >> /etc/sudoers 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 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) 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 Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal of this software and associated documentation files (the "Software"), to deal
+603 -227
View File
@@ -1,16 +1,37 @@
// dear imgui: Renderer for DirectX9 // dear imgui: Renderer Backend for DirectX9
// This needs to be used along with a Platform Binding (e.g. Win32) // This needs to be used along with a Platform Backend (e.g. Win32)
// Implemented features: // Implemented features:
// [X] Renderer: User texture binding. Use 'LPDIRECT3DTEXTURE9' as ImTextureID. Read the FAQ about ImTextureID in imgui.cpp. // [X] Renderer: User texture binding. Use 'LPDIRECT3DTEXTURE9' as texture identifier. Read the FAQ about ImTextureID/ImTextureRef!
// [X] Renderer: Support for large meshes (64k+ vertices) with 16-bits indices. // [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. // You can use unmodified imgui_impl_* files in your project. See examples/ folder for examples of using this.
// If you are new to dear imgui, read examples/README.txt and read the documentation at the top of imgui.cpp. // Prefer including the entire imgui/ repository into your project (either as a copy or as a submodule), and only build the backends you need.
// https://github.com/ocornut/imgui // 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 // CHANGELOG
// (minor and older changes stripped away, please see git history for details) // (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-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-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(). // 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-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. // 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> #include <algorithm>
// DirectX
#include <d3d9.h>
#include "imgui.h" #include "imgui.h"
#ifndef IMGUI_DISABLE
#include "imgui_impl_dx9.h"
// allow std::min use // allow std::min use
#ifdef min #ifdef min
#undef min #undef min
#endif #endif
// DirectX data // DirectX
static LPDIRECT3DDEVICE9 g_pd3dDevice = NULL; #include <d3d9.h>
static LPDIRECT3DVERTEXBUFFER9 g_pVB = NULL;
static LPDIRECT3DINDEXBUFFER9 g_pIB = NULL;
static LPDIRECT3DTEXTURE9 g_FontTexture = NULL;
static int g_VertexBufferSize = 5000, g_IndexBufferSize = 10000;
// 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) #define D3DFVF_CUSTOMVERTEX (D3DFVF_XYZ|D3DFVF_DIFFUSE|D3DFVF_TEX1)
// Render function. #ifdef IMGUI_USE_BGRA_PACKED_COLOR
// (this used to be set in io.RenderDrawListsFn and called by ImGui::Render(), but you can now call this directly from your main loop) #define IMGUI_COL_TO_DX9_ARGB(_COL) (_COL)
void ImGui_ImplDX9_RenderDrawData(ImDrawData *draw_data) { #else
#define IMGUI_COL_TO_DX9_ARGB(_COL) (((_COL) & 0xFF00FF00) | (((_COL) & 0xFF0000) >> 16) | (((_COL) & 0xFF) << 16))
#endif
// Avoid rendering when minimized // Backend data stored in io.BackendRendererUserData to allow support for multiple Dear ImGui contexts
if (draw_data->DisplaySize.x <= 0.0f || draw_data->DisplaySize.y <= 0.0f) // It is STRONGLY preferred that you use docking branch with multi-viewports (== single Dear ImGui context + multiple windows) instead of multiple Dear ImGui contexts.
return; static ImGui_ImplDX9_Data* ImGui_ImplDX9_GetBackendData()
{
return ImGui::GetCurrentContext() ? (ImGui_ImplDX9_Data*)ImGui::GetIO().BackendRendererUserData : nullptr;
}
// Create and grow buffers if needed // Forward Declarations
if (!g_pVB || g_VertexBufferSize < draw_data->TotalVtxCount) { static void ImGui_ImplDX9_InitMultiViewportSupport();
if (g_pVB) { static void ImGui_ImplDX9_ShutdownMultiViewportSupport();
g_pVB->Release(); static void ImGui_ImplDX9_CreateDeviceObjectsForPlatformWindows();
g_pVB = NULL; static void ImGui_ImplDX9_InvalidateDeviceObjectsForPlatformWindows();
}
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;
}
// Backup the DX9 state struct render_state_t {
IDirect3DStateBlock9 *d3d9_state_block = NULL; IDirect3DSurface9 *back_buffer;
if (g_pd3dDevice->CreateStateBlock(D3DSBT_ALL, &d3d9_state_block) < 0) IDirect3DSurface9 *depth_stencil;
return; 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) // Functions
D3DMATRIX last_world, last_view, last_projection; static void ImGui_ImplDX9_SetupRenderState(ImDrawData* draw_data, struct render_state_t *render_state)
g_pd3dDevice->GetTransform(D3DTS_WORLD, &last_world); {
g_pd3dDevice->GetTransform(D3DTS_VIEW, &last_view); ImGui_ImplDX9_Data* bd = ImGui_ImplDX9_GetBackendData();
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);
// Setup viewport // Setup viewport
D3DVIEWPORT9 vp; D3DVIEWPORT9 vp;
vp.X = vp.Y = 0; vp.X = vp.Y = 0;
vp.Width = (DWORD) draw_data->DisplaySize.x; vp.Width = (DWORD)draw_data->DisplaySize.x;
vp.Height = (DWORD) draw_data->DisplaySize.y; vp.Height = (DWORD)draw_data->DisplaySize.y;
vp.MinZ = 0.0f; vp.MinZ = 0.0f;
vp.MaxZ = 1.0f; vp.MaxZ = 1.0f;
g_pd3dDevice->SetViewport(&vp);
g_pd3dDevice->SetPixelShader(nullptr); LPDIRECT3DDEVICE9 device = bd->pd3dDevice;
g_pd3dDevice->SetVertexShader(nullptr); device->SetViewport(&vp);
D3DCAPS9 caps {}; // spice ldj
if (FAILED(g_pd3dDevice->GetDeviceCaps(&caps))) { {
caps.NumSimultaneousRTs = 0UL; 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 // save all previous render target state
for (size_t target = 0; target < std::min(8UL, caps.NumSimultaneousRTs); target++) { for (size_t target = 0; target < std::min(8UL, render_state->caps.NumSimultaneousRTs); target++) {
if (FAILED(g_pd3dDevice->GetRenderTarget(target, &render_targets[target]))) { if (FAILED(device->GetRenderTarget(target, &render_state->render_targets[target]))) {
render_targets[target] = nullptr; render_state->render_targets[target] = nullptr;
} }
} }
// get the previous depth stencil // get the previous depth stencil
if (FAILED(g_pd3dDevice->GetDepthStencilSurface(&depth_stencil))) { if (FAILED(device->GetDepthStencilSurface(&render_state->depth_stencil))) {
depth_stencil = nullptr; render_state->depth_stencil = nullptr;
} }
// set the back buffer as the current render target // set the back buffer as the current render target
if (SUCCEEDED(g_pd3dDevice->GetBackBuffer(0, 0, D3DBACKBUFFER_TYPE_MONO, &back_buffer))) { if (SUCCEEDED(device->GetBackBuffer(0, 0, D3DBACKBUFFER_TYPE_MONO, &render_state->back_buffer))) {
g_pd3dDevice->SetRenderTarget(0, back_buffer); device->SetRenderTarget(0, render_state->back_buffer);
g_pd3dDevice->SetDepthStencilSurface(nullptr); device->SetDepthStencilSurface(nullptr);
for (size_t target = 1; target < std::min(8UL, caps.NumSimultaneousRTs); target++) { for (size_t target = 1; target < std::min(8UL, render_state->caps.NumSimultaneousRTs); target++) {
g_pd3dDevice->SetRenderTarget(target, nullptr); device->SetRenderTarget(target, nullptr);
} }
} else { } else {
back_buffer = nullptr; render_state->back_buffer = nullptr;
}
} }
// Setup render state: fixed-pipeline, alpha-blending, no face culling, no depth testing, shade mode (for gradient) // Setup render state: fixed-pipeline, alpha-blending, no face culling, no depth testing, shade mode (for gradient), bilinear sampling.
g_pd3dDevice->SetRenderState(D3DRS_CULLMODE, D3DCULL_NONE); device->SetPixelShader(nullptr);
g_pd3dDevice->SetRenderState(D3DRS_LIGHTING, false); device->SetVertexShader(nullptr);
g_pd3dDevice->SetRenderState(D3DRS_ZENABLE, false); device->SetRenderState(D3DRS_FILLMODE, D3DFILL_SOLID);
g_pd3dDevice->SetRenderState(D3DRS_ALPHABLENDENABLE, true); device->SetRenderState(D3DRS_SHADEMODE, D3DSHADE_GOURAUD);
g_pd3dDevice->SetRenderState(D3DRS_ALPHATESTENABLE, false); device->SetRenderState(D3DRS_ZWRITEENABLE, FALSE);
g_pd3dDevice->SetRenderState(D3DRS_BLENDOP, D3DBLENDOP_ADD); device->SetRenderState(D3DRS_ALPHATESTENABLE, FALSE);
g_pd3dDevice->SetRenderState(D3DRS_SRCBLEND, D3DBLEND_SRCALPHA); device->SetRenderState(D3DRS_CULLMODE, D3DCULL_NONE);
g_pd3dDevice->SetRenderState(D3DRS_DESTBLEND, D3DBLEND_INVSRCALPHA); device->SetRenderState(D3DRS_ZENABLE, FALSE);
g_pd3dDevice->SetRenderState(D3DRS_SCISSORTESTENABLE, true); device->SetRenderState(D3DRS_ALPHABLENDENABLE, TRUE);
g_pd3dDevice->SetRenderState(D3DRS_SHADEMODE, D3DSHADE_GOURAUD); device->SetRenderState(D3DRS_BLENDOP, D3DBLENDOP_ADD);
g_pd3dDevice->SetRenderState(D3DRS_FOGENABLE, false); device->SetRenderState(D3DRS_SRCBLEND, D3DBLEND_SRCALPHA);
g_pd3dDevice->SetTextureStageState(0, D3DTSS_COLOROP, D3DTOP_MODULATE); device->SetRenderState(D3DRS_DESTBLEND, D3DBLEND_INVSRCALPHA);
g_pd3dDevice->SetTextureStageState(0, D3DTSS_COLORARG1, D3DTA_TEXTURE); device->SetRenderState(D3DRS_SEPARATEALPHABLENDENABLE, TRUE);
g_pd3dDevice->SetTextureStageState(0, D3DTSS_COLORARG2, D3DTA_DIFFUSE); device->SetRenderState(D3DRS_SRCBLENDALPHA, D3DBLEND_ONE);
g_pd3dDevice->SetTextureStageState(0, D3DTSS_ALPHAOP, D3DTOP_MODULATE); device->SetRenderState(D3DRS_DESTBLENDALPHA, D3DBLEND_INVSRCALPHA);
g_pd3dDevice->SetTextureStageState(0, D3DTSS_ALPHAARG1, D3DTA_TEXTURE); device->SetRenderState(D3DRS_SCISSORTESTENABLE, TRUE);
g_pd3dDevice->SetTextureStageState(0, D3DTSS_ALPHAARG2, D3DTA_DIFFUSE); device->SetRenderState(D3DRS_FOGENABLE, FALSE);
g_pd3dDevice->SetSamplerState(0, D3DSAMP_MINFILTER, D3DTEXF_LINEAR); device->SetRenderState(D3DRS_RANGEFOGENABLE, FALSE);
g_pd3dDevice->SetSamplerState(0, D3DSAMP_MAGFILTER, D3DTEXF_LINEAR); 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 // 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. // 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,159 +200,373 @@ void ImGui_ImplDX9_RenderDrawData(ImDrawData *draw_data) {
float R = draw_data->DisplayPos.x + draw_data->DisplaySize.x + 0.5f; float R = draw_data->DisplayPos.x + draw_data->DisplaySize.x + 0.5f;
float T = draw_data->DisplayPos.y + 0.5f; float T = draw_data->DisplayPos.y + 0.5f;
float B = draw_data->DisplayPos.y + draw_data->DisplaySize.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 = D3DMATRIX mat_projection =
{{{ { { {
2.0f / (R - L), 0.0f, 0.0f, 0.0f, 2.0f/(R-L), 0.0f, 0.0f, 0.0f,
0.0f, 2.0f / (T - B), 0.0f, 0.0f, 0.0f, 2.0f/(T-B), 0.0f, 0.0f,
0.0f, 0.0f, 0.5f, 0.0f, 0.0f, 0.0f, 0.5f, 0.0f,
(L + R) / (L - R), (T + B) / (B - T), 0.5f, 1.0f (L+R)/(L-R), (T+B)/(B-T), 0.5f, 1.0f
}}}; } } };
g_pd3dDevice->SetTransform(D3DTS_WORLD, &mat_identity); device->SetTransform(D3DTS_WORLD, &mat_identity);
g_pd3dDevice->SetTransform(D3DTS_VIEW, &mat_identity); device->SetTransform(D3DTS_VIEW, &mat_identity);
g_pd3dDevice->SetTransform(D3DTS_PROJECTION, &mat_projection); 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 // Render command lists
// (Because we merged all buffers into a single one, we maintain our own offset into them) // (Because we merged all buffers into a single one, we maintain our own offset into them)
int global_vtx_offset = 0; int global_vtx_offset = 0;
int global_idx_offset = 0; int global_idx_offset = 0;
ImVec2 clip_off = draw_data->DisplayPos; ImVec2 clip_off = draw_data->DisplayPos;
for (int n = 0; n < draw_data->CmdListsCount; n++) { for (const ImDrawList* draw_list : draw_data->CmdLists)
const ImDrawList *cmd_list = draw_data->CmdLists[n]; {
for (int cmd_i = 0; cmd_i < cmd_list->CmdBuffer.Size; cmd_i++) { for (int cmd_i = 0; cmd_i < draw_list->CmdBuffer.Size; cmd_i++)
const ImDrawCmd *pcmd = &cmd_list->CmdBuffer[cmd_i]; {
if (pcmd->UserCallback != NULL) { const ImDrawCmd* pcmd = &draw_list->CmdBuffer[cmd_i];
pcmd->UserCallback(cmd_list, pcmd); if (pcmd->UserCallback != nullptr)
} else { {
const RECT r = {(LONG) (pcmd->ClipRect.x - clip_off.x), (LONG) (pcmd->ClipRect.y - clip_off.y), // User callback, registered via ImDrawList::AddCallback()
(LONG) (pcmd->ClipRect.z - clip_off.x), (LONG) (pcmd->ClipRect.w - clip_off.y)}; // (ImDrawCallback_ResetRenderState is a special callback value used by the user to request the renderer to reset render state.)
auto texture = reinterpret_cast<IDirect3DBaseTexture9 *>(pcmd->TextureId); if (pcmd->UserCallback == ImDrawCallback_ResetRenderState)
g_pd3dDevice->SetTexture(0, texture); ImGui_ImplDX9_SetupRenderState(draw_data, &render_state);
g_pd3dDevice->SetScissorRect(&r); else
g_pd3dDevice->DrawIndexedPrimitive(D3DPT_TRIANGLELIST, pcmd->UserCallback(draw_list, pcmd);
pcmd->VtxOffset + global_vtx_offset, 0, }
(UINT) cmd_list->VtxBuffer.Size, else
pcmd->IdxOffset + global_idx_offset, pcmd->ElemCount / 3); {
// 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_idx_offset += draw_list->IdxBuffer.Size;
global_vtx_offset += cmd_list->VtxBuffer.Size; global_vtx_offset += draw_list->VtxBuffer.Size;
} }
if (back_buffer) { // When using multi-viewports, it appears that there's an odd logic in DirectX9 which prevent subsequent windows
back_buffer->Release(); // from rendering until the first window submits at least one draw call, even once. That's our workaround. (see #2560)
back_buffer = nullptr; if (global_vtx_offset == 0)
} bd->pd3dDevice->DrawIndexedPrimitive(D3DPT_TRIANGLELIST, 0, 0, 0, 0, 0);
// spice ldj
{
if (render_state.back_buffer) {
render_state.back_buffer->Release();
render_state.back_buffer = nullptr;
}
// restore previous depth stencil // restore previous depth stencil
if (depth_stencil) { if (render_state.depth_stencil) {
g_pd3dDevice->SetDepthStencilSurface(depth_stencil); device->SetDepthStencilSurface(render_state.depth_stencil);
depth_stencil->Release(); render_state.depth_stencil->Release();
depth_stencil = nullptr; render_state.depth_stencil = nullptr;
} }
// restore all render target state // restore all render target state
for (size_t target = 0; target < std::min(8UL, caps.NumSimultaneousRTs); target++) { for (size_t target = 0; target < std::min(8UL, render_state.caps.NumSimultaneousRTs); target++) {
auto render_target = render_targets[target]; auto render_target = render_state.render_targets[target];
if (render_target) { if (render_target) {
g_pd3dDevice->SetRenderTarget(target, render_target); device->SetRenderTarget(target, render_target);
render_target->Release(); render_target->Release();
} }
} }
}
// restore the DX9 transform // Restore the DX9 transform
g_pd3dDevice->SetTransform(D3DTS_WORLD, &last_world); device->SetTransform(D3DTS_WORLD, &last_world);
g_pd3dDevice->SetTransform(D3DTS_VIEW, &last_view); device->SetTransform(D3DTS_VIEW, &last_view);
g_pd3dDevice->SetTransform(D3DTS_PROJECTION, &last_projection); device->SetTransform(D3DTS_PROJECTION, &last_projection);
// restore the DX9 state // Restore the DX9 state
d3d9_state_block->Apply(); state_block->Apply();
d3d9_state_block->Release(); 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 bool ImGui_ImplDX9_Init(IDirect3DDevice9* device)
auto &io = ImGui::GetIO(); {
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.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. ImGuiPlatformIO& platform_io = ImGui::GetPlatformIO();
io.BackendFlags |= ImGuiBackendFlags_RendererHasVtxOffset; 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; 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(); ImGui_ImplDX9_InvalidateDeviceObjects();
if (bd->pd3dDevice) { bd->pd3dDevice->Release(); }
if (g_pd3dDevice) { io.BackendRendererName = nullptr;
g_pd3dDevice->Release(); io.BackendRendererUserData = nullptr;
g_pd3dDevice = NULL; 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() { void ImGui_ImplDX9_UpdateTexture(ImTextureData* tex)
// Build texture atlas {
ImGuiIO &io = ImGui::GetIO(); ImGui_ImplDX9_Data* bd = ImGui_ImplDX9_GetBackendData();
unsigned char *pixels;
int width, height, bytes_per_pixel;
io.Fonts->GetTexDataAsRGBA32(&pixels, &width, &height, &bytes_per_pixel);
// Upload texture to graphics system if (tex->Status == ImTextureStatus_WantCreate)
g_FontTexture = NULL; {
if (g_pd3dDevice->CreateTexture(width, height, 1, D3DUSAGE_DYNAMIC, D3DFMT_A8R8G8B8, // Create and upload new texture to graphics system
D3DPOOL_DEFAULT, &g_FontTexture, NULL) < 0) //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;
}
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; return false;
D3DLOCKED_RECT tex_locked_rect; ImGui_ImplDX9_CreateDeviceObjectsForPlatformWindows();
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; return true;
} }
bool ImGui_ImplDX9_CreateDeviceObjects() { void ImGui_ImplDX9_InvalidateDeviceObjects()
if (!g_pd3dDevice) { {
return false; ImGui_ImplDX9_Data* bd = ImGui_ImplDX9_GetBackendData();
} if (!bd || !bd->pd3dDevice)
return ImGui_ImplDX9_CreateFontsTexture();
}
void ImGui_ImplDX9_InvalidateDeviceObjects() {
if (!g_pd3dDevice)
return; return;
if (g_pVB) {
g_pVB->Release(); // Destroy all textures
g_pVB = NULL; for (ImTextureData* tex : ImGui::GetPlatformIO().Textures)
if (tex->RefCount == 1)
{
tex->SetStatus(ImTextureStatus_WantDestroy);
ImGui_ImplDX9_UpdateTexture(tex);
} }
if (g_pIB) { if (bd->pVB) { bd->pVB->Release(); bd->pVB = nullptr; }
g_pIB->Release(); if (bd->pIB) { bd->pIB->Release(); bd->pIB = nullptr; }
g_pIB = NULL; ImGui_ImplDX9_InvalidateDeviceObjectsForPlatformWindows();
}
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() { void ImGui_ImplDX9_NewFrame()
if (!g_FontTexture) { {
ImGui_ImplDX9_CreateDeviceObjects(); 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; IDirect3DSwapChain9 *swap_chain = nullptr;
if (SUCCEEDED(g_pd3dDevice->GetSwapChain(0, &swap_chain))) { if (SUCCEEDED(bd->pd3dDevice->GetSwapChain(0, &swap_chain))) {
auto &io = ImGui::GetIO(); auto &io = ImGui::GetIO();
D3DPRESENT_PARAMETERS present_params {}; D3DPRESENT_PARAMETERS present_params {};
@@ -357,4 +586,151 @@ void ImGui_ImplDX9_NewFrame() {
swap_chain->Release(); 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 // dear imgui: Renderer Backend for DirectX9
// This needs to be used along with a Platform Binding (e.g. Win32) // This needs to be used along with a Platform Backend (e.g. Win32)
// Implemented features: // Implemented features:
// [X] Renderer: User texture binding. Use 'LPDIRECT3DTEXTURE9' as ImTextureID. Read the FAQ about ImTextureID in imgui.cpp. // [X] Renderer: User texture binding. Use 'LPDIRECT3DTEXTURE9' as texture identifier. Read the FAQ about ImTextureID/ImTextureRef!
// [X] Renderer: Support for large meshes (64k+ vertices) with 16-bits indices. // [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. // You can use unmodified imgui_impl_* files in your project. See examples/ folder for examples of using this.
// If you are new to dear imgui, read examples/README.txt and read the documentation at the top of imgui.cpp. // Prefer including the entire imgui/ repository into your project (either as a copy or as a submodule), and only build the backends you need.
// https://github.com/ocornut/imgui // 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 #pragma once
#include "imgui.h" // IMGUI_IMPL_API
#include "imgui.h" #ifndef IMGUI_DISABLE
struct IDirect3DDevice9; 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_Shutdown();
IMGUI_IMPL_API void ImGui_ImplDX9_NewFrame(); 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 bool ImGui_ImplDX9_CreateDeviceObjects();
IMGUI_IMPL_API void ImGui_ImplDX9_InvalidateDeviceObjects(); 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 #pragma once
//---- Define assertion handler. Defaults to calling assert(). //---- 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) MyAssert(_EXPR)
#define IM_ASSERT(_EXPR) ((void)(_EXPR)) // Disable asserts #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. //---- 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_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. //---- 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. // 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_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_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_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 //#define IMGUI_DISABLE_SSE // Disable use of SSE intrinsics even if available
//---- Enable Test Engine / Automation features. //---- Enable Test Engine / Automation features.
@@ -59,12 +60,15 @@
//#define IMGUI_INCLUDE_IMGUI_USER_H //#define IMGUI_INCLUDE_IMGUI_USER_H
//#define IMGUI_USER_H_FILENAME "my_folder/my_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 #define IMGUI_USE_BGRA_PACKED_COLOR
//---- Performance optimization for DX9 //---- Performance optimization for DX9
#define IMGUI_OVERRIDE_DRAWVERT_STRUCT_LAYOUT struct ImDrawVert { ImVec2 pos; float z; ImU32 col; ImVec2 uv; } #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...) //---- 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 //#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) //---- 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). // 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'. // On Windows you may use vcpkg with 'vcpkg install freetype --triplet=x64-windows' + 'vcpkg integrate install'.
//#define IMGUI_ENABLE_FREETYPE //#define IMGUI_ENABLE_FREETYPE
//---- Use FreeType + plutosvg or lunasvg to render OpenType SVG fonts (SVGinOT) //---- Use FreeType + plutosvg or lunasvg to render OpenType SVG fonts (SVGinOT)
// Only works in combination with IMGUI_ENABLE_FREETYPE. // Only works in combination with IMGUI_ENABLE_FREETYPE.
// - lunasvg is currently easier to acquire/install, as e.g. it is part of vcpkg. // - 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.
// - 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.
// - Both require headers to be available in the include path + program to be linked with the library code (not provided). // - 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) // - (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 //#define IMGUI_ENABLE_FREETYPE_PLUTOSVG
@@ -130,6 +134,10 @@
//#define IM_DEBUG_BREAK IM_ASSERT(0) //#define IM_DEBUG_BREAK IM_ASSERT(0)
//#define IM_DEBUG_BREAK __debugbreak() //#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 //---- Debug Tools: Enable slower asserts
//#define IMGUI_DEBUG_PARANOID //#define IMGUI_DEBUG_PARANOID
+3313 -1827
View File
File diff suppressed because it is too large Load Diff
+1003 -497
View File
File diff suppressed because it is too large Load Diff
+2802 -2056
View File
File diff suppressed because it is too large Load Diff
+3175 -1112
View File
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+255 -161
View File
@@ -1,6 +1,7 @@
// Mini memory editor for Dear ImGui (to embed in your game/tools) // Mini memory editor for Dear ImGui (to embed in your game/tools)
// Get latest version at http://www.github.com/ocornut/imgui_club // 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! // Right-click anywhere to access the Options menu!
// You can adjust the keyboard repeat delay/rate in ImGuiIO. // You can adjust the keyboard repeat delay/rate in ImGuiIO.
// The code assume a mono-space font for simplicity! // 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.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.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.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. // - 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. // - 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. // - Using InputText() is awkward and maybe overkill here, consider implementing something custom.
@@ -51,15 +64,18 @@
#include <stdio.h> // sprintf, scanf #include <stdio.h> // sprintf, scanf
#include <stdint.h> // uint8_t, etc. #include <stdint.h> // uint8_t, etc.
#ifdef _MSC_VER #if defined(_MSC_VER) && !defined(snprintf)
#define _PRISizeT "I"
#define ImSnprintf _snprintf #define ImSnprintf _snprintf
#else #else
#define _PRISizeT "z"
#define ImSnprintf snprintf #define ImSnprintf snprintf
#endif #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 (push)
#pragma warning (disable: 4996) // warning C4996: 'sprintf': This function or variable may be unsafe. #pragma warning (disable: 4996) // warning C4996: 'sprintf': This function or variable may be unsafe.
#endif #endif
@@ -88,9 +104,17 @@ struct MemoryEditor
int OptAddrDigitsCount; // = 0 // number of addr digits to display (default calculated based on maximum displayed addr). 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 float OptFooterExtraHeight; // = 0 // space to reserve at the bottom of the widget to add custom widgets
ImU32 HighlightColor; // // background color of highlighted bytes. 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. // Function handlers
bool (*HighlightFn)(const ImU8* data, size_t off);//= 0 // optional handler to return Highlight property (to support non-contiguous highlighting). 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] // [Internal State]
bool ContentsWidthChanged; bool ContentsWidthChanged;
@@ -101,7 +125,7 @@ struct MemoryEditor
char AddrInputBuf[32]; char AddrInputBuf[32];
size_t GotoAddr; size_t GotoAddr;
size_t HighlightMin, HighlightMax; size_t HighlightMin, HighlightMax;
int PreviewEndianess; int PreviewEndianness;
ImGuiDataType PreviewDataType; ImGuiDataType PreviewDataType;
MemoryEditor() MemoryEditor()
@@ -120,9 +144,11 @@ struct MemoryEditor
OptAddrDigitsCount = 0; OptAddrDigitsCount = 0;
OptFooterExtraHeight = 0.0f; OptFooterExtraHeight = 0.0f;
HighlightColor = IM_COL32(255, 255, 255, 50); HighlightColor = IM_COL32(255, 255, 255, 50);
ReadFn = NULL; ReadFn = nullptr;
WriteFn = NULL; WriteFn = nullptr;
HighlightFn = NULL; HighlightFn = nullptr;
BgColorFn = nullptr;
UserData = nullptr;
// State/Internals // State/Internals
ContentsWidthChanged = false; ContentsWidthChanged = false;
@@ -131,8 +157,10 @@ struct MemoryEditor
memset(DataInputBuf, 0, sizeof(DataInputBuf)); memset(DataInputBuf, 0, sizeof(DataInputBuf));
memset(AddrInputBuf, 0, sizeof(AddrInputBuf)); memset(AddrInputBuf, 0, sizeof(AddrInputBuf));
GotoAddr = (size_t)-1; GotoAddr = (size_t)-1;
MouseHovered = false;
MouseHoveredAddr = 0;
HighlightMin = HighlightMax = (size_t)-1; HighlightMin = HighlightMax = (size_t)-1;
PreviewEndianess = 0; PreviewEndianness = 0;
PreviewDataType = ImGuiDataType_S32; PreviewDataType = ImGuiDataType_S32;
} }
@@ -145,16 +173,16 @@ struct MemoryEditor
struct Sizes struct Sizes
{ {
int AddrDigitsCount; int AddrDigitsCount; // Number of digits required to represent maximum address.
float LineHeight; float LineHeight; // Height of each line (no spacing).
float GlyphWidth; float GlyphWidth; // Glyph width (assume mono-space).
float HexCellWidth; float HexCellWidth; // Width of a hex edit cell ~2.5f * GlypHWidth.
float SpacingBetweenMidCols; float SpacingBetweenMidCols; // Spacing between each columns section (OptMidColsCount).
float PosHexStart; float OffsetHexMinX;
float PosHexEnd; float OffsetHexMaxX;
float PosAsciiStart; float OffsetAsciiMinX;
float PosAsciiEnd; float OffsetAsciiMaxX;
float WindowWidth; float WindowWidth; // Ideal window width.
Sizes() { memset(this, 0, sizeof(*this)); } 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.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.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.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.OffsetHexMinX = (s.AddrDigitsCount + 2) * s.GlyphWidth;
s.PosHexEnd = s.PosHexStart + (s.HexCellWidth * Cols); s.OffsetHexMaxX = s.OffsetHexMinX + (s.HexCellWidth * Cols);
s.PosAsciiStart = s.PosAsciiEnd = s.PosHexEnd; s.OffsetAsciiMinX = s.OffsetAsciiMaxX = s.OffsetHexMaxX;
if (OptShowAscii) if (OptShowAscii)
{ {
s.PosAsciiStart = s.PosHexEnd + s.GlyphWidth * 1; s.OffsetAsciiMinX = s.OffsetHexMaxX + s.GlyphWidth * 1;
if (OptMidColsCount > 0) if (OptMidColsCount > 0)
s.PosAsciiStart += (float)((Cols + OptMidColsCount - 1) / OptMidColsCount) * s.SpacingBetweenMidCols; s.OffsetAsciiMinX += (float)((Cols + OptMidColsCount - 1) / OptMidColsCount) * s.SpacingBetweenMidCols;
s.PosAsciiEnd = s.PosAsciiStart + Cols * s.GlyphWidth; 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 // Standalone Memory Editor window
@@ -194,8 +222,6 @@ struct MemoryEditor
Open = true; Open = true;
if (ImGui::Begin(title, &Open, ImGuiWindowFlags_NoScrollbar)) 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); DrawContents(mem_data, mem_size, base_display_addr);
if (ContentsWidthChanged) if (ContentsWidthChanged)
{ {
@@ -217,6 +243,8 @@ struct MemoryEditor
CalcSizes(s, mem_size, base_display_addr); CalcSizes(s, mem_size, base_display_addr);
ImGuiStyle& style = ImGui::GetStyle(); 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. // 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. // 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; const float height_separator = style.ItemSpacing.y;
@@ -225,20 +253,21 @@ struct MemoryEditor
footer_height += height_separator + ImGui::GetFrameHeightWithSpacing() * 1; footer_height += height_separator + ImGui::GetFrameHeightWithSpacing() * 1;
if (OptShowDataPreview) if (OptShowDataPreview)
footer_height += height_separator + ImGui::GetFrameHeightWithSpacing() * 1 + ImGui::GetTextLineHeightWithSpacing() * 3; 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(); ImDrawList* draw_list = ImGui::GetWindowDrawList();
ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(0, 0)); ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(0, 0));
ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, 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. // 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); const int line_total_count = (int)((mem_size + Cols - 1) / Cols);
ImGuiListClipper clipper; ImGuiListClipper clipper;
clipper.Begin(line_total_count, s.LineHeight); clipper.Begin(line_total_count, s.LineHeight);
bool data_next = false; bool data_next = false;
if (ReadOnly || DataEditingAddr >= mem_size) if (DataEditingAddr >= mem_size)
DataEditingAddr = (size_t)-1; DataEditingAddr = (size_t)-1;
if (DataPreviewAddr >= mem_size) if (DataPreviewAddr >= mem_size)
DataPreviewAddr = (size_t)-1; DataPreviewAddr = (size_t)-1;
@@ -249,16 +278,16 @@ struct MemoryEditor
if (DataEditingAddr != (size_t)-1) 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) // 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; } if (ImGui::IsKeyPressed(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(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(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; } else if (ImGui::IsKeyPressed(ImGuiKey_RightArrow) && (ptrdiff_t)DataEditingAddr < (ptrdiff_t)mem_size - 1) { data_editing_addr_next = DataEditingAddr + 1; }
} }
// Draw vertical separator // Draw vertical separator
ImVec2 window_pos = ImGui::GetWindowPos(); ImVec2 window_pos = ImGui::GetWindowPos();
if (OptShowAscii) 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_text = ImGui::GetColorU32(ImGuiCol_Text);
const ImU32 color_disabled = OptGreyOutZeroes ? ImGui::GetColorU32(ImGuiCol_TextDisabled) : color_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 = OptUpperCaseHex ? "%02X" : "%02x";
const char* format_byte_space = OptUpperCaseHex ? "%02X " : "%02x "; const char* format_byte_space = OptUpperCaseHex ? "%02X " : "%02x ";
MouseHovered = false;
MouseHoveredAddr = 0;
while (clipper.Step()) while (clipper.Step())
for (int line_i = clipper.DisplayStart; line_i < clipper.DisplayEnd; line_i++) // display only visible lines 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); ImGui::Text(format_address, s.AddrDigitsCount, base_display_addr + addr);
// Draw Hexadecimal // Draw Hexadecimal
for (int n = 0; n < Cols && addr < mem_size; n++, addr++) 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) if (OptMidColsCount > 0)
byte_pos_x += (float)(n / OptMidColsCount) * s.SpacingBetweenMidCols; byte_pos_x += (float)(n / OptMidColsCount) * s.SpacingBetweenMidCols;
ImGui::SameLine(byte_pos_x); ImGui::SameLine(byte_pos_x);
// Draw highlight // Draw highlight or custom background color
bool is_highlight_from_user_range = (addr >= HighlightMin && addr < HighlightMax); const bool is_highlight_from_user_range = (addr >= HighlightMin && addr < HighlightMax);
bool is_highlight_from_user_func = (HighlightFn && HighlightFn(mem_data, addr)); const bool is_highlight_from_user_func = (HighlightFn && HighlightFn(mem_data, addr, UserData));
bool is_highlight_from_preview = (addr >= DataPreviewAddr && addr < DataPreviewAddr + preview_data_type_size); 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) if (is_highlight_from_user_range || is_highlight_from_user_func || is_highlight_from_preview)
{ {
ImVec2 pos = ImGui::GetCursorScreenPos(); 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));
float highlight_width = s.GlyphWidth * 2; bg_color = HighlightColor;
bool is_next_byte_highlighted = (addr + 1 < mem_size) && ((HighlightMax != (size_t)-1 && addr + 1 < HighlightMax) || (HighlightFn && HighlightFn(mem_data, addr + 1))); }
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)) 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) 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) if (DataEditingAddr == addr)
@@ -308,17 +352,21 @@ struct MemoryEditor
if (DataEditingTakeFocus) if (DataEditingTakeFocus)
{ {
ImGui::SetKeyboardFocusHere(0); ImGui::SetKeyboardFocusHere(0);
sprintf(AddrInputBuf, format_data, s.AddrDigitsCount, base_display_addr + addr); ImSnprintf(AddrInputBuf, 32, format_data, s.AddrDigitsCount, base_display_addr + addr);
sprintf(DataInputBuf, format_byte, ReadFn ? ReadFn(mem_data, addr) : mem_data[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. // 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) static int Callback(ImGuiInputTextCallbackData* data)
{ {
UserData* user_data = (UserData*)data->UserData; InputTextUserData* user_data = (InputTextUserData*)data->UserData;
if (!data->HasSelection()) if (!data->HasSelection())
user_data->CursorPos = data->CursorPos; 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) if (data->SelectionStart == 0 && data->SelectionEnd == data->BufTextLen)
{ {
// When not editing a byte, always refresh its InputText content pulled from underlying memory data // 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 char CurrentBufOverwrite[3]; // Input
int CursorPos; // Output int CursorPos; // Output
}; };
UserData user_data; InputTextUserData input_text_user_data;
user_data.CursorPos = -1; input_text_user_data.CursorPos = -1;
sprintf(user_data.CurrentBufOverwrite, format_byte, ReadFn ? ReadFn(mem_data, addr) : mem_data[addr]); 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; ImGuiInputTextFlags flags = ImGuiInputTextFlags_CharsHexadecimal | ImGuiInputTextFlags_EnterReturnsTrue | ImGuiInputTextFlags_AutoSelectAll | ImGuiInputTextFlags_NoHorizontalScroll | ImGuiInputTextFlags_CallbackAlways;
#if IMGUI_VERSION_NUM >= 18104 if (ReadOnly)
flags |= ImGuiInputTextFlags_AlwaysOverwrite; flags |= ImGuiInputTextFlags_ReadOnly;
#else flags |= ImGuiInputTextFlags_AlwaysOverwrite; // was ImGuiInputTextFlags_AlwaysInsertMode
flags |= ImGuiInputTextFlags_AlwaysInsertMode;
#endif
ImGui::SetNextItemWidth(s.GlyphWidth * 2); 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; data_write = data_next = true;
else if (!DataEditingTakeFocus && !ImGui::IsItemActive()) else if (!DataEditingTakeFocus && !ImGui::IsItemActive())
DataEditingAddr = data_editing_addr_next = (size_t)-1; DataEditingAddr = data_editing_addr_next = (size_t)-1;
DataEditingTakeFocus = false; DataEditingTakeFocus = false;
if (user_data.CursorPos >= 2) if (input_text_user_data.CursorPos >= 2)
data_write = data_next = true; data_write = data_next = true;
if (data_editing_addr_next != (size_t)-1) if (data_editing_addr_next != (size_t)-1)
data_write = data_next = false; data_write = data_next = false;
unsigned int data_input_value = 0; 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) if (WriteFn)
WriteFn(mem_data, addr, (ImU8)data_input_value); WriteFn(mem_data, addr, (ImU8)data_input_value, UserData);
else else
mem_data[addr] = (ImU8)data_input_value; mem_data[addr] = (ImU8)data_input_value;
} }
if (ImGui::IsItemHovered())
{
MouseHovered = true;
MouseHoveredAddr = addr;
}
ImGui::PopID(); ImGui::PopID();
} }
else else
{ {
// NB: The trailing space is not visible but ensure there's no gap that the mouse cannot click on. // 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) if (OptShowHexII)
{ {
@@ -386,26 +437,40 @@ struct MemoryEditor
else else
ImGui::Text(format_byte_space, b); ImGui::Text(format_byte_space, b);
} }
if (!ReadOnly && ImGui::IsItemHovered() && ImGui::IsMouseClicked(0)) if (ImGui::IsItemHovered())
{
MouseHovered = true;
MouseHoveredAddr = addr;
if (ImGui::IsMouseClicked(0))
{ {
DataEditingTakeFocus = true; DataEditingTakeFocus = true;
data_editing_addr_next = addr; data_editing_addr_next = addr;
} }
} }
} }
}
if (OptShowAscii) if (OptShowAscii)
{ {
// Draw ASCII values // Draw ASCII values
ImGui::SameLine(s.PosAsciiStart); ImGui::SameLine(s.OffsetAsciiMinX);
ImVec2 pos = ImGui::GetCursorScreenPos(); 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); 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; DataEditingTakeFocus = true;
} }
if (ImGui::IsItemHovered())
{
MouseHovered = true;
MouseHoveredAddr = mouse_addr;
}
ImGui::PopID(); ImGui::PopID();
for (int n = 0; n < Cols && addr < mem_size; n++, addr++) 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_FrameBg));
draw_list->AddRectFilled(pos, ImVec2(pos.x + s.GlyphWidth, pos.y + s.LineHeight), ImGui::GetColorU32(ImGuiCol_TextSelectedBg)); 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; char display_c = (c < 32 || c >= 128) ? '.' : c;
draw_list->AddText(pos, (display_c == c) ? color_text : color_disabled, &display_c, &display_c + 1); draw_list->AddText(pos, (display_c == c) ? color_text : color_disabled, &display_c, &display_c + 1);
pos.x += s.GlyphWidth; pos.x += s.GlyphWidth;
@@ -422,10 +491,14 @@ struct MemoryEditor
} }
} }
ImGui::PopStyleVar(2); ImGui::PopStyleVar(2);
const float child_width = ImGui::GetWindowSize().x;
ImGui::EndChild(); 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) // 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::SetCursorPosX(s.WindowWidth);
ImGui::Dummy(ImVec2(0.0f, 0.0f));
ImGui::SetCursorScreenPos(backup_pos);
if (data_next && DataEditingAddr + 1 < mem_size) if (data_next && DataEditingAddr + 1 < mem_size)
{ {
@@ -450,18 +523,28 @@ struct MemoryEditor
ImGui::Separator(); ImGui::Separator();
DrawPreviewLine(s, mem_data, mem_size, base_display_addr); DrawPreviewLine(s, mem_data, mem_size, base_display_addr);
} }
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;
} }
void DrawOptionsLine(const Sizes& s, void* mem_data, size_t mem_size, size_t base_display_addr) 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));
IM_UNUSED(mem_data); if (OptShowOptions)
ImGuiStyle& style = ImGui::GetStyle(); if (ImGui::IsMouseHoveringRect(contents_pos_start, contents_pos_end))
const char* format_range = OptUpperCaseHex ? "Range %0*" _PRISizeT "X..%0*" _PRISizeT "X" : "Range %0*" _PRISizeT "x..%0*" _PRISizeT "x"; if (ImGui::IsWindowHovered(ImGuiHoveredFlags_ChildWindows) && ImGui::IsMouseReleased(ImGuiMouseButton_Right))
ImGui::OpenPopup("OptionsPopup");
// Options menu if (ImGui::BeginPopup("OptionsPopup"))
if (ImGui::Button("Options"))
ImGui::OpenPopup("context");
if (ImGui::BeginPopup("context"))
{ {
ImGui::SetNextItemWidth(s.GlyphWidth * 7 + style.FramePadding.x * 2.0f); 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; } 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(); 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::SameLine();
ImGui::Text(format_range, s.AddrDigitsCount, base_display_addr, s.AddrDigitsCount, base_display_addr + mem_size - 1); 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 (MouseHovered)
{ //{
if (GotoAddr < mem_size) // ImGui::SameLine();
{ // ImGui::Text("Hovered: %p", MouseHoveredAddr);
ImGui::BeginChild("##scrolling"); //}
ImGui::SetScrollFromPosY(ImGui::GetCursorStartPos().y + (GotoAddr / Cols) * ImGui::GetTextLineHeight());
ImGui::EndChild();
DataEditingAddr = DataPreviewAddr = GotoAddr;
DataEditingTakeFocus = true;
}
GotoAddr = (size_t)-1;
}
} }
void DrawPreviewLine(const Sizes& s, void* mem_data_void, size_t mem_size, size_t base_display_addr) 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::Text("Preview as:");
ImGui::SameLine(); ImGui::SameLine();
ImGui::SetNextItemWidth((s.GlyphWidth * 10.0f) + style.FramePadding.x * 2.0f + style.ItemInnerSpacing.x); 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)) if (ImGui::BeginCombo("##combo_type", DataTypeGetDesc(PreviewDataType), ImGuiComboFlags_HeightLargest))
{ {
for (int n = 0; n < ImGuiDataType_COUNT; n++) for (int n = 0; n < IM_ARRAYSIZE(supported_data_types); n++)
if (ImGui::Selectable(DataTypeGetDesc((ImGuiDataType)n), PreviewDataType == n)) {
PreviewDataType = (ImGuiDataType)n; ImGuiDataType data_type = supported_data_types[n];
if (ImGui::Selectable(DataTypeGetDesc(data_type), PreviewDataType == data_type))
PreviewDataType = data_type;
}
ImGui::EndCombo(); ImGui::EndCombo();
} }
ImGui::SameLine(); ImGui::SameLine();
ImGui::SetNextItemWidth((s.GlyphWidth * 6.0f) + style.FramePadding.x * 2.0f + style.ItemInnerSpacing.x); 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] = ""; char buf[128] = "";
float x = s.GlyphWidth * 6.0f; 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"); 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* DataTypeGetDesc(ImGuiDataType data_type) const
{ {
const char* descs[] = { "Int8", "Uint8", "Int16", "Uint16", "Int32", "Uint32", "Int64", "Uint64", "Float", "Double" }; 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]; return descs[data_type];
} }
size_t DataTypeGetSize(ImGuiDataType data_type) const size_t DataTypeGetSize(ImGuiDataType data_type) const
{ {
const size_t sizes[] = { 1, 1, 2, 2, 4, 4, 8, 8, sizeof(float), sizeof(double) }; 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]; return sizes[data_type];
} }
@@ -561,18 +654,18 @@ struct MemoryEditor
bool IsBigEndian() const bool IsBigEndian() const
{ {
uint16_t x = 1; ImU16 x = 1;
char c[2]; char c[2];
memcpy(c, &x, 2); memcpy(c, &x, 2);
return c[0] != 0; 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) if (is_little_endian)
{ {
uint8_t* dst = (uint8_t*)_dst; ImU8* dst = (ImU8*)_dst;
uint8_t* src = (uint8_t*)_src + s - 1; ImU8* src = (ImU8*)_src + s - 1;
for (int i = 0, n = (int)s; i < n; ++i) for (int i = 0, n = (int)s; i < n; ++i)
memcpy(dst++, src--, 1); memcpy(dst++, src--, 1);
return _dst; 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) if (is_little_endian)
{ {
@@ -591,23 +684,23 @@ struct MemoryEditor
} }
else else
{ {
uint8_t* dst = (uint8_t*)_dst; ImU8* dst = (ImU8*)_dst;
uint8_t* src = (uint8_t*)_src + s - 1; ImU8* src = (ImU8*)_src + s - 1;
for (int i = 0, n = (int)s; i < n; ++i) for (int i = 0, n = (int)s; i < n; ++i)
memcpy(dst++, src--, 1); memcpy(dst++, src--, 1);
return _dst; 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; static void* (*fp)(void*, void*, size_t, int) = nullptr;
if (fp == NULL) if (fp == nullptr)
fp = IsBigEndian() ? EndianessCopyBigEndian : EndianessCopyLittleEndian; fp = IsBigEndian() ? EndiannessCopyBigEndian : EndiannessCopyLittleEndian;
return fp(dst, src, size, PreviewEndianess); 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); IM_ASSERT(width <= 64);
size_t out_n = 0; size_t out_n = 0;
@@ -627,19 +720,19 @@ struct MemoryEditor
// [Internal] // [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 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 elem_size = DataTypeGetSize(data_type);
size_t size = addr + elem_size > mem_size ? mem_size - addr : elem_size; size_t size = addr + elem_size > mem_size ? mem_size - addr : elem_size;
if (ReadFn) if (ReadFn)
for (int i = 0, n = (int)size; i < n; ++i) 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 else
memcpy(buf, mem_data + addr, size); memcpy(buf, mem_data + addr, size);
if (data_format == DataFormat_Bin) if (data_format == DataFormat_Bin)
{ {
uint8_t binbuf[8]; ImU8 binbuf[8];
EndianessCopy(binbuf, buf, size); EndiannessCopy(binbuf, buf, size);
ImSnprintf(out_buf, out_buf_size, "%s", FormatBinary(binbuf, (int)size * 8)); ImSnprintf(out_buf, out_buf_size, "%s", FormatBinary(binbuf, (int)size * 8));
return; return;
} }
@@ -649,84 +742,85 @@ struct MemoryEditor
{ {
case ImGuiDataType_S8: case ImGuiDataType_S8:
{ {
int8_t int8 = 0; ImS8 data = 0;
EndianessCopy(&int8, buf, size); EndiannessCopy(&data, buf, size);
if (data_format == DataFormat_Dec) { ImSnprintf(out_buf, out_buf_size, "%hhd", int8); return; } 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", int8 & 0xFF); return; } if (data_format == DataFormat_Hex) { ImSnprintf(out_buf, out_buf_size, "0x%02x", data & 0xFF); return; }
break; break;
} }
case ImGuiDataType_U8: case ImGuiDataType_U8:
{ {
uint8_t uint8 = 0; ImU8 data = 0;
EndianessCopy(&uint8, buf, size); EndiannessCopy(&data, buf, size);
if (data_format == DataFormat_Dec) { ImSnprintf(out_buf, out_buf_size, "%hhu", uint8); return; } 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", uint8 & 0XFF); return; } if (data_format == DataFormat_Hex) { ImSnprintf(out_buf, out_buf_size, "0x%02x", data & 0XFF); return; }
break; break;
} }
case ImGuiDataType_S16: case ImGuiDataType_S16:
{ {
int16_t int16 = 0; ImS16 data = 0;
EndianessCopy(&int16, buf, size); EndiannessCopy(&data, buf, size);
if (data_format == DataFormat_Dec) { ImSnprintf(out_buf, out_buf_size, "%hd", int16); return; } 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", int16 & 0xFFFF); return; } if (data_format == DataFormat_Hex) { ImSnprintf(out_buf, out_buf_size, "0x%04x", data & 0xFFFF); return; }
break; break;
} }
case ImGuiDataType_U16: case ImGuiDataType_U16:
{ {
uint16_t uint16 = 0; ImU16 data = 0;
EndianessCopy(&uint16, buf, size); EndiannessCopy(&data, buf, size);
if (data_format == DataFormat_Dec) { ImSnprintf(out_buf, out_buf_size, "%hu", uint16); return; } 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", uint16 & 0xFFFF); return; } if (data_format == DataFormat_Hex) { ImSnprintf(out_buf, out_buf_size, "0x%04x", data & 0xFFFF); return; }
break; break;
} }
case ImGuiDataType_S32: case ImGuiDataType_S32:
{ {
int32_t int32 = 0; ImS32 data = 0;
EndianessCopy(&int32, buf, size); EndiannessCopy(&data, buf, size);
if (data_format == DataFormat_Dec) { ImSnprintf(out_buf, out_buf_size, "%d", int32); return; } 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", int32); return; } if (data_format == DataFormat_Hex) { ImSnprintf(out_buf, out_buf_size, "0x%08x", data); return; }
break; break;
} }
case ImGuiDataType_U32: case ImGuiDataType_U32:
{ {
uint32_t uint32 = 0; ImU32 data = 0;
EndianessCopy(&uint32, buf, size); EndiannessCopy(&data, buf, size);
if (data_format == DataFormat_Dec) { ImSnprintf(out_buf, out_buf_size, "%u", uint32); return; } 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", uint32); return; } if (data_format == DataFormat_Hex) { ImSnprintf(out_buf, out_buf_size, "0x%08x", data); return; }
break; break;
} }
case ImGuiDataType_S64: case ImGuiDataType_S64:
{ {
int64_t int64 = 0; ImS64 data = 0;
EndianessCopy(&int64, buf, size); EndiannessCopy(&data, buf, size);
if (data_format == DataFormat_Dec) { ImSnprintf(out_buf, out_buf_size, "%lld", (long long)int64); return; } 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)int64); return; } if (data_format == DataFormat_Hex) { ImSnprintf(out_buf, out_buf_size, "0x%016llx", (long long)data); return; }
break; break;
} }
case ImGuiDataType_U64: case ImGuiDataType_U64:
{ {
uint64_t uint64 = 0; ImU64 data = 0;
EndianessCopy(&uint64, buf, size); EndiannessCopy(&data, buf, size);
if (data_format == DataFormat_Dec) { ImSnprintf(out_buf, out_buf_size, "%llu", (long long)uint64); return; } 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)uint64); return; } if (data_format == DataFormat_Hex) { ImSnprintf(out_buf, out_buf_size, "0x%016llx", (long long)data); return; }
break; break;
} }
case ImGuiDataType_Float: case ImGuiDataType_Float:
{ {
float float32 = 0.0f; float data = 0.0f;
EndianessCopy(&float32, buf, size); EndiannessCopy(&data, buf, size);
if (data_format == DataFormat_Dec) { ImSnprintf(out_buf, out_buf_size, "%f", float32); return; } 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", float32); return; } if (data_format == DataFormat_Hex) { ImSnprintf(out_buf, out_buf_size, "%a", data); return; }
break; break;
} }
case ImGuiDataType_Double: case ImGuiDataType_Double:
{ {
double float64 = 0.0; double data = 0.0;
EndianessCopy(&float64, buf, size); EndiannessCopy(&data, buf, size);
if (data_format == DataFormat_Dec) { ImSnprintf(out_buf, out_buf_size, "%f", float64); return; } 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", float64); return; } if (data_format == DataFormat_Hex) { ImSnprintf(out_buf, out_buf_size, "%a", data); return; }
break; break;
} }
default:
case ImGuiDataType_COUNT: case ImGuiDataType_COUNT:
break; break;
} // Switch } // 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) { if (node->y > min_y) {
// raise min_y higher. // raise min_y higher.
// we've accounted for all waste up to min_y, // 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); waste_area += visited_width * (node->y - min_y);
min_y = node->y; min_y = node->y;
// the first time through, visited_width might be reduced // 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 // insert the new node into the right starting point, and
// let 'cur' point to the remaining nodes needing to be // let 'cur' point to the remaining nodes needing to be
// stiched back in // stitched back in
cur = *res.prev_link; cur = *res.prev_link;
if (cur->x < res.x) { 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) // - 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 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) // - 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_* // - 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 // stb_textedit.h - v1.14 - public domain - Sean Barrett
@@ -39,6 +40,7 @@
// //
// VERSION HISTORY // 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.14 (2021-07-11) page up/down, various fixes
// 1.13 (2019-02-07) fix bug in undo size management // 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 // 1.12 (2018-01-29) user can change STB_TEXTEDIT_KEYTYPE, fix redo to avoid crash
@@ -141,12 +143,14 @@
// with previous char) // with previous char)
// STB_TEXTEDIT_KEYTOTEXT(k) maps a keyboard input to an insertable character // STB_TEXTEDIT_KEYTOTEXT(k) maps a keyboard input to an insertable character
// (return type is int, -1 means not valid to insert) // (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_GETCHAR(obj,i) returns the i'th character of obj, 0-based
// STB_TEXTEDIT_NEWLINE the character returned by _GETCHAR() we recognize // STB_TEXTEDIT_NEWLINE the character returned by _GETCHAR() we recognize
// as manually wordwrapping for end-of-line positioning // as manually wordwrapping for end-of-line positioning
// //
// STB_TEXTEDIT_DELETECHARS(obj,i,n) delete n characters starting at i // 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 // 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_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 // 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 // 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 // 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, // 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 // if the STB_TEXTEDIT_KEYTOTEXT function is defined, selected keys are
// transformed into text and stb_textedit_text() is automatically called. // transformed into text and stb_textedit_text() is automatically called.
// //
// text: [DEAR IMGUI] added 2024-09 // text: (added 2025)
// call this to text inputs sent to the textfield. // 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 // When rendering, you can read the cursor position and selection state from
@@ -400,6 +413,16 @@ typedef struct
#define IMSTB_TEXTEDIT_memmove memmove #define IMSTB_TEXTEDIT_memmove memmove
#endif #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 // 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; StbTexteditRow r;
int n = STB_TEXTEDIT_STRINGLEN(str); 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.x0 = r.x1 = 0;
r.ymin = r.ymax = 0; r.ymin = r.ymax = 0;
r.num_chars = 0; r.num_chars = 0;
*out_side_on_line = 0;
// search rows to find one that straddles 'y' // search rows to find one that straddles 'y'
while (i < n) { 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 // below all text, return 'after' last character
if (i >= n) if (i >= n)
{
*out_side_on_line = 1;
return n; return n;
}
// check if it's before the beginning of the line // check if it's before the beginning of the line
if (x < r.x0) 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) { for (k=0; k < r.num_chars; k = IMSTB_TEXTEDIT_GETNEXTCHARINDEX(str, i + k) - i) {
float w = STB_TEXTEDIT_GETWIDTH(str, i, k); float w = STB_TEXTEDIT_GETWIDTH(str, i, k);
if (x < prev_x+w) { if (x < prev_x+w) {
*out_side_on_line = (k == 0) ? 0 : 1;
if (x < prev_x+w/2) if (x < prev_x+w/2)
return k+i; return k+i;
else 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 // 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) if (STB_TEXTEDIT_GETCHAR(str, i+r.num_chars-1) == STB_TEXTEDIT_NEWLINE)
return i+r.num_chars-1; return i+r.num_chars-1;
else 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 // 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 // goes off the top or bottom of the text
int side_on_line;
if( state->single_line ) if( state->single_line )
{ {
StbTexteditRow r; StbTexteditRow r;
@@ -478,16 +508,18 @@ static void stb_textedit_click(IMSTB_TEXTEDIT_STRING *str, STB_TexteditState *st
y = r.ymin; 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_start = state->cursor;
state->select_end = state->cursor; state->select_end = state->cursor;
state->has_preferred_x = 0; 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 // 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) static void stb_textedit_drag(IMSTB_TEXTEDIT_STRING *str, STB_TexteditState *state, float x, float y)
{ {
int p = 0; 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 // 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 // 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) if (state->select_start == state->select_end)
state->select_start = state->cursor; 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; 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); STB_TEXTEDIT_LAYOUTROW(&r, str, i);
if (n < i + r.num_chars) if (n < i + r.num_chars)
break; 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 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] break; // [DEAR IMGUI]
prev_start = i; prev_start = i;
@@ -648,15 +683,33 @@ static void stb_textedit_move_to_last(IMSTB_TEXTEDIT_STRING *str, STB_TexteditSt
} }
} }
// [DEAR IMGUI] // [DEAR IMGUI] Extracted this function so we can more easily add support for word-wrapping.
// Functions must be implemented for UTF8 support #ifndef STB_TEXTEDIT_MOVELINESTART
// Code in this file that uses those functions is modified for [DEAR IMGUI] and deviates from the original stb_textedit. static int stb_textedit_move_line_start(IMSTB_TEXTEDIT_STRING *str, STB_TexteditState *state, int cursor)
// There is not necessarily a '[DEAR IMGUI]' at the usage sites. {
#ifndef IMSTB_TEXTEDIT_GETPREVCHARINDEX if (state->single_line)
#define IMSTB_TEXTEDIT_GETPREVCHARINDEX(obj, idx) (idx - 1) 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 #endif
#ifndef IMSTB_TEXTEDIT_GETNEXTCHARINDEX #ifndef STB_TEXTEDIT_MOVELINEEND
#define IMSTB_TEXTEDIT_GETNEXTCHARINDEX(obj, idx) (idx + 1) 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 #endif
#ifdef STB_TEXTEDIT_IS_SPACE #ifdef STB_TEXTEDIT_IS_SPACE
@@ -668,9 +721,9 @@ static int is_word_boundary( IMSTB_TEXTEDIT_STRING *str, int idx )
#ifndef STB_TEXTEDIT_MOVEWORDLEFT #ifndef STB_TEXTEDIT_MOVEWORDLEFT
static int stb_textedit_move_to_word_previous( IMSTB_TEXTEDIT_STRING *str, int c ) static int stb_textedit_move_to_word_previous( IMSTB_TEXTEDIT_STRING *str, int c )
{ {
--c; // always move at least one character c = IMSTB_TEXTEDIT_GETPREVCHARINDEX( str, c ); // always move at least one character
while( c >= 0 && !is_word_boundary( str, c ) ) while (c >= 0 && !is_word_boundary(str, c))
--c; c = IMSTB_TEXTEDIT_GETPREVCHARINDEX(str, c);
if( c < 0 ) if( c < 0 )
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 ) static int stb_textedit_move_to_word_next( IMSTB_TEXTEDIT_STRING *str, int c )
{ {
const int len = STB_TEXTEDIT_STRINGLEN(str); 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 ) ) while( c < len && !is_word_boundary( str, c ) )
++c; c = IMSTB_TEXTEDIT_GETNEXTCHARINDEX(str, c);
if( c > len ) if( c > len )
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_clamp(str, state);
stb_textedit_delete_selection(str,state); stb_textedit_delete_selection(str,state);
// try to insert the characters // 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); stb_text_makeundo_insert(state, state->cursor, len);
state->cursor += len; state->cursor += len;
state->has_preferred_x = 0; 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 #define STB_TEXTEDIT_KEYTYPE int
#endif #endif
// API key: process text input
// [DEAR IMGUI] Added stb_textedit_text(), extracted out and called by stb_textedit_key() for backward compatibility. // [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) 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)) { 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_text_makeundo_replace(str, state, state->cursor, 1, 1);
STB_TEXTEDIT_DELETECHARS(str, state->cursor, 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->cursor += text_len;
state->has_preferred_x = 0; state->has_preferred_x = 0;
} }
} } else {
else {
stb_textedit_delete_selection(str, state); // implicitly clamps 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); stb_text_makeundo_insert(state, state->cursor, text_len);
state->cursor += text_len; state->cursor += text_len;
state->has_preferred_x = 0; state->has_preferred_x = 0;
@@ -771,6 +827,7 @@ retry:
switch (key) { switch (key) {
default: { default: {
#ifdef STB_TEXTEDIT_KEYTOTEXT #ifdef STB_TEXTEDIT_KEYTOTEXT
// This is not suitable for UTF-8 support.
int c = STB_TEXTEDIT_KEYTOTEXT(key); int c = STB_TEXTEDIT_KEYTOTEXT(key);
if (c > 0) { if (c > 0) {
IMSTB_TEXTEDIT_CHARTYPE ch = (IMSTB_TEXTEDIT_CHARTYPE)c; IMSTB_TEXTEDIT_CHARTYPE ch = (IMSTB_TEXTEDIT_CHARTYPE)c;
@@ -911,15 +968,16 @@ retry:
// [DEAR IMGUI] // [DEAR IMGUI]
// going down while being on the last line shouldn't bring us to that line end // 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) //if (STB_TEXTEDIT_GETCHAR(str, find.first_char + find.length - 1) != STB_TEXTEDIT_NEWLINE)
break; // break;
// now find character position down a row // now find character position down a row
state->cursor = start; state->cursor = start;
STB_TEXTEDIT_LAYOUTROW(&row, str, state->cursor); STB_TEXTEDIT_LAYOUTROW(&row, str, state->cursor);
x = row.x0; 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); float dx = STB_TEXTEDIT_GETWIDTH(str, start, i);
int next = IMSTB_TEXTEDIT_GETNEXTCHARINDEX(str, state->cursor);
#ifdef IMSTB_TEXTEDIT_GETWIDTH_NEWLINE #ifdef IMSTB_TEXTEDIT_GETWIDTH_NEWLINE
if (dx == IMSTB_TEXTEDIT_GETWIDTH_NEWLINE) if (dx == IMSTB_TEXTEDIT_GETWIDTH_NEWLINE)
break; break;
@@ -927,10 +985,13 @@ retry:
x += dx; x += dx;
if (x > goal_x) if (x > goal_x)
break; break;
state->cursor = IMSTB_TEXTEDIT_GETNEXTCHARINDEX(str, state->cursor); i += next - state->cursor;
state->cursor = next;
} }
stb_textedit_clamp(str, state); stb_textedit_clamp(str, state);
if (state->cursor == find.first_char + find.length)
str->LastMoveDirectionLR = ImGuiDir_Left;
state->has_preferred_x = 1; state->has_preferred_x = 1;
state->preferred_x = goal_x; state->preferred_x = goal_x;
@@ -980,8 +1041,9 @@ retry:
state->cursor = find.prev_first; state->cursor = find.prev_first;
STB_TEXTEDIT_LAYOUTROW(&row, str, state->cursor); STB_TEXTEDIT_LAYOUTROW(&row, str, state->cursor);
x = row.x0; 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); float dx = STB_TEXTEDIT_GETWIDTH(str, find.prev_first, i);
int next = IMSTB_TEXTEDIT_GETNEXTCHARINDEX(str, state->cursor);
#ifdef IMSTB_TEXTEDIT_GETWIDTH_NEWLINE #ifdef IMSTB_TEXTEDIT_GETWIDTH_NEWLINE
if (dx == IMSTB_TEXTEDIT_GETWIDTH_NEWLINE) if (dx == IMSTB_TEXTEDIT_GETWIDTH_NEWLINE)
break; break;
@@ -989,10 +1051,15 @@ retry:
x += dx; x += dx;
if (x > goal_x) if (x > goal_x)
break; break;
state->cursor = IMSTB_TEXTEDIT_GETNEXTCHARINDEX(str, state->cursor); i += next - state->cursor;
state->cursor = next;
} }
stb_textedit_clamp(str, state); 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->has_preferred_x = 1;
state->preferred_x = goal_x; state->preferred_x = goal_x;
@@ -1002,10 +1069,15 @@ retry:
// go to previous line // go to previous line
// (we need to scan previous line the hard way. maybe we could expose this as a new API function?) // (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; 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) while (prev_scan > 0)
--prev_scan; {
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.first_char = find.prev_first;
find.prev_first = prev_scan; find.prev_first = STB_TEXTEDIT_MOVELINESTART(str, state, prev_scan);
} }
break; break;
} }
@@ -1079,10 +1151,7 @@ retry:
case STB_TEXTEDIT_K_LINESTART: case STB_TEXTEDIT_K_LINESTART:
stb_textedit_clamp(str, state); stb_textedit_clamp(str, state);
stb_textedit_move_to_first(state); stb_textedit_move_to_first(state);
if (state->single_line) state->cursor = STB_TEXTEDIT_MOVELINESTART(str, state, state->cursor);
state->cursor = 0;
else while (state->cursor > 0 && STB_TEXTEDIT_GETCHAR(str, state->cursor-1) != STB_TEXTEDIT_NEWLINE)
--state->cursor;
state->has_preferred_x = 0; state->has_preferred_x = 0;
break; break;
@@ -1090,13 +1159,9 @@ retry:
case STB_TEXTEDIT_K_LINEEND2: case STB_TEXTEDIT_K_LINEEND2:
#endif #endif
case STB_TEXTEDIT_K_LINEEND: { case STB_TEXTEDIT_K_LINEEND: {
int n = STB_TEXTEDIT_STRINGLEN(str);
stb_textedit_clamp(str, state); stb_textedit_clamp(str, state);
stb_textedit_move_to_first(state); stb_textedit_move_to_last(str, state);
if (state->single_line) state->cursor = STB_TEXTEDIT_MOVELINEEND(str, state, state->cursor);
state->cursor = n;
else while (state->cursor < n && STB_TEXTEDIT_GETCHAR(str, state->cursor) != STB_TEXTEDIT_NEWLINE)
++state->cursor;
state->has_preferred_x = 0; state->has_preferred_x = 0;
break; break;
} }
@@ -1107,10 +1172,7 @@ retry:
case STB_TEXTEDIT_K_LINESTART | STB_TEXTEDIT_K_SHIFT: case STB_TEXTEDIT_K_LINESTART | STB_TEXTEDIT_K_SHIFT:
stb_textedit_clamp(str, state); stb_textedit_clamp(str, state);
stb_textedit_prep_selection_at_cursor(state); stb_textedit_prep_selection_at_cursor(state);
if (state->single_line) state->cursor = STB_TEXTEDIT_MOVELINESTART(str, state, state->cursor);
state->cursor = 0;
else while (state->cursor > 0 && STB_TEXTEDIT_GETCHAR(str, state->cursor-1) != STB_TEXTEDIT_NEWLINE)
--state->cursor;
state->select_end = state->cursor; state->select_end = state->cursor;
state->has_preferred_x = 0; state->has_preferred_x = 0;
break; break;
@@ -1119,13 +1181,9 @@ retry:
case STB_TEXTEDIT_K_LINEEND2 | STB_TEXTEDIT_K_SHIFT: case STB_TEXTEDIT_K_LINEEND2 | STB_TEXTEDIT_K_SHIFT:
#endif #endif
case STB_TEXTEDIT_K_LINEEND | STB_TEXTEDIT_K_SHIFT: { case STB_TEXTEDIT_K_LINEEND | STB_TEXTEDIT_K_SHIFT: {
int n = STB_TEXTEDIT_STRINGLEN(str);
stb_textedit_clamp(str, state); stb_textedit_clamp(str, state);
stb_textedit_prep_selection_at_cursor(state); stb_textedit_prep_selection_at_cursor(state);
if (state->single_line) state->cursor = STB_TEXTEDIT_MOVELINEEND(str, state, state->cursor);
state->cursor = n;
else while (state->cursor < n && STB_TEXTEDIT_GETCHAR(str, state->cursor) != STB_TEXTEDIT_NEWLINE)
++state->cursor;
state->select_end = state->cursor; state->select_end = state->cursor;
state->has_preferred_x = 0; state->has_preferred_x = 0;
break; break;
@@ -1300,7 +1358,7 @@ static void stb_text_undo(IMSTB_TEXTEDIT_STRING *str, STB_TexteditState *state)
// check type of recorded action: // check type of recorded action:
if (u.insert_length) { if (u.insert_length) {
// easy case: was a deletion, so we need to insert n characters // 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; 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) { if (r.insert_length) {
// easy case: need to insert n characters // 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; 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 // 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); 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 // 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); 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[0] = (float)x2;
q2[1] = (float)y2; q2[1] = (float)y2;
if (equal(q0,q1) || equal(q1,q2)) { if (equal(q0,q1) || equal(q1,q2)) {
x0 = (int)verts[i-1].x; x0 = (int)verts[i-1].x; //-V1048
y0 = (int)verts[i-1].y; y0 = (int)verts[i-1].y; //-V1048
x1 = (int)verts[i ].x; x1 = (int)verts[i ].x;
y1 = (int)verts[i ].y; y1 = (int)verts[i ].y;
if (y > STBTT_min(y0,y1) && y < STBTT_max(y0,y1) && x > STBTT_min(x0,x1)) { if (y > STBTT_min(y0,y1) && y < STBTT_max(y0,y1) && x > STBTT_min(x0,x1)) {
+6 -5
View File
@@ -38,11 +38,12 @@ std::vector<Analog> &games::bc::get_analogs() {
if (analogs.empty()) { if (analogs.empty()) {
analogs = GameAPI::Analogs::getAnalogs("Busou Shinki: Armored Princess Battle Conductor"); analogs = GameAPI::Analogs::getAnalogs("Busou Shinki: Armored Princess Battle Conductor");
GameAPI::Analogs::sortAnalogs( using GameAPI::Analogs::AnalogType;
&analogs,
"Stick X", GameAPI::Analogs::sortAnalogsWithType(&analogs, {
"Stick Y" { "Stick X", AnalogType::LinearCentered },
); { "Stick Y", AnalogType::LinearCentered }
});
} }
return analogs; return analogs;
+4
View File
@@ -65,6 +65,10 @@ namespace games::ccj {
libutils::load_library("libaio-iob2_video.dll"); libutils::load_library("libaio-iob2_video.dll");
libutils::load_library("win10actlog.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), detour::trampoline_try("execexe.dll", MAKEINTRESOURCE(11),
(void*)execexe_CreateFileW_hook,(void**)&execexe_CreateFileW_orig); (void*)execexe_CreateFileW_hook,(void**)&execexe_CreateFileW_orig);
+8 -7
View File
@@ -45,13 +45,14 @@ std::vector<Analog> &games::ccj::get_analogs() {
if (analogs.empty()) { if (analogs.empty()) {
analogs = GameAPI::Analogs::getAnalogs("Chase Chase Jokers"); analogs = GameAPI::Analogs::getAnalogs("Chase Chase Jokers");
GameAPI::Analogs::sortAnalogs( using GameAPI::Analogs::AnalogType;
&analogs,
"Joystick X", GameAPI::Analogs::sortAnalogsWithType(&analogs, {
"Joystick Y", { "Joystick X", AnalogType::LinearCentered },
"Trackball DX", { "Joystick Y", AnalogType::LinearCentered },
"Trackball DY" { "Trackball DX", AnalogType::LinearCentered },
); { "Trackball DY", AnalogType::LinearCentered }
});
} }
return analogs; return analogs;
+58 -11
View File
@@ -23,6 +23,7 @@
#include "p3io/usbmem.h" #include "p3io/usbmem.h"
#include "p4io/p4io.h" #include "p4io/p4io.h"
#include <cstring>
using namespace acioemu; using namespace acioemu;
@@ -53,9 +54,9 @@ namespace games::ddr {
return SendMessage_real(hWnd, Msg, wParam, lParam); return SendMessage_real(hWnd, Msg, wParam, lParam);
} }
bool contains_only_ascii(const std::string& str) { bool contains_only_ascii(const std::wstring& str) {
for (auto c: str) { for (auto c: str) {
if (static_cast<unsigned char>(c) > 127) { if (c > 127) {
return false; return false;
} }
} }
@@ -80,24 +81,24 @@ namespace games::ddr {
} }
if (fileutils::dir_exists(dir)) { if (fileutils::dir_exists(dir)) {
log_info("ddr", "looking for codecs in this directory: {}", dir.string()); log_info("ddr", "looking for codecs in this directory: {}", dir);
} else { } else {
log_info("ddr", "codecs directory not found: {}", dir.string()); log_info("ddr", "codecs directory not found: {}", dir);
return; return;
} }
for (const auto &file : std::filesystem::directory_iterator(dir)) { for (const auto &file : std::filesystem::directory_iterator(dir)) {
const auto &filename = file.path().filename(); const auto &filename = file.path().filename();
const auto extension = strtolower(filename.extension().string()); const auto extension = filename.extension().wstring();
if (extension != ".dll") { if (wcsicmp(extension.c_str(), L".dll") != 0) {
continue; continue;
} }
log_info("ddr", "found DLL: {}, size: {} bytes", filename.string(), file.file_size()); log_info("ddr", "found DLL: {}, size: {} bytes", filename, file.file_size());
if (filename == "k-clvsd.dll" || filename.string().find("xactengine") == 0) { if (filename == "k-clvsd.dll" || filename.wstring().find(L"xactengine") == 0) {
const std::wstring wcmd = L"regsvr32.exe /s \"" + file.path().wstring() + L"\""; const std::wstring wcmd = L"regsvr32.exe /s \"" + file.path().wstring() + L"\"";
const std::string cmd = "regsvr32.exe /s \"" + file.path().string() + "\""; const std::string cmd = fmt::format("regsvr32.exe /s \"{}\"", file.path());
int result = 0; int result = 0;
std::thread t([wcmd, &result]() { std::thread t([wcmd, &result]() {
@@ -111,12 +112,12 @@ namespace games::ddr {
log_warning("ddr", "`{}` failed, returned {}", cmd, result); log_warning("ddr", "`{}` failed, returned {}", cmd, result);
deferredlogs::defer_error_messages({ deferredlogs::defer_error_messages({
"DDR codec registration failure", "DDR codec registration failure",
fmt::format(" {} failed to register with error {}", filename.string(), result)}); fmt::format(" {} failed to register with error {}", filename, result)});
} }
static std::once_flag printed; static std::once_flag printed;
std::call_once(printed, [file]() { std::call_once(printed, [file]() {
if (!contains_only_ascii(file.path().string())) { if (!contains_only_ascii(file.path().wstring())) {
log_warning( log_warning(
"ddr", "ddr",
"BAD PATH ERROR\n\n" "BAD PATH ERROR\n\n"
@@ -290,4 +291,50 @@ namespace games::ddr {
// dispose device hook // dispose device hook
devicehook_dispose(); devicehook_dispose();
} }
static void get_analog_xy_axis(Analog &analog, bool &less, bool &more) {
if (!analog.isSet()) {
return;
}
const auto value = GameAPI::Analogs::getState(RI_MGR, analog);
// stepmania linux source:
// https://github.com/stepmania/stepmania/blob/d55acb1ba26f1c5b5e3048d6d6c0bd116625216f/src/arch/InputHandler/InputHandler_Linux_Event.cpp#L410
// for example, value cap with range [0, 255]:
// 127 = 0.498, neutral
// 128 = 0.502, both
// apparently some adapters report values like above; this obviously makes a lot of
// assumptions about bit width of the HID value cap and how the adapter reports
// neutral/both values, but this is good enough for parity with stepmania and its many forks
if (0.5001f < value && value < 0.75f) {
less |= true;
more |= true;
} else if (value <= 0.25f) {
less |= true;
} else if (value >= 0.75f) {
more |= true;
}
}
void get_analog_x_axis(int player, bool &left, bool &right) {
if (player != 1 && player != 2) {
log_fatal("ddr", "invalid player number: {}, expected 1 or 2", player);
return;
}
auto &analog = get_analogs().at(player == 1 ? Analogs::P1_LEFT_RIGHT : Analogs::P2_LEFT_RIGHT);
get_analog_xy_axis(analog, left, right);
}
void get_analog_y_axis(int player, bool &up, bool &down) {
if (player != 1 && player != 2) {
log_fatal("ddr", "invalid player number: {}, expected 1 or 2", player);
return;
}
auto &analog = get_analogs().at(player == 1 ? Analogs::P1_UP_DOWN : Analogs::P2_UP_DOWN);
get_analog_xy_axis(analog, up, down);
}
} }
+3
View File
@@ -23,4 +23,7 @@ namespace games::ddr {
private: private:
void register_codecs(); void register_codecs();
}; };
void get_analog_x_axis(int player, bool &left, bool &right);
void get_analog_y_axis(int player, bool &up, bool &down);
} }
+44
View File
@@ -35,6 +35,50 @@ std::vector<Button> &games::ddr::get_buttons() {
return analogs; return analogs;
} }
std::string games::ddr::get_buttons_help() {
// keep to max 100 characters wide
return
"For DDR pad arrows, double check that simultaneous Left+Right and Up+Down can be detected.\n"
"You should boot the game, enter test menu, and use Foot Panel Check.\n\n"
"When mapping arrows, try the following in order:\n\n"
" 1. If your controller uses face buttons (A/B/X/Y), bind them here.\n"
" 2. If your controller supports XInput, use that.\n"
" 3. If your controller uses analog axis for arrows, try Analogs tab.\n"
" 4. Otherwise, you will need to use remapping software or get a better adapter."
;
}
std::vector<Analog> &games::ddr::get_analogs() {
static std::vector<Analog> analogs;
if (analogs.empty()) {
analogs = GameAPI::Analogs::getAnalogs("Dance Dance Revolution");
using namespace GameAPI::Analogs;
GameAPI::Analogs::sortAnalogsWithType(&analogs, {
{ "P1 Left-Right (Axis Fix)", AnalogType::LinearCentered },
{ "P1 Up-Down (Axis Fix)", AnalogType::LinearCentered },
{ "P2 Left-Right (Axis Fix)", AnalogType::LinearCentered },
{ "P2 Up-Down (Axis Fix)", AnalogType::LinearCentered }
});
}
return analogs;
}
std::string games::ddr::get_analogs_help() {
// keep to max 100 characters wide
return
"Only use this if your DDR pad outputs analog axis for arrows.\n\n"
"If the pad uses face buttons (A/B/X/Y), use Buttons tab instead.\n\n"
"Spice will treat values <=25% as Left, >=75% as Right, ~=50% as neutral,\n"
"and value between 50% and 75% as both arrows.\n\n"
"This is the classic Stepmania \"Axis Fix\" which may or may not work with\n"
"your dance pad or your adapter."
;
}
std::vector<Light> &games::ddr::get_lights() { std::vector<Light> &games::ddr::get_lights() {
static std::vector<Light> lights; static std::vector<Light> lights;
+13
View File
@@ -33,6 +33,16 @@ namespace games::ddr {
}; };
} }
// all analogs in correct order
namespace Analogs {
enum {
P1_LEFT_RIGHT,
P1_UP_DOWN,
P2_LEFT_RIGHT,
P2_UP_DOWN
};
}
// all lights in correct order // all lights in correct order
namespace Lights { namespace Lights {
enum { enum {
@@ -177,5 +187,8 @@ namespace games::ddr {
// getters // getters
std::vector<Button> &get_buttons(); std::vector<Button> &get_buttons();
std::string get_buttons_help();
std::string get_analogs_help();
std::vector<Analog> &get_analogs();
std::vector<Light> &get_lights(); std::vector<Light> &get_lights();
} }
+49 -1
View File
@@ -498,32 +498,80 @@ int games::ddr::DDRP3IOHandle::device_io(
// shift table // shift table
static size_t shift_table[] = { static size_t shift_table[] = {
30, 28, 29, 8, 9, 10, 11, 12, 24, 25, 14, 15, 16, 17, 18, 19, 20, 26, 27, 22, 23 30, 28, 29, // service, test, coin
8, // p1 start
9, 10, 11, 12, // p1 panel
24, 25, 14, 15, // p1 menu
16, // p2 start
17, 18, 19, 20, // p2 panel
26, 27, 22, 23 // p2 menu
}; };
static size_t button_table[] = { static size_t button_table[] = {
Buttons::SERVICE, Buttons::SERVICE,
Buttons::TEST, Buttons::TEST,
Buttons::COIN_MECH, Buttons::COIN_MECH,
Buttons::P1_START, Buttons::P1_START,
Buttons::P1_PANEL_UP, Buttons::P1_PANEL_UP,
Buttons::P1_PANEL_DOWN, Buttons::P1_PANEL_DOWN,
Buttons::P1_PANEL_LEFT, Buttons::P1_PANEL_LEFT,
Buttons::P1_PANEL_RIGHT, Buttons::P1_PANEL_RIGHT,
Buttons::P1_MENU_UP, Buttons::P1_MENU_UP,
Buttons::P1_MENU_DOWN, Buttons::P1_MENU_DOWN,
Buttons::P1_MENU_LEFT, Buttons::P1_MENU_LEFT,
Buttons::P1_MENU_RIGHT, Buttons::P1_MENU_RIGHT,
Buttons::P2_START, Buttons::P2_START,
Buttons::P2_PANEL_UP, Buttons::P2_PANEL_UP,
Buttons::P2_PANEL_DOWN, Buttons::P2_PANEL_DOWN,
Buttons::P2_PANEL_LEFT, Buttons::P2_PANEL_LEFT,
Buttons::P2_PANEL_RIGHT, Buttons::P2_PANEL_RIGHT,
Buttons::P2_MENU_UP, Buttons::P2_MENU_UP,
Buttons::P2_MENU_DOWN, Buttons::P2_MENU_DOWN,
Buttons::P2_MENU_LEFT, Buttons::P2_MENU_LEFT,
Buttons::P2_MENU_RIGHT, Buttons::P2_MENU_RIGHT,
}; };
// get analogs
struct {
bool up;
bool down;
bool left;
bool right;
} analog_values[2] = {};
games::ddr::get_analog_x_axis(1, analog_values[0].left, analog_values[0].right);
games::ddr::get_analog_y_axis(1, analog_values[0].up, analog_values[0].down);
games::ddr::get_analog_x_axis(2, analog_values[1].left, analog_values[1].right);
games::ddr::get_analog_y_axis(2, analog_values[1].up, analog_values[1].down);
if (analog_values[0].up) {
controls |= 1 << 9;
}
if (analog_values[0].down) {
controls |= 1 << 10;
}
if (analog_values[0].left) {
controls |= 1 << 11;
}
if (analog_values[0].right) {
controls |= 1 << 12;
}
if (analog_values[1].up) {
controls |= 1 << 17;
}
if (analog_values[1].down) {
controls |= 1 << 18;
}
if (analog_values[1].left) {
controls |= 1 << 19;
}
if (analog_values[1].right) {
controls |= 1 << 20;
}
// update states // update states
auto &buttons = get_buttons(); auto &buttons = get_buttons();
size_t count = 0; size_t count = 0;
+8 -7
View File
@@ -35,13 +35,14 @@ std::vector<Analog> &games::ftt::get_analogs() {
if (analogs.empty()) { if (analogs.empty()) {
analogs = GameAPI::Analogs::getAnalogs("FutureTomTom"); analogs = GameAPI::Analogs::getAnalogs("FutureTomTom");
GameAPI::Analogs::sortAnalogs( using GameAPI::Analogs::AnalogType;
&analogs,
"Pad 1", GameAPI::Analogs::sortAnalogsWithType(&analogs, {
"Pad 2", { "Pad 1", AnalogType::LinearPositive },
"Pad 3", { "Pad 2", AnalogType::LinearPositive },
"Pad 4" { "Pad 3", AnalogType::LinearPositive },
); { "Pad 4", AnalogType::LinearPositive }
});
} }
return analogs; return analogs;
+1
View File
@@ -174,6 +174,7 @@ namespace games::gitadora {
static_assert(sizeof(AIO_IOB5_Y32D) == 0x2e8); static_assert(sizeof(AIO_IOB5_Y32D) == 0x2e8);
static_assert(sizeof(AIO_IOB2_BI2X_AC1) == 0x4570); static_assert(sizeof(AIO_IOB2_BI2X_AC1) == 0x4570);
static_assert(sizeof(AIO_IOB2_BI2X_WRFIRM) == 0x20f48); static_assert(sizeof(AIO_IOB2_BI2X_WRFIRM) == 0x20f48);
static_assert(sizeof(AIO_IOB2_BI2X_AC1__DEVSTATUS) == 0x2C8);
/* /*
* typedefs * typedefs
+12 -10
View File
@@ -86,19 +86,21 @@ std::vector<Button> &games::gitadora::get_buttons() {
std::vector<Analog> &games::gitadora::get_analogs() { std::vector<Analog> &games::gitadora::get_analogs() {
static std::vector<Analog> analogs; static std::vector<Analog> analogs;
using namespace GameAPI::Analogs;
if (analogs.empty()) { if (analogs.empty()) {
analogs = GameAPI::Analogs::getAnalogs("GitaDora"); analogs = GameAPI::Analogs::getAnalogs("GitaDora");
GameAPI::Analogs::sortAnalogs(&analogs, GameAPI::Analogs::sortAnalogsWithType(&analogs, {
"Guitar P1 Wail X", {"Guitar P1 Wail X", AnalogType::LinearCentered},
"Guitar P1 Wail Y", {"Guitar P1 Wail Y", AnalogType::LinearCentered},
"Guitar P1 Wail Z", {"Guitar P1 Wail Z", AnalogType::LinearCentered},
"Guitar P1 Knob", {"Guitar P1 Knob", AnalogType::Circular},
"Guitar P2 Wail X", {"Guitar P2 Wail X", AnalogType::LinearCentered},
"Guitar P2 Wail Y", {"Guitar P2 Wail Y", AnalogType::LinearCentered},
"Guitar P2 Wail Z", {"Guitar P2 Wail Z", AnalogType::LinearCentered},
"Guitar P2 Knob" {"Guitar P2 Knob", AnalogType::Circular}
); });
} }
return analogs; return analogs;
+1 -1
View File
@@ -1,6 +1,6 @@
#include "camera.h" #include "camera.h"
#if SPICE64 #if SPICE64 && !SPICE_XP
#include <d3d9.h> #include <d3d9.h>
#include "mf_wrappers.h" #include "mf_wrappers.h"
+1 -1
View File
@@ -1,6 +1,6 @@
#pragma once #pragma once
#if SPICE64 #if SPICE64 && !SPICE_XP
#include <vector> #include <vector>
#include "games/iidx/local_camera.h" #include "games/iidx/local_camera.h"
+56 -11
View File
@@ -30,6 +30,7 @@
#include "util/memutils.h" #include "util/memutils.h"
#include "util/sigscan.h" #include "util/sigscan.h"
#include "util/socd_cleaner.h" #include "util/socd_cleaner.h"
#include "util/sysutils.h"
#include "util/time.h" #include "util/time.h"
#include "util/utils.h" #include "util/utils.h"
#include "launcher/signal.h" #include "launcher/signal.h"
@@ -66,6 +67,8 @@ namespace games::iidx {
std::optional<std::string> SOUND_OUTPUT_DEVICE = std::nullopt; std::optional<std::string> SOUND_OUTPUT_DEVICE = std::nullopt;
std::optional<std::string> SOUND_OUTPUT_DEVICE_IN_EFFECT = std::nullopt; std::optional<std::string> SOUND_OUTPUT_DEVICE_IN_EFFECT = std::nullopt;
std::optional<std::string> ASIO_DRIVER = std::nullopt; std::optional<std::string> ASIO_DRIVER = std::nullopt;
uint32_t TT_DELAY_P1 = 0;
uint32_t TT_DELAY_P2 = 0;
uint8_t DIGITAL_TT_SENS = 4; uint8_t DIGITAL_TT_SENS = 4;
std::optional<std::string> SUBSCREEN_OVERLAY_SIZE = std::nullopt; std::optional<std::string> SUBSCREEN_OVERLAY_SIZE = std::nullopt;
std::optional<std::string> SCREEN_MODE = std::nullopt; std::optional<std::string> SCREEN_MODE = std::nullopt;
@@ -423,6 +426,14 @@ namespace games::iidx {
init_legacy_camera_hook(FLIP_CAMS); init_legacy_camera_hook(FLIP_CAMS);
} }
#if SPICE64
if (TDJ_MODE) {
sysutils::hook_EnumDisplayDevicesA();
}
#endif
// init cfgmgr32 hooks // init cfgmgr32 hooks
cfgmgr32hook_init(avs::game::DLL_INSTANCE); cfgmgr32hook_init(avs::game::DLL_INSTANCE);
} }
@@ -486,28 +497,28 @@ namespace games::iidx {
log_fatal("iidx", "BAD MODEL NAME ERROR - TDJ specified, must be LDJ instead"); 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) { if (!GRAPHICS_WINDOWED && D3D9_ADAPTER.has_value() && TDJ_MODE) {
IIDX_TDJ_MONITOR_WARNING = true; IIDX_TDJ_MONITOR_WARNING = true;
log_warning( log_warning(
"iidx", "iidx",
"\n\n!!! using -monitor option with TDJ is NOT recommended due to known !!!\n" "\n\n"
"!!! compatibility issues with the game !!!\n" "!!! using -dxmainadapter option with TDJ is NOT recommended !!!\n"
"!!! due to known compatibility issues with the game !!!\n"
"!!! !!!\n" "!!! !!!\n"
"!!! * game may launch in wrong resolution or refresh rate !!!\n" "!!! * game may launch in wrong resolution or refresh rate !!!\n"
"!!! * touch / mouse input may stop working in subscreen / overlay !!!\n" "!!! * touch / mouse input may stop working in subscreen / overlay !!!\n"
"!!! !!!\n" "!!! !!!\n"
"!!! recommendation is to NOT use -monitor and instead set the !!!\n" "!!! recommendation is to use the Change Main Monitor (-mainmonitor) !!!\n"
"!!! primary monitor in Windows settings before launching the game !!!\n\n" "!!! option instead of -dxmainadapter !!!\n\n"
); );
deferredlogs::defer_error_messages({ 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:", " due to known compatibility issues with the game:",
" * game may launch in wrong resolution or refresh rate", " * game may launch in wrong resolution or refresh rate",
" * touch / mouse input may stop working in subscreen / overlay", " * touch / mouse input may stop working in subscreen / overlay",
" recommended fix is to NOT use -monitor and instead set the primary", " use Change Main Monitor (-mainmonitor) option instead"
" monitor in Windows settings before launching the game"
}); });
} }
@@ -772,7 +783,42 @@ namespace games::iidx {
} }
// return higher 8 bit // return higher 8 bit
return (uint8_t) (ret_value >> 2); uint8_t result = (uint8_t) (ret_value >> 2);
// delay
if ((player == 0 && TT_DELAY_P1 > 0) ||
(player == 1 && TT_DELAY_P2 > 0)) {
static std::queue<std::pair<double, uint8_t>> delay_queue[2];
auto &queue = delay_queue[player];
const auto max_delta_ms =
static_cast<double>((player == 0) ? TT_DELAY_P1 : TT_DELAY_P2);
// always push a new value
const auto now = get_performance_milliseconds();
queue.push(std::make_pair(now, result));
// drain the queue down to reasonable length to prevent unconstrained growth
// this would accommodate 1 second at ~1000Hz
// (in reality all three iidx I/O emulation runs well under 500Hz)
while (queue.size() > 1024) {
queue.pop();
}
// pop until we find one that falls just under the time threshold
while (!queue.empty()) {
const auto delta_t = now - queue.front().first;
if (delta_t <= max_delta_ms) {
break;
}
queue.pop();
}
result = queue.front().second;
}
return result;
} }
unsigned char get_slider(uint8_t slider) { unsigned char get_slider(uint8_t slider) {
@@ -906,8 +952,7 @@ namespace games::iidx {
log_warning( log_warning(
"iidx", "iidx",
"Failed to apply ASIO compatibility fix for iidx32+. " "Failed to apply ASIO compatibility fix for iidx32+. "
"Unless patches are applied, your ASIO device ({}) may hang or fail to work", "Unless patches are applied, your ASIO device may hang or fail to work");
ASIO_DRIVER->c_str());
} else { } else {
log_info( log_info(
"iidx", "iidx",
+2
View File
@@ -31,6 +31,8 @@ namespace games::iidx {
extern bool NATIVE_TOUCH; extern bool NATIVE_TOUCH;
extern std::optional<std::string> SOUND_OUTPUT_DEVICE; extern std::optional<std::string> SOUND_OUTPUT_DEVICE;
extern std::optional<std::string> ASIO_DRIVER; extern std::optional<std::string> ASIO_DRIVER;
extern uint32_t TT_DELAY_P1;
extern uint32_t TT_DELAY_P2;
extern uint8_t DIGITAL_TT_SENS; extern uint8_t DIGITAL_TT_SENS;
extern std::optional<std::string> SUBSCREEN_OVERLAY_SIZE; extern std::optional<std::string> SUBSCREEN_OVERLAY_SIZE;
extern std::optional<std::string> SCREEN_MODE; extern std::optional<std::string> SCREEN_MODE;
+11 -10
View File
@@ -63,16 +63,17 @@ std::vector<Analog> &games::iidx::get_analogs() {
if (analogs.empty()) { if (analogs.empty()) {
analogs = GameAPI::Analogs::getAnalogs("Beatmania IIDX"); analogs = GameAPI::Analogs::getAnalogs("Beatmania IIDX");
GameAPI::Analogs::sortAnalogs( using namespace GameAPI::Analogs;
&analogs,
"Turntable P1", GameAPI::Analogs::sortAnalogsWithType(&analogs, {
"Turntable P2", { "Turntable P1", AnalogType::Circular },
"VEFX", { "Turntable P2", AnalogType::Circular },
"Low-EQ", { "VEFX", AnalogType::LinearPositive },
"Hi-EQ", { "Low-EQ", AnalogType::LinearPositive },
"Filter", { "Hi-EQ", AnalogType::LinearPositive },
"Play Volume" { "Filter", AnalogType::LinearPositive },
); { "Play Volume", AnalogType::LinearPositive }
});
} }
return analogs; return analogs;
} }
+1 -1
View File
@@ -1,6 +1,6 @@
#include "games/iidx/local_camera.h" #include "games/iidx/local_camera.h"
#if SPICE64 #if SPICE64 && !SPICE_XP
#include "util/logging.h" #include "util/logging.h"
#include "util/utils.h" #include "util/utils.h"
+1 -1
View File
@@ -1,6 +1,6 @@
#pragma once #pragma once
#if SPICE64 #if SPICE64 && !SPICE_XP
#include <d3d9.h> #include <d3d9.h>
#include <dxva2api.h> #include <dxva2api.h>
+50 -46
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<Analog> &> analogs;
static robin_hood::unordered_map<std::string, std::vector<Light> &> lights; static robin_hood::unordered_map<std::string, std::vector<Light> &> lights;
static robin_hood::unordered_map<std::string, std::vector<Option>> options; static robin_hood::unordered_map<std::string, std::vector<Option>> options;
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() { static void initialize() {
@@ -68,7 +68,7 @@ namespace games {
buttons_help.insert({ bbc, bbc::get_buttons_help() }); buttons_help.insert({ bbc, bbc::get_buttons_help() });
analogs.insert({ bbc, bbc::get_analogs() }); analogs.insert({ bbc, bbc::get_analogs() });
lights.insert({ bbc, bbc::get_lights() }); lights.insert({ bbc, bbc::get_lights() });
file_hints[bbc].emplace_back("bsch.dll"); file_hints[bbc].push_back({"bsch.dll"});
// hpm // hpm
const std::string hpm("HELLO! Pop'n Music"); const std::string hpm("HELLO! Pop'n Music");
@@ -76,32 +76,35 @@ namespace games {
buttons.insert({ hpm, hpm::get_buttons() }); buttons.insert({ hpm, hpm::get_buttons() });
buttons_help.insert({ hpm, hpm::get_buttons_help() }); buttons_help.insert({ hpm, hpm::get_buttons_help() });
lights.insert({ hpm, hpm::get_lights() }); lights.insert({ hpm, hpm::get_lights() });
file_hints[hpm].emplace_back("popn.dll"); file_hints[hpm].push_back({"popn.dll", "pdh.dll"});
// bs // bs
const std::string bs("Beatstream"); const std::string bs("Beatstream");
games.push_back(bs); games.push_back(bs);
buttons.insert({ bs, bs::get_buttons() }); buttons.insert({ bs, bs::get_buttons() });
lights.insert({ bs, bs::get_lights() }); lights.insert({ bs, bs::get_lights() });
file_hints[bs].emplace_back("beatstream.dll"); file_hints[bs].push_back({"beatstream.dll"});
file_hints[bs].emplace_back("beatstream1.dll"); file_hints[bs].push_back({"beatstream1.dll"});
file_hints[bs].emplace_back("beatstream2.dll"); file_hints[bs].push_back({"beatstream2.dll"});
// ddr // ddr
const std::string ddr("Dance Dance Revolution"); const std::string ddr("Dance Dance Revolution");
games.push_back(ddr); games.push_back(ddr);
buttons.insert({ ddr, ddr::get_buttons() }); buttons.insert({ ddr, ddr::get_buttons() });
lights.insert({ ddr, ddr::get_lights() }); lights.insert({ ddr, ddr::get_lights() });
file_hints[ddr].emplace_back("ddr.dll"); buttons_help.insert({ ddr, ddr::get_buttons_help() });
file_hints[ddr].emplace_back("mdxja_945.dll"); analogs_help.insert({ ddr, ddr::get_analogs_help() });
file_hints[ddr].emplace_back("arkmdxp3.dll"); analogs.insert({ ddr, ddr::get_analogs() });
file_hints[ddr].push_back({"ddr.dll"});
file_hints[ddr].push_back({"mdxja_945.dll"});
file_hints[ddr].push_back({"arkmdxp3.dll"});
// dea // dea
const std::string dea("Dance Evolution"); const std::string dea("Dance Evolution");
games.push_back(dea); games.push_back(dea);
buttons.insert({ dea, dea::get_buttons() }); buttons.insert({ dea, dea::get_buttons() });
lights.insert({ dea, dea::get_lights() }); lights.insert({ dea, dea::get_lights() });
file_hints[dea].emplace_back("arkkdm.dll"); file_hints[dea].push_back({"arkkdm.dll"});
// gitadora // gitadora
const std::string gitadora("GitaDora"); const std::string gitadora("GitaDora");
@@ -111,7 +114,7 @@ namespace games {
analogs_help.insert({ gitadora, gitadora::get_analogs_help() }); analogs_help.insert({ gitadora, gitadora::get_analogs_help() });
analogs.insert({ gitadora, gitadora::get_analogs() }); analogs.insert({ gitadora, gitadora::get_analogs() });
lights.insert({ gitadora, gitadora::get_lights() }); lights.insert({ gitadora, gitadora::get_lights() });
file_hints[gitadora].emplace_back("gdxg.dll"); file_hints[gitadora].push_back({"gdxg.dll"});
// iidx // iidx
const std::string iidx("Beatmania IIDX"); const std::string iidx("Beatmania IIDX");
@@ -120,7 +123,7 @@ namespace games {
buttons_help.insert({ iidx, iidx::get_buttons_help() }); buttons_help.insert({ iidx, iidx::get_buttons_help() });
analogs.insert({ iidx, iidx::get_analogs() }); analogs.insert({ iidx, iidx::get_analogs() });
lights.insert({ iidx, iidx::get_lights() }); lights.insert({ iidx, iidx::get_lights() });
file_hints[iidx].emplace_back("bm2dx.dll"); file_hints[iidx].push_back({"bm2dx.dll"});
// jb // jb
const std::string jb("Jubeat"); const std::string jb("Jubeat");
@@ -128,7 +131,7 @@ namespace games {
buttons.insert({ jb, jb::get_buttons() }); buttons.insert({ jb, jb::get_buttons() });
buttons_help.insert({ jb, jb::get_buttons_help() }); buttons_help.insert({ jb, jb::get_buttons_help() });
lights.insert({ jb, jb::get_lights() }); lights.insert({ jb, jb::get_lights() });
file_hints[jb].emplace_back("jubeat.dll"); file_hints[jb].push_back({"jubeat.dll"});
// mga // mga
const std::string mga("Metal Gear"); const std::string mga("Metal Gear");
@@ -136,7 +139,7 @@ namespace games {
buttons.insert({ mga, mga::get_buttons() }); buttons.insert({ mga, mga::get_buttons() });
analogs.insert({ mga, mga::get_analogs() }); analogs.insert({ mga, mga::get_analogs() });
lights.insert({ mga, mga::get_lights() }); lights.insert({ mga, mga::get_lights() });
file_hints[mga].emplace_back("launch.dll"); file_hints[mga].push_back({"launch.dll"});
// museca // museca
const std::string museca("Museca"); const std::string museca("Museca");
@@ -145,7 +148,7 @@ namespace games {
buttons_help.insert({ museca, museca::get_buttons_help() }); buttons_help.insert({ museca, museca::get_buttons_help() });
analogs.insert({ museca, museca::get_analogs() }); analogs.insert({ museca, museca::get_analogs() });
lights.insert({ museca, museca::get_lights() }); lights.insert({ museca, museca::get_lights() });
file_hints[museca].emplace_back("museca.dll"); file_hints[museca].push_back({"museca.dll"});
// nost // nost
const std::string nost("Nostalgia"); const std::string nost("Nostalgia");
@@ -154,7 +157,7 @@ namespace games {
buttons_help.insert({ nost, nost::get_buttons_help() }); buttons_help.insert({ nost, nost::get_buttons_help() });
analogs.insert({ nost, nost::get_analogs() }); analogs.insert({ nost, nost::get_analogs() });
lights.insert({ nost, nost::get_lights() }); lights.insert({ nost, nost::get_lights() });
file_hints[nost].emplace_back("nostalgia.dll"); file_hints[nost].push_back({"nostalgia.dll"});
// popn // popn
const std::string popn("Pop'n Music"); const std::string popn("Pop'n Music");
@@ -162,41 +165,42 @@ namespace games {
buttons.insert({ popn, popn::get_buttons() }); buttons.insert({ popn, popn::get_buttons() });
buttons_help.insert({ popn, popn::get_buttons_help() }); buttons_help.insert({ popn, popn::get_buttons_help() });
lights.insert({ popn, popn::get_lights() }); lights.insert({ popn, popn::get_lights() });
file_hints[popn].emplace_back("popn19.dll"); file_hints[popn].push_back({"popn19.dll"});
file_hints[popn].emplace_back("popn20.dll"); file_hints[popn].push_back({"popn20.dll"});
file_hints[popn].emplace_back("popn21.dll"); file_hints[popn].push_back({"popn21.dll"});
file_hints[popn].emplace_back("popn22.dll"); file_hints[popn].push_back({"popn22.dll"});
file_hints[popn].emplace_back("popn23.dll"); file_hints[popn].push_back({"popn23.dll"});
file_hints[popn].emplace_back("popn24.dll"); file_hints[popn].push_back({"popn24.dll"});
file_hints[popn].emplace_back("popn25.dll"); file_hints[popn].push_back({"popn25.dll"});
file_hints[popn].push_back({"popn.dll", "libaio-iob2_video.dll"});
// qma // qma
const std::string qma("Quiz Magic Academy"); const std::string qma("Quiz Magic Academy");
games.push_back(qma); games.push_back(qma);
buttons.insert({ qma, qma::get_buttons() }); buttons.insert({ qma, qma::get_buttons() });
lights.insert({ qma, qma::get_lights() }); lights.insert({ qma, qma::get_lights() });
file_hints[qma].emplace_back("client.dll"); file_hints[qma].push_back({"client.dll"});
// rb // rb
const std::string rb("Reflec Beat"); const std::string rb("Reflec Beat");
games.push_back(rb); games.push_back(rb);
buttons.insert({ rb, rb::get_buttons() }); buttons.insert({ rb, rb::get_buttons() });
lights.insert({ rb, rb::get_lights() }); lights.insert({ rb, rb::get_lights() });
file_hints[rb].emplace_back("reflecbeat.dll"); file_hints[rb].push_back({"reflecbeat.dll"});
// shogikai // shogikai
std::string shogikai("Tenkaichi Shogikai"); std::string shogikai("Tenkaichi Shogikai");
games.push_back(shogikai); games.push_back(shogikai);
buttons.insert({ shogikai, shogikai::get_buttons() }); buttons.insert({ shogikai, shogikai::get_buttons() });
lights.insert({ shogikai, shogikai::get_lights() }); lights.insert({ shogikai, shogikai::get_lights() });
file_hints[shogikai].emplace_back("shogi_engine.dll"); file_hints[shogikai].push_back({"shogi_engine.dll"});
// rf3d // rf3d
const std::string rf3d("Road Fighters 3D"); const std::string rf3d("Road Fighters 3D");
games.push_back(rf3d); games.push_back(rf3d);
buttons.insert({ rf3d, rf3d::get_buttons() }); buttons.insert({ rf3d, rf3d::get_buttons() });
analogs.insert({ rf3d, rf3d::get_analogs() }); analogs.insert({ rf3d, rf3d::get_analogs() });
file_hints[rf3d].emplace_back("jgt.dll"); file_hints[rf3d].push_back({"jgt.dll"});
// sc // sc
const std::string sc("Steel Chronicle"); const std::string sc("Steel Chronicle");
@@ -204,7 +208,7 @@ namespace games {
buttons.insert({ sc, sc::get_buttons() }); buttons.insert({ sc, sc::get_buttons() });
analogs.insert({ sc, sc::get_analogs() }); analogs.insert({ sc, sc::get_analogs() });
lights.insert({ sc, sc::get_lights() }); lights.insert({ sc, sc::get_lights() });
file_hints[sc].emplace_back("gamekgg.dll"); file_hints[sc].push_back({"gamekgg.dll"});
// sdvx // sdvx
const std::string sdvx("Sound Voltex"); const std::string sdvx("Sound Voltex");
@@ -213,13 +217,13 @@ namespace games {
buttons_help.insert({ sdvx, sdvx::get_buttons_help() }); buttons_help.insert({ sdvx, sdvx::get_buttons_help() });
analogs.insert({ sdvx, sdvx::get_analogs() }); analogs.insert({ sdvx, sdvx::get_analogs() });
lights.insert({ sdvx, sdvx::get_lights() }); lights.insert({ sdvx, sdvx::get_lights() });
file_hints[sdvx].emplace_back("soundvoltex.dll"); file_hints[sdvx].push_back({"soundvoltex.dll"});
// mfc // mfc
const std::string mfc("Mahjong Fight Club"); const std::string mfc("Mahjong Fight Club");
games.push_back(mfc); games.push_back(mfc);
buttons.insert({ mfc, mfc::get_buttons() }); buttons.insert({ mfc, mfc::get_buttons() });
file_hints[mfc].emplace_back("allinone.dll"); file_hints[mfc].push_back({"allinone.dll"});
// ftt // ftt
const std::string ftt("FutureTomTom"); const std::string ftt("FutureTomTom");
@@ -228,21 +232,21 @@ namespace games {
buttons_help.insert({ ftt, ftt::get_buttons_help() }); buttons_help.insert({ ftt, ftt::get_buttons_help() });
analogs.insert({ ftt, ftt::get_analogs() }); analogs.insert({ ftt, ftt::get_analogs() });
lights.insert({ ftt, ftt::get_lights() }); lights.insert({ ftt, ftt::get_lights() });
file_hints[ftt].emplace_back("arkmmd.dll"); file_hints[ftt].push_back({"arkmmd.dll"});
// loveplus // loveplus
const std::string loveplus("LovePlus"); const std::string loveplus("LovePlus");
games.push_back(loveplus); games.push_back(loveplus);
buttons.insert({ loveplus, loveplus::get_buttons() }); buttons.insert({ loveplus, loveplus::get_buttons() });
lights.insert({ loveplus, loveplus::get_lights() }); lights.insert({ loveplus, loveplus::get_lights() });
file_hints[loveplus].emplace_back("arkklp.dll"); file_hints[loveplus].push_back({"arkklp.dll"});
// scotto // scotto
const std::string scotto("Scotto"); const std::string scotto("Scotto");
games.push_back(scotto); games.push_back(scotto);
buttons.insert({ scotto, scotto::get_buttons() }); buttons.insert({ scotto, scotto::get_buttons() });
lights.insert({ scotto, scotto::get_lights() }); lights.insert({ scotto, scotto::get_lights() });
file_hints[scotto].emplace_back("scotto.dll"); file_hints[scotto].push_back({"scotto.dll"});
// drs // drs
const std::string drs("DANCERUSH"); const std::string drs("DANCERUSH");
@@ -250,14 +254,14 @@ namespace games {
buttons.insert({ drs, drs::get_buttons() }); buttons.insert({ drs, drs::get_buttons() });
buttons_help.insert({ drs, drs::get_buttons_help() }); buttons_help.insert({ drs, drs::get_buttons_help() });
lights.insert({ drs, drs::get_lights() }); lights.insert({ drs, drs::get_lights() });
file_hints[drs].emplace_back("superstep.dll"); file_hints[drs].push_back({"superstep.dll"});
// otoca // otoca
const std::string otoca("Otoca D'or"); const std::string otoca("Otoca D'or");
games.push_back(otoca); games.push_back(otoca);
buttons.insert({ otoca, otoca::get_buttons() }); buttons.insert({ otoca, otoca::get_buttons() });
buttons_help.insert({ otoca, otoca::get_buttons_help() }); buttons_help.insert({ otoca, otoca::get_buttons_help() });
file_hints[otoca].emplace_back("arkkep.dll"); file_hints[otoca].push_back({"arkkep.dll"});
// winning eleven // winning eleven
const std::string we("Winning Eleven"); const std::string we("Winning Eleven");
@@ -265,27 +269,27 @@ namespace games {
buttons.insert({ we, we::get_buttons() }); buttons.insert({ we, we::get_buttons() });
analogs.insert({ we, we::get_analogs() }); analogs.insert({ we, we::get_analogs() });
lights.insert({ we, we::get_lights() }); lights.insert({ we, we::get_lights() });
file_hints[we].emplace_back("weac12_bootstrap_release.dll"); file_hints[we].push_back({"weac12_bootstrap_release.dll"});
file_hints[we].emplace_back("arknck.dll"); file_hints[we].push_back({"arknck.dll"});
// silent scope: bone eater // silent scope: bone eater
const std::string silentscope("Silent Scope: Bone Eater"); const std::string silentscope("Silent Scope: Bone Eater");
games.push_back(silentscope); games.push_back(silentscope);
buttons.insert({ silentscope, silentscope::get_buttons() }); buttons.insert({ silentscope, silentscope::get_buttons() });
analogs.insert({ silentscope, silentscope::get_analogs() }); analogs.insert({ silentscope, silentscope::get_analogs() });
file_hints[silentscope].emplace_back("arkndd.dll"); file_hints[silentscope].push_back({"arkndd.dll"});
// charge machine // charge machine
const std::string pcm("Charge Machine"); const std::string pcm("Charge Machine");
games.push_back(pcm); games.push_back(pcm);
buttons.insert({ pcm, pcm::get_buttons() }); buttons.insert({ pcm, pcm::get_buttons() });
file_hints[pcm].emplace_back("launch.dll"); file_hints[pcm].push_back({"launch.dll"});
// ongaku paradise // ongaku paradise
const std::string op("Ongaku Paradise"); const std::string op("Ongaku Paradise");
games.push_back(op); games.push_back(op);
buttons.insert({ op, onpara::get_buttons() }); buttons.insert({ op, onpara::get_buttons() });
file_hints[op].emplace_back("arkjc9.dll"); file_hints[op].push_back({"arkjc9.dll"});
// bc // bc
const std::string bc("Busou Shinki: Armored Princess Battle Conductor"); const std::string bc("Busou Shinki: Armored Princess Battle Conductor");
@@ -293,7 +297,7 @@ namespace games {
buttons.insert({ bc, bc::get_buttons() }); buttons.insert({ bc, bc::get_buttons() });
buttons_help.insert({ bc, bc::get_buttons_help() }); buttons_help.insert({ bc, bc::get_buttons_help() });
analogs.insert({ bc, bc::get_analogs() }); 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 // ccj
const std::string ccj("Chase Chase Jokers"); const std::string ccj("Chase Chase Jokers");
@@ -302,21 +306,21 @@ namespace games {
buttons_help.insert({ ccj, ccj::get_buttons_help() }); buttons_help.insert({ ccj, ccj::get_buttons_help() });
analogs.insert({ ccj, ccj::get_analogs() }); analogs.insert({ ccj, ccj::get_analogs() });
lights.insert({ ccj, ccj::get_lights() }); lights.insert({ ccj, ccj::get_lights() });
file_hints[ccj].emplace_back("game/chaseproject.exe"); file_hints[ccj].push_back({"game/chaseproject.exe"});
// QuizKnock STADIUM // QuizKnock STADIUM
const std::string qks("QuizKnock STADIUM"); const std::string qks("QuizKnock STADIUM");
games.push_back(qks); games.push_back(qks);
buttons.insert({ qks, qks::get_buttons() }); buttons.insert({ qks, qks::get_buttons() });
buttons_help.insert({ qks, qks::get_buttons_help() }); 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 // Mahjong Fight Girl
const std::string mfg("Mahjong Fight Girl"); const std::string mfg("Mahjong Fight Girl");
games.push_back(mfg); games.push_back(mfg);
buttons.insert({ mfg, mfg::get_buttons() }); buttons.insert({ mfg, mfg::get_buttons() });
buttons_help.insert({ mfg, mfg::get_buttons_help() }); 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 // Polaris Chord
const std::string pc("Polaris Chord"); const std::string pc("Polaris Chord");
@@ -325,7 +329,7 @@ namespace games {
buttons_help.insert({ pc, pc::get_buttons_help() }); buttons_help.insert({ pc, pc::get_buttons_help() });
analogs.insert({ pc, pc::get_analogs() }); analogs.insert({ pc, pc::get_analogs() });
lights.insert({ pc, pc::get_lights() }); lights.insert({ pc, pc::get_lights() });
file_hints[pc].emplace_back("game/svm.exe"); file_hints[pc].push_back({"game/svm.exe"});
} }
const std::vector<std::string> &get_games() { const std::vector<std::string> &get_games() {
@@ -559,7 +563,7 @@ namespace games {
return &it->second; 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(); initialize();
auto it = file_hints.find(game); auto it = file_hints.find(game);
if (it == file_hints.end()) { 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<Analog> *get_analogs(const std::string &game);
std::vector<Light> *get_lights(const std::string &game); std::vector<Light> *get_lights(const std::string &game);
std::vector<Option> *get_options(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);
} }
+2 -2
View File
@@ -167,7 +167,7 @@ namespace games::jb {
void JBGame::pre_attach() { void JBGame::pre_attach() {
if (!cfg::CONFIGURATOR_STANDALONE) { if (!cfg::CONFIGURATOR_STANDALONE) {
const auto current_path = std::filesystem::current_path(); const auto current_path = std::filesystem::current_path();
log_misc("jubeat", "current working directory: {}", current_path.string()); log_misc("jubeat", "current working directory: {}", current_path);
if (current_path.parent_path() == current_path.root_path()) { if (current_path.parent_path() == current_path.root_path()) {
log_warning( log_warning(
"jubeat", "jubeat",
@@ -178,7 +178,7 @@ namespace games::jb {
" c:\\jubeat\\contents\\spice.exe <- OK\n\n" " c:\\jubeat\\contents\\spice.exe <- OK\n\n"
"To fix this, create a new directory and move ALL game files there.\n\n" "To fix this, create a new directory and move ALL game files there.\n\n"
"Your current working directory: {}\n", "Your current working directory: {}\n",
current_path.string()); current_path);
log_fatal( log_fatal(
"jubeat", "jubeat",
+5 -5
View File
@@ -36,11 +36,11 @@ std::vector<Analog> &games::mga::get_analogs() {
if (analogs.empty()) { if (analogs.empty()) {
analogs = GameAPI::Analogs::getAnalogs("Metal Gear"); analogs = GameAPI::Analogs::getAnalogs("Metal Gear");
GameAPI::Analogs::sortAnalogs( using namespace GameAPI::Analogs;
&analogs, GameAPI::Analogs::sortAnalogsWithType(&analogs, {
"Joy X", { "Joy X", AnalogType::LinearCentered },
"Joy Y" { "Joy Y", AnalogType::LinearCentered }
); });
} }
return analogs; return analogs;
+32 -31
View File
@@ -165,37 +165,38 @@ std::vector<Analog> &games::nost::get_analogs() {
if (analogs.empty()) { if (analogs.empty()) {
analogs = GameAPI::Analogs::getAnalogs("Nostalgia"); analogs = GameAPI::Analogs::getAnalogs("Nostalgia");
GameAPI::Analogs::sortAnalogs( using namespace GameAPI::Analogs;
&analogs,
"Key 1", GameAPI::Analogs::sortAnalogsWithType(&analogs, {
"Key 2", { "Key 1", AnalogType::LinearPositive },
"Key 3", { "Key 2", AnalogType::LinearPositive },
"Key 4", { "Key 3", AnalogType::LinearPositive },
"Key 5", { "Key 4", AnalogType::LinearPositive },
"Key 6", { "Key 5", AnalogType::LinearPositive },
"Key 7", { "Key 6", AnalogType::LinearPositive },
"Key 8", { "Key 7", AnalogType::LinearPositive },
"Key 9", { "Key 8", AnalogType::LinearPositive },
"Key 10", { "Key 9", AnalogType::LinearPositive },
"Key 11", { "Key 10", AnalogType::LinearPositive },
"Key 12", { "Key 11", AnalogType::LinearPositive },
"Key 13", { "Key 12", AnalogType::LinearPositive },
"Key 14", { "Key 13", AnalogType::LinearPositive },
"Key 15", { "Key 14", AnalogType::LinearPositive },
"Key 16", { "Key 15", AnalogType::LinearPositive },
"Key 17", { "Key 16", AnalogType::LinearPositive },
"Key 18", { "Key 17", AnalogType::LinearPositive },
"Key 19", { "Key 18", AnalogType::LinearPositive },
"Key 20", { "Key 19", AnalogType::LinearPositive },
"Key 21", { "Key 20", AnalogType::LinearPositive },
"Key 22", { "Key 21", AnalogType::LinearPositive },
"Key 23", { "Key 22", AnalogType::LinearPositive },
"Key 24", { "Key 23", AnalogType::LinearPositive },
"Key 25", { "Key 24", AnalogType::LinearPositive },
"Key 26", { "Key 25", AnalogType::LinearPositive },
"Key 27", { "Key 26", AnalogType::LinearPositive },
"Key 28" { "Key 27", AnalogType::LinearPositive },
); { "Key 28", AnalogType::LinearPositive }
});
} }
return analogs; return analogs;
} }
+6 -5
View File
@@ -51,11 +51,12 @@ std::vector<Analog> &games::pc::get_analogs() {
if (analogs.empty()) { if (analogs.empty()) {
analogs = GameAPI::Analogs::getAnalogs("Polaris Chord"); analogs = GameAPI::Analogs::getAnalogs("Polaris Chord");
GameAPI::Analogs::sortAnalogs( using GameAPI::Analogs::AnalogType;
&analogs,
"Fader-L", GameAPI::Analogs::sortAnalogsWithType(&analogs, {
"Fader-R" { "Fader-L", AnalogType::LinearCentered },
); { "Fader-R", AnalogType::LinearCentered }
});
} }
return analogs; return analogs;
+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", "Service",
"Test", "Test",
"Coin Mech", "Coin Mech",
"Button 1", "Headphones",
"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,
"Button 1", "Button 1",
"Button 2", "Button 2",
"Button 3", "Button 3",
@@ -51,55 +21,135 @@ std::vector<Light> &games::popn::get_lights() {
"Button 7", "Button 7",
"Button 8", "Button 8",
"Button 9", "Button 9",
"Top LED 1", "Red Pop-Kun",
"Top LED 2", "Blue Pop-Kun"
"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"
); );
} }
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; return lights;
} }
+62 -11
View File
@@ -11,6 +11,7 @@ namespace games::popn {
Service, Service,
Test, Test,
CoinMech, CoinMech,
Headphone,
Button1, Button1,
Button2, Button2,
Button3, Button3,
@@ -20,12 +21,14 @@ namespace games::popn {
Button7, Button7,
Button8, Button8,
Button9, Button9,
RedPop,
BluePop
}; };
} }
// all lights in correct order // all lights in correct order
namespace Lights { namespace Lights {
enum { typedef enum {
Button1, Button1,
Button2, Button2,
Button3, Button3,
@@ -35,6 +38,15 @@ namespace games::popn {
Button7, Button7,
Button8, Button8,
Button9, Button9,
HiLamp1,
HiLamp2,
HiLamp3,
HiLamp4,
HiLamp5,
LeftLamp1,
LeftLamp2,
RightLamp1,
RightLamp2,
TopLED1, TopLED1,
TopLED2, TopLED2,
TopLED3, TopLED3,
@@ -70,19 +82,58 @@ namespace games::popn {
TopLED_R, TopLED_R,
TopLED_G, TopLED_G,
TopLED_B, TopLED_B,
HiLamp1,
HiLamp2,
HiLamp3,
HiLamp4,
HiLamp5,
LeftLamp1,
LeftLamp2,
RightLamp1,
RightLamp2,
WooferLED_R, WooferLED_R,
WooferLED_G, WooferLED_G,
WooferLED_B, 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 // getters
+409
View File
@@ -1,7 +1,9 @@
#include "avs/game.h" #include "avs/game.h"
#include "popn.h" #include "popn.h"
#include "bi3a_hook.h"
#include <cstdint> #include <cstdint>
#include <cstring> #include <cstring>
#include "hooks/graphics/graphics.h"
#include "rawinput/rawinput.h" #include "rawinput/rawinput.h"
#include "util/detour.h" #include "util/detour.h"
#include "util/fileutils.h" #include "util/fileutils.h"
@@ -9,14 +11,327 @@
#include "util/utils.h" #include "util/utils.h"
#include "cfg/button.h" #include "cfg/button.h"
#include "cfg/api.h" #include "cfg/api.h"
#include "hooks/setupapihook.h"
#include "hooks/sleephook.h" #include "hooks/sleephook.h"
#include "launcher/launcher.h" #include "launcher/launcher.h"
#include "launcher/logger.h" #include "launcher/logger.h"
#include "misc/eamuse.h" #include "misc/eamuse.h"
#include "util/sysutils.h"
#include "io.h" #include "io.h"
#include "util/deferlog.h"
#include "misc/nativetouchhook.h"
#include "misc/wintouchemu.h"
namespace games::popn { 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() { static int __cdecl usbCheckAlive() {
return 1; return 1;
} }
@@ -227,6 +542,8 @@ namespace games::popn {
return MMSYSERR_NOERROR; return MMSYSERR_NOERROR;
} }
#endif
static bool log_hook(void *user, const std::string &data, logger::Style style, std::string &out) { static bool log_hook(void *user, const std::string &data, logger::Style style, std::string &out) {
// get rid of the log spam // get rid of the log spam
@@ -249,6 +566,8 @@ namespace games::popn {
void POPNGame::pre_attach() { void POPNGame::pre_attach() {
Game::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 // 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\\bookkeeping");
fileutils::dir_create_recursive_log("popn", "dev\\raw\\ranking"); fileutils::dir_create_recursive_log("popn", "dev\\raw\\ranking");
@@ -257,11 +576,15 @@ namespace games::popn {
// load without resolving references // load without resolving references
// makes game not trigger DLLMain which results in an error due to missing EZUSB device // 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); LoadLibraryExW((MODULE_PATH / "ezusb.dll").c_str(), nullptr, DONT_RESOLVE_DLL_REFERENCES);
#endif
} }
void POPNGame::attach() { void POPNGame::attach() {
Game::attach(); Game::attach();
#if !SPICE64
/* /*
* Fast Boot (TM) Patch * Fast Boot (TM) Patch
* Game tries to create some directories and if it fails it will sleep for 1 second * 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, detour::inline_hook((void *) usbWdtStartDone,
libutils::try_proc(ezusb, "?usbWdtStartDone@@YAHXZ")); 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 #pragma once
#include "avs/game.h"
#include "games/game.h" #include "games/game.h"
#include "util/tapeled.h"
namespace games::popn { 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 { class POPNGame : public games::Game {
public: public:
POPNGame(); POPNGame();
+7 -6
View File
@@ -35,12 +35,13 @@ std::vector<Analog> &games::rf3d::get_analogs() {
if (analogs.empty()) { if (analogs.empty()) {
analogs = GameAPI::Analogs::getAnalogs("Road Fighters 3D"); analogs = GameAPI::Analogs::getAnalogs("Road Fighters 3D");
GameAPI::Analogs::sortAnalogs( using GameAPI::Analogs::AnalogType;
&analogs,
"Wheel", GameAPI::Analogs::sortAnalogsWithType(&analogs, {
"Accelerate", { "Wheel", AnalogType::LinearCentered },
"Brake" { "Accelerate", AnalogType::LinearPositive },
); { "Brake", AnalogType::LinearPositive }
});
} }
return analogs; return analogs;
+8 -7
View File
@@ -31,13 +31,14 @@ std::vector<Analog> &games::sc::get_analogs() {
if (analogs.empty()) { if (analogs.empty()) {
analogs = GameAPI::Analogs::getAnalogs("Steel Chronicle"); analogs = GameAPI::Analogs::getAnalogs("Steel Chronicle");
GameAPI::Analogs::sortAnalogs( using GameAPI::Analogs::AnalogType;
&analogs,
"Left Stick X", GameAPI::Analogs::sortAnalogsWithType(&analogs, {
"Left Stick Y", { "Left Stick X", AnalogType::LinearCentered },
"Right Stick X", { "Left Stick Y", AnalogType::LinearCentered },
"Right Stick Y" { "Right Stick X", AnalogType::LinearCentered },
); { "Right Stick Y", AnalogType::LinearCentered }
});
} }
return analogs; return analogs;
+13 -8
View File
@@ -21,6 +21,7 @@
#include "util/socd_cleaner.h" #include "util/socd_cleaner.h"
#include "util/time.h" #include "util/time.h"
#include "util/libutils.h" #include "util/libutils.h"
#include "util/sysutils.h"
#include "misc/eamuse.h" #include "misc/eamuse.h"
#include "misc/nativetouchhook.h" #include "misc/nativetouchhook.h"
#include "misc/wintouchemu.h" #include "misc/wintouchemu.h"
@@ -350,28 +351,28 @@ namespace games::sdvx {
} }
#ifdef SPICE64 // SDVX5+ specific code #ifdef SPICE64 // SDVX5+ specific code
// check -monitor + UFC mode // check -dxmainadapter + UFC mode
if (!GRAPHICS_WINDOWED && D3D9_ADAPTER.has_value() && is_valkyrie_model()) { if (!GRAPHICS_WINDOWED && D3D9_ADAPTER.has_value() && is_valkyrie_model()) {
SHOW_VM_MONITOR_WARNING = true; SHOW_VM_MONITOR_WARNING = true;
log_warning( log_warning(
"sdvx", "sdvx",
"\n\n!!! using -monitor option with VM mode is NOT recommended due to !!!\n" "\n\n"
"!!! known compatibility issues with the game !!!\n" "!!! using -dxmainadapter option with Valkyrie mode is NOT !!!\n"
"!!! recommended due to known compatibility issues with the game !!!\n"
"!!! !!!\n" "!!! !!!\n"
"!!! * game may launch in wrong resolution or refresh rate !!!\n" "!!! * game may launch in wrong resolution or refresh rate !!!\n"
"!!! * touch / mouse input may stop working in subscreen / overlay !!!\n" "!!! * touch / mouse input may stop working in subscreen / overlay !!!\n"
"!!! !!!\n" "!!! !!!\n"
"!!! recommendation is to NOT use -monitor and instead set the !!!\n" "!!! recommendation is to use the Change Main Monitor (-mainmonitor) !!!\n"
"!!! primary monitor in Windows settings before launching the game !!!\n\n" "!!! option instead of -dxmainadapter !!!\n\n"
); );
deferredlogs::defer_error_messages({ 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:", " due to known compatibility issues with the game:",
" * game may launch in wrong resolution or refresh rate", " * game may launch in wrong resolution or refresh rate",
" * touch / mouse input may stop working in subscreen / overlay", " * touch / mouse input may stop working in subscreen / overlay",
" recommended fix is to NOT use -monitor and instead set the primary", " use Change Main Monitor (-mainmonitor) option instead"
" monitor in Windows settings before launching the game"
}); });
} }
#endif #endif
@@ -466,6 +467,7 @@ namespace games::sdvx {
ENABLE_COM_PORT_SCAN_HOOK = true; ENABLE_COM_PORT_SCAN_HOOK = true;
} }
} }
#endif #endif
// ASIO device hook // ASIO device hook
@@ -501,6 +503,9 @@ namespace games::sdvx {
// remove log spam // remove log spam
logger::hook_add(sdvx64_spam_remover, nullptr); logger::hook_add(sdvx64_spam_remover, nullptr);
if (is_valkyrie_model()) {
sysutils::hook_EnumDisplayDevicesA();
}
#endif #endif
} }
+6 -5
View File
@@ -31,11 +31,12 @@ std::vector<Analog> &games::silentscope::get_analogs() {
if (analogs.empty()) { if (analogs.empty()) {
analogs = GameAPI::Analogs::getAnalogs("Silent Scope: Bone Eater"); analogs = GameAPI::Analogs::getAnalogs("Silent Scope: Bone Eater");
GameAPI::Analogs::sortAnalogs( using GameAPI::Analogs::AnalogType;
&analogs,
"Gun X", GameAPI::Analogs::sortAnalogsWithType(&analogs, {
"Gun Y" { "Gun X", AnalogType::LinearCentered },
); { "Gun Y", AnalogType::LinearCentered }
});
} }
return analogs; return analogs;
+8 -7
View File
@@ -50,13 +50,14 @@ std::vector<Analog> &games::we::get_analogs() {
if (analogs.empty()) { if (analogs.empty()) {
analogs = GameAPI::Analogs::getAnalogs("Winning Eleven"); analogs = GameAPI::Analogs::getAnalogs("Winning Eleven");
GameAPI::Analogs::sortAnalogs( using GameAPI::Analogs::AnalogType;
&analogs,
"Pad Stick Left X", GameAPI::Analogs::sortAnalogsWithType(&analogs, {
"Pad Stick Left Y", { "Pad Stick Left X", AnalogType::LinearCentered },
"Pad Stick Right X", { "Pad Stick Left Y", AnalogType::LinearCentered },
"Pad Stick Right Y" { "Pad Stick Right X", AnalogType::LinearCentered },
); { "Pad Stick Right Y", AnalogType::LinearCentered }
});
} }
return analogs; return analogs;
+8 -6
View File
@@ -66,14 +66,16 @@ static HRESULT STDAPICALLTYPE CoCreateInstance_hook(
if (FAILED(ret)) { if (FAILED(ret)) {
if (IsEqualCLSID(rclsid, CLSID_MMDeviceEnumerator)) { if (IsEqualCLSID(rclsid, CLSID_MMDeviceEnumerator)) {
log_warning("audio", "CoCreateInstance failed for CLSID_MMDeviceEnumerator, hr={}", FMT_HRESULT(ret)); log_warning("audio", "CoCreateInstance failed for CLSID_MMDeviceEnumerator, hr={}", FMT_HRESULT(ret));
} } else if (IsEqualCLSID(rclsid, CLSID_DirectSoundI3DL2ReverbDMO)) {
// deal with reverb DMO missing from dsdmo.dll
// Windows 11 KB5052093 issue - reverb DMO went missing from dsdmo.dll (fails with 0x80040154) // it usually fails with 0x80040154 (REGDB_E_CLASSNOTREG), but we have also seen 0x8007007e (ERROR_MOD_NOT_FOUND)
if (IsEqualCLSID(rclsid, CLSID_DirectSoundI3DL2ReverbDMO) && ret == (HRESULT)0x80040154) { static std::once_flag printed;
std::call_once(printed, [ret]() {
log_warning( log_warning(
"audio", "audio",
"CoCreateInstance for CLSID_DirectSoundI3DL2ReverbDMO failed with 0x80040154, swap with CLSID_DirectSoundWavesReverbDMO " "CoCreateInstance for CLSID_DirectSoundI3DL2ReverbDMO failed with {}, swap with CLSID_DirectSoundWavesReverbDMO...",
"(workaround for Windows 11 KB5052093 issue); REVERB EX replaced with REVERB"); FMT_HRESULT(ret));
});
ret = CoCreateInstance_orig(CLSID_DirectSoundWavesReverbDMO, pUnkOuter, dwClsContext, riid, ppv); ret = CoCreateInstance_orig(CLSID_DirectSoundWavesReverbDMO, pUnkOuter, dwClsContext, riid, ppv);
if (FAILED(ret)) { if (FAILED(ret)) {
log_warning("audio", "CoCreateInstance(CLSID_DirectSoundWavesReverbDMO...) failed, hr={}", FMT_HRESULT(ret)); log_warning("audio", "CoCreateInstance(CLSID_DirectSoundWavesReverbDMO...) failed, hr={}", FMT_HRESULT(ret));
+2 -2
View File
@@ -291,7 +291,7 @@ static int avs_fs_mount(const char *mountpoint, const char *fsroot, const char *
std::filesystem::create_directories(mapped_path, err); std::filesystem::create_directories(mapped_path, err);
if (err) { if (err) {
log_warning("hooks::avs", "failed to create '{}': {}", mapped_path.string(), err.message()); log_warning("hooks::avs", "failed to create '{}': {}", mapped_path, err.message());
} else { } else {
// if this is the `e:\`, then create the special directories // if this is the `e:\`, then create the special directories
@@ -305,7 +305,7 @@ static int avs_fs_mount(const char *mountpoint, const char *fsroot, const char *
log_misc("hooks::avs", "source directory '{}' remapped to '{}'", log_misc("hooks::avs", "source directory '{}' remapped to '{}'",
fsroot, fsroot,
mapped_path.string()); mapped_path);
} }
new_fs_root = mapped_path.string(); new_fs_root = mapped_path.string();
@@ -15,6 +15,7 @@
#include "cfg/screen_resize.h" #include "cfg/screen_resize.h"
#include "games/gitadora/gitadora.h" #include "games/gitadora/gitadora.h"
#include "games/iidx/iidx.h" #include "games/iidx/iidx.h"
#include "games/popn/popn.h"
#include "games/sdvx/sdvx.h" #include "games/sdvx/sdvx.h"
#include "games/mfc/mfc.h" #include "games/mfc/mfc.h"
#include "games/io.h" #include "games/io.h"
@@ -462,7 +463,20 @@ HRESULT STDMETHODCALLTYPE WrappedIDirect3D9::RegisterSoftwareDevice(void *pIniti
} }
UINT STDMETHODCALLTYPE WrappedIDirect3D9::GetAdapterCount() { 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( HRESULT STDMETHODCALLTYPE WrappedIDirect3D9::GetAdapterIdentifier(
@@ -470,10 +484,26 @@ HRESULT STDMETHODCALLTYPE WrappedIDirect3D9::GetAdapterIdentifier(
DWORD Flags, DWORD Flags,
D3DADAPTER_IDENTIFIER9 *pIdentifier) 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)); CHECK_RESULT(pReal->GetAdapterIdentifier(Adapter, Flags, pIdentifier));
} }
UINT STDMETHODCALLTYPE WrappedIDirect3D9::GetAdapterModeCount(UINT Adapter, D3DFORMAT Format) { 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); return pReal->GetAdapterModeCount(Adapter, Format);
} }
@@ -483,6 +513,19 @@ HRESULT STDMETHODCALLTYPE WrappedIDirect3D9::EnumAdapterModes(
UINT Mode, UINT Mode,
D3DDISPLAYMODE *pMode) 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); HRESULT ret = pReal->EnumAdapterModes(Adapter, Format, Mode, pMode);
if (SUCCEEDED(ret) && pMode) { if (SUCCEEDED(ret) && pMode) {
@@ -552,18 +595,6 @@ HRESULT STDMETHODCALLTYPE WrappedIDirect3D9::EnumAdapterModes(
modified = true; 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); CHECK_RESULT(ret);
@@ -591,6 +622,11 @@ HRESULT STDMETHODCALLTYPE WrappedIDirect3D9::CheckDeviceFormat(
D3DRESOURCETYPE RType, D3DRESOURCETYPE RType,
D3DFORMAT CheckFormat) 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)); 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 // in windowed mode, LDJ will always launch two windows, no special handling needed here
} else if (avs::game::is_model("KFC")) { } 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 // user wants windowed mode but does not want subscreen at all
pCaps->NumberOfAdaptersInGroup = 1; pCaps->NumberOfAdaptersInGroup = 1;
} else { } else {
@@ -687,6 +723,8 @@ HRESULT STDMETHODCALLTYPE WrappedIDirect3D9::GetDeviceCaps(UINT Adapter, D3DDEVT
} else if (GRAPHICS_FORCE_SINGLE_ADAPTER) { } else if (GRAPHICS_FORCE_SINGLE_ADAPTER) {
pCaps->NumberOfAdaptersInGroup = 1; pCaps->NumberOfAdaptersInGroup = 1;
} }
} else if (games::popn::is_pikapika_model()) {
pCaps->NumberOfAdaptersInGroup = 2;
} }
return ret; return ret;
@@ -1033,7 +1071,8 @@ HRESULT STDMETHODCALLTYPE WrappedIDirect3D9::CreateDeviceEx(
// //
// note from MSDN: `pFullscreenDisplayMode` must be NULL for windowed mode. // note from MSDN: `pFullscreenDisplayMode` must be NULL for windowed mode.
if (GRAPHICS_WINDOWED) { 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"); log_misc("graphics::d3d9", "disabling adapter group device in windowed mode");
D3D9_BEHAVIOR_DISABLE |= D3DCREATE_ADAPTERGROUP_DEVICE; D3D9_BEHAVIOR_DISABLE |= D3DCREATE_ADAPTERGROUP_DEVICE;
@@ -1105,7 +1144,8 @@ HRESULT STDMETHODCALLTYPE WrappedIDirect3D9::CreateDeviceEx(
*ppReturnedDeviceInterface = new WrappedIDirect3DDevice9(hFocusWindow, *ppReturnedDeviceInterface); *ppReturnedDeviceInterface = new WrappedIDirect3DDevice9(hFocusWindow, *ppReturnedDeviceInterface);
// initialize sub screen if IIDX/SDVX requested a multi-head context // 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]); graphics_d3d9_ldj_init_sub_screen(*ppReturnedDeviceInterface, &pPresentationParameters[1]);
} }
} }
@@ -1144,8 +1184,6 @@ static void graphics_d3d9_ldj_init_sub_screen(IDirect3DDevice9Ex *device, D3DPRE
*/ */
if (GRAPHICS_WINDOWED) { if (GRAPHICS_WINDOWED) {
log_info("graphics::d3d9", "creating additional swap chain");
present_params->Windowed = true; present_params->Windowed = true;
present_params->FullScreen_RefreshRateInHz = 0; present_params->FullScreen_RefreshRateInHz = 0;
@@ -1154,18 +1192,23 @@ static void graphics_d3d9_ldj_init_sub_screen(IDirect3DDevice9Ex *device, D3DPRE
hr = device->CreateAdditionalSwapChain(present_params, &SUB_SWAP_CHAIN); hr = device->CreateAdditionalSwapChain(present_params, &SUB_SWAP_CHAIN);
if (FAILED(hr)) { if (FAILED(hr)) {
log_warning("graphics::d3d9", "failed to create additional swap chain, hr={}", FMT_HRESULT(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 { } else {
hr = device->GetSwapChain(1, &SUB_SWAP_CHAIN); hr = device->GetSwapChain(1, &SUB_SWAP_CHAIN);
if (FAILED(hr)) { if (FAILED(hr)) {
log_warning("graphics::d3d9", "failed to acquire fullscreen sub swap chain, hr={}", FMT_HRESULT(hr)); log_warning("graphics::d3d9", "failed to acquire fullscreen sub swap chain, hr={}", FMT_HRESULT(hr));
} else { } else {
log_info("graphics::d3d9", "acquired fullscreen sub swap chain");
return; return;
} }
hr = device->CreateAdditionalSwapChain(present_params, &SUB_SWAP_CHAIN); hr = device->CreateAdditionalSwapChain(present_params, &SUB_SWAP_CHAIN);
if (FAILED(hr)) { if (FAILED(hr)) {
log_warning("graphics::d3d9", "failed to get additional swap chain, hr={}", FMT_HRESULT(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");
} }
} }
} }
@@ -1208,7 +1251,10 @@ static void graphics_d3d9_ldj_on_present(IDirect3DDevice9 *wrapped_device) {
if (SUB_SWAP_CHAIN != nullptr) { if (SUB_SWAP_CHAIN != nullptr) {
wintouchemu::update(); 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); SUB_SWAP_CHAIN->Present(nullptr, nullptr, nullptr, nullptr, 0);
} }
} }
@@ -1408,7 +1454,8 @@ void graphics_d3d9_on_present(
const bool is_tdj = avs::game::is_model("LDJ") && games::iidx::TDJ_MODE; const bool is_tdj = avs::game::is_model("LDJ") && games::iidx::TDJ_MODE;
const bool is_gfdm_arena = const bool is_gfdm_arena =
games::gitadora::is_arena_model() && games::gitadora::ARENA_SINGLE_WINDOW; 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); graphics_d3d9_ldj_on_present(wrapped_device);
} }
@@ -251,7 +251,7 @@ HRESULT STDMETHODCALLTYPE WrappedIDirect3DDevice9::CreateAdditionalSwapChain(
HRESULT hr = pReal->CreateAdditionalSwapChain(pPresentationParameters, ppSwapChain); HRESULT hr = pReal->CreateAdditionalSwapChain(pPresentationParameters, ppSwapChain);
bool create_swap_chain = false; bool create_swap_chain = false;
if (avs::game::is_model({"LDJ", "KFC"})) { if (avs::game::is_model({"LDJ", "KFC", "M39"})) {
create_swap_chain = true; create_swap_chain = true;
} else if (games::gitadora::is_arena_model() && } else if (games::gitadora::is_arena_model() &&
games::gitadora::ARENA_SINGLE_WINDOW && games::gitadora::ARENA_SINGLE_WINDOW &&
@@ -305,7 +305,7 @@ HRESULT STDMETHODCALLTYPE WrappedIDirect3DDevice9::GetSwapChain(
} }
bool swap = false; bool swap = false;
if (iSwapChain == 1 && avs::game::is_model({"LDJ", "KFC"})) { if (iSwapChain == 1 && avs::game::is_model({"LDJ", "KFC", "M39"})) {
swap = true; swap = true;
} }
if (games::gitadora::is_arena_model() && if (games::gitadora::is_arena_model() &&
@@ -344,7 +344,7 @@ UINT STDMETHODCALLTYPE WrappedIDirect3DDevice9::GetNumberOfSwapChains() {
UINT n = pReal->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; n += 1;
} }
+367 -84
View File
@@ -1,3 +1,6 @@
// GetDisplayConfigBufferSizes etc is Vista+
#define _WIN32_WINNT 0x0601
#include <initguid.h> #include <initguid.h>
#include "graphics.h" #include "graphics.h"
@@ -13,6 +16,7 @@
#include "games/ddr/ddr.h" #include "games/ddr/ddr.h"
#include "games/gitadora/gitadora.h" #include "games/gitadora/gitadora.h"
#include "games/iidx/iidx.h" #include "games/iidx/iidx.h"
#include "games/popn/popn.h"
#include "hooks/graphics/backends/d3d9/d3d9_backend.h" #include "hooks/graphics/backends/d3d9/d3d9_backend.h"
#include "launcher/shutdown.h" #include "launcher/shutdown.h"
#include "overlay/overlay.h" #include "overlay/overlay.h"
@@ -35,6 +39,8 @@ struct CaptureData {
HWND TDJ_SUBSCREEN_WINDOW = nullptr; HWND TDJ_SUBSCREEN_WINDOW = nullptr;
HWND SDVX_SUBSCREEN_WINDOW = nullptr; HWND SDVX_SUBSCREEN_WINDOW = nullptr;
HWND GFDM_SUBSCREEN_WINDOW = nullptr; HWND GFDM_SUBSCREEN_WINDOW = nullptr;
HWND POPN_SUBSCREEN_WINDOW = nullptr;
bool FAKE_SUBSCREEN_ADAPTER = false;
// icon // icon
static HICON WINDOW_ICON = LoadIcon(GetModuleHandle(nullptr), MAKEINTRESOURCE(MAINICON)); static HICON WINDOW_ICON = LoadIcon(GetModuleHandle(nullptr), MAKEINTRESOURCE(MAINICON));
@@ -53,12 +59,16 @@ static CaptureData GRAPHICS_CAPTURE_BUFFER[GRAPHICS_CAPTURE_SCREEN_NO] {};
static std::mutex GRAPHICS_CAPTURE_BUFFER_M[GRAPHICS_CAPTURE_SCREEN_NO] {}; static std::mutex GRAPHICS_CAPTURE_BUFFER_M[GRAPHICS_CAPTURE_SCREEN_NO] {};
static std::condition_variable GRAPHICS_CAPTURE_CV[GRAPHICS_CAPTURE_SCREEN_NO] {}; static std::condition_variable GRAPHICS_CAPTURE_CV[GRAPHICS_CAPTURE_SCREEN_NO] {};
static std::optional<graphics_orientation> target_orientation_on_boot;
static UINT target_refresh_rate_on_boot = 0;
static bool monitor_settings_changed = false;
static bool monitor_layout_needs_reset = false;
// flag settings // flag settings
bool GRAPHICS_CAPTURE_CURSOR = false; bool GRAPHICS_CAPTURE_CURSOR = false;
bool GRAPHICS_LOG_HRESULT = false; bool GRAPHICS_LOG_HRESULT = false;
bool GRAPHICS_SDVX_FORCE_720 = false; bool GRAPHICS_SDVX_FORCE_720 = false;
bool GRAPHICS_SHOW_CURSOR = false; bool GRAPHICS_SHOW_CURSOR = false;
graphics_orientation GRAPHICS_ADJUST_ORIENTATION = ORIENTATION_NORMAL;
bool GRAPHICS_WINDOWED = false; bool GRAPHICS_WINDOWED = false;
std::vector<HWND> GRAPHICS_WINDOWS; std::vector<HWND> GRAPHICS_WINDOWS;
UINT GRAPHICS_FORCE_REFRESH = 0; UINT GRAPHICS_FORCE_REFRESH = 0;
@@ -99,6 +109,7 @@ static decltype(SetWindowLongA) *SetWindowLongA_orig = nullptr;
static decltype(SetWindowLongW) *SetWindowLongW_orig = nullptr; static decltype(SetWindowLongW) *SetWindowLongW_orig = nullptr;
static decltype(SetWindowPos) *SetWindowPos_orig = nullptr; static decltype(SetWindowPos) *SetWindowPos_orig = nullptr;
static decltype(ShowWindow) *ShowWindow_orig = nullptr; static decltype(ShowWindow) *ShowWindow_orig = nullptr;
static decltype(SetDisplayConfig) *SetDisplayConfig_addr = nullptr;
static void reset_window_hook(HWND hWnd) { static void reset_window_hook(HWND hWnd) {
overlay::destroy(hWnd); overlay::destroy(hWnd);
@@ -227,7 +238,7 @@ static LRESULT CALLBACK WindowProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM l
switch (uMsg) { switch (uMsg) {
case WM_MOVE: case WM_MOVE:
case WM_SIZE: { case WM_SIZE:
// Update SPICETOUCH space when the main window changes size or moves. // Update SPICETOUCH space when the main window changes size or moves.
// The update happens regardless of whether the "fake" spicetouch window is present or not. // The update happens regardless of whether the "fake" spicetouch window is present or not.
// This allows touches received on subscreen window to be translated correctly. // This allows touches received on subscreen window to be translated correctly.
@@ -244,7 +255,16 @@ static LRESULT CALLBACK WindowProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM l
SPICETOUCH_TOUCH_WIDTH, SPICETOUCH_TOUCH_HEIGHT, SPICETOUCH_TOUCH_WIDTH, SPICETOUCH_TOUCH_HEIGHT,
SWP_NOZORDER | SWP_NOREDRAW | SWP_NOREPOSITION | SWP_NOACTIVATE); SWP_NOZORDER | SWP_NOREDRAW | SWP_NOREPOSITION | SWP_NOACTIVATE);
} }
break;
case WM_ACTIVATEAPP:
if (wParam) {
// regained focus
// this *can* get called twice in a row when restoring, but update_monitor_at_runtime
// is idempotent (checks current display settings to see if changes are needed),
// so it shouldn't cause any issues
update_monitor_at_runtime();
} }
break;
default: default:
break; break;
} }
@@ -301,30 +321,6 @@ static BOOL WINAPI ClipCursor_hook(const RECT *lpRect) {
return ClipCursor_orig(lpRect); return ClipCursor_orig(lpRect);
} }
static void fix_up_rb_auto_rotate_dimensions(DWORD &w, DWORD &h) {
// reflec beat
// for whatever reason, rb creates the first window it weird dimensions (774x1389 for reflesia, for example)
// so doing ChangeDisplaySettings as-is ends up with DISP_CHANGE_BADMODE
// while a second window (with title D3DProxyWindow) does get created with correct dimensions, it's
// too late to rotate the window then; as a workaround force the display change now with patched up dimensions
bool changed = false;
if (w == 774) {
// LBR, MBR reflesia
w = 768;
h = 1360;
changed = true;
} else if (w == 1086) {
// MBR (before reflesia)
w = 1080;
h = 1920;
changed = true;
}
if (changed) {
log_misc("graphics", "fix auto-rotate dimensions for reflec beat: w={}, h={}", w, h);
}
}
static HWND WINAPI CreateWindowExA_hook(DWORD dwExStyle, LPCSTR lpClassName, LPCSTR lpWindowName, static HWND WINAPI CreateWindowExA_hook(DWORD dwExStyle, LPCSTR lpClassName, LPCSTR lpWindowName,
DWORD dwStyle, int x, int y, int nWidth, int nHeight, DWORD dwStyle, int x, int y, int nWidth, int nHeight,
HWND hWndParent, HMENU hMenu, HINSTANCE hInstance, HWND hWndParent, HMENU hMenu, HINSTANCE hInstance,
@@ -346,62 +342,6 @@ static HWND WINAPI CreateWindowExA_hook(DWORD dwExStyle, LPCSTR lpClassName, LPC
fmt::ptr(hInstance), fmt::ptr(hInstance),
fmt::ptr(lpParam)); 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 // gfdm
if (avs::game::is_model({"J32", "J33", "K32", "K33", "L32", "L33", "M32"})) { if (avs::game::is_model({"J32", "J33", "K32", "K33", "L32", "L33", "M32"})) {
// set window name // set window name
@@ -412,17 +352,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_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_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"); bool is_gfdm_sub_window = games::gitadora::is_arena_model() && window_name.ends_with("SMALL");
// hide maximize button (prevent misaligned touches) // 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); dwStyle &= ~(WS_MAXIMIZEBOX);
} }
// mouse clicks become misaligned when resized // mouse clicks become misaligned when resized
if (is_gfdm_sub_window) { if (is_gfdm_sub_window) {
dwStyle &= ~(WS_SIZEBOX); dwStyle &= ~(WS_SIZEBOX);
} }
if ((is_tdj_sub_window || is_sdvx_sub_window) && GRAPHICS_WSUB_BORDERLESS) { if ((is_tdj_sub_window || is_sdvx_sub_window || is_popn_sub_window) && GRAPHICS_WSUB_BORDERLESS) {
dwStyle &= ~(WS_OVERLAPPEDWINDOW); dwStyle &= ~(WS_OVERLAPPEDWINDOW);
} }
@@ -444,6 +385,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 // call original
HWND result = CreateWindowExA_orig(dwExStyle, lpClassName, lpWindowName, dwStyle, x, y, nWidth, nHeight, HWND result = CreateWindowExA_orig(dwExStyle, lpClassName, lpWindowName, dwStyle, x, y, nWidth, nHeight,
hWndParent, hMenu, hInstance, lpParam); hWndParent, hMenu, hInstance, lpParam);
@@ -471,6 +416,13 @@ static HWND WINAPI CreateWindowExA_hook(DWORD dwExStyle, LPCSTR lpClassName, LPC
graphics_hook_subscreen_window(GFDM_SUBSCREEN_WINDOW); 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); disable_touch_indicators(result);
log_misc( log_misc(
"graphics", "graphics",
@@ -539,6 +491,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 // call original
HWND result = CreateWindowExW_orig( HWND result = CreateWindowExW_orig(
dwExStyle, lpClassName, lpWindowName, dwStyle, x, y, nWidth, nHeight, dwExStyle, lpClassName, lpWindowName, dwStyle, x, y, nWidth, nHeight,
@@ -734,6 +698,13 @@ static BOOL WINAPI ShowWindow_hook(HWND hWnd, int nCmdShow) {
return true; 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 // call original
return ShowWindow_orig(hWnd, nCmdShow); return ShowWindow_orig(hWnd, nCmdShow);
} }
@@ -976,6 +947,9 @@ void graphics_hook_subscreen_window(HWND hWnd) {
if (GRAPHICS_WSUB_ALWAYS_ON_TOP) { if (GRAPHICS_WSUB_ALWAYS_ON_TOP) {
graphics_update_z_order(hWnd, true); graphics_update_z_order(hWnd, true);
} }
if (GRAPHICS_WINDOW_DISABLE_ROUNDED_CORNERS) {
graphics_set_corner_preference(hWnd, true);
}
} }
void graphics_screens_register(int screen) { void graphics_screens_register(int screen) {
@@ -1149,3 +1123,312 @@ std::string graphics_screenshot_genpath() {
id++; 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"));
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);
}
monitor_layout_needs_reset = true;
// a little extra time for windows to settle and redraw things
Sleep(2000);
}
void update_monitor(bool is_boot, std::optional<graphics_orientation> target_orientation, UINT target_refresh_rate) {
// note: all of this is only being done for the primary motnior
// 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 && target_refresh_rate != dm.dmDisplayFrequency) {
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
log_misc("graphics", "display settings are already up to date, no changes needed");
return;
}
const auto result = ChangeDisplaySettings(&dm, CDS_FULLSCREEN);
if (result != DISP_CHANGE_SUCCESSFUL) {
if (is_boot) {
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_warning(
"graphics",
"failed to update display settings ({}px x {}px @ {}Hz): error {}, double check options",
dm.dmPelsWidth,
dm.dmPelsHeight,
dm.dmDisplayFrequency,
result);
}
} else {
monitor_settings_changed = true;
// sleep for a little bit after changing monitor settings to delay game launch/resume
Sleep(1000);
log_info("graphics", "display settings updated successfully ({}px x {}px @ {}Hz)",
dm.dmPelsWidth,
dm.dmPelsHeight,
dm.dmDisplayFrequency);
}
}
void update_monitor_on_boot(std::optional<graphics_orientation> target_orientation, UINT target_refresh_rate) {
target_orientation_on_boot = target_orientation;
target_refresh_rate_on_boot = target_refresh_rate;
log_misc("graphics", "applying monitor updates at boot...");
update_monitor(true, target_orientation, target_refresh_rate);
}
void update_monitor_at_runtime() {
if (monitor_settings_changed) {
log_misc("graphics", "applying monitor updates at runtime as window regained focus...");
update_monitor(false, target_orientation_on_boot, target_refresh_rate_on_boot);
}
}
void reset_monitor_on_exit() {
// while CDS_FULLSCREEN is *supposed* to be temporary & the OS attempts to
// restore the original settings on exit, it can sometimes fail to do that;
// therefore, we try our best to clean things up on the way out
if (monitor_settings_changed) {
monitor_settings_changed = false;
log_misc("graphics", "resetting monitor settings on exit...");
ChangeDisplaySettingsW(nullptr, 0);
}
// same for this one.
if (monitor_layout_needs_reset) {
monitor_layout_needs_reset = false;
log_misc("graphics", "restoring primary monitor on exit...");
if (SetDisplayConfig_addr != nullptr) {
SetDisplayConfig_addr(
0,
nullptr,
0,
nullptr,
SDC_APPLY | SDC_USE_DATABASE_CURRENT);
}
}
}
+15 -2
View File
@@ -7,6 +7,9 @@
#include <windows.h> #include <windows.h>
#include <d3d9.h> #include <d3d9.h>
#if !SPICE_XP
#include <dwmapi.h>
#endif
#include "external/toojpeg/toojpeg.h" #include "external/toojpeg/toojpeg.h"
@@ -15,6 +18,7 @@ enum graphics_orientation {
ORIENTATION_CW = 0, ORIENTATION_CW = 0,
ORIENTATION_CCW = 1, ORIENTATION_CCW = 1,
ORIENTATION_NORMAL = 2, ORIENTATION_NORMAL = 2,
ORIENTATION_FLIPPED = 3
}; };
enum graphics_dx9on12_state { enum graphics_dx9on12_state {
@@ -29,7 +33,6 @@ extern bool GRAPHICS_LOG_HRESULT;
extern bool GRAPHICS_SDVX_FORCE_720; extern bool GRAPHICS_SDVX_FORCE_720;
extern bool GRAPHICS_SHOW_CURSOR; extern bool GRAPHICS_SHOW_CURSOR;
extern bool GRAPHICS_WINDOWED; extern bool GRAPHICS_WINDOWED;
extern graphics_orientation GRAPHICS_ADJUST_ORIENTATION;
extern std::vector<HWND> GRAPHICS_WINDOWS; extern std::vector<HWND> GRAPHICS_WINDOWS;
extern UINT GRAPHICS_FORCE_REFRESH; extern UINT GRAPHICS_FORCE_REFRESH;
extern std::optional<uint32_t> GRAPHICS_FORCE_REFRESH_SUB; extern std::optional<uint32_t> GRAPHICS_FORCE_REFRESH_SUB;
@@ -50,6 +53,7 @@ extern std::optional<std::pair<uint32_t, uint32_t>> GRAPHICS_WINDOW_SIZE;
extern std::optional<std::string> GRAPHICS_WINDOW_POS; extern std::optional<std::string> GRAPHICS_WINDOW_POS;
extern bool GRAPHICS_WINDOW_ALWAYS_ON_TOP; extern bool GRAPHICS_WINDOW_ALWAYS_ON_TOP;
extern bool GRAPHICS_WINDOW_BACKBUFFER_SCALE; extern bool GRAPHICS_WINDOW_BACKBUFFER_SCALE;
extern bool GRAPHICS_WINDOW_DISABLE_ROUNDED_CORNERS;
extern std::optional<HWND> GRAPHICS_HOOKED_WINDOW; extern std::optional<HWND> GRAPHICS_HOOKED_WINDOW;
extern bool GRAPHICS_IIDX_WSUB; extern bool GRAPHICS_IIDX_WSUB;
@@ -63,9 +67,11 @@ extern bool GRAPHICS_WSUB_BORDERLESS;
extern bool GRAPHICS_WSUB_ALWAYS_ON_TOP; extern bool GRAPHICS_WSUB_ALWAYS_ON_TOP;
extern HWND TDJ_SUBSCREEN_WINDOW; extern HWND TDJ_SUBSCREEN_WINDOW;
extern HWND SDVX_SUBSCREEN_WINDOW; extern HWND SDVX_SUBSCREEN_WINDOW;
extern HWND POPN_SUBSCREEN_WINDOW;
extern HWND GFDM_SUBSCREEN_WINDOW; extern HWND GFDM_SUBSCREEN_WINDOW;
extern bool SUBSCREEN_FORCE_REDRAW; extern bool SUBSCREEN_FORCE_REDRAW;
extern bool FAKE_SUBSCREEN_ADAPTER;
// settings // settings
extern std::string GRAPHICS_DEVICEID; extern std::string GRAPHICS_DEVICEID;
@@ -99,11 +105,18 @@ std::string graphics_screenshot_genpath();
// graphics_windowed.cpp // graphics_windowed.cpp
void graphics_windowed_wndproc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam); void graphics_windowed_wndproc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam);
void graphics_capture_initial_window(HWND hWnd); 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_update_z_order(HWND hWnd, bool always_on_top);
void graphics_set_corner_preference(HWND hWnd, bool disable);
void graphics_move_resize_window(HWND hWnd); void graphics_move_resize_window(HWND hWnd);
bool graphics_window_options_breaks_game(); bool graphics_window_options_breaks_game();
bool graphics_window_decoration_change_crashes_game(); bool graphics_window_decoration_change_crashes_game();
bool graphics_window_resize_breaks_game(); bool graphics_window_resize_breaks_game();
bool graphics_window_move_and_resize_breaks_game(); bool graphics_window_move_and_resize_breaks_game();
void graphics_load_windowed_subscreen_parameters(); 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);
void update_monitor_at_runtime();
void reset_monitor_on_exit();
@@ -24,6 +24,7 @@ std::optional<std::pair<uint32_t, uint32_t>> GRAPHICS_WINDOW_SIZE;
std::optional<std::string> GRAPHICS_WINDOW_POS; std::optional<std::string> GRAPHICS_WINDOW_POS;
bool GRAPHICS_WINDOW_ALWAYS_ON_TOP = false; bool GRAPHICS_WINDOW_ALWAYS_ON_TOP = false;
bool GRAPHICS_WINDOW_BACKBUFFER_SCALE = false; bool GRAPHICS_WINDOW_BACKBUFFER_SCALE = false;
bool GRAPHICS_WINDOW_DISABLE_ROUNDED_CORNERS = false;
std::optional<HWND> GRAPHICS_HOOKED_WINDOW; std::optional<HWND> GRAPHICS_HOOKED_WINDOW;
// IIDX Windowed Subscreen - starts out as false, enabled by IIDX module on pre-attach as needed // IIDX Windowed Subscreen - starts out as false, enabled by IIDX module on pre-attach as needed
@@ -121,18 +122,21 @@ void graphics_capture_initial_window(HWND hWnd) {
cfg::SCREENRESIZE->client_height); cfg::SCREENRESIZE->client_height);
graphics_move_resize_window(hWnd); graphics_move_resize_window(hWnd);
} }
// ddr hangs when window frame doesn't have overlapped
if (cfg::SCREENRESIZE->window_decoration != cfg::WindowDecorationMode::Default) { if (cfg::SCREENRESIZE->window_decoration != cfg::WindowDecorationMode::Default) {
log_info( log_info(
"graphics-windowed", "[{}] change window style - decoration: {}", "graphics-windowed", "[{}] change window style - decoration: {}",
fmt::ptr(hWnd), fmt::ptr(hWnd),
cfg::SCREENRESIZE->window_decoration); cfg::SCREENRESIZE->window_decoration);
graphics_update_window_style(hWnd); graphics_update_window_style(hWnd, true); // force
} }
if (cfg::SCREENRESIZE->window_always_on_top) { if (cfg::SCREENRESIZE->window_always_on_top) {
log_info("graphics-windowed", "[{}] change window z-order - always on top", fmt::ptr(hWnd)); log_info("graphics-windowed", "[{}] change window z-order - always on top", fmt::ptr(hWnd));
graphics_update_z_order(hWnd, true); graphics_update_z_order(hWnd, true);
} }
if (cfg::SCREENRESIZE->window_disable_round_corners) {
log_info("graphics-windowed", "[{}] disabling rounded corners", fmt::ptr(hWnd));
graphics_set_corner_preference(hWnd, true);
}
// ensure spicetouch coordinates are initialized // ensure spicetouch coordinates are initialized
update_spicetouch_window_dimensions(hWnd); update_spicetouch_window_dimensions(hWnd);
@@ -182,6 +186,10 @@ void graphics_load_windowed_parameters() {
cfg::SCREENRESIZE->window_always_on_top = true; cfg::SCREENRESIZE->window_always_on_top = true;
} }
if (GRAPHICS_WINDOW_DISABLE_ROUNDED_CORNERS) {
cfg::SCREENRESIZE->window_disable_round_corners = true;
}
log_debug("graphics-windowed", "graphics_load_windowed_parameters returned"); log_debug("graphics-windowed", "graphics_load_windowed_parameters returned");
} }
@@ -385,11 +393,11 @@ void graphics_wm_sizing_aspect_ratio(int edge, RECT& rect) {
log_debug("graphics-windowed", "graphics_wm_sizing_aspect_ratio returned"); 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) { if (!GRAPHICS_WINDOWED) {
return; return;
} }
if (graphics_window_decoration_change_crashes_game()) { if (!force_update && graphics_window_decoration_change_crashes_game()) {
return; 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"); log_debug("graphics-windowed", "graphics_update_z_order returned");
} }
void graphics_set_corner_preference(HWND hWnd, bool disable) {
if (!GRAPHICS_WINDOWED) {
return;
}
#if !SPICE_XP
log_debug("graphics-windowed", "graphics_set_corner_preference called");
DWM_WINDOW_CORNER_PREFERENCE corner_preference = disable ? DWMWCP_DONOTROUND : DWMWCP_DEFAULT;
DwmSetWindowAttribute(
hWnd,
DWMWA_WINDOW_CORNER_PREFERENCE,
&corner_preference, sizeof(corner_preference));
log_debug("graphics-windowed", "graphics_set_corner_preference returned");
#else
// don't link against DwmSetWindowAttribute on XP
log_debug("graphics-windowed", "graphics_set_corner_preference called; ignoring");
#endif
}
void graphics_move_resize_window(HWND hWnd) { void graphics_move_resize_window(HWND hWnd) {
if (!GRAPHICS_WINDOWED) { if (!GRAPHICS_WINDOWED) {
return; return;
@@ -503,8 +532,15 @@ bool graphics_window_decoration_change_crashes_game() {
static std::once_flag flag; static std::once_flag flag;
static bool result = false; static bool result = false;
std::call_once(flag, []() { std::call_once(flag, []() {
// ddr crashes when frame style changes // ddr and ftt crash when frame style changes
result = avs::game::is_model("MDX"); 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 // changing window decoration also changes dimensions and position
// so this must be prevented // so this must be prevented
@@ -560,3 +596,56 @@ bool graphics_window_resize_breaks_game() {
return result; 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");
}
}
+52
View File
@@ -30,6 +30,8 @@ static decltype(GetLocaleInfoEx) *GetLocaleInfoEx_orig = nullptr;
static decltype(GetSystemDefaultLCID) *GetSystemDefaultLCID_orig = nullptr; static decltype(GetSystemDefaultLCID) *GetSystemDefaultLCID_orig = nullptr;
static decltype(IsDBCSLeadByte) *IsDBCSLeadByte_orig = nullptr; static decltype(IsDBCSLeadByte) *IsDBCSLeadByte_orig = nullptr;
static decltype(WideCharToMultiByte) *WideCharToMultiByte_orig = nullptr; static decltype(WideCharToMultiByte) *WideCharToMultiByte_orig = nullptr;
static decltype(GetLocaleInfoA) *GetLocaleInfoA_orig = nullptr;
static decltype(GetThreadLocale) *GetThreadLocale_orig = nullptr;
#endif #endif
static NTSTATUS NTAPI RtlMultiByteToUnicodeN_hook( static NTSTATUS NTAPI RtlMultiByteToUnicodeN_hook(
@@ -94,6 +96,10 @@ static LCID WINAPI GetSystemDefaultLCID_hook() {
return 0x411; return 0x411;
} }
static LCID WINAPI GetThreadLocale_hook() {
return 0x411;
}
#endif #endif
static int WINAPI MultiByteToWideChar_hook( static int WINAPI MultiByteToWideChar_hook(
@@ -214,6 +220,31 @@ WideCharToMultiByte_hook(
lpUsedDefaultChar); 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 #endif
void hooks::lang::early_init() { void hooks::lang::early_init() {
@@ -244,6 +275,27 @@ void hooks::lang::early_init() {
} }
#ifdef SPICE64 #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 // for TDJ subscreen search keyboard
if (avs::game::is_model("LDJ") && games::iidx::TDJ_MODE) { if (avs::game::is_model("LDJ") && games::iidx::TDJ_MODE) {
log_info("hooks::lang", "hooking IsDBCSLeadByte"); log_info("hooks::lang", "hooking IsDBCSLeadByte");
+82 -26
View File
@@ -351,9 +351,9 @@ int main_implementation(int argc, char *argv[]) {
GRAPHICS_FORCE_SINGLE_ADAPTER = true; GRAPHICS_FORCE_SINGLE_ADAPTER = true;
GRAPHICS_PREVENT_SECONDARY_WINDOW = true; GRAPHICS_PREVENT_SECONDARY_WINDOW = true;
} }
if (options[launcher::Options::DisplayAdapter].is_active() && if (options[launcher::Options::DXDisplayAdapter].is_active() &&
options[launcher::Options::DisplayAdapter].value_uint32() != D3DADAPTER_DEFAULT) { options[launcher::Options::DXDisplayAdapter].value_uint32() != D3DADAPTER_DEFAULT) {
D3D9_ADAPTER = options[launcher::Options::DisplayAdapter].value_uint32(); D3D9_ADAPTER = options[launcher::Options::DXDisplayAdapter].value_uint32();
// when we fix up adapter numbers, we only fix the first adapter, and not any subsequent // 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 // 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()) { if (options[launcher::Options::AllowEA3Verbose].value_bool()) {
avs::ea3::EA3_DEBUG_VERBOSE = true; avs::ea3::EA3_DEBUG_VERBOSE = true;
} }
std::optional<graphics_orientation> monitor_orientation;
if (options[launcher::Options::spice2x_AutoOrientation].is_active()) { if (options[launcher::Options::spice2x_AutoOrientation].is_active()) {
GRAPHICS_ADJUST_ORIENTATION = monitor_orientation =
(graphics_orientation)options[launcher::Options::spice2x_AutoOrientation].value_uint32(); (graphics_orientation)options[launcher::Options::spice2x_AutoOrientation].value_uint32();
} else if (options[launcher::Options::AdjustOrientation].value_bool()) { } 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()) { if (options[launcher::Options::spice2x_NoD3D9DeviceHook].value_bool()) {
D3D9_DEVICE_HOOK_DISABLE = true; D3D9_DEVICE_HOOK_DISABLE = true;
// touch emulation gets disabled, might as well turn these on // 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()) { if (options[launcher::Options::spice2x_SDVXSubRedraw].value_bool()) {
SUBSCREEN_FORCE_REDRAW = true; 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()) { if (options[launcher::Options::LoadIIDXModule].value_bool()) {
attach_iidx = true; attach_iidx = true;
} }
@@ -512,6 +518,12 @@ int main_implementation(int argc, char *argv[]) {
if (options[launcher::Options::IIDXTDJMode].value_bool()) { if (options[launcher::Options::IIDXTDJMode].value_bool()) {
games::iidx::TDJ_MODE = true; games::iidx::TDJ_MODE = true;
} }
if (options[launcher::Options::IIDXTTDelayP1].is_active()) {
games::iidx::TT_DELAY_P1 = options[launcher::Options::IIDXTTDelayP1].value_uint32();
}
if (options[launcher::Options::IIDXTTDelayP2].is_active()) {
games::iidx::TT_DELAY_P2 = options[launcher::Options::IIDXTTDelayP2].value_uint32();
}
if (options[launcher::Options::spice2x_IIDXDigitalTTSensitivity].is_active()) { if (options[launcher::Options::spice2x_IIDXDigitalTTSensitivity].is_active()) {
games::iidx::DIGITAL_TT_SENS = (uint8_t) games::iidx::DIGITAL_TT_SENS = (uint8_t)
options[launcher::Options::spice2x_IIDXDigitalTTSensitivity].value_uint32(); options[launcher::Options::spice2x_IIDXDigitalTTSensitivity].value_uint32();
@@ -584,6 +596,16 @@ int main_implementation(int argc, char *argv[]) {
if (options[launcher::Options::PopnMusicForceSDMode].value_bool()) { if (options[launcher::Options::PopnMusicForceSDMode].value_bool()) {
avs::ea3::PCB_TYPE = 0; 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()) { if (options[launcher::Options::LoadMetalGearArcadeModule].value_bool()) {
attach_mga = true; attach_mga = true;
} }
@@ -980,7 +1002,7 @@ int main_implementation(int argc, char *argv[]) {
// do not explicitly set GRAPHICS_9_ON_12_STATE to default here; must respect // do not explicitly set GRAPHICS_9_ON_12_STATE to default here; must respect
// legacy Graphics9On12 option from above if set // 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; rawinput::NOLEGACY = true;
} }
if (options[launcher::Options::RichPresence].value_bool()) { if (options[launcher::Options::RichPresence].value_bool()) {
@@ -1104,6 +1126,7 @@ int main_implementation(int argc, char *argv[]) {
} }
GRAPHICS_WINDOW_ALWAYS_ON_TOP = options[launcher::Options::spice2x_WindowAlwaysOnTop].value_bool(); GRAPHICS_WINDOW_ALWAYS_ON_TOP = options[launcher::Options::spice2x_WindowAlwaysOnTop].value_bool();
GRAPHICS_WINDOW_BACKBUFFER_SCALE = options[launcher::Options::WindowForceScaling].value_bool(); GRAPHICS_WINDOW_BACKBUFFER_SCALE = options[launcher::Options::WindowForceScaling].value_bool();
GRAPHICS_WINDOW_DISABLE_ROUNDED_CORNERS = options[launcher::Options::WindowDisableRoundedCorners].value_bool();
// IIDX/SDVX Windowed Subscreen // IIDX/SDVX Windowed Subscreen
if (options[launcher::Options::spice2x_IIDXWindowedSubscreenSize].is_active()) { if (options[launcher::Options::spice2x_IIDXWindowedSubscreenSize].is_active()) {
@@ -1124,6 +1147,9 @@ int main_implementation(int argc, char *argv[]) {
options[launcher::Options::SDVXWindowedSubscreenAlwaysOnTop].value_bool()) { options[launcher::Options::SDVXWindowedSubscreenAlwaysOnTop].value_bool()) {
GRAPHICS_WSUB_ALWAYS_ON_TOP = true; 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()) { if (options[launcher::Options::spice2x_JubeatLegacyTouch].value_bool()) {
games::jb::TOUCH_LEGACY_BOX = true; games::jb::TOUCH_LEGACY_BOX = true;
@@ -1291,7 +1317,19 @@ int main_implementation(int argc, char *argv[]) {
} }
// log // 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 #ifdef SPICE64
log_info("launcher", "SpiceTools Bootstrap (x64) (spice2x)"); log_info("launcher", "SpiceTools Bootstrap (x64) (spice2x)");
#elif SPICE32_LARGE_ADDRESS_AWARE #elif SPICE32_LARGE_ADDRESS_AWARE
@@ -1299,12 +1337,6 @@ int main_implementation(int argc, char *argv[]) {
#else #else
log_info("launcher", "SpiceTools Bootstrap (x32) (spice2x)"); log_info("launcher", "SpiceTools Bootstrap (x32) (spice2x)");
#endif #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 #endif
log_info("launcher", "{}", VERSION_STRING); log_info("launcher", "{}", VERSION_STRING);
@@ -1742,11 +1774,21 @@ int main_implementation(int argc, char *argv[]) {
break; break;
} }
// HELLO! Pop'n Music
if (check_dll("popn.dll")) { if (check_dll("popn.dll")) {
avs::game::DLL_NAME = "popn.dll"; avs::game::DLL_NAME = "popn.dll";
attach_io = true; attach_io = 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; attach_hpm = true;
}
break; break;
} }
@@ -1799,9 +1841,7 @@ int main_implementation(int argc, char *argv[]) {
attach_ftt = true; attach_ftt = true;
// the game is windowed by default unless we set the env // the game is windowed by default unless we set the env
if (GRAPHICS_WINDOWED) { if (!GRAPHICS_WINDOWED) {
GRAPHICS_WINDOWED = false;
} else {
SetEnvironmentVariable("DAMAC_VIEWER_FULLSCREEN", "0"); SetEnvironmentVariable("DAMAC_VIEWER_FULLSCREEN", "0");
} }
@@ -2133,6 +2173,12 @@ int main_implementation(int argc, char *argv[]) {
sysutils::print_gpus(); 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 // initialize raw input
RI_MGR = std::make_unique<rawinput::RawInputManager>(); RI_MGR = std::make_unique<rawinput::RawInputManager>();
for (const auto &device : sextet_devices) { for (const auto &device : sextet_devices) {
@@ -2217,7 +2263,7 @@ int main_implementation(int argc, char *argv[]) {
game->attach(); game->attach();
} }
#ifdef SPICE64 #if SPICE64 && !SPICE_XP
if (!cfg::CONFIGURATOR_STANDALONE) { if (!cfg::CONFIGURATOR_STANDALONE) {
if (games::iidx::TDJ_CAMERA) { if (games::iidx::TDJ_CAMERA) {
games::iidx::init_camera_hooks(); games::iidx::init_camera_hooks();
@@ -2324,9 +2370,9 @@ int main_implementation(int argc, char *argv[]) {
// layeredfs // layeredfs
if (fileutils::dir_exists("data_mods") && if (fileutils::dir_exists("data_mods") &&
GetModuleHandleA("ifs_hook.dll") == nullptr) { GetModuleHandleA("ifs_hook.dll") == nullptr) {
log_warning("launcher", "data_mods directory found, but ifs_hook.dll is not present; mods will not load"); log_warning("launcher", "data_mods directory found, but ifs_hook.dll is not loaded; mods will not load");
deferredlogs::defer_error_messages({ deferredlogs::defer_error_messages({
"data_mods directory was found, but ifs_hook.dll is not present", "data_mods directory was found, but ifs_hook.dll is not loaded",
" your mods will not load", " your mods will not load",
" to fix this, download ifs_layeredfs and add it as a DLL hook (-k)", " to fix this, download ifs_layeredfs and add it as a DLL hook (-k)",
" https://github.com/mon/ifs_layeredfs" " https://github.com/mon/ifs_layeredfs"
@@ -2498,7 +2544,7 @@ int main_implementation(int argc, char *argv[]) {
// disable poke // disable poke
games::iidx::poke::disable(); games::iidx::poke::disable();
#ifdef SPICE64 #if SPICE64 && !SPICE_XP
games::iidx::camera_release(); games::iidx::camera_release();
#endif #endif
@@ -2569,9 +2615,10 @@ void dump_button_bindings(std::vector<Button> *buttons) {
if (button->isNaive()) { if (button->isNaive()) {
log_misc( log_misc(
"rawinput", " [{}] dev=Naive, vkey={}", "rawinput", " [{}] dev=Naive, vkey={} ({})",
button->getName(), button->getName(),
button->getVKey() button->getVKey(),
button->getVKeyString()
); );
} else { } else {
log_misc( log_misc(
@@ -2584,18 +2631,27 @@ void dump_button_bindings(std::vector<Button> *buttons) {
} }
for (auto& alt : button->getAlternatives()) { for (auto& alt : button->getAlternatives()) {
if (alt.getVKey() == INVALID_VKEY) { if (!alt.isValid()) {
continue; continue;
} }
if (alt.isNaive()) {
log_misc(
"rawinput", " [{}] (alt) dev=Naive, vkey={} ({})",
alt.getName(),
alt.getVKey(),
alt.getVKeyString()
);
} else {
log_misc( log_misc(
"rawinput", " [{}] (alt) dev={}, vkey={}, analogtype={}", "rawinput", " [{}] (alt) dev={}, vkey={}, analogtype={}",
button->getName(), alt.getName(),
alt.isNaive() ? "Naive" : alt.getDeviceIdentifier(), alt.getDeviceIdentifier(),
alt.getVKey(), alt.getVKey(),
static_cast<uint32_t>(alt.getAnalogType()) static_cast<uint32_t>(alt.getAnalogType())
); );
} }
} }
}
} }
void dump_analog_bindings() { void dump_analog_bindings() {
+146 -42
View File
@@ -23,6 +23,7 @@ static const std::vector<std::string> CATEGORY_ORDER_BASIC = {
"Game Options", "Game Options",
"Common", "Common",
"Network", "Network",
"Monitor",
"Graphics (Common)", "Graphics (Common)",
"Graphics (Full Screen)", "Graphics (Full Screen)",
"Graphics (Windowed)", "Graphics (Windowed)",
@@ -31,6 +32,7 @@ static const std::vector<std::string> CATEGORY_ORDER_BASIC = {
static const std::vector<std::string> CATEGORY_ORDER_ADVANCED = { static const std::vector<std::string> CATEGORY_ORDER_ADVANCED = {
"Game Options (Advanced)", "Game Options (Advanced)",
"Game Options (Peripherals)",
"Overlay", "Overlay",
"Network (Advanced)", "Network (Advanced)",
"Performance", "Performance",
@@ -206,9 +208,24 @@ static const std::vector<OptionDefinition> OPTION_DEFINITIONS = {
.category = "Common", .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", .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" "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. " "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.", "Disable Full Screen Optimizations for best results.",
@@ -216,21 +233,19 @@ static const std::vector<OptionDefinition> OPTION_DEFINITIONS = {
.category = "Graphics (Full Screen)", .category = "Graphics (Full Screen)",
}, },
{ {
.title = "Only Use One Monitor", .title = "Only Use Main Monitor For Full Screen",
.name = "graphics-force-single-adapter", .name = "graphics-force-single-adapter",
.desc = "Force the graphics device to be opened utilizing only one adapter in multi-monitor systems.\n\n" .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!", "May cause unstable framerate and desyncs, especially if monitors have different refresh rates!",
.type = OptionType::Bool, .type = OptionType::Bool,
.category = "Graphics (Full Screen)", .category = "Monitor",
}, },
{ {
.title = "Force Monitor Refresh Rate", .title = "Monitor Refresh Rate",
.name = "graphics-force-refresh", .name = "graphics-force-refresh",
.desc = .desc = "Change the refresh rate for the primary monitor before launching the game. It will be restored on exit.",
"Attempt to change the refresh rate for the primary monitor before the game boots; "
"works in both full screen and windowed modes, but known to fail for some games.",
.type = OptionType::Integer, .type = OptionType::Integer,
.category = "Graphics (Common)", .category = "Monitor",
}, },
{ {
// FullscreenResolution // FullscreenResolution
@@ -499,7 +514,7 @@ static const std::vector<OptionDefinition> OPTION_DEFINITIONS = {
.desc = "Flip the camera order.", .desc = "Flip the camera order.",
.type = OptionType::Bool, .type = OptionType::Bool,
.game_name = "Beatmania IIDX", .game_name = "Beatmania IIDX",
.category = "Game Options (Advanced)", .category = "Game Options (Peripherals)",
}, },
{ {
// IIDXDisableCameras // IIDXDisableCameras
@@ -509,7 +524,7 @@ static const std::vector<OptionDefinition> OPTION_DEFINITIONS = {
.type = OptionType::Bool, .type = OptionType::Bool,
.hidden = true, .hidden = true,
.game_name = "Beatmania IIDX", .game_name = "Beatmania IIDX",
.category = "Game Options", .category = "Game Options (Peripherals)",
}, },
{ {
// IIDXCabCamAccess // IIDXCabCamAccess
@@ -519,7 +534,7 @@ static const std::vector<OptionDefinition> OPTION_DEFINITIONS = {
"Only turn this on if you have OFFICIAL arcade cameras connected to the correct USB ports. Default: auto.", "Only turn this on if you have OFFICIAL arcade cameras connected to the correct USB ports. Default: auto.",
.type = OptionType::Enum, .type = OptionType::Enum,
.game_name = "Beatmania IIDX", .game_name = "Beatmania IIDX",
.category = "Game Options (Advanced)", .category = "Game Options (Peripherals)",
.elements = { .elements = {
{"auto", ""}, {"auto", ""},
{"off", ""}, {"off", ""},
@@ -535,7 +550,7 @@ static const std::vector<OptionDefinition> OPTION_DEFINITIONS = {
.desc = "Experimental camera support for IIDX 27+ via texture hooking. Requires camera(s) that support YUY2 or NV12 encoding.", .desc = "Experimental camera support for IIDX 27+ via texture hooking. Requires camera(s) that support YUY2 or NV12 encoding.",
.type = OptionType::Bool, .type = OptionType::Bool,
.game_name = "Beatmania IIDX", .game_name = "Beatmania IIDX",
.category = "Game Options (Advanced)", .category = "Game Options (Peripherals)",
}, },
{ {
// IIDXCamHookRatio // IIDXCamHookRatio
@@ -547,7 +562,7 @@ static const std::vector<OptionDefinition> OPTION_DEFINITIONS = {
.type = OptionType::Enum, .type = OptionType::Enum,
.hidden = true, .hidden = true,
.game_name = "Beatmania IIDX", .game_name = "Beatmania IIDX",
.category = "Game Options (Advanced)", .category = "Game Options (Peripherals)",
.elements = { .elements = {
{"43", "4:3"}, {"43", "4:3"},
{"169", "16:9"}, {"169", "16:9"},
@@ -565,7 +580,7 @@ static const std::vector<OptionDefinition> OPTION_DEFINITIONS = {
.type = OptionType::Text, .type = OptionType::Text,
.setting_name = "0x5817a0,0x6fffbd8,0xbbae40,0xe0,0x30", .setting_name = "0x5817a0,0x6fffbd8,0xbbae40,0xe0,0x30",
.game_name = "Beatmania IIDX", .game_name = "Beatmania IIDX",
.category = "Game Options (Advanced)", .category = "Game Options (Peripherals)",
}, },
{ {
// IIDXCamHookTopId // IIDXCamHookTopId
@@ -580,7 +595,7 @@ static const std::vector<OptionDefinition> OPTION_DEFINITIONS = {
.type = OptionType::Text, .type = OptionType::Text,
.setting_name = "vid_1234&pid_5678", .setting_name = "vid_1234&pid_5678",
.game_name = "Beatmania IIDX", .game_name = "Beatmania IIDX",
.category = "Game Options (Advanced)", .category = "Game Options (Peripherals)",
}, },
{ {
// IIDXCamHookFrontId // IIDXCamHookFrontId
@@ -595,7 +610,7 @@ static const std::vector<OptionDefinition> OPTION_DEFINITIONS = {
.type = OptionType::Text, .type = OptionType::Text,
.setting_name = "vid_90ab&pid_cdef", .setting_name = "vid_90ab&pid_cdef",
.game_name = "Beatmania IIDX", .game_name = "Beatmania IIDX",
.category = "Game Options (Advanced)", .category = "Game Options (Peripherals)",
}, },
{ {
.title = "IIDX Sound Output Device", .title = "IIDX Sound Output Device",
@@ -640,6 +655,30 @@ static const std::vector<OptionDefinition> OPTION_DEFINITIONS = {
.game_name = "Beatmania IIDX", .game_name = "Beatmania IIDX",
.category = "Game Options", .category = "Game Options",
}, },
{
// IIDXTTDelayP1
.title = "IIDX TT Delay ms (Player 1)",
.name = "iidxttdelayp1",
.desc = "Delays turntable by number of milliseconds. "
"Delay will be *at most* the specified period; maximum error is dependent on the game's poll rate. "
"As usual, changing any option requires a restart. Default: 0 (no delay).",
.type = OptionType::Integer,
.setting_name = "(0-500)",
.game_name = "Beatmania IIDX",
.category = "Game Options (Advanced)",
},
{
// IIDXTTDelayP2
.title = "IIDX TT Delay ms (Player 2)",
.name = "iidxttdelayp2",
.desc = "Delays turntable by number of milliseconds. "
"Delay will be *at most* the specified period; maximum error is dependent on the game's poll rate. "
"As usual, changing any option requires a restart. Default: 0 (no delay).",
.type = OptionType::Integer,
.setting_name = "(0-500)",
.game_name = "Beatmania IIDX",
.category = "Game Options (Advanced)",
},
{ {
// spice2x_IIDXDigitalTTSensitivity // spice2x_IIDXDigitalTTSensitivity
.title = "IIDX Digital TT Sensitivity", .title = "IIDX Digital TT Sensitivity",
@@ -758,11 +797,11 @@ static const std::vector<OptionDefinition> OPTION_DEFINITIONS = {
.category = "Game Options (Advanced)", .category = "Game Options (Advanced)",
}, },
{ {
.title = "SDVX Force 720p", .title = "SDVX Vivid Wave Force 720p Window (DEPRECATED - use -windowsize instead)",
.name = "sdvx720", .name = "sdvx720",
.desc = "Force Sound Voltex 720p display mode, used by older games. " .desc = "Old & deprecated option for launching Vivid Wave in 720p when using windowed mode.",
"For newer games, use window resize function instead.",
.type = OptionType::Bool, .type = OptionType::Bool,
.hidden = true,
.game_name = "Sound Voltex", .game_name = "Sound Voltex",
.category = "Game Options (Advanced)", .category = "Game Options (Advanced)",
}, },
@@ -772,7 +811,7 @@ static const std::vector<OptionDefinition> OPTION_DEFINITIONS = {
.desc = "Enable Sound Voltex printer emulation.", .desc = "Enable Sound Voltex printer emulation.",
.type = OptionType::Bool, .type = OptionType::Bool,
.game_name = "Sound Voltex", .game_name = "Sound Voltex",
.category = "Game Options (Advanced)", .category = "Game Options (Peripherals)",
}, },
{ {
// SDVXPrinterOutputPath // SDVXPrinterOutputPath
@@ -781,7 +820,7 @@ static const std::vector<OptionDefinition> OPTION_DEFINITIONS = {
.desc = "Path to folder where images will be stored.", .desc = "Path to folder where images will be stored.",
.type = OptionType::Text, .type = OptionType::Text,
.game_name = "Sound Voltex", .game_name = "Sound Voltex",
.category = "Game Options (Advanced)", .category = "Game Options (Peripherals)",
.picker = OptionPickerType::DirectoryPath, .picker = OptionPickerType::DirectoryPath,
}, },
{ {
@@ -790,7 +829,7 @@ static const std::vector<OptionDefinition> OPTION_DEFINITIONS = {
.desc = "Clean up saved images in the output directory on startup.", .desc = "Clean up saved images in the output directory on startup.",
.type = OptionType::Bool, .type = OptionType::Bool,
.game_name = "Sound Voltex", .game_name = "Sound Voltex",
.category = "Game Options (Advanced)", .category = "Game Options (Peripherals)",
}, },
{ {
.title = "SDVX Printer Output Overwrite", .title = "SDVX Printer Output Overwrite",
@@ -798,7 +837,7 @@ static const std::vector<OptionDefinition> OPTION_DEFINITIONS = {
.desc = "Always overwrite the same file in output directory.", .desc = "Always overwrite the same file in output directory.",
.type = OptionType::Bool, .type = OptionType::Bool,
.game_name = "Sound Voltex", .game_name = "Sound Voltex",
.category = "Game Options (Advanced)", .category = "Game Options (Peripherals)",
}, },
{ {
// SDVXPrinterOutputFormat // SDVXPrinterOutputFormat
@@ -808,7 +847,7 @@ static const std::vector<OptionDefinition> OPTION_DEFINITIONS = {
.type = OptionType::Text, .type = OptionType::Text,
.setting_name = "(png/bmp/tga/jpg)", .setting_name = "(png/bmp/tga/jpg)",
.game_name = "Sound Voltex", .game_name = "Sound Voltex",
.category = "Game Options (Advanced)", .category = "Game Options (Peripherals)",
}, },
{ {
.title = "SDVX Printer JPG Quality", .title = "SDVX Printer JPG Quality",
@@ -817,7 +856,7 @@ static const std::vector<OptionDefinition> OPTION_DEFINITIONS = {
.type = OptionType::Integer, .type = OptionType::Integer,
.setting_name = "(0-100)", .setting_name = "(0-100)",
.game_name = "Sound Voltex", .game_name = "Sound Voltex",
.category = "Game Options (Advanced)", .category = "Game Options (Peripherals)",
}, },
{ {
.title = "SDVX Disable Cameras (DEPRECATED - no longer needed)", .title = "SDVX Disable Cameras (DEPRECATED - no longer needed)",
@@ -828,7 +867,7 @@ static const std::vector<OptionDefinition> OPTION_DEFINITIONS = {
.type = OptionType::Bool, .type = OptionType::Bool,
.hidden = true, .hidden = true,
.game_name = "Sound Voltex", .game_name = "Sound Voltex",
.category = "Game Options", .category = "Game Options (Peripherals)",
}, },
{ {
.title = "SDVX FS Subscreen Native Touch Handling", .title = "SDVX FS Subscreen Native Touch Handling",
@@ -914,6 +953,17 @@ static const std::vector<OptionDefinition> OPTION_DEFINITIONS = {
{"bottomright", "for landscape"}, {"bottomright", "for landscape"},
}, },
}, },
{
// 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 = "Monitor",
.picker = OptionPickerType::Monitor,
},
{ {
.title = "Force Load DDR Module", .title = "Force Load DDR Module",
.name = "ddr", .name = "ddr",
@@ -963,6 +1013,38 @@ static const std::vector<OptionDefinition> OPTION_DEFINITIONS = {
.game_name = "Pop'n Music", .game_name = "Pop'n Music",
.category = "Game Options", .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", .title = "Force Load HELLO! Pop'n Music Module",
.name = "hpm", .name = "hpm",
@@ -1847,17 +1929,20 @@ static const std::vector<OptionDefinition> OPTION_DEFINITIONS = {
}, },
{ {
// spice2x_AutoOrientation // spice2x_AutoOrientation
.title = "Auto-rotate Display", .title = "Rotate Monitor",
.name = "sp2x-autoorientation", .name = "sp2x-autoorientation",
.display_name = "autoorientation", .display_name = "autoorientation",
.aliases= "autoorientation", .aliases= "autoorientation",
.desc = "Automatically adjust the orientation of your display when launched. " .desc = "Change the orientation of the primary display before launching the game. It will be restored on exit",
"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.",
.type = OptionType::Enum, .type = OptionType::Enum,
.category = "Graphics (Common)", .category = "Monitor",
// match graphics_orientation // match graphics_orientation enum
.elements = {{"0", "90 (CW)"}, {"1", "270 (CCW)"}}, .elements = {
{"0", "Portrait"},
{"1", "Portrait, Flipped"},
{"2", "Landscape"},
{"3", "Landscape, Flipped"}
},
}, },
{ {
.title = "AVS Log Level", .title = "AVS Log Level",
@@ -2170,6 +2255,14 @@ static const std::vector<OptionDefinition> OPTION_DEFINITIONS = {
.type = OptionType::Bool, .type = OptionType::Bool,
.category = "Graphics (Windowed)", .category = "Graphics (Windowed)",
}, },
{
// WindowDisableRoundedCorners
.title = "Disable Round Window Corners",
.name = "windownoroundcorners",
.desc = "Windows 11 and above only: Disables rounded corners on the game window(s).",
.type = OptionType::Bool,
.category = "Graphics (Windowed)",
},
{ {
// spice2x_IIDXWindowedSubscreenSize // spice2x_IIDXWindowedSubscreenSize
.title = "IIDX Windowed Subscreen Size", .title = "IIDX Windowed Subscreen Size",
@@ -2299,7 +2392,7 @@ static const std::vector<OptionDefinition> OPTION_DEFINITIONS = {
"Note: this is NOT for the motion camera - you still need a real RealSense camera for that!", "Note: this is NOT for the motion camera - you still need a real RealSense camera for that!",
.type = OptionType::Bool, .type = OptionType::Bool,
.game_name = "DANCERUSH", .game_name = "DANCERUSH",
.category = "Game Options", .category = "Game Options (Peripherals)",
}, },
{ {
// spice2x_IIDXNativeTouch // spice2x_IIDXNativeTouch
@@ -2326,6 +2419,17 @@ static const std::vector<OptionDefinition> OPTION_DEFINITIONS = {
.game_name = "Beatmania IIDX", .game_name = "Beatmania IIDX",
.category = "Game Options", .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 // spice2x_IIDXEmulateSubscreenKeypadTouch
.title = "IIDX TDJ Subscreen Keypad Touch Emulation", .title = "IIDX TDJ Subscreen Keypad Touch Emulation",
@@ -2622,7 +2726,7 @@ static const std::vector<OptionDefinition> OPTION_DEFINITIONS = {
.desc = "Allow game to access camera; camera must be compatible with game.", .desc = "Allow game to access camera; camera must be compatible with game.",
.type = OptionType::Bool, .type = OptionType::Bool,
.game_name = "LovePlus", .game_name = "LovePlus",
.category = "Game Options", .category = "Game Options (Peripherals)",
}, },
{ {
// LovePlusPrinterOutputPath // LovePlusPrinterOutputPath
@@ -2631,7 +2735,7 @@ static const std::vector<OptionDefinition> OPTION_DEFINITIONS = {
.desc = "Path to folder where images will be stored.", .desc = "Path to folder where images will be stored.",
.type = OptionType::Text, .type = OptionType::Text,
.game_name = "LovePlus", .game_name = "LovePlus",
.category = "Game Options (Advanced)", .category = "Game Options (Peripherals)",
.picker = OptionPickerType::DirectoryPath, .picker = OptionPickerType::DirectoryPath,
}, },
{ {
@@ -2640,7 +2744,7 @@ static const std::vector<OptionDefinition> OPTION_DEFINITIONS = {
.desc = "Clean up saved images in the output directory on startup.", .desc = "Clean up saved images in the output directory on startup.",
.type = OptionType::Bool, .type = OptionType::Bool,
.game_name = "LovePlus", .game_name = "LovePlus",
.category = "Game Options (Advanced)", .category = "Game Options (Peripherals)",
}, },
{ {
.title = "LovePlus Printer Output Overwrite", .title = "LovePlus Printer Output Overwrite",
@@ -2648,7 +2752,7 @@ static const std::vector<OptionDefinition> OPTION_DEFINITIONS = {
.desc = "Always overwrite the same file in output directory.", .desc = "Always overwrite the same file in output directory.",
.type = OptionType::Bool, .type = OptionType::Bool,
.game_name = "LovePlus", .game_name = "LovePlus",
.category = "Game Options (Advanced)", .category = "Game Options (Peripherals)",
}, },
{ {
// LovePlusPrinterOutputFormat // LovePlusPrinterOutputFormat
@@ -2658,7 +2762,7 @@ static const std::vector<OptionDefinition> OPTION_DEFINITIONS = {
.type = OptionType::Text, .type = OptionType::Text,
.setting_name = "(png/bmp/tga/jpg)", .setting_name = "(png/bmp/tga/jpg)",
.game_name = "LovePlus", .game_name = "LovePlus",
.category = "Game Options (Advanced)", .category = "Game Options (Peripherals)",
}, },
{ {
.title = "LovePlus Printer JPG Quality", .title = "LovePlus Printer JPG Quality",
@@ -2667,7 +2771,7 @@ static const std::vector<OptionDefinition> OPTION_DEFINITIONS = {
.type = OptionType::Integer, .type = OptionType::Integer,
.setting_name = "(0-100)", .setting_name = "(0-100)",
.game_name = "LovePlus", .game_name = "LovePlus",
.category = "Game Options (Advanced)", .category = "Game Options (Peripherals)",
}, },
{ {
// OptionConflictResolution // OptionConflictResolution
@@ -2688,7 +2792,7 @@ static const std::vector<OptionDefinition> OPTION_DEFINITIONS = {
"option to bypass camera error during boot, allowing you to try out the game.", "option to bypass camera error during boot, allowing you to try out the game.",
.type = OptionType::Bool, .type = OptionType::Bool,
.game_name = "Otoca D'or", .game_name = "Otoca D'or",
.category = "Game Options", .category = "Game Options (Peripherals)",
} }
}; };
+10 -1
View File
@@ -26,7 +26,8 @@ namespace launcher {
ExecuteScript, ExecuteScript,
CaptureCursor, CaptureCursor,
ShowCursor, ShowCursor,
DisplayAdapter, PrimaryMonitor,
DXDisplayAdapter,
GraphicsForceSingleAdapter, GraphicsForceSingleAdapter,
GraphicsForceRefresh, GraphicsForceRefresh,
FullscreenResolution, FullscreenResolution,
@@ -68,6 +69,8 @@ namespace launcher {
IIDXAsioDriver, IIDXAsioDriver,
IIDXBIO2FW, IIDXBIO2FW,
IIDXTDJMode, IIDXTDJMode,
IIDXTTDelayP1,
IIDXTTDelayP2,
spice2x_IIDXDigitalTTSensitivity, spice2x_IIDXDigitalTTSensitivity,
IIDXDigitalTTSocd, IIDXDigitalTTSocd,
spice2x_IIDXLDJForce720p, spice2x_IIDXLDJForce720p,
@@ -91,12 +94,16 @@ namespace launcher {
SDVXDigitalKnobSocd, SDVXDigitalKnobSocd,
spice2x_SDVXAsioDriver, spice2x_SDVXAsioDriver,
spice2x_SDVXSubPos, spice2x_SDVXSubPos,
SDVXSubMonitorOverride,
LoadDDRModule, LoadDDRModule,
DDR43Mode, DDR43Mode,
DDRSkipCodecRegisteration, DDRSkipCodecRegisteration,
LoadPopnMusicModule, LoadPopnMusicModule,
PopnMusicForceHDMode, PopnMusicForceHDMode,
PopnMusicForceSDMode, PopnMusicForceSDMode,
PopnNoSub,
PopnSubMonitorOverride,
PopnNativeTouch,
LoadHelloPopnMusicModule, LoadHelloPopnMusicModule,
LoadGitaDoraModule, LoadGitaDoraModule,
GitaDoraTwoChannelAudio, GitaDoraTwoChannelAudio,
@@ -228,6 +235,7 @@ namespace launcher {
spice2x_WindowPosition, spice2x_WindowPosition,
spice2x_WindowAlwaysOnTop, spice2x_WindowAlwaysOnTop,
WindowForceScaling, WindowForceScaling,
WindowDisableRoundedCorners,
spice2x_IIDXWindowedSubscreenSize, spice2x_IIDXWindowedSubscreenSize,
spice2x_IIDXWindowedSubscreenPosition, spice2x_IIDXWindowedSubscreenPosition,
IIDXWindowedSubscreenBorderless, IIDXWindowedSubscreenBorderless,
@@ -242,6 +250,7 @@ namespace launcher {
DRSRGBCameraHook, DRSRGBCameraHook,
spice2x_IIDXNativeTouch, spice2x_IIDXNativeTouch,
spice2x_IIDXNoSub, spice2x_IIDXNoSub,
IIDXSubMonitorOverride,
spice2x_IIDXEmulateSubscreenKeypadTouch, spice2x_IIDXEmulateSubscreenKeypadTouch,
spice2x_AutoCard, spice2x_AutoCard,
spice2x_LowLatencySharedAudio, spice2x_LowLatencySharedAudio,
+7 -4
View File
@@ -7,6 +7,7 @@
#include "easrv/easrv.h" #include "easrv/easrv.h"
#include "rawinput/rawinput.h" #include "rawinput/rawinput.h"
#include "hooks/audio/audio.h" #include "hooks/audio/audio.h"
#include "hooks/graphics/graphics.h"
#include "util/deferlog.h" #include "util/deferlog.h"
#include "util/logging.h" #include "util/logging.h"
@@ -22,6 +23,9 @@ namespace launcher {
// therefore, subsystems need to be guarded against multiple unload attempts // therefore, subsystems need to be guarded against multiple unload attempts
log_info("launcher", "stopping subsystems"); log_info("launcher", "stopping subsystems");
// reset monitor settings
reset_monitor_on_exit();
// before shutting down logger, dump any deferred log messages // before shutting down logger, dump any deferred log messages
deferredlogs::dump_to_logger(); deferredlogs::dump_to_logger();
@@ -86,13 +90,12 @@ namespace launcher {
if (LAUNCHER_ARGC > 0) { if (LAUNCHER_ARGC > 0) {
// build cmd line // build cmd line
std::stringstream cmd_line; std::string cmd_line = "START \"\" ";
cmd_line << "START \"\" ";
for (int i = 0; i < LAUNCHER_ARGC; i++) for (int i = 0; i < LAUNCHER_ARGC; i++)
cmd_line << " \"" << LAUNCHER_ARGV[i] << "\""; cmd_line += " \"" + std::string(LAUNCHER_ARGV[i]) + "\"";
// run command // run command
system(cmd_line.str().c_str()); system(cmd_line.c_str());
} }
} }
+4 -4
View File
@@ -131,7 +131,7 @@ bool eamuse_get_card_from_file(const std::filesystem::path &path, uint8_t *card,
// open file // open file
std::ifstream f(path); std::ifstream f(path);
if (!f) { if (!f) {
log_warning("eamuse", "{} can not be opened!", path.string()); log_warning("eamuse", "{} can not be opened!", path);
return false; return false;
} }
@@ -142,7 +142,7 @@ bool eamuse_get_card_from_file(const std::filesystem::path &path, uint8_t *card,
// check size // check size
if (length < 16) { if (length < 16) {
log_warning("eamuse", "{} is too small (must be at least 16 characters)", path.string()); log_warning("eamuse", "{} is too small (must be at least 16 characters)", path);
return false; return false;
} }
@@ -160,14 +160,14 @@ bool eamuse_get_card_from_file(const std::filesystem::path &path, uint8_t *card,
if (!digit && !character_big && !character_small) { if (!digit && !character_big && !character_small) {
log_warning("eamuse", log_warning("eamuse",
"{} contains an invalid character sequence at byte {} (16 characters, 0-9/A-F only)", "{} contains an invalid character sequence at byte {} (16 characters, 0-9/A-F only)",
path.string(), n); path, n);
return false; return false;
} }
} }
// info // info
log_info("eamuse", "[P{}] Inserted {}: {}", index+1, path.string(), buffer); log_info("eamuse", "[P{}] Inserted {}: {}", index+1, path, buffer);
// convert hex to bytes // convert hex to bytes
hex2bin(buffer, card); hex2bin(buffer, card);
+5
View File
@@ -12,6 +12,7 @@
#include "cfg/screen_resize.h" #include "cfg/screen_resize.h"
#include "games/gitadora/gitadora.h" #include "games/gitadora/gitadora.h"
#include "games/iidx/iidx.h" #include "games/iidx/iidx.h"
#include "games/popn/popn.h"
#include "hooks/graphics/graphics.h" #include "hooks/graphics/graphics.h"
#include "overlay/overlay.h" #include "overlay/overlay.h"
#include "overlay/windows/generic_sub.h" #include "overlay/windows/generic_sub.h"
@@ -405,6 +406,10 @@ namespace wintouchemu {
// use mouse position as ImGui overlay will block the touch window // use mouse position as ImGui overlay will block the touch window
log_info("wintouchemu", "use mouse cursor API for ldj overlay subscreen"); log_info("wintouchemu", "use mouse cursor API for ldj overlay subscreen");
USE_MOUSE = true; 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) { } else if (games::gitadora::is_arena_model() && games::gitadora::ARENA_SINGLE_WINDOW) {
log_info("wintouchemu", "use mouse cursor API for gitadora overlay subscreen"); log_info("wintouchemu", "use mouse cursor API for gitadora overlay subscreen");
USE_MOUSE = true; USE_MOUSE = true;
+2
View File
@@ -169,8 +169,10 @@ namespace ImGui {
ImGui::PushStyleColor(ImGuiCol_Header, 0); ImGui::PushStyleColor(ImGuiCol_Header, 0);
ImGui::PushStyleColor(ImGuiCol_HeaderHovered, 0); ImGui::PushStyleColor(ImGuiCol_HeaderHovered, 0);
ImGui::PushStyleColor(ImGuiCol_HeaderActive, 0); ImGui::PushStyleColor(ImGuiCol_HeaderActive, 0);
ImGui::PushTabStop(false); // prevent tab navigation
ImGui::Selectable("##row", false, ImGui::Selectable("##row", false,
ImGuiSelectableFlags_SpanAllColumns | ImGuiSelectableFlags_AllowOverlap); ImGuiSelectableFlags_SpanAllColumns | ImGuiSelectableFlags_AllowOverlap);
ImGui::PopTabStop();
ImGui::PopStyleColor(3); ImGui::PopStyleColor(3);
if (ImGui::IsItemHovered()) { if (ImGui::IsItemHovered()) {
ImGui::TableSetBgColor(ImGuiTableBgTarget_RowBg1, IM_COL32(200, 200, 200, 24)); ImGui::TableSetBgColor(ImGuiTableBgTarget_RowBg1, IM_COL32(200, 200, 200, 24));
+185 -86
View File
@@ -15,6 +15,15 @@
#include "util/utils.h" #include "util/utils.h"
#include "hooks/graphics/graphics.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) || \ #if !defined(IMGUI_ENABLE_WIN32_DEFAULT_IME_FUNCTIONS) || \
!defined(IMGUI_DISABLE_DEFAULT_ALLOCATORS) || \ !defined(IMGUI_DISABLE_DEFAULT_ALLOCATORS) || \
!defined(IMGUI_USE_BGRA_PACKED_COLOR) || \ !defined(IMGUI_USE_BGRA_PACKED_COLOR) || \
@@ -33,6 +42,95 @@ static ImGuiMouseCursor g_LastMouseCursor = ImGuiMouseCursor_COUNT;
static double g_LastMouseMovement = 0.f; static double g_LastMouseMovement = 0.f;
static bool g_MouseCursorAutoHide = false; 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) { bool ImGui_ImplSpice_Init(HWND hWnd) {
log_misc("imgui_impl_spice", "init"); log_misc("imgui_impl_spice", "init");
@@ -59,30 +157,6 @@ bool ImGui_ImplSpice_Init(HWND hWnd) {
io.BackendPlatformName = "imgui_impl_spice"; io.BackendPlatformName = "imgui_impl_spice";
io.ConfigErrorRecoveryEnableTooltip = true; 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 // get display size
ImGui_ImplSpice_UpdateDisplaySize(); ImGui_ImplSpice_UpdateDisplaySize();
@@ -191,7 +265,8 @@ static void ImGui_ImplSpice_UpdateMousePos() {
// trigger any overlay, don't process anything else // trigger any overlay, don't process anything else
const auto is_windowed_subscreen = const auto is_windowed_subscreen =
(GRAPHICS_IIDX_WSUB && active_window == TDJ_SUBSCREEN_WINDOW) || (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) { if (is_windowed_subscreen) {
io.MousePos = ImVec2(-FLT_MAX, -FLT_MAX); io.MousePos = ImVec2(-FLT_MAX, -FLT_MAX);
return; return;
@@ -248,7 +323,7 @@ static void ImGui_ImplSpice_UpdateMousePos() {
} }
// delay press // 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) { if (last_touch_id == ~0u) {
last_touch_id = tp.id; last_touch_id = tp.id;
} }
@@ -274,39 +349,26 @@ void ImGui_ImplSpice_NewFrame() {
io.DeltaTime = (float) (current_time - g_Time) / g_TicksPerSecond; io.DeltaTime = (float) (current_time - g_Time) / g_TicksPerSecond;
g_Time = current_time; 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 // remember old state
BYTE KeysDownOld[sizeof(io.KeysDown)]; std::array<BYTE, VKEY_MAX> KeysDownOld;
for (size_t i = 0; i < sizeof(io.KeysDown); i++) { for (size_t i = 0; i < VKEY_MAX; i++) {
KeysDownOld[i] = io.KeysDown[i] ? ~0 : 0; 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 // reset keys state
io.MouseWheel = 0; g_MouseDown.fill(false);
io.KeyCtrl = false; g_KeysDown.fill(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() || rawinput::OS_WINDOW_ACTIVE) {
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;
// apply windows mouse buttons // apply windows mouse buttons
io.MouseDown[0] |= (get_async_primary_mouse()) != 0; g_MouseDown[ImGuiMouseButton_Left] |= (get_async_primary_mouse()) != 0;
io.MouseDown[1] |= (get_async_secondary_mouse()) != 0; g_MouseDown[ImGuiMouseButton_Right] |= (get_async_secondary_mouse()) != 0;
io.MouseDown[2] |= (GetAsyncKeyState(VK_MBUTTON)) != 0; g_MouseDown[ImGuiMouseButton_Middle] |= (GetAsyncKeyState(VK_MBUTTON)) != 0;
// read new keys state // read new keys state
static long mouse_wheel_last = 0; static long mouse_wheel_last = 0;
@@ -319,62 +381,46 @@ void ImGui_ImplSpice_NewFrame() {
auto &mouse = device.mouseInfo; auto &mouse = device.mouseInfo;
// mouse button triggers // mouse button triggers
if (accept_new_input) {
if (GetSystemMetrics(SM_SWAPBUTTON)) { if (GetSystemMetrics(SM_SWAPBUTTON)) {
if (mouse->key_states[rawinput::MOUSEBTN_RIGHT]) { if (mouse->key_states[rawinput::MOUSEBTN_RIGHT]) {
io.MouseDown[0] = true; g_MouseDown[ImGuiMouseButton_Left] = true;
} }
if (mouse->key_states[rawinput::MOUSEBTN_LEFT]) { if (mouse->key_states[rawinput::MOUSEBTN_LEFT]) {
io.MouseDown[1] = true; g_MouseDown[ImGuiMouseButton_Right] = true;
} }
} else { } else {
if (mouse->key_states[rawinput::MOUSEBTN_LEFT]) { if (mouse->key_states[rawinput::MOUSEBTN_LEFT]) {
io.MouseDown[0] = true; g_MouseDown[ImGuiMouseButton_Left] = true;
} }
if (mouse->key_states[rawinput::MOUSEBTN_RIGHT]) { if (mouse->key_states[rawinput::MOUSEBTN_RIGHT]) {
io.MouseDown[1] = true; g_MouseDown[ImGuiMouseButton_Right] = true;
} }
} }
if (mouse->key_states[rawinput::MOUSEBTN_MIDDLE]) { 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 // final mouse wheel value should be all devices combined
// need to continuously calculate mouse wheel position even when not in focus
// to avoid values jumping (since mouse wheel values are absolute not relative)
mouse_wheel += mouse->pos_wheel; mouse_wheel += mouse->pos_wheel;
break; break;
} }
case rawinput::KEYBOARD: { case rawinput::KEYBOARD: {
if (accept_new_input) {
// iterate all virtual key codes // iterate all virtual key codes
for (size_t vKey = 0; vKey < 256; vKey++) { for (size_t vKey = 0; vKey < VKEY_MAX; vKey++) {
// get state (combined from all pages) // get state (combined from all pages)
auto &key_states = device.keyboardInfo->key_states; auto &key_states = device.keyboardInfo->key_states;
bool state = false;
for (size_t page_index = 0; page_index < 1024; page_index += 256) { for (size_t page_index = 0; page_index < 1024; page_index += 256) {
state |= key_states[page_index + vKey]; g_KeysDown[vKey] |= 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]);
}
}
}
}
break; break;
} }
default: default:
@@ -383,22 +429,75 @@ 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 // set mouse wheel
auto wheel_diff = mouse_wheel - mouse_wheel_last; long wheel_diff = mouse_wheel - mouse_wheel_last;
mouse_wheel_last = mouse_wheel; mouse_wheel_last = mouse_wheel;
io.MouseWheel = wheel_diff; if (wheel_diff != 0 && accept_new_input) {
io.AddMouseWheelEvent(0, wheel_diff);
}
// update OS mouse position // update OS mouse position
const auto old_mouse_pos = io.MousePos; const auto old_mouse_pos = io.MousePos;
if (accept_new_input) {
ImGui_ImplSpice_UpdateMousePos(); ImGui_ImplSpice_UpdateMousePos();
}
const auto new_mouse_pos = io.MousePos; 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 // automatically hide cursor
if (g_MouseCursorAutoHide) { if (g_MouseCursorAutoHide) {
if (old_mouse_pos.x != new_mouse_pos.x || if (old_mouse_pos.x != new_mouse_pos.x ||
old_mouse_pos.y != new_mouse_pos.y || old_mouse_pos.y != new_mouse_pos.y ||
wheel_diff != 0 || wheel_diff != 0 ||
io.MouseDown[0] || io.MouseDown[1] || io.MouseDown[2]) { g_MouseDown[ImGuiMouseButton_Left] || g_MouseDown[ImGuiMouseButton_Right] || g_MouseDown[ImGuiMouseButton_Middle]) {
// mouse moved, update time and show the cursor // mouse moved, update time and show the cursor
g_LastMouseMovement = get_performance_milliseconds(); g_LastMouseMovement = get_performance_milliseconds();
+3 -3
View File
@@ -501,7 +501,7 @@ void paint_draw_cmd(
const SwOptions& options, const SwOptions& options,
Stats* stats) 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; const auto offset = pcmd.IdxOffset;
// ImGui uses the first pixel for "white". // ImGui uses the first pixel for "white".
@@ -678,7 +678,7 @@ void bind_imgui_painting()
int font_width, font_height; int font_width, font_height;
io.Fonts->GetTexDataAsAlpha8(&tex_data, &font_width, &font_height); io.Fonts->GetTexDataAsAlpha8(&tex_data, &font_width, &font_height);
const auto texture = new Texture{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? 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() void unbind_imgui_painting()
{ {
ImGuiIO& io = ImGui::GetIO(); ImGuiIO& io = ImGui::GetIO();
delete reinterpret_cast<Texture*>(io.Fonts->TexID); delete reinterpret_cast<Texture*>(io.Fonts->TexID.GetTexID());
io.Fonts = nullptr; io.Fonts = nullptr;
} }
+9 -5
View File
@@ -5,6 +5,7 @@
#include "games/io.h" #include "games/io.h"
#include "games/gitadora/gitadora.h" #include "games/gitadora/gitadora.h"
#include "games/iidx/iidx.h" #include "games/iidx/iidx.h"
#include "games/popn/popn.h"
#include "hooks/graphics/graphics.h" #include "hooks/graphics/graphics.h"
#include "misc/eamuse.h" #include "misc/eamuse.h"
#include "touch/touch.h" #include "touch/touch.h"
@@ -35,6 +36,7 @@
#include "windows/iopanel_ddr.h" #include "windows/iopanel_ddr.h"
#include "windows/iopanel_gfdm.h" #include "windows/iopanel_gfdm.h"
#include "windows/iopanel_iidx.h" #include "windows/iopanel_iidx.h"
#include "windows/popn_sub.h"
#include "windows/sdvx_sub.h" #include "windows/sdvx_sub.h"
#include "windows/keypad.h" #include "windows/keypad.h"
#include "windows/log.h" #include "windows/log.h"
@@ -185,7 +187,7 @@ void overlay::SpiceOverlay::init() {
if (UI_SCALE_PERCENT.has_value()) { if (UI_SCALE_PERCENT.has_value()) {
const auto scale = static_cast<float>(UI_SCALE_PERCENT.value()) / 100.f; const auto scale = static_cast<float>(UI_SCALE_PERCENT.value()) / 100.f;
ImGui::GetStyle().ScaleAllSizes(scale); ImGui::GetStyle().ScaleAllSizes(scale);
ImGui::GetIO().FontGlobalScale = scale; ImGui::GetStyle().FontScaleMain = scale;
} }
// based on CrimsonVesuvius theme from // based on CrimsonVesuvius theme from
@@ -283,7 +285,7 @@ void overlay::SpiceOverlay::init() {
io.FontAllowUserScaling = true; io.FontAllowUserScaling = true;
// add default font // add default font
io.Fonts->AddFontDefault(); io.Fonts->AddFontDefaultBitmap();
// add fallback fonts for missing glyph ranges // add fallback fonts for missing glyph ranges
ImFontConfig config {}; ImFontConfig config {};
@@ -350,7 +352,7 @@ void overlay::SpiceOverlay::init() {
this->window_add(window_config = new overlay::windows::Config(this)); this->window_add(window_config = new overlay::windows::Config(this));
this->window_add(window_control = new overlay::windows::Control(this)); this->window_add(window_control = new overlay::windows::Control(this));
this->window_add(window_log = new overlay::windows::Log(this)); this->window_add(window_log = new overlay::windows::Log(this));
#ifdef SPICE64 #if SPICE64 && !SPICE_XP
if (avs::game::is_model("LDJ")) { if (avs::game::is_model("LDJ")) {
this->window_add(window_camera = new overlay::windows::CameraControl(this)); this->window_add(window_camera = new overlay::windows::CameraControl(this));
} }
@@ -415,6 +417,8 @@ void overlay::SpiceOverlay::init() {
window_sub = new overlay::windows::SDVXSubScreen(this); window_sub = new overlay::windows::SDVXSubScreen(this);
} else if (games::gitadora::is_arena_model() && games::gitadora::ARENA_SINGLE_WINDOW) { } else if (games::gitadora::is_arena_model() && games::gitadora::ARENA_SINGLE_WINDOW) {
window_sub = new overlay::windows::GitaDoraSubScreen(this); window_sub = new overlay::windows::GitaDoraSubScreen(this);
} else if (games::popn::is_pikapika_model()) {
window_sub = new overlay::windows::PopnSubScreen(this);
} }
if (window_sub) { if (window_sub) {
@@ -729,13 +733,13 @@ void overlay::SpiceOverlay::add_font(const char* font, ImFontConfig* config, con
full_path += font; full_path += font;
if (fileutils::file_exists(full_path)) { if (fileutils::file_exists(full_path)) {
log_misc("overlay", "loading font: {}", full_path.string()); log_misc("overlay", "loading font: {}", full_path);
ImGui::GetIO().Fonts->AddFontFromFileTTF( ImGui::GetIO().Fonts->AddFontFromFileTTF(
full_path.string().c_str(), full_path.string().c_str(),
13.0f, 13.0f,
config, config,
glyphs); glyphs);
} else { } else {
log_misc("overlay", "font not found: {}", full_path.string()); log_misc("overlay", "font not found: {}", full_path);
} }
} }
@@ -1,6 +1,6 @@
#include "camera_control.h" #include "camera_control.h"
#if SPICE64 #if SPICE64 && !SPICE_XP
#include <games/io.h> #include <games/io.h>
#include <strmif.h> #include <strmif.h>
+1 -1
View File
@@ -1,6 +1,6 @@
#pragma once #pragma once
#if SPICE64 #if SPICE64 && !SPICE_XP
#include "overlay/window.h" #include "overlay/window.h"
#include <strmif.h> #include <strmif.h>
+2 -2
View File
@@ -152,8 +152,8 @@ namespace overlay::windows {
void CardManager::open_card_editor() { void CardManager::open_card_editor() {
if (this->current_card) { if (this->current_card) {
const auto card = this->current_card; const auto card = this->current_card;
strcpy_s(this->name_buffer, std::size(this->name_buffer), card->name.c_str()); snprintf(this->name_buffer, std::size(this->name_buffer), "%s", card->name.c_str());
strcpy_s(this->card_buffer, std::size(this->card_buffer), card->id.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[0] = card->color[0];
this->color_buffer[1] = card->color[1]; this->color_buffer[1] = card->color[1];
this->color_buffer[2] = card->color[2]; this->color_buffer[2] = card->color[2];
+379 -131
View File
@@ -17,6 +17,7 @@
#include "external/imgui/misc/cpp/imgui_stdlib.h" #include "external/imgui/misc/cpp/imgui_stdlib.h"
#include "games/io.h" #include "games/io.h"
#include "games/sdvx/sdvx.h" #include "games/sdvx/sdvx.h"
#include "games/popn/popn.h"
#include "avs/core.h" #include "avs/core.h"
#include "avs/ea3.h" #include "avs/ea3.h"
#include "avs/game.h" #include "avs/game.h"
@@ -34,6 +35,7 @@
#include "util/resutils.h" #include "util/resutils.h"
#include "util/scope_guard.h" #include "util/scope_guard.h"
#include "util/time.h" #include "util/time.h"
#include "util/sysutils.h"
#include "util/utils.h" #include "util/utils.h"
#ifdef min #ifdef min
@@ -58,6 +60,7 @@ namespace overlay::windows {
const auto PROJECT_URL = "https://spice2x.github.io"; const auto PROJECT_URL = "https://spice2x.github.io";
constexpr ImVec4 TEXT_COLOR_GREEN(0.f, 1.f, 0.f, 1.f); constexpr ImVec4 TEXT_COLOR_GREEN(0.f, 1.f, 0.f, 1.f);
constexpr ImVec4 TEXT_COLOR_RED(1.f, 0.f, 0.f, 1.f);
constexpr uint32_t OPTION_INPUT_TEXT_WIDTH = 512; constexpr uint32_t OPTION_INPUT_TEXT_WIDTH = 512;
std::unique_ptr<AsioDriverList> asio_driver_list; std::unique_ptr<AsioDriverList> asio_driver_list;
@@ -112,13 +115,23 @@ namespace overlay::windows {
if (cfg::CONFIGURATOR_STANDALONE) { if (cfg::CONFIGURATOR_STANDALONE) {
const auto file_hints = games::get_game_file_hints(game_name); const auto file_hints = games::get_game_file_hints(game_name);
if (file_hints) { if (file_hints) {
for (auto &file_hint : *file_hints) { for (auto &file_hint_list : *file_hints) {
if (fileutils::file_exists(file_hint) || bool matches_all_files = true;
for (auto &file_hint : file_hint_list) {
const auto matched =
(fileutils::file_exists(file_hint) ||
fileutils::dir_exists(file_hint) || fileutils::dir_exists(file_hint) ||
fileutils::file_exists(std::filesystem::path("modules") / file_hint) || fileutils::file_exists(std::filesystem::path("modules") / file_hint) ||
fileutils::file_exists(std::filesystem::path("contents") / file_hint) || fileutils::file_exists(std::filesystem::path("contents") / file_hint) ||
fileutils::file_exists(MODULE_PATH / file_hint)) fileutils::file_exists(MODULE_PATH / file_hint));
{
if (!matched) {
matches_all_files = false;
break;
}
}
if (matches_all_files) {
this->games_selected = games_list.size() - 1; this->games_selected = games_list.size() - 1;
this->games_selected_name = game_name; this->games_selected_name = game_name;
eamuse_set_game(game_name); eamuse_set_game(game_name);
@@ -271,18 +284,8 @@ namespace overlay::windows {
// keypad buttons // keypad buttons
ImGui::TextUnformatted(""); ImGui::TextUnformatted("");
if (this->games_selected_name == "Beatmania IIDX") { this->build_keypad_warning();
ImGui::Indent(INDENT);
ImGui::TextColored(
ImVec4(1, 0.5f, 0.5f, 1.f),
"WARNING: Lightning Model (TDJ) I/O will ignore the keypad!");
ImGui::TextWrapped(
"Use Toggle Sub Screen button to show the overlay and use your mouse, "
"connect using SpiceCompanion app, or connect a touch screen to enter "
"the PIN.");
ImGui::Unindent(INDENT);
ImGui::TextUnformatted("");
}
auto keypad_buttons = games::get_buttons_keypads(this->games_selected_name); auto keypad_buttons = games::get_buttons_keypads(this->games_selected_name);
auto keypad_count = eamuse_get_game_keypads_name(); auto keypad_count = eamuse_get_game_keypads_name();
if (keypad_count == 1) { if (keypad_count == 1) {
@@ -590,6 +593,34 @@ namespace overlay::windows {
} }
} }
void Config::build_keypad_warning() {
// not checking the -iidxtdj option here, we could do that in the future
const bool is_tdj = this->games_selected_name == "Beatmania IIDX";
// not using games::popn::is_pikapika_model() here since that always returns false on 32-bit
const bool is_popn = this->games_selected_name == "Pop'n Music" && avs::game::SPEC[0] == 'D';
if (!is_tdj && !is_popn) {
return;
}
ImGui::Indent(INDENT);
if (is_tdj) {
ImGui::TextColored(
ImVec4(1, 0.5f, 0.5f, 1.f),
"WARNING: Lightning Model (TDJ) I/O will ignore keypad number input!");
} else if (is_popn) {
ImGui::TextColored(
ImVec4(1, 0.5f, 0.5f, 1.f),
"WARNING: PikaPika Pop-Kun model will ignore keypad number input!");
}
ImGui::TextWrapped(
"Use Toggle Sub Screen button (Overlay tab) to show the overlay and use your mouse, "
"connect using SpiceCompanion app, or connect a touch screen to enter "
"the PIN.");
ImGui::Unindent(INDENT);
ImGui::TextUnformatted("");
}
void Config::build_buttons(const std::string &name, std::vector<Button> *buttons, int min, int max) { void Config::build_buttons(const std::string &name, std::vector<Button> *buttons, int min, int max) {
ImGui::AlignTextToFramePadding(); ImGui::AlignTextToFramePadding();
ImGui::TextColored(ImVec4(1.f, 0.7f, 0, 1), "%s Buttons", name.c_str()); ImGui::TextColored(ImVec4(1.f, 0.7f, 0, 1), "%s Buttons", name.c_str());
@@ -661,7 +692,7 @@ namespace overlay::windows {
// longest column is probably "Toggle Virtual Keypad P1" in Overlay tab // longest column is probably "Toggle Virtual Keypad P1" in Overlay tab
ImGui::TableSetupColumn("Name", ImGuiTableColumnFlags_WidthFixed, overlay::apply_scaling(220)); ImGui::TableSetupColumn("Name", ImGuiTableColumnFlags_WidthFixed, overlay::apply_scaling(220));
ImGui::TableSetupColumn("Binding", ImGuiTableColumnFlags_WidthStretch); ImGui::TableSetupColumn("Binding", ImGuiTableColumnFlags_WidthStretch);
ImGui::TableSetupColumn("Actions", ImGuiTableColumnFlags_WidthFixed, overlay::apply_scaling(140)); ImGui::TableSetupColumn("Actions", ImGuiTableColumnFlags_WidthFixed, overlay::apply_scaling(170));
// check if empty // check if empty
if (!buttons || buttons->empty()) { if (!buttons || buttons->empty()) {
@@ -901,8 +932,8 @@ namespace overlay::windows {
// naive binding // naive binding
ImGui::SameLine(); ImGui::SameLine();
std::string naive_string = "Naive " + button_name; std::string naive_string = "Naive/XInput " + button_name;
if (ImGui::Button("Naive") if (ImGui::Button("Naive/XInput")
|| (buttons_many_active && buttons_many_active_section == name && !buttons_bind_active || (buttons_many_active && buttons_many_active_section == name && !buttons_bind_active
&& alt_index == 0 && alt_index == 0
&& buttons_many_naive && buttons_many_index == button_it && buttons_many_naive && buttons_many_index == button_it
@@ -922,11 +953,12 @@ namespace overlay::windows {
} }
if (ImGui::IsItemHovered(ImGui::TOOLTIP_FLAGS)) { if (ImGui::IsItemHovered(ImGui::TOOLTIP_FLAGS)) {
ImGui::HelpTooltip( ImGui::HelpTooltip(
"Uses GetAsyncKeyState to check for any keyboard / mouse input. " "Alternative to Bind, using GetAsyncKeyState for KB/Mouse and XInput for controllers.\n\n"
"For best performance, Bind should be preferred, but this can be used when:\n" "This can be used when:\n"
" * you don't care about which device is used\n" " * you want to use XInput for gamepads (for analog triggers)\n"
" * you don't care about which keyboard/mouse is used\n"
" * you want to use input remapping or automation software\n" " * you want to use input remapping or automation software\n"
" * if you have NKRO issues with Bind"); " * you have NKRO issues with Bind");
} }
naive_button_popup(naive_string, button, button_it_max, alt_index); naive_button_popup(naive_string, button, button_it_max, alt_index);
@@ -986,7 +1018,7 @@ namespace overlay::windows {
} }
// Naive Many // Naive Many
if (ImGui::MenuItem("Naive (many)")) { if (ImGui::MenuItem("Naive/XInput (many)")) {
buttons_many_active = true; buttons_many_active = true;
buttons_many_index = button_it; buttons_many_index = button_it;
buttons_many_naive = true; buttons_many_naive = true;
@@ -1031,6 +1063,7 @@ namespace overlay::windows {
button->setAnalogType(BAT_NONE); button->setAnalogType(BAT_NONE);
button->setDebounceUp(0.0); button->setDebounceUp(0.0);
button->setDebounceDown(0.0); button->setDebounceDown(0.0);
button->setBatThreshold(0);
button->setVelocityThreshold(0); button->setVelocityThreshold(0);
button->setInvert(false); button->setInvert(false);
button->setLastState(GameAPI::Buttons::BUTTON_NOT_PRESSED); button->setLastState(GameAPI::Buttons::BUTTON_NOT_PRESSED);
@@ -1173,6 +1206,16 @@ namespace overlay::windows {
case rawinput::HID: { case rawinput::HID: {
auto hid = device->hidInfo; auto hid = device->hidInfo;
struct temp_button {
std::string device_name;
unsigned short vkey;
ButtonAnalogType analog_type;
};
// use this to prioritize button binding over value types
// (for controllers that output both)
std::optional<temp_button> button_to_bind;
// ignore touchscreen and digitizer button inputs // ignore touchscreen and digitizer button inputs
// digitizer has funky stuff like "Touch Valid" "Data Valid" always held high // digitizer has funky stuff like "Touch Valid" "Data Valid" always held high
if (!rawinput::touch::is_touchscreen(device) && if (!rawinput::touch::is_touchscreen(device) &&
@@ -1186,18 +1229,11 @@ namespace overlay::windows {
// check if button is down // check if button is down
if (button_states[i]) { if (button_states[i]) {
button_to_bind = {
// bind key device->name,
button->setDeviceIdentifier(device->name); static_cast<unsigned short>(button_index + i),
button->setVKey(static_cast<unsigned short>(button_index + i)); BAT_NONE
button->setAnalogType(BAT_NONE); };
::Config::getInstance().updateBinding(
games_list[games_selected], *button,
alt_index - 1);
ImGui::CloseCurrentPopup();
buttons_bind_active = false;
inc_buttons_many_index(button_it_max);
RI_MGR->devices_midi_freeze(false);
break; break;
} }
} }
@@ -1206,6 +1242,7 @@ namespace overlay::windows {
} }
// value caps // value caps
if (!button_to_bind.has_value()) {
auto value_states = &hid->value_states; auto value_states = &hid->value_states;
auto bind_value_states = &hid->bind_value_states; auto bind_value_states = &hid->bind_value_states;
auto value_names = &hid->value_caps_names; auto value_names = &hid->value_caps_names;
@@ -1228,19 +1265,11 @@ namespace overlay::windows {
auto bat = normalized > 0 ? BAT_POSITIVE : BAT_NEGATIVE; auto bat = normalized > 0 ? BAT_POSITIVE : BAT_NEGATIVE;
// bind value // bind value
button->setDeviceIdentifier(device->name); button_to_bind = {
button->setVKey(static_cast<unsigned short>(i)); device->name,
button->setAnalogType(bat); static_cast<unsigned short>(i),
button->setDebounceUp(0.0); bat
button->setDebounceDown(0.0); };
button->setVelocityThreshold(0);
::Config::getInstance().updateBinding(
games_list[games_selected], *button,
alt_index - 1);
ImGui::CloseCurrentPopup();
buttons_bind_active = false;
inc_buttons_many_index(button_it_max);
RI_MGR->devices_midi_freeze(false);
// usually, turntables are X, knobs are X and Y // usually, turntables are X, knobs are X and Y
// gamepad triggers are Z, and Rx/Ry are right thumb stick // gamepad triggers are Z, and Rx/Ry are right thumb stick
@@ -1253,6 +1282,8 @@ namespace overlay::windows {
button->getName().find("TT-") != std::string::npos || // iidx button->getName().find("TT-") != std::string::npos || // iidx
button->getName().find("Knob") != std::string::npos || // gitadora guitar button->getName().find("Knob") != std::string::npos || // gitadora guitar
button->getName().find("Disk") != std::string::npos || // museca, bishibashi button->getName().find("Disk") != std::string::npos || // museca, bishibashi
button->getName().find("P1 Panel") != std::string::npos || // ddr
button->getName().find("P2 Panel") != std::string::npos || // ddr
button->getName().find("VOL-") != std::string::npos)) { // sdvx button->getName().find("VOL-") != std::string::npos)) { // sdvx
this->analog_as_button_warning_show_next_frame = this->analog_as_button_warning_show_next_frame =
std::make_pair(button->getName(), alt_index); std::make_pair(button->getName(), alt_index);
@@ -1276,11 +1307,25 @@ namespace overlay::windows {
if (buffer[0] != BAT_NONE && buffer[0] != buffer_bind[0]) { if (buffer[0] != BAT_NONE && buffer[0] != buffer_bind[0]) {
// bind value // bind value
button->setDeviceIdentifier(device->name); button_to_bind = {
button->setVKey(static_cast<unsigned short>(i)); device->name,
button->setAnalogType(buffer[0]); static_cast<unsigned short>(i),
buffer[0]
};
break;
}
}
}
}
if (button_to_bind.has_value()) {
const auto &b = button_to_bind.value();
button->setDeviceIdentifier(b.device_name);
button->setVKey(b.vkey);
button->setAnalogType(b.analog_type);
button->setDebounceUp(0.0); button->setDebounceUp(0.0);
button->setDebounceDown(0.0); button->setDebounceDown(0.0);
button->setBatThreshold(0);
button->setVelocityThreshold(0); button->setVelocityThreshold(0);
::Config::getInstance().updateBinding( ::Config::getInstance().updateBinding(
games_list[games_selected], *button, games_list[games_selected], *button,
@@ -1289,10 +1334,8 @@ namespace overlay::windows {
buttons_bind_active = false; buttons_bind_active = false;
inc_buttons_many_index(button_it_max); inc_buttons_many_index(button_it_max);
RI_MGR->devices_midi_freeze(false); RI_MGR->devices_midi_freeze(false);
break;
}
}
} }
break; break;
} }
case rawinput::MIDI: { case rawinput::MIDI: {
@@ -1328,6 +1371,7 @@ namespace overlay::windows {
button->setAnalogType(BAT_NONE); button->setAnalogType(BAT_NONE);
button->setDebounceUp(0.0); button->setDebounceUp(0.0);
button->setDebounceDown(0.0); button->setDebounceDown(0.0);
button->setBatThreshold(0);
// same idea as setMidiVKey - keep velocity threshold consistent // same idea as setMidiVKey - keep velocity threshold consistent
button->setVelocityThreshold( button->setVelocityThreshold(
device->midiInfo->v2_velocity_threshold[button->getVKey()]); device->midiInfo->v2_velocity_threshold[button->getVKey()]);
@@ -1353,6 +1397,7 @@ namespace overlay::windows {
button->setAnalogType(BAT_MIDI_CTRL_PRECISION); button->setAnalogType(BAT_MIDI_CTRL_PRECISION);
button->setDebounceUp(0.0); button->setDebounceUp(0.0);
button->setDebounceDown(0.0); button->setDebounceDown(0.0);
button->setBatThreshold(0);
button->setVelocityThreshold(0); button->setVelocityThreshold(0);
::Config::getInstance().updateBinding( ::Config::getInstance().updateBinding(
games_list[games_selected], *button, games_list[games_selected], *button,
@@ -1379,6 +1424,7 @@ namespace overlay::windows {
button->setAnalogType(BAT_MIDI_CTRL_SINGLE); button->setAnalogType(BAT_MIDI_CTRL_SINGLE);
button->setDebounceUp(0.0); button->setDebounceUp(0.0);
button->setDebounceDown(0.0); button->setDebounceDown(0.0);
button->setBatThreshold(0);
button->setVelocityThreshold(0); button->setVelocityThreshold(0);
::Config::getInstance().updateBinding( ::Config::getInstance().updateBinding(
games_list[games_selected], *button, games_list[games_selected], *button,
@@ -1405,6 +1451,7 @@ namespace overlay::windows {
button->setAnalogType(BAT_MIDI_CTRL_ONOFF); button->setAnalogType(BAT_MIDI_CTRL_ONOFF);
button->setDebounceUp(0.0); button->setDebounceUp(0.0);
button->setDebounceDown(0.0); button->setDebounceDown(0.0);
button->setBatThreshold(0);
button->setVelocityThreshold(0); button->setVelocityThreshold(0);
::Config::getInstance().updateBinding( ::Config::getInstance().updateBinding(
games_list[games_selected], *button, games_list[games_selected], *button,
@@ -1430,6 +1477,7 @@ namespace overlay::windows {
button->setAnalogType(BAT_MIDI_PITCH_DOWN); button->setAnalogType(BAT_MIDI_PITCH_DOWN);
button->setDebounceUp(0.0); button->setDebounceUp(0.0);
button->setDebounceDown(0.0); button->setDebounceDown(0.0);
button->setBatThreshold(0);
button->setVelocityThreshold(0); button->setVelocityThreshold(0);
::Config::getInstance().updateBinding( ::Config::getInstance().updateBinding(
games_list[games_selected], *button, games_list[games_selected], *button,
@@ -1450,6 +1498,7 @@ namespace overlay::windows {
button->setAnalogType(BAT_MIDI_PITCH_UP); button->setAnalogType(BAT_MIDI_PITCH_UP);
button->setDebounceUp(0.0); button->setDebounceUp(0.0);
button->setDebounceDown(0.0); button->setDebounceDown(0.0);
button->setBatThreshold(0);
button->setVelocityThreshold(0); button->setVelocityThreshold(0);
::Config::getInstance().updateBinding( ::Config::getInstance().updateBinding(
games_list[games_selected], *button, games_list[games_selected], *button,
@@ -1479,6 +1528,7 @@ namespace overlay::windows {
button->setAnalogType(BAT_NONE); button->setAnalogType(BAT_NONE);
button->setDebounceUp(0.0); button->setDebounceUp(0.0);
button->setDebounceDown(0.0); button->setDebounceDown(0.0);
button->setBatThreshold(0);
button->setVelocityThreshold(0); button->setVelocityThreshold(0);
::Config::getInstance().updateBinding( ::Config::getInstance().updateBinding(
games_list[games_selected], *button, games_list[games_selected], *button,
@@ -1543,6 +1593,22 @@ namespace overlay::windows {
} }
} }
// check xinput
if (check_devices) {
xinput::XINPUT_NEW_BUTTON xinput;
if (RI_MGR->XINPUT_MGR->get_any_button_pressed(xinput)) {
button->setDeviceIdentifier(xinput::get_device_desc(xinput.player));
button->setVKey(static_cast<uint16_t>(xinput.button));
::Config::getInstance().updateBinding(
games_list[games_selected], *button, alt_index - 1);
inc_buttons_many_index(button_it_max);
buttons_bind_active = false;
ImGui::CloseCurrentPopup();
check_devices = false;
}
}
// check GetAsyncKeyState
if (check_devices) { if (check_devices) {
// get new keyboard state // get new keyboard state
// these are async, and some keys generate multiple vKeys (e.g., VK_SHIFT, VK_LSHIFT) // these are async, and some keys generate multiple vKeys (e.g., VK_SHIFT, VK_LSHIFT)
@@ -1587,6 +1653,7 @@ namespace overlay::windows {
button->setVKey(vKey); button->setVKey(vKey);
button->setDebounceUp(0.0); button->setDebounceUp(0.0);
button->setDebounceDown(0.0); button->setDebounceDown(0.0);
button->setBatThreshold(0);
button->setVelocityThreshold(0); button->setVelocityThreshold(0);
::Config::getInstance().updateBinding( ::Config::getInstance().updateBinding(
games_list[games_selected], *button, games_list[games_selected], *button,
@@ -1638,6 +1705,9 @@ namespace overlay::windows {
button->setDeviceIdentifier(device.name); button->setDeviceIdentifier(device.name);
// reset controls when switching devices // reset controls when switching devices
button->setAnalogType(ButtonAnalogType::BAT_NONE); button->setAnalogType(ButtonAnalogType::BAT_NONE);
button->setDebounceUp(0.0);
button->setDebounceDown(0.0);
button->setBatThreshold(0);
button->setVelocityThreshold(0); button->setVelocityThreshold(0);
button->setVKey(0); button->setVKey(0);
button->setInvert(false); button->setInvert(false);
@@ -1933,6 +2003,7 @@ namespace overlay::windows {
int vKey = button->getVKey(); int vKey = button->getVKey();
if (ImGui::InputInt(button->isNaive() ? "Virtual Key" : "Index", &vKey, 1, 1)) { if (ImGui::InputInt(button->isNaive() ? "Virtual Key" : "Index", &vKey, 1, 1)) {
button->setVKey(vKey); button->setVKey(vKey);
button->setInvert(false);
} }
if (ImGui::IsItemDeactivatedAfterEdit()) { if (ImGui::IsItemDeactivatedAfterEdit()) {
dirty = true; dirty = true;
@@ -2000,12 +2071,65 @@ namespace overlay::windows {
"This setting will add noticable input lag."); "This setting will add noticable input lag.");
} }
// invert // invert (widget hidden for naive + 0xff)
if (!(button->isNaive() && button->getVKey() == INVALID_VKEY)) {
bool invert = button->getInvert(); bool invert = button->getInvert();
if (ImGui::Checkbox("Invert", &invert)) { if (ImGui::Checkbox("Invert Resulting Value", &invert)) {
button->setInvert(invert); button->setInvert(invert);
dirty = true; dirty = true;
} }
}
// always on (naive + vkey 0xff + invert)
if (button->isNaive() && alt_index == 0) {
bool always_on = button->isNaive() && button->getVKey() == INVALID_VKEY && button->getInvert();
if (ImGui::Checkbox("Always On", &always_on)) {
if (always_on) {
button->setVKey(INVALID_VKEY);
button->setInvert(true);
} else {
button->setInvert(false);
}
dirty = true;
}
}
// bat threshold
if (device != nullptr && device->type == rawinput::HID &&
(button->getAnalogType() == BAT_POSITIVE || button->getAnalogType() == BAT_NEGATIVE)) {
int bat_threshold = button->getBatThreshold();
bool is_active = (bat_threshold > 0);
if (ImGui::Checkbox("Custom Analog Threshold", &is_active)) {
if (!is_active) {
button->setBatThreshold(0);
} else if (bat_threshold == 0) {
button->setBatThreshold(60);
}
dirty = true;
}
ImGui::SameLine();
ImGui::HelpMarker(
"Assuming that 50% is the center, value exceeding 60% triggers the button by default. "
"With this option, you can customize the trigger threshold.\n\n"
"Setting this to 51% means tiny movements will trigger the button (small deadzone), "
"while setting this to 99% means the button will only trigger at maximum value (large deadzone).\n\n"
"Setting this to below 50% adjusts the neutral / center position.");
if (is_active) {
if (ImGui::SliderInt(
"Analog Threshold",
&bat_threshold,
1, 99,
"%d%%",
ImGuiSliderFlags_AlwaysClamp)) {
button->setBatThreshold(bat_threshold);
}
if (ImGui::IsItemDeactivatedAfterEdit()) {
dirty = true;
}
}
}
// state display // state display
ImGui::TextUnformatted(""); ImGui::TextUnformatted("");
@@ -2085,9 +2209,9 @@ namespace overlay::windows {
ImGui::Separator(); ImGui::Separator();
if (ImGui::BeginTable("AnalogsTable", 3, ImGuiTableFlags_Resizable | ImGuiTableFlags_RowBg)) { if (ImGui::BeginTable("AnalogsTable", 3, ImGuiTableFlags_Resizable | ImGuiTableFlags_RowBg)) {
ImGui::TableSetupColumn("Name", ImGuiTableColumnFlags_WidthFixed, overlay::apply_scaling(220)); ImGui::TableSetupColumn("Name", ImGuiTableColumnFlags_WidthFixed, overlay::apply_scaling(240));
ImGui::TableSetupColumn("Binding", ImGuiTableColumnFlags_WidthStretch); ImGui::TableSetupColumn("Binding", ImGuiTableColumnFlags_WidthStretch);
ImGui::TableSetupColumn("Actions", ImGuiTableColumnFlags_WidthFixed, overlay::apply_scaling(100)); ImGui::TableSetupColumn("Actions", ImGuiTableColumnFlags_WidthFixed, overlay::apply_scaling(80));
// check if empty // check if empty
if (!analogs || analogs->empty()) { if (!analogs || analogs->empty()) {
@@ -2146,8 +2270,9 @@ namespace overlay::windows {
} }
// analog binding // analog binding
const std::string title = "Analog Binding (" + analog.getName() + ")";
if (ImGui::Button("Set")) { if (ImGui::Button("Set")) {
ImGui::OpenPopup("Analog Binding"); ImGui::OpenPopup(title.c_str());
// get devices // get devices
this->analogs_devices.clear(); this->analogs_devices.clear();
@@ -2163,6 +2288,9 @@ namespace overlay::windows {
case rawinput::MIDI: case rawinput::MIDI:
this->analogs_devices.emplace_back(&device); this->analogs_devices.emplace_back(&device);
break; break;
case rawinput::XINPUT_GAMEPAD:
this->analogs_devices.emplace_back(&device);
break;
default: default:
continue; continue;
} }
@@ -2174,7 +2302,7 @@ namespace overlay::windows {
} }
} }
edit_analog_popup(analog); edit_analog_popup(analog, title);
// row hover detection (invisible selectable that spans entire row) // row hover detection (invisible selectable that spans entire row)
ImGui::InvisibleTableRowSelectable(); ImGui::InvisibleTableRowSelectable();
@@ -2188,16 +2316,15 @@ namespace overlay::windows {
} }
} }
void Config::edit_analog_popup(Analog &analog) { void Config::edit_analog_popup(Analog &analog, std::string title) {
if (ImGui::BeginPopupModal("Analog Binding", NULL, ImGuiWindowFlags_AlwaysAutoResize)) { if (ImGui::BeginPopupModal(title.c_str(), NULL, ImGuiWindowFlags_AlwaysAutoResize)) {
// device selector // device selector
auto analog_device_changed = ImGui::Combo( auto analog_device_changed = ImGui::Combo(
"Device", "Device",
&this->analogs_devices_selected, &this->analogs_devices_selected,
[](void* data, int i, const char **item) { [](void* data, int i) {
*item = ((std::vector<rawinput::Device*>*) data)->at(i)->desc.c_str(); return ((std::vector<rawinput::Device*>*) data)->at(i)->desc.c_str();
return true;
}, },
&this->analogs_devices, (int) this->analogs_devices.size()); &this->analogs_devices, (int) this->analogs_devices.size());
@@ -2288,6 +2415,15 @@ namespace overlay::windows {
onoff.size()); onoff.size());
} }
} }
break;
}
case rawinput::XINPUT_GAMEPAD: {
for (int i = 0; i < static_cast<int>(xinput::XInputAnalogEnum::COUNT); i++) {
const auto v = static_cast<xinput::XInputAnalogEnum>(i);
control_names.push_back(xinput::get_analog_string(v));
}
break;
} }
default: default:
break; break;
@@ -2316,9 +2452,8 @@ namespace overlay::windows {
// controls // controls
ImGui::Combo("Control", ImGui::Combo("Control",
&this->analogs_devices_control_selected, &this->analogs_devices_control_selected,
[](void* data, int i, const char **item) { [](void* data, int i) {
*item = ((std::vector<std::string>*) data)->at(i).c_str(); return ((std::vector<std::string>*) data)->at(i).c_str();
return true;
}, },
&control_names, control_names.size()); &control_names, control_names.size());
@@ -2389,31 +2524,45 @@ namespace overlay::windows {
const bool value_changed = const bool value_changed =
ImGui::SliderFloat("Sensitivity", &sensitivity, 0.f, 2.f, "%.3f"); ImGui::SliderFloat("Sensitivity", &sensitivity, 0.f, 2.f, "%.3f");
ImGui::SameLine(); ImGui::SameLine();
if (device->type == rawinput::HID && analog.getType() != GameAPI::Analogs::AnalogType::Circular) {
ImGui::HelpMarker(
"Adjust the analog sensitivity curve; "
"values <1.0 are less sensitive around neutral, "
"values >1.0 are more sensitive.");
} else {
ImGui::HelpMarker( ImGui::HelpMarker(
"Adjust floating point multiplier to relative movement.\n\n" "Adjust floating point multiplier to relative movement.\n\n"
"Value is squared before being multiplied (e.g., 1.44 is 2x sensitivity, 2.00 is 4x).\n\n" "Value is squared before being multiplied (e.g., 1.44 is 2x sensitivity, 2.00 is 4x).");
"Dependent on how often the game polls for input. Intended for angular input (knobs, turntables)"); }
if (value_changed) { if (value_changed) {
analog.setSensitivity(sensitivity * sensitivity); analog.setSensitivity(sensitivity * sensitivity);
} }
} }
if (device->type == rawinput::HID || device->type == rawinput::MIDI) {
// hide deadzone for circular analog since it doesn't make any sense
if ((device->type == rawinput::HID || device->type == rawinput::MIDI) &&
analog.getType() != GameAPI::Analogs::AnalogType::Circular) {
auto deadzone = analog.getDeadzone(); auto deadzone = analog.getDeadzone();
// for back compat (before each analog had a type)
if (deadzone < 0.f) {
deadzone = -deadzone;
analog.setDeadzone(deadzone);
}
const bool value_changed = const bool value_changed =
ImGui::SliderFloat("Deadzone", &deadzone, -0.999f, 0.999f, "%.3f"); ImGui::SliderFloat("Deadzone", &deadzone, 0.f, 0.999f, "%.3f");
if (value_changed) { if (value_changed) {
analog.setDeadzone(deadzone); analog.setDeadzone(deadzone);
} }
ImGui::SameLine(); ImGui::SameLine();
ImGui::HelpMarker("Positive values specify a deadzone around the middle.\n" ImGui::HelpMarker("Specify the deadzone that gets applied to at-rest (neutral) value.");
"Negative values specify a deadzone from the minimum value.");
// deadzone mirror // deadzone mirror
bool deadzone_mirror = analog.getDeadzoneMirror(); bool deadzone_mirror = analog.getDeadzoneMirror();
ImGui::Checkbox("Deadzone Mirror", &deadzone_mirror); ImGui::Checkbox("Deadzone Mirror", &deadzone_mirror);
ImGui::SameLine(); ImGui::SameLine();
ImGui::HelpMarker("Positive deadzone values cut off at edges instead.\n" ImGui::HelpMarker("Apply deadzone to extreme value(s) instead of neutral.");
"Negative deadzone values cut off at maximum value instead.");
if (deadzone_mirror != analog.getDeadzoneMirror()) { if (deadzone_mirror != analog.getDeadzoneMirror()) {
analog.setDeadzoneMirror(deadzone_mirror); analog.setDeadzoneMirror(deadzone_mirror);
} }
@@ -2432,47 +2581,20 @@ namespace overlay::windows {
if (this->analogs_devices_selected >= 0) { if (this->analogs_devices_selected >= 0) {
const auto device = this->analogs_devices.at(this->analogs_devices_selected); const auto device = this->analogs_devices.at(this->analogs_devices_selected);
if (device->type == rawinput::HID) { if (device->type == rawinput::HID) {
if (analog.getType() == GameAPI::Analogs::AnalogType::Circular) {
// smoothing // smoothing
bool smoothing = analog.getSmoothing(); bool smoothing = analog.getSmoothing();
ImGui::BeginDisabled(analog.isRelativeMode());
ImGui::Checkbox("Smooth Axis (adds latency)", &smoothing); ImGui::Checkbox("Smooth Axis (adds latency)", &smoothing);
ImGui::SameLine(); ImGui::SameLine();
ImGui::HelpMarker( ImGui::HelpMarker(
"Apply a moving average algorithm; intended for angular input (knobs, turntables). " "Apply a moving average algorithm; intended for angular input (knobs, turntables). "
"Adds a slight bit of latency to input as the algorithm averages out recent input. " "Adds a slight bit of latency to input as the algorithm averages out recent input. "
"Only use in dire situations where the input is too jittery for the game."); "Only use in dire situations where the input is too jittery for the game.");
ImGui::EndDisabled();
if (smoothing != analog.getSmoothing()) { if (smoothing != analog.getSmoothing()) {
analog.setSmoothing(smoothing); analog.setSmoothing(smoothing);
} }
// relative input mode
bool relative_analog = analog.isRelativeMode();
ImGui::Checkbox("Relative Axis (experimental)", &relative_analog);
ImGui::SameLine();
ImGui::HelpMarker(
"Use relative directional input instead of positional values.\n\n"
"Can be used to translate analog sticks to knob input, for example.\n\n"
"WARNING: speed depends on how often the game polls for input! "
"Strongly recommended that you go into the game's test menu instead "
"of adjusting in spicecfg.");
if (relative_analog != analog.isRelativeMode()) {
analog.setRelativeMode(relative_analog);
} }
// delay buffer
int delay = analog.getDelayBufferDepth();
ImGui::InputInt("Delay (experimental)", &delay, 1, 10);
if (ImGui::IsItemDeactivatedAfterEdit()) {
delay = CLAMP(delay, 0, 256);
analog.setDelayBufferDepth(delay);
}
ImGui::SameLine();
ImGui::HelpMarker(
"Adds a delay to input. This is poll-based, not time-based.\n\n"
"WARNING: delay depends on how often the game polls for input! "
"Strongly recommended that you go into the game's test menu instead "
"of adjusting in spicecfg.");
} }
} }
@@ -2483,10 +2605,12 @@ namespace overlay::windows {
ImGui::ProgressBar(value); ImGui::ProgressBar(value);
// centered knob preview // centered knob preview
if (analog.getType() == GameAPI::Analogs::AnalogType::Circular) {
const float knob_size = 64.f; const float knob_size = 64.f;
auto width = ImGui::GetContentRegionAvail().x - knob_size; auto width = ImGui::GetContentRegionAvail().x - knob_size;
ImGui::SetCursorPosX(ImGui::GetCursorPosX() + (width / 2)); ImGui::SetCursorPosX(ImGui::GetCursorPosX() + (width / 2));
ImGui::Knob(value, knob_size); ImGui::Knob(value, knob_size);
}
// update analog // update analog
if (analogs_devices_selected >= 0 && analogs_devices_selected < (int) analogs_devices.size()) { if (analogs_devices_selected >= 0 && analogs_devices_selected < (int) analogs_devices.size()) {
@@ -2725,6 +2849,7 @@ namespace overlay::windows {
case rawinput::PIUIO_DEVICE: case rawinput::PIUIO_DEVICE:
case rawinput::SMX_STAGE: case rawinput::SMX_STAGE:
case rawinput::SMX_DEDICAB: case rawinput::SMX_DEDICAB:
case rawinput::XINPUT_GAMEPAD:
this->auto_match_devices.emplace_back(&device); this->auto_match_devices.emplace_back(&device);
break; break;
default: default:
@@ -2891,6 +3016,7 @@ namespace overlay::windows {
case rawinput::PIUIO_DEVICE: case rawinput::PIUIO_DEVICE:
case rawinput::SMX_STAGE: case rawinput::SMX_STAGE:
case rawinput::SMX_DEDICAB: case rawinput::SMX_DEDICAB:
case rawinput::XINPUT_GAMEPAD:
this->lights_devices.emplace_back(&device); this->lights_devices.emplace_back(&device);
break; break;
default: default:
@@ -2956,9 +3082,8 @@ namespace overlay::windows {
bool control_changed = false; bool control_changed = false;
if (ImGui::Combo("Device", if (ImGui::Combo("Device",
&this->lights_devices_selected, &this->lights_devices_selected,
[] (void* data, int i, const char **item) { [] (void* data, int i) {
*item = ((std::vector<rawinput::Device*>*) data)->at(i)->desc.c_str(); return ((std::vector<rawinput::Device*>*) data)->at(i)->desc.c_str();
return true;
}, },
&this->lights_devices, (int) this->lights_devices.size())) { &this->lights_devices, (int) this->lights_devices.size())) {
this->lights_devices_control_selected = 0; this->lights_devices_control_selected = 0;
@@ -3045,6 +3170,15 @@ namespace overlay::windows {
} }
break; break;
} }
case rawinput::XINPUT_GAMEPAD: {
// add all names of XInput gamepad device
for (int i = 0; i < static_cast<int>(xinput::XInputOutputEnum::COUNT); i++) {
control_names.emplace_back(
xinput::get_output_string(static_cast<xinput::XInputOutputEnum>(i)));
}
break;
}
default: default:
break; break;
} }
@@ -3053,9 +3187,8 @@ namespace overlay::windows {
// controls // controls
if (ImGui::Combo("Light Control", if (ImGui::Combo("Light Control",
&this->lights_devices_control_selected, &this->lights_devices_control_selected,
[] (void* data, int i, const char **item) { [] (void* data, int i) {
*item = ((std::vector<std::string> *) data)->at(i).c_str(); return ((std::vector<std::string> *) data)->at(i).c_str();
return true;
}, },
&control_names, control_names.size())) { &control_names, control_names.size())) {
control_changed = true; control_changed = true;
@@ -3136,10 +3269,9 @@ namespace overlay::windows {
ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x);
bool device_changed = ImGui::Combo("##AutoMatchDevice", bool device_changed = ImGui::Combo("##AutoMatchDevice",
&this->auto_match_device_selected, &this->auto_match_device_selected,
[] (void* data, int i, const char **item) { [] (void* data, int i) {
auto *devs = (std::vector<rawinput::Device*>*) data; auto *devs = (std::vector<rawinput::Device*>*) data;
*item = devs->at(i)->desc.c_str(); return devs->at(i)->desc.c_str();
return true;
}, },
&this->auto_match_devices, &this->auto_match_devices,
(int) this->auto_match_devices.size()); (int) this->auto_match_devices.size());
@@ -3592,6 +3724,13 @@ namespace overlay::windows {
} }
break; break;
} }
case rawinput::XINPUT_GAMEPAD: {
for (int i = 0; i < static_cast<int>(xinput::XInputOutputEnum::COUNT); i++) {
raw_names.emplace_back(
xinput::get_output_string(static_cast<xinput::XInputOutputEnum>(i)));
}
break;
}
default: default:
break; break;
} }
@@ -3831,18 +3970,26 @@ namespace overlay::windows {
// generate button // generate button
ImGui::SameLine(); ImGui::SameLine();
if (ImGui::Button("Generate")) { if (ImGui::Button("Generate")) {
if (!this->keypads_card_overwrite_confirmed[player] && buffer_len > 0) {
ImGui::OpenPopup("Card Generator##GenerateCardConfirm");
} else {
generate_ea_card(buffer); generate_ea_card(buffer);
card_changed = true; card_changed = true;
} }
}
// clear button // clear button
if (option.is_active()) { if (option.is_active()) {
ImGui::SameLine(); ImGui::SameLine();
if (ImGui::Button("Clear")) { if (ImGui::Button("Clear")) {
if (!this->keypads_card_overwrite_confirmed[player] && buffer_len > 0) {
ImGui::OpenPopup("Clear Card##ClearCardConfirm");
} else {
buffer[0] = '\0'; buffer[0] = '\0';
card_changed = true; card_changed = true;
} }
} }
}
} else { } else {
if (ImGui::IsItemHovered(ImGui::TOOLTIP_FLAGS)) { if (ImGui::IsItemHovered(ImGui::TOOLTIP_FLAGS)) {
ImGui::HelpTooltip( ImGui::HelpTooltip(
@@ -3857,6 +4004,61 @@ namespace overlay::windows {
ImGui::TextColored(ImVec4(1.f, 0.f, 0.f, 1.f), "Invalid card number"); ImGui::TextColored(ImVec4(1.f, 0.f, 0.f, 1.f), "Invalid card number");
} }
if (ImGui::BeginPopupModal(
"Card Generator##GenerateCardConfirm",
nullptr,
ImGuiWindowFlags_AlwaysAutoResize)) {
ImGui::TextUnformatted(
"What do you want to do?");
ImGui::Spacing();
ImGui::Separator();
ImGui::Spacing();
ImGui::AlignTextToFramePadding();
ImGui::Bullet();
ImGui::SameLine();
if (ImGui::Button("Discard current card and generate new one")) {
this->keypads_card_overwrite_confirmed[player] = true;
generate_ea_card(buffer);
card_changed = true;
ImGui::CloseCurrentPopup();
}
ImGui::AlignTextToFramePadding();
ImGui::Bullet();
ImGui::SameLine();
if (ImGui::Button("Keep current card and cancel")) {
ImGui::CloseCurrentPopup();
}
ImGui::EndPopup();
}
if (ImGui::BeginPopupModal(
"Clear Card##ClearCardConfirm",
nullptr,
ImGuiWindowFlags_AlwaysAutoResize)) {
ImGui::TextUnformatted(
"This cannot be undone. Are you sure?");
ImGui::Spacing();
ImGui::Separator();
ImGui::Spacing();
ImGui::AlignTextToFramePadding();
ImGui::Bullet();
ImGui::SameLine();
if (ImGui::Button("Yes, delete card number")) {
this->keypads_card_overwrite_confirmed[player] = true;
buffer[0] = '\0';
card_changed = true;
ImGui::CloseCurrentPopup();
}
ImGui::AlignTextToFramePadding();
ImGui::Bullet();
ImGui::SameLine();
if (ImGui::Button("No, keep my card number")) {
ImGui::CloseCurrentPopup();
}
ImGui::EndPopup();
}
ImGui::TreePop(); ImGui::TreePop();
} }
@@ -3995,7 +4197,7 @@ namespace overlay::windows {
} }
ImGui::SameLine(); ImGui::SameLine();
if (ImGui::Button("Edit")) { if (ImGui::Button("Edit in Notepad")) {
std::filesystem::path path; std::filesystem::path path;
if (!bindings.card_paths[player].empty()) { if (!bindings.card_paths[player].empty()) {
path = bindings.card_paths[player]; path = bindings.card_paths[player];
@@ -4023,19 +4225,20 @@ namespace overlay::windows {
} }
// card number box // card number box
ImGui::PushStyleColor(
ImGuiCol_Text,
this->keypads_card_file_contents_valid[player] ? ImVec4(1.f, 1.f, 1.f, 1.f) :
ImVec4(1.f, 0.f, 0.f, 1.f));
ImGui::SetNextItemWidth(TEXT_INPUT_WIDTH); ImGui::SetNextItemWidth(TEXT_INPUT_WIDTH);
ImGui::BeginDisabled(); ImGui::BeginDisabled();
if (this->keypads_card_number[player][0] != 0) { if (this->keypads_card_number[player][0] != 0) {
ImGui::PushStyleColor(
ImGuiCol_Text,
this->keypads_card_file_contents_valid[player] ?
TEXT_COLOR_GREEN : TEXT_COLOR_RED);
ImGui::Text("Card from file: %s", this->keypads_card_number[player]); ImGui::Text("Card from file: %s", this->keypads_card_number[player]);
ImGui::PopStyleColor();
} else { } else {
ImGui::TextUnformatted("Failed to read file."); ImGui::TextColored(TEXT_COLOR_RED, "Failed to read file.");
} }
ImGui::EndDisabled(); ImGui::EndDisabled();
ImGui::PopStyleColor();
} }
ImGui::TreePop(); ImGui::TreePop();
@@ -4177,6 +4380,8 @@ namespace overlay::windows {
return "File Picker"; return "File Picker";
case OptionPickerType::DirectoryPath: case OptionPickerType::DirectoryPath:
return "Folder Picker"; return "Folder Picker";
case OptionPickerType::Monitor:
return "Monitor Picker";
default: default:
return "Unknown Picker"; return "Unknown Picker";
}; };
@@ -4226,7 +4431,9 @@ namespace overlay::windows {
ImGui::TextUnformatted("Requires restart! Showing all procs in Group 0."); ImGui::TextUnformatted("Requires restart! Showing all procs in Group 0.");
ImGui::TextUnformatted(""); ImGui::TextUnformatted("");
ImGui::TextUnformatted("Pick CPU cores to use:"); ImGui::TextUnformatted("Pick CPU cores to use:");
const uint64_t cpu_count = GetActiveProcessorCount(0); SYSTEM_INFO info;
GetSystemInfo(&info);
const uint64_t cpu_count = info.dwNumberOfProcessors;
uint64_t affinity = 0; uint64_t affinity = 0;
if (!option.value.empty()) { if (!option.value.empty()) {
try { try {
@@ -4407,6 +4614,30 @@ namespace overlay::windows {
ImGui::EndDisabled(); ImGui::EndDisabled();
} }
} else if (definition.picker == OptionPickerType::Monitor) {
const auto &monitors = sysutils::enumerate_monitors();
if (monitors.empty()) {
ImGui::TextUnformatted("Failed to fetch monitors. Requires Win7+");
} else {
ImGui::TextUnformatted("Pick from monitors:");
ImGui::SetNextItemWidth(300.f);
if (ImGui::BeginListBox("##monitors")) {
for (const auto &monitor : monitors) {
const bool is_selected = option.value == monitor.display_name;
auto friendly = wchar_to_u8(monitor.friendly_name.c_str());
auto str = fmt::format("{} ({})", monitor.display_name, friendly);
if (monitor.is_primary) {
str += " [Primary]";
}
if (ImGui::Selectable(str.c_str(), is_selected)) {
option.value = monitor.display_name;
}
}
ImGui::EndListBox();
}
}
} else { } else {
ImGui::TextUnformatted("No picker available for this option. How did you get here?"); ImGui::TextUnformatted("No picker available for this option. How did you get here?");
} }
@@ -4495,7 +4726,7 @@ namespace overlay::windows {
if (definition.category.empty()) { if (definition.category.empty()) {
ImGui::TextDisabled("-"); ImGui::TextDisabled("-");
} else { } else {
ImGui::TextDisabled(definition.category.c_str()); ImGui::TextDisabled("%s", definition.category.c_str());
} }
} }
@@ -4787,12 +5018,24 @@ namespace overlay::windows {
// check if empty // check if empty
if (options_count == 0) { if (options_count == 0) {
ImGui::TableNextRow(); ImGui::TableNextRow();
// option category
if (filter != nullptr) {
ImGui::TableNextColumn(); ImGui::TableNextColumn();
ImGui::TextDisabled("-");
}
// name of option
ImGui::TableNextColumn();
if (filter == nullptr) {
ImGui::Indent(INDENT); ImGui::Indent(INDENT);
}
ImGui::TextDisabled("-"); ImGui::TextDisabled("-");
if (filter == nullptr) {
ImGui::Unindent(INDENT); ImGui::Unindent(INDENT);
ImGui::TableNextColumn(); }
ImGui::TextDisabled("-");
// widget
ImGui::TableNextColumn(); ImGui::TableNextColumn();
ImGui::TextDisabled("-"); ImGui::TextDisabled("-");
} }
@@ -4803,14 +5046,19 @@ namespace overlay::windows {
} }
void Config::build_about() { void Config::build_about() {
#ifndef SPICE_LINUX #if SPICE_LINUX
ImGui::TextUnformatted(std::string( ImGui::TextUnformatted(std::string(
"spice2x (a fork of SpiceTools)\r\n" "spice2x (a fork of SpiceTools) for Linux\r\n"
"=========================\r\n" +
to_string(VERSION_STRING)).c_str());
#elif SPICE_XP
ImGui::TextUnformatted(std::string(
"spice2x (a fork of SpiceTools) for WinXP\r\n"
"=========================\r\n" + "=========================\r\n" +
to_string(VERSION_STRING)).c_str()); to_string(VERSION_STRING)).c_str());
#else #else
ImGui::TextUnformatted(std::string( ImGui::TextUnformatted(std::string(
"spice2x (a fork of SpiceTools) for Linux\r\n" "spice2x (a fork of SpiceTools)\r\n"
"=========================\r\n" + "=========================\r\n" +
to_string(VERSION_STRING)).c_str()); to_string(VERSION_STRING)).c_str());
#endif #endif
@@ -5101,6 +5349,7 @@ namespace overlay::windows {
btn.setInvert(entry->invert); btn.setInvert(entry->invert);
btn.setDebounceUp(entry->debounce_up); btn.setDebounceUp(entry->debounce_up);
btn.setDebounceDown(entry->debounce_down); btn.setDebounceDown(entry->debounce_down);
btn.setBatThreshold(entry->bat_threshold);
btn.setVelocityThreshold(entry->velocity_threshold); btn.setVelocityThreshold(entry->velocity_threshold);
::Config::getInstance().updateBinding(game, btn, -1); ::Config::getInstance().updateBinding(game, btn, -1);
} else { } else {
@@ -5111,6 +5360,7 @@ namespace overlay::windows {
alt_btn.setInvert(entry->invert); alt_btn.setInvert(entry->invert);
alt_btn.setDebounceUp(entry->debounce_up); alt_btn.setDebounceUp(entry->debounce_up);
alt_btn.setDebounceDown(entry->debounce_down); alt_btn.setDebounceDown(entry->debounce_down);
alt_btn.setBatThreshold(entry->bat_threshold);
alt_btn.setVelocityThreshold(entry->velocity_threshold); alt_btn.setVelocityThreshold(entry->velocity_threshold);
alt_btn.setTemporary(true); alt_btn.setTemporary(true);
btn.getAlternatives().push_back(alt_btn); btn.getAlternatives().push_back(alt_btn);
@@ -5156,8 +5406,6 @@ namespace overlay::windows {
a.setInvert(ta.invert); a.setInvert(ta.invert);
a.setSmoothing(ta.smoothing); a.setSmoothing(ta.smoothing);
a.setMultiplier(ta.multiplier); a.setMultiplier(ta.multiplier);
a.setRelativeMode(ta.relative_mode);
a.setDelayBufferDepth(ta.delay_buffer_depth);
::Config::getInstance().updateBinding(game, a); ::Config::getInstance().updateBinding(game, a);
break; break;
} }
+5 -2
View File
@@ -87,7 +87,7 @@ namespace overlay::windows {
bool auto_match_soft_enabled = true; bool auto_match_soft_enabled = true;
bool auto_match_p2 = false; bool auto_match_p2 = false;
// keypads tab // cards tab
int keypads_selected[2] {}; int keypads_selected[2] {};
char keypads_card_path[2][1024] {}; char keypads_card_path[2][1024] {};
std::thread *keypads_card_select = nullptr; std::thread *keypads_card_select = nullptr;
@@ -96,6 +96,7 @@ namespace overlay::windows {
char keypads_card_number[2][18] {}; char keypads_card_number[2][18] {};
bool keypads_card_override_valid[2] = { false, false }; bool keypads_card_override_valid[2] = { false, false };
bool keypads_card_file_contents_valid[2] = { false, false }; bool keypads_card_file_contents_valid[2] = { false, false };
bool keypads_card_overwrite_confirmed[2];
// presets tab // presets tab
std::vector<ControllerTemplate> templates_cache; std::vector<ControllerTemplate> templates_cache;
@@ -161,7 +162,7 @@ namespace overlay::windows {
unsigned int get_keypad_top_row(const Button &button); unsigned int get_keypad_top_row(const Button &button);
void build_analogs(const std::string &name, std::vector<Analog> *analogs); void build_analogs(const std::string &name, std::vector<Analog> *analogs);
void edit_analog_popup(Analog &analog); void edit_analog_popup(Analog &analog, std::string title);
void update() override; void update() override;
void stop_lights_test(); void stop_lights_test();
@@ -192,6 +193,8 @@ namespace overlay::windows {
std::vector<Option> *options, const std::string &category, const std::string *filter=nullptr); std::vector<Option> *options, const std::string &category, const std::string *filter=nullptr);
void build_about(); void build_about();
void build_launcher(); void build_launcher();
void build_keypad_warning();
void launch_shell(LPCSTR app, LPCSTR file=nullptr); void launch_shell(LPCSTR app, LPCSTR file=nullptr);
void build_menu(int *game_selected); void build_menu(int *game_selected);
+13 -10
View File
@@ -628,13 +628,12 @@ namespace overlay::windows {
ImGui::Text("Wheel: %ld", mouse->pos_wheel); ImGui::Text("Wheel: %ld", mouse->pos_wheel);
// keys // keys
std::stringstream keys; std::string keys = "[";
keys << "[";
for (auto key : mouse->key_states) { for (auto key : mouse->key_states) {
keys << (key ? "1," : "0,"); keys += (key ? "1," : "0,");
} }
keys << "]"; keys += "]";
ImGui::Text("Keys: %s", keys.str().c_str()); ImGui::Text("Keys: %s", keys.c_str());
break; break;
} }
@@ -643,15 +642,14 @@ namespace overlay::windows {
ImGui::Text("Type: Keyboard"); ImGui::Text("Type: Keyboard");
// keys // keys
std::stringstream keys; std::string keys = "[";
keys << "[";
for (size_t i = 0; i < std::size(keyboard->key_states); i++) { for (size_t i = 0; i < std::size(keyboard->key_states); i++) {
if (keyboard->key_states[i]) { if (keyboard->key_states[i]) {
keys << i << ","; keys += std::to_string(i) + ",";
} }
} }
keys << "]"; keys += "]";
ImGui::Text("Keys: %s", keys.str().c_str()); ImGui::Text("Keys: %s", keys.c_str());
break; break;
} }
@@ -732,6 +730,10 @@ namespace overlay::windows {
ImGui::Text("Type: PIUIO"); ImGui::Text("Type: PIUIO");
break; break;
} }
case rawinput::XINPUT_GAMEPAD: {
ImGui::Text("Type: XInput");
break;
}
case rawinput::DESTROYED: { case rawinput::DESTROYED: {
ImGui::Text("Disconnected."); ImGui::Text("Disconnected.");
break; break;
@@ -739,6 +741,7 @@ namespace overlay::windows {
case rawinput::UNKNOWN: case rawinput::UNKNOWN:
default: default:
ImGui::Text("Type: Unknown"); ImGui::Text("Type: Unknown");
break;
} }
ImGui::TreePop(); ImGui::TreePop();
} }
@@ -17,6 +17,7 @@ namespace overlay::windows {
bool invert = false; bool invert = false;
double debounce_up = 0.0; double debounce_up = 0.0;
double debounce_down = 0.0; double debounce_down = 0.0;
int bat_threshold = 0;
unsigned short velocity_threshold = 0; unsigned short velocity_threshold = 0;
bool is_naive() const { return device_identifier.empty() && vKey != INVALID_VKEY; } bool is_naive() const { return device_identifier.empty() && vKey != INVALID_VKEY; }
@@ -31,6 +32,7 @@ namespace overlay::windows {
e.invert = btn.getInvert(); e.invert = btn.getInvert();
e.debounce_up = btn.getDebounceUp(); e.debounce_up = btn.getDebounceUp();
e.debounce_down = btn.getDebounceDown(); e.debounce_down = btn.getDebounceDown();
e.bat_threshold = btn.getBatThreshold();
e.velocity_threshold = btn.getVelocityThreshold(); e.velocity_threshold = btn.getVelocityThreshold();
return e; return e;
} }
@@ -69,8 +71,6 @@ namespace overlay::windows {
bool invert = false; bool invert = false;
bool smoothing = false; bool smoothing = false;
int multiplier = 1; int multiplier = 1;
bool relative_mode = false;
int delay_buffer_depth = 0;
bool is_device() const { return !device_identifier.empty(); } bool is_device() const { return !device_identifier.empty(); }
bool is_unbound() const { return device_identifier.empty() && index == 0xFF; } bool is_unbound() const { return device_identifier.empty() && index == 0xFF; }
@@ -87,8 +87,6 @@ namespace overlay::windows {
invert = a.getInvert(); invert = a.getInvert();
smoothing = a.getSmoothing(); smoothing = a.getSmoothing();
multiplier = a.getMultiplier(); multiplier = a.getMultiplier();
relative_mode = a.isRelativeMode();
delay_buffer_depth = a.getDelayBufferDepth();
} }
}; };
@@ -4,6 +4,7 @@
#include "util/logging.h" #include "util/logging.h"
#include "games/iidx/iidx.h" #include "games/iidx/iidx.h"
#include "games/gitadora/gitadora.h" #include "games/gitadora/gitadora.h"
#include "games/popn/popn.h"
namespace overlay::windows { namespace overlay::windows {
@@ -72,6 +73,8 @@ namespace overlay::windows {
sub = "Show DRS Dance Floor"; sub = "Show DRS Dance Floor";
} else if (games::gitadora::is_arena_model() && games::gitadora::ARENA_SINGLE_WINDOW) { } else if (games::gitadora::is_arena_model() && games::gitadora::ARENA_SINGLE_WINDOW) {
sub = "Show GITADORA Subscreen"; sub = "Show GITADORA Subscreen";
} else if (games::popn::is_pikapika_model()) {
sub = "Show Pop'n Subscreen";
} }
build_button(this->overlay->window_sub, sub, size, NextItem::NEW_LINE, false); build_button(this->overlay->window_sub, sub, size, NextItem::NEW_LINE, false);
+1 -1
View File
@@ -14,7 +14,7 @@ namespace overlay::windows {
if (GRAPHICS_IIDX_WSUB) { if (GRAPHICS_IIDX_WSUB) {
this->disabled_message = "Close this overlay and use the second window. (try ALT+TAB)"; this->disabled_message = "Close this overlay and use the second window. (try ALT+TAB)";
} else if (games::iidx::IIDX_TDJ_MONITOR_WARNING) { } else if (games::iidx::IIDX_TDJ_MONITOR_WARNING) {
this->disabled_message = "TDJ mode subscreen overlay is not compatible with -monitor option."; this->disabled_message = "TDJ mode subscreen overlay is not compatible with -dxmainadapter option; use -mainmonitor instead";
} }
float size = 0.5f; float size = 0.5f;

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