Author SHA1 Message Date
bicarusandGitHub b53447bed5 popn: fix subscreen redraw option causing graphical glitches (#854)
Continuous Integration / Build (push) Failing after 6s
## Link to GitHub Issue or related Pull Request, if one exists
#0

## Description of change
Old behavior: Forced redraw presented the subscreen every main frame,
even when the game already presented it, causing duplicate presents and
tearing in popn (was fine in sdvx)

New behavior: Forced redraw acts as a fallback, presenting only when the
game skips or fails a subscreen update.

## Testing
Popn - no more glitching
Nabla - no regression
2026-08-06 04:14:31 -07:00
bicarusandGitHub 4959a58de3 iidx: don't hook legacy camera unless requested (#856)
## Link to GitHub Issue or related Pull Request, if one exists
Fixes #855 

## Description of change
Legacy camera hooks are specifically for IIDX 25/26 and they only
perform redirection of device discovery (emulates USB vendor/device ID
and USB port). It has very limited use since most cameras are
practically unusable in IIDX 25/26. Don't enable it, unless explicitly
requested by the user.

New truth table for `-iidxcabcams` :

| Mode | `-iidx` off (cab setup) | `-iidx` on |
|---|---|---|
| `auto` | Native cameras enabled (same as `on`) | Cameras disabled
(same as `off`) |
| `off` | Cameras disabled | Cameras disabled |
| `on` | Native cameras enabled | Native cameras enabled |
| `legacy` | Native cameras enabled | IIDX 25/26 discovery emulation
enabled |

## Testing
2026-08-06 03:44:52 -07:00
bicarusandGitHub 9141ff453b lang: detect forced UTF-8 ACP, try to opt-out (#853)
## Link to GitHub Issue or related Pull Request, if one exists
n/a

## Description of change

Fixes broken Japanese text when Windows `Use Unicode UTF-8 for worldwide
language support` setting is enabled. This setting changes the system
ACP to UTF-8 (65001), causing legacy Shift-JIS lead-byte checks to fail.

Windows 11: requests the legacy process code page through the manifest.

Windows 10: detects ACP 65001 (UTF-8), warns the user via deferred log,
and applies compatibility hook (only for popn pika model for now)

Windows 7 and below - UTF-8 option doesn't exist.

## Testing
2026-08-04 09:04:29 -07:00
bicarusandGitHub f955ab984c iidx: allow main window to receive touches in windows TDJ subscreen mode (#850)
## Link to GitHub Issue or related Pull Request, if one exists
n/a

## Description of change
This allows API touches to be recognized even if the main window is in
focus.

## Testing
2026-08-02 16:46:33 -07:00
bicarus-dev 435b9ce9aa output device name in message 2026-08-02 12:29:21 -07:00
bicarusandGitHub 1ec7528dac gitadora: option to disable frame limiter (#848)
## Link to GitHub Issue or related Pull Request, if one exists
n/a

## Description of change
There is a patch for GITADORA series that fixes frame pacing on modern
Windows. Turn it into a signature patch and embed it into the game.

## Testing
should work for all 64-bit gitadora versions, pre-GWD
2026-08-02 03:35:07 -07:00
bicarus-dev e7822aad8e remove volume boost from quick options 2026-08-02 01:04:11 -07:00
bicarus-dev 8140e83e46 mark -sdvxlandscape as experimental 2026-08-02 01:01:09 -07:00
bicarusandGitHub cc0899290e sdvx: re-enable landscape mode (#847)
## Link to GitHub Issue or related Pull Request, if one exists
n/a

## Description of change
Un-deprecate SDVX landscape mode. The game calls `GetViewport` to figure
out the camera position, so if we return a sane value the game goes back
to rendering correctly.

## Testing
Tested nabla only
2026-08-02 00:50:04 -07:00
3012139028 gitadora: add native dual fullscreen for Arena (#846)
## Link to GitHub Issue or related Pull Request, if one exists

Fixed https://github.com/spice2x/spice2x.github.io/issues/740

## Description of change

- Enable native D3D9 adapter-group dual fullscreen for the GITADORA
Arena model when the two-screen MAIN + SMALL configuration is selected
without windowed mode.
- Present MAIN and SMALL on their respective monitor heads while keeping
the invisible LEFT and RIGHT targets offscreen.
- Scope the new fake-swap-chain query behavior to the hidden GITADORA
two-head targets, preserving existing behavior for other games and
configurations.

The existing borderless-windowed two-screen path could not consistently
keep input and game timing synchronized and also reduced rendering
performance. Native dual fullscreen avoids that windowed composition
path.

## Testing

- Manually tested GITADORA Arena with separate MAIN and SMALL monitors
in fullscreen mode. Windowed mode still works if the user chooses to use
that.
- Confirmed both displays render correctly, touch input works on the
SMALL screen, gameplay input stays synchronized, and gameplay holds a
steady 60 FPS.
- Built `spicetools_spice64` at commit `e4a98e9` with the repository
Docker toolchain.
- Confirmed no new compiler warnings.
- Confirmed the forbidden static DLL import check passes.
- Confirmed the Windows 7 DLL compatibility check reports `All DLLs
OK!`.

---------

Co-authored-by: vgod <428979+vgod@users.noreply.github.com>
2026-08-02 00:03:36 -07:00
bicarusandGitHub c590b87cff jb: add t44 support (#845)
With help from certain sea creature.
2026-08-01 01:20:31 -07:00
bicarusandGitHub 5cabd026ae misc: various performance clean up (#844)
## Link to GitHub Issue or related Pull Request, if one exists
n/a

## Description of change

- Replace unnecessary precision timers with standard sleeps to reduce
wakeups and background CPU usage.
- Reuse buffers in raw input, touchscreen, and HID output paths to
eliminate steady-state allocations.
- Pre-index HID button groups and correctly process batched HID reports.

No functional changes.

## Testing
2026-07-28 21:05:32 -07:00
bicarusandGitHub 7ccd938f9b overlay: mouse-as-touch needs to check if subscreen window is active (#842)
## Link to GitHub Issue or related Pull Request, if one exists
Fixes #840 

## Description of change
Currently, for subscreen games, mouse events are still delivered even
when the subscreen overlay window is not visible.

Change this so that the subscreen overlay must be active and under the
mouse cursor for the mouse-to-touch transformation to occur. Applies to
both native and wintouchemu.

## Testing
Needs to test everything again..

iidx:

- [ ]  full screen with overlay
- [ ]  single window with overlay
- [ ]  two window

Test: mouse, poke, api, real touch screen

sdvx:

- [ ] full screen with overlay
- [ ] windowed

popn

- [x] full screen with overlay
- [x] window with overlay
- [x] dedicated window

gitadora

- [x] single window with overlay
- [x] dedicated sub window

nostalgia

- [x] fullscreen
- [x] windowed

test: poke

wintouchemu
- [ ] Do all of the above again with wintouchemu
2026-07-28 01:14:47 -07:00
bicarusandGitHub 88a210e82e iidx: camera hook improvements (#841)
## Link to GitHub Issue or related Pull Request, if one exists
#201

## Description of change

- Adds MJPEG camera support using Media Foundation decoding.
- Moves camera capture to asynchronous Source Reader callbacks.
- Supports native NV12 and YUY2 capture, preferring target-sized native
formats over MJPEG when possible.
- Improves capture and rendering performance through callback-paced
reads, bulk frame uploads, optimized row-based flips, and lazy
allocation of flip textures.
- Makes runtime media-type changes interrupt pending reads and safely
flush and reconfigure the Source Reader.
- Improves automatic mode selection based on aspect ratio, proximity to
1280x720, frame rate, and capture format.
- Hardens camera shutdown, flush handling, media-type changes, and
repeated capture failures.

## Testing
Tested with two cameras in tdj
2026-07-27 08:42:46 -07:00
bicarusandGitHub 74e619df37 sdvx: fix check for enable_console in avs_config.xml (#838)
## Link to GitHub Issue or related Pull Request, if one exists
Regressed by #657 

## Description of change
Workaround for SDVX4 was applied too broadly and caused sdvx 1/2/3 to
not boot, depending on contents of avs-config.xml.

`property_search_safe` throws a fatal error if the node is not present.
What we wanted to do was to check for presence.

## Testing
sanity checked sdvx 1/2/3/4
2026-07-26 16:16:25 -07:00
bicarusandGitHub 10a97b9c63 signal: dump exception context, log dump creation failure (#837)
More diagnostics info for game crashes.
2026-07-26 15:25:24 -07:00
bicarusandGitHub 159043803c imgui: fix crash when launching from UNC path (network shares) (#836)
## Link to GitHub Issue or related Pull Request, if one exists
n/a

## Description of change
ImGui filebrowser extension crashes due to MinGW quirk about UNC path
handling.

## Testing
2026-07-26 04:30:06 -07:00
bicarusandGitHub bd2fbfcb67 touch: restore wintouchemu, fall back to it when native touch hooks fail (#834)
## Link to GitHub Issue or related Pull Request, if one exists
Fixes #833

## Description of change
Last couple PRs - such as #820 #827 #828 - made the native touch hook &
touch injection using `InjectTouchInput` the default path, since it
performs much more reliably with both real touch screens and mouse (or
any other synthetic source).

However, user has reported that WINE lacks `InjectTouchInput` which
means this won't work.

As a fix, revive the old wintouchemu code. Native touch is still the
default, but under following circumstances:

1. if `-touchemuforce` is set, or
2. if any of the required Windows touch APIs are unavailable

then we fail over from native touch to wintouchemu code. 

For Linux, condition #2 would be hit during init, and gracefully switch
over.

Caveat: the poke code for IIDX and Nost will continue to require native
touch, I do not want to maintain two paths for this. This means that
iidx poke will stop working on Linux, unfortunately.

## Testing
Tested on Windows with `-touchemuforce` set. This is mostly reverting
Linux code path back to where we were last release, so this should just
work with wine.
2026-07-26 04:29:45 -07:00
bicarusandGitHub 64600826b7 nost: touch piano mode (#830)
## Link to GitHub Issue or related Pull Request, if one exists
n/a

## Description of change
Touch piano mode. Allows piano keys to be played with the touch screen.
Since the touch screen is still needed for menu navigation (including
during songs) a toggle switch is added so that the user can flip between
navigation mode and piano mode.

The touch targets are pixel-perfect. Don't let the game's piano
animations fool you.

## Testing
2026-07-23 15:49:50 -07:00
bicarusandGitHub 80deb4bbbd iidx: hide tdj sub screen in fullscreen single monitor mode (#829)
## Link to GitHub Issue or related Pull Request, if one exists
n/a

## Description of change
Prevent the game from launching a blank window when launching fullscreen
with sub disabled.

## Testing
2026-07-22 14:37:29 -07:00
bicarusandGitHub 00c8bb8ce6 gitadora: use native touch for single-window mode (#828)
## Link to GitHub Issue or related Pull Request, if one exists
#814 

## Description of change
Make gitadora arena model use native touch instead of wintouchemu. Only
single window mode with overlay is affected.

This removes the last consumer of `INJECT_MOUSE_AS_WM_TOUCH` so all of
that code can be deleted.

## Testing
Tested touch screen and mouse interaction with single window mode.
2026-07-22 01:36:06 -07:00
bicarusandGitHub 09ed8b948c nost: move nostalgia from wintouchemu to native touch hook (#827)
## Link to GitHub Issue or related Pull Request, if one exists
#814

## Description of change
Switch over Nostalgia from wintouchemu to native touch hook.

Nostalgia has some strict timing requirements (touches must be updated
on every acio poll) so this takes a slightly different path to maintain
mouse holds.

Only a handful of consumers of wintouchemu remain:

1. beatstream - but it's off by default, only enabled as errata for
buggy touchscreens, or if the user forces it on (for Show Cursor)
2. gitadora single-window overlay - this is just for mouse so not a big
deal.
3. MFC HG mode - not a priority to fix.

## Testing
Tested full screen and windowed mode with touch / mouse / poke.
2026-07-22 00:56:03 -07:00
bicarusandGitHub e2daefbb6a overlay: can't press enter to select sub-tab in Controller tab (#826)
## Link to GitHub Issue or related Pull Request, if one exists
Another bug reported by a sea creature

## Description of change
Fix keyboard navigation in Controllers sub tabs.

## Testing
2026-07-21 21:13:18 -07:00
bicarusandGitHub 39bf17aafe patcher: fix plus minus buttons for number patches (#825)
## Link to GitHub Issue or related Pull Request, if one exists
Reported by a certain sea reptile

## Description of change
Fix the + / - buttons for number patches not working. Regressed by one
of the ImGui version updates.

## Testing
seems fine now
2026-07-21 17:46:18 -07:00
bicarusandGitHub 4836cd94ab touch: inject touches from spiceapi to native touch hooks (#824)
## Link to GitHub Issue or related Pull Request, if one exists
Fixes #814

## Description of change
Touches via the API wasn't being delivered to native touch hook;
implement this, which closes the last gap we have vs. wintouchemu.

While we're here, implement popn subscreen support for companion as
well.

## Testing
Tested for iidx/sdvx/popn
2026-07-21 16:57:11 -07:00
bicarusandGitHub 9d41ca2515 nost: xact audio hook (#823)
## Link to GitHub Issue or related Pull Request, if one exists
Fixes #822

## Description of change
Add hooks for `libxact.dll` which only Nostalgia uses for audio. Detect
cases where the game would fail to launch (wrong number of channels) and
log a warning / deferred log.

## Testing
tested 2024102200
2026-07-21 15:33:30 -07:00
bicarusandGitHub 18421ffbfe touch: make native touch the default for iidx/sdvx/popn (#820)
## Link to GitHub Issue or related Pull Request, if one exists
Fixes #814

## Description of change
Remove iidx/sdvx/popon `native touch` options.

Remove dead code in wintouchemu that deals with these games.

Native touch is now at feature-parity with wintouchemu. Native touch
options work better with touch screens, while rawinput-based wintouchemu
has compat issues on some touchscreens. For these subscreen games
deprecate the usage of wintouchemu and make them use native touch stack
by default.

### For future consideration
Gitadora - not sure what to do with this one. We only use wintouchemu
for the single window case. For all other configurations, the game
accepts mouse and touch input without any hooks, so perhaps nothing
needs to be done here.

Nostalgia - consider moving away from wintouchemu and adopt native touch
hook here as well.

## Testing
WIP
2026-07-21 02:15:56 -07:00
bicarusandGitHub 3f0921d983 gitadora: more windowed mode fixes (#819)
## Link to GitHub Issue or related Pull Request, if one exists
User reported

## Description of change

Sometimes the game moved the main window after `CreateWindowExA`
returned but before D3D initialization called
`graphics_capture_initial_window`. During this gap,
`GRAPHICS_HOOKED_WINDOW` was not set, so the placement hooks could not
identify the main window and preserve its monitor override. GITADORA
main, LEFT, and RIGHT windows are now registered immediately after
creation so later placement calls cannot undo their overrides.

This also adds `-gdwsmallsize` and `-gdwsmallpos` for explicitly setting
the SMALL window size and position. Explicit values override the
corresponding monitor-derived geometry, allowing the SMALL window's
10:16 aspect ratio to be preserved instead of stretching it to fill a
monitor.

## Testing
2026-07-21 00:54:09 -07:00
bicarusandGitHub ed7318c270 rawinput: modifiers (key combinations) (#813)
## Link to GitHub Issue or related Pull Request, if one exists
Fixes #635

## Description of change
Adds optional **button modifiers**, allowing a binding to require one or
more "modifier" buttons to be held before it activates.

- New "Modifiers" controller page in the overlay config to bind the
Modifier 1–4 source buttons.
- Each binding gains a 4-bit modifier_mask (Modifier 1-4). It is
persisted as an optional modifiers XML attribute on button nodes and
controller-preset entries. The attribute is optional and defaults to 0,
so existing config files and presets load unchanged and the feature is
off unless the user opts in.
- The button Edit properties popup gains a "Modifiers" dropdown to pick
which modifiers a binding requires. Doesn't apply to MIDI though.
- Input evaluation skips a binding whose required modifiers are not
held, falling through to its alternatives; velocity reporting uses the
same gated path.
- Controller-preset templates gain a "Modifiers" group for importing and
exporting.

## Testing
2026-07-21 00:26:36 -07:00
bicarusandGitHub 623e1e3998 touch: inject mouse and poke into native touch modes (#815)
## Link to GitHub Issue or related Pull Request, if one exists
Part 1 of many fixes for #814.

## Description of change
For the native touch hook - add code to inject synthetic touches using
Windows API (`InjectTouchInput`).

Note that this is Windows 8+ only, but we can make an assumption that we
are running on Win10+ for these games (TDJ/UFC/High Cheers) since the
cabs assume Win10.

Detect mouse events and allow IIDX poke to call into this to inject
synthetic touches.

Also, update the nativetouchhook to independently calculate window size
and rotation, instead of relying on rawinput layer.

## Testing
Tested to work with native touch option on IIDX, SDVX, POPN, all full
screen / windowed / sub on/off combinations.

Edge cases:

* sdvx main monitor rotated 270 deg
* touch invert option
* windowed mode resize / moved

All seem to work.
2026-07-21 00:04:38 -07:00
bicarusandGitHub 558e3d0cb3 rawinput: avoid enumerationg adapter modes on first touch (#817)
## Link to GitHub Issue or related Pull Request, if one exists
Regressed by #790

## Description of change
Avoid enumerating every display mode with `EnumDisplaySettings` during
the first raw touchscreen report, as it can take a couple hundred
milliseconds.

The latency-sensitive path now refreshes only the current display size
using `GetWindowRect(GetDesktopWindow())` and orientation using
`EnumDisplaySettingsEx(..., ENUM_CURRENT_SETTINGS, ..., EDS_RAWMODE)`.
Full `EnumDisplaySettings` enumeration runs only when native resolution
is needed for aspect compensation.

Jubeat and Reflec Beat now enable aspect compensation before
initializing touch input.

## Testing
Sanity checked rb/jb on laptops with non-16:9 ratio
2026-07-20 03:00:34 -07:00
bicarusandGitHub d0db547dd0 sdvx: implement NVAPI for non-NVIDIA GPUs (#816)
## Link to GitHub Issue or related Pull Request, if one exists
n/a

## Description of change

In summary: for SDVX VM mode, this PR eliminates the need for `Note FPS
Target 120Hz` / `Game FPS Target 120Hz` patches typically used for AMD
GPU via synthetic responses provided via a fake NVAPI layer.

Before this change, playing on AMD GPU required patches to force the
game to render at 60Hz (See #107 for relevant discussion). Basically, if
NVAPI query fails, the game shows

`I:NvDisplayConfig: GetMainDisplayRefreshRate = 60.000hz (DUMMY)`

the game renders at 120Hz but the notes render at 60Hz, which would look
stuttery.

After this change, even without a real `nvapi64.dll`, spice will provide
a stub implementation, just enough to convince the game that the main
display is 120Hz, which causes notes to render at 120Hz as well.

This can be overridden by `-graphics-force-refresh` option.

Important note: if user has the `Note FPS Target 60Hz` patch but running
the game at 120Hz, before this change the notes would render at 60Hz,
but after this they will be at 120Hz. I don't know why anyone would
intentionally lower the notes framerate, so this is not going to be
fixed. If the user wants to run the game at 60Hz they can do so via
`-graphics-force-refresh`.

Another change - `-nonvapi` will always force down this synthetic nvapi
path, even if the system has the real `nvapi64.dll`. Even on NVDIA GPU
this option can be used to force this.

## Testing
2026-07-19 23:43:14 -07:00
bicarusandGitHub be4bb00390 patcher: patch groups (#812)
## Link to GitHub Issue or related Pull Request, if one exists
Fixes #245

## Description of change

Introduce patch groups. JSON schema now allows for a `"type": "group"`:

```json
    {
        "type": "group",
        "gameCode": "LDJ",
        "id": "timer-freeze",
        "name": "Timer Freeze Patches",
        "description": "Freezes various timers in the game."
    },
    {
        "name": "Standard/Menu Timer Freeze",
        "description": "Freezes all non-premium area timers.",
        "caution": "",
        "gameCode": "LDJ",
        "type": "memory",
        "group": "timer-freeze",
        "patches": [
            {
                "offset": 9962743,
                "dllName": "bm2dx.dll",
                "dataDisabled": "0F84",
                "dataEnabled": "90E9"
            }
        ]
    },
    {
        "name": "Premium Free Timer Freeze",
        "description": "Freezes all premium area timers.",
        "caution": "",
        "gameCode": "LDJ",
        "type": "memory",
        "group": "timer-freeze",
        "patches": [
            {
                "offset": 9111965,
                "dllName": "bm2dx.dll",
                "dataDisabled": "7E",
                "dataEnabled": "EB"
            }
        ]
    },
```

In the UI, these will show up as tree nodes. The parent is not actually
treated as a patch; e.g., its state is not saved to the patch manager
config file; only the child patches states are managed, the parent's
state only bubble up only in the UI.

In downlevel versions of spice, group parents will show up as a patch of
invalid type. Child patches will still show up as individual patches.

## Testing
*how was the code tested?*
2026-07-19 02:10:13 -07:00
bicarusandGitHub a8cc85531c overlay: disable subscreen overlay if "native touch" option is on (#811)
## Link to GitHub Issue or related Pull Request, if one exists
#345 

## Description of change
If IIDX/SDVX/POPN "native touch" option is enabled, show an error
message if user toggles subscreen window.

Those options are meant for users with touch screens, and disables mouse
interactions. Users who like to randomly turn on options often latch on
to this option and get confused when clicks don't register.

## Testing
2026-07-18 21:49:50 -07:00
bicarusandGitHub 2dea0d27bf rawinput: fix hotplug for keyboard and mouse (#810)
## Link to GitHub Issue or related Pull Request, if one exists
n/a

## Description of change
Fix a long-standing issue of hotplug not working for keyboards and mice.
The code only registered for HID notifications; we also need to do it
for the keyboard GUID and mouse GUID.

## Testing
Confirmed that keyboards and mice can be hot unplugged and plugged.
2026-07-18 17:38:59 -07:00
bicarusandGitHub 61a0220c1a audio: retain WASAPI clients only after initialization (#807)
## Link to GitHub Issue or related Pull Request, if one exists
Fixes #782 

## Description of change

This addresses an issue only seen on Linux + WINE.

Defer retaining WASAPI clients until `Initialize` succeeds.

Before this PR, temporary clients created during DirectShow device
capability probing were retained as active audio clients, causing
Wine/Linux crashes in DDR and popn.

Also covers IAudioClient3, dummy, ASIO, WaveOut, and null-device
initialization paths, for consistency.

## Testing
WIP
2026-07-18 03:09:37 -07:00
bicarusandGitHub c080bbe301 rawinput: avoid deadlock when binding analog axis as a button (#809)
## Link to GitHub Issue or related Pull Request, if one exists
Regressed by #793

## Description of change
Due to lock inversion, when binding an analog axis as a button, spice
deadlocks. Fix that.

Also create a separate `unordered_map` that keeps track of device
handles so that `WM_INPUT` handle can look up devices without having to
acquire the larger `devices_mutex` which could be held by (potentially)
lengthy operations like hotplug.

Fix more synchronization issues around hotplug. Latent bug exposed by
MIDI 2.0 issues.

## Testing
2026-07-18 03:09:27 -07:00
bicarusandGitHub e1d1b39567 patcher: refactor (#808)
## Link to GitHub Issue or related Pull Request, if one exists
n/a

## Description of change
Refactor patch manager.

This one file had the UI logic, config save/load, parsing patches JSON,
applying memory patches, everything all in one cpp file. Refactor and
separate out the layers.

## Testing
Sanity checked patch workflows.
2026-07-17 03:48:01 -07:00
bicarus-dev 2aae8ad9ca move updater scripts 2026-07-15 02:14:19 -07:00
bicarusandGitHub 1b6d1af951 jb, shogikai: double buffer overlay, run at ~60fps (#805)
## Link to GitHub Issue or related Pull Request, if one exists
n/a

## Description of change
GDI bit blit overlay used exclusively for jubeat & shogikai was running
at 30fps, which caused the Jubeat touch debug UI to not render fast
enough. Run it at 60 fps, and also perform double buffering to avoid
flickering.

This could have been scoped to JB but I made the change for both. XP
builds will keep running at 30fps since those people run on cabs with
ancient hardware.

## Testing
Tested jubeat and shogikai, overlay renders without flickering at 60 fps
2026-07-15 01:31:29 -07:00
bicarusandGitHub 5c3617c05f rawinput: fix ttl logic in raw touch (#806)
## Link to GitHub Issue or related Pull Request, if one exists
n/a

## Description of change
Fixes held touch contacts briefly dropping under rapid multi-touch (e.g.
holding several jubeat buttons and tapping elsewhere many times would
make the holds briefly release and the hold again).

A HID digitizer reports a fixed number of contact slots per report and
cycles the rest through later reports, so the touch layer keeps an
unreported contact alive for a few reports (a per-contact TTL). The TTL
was only set for contacts in the current report and had no slack, so
when the contact count rose (from the rapid taps) the reporting cycle
got longer and stationary holds were expired before the device cycled
back to them.

Now, the cycle length is recomputed every report and every tracked
contact's TTL is extended to it (never shrunk), so a rising contact
count can't starve a currently held contact. The 50 ms last-report
timeout still removes genuinely lifted contacts.

## Testing
Tested on jb, rb.
2026-07-14 23:46:17 -07:00
bicarusandGitHub a9a943fbfe jb: fix card not detected on title screen (#804)
## Link to GitHub Issue or related Pull Request, if one exists
n/a

## Description of change
Fix a long standing issue with card reader behavior in jubeat.

sciunit emulation has a bug - when on the title screen / demo loop,
inserting a card did not generate an interrupt. Users always had to
press the game start button and card in. Fix this by reversing what the
actual sciunit does and mirroring the state machine behavior.

## Testing
Tested L44 and K44.
2026-07-14 17:06:00 -07:00
bicarus-dev b094a9e352 deprecate insert card button 2026-07-14 02:49:46 -07:00
bicarusandGitHub 4bd18afb12 rb: fix alignment of vertical lines in debug view (#803)
they were off by third of a column
2026-07-14 00:18:55 -07:00
bicarusandGitHub 0b24e703a1 overlay: re-enable keyboard navigation (#802)
## Link to GitHub Issue or related Pull Request, if one exists
Fixes #800 

## Description of change
Restores keyboard navigation by default in spicecfg, and add an option
to optionally enable it in the overlay.

## Testing
2026-07-14 00:18:21 -07:00
bicarusandGitHub 1a1be7a393 rb: touch debug overlay, tweak scaling (#801)
## Link to GitHub Issue or related Pull Request, if one exists
n/a

## Description of change
Similar to #797, draw an overlay to debug touch issues for reflec beat.

For rb, we draw on the ImGui surface. Draw horizontal and vertical line
for triggered sensors, and also a bounding box if scaling is in use.

Make small tweaks to how scaling works.

## Testing
Verified volzza 2 / reflesia / 1st game (landscape KBR)
2026-07-13 23:30:41 -07:00
bicarusandGitHub a2d8f60d12 jb: only show debug overlay for jubeat (#799) 2026-07-13 19:42:27 -07:00
bicarusandGitHub 5d25fdb035 jb: touch debounce (#798)
## Link to GitHub Issue or related Pull Request, if one exists
n/a

## Description of change
Debouce option for touch input. Applies to all touch handlers (rawinput,
win8, win7) since it sits at the touch layer, and then consumed by jb
touch logic.

## Testing
2026-07-13 02:06:54 -07:00
bicarusandGitHub c255e3a1db jb: option to draw debug graphics to show touch interactions (#797)
## Link to GitHub Issue or related Pull Request, if one exists
related to #697 

## Description of change
Add a new option that paints touch targets and touch points for jubeat;
gets enabled if there is a touch screen connected. This is to help
explain / debug issues when users have their touch screen issues, which
is usually due to misalignment.

Fix the touch targets being off by 1 pixel (center gap is 1px wider in
both directions).

Also, simplify the `improved` algorithm to simulate a finger and trigger
nearest-neighbor for the AC dimensions. In terms of touch performance
this is near identical to what we had before, but it allows us to draw a
nice debug overlay instead of just grids.

## Testing
Tested portrait and landscape versions of jb.
2026-07-13 00:31:05 -07:00
bicarusandGitHub d7c144646f rawinput: deal with high poll rate devices, use 3x3 for default for rb touch emu (#796)
## Link to GitHub Issue or related Pull Request, if one exists
n/a

## Description of change
Two changes:

1. Handle all HID events in `WM_INPUT` and not just the first one. This
only really affects 4000Hz / 8000Hz HID devices.
2. Go back to `3x3` as the default for rb IR touch emulation and remove
`1x1` as an option as it does not perform as well as I expected.

## Testing
2026-07-12 10:32:29 -07:00
bicarus-dev 27077aa21f bump action-gh-release version 2026-07-12 01:45:16 -07:00
bicarus-dev 632dcf7b41 clean up option text for consistency 2026-07-12 00:00:31 -07:00
bicarusandGitHub a25bbdc9c9 rawinput: refactor midi (#795)
pure movement of code only, no functional changes
2026-07-11 23:59:34 -07:00
bicarusandGitHub 47d886306e rawinput: handle midi scanning asynchronously, fix midi hotplug (#793)
## Link to GitHub Issue or related Pull Request, if one exists
Fixes #603

## Description of change
Scanning for midi devices can take a while on Windows 11 with MIDI 2.0
service. Sometimes it'll take a couple seconds. Sometimes, on a PC with
zero MIDI devices, it takes 10-11 seconds.

This was causing two issues:

1. slow startup time
2. crash on invalid memory access due to a race condition

Address both.

Also fix a bug: when a device change event fires, we do a MIDI scan, and
invalidated all existing MIDI devices in favor of creating new handles.
Stop doing this, and instead check for duplicates by matching the ID and
keep existing device handles alone. Properly clean up devices on unplug.

Also: fix MIDI buttons being stuck on when unplugged while holding a
key.

## Testing
Tested with Nostroller in MIDI mode. rtpMIDI works too.
2026-07-11 21:57:55 -07:00
bicarus-dev edda658fa4 update rawtouchaspect text 2026-07-11 21:16:34 -07:00
bicarus-dev 1ab20f1b56 update wintouch option text 2026-07-11 21:06:29 -07:00
bicarusandGitHub 0e9093a74b xinput: allow for newer xinput DLL (#794)
Fixes #792
2026-07-11 03:04:49 -07:00
bicarusandGitHub f52eaea76c touch: fix indicators showing in touch overlay, disable additional gestures (#791) 2026-07-11 00:24:36 -07:00
bicarusandGitHub c3fcf2f417 touch: rawinput touch to compensate for letterboxing (#790)
## Link to GitHub Issue or related Pull Request, if one exists

Fixes #697

## Description of change

Adds aspect-ratio compensation for raw input touch handler.

This addresses the problem with playing rawinput touch games - namely
jubeat and reflec beat - on a display that is not 16:9, and user chose
to preserve the aspect ratio. This happens a lot on:

* 16:10 touch screens
* touch screen laptops (which range from 3:2, 16:10, and so on).

Root cause is a **Windows Raw Input limitation**. Unlike
`WM_POINTER`/`WM_TOUCH`, where the OS already maps digitizer coordinates
to screen space (applying the display's calibration/scaling), Raw Input
delivers the HID digitizer's *raw* logical coordinates, spanning the
whole physical panel, with **no calibration or display-mapping data
attached**. We have to reconstruct that mapping ourselves.

When a game runs at a display mode whose aspect ratio differs from the
panel's native resolution, Windows scales the image with
letterboxing/pillarboxing (black bars). That is standard OS/GPU
behavior. Because Raw Input gives us no mapping to account for those
bars, touches land off-target. This change detects the mismatch and
remaps each touch from panel-space into the displayed image region;
touches inside the black bars are treated as released.

Adds new launcher option **`-rawtouchaspect`**.

Notes:

- Native resolution is the largest-area mode from `EnumDisplaySettings`
on the **primary** display, consistent with the existing primary-only
touch pipeline.
- Math assumes centered, aspect-preserving scaling; only one axis is
ever inset.
- A small epsilon (0.01) makes near-matched panels (e.g. 1366×768 vs
true 16:9) a no-op.
- If the user forces a stretched image, then the touches will be off
again. In this case, the user needs to use the `force off` setting.

## Testing

On my tiny Surface Go tablet, 
* checked that jubeat squares align correctly
* good enough to full combo level 11 for reflec beat
2026-07-10 23:23:45 -07:00
bicarusandGitHub 8d04eba21d build: check for win7 compat, fix 32-bit xp check (#789)
## Link to GitHub Issue or related Pull Request, if one exists
#787 

## Description of change
Updates usage of `windows_dll_compat_checker`

## Testing
it builds
2026-07-10 01:51:04 -07:00
bicarusandGitHub 8d42c0ce77 asio: fix XP build; remove call to RegGetValueA (#788)
## Link to GitHub Issue or related Pull Request, if one exists
Fixes #787 

## Description of change
Remove the call to `RegGetValueA` as it is Vista and up. Replace with
`RegOpenKeyExA` + `RegQueryValueExA`

## Testing
2026-07-10 00:26:03 -07:00
bicarus-dev 829503568e fix UI text for -sdvxnolive2d 2026-07-06 01:27:18 -07:00
bicarusandGitHub 746fdb5409 build: manual update scripts (#786)
people asked for this often enough
2026-07-02 23:48:52 -07:00
bicarusandGitHub 67da892fe9 cfg: aliases for -network and -subnet (#785)
## Link to GitHub Issue or related Pull Request, if one exists
n/a

## Description of change
`-network` and `-subnet` are poorly named. Add new names for them that
are more explicit.

Old command line parameters will continue to work, this is an additive
change.

## Testing
2026-07-01 02:41:14 -07:00
bicarusandGitHub dc191487e0 Update CONTRIBUTING.md 2026-06-30 17:45:23 -07:00
bicarusandGitHub 5c0c405800 Update CONTRIBUTING.md 2026-06-30 14:32:30 -07:00
bicarus-dev 60166e6ba1 update gh actions 2026-06-30 02:29:44 -07:00
bicarusandGitHub cb0b59600a build: GitHub action for creating a draft relase (#783) 2026-06-30 00:53:08 -07:00
bicarusandGitHub b9f3be4df2 asio: cache result from CoCreateInstance to work around buggy asio drivers (#781)
## Link to GitHub Issue or related Pull Request, if one exists
n/a

## Description of change
IIDX likes to call `CoCreateInstance` and `init` multiple times on ASIO
drivers, but some ASIO drivers like `Neva Uno` really don't like that
and ends up crashing. In our wrapper, cache the instances and try to
reuse them for better compatibility.

## Testing
Worked fine for FlexASIO / Xonar AE / Realtek ASIO though none of these
drivers repro the crashing behavior.
2026-06-28 22:37:48 -07:00
bicarusandGitHub cf86fbd238 graphics: remove static import of d3d9 (#780)
## Link to GitHub Issue or related Pull Request, if one exists
Fixes #779
Regressed by #720 

## Description of change
#720 introduced a static dependency on DX9 which caused `d3d9.dll` to be
loaded at boot from `system32`. Some third party hooks (like
ifs_layeredfs and dxvk) rely on supplying a custom `d3d9.dll` in the DLL
search path (usually in modules) but this change caused Windows to skip
that check.

Remove the hard dependency on d3d9 and add a CMake check to ensure that
compiled binaries do not accidentally introduce new static imports in
the future.

## Testing
Confirmed that dxvk runs again.
2026-06-27 15:23:59 -07:00
bicarusandGitHub 3fdb369128 sdvx: option to disable Live2D (#777)
## Link to GitHub Issue or related Pull Request, if one exists
n/a

## Description of change
Adds an option to disable Live2D. Can be disabled everywhere, or just
during a song.

## Testing
Tested EG final and recent Nabla.
2026-06-27 02:34:19 -07:00
bicarusandGitHub db7defff5a overlay: fix OS cursor showing up in some games (#776)
## Link to GitHub Issue or related Pull Request, if one exists
regressed by #766, fixes #775 

## Description of change
Some games like DDR leave `ShowCursor` ref count at non-negative number,
so when spice handles `WM_SETCURSOR` to change the cursor shape, it ends
up showing the OS cursor.

## Testing
2026-06-25 15:30:59 -07:00
bicarusandGitHub 451cbec0b9 overlay: OBS control window via WebSocket API (#773)
## Link to GitHub Issue or related Pull Request, if one exists
n/a

## Description of change
Adds an overlay window widget for controlling OBS over WebSocket.

Add new lines to the FPS widget that shows recording / streaming timers.

Show notifications for major state changes (stream started/stopped,
recording started/paused/resumed/stopped)

## Testing
2026-06-24 02:44:36 -07:00
bicarusandGitHub 5c69e295ab ccj: fix mouse trackball cursor wrap (#774)
I don't have a repro but a user reported that the cursor doesn't wrap
around on one side. Correctly account for windows bleeding over to other
monitors.
2026-06-21 17:03:44 -07:00
bicarusandGitHub 5065a92d55 launcher: don't warn about hook option conflicts (#771)
DLL hook options (-k and -z) allow multiple flags being specified in
both spicecfg and via command line; they never conflict as they are
additive. Remove the warning about option conflicts.
2026-06-20 15:46:45 -07:00
bicarusandGitHub 5483e8e2c0 ponp: make Force Sub Redraw an option (#770)
## Link to GitHub Issue or related Pull Request, if one exists
n/a

## Description of change
Similar to how it is on SDVX, turning this on can cause graphical
glitches depending on the GPU, so it needs to be an option that users
can toggle.

## Testing
2026-06-19 22:30:08 -07:00
bicarusandGitHub e8949a2612 overlay: fix highlights, alignment in presets table (#769)
* Fix table row highlight hover detection in Options tab when row has
two lines of text
* Fix text alignment in controller presets table
2026-06-19 00:40:54 -07:00
bicarusandGitHub db71a0b24d cfg: center window on launch (#768) 2026-06-18 23:02:23 -07:00
bicarusandGitHub d3d5422768 cfg: enforce minimum window size (#767) 2026-06-18 21:10:42 -07:00
bicarus-dev 5185f753e5 fix truncated text 2026-06-18 17:32:59 -07:00
bicarusandGitHub eb897d0f1b overlay: fix cursor visibility (#766)
After #739 there was a regression where the cursor visibility became
inconsistent if the FPS display was shown and then another overlay
window was toggled. Fix the imgui cursor toggle logic to only check for
the overlay layer visibility.
2026-06-17 20:12:56 -07:00
bicarusandGitHub 50fc80a1c3 graphics: respect Windows dark mode (#765)
Only affects the caption bar.

Should work for:

- [x] standalone configurator
- [x] dx9 games, including multi-window games like gitadora
- [x] Unity games
2026-06-17 11:51:31 -07:00
bicarusandGitHub 301e15c44d overlay: make tabs bigger (#764) 2026-06-17 01:14:50 -07:00
bicarus-dev b6f886a7c5 fix option text for -iidxtdjsubsize 2026-06-16 01:47:47 -07:00
bicarusandGitHub 0b085286d3 cfg: ignore nolegacy (#763)
## Link to GitHub Issue or related Pull Request, if one exists
Fixes #762

## Description of change
Fix -nolegacy causing input to be not processed in spicecfg

Need to fix `run_cfg` and `CONFIGURATOR_STANDALONE` for real soon.

## Testing
2026-06-15 14:48:18 -07:00
bicarusandGitHub 29d3d9bb52 overlay: add sorting to patches table (#761) 2026-06-15 14:47:15 -07:00
bicarus-dev bf05a3ef30 update crash message 2026-06-15 12:55:15 -07:00
bicarusandGitHub 6280ac3b0a imgui: update imgui to head of docking branch (v1.92.8) (#756)
## Tested:

- [x] LDJ with sub
- [x] sdvx
- [x] dx11 game
2026-06-15 10:40:38 -07:00
bicarus-dev cc9df27569 change ea url template 2026-06-15 10:33:34 -07:00
bicarusandGitHub ac51acdafe cfg: fix config save atomicity (#760)
## Link to GitHub Issue or related Pull Request, if one exists
n/a

## Description of change
Fully flush `tmp` file and then use `MoveFileExW` instead of `CopyFileW`
which guarantees atomic operation on NTFS.

## Testing
2026-06-15 02:06:04 -07:00
bicarusandGitHub 6fec169347 overlay: toggle main menu independently (#759)
Recently there was a change to make the main menu also toggle the
overlay; revert that.
2026-06-15 01:45:02 -07:00
bicarusandGitHub 4af006e869 wasapi: address buffer overflow when using -wasapishared (#758)
## Link to GitHub Issue or related Pull Request, if one exists
Building on #745 

## Description of change
With `-wasapishared`, some users see `AUDCLNT_E_BUFFER_TOO_LARGE`
(`0x88890006`).

The existing one-device-period buffer clamp wasn't enough on endpoints
with a small shared buffer; the shared buffer is double-buffered, so a
full-buffer write often exceeds the free space.

### Fix

Added a FIFO bridge to `SharedRedirect` that decouples the game's
per-event writes from the shared engine's clock:

- **`GetBuffer`** hands the game a pointer into the FIFO tail to write
in place.
- **`ReleaseBuffer`** commits the write and drains `min(pending,
device_free)` frames to the device - so a write can never exceed what
the device accepts, structurally preventing the overflow on any
endpoint.
- **`GetCurrentPadding`** reports the FIFO fill level (capped to the
reported buffer size) so poll/timer-driven games pace correctly against
the virtual buffer.

## Testing
2026-06-14 19:57:28 -07:00
bicarus-dev 256ef4d341 update -wasapishared option text 2026-06-14 17:53:01 -07:00
bicarusandGitHub 698f2ffc2c overlay: add vertical row padding (#757)
Slightly decrease information density by adding a couple pixels of
vertical padding to each table row in the configurator.
2026-06-14 12:29:32 -07:00
bicarusandGitHub 4879a35eb8 overlay: remove keyboard / gamepad tab navigation (#755)
Tired of constantly fighting unintentional keyboard navigation behavior.

Also, remove `ImGuiConfigFlags_IsTouchScreen` which literally does
nothing.
2026-06-12 21:28:34 -07:00
bicarusandGitHub fd7e850789 ccj: fix mouse wheel input in overlay (#754)
## Link to GitHub Issue or related Pull Request, if one exists
#251 

## Description of change
CCJ registers for rawinput mouse. We allowed the rawinput registration
to go through to the OS, which prevents spice's rawinput stack from
using the mouse. This caused the overlay to not accept mouse input. Fix
it by not letting the rawinput registration through. Luckily, mouse
input continues to work properly in CCJ (for touch).

Also, fix a bug in DX11 overlay when toggling the main menu.

## Testing
2026-06-12 03:42:59 -07:00
bicarusandGitHub 0cf07c38da overlay: left nav for cards tab (#753)
Enable left nav for Card tab, splitting the single page into multiple
sub tabs.

Card Manager is now embedded in this UI (but can still be launched as a
separate window)
2026-06-12 00:57:52 -07:00
bicarusandGitHub 1f073b2d75 overlay: combine buttons/analogs/overlay/lights (#752)
Same idea as #747 except for input/output binding.
2026-06-11 23:38:09 -07:00
jessemarthinandGitHub 3037214932 Fix capture.get_jpg blocking during game load for Companion Mirror (#750)
Add a timeout and skip signalling to the D3D9 capture path so the API
thread no longer waits indefinitely when Present stops during loading.
Return the last successful JPEG frame as a fallback.

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

## Description of change
* Fix `capture.get_jpg` blocking indefinitely when D3D9 screen capture
cannot
  complete during game loading (e.g. no Present calls).
* Add a 2-second timeout to `graphics_capture_receive_jpeg()` and cancel
  pending capture requests on timeout.
* Signal capture skip from D3D9 failure paths via
`graphics_capture_skip()`.
* Cache the last successful JPEG per screen and return it as a fallback
  when a new capture fails.
Tested with **stock iOS Spice Companion** only. I have not tested
Android or
other Companion clients. The iOS client implementation differs from the
others; this fix is server-side only.
No config file, CLI option, or API schema changes.

## Testing
* Built locally with llvm-mingw cross-compiler (WSL).
* Docker build (`src/spice2x/build_docker.sh`): Pending
* Tested with stock iOS Spice Companion on iPad: Mirror remains
connected
  during game loading instead of returning to KeyPad.
2026-06-11 19:14:47 -07:00
drmextandGitHub 18d6d9783a overlay: fix Options tab contents jitter (#751)
## Link to GitHub Issue or related Pull Request, if one exists
#747

## Description of change
The old approach called ImGui’s `SetScrollHereY()` while the options
list was still being laid out. That caused one-frame scroll corrections
and edge-snap jitter near scroll boundaries.

The new approach in `build_options_tab()`:

1. **On click** - set a flag (`options_scroll_pending` for categories,
`options_scroll_top` for group headers).
2. **During layout** - when building the target category, record its
content position with `GetCursorPosY()` (don’t scroll yet).
3. **After all options are built** - apply scroll once by setting
`window->Scroll.y` directly, and only if the position actually needs to
change (0.5px threshold).

That deferred, single-pass scroll avoids the shake while still
supporting scroll-up, scroll-down, and re-click-to-recenter.


## Testing
In spicecfg.exe and in-game overlay, Options tab:

Open groups with many categories

Scroll content down manually, then click a higher category in the left
nav - content scrolls up to that section

Re-click the same category after manual scroll - jumps back without
shake

Rapid re-clicks when already aligned - no jitter

Switch group headers - still scrolls to top; re-click at top - no shake
2026-06-11 14:03:33 -07:00
bicarusandGitHub 267add3227 iidx: set SOUND_OUTPUT_DEVICE even when -iidx is not enabled (#749)
## Link to GitHub Issue or related Pull Request, if one exists
n/a

## Description of change
For cab usage, when `-exec bm2dx.dll` is used without `-iidx` then
SOUND_OUTPUT_DEVICE never gets set.

Change this so that `SOUND_OUTPUT_DEVICE` gets set unconditionally if
`-iidxsounddevice` is set.

## Testing
2026-06-11 00:40:53 -07:00
bicarusandGitHub a170627f52 overlay: redesign Options tab(s) (#747)
## Link to GitHub Issue or related Pull Request, if one exists
#0

## Description of change
Combine API/Options/Advanced/Development/Search tabs into one Options
tab and add a left nav for navigation categories.

Reduce information density for options table by making each row double
the height and slightly increasing widget sizes.

## Testing
2026-06-11 00:17:54 -07:00
drmextandGitHub 5c244eaca7 improve E004 card generation and placeholder pcbid (#748)
## Link to GitHub Issue or related Pull Request, if one exists
#73

## Description of change
Generated cards now start with E0040100 to fully match real cards. The
placeholder example `E004010000000000` is replaced with
`E0040100FFFFFFFF`.

The invalid placeholder/fallback PCBID `04040000000000000000` is
replaced with `01201000000000010101`, which contains a valid
header/checksum, making it closer resemble a real PCBID while still
clearly being an example.
2026-06-10 23:24:43 -07:00
bicarusandGitHub 6bcadccf09 audio: WASAPI Force Shared Mode option (#745)
## Link to GitHub Issue or related Pull Request, if one exists
n/a

## Description of change
This new option, `-wasapishared`, detects when the game opens WASAPI
Exclusive stream and forcibly opens a shared mode stream instead on the
real device.

#### Benefits of this:

1. No need for `force wasapi shared` patches
2. No need to change sample rate of audio devices before starting up
games
3. Adds ability to boot in shared mode even for games that do not have
shared wasapi changes (old GITADORA, popn HC)

As a result, this option is strictly better than `shared wasapi` patches
available for many games.

#### Downsides:
1. OS resampling will add a small latency, but not big enough to be
noticeable.
2. Using shared mode instead of exclusive mode adds latency, of course.

(If you cared about latency, you would use the low latency option, or
use exclusive or asio)

Basically it's the "make things work" button for audio that should work
for almost all games that we support.

The option has no effect if the game opens in shared mode.

## Testing
SDVX7 - ok
GITADORA GW - ok
popn HC - ok
IIDX - ok
2026-06-10 00:20:59 -07:00
bicarus-dev ccb3bac48c defer error for -exec 2026-06-09 00:09:00 -07:00
bicarusandGitHub 0a7ecf82a9 build: produce shared objects for spice.exe / spice_laa.exe, disable 64-bit winxp builds (#744)
to speed up clean builds. GitHub CI time went from 20 minutes -> 10
minutes.
2026-06-08 17:42:41 -07:00
bicarus-dev 1c6929dd84 Move full screen Monitor options to Graphics (Full Screen) 2026-06-08 15:53:54 -07:00
bicarusandGitHub 1d3d9040f2 Update bug_report.md 2026-06-08 00:51:48 -07:00
bicarusandGitHub 8f1069628e Update bug_report.md 2026-06-08 00:50:33 -07:00
bicarus-dev f8c585a9d1 update main menu layout 2026-06-08 00:27:42 -07:00
bicarusandGitHub 9651051990 overlay: make overlay toggle behavior more consistent (#743)
Clean up how various overlay toggle shortcuts work with the main menu
visible.
2026-06-07 21:56:06 -07:00
bicarusandGitHub a54a62d0d7 overlay: fix dummy marker spacing (#741)
Fix alignment in patches tab
2026-06-07 19:14:43 -07:00
bicarusandGitHub 100caa0f5c overlay: refactor overlay layering (#739)
## Link to GitHub Issue or related Pull Request, if one exists
n/a

## Description of change

Create three distinct layers for the overlay:

1. Bottommost persistent layer - non-interactable layer that is always
on. This was only for notifications, but now the FPS widget lives here
as well.
2. Overlay windows layer - most interactable windows go here.
3. Topmost main menu - this is reserved for the main menu (escape key)
and this is a modal dialog that occludes the layers below.

Why? 

- `toggle overlay` behavior with FPS widget *also* toggling on/off was a
bit confusing (now they're two separate keys)
- FPS widget is popular, but it caused the entire overlay to be active,
which affects how input is handled
- the main menu being a standalone window was a little awkward (now it's
a modal)

## Testing
2026-06-07 17:23:08 -07:00
bicarus-dev bb87aa2944 add placeholder text for ea url option 2026-06-07 01:48:52 -07:00
bicarusandGitHub 2e2968d1bc overlay: remove dependency on d3dcompiler (#738)
## Link to GitHub Issue or related Pull Request, if one exists
Fixes #737, regressed by #707

## Description of change
ImGui backend for DX11 pulled in dependency on a specific version of
`d3dcompiler_47.dll` which is not present on stock Win7 OS image.

Change this to a runtime load of the DLL and disable the overlay if we
can't find one.

## Testing
- [x] Win11, DX11 overlay
- [x] Win11, DX9 overlay
- [x] Win7, DX9 overlay
2026-06-06 14:06:37 -07:00
bicarusandGitHub 0457262472 audio: asio "downmix" 7.1 to stereo (#736)
## Link to GitHub Issue or related Pull Request, if one exists
#730 

## Description of change
Add an option that extracts channels from 7.1 ASIO and presents to 2-ch
ASIO. Same idea as the SDVX 2ch fix except for gitadora we are
duplicating the center channel to front.

## Testing
Tested SDVX and GFDM Arena.
2026-06-06 03:11:40 -07:00
bicarus-dev f23c359114 minify patches json again 2026-06-05 18:05:48 -07:00
bicarus-dev 81e74dcb97 remove museca difficulty patches 2026-06-05 17:45:36 -07:00
bicarus-dev 8ead941c6c format patches json 2026-06-05 17:22:38 -07:00
bicarusandGitHub ece03cabba cfg: asio driver selector must list both 32-bit and 64-bit drivers (#734)
## Link to GitHub Issue or related Pull Request, if one exists
#730 

## Description of change
spicecfg is a 32-bit application. When it uses the ASIO SDK it only saw
the 32-bit drivers.

There are some ASIO drivers that have different names under 32-bit and
64-bit (Xonar AE is one of them)... so we have to manually scan the
registry and surface both the WOW32 and WOW64 nodes.

## Testing
<img width="329" height="309" alt="image"
src="https://github.com/user-attachments/assets/7abecf76-6835-4df5-8c2e-e7b425130f4c"
/>

Checked both 32-bit and 64-bit configurator, identical results.
2026-06-05 02:05:03 -07:00
bicarusandGitHub 4b7f68f920 gitadora: (arena model) flip realtek option (#733)
## Link to GitHub Issue or related Pull Request, if one exists
#730 

## Description of change
Flip the option - enable the Realtek hack by default, unless the user
chooses not to.

## Testing
Tested ASIO path and WASAPI path. WASAPI path should be unaffected as
the game just picks the default audio device.
2026-06-05 01:38:46 -07:00
bicarusandGitHub 2dae86a6f2 gitadora: (arena model) simulate Realtek device, fix asio redirect hooks (#732)
## Link to GitHub Issue or related Pull Request, if one exists
#730, #718

## Description of change

Part 1)

When ASIO is in use, the game also looks for a Realtek device so that it
can open a WASAPI Exclusive mode stream for headphones.

Add an option to fake that, in case the user doesn't have a Realtek
device.

Part 2)

`-gdaasio` wasn't workign properly - fix the logic.

## Testing
Seems to work with FlexASIO, and Xonar with no real Realtek device.
2026-06-05 00:08:52 -07:00
bicarusandGitHub 85058c2156 audio: create a wrapper for asio drivers (#731)
## Link to GitHub Issue or related Pull Request, if one exists
n/a

## Description of change
Create `WrappedAsio`, similar to how we wrap `IAudioEndpoint`

So far, the wrapper does these things:

1. logging (for diagnosis, since iidx and gfdm don't produce any logs
when asio succeeds)
2. iidx32+ hack to work around refcount mismatch issue
3. sdvx valk cab hack to force 2-channel audio ("downmixing" by taking
only the front channels)
4. honor volume boost

As a result of `#2` the mempatch was removed from `iidx.cpp` since we
can tackle it cleanly in the hook. `#3` also removes the need for manual
patches.

Real downmixing is hard & expensive on the CPU so it was not
implemented.

## Testing
Tested iidx/sdvx/gfdm with xonar and flexasio
2026-06-04 09:24:44 -07:00
bicarusandGitHub ea2f4c5572 gitadora: (arena model) complain loudly about bad prop files (#729)
## Link to GitHub Issue or related Pull Request, if one exists
n/a

## Description of change
Refuse to load if `libaio.dll` exists but `<spec>` is set incorrectly
(almost certainly because the user copied old prop files from downlevel
version)

## Testing
2026-06-02 17:47:51 -07:00
bicarusandGitHub 48033816a8 audio: WASAPI exclusive resampling, buffer size increase options (#727)
## Link to GitHub Issue or related Pull Request, if one exists
n/a

## Description of change

Resampler:
Implement resampler for exclusive mode streams, as we are seeing more
and more devices - not just laptops but onboard audio devices - that
only support 48khz and not 44.1khz. Should work with volume boost (gain
calculated inside resample) and also downmixer (hands off intermediate
scratch buffers).

Buffer size increase:
By default many of these games request a tiny buffer when in shared mode
(TDJ uses 3ms). On some audio setup this results in crackling due to
underflow. Add an option to forcibly increase the buffer size.

## Testing
With resampler set to 48kHz and buffer set to 20ms I can reliably boot
and play IIDX on my display port monitor's speakers; previously this
wasn't possible. IIDX is event-driven.

Tested SDVX7 as well at 48kHz, which opens timer-driven streams.
2026-06-02 01:55:50 -07:00
bicarus-dev b517ef3182 update options 2026-06-01 02:29:12 -07:00
bicarusandGitHub 6fef16353e otoca: patch up game for printing holo cards (#726)
## Link to GitHub Issue or related Pull Request, if one exists
Fixes #154

## Description of change
Patch the game to skip over special holo printing logic and redirect to
the normal printer, resolving the soft lock + printer failure.

## Testing
Tested `NCG-2019012900`. Other versions might not work though since this
is patch-based not hook-based.
2026-05-31 23:08:17 -07:00
bicarusandGitHub 84ea3f9e8e audio: stereo downmix, volume boost options (#722)
## Link to GitHub Issue or related Pull Request, if one exists
Fixes #717, fixes #647

## Description of change
Adds an option to downmix surround sound (5.1, 7.1, etc) down to stereo
(2 speakers). This can be used in most WASAPI games, including Gitadora
and FTT.

How it works: when the game tries to open surround format (say, 7.1) we
create a fake buffer and tell the game that it is supported. In reality
we open a 2-channel stream with the real sound card. When the stream
begins, we downmix the channels down to two (using one of many
algorithms) and output to the sound card.

While we're here, implement an option to boost the game volume by some
decibel value, which is a feature often requested by SDVX players. This
was needed since downmixing can sometimes result in quieter audio.

## Testing
Tested GW Delta and FTT.

Downmix doesn't work on IIDX (32 bits) but volume boost works.
2026-05-31 20:49:22 -07:00
AzaleaandGitHub 6bb6b0a301 [O] Filter GetIpAddrTable to selected network adapter (#724)
## Description of change
Filter GetIpAddrTable to selected network adapter. 

Fixes the IP address error (5-1506-0000) on gitadora gw delta when
multiple network adapters are present.

## Testing
Tested on Gitadora GW Delta with both WiFi and Tailscale adapters
present.
2026-05-31 16:17:47 -07:00
drmextandGitHub 465d6c6c18 cfg: fix alt+f4 exit (#723)
## Link to GitHub Issue or related Pull Request, if one exists
#720

## Description of change
Pass `VK_F4` sys-key messages through to `DefWindowProc` before ImGui
ingestion, restoring the normal Alt+F4 -> `WM_CLOSE` ->
`launcher::shutdown()` path.

## Testing
- [x] Launch `spicecfg` (D3D9 default path) and press Alt+F4; window
closes and process exits
- [x] Launch `spicecfg` (software path) and press Alt+F4; window closes
and process exits
- [x] Close via title-bar X; still works
2026-05-30 23:59:54 -07:00
SelundineandGitHub ef384e85dd Add GITADORA Arena windowed monitor layout support, allows the SMALL touch window to be resized (#716)
## Link to GitHub Issue or related Pull Request, if one exists
N/A

## Description of change
Adds GITADORA Arena windowed monitor layout options for the GITADORA,
LEFT, RIGHT, and SMALL windows.

This also applies the selected window border style to all four GITADORA
Arena windows, and allows the SMALL touch window to be resized in
windowed mode while keeping touch coordinates updated.

## Testing
- Manually tested GITADORA Arena windowed mode:
  - default, resizable, and borderless window styles
  - per-window monitor placement
  - SMALL resize
  - SMALL touch coordinate mapping after resize
2026-05-30 23:09:06 -07:00
bicarusandGitHub 7ee04879e2 overlay: option to force software renderer in configurator (#721)
## Link to GitHub Issue or related Pull Request, if one exists
chicken bit for #720
2026-05-30 15:24:07 -07:00
drmextandGitHub 4c73200f58 cfg: d3d9 standalone configurator (#720)
## Description of change
**Configurator (spicecfg.exe)**

- Attempts to create a D3D9 device on startup and uses the hardware
ImGui DX9 path when successful; falls back to the existing software
rasterizer automatically if D3D9 init fails.
- Rewrites the configurator window loop: refresh-rate-aware render
timer, minimize/resize/`WM_DISPLAYCHANGE` handling, direct Win32 ->
ImGui input (keyboard, mouse, wheel), and optimized software painting
via `SetDIBitsToDevice` instead of per-frame GDI `HBITMAP` allocation.

**Overlay / input (scoped to standalone configurator)**

- Skips expensive per-frame rawinput polling in
`ImGui_ImplSpice_NewFrame` when `CONFIGURATOR_STANDALONE` is set (Win32
messages drive input instead).
- Adds software-renderer idle-frame detection (`sw_pixels_dirty` +
draw-data hash) so spicecfg only repaints when pixels actually change.
- Adds `ImGuiWindowFlags_NoScrollWithMouse` on the configurator root
window to prevent scroll jolt on non-scrollable child widgets.
2026-05-30 12:52:35 -07:00
bicarus-dev 31aabe9786 fix option name 2026-05-30 01:22:09 -07:00
bicarusandGitHub 4ea55a61b8 gitadora: (arena model) asio option (#718)
## Link to GitHub Issue or related Pull Request, if one exists
#717 

## Description of change
Add an option to override the ASIO device.

By default the game will try to use any ASIO driver that contains the
string `XONAR` in it.

## Testing
Tested with FlexASIO.
2026-05-30 00:03:06 -07:00
bicarusandGitHub 0209b80a22 graphics: address perf issues with DX9 image resize logic (#712)
## Link to GitHub Issue or related Pull Request, if one exists
n/a

## Description of change
Move resize logic from `EndScene` to `Present`/`PresentEx` (resolves
Live2D frame drops in some extreme settings)

Plug small memory leak

Remove unnecessary surface Lock/Unlock

## Testing
Tested IIDX, SDVX, DDR.
2026-05-29 09:33:31 -07:00
drmextandGitHub 0511dfb6ca rawinput: fix spicecfg close delay (#715)
## Link to GitHub Issue or related Pull Request, if one exists
#0

## Description of change
Closing spicecfg was blocked for up to ~495 ms. Runtime behavior is
unchanged: same flush interval and same output path during normal
operation. Only shutdown teardown is faster.

## Testing
Opened and closed spicecfg.exe repeatedly; the window closes immediately
with no hang
2026-05-29 09:33:08 -07:00
drmextandGitHub b558df3340 cfg: optimize saving config (#714)
## Link to GitHub Issue or related Pull Request, if one exists
#0

## Description of change
Save config xml once instead of ~33 times

## Testing
xml is still saved properly
2026-05-29 09:09:02 -07:00
bicarusandGitHub 89602cfcab misc: clean up eamuse_get_game (#713)
No need to allocate a new `std::string` every call...
2026-05-29 09:08:07 -07:00
drmextandGitHub b4557993c9 cfg: optimize saving patches to file on disk (#711)
## Link to GitHub Issue or related Pull Request, if one exists
#0

## Description of change
Saving patches to file used to rewrite the file repeatedly, now it loads
and saves only once. Original file timestamp is now preserved on the
backup.

## Testing
Saved a very large patch set that used to take 10+ seconds; now it is
instant. Confirmed hex diffs are still accurate.
2026-05-29 02:20:39 -07:00
bicarusandGitHub f119d7c988 overlay: fix z-ordering of subscreen windows (#710)
## Link to GitHub Issue or related Pull Request, if one exists
n/a

## Description of change
Subscreen overlay window used to draw above the main game surface, but
below any other ImGui widgets. Fix this so that the subscreen image is
drawn at the same z-level as the sub window itself.

This has been a long-standing issue - at least since 2023 when spice2x
first forked...

## Testing
Tested TDJ in windowed mode.
2026-05-29 02:03:31 -07:00
bicarusandGitHub b3d8d0aea9 overlay: implement dx11 backend (#707)
## Link to GitHub Issue or related Pull Request, if one exists
Fixes #134 

## Description of change
Add ImGui DX11 backend implementation, and integrate into the spice
overlay.

The tricky part is that some of the Unity games:

1. Launch multiple windows, and
2. Can be resized (even full screen games launch at a certain resolution
and then expand to fit desktop resolution)
3. lazy load DX11 DLLs

This PR also adds screenshot functionality, but screen resize is not
implemented.

## Testing

Seems to be working for most games. Need to do final tests for:

- [x] CCJ (attract movie plays as well)
- [x] Busou Shinki (title movie plays)
- [x] MFG
- [x] QuizKnock
- [x] Polaris Chord
- [x] check dx9 for regression
2026-05-29 00:47:28 -07:00
drmextandGitHub a977cba772 overlay: prevent save button shifting patches text (#709)
## Link to GitHub Issue or related Pull Request, if one exists
#0

## Description of change
When opening the patches tab by pressing F4 in game, the Save button
doesn't appear until after initially selecting/deselecting a patch, then
it shifts all of the patch text below. To solve this annoyance, this
keeps the Save button in a fixed position (starts greyed out until a
selection) and no longer shifts the text.

## Testing
Confirmed saving selected patches still works properly in the overlay
and standalone cfg
2026-05-28 23:58:09 -07:00
bicarusandGitHub 489d40d87d utils: dump memory info on crash (#708)
For diagnosing out of memory errors / malloc failures.
2026-05-28 23:57:34 -07:00
bicarus-dev 3a468a9a3b fix up option name 2026-05-27 21:06:05 -07:00
bicarusandGitHub a2e220d3f4 gitadora: (arena model) unify window layout options (#706)
## Link to GitHub Issue or related Pull Request, if one exists
#0

## Description of change
Combine the two layout options into one

## Testing

- [x] windowed, x4
- [x] windowed, x2
- [x] windowed, x1 with sub
- [x] fs
2026-05-27 21:04:23 -07:00
AzaleaandGitHub febc02b50b [+] Hide sides on gitadora 𝛿 (#705)
## Description of change
Added an option to only disable the left/right screens of gitadora gw
delta while exposing the touch panel screen.

## Testing
Tested by hand on CachyOS + hyprland + gamescope + wine + vnc + phone +
cat 🐈‍⬛

<img width="2560" height="1707" alt="image"
src="https://github.com/user-attachments/assets/a2ef0172-68e2-43c2-89c3-4863698f2f00"
/>
2026-05-27 18:57:10 -07:00
bicarusandGitHub 311404f56b overlay: toast notifications (#704)
## Link to GitHub Issue or related Pull Request, if one exists
n/a

## Description of change
Adds toast notifications to the overlay.

Toast notifications are transient windows (shown for about 4 seconds).
These are drawn on top of the game even when the overlay is inactive
(i.e., when no window is open), and does not cause any input sink
behavior.

Add toasts for some common user-driven actions that could use a
notification, such as:

* screenshot
* card insert
* PIN macro
* API client connect/disconnect
* virtual printer
* screen resize toggle / scene switch

Add an option to change where the toasts are displayed, or to turn it
off entirely.

Add SDK function for it, and add it to the samples.

## Testing
Tested DDR (shown at bottom right, as is for most games) and RB (shown
top right by default).
2026-05-27 00:45:27 -07:00
bicarus-dev d929cdf7c8 clean up for release 2026-05-25 17:30:38 -07:00
bicarusandGitHub 2b930ad95f gitadora: warn about 2ch audio option not working on arena model (#702)
shrug
2026-05-25 17:00:49 -07:00
bicarusandGitHub 3c60f3966b rb: more touch options (#699)
## Link to GitHub Issue or related Pull Request, if one exists
n/a

## Description of change
Add two options:

1. option to adjust poll rate
2. option to change emulation size

Also, the default configuration is changing from:

* ~1000Hz ish to 120Hz (default)
* 3x3 point to 1x1 point

In theory this does not make a meaningful change to how the game plays.
But if people want the old behavior they can change it.

## Testing
Tested on Volzza2 and Reflesia.
2026-05-25 16:02:17 -07:00
c59d399ab8 Automated PIN Macro after auto-inserting a card (#698)
## Description of change
Adds a way to automatically use the PIN Macro for P1, P2 or both when
Auto Card Insert is used.

### Launcher changes: 
 - Added a checkbox to enable/disable the feature
 - Added a textbox for each player to set what to look for in the logs.

### How it works:
The feature requires Auto Card Insert and PIN Macro to be enabled and
set.
It uses the Auto Card Insert value to enable the feature for P1, P2 or
both, with a "master" checkbox to completely disable the feature. It
then relies on the current PIN Macro code to send the PIN to the games.

The games logs send a message when a new scene is loaded with the name
of the scene. By hooking on the logs, we can see when a PIN is required
and send the relevant macro. Games have different scene names, and
sometimes one for each player, so the field is configurable per game per
player. These could be hard-coded in a lookup table but I didn't want to
recompile the project every time I wanted to test, and I don't have all
the games to get this info from.

The PIN Macro thread is used to hook to the logs and check if the
defined string appears, which triggers the PIN macro just like a button
press would.

Setting nothing for the log trigger disables the feature for that
player. It's useful if someone wants to have Auto Card Insert enabled
for both players but only Auto Pin on 1 player, but is mainly there so
the check won't happen if nothing is set.

## Testing
I used SDVX and DDR to have a single and a two player test with the
feature both enabled and disabled. I also tested multiple combinations
of PIN Macro settings and Auto Card Insert settings with no apparent
issues. The PIN is automatically entered as soon as the game requests
it.

## Discussion

I know that hooking to the logs might not be the best solution. I'm very
open to discussions about this, and understand that this might be a deal
breaker for this feature. I made this to use with my setup and thought
I'd try opening this PR to see if the feature would be useful for
others.

I haven't found another way to make sure we're on the correct screen to
launch the PIN Macro, and sending the macro every time the Auto Card
Insert finishes had sync issues where the end of the PIN would actually
be registered by the game.

It would also be much more convenient for the users to hardcode a lookup
table for every games' log-trigger but I don't have the games currently
so I can't check the logs to see what would be best.

---------

Co-authored-by: Maxime St-Pierre <maxime.stpierre@ticketmaster.com>
2026-05-25 16:00:06 -07:00
bicarusandGitHub f69e40fa26 gitadora: (arena model) booting fullscreen with one monitor (#696)
## Link to GitHub Issue or related Pull Request, if one exists
#477 

## Description of change
By default the game needs 4 monitors.

With the new option to disable subscreens, you can now boot fullscreen
with a single 4K monitor, with subscreen overlay enabled. Side monitors
are not shown in the overlay; only the touch screen is emulated.

## Future work

- [ ] custom resolution (-forceres) doesn't work properly (hooking
ResetEx does allow the game to boot but will render incorrectly;
probably needs hex edits)
- [x] enable this automatically if the user has fewer than 4 monitors
- [ ] ability to boot with a 4k main screen + small subscreen (with no
side screens which do nothing gameplay wise), but this whole display
emulation is a nightmare to work with & test thoroughly so it'll have to
be in the future.


## Testing
Checklist:

- [x] fs on 1 monitor with option set
- [x] fs on 2 monitor with option set
- [x] windowed - default (4 windows, no overlay)
- [x] windowed - with option set (1 window, overlay)
- [x] fs with 4 monitors?
- [x] check popn hc for regression
2026-05-18 15:28:12 -07:00
bicarusandGitHub e9dcc5717e launcher: option to run as user without elevation (#695)
## Link to GitHub Issue or related Pull Request, if one exists
#694 

## Description of change
Before this change, we have it set in the executable manifest to always
ask for UAC elevation.

This change removes that and instead attempts to prompt for elevation
when we launch, depending on the new developer option that is being
added here to run as current user instead of elevating.

The default behavior continues to run as admin. This is still the safest
in terms of regression risk.

A bit of history on this: while spicecfg never technically needed to
elevate, we continue to do so to avoid difference in behavior when the
game is launched. For example, if the user is using an input remapper
running without elevation, it would inject input into spicecfg running
as user but fail to do so if the game runs elevated.

WinXP builds are unaffected (there is no UAC).

Some features will straight up fail without elevation so this will
remain as a debug option. To name a few (not a complete list):

* reboot/shutdown of PC
* running at realtime process priority
* `-nvprofile` option
* ...

## Testing
2026-05-16 16:27:59 -07:00
drmextandGitHub 5bec3d5db7 network: icmp emulation (#694)
## Link to GitHub Issue or related Pull Request, if one exists
#0

## Description of change
Add `-icmphook` option under spicecfg Development/Network for emulating
keepalive ping replies in user mode, so games do not need to open
privileged raw ICMP sockets. This makes connecting to networks possible
on macOS under Whisky, and on Windows with ICMP firewalled or spice
running without Administrator privileges.

## Testing
Tested DDR and SDVX on Windows 10 and macOS Tahoe. Network check status
in test menu shows `CONNECTED` when -icmphook is enabled, and `NOT
CONNECTED` when it is disabled. Actual network functionality works as
expected (under previously impossible conditions).
2026-05-14 00:44:14 -07:00
bicarus-dev 6ec2b47345 fix option text 2026-05-13 17:54:25 -07:00
bicarusandGitHub ebda16beae rb: update reflec beat to use new timer (#692)
## Link to GitHub Issue or related Pull Request, if one exists
Continuation of #682

## Description of change
RB uses SleepEx, so it was missed during the search while working on
#682.

## Testing
Plays about the same on a 200Hz touch monitor.
2026-05-10 19:47:33 -07:00
bicarusandGitHub c29a26f183 graphics: hide subscreen window from task bar and alt+tab list when in full screen (#690)
## Link to GitHub Issue or related Pull Request, if one exists
n/a

## Description of change
When booting in fullscreen, hide the subscreen from the alt+tab list and
from the task bar. Alt-tabbing to it does nothing, so this is just to
avoid confusion.

No change to windowed mode.

## Testing
Tested TDJ/SDVX/Popn. Didn't test Gitadora but it's probably fine..
2026-05-10 05:33:18 -07:00
bicarusandGitHub 2014e9cf75 graphics: option to change monitor resolution (#689)
## Link to GitHub Issue or related Pull Request, if one exists
n/a

## Description of change
Add a new option under `Monitor` group to change the monitor resolution
before booting the game, very similar to how orientation / refresh rate
options work.

This would be useful for games like Polaris Chord that boots at current
resolution but manages to have graphical issues when not in 1080p.

## Testing
2026-05-09 21:32:43 -07:00
bicarusandGitHub 33f2e1d4b7 rawinput, touch: attempt to remove touchscreens injecting mouse events (#687)
## Link to GitHub Issue or related Pull Request, if one exists
Fixes #562

## Description of change

### rawinput touch fixes
In rawinput touch handler, if the mouse events (`WM_LBUTTONDOWN`,
`WM_MOUSEMOVE`, `WM_LBUTTONUP`) originated from a touch screen, drop
them on the floor instead of treating them as touch events.

This is an attempt to fix phantom touch issues we see with some screens.

Fix another issue where, if mouse is clicked inside the window, and then
dragged outside the window and button is released, touch point (id 0)
continues to be held.

### wintouchemu fixes
If we detected a touch input recently (500ms), ignore new mouse clicks.
This isn't perfect, but hopefully it helps with filtering out mouse
clicks originating from touch screens.

Also, switch over to detecting the primary mouse button correctly, not
just the left mouse button.

### notes

Future investigation: are there really touchscreens that report a mouse
HID capability & insert mouse events? We could have a way to filter them
at rawinput level but it'll be ugly.

## Testing
Tried jubeat, beatstream..
2026-05-09 17:52:43 -07:00
bicarusandGitHub eb037542b4 jubeat: introduce new algorithm for touch (#686)
## Description of change
There was enough feedback that the current algorithm is not ideal for
most touch screens because most touch screens are not 24". In the
current algorithm touching the gap is no-op, so people think the game is
dropping touches.

This PR introduces a new algorithm that inspect touches on the gap and
triggers the nearest square.

## Testing
Tested landscape and portrait jubeat.
2026-05-09 04:22:29 -07:00
bicarusandGitHub 039b42ad53 fix build (#684) 2026-05-08 22:46:29 -07:00
bicarusandGitHub 33b4718744 overlay: fix mouse events queued up while overlay is hidden (#683)
## Link to GitHub Issue or related Pull Request, if one exists
continuation of #677

## Description of change
When overlay was hidden, when mouse buttons were clicked, they were
queued up and replayed when overlay became visible again.

This is due to improper usage of `GetAsyncKeyState`, per documentation.

## Testing
2026-05-08 03:37:46 -07:00
bicarusandGitHub 71ba9b6b47 os: implement win10 high-resolution timer as replacement for Sleep() / sleep_for() (#682)
## Link to GitHub Issue or related Pull Request, if one exists
Fixes #681 

## Description of change
`Sleep` and `sleep_for()` can be very inaccurate and varies depending on
what the OS gives us...

### `timeBeginPeriod(1)`
On boot, we are now calling `timeBeginPeriod(1)`, which affects the
whole process but makes `Sleep` more accurate. There is some risk here
if any game was relying on doing things like `Sleep(1)` and expecting it
to run for 15.6ms. Most games already call `timeBeginPeriod(1)` in the
game engine, though not the whole time, so I'm hoping that this is not
too impactful.

### Opt out of Win11 power throttling
Ensure that timer resolution change above is respected even when the
window is occluded / minimized by opting out of throttling via
`PROCESS_POWER_THROTTLING_IGNORE_TIMER_RESOLUTION`.

### Use Win10 high resolution timer instead of Sleep

On Win10 1803 and above, there is a new OS-level API for high resolution
timers; if this is available, use it
(`CREATE_WAITABLE_TIMER_HIGH_RESOLUTION`). Worth noting that WINE
doesn't support this currently.

If not, fall back to `Sleep`, which is significantly better than
`sleep_for()` in my experiments.

Callers of Sleep / sleep_for were replaced with this new timer. Most of
them anyway; calls to Sleep() with more than 100ms+ was left alone.

### Add an option as a chicken bit

To opt out I'm adding a new option called `Use Legacy Timers` which will
revert to behavior before this PR. The code paths that switched from
`sleep_for` to `Sleep` will remain in place though, not affected by the
option.

## Expected changes
In some I/O emulation modules, poll threads may run more frequently,
resulting in lower latency.

It also means that spice overall may use more CPU resources and power.
If you don't like this, you can always enable the option to opt out;
e.g., if you're on old arcade cab PC.

## Testing

DDR p4io - ok
drs touch hook - ok
IIDX camera hook - ok
CCJ trackball - ok
2026-05-08 02:25:20 -07:00
bicarusandGitHub 1957917270 sdk: update cpp sample for DDR, fix DDR MDXF polling when api/sdk is providing input (#679)
## Link to GitHub Issue or related Pull Request, if one exists
#676 
#452 

## Description of change

When spice SDK or API sets or clears button and analog override, we
don't tell MDXF about it, so it may fall behind. This is a special case
for DDR MDXF I/O emulation. For Naive/Xinput I think we are getting
lucky because they usually also generate WM_INPUT events.

Update the C++ SDK sample to provide more interesting example of
emulating DDR arrows with the keyboard.

## Testing
2026-05-07 18:10:14 -07:00
bicarusandGitHub b9ccddf47d launcher: prevent early hook from launching in standalone configurator (#678)
## Description of change
Seems to be an oversight when it was initially implemented; hooks should
not load when launching `spicecfg` (or `spice -cfg` or `spice64 -cfg`).
2026-05-07 00:26:18 -07:00
bicarusandGitHub cd0ba51b5a sdk: introduce Spice SDK (#676)
## Link to GitHub Issue or related Pull Request, if one exists
n/a

## Description of change
Adds a flat-C, headers-only library for writing DLL plugins with spice.

This is meant to be an alternative to writing applications over spice
API which doesn't suit some scenarios like writing custom
high-performance I/O modules.

All you have to do is:

1. Create a 32-bit or 64-bit DLL with `spice_sdk_entry_point` as a
DLLExport
1. Include `spicesdk.h`, and optionally `spicesdk_io.h` (see
`extras\sdk\include`)
1. `spice_sdk_entry_point` will be called when hooks get initialized.
Call `init` with valid parameters.
1. When `init` returns, `SPICE_SDK_V0` will be filled out with a series
of function pointers; your DLL can now start calling into it.
1. Compile your DLL and add it as a DLL hook (`-k`) in spice when
launching the game.

See `v0_cpp.cpp` for an example.

V0 implements the following functions:

```
    log;

    get_game_info;
    get_avs_info;

    get_button;
    set_button;

    get_analog;
    set_analog;

    get_light;
    set_light;

    set_touch;
    clear_touch;

    insert_card;
    set_keypad;
```

V0 is focused on providing common utility functions needed in a lot of
DLL hooks (get AVS info, log messages to file), and implementing I/O.

In the future, we could have more interesting things like:

* scanning and modifying memory 
* installing DLL hooks
* having direct hooks into I/O emulation modules
* getting full tape LEDs
* having a callback into DX9 `Present` call
* drawing an ImGui window

More detailed documentation is coming soon in form of a wiki page.

## Testing
See included sample DLL.
2026-05-06 23:29:52 -07:00
bicarusandGitHub 1f23d88c7a overlay: prevent overlay from queueing up keyboard input while overlay is not visible (#677)
## Link to GitHub Issue or related Pull Request, if one exists
Fixes #675

Yet another regression from #604 

## Description of change
When overlay was hidden (not visible) it was still accepting keyboard
input, so when it became visible again the queued up keystrokes got
processed all at once.

Also, fix a bug with monitor settings update kicking in when in windowed
mode - this really should have been only for full screen.

## Testing
Tested DDR windowed / full screen, TDJ with subscreen overlay windowed /
fullscreen.
2026-05-06 01:00:52 -07:00
bicarus-dev 46e43ab09c fix typo 2026-05-04 23:57:35 -07:00
bicarusandGitHub 96950b6b4e rawinput: reimplement analog relative mode and delay (#674)
## Link to GitHub Issue or related Pull Request, if one exists
#181 

## Description of change
Implement analog relative mode and delay option using a new
millisecond-based algorithm.

## Testing
WIP
2026-05-04 22:50:11 -07:00
bicarusandGitHub 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
bicarusandGitHub 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
bicarusandGitHub 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
dinandGitHub 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
bicarusandGitHub 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
bicarusandGitHub 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
bicarusandGitHub 7b862768b0 overlay: show name of analog control in popup title (#667) 2026-05-02 00:01:33 -07:00
bicarusandGitHub 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
llm96andGitHub 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
bicarusandGitHub 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
bicarusandGitHub 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
bicarusandGitHub 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
WillandGitHub 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
bicarusandGitHub 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
bicarusandGitHub 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
bicarusandGitHub 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
bicarusandGitHub 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
bicarusandGitHub 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
bicarusandGitHub 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
bicarusandGitHub 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
bicarusandGitHub 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
bicarusandGitHub aadd2bdd59 cfg: deprecate -sdvx720, add new category for camera/printer options (#650) 2026-04-18 15:56:15 -07:00
EmmaandGitHub 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
bicarusandGitHub 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
bicarusandGitHub 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
bicarusandGitHub b38160d6c2 popn: native touch option, keypad warning (#640)
#639 

the usual
2026-04-15 19:31:26 -07:00
EmmaandGitHub 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
EmmaandGitHub 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
bicarusandGitHub 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
bicarusandGitHub 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
bicarusandGitHub 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
bicarusandGitHub 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
bicarusandGitHub 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
bicarusandGitHub 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
bicarusandGitHub 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
bicarusandGitHub 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
bicarusandGitHub 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
bicarusandGitHub 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
bicarusandGitHub 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
bicarusandGitHub 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
bicarusandGitHub 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
bicarusandGitHub 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
bicarusandGitHub 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
bicarusandGitHub 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
bicarusandGitHub 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
bicarusandGitHub 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
bicarusandGitHub 6f4824b648 misc: improve monitor logging (#614) 2026-04-06 22:22:34 -07:00
bicarusandGitHub 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
WillandGitHub 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
bicarusandGitHub 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
bicarusandGitHub 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
bicarusandGitHub 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
bicarusandGitHub 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
bicarusandGitHub 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
bicarusandGitHub 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
bicarusandGitHub 1ad6a9412e imgui: update imgui to v1.92.7, update dx9 backend to latest (#605) 2026-04-03 01:51:08 -07:00
bicarusandGitHub 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
bicarusandGitHub 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
bicarusandGitHub 220b27159b ea3: don't treat failure from dll_entry_init as fatal (#601)
## Link to GitHub Issue or related Pull Request, if one exists
fixes #600
2026-03-30 02:31:08 -07:00
bicarusandGitHub b06a0a7d47 overlay: card override input should be disabled when overridden with cmd line (#599) 2026-03-29 20:36:42 -07:00
bicarusandGitHub d66af8e16b overlay: options tab(s) clean up (#598)
## Description of change
Merge option name and command line parameter columns.
Expand the size of widget column.
In Search tab, add a new column to display the category.
Add a highlight effect to rows when hovered over - apply this to all
tables in the configurator UI.
2026-03-28 18:21:53 -07:00
bicarus-dev ad366ecdd0 update fatal crash message 2026-03-27 18:46:08 -07:00
llm96andGitHub 22cb4d689c utils: print DLL info for early hooks (#597)
## Link to GitHub Issue or related Pull Request, if one exists
#596

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

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

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

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

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

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

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

## Description of change

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

N/A

## Description of change

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

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

## Testing

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

## Description of change

### Controller Presets

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

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

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

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

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

## Testing

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

## Description of change

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

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

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

n/a

## Description of change

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

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

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

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

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

## Description of change

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

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

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

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

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

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

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

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

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

Implemented a few for now:

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

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

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

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

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

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

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

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

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

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

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

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

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

## Testing
An API client was made sending touch states, both valid and invalid
(oversized). The valid states are handled identically in the test menu
while the oversized states no longer crash.
2026-02-26 10:47:08 -08:00
bicarusandGitHub dc82c980b1 iidx: show error message for emulated I/O mismatch in LDJ/TDJ sub overlay (#555)
## Link to GitHub Issue, if one exists
#345 

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

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

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

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

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

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

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

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

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

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

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

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

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


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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

## Testing
Tested:

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

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

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

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

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

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

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

This causes a lot of confusion for:

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

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

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

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

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

## Testing
Seems to work with my crappy Logitech C270

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

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

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

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

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

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

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

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

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

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

## Testing
Tested both GF and DM.

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

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

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

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

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

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

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

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

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

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

## Description of change
Implement `WrappedIAudioClient3`.

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

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

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

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

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

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

## Description of change
Use fmt library for formatting time

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

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

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

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

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

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

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

The warning label will be kept with slightly reworded text.

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

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

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

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

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

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

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

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

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

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

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

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

## Description of change
Two issues:

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

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

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

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

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

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

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

Gitadora Arena will receive 250% by default.

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

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

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

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

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

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

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

## Testing
Verified in terminal.
2025-12-31 01:15:19 -08:00
bicarusandGitHub 8de7e5a9e4 sdvx: prevent the game from trying a random ASIO driver (#488)
## Link to GitHub Issue, if one exists
n/a

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

## Testing
Tested 4 combinations:

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

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

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

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

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

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

## Testing
- Verified IO still functions properly
- Verified decrease in overall CPU usage
2025-12-21 20:49:39 -08:00
bicarus-devandGitHub 6cb507f4fa Update CONTRIBUTING.md 2025-12-21 18:57:42 -08:00
bicarus-devandGitHub de05af052f build: turn warnings into errors for gcc (#466)
for CI
2025-12-21 03:52:30 -08:00
bicarus-devandGitHub c767918729 nost: avoid pass-by-value in PANB I/O (#465) 2025-12-20 02:33:25 -08:00
bicarus-devandGitHub fbc8bc2502 ddr: tweaks to mdxf i/o (#464)
## Link to GitHub Issue, if one exists
#452

## Description of change
* Split the MDXF I/O option into three: auto, thread, and backfill.
`auto` is the same as what `thread` used to be before this change (based
on I/O poll rate), but `thread` now always forces thread poll mode.
`auto` is the default (behavior wise, same as before)
* For the poll rate measurement, start polling after 20s mark to avoid
frame drops during game boot
* Minor code clean up

## Testing
manual test in windowed + full screen
2025-12-20 01:37:56 -08:00
bicarus-dev c38f42cb19 update options text 2025-12-19 02:15:56 -08:00
cchikeandGitHub 42d8bbc92f MDX: Exposed P4IO Buffer Filling Mode to Spicecfg (#463)
## Description of change
Extension of #460 
An option was exposed to `spicecfg` (`P4IOBufferMode`) for toggling
between the Thread and Backfill buffer fill implementations from the
prior PR.

## Testing
- Verified the option properly toggled between the two implementations
2025-12-19 02:14:10 -08:00
bicarus-devandGitHub a09905792d iidx: workaround for touch monitors that report contact area (#462)
## Link to GitHub Issue, if one exists
n/a

## Description of change
When touch monitors send touch area size information
(`TOUCHEVENTFMASK_CONTACTAREA` flag and `cxContact` / `cyContact` fields
of `TOUCHINPUT` struct) from `GetTouchInputInfo`, IIDX gets confused and
does some unexpected things when interacting with the subscreen.

As a workaround, hook `GetTouchInputInfo` and clear out the flag &
values.

This is only done for IIDX AND when -iidxnativetouch is on.

## Testing
Tested on a Dell touch monitor, simulating "bad" values.
2025-12-18 15:19:09 -08:00
cchikeandGitHub f113eb4f52 MDX: Merged Thread and Backfill Implementations (P4IO Timing Fix) (#460)
## Link to GitHub Issue, if one exists
#452 

## Description of change
(Another extension of #450)
Merged the old threading implementation into the current backfill
implementation because it was showing signs of having slightly better
timing performance. The following description is taken from the
strikethrough text from the referenced PR above:
>When the module is booted, it counts how many times
ac_io_mdxf_update_control_status_buffer was called in the first three
seconds, then calculates the approximate refresh rate of the current
game instance. If the refresh rate is under THRESHOLD_REFRESH_RATE
(120Hz), a low-priority thread dedicated to inserting polls into the
ring buffers is started and run at THREAD_REFRESH_RATE_HZ (125Hz).
Combined with the polls inserted by the main arkmdxp4 thread and
rawinput real-time events, the 125Hz update cadence is enough to bring
the span of poll history represented by the ring buffers closer to
parity with what arkmdxp4 expects.

The default mode is `THREAD_MODE` but can be switched to `BACKFILL_MODE`
(which could possibly be made as a `spicecfg` option).

There was also a fixed bug in the `ac_io_mdxf_get_control_status_buffer`
function, which should not have been writing to the `head` pointer. This
was allowing for an edge case situation where the `head` pointer was
advanced in another thread then rewound back to the previous `head` in
this thread. The real implementation only reads the pointer and returns
it.

## Testing
- Logged the ring buffer states after many iterations and verified
regular structure
- Same playtesting as the prior PRs
2025-12-18 09:27:33 -08:00
bicarus-devandGitHub f726748ebe build: fix debug build (#461)
Fix build break of DEBUG build after updating FMT lib
2025-12-18 02:48:26 -08:00
bicarus-devandGitHub 371c957025 cfg: cap max length on preview for buttons edit dialog (#459)
## Link to GitHub Issue, if one exists
Related to #458

## Description of change
Truncate text if button's identifier string might overflow the popup
dialog width

## Testing
2025-12-17 16:04:45 -08:00
llm96andGitHub 83aa907b13 rawinput: append HID product string to description (#458)
## Link to GitHub Issue, if one exists
N/A

## Description of change
Append product strings to descriptions. Useful for differentiating
devices in the analog binding dialog.

Before | After
------ | ------

![0](https://github.com/user-attachments/assets/803c5a69-2ed9-40f8-a147-f1b4f3a7e220)
|
![1](https://github.com/user-attachments/assets/7407a3cc-eebb-4529-be43-2eb177645bb7)

## Testing
`spice64 -iidx -cfg` ⟶ Analogs ⟶ Bind
2025-12-17 15:03:16 -08:00
bicarus-devandGitHub 71b144918c rawinput: stop iterating devices if one was found (#457)
## Link to GitHub Issue, if one exists
n/a

## Description of change
This would save a handful of instructions at best but it was bothering
me

## Testing
Tested with two controllers + naive with ddr
2025-12-17 05:40:27 -08:00
cchikeandGitHub e618a05b08 MDX: Updated Ring Buffer Backfill Implementation (P4IO Timing Fix) (#456)
## Link to GitHub Issue, if one exists
#452  

## Description of change
- Extension of #450 
- Moved the call to `arkGetTickTime64()` outside the update function to
bring the timestamps closer to real-time
- Changed the update function to only call `getState()` if called from
`rawinput`, otherwise just fill the ring buffer entries with the last
known state
- Changed the update function to only write one ring buffer entry if
called from `rawinput`
- Moved the call to `mdxf_poll()` within `rawinput.cpp` outside the loop
that iterates over all devices

## Testing
- Logging was done to verify the span of poll history covered by each
request for 7 ring buffer entries was ~28ms, as the game expects
- Observed some improvement in timing precision since the prior
implementation
2025-12-17 02:45:22 -08:00
bicarus-devandGitHub d0f3ffd12f linux, audio: add hooks acmFormatSuggest (#455)
## Link to GitHub Issue, if one exists
n/a

## Description of change

`acmFormatSuggest` is known to cause significant perf issues on Linux;
add a hook to return cached results.

The fix is only active in the Linux variant.

For discussion, see
* https://codeberg.org/nixac/spicetools/issues/2
* https://codeberg.org/nixac/spicetools/issues/4

## Testing
I've only tested this on Windows, checking if iidx and popn can boot,
play songs without audio issues.
2025-12-16 22:56:22 -08:00
bicarus-devandGitHub 6d18d6a496 troubleshooter: address false positive (#454)
## Link to GitHub Issue, if one exists
#447

## Description of change
Allow GetAdaptersInfo to fail with ERROR_BUFFER_OVERFLOW.
2025-12-16 16:47:02 -08:00
bicarus-devandGitHub b0810558a7 iidx,sdvx: error message for subscreen unavailable does not draw a proper ImGui window (#453)
## Link to GitHub Issue, if one exists
n/a

## Description of change
In cases where the subscreen overlay window does not have anything to
show (either via misconfiguration or an error), ensure that the window
border is shown with the close button. Additionally, add a small button
to close the dialog to help the user.

## Testing
manual validation
2025-12-16 15:24:46 -08:00
bicarus-devandGitHub 6ae2e4003c scard: stop polling if scard service dies (#451)
## Link to GitHub Issue, if one exists
n/a

## Description of change
If `SCardGetStatusChange` returns `SCARD_E_SERVICE_STOPPED`, stop
polling.

Add some sleep if it fails for other reasons to prevent busy loops.

## Testing
Tested with ACR122U reader.
2025-12-15 20:55:14 -08:00
cchikeandGitHub 8545f1cbdc MDX: Added Backfill Logic For Ring Buffer Health (P4IO Timing Fix) (#450)
## Context
While testing the emulator on 60Hz for a while, I noticed there was a
pretty consistent timing bug with steps that had quick releases in
between them (mainly footswitches and sometimes jacks). As mentioned in
my previous commits, `arkmdxp4` determines press/release times for steps
based on a 7-poll history on every frame, which is normally populated at
~250Hz from the MDXF board in `libacio2`, yielding around 28ms of
polling history. When emulated, this is instead being populated
internally from `arkmdxp4` once per frame, meaning the 7-poll history
could have up to `1000/60 * 7 = 117ms` worth of step history at 60Hz. In
the case of a footswitch/jack, if you account for the real-time press
and release event that's inserted from `rawinput` during that time,
there'd actually be up `1000/60 * (7-2) = 83ms` worth of step history.
This is still a large enough window of time for `arkmdxp4` to start
confusing older on-state polls with more recent ones. Here is a real
ring buffer I logged when I reproduced the bug:

```
Frame #6: 82ms | 1
          70ms | 1 <- rawinput real-time press
Frame #5: 66ms | 0
Frame #4: 49ms | 0
Frame #3: 34ms | 0 <- rawinput real-time release (coincided with frame update)
Frame #2: 16ms | 1 <- stale "held" poll
Frame #1:  0ms | 1 <- stale "held" poll
```

When `arkmdxp4` determines that a new press event occurred, it uses the
timestamp of the oldest on-state poll in the ring buffer as the press
time. In this case, it uses time 0ms instead of the correct 70ms,
resulting in the game thinking the player stepped 70ms before they
actually did. For reference, that same ring buffer snapshot would've
looked like this in the real implementation:

```
Frame #6: 82ms | 1
          78ms | 1
          74ms | 1
          70ms | 1
Frame #5: 66ms | 0
          62ms | 0
          58ms | 0
```

The correct time 70ms would be assigned to that step in this case. This
demonstrates the importance of keeping the ring buffers filled with only
recent poll history, closer to the threshold of around ~28ms that the
real implementation expects.

## Description of change
~~When the module is booted, it counts how many times
`ac_io_mdxf_update_control_status_buffer` was called in the first three
seconds, then calculates the approximate refresh rate of the current
game instance. If the refresh rate is under `THRESHOLD_REFRESH_RATE`
(120Hz), a low-priority thread dedicated to inserting polls into the
ring buffers is started and run at `THREAD_REFRESH_RATE_HZ` (125Hz).
Combined with the polls inserted by the main `arkmdxp4` thread and
`rawinput` real-time events, the 125Hz update cadence is enough to bring
the span of poll history represented by the ring buffers closer to
parity with what `arkmdxp4` expects.~~

EDIT: The prior thread implementation was replaced with new logic that,
on each call to the update function, backfills the ring buffer with
entries representing the last known state of the controller. For
example, given the ring buffer with a single entry:

```
0ms | 0
```
When the update function is called on the next frame at 60Hz, the
function will insert intermediate entries as "padding" before updating
to its current state:

```
16ms | 1 <- state at current frame
12ms | 0 <- "padding"
8ms  | 0 <- "padding"
4ms  | 0 <- "padding"
0ms  | 0 <- last known state
```

## Considerations
~~In the future, it might be worth exploring a route that interpolates
polls instead of relying on a thread for inserting polls (in other
words, creating intermediate poll entries based on the known real-time
press and release events from `rawinput`). For example, if `rawinput`
reported a press at time 10ms and a release at time 30ms, it's known
that any poll in between those times would report an on-state for that
arrow. However, considering this would involve adding multiple entries
per call (for times in the past) and how the update function can be
called from two separate threads, it may be introduce hard-to-debug edge
cases and add unnecessary complexity overall.~~

EDIT: Implemented

## Testing
~~-Verified the thread was inserting polls at the expected cadence
-Verified the thread is only created when the game's refresh rate is
below the threshold
-Verified the thread introduced no significant increase in CPU~~
- Verified the entries were being backfilled correctly on several
different refresh rate instances
- Verified the bug could no longer be reproduced
2025-12-15 19:46:41 -08:00
bicarus-devandGitHub e0530fedad build: update fmt library to 12.1.0 (#449) 2025-12-14 14:36:38 -08:00
bicarus-devandGitHub b220cbf336 build: remove linalg library (#448)
I think this was used for DRS VR support.
2025-12-14 04:37:28 -08:00
bicarus-devandGitHub f68a3439d3 troubleshooter: detect lack of ipv4 (#447)
## Link to GitHub Issue, if one exists
#345 

## Description of change
Most games will fail show a network error if there is no network adapter
with a valid IPv4 address

Detect this case in netfix code and show a warning message in log +
deferred error message.

## Testing
Tested with no network adapters, with IPv6 only adapter, and a
disconnected NIC.
2025-12-14 00:07:22 -08:00
cchikeandGitHub 6f58d9ab68 MDX: Decoupled Input Polling Rate From Game Refresh Rate (P4IO Timing Fix) (#446)
## Context
In the real implementation of `ac_io_mdxf_update_control_status_buffer`,
it doesn't actually add entries to the pad state ring buffers (it only
marks the head entry as the "last consumed" entry), and `arkmdxp4.dll`
expects the ring buffers to be updated at regular intervals inside
`libacio2.dll` at ~250Hz per player. Instead, in the emulated version,
the updates happen in this function and that regular interval is
established internally using the game's refresh rate (60Hz, 120Hz,
144Hz, etc.). This effectively locks the polling rate of pad input to
that refresh rate, which is quite low for pad input.

## Description of change
The polling rate lock was circumvented by calling
`ac_io_mdxf_update_control_status_buffer` as soon as the game receives
controller input in `rawinput.cpp`. A simple wrapper function
`mdxf_poll` was exposed so press and release events could be added to
the ring buffers in real time as they happen. After each controller
event in `rawinput.cpp`, `mdxf_poll` calls
`ac_io_mdxf_update_control_status_buffer` for each player, checking the
pad's current state and adding it to the ring buffer if it's changed
since the last call. This results in the 0 -> 1 (press) and 1 -> 0
(release) state transitions used for gameplay to be based only on the
real-time events inserted from `rawinput.cpp`, while leaving the rest of
the entries generated from the main `arkmdxp4.dll` thread as "padding"
the game expects (I actually tried reducing the ring buffers to *only*
write entries on state transitions, but it broke gameplay, which means
the game still depends regular reporting of the pad state). Here's an
example of how a ring buffer would look if an arrow was pressed at some
time 32ms (assume 165Hz game refresh rate, ~6ms updates):

```
Old (only frame updates):
1 | 36ms
0 | 30ms
0 | 24ms
0 | 18ms
0 | 12ms
0 | 6ms
0 | 0ms
```
The game only sees the press event at time 36ms on frame update, so time
36ms is assigned to the press time of that arrow (4ms later than the
real press time).

```
New (frame updates + event updates from rawinput.cpp):
1 | 36ms
1 | 32ms <- from rawinput.cpp
0 | 30ms
0 | 24ms
0 | 18ms
0 | 12ms
0 | 6ms
```
The game sees the real press time 32ms on frame update, so the correct
press time 32ms is assigned.

## Testing
- Played on various refresh rates and verified the controller input
remains highly responsive.
- Inspected the ring buffers with a debugger and verified entries for
press and release events from `rawinput.cpp` were correctly being added
for their respective players.
2025-12-12 19:03:04 -08:00
bicarus-devandGitHub c3428236f8 iidx: option to disable recording function (#445)
## Link to GitHub Issue, if one exists
#442 

## Description of change
Adds a new option that intentionally fails the recording functionality.

## Testing
Tested on iidx32.
2025-12-11 22:03:51 -08:00
bicarus-devandGitHub 6856a0950c iidx: fix play record feature broken by new NVIDIA driver update (#444)
## Link to GitHub Issue, if one exists
fixes #442

## Description of change
New NVIDIA driver update (591.44) dropped support for older NVENC API.
Specifically,

* TDJ uses `NV_ENC_PRESET_HQ_GUID` but this is no longer supported; must
be swapped with new encoder presets
* `NvEncGetEncodePresetConfig` does not take new encoder presets, the
-Ex version must be used

All of this happens because bm2dx.dll takes code from an older NVENC
sample.

To address this,

* Install additional hooks for NVENC API
* Hijack calls to `NvEncGetEncodePresetConfig` and instead swap with
`NvEncGetEncodePresetConfigEx` (only if the initial call fails, which is
how we detect old vs new driver), replacing `NV_ENC_PRESET_HQ_GUID` with
`NV_ENC_PRESET_P4_GUID` and `NV_ENC_TUNING_INFO_HIGH_QUALITY`
(functionally equivalent according to NVIDIA docs -
https://docs.nvidia.com/video-technologies/video-codec-sdk/11.1/nvenc-preset-migration-guide/index.html)
* When calling `nvEncInitializeEncoder`, hijack and swap in the preset
GUID again.

## Testing
Tested on 591.44 driver on a RTX 4070.

The resulting videos were compared as well, and they are practically
identical from encoder perspective.
2025-12-11 16:09:09 -08:00
cchikeandGitHub dc7d8515d3 MDX: Fixed Ring Buffer Traversal Direction (P4IO Timing Fix) (#441)
## Description of change
In the real `libacio2.dll` implementation,
`ac_io_mdxf_get_control_status_buffer` returns the ring buffer entry at
`(head - index) mod size` but this function was using `(head + index)
mod size` instead. Basically, `index` is supposed to indicate how many
entries back, not forward. This was causing the polling history of each
player to be returned backwards. A few other things were updated to
bring the function's behavior closer to the real implementation:

1. Number of ring buffer entries per player were increased to 16 (this
is the size of them in the real implementation)
2. `ac_io_mdxf_get_control_status_buffer` now returns the current head
of the ring buffer (instead of a success/failure boolean), which is what
`arkmdxp4.dll` expects.

## Testing
I played a few songs to make sure nothing was broken. I also attached a
debugger and verified the ring buffer entries are *actually* now being
returned in reverse chronological order as intended.
2025-12-11 10:38:47 -08:00
bicarus-devandGitHub e100093ead iidx: dump more log from nvenc hooks (#443)
## Link to GitHub Issue, if one exists
For #442

## Description of change
Logs more info about nvenc hook calls to detect failures

## Testing
2025-12-11 09:39:23 -08:00
cchikeandGitHub e5808257ca MDX: Fixed Status Control Buffer Emulators (P4IO Timing Fix) (#439)
## Description of change
In its current state, the emulated `libacio2.dll` functions
`ac_io_mdxf_get_control_status_buffer` and
`ac_io_mdxf_update_control_status_buffer` only operate using a single
head pointer (`COUNTER`) for the ring buffers of
`[panel_state,timestamp]` (simplified for brevity here) polls for both
players, when they should be maintaining a separate head pointer for
each ring buffer. On every frame, `arkmdxp4.dll` makes calls to
`ac_io_mdxf_update_control_status_buffer`, which [in this emulated
version] advances the head pointer, creates a new entry for the latest
panel state info along with the current time, and stores it at the head
of the ring buffer. This function is called for each player: After the
call for 1P, the head pointer is advanced by 1, so by the time it's
called for 2P, the head pointer had already been advanced by 1, meaning
the new entry for 2P's panel state ring buffer is written two entries
ahead of where the last entry for 2P was written. This is an example of
how the entries were written for each ring buffer:

```
Frame 1:
1P[0] = t0
2P[1] = t0

Frame 2: 
1P[2] = t1
2P[3] = t1

Frame 3:
1P[4] = t2
2P[5] = t2

Frame 4:
1P[6] = t3
2P[0] = t3

...

Frame 7:
1P[5] = t6
2P[6] = t6

Frame 8:
1P[0] = t7
2P[1] = t7
```

This is an issue because `arkmdxp4.dll` calls
`ac_io_mdxf_get_control_status_buffer` on every frame to load the last 7
polls for each player, which depends on the ring buffer entries being in
reverse chronological order to properly determine when an arrow is
pressed and when it was released, but this is what it sees from the ring
buffers after 8 frames:

```
1P: t7 -> t3 -> t6 -> t2 -> t5 -> t1 -> t4
2P: t7 -> t3 -> t6 -> t2 -> t5 -> t1 -> t4
```

In this case, `arkmdxp4.dll` assigns step times based on polls that are
out of order, resulting in unpredictable timestamp assignment to each
step, with the issue compounding at lower framerates since the timestamp
deltas between each poll will be greater (see side note). After
implementing two separate head pointers for each ring buffer, those same
two arrays become:

```
1P: t7 -> t6 -> t5 -> t4 -> t3 -> t2 -> t1
2P: t7 -> t6 -> t5 -> t4 -> t3 -> t2 -> t1
```

This is how `arkmdxp4.dll` expects this data to be structured, so timing
should become more consistent after this change.

Side note: in this particular p4io implementation, the only place the
polls are updated is once per frame on the calls to
`ac_io_mdxf_update_control_status_buffer`, meaning the polling rate of
the controller used is locked to the framerate of the game. There should
be another thread not tied to the main one that listens to input from
the OS and updates these ring buffers on a much faster cadence.

## Testing
I played a few songs to make sure nothing was broken. I also attached a
debugger and verified the ring buffer entries are now being returned in
reverse chronological order as intended.
2025-12-09 19:15:15 -08:00
bicarus-devandGitHub 283ec3960a ea3: add warning message for missing <soft> node (#440)
## Link to GitHub Issue, if one exists
#345 

## Description of change
Add some descriptive error message for the case where we couldn't find
softid in prop files.

## Testing
manual
2025-12-09 19:14:58 -08:00
bicarus-devandGitHub 0ec37ac6ea troubleshooter: log soft id (#436)
## Link to GitHub Issue, if one exists
n/a

## Description of change
Log soft ID (game datecode etc) at the top of auto-troubleshooter output
2025-12-08 17:35:59 -08:00
duel0213andGitHub 0ef0b84308 mdx: change init_code instead of security_code for gold cab (#438)
## Link to GitHub Issue, if one exists
n/a

## Description of change
This changes previous security code change to init code change (#435)

## Testing
2025-12-08 17:14:35 -08:00
duel0213andGitHub 62c30d8895 mdx: fix io mode bio2 boot crash (#435)
## Link to GitHub Issue, if one exists
n/a

## Description of change
Currently when 'bio2' specified on 'io' parameter of app-config the game
crashed due to security code check.
These codes changes security code to corresponding one and adds fake
BIO2 entry to satisfy BIO2 check.

## Testing
Tested with MDX-003 and J:I:B ident.
The game boots and A3 now displays gold cab theme and displays MATCHING
GROUP, MATCHING SIDE on NETWORK OPTIONS test mode menu.
2025-12-08 10:16:16 -08:00
bicarus-devandGitHub 4e86cb16a2 cfg: tweaks to analog binding UI (#434)
## Link to GitHub Issue, if one exists
n/a

## Description of change
Add the raw device handle string to tooltip.
Add `Reset` button that clears all values.

## Testing
manual testing
2025-12-06 01:47:00 -08:00
bicarus-devandGitHub 76f401de95 iidx: rework wasapi/asio detection for iidx33+ (#433)
## Link to GitHub Issue, if one exists
Fixes #432 

## Description of change
Newer versions of iidx33+ reintroduces `SOUND_OUTPUT_DEVICE` env var.
iidx module must account for its return

## Testing
2025-12-05 15:33:09 -08:00
bicarus-devandGitHub 5947779502 iidx,sdvx: warn user when using -monitor with TDJ/UFC mode (#431)
## Link to GitHub Issue, if one exists
#345 

## Description of change
Warn user via warning message in log + auto-troubleshooter entry when
-monitor option is used in full screen with TDJ and UFC mode.

This covers yet another case where TDJ subscreen does not respond to
mouse clicks (because the game expects them to come through the primary
display). For UFC, -monitor usually results in a failure to create DX
adapter.

## Testing
Manual verification with TDJ/UFC modes and two monitors.
2025-12-04 21:57:36 -08:00
bicarus-devandGitHub 61c97ccaab net: ignore -ea if -url is set (#430)
## Link to GitHub Issue, if one exists
#345 

## Description of change
Ignore -ea if -url is provided

## Testing
2025-12-01 16:04:32 -08:00
bicarus-devandGitHub 12d6a88c60 iidx: turn off camera access by default (#428)
## Link to GitHub Issue, if one exists
For #345 
Similar idea as https://github.com/spice2x/spice2x.github.io/pull/350

## Description of change
Disable camera access by default by setting `CONNECT_CAMERA` environment
var to 0 before launching. This was one of those "required" things
people clicked on, but now it's done automatically to reduce friction
for new users.

On cab setups (i.e., running without -iidx module), cameras will remain
on, unless user overrides it.

On franken setups (partial I/O) - user will need to specify what they
want via the new option, we can't guess correctly.

## Testing
2025-11-30 16:13:34 -08:00
bicarus-devandGitHub 984b41a1ae cfg: fix combo boxes (#429)
## Link to GitHub Issue, if one exists
n/a

## Description of change
Combo boxes for options were not highlighting the currently selected
item. Also, when there were no description provided for an option, it
was still showing the empty `()` in the selected value.

## Testing
2025-11-30 01:59:25 -08:00
duel0213andGitHub d06b8bd731 mfc: fix 2025 version touch (#426)
## Link to GitHub Issue, if one exists
n/a

## Description of change
HG cabinet uses wintouch and it requires TOUCHEVENTF_PRIMARY flag in
order to accept touch inputs.
Added wintouchemu on game hook and TOUCHEVENTF_PRIMARY flag on
wintouchemu.
 
## Testing
The game boots into test menu, touch now works. (tested in I/O TEST ->
TOUCH PANEL CHECK with '-s' flag)
TDJ subscreen overlay and nostalgia seems works fine regardless added
TOUCHEVENTF_PRIMARY flag but more testing required.
2025-11-25 17:14:07 -08:00
bicarus-dev 0dad96b876 check for standalone cfg for fatal options 2025-11-23 09:36:38 -08:00
bicarus-dev def296095b reword log message to avoid confusion 2025-11-22 16:45:27 -08:00
bicarus-devandGitHub 750b1fea80 patches: disable patches tab for unity games (#425)
## Description of change
To redirect confused users why no one is developing a remote patch
server for PolCho

## Testing
checked spicecfg in working vs non-working games
2025-11-17 19:16:19 -08:00
aixxeandGitHub 4c87078807 Refactor IIDX camhook texture replacement (#424)
## Link to GitHub Issue, if one exists
Fixes #393 

## Description of change
- Fixes compatibility with IIDX 33
- Adjusts the pattern scan for "hook A" to be compatible with IIDX 27
- Refactors texture replacement to use Camera::CCameraManager2, removing
the need for a second hook

Related: https://github.com/aixxe/2dxcamhook/compare/0.4.0.0...0.5.0.0

## Testing
Booted with `-iidxcamhook` on IIDX 27, 28, 29, 30, 31, 32 & 33
Confirmed both cameras were visible in I/O test, music select settings &
gameplay
2025-11-17 01:21:39 -08:00
bicarus-devandGitHub 052698afa3 rawinput: use UTF-8 for device descriptor (#422)
## Link to GitHub Issue, if one exists
n/a

## Description of change
For HID device descriptor, use the "W" version of Windows API, and make
sure UTF-8 is being used throughout since that's the only format IMGUI
supports.

Before this change, non-Latin characters would have shown up as `HID ???
?????` for a mouse, for example.

Due to the way ImGui loads fonts, only the following languages are
supported:

* Simplified Chinese
* Cyrillic
* Japanese
* Korean
* Thai
* Vietnamese

## Testing
Tested various combination of Windows UI language and non-Unicode
language.
2025-11-16 00:08:30 -08:00
bicarus-devandGitHub 9e2b04d5db troubleshooter: update link for vcredist (#423)
## Description of change
RIP abbodi1406
2025-11-16 00:07:36 -08:00
bicarus-devandGitHub 5641e06a19 iidx: fix kana input broken in subscreen when system language is not Japanese (#421)
## Link to GitHub Issue, if one exists
user-reported

## Description of change
When the system non-Unicode language is not set to Japanese, TDJ
subscreen katakana keyboard was broken (weird behavior with characters
not erasing, ending with corrupt characters, etc)

To fix this, add a hook for `IsDBCSLeadByte`. `IsDBCSLeadByte` assumes
system ACP (which may not be SHIFT_JIS). Instead, redirect bm2dx.dll to
use `IsDBCSLeadByteEx` instead with SHIFT_JIS explicitly specified as a
parameter.

Presumably the game was using IsDBCSLeadByte to manage the keyboard
typing logic.... which is weird because SDVX doesn't do the same.

## Testing
Tested iidx 31 and tried to do searches.
2025-11-10 11:35:46 -08:00
bicarus-devandGitHub 88ec6dbcee launcher: hide pcbid when -cmdoverride specified (#420)
## Link to GitHub Issue, if one exists
Fixes #419 

## Description of change
-cmdoverride caused pcbid to be logged in plain text

## Testing
2025-11-07 14:20:37 -08:00
bicarus-devandGitHub 1b614c94a7 mfc: remove log spam (#418)
## Link to GitHub Issue, if one exists
n/a

## Description of change
Remove mutex lock/unlock log spam

Are they important? Idk

## Testing
2025-11-06 17:51:39 -08:00
bicarus-devandGitHub 37c5913f69 mfc: detect 2025 version (#416)
## Link to GitHub Issue, if one exists
n/a

## Description of change
They moved some DLLs around so MFC wasn't being auto-detected, and I/O
hooks were not being performed. Fix that.

## Testing
The game boots into test menu, some i/o works, touch doesn't, not
playable yet.
2025-11-05 19:23:10 -08:00
wrigglebugandGitHub 2eca668593 update readme.txt (#415)
please merge this important code
2025-11-02 23:29:48 -08:00
bicarus-devandGitHub aec1da9d16 troubleshooter: detect ea3_report_posev error (#413)
## Link to GitHub Issue, if one exists
#345 

## Description of change
When `ea3-pos: ea3_report_posev: no such node:` is detected, add a
deferred error message and guide user to the FAQ.

## Testing
Tested SDVX with the missing XML node.
2025-11-02 14:13:04 -08:00
sp2xdev 548b441f1f update build script 2025-11-01 17:33:40 -07:00
bicarus-devandGitHub 98bc285d98 logger: show message popup for log_fatal (#412)
## Link to GitHub Issue, if one exists
#410 

## Description of change
When raising fatal errors via `log_fatal`, also show a modal popup.

Update instances of `log_fatal` where the message was too long to fit.

## Testing
Tested various failure conditions.
2025-11-01 13:41:58 -07:00
bicarus-devandGitHub 553c180bee signal: force minimize widnows before showing crash handler dialog (#411)
## Link to GitHub Issue, if one exists
#410 

## Description of change
Forcibly minimize all windows before showing the crash dialog. Without
this, the dialog shows "behind" the game when launching multi-monitor
fullscreen games (TDJ, VM)

## Testing
Tested TDJ and VM in fullscreen.
2025-11-01 02:08:14 -07:00
bicarus-devandGitHub 537252440e signal: show Windows popup window on crash (#410)
## Link to GitHub Issue, if one exists
#345 

## Description of change
Show a MessageBox when signal detects a crash.

## Testing
Tested full screen and windowed games.
2025-10-31 20:54:56 -07:00
sp2xdev 13a0877d38 fix up deferred messages formatting 2025-10-28 16:49:14 -07:00
bicarus-devandGitHub 50691b79f1 troubleshooter: add cases for audio hook and dx9on12 (#408)
## Link to GitHub Issue, if one exists
#345

## Description of change
Add auto-troubleshooter messages for when audio hooks are disabled, and
when 9on12 is turned on by user.

## Testing
Manual verification
2025-10-28 16:29:49 -07:00
bicarus-devandGitHub ad229dabdb update build script (#407)
## Link to GitHub Issue, if one exists
n/a

## Description of change
Create a "full" version of the archive with api and extra binaries. The
normal archive remains in place with just src and stubs.
2025-10-27 23:00:32 -07:00
bicarus-devandGitHub bdff33a420 overlay: fix font loading (#406)
## Link to GitHub Issue, if one exists
n/a

## Description of change
Fix font loading - when the code was refactored in #286 I used the wrong
file extension for couple of them.
2025-10-20 00:53:28 -07:00
bicarus-devandGitHub 4b21821aa4 Introduce builds for Linux, split archive into release and extras (#405)
## Link to GitHub Issue, if one exists
n/a

## Description of change
Add a "Linux" variant of spicecfg/spice/spice64.exe - currently these
are the same as Windows binaries but built without dependency on
WinSCard, which makes it easier to run on WINE.

Split dist archive into two, move api and extras into the new extras
zip.

## Testing
I only checked to see that Linux variants run fine on Windows, and
checked with dumpbin to confirm that the dependency on winscard DLL is
removed. No official support will be provided for Linux builds.
2025-10-16 16:19:10 -07:00
bicarus-devandGitHub 6b47b5db50 iidx: set CONNECT_CAMERA env var even when iidx module is not enabled (#404)
## Link to GitHub Issue, if one exists
Fixes #403 

## Description of change
Set CONNECT_CAMERA env var even when iidx module is not enabled, so that
camhook can work without -iidx being set with real IO

## Testing
tested with emulated i/o and real i/o by user (see linked issue)
2025-10-15 13:50:12 -07:00
kazakhstan-silaandGitHub a10ca5236b add option to remove winscard dependency (#400)
## Link to GitHub Issue, if one exists
n/a

## Description of change
Add CMake option to disable SCard functionality. Needed by some builds
of WINE, where distributor opted to not enable scard functionality for
whatever reason

## Testing
Building with `cmake -DWITH_SCARD=OFF` does not link against winscard
Building without this option at all links against winscard
2025-10-13 14:13:51 -07:00
bicarus-devandGitHub be2dab9a1a Image duplication for screen resize (#401)
## Link to GitHub Issue, if one exists
#399 

## Description of change
Add image duplication option to screen resize, allowing overflow images
to wrap around the other side.

## Testing
Tested with DDR, IIDX (windowed and full screen TDJ), SDVX (orientation
swap)
2025-10-12 20:34:45 -07:00
AllanCatandGitHub 02711cdee1 Fixed an issue where ext (release_code) wasn't detected and replaced correctly when using user specified bootstrap.xml path. (#398)
## Link to GitHub Issue, if one exists
#0

## Description of change
The original launcher::detect_bootstrap_release_code() wasn't detecting
user specified bootstrap.xml path, and was causing issues for
patch_manager and other ext check spots prior to ea3 init (as ea3 does
proper path check itself).
I've added the user path check and proper xml parsing from
detect_gameversion().

## Testing
Tested with prop/bootstrap.xml presents.
Tested with prop/bootstrap.xml not presents and -b
/path/to/bootstrap.xml
2025-10-10 21:35:34 -07:00
sp2xdev c6b10402f4 remove changelog from binary 2025-10-06 19:24:36 -07:00
bicarus-devandGitHub 58ea6ce17e troubleshooter: add more detection (#397)
## Description of change
Detect and log troubleshooter messages for:

* (early) hook DLL load failure
* data_mods folder without ifs_hook.dll
* spice version (print out current version and add a link to github
download)

## Testing
2025-10-06 08:03:06 -07:00
bicarus-devandGitHub 8cc00c371a troubleshooter: address common issues in iidx and popn (#396)
## Description of change
iidx: 9on12 crashes iidx31+

popn: special message for createdevice failures (resolution issue)

## Testing
manually test both cases
2025-10-04 15:21:33 -07:00
bicarus-devandGitHub 8e2985d8d0 troubleshooter: properly fix deadlock (#395)
## Description of change
Finally figured out where the deadlock was coming from - it's because
the `dump_to_logger` was writing `SuperstepSound: Audio device is not
available` to the logger which calls back into the log hook, and then
emits another deferred message, trying to acquire the lock again on the
same stack.

## Testing
Tested the superstep sound failure case again.
2025-10-04 13:50:04 -07:00
bicarus-devandGitHub 8d3b15641f logger: more fixes to deferred logging (troubleshooter) (#394)
## Description of change
Various fixes to deferred logger -

* Rename to "auto troubleshooter"
* Fix potential deadlock
* Reword the error messages to fit in a narrower column

## Testing
tested all the error cases
2025-10-04 02:13:39 -07:00
bicarus-devandGitHub 033e9f926a logger: warn about log level being set too high (#392)
## Link to GitHub Issue, if one exists
n/a

## Description of change
If log level is anything but `misc` or `all`, complain using the
deferred logger.

## Testing
Tested legacy AVS (DDR X2) and newer games.
2025-10-03 19:16:59 -07:00
sp2xdev da58503b47 use lock_guard and not unique_lock unless unlocking is needed 2025-10-03 00:07:54 -07:00
bicarus-devandGitHub cffee2b7f8 logger: add mutex to deferred logger (#391)
## Link to GitHub Issue, if one exists
[#390 ](https://github.com/spice2x/spice2x.github.io/pull/390)

## Description of change
Just like the normal logger, deferred logging can be called from any
component at any time, so accessing the vector needs to be guarded.

## Testing
2025-10-02 23:38:11 -07:00
bicarus-devandGitHub 32801319fa Create a class for deferred error logging (#390)
## Link to GitHub Issue, if one exists
#388 

## Description of change
Instead of setting flags in `signal` and logging when we crash, create a
class for logging deferred semi-fatal messages, and dump them to the log
immediately before crashing or shutting down.

## Testing
Tested each failure case by injection.
2025-10-02 18:42:49 -07:00
s2andGitHub afbe013811 pcm: support 2024091200 version (#389)
## Link to GitHub Issue, if one exists
none

## Description of change
Supports PCM version 2024091200.

## Testing
1. After starting the PCM, touch card and auth.
2. Press the bill insert button.
3. Check account balance has increased.
2025-10-02 15:26:12 -07:00
sp2xdev 862e4717f6 point users to vcredist aio package 2025-09-30 22:29:09 -07:00
bicarus-devandGitHub d3fa27ae4a Detect D3D9 CreateDevice / CreateDeviceEx failures (#388)
## Link to GitHub Issue, if one exists
#345 

## Description of change
Show a block of warning text if the game crashes after CreateDevice(Ex)
fails.

## Testing
Tested by injecting invalid resolution into spicecfg.
2025-09-30 22:13:02 -07:00
b487cf2bcf Print missing DLLs on library load failure (#387)
## Description of change
When the game library fails to load, recursively iterate its import
table to narrow down the missing library.

If the dependency is a commonly missing one, give the user a hint on
where to find it.

## Testing
System with an AMD GPU and LDJ-010 IIDX:
```
[2025/10/01 00:32:41] M:dependencies: bm2dx.dll
[2025/10/01 00:32:41] M:dependencies:   |-- nvEncodeAPI64.dll
[2025/10/01 00:32:41] W:dependencies:   | |-- [!] NVIDIA Graphics Driver
[2025/10/01 00:32:41] W:dependencies:   | |--     For non-NVIDIA GPUs, copy the stub file from the spice2x release .zip.
[2025/10/01 00:32:41] F:libutils: DLL failed to load - this is a common error. Please carefully read ALL of the following steps for a fix:
```

Same system, but with SDVX:
```
[2025/10/01 00:33:53] M:dependencies: soundvoltex.dll
[2025/10/01 00:33:53] M:dependencies:   |-- nvcuda.dll
[2025/10/01 00:33:53] W:dependencies:   | |-- [!] NVIDIA Graphics Driver
[2025/10/01 00:33:53] W:dependencies:   | |--     For non-NVIDIA GPUs, copy the stub file from the spice2x release .zip.
[2025/10/01 00:33:53] M:dependencies:   |-- nvcuvid.dll
[2025/10/01 00:33:53] W:dependencies:   | |-- [!] NVIDIA Graphics Driver
[2025/10/01 00:33:53] W:dependencies:   | |--     For non-NVIDIA GPUs, copy the stub file from the spice2x release .zip.
[2025/10/01 00:33:53] F:libutils: DLL failed to load - this is a common error. Please carefully read ALL of the following steps for a fix:
```

Contrived example of a missing dependency of a dependency:
```
[2025/10/01 00:30:59] M:dependencies: bm2dx.dll
[2025/10/01 00:30:59] M:dependencies:   |-- libaio.dll
[2025/10/01 00:30:59] W:dependencies:   | |-- [!] The library could not be loaded. (126)
[2025/10/01 00:30:59] M:dependencies:   |-- libaio-iob.dll
[2025/10/01 00:30:59] M:dependencies:   | |-- libaio.dll
[2025/10/01 00:30:59] M:dependencies:   |-- libaio-iob2_video.dll
[2025/10/01 00:30:59] M:dependencies:   | |-- libaio.dll
[2025/10/01 00:30:59] M:dependencies:   | |-- libaio-iob.dll
[2025/10/01 00:30:59] F:libutils: DLL failed to load - this is a common error. Please carefully read ALL of the following steps for a fix:
```

Another example of a missing file, but with 32-bit IIDX 24:
```
[2025/10/01 00:42:13] M:dependencies: bm2dx.dll
[2025/10/01 00:42:13] M:dependencies:   |-- libafp-win32.dll
[2025/10/01 00:42:13] W:dependencies:   | |-- [!] The library could not be loaded. (126)
[2025/10/01 00:42:13] M:dependencies:   |-- libafputils-win32.dll
[2025/10/01 00:42:13] M:dependencies:   | |-- libafp-win32.dll
[2025/10/01 00:42:13] F:libutils: DLL failed to load - this is a common error. Please carefully read ALL of the following steps for a fix:
```

---------

Co-authored-by: aixxe <me@aixxe.net>
2025-09-30 22:12:08 -07:00
sp2xdev 0fc3aa5818 update changelog 2025-09-30 13:07:49 -07:00
sp2xdev 276874f882 update options 2025-09-29 19:28:08 -07:00
sp2xdev 1fdd270480 hide bio2 firmware update option 2025-09-29 19:03:13 -07:00
bicarus-devandGitHub 07cb6048a0 Add explanation for common exception codes (#386)
## Link to GitHub Issue, if one exists
#345 

## Description of change
Add a little warning message that shows when well-known exception codes
are encountered & caught by signal hooks.

## Testing
Tested by injecting bad instructions into the DLL.
2025-09-28 23:49:08 -07:00
bicarus-devandGitHub 61d79f76b2 ea3: remove verbose debug logs by default (#385)
## Link to GitHub Issue, if one exists
n/a

## Description of change
Recently distributed iidx data has this enabled in ea3-config.xml by
default, exposing people's PCBID in the logs.

Find the node in the XML and remove it on boot, unless user overrides it
with a new option.

## Testing
Tested using asphyxia.
2025-09-28 20:31:48 -07:00
sp2xdev 7335d07e30 update changelog 2025-09-27 18:38:57 -07:00
bicarus-devandGitHub 9f99b71112 avs: when nvram/raw folder creation fails, show a warning message when the game eventually crashes (#383)
## Link to GitHub Issue, if one exists
#345 

## Description of change
When the directory specified in the mounttable (in avs-config.xml) lead
to bad path, remember this and show a warning message block before the
game crashes.

## Testing
Tested with simulated bad mounttable set up.
2025-09-27 18:16:20 -07:00
sp2xdev 5c3921a99c fix up error messages 2025-09-27 14:31:29 -07:00
sp2xdev 9fc397b8bc update loadlibrary failure message, again 2025-09-27 02:16:28 -07:00
bicarus-devandGitHub ddc8061268 iidx: apply signature patch for ASIO compatibility fix in iidx32 (#381)
## Link to GitHub Issue, if one exists
n/a

## Description of change
Apply this fix automatically when needed -
https://github.com/two-torial/sp2xpatcher/pull/33

## Testing
Tested iidx31 to see that the patch does not apply
Checked -audiohookdisable to see that the logic gets skipped
Tested 2025041500 and 2025082500.
2025-09-26 01:26:49 -07:00
sp2xdev c0698a3872 update changelog 2025-09-21 19:44:47 -07:00
kazakhstan-silaandGitHub 1ed3521091 patcher: add gitadora boot.dll to extra DLL list (#379)
## Link to GitHub Issue, if one exists
n/a

## Description of change
Allow importing patches for `boot.dll`

## Testing
Observed patch for the new DLL being attempted to be imported in the
logs.
2025-09-21 12:51:04 -07:00
bicarus-devandGitHub 0dda920448 iidx, sdvx: show error text when subscreen fails to render (#378)
## Link to GitHub Issue, if one exists
#345 

## Description of change
For IIDX / SDVX, when the subscreen surface failed to capture, display
an error message, suggesting the user to patch the game as needed.

Prior to this change, the subscreen overlay just showed an invisible
window if this happened.

## Testing
Tested IIDX windowed / full screen, and voltex full screen.
2025-09-21 00:20:41 -07:00
bicarus-devandGitHub 5f7580b69e pc: add polaris chord button lights (#377)
## Link to GitHub Issue, if one exists
n/a

## Description of change
Add lights output for the buttons lamp array. Only implemented the top
row of lights and not all of them, this is good enough.

## Testing
Tested with a Nostroller and also spiceremote.py.
2025-09-20 16:37:39 -07:00
sp2xdev 3ef1b3ecaa update changelog 2025-09-20 04:31:11 -07:00
bicarus-devandGitHub 931811da14 iidx,sdvx: detect SuperstepSound init failures and log a message upon crash (#376)
## Link to GitHub Issue, if one exists
#345 

## Description of change
If we see SuperstepSound failure during boot, remember it, and when the
game eventually crashes, log a series of warning messages to surface why
the game likely failed to launch.

## Testing
Tested both iidx and sdvx with an audio device set to not allow
exclusive mode.
2025-09-20 04:17:40 -07:00
bicarus-devandGitHub 7624ded512 pc: add option to translate absolute analog values to fader input (#375)
## Link to GitHub Issue, if one exists
n/a

## Description of change
Add a new option that allows absolute position analog input, such as
SDVX controller knobs and IIDX turntables, to be bound as input for
Polaris Chord faders.

In practice this means turning a knob counter-clockwise results in fader
being held left (relative to velocity), and vice versa.

## Testing
Tested using FauceTwo and a mouse.
2025-09-20 03:40:18 -07:00
sp2xdev a5252939dd update iidx audio patch signature 2025-09-20 03:40:04 -07:00
GEEKiDoSandGitHub 808ac0c557 Fix msvc compiler warnings (#374)
To be noticed I edited thirdparty header for fmt library (#373), idk if
it is acceptable
2025-09-18 21:57:38 -07:00
sp2xdev 4e138b244e add dsound.dll to custom DLL detection 2025-09-18 21:49:43 -07:00
sp2xdev 3fcd1bdbc9 fix option strings for force load 2025-09-18 15:59:06 -07:00
sp2xdev f857e63d5d update changelog 2025-09-18 15:27:22 -07:00
GEEKiDoSandGitHub b462c838f6 Fix broken UI in windowed mode for Polaris Chord (#372) 2025-09-18 09:44:23 -07:00
sp2xdev 4d658bf273 Intel SDE is not an override 2025-09-18 02:35:32 -07:00
sp2xdev 67721c416b update UI strings 2025-09-18 02:33:18 -07:00
bicarus-devandGitHub 0b5f7787be launcher: show a giant warning when the user specifies -exec option (#371)
## Link to GitHub Issue, if one exists
#345

## Description of change
If `-exec` is specified, log a giant warning.

Also, fix some compiler warnings in MFG.

## Testing
Confirmed message being logged in log.txt.
2025-09-17 17:03:57 -07:00
sp2xdev a554c8f923 update changelog 2025-09-17 10:40:21 -07:00
bicarus-devandGitHub 6592191624 pc: fix certain controller bindings not working (#368)
## Link to GitHub Issue, if one exists
Fixes #364 

## Description of change
The game attempts to hook raw input, but raw input API can only be used
by one window per process. Hook `RegisterRawInputDevices` and prevent
the game from registration for raw input notifications.

## Testing
Tested that HID keyboard works with `Bind` option.
2025-09-17 03:55:02 -07:00
bicarus-devandGitHub 164592b16d pc: add language hook (#367)
## Link to GitHub Issue, if one exists
Fixes #362 

## Description of change
Add a hook for `GetLocaleInfoEx` and return `ja-JP` to fix the
"translation not found" issue in XIF.

## Testing
Tested with/without Disable ACP option.
2025-09-17 01:53:50 -07:00
bicarus-devandGitHub 81f879e7ff pc: fix fader emulation (#366)
## Link to GitHub Issue, if one exists
Fixes #365 

## Description of change
Always check both analog and digital input when calculating fader
offset.

## Testing
Tested with keyboard and xbox one controller.
2025-09-16 19:36:22 -07:00
sp2xdev 1af8b3de61 update discord rich presence 2025-09-15 21:54:59 -07:00
sp2xdev c33fef606f update change log 2025-09-15 21:39:02 -07:00
oleg238948234andGitHub a9fc6ef1b2 Add Polaris Chord Support (#361)
## Link to GitHub Issue, if one exists
n/a

## Description of change
Adds support for Polaris Chord

## Testing
Tested with XIF:J:A:A:2024122300. IO/ICCA works, game successfully
connects to server and is playable.
2025-09-15 21:08:17 -07:00
bicarus-devandGitHub d530bfcd65 overlay: tweak imgui colors (#359)
## Link to GitHub Issue, if one exists
n/a

## Description of change
Change the dark brown colors used in widget backgrounds to dark gray.

Decrease the in-game overlay transparency (more opaque): 80% -> 96%.

## Testing
Tested cfg and in-game overlay.
2025-09-14 02:29:14 -07:00
sp2xdev c96f4330d2 Merge branch 'main' of https://github.com/spice2x/spice2x.github.io 2025-08-21 08:29:33 -07:00
sp2xdev 26a2a0f481 update changelog 2025-08-21 08:29:27 -07:00
bicarus-devandGitHub 11b9790e6c cfg: rearrange options (#357)
## Link to GitHub Issue, if one exists
n/a

## Description of change
In an attempt to make the Options tab smaller, move out more options to
advanced / development tabs.

## Testing
n/a
2025-08-19 02:41:56 -07:00
bicarus-devandGitHub 1dfcf8c087 ddr: check for common DLL errors (#355)
## Link to GitHub Issue, if one exists
#345 

## Description of change
Check if the path to codecs directory contains non-ASCII characters, and
display a giant warning. This is to help with troubleshooting, since
registration of xactengine silently fails (creates a bad registry entry)
if the path contains non-ASCII chars.

Log the size of codec DLLs, and log any failures from `regsvr32`.

Detect the case where user specified `-exec gamemdx.dll` for DDR which
seems to be a common pitfall. This doesn't work because gamemdx.dll does
not have a DLL entry, but for some reason people are really tempted to
do this.

## Testing
Validated the error cases.
2025-08-18 00:00:38 -07:00
bicarus-devandGitHub 5c7ad0d51e ea3: supply default pcbid if not present (#356)
## Link to GitHub Issue, if one exists
#345 

## Description of change
If ea3 xml is missing pcbid node, AND there is no pcbid override, supply
a default value. Useful for quickly getting offline clients up &
running.

## Testing
Tested with/without pcbid override, with/without pcbid node in xml.
2025-08-17 23:52:15 -07:00
sp2xdev 587ae86341 update changelog 2025-08-16 19:22:28 -07:00
bicarus-devandGitHub 926f2ef2a0 ddr: enclose file path in quotes & use wchar when registering codecs (#354)
## Link to GitHub Issue, if one exists
#345 

## Description of change
Account for the path to codec DLL having spaces in it and use wide
chars.

## Testing
Testing 32 and 64 bits.
2025-08-16 19:18:15 -07:00
sp2xdev d262e02f8c update changelog 2025-08-11 00:20:04 -07:00
bicarus-devandGitHub f6b63473a0 ddr: automatically register codecs on launch (#351)
## Link to GitHub Issue, if one exists
#345 

## Description of change
On launch of spice.exe / spice64.exe, call `regsvr32` on known codecs in
the `com` directory.

Add an option to skip this (`-ddrnocodec`) as a chicken bit.

## Testing
Tested 32 and 64 bit DDR.
2025-08-09 20:53:09 -07:00
bicarus-devandGitHub 1ad45edd6e sdvx: remove -sdvxdisablecams, simplify camera hook (#350)
## Link to GitHub Issue, if one exists
#345

## Description of change
Simplify camera hook in SDVX5/6. Camera hook will always be installed
and return 0 cameras.

Deprecate `-sdvxdisablecams` option as it is no longer needed as a
result of this change.

## Testing
Tested SDVX5 final, SDVX6 year 1, and a recent SDVX6 build.
2025-08-09 03:01:10 -07:00
bicarus-devandGitHub e79de3b117 Dump button and analog bindings to log on launch (#349)
## Link to GitHub Issue, if one exists
#345 

## Description of change
On launch of spice/spice64, dump bindings in Buttons and Analog tab

## Testing
Tested on 32 bit and 64 bit
2025-08-04 21:40:09 -07:00
sp2xdev f783a5a1cd clarify wording for -loglevel 2025-08-02 14:19:56 -07:00
sp2xdev 7848d5c237 detect custom msvcr100.dll 2025-07-25 18:36:55 -07:00
EmmaandGitHub cec735a81b Check for IFS LayeredFS after loading hooks (#342)
## Link to GitHub Issue, if one exists
N/A

## Description of change
Minor fix for a warning in the log. Moves the check for IFS LayeredFS
_after_ loading hooks. Also checks for the module with `GetModuleHandle`
instead, which should cover all paths as long as `ifs_hook.dll` hasn't
been renamed. 😅

## Testing

Before:
```
[2025/07/19 15:59:56] W:launcher: data_mods directory was found, but ifs_hook.dll is not present; your mods will not load
[2025/07/19 15:59:56] W:launcher: to fix this, download ifs_layeredfs and add it as a DLL hook (-k)
[2025/07/19 15:59:56] W:launcher: https://github.com/mon/ifs_layeredfs
[2025/07/19 15:59:56] I:launcher: msvcrt!__argc value before: 0
[2025/07/19 15:59:56] I:launcher: msvcrt!__argc value after: 57
[2025/07/19 15:59:56] I:launcher: loading hook DLL C:\Users\Emma\AppData\Local\Programs\SpiceTools\hooks\64\ifs_hook.dll
[2025/07/19 15:59:56] I:libraryhook: LibraryHook Attach
```

After changes - no warning observed:
```
[2025/07/19 15:59:08] I:launcher: msvcrt!__argc value before: 0
[2025/07/19 15:59:08] I:launcher: msvcrt!__argc value after: 57
[2025/07/19 15:59:08] I:launcher: loading hook DLL C:\Users\Emma\AppData\Local\Programs\SpiceTools\hooks\64\ifs_hook.dll
[2025/07/19 15:59:08] I:libraryhook: LibraryHook Attach
```
2025-07-19 16:17:07 -07:00
sp2xdev 753702e32e Fix build break due to upstream header file changes 2025-07-19 15:36:21 -07:00
sp2xdevandGitHub 7bd7503951 Update README.md 2025-06-25 01:03:39 -07:00
sp2xdev d0c38956bf update changelog 2025-06-02 17:51:04 -07:00
bicarus-devandGitHub 77bca2baee patcher: add gitadora libshare-pj.dll to extra DLL list (#335)
## Link to GitHub Issue, if one exists
n/a

## Description of change
Allow importing patches for `libshare-pj.dll`

## Testing
Observed patch for the new DLL being attempted to be imported in the
logs.
2025-06-02 02:56:04 -07:00
bicarus-devandGitHub 46e8c8a5a5 acio: detect crash during acio init due to RTSS/Afterburner and complain (#334)
## Link to GitHub Issue, if one exists
n/a

## Description of change
When an exception is caught during ACIO hook init, print a special
message to suggest the user to disable RTSS and MSI Afterburner, since
they are known to cause issues.

## Testing
Tried with RTSS enabled with High detection and with it disabled. Also
tried to crash the game a different way and check that the message is
not printed and the callstack is shown propeerly.
2025-06-01 14:52:13 -07:00
sp2xdev 4d58e5d080 Merge branch 'main' of https://github.com/spice2x/spice2x.github.io 2025-05-30 22:21:11 -07:00
sp2xdev 168084d672 update changelog 2025-05-30 22:21:03 -07:00
bicarus-devandGitHub 740ed90d95 iidx: fix windowed subscreen mouse clicks being ignored when overlay is active (#333)
## Link to GitHub Issue, if one exists
n/a

## Description of change
Fix an issue when clicking on the TDJ windowed subscreen with overlay
active will occasionally drop all future mouse clicks, until the overlay
is completely hidden.

## Testing
Tested TDJ windowed subscreen, with poke, with auto-pin, and with
spicecompanion.

SDVX: this was never an issue for UFC mode, but this change doesn't make
any functional difference either.
2025-05-30 22:20:03 -07:00
sp2xdev 61c17f15b4 update changelog 2025-05-30 20:19:03 -07:00
bicarus-devandGitHub 99035f312f cfg: reorganize menu bar (#330)
## Link to GitHub Issue, if one exists
n/a

## Description of change
Add `Help` menu. 

Move Licenses and About dialog under Help.

Add links to the wiki.

Make game selection drop-down slightly wider to make better use of space
that has been freed up.

## Testing
tested in cfg (with/without game), and in-game overlay
2025-05-25 01:32:59 -07:00
bicarus-devandGitHub aafc916bb6 launcher: detect custom nvidia DLLs (#329)
## Link to GitHub Issue, if one exists
n/a

## Description of change
Complain if nvcuda.dll, nvcuvid.dll, or nvEncodeAPI64.dll are found in
modules or in root directory. This would complain for spice-provided
stubs as well.

## Testing
2025-05-24 12:49:58 -07:00
bicarus-devandGitHub c5eb1e999b launcher: option to make command line args take precedence (#327)
## Link to GitHub Issue, if one exists
Fixes #190 

## Description of change
When `-cmdoverride` is specified, any command line arguments are taken
first before merging with what is in the config file.

## Testing
Still testing...
2025-05-15 23:26:38 -07:00
ASleepyCatandGitHub 7c15452c1e Add support for outputting IIDX tape LED data via spiceapi (#315)
## Link to GitHub Issue, if one exists
#41 (partially)

## Description of change
This PR adds support for outputting IIDX tape LED data via spiceapi
(`iidx_tapeled_get()`). It returns the data as a `dict`, with the key
being the LED name and the value being the list of RGB values. You can
optionally pass in a variadic list of names to filter the returned data.

The new API function is only implemented in Python since I'm not able to
test the wrappers in other languages.

An example script using the new API is available in this Beef Board PR:
https://github.com/HWXLR8/beef-board/pull/133.

Demo use case: https://streamable.com/pq5e1e

## Testing
Tested in IIDX 32 with custom Beef Board firmware along with a Python
script to read and send the tape LED data over USB.
2025-05-12 21:27:20 -07:00
sp2xdev 4bc849ccdb update changelog 2025-05-09 14:26:51 -07:00
bicarus-devandGitHub 58481a035b printer: fix orientation of resulting image (#324)
## Link to GitHub Issue, if one exists
n/a

## Description of change
Rotate the printer output so that it's the correct orientation
(portrait, not landscape).

## Testing
Tested via test image, and also in-game, for LP and SDVX.
2025-05-09 09:48:59 -07:00
bicarus-devandGitHub 66b3de8e5c loveplus: add printer options (#325)
## Link to GitHub Issue, if one exists
n/a

## Description of change
Add the same options as SDVX printer.

## Testing
Tested all options with LP.
2025-05-09 09:48:00 -07:00
bicarus-devandGitHub 257afe0cd3 loveplus: remove log spam (#326)
## Link to GitHub Issue, if one exists
n/a

## Description of change
Remove spammy logging in LP

## Testing
It works 👍
2025-05-09 09:47:38 -07:00
bicarus-devandGitHub aafea3b1c1 loveplus: stub impl of cpusbxpkm.dll (printer) (#319)
## Link to GitHub Issue, if one exists
n/a

## Description of change
Add a stub for `cpusbxpkm.dll`. The stub doesn't work, it's only enough
to allow the game to load; but that is irrelevant since spice hooks the
printer at runtime.

## Testing
Observed the game boot, and was able to print out the test image from
test menu. Will do more testing on a real server.
2025-05-08 23:03:26 -07:00
bicarus-devandGitHub 346a159157 loveplus: full screen mode (#323)
## Link to GitHub Issue, if one exists
Fixes #322 

## Description of change
Ensure game launches in full screen when windowed mode setting is off.
Before this PR, the game always launched in windowed mode.

## Testing
Seems to run fine in fullscreen with the monitor in portrait mode.
It does seem to run at even lower framerate (around 24fps, vs. 30fps in
windowed) but it might be expected.
Some weirdness with the mouse cursor when the overlay is active (two
cursors).
2025-05-08 17:25:15 -07:00
bicarus-devandGitHub 54028d8cbb loveplus: improve window searching for touch hook (#321)
## Link to GitHub Issue, if one exists
Fixes #320 

## Description of change
Replacing the existing logic that searches for game window to hook touch
on.

Eventually, we should fix all the other games that hook touch by
matching on window title with a call to `FindProcessWindowBeginsWith`.

## Testing
Tested with notepad open that was also titled `LovePlusAC`. Game hooked
correctly when the game was in focus during boot, and also when notepad
was in focus.
2025-05-07 21:41:48 -07:00
bicarus-devandGitHub 621b702ed4 loveplus: enable cam access (#318)
## Link to GitHub Issue, if one exists
#316 
(this does not fix the issue!)

## Description of change
Add an option to omit `-noCamera` option. Doing so allows the game to
boot and search for a compatible camera using KS API, but will likely
fail since we don't know what cameras are compatible. You end up with a
camera error during boot, which can be ignored by flipping a switch in
the test menu to disable the camera.

No hooks are currently done to make the camera work!

## Testing
Tested to see that turning this on triggers the camera error. No known
camera at the moment.
2025-05-07 20:43:16 -07:00
bicarus-devandGitHub 26ea8a1b92 api: check for window focus when accepting input (#317)
## Link to GitHub Issue, if one exists
Fixes #139 

## Description of change
Add an option for detecting window focus when accepting input.

The new default will be to have the following:
 * naive bindings will check for window focus
* rawinput binds are not affected, will continue to be accepted
regardless of focus

Other options (always / never choices) can be picked by the user to
tweak the behavior if needed.

Clean up usage of `0xff` constant to represent invalid naive vkey.

## Testing
Still in progress, especially in regards to performance.

On a i7-9700 system, 10000 calls to `superexit::has_focus()` takes
250-350 microseconds, so this should not be a big deal at all.
2025-05-07 13:22:29 -07:00
bicarus-devandGitHub 6e7bf99af8 util: print dimensions and refresh rate of monitors (#314)
## Link to GitHub Issue, if one exists
n/a

## Description of change
Dump monitor resolution and refresh rate to the log on launch.

## Testing
Tested with two monitors.
2025-05-05 16:48:55 -07:00
bicarus-devandGitHub df9d9d8673 api: quality options for screen mirroring (#313)
## Link to GitHub Issue, if one exists
n/a

## Description of change
Allow the server to override client's quality options for screen
mirroring.

On Windows / Android versions (flutter-based) clients, this is already
in the options of those apps. However, the iOS version lacks the quality
options.

## Testing
Tested with iOS and Windows clients.
2025-05-04 20:10:56 -07:00
sp2xdev b3cee1dfcc fix build 2025-05-04 15:53:46 -07:00
bicarus-devandGitHub f40c382903 net: mark netadapter IP as sensitive option (#312)
## Link to GitHub Issue, if one exists
n/a

## Description of change
Mark the option as "sensitive" which censors the option value (adapter's
IP address)
Remove the log entry from spice

This isn't perfect because most of the games like to log the IP address
and subnet anyway, but at least spice itself will stop doing it.

## Testing
Tested 64 bit LDJ with parameter set.
2025-05-04 13:20:37 -07:00
sp2xdevandGitHub e4cfb808bf Update CONTRIBUTING.md 2025-05-04 00:38:10 -07:00
sp2xdevandGitHub 61d14a16e8 Update CONTRIBUTING.md 2025-05-04 00:33:43 -07:00
bicarus-devandGitHub 521e87ea41 patchmanager: always log patch identifier of main DLL (#311)
## Link to GitHub Issue, if one exists
n/a

## Description of change
Always log the DLL identifier string in patch manager, even if there are
no patches.

## Testing
Tested 32 / 64 bits.
2025-05-04 00:23:51 -07:00
bicarus-devandGitHub 2b735ffd55 launcher: check for custom d3d9.dll in user's game directory (#309)
## Link to GitHub Issue, if one exists
n/a

## Description of change
Log a message if d3d9.dll (and friends) are found in the game root or
the modules directory.

This is to help with troubleshooting in cases where the user is unaware
that d3d9 is being used to inject other DLLs (e.g., ifs_layeredfs), or
when DX9 reimplementation is causing issues / crashes (e.g., dxvk).

## Testing
Observed the new messages being logged in both modules / root directory.
2025-05-02 01:51:23 -07:00
bicarus-devandGitHub 4f72b0a98b graphics: more window options for windowed subscreen (#308)
## Link to GitHub Issue, if one exists
Fixes #307 

## Description of change
Add an option for making TDJ/UFC subscreen window borderless in windowed
mode

Add another option for keeping the subscreen "always on top".

Add size / position arguments to SDVX windowed subscreen, exactly the
same as what is allowed for IIDX.

## Testing
Seems to work....
2025-04-27 21:20:55 -07:00
sp2xdev 474f5f5203 fatal error for sdvxlandscape 2025-04-25 15:53:58 -07:00
sp2xdev 205cf29aff release build script and update changelog 2025-04-24 17:21:34 -07:00
bicarus-devandGitHub bf9074a76e launcher: complain when duplicate DLLs are detected (#306)
## Link to GitHub Issue, if one exists
#304 

## Description of change
Complain loudly about DLL conflicts. Show warnings for each DLL that
exists in both modules directory and the root.

This is not based on LoadLibrary hooks, just enumerating DLLs via
filesystem access.

Deliberately not using `log_fatal` since these are not necessarily going
to crash the game. Some people just love to have messy installs and if
the game booted fine before, it should continue to boot.

## Testing
Tested with/without `-modules`, with/without duplicate DLLs.
2025-04-24 10:35:34 -07:00
bicarus-devandGitHub 44e1edb50c avs: make max path check stricter (#305)
## Link to GitHub Issue, if one exists
n/a

## Description of change
The check to warn the user about potential MAX_PATH violation should be
stricter. Saw a user with 150 characters run into a crash during boot
for SDVX3.

## Testing
Tested paths under 130 / over 130, in both 32/64 bits.
2025-04-24 10:18:09 -07:00
bicarus-devandGitHub 41d0dce6e9 cfg: use %appdata%\spice2x for new config files (#300)
## Link to GitHub Issue, if one exists
n/a

## Description of change

For JSON config files of the following features:

* patch manager
* screen resize
* IIDX camera hook
* card manager

when saving a new file, store them in %appdata%\spice2x instead of
%appdata%.

On load:
1. If the JSON file exists in %appdata%\spice2x, use that (new path)
1. If the JSON file exists in %appdata%, continue to use that (legacy
path)

It's common for people to have mixed versions of spicetools/spice2x so
we'll continue to read from the %appdata% root if the files are there,
but with a preference for the new path. We will not forcibly move files.

spicetools.xml will continue to live in the %appdata% root. Moving this
will confuse a lot of people, so I'm avoiding this.

Also, this fixes `-patchcfgpath` and `-resizecfgpath` to create
directories as needed (previously the parent directory must have existed
first)

## Testing
Tested -

* existing config files are continued to be read from %appdata%
* new files get created in %appdata%\spice2x\... (both in spicecfg and
in overlay)
* can provide custom path for `-patchcfgpath` `-resizecfgpath` and
observe directories + file created in custom path, try absolute or local
relative paths
2025-04-22 00:35:10 -07:00
bicarus-devandGitHub 598422b701 build: large address aware variant of 32-bit spice.exe (#298)
## Link to GitHub Issue, if one exists
Fixes #280

## Description of change
Introduce a variant of 32bit spice.exe that is large address aware.

## Compiling
👍 

## Testing
Minimal testing has been done to see if the LAA variant can boot
existing 32 bit games.
2025-04-22 00:21:45 -07:00
bicarus-devandGitHub b58a1ccfcc sdvx: disable sdvx landscape option (#303)
## Link to GitHub Issue, if one exists
Fixes #277 

## Description of change
To avoid confusion, temporarily remove `-sdvxlandscape` because of the
issue described in the bug above.

`forceresswap` is still kept, so there is still a way to do exactly what
`-sdvxlandscape` used to do, if someone really wants it.

## Testing
Checked that `-sdvxlandscape` is ignored, and that `-sdvxlandscape`
continues to be honored.
2025-04-19 20:44:24 -07:00
ASleepyCatandGitHub 60efd643d1 Add ccache support for faster incremental builds (#301)
## Description of change
Adds `ccache` support for local compiling and Github Actions
environments. This significantly speeds up incremental builds, but cold
starts will take a little longer to compile.

The build script now has an `-i` flag to ignore the build cache if
needed. You can also manually delete the `.ccache` folder to force a
fresh build too.

For the GA workflow, it uses the
[`ccache-action`](https://github.com/hendrikmuhs/ccache-action) action
to manage and store the cached build objects. This cache should be
accessible to all branches.

I also removed the `--no-cache` flag when building the
`spicetools/spice` image since Docker will automatically invalidate
`COPY` layers if it detects the copied files have changed.

### Compile times
Compiled with a 7800X3D, 32GB 6000MT/s RAM.
| `main` | `ccache` (fresh) | `ccache` (cached) | `ccache` (ignore
cache) |
|--------|--------|--------|--------|
| 2m46.047s | 3m8.886s | 0m32.638s | 2m45.148s |

## Testing
Tested incremental builds by using another branch that's rebased on top
of this.
2025-04-19 20:43:10 -07:00
sp2xdevandGitHub 0596eeb6fe Update pull_request_template.md 2025-04-19 14:24:21 -07:00
ASleepyCatandGitHub 3d5c64feb7 Fix button HID string ranges not being retrieved correctly (#299)
## Description of change
Currently, retrieving HID strings using string index minimum/maximum
ranges for buttons is broken as it uses the same index for each usage
cap.

Now it properly ranges over the minimum/maximum ranges.

Before:

![image](https://github.com/user-attachments/assets/da8d0162-07d9-4027-9933-2348fbf43a27)

After:

![image](https://github.com/user-attachments/assets/c00f81fe-d9d4-43f8-9bdd-d570ef298479)


## Compiling
Check CI.

## Testing
Tested with Beef Board firmware using string index ranges in the USB HID
descriptor.
2025-04-18 01:23:00 -07:00
sp2xdev 610984f40e update changelog 2025-04-07 17:04:36 -07:00
sp2xdev 1e271b7352 Merge branch 'main' of https://github.com/spice2x/spice2x.github.io 2025-04-07 17:04:14 -07:00
bicarus-devandGitHub 07c78caf01 build: add git commit hash to produced artifact (#297)
## Link to GitHub Issue, if one exists
n/a

## Description of change
Previously, the artifact produced by the CI action was `spice2x.zip`.

With this change, it will be `spice2x-ci-1234567.zip` where 1234567 is
the first 7 chars of the commit hash.

Note that for PRs, github produces a merge commit before queueing the
action, so the commit hash won't match up with the HEAD of a fork. The
goal is to just generate unique-ish filenames so this is not a huge
deal.

## Compiling
## Testing

Action has been run in the fork.
2025-04-07 15:56:46 -07:00
sp2xdev d153209d8a update changelog 2025-04-07 02:14:51 -07:00
bicarus-devandGitHub 48186245fe overlay: update UI for card manager (#294)
## Link to GitHub Issue, if one exists
n/a

## Description of change
Change the usage pattern for Card Manager UI.

### Previous behavior

Previously, it was a list of cards that you select, and click a button
to insert as P1 or P2.

This is fine, but users got confused when they pressed `Insert Card`
overlay key and got a different card inserted, or when auto-insert
didn't work as expected.

Additionally, if you play a game without continue (IIDX/DDR for
example), then it's cumbersome to bring up the overlay and click on
insert card every time.

### New behavior

Insert Card overlay now lets you pick a card and *slot* it into P1 or P2
side.

From that point on, the slotted card number is used as the override,
replacing what was previously passed to `-card0` or `-card1`. This means
that `Insert Card` button and auto-insert will pick up the new card and
use that instead.

## Compiling
👍 

## Testing
Tested 1p-only game (popn) and 2p games (DDR, IIDX).
2025-04-07 01:49:07 -07:00
bicarus-devandGitHub f7c274591a museca: add Start and 10KEY lights (#296)
## Link to GitHub Issue, if one exists
Fixes #295 

## Description of change
Add missing `Start` and `10KEY` lights output, as see in the test menu.

## Compiling
👍 

## Testing
Seems fine in museca test mode.
2025-04-06 19:25:39 -07:00
ASleepyCatandGitHub d1a20e58b6 Add CI and improve build system (#293)
## Link to GitHub Issue, if one exists
Fixes #292

## Description of change
This PR adds CI with Github Actions. Runs on every push and pull
request. If a new commit is pushed, any previous runs are cancelled for
that branch.

In total, these optimisations (on a 7800X3D with 32GB 6000MT/s RAM and
an NVMe Gen4 SSD):
* Reduces `spicetools/deps` image size by around 200 MB, and build time
by almost 50%
* Reduces compilation time by around 9%

Changes:
* Pull archlinux image from GHCR so that we don't run into pull limits
from docker.io
* Use `base-devel` archlinux image
* Reduces image build time from 141 seconds to 109 seconds on my machine
  * Clean up unneeded packages
* Small reduction in `spicetools/deps` image size (4.35 GB vs 4.14 GB)
* Use precompiled `yay` binary
* Reduces image build time from 163 seconds to 141 seconds on my machine
* Use `nproc` to get the number of cores instead of parsing
`/proc/cpuinfo` with `awk`
* Use `ninja` to compile
  * Speeds up compile times a little, around 13 seconds on my machine
* Don't run `docker run` in interactive TTY in `build_docker.sh`
  * Github Actions disallows interactive TTYs
  * Still allowed for the batch script
* `time` the compilation process
* Enable BuildKit in the bash script
  * Required for build contexts
* Don't use `USER` instruction
  * Breaks Github Actions, doesn't seem to affect build?
* More info:
https://docs.github.com/en/actions/sharing-automations/creating-actions/dockerfile-support-for-github-actions#user
  * `USER` also seems to cause issues on a mounted ReFS image
* Add executable bits to bash scripts

Future ideas:
* Combine `external/Dockerfile` and `./Dockerfile` into one to
deduplicate images and save disk space
* Fix the weird `/src/src` directory structure in the Dockerfile and
build scripts
* Investigate CMake and ninja optimisations?

## Compiling
Check CI status on my fork.

## Testing
Tested with docker on Windows. Also play tested a bit to make sure the
Github Actions artifact works.
2025-04-06 00:29:02 -07:00
sp2xdev 1e02c31524 update changelog 2025-04-04 22:13:01 -07:00
bicarus-devandGitHub 66fab48a5b options: update monitor option text (#291)
## Link to GitHub Issue, if one exists
n/a

## Description of change
Update tooltip for `-monitor` option. Move to `Graphics (Full Screen)`
to reduce clutter.

## Compilin
👍 

## Testing
n/a
2025-04-04 22:09:21 -07:00
bicarus-devandGitHub ae06652148 touch: avoid calling is_touch_available too early (#289)
## Link to GitHub Issue, if one exists
Fixes #120 

## Description of change
Don't call `is_touch_available` in launcher before RawInput stack is
initialized. This causes us to incorrectly fall back to the Win7/Win8
touch handler.

The desired behavior is to use rawinput touch by default for all games,
unless overridden by the user via `-wintouch`.

Add a bunch of logging so we can spot who's calling
`is_touch_available`.

As a side effect, a handful of games that previously used Win7/8 touch
will now use RawInput touch instead. If this causes any issues, they can
turn `-wintouch` on to force Win7/8 touch logic again.

## Compiling
👍 

## Testing
Test:

* RB
* -wintouch
* TDJ, UFC subscreen and native touch
* overlay, especially in multi-mon
2025-04-04 21:31:18 -07:00
sp2xdev bd6c8d3f3c add gitignore to root 2025-04-04 17:51:40 -07:00
bicarus-devandGitHub e88abec2a6 overlay: remove navigator input (#288)
## Link to GitHub Issue, if one exists
n/a

## Description of change
No one uses this, remove them to reduce clutter. This is in preparation
to migrate from deprecated ImGui IO model to new one, and this is part
of that.

## Compiling
👍 

## Testing
2025-04-03 21:16:48 -07:00
bicarus-devandGitHub 6de8b529d2 imgui: update to v1.91.4, make various fixes to overlay (#286)
## Link to GitHub Issue, if one exists
n/a

## Description of change
Update ImGui to `v1.91.4`. We can't update to `v1.91.5` or newer just
yet because impl_spice depends on the old IO model of ImGui which got
removed in `v1.91.5`.

We also can't update the DX9 renderer right now, because at some point
overlay breaks in TDJ (and only in TDJ...)

Enable the new debug logging functionality in ImGui. Add a button in
overlay to toggle debug log window.

Fix some minor bugs spotted by the new ImGui debug log: mismatched API
calls, font loading issues.

## Compiling
👍 

## Testing
In progress.
2025-04-03 19:14:48 -07:00
bicarus-devandGitHub 7301cdaa6c imgui: reorganize imgui library files to mirror what's in the imgui releases (#285)
## Link to GitHub Issue, if one exists
n/a

## Description of change
Functionally, no change.

Move ImGui library files around so that they mirror how they are
structured in imgui releases. This will make it easier to migrate to a
future version of ImGui.

Add a handful of static asserts to check that we have the necessary
features enabled / disabled in imgui header.

## Compiling
👍 

## Testing
Tested 32-bit overlay, 64-bit overlay, TDJ, and UFC overlays, and
spicecfg.

Interesting finds during ImGui testing (not part of this PR, but for
future reference when upgrading imgui version):
* Updating dx9 backend to the newest causes TDJ to stop rendering the
overlay completely (fine in UFC or any other game)
* Omitting `IMGUI_USE_BGRA_PACKED_COLOR` causes spicecfg software
renderer to use the wrong color (RGB format parsing error probably)
* At some point - v1.90? the `About` and `Licenses` buttons in spicecfg
stop responding after an upgrade, maybe our improper usage of MenuItem?
* Using the docking branch of ImGui is not required but we should just
stick to it for multi-viewport functionality which we may want to use
later.
2025-04-02 22:56:52 -07:00
bicarus-devandGitHub 6969dfcdd1 overlay: migrate configurator UI from ImGui Columns to Tables (#282)
## Link to GitHub Issue, if one exists
n/a

## Description of change
`ImGui::Columns` is a deprecated component with many issues. Migrate
Buttons/Analog/Overlay/Lights/Options... tabs to the new table API.
Clean up some UI elements around it to reduce clutter.

Note: there are still other usages of Columns API (MIDI debug output,
ACIO viewer, window manager) but no one hardly ever sees those.
Eventually they will need to be migrated if ImGui ever drops support for
them.

## Compiling
👍 

## Testing
Testing all games in spicecfg and overlay.
2025-04-02 18:33:39 -07:00
bicarus-devandGitHub edaf5a386f overlay: fix mouse cursor getting stuck in wrong shape when leaving and coming back to window (#283)
## Link to GitHub Issue, if one exists
Fixes #27 

## Description of change
In standalone configurator mode, when a mouse cursor moves outside the
window and comes back, it may get stuck in the wrong cursor type (e.g.,
get stuck in horizontal resize mode). This is a side effect of software
rendering we do that doesn't deal nicely with mouse events (i.e., not at
all).

## Compiling
Should be fine.

## Testing
Testing spicecfg, spice -cfg, spice64 -cfg, and in-game overlay.
2025-04-02 18:32:30 -07:00
bicarus-devandGitHub 302f08dd57 patcher: show errors on invalid json response from remote (#281)
## Link to GitHub Issue, if one exists
#0

## Description of change
When patcher URL returns an invalid response (as in, cannot be parsed as
JSON), show an error message to user.

Add a "copy text" button to copy the error message.

Fix WarnTooltip so that can handle the case where description is an
empty string for a patch - previously this was just showing as multiple
lines of empty space and then the warning.

## Compiling
It's fine except Docker build currently fails due to cmake version
issue.

## Testing
Tested valid / invalid datecodes and checked against responses.
2025-04-02 18:32:15 -07:00
sp2xdev 68f5a614ef update discord-rpc cmake version 2025-04-01 02:35:41 -07:00
sp2xdev 5db43031f2 fix option descriptions 2025-03-29 01:35:02 -07:00
sp2xdev 873ade757a show patch description in patch warn marker 2025-03-29 00:10:42 -07:00
sp2xdev 3c8dc9de32 overlay: change active option color to green 2025-03-28 23:49:12 -07:00
sp2xdev 4694b59cec update changelog 2025-03-28 20:36:52 -07:00
bicarus-devandGitHub 4f1ada7a2f overlay: help tooltip updates (#279)
## Link to GitHub Issue, if one exists
n/a

## Description of change

In Options (and in Advanced/API/Search tabs), remove the help marker
`(?)` and instead add a tooltip to the option name label and the control
widgets like the checkbox, text input, and combo. This is to help with
discoverability as many users failed to discover the `(?)` widget.

Similarly, update the Patches tab to remove `(?)` and add tooltips to
widgets. Patches with warnings `(!)` will continue to show it to
distinguish it from other patches.

Update styling for the tooltip (dark red background -> dark gray) so
that it stands out from other widgets.

Remove the purple option text used for game-specific options.

## Compiling
👍 

## Testing
Tested in spicecfg, and in-game overlay.
2025-03-28 20:34:58 -07:00
bicarus-devandGitHub 104a9cbffd graphics: rewrite image scaler (#278)
## Link to GitHub Issue, if one exists
Fixes #276 

## Description of change

Note: this change is not compatible with previous screen resize config;
i.e., if you had the zoom setting dialed into a certain view, after this
change it won't be the same area, so you'll have to set up a scene
again. I think this is a worthwhile cost since the old logic is just
broken in weird ways, and preserving old settings is really tedious math
(for a feature that not many people use). Besides, this change will only
use the new Scenes values from the JSON.

Rewrite the DX9 image scaler logic.

* Previously, the rendering surface was fixed at `4096*4096 px`. Now,
this is relative to the backbuffer dimensions, which depends on the game
& respects user provided `-forceres`.
* Remove "center" option, make it the default.

Above two changes fix the aspect ratio issue (scaling in portrait games
not respecting screen ratio) and things breaking at higher res (1080p
when zoomed out far enough, or when forced to run at 4k resolution for
example)

* Use `scenes` leaf of screen resize JSON config for Scene 1 as well,
completely removing ourselves from being compatible with older spice2x
versions for these values.
* Add additional bounds check before calling `StretchRect` to avoid
users getting stuck with a bad layout, which can kill performance.

## Compiling
🦾 

## Testing
Tested 32 bit (popn / ddr), 64 bit (ldj), portrait and landscape modes
(kfc)
2025-03-28 17:23:01 -07:00
bicarus-devandGitHub 45a52cff90 sdvx: landscape mode (#277)
## Link to GitHub Issue, if one exists
n/a

## Description of change
Add `SDVX Landscape Mode` option, allowing you to play SDVX in landscape
monitor orientation, with black borders on left and right - similar to
how EAC does it. This is meant to be an alternative to what people do
today, which is launching in windowed mode.

By default, with just `-sdvxlandscape` enabled, EG will launch at
1920x1080 (transposed from the original 1080x1920 resolution).

It is possible to combine with `-forceres` to launch at higher
resolutions. At some point though, when rendering at large resolutions
past 1440p, at some point scaling may break due to #276.

Disabled on 32-bit SDVX since dx9 `CreateDeviceEx` call is required;
`CreateDevice` won't work. For the same reason this isn't being made a
generic option for museca/rb/etc. In theory you can supply
`-sdvxlandscape` to games like IIDX and play landscape games in portrait
mode, although again, scaling my break due to #276.

Also, add new options (bottomleft/right) for subscreen overlay position
so that they don't overlap with the main screen in landscape mode.

## Compiling
🙂

## Testing
Tested a couple versions of VW and EG.
2025-03-26 20:21:05 -07:00
sp2xdev bf79b5d2aa update changelog 2025-03-24 16:10:10 -07:00
sp2xdev e74c60f61d MFG code review cleanup 2025-03-24 16:08:22 -07:00
bicarus-devandGitHub 89921e5ec2 graphics: custom full screen resolution (#274)
## Link to GitHub Issue, if one exists
n/a

## Description of change
Adds a new option to forcibly set the full screen resolution.

Same idea as -windowscale.

## Compiling
🔺 

## Testing
Seems to be showing similar results as -windowscale:

* Works GREAT for IIDX/SDVX
* popn launches at correct resolution but only draws a small image (at
native res) - can be scaled with F11 menu
* nostalgia is broken in weird ways, image is drawn correctly but bottom
`credits` text draws at wrong offset, game's wintouch logic broken
(perhaps expectedly)

## Other notes
In theory this could enable "play sdvx in landscape mode" option but the
image scaling doesn't quite work with extreme values.
2025-03-24 16:07:04 -07:00
oleg238948234andGitHub 1b7ebd6fc2 Add Mahjong Fight Girl Support (#275)
## Link to GitHub Issue, if one exists
n/a

## Description of change
Adds Mahjong Fight Girl Support

## Compiling
+

## Testing
Tested with VFG:J:E:A:2024100800 in HG mode with VFG IO. No
modifications made to data, other than avs-config.xml and
ea3-config.xml. B and C modes don't work because of serial touchscreen.
UKS mode doesn't work because of IO.
2025-03-24 16:06:55 -07:00
sp2xdev 165cc6028f update changelog 2025-03-23 18:15:10 -07:00
bicarus-devandGitHub 8dc835bf52 Auto PIN entry (#272)
## Link to GitHub Issue, if one exists
Fixes https://github.com/spice2x/spice2x.github.io/issues/193

## Description of change
Merges guardianblue's changes plus some code review feedback.

Adds the ability to automatically type out the digits of a PIN when an
overlay shortcut is pressed.

## Compiling
💯 

## Testing
Tested:
* 32 bit popn
* TDJ, 1p/2p
* KFC, UFC
2025-03-23 18:05:38 -07:00
bicarus-devandGitHub 8241641502 audio: log errors when RegOpenKey fails for ASIO registry hooks (#273)
## Link to GitHub Issue, if one exists
n/a

## Description of change
When RegOpenKey* hooks fail when substituting user's ASIO driver, log a
warning message.

This can happen when the user types in the wrong value for `-iidxasio`
or `-sdvxasio`, which is a common issue.

## Compiling
🥇 

## Testing
Tested LDJ/KFC
2025-03-23 18:02:41 -07:00
bicarus-devandGitHub 6de122c9f8 api: allow lights.read() to be filtered by light names (#271)
## Link to GitHub Issue, if one exists
Fixes https://github.com/spice2x/spice2x.github.io/issues/179

## Description of change
Previously, lights.read() did not accept any parameters, instead it
returned all lights implemented by the current game.
Problem is that this is too large for some embedded implementations;
also just inefficient.

Add code to accept light names as strings and only return those in the
result.
For performance, use `robin_hood::unordered_map` to speed up the lookup
operation, which was previously a linear search.
Add this to Python wrapper library. Did not bother with others.

Also, as a bonus - update lights.write_reset() to also accept a flat
list of strings, as opposed to array containing a string, which was
silly.

## Compiling
 

## Testing
Tested using spiceremote and writing a short Python program.
2025-03-22 21:50:51 -07:00
bicarus-devandGitHub b267ad09ac Multiple "scenes" for screen resize (#270)
## Link to GitHub Issue, if one exists
Fixes #263 

## Description of change
Add "scenes" to screen resize.

* UI changes - F11 menu now has a "scene switcher" for `Scene 1/2/3/4`.
Clean up other UI bits.
* Add hotkeys for `Screen Resize Scene 1`, `Screen Resize 2`, and so on.
* Detect hot key and trigger resizes.
* Add additional objects to screen_resize.json for saving and loading
the new scene settings.
* Existing screen resize data is backwards compatible (can be
loaded/saved to JSON), and is treated as `Scene 1`.
* Add API endpoint for `Resize`, allow toggling resize on/off, switching
active scene
* Add Python wrapper. Did not bother with the other wrappers (Dart, C++,
etc)...

## Compiling
🥇 

## Testing
Still testing, but seems to work fine for TDJ.
2025-03-21 21:52:47 -07:00
sp2xdevandGitHub e0dd371d61 Update README.md 2025-03-21 03:09:17 -07:00
bicarus-devandGitHub f8f45a0cbd When UCRT is in use, hackishly populate msvcrt!__argc and msvcrt!__argv for DLL hook compat (#267)
## Link to GitHub Issue, if one exists
#264 

## Description of change
When spice2x is compiled with UCRT, fix up `__argc` and `__argv` for
msvcrt since some DLL hooks rely on them.

## Compiling
👍 

## Testing
Tested on Win10 LTSC, Win11, and Win7.
2025-03-20 18:32:05 -07:00
bicarus-devandGitHub bf8ba050ce Improve render scale help text in F11 menu (#269)
## Link to GitHub Issue, if one exists
n/a

## Description of change
Previously, F11 menu just provided text-based hint on how to enable
-windowscale option.

Instead, show a disabled checkbox that correctly reflects the current
state, nudging the user to read the tooltip.

Elaborate the option tooltip for -windowscale as well for consistency &
explain *why* you might want to enable this.

## Compiling
💯 

## Testing
Tested 32/64 bit.
2025-03-20 18:31:52 -07:00
sp2xdev 5fe7d1d993 fix docker build files post github migration 2025-03-16 00:34:50 -07:00
sp2xdev cdcd8849f1 update changelog 2025-03-15 22:55:51 -07:00
bicarus-devandGitHub 4c215aa5bb Fix last character being truncated in clipboard::copy_text due to off-by-one error (#262)
## Link to GitHub Issue, if one exists
n/a, user reported

## Description of change
When copying text to clipboard, the last character was being dropped due
to incorrect string buffer size calculation.

Also, clean up callers to just pass std:string by reference instead of
going back and forth between C-style strings.

Lastly, fix `git archive` in `build_all.sh` which broke after github
migration.

## Compiling
Yes

## Testing
Tested all three callers - IIDX cam hook, scard reader in Cards tab, and
patch manager datecode.
2025-03-15 22:54:13 -07:00
sp2xdevandGitHub ab5c343c7d Delete .github/ISSUE_TEMPLATE/patch-submission.md 2025-03-11 01:44:05 -07:00
sp2xdevandGitHub 96cbf11270 Update README.md 2025-03-10 19:26:08 -07:00
sp2xdevandGitHub c734e410f5 Update CONTRIBUTING.md 2025-03-10 19:24:04 -07:00
sp2xdevandGitHub 1eed9ea2db Create pull_request_template.md 2025-03-10 19:22:04 -07:00
sp2xdevandGitHub 7d436e0051 Update README.md 2025-03-10 19:17:58 -07:00
sp2xdev ed8c72852f Initial commit for GitHub migration based on spice2x-25-03-03 2025-03-10 19:16:49 -07:00
sp2xdev 94af8d32c9 remove src placeholder file 2025-03-10 19:12:23 -07:00
sp2xdevandGitHub 5fe96a4091 Update README.md 2025-03-06 16:07:19 -08:00
sp2xdevandGitHub d1e86aefb9 Update CONTRIBUTING.md 2025-02-10 11:00:06 -08:00
sp2xdevandGitHub ecd53b4222 Update README.md 2025-01-20 14:16:30 -08:00
sp2xdevandGitHub 9f02febf43 Update README.md 2025-01-20 14:15:42 -08:00
sp2xdevandGitHub a2ff2811e3 Update README.md 2025-01-20 14:15:00 -08:00
sp2xdevandGitHub ccf46a6c77 Update README.md 2025-01-20 02:22:30 -08:00
sp2xdevandGitHub 6ca215a9fd Update README.md 2025-01-20 02:21:43 -08:00
sp2xdevandGitHub 520411782e Update README.md 2025-01-18 15:54:03 -08:00
sp2xdevandGitHub c56e94316d Update README.md 2025-01-18 15:53:39 -08:00
sp2xdevandGitHub 461563c6e6 Update README.md 2025-01-18 15:52:51 -08:00
sp2xdevandGitHub 164615a9e3 Update README.md 2025-01-02 15:07:09 -08:00
sp2xdevandGitHub 1040b32f3e Update README.md 2025-01-02 15:06:08 -08:00
sp2xdevandGitHub d0719260e7 Update README.md 2025-01-02 15:05:15 -08:00
sp2xdevandGitHub 33d98d8217 Update bug_report.md 2024-12-26 22:48:04 -08:00
sp2xdevandGitHub e1f077a11b Update README.md 2024-12-06 16:27:42 -08:00
sp2xdevandGitHub bd93d9cd11 Update CONTRIBUTING.md 2024-11-29 15:17:05 -08:00
sp2xdevandGitHub 004d1bbc7a Update CONTRIBUTING.md 2024-10-22 23:10:07 -07:00
sp2xdevandGitHub 99eb254e10 Update CONTRIBUTING.md 2024-10-20 17:51:48 -07:00
sp2xdevandGitHub 72c45f4ce1 Update CONTRIBUTING.md 2024-10-20 17:51:30 -07:00
sp2xdevandGitHub 3d3f2f5dd5 Update CONTRIBUTING.md 2024-10-20 17:19:59 -07:00
sp2xdevandGitHub be37865b74 Update issue templates 2024-08-04 00:25:48 -07:00
sp2xdevandGitHub 5bce47e487 Update CONTRIBUTING.md 2024-07-28 22:57:33 -07:00
sp2xdevandGitHub ccf2009726 Update CONTRIBUTING.md 2024-07-27 18:50:20 -07:00
sp2xdevandGitHub 0253cb84a7 Update README.md 2024-07-20 17:39:06 -07:00
sp2xdevandGitHub 4d7017bd07 Update README.md 2024-07-20 17:38:17 -07:00
sp2xdevandGitHub cb4bd6e80d Update README.md 2024-07-20 16:56:25 -07:00
sp2xdevandGitHub 7ed33e261e Update README.md 2024-07-20 16:50:48 -07:00
sp2xdevandGitHub a7e7eb36be Update CONTRIBUTING.md 2024-07-15 19:04:13 -07:00
sp2xdevandGitHub 962db5d4d4 Update CONTRIBUTING.md 2024-07-15 19:03:50 -07:00
sp2xdevandGitHub b59db8dd14 Update CONTRIBUTING.md 2024-07-15 19:03:36 -07:00
sp2xdevandGitHub 680e7a7bda Update issue templates 2024-07-15 19:02:45 -07:00
sp2xdevandGitHub 5e67ce5067 Update CONTRIBUTING.md 2024-07-15 18:56:39 -07:00
sp2xdevandGitHub 4051c728fc Update README.md 2024-06-26 02:13:37 -07:00
sp2xdevandGitHub ae4dbece0a Update README.md 2024-06-18 19:14:08 -07:00
sp2xdevandGitHub 6551687708 Update README.md 2024-05-28 15:41:12 -07:00
sp2xdevandGitHub bd690e183d Update CONTRIBUTING.md 2024-05-28 15:38:36 -07:00
sp2xdevandGitHub 550dd08f19 Update README.md 2024-05-17 01:33:59 -07:00
sp2xdevandGitHub eafe7a834a Create CONTRIBUTING.md 2024-05-17 01:32:54 -07:00
sp2xdevandGitHub 6e8c87921b Update issue templates 2024-04-30 03:00:03 -07:00
sp2xdevandGitHub ccf4ac2080 Update README.md 2024-04-25 18:01:39 -07:00
sp2xdevandGitHub 98188524d7 Update README.md 2024-04-25 18:00:30 -07:00
sp2xdevandGitHub 7477a9996d Update README.md 2024-04-25 17:28:34 -07:00
sp2xdevandGitHub ab2625ec73 Update README.md 2024-04-25 17:27:51 -07:00
sp2xdevandGitHub df5da69be6 Update README.md 2024-04-25 17:27:16 -07:00
sp2xdevandGitHub 5618ae9694 Update README.md 2024-04-25 17:26:16 -07:00
sp2xdevandGitHub 8a4236fba5 Update README.md 2024-04-25 17:25:48 -07:00
sp2xdevandGitHub d5104ac117 Update README.md 2024-03-24 21:42:37 -07:00
sp2xdevandGitHub 8c8e2de4f6 Update issue templates 2024-03-24 15:32:20 -07:00
sp2xdevandGitHub cb6252b15f Update issue templates 2024-03-24 15:30:09 -07:00
sp2xdevandGitHub 864eec2391 Update README.md 2024-03-24 01:57:19 -07:00
sp2xdevandGitHub 56ad570231 Update README.md 2024-03-24 01:54:04 -07:00
sp2xdevandGitHub 33e0611ccf Create SOURCE_FILES.md 2024-03-18 13:37:37 -07:00
sp2xdevandGitHub c12679e815 Update README.md 2024-03-04 19:28:04 -08:00
sp2xdevandGitHub f3415b6691 Update README.md 2024-02-19 00:46:45 -08:00
sp2xdevandGitHub c6f9ce9639 Update README.md 2024-02-18 19:02:15 -08:00
sp2xdevandGitHub 11cd407f33 Update README.md 2024-02-18 19:00:38 -08:00
sp2xdevandGitHub 4055bf94ff Update README.md 2024-02-18 18:59:40 -08:00
sp2xdevandGitHub 3274360961 Update README.md 2024-02-18 18:57:26 -08:00
sp2xdevandGitHub 2bf69f8f1f Update README.md 2024-02-18 18:55:32 -08:00
sp2xdevandGitHub e531993e00 Update README.md 2024-02-11 23:39:52 -08:00
sp2xdevandGitHub f370c4683c Update README.md 2024-02-11 23:02:19 -08:00
sp2xdevandGitHub a905ebba69 Update README.md 2024-02-11 23:01:27 -08:00
sp2xdevandGitHub 2b0df61b0a Update README.md 2024-02-01 03:24:51 -08:00
sp2xdevandGitHub ee182aa083 Update README.md 2024-01-28 19:23:11 -08:00
sp2xdevandGitHub 6fc7990d6b Update README.md 2024-01-28 18:39:33 -08:00
sp2xdevandGitHub ab47675a8a Update README.md 2024-01-28 18:35:38 -08:00
sp2xdevandGitHub 4d703fd433 Update README.md 2024-01-28 18:24:24 -08:00
sp2xdevandGitHub 9e591c6f0d Update README.md 2024-01-28 18:21:08 -08:00
sp2xdevandGitHub 7ecb92ae94 Update README.md 2024-01-28 18:19:54 -08:00
sp2xdevandGitHub 2429011d65 Update README.md 2024-01-26 20:35:55 -08:00
sp2xdevandGitHub f48c30df8b Update issue templates 2024-01-16 19:54:30 -08:00
sp2xdevandGitHub 5465f1b364 Update issue templates 2023-12-31 18:30:48 -08:00
sp2xdevandGitHub e5875c6d73 Update issue templates 2023-12-31 18:17:38 -08:00
sp2xdevandGitHub 5dc53e24e7 Update issue templates 2023-12-28 00:55:02 -08:00
sp2xdevandGitHub d3e9575b75 Update issue templates 2023-12-28 00:52:47 -08:00
sp2xdevandGitHub a22eec22dc Update issue templates 2023-12-28 00:50:10 -08:00
sp2xdevandGitHub dad518a6bf Update issue templates 2023-12-23 14:21:10 -08:00
sp2xdevandGitHub db5f8abac1 Update issue templates 2023-12-23 14:13:59 -08:00
sp2xdevandGitHub a933ea551c Update issue templates 2023-12-23 14:13:24 -08:00
sp2xdevandGitHub 6ceabdb746 Add LICENSE file 2023-12-15 19:50:06 -08:00
sp2xdevandGitHub 834def4b70 Update README.md 2023-12-15 19:49:45 -08:00
sp2xdevandGitHub 06b9fcc0a4 Update README.md 2023-11-11 18:29:24 -08:00
sp2xdevandGitHub 3ecbdb5ae8 Update README.md 2023-11-11 18:27:38 -08:00
sp2xdevandGitHub 36d35fc196 Update README.md 2023-11-11 18:27:09 -08:00
sp2xdevandGitHub 78473d9a1b Update README.md 2023-11-06 18:11:53 -08:00
sp2xdevandGitHub 5847e8b63a Update README.md 2023-08-13 20:57:40 -07:00
sp2xdevandGitHub c18091798a Update README.md 2023-08-13 12:47:52 -07:00
sp2xdevandGitHub 9e84266b60 Update README.md 2023-08-13 12:46:37 -07:00
sp2xdevandGitHub 720478670e Update README.md 2023-08-13 12:46:18 -07:00
sp2xdevandGitHub 0b1335645a Update README.md 2023-08-13 12:44:25 -07:00
sp2xdevandGitHub 706a54df6e Update README.md 2023-03-12 01:47:38 -08:00
sp2xdevandGitHub 83a76757d6 Update README.md 2023-03-12 01:11:24 -08:00
sp2xdevandGitHub d8cdf58222 Update README.md 2023-03-12 01:01:23 -08:00
sp2xdevandGitHub c9469cc1ea Initial commit 2023-03-12 00:39:30 -08:00
1245 changed files with 82111 additions and 42030 deletions
+26
View File
@@ -0,0 +1,26 @@
---
name: Bug report
about: Create a report to help us improve
title: ''
labels: bug
assignees: ''
---
## Game and version
*name of game, version of game*
## Version of spice2x
*please remember to test the LATEST beta version before posting a bug about it*
## Describe the issue
*what's the issue?*
## Attached log.txt file, if available
> [!WARNING]
> Please make sure you remove any personally identifiable information from the log file.
> Please set `-loglevel` to `all` before launching the game for more verbose logs.
## A note on issue management
`Closed as not planned` means exactly that - it's closed, because it's not on our plans to address them any time soon. It doesn't mean your issue is invalid, it just means it's not a priority, and we can always revisit them later.
@@ -0,0 +1,10 @@
---
name: Documentation update
about: For suggesting edits to the wiki page.
title: ''
labels: documentation
assignees: ''
---
+10
View File
@@ -0,0 +1,10 @@
---
name: Feature request
about: Suggest an idea for this project
title: ''
labels: enhancement
assignees: ''
---
+32
View File
@@ -0,0 +1,32 @@
on: [push, pull_request]
name: Continuous Integration
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
fw-ci:
name: Build
runs-on: ubuntu-latest
defaults:
run:
working-directory: ./src/spice2x
steps:
- uses: actions/checkout@v5
- name: Set ccache environment variables
run: |
echo "CCACHE_DIR=${{ github.workspace }}/src/spice2x/.ccache" >> $GITHUB_ENV
- name: Install ccache
uses: hendrikmuhs/ccache-action@v1.2.23
- name: Calculate commit SHA
id: vars
run: |
calculatedSha=$(git rev-parse --short ${{ github.sha }})
echo "COMMIT_SHORT_SHA=$calculatedSha" >> $GITHUB_ENV
- name: Compile
run: ./build_docker.sh
- uses: actions/upload-artifact@v6
with:
name: spice2x-ci-${{ env.COMMIT_SHORT_SHA }}
path: src/spice2x/bin
if-no-files-found: error
+49
View File
@@ -0,0 +1,49 @@
name: Draft Release
on:
workflow_dispatch:
permissions:
contents: write
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
release:
name: Build and Draft Release
runs-on: ubuntu-latest
defaults:
run:
working-directory: ./src/spice2x
steps:
- uses: actions/checkout@v5
with:
ref: main
fetch-depth: 0
- name: Clean leftover build artifacts
run: |
rm -rf .ccache dist bin cmake-build-*
- name: Compile
run: ./build_docker.sh
- name: Determine release name from dist filename
run: |
dist=$(basename "$(ls dist/spice2x-*.zip | grep -v -- '-full.zip')")
# strip the ".zip" to get the base name, e.g. spice2x-26-06-28
name="${dist%.zip}"
# the tag is the date portion, e.g. 26-06-28
tag="${name#spice2x-}"
echo "RELEASE_NAME=$name" >> $GITHUB_ENV
echo "RELEASE_TAG=$tag" >> $GITHUB_ENV
- name: Create draft release
uses: softprops/action-gh-release@v3
with:
draft: true
prerelease: true
tag_name: ${{ env.RELEASE_TAG }}
name: ${{ env.RELEASE_NAME }}
target_commitish: main
generate_release_notes: true
files: |
src/spice2x/dist/spice2x-*.zip
+1 -14
View File
@@ -1,6 +1,7 @@
.vscode/* .vscode/*
# JetBrains IDEs # JetBrains IDEs
.idea
.idea_modules .idea_modules
*.iws *.iws
@@ -10,17 +11,3 @@ cmake-build*
# user config # user config
build_all.local.sh build_all.local.sh
build_docker.local.sh build_docker.local.sh
# output from build script
bin/*
dist/*
build/*.dll
external/cv2pdb/*
.ccache/*
# Visual Studio
.vs
out
CMakeSettings.json
-10
View File
@@ -1,10 +0,0 @@
# Default ignored files
/shelf/
/workspace.xml
# Editor-based HTTP Client requests
/httpRequests/
# Datasource local storage ignored files
/dataSources/
/dataSources.local.xml
# Unnecessary plugin data
/markdown-navigator.xml
-11
View File
@@ -1,11 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="CMakeSharedSettings">
<configurations>
<configuration PROFILE_NAME="Release_x64[WIN_32]" ENABLED="true" GENERATION_DIR="bin/build/x64/Release" CONFIG_NAME="Release" TOOLCHAIN_NAME="MinGW [Crosscompile]" GENERATION_OPTIONS="-G &quot;Unix Makefiles&quot; -DCMAKE_TOOLCHAIN_FILE=&quot;external/cmake-various/toolchain/toolchain-mingw_nix-win-x64.cmake&quot;" />
<configuration PROFILE_NAME="Release_x86[WIN_32]" ENABLED="true" GENERATION_DIR="bin/build/x86/Release" CONFIG_NAME="Release" TOOLCHAIN_NAME="MinGW [Crosscompile]" GENERATION_OPTIONS="-G &quot;Unix Makefiles&quot; -DCMAKE_TOOLCHAIN_FILE=&quot;external/cmake-various/toolchain/toolchain-mingw_nix-win-x86.cmake&quot;" />
<configuration PROFILE_NAME="Debug_x64[WIN_32]" ENABLED="true" GENERATION_DIR="bin/build/x64/Debug" CONFIG_NAME="Debug" TOOLCHAIN_NAME="MinGW [Crosscompile]" GENERATION_OPTIONS="-G &quot;Unix Makefiles&quot; -DCMAKE_TOOLCHAIN_FILE=&quot;external/cmake-various/toolchain/toolchain-mingw_nix-win-x64.cmake&quot;" />
<configuration PROFILE_NAME="Debug_x86[WIN_32]" ENABLED="true" GENERATION_DIR="bin/build/x86/Debug" CONFIG_NAME="Debug" TOOLCHAIN_NAME="MinGW [Crosscompile]" GENERATION_OPTIONS="-G &quot;Unix Makefiles&quot; -DCMAKE_TOOLCHAIN_FILE=&quot;external/cmake-various/toolchain/toolchain-mingw_nix-win-x86.cmake&quot;" />
</configurations>
</component>
</project>
-72
View File
@@ -1,72 +0,0 @@
<component name="InspectionProjectProfileManager">
<profile version="1.0">
<option name="myName" value="Project Default" />
<inspection_tool class="ConstantConditionsOC" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="DuplicatedCode" enabled="false" level="WEAK WARNING" enabled_by_default="false" />
<inspection_tool class="PyBroadExceptionInspection" enabled="false" level="WEAK WARNING" enabled_by_default="false" />
<inspection_tool class="PyCompatibilityInspection" enabled="true" level="WARNING" enabled_by_default="true">
<option name="ourVersions">
<value>
<list size="4">
<item index="0" class="java.lang.String" itemvalue="2.7" />
<item index="1" class="java.lang.String" itemvalue="3.7" />
<item index="2" class="java.lang.String" itemvalue="3.8" />
<item index="3" class="java.lang.String" itemvalue="3.9" />
</list>
</value>
</option>
</inspection_tool>
<inspection_tool class="PyDefaultArgumentInspection" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="PyInterpreterInspection" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="PyPep8Inspection" enabled="true" level="WEAK WARNING" enabled_by_default="true">
<option name="ignoredErrors">
<list>
<option value="E266" />
<option value="E701" />
<option value="E265" />
</list>
</option>
</inspection_tool>
<inspection_tool class="PyPep8NamingInspection" enabled="true" level="WEAK WARNING" enabled_by_default="true">
<option name="ignoredErrors">
<list>
<option value="N806" />
<option value="N803" />
<option value="N802" />
</list>
</option>
</inspection_tool>
<inspection_tool class="PyTypeCheckerInspection" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="PyUnusedLocalInspection" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="ShellCheck" enabled="true" level="ERROR" enabled_by_default="true">
<shellcheck_settings value="SC2164" />
</inspection_tool>
<inspection_tool class="Simplify" enabled="false" level="WARNING" enabled_by_default="false">
<option name="clangTidyCheckOptions">
<list>
<ClangTidyCheckOption>
<option name="optionName" value="clion-simplify.SimplifyConstantConditions" />
<option name="optionValue" value="1" />
</ClangTidyCheckOption>
<ClangTidyCheckOption>
<option name="optionName" value="clion-simplify.SimplifyIfWithReturn" />
<option name="optionValue" value="0" />
</ClangTidyCheckOption>
<ClangTidyCheckOption>
<option name="optionName" value="clion-simplify.SimplifyEqualToTrueFalse" />
<option name="optionValue" value="1" />
</ClangTidyCheckOption>
<ClangTidyCheckOption>
<option name="optionName" value="clion-simplify.SimplifyTernaryWithConstantBranch" />
<option name="optionValue" value="1" />
</ClangTidyCheckOption>
</list>
</option>
</inspection_tool>
<inspection_tool class="SpellCheckingInspection" enabled="true" level="TYPO" enabled_by_default="true">
<option name="processCode" value="false" />
<option name="processLiterals" value="true" />
<option name="processComments" value="true" />
</inspection_tool>
</profile>
</component>
-10
View File
@@ -1,10 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="MarkdownEnhProjectSettings">
<AnnotatorSettings targetHasSpaces="true" linkCaseMismatch="true" wikiCaseMismatch="true" wikiLinkHasDashes="true" notUnderWikiHome="true" targetNotWikiPageExt="true" notUnderSourceWikiHome="true" targetNameHasAnchor="true" targetPathHasAnchor="true" wikiLinkHasSlash="true" wikiLinkHasSubdir="true" wikiLinkHasOnlyAnchor="true" linkTargetsWikiHasExt="true" linkTargetsWikiHasBadExt="true" notUnderSameRepo="true" targetNotUnderVcs="false" linkNeedsExt="true" linkHasBadExt="true" linkTargetNeedsExt="true" linkTargetHasBadExt="true" wikiLinkNotInWiki="true" imageTargetNotInRaw="true" repoRelativeAcrossVcsRoots="true" multipleWikiTargetsMatch="true" unresolvedLinkReference="true" linkIsIgnored="true" anchorIsIgnored="true" anchorIsUnresolved="true" anchorLineReferenceIsUnresolved="true" anchorLineReferenceFormat="true" anchorHasDuplicates="true" abbreviationDuplicates="true" abbreviationNotUsed="true" attributeIdDuplicateDefinition="true" attributeIdNotUsed="true" footnoteDuplicateDefinition="true" footnoteUnresolved="true" footnoteDuplicates="true" footnoteNotUsed="true" macroDuplicateDefinition="true" macroUnresolved="true" macroDuplicates="true" macroNotUsed="true" referenceDuplicateDefinition="true" referenceUnresolved="true" referenceDuplicates="true" referenceNotUsed="true" referenceUnresolvedNumericId="true" enumRefDuplicateDefinition="true" enumRefUnresolved="true" enumRefDuplicates="true" enumRefNotUsed="true" enumRefLinkUnresolved="true" enumRefLinkDuplicates="true" simTocUpdateNeeded="true" simTocTitleSpaceNeeded="true" />
<HtmlExportSettings updateOnSave="false" parentDir="" targetDir="" cssDir="css" scriptDir="js" plainHtml="false" imageDir="" copyLinkedImages="false" imagePathType="0" targetPathType="2" targetExt="" useTargetExt="false" noCssNoScripts="false" useElementStyleAttribute="false" linkToExportedHtml="true" exportOnSettingsChange="true" regenerateOnProjectOpen="false" linkFormatType="HTTP_ABSOLUTE" />
<LinkMapSettings>
<textMaps />
</LinkMapSettings>
</component>
</project>
-71
View File
@@ -1,71 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="FlexmarkProjectSettings">
<FlexmarkHtmlSettings flexmarkSpecExampleRendering="0" flexmarkSpecExampleRenderHtml="false">
<flexmarkSectionLanguages>
<option name="1" value="Markdown" />
<option name="2" value="HTML" />
<option name="3" value="flexmark-ast:1" />
</flexmarkSectionLanguages>
</FlexmarkHtmlSettings>
</component>
<component name="MarkdownProjectSettings">
<PreviewSettings splitEditorLayout="SPLIT" splitEditorPreview="PREVIEW" useGrayscaleRendering="false" zoomFactor="1.0" maxImageWidth="0" synchronizePreviewPosition="true" highlightPreviewType="LINE" highlightFadeOut="5" highlightOnTyping="true" synchronizeSourcePosition="true" verticallyAlignSourceAndPreviewSyncPosition="true" showSearchHighlightsInPreview="true" showSelectionInPreview="true" lastLayoutSetsDefault="false">
<PanelProvider>
<provider providerId="com.vladsch.md.nav.editor.swing.html.panel" providerName="Default - Swing" />
</PanelProvider>
</PreviewSettings>
<ParserSettings gitHubSyntaxChange="false" correctedInvalidSettings="false" emojiShortcuts="1" emojiImages="0">
<PegdownExtensions>
<option name="ATXHEADERSPACE" value="true" />
<option name="FENCED_CODE_BLOCKS" value="true" />
<option name="INTELLIJ_DUMMY_IDENTIFIER" value="true" />
<option name="RELAXEDHRULES" value="true" />
<option name="STRIKETHROUGH" value="true" />
<option name="TABLES" value="true" />
<option name="TASKLISTITEMS" value="true" />
</PegdownExtensions>
<ParserOptions>
<option name="COMMONMARK_LISTS" value="true" />
<option name="EMOJI_SHORTCUTS" value="true" />
<option name="GFM_TABLE_RENDERING" value="true" />
<option name="PRODUCTION_SPEC_PARSER" value="true" />
<option name="SIM_TOC_BLANK_LINE_SPACER" value="true" />
</ParserOptions>
</ParserSettings>
<HtmlSettings headerTopEnabled="false" headerBottomEnabled="false" bodyTopEnabled="false" bodyBottomEnabled="false" addPageHeader="false" addAnchorLinks="false" anchorLinksWrapText="false" imageUriSerials="false" addDocTypeHtml="true" noParaTags="false" defaultUrlTitle="false" migratedPlantUml="true" migratedAnchorLinks="true" plantUmlConversion="0">
<GeneratorProvider>
<provider providerId="com.vladsch.md.nav.editor.text.html.generator" providerName="Unmodified HTML Generator" />
</GeneratorProvider>
<headerTop />
<headerBottom />
<bodyTop />
<bodyBottom />
<fencedCodeConversions>
<option name="c4plantuml" value="NONE" />
<option name="ditaa" value="NONE" />
<option name="erd" value="NONE" />
<option name="graphviz" value="NONE" />
<option name="latex" value="KATEX" />
<option name="math" value="KATEX" />
<option name="mermaid" value="NONE" />
<option name="nomnoml" value="NONE" />
<option name="plantuml" value="NONE" />
<option name="puml" value="NONE" />
<option name="svgbob" value="NONE" />
<option name="umlet" value="NONE" />
<option name="vega" value="NONE" />
<option name="vegalite" value="NONE" />
<option name="wavedrom" value="NONE" />
</fencedCodeConversions>
</HtmlSettings>
<CssSettings previewScheme="UI_SCHEME" cssUri="" isCssUriEnabled="false" isCssUriSerial="true" isCssTextEnabled="false" isDynamicPageWidth="true">
<StylesheetProvider>
<provider providerId="com.vladsch.md.nav.editor.text.html.css" providerName="No Stylesheet" />
</StylesheetProvider>
<ScriptProviders />
<cssText />
<cssUriHistory />
</CssSettings>
</component>
</project>
-4
View File
@@ -1,4 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="CMakeWorkspace" PROJECT_DIR="$PROJECT_DIR$" />
</project>
-8
View File
@@ -1,8 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/.idea/spicetools.iml" filepath="$PROJECT_DIR$/.idea/spicetools.iml" />
</modules>
</component>
</project>
-17
View File
@@ -1,17 +0,0 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="Clion[Init]" type="ShConfigurationType" activateToolWindowBeforeRun="false">
<option name="SCRIPT_TEXT" value="" />
<option name="INDEPENDENT_SCRIPT_PATH" value="true" />
<option name="SCRIPT_PATH" value="$PROJECT_DIR$/cmake.sh" />
<option name="SCRIPT_OPTIONS" value="clion init" />
<option name="INDEPENDENT_SCRIPT_WORKING_DIRECTORY" value="true" />
<option name="SCRIPT_WORKING_DIRECTORY" value="$PROJECT_DIR$" />
<option name="INDEPENDENT_INTERPRETER_PATH" value="true" />
<option name="INTERPRETER_PATH" value="/usr/bin/bash" />
<option name="INTERPRETER_OPTIONS" value="" />
<option name="EXECUTE_IN_TERMINAL" value="false" />
<option name="EXECUTE_SCRIPT_FILE" value="true" />
<envs />
<method v="2" />
</configuration>
</component>
-23
View File
@@ -1,23 +0,0 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="Deploy[Debug_x64]" type="ShConfigurationType">
<option name="SCRIPT_TEXT" value="" />
<option name="INDEPENDENT_SCRIPT_PATH" value="true" />
<option name="SCRIPT_PATH" value="$PROJECT_DIR$/cmake.sh" />
<option name="SCRIPT_OPTIONS" value="deploy all" />
<option name="INDEPENDENT_SCRIPT_WORKING_DIRECTORY" value="true" />
<option name="SCRIPT_WORKING_DIRECTORY" value="$PROJECT_DIR$" />
<option name="INDEPENDENT_INTERPRETER_PATH" value="true" />
<option name="INTERPRETER_PATH" value="/usr/bin/bash" />
<option name="INTERPRETER_OPTIONS" value="" />
<option name="EXECUTE_IN_TERMINAL" value="false" />
<option name="EXECUTE_SCRIPT_FILE" value="true" />
<envs>
<env name="_G_clean" value="false" />
<env name="_G_debug" value="true" />
</envs>
<method v="2">
<option name="RunConfigurationTask" enabled="true" run_configuration_name="Clion[Init]" run_configuration_type="ShConfigurationType" />
<option name="RunConfigurationTask" enabled="false" run_configuration_name="spicetools_spice64" run_configuration_type="CMakeRunConfiguration" run_configuration_target="CMakeBuildProfile:Debug_x64[WIN_32]" />
</method>
</configuration>
</component>
-24
View File
@@ -1,24 +0,0 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="Deploy[Debug_x86]" type="ShConfigurationType">
<option name="SCRIPT_TEXT" value="" />
<option name="INDEPENDENT_SCRIPT_PATH" value="true" />
<option name="SCRIPT_PATH" value="$PROJECT_DIR$/cmake.sh" />
<option name="SCRIPT_OPTIONS" value="deploy all" />
<option name="INDEPENDENT_SCRIPT_WORKING_DIRECTORY" value="true" />
<option name="SCRIPT_WORKING_DIRECTORY" value="$PROJECT_DIR$" />
<option name="INDEPENDENT_INTERPRETER_PATH" value="true" />
<option name="INTERPRETER_PATH" value="/usr/bin/bash" />
<option name="INTERPRETER_OPTIONS" value="" />
<option name="EXECUTE_IN_TERMINAL" value="false" />
<option name="EXECUTE_SCRIPT_FILE" value="true" />
<envs>
<env name="_G_clean" value="false" />
<env name="_G_debug" value="true" />
</envs>
<method v="2">
<option name="RunConfigurationTask" enabled="true" run_configuration_name="Clion[Init]" run_configuration_type="ShConfigurationType" />
<option name="RunConfigurationTask" enabled="false" run_configuration_name="spicetools_cfg" run_configuration_type="CMakeRunConfiguration" run_configuration_target="CMakeBuildProfile:Debug_x86[WIN_32]" />
<option name="RunConfigurationTask" enabled="false" run_configuration_name="spicetools_spice" run_configuration_type="CMakeRunConfiguration" run_configuration_target="CMakeBuildProfile:Debug_x86[WIN_32]" />
</method>
</configuration>
</component>
-24
View File
@@ -1,24 +0,0 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="Deploy[Extra_x64]" type="ShConfigurationType">
<option name="SCRIPT_TEXT" value="" />
<option name="INDEPENDENT_SCRIPT_PATH" value="true" />
<option name="SCRIPT_PATH" value="$PROJECT_DIR$/cmake.sh" />
<option name="SCRIPT_OPTIONS" value="deploy all" />
<option name="INDEPENDENT_SCRIPT_WORKING_DIRECTORY" value="true" />
<option name="SCRIPT_WORKING_DIRECTORY" value="$PROJECT_DIR$" />
<option name="INDEPENDENT_INTERPRETER_PATH" value="true" />
<option name="INTERPRETER_PATH" value="/usr/bin/bash" />
<option name="INTERPRETER_OPTIONS" value="" />
<option name="EXECUTE_IN_TERMINAL" value="false" />
<option name="EXECUTE_SCRIPT_FILE" value="true" />
<envs>
<env name="_G_clean" value="false" />
<env name="_G_debug" value="false" />
</envs>
<method v="2">
<option name="RunConfigurationTask" enabled="true" run_configuration_name="spicetools_stubs_nvEncodeAPI64" run_configuration_type="CMakeRunConfiguration" run_configuration_target="CMakeBuildProfile:Release_x64[WIN_32]" />
<option name="RunConfigurationTask" enabled="true" run_configuration_name="spicetools_stubs_nvcuda" run_configuration_type="CMakeRunConfiguration" run_configuration_target="CMakeBuildProfile:Release_x64[WIN_32]" />
<option name="RunConfigurationTask" enabled="true" run_configuration_name="spicetools_stubs_nvcuvid" run_configuration_type="CMakeRunConfiguration" run_configuration_target="CMakeBuildProfile:Release_x64[WIN_32]" />
</method>
</configuration>
</component>
-23
View File
@@ -1,23 +0,0 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="Deploy[Extra_x86]" type="ShConfigurationType">
<option name="SCRIPT_TEXT" value="" />
<option name="INDEPENDENT_SCRIPT_PATH" value="true" />
<option name="SCRIPT_PATH" value="$PROJECT_DIR$/cmake.sh" />
<option name="SCRIPT_OPTIONS" value="deploy all" />
<option name="INDEPENDENT_SCRIPT_WORKING_DIRECTORY" value="true" />
<option name="SCRIPT_WORKING_DIRECTORY" value="$PROJECT_DIR$" />
<option name="INDEPENDENT_INTERPRETER_PATH" value="true" />
<option name="INTERPRETER_PATH" value="/usr/bin/bash" />
<option name="INTERPRETER_OPTIONS" value="" />
<option name="EXECUTE_IN_TERMINAL" value="false" />
<option name="EXECUTE_SCRIPT_FILE" value="true" />
<envs>
<env name="_G_clean" value="false" />
<env name="_G_debug" value="false" />
</envs>
<method v="2">
<option name="RunConfigurationTask" enabled="false" run_configuration_name="spicetools_stubs_cpusbxpkm" run_configuration_type="CMakeRunConfiguration" run_configuration_target="CMakeBuildProfile:Release_x86[WIN_32]" />
<option name="RunConfigurationTask" enabled="true" run_configuration_name="spicetools_spice_laa" run_configuration_type="CMakeRunConfiguration" run_configuration_target="CMakeBuildProfile:Release_x86[WIN_32]" />
</method>
</configuration>
</component>
-23
View File
@@ -1,23 +0,0 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="Deploy[Release_x64]" type="ShConfigurationType">
<option name="SCRIPT_TEXT" value="" />
<option name="INDEPENDENT_SCRIPT_PATH" value="true" />
<option name="SCRIPT_PATH" value="$PROJECT_DIR$/cmake.sh" />
<option name="SCRIPT_OPTIONS" value="deploy all" />
<option name="INDEPENDENT_SCRIPT_WORKING_DIRECTORY" value="true" />
<option name="SCRIPT_WORKING_DIRECTORY" value="$PROJECT_DIR$" />
<option name="INDEPENDENT_INTERPRETER_PATH" value="true" />
<option name="INTERPRETER_PATH" value="/usr/bin/bash" />
<option name="INTERPRETER_OPTIONS" value="" />
<option name="EXECUTE_IN_TERMINAL" value="false" />
<option name="EXECUTE_SCRIPT_FILE" value="true" />
<envs>
<env name="_G_clean" value="false" />
<env name="_G_debug" value="false" />
</envs>
<method v="2">
<option name="RunConfigurationTask" enabled="true" run_configuration_name="Clion[Init]" run_configuration_type="ShConfigurationType" />
<option name="RunConfigurationTask" enabled="true" run_configuration_name="spicetools_spice64" run_configuration_type="CMakeRunConfiguration" run_configuration_target="CMakeBuildProfile:Release_x64[WIN_32]" />
</method>
</configuration>
</component>
-24
View File
@@ -1,24 +0,0 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="Deploy[Release_x86]" type="ShConfigurationType">
<option name="SCRIPT_TEXT" value="" />
<option name="INDEPENDENT_SCRIPT_PATH" value="true" />
<option name="SCRIPT_PATH" value="$PROJECT_DIR$/cmake.sh" />
<option name="SCRIPT_OPTIONS" value="deploy all" />
<option name="INDEPENDENT_SCRIPT_WORKING_DIRECTORY" value="true" />
<option name="SCRIPT_WORKING_DIRECTORY" value="$PROJECT_DIR$" />
<option name="INDEPENDENT_INTERPRETER_PATH" value="true" />
<option name="INTERPRETER_PATH" value="/usr/bin/bash" />
<option name="INTERPRETER_OPTIONS" value="" />
<option name="EXECUTE_IN_TERMINAL" value="false" />
<option name="EXECUTE_SCRIPT_FILE" value="true" />
<envs>
<env name="_G_clean" value="false" />
<env name="_G_debug" value="false" />
</envs>
<method v="2">
<option name="RunConfigurationTask" enabled="true" run_configuration_name="Clion[Init]" run_configuration_type="ShConfigurationType" />
<option name="RunConfigurationTask" enabled="true" run_configuration_name="spicetools_cfg" run_configuration_type="CMakeRunConfiguration" run_configuration_target="CMakeBuildProfile:Release_x86[WIN_32]" />
<option name="RunConfigurationTask" enabled="false" run_configuration_name="spicetools_spice" run_configuration_type="CMakeRunConfiguration" run_configuration_target="CMakeBuildProfile:Release_x86[WIN_32]" />
</method>
</configuration>
</component>
-25
View File
@@ -1,25 +0,0 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="Distribute[Release]" type="ShConfigurationType">
<option name="SCRIPT_TEXT" value="" />
<option name="INDEPENDENT_SCRIPT_PATH" value="true" />
<option name="SCRIPT_PATH" value="$PROJECT_DIR$/cmake.sh" />
<option name="SCRIPT_OPTIONS" value="distribute all" />
<option name="INDEPENDENT_SCRIPT_WORKING_DIRECTORY" value="true" />
<option name="SCRIPT_WORKING_DIRECTORY" value="$PROJECT_DIR$" />
<option name="INDEPENDENT_INTERPRETER_PATH" value="true" />
<option name="INTERPRETER_PATH" value="/usr/bin/bash" />
<option name="INTERPRETER_OPTIONS" value="" />
<option name="EXECUTE_IN_TERMINAL" value="false" />
<option name="EXECUTE_SCRIPT_FILE" value="true" />
<envs>
<env name="_G_clean" value="false" />
<env name="_G_debug" value="false" />
</envs>
<method v="2">
<option name="RunConfigurationTask" enabled="true" run_configuration_name="Deploy[Release_x64]" run_configuration_type="ShConfigurationType" />
<option name="RunConfigurationTask" enabled="true" run_configuration_name="Deploy[Release_x86]" run_configuration_type="ShConfigurationType" />
<option name="RunConfigurationTask" enabled="true" run_configuration_name="Deploy[Extra_x64]" run_configuration_type="ShConfigurationType" />
<option name="RunConfigurationTask" enabled="true" run_configuration_name="Deploy[Extra_x86]" run_configuration_type="ShConfigurationType" />
</method>
</configuration>
</component>
-9
View File
@@ -1,9 +0,0 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="spicetools_cfg" type="CMakeRunConfiguration" factoryName="Application" folderName="32" activateToolWindowBeforeRun="false" REDIRECT_INPUT="false" ELEVATE="false" USE_EXTERNAL_CONSOLE="false" PASS_PARENT_ENVS_2="true" PROJECT_NAME="spicetools" TARGET_NAME="spicetools_cfg" CONFIG_NAME="Release_x64[WIN_32]" RUN_PATH="/bin/true">
<method v="2">
<option name="CMake.Explicit.Target" predefinedTargetName="spicetools_stubs_kbt" />
<option name="CMake.Explicit.Target" predefinedTargetName="spicetools_stubs_kld" />
<option name="com.jetbrains.cidr.execution.CidrBuildBeforeRunTaskProvider$BuildBeforeRunTask" enabled="true" />
</method>
</configuration>
</component>
-9
View File
@@ -1,9 +0,0 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="spicetools_spice" type="CMakeRunConfiguration" factoryName="Application" folderName="32" activateToolWindowBeforeRun="false" REDIRECT_INPUT="false" ELEVATE="false" USE_EXTERNAL_CONSOLE="false" PASS_PARENT_ENVS_2="true" PROJECT_NAME="spicetools" TARGET_NAME="spicetools_spice" CONFIG_NAME="Release_x64[WIN_32]" RUN_PATH="/bin/true">
<method v="2">
<option name="CMake.Explicit.Target" predefinedTargetName="spicetools_stubs_kbt" />
<option name="CMake.Explicit.Target" predefinedTargetName="spicetools_stubs_kld" />
<option name="com.jetbrains.cidr.execution.CidrBuildBeforeRunTaskProvider$BuildBeforeRunTask" enabled="true" />
</method>
</configuration>
</component>
-9
View File
@@ -1,9 +0,0 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="spicetools_spice64" type="CMakeRunConfiguration" factoryName="Application" folderName="64" activateToolWindowBeforeRun="false" REDIRECT_INPUT="false" ELEVATE="false" USE_EXTERNAL_CONSOLE="false" PASS_PARENT_ENVS_2="true" PROJECT_NAME="spicetools" TARGET_NAME="spicetools_spice64" CONFIG_NAME="Release_x64[WIN_32]" RUN_PATH="/bin/true">
<method v="2">
<option name="CMake.Explicit.Target" predefinedTargetName="spicetools_stubs_kbt64" />
<option name="CMake.Explicit.Target" predefinedTargetName="spicetools_stubs_kld64" />
<option name="com.jetbrains.cidr.execution.CidrBuildBeforeRunTaskProvider$BuildBeforeRunTask" enabled="true" />
</method>
</configuration>
</component>
-9
View File
@@ -1,9 +0,0 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="spicetools_spice_laa" type="CMakeRunConfiguration" factoryName="Application" folderName="32" activateToolWindowBeforeRun="false" REDIRECT_INPUT="false" ELEVATE="false" USE_EXTERNAL_CONSOLE="false" PASS_PARENT_ENVS_2="true" PROJECT_NAME="spicetools" TARGET_NAME="spicetools_spice_laa" CONFIG_NAME="Release_x64[WIN_32]" RUN_PATH="/bin/true">
<method v="2">
<option name="CMake.Explicit.Target" predefinedTargetName="spicetools_stubs_kbt" />
<option name="CMake.Explicit.Target" predefinedTargetName="spicetools_stubs_kld" />
<option name="com.jetbrains.cidr.execution.CidrBuildBeforeRunTaskProvider$BuildBeforeRunTask" enabled="true" />
</method>
</configuration>
</component>
-7
View File
@@ -1,7 +0,0 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="spicetools_stubs_cpusbxpkm" type="CMakeRunConfiguration" factoryName="Application" folderName="32" activateToolWindowBeforeRun="false" REDIRECT_INPUT="false" ELEVATE="false" USE_EXTERNAL_CONSOLE="false" PASS_PARENT_ENVS_2="true" PROJECT_NAME="spicetools" TARGET_NAME="spicetools_stubs_cpusbxpkm" CONFIG_NAME="Release_x64[WIN_32]" RUN_PATH="/bin/true">
<method v="2">
<option name="com.jetbrains.cidr.execution.CidrBuildBeforeRunTaskProvider$BuildBeforeRunTask" enabled="true" />
</method>
</configuration>
</component>
@@ -1,7 +0,0 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="spicetools_stubs_nvEncodeAPI64" type="CMakeRunConfiguration" factoryName="Application" folderName="64" activateToolWindowBeforeRun="false" REDIRECT_INPUT="false" ELEVATE="false" USE_EXTERNAL_CONSOLE="false" PASS_PARENT_ENVS_2="true" PROJECT_NAME="spicetools" TARGET_NAME="spicetools_stubs_nvEncodeAPI64" CONFIG_NAME="Release_x64[WIN_32]" RUN_PATH="/bin/true">
<method v="2">
<option name="com.jetbrains.cidr.execution.CidrBuildBeforeRunTaskProvider$BuildBeforeRunTask" enabled="true" />
</method>
</configuration>
</component>
-7
View File
@@ -1,7 +0,0 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="spicetools_stubs_nvcuda" type="CMakeRunConfiguration" factoryName="Application" folderName="64" activateToolWindowBeforeRun="false" REDIRECT_INPUT="false" ELEVATE="false" USE_EXTERNAL_CONSOLE="false" PASS_PARENT_ENVS_2="true" PROJECT_NAME="spicetools" TARGET_NAME="spicetools_stubs_nvcuda" CONFIG_NAME="Release_x64[WIN_32]" RUN_PATH="/bin/true">
<method v="2">
<option name="com.jetbrains.cidr.execution.CidrBuildBeforeRunTaskProvider$BuildBeforeRunTask" enabled="true" />
</method>
</configuration>
</component>
-7
View File
@@ -1,7 +0,0 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="spicetools_stubs_nvcuvid" type="CMakeRunConfiguration" factoryName="Application" folderName="64" activateToolWindowBeforeRun="false" REDIRECT_INPUT="false" ELEVATE="false" USE_EXTERNAL_CONSOLE="false" PASS_PARENT_ENVS_2="true" PROJECT_NAME="spicetools" TARGET_NAME="spicetools_stubs_nvcuvid" CONFIG_NAME="Release_x64[WIN_32]" RUN_PATH="/bin/true">
<method v="2">
<option name="com.jetbrains.cidr.execution.CidrBuildBeforeRunTaskProvider$BuildBeforeRunTask" enabled="true" />
</method>
</configuration>
</component>
-2
View File
@@ -1,2 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<module classpath="CMake" type="CPP_MODULE" version="4" />
Generated
-6
View File
@@ -1,6 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="" vcs="Git" />
</component>
</project>
+108
View File
@@ -0,0 +1,108 @@
## Contributing
Baseline rules for patch submissions are as follows. Any patches violating the rules below will not be accepted.
To contribute, fork the repo (just the main branch), make changes in your fork, and contribute to upstream by opening a pull request to the main repo.
### Adding support for games
* No BomberGirl, as long as the Free To Play service is still alive.
* No support for that one touch screen game that dispenses plushies of mouse character (`O26`).
* No support for eaCloud ("Konasute") games.
* For a new game that was never supported (not a new version of a game, but rather a new series): please wait 1 year after official AC release in Japan.
* For new version of an already supported game: proceed with caution and use generally-accepted community guidelines.
### Pull requests that will not be accepted
* Performing decryption of encrypted files.
* Including (bundling), distributing, or linking to game data, or making it easier to find/download game data.
* Any features that [phone home](https://en.wikipedia.org/wiki/Phoning_home)
* This includes automatic updaters.
* Exception: we have allowed the "patch import from URL" feature, but this has only been done under very careful considerations and plenty of warnings to the user.
* Built-in EA server that saves data. They should live in a separate project.
* Any additions to built-in patches (modifications / removals are OK if we have a good reason).
* Changes to the software license terms.
* Localization / translation for UI text. It becomes too difficult to manage by the maintainers.
### Avoiding regressions
The biggest risk in making code changes to spice is the risk of regressions. There is a **lot** of shared code used by many different games, reaching back game versions that are more than a decade old. It is practically impossible to test all supported games and versions.
Additionally, there's a lot of code that get exercised on specific hardware - including hardware that you probably don't have access to (e.g., ICCA reader, real cabinet I/O, specific model of a IIDX controller, particular brand of touch screens...)
Therefore, when making code changes, please be extremely careful about containing / scoping your changes. Make targeted bug fixes scoped to handful of game versions and hardware configuration. When adding new features, make it off by default, unless there is a really good reason to make it the default. If you make a new default, add an option that disables it so that users can opt out as needed.
#### Config file compatibility
Do not change the names of buttons binds, analogs, strings for command line parameters, etc - since they are used as unique identifiers in config files. If they are changed, you will introduce an incompatibility with previous versions of config file.
### Code quality requirements
* Test for regressions, at least in and around the component you are modifying:
* All currently supported games / versions must continue to work.
* Backwards compatibility must be preserved, unless there is a really good reason to break it. This includes (but not limited to): global/local config files, command line parameters, game patches, device interop (e.g., card readers), and SpiceAPI / Companion interop.
* Reasonable level of compatibility with the last release of original spicetools is expected. The stated goal of spice2x is to be a drop-in replacement for spicetools.
* Simply put, if someone has an existing install of spicetools/spice2x, copying over new version of spice2x should not result in vastly different behavior or major loss of functionality.
* Make sure you compile with the included Docker script and ensure you do not introduce **any** new compiler warnings or build breaks. The Docker script is the standard build environment, your custom Linux build environment or MSVC can be used during development, but you must validate the final build using Docker.
* Do not make code changes in unrelated areas; i.e., do not run code linters and auto-formatters for parts of the code that you didn't modify.
* Try to submit smaller chunks of code, instead one gigantic patch. For example, don't submit a patch for "Improve feature XYZ"; instead, submit "Change how A works to prepare for feature XYZ" "Refactor B for feature XYZ" "Add feature B to enable feature XYZ".
* Write to the log for anything useful - it helps immensely with troubleshooting and debugging. At the same time though, avoid spamming the log for something trivial.
### UI text
spice2x has a global audience; majority of the user base do not speak English as their first language.
Use simple English, avoid colloquialism, and use concise language, even if it's slightly technical.
### Using OS APIs
Avoid making permanent changes to user's OS configuration. For example, spice must not make a call to set power profile to Maximum Performance, or switch default audio device. Making the reverse call to restore settings on game shutdown is **not** good enough; there is no guarantee that spice will gracefully shutdown, since games (or spice) can crash. This is to avoid inadvertently putting user's PC into a bad state, which can be seen as malware-like behavior.
Watch out for legacy OS compatibility. Currently, the minimum support floor is Windows 7. If you use any Windows API, make sure it's supported in Windows 7. If you need to use API that is not present in Windows 7, you must not directly link against it, otherwise spice will fail to launch on older OSes. Take a look at Windows 8 touch code (win8.cpp) for examples on how to discover OS APIs via pointers.
### Code style requirements
* Indents are four spaces.
* Try to keep under 100 characters per line. This includes comments and string literals.
* Avoid making unnecessary formatting or whitespace changes in a PR; e.g., watch out for text editor automatically stripping trailing whitespace. It pollutes the diff and slows down code review.
* For signed/unsigned integer types, prefer `cstdint` types; e.g., use `int32_t`, `uint64_t` and not `int` and `unsigned long long`.
* Always use \{ curly braces \} when appropriate; do not omit them even when it's optional; such as `for` `if` `else`, etc.
OK:
```c
if (conditional) {
DoSomething();
}
```
Not OK:
```c
if (conditional)
DoSomething();
```
Not OK:
```c
if (conditional) DoSomething();
```
* Opening curly braces should appear at the end, not in a line on its own.
OK:
```c
if (conditional) {
DoSomething();
}
```
Not OK:
```c
if (conditional)
{
DoSomething();
}
```
* Please give [C++ Core Guidelines](https://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines) a read. A few things to point out in spice codebase:
* We don't use GSL.
* Don't throw exceptions.
* Stick to smart pointers. For interfacing with C Win32 API and raw buffers, use `unique_plain_ptr`.
* Writing in C is also completely acceptable.
* Other than that, there are no strict rules for code formatting, but please attempt to emulate the style around the code you are modifying.
+36 -273
View File
@@ -1,298 +1,61 @@
SpiceTools <br/><br/><br/><br/><br/><br/><br/><br/>
==========
This is a loader for various arcade games developed by 573.
The project is using CMake as it's build system, with a custom build
script for making packages ready for distribution and to keep it easy
for people not knowing how to use CMake.
## Building/Distribution # This is the GitHub page for code development. If you just want to download the latest spice2x EXE, visit the homepage instead:
We're currently using Arch Linux for building the binaries. # 🌶️🌶️ [https://spice2x.github.io/](https://spice2x.github.io/) 🌶️🌶️
You'll need:
- MinGW-64 packages (can be found in the AUR)
- bash
- git
- zip
- upx (optional)
For any other GNU/Linux distributions (or Windows lol), you're on your # If you have problems, read [Known Issues (FAQ)](https://github.com/spice2x/spice2x.github.io/wiki/Known-issues) wiki page first, most of your questions are already answered.
own.
To build the project, run: <br/><br/><br/><br/><br/><br/><br/><br/>
$ ./build_all.sh # spice2x
## Build Configuration ## Overview
You can tweak some settings at the beginning of the build script. You spice2x is a continuation of SpiceTools project. Original development of [SpiceTools](https://github.com/spicetools/spicetools) has stalled in 2022; spice2x is led by a new group of developers.
might want to modify the paths of the toolchains if yours differ. It's
also possible to disable UPX compression and source distribution for
example.
If you're not using an IDE and want to run the build script from command spice2x team does not provide any tools to circumvent software copy protection, nor distribute any copyright-protected game data.
line manually each time you make a change, you can set CLEAN_BUILD to 0
so it will only compile what's needed. When modifying the resources you
should build from scratch, since CMake isn't able to track changes of
those files (e.g. changelog.txt, licenses.txt).
You can put your custom build script under "build_all.local.sh" if you [Features](https://github.com/spice2x/spice2x.github.io/wiki/spice2x-features)
don't want git to track the changes you made to the build settings.
## Debug Build [List of supported games](https://github.com/spice2x/spice2x.github.io/wiki/List-of-supported-games)
To get proper stackstraces, you need to build with the DEBUG setting ## How do I set up game data & start playing?
enabled. This will set the CMake build type to debug and the script
will try to make use of cv2pdb.
Check external/cv2pdb/README.md for details on how to set it up.
It is required to use cv2pdb to generate PDB output, because at time of
writing, MinGW still didn't support Microsoft's proprietary file format.
## API By policy,
SpiceTools is providing a TCP/JSON based API. You can enable it with the * we do **NOT** provide links on where to acquire game data
`-api [PORT]` option. It's recommended to also set a password with * we do **NOT** provide tools or instructions on decrypting data
`-apipass [PASS]`. * we do **NOT** provide guides on setting up & basic troubleshooting
The protocol is meant to be simple, easy to use and with few overhead. please do not ask for these, as it will never happen here.
To connect to the API, just open a TCP connection to the host computer
at the specified port.
To make a request you write the UTF-8 encoded JSON contents to the
socket. To mark the end of the request, you need to terminate the JSON
string with `0x00`. The server returns its answer on the same way,
UTF-8 encoded JSON terminated with NULL.
To save space, by default the JSONs are without whitespace. To enable ## Submitting to the Issue Tracker
pretty printing, use `-apipretty`.
If you want to test the API server without running a game, you can **Please file bugs if you find them!** If you just complain about something in Discord servers, we can't see them, so they'll never be fixed.
run it headless via `-apidebug`.
If a password is specified, both request and response are encrypted Rules for filing a new issue or adding comments to existing issues in the tracker:
using RC4 with the key being the password encoded in UTF-8.
While only providing weak security when the password is static,
it's simple to implement and requires no authentication protocol.
The password is also able to change dynamically, see
`control.session_refresh()` for details.
If you don't want to worry about all the details, you can just use one * Low effort submissions will be simply deleted, and repeated attempts will get you banned.
of the supplied libraries (check /api/resources). There's also a little * Check the [known issues](https://github.com/spice2x/spice2x.github.io/wiki/Known-issues) page first before reporting a new issue. If you have some new information or workarounds for a known issue, you can file a documentation bug as well.
remote control GUI available for python. * Use the search function and see if there is an existing issue.
* This is not the place to obtain a guide or receive basic troubleshooting.
* Don't file a bug demanding game XYZ to be supported.
* **Do not upload game data - any part of it, XML files included!**
* **Do not mention or link to external websites that distribute game data!**
* **Do not link to external websites that provide guides on how to run games!**
### WebSocket New GitHub accounts are prevented from creating new issues to prevent spam. Maintainers of this project reserve the right to close or delete any issues that violate the rules above, or any low effort issues. If you ask for very basic troubleshooting help with setting up a game, we'll just delete them without warning.
SpiceTools opens a WebSocket server on the specified port plus one.
That means if the API is on 1337, the WebSocket server will be on 1338.
The protocol is the very same as for the normal API, but instead of
directly sending the data over TCP you need to send binary datapackets.
The included dart spiceapi library also has a WebSocket implementation.
### Example Call ## Contributing
This example inserts the a card into P1's reader slot. **We encourage the community to submit bugs and suggestions via the issue tracker, and to contribute code changes by opening pull requests**
If you need more examples, you can check the example code.
#### Request If you want to resolve any reported (or not reported) bugs, implement features, add support for new games, or fix a [known issue](https://github.com/spice2x/spice2x.github.io/wiki/Known-issues) - feel free to reach out via the Issue tracker or by opening a Pull Request. All submitted code is assumed to be GPLv3 compliant.
```JSON
{
"id": 1,
"module": "card",
"function": "insert",
"params": [0, "E004010000000000"]
}
```
#### Response
```JSON
{
"id": 1,
"errors": [],
"data": []
}
```
### Modules We explicitly do **NOT** have a Discord server for dicussions - we try to do everything out in the open on this GitHub repo. That being said we haven't opened a Discussions tab here because we know it's going to be immediately filled with support requests.
For the sake of simplifying this documentation, I will describe functions Please see [CONTRIBUTING page](https://github.com/spice2x/spice2x.github.io/blob/main/CONTRIBUTING.md) for a full list of guidelines when submitting code.
as if they were called via a normal programming language. That means,
besides `module`/`function` needing to be set accordingly, the `params` field
must contain all the parameters described below. Some of them are optional.
The returning data will always be in the `data` field of the response.
Errors will be reported as strings in the `errors` field. Sometimes, you can ## Additional information
receive more than one error. The error messages may be changed/improved in the
future, so you shouldn't rely on them. If the error list is empty, the function
executed successfully.
The `id` of the response will always match the `id` of the request. The API Please read [README.md](https://github.com/spice2x/spice2x.github.io/blob/main/src/spice2x/README.md) inside `src/spice2x`.
server itself doesn't expect any logic of your ID choices, however you can use
them to make sure your answers aren't arriving out of order. Currently it
doesn't matter since the TCP protocol doesn't allow for out of order data,
however this may change when/if support for UDP is being introduced. The only
restriction is that the ID has to be a valid 64-bit unsigned integer.
#### Card
- insert(index: uint, card_id: hex)
- inserts a card which gets read by the emulated card readers for the game
- index has to be either 0 (for P1) or 1 (for P2)
- card_id has to be a valid 16-character hex string
#### Coin
- get()
- returns the amount of unprocessed coins in queue
- not equal to the amount of coins shown ingame since the game may drain it
- set(amount: int)
- sets the amount of coins in queue for the game to process
- insert(amount: int)
- adds the amount to the coins in queue
- amount is optional and defaults to 1
- blocker_get()
- returns the current coin blocker state (false: open, true: closed)
#### Info
- avs()
- returns a dict including the AVS model, dest, spec, rev and ext
- launcher()
- returns a dict including the version, compile date/time, system time and
the arguments used to start the process
- memory()
- returns a dict including total, total used and bytes used by the process
for both physical and virtual RAM
#### Keypads
For all functions in this module, the keypad parameter must be
either 0 (for P1) or 1 (for P2). Accepted keypad characters are "0" to "9" for
the single digit numbers, "A" for the double zero and "D" for the decimal key.
- write(keypad: uint, input: str)
- writes all characters in input sequentially to the keypad
- this should be used for things like PIN input
- set(keypad: uint, key: char, ...)
- clears all overrides, then applies each given key as override
- if a key is overridden, it shows up as pressed in-game
- get(keypad: uint)
- returns all pressed keys of the specified keypad
#### Analogs/Buttons/Lights
All of those three modules have equally named methods for you to call.
- read()
- returns an array of state objects containing name, state and a bool
- the bool indicates if the object is active (e.g. the button was overridden)
- write([name: str, state: float], ...)
- applies the states as an override value
- the device binding via the config will be ignored while an override is set
- if an override value is set, the object will be marked as active
- write_reset([name: str], ...)
- removes the override value from the objects specified by name
- if no names were passed, all overrides will be removed
##### Additional API for lights
- read(name: string, ...)
- same as read(), but you can specify light names
- write_reset(name: str, ...)
- same as write_reset(), but it accepts a flat list of strings
#### Touch
- read()
- returns an array of state objects containing id, x and y
- write([id: uint, x: int, y: int], ...)
- adds the given touch points having an unknown ID
- overrides old touch points when the ID matches
- write_reset(id: uint, ...)
- removes the touch points matching one of the given IDs
#### Control
This module only functions if a password is being used to communicate,
with the single exception of session_refresh().
- raise(signal: str)
- raises a signal to the current process
- signal is a string and must be one of the following values:
"SIGABRT", "SIGFPE", "SIGILL", "SIGINT", "SIGSEGV", "SIGTERM"
- the signal will be raised while the message is being processed
- exit(code: int)
- code is optional and defaults to 0
- the process will end while the message is being processed
- restart()
- spawns a new instance with the same arguments as passed to the main executable
- session_refresh()
- generates a new secure password and applies it after the response
- can be used to increase the security by using the password as some kind of
session token
- recommended to call directly after connecting and once in a while for
persistent connections
- shutdown()
- tries to force shutdown the computer
- reboot()
- tries to force reboot the computer
#### Memory
This module only functions if a password is being used to communicate.
All offsets are specified in file offsets of the corresponding `dll_name`,
which also means that your hex edits are applicable directly.
- write(dll_name: str, data: hex, offset: uint)
- writes the bytes in data to the specified offset
- read(dll_name: str, offset: uint, size: uint)
- reads bytes starting from offset and returns it
- signature(dll_name: str, signature: hex, replacement: hex, offset: uint,
usage: uint)
- tries to find the signature in the module's memory
- unknown bytes are masked out via "??" (e.g. "75??90????74")
- masking out bytes works for both the signature and the replacement
- offset is the byte difference between the offset of the found signature and
the offset where the replacement data gets written to
- usage is the number of the result being used for replacement, starting at 0
- returns the offset where the replacement data gets written to
- the replacement string can be empty, so you can only get the address of the
signature while not actually replacing anything
#### IIDX
- ticker_get()
- returns a string of the characters displayed on the 16 segment display
- ticker_set(text: str)
- sets the contents of the 16 segment display and disables writes from game
- ticker_reset()
- re-enables writes from game
- tapeled_get(name: str, ...)
- returns a list containing a dict of the current tape LED states. The dict keys are:
- `Stage Left`
- `Stage Right`
- `Cabinet Left`
- `Cabinet Right`
- `Control Panel Under`
- `Ceiling Left`
- `Title Left`
- `Title Right`
- `Ceiling Right`
- `Touch Panel Left`
- `Touch Panel Right`
- `Side Panel Left Inner`
- `Side Panel Left Outer`
- `Side Panel Left`
- `Side Panel Right Outer`
- `Side Panel Right Inner`
- `Side Panel Right`
#### LCD
- info()
- returns information about the serial LCD controller some games use
#### Resize
- image_resize_enable(enable: bool)
- enables or disables image resize state
- image_resize_set_scene(scene: int)
- sets the active scene for image resize state; set to 0 to disable resize
## Native wrapper libraries
Spicetools provides wrapper libraries in: Arduino, C++, Dart, and Python.
Python is the only one that is fully spec compliant.
Other libraries may be missing features and contain bugs; please feel free to
contribute code to fill the gaps if you work on a project using these libraries.
## License
Unless otherwise noted, all files are licensed under the GPLv3.
See the LICENSE file for the full license text.
Files not originating from this project should be placed in "external",
they each have their own license.
###### What that means for you
- If you want to distribute the binaries, you'll have to include the
sources, otherwise you're violating the GPL.
- If you want to merge some of this project's code into similar
software, that software has to be put under the GPL as well.
You'll probably have to modify a lot anyways, so you can just use the
source as some kind of documentation.
-24
View File
@@ -1,24 +0,0 @@
#pragma once
#include <vector>
#include "api/module.h"
#include "api/request.h"
namespace api::modules {
class Touch : public Module {
public:
Touch();
private:
bool is_sdvx;
bool is_tdj_fhd;
// function definitions
void read(Request &req, Response &res);
void write(Request &req, Response &res);
void write_reset(Request &req, Response &res);
void apply_touch_errata(int &x, int &y);
};
}
-10
View File
@@ -1,10 +0,0 @@
#ifndef RCDATA
#define RCDATA 10
#endif
#ifdef SPICE64
DMO_BLOB RCDATA "dsdmo.x86_64.dll"
#else
DMO_BLOB RCDATA "dsdmo.i686.dll"
#endif
-4
View File
@@ -1,4 +0,0 @@
#!/bin/bash
docker build --pull "$PWD/external/docker" -t spicetools/deps --platform linux/x86_64
docker build --build-context gitroot="$PWD/../../.git" . -t spicetools/spice:latest
docker run --rm -v "$PWD/dist:/src/src/spice2x/dist" -v "$PWD/bin:/src/src/spice2x/bin" -v "$PWD/.ccache:/src/src/spice2x/.ccache" spicetools/spice "$@"
-31
View File
@@ -1,31 +0,0 @@
#include "configurator.h"
#include "overlay/overlay.h"
namespace cfg {
// globals
bool CONFIGURATOR_STANDALONE = false;
ConfigType CONFIGURATOR_TYPE = ConfigType::Config;
Configurator::Configurator() {
CONFIGURATOR_STANDALONE = true;
}
Configurator::~Configurator() {
CONFIGURATOR_STANDALONE = false;
}
void Configurator::run() {
// create instance
overlay::ENABLED = true;
overlay::create_software(this->wnd.hWnd);
overlay::OVERLAY->set_active(true);
overlay::OVERLAY->hotkeys_enable = false;
ImGui::GetIO().MouseDrawCursor = false;
// run window
this->wnd.run();
}
}
-173
View File
@@ -1,173 +0,0 @@
#include "configurator_wnd.h"
#include <windows.h>
#include "build/defs.h"
#include "launcher/shutdown.h"
#include "overlay/overlay.h"
#include "util/logging.h"
#include "cfg/configurator.h"
#include "icon.h"
static const char *CLASS_NAME = "ConfiguratorWindow";
static std::string WINDOW_TITLE;
static int WINDOW_SIZE_X = 800;
static int WINDOW_SIZE_Y = 600;
static HICON WINDOW_ICON = LoadIcon(GetModuleHandle(nullptr), MAKEINTRESOURCE(MAINICON));
cfg::ConfiguratorWindow::ConfiguratorWindow() {
// register the window class
WNDCLASS wc {};
wc.lpfnWndProc = ConfiguratorWindow::window_proc;
wc.hInstance = GetModuleHandle(NULL);
wc.lpszClassName = CLASS_NAME;
wc.hbrBackground = NULL;
wc.hIcon = WINDOW_ICON;
RegisterClass(&wc);
// determine window title
if (cfg::CONFIGURATOR_TYPE == cfg::ConfigType::Config) {
WINDOW_TITLE = "spice2x config (" + to_string(VERSION_STRING_CFG) + ")";
WINDOW_SIZE_X = 800;
WINDOW_SIZE_Y = 600;
}
// open window
this->hWnd = CreateWindowEx(
0,
CLASS_NAME,
WINDOW_TITLE.c_str(),
WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT,
NULL,
NULL,
GetModuleHandle(NULL),
(LPVOID) this);
if (this->hWnd) {
overlay::USE_WM_CHAR_FOR_IMGUI_CHAR_INPUT = true;
}
}
cfg::ConfiguratorWindow::~ConfiguratorWindow() {
// close window
DestroyWindow(this->hWnd);
// unregister class
UnregisterClass(CLASS_NAME, GetModuleHandle(NULL));
}
void cfg::ConfiguratorWindow::run() {
// show window
SetWindowPos(this->hWnd, HWND_TOP, 0, 0, WINDOW_SIZE_X, WINDOW_SIZE_Y, 0);
ShowWindow(this->hWnd, SW_SHOWNORMAL);
UpdateWindow(this->hWnd);
// draw overlay in 60 FPS
SetTimer(this->hWnd, 1, 1000 / 60, nullptr);
// window loop
BOOL ret;
MSG msg;
while ((ret = GetMessage(&msg, nullptr, 0, 0)) != 0) {
if (ret == -1) {
break;
} else {
TranslateMessage(&msg);
DispatchMessage(&msg);
}
}
}
LRESULT CALLBACK cfg::ConfiguratorWindow::window_proc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) {
switch (uMsg) {
case WM_CHAR: {
// input characters if overlay is active
if (overlay::OVERLAY && overlay::OVERLAY->has_focus()) {
overlay::OVERLAY->input_char((unsigned int) wParam);
return true;
}
break;
}
case WM_CREATE: {
// set user data of window to class pointer
auto create_struct = reinterpret_cast<LPCREATESTRUCT>(lParam);
SetWindowLongPtrW(hWnd, GWLP_USERDATA, reinterpret_cast<LONG_PTR>(create_struct->lpCreateParams));
break;
}
case WM_CLOSE:
case WM_DESTROY: {
// exit process
launcher::shutdown();
break;
}
case WM_TIMER: {
// update overlay
if (overlay::OVERLAY) {
overlay::OVERLAY->update();
overlay::OVERLAY->set_active(true);
overlay::OVERLAY->new_frame();
overlay::OVERLAY->render();
}
// repaint window
InvalidateRect(hWnd, nullptr, TRUE);
break;
}
case WM_ERASEBKGND: {
return 1;
}
case WM_PAINT: {
// render overlay
if (overlay::OVERLAY) {
// get pixel data
int width, height;
uint32_t *pixel_data = overlay::OVERLAY->sw_get_pixel_data(&width, &height);
if (width > 0 && height > 0) {
// create bitmap
HBITMAP bitmap = CreateBitmap(width, height, 1, 8 * sizeof(uint32_t), pixel_data);
// prepare paint
PAINTSTRUCT paint{};
HDC hdc = BeginPaint(hWnd, &paint);
HDC hdcMem = CreateCompatibleDC(hdc);
SetBkMode(hdc, TRANSPARENT);
// draw bitmap
SelectObject(hdcMem, bitmap);
BitBlt(hdc, paint.rcPaint.left, paint.rcPaint.top,
paint.rcPaint.right - paint.rcPaint.left,
paint.rcPaint.bottom - paint.rcPaint.top,
hdcMem, paint.rcPaint.left, paint.rcPaint.top, SRCCOPY);
// delete bitmap
DeleteObject(bitmap);
// clean up
DeleteDC(hdcMem);
EndPaint(hWnd, &paint);
} else {
return DefWindowProc(hWnd, uMsg, wParam, lParam);
}
} else {
return DefWindowProc(hWnd, uMsg, wParam, lParam);
}
break;
}
default:
return DefWindowProc(hWnd, uMsg, wParam, lParam);
}
return 0;
}
-19
View File
@@ -1,19 +0,0 @@
#pragma once
#include <windows.h>
namespace cfg {
class ConfiguratorWindow {
public:
HWND hWnd;
ConfiguratorWindow();
~ConfiguratorWindow();
void run();
static LRESULT CALLBACK window_proc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam);
};
}
-88
View File
@@ -1,88 +0,0 @@
#!/bin/bash
: ${_G_buildir:="bin/build"}
: ${_G_bindir:="bin"}
: ${_G_debug:=false}
: ${_G_clean:=false}
# Hidden build all temporary environment for sourcing
<<__BUILDALLINIT__
# Auto-config for build_all.sh
GIT_BRANCH=$(git rev-parse --abbrev-ref HEAD 2> /dev/null || echo "none")
GIT_HEAD=$(git rev-parse --short HEAD || echo "none")
BUILD_TYPE=$({ $_G_debug && echo Debug; } || echo Release)
OUTDIR="$_G_bindir/${BUILD_TYPE}"
OUTDIR_EXTRAS="$OUTDIR/extras"
DIST_FOLDER="$_G_bindir/Distribution"
DIST_NAME="${GIT_BRANCH}-${GIT_HEAD}.zip"
DIST_COMMENT="$(date +%Y)/$(date +%m)/$(date +%d):$(git rev-parse HEAD)"
DIST_ENABLE=0
UPX_ENABLE="$DIST_ENABLE"
IGNORE_CACHE=0
INCLUDE_SRC=0
UPX_FLAGS="-q --best --lzma --compress-exports=0"
CLEAN_BUILD=$({ $_G_clean && echo 1; } || echo 0)
DEBUG=$({ $_G_debug && echo 1; } || echo 0)
TOOLCHAIN_32="external/cmake-various/toolchain/toolchain-mingw_nix-win-x86.cmake"
TARGETS_32="spicetools_stubs_kbt spicetools_stubs_kld spicetools_cfg spicetools_spice spicetools_spice_laa spicetools_stubs_cpusbxpkm"
BUILDDIR_32="$_G_buildir/x86/${BUILD_TYPE}"
TOOLCHAIN_64="external/cmake-various/toolchain/toolchain-mingw_nix-win-x64.cmake"
TARGETS_64="spicetools_stubs_kbt64 spicetools_stubs_kld64 spicetools_spice64 spicetools_stubs_nvEncodeAPI64 spicetools_stubs_nvcuvid spicetools_stubs_nvcuda"
BUILDDIR_64="$_G_buildir/x64/${BUILD_TYPE}"
__BUILDALLINIT__
run_build_all() {
# Init build_all.sh
source <(sed -n '/__BUILDALLINIT__/{:a;n;/__BUILDALLINIT__/q;p;$!ba}' "$0")
source <(head -n +20 ./build_all.sh)
# Run build_all.sh
source <(tail -n +80 ./build_all.sh | head -n -3 | sed -e 's~mkdir -p ${OUTDIR}/src~~' -e '/linux/d')
}
run_deploy_all() {
# Init build_all.sh
source <(sed -n '/__BUILDALLINIT__/{:a;n;/__BUILDALLINIT__/q;p;$!ba}' "$0")
source <(head -n +20 ./build_all.sh)
# Run deploy in build_all.sh
set +e
source <(tail -n +182 ./build_all.sh | head -n -3 | sed -e 's~mkdir -p ${OUTDIR}/src~~' -e '/linux/d')
}
run_distribute_all() {
# Init build_all.sh
source <(sed -n '/__BUILDALLINIT__/{:a;n;/__BUILDALLINIT__/q;p;$!ba}' "$0")
source <(head -n +20 ./build_all.sh)
# Run deploy in build_all.sh
source <(tail -n +182 ./build_all.sh | head -n -3 | sed -e 's~mkdir -p ${OUTDIR}/src~~' -e '/linux/d')
# Use old distribute logic
echo "Building dist..."
mkdir -p ${DIST_FOLDER}
rm -rf ${DIST_FOLDER}/${DIST_NAME}
pushd ${OUTDIR} > /dev/null
zip -qrXT9 $OLDPWD/${DIST_FOLDER}/${DIST_NAME} . -z <<< "$DIST_COMMENT"
popd > /dev/null
}
run_clion_init() {
# Init cmake.sh only
source <(sed -n '/__BUILDALLINIT__/{:a;n;/__BUILDALLINIT__/q;p;$!ba}' "$0")
# Clion specific first-time setup
}
# Sanity checks
[ -d "external/cmake-various/toolchain" ] || { echo "Toolchain not found" ; exit 1; }
[ "$(md5sum ./build_all.sh | cut -c -32)" = "5c5d7152a1bff96962223458fdf690a2" ] || { echo "Version mismatch for build_all.sh " ; exit 1; }
# Run requested task
if [[ $(type -t "run_${1}_${2}") == function ]]; then
echo "Starting task: 'run_${1}_${2}'"
"run_${1}_${2}"
else
echo "Invalid task: 'run_${1}_${2}'"
exit 1
fi
-360
View File
@@ -1,360 +0,0 @@
// dear imgui: Renderer for DirectX9
// This needs to be used along with a Platform Binding (e.g. Win32)
// Implemented features:
// [X] Renderer: User texture binding. Use 'LPDIRECT3DTEXTURE9' as ImTextureID. Read the FAQ about ImTextureID in imgui.cpp.
// [X] Renderer: Support for large meshes (64k+ vertices) with 16-bits indices.
// You can copy and use unmodified imgui_impl_* files in your project. See main.cpp for an example of using this.
// If you are new to dear imgui, read examples/README.txt and read the documentation at the top of imgui.cpp.
// https://github.com/ocornut/imgui
// CHANGELOG
// (minor and older changes stripped away, please see git history for details)
// 2019-05-29: DirectX9: Added support for large mesh (64K+ vertices), enable ImGuiBackendFlags_RendererHasVtxOffset flag.
// 2019-04-30: DirectX9: Added support for special ImDrawCallback_ResetRenderState callback to reset render state.
// 2019-03-29: Misc: Fixed erroneous assert in ImGui_ImplDX9_InvalidateDeviceObjects().
// 2019-01-16: Misc: Disabled fog before drawing UI's. Fixes issue #2288.
// 2018-11-30: Misc: Setting up io.BackendRendererName so it can be displayed in the About Window.
// 2018-06-08: Misc: Extracted imgui_impl_dx9.cpp/.h away from the old combined DX9+Win32 example.
// 2018-06-08: DirectX9: Use draw_data->DisplayPos and draw_data->DisplaySize to setup projection matrix and clipping rectangle.
// 2018-05-07: Render: Saving/restoring Transform because they don't seem to be included in the StateBlock. Setting shading mode to Gouraud.
// 2018-02-16: Misc: Obsoleted the io.RenderDrawListsFn callback and exposed ImGui_ImplDX9_RenderDrawData() in the .h file so you can call it yourself.
// 2018-02-06: Misc: Removed call to ImGui::Shutdown() which is not available from 1.60 WIP, user needs to call CreateContext/DestroyContext themselves.
#include "imgui_impl_dx9.h"
#include <algorithm>
// DirectX
#include <d3d9.h>
#include "imgui.h"
// allow std::min use
#ifdef min
#undef min
#endif
// DirectX data
static LPDIRECT3DDEVICE9 g_pd3dDevice = NULL;
static LPDIRECT3DVERTEXBUFFER9 g_pVB = NULL;
static LPDIRECT3DINDEXBUFFER9 g_pIB = NULL;
static LPDIRECT3DTEXTURE9 g_FontTexture = NULL;
static int g_VertexBufferSize = 5000, g_IndexBufferSize = 10000;
#define D3DFVF_CUSTOMVERTEX (D3DFVF_XYZ|D3DFVF_DIFFUSE|D3DFVF_TEX1)
// Render function.
// (this used to be set in io.RenderDrawListsFn and called by ImGui::Render(), but you can now call this directly from your main loop)
void ImGui_ImplDX9_RenderDrawData(ImDrawData *draw_data) {
// Avoid rendering when minimized
if (draw_data->DisplaySize.x <= 0.0f || draw_data->DisplaySize.y <= 0.0f)
return;
// Create and grow buffers if needed
if (!g_pVB || g_VertexBufferSize < draw_data->TotalVtxCount) {
if (g_pVB) {
g_pVB->Release();
g_pVB = NULL;
}
g_VertexBufferSize = draw_data->TotalVtxCount + 5000;
if (g_pd3dDevice->CreateVertexBuffer(g_VertexBufferSize * sizeof(ImDrawVert),
D3DUSAGE_DYNAMIC | D3DUSAGE_WRITEONLY, D3DFVF_CUSTOMVERTEX,
D3DPOOL_DEFAULT, &g_pVB, NULL) < 0)
return;
}
if (!g_pIB || g_IndexBufferSize < draw_data->TotalIdxCount) {
if (g_pIB) {
g_pIB->Release();
g_pIB = NULL;
}
g_IndexBufferSize = draw_data->TotalIdxCount + 10000;
if (g_pd3dDevice->CreateIndexBuffer(g_IndexBufferSize * sizeof(ImDrawIdx),
D3DUSAGE_DYNAMIC | D3DUSAGE_WRITEONLY,
sizeof(ImDrawIdx) == 2 ? D3DFMT_INDEX16 : D3DFMT_INDEX32,
D3DPOOL_DEFAULT, &g_pIB, NULL) < 0)
return;
}
// Backup the DX9 state
IDirect3DStateBlock9 *d3d9_state_block = NULL;
if (g_pd3dDevice->CreateStateBlock(D3DSBT_ALL, &d3d9_state_block) < 0)
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;
g_pd3dDevice->GetTransform(D3DTS_WORLD, &last_world);
g_pd3dDevice->GetTransform(D3DTS_VIEW, &last_view);
g_pd3dDevice->GetTransform(D3DTS_PROJECTION, &last_projection);
// Copy all vertices into a single contiguous buffer
ImDrawVert *vtx_dst;
ImDrawIdx *idx_dst;
if (g_pVB->Lock(0, (UINT) (draw_data->TotalVtxCount * sizeof(ImDrawVert)), (void **) &vtx_dst,
D3DLOCK_DISCARD) < 0)
return;
if (g_pIB->Lock(0, (UINT) (draw_data->TotalIdxCount * sizeof(ImDrawIdx)), (void **) &idx_dst,
D3DLOCK_DISCARD) < 0)
return;
for (int n = 0; n < draw_data->CmdListsCount; n++) {
const ImDrawList *cmd_list = draw_data->CmdLists[n];
memcpy(vtx_dst, cmd_list->VtxBuffer.Data, cmd_list->VtxBuffer.Size * sizeof(ImDrawVert));
memcpy(idx_dst, cmd_list->IdxBuffer.Data, cmd_list->IdxBuffer.Size * sizeof(ImDrawIdx));
vtx_dst += cmd_list->VtxBuffer.Size;
idx_dst += cmd_list->IdxBuffer.Size;
}
g_pVB->Unlock();
g_pIB->Unlock();
g_pd3dDevice->SetStreamSource(0, g_pVB, 0, sizeof(ImDrawVert));
g_pd3dDevice->SetIndices(g_pIB);
g_pd3dDevice->SetFVF(D3DFVF_CUSTOMVERTEX);
// Setup viewport
D3DVIEWPORT9 vp;
vp.X = vp.Y = 0;
vp.Width = (DWORD) draw_data->DisplaySize.x;
vp.Height = (DWORD) draw_data->DisplaySize.y;
vp.MinZ = 0.0f;
vp.MaxZ = 1.0f;
g_pd3dDevice->SetViewport(&vp);
g_pd3dDevice->SetPixelShader(nullptr);
g_pd3dDevice->SetVertexShader(nullptr);
D3DCAPS9 caps {};
if (FAILED(g_pd3dDevice->GetDeviceCaps(&caps))) {
caps.NumSimultaneousRTs = 0UL;
}
IDirect3DSurface9 *back_buffer = nullptr;
IDirect3DSurface9 *depth_stencil = nullptr;
IDirect3DSurface9 *render_targets[8];
// save all previous render target state
for (size_t target = 0; target < std::min(8UL, caps.NumSimultaneousRTs); target++) {
if (FAILED(g_pd3dDevice->GetRenderTarget(target, &render_targets[target]))) {
render_targets[target] = nullptr;
}
}
// get the previous depth stencil
if (FAILED(g_pd3dDevice->GetDepthStencilSurface(&depth_stencil))) {
depth_stencil = nullptr;
}
// set the back buffer as the current render target
if (SUCCEEDED(g_pd3dDevice->GetBackBuffer(0, 0, D3DBACKBUFFER_TYPE_MONO, &back_buffer))) {
g_pd3dDevice->SetRenderTarget(0, back_buffer);
g_pd3dDevice->SetDepthStencilSurface(nullptr);
for (size_t target = 1; target < std::min(8UL, caps.NumSimultaneousRTs); target++) {
g_pd3dDevice->SetRenderTarget(target, nullptr);
}
} else {
back_buffer = nullptr;
}
// Setup render state: fixed-pipeline, alpha-blending, no face culling, no depth testing, shade mode (for gradient)
g_pd3dDevice->SetRenderState(D3DRS_CULLMODE, D3DCULL_NONE);
g_pd3dDevice->SetRenderState(D3DRS_LIGHTING, false);
g_pd3dDevice->SetRenderState(D3DRS_ZENABLE, false);
g_pd3dDevice->SetRenderState(D3DRS_ALPHABLENDENABLE, true);
g_pd3dDevice->SetRenderState(D3DRS_ALPHATESTENABLE, false);
g_pd3dDevice->SetRenderState(D3DRS_BLENDOP, D3DBLENDOP_ADD);
g_pd3dDevice->SetRenderState(D3DRS_SRCBLEND, D3DBLEND_SRCALPHA);
g_pd3dDevice->SetRenderState(D3DRS_DESTBLEND, D3DBLEND_INVSRCALPHA);
g_pd3dDevice->SetRenderState(D3DRS_SCISSORTESTENABLE, true);
g_pd3dDevice->SetRenderState(D3DRS_SHADEMODE, D3DSHADE_GOURAUD);
g_pd3dDevice->SetRenderState(D3DRS_FOGENABLE, false);
g_pd3dDevice->SetTextureStageState(0, D3DTSS_COLOROP, D3DTOP_MODULATE);
g_pd3dDevice->SetTextureStageState(0, D3DTSS_COLORARG1, D3DTA_TEXTURE);
g_pd3dDevice->SetTextureStageState(0, D3DTSS_COLORARG2, D3DTA_DIFFUSE);
g_pd3dDevice->SetTextureStageState(0, D3DTSS_ALPHAOP, D3DTOP_MODULATE);
g_pd3dDevice->SetTextureStageState(0, D3DTSS_ALPHAARG1, D3DTA_TEXTURE);
g_pd3dDevice->SetTextureStageState(0, D3DTSS_ALPHAARG2, D3DTA_DIFFUSE);
g_pd3dDevice->SetSamplerState(0, D3DSAMP_MINFILTER, D3DTEXF_LINEAR);
g_pd3dDevice->SetSamplerState(0, D3DSAMP_MAGFILTER, D3DTEXF_LINEAR);
// Setup 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.
// Being agnostic of whether <d3dx9.h> or <DirectXMath.h> can be used, we aren't relying on D3DXMatrixIdentity()/D3DXMatrixOrthoOffCenterLH() or DirectX::XMMatrixIdentity()/DirectX::XMMatrixOrthographicOffCenterLH()
{
float L = draw_data->DisplayPos.x + 0.5f;
float R = draw_data->DisplayPos.x + draw_data->DisplaySize.x + 0.5f;
float T = draw_data->DisplayPos.y + 0.5f;
float B = draw_data->DisplayPos.y + draw_data->DisplaySize.y + 0.5f;
D3DMATRIX mat_identity = {{{1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f}}};
D3DMATRIX mat_projection =
{{{
2.0f / (R - L), 0.0f, 0.0f, 0.0f,
0.0f, 2.0f / (T - B), 0.0f, 0.0f,
0.0f, 0.0f, 0.5f, 0.0f,
(L + R) / (L - R), (T + B) / (B - T), 0.5f, 1.0f
}}};
g_pd3dDevice->SetTransform(D3DTS_WORLD, &mat_identity);
g_pd3dDevice->SetTransform(D3DTS_VIEW, &mat_identity);
g_pd3dDevice->SetTransform(D3DTS_PROJECTION, &mat_projection);
}
// Render command lists
// (Because we merged all buffers into a single one, we maintain our own offset into them)
int global_vtx_offset = 0;
int global_idx_offset = 0;
ImVec2 clip_off = draw_data->DisplayPos;
for (int n = 0; n < draw_data->CmdListsCount; n++) {
const ImDrawList *cmd_list = draw_data->CmdLists[n];
for (int cmd_i = 0; cmd_i < cmd_list->CmdBuffer.Size; cmd_i++) {
const ImDrawCmd *pcmd = &cmd_list->CmdBuffer[cmd_i];
if (pcmd->UserCallback != NULL) {
pcmd->UserCallback(cmd_list, pcmd);
} else {
const RECT r = {(LONG) (pcmd->ClipRect.x - clip_off.x), (LONG) (pcmd->ClipRect.y - clip_off.y),
(LONG) (pcmd->ClipRect.z - clip_off.x), (LONG) (pcmd->ClipRect.w - clip_off.y)};
auto texture = reinterpret_cast<IDirect3DBaseTexture9 *>(pcmd->TextureId);
g_pd3dDevice->SetTexture(0, texture);
g_pd3dDevice->SetScissorRect(&r);
g_pd3dDevice->DrawIndexedPrimitive(D3DPT_TRIANGLELIST,
pcmd->VtxOffset + global_vtx_offset, 0,
(UINT) cmd_list->VtxBuffer.Size,
pcmd->IdxOffset + global_idx_offset, pcmd->ElemCount / 3);
}
}
global_idx_offset += cmd_list->IdxBuffer.Size;
global_vtx_offset += cmd_list->VtxBuffer.Size;
}
if (back_buffer) {
back_buffer->Release();
back_buffer = nullptr;
}
// restore previous depth stencil
if (depth_stencil) {
g_pd3dDevice->SetDepthStencilSurface(depth_stencil);
depth_stencil->Release();
depth_stencil = nullptr;
}
// restore all render target state
for (size_t target = 0; target < std::min(8UL, caps.NumSimultaneousRTs); target++) {
auto render_target = render_targets[target];
if (render_target) {
g_pd3dDevice->SetRenderTarget(target, render_target);
render_target->Release();
}
}
// restore the DX9 transform
g_pd3dDevice->SetTransform(D3DTS_WORLD, &last_world);
g_pd3dDevice->SetTransform(D3DTS_VIEW, &last_view);
g_pd3dDevice->SetTransform(D3DTS_PROJECTION, &last_projection);
// restore the DX9 state
d3d9_state_block->Apply();
d3d9_state_block->Release();
}
bool ImGui_ImplDX9_Init(IDirect3DDevice9 *device) {
// Setup back-end capabilities flags
auto &io = ImGui::GetIO();
io.BackendRendererName = "imgui_impl_dx9";
// We can honor the ImDrawCmd::VtxOffset field, allowing for large meshes.
io.BackendFlags |= ImGuiBackendFlags_RendererHasVtxOffset;
g_pd3dDevice = device;
g_pd3dDevice->AddRef();
return true;
}
void ImGui_ImplDX9_Shutdown() {
ImGui_ImplDX9_InvalidateDeviceObjects();
if (g_pd3dDevice) {
g_pd3dDevice->Release();
g_pd3dDevice = NULL;
}
}
static bool ImGui_ImplDX9_CreateFontsTexture() {
// Build texture atlas
ImGuiIO &io = ImGui::GetIO();
unsigned char *pixels;
int width, height, bytes_per_pixel;
io.Fonts->GetTexDataAsRGBA32(&pixels, &width, &height, &bytes_per_pixel);
// Upload texture to graphics system
g_FontTexture = NULL;
if (g_pd3dDevice->CreateTexture(width, height, 1, D3DUSAGE_DYNAMIC, D3DFMT_A8R8G8B8,
D3DPOOL_DEFAULT, &g_FontTexture, NULL) < 0)
return false;
D3DLOCKED_RECT tex_locked_rect;
if (g_FontTexture->LockRect(0, &tex_locked_rect, NULL, 0) != D3D_OK)
return false;
for (int y = 0; y < height; y++)
memcpy((unsigned char *) tex_locked_rect.pBits + tex_locked_rect.Pitch * y,
pixels + (width * bytes_per_pixel) * y, (width * bytes_per_pixel));
g_FontTexture->UnlockRect(0);
// Store our identifier
io.Fonts->TexID = (ImTextureID) g_FontTexture;
return true;
}
bool ImGui_ImplDX9_CreateDeviceObjects() {
if (!g_pd3dDevice) {
return false;
}
return ImGui_ImplDX9_CreateFontsTexture();
}
void ImGui_ImplDX9_InvalidateDeviceObjects() {
if (!g_pd3dDevice)
return;
if (g_pVB) {
g_pVB->Release();
g_pVB = NULL;
}
if (g_pIB) {
g_pIB->Release();
g_pIB = NULL;
}
if (g_FontTexture) {
g_FontTexture->Release();
g_FontTexture = NULL;
ImGui::GetIO().Fonts->TexID = 0;
} // We copied g_pFontTextureView to io.Fonts->TexID so let's clear that as well.
}
void ImGui_ImplDX9_NewFrame() {
if (!g_FontTexture) {
ImGui_ImplDX9_CreateDeviceObjects();
}
IDirect3DSwapChain9 *swap_chain = nullptr;
if (SUCCEEDED(g_pd3dDevice->GetSwapChain(0, &swap_chain))) {
auto &io = ImGui::GetIO();
D3DPRESENT_PARAMETERS present_params {};
if (SUCCEEDED(swap_chain->GetPresentParameters(&present_params))) {
if (present_params.BackBufferWidth != 0 && present_params.BackBufferHeight != 0) {
io.DisplaySize.x = static_cast<float>(present_params.BackBufferWidth);
io.DisplaySize.y = static_cast<float>(present_params.BackBufferHeight);
} else {
RECT rect {};
GetClientRect(present_params.hDeviceWindow, &rect);
io.DisplaySize.x = static_cast<float>(rect.right - rect.left);
io.DisplaySize.y = static_cast<float>(rect.bottom - rect.top);
}
}
swap_chain->Release();
}
}
-25
View File
@@ -1,25 +0,0 @@
// dear imgui: Renderer for DirectX9
// This needs to be used along with a Platform Binding (e.g. Win32)
// Implemented features:
// [X] Renderer: User texture binding. Use 'LPDIRECT3DTEXTURE9' as ImTextureID. Read the FAQ about ImTextureID in imgui.cpp.
// [X] Renderer: Support for large meshes (64k+ vertices) with 16-bits indices.
// You can copy and use unmodified imgui_impl_* files in your project. See main.cpp for an example of using this.
// If you are new to dear imgui, read examples/README.txt and read the documentation at the top of imgui.cpp.
// https://github.com/ocornut/imgui
#pragma once
#include "imgui.h"
struct IDirect3DDevice9;
IMGUI_IMPL_API bool ImGui_ImplDX9_Init(IDirect3DDevice9 *device);
IMGUI_IMPL_API void ImGui_ImplDX9_Shutdown();
IMGUI_IMPL_API void ImGui_ImplDX9_NewFrame();
IMGUI_IMPL_API void ImGui_ImplDX9_RenderDrawData(ImDrawData *draw_data);
// Use if you want to reset your rendering device without losing ImGui state.
IMGUI_IMPL_API bool ImGui_ImplDX9_CreateDeviceObjects();
IMGUI_IMPL_API void ImGui_ImplDX9_InvalidateDeviceObjects();
-4693
View File
File diff suppressed because it is too large Load Diff
-268
View File
@@ -1,268 +0,0 @@
#include "trackball.h"
#include <chrono>
#include <thread>
#include "util/detour.h"
#include "util/logging.h"
#include "rawinput/rawinput.h"
#include "games/io.h"
#include "util/utils.h"
#include "io.h"
namespace games::ccj {
bool MOUSE_TRACKBALL = false;
bool MOUSE_TRACKBALL_USE_TOGGLE = false;
uint8_t TRACKBALL_SENSITIVITY = 10;
static HANDLE fakeHandle = (HANDLE)0xDEADBEEFull;
static HWND hWnd = nullptr;
static WNDPROC wndProc = nullptr;
static std::thread *tbThread = nullptr;
static bool tbThreadRunning = false;
static const wchar_t *fakeDeviceName = L"VID_1241&PID_1111";
static const wchar_t *windowName = L"ChaseProject";
static decltype(GetRawInputDeviceList) *GetRawInputDeviceList_orig = nullptr;
static decltype(GetRawInputDeviceInfoW) *GetRawInputDeviceInfoW_orig = nullptr;
static decltype(SetWindowLongPtrW) *SetWindowLongPtrW_orig = nullptr;
static decltype(GetRawInputData) *GetRawInputData_orig = nullptr;
static decltype(RegisterRawInputDevices) *RegisterRawInputDevices_orig = nullptr;
static UINT WINAPI GetRawInputDeviceList_hook(PRAWINPUTDEVICELIST pRawInputDeviceList, PUINT puiNumDevices,
UINT cbSize) {
auto result = GetRawInputDeviceList_orig(pRawInputDeviceList, puiNumDevices, cbSize);
if (result == 0xFFFFFFFF)
return result;
if (pRawInputDeviceList == NULL) {
(*puiNumDevices)++;
} else if (result < *puiNumDevices) {
pRawInputDeviceList[result] = {fakeHandle, 0};
result++;
}
return result;
}
static UINT WINAPI GetRawInputDeviceInfoW_hook(HANDLE hDevice, UINT uiCommand, LPVOID pData, PUINT pcbSize) {
if (hDevice != fakeHandle || uiCommand != RIDI_DEVICENAME)
return GetRawInputDeviceInfoW_orig(hDevice, uiCommand, pData, pcbSize);
const auto requiredLen = (wcslen(fakeDeviceName) + 1) * sizeof(wchar_t);
if (*pcbSize < requiredLen) {
*pcbSize = requiredLen;
return 0xFFFFFFFF;
}
if (pData == NULL) {
*pcbSize = requiredLen;
return 0;
}
wcscpy((wchar_t*)pData, fakeDeviceName);
return requiredLen;
}
static LONG_PTR WINAPI SetWindowLongPtrW_hook(HWND _hWnd, int nIndex, LONG_PTR dwNewLong) {
wchar_t buffer[256];
if (nIndex != GWLP_WNDPROC || GetWindowTextW(_hWnd, buffer, 256) == 0 || !wcswcs(buffer, windowName))
return SetWindowLongPtrW_orig(_hWnd, nIndex, dwNewLong);
hWnd = _hWnd;
wndProc = (WNDPROC)dwNewLong;
return SetWindowLongPtrW_orig(_hWnd, nIndex, dwNewLong);
}
static UINT WINAPI GetRawInputData_hook(HRAWINPUT hRawInput, UINT uiCommand, LPVOID pData, PUINT pcbSize, UINT cbSizeHeader) {
if (hRawInput != fakeHandle)
return GetRawInputData_orig(hRawInput, uiCommand, pData, pcbSize, cbSizeHeader);
if (pData == NULL) {
if (uiCommand == RID_HEADER)
*pcbSize = sizeof(RAWINPUTHEADER);
else
*pcbSize = sizeof(RAWINPUT);
return 0;
}
const RAWINPUTHEADER header = { RIM_TYPEMOUSE, sizeof(RAWINPUT), fakeHandle, 0 };
if (uiCommand == RID_HEADER) {
if (*pcbSize < sizeof(RAWINPUTHEADER)) {
SetLastError(ERROR_INSUFFICIENT_BUFFER);
return 0xFFFFFFFF;
}
*((RAWINPUTHEADER*)pData) = header;
return sizeof(RAWINPUTHEADER);
} else if (uiCommand == RID_INPUT) {
if (*pcbSize < sizeof(RAWINPUT)) {
SetLastError(ERROR_INSUFFICIENT_BUFFER);
return 0xFFFFFFFF;
}
RAWMOUSE rawMouse {};
if (MOUSE_TRACKBALL) {
static bool active = false;
static bool lastState = false;
static std::chrono::time_point<std::chrono::steady_clock> lastModified = std::chrono::steady_clock::now();
static std::chrono::milliseconds debounceDuration(100);
auto currentTime = std::chrono::steady_clock::now();
bool pressed = get_async_secondary_mouse() & 0x8000;
bool focused = GetForegroundWindow() == hWnd;
if (focused && MOUSE_TRACKBALL_USE_TOGGLE && pressed && (currentTime - lastModified > debounceDuration)) {
active = !active;
lastModified = currentTime;
}
if (focused && ((MOUSE_TRACKBALL_USE_TOGGLE && active) || (!MOUSE_TRACKBALL_USE_TOGGLE && pressed))) {
POINT cursor;
RECT client;
GetClientRect(hWnd, &client);
int sx = client.right - client.left;
int sy = client.bottom - client.top;
GetCursorPos(&cursor);
ScreenToClient(hWnd, &cursor);
static int lastX = cursor.x;
static int lastY = cursor.y;
if (!lastState) {
lastX = cursor.x;
lastY = cursor.y;
lastState = true;
}
rawMouse.usFlags = MOUSE_MOVE_RELATIVE;
rawMouse.lLastX = (int)((float)(cursor.x - lastX) * (float)TRACKBALL_SENSITIVITY / 20.0f);
rawMouse.lLastY = (int)((float)(lastY - cursor.y) * (float)TRACKBALL_SENSITIVITY / 20.0f);
bool updateCursor = false;
if (cursor.x <= 0) {
updateCursor = true;
cursor.x = sx - 5;
} else if (cursor.x >= sx - 1) {
updateCursor = true;
cursor.x = 5;
}
if (cursor.y <= 0) {
updateCursor = true;
cursor.y = sy - 5;
} else if (cursor.y >= sy - 1) {
updateCursor = true;
cursor.y = 5;
}
lastX = cursor.x;
lastY = cursor.y;
if (updateCursor) {
ClientToScreen(hWnd, &cursor);
SetCursorPos(cursor.x, cursor.y);
}
} else if (lastState && !active) {
lastState = false;
}
} else {
rawMouse.usFlags = MOUSE_MOVE_RELATIVE;
auto &analogs = get_analogs();
if (analogs[Analogs::Trackball_DX].isSet() || analogs[Analogs::Trackball_DY].isSet()) {
float x = GameAPI::Analogs::getState(RI_MGR, analogs[Analogs::Trackball_DX]) * 2.0f - 1.0f;
float y = GameAPI::Analogs::getState(RI_MGR, analogs[Analogs::Trackball_DY]) * 2.0f - 1.0f;
rawMouse.lLastX = (long) (x * (float) TRACKBALL_SENSITIVITY);
rawMouse.lLastY = (long) (-y * (float) TRACKBALL_SENSITIVITY);
}
auto &buttons = get_buttons();
if (GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::Trackball_Up]))
rawMouse.lLastY = TRACKBALL_SENSITIVITY;
if (GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::Trackball_Down]))
rawMouse.lLastY = -TRACKBALL_SENSITIVITY;
if (GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::Trackball_Left]))
rawMouse.lLastX = -TRACKBALL_SENSITIVITY;
if (GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::Trackball_Right]))
rawMouse.lLastX = TRACKBALL_SENSITIVITY;
}
*((RAWINPUT*)pData) = { header, { rawMouse } };
return sizeof(RAWINPUT);
}
return 0xFFFFFFFF;
}
static BOOL WINAPI RegisterRawInputDevices_hook(PCRAWINPUTDEVICE pRawInputDevices, UINT uiNumDevices, UINT cbSize) {
if (uiNumDevices == 2 && pRawInputDevices[1].usUsage == HID_USAGE_GENERIC_GAMEPAD)
uiNumDevices = 1;
return RegisterRawInputDevices_orig(pRawInputDevices, uiNumDevices, cbSize);
}
void trackball_hook_init() {
// avoid double init
static bool initialized = false;
if (initialized) {
return;
} else {
initialized = true;
}
// announce
log_info("trackball", "init");
// user32
const auto user32Dll = "user32.dll";
detour::trampoline_try(user32Dll, "GetRawInputDeviceList",
GetRawInputDeviceList_hook, &GetRawInputDeviceList_orig);
detour::trampoline_try(user32Dll, "GetRawInputDeviceInfoW",
GetRawInputDeviceInfoW_hook, &GetRawInputDeviceInfoW_orig);
detour::trampoline_try(user32Dll, "SetWindowLongPtrW",
SetWindowLongPtrW_hook, &SetWindowLongPtrW_orig);
detour::trampoline_try(user32Dll, "GetRawInputData",
GetRawInputData_hook, &GetRawInputData_orig);
detour::trampoline_try(user32Dll, "RegisterRawInputDevices",
RegisterRawInputDevices_hook, &RegisterRawInputDevices_orig);
}
void trackball_thread_start() {
using namespace std::chrono_literals;
tbThreadRunning = true;
log_info("trackball", "thread start, use mouse: {}, toggle: {}", MOUSE_TRACKBALL, MOUSE_TRACKBALL_USE_TOGGLE);
tbThread = new std::thread([&] {
while (tbThreadRunning) {
if (hWnd && wndProc) {
wndProc(hWnd, WM_INPUT, RIM_INPUT, (LPARAM)fakeHandle);
}
if (!tbThreadRunning)
break;
std::this_thread::sleep_for(10ms);
}
});
}
void trackball_thread_stop() {
tbThreadRunning = false;
if (tbThread)
tbThread->join();
log_info("trackball", "thread stop");
delete tbThread;
}
}
-178
View File
@@ -1,178 +0,0 @@
#include "io.h"
std::vector<Button> &games::ddr::get_buttons() {
static std::vector<Button> analogs;
if (analogs.empty()) {
analogs = GameAPI::Buttons::getButtons("Dance Dance Revolution");
GameAPI::Buttons::sortButtons(
&analogs,
"Service",
"Test",
"Coin Mech",
"P1 Start",
"P1 Panel Up",
"P1 Panel Down",
"P1 Panel Left",
"P1 Panel Right",
"P1 Menu Up",
"P1 Menu Down",
"P1 Menu Left",
"P1 Menu Right",
"P2 Start",
"P2 Panel Up",
"P2 Panel Down",
"P2 Panel Left",
"P2 Panel Right",
"P2 Menu Up",
"P2 Menu Down",
"P2 Menu Left",
"P2 Menu Right"
);
}
return analogs;
}
std::vector<Light> &games::ddr::get_lights() {
static std::vector<Light> lights;
if (lights.empty()) {
lights = GameAPI::Lights::getLights("Dance Dance Revolution");
GameAPI::Lights::sortLights(
&lights,
"P1 Foot Left",
"P1 Foot Up",
"P1 Foot Right",
"P1 Foot Down",
"P2 Foot Left",
"P2 Foot Up",
"P2 Foot Right",
"P2 Foot Down",
"Spot Red",
"Spot Blue",
"Top Spot Red",
"Top Spot Blue",
"P1 Halogen Upper",
"P1 Halogen Lower",
"P2 Halogen Upper",
"P2 Halogen Lower",
"P1 Button",
"P2 Button",
"Neon",
"HD P1 Start",
"HD P1 Menu Left-Right",
"HD P1 Menu Up-Down",
"HD P2 Start",
"HD P2 Menu Left-Right",
"HD P2 Menu Up-Down",
"HD P1 Speaker F R",
"HD P1 Speaker F G",
"HD P1 Speaker F B",
"HD P1 Speaker W R",
"HD P1 Speaker W G",
"HD P1 Speaker W B",
"HD P2 Speaker F R",
"HD P2 Speaker F G",
"HD P2 Speaker F B",
"HD P2 Speaker W R",
"HD P2 Speaker W G",
"HD P2 Speaker W B",
"WHITE Speaker Top R",
"WHITE Speaker Top G",
"WHITE Speaker Top B",
"WHITE Speaker Bottom R",
"WHITE Speaker Bottom G",
"WHITE Speaker Bottom B",
"WHITE Woofer R",
"WHITE Woofer G",
"WHITE Woofer B",
"GOLD P1 Menu Start",
"GOLD P1 Menu Up",
"GOLD P1 Menu Down",
"GOLD P1 Menu Left",
"GOLD P1 Menu Right",
"GOLD P2 Menu Start",
"GOLD P2 Menu Up",
"GOLD P2 Menu Down",
"GOLD P2 Menu Left",
"GOLD P2 Menu Right",
"GOLD Title Panel Left",
"GOLD Title Panel Center",
"GOLD Title Panel Right",
"GOLD P1 Woofer Corner",
"GOLD P2 Woofer Corner",
"GOLD P1 Card Unit R",
"GOLD P1 Card Unit G",
"GOLD P1 Card Unit B",
"GOLD P2 Card Unit R",
"GOLD P2 Card Unit G",
"GOLD P2 Card Unit B",
"GOLD Top Panel Avg R",
"GOLD Top Panel Avg G",
"GOLD Top Panel Avg B",
"GOLD Monitor Side Left Avg R",
"GOLD Monitor Side Left Avg G",
"GOLD Monitor Side Left Avg B",
"GOLD Monitor Side Right Avg R",
"GOLD Monitor Side Right Avg G",
"GOLD Monitor Side Right Avg B",
"GOLD P1 Foot Up Avg R",
"GOLD P1 Foot Up Avg G",
"GOLD P1 Foot Up Avg B",
"GOLD P1 Foot Down Avg R",
"GOLD P1 Foot Down Avg G",
"GOLD P1 Foot Down Avg B",
"GOLD P1 Foot Left Avg R",
"GOLD P1 Foot Left Avg G",
"GOLD P1 Foot Left Avg B",
"GOLD P1 Foot Right Avg R",
"GOLD P1 Foot Right Avg G",
"GOLD P1 Foot Right Avg B",
"GOLD P2 Foot Up Avg R",
"GOLD P2 Foot Up Avg G",
"GOLD P2 Foot Up Avg B",
"GOLD P2 Foot Down Avg R",
"GOLD P2 Foot Down Avg G",
"GOLD P2 Foot Down Avg B",
"GOLD P2 Foot Left Avg R",
"GOLD P2 Foot Left Avg G",
"GOLD P2 Foot Left Avg B",
"GOLD P2 Foot Right Avg R",
"GOLD P2 Foot Right Avg G",
"GOLD P2 Foot Right Avg B",
"GOLD P1 Stage Corner Up-Left",
"GOLD P1 Stage Corner Up-Right",
"GOLD P1 Stage Corner Down-Left",
"GOLD P1 Stage Corner Down-Right",
"GOLD P2 Stage Corner Up-Left",
"GOLD P2 Stage Corner Up-Right",
"GOLD P2 Stage Corner Down-Left",
"GOLD P2 Stage Corner Down-Right"
);
}
return lights;
}
-337
View File
@@ -1,337 +0,0 @@
#include "gitadora.h"
#include "handle.h"
#include "bi2x_hook.h"
#include <unordered_map>
#include "cfg/configurator.h"
#include "hooks/audio/mme.h"
#include "hooks/graphics/graphics.h"
#include "misc/wintouchemu.h"
#include "overlay/overlay.h"
#include "util/cpuutils.h"
#include "util/detour.h"
#include "util/libutils.h"
#include "util/logging.h"
#include "util/sigscan.h"
#include "util/socd_cleaner.h"
#include "hooks/setupapihook.h"
namespace games::gitadora {
// settings
bool TWOCHANNEL = false;
std::optional<unsigned int> CAB_TYPE = std::nullopt;
bool ARENA_SINGLE_WINDOW = false;
bool P1_LEFTY = false;
bool P2_LEFTY = false;
std::optional<std::string> SUBSCREEN_OVERLAY_SIZE;
std::optional<socd::SocdAlgorithm> PICK_ALGO = socd::SocdAlgorithm::PreferRecent;
/*
* Prevent GitaDora from creating folders on F drive
*/
#ifdef SPICE64
static DWORD WINAPI GetLogicalDrives_hook() {
return GetLogicalDrives() | 0x20;
}
static UINT WINAPI GetDriveTypeA_hook(LPCSTR lpRootPathName) {
if (!strcmp(lpRootPathName, "F:\\")) {
return DRIVE_FIXED;
}
return GetDriveTypeA(lpRootPathName);
}
static BOOL WINAPI CreateDirectoryA_hook(LPCSTR lpPathName, LPSECURITY_ATTRIBUTES lpSecurityAttributes) {
if (!strncmp(lpPathName, "F:/", 3)) {
return TRUE;
}
return CreateDirectoryA(lpPathName, lpSecurityAttributes);
}
#endif
/*
* GitaDora checks if the IP address has changed, and if it has it throws 5-1506-0000 like jubeat.
* We don't want this so we patch it out.
*/
static char __cdecl eam_network_detected_ip_change() {
return 0;
}
/*
* GitaDora checks if the server it connects to is in the 192.168.0.0/16 or 169.254.0.0/16 subnet.
* If it is, it downright refuses to use it and errors with no visible indication.
* We don't want this so we patch it out.
*/
static char __cdecl eam_network_settings_conflict() {
return 0;
}
/*
* Prevent GitaDora from changing the volume setting.
*/
static long __cdecl bmsd2_set_windows_volume(int volume) {
return 0;
}
#ifdef SPICE64
/*
* Two Channel Audio Mode
* We proxy bmsd2_boot_hook and modify the last parameter which is apparently the channel count.
* Since this apparently isn't the only thing required we need a signature scan to modify a value as well.
*/
typedef int (__cdecl *bmsd2_boot_t)(long a1, int a2, long a3, char channel_count);
static bmsd2_boot_t bmsd2_boot_orig = nullptr;
static int __cdecl bmsd2_boot_hook(long a1, int a2, long a3, char channel_count) {
return bmsd2_boot_orig(a1, a2, a3, 2);
}
#endif
/*
* Command Line Arguments
* We hook this to override specific values.
* This currently disables the ability to specify your own in the app-config.xml (param/cmdline __type="str")
*/
static bool __cdecl sys_code_get_cmdline(const char *cmdline) {
if (strcmp(cmdline, "-d") == 0) {
return true;
} else if (strcmp(cmdline, "-DM") == 0) {
return true;
} else if (strcmp(cmdline, "-WINDOW") == 0) {
return GRAPHICS_WINDOWED;
} else if (strcmp(cmdline, "-LOGOUT") == 0) {
return false;
} else if (strcmp(cmdline, "-AOU") == 0) {
return false;
} else if (strcmp(cmdline, "-QCMODE") == 0) {
return false;
} else if (strcmp(cmdline, "-FACTORY") == 0) {
return false;
}
return false;
}
/*
* System Setting Parameter Overrides
*/
static std::unordered_map<std::string, long> SYS_SETTINGS;
static std::unordered_map<std::string, long> SYS_DEBUG_DIPS;
static long __cdecl sys_setting_get_param(const char *param) {
// overrides
if (strcmp(param, "PRODUCTION_MODE") == 0) {
return 0;
} else if (strcmp(param, "ENABLE_DISP_ID") == 0) {
return 0;
} else if (CAB_TYPE.has_value() && strcmp(param, "VER_MACHINE") == 0) {
return CAB_TYPE.value() << 12;
}
// map lookup
auto it = SYS_SETTINGS.find(param);
if (it != SYS_SETTINGS.end()) {
return it->second;
}
return -1;
}
static long __cdecl sys_setting_set_param(const char *param, long value) {
SYS_SETTINGS[std::string(param)] = value;
return 1;
}
static long __cdecl sys_debug_dip_get_param(const char *param) {
// overrides
if (strcmp(param, "sysinfo") == 0) {
return 0;
} else if (strcmp(param, "jobbar1") == 0) {
return 0;
} else if (strcmp(param, "jobbar2") == 0) {
return 0;
} else if (strcmp(param, "serial") == 0) {
return 0;
} else if (strcmp(param, "warnvpf") == 0) {
return 0;
} else if (strcmp(param, "scrshot") == 0) {
return 0;
} else if (strcmp(param, "eamxml") == 0) {
return 0;
} else if (strcmp(param, "offset") == 0) {
return 0;
} else if (strcmp(param, "autodbg") == 0) {
return 0;
} else if (strcmp(param, "develop") == 0) {
return 0;
} else if (strcmp(param, "effect_test") == 0) {
return 0;
} else if (strcmp(param, "voice_type2") == 0) {
return 0;
}
// map lookup
auto it = SYS_DEBUG_DIPS.find(param);
if (it != SYS_DEBUG_DIPS.end()) {
return it->second;
}
return -1;
}
static long __cdecl sys_debug_dip_set_param(const char *param, long value) {
SYS_DEBUG_DIPS[std::string(param)] = value;
return 1;
}
GitaDoraGame::GitaDoraGame() : Game("GitaDora") {
}
void GitaDoraGame::pre_attach() {
Game::pre_attach();
if (!cfg::CONFIGURATOR_STANDALONE) {
if (CAB_TYPE.has_value()) {
log_info("gitadora", "cab type: {}", CAB_TYPE.value());
} else {
log_warning("gitadora", "cab type: not set");
}
log_info("gitadora", "applying processor affinity workaround to prevent hangs...");
#ifdef SPICE64
// workaround for hang on title screen, on systems with many SMT threads
// exact cause is unknown; most likely a bad assumption in some video decoder
// 0xFF (first 8 LPs) seems to work well for most people
cpuutils::set_processor_affinity(0xFF, false);
#else
// XG versions ran on ancient dual-core AMD systems
// having more cores cause random hangs on song select screen
cpuutils::set_processor_affinity(0x3, false);
// check invalid cab type
if (CAB_TYPE.has_value() && CAB_TYPE.value() == 3) {
log_fatal("gitadora", "Cabinet type 3 (SD2) not supported on XG series");
}
#endif
// arena model launches a tiny window yet backbuffer at 4k, resulting in unusable overlay
// force scaling to make things usable
if (!overlay::UI_SCALE_PERCENT.has_value() && is_arena_model() &&
GRAPHICS_WINDOWED && !cfg::CONFIGURATOR_STANDALONE) {
overlay::UI_SCALE_PERCENT = 250;
}
// for guitar wail SOCD cleaning
socd::ALGORITHM = socd::SocdAlgorithm::PreferRecent;
// for guitar picking
if (PICK_ALGO.has_value()) {
log_info("gitadora", "guitar pick SOCD algorithm: {}", static_cast<int>(PICK_ALGO.value()));
} else {
log_info("gitadora", "guitar pick SOCD algorithm: legacy");
}
}
}
void GitaDoraGame::attach() {
Game::attach();
// modules
HMODULE sharepj_module = libutils::try_module("libshare-pj.dll");
HMODULE bmsd2_module = libutils::try_module("libbmsd2.dll");
HMODULE system_module = libutils::try_module("libsystem.dll");
// patches
detour::inline_hook((void *) eam_network_detected_ip_change, libutils::try_proc(
sharepj_module, "eam_network_detected_ip_change"));
detour::inline_hook((void *) eam_network_settings_conflict, libutils::try_proc(
sharepj_module, "eam_network_settings_conflict"));
detour::inline_hook((void *) bmsd2_set_windows_volume, libutils::try_proc(
bmsd2_module, "bmsd2_set_windows_volume"));
detour::inline_hook((void *) sys_code_get_cmdline, libutils::try_proc(
system_module, "sys_code_get_cmdline"));
detour::inline_hook((void *) sys_setting_get_param, libutils::try_proc(
system_module, "sys_setting_get_param"));
detour::inline_hook((void *) sys_setting_set_param, libutils::try_proc(
system_module, "sys_setting_set_param"));
detour::inline_hook((void *) sys_debug_dip_get_param, libutils::try_proc(
system_module, "sys_debug_dip_get_param"));
detour::inline_hook((void *) sys_debug_dip_set_param, libutils::try_proc(
system_module, "sys_debug_dip_set_param"));
#ifdef SPICE64
// gitadora arena model
auto aio = libutils::try_library("libaio.dll");
if (aio != nullptr) {
SETUPAPI_SETTINGS settings{};
settings.class_guid[0] = 0x86E0D1E0;
settings.class_guid[1] = 0x11D08089;
settings.class_guid[2] = 0x0008E49C;
settings.class_guid[3] = 0x731F303E;
const char property[] = "1CCF(8050)_000";
const char property_hardwareid[] = "USB\\VID_1CCF&PID_8050&MI_00\\000";
memcpy(settings.property_devicedesc, property, sizeof(property));
memcpy(settings.property_hardwareid, property_hardwareid, sizeof(property_hardwareid));
setupapihook_init(avs::game::DLL_INSTANCE);
setupapihook_add(settings);
// Gitadora IO(J32D/J33I) board emulation
devicehook_init(avs::game::DLL_INSTANCE);
devicehook_add(new GitaDoraSerialHandle());
// test/service/coin buttons
bi2x_hook_init();
// f: drive hook
detour::iat_try("GetLogicalDrives", GetLogicalDrives_hook, avs::game::DLL_INSTANCE);
detour::iat_try("GetDriveTypeA", GetDriveTypeA_hook, avs::game::DLL_INSTANCE);
detour::iat_try("CreateDirectoryA", CreateDirectoryA_hook, avs::game::DLL_INSTANCE);
// volume change prevention
hooks::audio::mme::init(avs::game::DLL_INSTANCE);
// touch hook
if (ARENA_SINGLE_WINDOW) {
wintouchemu::FORCE = true;
wintouchemu::INJECT_MOUSE_AS_WM_TOUCH = true;
wintouchemu::hook("GITADORA", avs::game::DLL_INSTANCE);
}
return;
}
HMODULE gdme_module = libutils::try_module("libgdme.dll");
// window patch
if (GRAPHICS_WINDOWED && !replace_pattern(
gdme_module,
"754185ED753D8B4118BF0000CB02",
"9090????9090??????????????12", 0, 0))
{
log_warning("gitadora", "windowed mode failed");
}
HMODULE bmsd_engine_module = libutils::try_module("libbmsd-engine.dll");
HMODULE bmsd_module = libutils::try_module("libbmsd.dll");
// two channel mod
if (TWOCHANNEL) {
bmsd2_boot_orig = detour::iat_try("bmsd2_boot", bmsd2_boot_hook, bmsd_module);
if (!(replace_pattern(bmsd_engine_module, "33000000488D", "03??????????", 0, 0) ||
replace_pattern(bmsd_engine_module, "330000000F10", "03??????????", 0, 0)))
{
log_warning("gitadora", "two channel mode failed");
}
}
#endif
}
}
-259
View File
@@ -1,259 +0,0 @@
#include "io.h"
std::vector<Button> &games::gitadora::get_buttons() {
static std::vector<Button> buttons;
if (buttons.empty()) {
buttons = GameAPI::Buttons::getButtons("GitaDora");
GameAPI::Buttons::sortButtons(&buttons,
"Service",
"Test",
"Coin",
"Headphone",
"Guitar P1 Start",
"Guitar P1 Up",
"Guitar P1 Down",
"Guitar P1 Left",
"Guitar P1 Right",
"Guitar P1 Help",
"Guitar P1 Effect 1",
"Guitar P1 Effect 2",
"Guitar P1 Effect 3",
"Guitar P1 Effect Pedal",
"Guitar P1 Button Extra 1",
"Guitar P1 Button Extra 2",
"Guitar P1 Pick Up",
"Guitar P1 Pick Down",
"Guitar P1 R",
"Guitar P1 G",
"Guitar P1 B",
"Guitar P1 Y",
"Guitar P1 P",
"Guitar P1 Knob Up",
"Guitar P1 Knob Down",
"Guitar P1 Wail Up",
"Guitar P1 Wail Down",
"Guitar P2 Start",
"Guitar P2 Up",
"Guitar P2 Down",
"Guitar P2 Left",
"Guitar P2 Right",
"Guitar P2 Help",
"Guitar P2 Effect 1",
"Guitar P2 Effect 2",
"Guitar P2 Effect 3",
"Guitar P2 Effect Pedal",
"Guitar P2 Button Extra 1",
"Guitar P2 Button Extra 2",
"Guitar P2 Pick Up",
"Guitar P2 Pick Down",
"Guitar P2 R",
"Guitar P2 G",
"Guitar P2 B",
"Guitar P2 Y",
"Guitar P2 P",
"Guitar P2 Knob Up",
"Guitar P2 Knob Down",
"Guitar P2 Wail Up",
"Guitar P2 Wail Down",
"Drum Start",
"Drum Up",
"Drum Down",
"Drum Left",
"Drum Right",
"Drum Help",
"Drum Button Extra 1",
"Drum Button Extra 2",
"Drum Left Cymbal",
"Drum Hi-Hat",
"Drum Left Pedal",
"Drum Snare",
"Drum Hi-Tom",
"Drum Bass Pedal",
"Drum Low-Tom",
"Drum Floor Tom",
"Drum Right Cymbal",
"Drum Hi-Hat Closed",
"Drum Hi-Hat Half-Open"
);
}
return buttons;
}
std::vector<Analog> &games::gitadora::get_analogs() {
static std::vector<Analog> analogs;
if (analogs.empty()) {
analogs = GameAPI::Analogs::getAnalogs("GitaDora");
GameAPI::Analogs::sortAnalogs(&analogs,
"Guitar P1 Wail X",
"Guitar P1 Wail Y",
"Guitar P1 Wail Z",
"Guitar P1 Knob",
"Guitar P2 Wail X",
"Guitar P2 Wail Y",
"Guitar P2 Wail Z",
"Guitar P2 Knob"
);
}
return analogs;
}
std::string games::gitadora::get_buttons_help() {
// keep to max 100 characters wide
return
"guitar:\n"
"\n"
" < R G B Y P --- Pick ] \n"
"\n"
" If you normally hold your guitar left-handed (frets on right hand):\n"
" * here, hold the guitar as right-handed (frets on left hand),\n"
" and bind the buttons, pick, and wail controls\n"
" * in Options tab, turn on GitaDora Lefty Guitar\n"
" * finally, use the in-game option to turn on LEFT mode\n"
"\n"
"\n"
"drums:\n"
"\n"
" LeftCymbal RightCymbal\n"
" HiHat HiTom LowTom \n"
" Snare FloorTom \n"
" -------------------------------------------------------\n"
" Left Bass\n"
" Pedal Pedal\n"
"\n"
" For MIDI drums with Open/Closed HiHat configurations or pads with\n"
" multiple hit zones, ensure you bind all variation using the Pages\n"
" button at the bottom."
;
}
std::string games::gitadora::get_analogs_help() {
// keep to max 100 characters wide
return
"guitar:\n"
"\n"
"X axis: 0% when body is held facing monitor, 50% when flat on a table\n"
"Y axis: 50% when held horizontal, 0% when neck is raised, 100% pointing down\n"
"Z axis: 50% at rest, 100% when swinging neck toward monitor (only used in XG series)\n"
"\n"
"You need both X and Y axis for up/down wail to work correctly in-game.\n"
"If you only have Y axis, consider using digital wailing instead.\n"
"\n"
"Ensure you clear all analog bindings if you want to use digital wailing.\n"
"\n"
"If you hold your guitar left-handed (frets on the right hand),\n"
"analog bindings will likely not work as intended.\n"
"Consider using digital wailing in Buttons tab."
;
}
std::vector<Light> &games::gitadora::get_lights() {
static std::vector<Light> lights;
if (lights.empty()) {
lights = GameAPI::Lights::getLights("GitaDora");
GameAPI::Lights::sortLights(&lights,
"Guitar P1 Motor",
"Guitar P2 Motor",
"P1 Start",
"P1 Menu Up Down (DX)",
"P1 Menu Left Right (DX)",
"P1 Help (DX)",
"P2 Start",
"P2 Menu Up Down (DX)",
"P2 Menu Left Right (DX)",
"P2 Help (DX)",
"Drum Left Cymbal",
"Drum Hi-Hat",
"Drum Snare",
"Drum High Tom",
"Drum Low Tom",
"Drum Floor Tom",
"Drum Right Cymbal",
"Drum Woofer R",
"Drum Woofer G",
"Drum Woofer B",
"Drum Stage R (DX)",
"Drum Stage G (DX)",
"Drum Stage B (DX)",
"Spot Left (DX)",
"Spot Right (DX)",
"Spot Center Left (DX)",
"Spot Center Right (DX)",
"Drum Spot Rear Left (DX)",
"Drum Spot Rear Right (DX)",
"Guitar Lower Left R (DX)",
"Guitar Lower Left G (DX)",
"Guitar Lower Left B (DX)",
"Guitar Lower Right R (DX)",
"Guitar Lower Right G (DX)",
"Guitar Lower Right B (DX)",
"Guitar Left Speaker Upper R (DX)",
"Guitar Left Speaker Upper G (DX)",
"Guitar Left Speaker Upper B (DX)",
"Guitar Left Speaker Mid Up Left R (DX)",
"Guitar Left Speaker Mid Up Left G (DX)",
"Guitar Left Speaker Mid Up Left B (DX)",
"Guitar Left Speaker Mid Up Right R (DX)",
"Guitar Left Speaker Mid Up Right G (DX)",
"Guitar Left Speaker Mid Up Right B (DX)",
"Guitar Left Speaker Mid Low Left R (DX)",
"Guitar Left Speaker Mid Low Left G (DX)",
"Guitar Left Speaker Mid Low Left B (DX)",
"Guitar Left Speaker Mid Low Right R (DX)",
"Guitar Left Speaker Mid Low Right G (DX)",
"Guitar Left Speaker Mid Low Right B (DX)",
"Guitar Left Speaker Lower R (DX)",
"Guitar Left Speaker Lower G (DX)",
"Guitar Left Speaker Lower B (DX)",
"Guitar Right Speaker Upper R (DX)",
"Guitar Right Speaker Upper G (DX)",
"Guitar Right Speaker Upper B (DX)",
"Guitar Right Speaker Mid Up Left R (DX)",
"Guitar Right Speaker Mid Up Left G (DX)",
"Guitar Right Speaker Mid Up Left B (DX)",
"Guitar Right Speaker Mid Up Right R (DX)",
"Guitar Right Speaker Mid Up Right G (DX)",
"Guitar Right Speaker Mid Up Right B (DX)",
"Guitar Right Speaker Mid Low Left R (DX)",
"Guitar Right Speaker Mid Low Left G (DX)",
"Guitar Right Speaker Mid Low Left B (DX)",
"Guitar Right Speaker Mid Low Right R (DX)",
"Guitar Right Speaker Mid Low Right G (DX)",
"Guitar Right Speaker Mid Low Right B (DX)",
"Guitar Right Speaker Lower R (DX)",
"Guitar Right Speaker Lower G (DX)",
"Guitar Right Speaker Lower B (DX)",
"Title Average R (Arena)",
"Title Average G (Arena)",
"Title Average B (Arena)",
"Woofer R (Arena)",
"Woofer G (Arena)",
"Woofer B (Arena)",
"Card Reader R (Arena)",
"Card Reader G (Arena)",
"Card Reader B (Arena)"
);
}
return lights;
}
-187
View File
@@ -1,187 +0,0 @@
#include "io.h"
std::vector<Button> &games::iidx::get_buttons() {
static std::vector<Button> buttons;
if (buttons.empty()) {
buttons = GameAPI::Buttons::getButtons("Beatmania IIDX");
GameAPI::Buttons::sortButtons(
&buttons,
"Service",
"Test",
"Coin Mech",
"P1 1",
"P1 2",
"P1 3",
"P1 4",
"P1 5",
"P1 6",
"P1 7",
"P1 TT+",
"P1 TT-",
"P1 TT+/-",
"P1 TT+/- Alternate",
"P1 Start",
"P2 1",
"P2 2",
"P2 3",
"P2 4",
"P2 5",
"P2 6",
"P2 7",
"P2 TT+",
"P2 TT-",
"P2 TT+/-",
"P2 TT+/- Alternate",
"P2 Start",
"EFFECT",
"VEFX",
"P1 Headphone",
"P2 Headphone"
);
}
return buttons;
}
std::string games::iidx::get_buttons_help() {
// keep to max 100 characters wide
return
" P1 EFCT P2 \n"
" VEFX \n"
" 2 4 6 2 4 6 \n"
" TT 1 3 5 7 1 3 5 7 TT \n"
"\n"
"For controllers, use Analog tab for turntable (TT)."
;
}
std::vector<Analog> &games::iidx::get_analogs() {
static std::vector<Analog> analogs;
if (analogs.empty()) {
analogs = GameAPI::Analogs::getAnalogs("Beatmania IIDX");
GameAPI::Analogs::sortAnalogs(
&analogs,
"Turntable P1",
"Turntable P2",
"VEFX",
"Low-EQ",
"Hi-EQ",
"Filter",
"Play Volume"
);
}
return analogs;
}
std::vector<Light> &games::iidx::get_lights() {
static std::vector<Light> lights;
if (lights.empty()) {
lights = GameAPI::Lights::getLights("Beatmania IIDX");
GameAPI::Lights::sortLights(
&lights,
"P1 1",
"P1 2",
"P1 3",
"P1 4",
"P1 5",
"P1 6",
"P1 7",
"P2 1",
"P2 2",
"P2 3",
"P2 4",
"P2 5",
"P2 6",
"P2 7",
"P1 Start",
"P2 Start",
"VEFX",
"Effect",
"Spot Light 1",
"Spot Light 2",
"Spot Light 3",
"Spot Light 4",
"Spot Light 5",
"Spot Light 6",
"Spot Light 7",
"Spot Light 8",
"Neon Lamp",
"Woofer R",
"Woofer G",
"Woofer B",
"IC Card Reader P1 R",
"IC Card Reader P1 G",
"IC Card Reader P1 B",
"IC Card Reader P2 R",
"IC Card Reader P2 G",
"IC Card Reader P2 B",
"Turntable P1 R",
"Turntable P1 G",
"Turntable P1 B",
"Turntable P2 R",
"Turntable P2 G",
"Turntable P2 B",
"Turntable P1 Resistance",
"Turntable P2 Resistance",
"Stage Left Avg R",
"Stage Left Avg G",
"Stage Left Avg B",
"Stage Right Avg R",
"Stage Right Avg G",
"Stage Right Avg B",
"Cabinet Left Avg R",
"Cabinet Left Avg G",
"Cabinet Left Avg B",
"Cabinet Right Avg R",
"Cabinet Right Avg G",
"Cabinet Right Avg B",
"Control Panel Under Avg R",
"Control Panel Under Avg G",
"Control Panel Under Avg B",
"Ceiling Left Avg R",
"Ceiling Left Avg G",
"Ceiling Left Avg B",
"Title Left Avg R",
"Title Left Avg G",
"Title Left Avg B",
"Title Right Avg R",
"Title Right Avg G",
"Title Right Avg B",
"Ceiling Right Avg R",
"Ceiling Right Avg G",
"Ceiling Right Avg B",
"Touch Panel Left Avg R",
"Touch Panel Left Avg G",
"Touch Panel Left Avg B",
"Touch Panel Right Avg R",
"Touch Panel Right Avg G",
"Touch Panel Right Avg B",
"Side Panel Left Inner Avg R",
"Side Panel Left Inner Avg G",
"Side Panel Left Inner Avg B",
"Side Panel Left Outer Avg R",
"Side Panel Left Outer Avg G",
"Side Panel Left Outer Avg B",
"Side Panel Left Avg R",
"Side Panel Left Avg G",
"Side Panel Left Avg B",
"Side Panel Right Outer Avg R",
"Side Panel Right Outer Avg G",
"Side Panel Right Outer Avg B",
"Side Panel Right Inner Avg R",
"Side Panel Right Inner Avg G",
"Side Panel Right Inner Avg B",
"Side Panel Right Avg R",
"Side Panel Right Avg G",
"Side Panel Right Avg B"
);
}
return lights;
}
-900
View File
@@ -1,900 +0,0 @@
#include "games/iidx/local_camera.h"
#if SPICE64
#include "util/logging.h"
#include "util/utils.h"
#include "mf_wrappers.h"
std::string CAMERA_CONTROL_LABELS[] = {
"Pan",
"Tilt",
"Roll",
"Zoom",
"Exposure",
"Iris",
"Focus"
};
std::string DRAW_MODE_LABELS[] = {
"Stretch",
"Crop",
"Letterbox",
"Crop to 4:3",
"Letterbox to 4:3",
};
// static HRESULT printTextureLevelDesc(LPDIRECT3DTEXTURE9 texture) {
// HRESULT hr = S_OK;
// D3DSURFACE_DESC desc;
// hr = texture->GetLevelDesc(0, &desc);
// log_info("iidx:camhook", "Texture Desc Size: {}x{} Res Type: {} Format: {} Usage: {}", desc.Width, desc.Height, (int) desc.Type, (int) desc.Format, (int) desc.Usage);
// return hr;
// }
LONG TARGET_SURFACE_WIDTH = 1280;
LONG TARGET_SURFACE_HEIGHT = 720;
double RATIO_16_9 = 16.0 / 9.0;
double RATIO_4_3 = 4.0 / 3.0;
namespace games::iidx {
IIDXLocalCamera::IIDXLocalCamera(
std::string name,
BOOL prefer_16_by_9,
IMFActivate *pActivate,
IDirect3DDeviceManager9 *pD3DManager,
LPDIRECT3DDEVICE9EX device,
LPDIRECT3DTEXTURE9 *camera_texture_target,
LPDIRECT3DTEXTURE9 *preview_texture_target
):
m_nRefCount(1),
m_name(name),
m_prefer_16_by_9(prefer_16_by_9),
m_device(device),
m_camera_texture_target(camera_texture_target),
m_preview_texture_target(preview_texture_target),
m_camera_texture_original(*camera_texture_target),
m_preview_texture_original(*preview_texture_target)
{
InitializeCriticalSection(&m_critsec);
HRESULT hr = S_OK;
IMFAttributes *pAttributes = nullptr;
EnterCriticalSection(&m_critsec);
log_info("iidx:camhook", "[{}] Creating camera", m_name);
// Get friendly name for log purposes
WCHAR *pwszFriendlyName = nullptr;
UINT32 m_cchFriendlyName = 0;
hr = pActivate->GetAllocatedString(
MF_DEVSOURCE_ATTRIBUTE_FRIENDLY_NAME,
&pwszFriendlyName,
&m_cchFriendlyName
);
if (SUCCEEDED(hr) && pwszFriendlyName != nullptr) {
log_misc("iidx:camhook", "[{}] Name: {}", m_name, ws2s(pwszFriendlyName));
m_friendly_name = ws2s(pwszFriendlyName);
CoTaskMemFree(pwszFriendlyName);
pwszFriendlyName = nullptr;
}
// Retrive symlink of Camera for control configurations
hr = pActivate->GetAllocatedString(
MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE_VIDCAP_SYMBOLIC_LINK,
&m_pwszSymbolicLink,
&m_cchSymbolicLink
);
if (FAILED(hr)) {
log_warning("iidx:camhook", "[{}] GetAllocatedString failed with {:#x}", m_name, (ULONG)hr);
goto done;
}
log_misc("iidx:camhook", "[{}] Symlink: {}", m_name, GetSymLink());
// Create the media source object.
hr = pActivate->ActivateObject(IID_PPV_ARGS(&m_pSource));
if (FAILED(hr)) {
log_warning("iidx:camhook", "[{}] ActivateObject failed with {:#x}", m_name, (ULONG)hr);
goto done;
}
// Retain reference to the camera
m_pSource->AddRef();
log_misc("iidx:camhook", "[{}] Activated", m_name);
// Create an attribute store to hold initialization settings.
hr = WrappedMFCreateAttributes(&pAttributes, 2);
if (FAILED(hr)) {
log_warning("iidx:camhook", "[{}] MFCreateAttributes failed with {:#x}", m_name, (ULONG)hr);
goto done;
}
hr = pAttributes->SetUnknown(MF_SOURCE_READER_D3D_MANAGER, pD3DManager);
if (FAILED(hr)) {
log_warning("iidx:camhook", "[{}] SetUnknown(MF_SOURCE_READER_D3D_MANAGER) failed with {:#x}", m_name, (ULONG)hr);
goto done;
}
hr = pAttributes->SetUINT32(MF_SOURCE_READER_DISABLE_DXVA, FALSE);
if (FAILED(hr)) {
log_warning("iidx:camhook", "[{}] SetUINT32(MF_SOURCE_READER_DISABLE_DXVA) failed with {:#x}", m_name, (ULONG)hr);
goto done;
}
// TODO: Color space conversion
// if (SUCCEEDED(hr)) {
// hr = pAttributes->SetUINT32(MF_SOURCE_READER_ENABLE_ADVANCED_VIDEO_PROCESSING, TRUE);
// }
// if (SUCCEEDED(hr)) {
// hr = pAttributes->SetUINT32(MF_READWRITE_DISABLE_CONVERTERS, FALSE);
// }
// Create the source reader.
hr = WrappedMFCreateSourceReaderFromMediaSource(
m_pSource,
pAttributes,
&m_pSourceReader
);
if (FAILED(hr)) {
log_warning("iidx:camhook", "[{}] MFCreateSourceReaderFromMediaSource failed with {:#x}", m_name, (ULONG)hr);
goto done;
}
log_misc("iidx:camhook", "[{}] Created source reader", m_name);
hr = InitTargetTexture();
if (FAILED(hr)) {
log_warning("iidx:camhook", "[{}] InitTargetTexture failed with {:#x}", m_name, (ULONG)hr);
goto done;
}
// Camera should be still usable even if camera control is not supported
InitCameraControl();
done:
if (SUCCEEDED(hr)) {
m_initialized = true;
log_misc("iidx:camhook", "[{}] Initialized", m_name);
} else {
log_warning("iidx:camhook", "[{}] Failed to create camera: {:#x}", m_name, (ULONG)hr);
}
SafeRelease(&pAttributes);
LeaveCriticalSection(&m_critsec);
}
HRESULT IIDXLocalCamera::StartCapture() {
HRESULT hr = S_OK;
IMFMediaType *pType = nullptr;
if (!m_initialized) {
log_warning("iidx:camhook", "[{}] Camera not initialized", m_name);
return E_FAIL;
}
// Try to find a suitable output type.
log_misc("iidx:camhook", "[{}] Find best media type", m_name);
UINT32 bestWidth = 0;
double bestFrameRate = 0;
// The loop should terminate by MF_E_NO_MORE_TYPES
// Adding a hard limit just in case
for (DWORD i = 0; i < 1000; i++) {
hr = m_pSourceReader->GetNativeMediaType(
(DWORD)MF_SOURCE_READER_FIRST_VIDEO_STREAM,
i,
&pType
);
if (FAILED(hr)) {
if (hr != MF_E_NO_MORE_TYPES) {
log_warning("iidx:camhook", "[{}] Cannot get media type {} {:#x}", m_name, i, (ULONG)hr);
}
break;
}
hr = TryMediaType(pType, &bestWidth, &bestFrameRate);
if (SUCCEEDED(hr)) {
MediaTypeInfo info = GetMediaTypeInfo(pType);
m_mediaTypeInfos.push_back(info);
if (hr == S_OK) {
m_pAutoMediaType = pType;
}
} else {
// Invalid media type (e.g. no conversion function)
SafeRelease(&pType);
}
}
// Sort available media types
std::sort(m_mediaTypeInfos.begin(), m_mediaTypeInfos.end(), [](const MediaTypeInfo &a, const MediaTypeInfo &b) {
if (a.width != b.width) {
return a.width > b.width;
}
if (a.height != b.height) {
return a.height > b.height;
}
if (a.frameRate != b.frameRate) {
return (int)a.frameRate > (int)b.frameRate;
}
return a.subtype.Data1 > b.subtype.Data1;
});
if (!m_pAutoMediaType) {
m_pAutoMediaType = m_mediaTypeInfos.front().p_mediaType;
}
IMFMediaType *pSelectedMediaType = nullptr;
// Find media type specified by user configurations
if (!m_useAutoMediaType && m_selectedMediaTypeDescription.length() > 0) {
log_info("iidx:camhook", "[{}] Use media type from config {}", m_name, m_selectedMediaTypeDescription);
auto it = std::find_if(m_mediaTypeInfos.begin(), m_mediaTypeInfos.end(), [this](const MediaTypeInfo &item){
return item.description.compare(this->m_selectedMediaTypeDescription) == 0;
});
if (it != m_mediaTypeInfos.end()) {
pSelectedMediaType = (*it).p_mediaType;
}
}
hr = S_OK;
if (!pSelectedMediaType) {
pSelectedMediaType = m_pAutoMediaType;
}
if (SUCCEEDED(hr)) {
hr = ChangeMediaType(pSelectedMediaType);
}
if (SUCCEEDED(hr)) {
log_info("iidx:camhook", "[{}] Creating thread", m_name);
CreateThread();
}
return hr;
}
HRESULT IIDXLocalCamera::ChangeMediaType(IMFMediaType *pType) {
HRESULT hr = S_OK;
MediaTypeInfo info = GetMediaTypeInfo(pType);
log_info("iidx:camhook", "[{}] Changing media type: {}", m_name, info.description);
auto it = std::find_if(m_mediaTypeInfos.begin(), m_mediaTypeInfos.end(), [pType](const MediaTypeInfo &item) {
return item.p_mediaType == pType;
});
m_selectedMediaTypeIndex = it - m_mediaTypeInfos.begin();
m_selectedMediaTypeDescription = info.description;
if (SUCCEEDED(hr)) {
hr = m_pSourceReader->SetCurrentMediaType(
(DWORD)MF_SOURCE_READER_FIRST_VIDEO_STREAM,
NULL,
pType
);
}
if (SUCCEEDED(hr)) {
m_cameraWidth = info.width;
m_cameraHeight = info.height;
UpdateDrawRect();
}
return hr;
}
void IIDXLocalCamera::UpdateDrawRect() {
double cameraRatio = (double)m_cameraWidth / m_cameraHeight;
RECT cameraRect = {0, 0, m_cameraWidth, m_cameraHeight};
RECT targetRect = {0, 0, TARGET_SURFACE_WIDTH, TARGET_SURFACE_HEIGHT};
switch (m_drawMode) {
case DrawModeStretch: {
CopyRect(&m_rcSource, &cameraRect);
CopyRect(&m_rcDest, &targetRect);
break;
}
case DrawModeCrop: {
if (cameraRatio > RATIO_16_9) {
// take full source height, crop left/right
LONG croppedWidth = m_cameraHeight * RATIO_16_9;
m_rcSource.left = (LONG)(m_cameraWidth - croppedWidth) / 2;
m_rcSource.top = 0;
m_rcSource.right = m_rcSource.left + croppedWidth;
m_rcSource.bottom = m_cameraHeight;
} else {
// take full source width, crop top/bottom
LONG croppedHeight = m_cameraWidth / RATIO_16_9;
m_rcSource.left = 0;
m_rcSource.top = (LONG)(m_cameraHeight - croppedHeight) / 2;
m_rcSource.right = m_cameraWidth;
m_rcSource.bottom = m_rcSource.top + croppedHeight;
}
CopyRect(&m_rcDest, &targetRect);
break;
}
case DrawModeLetterbox: {
CopyRect(&m_rcSource, &cameraRect);
if (cameraRatio > RATIO_16_9) {
// take full dest width, empty top/bottom
LONG boxedHeight = TARGET_SURFACE_WIDTH / cameraRatio;
m_rcDest.left = 0;
m_rcDest.top = (LONG)(TARGET_SURFACE_HEIGHT - boxedHeight) / 2;
m_rcDest.right = TARGET_SURFACE_WIDTH;
m_rcDest.bottom = m_rcDest.top + boxedHeight;
} else {
// take full dest height, empty top/bottom
LONG boxedWidth = TARGET_SURFACE_HEIGHT * cameraRatio;
m_rcDest.left = (LONG)(TARGET_SURFACE_WIDTH - boxedWidth) / 2;
m_rcDest.top = 0;
m_rcDest.right = m_rcDest.left + boxedWidth;
m_rcDest.bottom = TARGET_SURFACE_HEIGHT;
}
break;
}
case DrawModeCrop4_3: {
if (cameraRatio > RATIO_4_3) {
// take full source height, crop left/right
LONG croppedWidth = m_cameraHeight * RATIO_4_3;
m_rcSource.left = (LONG)(m_cameraWidth - croppedWidth) / 2;
m_rcSource.top = 0;
m_rcSource.right = m_rcSource.left + croppedWidth;
m_rcSource.bottom = m_cameraHeight;
} else {
// take full source width, crop top/bottom
LONG croppedHeight = m_cameraWidth / RATIO_4_3;
m_rcSource.left = 0;
m_rcSource.top = (LONG)(m_cameraHeight - croppedHeight) / 2;
m_rcSource.right = m_cameraWidth;
m_rcSource.bottom = m_rcSource.top + croppedHeight;
}
CopyRect(&m_rcDest, &targetRect);
break;
}
case DrawModeLetterbox4_3: {
CopyRect(&m_rcSource, &cameraRect);
if (cameraRatio > RATIO_4_3) {
// take full dest width, empty top/bottom
LONG boxedHeight = TARGET_SURFACE_HEIGHT / RATIO_4_3;
m_rcDest.left = 0;
m_rcDest.top = (LONG)(TARGET_SURFACE_HEIGHT - boxedHeight) / 2;
m_rcDest.right = TARGET_SURFACE_WIDTH;
m_rcDest.bottom = m_rcDest.top + boxedHeight;
} else {
// take full dest height, empty top/bottom
LONG boxedWidth = TARGET_SURFACE_WIDTH * RATIO_4_3;
m_rcDest.left = (LONG)(TARGET_SURFACE_WIDTH - boxedWidth) / 2;
m_rcDest.top = 0;
m_rcDest.right = m_rcDest.left + boxedWidth;
m_rcDest.bottom = TARGET_SURFACE_HEIGHT;
}
break;
}
}
// ensure the rects are valid
IntersectRect(&m_rcSource, &m_rcSource, &cameraRect);
IntersectRect(&m_rcDest, &m_rcDest, &targetRect);
log_info(
"iidx:camhook", "[{}] Update draw rect mode={} src=({}, {}, {}, {}) dest=({}, {}, {}, {})",
m_name,
DRAW_MODE_LABELS[m_drawMode],
m_rcSource.left,
m_rcSource.top,
m_rcSource.right,
m_rcSource.bottom,
m_rcDest.left,
m_rcDest.top,
m_rcDest.right,
m_rcDest.bottom
);
m_device->ColorFill(m_pDestSurf, &targetRect, D3DCOLOR_XRGB(0, 0, 0));
}
void IIDXLocalCamera::CreateThread() {
// Create thread
m_drawThread = new std::thread([this]() {
SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_BELOW_NORMAL);
double accumulator = 0.0;
while (this->m_active) {
this->Render();
double frameTimeMicroSec = (1000000.0 / this->m_frameRate);
int floorFrameTimeMicroSec = floor(frameTimeMicroSec);
// This maybe an overkill but who knows
accumulator += (frameTimeMicroSec - floorFrameTimeMicroSec);
if (accumulator > 1.0) {
accumulator -= 1.0;
floorFrameTimeMicroSec += 1;
}
std::this_thread::sleep_for(std::chrono::microseconds(floorFrameTimeMicroSec));
}
});
}
LPDIRECT3DTEXTURE9 IIDXLocalCamera::GetTexture() {
return m_texture;
}
IAMCameraControl* IIDXLocalCamera::GetCameraControl() {
return m_pCameraControl;
}
HRESULT IIDXLocalCamera::InitCameraControl() {
HRESULT hr = S_OK;
log_misc("iidx:camhook", "[{}] Init camera control", m_name);
hr = m_pSource->QueryInterface(IID_IAMCameraControl, (void**)&m_pCameraControl);
if (FAILED(hr)) {
// The device does not support IAMCameraControl
log_warning("iidx:camhook", "[{}] Camera control not supported", m_name);
return E_FAIL;
}
for (size_t i = 0; i < CAMERA_CONTROL_PROP_SIZE; i++) {
long minValue = 0;
long maxValue = 0;
long delta = 0;
long defaultValue = 0;
long defFlags = 0;
long value = 0;
long valueFlags = 0;
m_pCameraControl->GetRange(
i,
&minValue,
&maxValue,
&delta,
&defaultValue,
&defFlags
);
m_pCameraControl->Get(
i,
&value,
&valueFlags
);
m_controlProps.push_back({
minValue,
maxValue,
delta,
defaultValue,
defFlags,
value,
valueFlags,
});
CameraControlProp prop = m_controlProps.at(i);
log_misc(
"iidx:camhook", "[{}] >> {} range=({}, {}) default={} delta={} dFlags={} value={} vFlags={}",
m_name,
CAMERA_CONTROL_LABELS[i],
prop.minValue, prop.maxValue,
prop.defaultValue,
prop.delta,
prop.defFlags,
prop.value,
prop.valueFlags
);
}
m_controlOptionsInitialized = true;
return hr;
}
HRESULT IIDXLocalCamera::GetCameraControlProp(int index, CameraControlProp *pProp) {
if (!m_controlOptionsInitialized) {
return E_FAIL;
}
auto targetProp = m_controlProps.at(index);
pProp->minValue = targetProp.minValue;
pProp->maxValue = targetProp.maxValue;
pProp->defaultValue = targetProp.defaultValue;
pProp->delta = targetProp.delta;
pProp->defFlags = targetProp.defFlags;
pProp->value = targetProp.value;
pProp->valueFlags = targetProp.valueFlags;
return S_OK;
}
HRESULT IIDXLocalCamera::SetCameraControlProp(int index, long value, long flags) {
if (!m_controlOptionsInitialized || !m_allowManualControl) {
return E_FAIL;
}
if (index < 0 || index >= CAMERA_CONTROL_PROP_SIZE) {
return E_INVALIDARG;
}
auto targetProp = &(m_controlProps.at(index));
HRESULT hr = m_pCameraControl->Set(index, value, flags);
if (SUCCEEDED(hr)) {
m_pCameraControl->Get(
index,
&targetProp->value,
&targetProp->valueFlags
);
}
return hr;
}
HRESULT IIDXLocalCamera::ResetCameraControlProps() {
log_info("iidx:camhook", "[{}] Reset camera control", m_name);
for (size_t i = 0; i < CAMERA_CONTROL_PROP_SIZE; i++) {
CameraControlProp prop = m_controlProps.at(i);
SetCameraControlProp(i, prop.defaultValue, prop.defFlags);
}
return S_OK;
}
std::string IIDXLocalCamera::GetName() {
return m_name;
}
std::string IIDXLocalCamera::GetFriendlyName() {
return m_friendly_name;
}
std::string IIDXLocalCamera::GetSymLink() {
if (!m_pwszSymbolicLink) {
return "(unknown)";
}
return ws2s(m_pwszSymbolicLink);
}
MediaTypeInfo IIDXLocalCamera::GetMediaTypeInfo(IMFMediaType *pType) {
MediaTypeInfo info = {};
HRESULT hr = S_OK;
MFRatio frameRate = { 0, 0 };
info.p_mediaType = pType;
// Find the video subtype.
hr = pType->GetGUID(MF_MT_SUBTYPE, &info.subtype);
if (FAILED(hr)) { goto done; }
// Get the frame size.
hr = MFGetAttributeSize(pType, MF_MT_FRAME_SIZE, &info.width, &info.height);
if (FAILED(hr)) { goto done; }
// Get frame rate
hr = MFGetAttributeRatio(
pType,
MF_MT_FRAME_RATE,
(UINT32*)&frameRate.Numerator,
(UINT32*)&frameRate.Denominator
);
if (FAILED(hr)) { goto done; }
info.frameRate = frameRate.Numerator / frameRate.Denominator;
info.description = fmt::format(
"{}x{} @{}FPS {}",
info.width,
info.height,
(int)info.frameRate,
GetVideoFormatName(info.subtype)
);
done:
return info;
}
std::string IIDXLocalCamera::GetVideoFormatName(GUID subtype) {
if (subtype == MFVideoFormat_YUY2) {
return "YUY2";
}
if (subtype == MFVideoFormat_NV12) {
return "NV12";
}
if (subtype == MFVideoFormat_MJPG) {
return "MJPG";
}
return "Unknown";
}
/**
* Return values:
* S_OK: this is a "better" media type than the existing one
* S_FALSE: valid media type, but not "better"
* E_*: invalid media type
*/
HRESULT IIDXLocalCamera::TryMediaType(IMFMediaType *pType, UINT32 *pBestWidth, double *pBestFrameRate) {
HRESULT hr = S_OK;
UINT32 width = 0, height = 0;
GUID subtype = { 0, 0, 0, 0 };
MFRatio frameRate = { 0, 0 };
hr = pType->GetGUID(MF_MT_SUBTYPE, &subtype);
if (FAILED(hr)) {
log_warning("iidx:camhook", "[{}] Failed to get subtype: {:#x}", m_name, (ULONG)hr);
return hr;
}
hr = MFGetAttributeSize(pType, MF_MT_FRAME_SIZE, &width, &height);
if (FAILED(hr)) {
log_warning("iidx:camhook", "[{}] Failed to get frame size: {:#x}", m_name, (ULONG)hr);
return hr;
}
// Only support format with converters
// TODO: verify conversion support with DXVA
if (subtype != MFVideoFormat_YUY2 && subtype != MFVideoFormat_NV12) {
return E_FAIL;
}
// Frame rate
hr = MFGetAttributeRatio(
pType,
MF_MT_FRAME_RATE,
(UINT32*)&frameRate.Numerator,
(UINT32*)&frameRate.Denominator
);
if (FAILED(hr)) {
log_warning("iidx:camhook", "[{}] Failed to get frame rate: {:#x}", m_name, (ULONG)hr);
return hr;
}
double frameRateValue = frameRate.Numerator / frameRate.Denominator;
// Filter by aspect ratio
auto aspect_ratio = 4.f / 3.f;
if (m_prefer_16_by_9) {
aspect_ratio = 16.f / 9.f;
}
if (fabs((height * aspect_ratio) - width) > 0.01f) {
return S_FALSE;
}
// If we have 1280x720 already, only try for better frame rate
if ((*pBestWidth >= (UINT32)TARGET_SURFACE_WIDTH) && (width > *pBestWidth) && (frameRateValue < *pBestFrameRate)) {
return S_FALSE;
}
// Check if this format has better resolution / frame rate
if ((width > *pBestWidth) || (width >= (UINT32)TARGET_SURFACE_WIDTH && frameRateValue >= *pBestFrameRate)) {
// log_misc(
// "iidx:camhook", "Better media type {} ({}x{}) @({} FPS)",
// GetVideoFormatName(subtype),
// width,
// height,
// (int)frameRateValue
// );
*pBestWidth = width;
*pBestFrameRate = frameRateValue;
return S_OK;
}
return S_FALSE;
}
HRESULT IIDXLocalCamera::InitTargetTexture() {
HRESULT hr = S_OK;
// Create a new destination texture
hr = m_device->CreateTexture(TARGET_SURFACE_WIDTH, TARGET_SURFACE_HEIGHT, 1, D3DUSAGE_RENDERTARGET, D3DFMT_X8R8G8B8, D3DPOOL_DEFAULT, &m_texture, NULL);
if (FAILED(hr)) { goto done; }
// Create a D3D9 surface for the destination texture so that camera sample can be drawn onto it
hr = m_texture->GetSurfaceLevel(0, &m_pDestSurf);
if (FAILED(hr)) { goto done; }
// Make the game use this new texture as camera stream source
*m_camera_texture_target = m_texture;
*m_preview_texture_target = m_texture;
m_active = TRUE;
// Create texture for colour conversion
hr = m_device->CreateTexture(TARGET_SURFACE_WIDTH, TARGET_SURFACE_HEIGHT, 1, D3DUSAGE_RENDERTARGET, D3DFMT_X8R8G8B8, D3DPOOL_DEFAULT, &m_conversionTexture, NULL);
if (FAILED(hr)) { goto done; }
hr = m_conversionTexture->GetSurfaceLevel(0, &m_pConversionSurf);
if (FAILED(hr)) { goto done; }
// Create texture for transformation
hr = m_device->CreateTexture(TARGET_SURFACE_WIDTH, TARGET_SURFACE_HEIGHT, 1, D3DUSAGE_DYNAMIC, D3DFMT_X8R8G8B8, D3DPOOL_DEFAULT, &m_transformTexture, NULL);
if (FAILED(hr)) { goto done; }
hr = m_transformTexture->GetSurfaceLevel(0, &m_pTransformSurf);
if (FAILED(hr)) { goto done; }
// Create texture for transformation result so that we don't have to screw our brain doing in-memory flipping
hr = m_device->CreateTexture(TARGET_SURFACE_WIDTH, TARGET_SURFACE_HEIGHT, 1, D3DUSAGE_DYNAMIC, D3DFMT_X8R8G8B8, D3DPOOL_DEFAULT, &m_transformResultTexture, NULL);
if (FAILED(hr)) { goto done; }
hr = m_transformResultTexture->GetSurfaceLevel(0, &m_pTransformResultSurf);
if (FAILED(hr)) { goto done; }
// printTextureLevelDesc(m_texture);
// printTextureLevelDesc(m_texture_original);
done:
if (SUCCEEDED(hr)) {
log_misc("iidx:camhook", "[{}] Created texture", m_name);
} else {
log_warning("iidx:camhook", "[{}] Failed to create texture: {:#x}", m_name, (ULONG)hr);
}
return hr;
}
HRESULT IIDXLocalCamera::FlushDrawCommands() {
IDirect3DQuery9* pEventQuery = nullptr;
// It is necessary to flush the command queue
// or the data is not ready for the receiver to read.
// Adapted from : https://msdn.microsoft.com/en-us/library/windows/desktop/bb172234%28v=vs.85%29.aspx
// Also see : http://www.ogre3d.org/forums/viewtopic.php?f=5&t=50486
m_device->CreateQuery(D3DQUERYTYPE_EVENT, &pEventQuery);
if (pEventQuery) {
pEventQuery->Issue(D3DISSUE_END);
while (S_FALSE == pEventQuery->GetData(NULL, 0, D3DGETDATA_FLUSH));
pEventQuery->Release(); // Must be released or causes a leak and reference count increment
}
return S_OK;
}
HRESULT IIDXLocalCamera::DrawSample(IMFMediaBuffer *pSrcBuffer) {
if (!m_active) {
return E_FAIL;
}
// snap variables now so they don't change while inside the critical section
const auto flip_h = m_flipHorizontal;
const auto flip_v = m_flipVertical;
EnterCriticalSection(&m_critsec);
HRESULT hr = S_OK;
IDirect3DSurface9 *pCameraSurf = NULL;
hr = WrappedMFGetService(pSrcBuffer, MR_BUFFER_SERVICE, IID_PPV_ARGS(&pCameraSurf));
if (flip_h || flip_v) {
// Stretch Camera content to texture and perform color space conversion
hr = m_device->StretchRect(pCameraSurf, &m_rcSource, m_pConversionSurf, &m_rcDest, D3DTEXF_LINEAR);
// Copy converted camera content to dynamic texture for vertical/horizontal flipping
hr = m_device->StretchRect(m_pConversionSurf, NULL, m_pTransformSurf, NULL, D3DTEXF_NONE);
// Transform
D3DLOCKED_RECT srcLockedRect;
hr = m_pTransformSurf->LockRect(&srcLockedRect, NULL, D3DLOCK_NOSYSLOCK | D3DLOCK_READONLY);
D3DLOCKED_RECT destLockedRect;
hr = m_pTransformResultSurf->LockRect(&destLockedRect, NULL, D3DLOCK_NOSYSLOCK);
BYTE* pSrc = (BYTE*)srcLockedRect.pBits;
BYTE* pDest = (BYTE*)destLockedRect.pBits;
const int pixelSize = 4;
if (flip_v) {
pDest += destLockedRect.Pitch * (TARGET_SURFACE_HEIGHT - 1);
}
for (int y = 0; y < TARGET_SURFACE_HEIGHT; y++) {
for (int x = 0; x < TARGET_SURFACE_WIDTH; x++) {
memcpy(
pDest + x * pixelSize,
pSrc + (flip_h ? (TARGET_SURFACE_WIDTH - x - 1) : x) * pixelSize,
pixelSize
);
}
pSrc += srcLockedRect.Pitch;
if (flip_v) {
pDest -= destLockedRect.Pitch;
} else {
pDest += destLockedRect.Pitch;
}
}
m_pTransformSurf->UnlockRect();
m_pTransformResultSurf->UnlockRect();
// Stretch camera texture to transform surface
hr = m_device->StretchRect(m_pTransformResultSurf, NULL, m_pDestSurf, NULL, D3DTEXF_NONE);
} else {
// No transformation needed, stretch to destination texture directly
hr = m_device->StretchRect(pCameraSurf, &m_rcSource, m_pDestSurf, &m_rcDest, D3DTEXF_LINEAR);
}
FlushDrawCommands();
if (FAILED(hr)) {
log_warning("iidx:camhook", "Error in DrawSample {:#x}", (ULONG)hr);
}
SafeRelease(&pCameraSurf);
LeaveCriticalSection(&m_critsec);
return hr;
}
HRESULT IIDXLocalCamera::ReadSample() {
HRESULT hr;
DWORD streamIndex, flags;
LONGLONG llTimeStamp;
IMFSample *pSample = nullptr;
IMFMediaBuffer *pBuffer = nullptr;
hr = m_pSourceReader->ReadSample(
MF_SOURCE_READER_FIRST_VIDEO_STREAM,
0,
&streamIndex,
&flags,
&llTimeStamp,
&pSample
);
if (pSample) {
// Draw to D3D
pSample->GetBufferByIndex(0, &pBuffer);
hr = DrawSample(pBuffer);
}
SafeRelease(&pBuffer);
SafeRelease(&pSample);
return hr;
}
LPDIRECT3DTEXTURE9 IIDXLocalCamera::Render() {
if (!m_active) {
return nullptr;
}
HRESULT hr = ReadSample();
if (FAILED(hr)) {
return nullptr;
}
return m_texture;
}
ULONG IIDXLocalCamera::Release() {
log_info("iidx:camhook", "[{}] Release camera", m_name);
m_active = false;
ULONG uCount = InterlockedDecrement(&m_nRefCount);
*m_camera_texture_target = m_camera_texture_original;
*m_preview_texture_target = m_preview_texture_original;
for (size_t i = 0; i < m_mediaTypeInfos.size(); i++) {
SafeRelease(&(m_mediaTypeInfos.at(i).p_mediaType));
}
SafeRelease(&m_pDestSurf);
SafeRelease(&m_pTransformSurf);
SafeRelease(&m_pConversionSurf);
SafeRelease(&m_pTransformResultSurf);
SafeRelease(&m_texture);
SafeRelease(&m_conversionTexture);
SafeRelease(&m_transformTexture);
SafeRelease(&m_transformResultTexture);
if (m_pSource) {
m_pSource->Shutdown();
m_pSource->Release();
}
CoTaskMemFree(m_pwszSymbolicLink);
m_pwszSymbolicLink = NULL;
m_cchSymbolicLink = 0;
if (uCount == 0) {
delete this;
}
// For thread safety, return a temporary variable.
return uCount;
}
}
#endif
-217
View File
@@ -1,217 +0,0 @@
#include "jb.h"
#include <windows.h>
#include <filesystem>
#include "avs/game.h"
#include "cfg/configurator.h"
#include "hooks/graphics/graphics.h"
#include "touch/touch.h"
#include "util/logging.h"
#include "util/utils.h"
#include "util/detour.h"
#include "util/libutils.h"
#define JB_BUTTON_SIZE 160
#define JB_BUTTON_GAP 37
#define JB_BUTTON_HITBOX (JB_BUTTON_SIZE + JB_BUTTON_GAP)
namespace games::jb {
// touch stuff
bool TOUCH_LEGACY_BOX = false;
static bool TOUCH_ENABLE = false;
static bool TOUCH_ATTACHED = false;
static bool IS_PORTRAIT = true;
static std::vector<TouchPoint> TOUCH_POINTS;
bool TOUCH_STATE[16];
void touch_update() {
// check if touch enabled
if (!TOUCH_ENABLE) {
return;
}
// attach touch module
if (!TOUCH_ATTACHED) {
/*
* Find the game window.
* We check the foreground window first, then fall back to searching for the window title
* All game versions seem to have their model first in the window title
*/
HWND wnd = GetForegroundWindow();
if (!string_begins_with(GetActiveWindowTitle(), avs::game::MODEL)) {
wnd = FindWindowBeginsWith(avs::game::MODEL);
}
// check if we have a window handle
if (!wnd) {
log_warning("jubeat", "could not find window handle for touch");
TOUCH_ENABLE = false;
return;
}
// attach touch hook
log_info("jubeat", "using window handle for touch: {}", fmt::ptr(wnd));
touch_create_wnd(wnd, true);
// show cursor
if (GRAPHICS_SHOW_CURSOR) {
ShowCursor(TRUE);
}
// earlier games use a different screen orientation
if (!avs::game::is_model("L44")) {
IS_PORTRAIT = false;
}
// set attached
TOUCH_ATTACHED = true;
}
// reset touch state
memset(TOUCH_STATE, 0, sizeof(TOUCH_STATE));
// check touch points
// note that the IO code in device.cpp will correctly compensate for orientation, depending on the model.
TOUCH_POINTS.clear();
touch_get_points(TOUCH_POINTS);
if (TOUCH_LEGACY_BOX) {
auto offset = IS_PORTRAIT ? 580 : 0;
for (auto &tp : TOUCH_POINTS) {
// get grid coordinates
int x = tp.x * 4 / 768;
int y = (tp.y - offset) * 4 / (1360 - 580);
// set the corresponding state
int index = y * 4 + x;
if (index >= 0 && index < 16) {
TOUCH_STATE[index] = true;
}
}
} else {
for (auto &tp : TOUCH_POINTS) {
int x_relative = tp.x;
int y_relative = tp.y;
// x_relative and y_relative are relative to the top-left pixel of the first button
if (IS_PORTRAIT) {
// which is at (8, 602) in portrait:
// X: [8...759] (752 pixels wide)
// Y: [602...1353] (752 pixels high)
x_relative -= 8;
y_relative -= 602;
} else {
// and at (8, 8) in landscape
x_relative -= 8;
y_relative -= 8;
}
if (x_relative < 0 || y_relative < 0) {
continue;
}
// x_hitbox and y_hitbox is relative to top-left pixel of each button
int x_index = x_relative / JB_BUTTON_HITBOX;
int x_hitbox = x_relative % JB_BUTTON_HITBOX;
int y_index = y_relative / JB_BUTTON_HITBOX;
int y_hitbox = y_relative % JB_BUTTON_HITBOX;
// check if the gap was touched
if (x_hitbox > JB_BUTTON_SIZE || y_hitbox > JB_BUTTON_SIZE) {
continue;
}
// set the corresponding state
int index = y_index * 4 + x_index;
if (0 <= index && index < 16) {
TOUCH_STATE[index] = true;
}
}
}
}
/*
* to fix "IP ADDR CHANGE" errors on boot and in-game when using weird network setups such as a VPN
*/
static BOOL __stdcall network_addr_is_changed() {
return 0;
}
/*
* to fix lag spikes when game tries to ping "eamuse.konami.fun" every few minutes
*/
static BOOL __stdcall network_get_network_check_info() {
return 0;
}
/*
* to fix network error on non DHCP interfaces
*/
static BOOL __cdecl network_get_dhcp_result() {
return 1;
}
static int __cdecl GFDbgSetReportFunc(void *func) {
log_misc("jubeat", "GFDbgSetReportFunc hook hit");
return 0;
}
JBGame::JBGame() : Game("Jubeat") {
}
void JBGame::pre_attach() {
if (!cfg::CONFIGURATOR_STANDALONE) {
const auto current_path = std::filesystem::current_path();
log_misc("jubeat", "current working directory: {}", current_path.string());
if (current_path.parent_path() == current_path.root_path()) {
log_warning(
"jubeat",
"\n\nInvalid path error; jubeat cannot run from a directory in the drive root\n"
"The game will overflow the stack and silently fail to boot\n\n"
"Instead, it must be at least two levels deep, for example:\n"
" c:\\jubeat\\spice.exe <- CRASH\n"
" c:\\jubeat\\contents\\spice.exe <- OK\n\n"
"To fix this, create a new directory and move ALL game files there.\n\n"
"Your current working directory: {}\n",
current_path.string());
log_fatal(
"jubeat",
"Invalid path error; jubeat cannot run from a directory in the drive root");
}
}
}
void JBGame::attach() {
Game::attach();
// enable touch
TOUCH_ENABLE = true;
// enable debug logging of gftools
HMODULE gftools = libutils::try_module("gftools.dll");
detour::inline_hook((void *) GFDbgSetReportFunc, libutils::try_proc(
gftools, "GFDbgSetReportFunc"));
// apply patches
HMODULE network = libutils::try_module("network.dll");
detour::inline_hook((void *) network_addr_is_changed, libutils::try_proc(
network, "network_addr_is_changed"));
detour::inline_hook((void *) network_get_network_check_info, libutils::try_proc(
network, "network_get_network_check_info"));
detour::inline_hook((void *) network_get_dhcp_result, libutils::try_proc(
network, "network_get_dhcp_result"));
}
void JBGame::detach() {
Game::detach();
// disable touch
TOUCH_ENABLE = false;
}
}
-204
View File
@@ -1,204 +0,0 @@
#include "poke.h"
#include <thread>
#include <optional>
#include "games/nost/io.h"
#include "cfg/api.h"
#include "rawinput/rawinput.h"
#include "misc/eamuse.h"
#include "touch/touch.h"
#include "util/logging.h"
namespace games::nost::poke {
static std::thread *THREAD = nullptr;
static volatile bool THREAD_RUNNING = false;
static const std::unordered_map<int, std::pair<LONG, LONG>> NOST_POKE_NUM {
{EAM_IO_KEYPAD_0, {760, 440}},
{EAM_IO_KEYPAD_1, {760, 385}},
{EAM_IO_KEYPAD_2, {820, 385}},
{EAM_IO_KEYPAD_3, {880, 385}},
{EAM_IO_KEYPAD_4, {760, 325}},
{EAM_IO_KEYPAD_5, {820, 325}},
{EAM_IO_KEYPAD_6, {880, 325}},
{EAM_IO_KEYPAD_7, {760, 265}},
{EAM_IO_KEYPAD_8, {820, 265}},
{EAM_IO_KEYPAD_9, {880, 265}},
{EAM_IO_KEYPAD_00, {820, 440}}, // erase button
{EAM_IO_KEYPAD_DECIMAL, {880, 440}} // also erase button
};
static const std::unordered_map<games::nost::Buttons::Button, std::pair<LONG, LONG>> NOST_POKE {
{games::nost::Buttons::Button::PokeConfirm, {1100, 525}},
{games::nost::Buttons::Button::PokeBack, {340, 100}},
{games::nost::Buttons::Button::PokeSong1, {450, 190}},
{games::nost::Buttons::Button::PokeSong2, {450, 330}},
{games::nost::Buttons::Button::PokeSong3, {450, 480}},
{games::nost::Buttons::Button::PokeSong4, {840, 190}},
{games::nost::Buttons::Button::PokeSong5, {840, 330}},
{games::nost::Buttons::Button::PokeSong6, {840, 480}},
{games::nost::Buttons::Button::PokeDifficulty1, {820, 240}},
{games::nost::Buttons::Button::PokeDifficulty2, {820, 320}},
{games::nost::Buttons::Button::PokeDifficulty3, {820, 410}},
{games::nost::Buttons::Button::PokeDifficulty4, {820, 490}},
};
void clear_touch_points(std::vector<TouchPoint> *touch_points) {
std::vector<DWORD> touch_ids;
for (auto &touch : *touch_points) {
touch_ids.emplace_back(touch.id);
}
touch_remove_points(&touch_ids);
touch_points->clear();
}
void enable() {
// check if already running
if (THREAD) {
return;
}
// create new thread
THREAD_RUNNING = true;
THREAD = new std::thread([] {
const DWORD touch_id = (DWORD)(0xFFFFFFFE);
const int swipe_anim_total_frames = 6;
const int swipe_anim_y = 300;
const int swipe_next_page_x_begin = 1120;
const int swipe_next_page_x_end = 1120-120;
const int swipe_prev_page_x_begin = 280-120;
const int swipe_prev_page_x_end = 280;
int next_page_anim_index = -1;
int prev_page_anim_index = -1;
std::vector<TouchPoint> touch_points;
bool touch_release = false;
// log
log_info("poke", "enabled");
// set variable to false to stop
while (THREAD_RUNNING) {
// clean up touch from last frame
if (!touch_points.empty()) {
if (touch_release) {
clear_touch_points(&touch_points);
touch_release = false;
} else {
touch_points.clear();
}
}
auto &buttons = games::nost::get_buttons();
if (0 <= next_page_anim_index) {
const auto delta =
(swipe_next_page_x_end - swipe_next_page_x_begin)
* (swipe_anim_total_frames - next_page_anim_index) / swipe_anim_total_frames;
TouchPoint tp {
.id = touch_id,
.x = (LONG)swipe_next_page_x_begin + delta,
.y = (LONG)swipe_anim_y,
.mouse = true,
};
touch_points.emplace_back(tp);
next_page_anim_index--;
if (next_page_anim_index < 0) {
touch_release = true;
}
} else if (0 <= prev_page_anim_index) {
const auto delta =
(swipe_prev_page_x_end - swipe_prev_page_x_begin)
* (swipe_anim_total_frames - prev_page_anim_index) / swipe_anim_total_frames;
TouchPoint tp {
.id = touch_id,
.x = (LONG)swipe_prev_page_x_begin + delta,
.y = (LONG)swipe_anim_y,
.mouse = true,
};
touch_points.emplace_back(tp);
prev_page_anim_index--;
if (prev_page_anim_index < 0) {
touch_release = true;
}
} else {
for (const auto& it : NOST_POKE) {
if (GameAPI::Buttons::getState(RI_MGR, buttons.at(it.first))) {
TouchPoint tp {
.id = touch_id,
.x = it.second.first,
.y = it.second.second,
.mouse = true,
};
touch_points.emplace_back(tp);
touch_release = true;
break;
}
}
if (!touch_release) {
const auto state = eamuse_get_keypad_state(0);
if (state) {
for (const auto& it : NOST_POKE_NUM) {
if (state & (1 << it.first)) {
TouchPoint tp {
.id = touch_id,
.x = it.second.first,
.y = it.second.second,
.mouse = true,
};
touch_points.emplace_back(tp);
touch_release = true;
break;
}
}
}
}
// start animations for next frame
if (!touch_release) {
if (GameAPI::Buttons::getState(RI_MGR, buttons.at(games::nost::Buttons::PokeNextPage))) {
next_page_anim_index = swipe_anim_total_frames;
}
if (GameAPI::Buttons::getState(RI_MGR, buttons.at(games::nost::Buttons::PokePrevPage))) {
prev_page_anim_index = swipe_anim_total_frames;
}
}
}
if (!touch_points.empty()) {
touch_write_points(&touch_points);
}
// slow down
Sleep(30);
}
return nullptr;
});
}
void disable() {
if (!THREAD) {
return;
}
// stop old thread
THREAD_RUNNING = false;
THREAD->join();
// delete thread
delete THREAD;
THREAD = nullptr;
// log
log_info("poke", "disabled");
}
}
-27
View File
@@ -1,27 +0,0 @@
#include "otoca.h"
#include "p4io.h"
#include "games/shared/printer.h"
#include "util/libutils.h"
namespace games::otoca {
OtocaGame::OtocaGame() : Game("Otoca D'or") {
}
OtocaGame::~OtocaGame() {
}
void OtocaGame::attach() {
Game::attach();
/*
* arkkep.dll uses LoadLibrary to access game.dll
* we preload it so that hooks (e.g. for graphics) will work
*/
libutils::try_library("game.dll");
// initialize IO hooks
p4io_hook();
games::shared::printer_attach();
}
}
-105
View File
@@ -1,105 +0,0 @@
#include "io.h"
std::vector<Button> &games::popn::get_buttons() {
static std::vector<Button> buttons;
if (buttons.empty()) {
buttons = GameAPI::Buttons::getButtons("Pop'n Music");
GameAPI::Buttons::sortButtons(
&buttons,
"Service",
"Test",
"Coin Mech",
"Button 1",
"Button 2",
"Button 3",
"Button 4",
"Button 5",
"Button 6",
"Button 7",
"Button 8",
"Button 9"
);
}
return buttons;
}
std::string games::popn::get_buttons_help() {
// keep to max 100 characters wide
return
" 2 4 6 8 \n"
" 1 3 5 7 9 "
;
}
std::vector<Light> &games::popn::get_lights() {
static std::vector<Light> lights;
if (lights.empty()) {
lights = GameAPI::Lights::getLights("Pop'n Music");
GameAPI::Lights::sortLights(
&lights,
"Button 1",
"Button 2",
"Button 3",
"Button 4",
"Button 5",
"Button 6",
"Button 7",
"Button 8",
"Button 9",
"Top LED 1",
"Top LED 2",
"Top LED 3",
"Top LED 4",
"Top LED 5",
"Top LED 6",
"Top LED 7",
"Top LED 8",
"Top LED 9",
"Top LED 10",
"Top LED 11",
"Top LED 12",
"Top LED 13",
"Top LED 14",
"Top LED 15",
"Top LED 16",
"Top LED 17",
"Top LED 18",
"Top LED 19",
"Top LED 20",
"Top LED 21",
"Top LED 22",
"Top LED 23",
"Top LED 24",
"Top LED 25",
"Top LED 26",
"Top LED 27",
"Top LED 28",
"Top LED 29",
"Top LED 30",
"Top LED 31",
"Top LED 32",
"Top LED R",
"Top LED G",
"Top LED B",
"Hi Lamp 1",
"Hi Lamp 2",
"Hi Lamp 3",
"Hi Lamp 4",
"Hi Lamp 5",
"Left Lamp 1",
"Left Lamp 2",
"Right Lamp 1",
"Right Lamp 2",
"Woofer LED R",
"Woofer LED G",
"Woofer LED B"
);
}
return lights;
}
-324
View File
@@ -1,324 +0,0 @@
#include "avs/game.h"
#include "popn.h"
#include <cstdint>
#include <cstring>
#include "rawinput/rawinput.h"
#include "util/detour.h"
#include "util/fileutils.h"
#include "util/libutils.h"
#include "util/utils.h"
#include "cfg/button.h"
#include "cfg/api.h"
#include "hooks/sleephook.h"
#include "launcher/launcher.h"
#include "launcher/logger.h"
#include "misc/eamuse.h"
#include "io.h"
namespace games::popn {
static int __cdecl usbCheckAlive() {
return 1;
}
static int __cdecl usbCheckSecurityNew() {
return 0;
}
static int __cdecl usbCoinGet() {
return eamuse_coin_get_stock();
}
static int __cdecl usbCoinMode() {
return 0;
}
static int __cdecl usbEnd() {
return 0;
}
static int __cdecl usbFirmResult() {
return 0;
}
static int __cdecl usbGetKEYID() {
return 0;
}
static int __cdecl usbGetSecurity() {
return 0;
}
static int __cdecl usbLamp(uint32_t lamp_bits) {
// get lights
auto &lights = get_lights();
// bit scan
static const size_t mapping[] = {
Lights::Button1,
Lights::Button2,
Lights::Button3,
Lights::Button4,
Lights::Button5,
Lights::Button6,
Lights::Button7,
Lights::Button8,
Lights::Button9,
Lights::HiLamp1,
Lights::HiLamp2,
Lights::HiLamp3,
Lights::HiLamp4,
Lights::HiLamp5,
Lights::LeftLamp1,
Lights::LeftLamp2,
Lights::RightLamp1,
Lights::RightLamp2,
};
// sw lamps
for (size_t i = 0; i < 9; i++) {
float value = (lamp_bits & (1 << (i + 23))) ? 1.f : 0.f;
GameAPI::Lights::writeLight(RI_MGR, lights.at(mapping[i]), value);
}
// neon lamps
for (size_t i = 0; i < 5; i++) {
float value = (lamp_bits & (1 << i)) ? 1.f : 0.f;
GameAPI::Lights::writeLight(RI_MGR, lights.at(mapping[i + 9]), value);
}
// screen lamps
for (size_t i = 0; i < 4; i++) {
float value = (lamp_bits & (1 << (i + 8))) ? 1.f : 0.f;
GameAPI::Lights::writeLight(RI_MGR, lights.at(mapping[i + 14]), value);
}
// flush output
RI_MGR->devices_flush_output();
// return no error
return 0;
}
static int __cdecl usbPadRead(unsigned int *pad_bits) {
// get buttons
auto &buttons = get_buttons();
// reset
*pad_bits = 0;
if (GameAPI::Buttons::getState(RI_MGR, buttons.at(Buttons::Service))) {
*pad_bits |= 1 << 6;
}
if (GameAPI::Buttons::getState(RI_MGR, buttons.at(Buttons::Test))) {
*pad_bits |= 1 << 7;
}
if (GameAPI::Buttons::getState(RI_MGR, buttons.at(Buttons::CoinMech))) {
*pad_bits |= 1 << 22;
}
// buttons
static const size_t mapping[] = {
Buttons::Button1,
Buttons::Button2,
Buttons::Button3,
Buttons::Button4,
Buttons::Button5,
Buttons::Button6,
Buttons::Button7,
Buttons::Button8,
Buttons::Button9,
};
for (size_t i = 0; i < 9; i++) {
if (GameAPI::Buttons::getState(RI_MGR, buttons.at(mapping[i]))) {
*pad_bits |= 1 << (8 + i);
}
}
// return no error
return 0;
}
static int __cdecl usbPadReadLast(uint8_t *a1) {
memset(a1, 0, 40);
return 0;
}
static int __cdecl usbSecurityInit() {
return 0;
}
static int __cdecl usbSecurityInitDone() {
return 0;
}
static int __cdecl usbSecuritySearch() {
return 0;
}
static int __cdecl usbSecuritySearchDone() {
return 0;
}
static int __cdecl usbSecuritySelect() {
return 0;
}
static int __cdecl usbSecuritySelectDone() {
return 0;
}
static int __cdecl usbSetExtIo(uint32_t rgb_bits) {
// get lights
auto &lights = get_lights();
// bit scan
static const size_t mapping[] = {
Lights::WooferLED_R,
Lights::WooferLED_G,
Lights::WooferLED_B,
};
for (size_t i = 0; i < 3; i++) {
float value = (rgb_bits & (1 << (i + 1)));
GameAPI::Lights::writeLight(RI_MGR, lights.at(mapping[i]), value);
}
// flush output
RI_MGR->devices_flush_output();
return 0;
}
static int __cdecl usbStart() {
return 0;
}
static int __cdecl usbWdtReset() {
return 0;
}
static int __cdecl usbWdtStart() {
return 0;
}
static int __cdecl usbWdtStartDone() {
return 0;
}
static decltype(DeleteFileA)* DeleteFileA_real = nullptr;
static BOOL WINAPI DeleteFileA_hook(LPCSTR lpFileName) {
// fantasia deletes a bunch of drmfs files in dll_entry_init
if (string_begins_with(lpFileName, "D:\\L39\\contents")) {
return true;
}
return DeleteFileA_real(lpFileName);
}
static decltype(mixerSetControlDetails)* mixerSetControlDetails_real = nullptr;
static MMRESULT WINAPI mixerSetControlDetails_hook(HMIXEROBJ hmxobj, LPMIXERCONTROLDETAILS pmxcd, DWORD fdwDetails) {
mixerSetControlDetails_real(hmxobj, pmxcd, fdwDetails);
return MMSYSERR_NOERROR;
}
static bool log_hook(void *user, const std::string &data, logger::Style style, std::string &out) {
// get rid of the log spam
if (data.find(" I:CabinetSettingFile: label:") != std::string::npos) {
out.clear();
return true;
} else {
return false;
}
}
POPNGame::POPNGame() : Game("Pop'n Music") {
logger::hook_add(log_hook, this);
}
POPNGame::~POPNGame() {
logger::hook_remove(log_hook, this);
}
void POPNGame::pre_attach() {
Game::pre_attach();
// only needed for older versions (19-21, inclusive) but create them anyway since 21 and above are all M39
fileutils::dir_create_recursive_log("popn", "dev\\raw\\bookkeeping");
fileutils::dir_create_recursive_log("popn", "dev\\raw\\ranking");
fileutils::dir_create_recursive_log("popn", "dev\\raw\\settings");
// load without resolving references
// makes game not trigger DLLMain which results in an error due to missing EZUSB device
LoadLibraryExW((MODULE_PATH / "ezusb.dll").c_str(), nullptr, DONT_RESOLVE_DLL_REFERENCES);
}
void POPNGame::attach() {
Game::attach();
/*
* Fast Boot (TM) Patch
* Game tries to create some directories and if it fails it will sleep for 1 second
* We make it sleep 1ms each time instead
*/
hooks::sleep::init(999, 1);
DeleteFileA_real = detour::iat_try("DeleteFileA", DeleteFileA_hook, avs::game::DLL_INSTANCE);
mixerSetControlDetails_real = detour::iat_try("mixerSetControlDetails", mixerSetControlDetails_hook, avs::game::DLL_INSTANCE);
// EZUSB hooks
HINSTANCE ezusb = libutils::try_library("ezusb.dll");
if (ezusb) {
detour::inline_hook((void *) usbCheckAlive,
libutils::try_proc(ezusb, "?usbCheckAlive@@YAHXZ"));
detour::inline_hook((void *) usbCheckSecurityNew,
libutils::try_proc(ezusb, "?usbCheckSecurityNew@@YAHH@Z"));
detour::inline_hook((void *) usbCoinGet,
libutils::try_proc(ezusb, "?usbCoinGet@@YAHH@Z"));
detour::inline_hook((void *) usbCoinMode,
libutils::try_proc(ezusb, "?usbCoinMode@@YAHH@Z"));
detour::inline_hook((void *) usbEnd,
libutils::try_proc(ezusb, "?usbEnd@@YAHXZ"));
detour::inline_hook((void *) usbFirmResult,
libutils::try_proc(ezusb, "?usbFirmResult@@YAHXZ"));
detour::inline_hook((void *) usbGetKEYID,
libutils::try_proc(ezusb, "?usbGetKEYID@@YAHPAEH@Z"));
detour::inline_hook((void *) usbGetSecurity,
libutils::try_proc(ezusb, "?usbGetSecurity@@YAHHPAE@Z"));
detour::inline_hook((void *) usbLamp,
libutils::try_proc(ezusb, "?usbLamp@@YAHH@Z"));
detour::inline_hook((void *) usbPadRead,
libutils::try_proc(ezusb, "?usbPadRead@@YAHPAK@Z"));
detour::inline_hook((void *) usbPadReadLast,
libutils::try_proc(ezusb, "?usbPadReadLast@@YAHPAE@Z"));
detour::inline_hook((void *) usbSecurityInit,
libutils::try_proc(ezusb, "?usbSecurityInit@@YAHXZ"));
detour::inline_hook((void *) usbSecurityInitDone,
libutils::try_proc(ezusb, "?usbSecurityInitDone@@YAHXZ"));
detour::inline_hook((void *) usbSecuritySearch,
libutils::try_proc(ezusb, "?usbSecuritySearch@@YAHXZ"));
detour::inline_hook((void *) usbSecuritySearchDone,
libutils::try_proc(ezusb, "?usbSecuritySearchDone@@YAHXZ"));
detour::inline_hook((void *) usbSecuritySelect,
libutils::try_proc(ezusb, "?usbSecuritySelect@@YAHH@Z"));
detour::inline_hook((void *) usbSecuritySelectDone,
libutils::try_proc(ezusb, "?usbSecuritySelectDone@@YAHXZ"));
detour::inline_hook((void *) usbSetExtIo,
libutils::try_proc(ezusb, "?usbSetExtIo@@YAHH@Z"));
detour::inline_hook((void *) usbStart,
libutils::try_proc(ezusb, "?usbStart@@YAHH@Z"));
detour::inline_hook((void *) usbWdtReset,
libutils::try_proc(ezusb, "?usbWdtReset@@YAHXZ"));
detour::inline_hook((void *) usbWdtStart,
libutils::try_proc(ezusb, "?usbWdtStart@@YAHH@Z"));
detour::inline_hook((void *) usbWdtStartDone,
libutils::try_proc(ezusb, "?usbWdtStartDone@@YAHXZ"));
}
}
}
-14
View File
@@ -1,14 +0,0 @@
#pragma once
#include "games/game.h"
namespace games::popn {
class POPNGame : public games::Game {
public:
POPNGame();
~POPNGame() override;
virtual void pre_attach() override;
virtual void attach() override;
};
}
-56
View File
@@ -1,56 +0,0 @@
#include "rb.h"
#include "avs/game.h"
#include "hooks/devicehook.h"
#include "hooks/audio/backends/dsound/dsound_backend.h"
#include "util/detour.h"
#include "touch.h"
static decltype(SleepEx) *SleepEx_orig;
static DWORD WINAPI SleepEx_hook(DWORD dwMilliseconds, BOOL bAltertable) {
/*
* Increase touch poll from ~110 FPS to ~500 FPS
*/
if (dwMilliseconds == 8) {
static bool initialized = false;
if (!initialized) {
initialized = true;
// if we only sleep for 1ms we also don't need the high priority RB sets
SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_BELOW_NORMAL);
}
dwMilliseconds = 1;
}
// call original
return SleepEx_orig(dwMilliseconds, bAltertable);
}
games::rb::RBGame::RBGame() : Game("Reflec Beat") {
}
void games::rb::RBGame::attach() {
Game::attach();
// init stuff
devicehook_init();
// add touch device
devicehook_add(new ReflecBeatTouchDeviceHandle(false));
if (avs::game::is_model({"KBR", "LBR"})) {
// dsound.dll hook
audio_dsound_init();
}
// hooks
SleepEx_orig = detour::iat_try("SleepEx", SleepEx_hook, avs::game::DLL_INSTANCE);
}
void games::rb::RBGame::detach() {
Game::detach();
devicehook_dispose();
}
-142
View File
@@ -1,142 +0,0 @@
#include "io.h"
std::vector<Button> &games::sdvx::get_buttons() {
static std::vector<Button> buttons;
if (buttons.empty()) {
buttons = GameAPI::Buttons::getButtons("Sound Voltex");
GameAPI::Buttons::sortButtons(
&buttons,
"Service",
"Test",
"Coin Mech",
"BT-A",
"BT-B",
"BT-C",
"BT-D",
"FX-L",
"FX-R",
"Start",
"VOL-L Left",
"VOL-L Right",
"VOL-R Left",
"VOL-R Right",
"Headphone"
);
}
return buttons;
}
std::string games::sdvx::get_buttons_help() {
// keep to max 100 characters wide
return
" Start\n"
"\n"
" VOL-L VOL_R\n"
"\n"
" BT-A BT-B BT-C BT-D\n"
" FX-L FX-R \n"
"\n"
"For controllers, use Analog tab for knobs."
;
}
std::vector<Analog> &games::sdvx::get_analogs() {
static std::vector<Analog> analogs;
if (analogs.empty()) {
analogs = GameAPI::Analogs::getAnalogs("Sound Voltex");
GameAPI::Analogs::sortAnalogs(
&analogs,
"VOL-L",
"VOL-R"
);
}
return analogs;
}
std::vector<Light> &games::sdvx::get_lights() {
static std::vector<Light> lights;
if (lights.empty()) {
lights = GameAPI::Lights::getLights("Sound Voltex");
GameAPI::Lights::sortLights(
&lights,
"BT-A",
"BT-B",
"BT-C",
"BT-D",
"FX-L",
"FX-R",
"Start",
"Wing Left Up R",
"Wing Left Up G",
"Wing Left Up B",
"Wing Right Up R",
"Wing Right Up G",
"Wing Right Up B",
"Wing Left Low R",
"Wing Left Low G",
"Wing Left Low B",
"Wing Right Low R",
"Wing Right Low G",
"Wing Right Low B",
"Woofer R",
"Woofer G",
"Woofer B",
"Controller R",
"Controller G",
"Controller B",
"Generator R",
"Generator G",
"Generator B",
"Pop",
"Title Left",
"Title Right",
"Volume Sound",
"Volume Headphone",
"Volume External",
"Volume Woofer",
"IC Card Reader R",
"IC Card Reader G",
"IC Card Reader B",
"Title Avg R",
"Title Avg G",
"Title Avg B",
"Upper Left Speaker Avg R",
"Upper Left Speaker Avg G",
"Upper Left Speaker Avg B",
"Upper Right Speaker Avg R",
"Upper Right Speaker Avg G",
"Upper Right Speaker Avg B",
"Left Wing Avg R",
"Left Wing Avg G",
"Left Wing Avg B",
"Right Wing Avg R",
"Right Wing Avg G",
"Right Wing Avg B",
"Lower Left Speaker Avg R",
"Lower Left Speaker Avg G",
"Lower Left Speaker Avg B",
"Lower Right Speaker Avg R",
"Lower Right Speaker Avg G",
"Lower Right Speaker Avg B",
"Control Panel Avg R",
"Control Panel Avg G",
"Control Panel Avg B",
"Woofer Avg R",
"Woofer Avg G",
"Woofer Avg B",
"V Unit Avg R",
"V Unit Avg G",
"V Unit Avg B"
);
}
return lights;
}
-120
View File
@@ -1,120 +0,0 @@
#include "audio.h"
#include <vector>
#include <windows.h>
#include <initguid.h>
#include <audioclient.h>
#include <mmdeviceapi.h>
#include "hooks/audio/backends/mmdevice/device_enumerator.h"
#include "util/detour.h"
#include "util/logging.h"
#include "util/memutils.h"
#include "audio_private.h"
#include "acm.h"
#ifdef _MSC_VER
DEFINE_GUID(CLSID_MMDeviceEnumerator,
0xBCDE0395, 0xE52F, 0x467C,
0x8E, 0x3D, 0xC4, 0x57, 0x92, 0x91, 0x69, 0x2E);
#endif
// {ef985e71-d5c7-42d4-ba4d-2d073e2e96f4}
DEFINE_GUID(CLSID_DirectSoundI3DL2ReverbDMO,
0xef985e71, 0xd5c7, 0x42d4,
0xba, 0x4d, 0x2d, 0x07, 0x3e, 0x2e, 0x96, 0xf4);
// {87fc0268-9a55-4360-95aa-004a1d9de26c}
DEFINE_GUID(CLSID_DirectSoundWavesReverbDMO,
0x87fc0268, 0x9a55, 0x4360,
0x95, 0xaa, 0x00, 0x4a, 0x1d, 0x9d, 0xe2, 0x6c);
// function pointers
static decltype(CoCreateInstance) *CoCreateInstance_orig = nullptr;
namespace hooks::audio {
// public globals
bool ENABLED = true;
bool VOLUME_HOOK_ENABLED = true;
bool USE_DUMMY = false;
WAVEFORMATEXTENSIBLE FORMAT {};
std::optional<Backend> BACKEND = std::nullopt;
size_t ASIO_DRIVER_ID = 0;
bool ASIO_FORCE_UNLOAD_ON_STOP = false;
// private globals
IAudioClient *CLIENT = nullptr;
std::mutex INITIALIZE_LOCK; // for asio
}
static HRESULT STDAPICALLTYPE CoCreateInstance_hook(
REFCLSID rclsid,
LPUNKNOWN pUnkOuter,
DWORD dwClsContext,
REFIID riid,
LPVOID *ppv)
{
HRESULT ret;
// call original
ret = CoCreateInstance_orig(rclsid, pUnkOuter, dwClsContext, riid, ppv);
if (FAILED(ret)) {
if (IsEqualCLSID(rclsid, CLSID_MMDeviceEnumerator)) {
log_warning("audio", "CoCreateInstance failed for CLSID_MMDeviceEnumerator, hr={}", FMT_HRESULT(ret));
}
// Windows 11 KB5052093 issue - reverb DMO went missing from dsdmo.dll (fails with 0x80040154)
if (IsEqualCLSID(rclsid, CLSID_DirectSoundI3DL2ReverbDMO) && ret == (HRESULT)0x80040154) {
log_warning(
"audio",
"CoCreateInstance for CLSID_DirectSoundI3DL2ReverbDMO failed with 0x80040154, swap with CLSID_DirectSoundWavesReverbDMO "
"(workaround for Windows 11 KB5052093 issue); REVERB EX replaced with REVERB");
ret = CoCreateInstance_orig(CLSID_DirectSoundWavesReverbDMO, pUnkOuter, dwClsContext, riid, ppv);
if (FAILED(ret)) {
log_warning("audio", "CoCreateInstance(CLSID_DirectSoundWavesReverbDMO...) failed, hr={}", FMT_HRESULT(ret));
}
}
return ret;
}
// check if this is the audio device enumerator
if (IsEqualCLSID(rclsid, CLSID_MMDeviceEnumerator)) {
// wrap object
auto mmde = reinterpret_cast<IMMDeviceEnumerator **>(ppv);
*mmde = new WrappedIMMDeviceEnumerator(*mmde);
}
// return original result
return ret;
}
namespace hooks::audio {
void init() {
if (!ENABLED) {
return;
}
log_info("audio", "initializing");
init_low_latency();
hooks::audio::acm::init();
// general hooks
CoCreateInstance_orig = detour::iat_try("CoCreateInstance", CoCreateInstance_hook);
}
void stop() {
log_info("audio", "stopping");
if (CLIENT) {
CLIENT->Stop();
CLIENT->Release();
CLIENT = nullptr;
}
stop_low_latency();
}
}
-45
View File
@@ -1,45 +0,0 @@
#pragma once
#include <optional>
#include <windows.h>
#include <mmreg.h>
#ifndef _MSC_VER
#include <ks.h>
#include <ksmedia.h>
#endif
namespace hooks::audio {
enum class Backend {
Asio,
WaveOut,
Pipewire,
None
};
extern bool ENABLED;
extern bool VOLUME_HOOK_ENABLED;
extern bool USE_DUMMY;
extern WAVEFORMATEXTENSIBLE FORMAT;
extern std::optional<Backend> BACKEND;
extern size_t ASIO_DRIVER_ID;
extern bool ASIO_FORCE_UNLOAD_ON_STOP;
extern bool LOW_LATENCY_SHARED_WASAPI;
void init();
void stop();
inline std::optional<Backend> name_to_backend(const char *value) {
if (_stricmp(value, "asio") == 0) {
return Backend::Asio;
} else if (_stricmp(value, "waveout") == 0) {
return Backend::WaveOut;
} else if (_stricmp(value, "pipewire") == 0) {
return Backend::Pipewire;
} else if (_stricmp(value, "none") == 0) {
return Backend::None;
}
return std::nullopt;
}
}
@@ -1,88 +0,0 @@
#include "audio_render_client.h"
#include "audio_client.h"
#include "wasapi_private.h"
const char CLASS_NAME[] = "WrappedIAudioRenderClient";
HRESULT STDMETHODCALLTYPE WrappedIAudioRenderClient::QueryInterface(REFIID riid, void **ppvObj) {
if (ppvObj == nullptr) {
return E_POINTER;
}
if (riid == IID_WrappedIAudioRenderClient ||
riid == IID_IAudioRenderClient)
{
this->AddRef();
*ppvObj = this;
return S_OK;
}
return pReal->QueryInterface(riid, ppvObj);
}
ULONG STDMETHODCALLTYPE WrappedIAudioRenderClient::AddRef() {
return pReal->AddRef();
}
ULONG STDMETHODCALLTYPE WrappedIAudioRenderClient::Release() {
// get reference count of underlying interface
ULONG refs = pReal != nullptr ? pReal->Release() : 0;
if (refs == 0) {
delete this;
}
return refs;
}
// IAudioRenderClient
HRESULT STDMETHODCALLTYPE WrappedIAudioRenderClient::GetBuffer(UINT32 NumFramesRequested, BYTE **ppData) {
static std::once_flag printed;
std::call_once(printed, []() {
log_misc("audio::wasapi", "WrappedIAudioRenderClient::GetBuffer");
});
if (this->client->backend) {
SAFE_CALL("AudioBackend", "on_get_buffer", this->client->backend->on_get_buffer(
NumFramesRequested,
ppData));
return S_OK;
}
// call original
HRESULT ret = pReal->GetBuffer(NumFramesRequested, ppData);
// store buffer reference
if (SUCCEEDED(ret)) {
this->audio_buffer = *ppData;
}
CHECK_RESULT(ret);
}
HRESULT STDMETHODCALLTYPE WrappedIAudioRenderClient::ReleaseBuffer(UINT32 NumFramesWritten, DWORD dwFlags) {
static std::once_flag printed;
std::call_once(printed, []() {
log_misc("audio::wasapi", "WrappedIAudioRenderClient::ReleaseBuffer");
});
if (this->client->backend) {
SAFE_CALL("AudioBackend", "on_release_buffer", this->client->backend->on_release_buffer(
NumFramesWritten,
dwFlags));
return S_OK;
}
// fix for audio pop effect
if (this->buffers_to_mute > 0 && this->client->frame_size > 0) {
// zero out = mute
memset(this->audio_buffer, 0, NumFramesWritten * this->client->frame_size);
this->buffers_to_mute--;
}
CHECK_RESULT(pReal->ReleaseBuffer(NumFramesWritten, dwFlags));
}
-20
View File
@@ -1,20 +0,0 @@
#include "util.h"
#include <audioclient.h>
#include "util/flags_helper.h"
#include "defs.h"
std::string stream_flags_str(DWORD flags) {
FLAGS_START(flags);
FLAG(flags, AUDCLNT_STREAMFLAGS_CROSSPROCESS);
FLAG(flags, AUDCLNT_STREAMFLAGS_LOOPBACK);
FLAG(flags, AUDCLNT_STREAMFLAGS_EVENTCALLBACK);
FLAG(flags, AUDCLNT_STREAMFLAGS_NOPERSIST);
FLAG(flags, AUDCLNT_STREAMFLAGS_RATEADJUST);
FLAG(flags, AUDCLNT_STREAMFLAGS_PREVENT_LOOPBACK_CAPTURE);
FLAG(flags, AUDCLNT_STREAMFLAGS_AUTOCONVERTPCM);
FLAG(flags, AUDCLNT_STREAMFLAGS_SRC_DEFAULT_QUALITY);
FLAGS_END(flags);
}
-8
View File
@@ -1,8 +0,0 @@
#pragma once
#include <string>
#include <windows.h>
#include <mmreg.h>
std::string stream_flags_str(DWORD flags);
-99
View File
@@ -1,99 +0,0 @@
#include "none.h"
#include "hooks/audio/audio.h"
#include "hooks/audio/backends/wasapi/audio_client.h"
const WAVEFORMATEXTENSIBLE &NoneBackend::format() const noexcept {
return format_;
}
HRESULT NoneBackend::on_initialize(
AUDCLNT_SHAREMODE *ShareMode,
DWORD *StreamFlags,
REFERENCE_TIME *hnsBufferDuration,
REFERENCE_TIME *hnsPeriodicity,
const WAVEFORMATEX *pFormat,
LPCGUID AudioSessionGuid) noexcept
{
*ShareMode = AUDCLNT_SHAREMODE_SHARED;
*StreamFlags = AUDCLNT_STREAMFLAGS_EVENTCALLBACK |
AUDCLNT_STREAMFLAGS_RATEADJUST |
AUDCLNT_STREAMFLAGS_AUTOCONVERTPCM |
AUDCLNT_STREAMFLAGS_SRC_DEFAULT_QUALITY;
*hnsBufferDuration = 100000;
*hnsPeriodicity = 100000;
log_info("audio::none", "on_initialize");
return S_OK;
}
HRESULT NoneBackend::on_get_buffer_size(uint32_t *buffer_frames) noexcept {
*buffer_frames = 0;
return S_OK;
}
HRESULT NoneBackend::on_get_stream_latency(REFERENCE_TIME *latency) noexcept {
*latency = 100000;
return S_OK;
}
HRESULT NoneBackend::on_get_current_padding(std::optional<uint32_t> &padding_frames) noexcept {
padding_frames = 0;
return S_OK;
}
HRESULT NoneBackend::on_is_format_supported(
AUDCLNT_SHAREMODE *ShareMode,
const WAVEFORMATEX *pFormat,
WAVEFORMATEX **ppClosestMatch) noexcept
{
// only accept 44.1 kHz, stereo, 16-bits per channel
if (*ShareMode == AUDCLNT_SHAREMODE_EXCLUSIVE &&
pFormat->nChannels == 2 &&
pFormat->nSamplesPerSec == 44100 &&
pFormat->wBitsPerSample == 16)
{
return S_OK;
}
return AUDCLNT_E_UNSUPPORTED_FORMAT;
}
HRESULT NoneBackend::on_get_mix_format(WAVEFORMATEX **pp_device_format) noexcept {
return E_NOTIMPL;
}
HRESULT NoneBackend::on_get_device_period(
REFERENCE_TIME *default_device_period,
REFERENCE_TIME *minimum_device_period)
{
*default_device_period = 10000;
*minimum_device_period = 10000;
return S_OK;
}
HRESULT NoneBackend::on_start() noexcept {
return S_OK;
}
HRESULT NoneBackend::on_stop() noexcept {
return S_OK;
}
HRESULT NoneBackend::on_set_event_handle(HANDLE *event_handle) {
*event_handle = CreateEvent(nullptr, true, false, nullptr);
return S_OK;
}
HRESULT NoneBackend::on_get_buffer(uint32_t num_frames_requested, BYTE **ppData) {
static BYTE buf[10000];
*ppData = buf;
return S_OK;
}
HRESULT NoneBackend::on_release_buffer(uint32_t num_frames_written, DWORD dwFlags) {
return S_OK;
}
NoneBackend::NoneBackend() : format_(hooks::audio::FORMAT)
{
}
-48
View File
@@ -1,48 +0,0 @@
#pragma once
#include "backend.h"
struct NoneBackend final : AudioBackend {
public:
explicit NoneBackend();
~NoneBackend() final = default;
[[nodiscard]] const WAVEFORMATEXTENSIBLE &format() const noexcept override;
HRESULT on_initialize(
AUDCLNT_SHAREMODE *ShareMode,
DWORD *StreamFlags,
REFERENCE_TIME *hnsBufferDuration,
REFERENCE_TIME *hnsPeriodicity,
const WAVEFORMATEX *pFormat,
LPCGUID AudioSessionGuid) noexcept override;
HRESULT on_get_buffer_size(uint32_t *buffer_frames) noexcept override;
HRESULT on_get_stream_latency(REFERENCE_TIME *latency) noexcept override;
HRESULT on_get_current_padding(std::optional<uint32_t> &padding_frames) noexcept override;
HRESULT on_is_format_supported(
AUDCLNT_SHAREMODE *ShareMode,
const WAVEFORMATEX *pFormat,
WAVEFORMATEX **ppClosestMatch) noexcept override;
HRESULT on_get_mix_format(WAVEFORMATEX **pp_device_format) noexcept override;
HRESULT on_get_device_period(
REFERENCE_TIME *default_device_period,
REFERENCE_TIME *minimum_device_period) override;
HRESULT on_start() noexcept override;
HRESULT on_stop() noexcept override;
HRESULT on_set_event_handle(HANDLE *event_handle) override;
HRESULT on_get_buffer(uint32_t num_frames_requested, BYTE **ppData) override;
HRESULT on_release_buffer(uint32_t num_frames_written, DWORD dwFlags) override;
private:
const WAVEFORMATEXTENSIBLE &format_;
BYTE *active_sound_buffer = nullptr;
};
-300
View File
@@ -1,300 +0,0 @@
#include <winternl.h>
#include "pipewire.h"
#include "hooks/audio/audio.h"
#include "hooks/audio/backends/wasapi/audio_client.h"
#include "util/libutils.h"
#include "launcher/launcher.h"
#include "hooks/audio/util.h"
#include "hooks/audio/backends/wasapi/util.h"
/*
... ShareMode : AUDCLNT_SHAREMODE_EXCLUSIVE <- backend to reimplement, THIS is the audio engine, GAME is client https://learn.microsoft.com/en-us/windows/win32/coreaudio/audclnt-streamflags-xxx-constants
... StreamFlags : AUDCLNT_STREAMFLAGS_EVENTCALLBACK | AUDCLNT_STREAMFLAGS_NOPERSIST <- first is the whole reason for relay_handle_ requirement, second flag not tested
... hnsBufferDuration : 10000 <- wasapi people inventing new time units, 1ms (since 1unit==100ns, 10000*0.0001ms)
... hnsPeriodicity : 10000 <- same as above (1ms)
... nChannels : 2 <- channel_count
... nSamplesPerSec : 44100 <- bitrate
... nAvgBytesPerSec : 176400 <- raw buffer size per second (bitrate * stride)(bytes)
... nBlockAlign : 4 <- stride (bytes)
... wBitsPerSample : 16 <- sample size (bits)
... wValidBitsPerSample : 16 <- same as above
... dwChannelMask : SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT <- position config
_INFO: Code pipeline
INIT: on_is_format_supported()?->on_initialize()->on_set_event_handle()-:(LOOPx1):->on_start()
LOOP: if relay_handle_: on_get_buffer_size()->on_get_buffer()->..->on_release_buffer()
DEINIT: on_stop()
_REV: POSSIBLE ALTERNATIVES:
Check if the stream is driving. The stream needs to have the
* PW_STREAM_FLAG_DRIVER set. When the stream is driving,
* pw_stream_trigger_process() needs to be called when data is
* available (output) or needed (input). Since 0.3.34
bool pw_stream_is_driving(struct pw_stream *stream);
* */
/* Imports/Exports (refer to bmsound-wine.dll.spec) */
typedef void (*BmswConfigInit_t)(const char *);
typedef void (*BmswExperimentalForceProfile_t)(const char *);
typedef int(*BmswClientFormatIsSupported_t)(DWORD, DWORD, DWORD, void *);
typedef int(*BmswClientFormatPeriodFPC_t)(void *);
typedef REFERENCE_TIME(*BmswClientFormatPeriodWRT_t)(void *);
typedef void *(*BmswClientCreate_t)(const char *, void *, void *);
typedef int(*BmswClientStart_t)(void *);
typedef int(*BmswClientStop_t)(void *);
typedef int(*BmswClientDestroy_t)(void *);
typedef unsigned char *(*BmswClientGetBuffer_t)(void *, uint32_t);
typedef int(*BmswClientReleaseBuffer_t)(void *, uint32_t);
typedef int(*BmswClientAwaitBuffer_t)(void *);
typedef void (*BmswClientUpdateCallback_t)(void *, void *, void *);
static BmswConfigInit_t BmswConfigInit;
[[maybe_unused]] static BmswExperimentalForceProfile_t BmswExperimentalForceProfile;
static BmswClientFormatIsSupported_t BmswClientFormatIsSupported;
static BmswClientFormatPeriodFPC_t BmswClientFormatPeriodFPC;
static BmswClientFormatPeriodWRT_t BmswClientFormatPeriodWRT;
static BmswClientCreate_t BmswClientCreate;
static BmswClientStart_t BmswClientStart;
static BmswClientStop_t BmswClientStop;
static BmswClientDestroy_t BmswClientDestroy;
static BmswClientGetBuffer_t BmswClientGetBuffer;
static BmswClientReleaseBuffer_t BmswClientReleaseBuffer;
static BmswClientAwaitBuffer_t BmswClientAwaitBuffer;
[[maybe_unused]] static BmswClientUpdateCallback_t BmswClientUpdateCallback;
static HMODULE bmsw_ = nullptr;
/* Audio init (unless specified otherwise, run once at start) */
// Reports to game whether each requested audio format is available for device (first success return will be used)
HRESULT PipewireBackend::on_is_format_supported(AUDCLNT_SHAREMODE *ShareMode, const WAVEFORMATEX *pFormat, WAVEFORMATEX **ppClosestMatch) noexcept
{
// Format reporting and filtering
log_info("audio::pipewire", "{}", __FUNCTION__);
log_misc("audio::pipewire", "Checking backend support for {} channels, {} Hz, {}-bit",
pFormat->nChannels,
pFormat->nSamplesPerSec,
pFormat->wBitsPerSample);
// IIDX? will always request format that was last checked through this function
if (*ShareMode != AUDCLNT_SHAREMODE_EXCLUSIVE) return AUDCLNT_E_UNSUPPORTED_FORMAT;
// Request format support from real backend (only accepts 44.1 kHz, stereo, 16-bits per channel for now)
return BmswClientFormatIsSupported(pFormat->nSamplesPerSec, pFormat->nChannels, pFormat->wBitsPerSample, nullptr) == 0 ? S_OK : AUDCLNT_E_UNSUPPORTED_FORMAT;
}
// Populate hnsPeriodicity, _INFO: runs before on_initialize, requires configured client stream data
HRESULT PipewireBackend::on_get_device_period(REFERENCE_TIME *default_device_period, REFERENCE_TIME *minimum_device_period)
{
log_info("audio::pipewire", "{}", __FUNCTION__);
*default_device_period = wrt_;
*minimum_device_period = wrt_;
return S_OK;
}
// This expects to be run once on_is_format_supported() succeeds, initializes backend based on passed arguments (and updates them if needed)
HRESULT PipewireBackend::on_initialize(AUDCLNT_SHAREMODE *ShareMode, DWORD *StreamFlags, REFERENCE_TIME *hnsBufferDuration, REFERENCE_TIME *hnsPeriodicity, const WAVEFORMATEX *pFormat, LPCGUID AudioSessionGuid) noexcept
{
log_info("audio::pipewire", "{}", __FUNCTION__);
// Initialize pipewire client (without starting the thread)
client_ = BmswClientCreate(GAME_INSTANCE->title(), nullptr, nullptr);
notif_ = std::thread(PipewireBackend::notif_poll, this);
if (!client_)
log_fatal("audio::pipewire", "Client could not be initialized");
log_info("audio::pipewire", "Client initialized: '{}'", fmt::ptr(client_));
// Adjust WASAPI configuration visible to game (passed arguments are populated and should match that of last on_is_format_supported call)
*hnsBufferDuration = wrt_;
*hnsPeriodicity = wrt_;
//_TODO: Init info
log_info("audio::pipewire", "Device Info:");
log_info("audio::pipewire", "... hnsBufferDuration : {}", *hnsBufferDuration);
log_info("audio::pipewire", "... hnsPeriodicity : {}", *hnsPeriodicity);
return S_OK;
}
// This takes ownership over shared event handle exclusive to AUDCLNT_STREAMFLAGS_EVENTCALLBACK _INFO: first loop iteration runs directly after this, before on_start() call
HRESULT PipewireBackend::on_set_event_handle(HANDLE *event_handle)
{
log_info("audio::pipewire", "{}", __FUNCTION__);
// Take over WASAPI's owned handle pre-initialized by client and replace with always off dummy, cleaning up previously owned handle
if (relay_handle_) CloseHandle(relay_handle_);
relay_handle_ = *event_handle;
*event_handle = CreateEvent(nullptr, true, false, nullptr);
//BmswClientUpdateCallback(client_, (void *) 0, relay_handle_);
return S_OK;
ULONG ntbuf_size = sizeof(OBJECT_NAME_INFORMATION) + 1024;
char ntbuf[ntbuf_size];
OBJECT_NAME_INFORMATION *ntinfo = (OBJECT_NAME_INFORMATION *) ntbuf;
NTSTATUS status = NtQueryObject(*event_handle, ObjectNameInformation, ntinfo, ntbuf_size, &ntbuf_size);
if (NT_SUCCESS(status))
{
// possible to rename anonymous handle to access by name
}
}
HRESULT PipewireBackend::on_start() noexcept
{
log_info("audio::pipewire", "{}", __FUNCTION__);
BmswClientStart(client_);
return S_OK;
}
PipewireBackend::PipewireBackend() : relay_handle_(CreateEvent(nullptr, true, false, nullptr)), format_(hooks::audio::FORMAT), client_(nullptr), notif_state_(-1)
{
log_info("audio::pipewire", "{}", __FUNCTION__);
// Initialize bmsound-wine.dll once
if (!bmsw_ && (bmsw_ = libutils::try_library(MODULE_PATH / "bmsound-wine.dll")))
{
BmswConfigInit = (BmswConfigInit_t) GetProcAddress(bmsw_, "BmswConfigInit");
BmswExperimentalForceProfile = (BmswExperimentalForceProfile_t) GetProcAddress(bmsw_, "BmswExperimentalForceProfile");
BmswClientFormatIsSupported = (BmswClientFormatIsSupported_t) GetProcAddress(bmsw_, "BmswClientFormatIsSupported");
BmswClientFormatPeriodWRT = (BmswClientFormatPeriodWRT_t) GetProcAddress(bmsw_, "BmswClientFormatPeriodWRT");
BmswClientFormatPeriodFPC = (BmswClientFormatPeriodFPC_t) GetProcAddress(bmsw_, "BmswClientFormatPeriodFPC");
BmswClientCreate = (BmswClientCreate_t) GetProcAddress(bmsw_, "BmswClientCreate");
BmswClientStart = (BmswClientStart_t) GetProcAddress(bmsw_, "BmswClientStart");
BmswClientStop = (BmswClientStop_t) GetProcAddress(bmsw_, "BmswClientStop");
BmswClientDestroy = (BmswClientDestroy_t) GetProcAddress(bmsw_, "BmswClientDestroy");
BmswClientGetBuffer = (BmswClientGetBuffer_t) GetProcAddress(bmsw_, "BmswClientGetBuffer");
BmswClientReleaseBuffer = (BmswClientReleaseBuffer_t) GetProcAddress(bmsw_, "BmswClientReleaseBuffer");
BmswClientAwaitBuffer = (BmswClientAwaitBuffer_t) GetProcAddress(bmsw_, "BmswClientAwaitBuffer");
BmswClientUpdateCallback = (BmswClientUpdateCallback_t) GetProcAddress(bmsw_, "BmswClientUpdateCallback");
// Load config
BmswConfigInit("prop/linux.json");
//BmswExperimentalForceProfile("notif_spice");
}
if (bmsw_)
{
// Sync config
wrt_ = BmswClientFormatPeriodWRT(nullptr); //_INFO: wrt affects reported latency (1:100ns)
fpc_ = BmswClientFormatPeriodFPC(nullptr);
return;
}
log_fatal("audio::pipewire", "Library not found: '{}'", (MODULE_PATH / "bmsound-wine.dll").string());
}
/* Audio callback loop (reacts to state of relay_handle_, most likely separate thread */
// _REV: Synchronizing client and backend with nanotime may be necessary (bmsound-pw endpoint dependant)
inline static void callback_notify(void *self)
{
//log_info("audio::pipewire", "{}", __FUNCTION__);
auto *self_ = (PipewireBackend *) self;
//std::this_thread::sleep_for(std::chrono::nanoseconds(10 * 1000 * 1000 - 227));
if (!SetEvent(self_->relay_handle_)) // has to be called for game-side audio loop to continue
{
DWORD last_error = GetLastError();
log_warning("audio::pipewire", "SetEvent failed({}): {}",
last_error,
std::system_category().message(last_error));
}
}
void PipewireBackend::notif_poll(PipewireBackend *self)
{
self->notif_state_ = BmswClientAwaitBuffer(self->client_);
while (self->notif_state_ == 1)
{
BmswClientAwaitBuffer(self->client_);
callback_notify(self);
}
self->notif_state_ = -1;
}
// Amount in frames of data we may handle at once (which should always end up being what gets send by client, unless overrun happens)
HRESULT PipewireBackend::on_get_buffer_size(uint32_t *buffer_frames) noexcept
{
static int iterc = -1;
if (iterc == -1)
{
log_info("audio::pipewire", "{}, frames: {} (INITIAL HIT)", __FUNCTION__, fpc_);
iterc = 0;
}
*buffer_frames = fpc_;
return S_OK;
}
// Wants a raw stream buffer to be assigned into *ppData, this stream will have x amount of sound frames stored into it by a client and assumes exclusive access until next on_release_buffer
HRESULT PipewireBackend::on_get_buffer(uint32_t num_frames_requested, BYTE **ppData)
{
static int iterc = -1;
if (iterc == -1)
{
log_info("audio::pipewire", "{}, frames: {} (INITIAL HIT)", __FUNCTION__, num_frames_requested);
}
iterc++;
if (iterc > 999999)
{
log_info("audio::pipewire", "on_get_buffer, frames: {} (HIT {})", num_frames_requested, iterc);
iterc = 0;
}
*ppData = BmswClientGetBuffer(client_, num_frames_requested);
return S_OK;
}
// This releases access to buffer from last on_get_buffer and implies x amount of frames being valid data to be streamed
HRESULT PipewireBackend::on_release_buffer(uint32_t num_frames_written, DWORD dwFlags)
{
static int iterc = -1;
if (iterc == -1)
{
log_info("audio::pipewire", "{}, frames: {} (INITIAL HIT)", __FUNCTION__, num_frames_written);
iterc = 0;
}
BmswClientReleaseBuffer(client_, num_frames_written);
if (notif_state_ == -1) callback_notify(this);
return S_OK;
}
/* Audio deinit (unless specified otherwise, run once at termination) */
HRESULT PipewireBackend::on_stop() noexcept
{
log_info("audio::pipewire", "{}", __FUNCTION__);
BmswClientStop(client_);
return S_OK;
}
PipewireBackend::~PipewireBackend()
{
log_info("audio::pipewire", "~PipewireBackend");
if (notif_state_ == 1)
{
notif_state_ = 0;
notif_.join();
if (notif_state_ != -1)
{
log_warning("audio::pipewire", "Errors during thread cleanup: '{}'", (const int) notif_state_);
}
}
if (client_) BmswClientDestroy(client_);
if (relay_handle_) CloseHandle(relay_handle_);
}
/* Unsorted (unknown callers, should be considered as unimplemented/untested) *///_BUG: does this need implementation?
const WAVEFORMATEXTENSIBLE &PipewireBackend::format() const noexcept
{
log_info("audio::pipewire", "{}", __FUNCTION__);
return format_;
}
HRESULT PipewireBackend::on_get_stream_latency(REFERENCE_TIME *latency) noexcept
{
// log_info("audio::pipewire", "{}", __FUNCTION__);
*latency = BmswClientFormatPeriodWRT(client_);
return S_OK;
}
HRESULT PipewireBackend::on_get_current_padding(std::optional<uint32_t> &padding_frames) noexcept
{
// log_info("audio::pipewire", "{}", __FUNCTION__);
padding_frames = 0;
return S_OK;
}
HRESULT PipewireBackend::on_get_mix_format(WAVEFORMATEX **pp_device_format) noexcept
{
log_info("audio::pipewire", "{}", __FUNCTION__);
return E_NOTIMPL;
}
-38
View File
@@ -1,38 +0,0 @@
#pragma once
#include "backend.h"
#include <thread>
struct PipewireBackend final : AudioBackend
{
public:
HANDLE relay_handle_;
explicit PipewireBackend();
~PipewireBackend() final;
[[nodiscard]] const WAVEFORMATEXTENSIBLE &format() const noexcept override;
HRESULT on_initialize(AUDCLNT_SHAREMODE *ShareMode, DWORD *StreamFlags, REFERENCE_TIME *hnsBufferDuration, REFERENCE_TIME *hnsPeriodicity, const WAVEFORMATEX *pFormat, LPCGUID AudioSessionGuid) noexcept override;
HRESULT on_get_buffer_size(uint32_t *buffer_frames) noexcept override;
HRESULT on_get_stream_latency(REFERENCE_TIME *latency) noexcept override;
HRESULT on_get_current_padding(std::optional<uint32_t> &padding_frames) noexcept override;
HRESULT on_is_format_supported(AUDCLNT_SHAREMODE *ShareMode, const WAVEFORMATEX *pFormat, WAVEFORMATEX **ppClosestMatch) noexcept override;
HRESULT on_get_mix_format(WAVEFORMATEX **pp_device_format) noexcept override;
HRESULT on_get_device_period(REFERENCE_TIME *default_device_period, REFERENCE_TIME *minimum_device_period) override;
HRESULT on_start() noexcept override;
HRESULT on_stop() noexcept override;
HRESULT on_set_event_handle(HANDLE *event_handle) override;
HRESULT on_get_buffer(uint32_t num_frames_requested, BYTE **ppData) override;
HRESULT on_release_buffer(uint32_t num_frames_written, DWORD dwFlags) override;
static void notif_poll(PipewireBackend *self);
private:
int fpc_;
REFERENCE_TIME wrt_;
const WAVEFORMATEXTENSIBLE &format_;
void *client_;
std::thread notif_;
volatile int notif_state_;
};
-224
View File
@@ -1,224 +0,0 @@
#include "develhook.h"
#include "util/logging.h"
#include "util/detour.h"
#include "util/libutils.h"
#include "util/utils.h"
#include "util/sigscan.h"
#include "avs/core.h"
#include "avs/game.h"
#include "hooks/wmischook.h"
#include "external/stackwalker/stackwalker.h"
#include <vector>
#include <string>
#include <sstream>
namespace hooks::devel
{
static class Thumbnail : public Routine
{
public:
using Routine::Routine;
void entry_main() override
{
log_warning("hooks::devel::thumbnail", "Forcing thumbnail patch..");
hooks::wmisc::apply_performance_hacks(avs::game::DLL_INSTANCE, "FORCE", "thumbnail");
}
} thumbnail_("wmisc_thum");
static class Msacm : public Routine
{
public:
using Routine::Routine;
void entry_main() override
{
log_warning("hooks::devel::msacm", "Forcing msacm patch..");
hooks::wmisc::apply_performance_hacks(avs::game::DLL_INSTANCE, "FORCE", "msacm");
}
} msacm_("wmisc_acm");
static class DmoEffector : public Routine
{
public:
using Routine::Routine;
void entry_main() override
{
log_warning("hooks::devel::msacm", "Forcing msacm patch..");
hooks::wmisc::apply_performance_hacks(avs::game::DLL_INSTANCE, "FORCE", "dmoeffector");
}
} dmoeffector_("wmisc_dmoeffector");
static class Wmisc : public Routine
{
public:
using Routine::Routine;
void attach() override
{
log_warning("hooks::devel::wmisc", "Disabling auto-patching..");
hooks::wmisc::skip_patching_by_model(true);
}
void entry_main() override
{
// Reenable logging
hooks::wmisc::apply_performance_hacks(avs::game::DLL_INSTANCE, "FORCE", "bmsound");
}
} wmisc_("disable_auto_wmisc");
static class Avs_fs : public Routine
{
private:
static constexpr char needle_[] = ""; // set to "" to print all
inline static avs::core::AVS_FS_LSTAT_T avs_fs_lstat_;
inline static avs::core::AVS_FS_MOUNT_T avs_fs_mount_;
inline static avs::core::AVS_FS_OPEN_T avs_fs_open_;
static int on_avs_fs_lstat(const char *path, struct avs::core::avs_stat *stat)
{
if (strstr(path, needle_))
{
log_info("hooks::devel", "{}({})", __FUNCTION__, path);
}
return avs_fs_lstat_(path, stat);
}
static avs::core::avs_file_t on_avs_fs_mount(const char *mountpoint, const char *fsroot, const char *fstype, void *data)
{
if (strstr(fsroot, needle_))
{
log_info("hooks::devel", "{}({}=>{}) @{} :nullptr data?{} ", __FUNCTION__, fsroot, mountpoint, fstype, data == nullptr);
}
return avs_fs_mount_(mountpoint, fsroot, fstype, data);
}
static avs::core::avs_file_t on_avs_fs_open(const char *name, uint16_t mode, int flags)
{
if (strstr(name, needle_))
{
log_info("hooks::devel", "{}({}::{}) @{}", __FUNCTION__, name, mode, flags);
}
return avs_fs_open_(name, mode, flags);
}
public:
using Routine::Routine;
void entry_main() override
{
detour::trampoline(reinterpret_cast<void *>(avs::core::avs_fs_lstat), reinterpret_cast<void *>(on_avs_fs_lstat), reinterpret_cast<void **>(&avs_fs_lstat_));
detour::trampoline(reinterpret_cast<void *>(avs::core::avs_fs_mount), reinterpret_cast<void *>(on_avs_fs_mount), reinterpret_cast<void **>(&avs_fs_mount_));
detour::trampoline(reinterpret_cast<void *>(avs::core::avs_fs_open), reinterpret_cast<void *>(on_avs_fs_open), reinterpret_cast<void **>(&avs_fs_open_));
}
} avs_fs_("avs_fs_detour");
static class CMoviePlayer : public Routine
{
private:
typedef void (*GetWmvChunk_t)(uint8_t *param_1);
inline static GetWmvChunk_t GetWmvChunk_;
static void on_get_wmv_chunk_(uint8_t *param_1)
{
static FILE *fp = nullptr;
if (!fp)
{
fp = fopen("data/movie/testbg2_fhd.wmv", "rb");
if (!fp)
log_warning("hooks::devel::CMoviePlayer", "Missing wmv file");
}
// Read from file (skip AVS)
if (fp)
{
int chunksz = *(uint32_t *) (param_1 + 0x10);
log_misc("hooks::devel::CMoviePlayer", "Reading {} bytes", chunksz);
int readsz = fread((void *) *(intptr_t *) (param_1 + 0x08), chunksz, 1, fp);
*(int *) (param_1 + 0x14) = readsz;
if(readsz < chunksz)
{
fclose(fp);
fp = nullptr;
}
}
}
public:
using Routine::Routine;
void entry_main() override
{
log_warning("hooks::devel", "Test code for SDVX5::CMoviePlayer only");
intptr_t TX_ThumExists = find_pattern_from(avs::game::DLL_INSTANCE, "40534883ec20448b4110488bd9488b51088b09ff150000000089431485c0791d448bc0488d1500000000488d0d000000004883c4205b48ff25000000004883c4205bc3", "XXXXXXXXXXXXXXXXXXXXX????XXXXXXXXXXXXX????XXX????XXXXXXXX????XXXXXX", 0, 0, 0);
if (!detour::trampoline_try(reinterpret_cast<void *>(TX_ThumExists), reinterpret_cast<void *>(on_get_wmv_chunk_), reinterpret_cast<void **>(&GetWmvChunk_)))
{
log_fatal("hooks::devel", "Hook failed");
}
}
} cmovieplayer_("wmisc_cmovieplayer");
std::vector<std::string> from_env(const char *str)
{
std::vector<std::string> r;
std::stringstream ss(getenv(str) ? getenv(str) : "");
std::string flag;
while (std::getline(ss, flag, ';'))
{
r.push_back(flag);
}
return r;
}
static std::vector<Routine *> &routines()
{
static std::vector<Routine *> routines_;
if (routines_.empty())
{
//Default routines
routines_.push_back(&avs_fs_);
routines_.push_back(&thumbnail_);
routines_.push_back(&msacm_);
routines_.push_back(&wmisc_);
routines_.push_back(&cmovieplayer_);
routines_.push_back(&dmoeffector_);
}
return routines_;
}
Routine *active_routine(const char *name)
{
unsigned int hash = to_hash(name);
for (auto &routine: routines())
{
if (routine->hash() == hash)
return routine;
}
return nullptr;
}
void bind_routine(Routine *routine)
{
routines().push_back(routine);
}
void log_environment()
{
log_warning("hooks::devel", "WINEDLLOVERRIDES: '{}'", getenv("WINEDLLOVERRIDES") ? getenv("WINEDLLOVERRIDES") : "null");
}
void entry_main()
{
log_environment();
const auto rnames = from_env("SPICE_DEVEL");
if (!rnames.empty()) log_info("hooks::devel", "Run subroutines..");
for (const auto &routinename: from_env("SPICE_DEVEL"))
{
Routine *routine = active_routine(routinename.c_str());
if (routine) routine->entry_main();
}
}
void attach()
{
const auto rnames = from_env("SPICE_DEVEL");
if (!rnames.empty()) log_info("hooks::devel", "Attach subroutines..");
for (const auto &rname: rnames)
{
Routine *routine = active_routine(rname.c_str());
if (routine) routine->attach();
}
}
}
-25
View File
@@ -1,25 +0,0 @@
#pragma once
#include <cstdint>
#include "util/utils.h"
namespace hooks::devel
{
//_INFO: use attach()/entry_main() to run task before game::attach()/game::entry_main()
class Routine
{
private:
unsigned int hash_;
public:
[[nodiscard]] inline unsigned int constexpr hash() const { return hash_; }
virtual void attach() {}
virtual void entry_main() {}
explicit Routine(const char *name) : hash_(to_hash(name)) {}
virtual ~Routine() = default;
};
Routine *active_routine(const char *name);
void bind_routine(Routine *routine);
void attach();
void entry_main();
}
-59
View File
@@ -1,59 +0,0 @@
#include "external/nvapi/nvapi.h"
#include "external/nvapi/NvApiDriverSettings.h"
#include "hooks/libraryhook.h"
#include "util/detour.h"
#include "util/libutils.h"
#include "util/logging.h"
#include "nvapi_hook.h"
namespace nvapi_hook {
bool BYPASS_NVAPI = false;
typedef uintptr_t *(*NvAPI_QueryInterface_t)(unsigned int);
static NvAPI_QueryInterface_t NvAPI_QueryInterface_orig = nullptr;
static uintptr_t* __cdecl NvAPI_QueryInterface_hook(unsigned int func_code);
static NvAPI_Status __cdecl NvAPI_DISP_SetDisplayConfig_hook(
NvU32 pathInfoCount, NV_DISPLAYCONFIG_PATH_INFO *pathInfo, NvU32 flags);
void initialize(HINSTANCE dll) {
#ifdef SPICE64
std::string nvapi_dll = "nvapi64.dll";
#else
std::string nvapi_dll = "nvapi.dll";
#endif
detour::trampoline_try(
nvapi_dll.c_str(), "nvapi_QueryInterface",
NvAPI_QueryInterface_hook, &NvAPI_QueryInterface_orig);
}
uintptr_t* __cdecl NvAPI_QueryInterface_hook(unsigned int func_code) {
if (BYPASS_NVAPI) {
log_misc(
"nvapi_hook",
"NvAPI_QueryInterface(0x{:x}) - block all calls to nvapi (-nonvapi)",
func_code);
return nullptr;
}
// NvAPI_DISP_SetDisplayConfig
if (func_code == 0x5D8CF8DE) {
log_misc("nvapi_hook", "NvAPI_QueryInterface(NvAPI_DISP_SetDisplayConfig) - hooked");
return (uintptr_t *)NvAPI_DISP_SetDisplayConfig_hook;
}
// all others: let the game call nvapi directly
log_misc("nvapi_hook", "NvAPI_QueryInterface(0x{:x}) - pass through to nvapi", func_code);
return NvAPI_QueryInterface_orig(func_code);
}
NvAPI_Status __cdecl NvAPI_DISP_SetDisplayConfig_hook(
NvU32 pathInfoCount, NV_DISPLAYCONFIG_PATH_INFO *pathInfo, NvU32 flags) {
log_misc("nvapi_hook", "NvAPI_DISP_SetDisplayConfig_hook - do nothing and return");
return NVAPI_OK;
}
}
-971
View File
@@ -1,971 +0,0 @@
#include "sndbhook.h"
#include "hooks/develhook.h"
#include "util/logging.h"
#include "util/memutils.h"
#include "util/sigscan.h"
#include "util/detour.h"
#include "util/libutils.h"
#include "util/utils.h"
#include "avs/core.h"
#include <cstring>
// This implementation outsources libavcodec from host ffmpeg (so that linking ffmpeg stack with spicetools isn't required)
namespace hooks::soundbank::avcodec
{
enum pcm_resampler : int8_t
{
bmswac_resampler_none = -1,
bmswac_resampler_u8,
bmswac_resampler_s16,
bmswac_resampler_s16_ad,
bmswac_resampler_f32
};
typedef enum pcm_resampler pcm_resampler_t;
typedef void (*BmswConfigInit_t)(const char *path);
typedef int (*BmswSymlink_t)(const char *srcwpath, const char *dstwpath, int canonic);
typedef uint64_t (*BmswTranscoderEnumValidSoundbanksAddr_t)();
typedef pcm_resampler (*BmswTranscoderResampler_t)();
typedef int (*BmswTranscoderAsfToWav_t)(const uint8_t *srcasf, uint32_t srcsz, uint8_t **dstwav, uint32_t *dstsz, pcm_resampler_t resampler);
static BmswConfigInit_t BmswConfigInit;
static BmswSymlink_t BmswSymlink;
static BmswTranscoderEnumValidSoundbanksAddr_t BmswTranscoderEnumValidSoundbanksAddr;
static BmswTranscoderResampler_t BmswTranscoderResampler;
static BmswTranscoderAsfToWav_t BmswTranscoderAsfToWav;
static HMODULE bmsw_ = nullptr;
bool init()
{
log_info("hooks::soundbank::avcodec", "{}", __FUNCTION__);
// Initialize bmsound-wine.dll once
if (bmsw_)
return true;
if ((bmsw_ = libutils::try_library(MODULE_PATH / "bmsound-wine.dll")))
{
BmswConfigInit = (BmswConfigInit_t) GetProcAddress(bmsw_, "BmswConfigInit");
BmswSymlink = (BmswSymlink_t) GetProcAddress(bmsw_, "BmswSymlink");
BmswTranscoderEnumValidSoundbanksAddr = (BmswTranscoderEnumValidSoundbanksAddr_t) GetProcAddress(bmsw_, "BmswTranscoderEnumValidSoundbanksAddr");
BmswTranscoderResampler = (BmswTranscoderResampler_t) GetProcAddress(bmsw_, "BmswTranscoderResampler");
BmswTranscoderAsfToWav = (BmswTranscoderAsfToWav_t) GetProcAddress(bmsw_, "BmswTranscoderAsfToWav");
//_REM: 0.2.x support (old behaviour), remove on 0.3.0
if (!BmswTranscoderEnumValidSoundbanksAddr || !BmswTranscoderResampler)
{
BmswTranscoderEnumValidSoundbanksAddr = (BmswTranscoderEnumValidSoundbanksAddr_t) GetProcAddress(bmsw_, "BmswEnumValidSoundbanksAddr");
BmswTranscoderResampler = []() -> pcm_resampler
{
return bmswac_resampler_s16_ad;
};
}
// Load config
BmswConfigInit("prop/linux.json");
return true;
}
log_warning("hooks::soundbank", "Library not found: '{}'", (MODULE_PATH / "bmsound-wine.dll").string());
return false;
}
void deinit()
{
log_info("hooks::soundbank::avcodec", "{}", __FUNCTION__);
}
}
// Replicates wine MF wma=>wav playback bug 1:1, follows https://github.com/microsoft/Windows-classic-samples/blob/main/Samples/Win7Samples/multimedia/mediafoundation/AudioClip/main.cpp
#include <mfapi.h>
#include <mfidl.h>
#include <mfreadwrite.h>
#include <mfobjects.h>
#include <shlwapi.h>
namespace hooks::soundbank::mf_broken
{
typedef HRESULT (*MFCreateMFByteStreamOnStream_t)(IStream *, IMFByteStream **);
typedef HRESULT (*MFCreateSourceReaderFromByteStream_t)(IMFByteStream *, IMFAttributes *, IMFSourceReader **);
typedef HRESULT (*MFStartup_t)(ULONG, DWORD);
typedef HRESULT (*MFShutdown_t)();
MFCreateMFByteStreamOnStream_t MFCreateMFByteStreamOnStream;
MFCreateSourceReaderFromByteStream_t MFCreateSourceReaderFromByteStream;
MFStartup_t MFStartup;
MFShutdown_t MFShutdown;
struct wav_hdr
{
char sig_riff[4] = {'R', 'I', 'F', 'F'};
uint32_t filesz = -1;
char sig_wave[4] = {'W', 'A', 'V', 'E'};
char sig_fmt[4] = {'f', 'm', 't', ' '};
uint32_t chunksize = 16;
uint16_t format = 1;
uint16_t channels = 2;
uint32_t samplerate = 44100;
uint32_t byterate = 44100 * 2 * 2;
uint16_t stride = 2 * 2;
uint16_t bitdepth = 16;
char sig_data[4] = {'d', 'a', 't', 'a'};
uint32_t datasz = -1;
};
HRESULT asf_unpack_pcm_s16le(BYTE *dst, IMFSourceReader *srcreader, uint32_t szmax, uint32_t *sz)
{
HRESULT hr = S_OK;
size_t srcpos = 0;
size_t srcchunk = 0;
BYTE *audiobuf = nullptr;
IMFSample *samples = nullptr;
IMFMediaBuffer *mediabuf = nullptr;
while (true)
{
DWORD flags = 0;
// Get next samples chunk, in memcpy capable format
hr = srcreader->ReadSample((DWORD) MF_SOURCE_READER_FIRST_AUDIO_STREAM, 0, nullptr, &flags, nullptr, &samples);
if (FAILED(hr))
{
log_warning("hooks::soundbank", "ReadSample() failure, skipping..");
break;
}
if (flags & MF_SOURCE_READERF_CURRENTMEDIATYPECHANGED)
{
log_warning("hooks::soundbank", "Unsupported WAVE format, skipping..");
samples->Release();
break;
}
if (flags & MF_SOURCE_READERF_ENDOFSTREAM)
{
break;
}
if (samples == nullptr)
{
log_warning("hooks::soundbank", "Missing samples");
continue;
}
hr = samples->ConvertToContiguousBuffer(&mediabuf);
if (FAILED(hr))
{
log_warning("hooks::soundbank", "ConvertToContiguousBuffer() failure, skipping..");
samples->Release();
break;
}
// Store to destination (szmax as overflow check)
mediabuf->Lock(&audiobuf, nullptr, reinterpret_cast<DWORD *>(&srcchunk));
if (szmax - srcpos < srcchunk)
{
srcchunk = (signed int) szmax - srcpos;
}
memcpy(dst + srcpos, audiobuf, srcchunk);
srcpos += srcchunk;
hr = mediabuf->Unlock();
// Necessary per samples chunk
audiobuf = nullptr;
samples->Release();
mediabuf->Release();
if (srcpos >= szmax)
{
break;
}
}
*sz = srcpos;
return hr;
}
HRESULT asf_to_wav(const BYTE *srcasf, uint32_t srcsz, BYTE **dstwav, uint32_t *dstsz)
{
HRESULT hr = S_OK;
// Reuse raw asf container into a byte stream https://stackoverflow.com/questions/46493191/create-imfbytestream-from-byte-array
IStream *shstream = SHCreateMemStream(srcasf, srcsz);
IMFByteStream *bstream;
MFCreateMFByteStreamOnStream(shstream, &bstream);
// Create preconfigured from source buffer reader
IMFSourceReader *srcreader;
hr = MFCreateSourceReaderFromByteStream(bstream, nullptr, &srcreader);
// Setup wav metadata/header (s16le@44.1kHz 2ch)
UINT32 a, b, c;
IMFMediaType *mediatype;
PROPVARIANT duration;
srcreader->GetCurrentMediaType(MF_SOURCE_READER_FIRST_AUDIO_STREAM, &mediatype);
srcreader->GetPresentationAttribute(MF_SOURCE_READER_MEDIASOURCE, MF_PD_DURATION, &duration);
#ifdef _WIN64
mediatype->GetUINT32(MF_MT_AUDIO_NUM_CHANNELS, &a);
mediatype->GetUINT32(MF_MT_AUDIO_SAMPLES_PER_SECOND, &b);
mediatype->GetUINT32(MF_MT_AUDIO_BITS_PER_SAMPLE, &c);
#else
a = 2;
b = 44100;
c = 16;
#endif
wav_hdr dsthdr = {
.channels=static_cast<uint16_t>(a),
.samplerate=b,
.bitdepth=static_cast<uint16_t>(c)
};
// ASF reader => PCM data
if (!*dstwav) *dstwav = (BYTE *) malloc(size_t(((double) duration.uhVal.QuadPart / 10000000.0 * dsthdr.samplerate + 4096) * dsthdr.bitdepth * dsthdr.channels) + sizeof(struct wav_hdr));
asf_unpack_pcm_s16le(*dstwav + sizeof(struct wav_hdr), srcreader, 0xFFFFFFFF, dstsz); //_INFO: overflow handled elsewhere
// Finalize and store header
dsthdr.datasz = *dstsz;
dsthdr.filesz = *dstsz + sizeof(struct wav_hdr) - 8;
memcpy(*dstwav, &dsthdr, sizeof(struct wav_hdr));
*dstsz += sizeof(wav_hdr);
// Cleanup without releasing srcasf
PropVariantClear(&duration);
mediatype->Release();
srcreader->Release();
bstream->Close();
shstream->Release();
return hr;
}
bool init()
{
HMODULE mf_ = libutils::try_library("mf.dll");
HMODULE mfreadwrite_ = libutils::try_library("mfreadwrite.dll");
HMODULE mfplat_ = libutils::try_library("mfplat.dll");
if (!mf_ || !mfreadwrite_ || !mfplat_) return false;
// Required by linker
MFCreateMFByteStreamOnStream = (MFCreateMFByteStreamOnStream_t) GetProcAddress(mfplat_, "MFCreateMFByteStreamOnStream");
MFStartup = (MFStartup_t) GetProcAddress(mfplat_, "MFStartup");
MFShutdown = (MFShutdown_t) GetProcAddress(mfplat_, "MFShutdown");
MFCreateSourceReaderFromByteStream = (MFCreateSourceReaderFromByteStream_t) GetProcAddress(mfreadwrite_, "MFCreateSourceReaderFromByteStream");
if (!MFCreateMFByteStreamOnStream || !MFCreateSourceReaderFromByteStream || !MFStartup || !MFShutdown || FAILED(MFStartup(MF_VERSION, MFSTARTUP_FULL)))
return false;
return true;
}
void deinit()
{
MFShutdown();
}
}
namespace hooks::soundbank
{
constexpr char SNDPATHFMT[] = "%05d/%05d";
constexpr size_t SNDPATHFMTMAX = sizeof(SNDPATHFMT) - 1;
constexpr size_t MNTPATHMAX = sizeof("/sdAABBB/AABBB/AABBBC_pre.2dx");
constexpr size_t AVSPATHMAX = sizeof("/data/sound/AABBB/AABBBC_pre.2dx");
constexpr uint8_t CACHEMAX = 8;
constexpr int operator "" _i(long double x) { return static_cast<int>(x); }
typedef struct snd_bank snd_bank;
typedef struct snd_s3p snd_s3p;
typedef struct snd_2dx snd_2dx;
typedef void *(*EnumValidSoundbanks_t)(void *, int32_t, int32_t);
struct snd_2dx
{
public:
struct bank
{
struct voice_hdr
{
char sig[4];
uint32_t fbegin;
uint32_t sz;
int16_t unk0;
int16_t type;
int16_t unk1;
int16_t volume;
int32_t unk2;
[[nodiscard]] constexpr BYTE *wave() noexcept
{
return ((BYTE *) this) + this->fbegin;
}
};
struct voice_ptr
{
uint32_t fbegin;
[[nodiscard]] constexpr void sz(voice_hdr *val) noexcept
{
(this + 1)->fbegin = this->fbegin + val->fbegin + val->sz; // see constructor
}
[[nodiscard]] constexpr size_t sz() const noexcept
{
return (this + 1)->fbegin > 0 ? (this + 1)->fbegin - this->fbegin : 0; // see constructor
}
};
struct
{
char name[16];
uint32_t sz;
uint32_t voice_cnt;
char unk[48];
} header;
voice_ptr table;
[[nodiscard]] constexpr size_t sz() const noexcept
{
return (&table + header.voice_cnt - 1)->fbegin + (&table + header.voice_cnt - 1)->sz();
}
};
[[nodiscard]] static constexpr size_t assertsz_asf(size_t sz, int8_t resampler = 1) noexcept
{
resampler = (resampler > 1 || resampler == -1) ? 2 : 1; // resampler ratio multiplier (see pcm_resampler enum, original data is float)
return static_cast<size_t>(static_cast<double>(sz) * 14.8) * resampler + WAVHDRMAX; // 0.wma is consistent 180 therefore VBR Q90, sometimes 160 though, but samples are all over the place
}
[[nodiscard]] constexpr const decltype(bank::header) *header() const noexcept
{
return &this->bank_->header;
}
[[nodiscard]] constexpr bank::voice_ptr *voice_ptr(size_t i) const noexcept
{
return &bank_->table + i;
}
[[nodiscard]] constexpr bank::voice_hdr *voice(size_t i) const noexcept
{
return (bank::voice_hdr *) ((char *) this->bank_ + this->voice_ptr(i)->fbegin);
}
[[nodiscard]] constexpr size_t voice_emplace(size_t assertsz) noexcept //_INFO: assertsz guaranteed until serialize(true), overridden by voice_ptr::sz(v), compute value with assertsz_*()
{
// memory space assertion
if (this->voice_ptr(this->bank_->header.voice_cnt)->fbegin + assertsz >= banksz_)
{
log_info("hooks::soundbank", "snd_2dx::voice_emplace({}=>{})", banksz_, banksz_ + assertsz + ALLOCSIZE);
banksz_ += assertsz + ALLOCSIZE;
bank_ = (bank *) realloc(bank_, banksz_);
}
// voice_ptr updating works through voice_ptr::sz()
//_REV: table and header.sz resizing event if voice_cnt == 4077, as voice_ptr::sz() needs to read 1 ahead
// voice_hdr setup
bank::voice_hdr *voice_hdr = this->voice(this->bank_->header.voice_cnt);
std::copy("2DX9", &"2DX9"[4], voice_hdr->sig);
voice_hdr->fbegin = sizeof(bank::voice_hdr);
voice_hdr->sz = 0;
voice_hdr->unk0 = (bank_->header.name[1] - '0') % 2 == 1 ? 0x3231 : 0x3230; // 0x3230/0x3231 for even/odd style
voice_hdr->type = this->bank_->header.voice_cnt == 0 ? 0 : -1; // special case for Voice[0]
voice_hdr->unk1 = 0x40;
voice_hdr->volume = 1;
voice_hdr->unk2 = 0x0;
return this->bank_->header.voice_cnt++;
}
static snd_2dx *unpack(char *refbank) = delete;
static snd_2dx *pack(int id);
bank *serialize(bool trim);
~snd_2dx();
private:
static constexpr size_t WAVHDRMAX = 78;
static constexpr size_t ALLOCSIZE = 4 * 1024 * 1024;
struct bank *bank_;
size_t banksz_;
explicit snd_2dx(uint8_t *bindbank);
};
snd_2dx::snd_2dx(uint8_t *bindbank) : bank_(reinterpret_cast<struct bank *>(bindbank)), banksz_(0)
{
if (bank_)
return;
// Necessary bank header initialization
banksz_ = ALLOCSIZE;
bank_ = (bank *) malloc(ALLOCSIZE);
memset(&bank_->header, 0, sizeof(bank_->header) + 4078 * sizeof(bank::voice_ptr)); // initializes bank::voice_ptr::sz and bank::header.unk
bank_->header.name[0] = '\0';
bank_->header.voice_cnt = 0;
bank_->header.sz = sizeof(bank_->header) + 4078 * sizeof(bank::voice_ptr); // 4078 keysounds limit (16KB header), refer to voice_emplace()
// Necessary voice pointer table initialization (refer to voice_ptr::sz())
(&bank_->table)[0].fbegin = bank_->header.sz;
}
snd_2dx *snd_2dx::pack(int id)
{
snd_2dx *r = new snd_2dx(nullptr);
snprintf(r->bank_->header.name, sizeof(r->bank_->header.name), "%05d.2dx", id);
return r;
}
snd_2dx::bank *snd_2dx::serialize(bool trim)
{
if (trim)
{
//_REV: prune voice_ptr for unused voice slots + move data segment up adjusting voice_ptr->fbegin
}
return bank_;
}
snd_2dx::~snd_2dx()
{
free(bank_);
}
struct snd_s3p
{
public:
struct bank
{
struct voice_hdr
{
char sig[4]; // S3V0
uint32_t fbegin;
uint32_t sz;
uint8_t unk[20];
[[nodiscard]] constexpr BYTE *asf() const noexcept
{
return ((BYTE *) this) + this->fbegin;
}
};
struct voice_ptr
{
uint32_t fbegin;
uint32_t sz;
};
struct
{
const char sig[4]; // S3P0
uint32_t voice_cnt;
} header;
voice_ptr table;
};
[[nodiscard]] constexpr const decltype(bank::header) *header() const noexcept
{
return &this->bank_->header;
}
[[nodiscard]] constexpr bank::voice_ptr *voice_ptr(size_t i) const noexcept
{
return &bank_->table + i;
}
[[nodiscard]] constexpr bank::voice_hdr *voice(size_t i) const noexcept
{
return (bank::voice_hdr *) ((char *) this->bank_ + this->voice_ptr(i)->fbegin);
}
static snd_s3p *unpack(uint8_t *refbank);
static snd_s3p *pack(int id) = delete;
bank *serialize(bool trim) = delete;
~snd_s3p();
private:
struct bank *bank_;
size_t banksz_; //_TODO: keep track of allocated memory
explicit snd_s3p(uint8_t *bindbank);
};
snd_s3p::snd_s3p(uint8_t *bindbank) : bank_(reinterpret_cast<struct bank *>(bindbank)), banksz_(0)
{
}
snd_s3p::~snd_s3p()
{
free(bank_);
}
snd_s3p *snd_s3p::unpack(uint8_t *refbank)
{
return refbank ? new snd_s3p(refbank) : nullptr;
}
struct snd_bank
{
public:
static snd_bank *new_instance(int id);
static void flush_cache();
static void pop_cache();
static snd_bank *get_bank(int id, const char suffix = '0');
static uint8_t *read_bank_any(const char *avspath, const char *ext, size_t *sz);
static bool has_format(const char *avspath, const char *ext);
static avs::core::avs_file_t map_ifs(char *avspath);
static void dump(const char *path, const uint8_t *src, size_t sz);
int id;
char avspath[AVSPATHMAX];
void cache_bank_any(const uint8_t *sndbuf, size_t sz);
[[nodiscard]] constexpr bool is_cached() const noexcept
{
return uid_ != UID_INVALID;
}
[[nodiscard]] constexpr const char *sndpath() const noexcept
{
const char *it = avspath + AVSPATHMAX;
size_t c = 0;
while (it >= avspath && c < 2)
{
it--;
if (*it == '/') c++;
}
it++;
if (strlen(it) > SNDPATHFMTMAX)
return SNDPATHFMT;
return it;
}
inline static avcodec::pcm_resampler_t resampler() noexcept
{
return resampler_;
}
private:
static constexpr avs::core::avs_file_t E_NOT_FOUND = 0x80070002;
static constexpr uint8_t UID_INVALID = 0x00;
static snd_bank *cache_;
static size_t cachesz_;
static avcodec::pcm_resampler_t resampler_;
friend void hooks::soundbank::init(HINSTANCE hmodule, const char *model, const char *ext);
uint8_t uid_;
uint32_t mnt_;
snd_bank *next_;
snd_bank();
~snd_bank();
};
void snd_bank::dump(const char *path, const uint8_t *src, size_t sz)
{
FILE *fh = fopen(path, "wb");
fwrite(src, sz, 1, fh);
fclose(fh);
}
snd_bank *snd_bank::new_instance(int id)
{
snd_bank *prev, *instance;
// Latest entry should always be head
instance = new snd_bank();
instance->id = id;
instance->next_ = cache_;
cache_ = instance;
cachesz_ += 1;
// Clean cache (last entry indexing target excluded, cache limit reached)
if (instance->next_ && instance->next_->uid_ == UID_INVALID)
{
log_info("hooks::soundbank", "Cache prune");
prev = instance->next_;
instance->next_ = prev->next_;
log_info("hooks::soundbank", "Removing {}::Bank[CACHE]::{}", prev->id, (uint32_t) prev->uid_);
delete prev;
cachesz_ -= 1;
}
if (cachesz_ > CACHEMAX)
{
log_info("hooks::soundbank", "Cache full");
for (uint8_t i = 0; i < CACHEMAX; i += 2)
{
prev = nullptr;
instance = cache_;
while (instance && instance->next_)
{
prev = instance;
instance = instance->next_;
}
prev->next_ = nullptr;
delete instance;
cachesz_ -= 1;
}
}
return cache_;
}
void snd_bank::cache_bank_any(const uint8_t *sndbuf, size_t sz)
{
// Update struct using next free cache uid
if (uid_ == UID_INVALID)
{
snd_bank *it = cache_;
while (it)
{
if (uid_ == UID_INVALID || (uid_ == it->uid_ && it != this))
{
uid_--;
it = cache_;
continue;
}
it = it->next_;
}
}
snprintf(this->avspath, sizeof(this->avspath), "data/sound/system/%02x", (unsigned int) uid_);
// Store file
char path[AVSPATHMAX];
snprintf(path, sizeof(path), "%s.2dx", this->avspath);
log_info("hooks::soundbank", "Storing as intermediate soundbank at '{}'", (const char *) path);
dump(path, sndbuf, sz);
}
snd_bank::snd_bank() : id(0), avspath(""), uid_(UID_INVALID), mnt_(E_NOT_FOUND), next_(nullptr)
{
}
snd_bank::~snd_bank()
{
if (uid_ != UID_INVALID)
{
char path[AVSPATHMAX];
snprintf(path, sizeof(path), "data/sound/system/%02x.2dx", uid_);
log_info("hooks::soundbank", "{}::Bank[CACHE]::Purge '{}'", this->id, (const char *) path);
remove(path);
}
if (mnt_ != E_NOT_FOUND)
{
log_info("hooks::soundbank", "{}::Bank[CACHE]::Purge mount(0x{:08x})", this->id, mnt_);
avs::core::avs_fs_umount(mnt_);
}
}
snd_bank *snd_bank::get_bank(int id, const char suffix)
{
// Find in cache (latest=>oldest)
id = id + ((suffix - '0') * 1e6_i);
for (snd_bank *it = cache_; it; it = it->next_)
if (it->id == id) return it;
// Find in avs (has valid s3p/.2dx resource at data/sound/AABBB/AABBB.2dx)
snd_bank *bank = new_instance(id);
snprintf(bank->avspath, AVSPATHMAX, "data/sound/%05d", (id % 1e6_i));
if (has_format(bank->avspath, ".ifs"))
bank->mnt_ = map_ifs(bank->avspath);
snprintf(bank->avspath + strlen(bank->avspath), 7, "/%05d", (id % 1e6_i));
if (id / 1e6_i) snprintf(bank->avspath + strlen(bank->avspath), 2, "%c", '0' + (id / 1e6_i));
if (!(has_format(bank->avspath, ".s3p") || has_format(bank->avspath, ".2dx")))
{
pop_cache();
bank = nullptr;
}
return bank;
}
void snd_bank::flush_cache()
{
snd_bank *prev;
while (cache_)
{
prev = cache_;
cache_ = cache_->next_;
delete prev;
}
}
void snd_bank::pop_cache()
{
snd_bank *prev = cache_;
cache_ = cache_->next_;
delete prev;
}
uint8_t *snd_bank::read_bank_any(const char *avspath, const char *ext, size_t *sz)
{
constexpr uint16_t O_RDONLY = 1; // rw:12 consistent across versions with s3p support
uint8_t *r = nullptr;
char path[AVSPATHMAX];
snprintf(path, sizeof(path), "%s%s", avspath, ext);
avs::core::avs_file_t fd = avs::core::avs_fs_open(path, O_RDONLY, 420);
if (fd != E_NOT_FOUND)
{
{
avs::core::avs_stat st; // NOLINT(cppcoreguidelines-pro-type-member-init)
avs::core::avs_fs_fstat(fd, &st);
*sz = st.filesize;
}
r = (uint8_t *) malloc(*sz);
if (r) avs::core::avs_fs_read(fd, r, *sz);
avs::core::avs_fs_close(fd);
}
return r;
}
bool snd_bank::has_format(const char *avspath, const char *ext)
{
char path[AVSPATHMAX] = "";
snprintf(path, sizeof(path), "%s%s", avspath, ext);
avs::core::avs_stat st; // NOLINT(cppcoreguidelines-pro-type-member-init)
bool r = static_cast<bool>(avs::core::avs_fs_lstat(path, &st)); // lstat should be faster than open
log_info("hooks::soundbank", "'{}' exists? => {}", path, r);
return static_cast<bool>(r);
}
avs::core::avs_file_t snd_bank::map_ifs(char *avspath)
{
// Map */${name}.ifs => /sc${name}
char ifspath[AVSPATHMAX];
char mntpath[MNTPATHMAX];
const char *it = avspath + strlen(avspath);
while (it >= avspath && *it != '/')
{
it--;
}
it++;
snprintf(ifspath, sizeof(ifspath), "%s.ifs", avspath);
snprintf(mntpath, sizeof(mntpath), "sc%s", it);
avs::core::avs_file_t r = avs::core::avs_fs_mount(mntpath, ifspath, "imagefs", nullptr);
// Finalize avspath
if (r != E_NOT_FOUND)
{
snprintf(mntpath + strlen(mntpath), strlen(it) + 2, "/%s", it);
log_info("hooks::soundbank", "'{}' => '{}'", avspath, mntpath);
strcpy(avspath, mntpath);
}
return r;
}
static struct offset_t
{
intptr_t RD_05d_s3p; // %05d/%05d.s3p
intptr_t RD_05d_c_s3p; // %05d/%05d%c.s3p
intptr_t RD_05d_2dx; // %05d/%05d.2dx
intptr_t RD_05d_c_2dx; // %05d/%05d%c.2dx
intptr_t TX_EnumValidSoundbanks; // enumerates above databank paths, simple exist check (lstat()/OPEN -> CLOSE_NOWRITE only)
intptr_t DAT_song_titlel;
intptr_t DAT_song_artistl;
intptr_t DAT_song_id;
bool is_lstr_wchar;
memutils::VProtectGuard *guard;
} offset_;
snd_bank *snd_bank::cache_ = nullptr;
size_t snd_bank::cachesz_ = 0;
avcodec::pcm_resampler_t snd_bank::resampler_ = avcodec::bmswac_resampler_s16;
static EnumValidSoundbanks_t EnumValidSoundbanks;
void *on_enum_valid_soundbanks(void *song, int32_t difficulty, int32_t unk2)
{
// Keysounds populated into CtrlSound::Bank[2][X] = [0:s3p][1:2dx][2:_pre.2dx], always uses lowest i databank for resource confirmed valid by this function
// Struct memory layout 1:1 with data/info/(0/1)/music_*.bin
snd_bank *keysounds = nullptr;
const char *titlel = (const char *) song + offset_.DAT_song_titlel;
const char *artistl = (const char *) song + offset_.DAT_song_artistl;
const int32_t id = *(int32_t *) ((const char *) song + offset_.DAT_song_id);
const char suffix = *((const char *) song + offset_.DAT_song_id + 8 + difficulty);
if (offset_.is_lstr_wchar)
{
std::wstring_convert<std::codecvt_utf8<wchar_t>> converter;
log_info("hooks::soundbank", "Processing s3p->2dx transcoding routine for [{}]({} - {})",
id,
converter.to_bytes(std::wstring(reinterpret_cast<const wchar_t *>(artistl), wcslen(reinterpret_cast<const wchar_t *>(artistl)))),
converter.to_bytes(std::wstring(reinterpret_cast<const wchar_t *>(titlel), wcslen(reinterpret_cast<const wchar_t *>(titlel))))
);
}
else
log_info("hooks::soundbank", "Processing s3p->2dx transcoding routine for [{}]({} - {})", id, artistl, titlel);
if (!(keysounds = snd_bank::get_bank(id, suffix)))
{
log_warning("hooks::soundbank", "soundbank::get_bank() was 0x0, this shouldn't happen..");
memcpy(reinterpret_cast<void *>(offset_.RD_05d_2dx), "%05d/%05d", SNDPATHFMTMAX);
return EnumValidSoundbanks(song, difficulty, unk2);
}
else if (!snd_bank::has_format(keysounds->avspath, ".2dx"))
{
// Get s3p and process into s3p soundbank for intermediate wma voice storage
size_t szs3p;
uint8_t *bufs3p = snd_bank::read_bank_any(keysounds->avspath, ".s3p", &szs3p);
snd_s3p *snds3p = snd_s3p::unpack(bufs3p);
// Process wma voices into wav voices stored directly into 2dx soundbank
size_t i, j;
snd_2dx *snd2dx = snd_2dx::pack(id);
for (i = 0; i < snds3p->header()->voice_cnt; i++)
{
// WMA=>WAV transcode
snd_s3p::bank::voice_hdr *voicesrc = snds3p->voice(i);
j = snd2dx->voice_emplace(snd_2dx::assertsz_asf(voicesrc->sz));
snd_2dx::bank::voice_hdr *voicedst = snd2dx->voice(j);
BYTE *voicedst_wave = voicedst->wave();
avcodec::BmswTranscoderAsfToWav(voicesrc->asf(), voicesrc->sz, &voicedst_wave, &voicedst->sz, snd_bank::resampler());
snd2dx->voice_ptr(j)->sz(voicedst); // updates voice_ptr table, do not skip
}
// Output to cache (reuse output file, while in valid cache)
snd_2dx::bank *buf2dx = snd2dx->serialize(false);
keysounds->cache_bank_any(reinterpret_cast<const uint8_t *>(buf2dx), buf2dx->sz());
//_REV: symlink system/uid_.2dx -> /tmp/uid_.2dx
}
// Resume execution (force %05.2dx branch)
const char *t = keysounds->sndpath();
memcpy(reinterpret_cast<void *>(offset_.RD_05d_2dx), t, SNDPATHFMTMAX);
log_info("hooks::soundbank", "RD_05d_2dx set to '{}'", reinterpret_cast<const char *>(offset_.RD_05d_2dx));
log_info("hooks::soundbank", "Processing completed");
*((char *) song + offset_.DAT_song_id + 8 + difficulty) = keysounds->is_cached() ? '0' : suffix;
void *r = EnumValidSoundbanks(song, difficulty, unk2);
*((char *) song + offset_.DAT_song_id + 8 + difficulty) = suffix;
return r;
}
static class Tests : public hooks::devel::Routine
{
public:
using Routine::Routine;
void entry_main() override
{
size_t szs3p;
BYTE *wav = nullptr;
uint32_t wavsz = 0;
char avspath[AVSPATHMAX];
avs::core::avs_file_t avsmnt = 0;
char sig[5] = " ";
int id = 25073;
if (!mf_broken::init())
{
log_warning("hooks::soundbank", "MF initialization failed, skipping..");
}
// Filesystem tests, s3p unpacking
snprintf(avspath, sizeof(avspath), "data/sound/%05d", id);
if (snd_bank::has_format(avspath, ".ifs")) avsmnt = snd_bank::map_ifs(avspath);
snprintf(avspath + strlen(avspath), 7, "/%05d", id);
snd_bank::has_format(avspath, ".s3p");
snd_bank::has_format(avspath, ".2dx");
snd_bank::has_format(avspath, "_pre.2dx");
uint8_t *bufs3p = snd_bank::read_bank_any(avspath, ".s3p", &szs3p);
snd_s3p *snds3p = snd_s3p::unpack(bufs3p);
if (avsmnt) avs::core::avs_fs_umount(avsmnt);
// S3P => WMA => WAV => 2DX pipeline
if (snds3p)
{
// S3P/S3V metadata
memcpy(sig, snds3p->header()->sig, 4);
log_info("hooks::soundbank", "{}::Bank[S3P]::Signature {}", id, sig);
log_info("hooks::soundbank", "{}::Bank[S3P]::Size {}", id, szs3p);
log_info("hooks::soundbank", "{}::Bank[S3P]::VoiceCount {}", id, snds3p->header()->voice_cnt);
log_info("hooks::soundbank", "{}::Bank[S3P]::Voice[0]::Size {}", id, snds3p->voice_ptr(0)->sz);
memcpy(sig, snds3p->voice(0)->sig, 4);
log_info("hooks::soundbank", "{}::Bank[S3P]::Voice[0]::Signature {}", id, sig);
snd_bank::dump("raw_wma.wma", snds3p->voice(0)->asf(), snds3p->voice(0)->sz);
// MF codec (tainted PCM data, bad)
mf_broken::asf_to_wav(snds3p->voice(0)->asf(), snds3p->voice(0)->sz, &wav, &wavsz);
snd_bank::dump("mf_wav.wav", wav, wavsz);
free(wav);
wav = nullptr;
wavsz = 0;
// AV codec (1:1 unmodified PCM data, other than resampling if enabled)
wav = (BYTE *) malloc(snd_2dx::assertsz_asf(snds3p->voice(0)->sz, avcodec::bmswac_resampler_f32));
avcodec::BmswTranscoderAsfToWav(snds3p->voice(0)->asf(), snds3p->voice(0)->sz, &wav, &wavsz, avcodec::bmswac_resampler_f32);
snd_bank::dump("ac_wav.wav", wav, wavsz);
free(wav);
wav = nullptr;
wavsz = 0;
// S3P=>2DX (max 4076 voices)
size_t i, j;
snd_2dx *snd2dx = snd_2dx::pack(id);
log_info("hooks::soundbank", "{}::Bank[2DX]::Name {}", id, snd2dx->header()->name);
for (i = 0; i < snds3p->header()->voice_cnt; i++)
{
// WMA=>WAV transcode
snd_s3p::bank::voice_hdr *voicesrc = snds3p->voice(i);
j = snd2dx->voice_emplace(snd_2dx::assertsz_asf(voicesrc->sz));
snd_2dx::bank::voice_hdr *voicedst = snd2dx->voice(j);
BYTE *voicedst_wave = voicedst->wave();
//mf_broken::asf_to_wav(voicesrc->asf(), voicesrc->sz, &voicedst_wave, &voicedst->sz);
avcodec::BmswTranscoderAsfToWav(voicesrc->asf(), voicesrc->sz, &voicedst_wave, &voicedst->sz, snd_bank::resampler());
snd2dx->voice_ptr(j)->sz(voicedst); // updates voice_ptr table, do not skip
}
// Valid 2DX (trim is not necessary)
log_info("hooks::soundbank", "{}::Bank[2DX]::Dumping", id);
snd_2dx::bank *buf2dx = snd2dx->serialize(false);
snd_bank::dump("ac_soundbank.2dx", reinterpret_cast<const uint8_t *>(buf2dx), buf2dx->sz());
}
else
{
log_info("hooks::soundbank", "Tests failed");
}
// Caching and intermediate 2dx storage detection
EnumValidSoundbanks_t t = EnumValidSoundbanks;
EnumValidSoundbanks = [](void *, int32_t, int32_t) -> void * { return nullptr; };
char *tmp = (char *) malloc(0x3b8 + strlen("00000000") + 1);
int arr[6] = {25073, 24016, 26057, 25073, 26087, 29095};
strcpy(tmp, "SJISTITLE");
strcpy(tmp + 0xc0, "SJISARTIST");
strcpy(tmp + 0x3b8, "00000000");
for (int it: arr)
{
*(int32_t *) (tmp + 0x3b0) = it;
on_enum_valid_soundbanks(tmp, 3, 0);
}
free(tmp);
EnumValidSoundbanks = t;
snd_bank::flush_cache();
mf_broken::deinit();
}
} tests_("sndb_test");
void init(HINSTANCE hmodule, const char *model, const char *ext)
{
if (to_hash(model) != to_hash("LDJ")) return;
if (false && IsDebuggerPresent())
{
log_warning("hooks::soundbank", "Debugger detected, skipping..");
return;
}
if (!avcodec::init())
{
log_warning("hooks::soundbank", "AVCodec initialization failed, skipping..");
return;
}
// Override .data (per datacode)
long extl = strtol(ext, nullptr, 10);
if (extl < 2019100800)
{
offset_.DAT_song_titlel = 0x00;
offset_.DAT_song_artistl = 0xc0;
offset_.DAT_song_id = 0x1c8;
offset_.is_lstr_wchar = false;
snd_bank::resampler_ = avcodec::bmswac_resampler_s16_ad;
}
else if (extl < 2024082700)
{
offset_.DAT_song_titlel = 0x00;
offset_.DAT_song_artistl = 0xc0;
offset_.DAT_song_id = 0x3b0;
offset_.is_lstr_wchar = false;
}
else
{
offset_.DAT_song_titlel = 0x00;
offset_.DAT_song_artistl = 0x1c0;
offset_.DAT_song_id = 0x67c;
offset_.is_lstr_wchar = true;
}
// Override .text (pattern match)
offset_.TX_EnumValidSoundbanks = patch_pipeline(
hmodule,
[](HMODULE hmodule, intptr_t addr) { return find_asm_leacstr_from(hmodule, R8, "%05d/%05d.2dx"); },
[](HMODULE hmodule, intptr_t addr) { return find_pattern_from(hmodule, "CCCCCCCCCCCCCCCC", "XXXXXXXX", 8, 0, addr, true); }
);
snd_bank::resampler_ = avcodec::BmswTranscoderResampler() == avcodec::bmswac_resampler_none ? snd_bank::resampler_ : avcodec::BmswTranscoderResampler();
offset_.TX_EnumValidSoundbanks = avcodec::BmswTranscoderEnumValidSoundbanksAddr() == 0x0 ? offset_.TX_EnumValidSoundbanks : (intptr_t) hmodule + (int64_t) avcodec::BmswTranscoderEnumValidSoundbanksAddr();
if (!offset_.TX_EnumValidSoundbanks)
{
log_warning("hooks::soundbank", "Unsupported game version({}), skipping..", ext);
return;
}
log_info("hooks::soundbank", "Supported game version({})", ext);
// Override .rdata (pattern match)
offset_.RD_05d_s3p = replace_pattern(hmodule, "253035642f253035642e73337000", "003035642f253035642e73337000", 0, 0);
offset_.RD_05d_c_s3p = replace_pattern(hmodule, "253035642f2530356425632e73337000", "003035642f2530356425632e73337000", 0, 0);
offset_.RD_05d_2dx = replace_pattern(hmodule, "253035642F253035642E32647800", "73797374656D2F30302E32647800", 0, 0);
offset_.RD_05d_c_2dx = replace_pattern(hmodule, "253035642f2530356425632e32647800", "253035642f2530356425632e32647800", 0, 0);
if (!(offset_.TX_EnumValidSoundbanks && offset_.RD_05d_s3p && offset_.RD_05d_c_s3p && offset_.RD_05d_2dx && offset_.RD_05d_c_2dx))
{
log_warning("hooks::soundbank", "Could not find valid offsets, skipping..");
return;
}
// Setup on_enum_valid_ksbd and optional tests
log_info("hooks::soundbank", "Hooking EnumValidSoundbanks(0x{:x})", offset_.TX_EnumValidSoundbanks);
offset_.guard = new memutils::VProtectGuard((void *) offset_.RD_05d_2dx, SNDPATHFMTMAX);
detour::trampoline(reinterpret_cast<void *>(offset_.TX_EnumValidSoundbanks), reinterpret_cast<void *>(on_enum_valid_soundbanks), reinterpret_cast<void **>(&EnumValidSoundbanks));
hooks::devel::bind_routine(&tests_);
log_info("hooks::soundbank", "Soundbank preprocessor ready");
}
void deinit(HINSTANCE hmodule)
{
log_info("hooks::soundbank", "Releasing soundbank preprocessor");
snd_bank::flush_cache();
delete offset_.guard;
avcodec::deinit();
}
}
-8
View File
@@ -1,8 +0,0 @@
#pragma once
#include <windows.h>
namespace hooks::soundbank
{
void init(HINSTANCE hmodule, const char *model, const char *ext);
void deinit(HINSTANCE hmodule);
}
-266
View File
@@ -1,266 +0,0 @@
#include "wmischook.h"
#include "util/logging.h"
#include "util/detour.h"
#include "util/sigscan.h"
#include "util/libutils.h"
#include "util/utils.h"
#include <mmsystem.h>
namespace hooks::wmisc::msacm
{
typedef MMRESULT (__stdcall *acmFormatSuggest_t)(HANDLE had, LPWAVEFORMATEX pwfxSrc, LPWAVEFORMATEX pwfxDst, DWORD cbwfxDst, DWORD fdwSuggest);
static acmFormatSuggest_t acmFormatSuggest;
static uint64_t hash_waveformatex(const WAVEFORMATEX *pwfx, DWORD fdwSuggest)
{
//_INFO: no clashes, likely faster than hashing sizeof(WAVEFORMATEX) buffer
uint64_t h = 0;
h ^= (uint64_t) pwfx->nSamplesPerSec << 0;
h ^= (uint64_t) pwfx->nAvgBytesPerSec << 1;
h ^= (uint64_t) pwfx->nBlockAlign << 2;
h ^= (uint64_t) pwfx->wBitsPerSample << 3;
h ^= (uint64_t) pwfx->cbSize << 4;
h ^= (uint64_t) fdwSuggest << 5;
h ^= (uint64_t) pwfx->wFormatTag << 6;
h ^= (uint64_t) pwfx->nChannels << 7;
return h;
}
MMRESULT __stdcall on_acmFormatSuggest(HANDLE had, LPWAVEFORMATEX pwfxSrc, LPWAVEFORMATEX pwfxDst, DWORD cbwfxDst, DWORD fdwSuggest)
{
static std::unordered_map<uint64_t, std::vector<char>> acmformats;
static std::mutex mutex;
// Find cached formats
size_t hash = hash_waveformatex(pwfxSrc, fdwSuggest);
{
std::lock_guard lock(mutex);
auto it = acmformats.find(hash);
if (it != acmformats.end())
{
// Copy cached result
memcpy(pwfxDst, it->second.data(), std::min(cbwfxDst, (DWORD) (sizeof(WAVEFORMATEX) + ((WAVEFORMATEX *) it->second.data())->cbSize)));
return MMSYSERR_NOERROR;
}
}
// Cache unique formats
log_info("hooks::wmisc::msacm", "Forwarding acmFormatSuggest({}) request due to cache miss", reinterpret_cast<void *>(hash));
MMRESULT r = acmFormatSuggest(had, pwfxSrc, pwfxDst, cbwfxDst, fdwSuggest);
if (r == MMSYSERR_NOERROR)
{
std::lock_guard lock(mutex);
acmformats[hash].resize(sizeof(WAVEFORMATEX) + pwfxDst->cbSize);
memcpy(acmformats[hash].data(), pwfxDst, sizeof(WAVEFORMATEX) + pwfxDst->cbSize);
}
return r;
}
void attach(HINSTANCE hmodule)
{
// Override acmFormatSuggest
HMODULE msacm = nullptr;
if ((msacm = libutils::try_library("msacm32.dll")))
{
acmFormatSuggest = (acmFormatSuggest_t) GetProcAddress(msacm, "acmFormatSuggest");
}
log_info("hooks::wmisc::msacm", "Hooking acmFormatSuggest({})", reinterpret_cast<void *>(acmFormatSuggest));
detour::trampoline_try(reinterpret_cast<void *>(acmFormatSuggest), reinterpret_cast<void *>(on_acmFormatSuggest), reinterpret_cast<void **>(&acmFormatSuggest));
}
}
namespace hooks::wmisc::thumbnail
{
typedef bool (*ThumExists_t)(void *titleUTF_getter, int32_t id, char flag);
static ThumExists_t ThumExists;
bool on_thum_exists_(void *titleUTF_getter, int32_t id, char flag)
{
static std::unordered_map<int32_t, bool> thumids;
auto it = thumids.find(id);
if (it != thumids.end())
{
return it->second;
}
else
{
// log_misc("hooks::wmisc::thumbnail", "Caching state for [{}]", id);
it = thumids.insert({id, ThumExists(titleUTF_getter, id, flag)}).first;
}
return it->second;
}
void attach(HINSTANCE hmodule)
{
// Reverse lookup function head (PUSH RDI) for ThumExists() specific signature, quietly fail if invalid
intptr_t TX_ThumExists = patch_pipeline(
hmodule,
[](HMODULE hmodule, intptr_t addr) { return find_pattern_from(hmodule, "83f801750c4084ff750732c0", "XXXXXXXXXXXX", 0, 0, 0); },
[](HMODULE hmodule, intptr_t addr) { return find_pattern_from(hmodule, "4057", "XX", 0, 0, addr, true); }
);
log_info("hooks::wmisc::thumbnail", "Hooking ThumExists({})", reinterpret_cast<void *>(TX_ThumExists));
detour::trampoline_try(reinterpret_cast<void *>(TX_ThumExists), reinterpret_cast<void *>(on_thum_exists_), reinterpret_cast<void **>(&ThumExists));
}
}
namespace hooks::wmisc::bmsound
{
typedef void (*BmswLogInit_t)(void *cb);
extern "C" void __cdecl on_log_msg(char level, const char *scope, const char *msg)
{
switch (level)
{
case 'I':
log_info(scope, "{}", msg);
break;
case 'W':
log_warning(scope, "{}", msg);
break;
case 'E':
log_fatal(scope, "{}", msg);
break;
case 'M':
default:
log_misc(scope, "{}", msg);
break;
}
}
void attach(HINSTANCE hmodule)
{
BmswLogInit_t BmswLogInit = nullptr;
HMODULE bmsw;
if ((bmsw = libutils::try_library(MODULE_PATH / "bmsound-wine.dll")))
{
BmswLogInit = (BmswLogInit_t) GetProcAddress(bmsw, "BmswLogInit");
}
if (BmswLogInit) //_REM: keep 0.2.x backward compatibility until 0.3.0
{
BmswLogInit(reinterpret_cast<void *>(on_log_msg));
}
}
}
namespace hooks::wmisc::cmovieplayer
{
typedef void (*GetWmvChunk_t)(uint8_t *unk);
static GetWmvChunk_t GetWmvChunk_;
void on_get_wmv_chunk_(uint8_t *unk)
{
size_t chunksz = *(uint32_t *) (unk + 0x10);
void *readbuf = (void *) *(intptr_t *) (unk + 0x08);
int *readsz = (int *) (unk + 0x14);
//_REV: For now simply disable broken video stream
*readsz = 0;
}
void attach(HINSTANCE hmodule)
{
// Remove crashing movie
intptr_t TX_ThumExists = find_pattern_from(hmodule, "40534883ec20448b4110488bd9488b51088b09ff150000000089431485c0791d448bc0488d1500000000488d0d000000004883c4205b48ff25000000004883c4205bc3", "XXXXXXXXXXXXXXXXXXXXX????XXXXXXXXXXXXX????XXX????XXXXXXXX????XXXXXX", 0, 0, 0);
log_info("hooks::wmisc::cmovieplayer", "Hooking GetWmvChunk({})", reinterpret_cast<void *>(TX_ThumExists));
if (detour::trampoline_try(reinterpret_cast<void *>(TX_ThumExists), reinterpret_cast<void *>(on_get_wmv_chunk_), reinterpret_cast<void **>(&GetWmvChunk_)))
{
log_warning("hooks::wmisc::cmovieplayer", "Video playback for wmv won't work");
}
}
}
namespace hooks::wmisc::dmoeffector
{
typedef HRESULT (STDAPICALLTYPE *DllRegisterServer_t)();
HMODULE LoadLibraryRsrc(const char *rsrcname) {
// Unpack library blob
HRSRC hrsrc = FindResourceA(nullptr, rsrcname, RT_RCDATA);
void* rsrcdata = LockResource(LoadResource(nullptr, hrsrc));
DWORD rsrcsz = SizeofResource(nullptr, hrsrc);
// _REV: could use tmpfs instead if bmsound_wine provides tempfile wrapper in future
// Store as temporary file (no direct way to in-memory load without involving custom loaders like MemoryModule)
char tempPath[MAX_PATH];
char tempFile[MAX_PATH];
GetTempPathA(MAX_PATH, tempPath);
GetTempFileNameA(tempPath, rsrcname, 0, tempFile);
MoveFileExA(tempFile, nullptr, MOVEFILE_DELAY_UNTIL_REBOOT); // wine blocks DeleteFileA operations on LoadLibraryA filepaths
FILE *fh = fopen(tempFile, "wb");
fwrite(rsrcdata, rsrcsz, 1, fh);
fclose(fh);
// Map temporary library buffer
HMODULE hmodule = LoadLibraryA(tempFile);
return hmodule;
}
void attach(HINSTANCE hmodule)
{
HMODULE libdmo = LoadLibraryRsrc("DMO_BLOB");
if(libdmo)
{
log_info("hooks::wmisc::dmoeffector", "Registering COM server for DMO({})", reinterpret_cast<void *>(libdmo));
auto DllRegisterServer = (DllRegisterServer_t)GetProcAddress(libdmo, "DllRegisterServer");
if(DllRegisterServer && SUCCEEDED(DllRegisterServer())) return;
}
log_warning("hooks::wmisc::dmoeffector", "Loading embedded dsdmo.dll failed, error code: {}", GetLastError());
}
}
namespace hooks::wmisc
{
static bool patchmodel_ = true;
void skip_patching_by_model(bool val)
{
patchmodel_ = !val;
}
void apply_performance_hacks(HINSTANCE hmodule, const char *model, const char *ext)
{
if (!patchmodel_ && strcmp(model, "FORCE") != 0) return;
log_info("hooks::wmisc", "Enabling performance patches for {}-{}", model, ext);
long extl = strtol(ext, nullptr, 10);
switch (to_hash(model))
{
case to_hash("LDJ"):
dmoeffector::attach(hmodule);
thumbnail::attach(hmodule);
msacm::attach(hmodule);
bmsound::attach(hmodule);
break;
case to_hash("M39"):
msacm::attach(hmodule);
bmsound::attach(hmodule);
break;
case to_hash("KFC"):
dmoeffector::attach(hmodule);
msacm::attach(hmodule);
bmsound::attach(hmodule);
if (extl > 2019020600 && extl < 2021011800) // SDVX5
{
cmovieplayer::attach(hmodule);
}
break;
case to_hash("FORCE"):
switch (to_hash(ext))
{
case to_hash("thumbnail"):
thumbnail::attach(hmodule);
break;
case to_hash("msacm"):
msacm::attach(hmodule);
break;
case to_hash("bmsound"):
bmsound::attach(hmodule);
break;
case to_hash("cmovieplayer"):
cmovieplayer::attach(hmodule);
break;
case to_hash("dmoeffector"):
dmoeffector::attach(hmodule);
break;
default:
break;
}
break;
default:
break;
}
}
}
-9
View File
@@ -1,9 +0,0 @@
#pragma once
#include <windows.h>
namespace hooks::wmisc
{
void skip_patching_by_model(bool val);
void apply_performance_hacks(HINSTANCE hmodule, const char *model, const char *ext);
}
-265
View File
@@ -1,265 +0,0 @@
#include "signal.h"
#include <future>
#include <exception>
#include <windows.h>
#include <dbghelp.h>
#include "acio/acio.h"
#include "external/stackwalker/stackwalker.h"
#include "hooks/libraryhook.h"
#include "launcher/shutdown.h"
#include "util/deferlog.h"
#include "util/detour.h"
#include "util/libutils.h"
#include "util/logging.h"
#include "cfg/configurator.h"
#include "logger.h"
// MSVC compatibility
#ifdef exception_code
#undef exception_code
#endif
static decltype(MiniDumpWriteDump) *MiniDumpWriteDump_local = nullptr;
namespace launcher::signal {
// settings
bool DISABLE = false;
bool USE_VEH_WORKAROUND = false;
}
#define V(variant) case variant: return #variant
static std::string control_code(DWORD dwCtrlType) {
switch (dwCtrlType) {
V(CTRL_C_EVENT);
V(CTRL_BREAK_EVENT);
V(CTRL_CLOSE_EVENT);
V(CTRL_LOGOFF_EVENT);
V(CTRL_SHUTDOWN_EVENT);
default:
return "Unknown(0x" + to_hex(dwCtrlType) + ")";
}
}
static std::string exception_code(struct _EXCEPTION_RECORD *ExceptionRecord) {
switch (ExceptionRecord->ExceptionCode) {
V(EXCEPTION_ACCESS_VIOLATION);
V(EXCEPTION_ARRAY_BOUNDS_EXCEEDED);
V(EXCEPTION_BREAKPOINT);
V(EXCEPTION_DATATYPE_MISALIGNMENT);
V(EXCEPTION_FLT_DENORMAL_OPERAND);
V(EXCEPTION_FLT_DIVIDE_BY_ZERO);
V(EXCEPTION_FLT_INEXACT_RESULT);
V(EXCEPTION_FLT_INVALID_OPERATION);
V(EXCEPTION_FLT_OVERFLOW);
V(EXCEPTION_FLT_STACK_CHECK);
V(EXCEPTION_FLT_UNDERFLOW);
V(EXCEPTION_ILLEGAL_INSTRUCTION);
V(EXCEPTION_IN_PAGE_ERROR);
V(EXCEPTION_INT_DIVIDE_BY_ZERO);
V(EXCEPTION_INT_OVERFLOW);
V(EXCEPTION_INVALID_DISPOSITION);
V(EXCEPTION_NONCONTINUABLE_EXCEPTION);
V(EXCEPTION_PRIV_INSTRUCTION);
V(EXCEPTION_SINGLE_STEP);
V(EXCEPTION_STACK_OVERFLOW);
V(DBG_CONTROL_C);
default:
return "Unknown(0x" + to_hex(ExceptionRecord->ExceptionCode) + ")";
}
}
#undef V
static BOOL WINAPI HandlerRoutine(DWORD dwCtrlType) {
log_info("signal", "console ctrl handler called: {}", control_code(dwCtrlType));
if (dwCtrlType == CTRL_C_EVENT) {
launcher::shutdown();
} else if (dwCtrlType == CTRL_CLOSE_EVENT) {
launcher::shutdown();
}
return FALSE;
}
static LONG WINAPI TopLevelExceptionFilter(struct _EXCEPTION_POINTERS *ExceptionInfo) {
// ignore signal if disabled or no exception info provided
if (!launcher::signal::DISABLE && ExceptionInfo != nullptr) {
// get exception record
struct _EXCEPTION_RECORD *ExceptionRecord = ExceptionInfo->ExceptionRecord;
// print signal
log_warning("signal", "exception raised: {}", exception_code(ExceptionRecord));
switch (ExceptionRecord->ExceptionCode) {
case EXCEPTION_ILLEGAL_INSTRUCTION:
deferredlogs::defer_error_messages({
"illegal instruction exception:",
" either your CPU is too old (e.g., does not support SSE4.2 or AVX2 ",
" but perhaps the game requires it); or, a bad patch was applied."
});
break;
default:
break;
}
// check ACIO init failures
if (acio::IO_INIT_IN_PROGRESS) {
deferredlogs::defer_error_messages({
"exception raised during ACIO init, this usually happens when ",
" a third party application interferes with hooks",
" please check for the following, disable them, and try launching the game again:",
" * RivaTuner Statistics Server (RTSS)",
" * MSI Afterburner",
" * kernel mode anti-cheat"
});
}
// dump deferred logs BEFORE stack trace since some errors cause stack trace logic to hang
// (e.g., ACIO init hang due to RTSS)
deferredlogs::dump_to_logger(true);
// walk the exception chain
struct _EXCEPTION_RECORD *record_cause = ExceptionRecord->ExceptionRecord;
while (record_cause != nullptr) {
log_warning("signal", "caused by: {}", exception_code(record_cause));
record_cause = record_cause->ExceptionRecord;
}
// print stacktrace
StackWalker sw;
log_info("signal", "printing callstack");
if (!sw.ShowCallstack(GetCurrentThread(), ExceptionInfo->ContextRecord)) {
log_warning("signal", "failed to print callstack");
}
if (MiniDumpWriteDump_local != nullptr) {
HANDLE minidump_file = CreateFileA(
"minidump.dmp",
GENERIC_WRITE,
0,
nullptr,
CREATE_ALWAYS,
FILE_ATTRIBUTE_NORMAL,
nullptr);
if (minidump_file != INVALID_HANDLE_VALUE) {
MINIDUMP_EXCEPTION_INFORMATION ExceptionParam {};
ExceptionParam.ThreadId = GetCurrentThreadId();
ExceptionParam.ExceptionPointers = ExceptionInfo;
ExceptionParam.ClientPointers = FALSE;
MiniDumpWriteDump_local(
GetCurrentProcess(),
GetCurrentProcessId(),
minidump_file,
MiniDumpNormal,
&ExceptionParam,
nullptr,
nullptr);
CloseHandle(minidump_file);
} else {
log_warning("signal", "failed to create 'minidump.dmp' for minidump: 0x{:08x}",
GetLastError());
}
} else {
log_warning("signal", "minidump creation function not available, skipping");
}
// this will stall all UI threads for this process
show_popup_for_crash();
log_fatal("signal", "end");
}
return EXCEPTION_CONTINUE_SEARCH;
}
static BOOL WINAPI SetConsoleCtrlHandler_hook(PHANDLER_ROUTINE pHandlerRoutine, BOOL Add) {
static std::once_flag printed;
std::call_once(printed, []() {
log_misc("signal", "SetConsoleCtrlHandler hook hit (printing once)");
});
return TRUE;
}
static LPTOP_LEVEL_EXCEPTION_FILTER WINAPI SetUnhandledExceptionFilter_hook(
LPTOP_LEVEL_EXCEPTION_FILTER lpTopLevelExceptionFilter) {
static std::once_flag printed;
std::call_once(printed, []() {
log_info("signal", "SetUnhandledExceptionFilter hook hit (printing once)");
});
return nullptr;
}
static PVOID WINAPI AddVectoredExceptionHandler_hook(ULONG First, PVECTORED_EXCEPTION_HANDLER Handler) {
static std::once_flag printed;
std::call_once(printed, []() {
log_info("signal", "AddVectoredExceptionHandler hook hit (printing once)");
});
return launcher::signal::USE_VEH_WORKAROUND ? INVALID_HANDLE_VALUE : nullptr;
}
void launcher::signal::attach() {
if (launcher::signal::DISABLE) {
return;
}
log_info("signal", "attaching...");
// set a `std::terminate` handler so `std::abort()` is not called by default
std::set_terminate([]() {
log_warning("signal", "std::terminate called");
launcher::kill();
});
// NOTE: inline hooks are not used here as they have caused EXCEPTION_ACCESS_VIOLATION in the past
// when hooking these methods
// hook relevant functions
libraryhook_hook_proc("SetConsoleCtrlHandler", SetConsoleCtrlHandler_hook);
libraryhook_hook_proc("SetUnhandledExceptionFilter", SetUnhandledExceptionFilter_hook);
libraryhook_hook_proc("AddVectoredExceptionHandler", AddVectoredExceptionHandler_hook);
libraryhook_enable();
// hook in all loaded modules
detour::iat_try("SetConsoleCtrlHandler", SetConsoleCtrlHandler_hook);
detour::iat_try("SetUnhandledExceptionFilter", SetUnhandledExceptionFilter_hook);
detour::iat_try("AddVectoredExceptionHandler", AddVectoredExceptionHandler_hook);
log_info("signal", "attached");
}
void launcher::signal::init() {
// load debug help library
if (!cfg::CONFIGURATOR_STANDALONE) {
auto dbghelp = libutils::try_library("dbghelp.dll");
if (dbghelp != nullptr) {
MiniDumpWriteDump_local = libutils::try_proc<decltype(MiniDumpWriteDump) *>(
dbghelp, "MiniDumpWriteDump");
}
}
// register our console ctrl handler
SetConsoleCtrlHandler(HandlerRoutine, TRUE);
// register our exception handler
SetUnhandledExceptionFilter(TopLevelExceptionFilter);
}
-91
View File
@@ -1,91 +0,0 @@
// enable touch functions - set version to windows 7
// mingw otherwise doesn't load touch stuff
#define _WIN32_WINNT 0x0601
#include "wintouchemu.h"
#include "util/detour.h"
#include "util/logging.h"
#define TOUCH_SIMULATE_FAT_FINGERS 0
#define TOUCH_DEBUG_VERBOSE 0
#if TOUCH_DEBUG_VERBOSE
#define log_debug(module, format_str, ...) logger::push( \
LOG_FORMAT("M", module, format_str, ## __VA_ARGS__), logger::Style::GREY)
#else
#define log_debug(module, format_str, ...)
#endif
namespace nativetouchhook {
static decltype(GetTouchInputInfo) *GetTouchInputInfo_orig = nullptr;
static void strip_contact_size(PTOUCHINPUT point) {
#if TOUCH_SIMULATE_FAT_FINGERS
point->dwMask |= 0x004;
point->cxContact = 80 * 100;
point->cyContact = 60 * 100;
#endif
// most monitors do not set TOUCHEVENTFMASK_CONTACTAREA, but for
// monitors that do set it, IIDX can get very confused (SDVX is not
// affected)
//
// while the test menu and the touch "glow" seem to work properly,
// interacting with subscreen menu items or entering PIN becomes
// very unpredictable
//
// to fix this, simply remove the contact area width and height
//
// note: test menu > I/O > touch test gives 5 numbers:
// n: x, y, w, h
// where
// n is the nth touch input since boot
// x, y are coordinates (center of finger)
// w, h are contact width and height
//
// when TOUCHEVENTFMASK_CONTACTAREA is not set, w/h will
// automatically be seen as 1x1, which works perfectly fine
log_debug(
"touch::native",
"[{}, {}] dwMask = 0x{:x}, cxContact = {}, cyContact = {}",
point->x / 100,
point->y / 100,
point->dwMask,
point->cxContact,
point->cyContact);
point->dwMask &= ~(0x004ul); // clear TOUCHEVENTFMASK_CONTACTAREA
point->cxContact = 0;
point->cyContact = 0;
}
static BOOL WINAPI GetTouchInputInfoHook(
HTOUCHINPUT hTouchInput, UINT cInputs, PTOUCHINPUT pInputs, int cbSize) {
// call the original fist
const auto result = GetTouchInputInfo_orig(hTouchInput, cInputs, pInputs, cbSize);
if (result == 0) {
return result;
}
for (size_t i = 0; i < cInputs; i++) {
PTOUCHINPUT point = &pInputs[i];
strip_contact_size(point);
}
return result;
}
void hook(HMODULE module) {
GetTouchInputInfo_orig = detour::iat_try("GetTouchInputInfo", GetTouchInputInfoHook, module);
if (GetTouchInputInfo_orig != nullptr) {
log_misc("touch::native", "GetTouchInputInfo hooked");
}
}
}
-6
View File
@@ -1,6 +0,0 @@
#include <windows.h>
namespace nativetouchhook {
void hook(HMODULE module);
}
-408
View File
@@ -1,408 +0,0 @@
#include "impl_spice.h"
#include <windows.h>
#include "cfg/configurator.h"
#include "games/io.h"
#include "launcher/launcher.h"
#include "launcher/superexit.h"
#include "misc/eamuse.h"
#include "overlay/overlay.h"
#include "rawinput/rawinput.h"
#include "touch/touch.h"
#include "util/logging.h"
#include "util/utils.h"
#include "hooks/graphics/graphics.h"
#if !defined(IMGUI_ENABLE_WIN32_DEFAULT_IME_FUNCTIONS) || \
!defined(IMGUI_DISABLE_DEFAULT_ALLOCATORS) || \
!defined(IMGUI_USE_BGRA_PACKED_COLOR) || \
!defined(IMGUI_HAS_VIEWPORT) || \
!defined(IMGUI_HAS_DOCK) || \
!defined(IMGUI_DISABLE_DEMO_WINDOWS) || \
defined(IMGUI_DISABLE_DEBUG_TOOLS)
#error "fix imconfig.h after updating imgui version"
#endif
// state
static HWND g_hWnd = nullptr;
static INT64 g_Time = 0;
static INT64 g_TicksPerSecond = 0;
static ImGuiMouseCursor g_LastMouseCursor = ImGuiMouseCursor_COUNT;
bool ImGui_ImplSpice_Init(HWND hWnd) {
log_misc("imgui_impl_spice", "init");
// check if already initialized
if (g_hWnd != nullptr) {
if (g_hWnd == hWnd) {
return true;
} else {
ImGui_ImplSpice_Shutdown();
}
}
// init performance stuff
if (!::QueryPerformanceFrequency((LARGE_INTEGER *)&g_TicksPerSecond))
return false;
if (!::QueryPerformanceCounter((LARGE_INTEGER *)&g_Time))
return false;
// setup back-end capabilities flags
g_hWnd = hWnd;
ImGuiIO &io = ImGui::GetIO();
io.BackendFlags |= ImGuiBackendFlags_HasMouseCursors;
io.BackendFlags |= ImGuiBackendFlags_HasSetMousePos;
io.BackendPlatformName = "imgui_impl_spice";
io.ConfigErrorRecoveryEnableTooltip = true;
// keyboard mapping
io.KeyMap[ImGuiKey_Tab] = VK_TAB;
io.KeyMap[ImGuiKey_LeftArrow] = VK_LEFT;
io.KeyMap[ImGuiKey_RightArrow] = VK_RIGHT;
io.KeyMap[ImGuiKey_UpArrow] = VK_UP;
io.KeyMap[ImGuiKey_DownArrow] = VK_DOWN;
io.KeyMap[ImGuiKey_PageUp] = VK_PRIOR;
io.KeyMap[ImGuiKey_PageDown] = VK_NEXT;
io.KeyMap[ImGuiKey_Home] = VK_HOME;
io.KeyMap[ImGuiKey_End] = VK_END;
io.KeyMap[ImGuiKey_Insert] = VK_INSERT;
io.KeyMap[ImGuiKey_Delete] = VK_DELETE;
io.KeyMap[ImGuiKey_Backspace] = VK_BACK;
io.KeyMap[ImGuiKey_Space] = VK_SPACE;
io.KeyMap[ImGuiKey_Enter] = VK_RETURN;
io.KeyMap[ImGuiKey_Escape] = VK_ESCAPE;
io.KeyMap[ImGuiKey_KeypadEnter] = VK_RETURN;
io.KeyMap[ImGuiKey_A] = 'A';
io.KeyMap[ImGuiKey_C] = 'C';
io.KeyMap[ImGuiKey_V] = 'V';
io.KeyMap[ImGuiKey_X] = 'X';
io.KeyMap[ImGuiKey_Y] = 'Y';
io.KeyMap[ImGuiKey_Z] = 'Z';
// get display size
ImGui_ImplSpice_UpdateDisplaySize();
// return success
return true;
}
void ImGui_ImplSpice_Shutdown() {
log_misc("imgui_impl_spice", "shutdown");
// reset window handle
g_hWnd = nullptr;
}
void ImGui_ImplSpice_UpdateDisplaySize() {
// get display size
RECT rect;
::GetClientRect(g_hWnd, &rect);
ImGui::GetIO().DisplaySize = ImVec2((float)(rect.right - rect.left), (float)(rect.bottom - rect.top));
}
bool ImGui_ImplSpice_UpdateMouseCursor() {
// check if cursor should be changed
auto &io = ImGui::GetIO();
if (io.ConfigFlags & ImGuiConfigFlags_NoMouseCursorChange) {
return false;
}
// update cursor
ImGuiMouseCursor imgui_cursor = ImGui::GetMouseCursor();
if (imgui_cursor == ImGuiMouseCursor_None || io.MouseDrawCursor) {
// hide OS mouse cursor if imgui is drawing it or if it wants no cursor
::SetCursor(nullptr);
} else {
// show OS mouse cursor
LPTSTR win32_cursor = IDC_ARROW;
switch (imgui_cursor) {
case ImGuiMouseCursor_Arrow:
win32_cursor = IDC_ARROW;
break;
case ImGuiMouseCursor_TextInput:
win32_cursor = IDC_IBEAM;
break;
case ImGuiMouseCursor_ResizeAll:
win32_cursor = IDC_SIZEALL;
break;
case ImGuiMouseCursor_ResizeEW:
win32_cursor = IDC_SIZEWE;
break;
case ImGuiMouseCursor_ResizeNS:
win32_cursor = IDC_SIZENS;
break;
case ImGuiMouseCursor_ResizeNESW:
win32_cursor = IDC_SIZENESW;
break;
case ImGuiMouseCursor_ResizeNWSE:
win32_cursor = IDC_SIZENWSE;
break;
case ImGuiMouseCursor_Hand:
win32_cursor = IDC_HAND;
break;
default:
break;
}
::SetCursor(::LoadCursor(nullptr, win32_cursor));
}
return true;
}
static void ImGui_ImplSpice_UpdateMousePos() {
// get current window size
RECT rect;
if (GetClientRect(g_hWnd, &rect)) {
ImVec2 window_size(
(float)(rect.right - rect.left),
(float)(rect.bottom - rect.top));
// set OS mouse position if requested
auto &io = ImGui::GetIO();
if (io.WantSetMousePos) {
POINT pos {
.x = static_cast<long>(io.MousePos.x),
.y = static_cast<long>(io.MousePos.y),
};
::ClientToScreen(g_hWnd, &pos);
::SetCursorPos(
static_cast<int>(pos.x / io.DisplaySize.x * window_size.x),
static_cast<int>(pos.y / io.DisplaySize.y * window_size.y));
}
const auto active_window = ::GetForegroundWindow();
// if the main focus is a windowed subscreen, put the imgui cursor in a place that won't
// trigger any overlay, don't process anything else
const auto is_windowed_subscreen =
(GRAPHICS_IIDX_WSUB && active_window == TDJ_SUBSCREEN_WINDOW) ||
(active_window == SDVX_SUBSCREEN_WINDOW);
if (is_windowed_subscreen) {
io.MousePos = ImVec2(-FLT_MAX, -FLT_MAX);
return;
}
// set mouse position
io.MousePos = ImVec2(-FLT_MAX, -FLT_MAX);
POINT pos;
if (active_window) {
if (active_window == g_hWnd
|| ::IsChild(active_window, g_hWnd)
|| ::IsChild(g_hWnd, active_window)
|| active_window == SPICETOUCH_TOUCH_HWND) {
if (::GetCursorPos(&pos) && ::ScreenToClient(g_hWnd, &pos)) {
io.MousePos = ImVec2(
(float) pos.x * io.DisplaySize.x / window_size.x,
(float) pos.y * io.DisplaySize.y / window_size.y);
}
}
}
// fallback to touch hwnd
if (io.MousePos.x == -FLT_MAX || io.MousePos.y == -FLT_MAX) {
if (SPICETOUCH_TOUCH_HWND) {
if (::GetCursorPos(&pos) && ::ScreenToClient(SPICETOUCH_TOUCH_HWND, &pos)) {
io.MousePos = ImVec2(
(float) pos.x * io.DisplaySize.x / window_size.x,
(float) pos.y * io.DisplaySize.y / window_size.y);
}
}
}
// alternatively check touch
std::vector<TouchPoint> touch_points;
touch_get_points(touch_points, true);
static size_t delay_touch = 0;
static size_t delay_touch_target = 2;
static DWORD last_touch_id = ~0u;
if (!touch_points.empty() && !is_windowed_subscreen) {
// use the first touch point
auto &tp = touch_points[0];
io.MousePos.x = tp.x * io.DisplaySize.x / window_size.x;
io.MousePos.y = tp.y * io.DisplaySize.y / window_size.y;
// update cursor position
if (!tp.mouse) {
pos.x = static_cast<long>(io.MousePos.x);
pos.y = static_cast<long>(io.MousePos.y);
::ClientToScreen(g_hWnd, &pos);
::SetCursorPos(
static_cast<long>(pos.x / io.DisplaySize.x * window_size.x),
static_cast<long>(pos.y / io.DisplaySize.y * window_size.y));
}
// delay press
io.MouseDown[0] = delay_touch++ >= delay_touch_target && last_touch_id == tp.id;
if (last_touch_id == ~0u) {
last_touch_id = tp.id;
}
} else {
// reset
delay_touch = 0;
last_touch_id = ~0;
}
}
}
void ImGui_ImplSpice_NewFrame() {
// check if font is built
ImGuiIO& io = ImGui::GetIO();
IM_ASSERT(io.Fonts->IsBuilt());
// setup time step
INT64 current_time;
::QueryPerformanceCounter((LARGE_INTEGER *)&current_time);
io.DeltaTime = (float) (current_time - g_Time) / g_TicksPerSecond;
g_Time = current_time;
// remember old state
BYTE KeysDownOld[sizeof(io.KeysDown)];
for (size_t i = 0; i < sizeof(io.KeysDown); i++) {
KeysDownOld[i] = io.KeysDown[i] ? ~0 : 0;
}
KeysDownOld[VK_SHIFT] |= KeysDownOld[VK_LSHIFT];
KeysDownOld[VK_SHIFT] |= KeysDownOld[VK_RSHIFT];
// reset keys state
io.MouseWheel = 0;
io.KeyCtrl = false;
io.KeyShift = false;
io.KeyAlt = false;
io.KeySuper = false;
memset(io.KeysDown, false, sizeof(io.KeysDown));
memset(io.MouseDown, false, sizeof(io.MouseDown));
// early quit if window not in focus
if (!superexit::has_focus()) {
return;
}
// read keyboard modifiers inputs
io.KeyCtrl = (::GetKeyState(VK_CONTROL) & 0x8000) != 0;
io.KeyShift = (::GetKeyState(VK_SHIFT) & 0x8000) != 0;
io.KeyAlt = (::GetKeyState(VK_MENU) & 0x8000) != 0;
io.KeySuper = (::GetKeyState(VK_LWIN) & 0x8000) != 0;
io.KeySuper |= (::GetKeyState(VK_RWIN) & 0x8000) != 0;
// apply windows mouse buttons
io.MouseDown[0] |= (get_async_primary_mouse()) != 0;
io.MouseDown[1] |= (get_async_secondary_mouse()) != 0;
io.MouseDown[2] |= (GetAsyncKeyState(VK_MBUTTON)) != 0;
// read new keys state
static long mouse_wheel_last = 0;
long mouse_wheel = 0;
if (RI_MGR != nullptr) {
auto devices = RI_MGR->devices_get();
for (auto &device : devices) {
switch (device.type) {
case rawinput::MOUSE: {
auto &mouse = device.mouseInfo;
// mouse button triggers
if (GetSystemMetrics(SM_SWAPBUTTON)) {
if (mouse->key_states[rawinput::MOUSEBTN_RIGHT]) {
io.MouseDown[0] = true;
}
if (mouse->key_states[rawinput::MOUSEBTN_LEFT]) {
io.MouseDown[1] = true;
}
} else {
if (mouse->key_states[rawinput::MOUSEBTN_LEFT]) {
io.MouseDown[0] = true;
}
if (mouse->key_states[rawinput::MOUSEBTN_RIGHT]) {
io.MouseDown[1] = true;
}
}
if (mouse->key_states[rawinput::MOUSEBTN_MIDDLE]) {
io.MouseDown[2] = true;
}
// final mouse wheel value should be all devices combined
mouse_wheel += mouse->pos_wheel;
break;
}
case rawinput::KEYBOARD: {
// iterate all virtual key codes
for (size_t vKey = 0; vKey < 256; vKey++) {
// get state (combined from all pages)
auto &key_states = device.keyboardInfo->key_states;
bool state = false;
for (size_t page_index = 0; page_index < 1024; page_index += 256) {
state |= key_states[page_index + vKey];
}
// trigger
io.KeysDown[vKey] |= state;
// generate character input, but only if WM_CHAR didn't take over the
// functionality
if (!overlay::USE_WM_CHAR_FOR_IMGUI_CHAR_INPUT && !KeysDownOld[vKey] && state) {
UCHAR buf[2];
auto ret = ToAscii(
static_cast<UINT>(vKey),
0,
static_cast<const BYTE *>(KeysDownOld),
reinterpret_cast<LPWORD>(buf),
0);
if (ret > 0) {
for (int i = 0; i < ret; i++) {
overlay::OVERLAY->input_char(buf[i]);
}
}
}
}
break;
}
default:
break;
}
}
}
// set mouse wheel
auto mouse_diff = mouse_wheel - mouse_wheel_last;
mouse_wheel_last = mouse_wheel;
io.MouseWheel = mouse_diff;
// update OS mouse position
ImGui_ImplSpice_UpdateMousePos();
if (cfg::CONFIGURATOR_STANDALONE) {
// if cursor is inside the client area, always set the OS cursor to what ImGui wants
// this is to deal with cases where mouse cursor changes outside the client rect and comes
// back into the window
// i'm sure there might be better ways to deal with this but this works so whatever, right?
RECT client_rect;
if (GetClientRect(g_hWnd, &client_rect)) {
POINT cursor;
if (GetCursorPos(&cursor) && ScreenToClient(g_hWnd, &cursor)) {
if (client_rect.left < cursor.x && cursor.x < client_rect.right &&
client_rect.top < cursor.y && cursor.y < client_rect.bottom) {
ImGui_ImplSpice_UpdateMouseCursor();
}
}
}
} else {
// update OS mouse cursor with the cursor requested by imgui
ImGuiMouseCursor mouse_cursor = io.MouseDrawCursor ? ImGuiMouseCursor_None : ImGui::GetMouseCursor();
if (g_LastMouseCursor != mouse_cursor) {
g_LastMouseCursor = mouse_cursor;
ImGui_ImplSpice_UpdateMouseCursor();
}
}
}
-157
View File
@@ -1,157 +0,0 @@
#pragma once
#include <memory>
#include <mutex>
#include <functional>
#include <optional>
#include <vector>
#include <windows.h>
#include <d3d9.h>
#include "external/imgui/imgui.h"
namespace overlay {
class Window;
enum class OverlayRenderer {
D3D9,
SOFTWARE,
};
// settings
extern bool ENABLED;
extern bool AUTO_SHOW_FPS;
extern bool AUTO_SHOW_SUBSCREEN;
extern bool AUTO_SHOW_IOPANEL;
extern bool AUTO_SHOW_KEYPAD_P1;
extern bool AUTO_SHOW_KEYPAD_P2;
extern bool USE_WM_CHAR_FOR_IMGUI_CHAR_INPUT;
extern bool FPS_SHOULD_FLIP;
extern bool SHOW_DEBUG_LOG_WINDOW;
extern std::optional<uint32_t> UI_SCALE_PERCENT;
template <typename T>
float apply_scaling(T input) {
if (!UI_SCALE_PERCENT.has_value()) {
return input;
}
return static_cast<float>(input) * UI_SCALE_PERCENT.value() / 100.f;
}
ImVec2 apply_scaling_to_vector(const ImVec2& input);
ImVec2 apply_scaling_to_vector(float x, float y);
class SpiceOverlay {
public:
D3DDEVICE_CREATION_PARAMETERS creation_parameters {};
D3DADAPTER_IDENTIFIER9 adapter_identifier {};
bool hotkeys_enable = true;
// windows
Window *window_fps = nullptr;
Window *window_iopanel = nullptr;
Window *window_config = nullptr;
Window *window_keypad1 = nullptr;
Window *window_keypad2 = nullptr;
Window *window_cards = nullptr;
Window *window_control = nullptr;
Window *window_resize = nullptr;
Window *window_camera = nullptr;
Window *window_sub = nullptr;
Window *window_log = nullptr;
explicit SpiceOverlay(HWND hWnd, IDirect3D9 *d3d, IDirect3DDevice9 *device);
explicit SpiceOverlay(HWND hWnd);
~SpiceOverlay();
void window_add(Window *wnd);
void new_frame();
void render();
void update();
void toggle_active(bool overlay_key = false);
void show_main_menu();
void set_active(bool active);
bool get_active();
bool has_focus();
bool hotkeys_triggered();
static bool update_cursor();
static void reset_invalidate();
static void reset_recreate();
void input_char(unsigned int c);
uint32_t *sw_get_pixel_data(int *width, int *height);
inline bool uses_window(HWND hWnd) {
return this->hWnd == hWnd;
}
inline bool uses_context(IDirect3D9 *other) {
return this->d3d == other;
}
inline bool uses_device(IDirect3DDevice9 *other) {
return this->device == other;
}
inline IDirect3DDevice9 *get_device() {
return this->device;
}
bool can_transform_touch_input() {
return (this->subscreen_mouse_handler != nullptr);
}
bool transform_touch_point(LONG *x, LONG *y) {
if (this->get_active() && this->subscreen_mouse_handler) {
return this->subscreen_mouse_handler(x, y);
} else {
return true;
}
}
void set_subscreen_mouse_handler(const std::function<bool(LONG *, LONG *)> &f) {
this->subscreen_mouse_handler = f;
}
// renderer
OverlayRenderer renderer;
float total_elapsed = 0.f;
private:
HWND hWnd = nullptr;
// D3D9
IDirect3D9 *d3d = nullptr;
IDirect3DDevice9 *device = nullptr;
// software
std::vector<uint32_t> pixel_data;
size_t pixel_data_width = 0;
size_t pixel_data_height = 0;
std::vector<std::unique_ptr<Window>> windows;
Window *window_main_menu = nullptr;
std::function<bool(LONG *, LONG *)> subscreen_mouse_handler = nullptr;
bool active = false;
bool toggle_down = false;
bool main_menu_down = false;
bool hotkey_toggle = false;
bool hotkey_toggle_last = false;
void init();
void add_font(const char* font, ImFontConfig* config, const ImWchar* glyphs);
};
// global
extern std::mutex OVERLAY_MUTEX;
extern std::unique_ptr<overlay::SpiceOverlay> OVERLAY;
extern ImFont* DSEG_FONT;
// synchronized helpers
void create_d3d9(HWND hWnd, IDirect3D9 *d3d, IDirect3DDevice9 *device);
void create_software(HWND hWnd);
void destroy(HWND hWnd = nullptr);
}
File diff suppressed because it is too large Load Diff
-127
View File
@@ -1,127 +0,0 @@
#pragma once
#include <optional>
#include "cfg/game.h"
#include "overlay/window.h"
#include "rawinput/device.h"
#include "external/imgui/imgui_filebrowser.h"
#include "patch_manager.h"
namespace overlay::windows {
enum class ConfigTab {
CONFIG_TAB_INVALID,
CONFIG_TAB_BUTTONS,
CONFIG_TAB_ANALOGS,
CONFIG_TAB_OVERLAY,
CONFIG_TAB_LIGHTS,
CONFIG_TAB_CARDS,
CONFIG_TAB_PATCHES,
CONFIG_TAB_API,
CONFIG_TAB_OPTIONS,
CONFIG_TAB_ADVANCED,
CONFIG_TAB_DEV,
CONFIG_TAB_SEARCH,
};
class Config : public Window {
private:
// game selection
int games_selected = -1;
std::string games_selected_name = "";
std::vector<Game> games_list;
std::vector<const char *> games_names;
// tabs ui
ConfigTab tab_selected = ConfigTab::CONFIG_TAB_INVALID;
// buttons tab
bool buttons_keyboard_state[0xFF];
bool buttons_bind_active = false;
bool buttons_many_active = false;
std::string buttons_many_active_section = "";
bool buttons_many_naive = false;
int buttons_many_delay = 0;
int buttons_many_index = -1;
void inc_buttons_many_index(int index_max);
// analogs tab
std::vector<rawinput::Device *> analogs_devices;
int analogs_devices_selected = -1;
int analogs_devices_control_selected = -1;
// lights tab
std::vector<rawinput::Device *> lights_devices;
int lights_devices_selected = -1;
int lights_devices_control_selected = -1;
// keypads tab
int keypads_selected[2] {};
char keypads_card_path[2][1024] {};
std::thread *keypads_card_select = nullptr;
bool keypads_card_select_done = false;
ImGui::FileBrowser keypads_card_select_browser[2];
char keypads_card_number[2][18] {};
// patches tab
std::unique_ptr<PatchManager> patch_manager;
// options tab
bool options_show_hidden = false;
bool options_dirty = false;
int options_category = 0;
std::string search_filter = "";
std::string search_filter_in_lower_case = "";
void build_buttons(const std::string &name, std::vector<Button> *buttons, int min = 0, int max = -1);
void build_button(
const std::string &name,
Button &primary_button,
Button *button,
const int button_it,
const int button_it_max,
const int alt_index);
void bind_button_popup(const std::string &bind_name, Button *button, const int button_it_max, const int alt_index);
void naive_button_popup(const std::string &naive_string, Button *button, const int button_it_max, const int alt_index);
void edit_button_popup(
const std::string &edit_name,
const std::string &button_display,
Button *button,
const float button_velocity,
const int alt_index);
void clear_button(Button *button, const int alt_index);
void build_analogs(const std::string &name, std::vector<Analog> *analogs);
void edit_analog_popup(Analog &analog);
void build_lights(const std::string &name, std::vector<Light> *lights);
void build_light(Light &primary_light, Light *light, const int light_index, const int alt_index);
void clear_light(Light *light, const int alt_index);
void edit_light_popup(Light &primary_light, Light *light, const int alt_index);
void build_cards();
void build_options(
std::vector<Option> *options, const std::string &category, const std::string *filter=nullptr);
void build_about();
void build_launcher();
void launch_shell(LPCSTR app, LPCSTR file=nullptr);
void build_menu(int *game_selected);
void shutdown_system(bool force, bool reboot_instead);
void set_alternating_row_colors(const int row_index);
public:
Config(SpiceOverlay *overlay);
~Config() override;
void read_card(int player = -1);
void write_card(int player);
void build_content() override;
};
}
-58
View File
@@ -1,58 +0,0 @@
#include <iomanip>
#include <sstream>
#include "external/fmt/include/fmt/chrono.h"
#include "fps.h"
namespace overlay::windows {
FPS::FPS(SpiceOverlay *overlay) : Window(overlay) {
this->title = "Stats";
this->flags = ImGuiWindowFlags_NoTitleBar
| ImGuiWindowFlags_NoResize
| ImGuiWindowFlags_NoCollapse
| ImGuiWindowFlags_AlwaysAutoResize
| ImGuiWindowFlags_NoFocusOnAppearing
| ImGuiWindowFlags_NoNavFocus
| ImGuiWindowFlags_NoNavInputs
| ImGuiWindowFlags_NoDocking;
this->bg_alpha = 0.5f;
this->start_time =
std::chrono::floor<std::chrono::seconds>(std::chrono::system_clock::now());
}
void FPS::calculate_initial_window() {
// width is 114x82 px with window decoration, 98x47 for the content
int pos_x =
overlay::FPS_SHOULD_FLIP ?
overlay::apply_scaling(8) :
ImGui::GetIO().DisplaySize.x - overlay::apply_scaling(122);
this->init_pos = ImVec2(pos_x, overlay::apply_scaling(8));
}
void FPS::build_content() {
// frame timers
ImGuiIO &io = ImGui::GetIO();
ImGui::Text("FPS: %.1f", io.Framerate);
// ImGui::Text("FT: %.2fms", 1000 / io.Framerate);
const auto now = std::chrono::system_clock::now();
const auto now_s = std::chrono::floor<std::chrono::seconds>(now);
// current time
{
const std::time_t tt = std::chrono::system_clock::to_time_t(now_s);
std::tm local_tm{};
localtime_s(&local_tm, &tt);
ImGui::TextUnformatted(fmt::format("Time: {:%H:%M:%S}", local_tm).c_str());
}
// elapsed time
{
const auto uptime = now_s - this->start_time;
ImGui::TextUnformatted(
fmt::format("Up: {:%H:%M:%S}",
std::chrono::floor<std::chrono::seconds>(uptime)).c_str());
}
}
}
File diff suppressed because it is too large Load Diff
-174
View File
@@ -1,174 +0,0 @@
#pragma once
#include "overlay/window.h"
#include <map>
#include <functional>
#include <filesystem>
#include <optional>
#include "external/rapidjson/document.h"
namespace overlay::windows {
enum class PatchType {
Unknown,
Memory,
Signature,
Union,
Integer,
};
enum class PatchStatus {
Error,
Disabled,
Enabled,
};
enum class PatchUrlStatus {
Valid,
Invalid,
Unapplied,
ValidButNoData,
Partial,
};
struct MemoryPatch {
std::string dll_name = "";
std::shared_ptr<uint8_t[]> data_disabled = nullptr;
size_t data_disabled_len = 0;
std::shared_ptr<uint8_t[]> data_enabled = nullptr;
size_t data_enabled_len = 0;
uint64_t data_offset = 0;
uint8_t *data_offset_ptr = nullptr;
bool fatal_error = false;
};
struct PatchData;
struct SignaturePatch {
std::string dll_name = "";
std::string signature = "", replacement = "";
uint64_t offset = 0;
int64_t usage = 0;
MemoryPatch to_memory(PatchData *patch);
};
struct UnionPatch {
std::string name = "";
std::string dll_name = "";
std::shared_ptr<uint8_t[]> data = nullptr;
size_t data_len = 0;
uint64_t offset = 0;
uint8_t* data_offset_ptr = nullptr;
bool fatal_error = false;
};
struct NumberPatch {
std::string dll_name = "";
uint64_t data_offset = 0;
uint8_t* data_offset_ptr = nullptr;
int32_t min;
int32_t max;
int32_t value;
size_t size_in_bytes;
bool fatal_error = false;
};
struct PatchData {
bool enabled;
std::string game_code;
int datecode_min = 0;
int datecode_max = 0;
std::string name, description, caution;
std::string name_in_lower_case = "";
PatchType type;
bool preset;
std::vector<MemoryPatch> patches_memory;
std::vector<UnionPatch> patches_union;
NumberPatch patch_number;
PatchStatus last_status;
std::string hash;
bool unverified = false;
std::string peIdentifier;
std::string error_reason = "";
// for union patch only
std::string selected_union_name = "";
};
extern std::optional<std::string> PATCH_MANAGER_CFG_PATH_OVERRIDE;
std::string get_game_identifier(const std::filesystem::path& dll_path, bool print_info=false);
class PatchManager : public Window {
public:
PatchManager(SpiceOverlay *overlay, bool apply_patches = false);
~PatchManager() override;
void build_content() override;
void reload_local_patches(bool apply_patches = false);
bool import_remote_patches_to_disk();
bool load_from_patches_json(bool apply_patches);
bool import_remote_patches_for_dll(const std::string& url, const std::string& dll_name);
void hard_apply_patches();
void load_embedded_patches(bool apply_patches);
private:
// configuration
static std::filesystem::path config_path;
static bool config_dirty;
static bool setting_auto_apply;
static std::vector<std::string> setting_auto_apply_list;
static std::vector<std::string> setting_patches_enabled;
static std::map<std::string, std::string> setting_union_patches_enabled;
static std::map<std::string, int64_t> setting_int_patches_enabled;
static std::string patch_url;
static std::string patch_name_filter;
static std::filesystem::path LOCAL_PATCHES_PATH;
static std::string ACTIVE_JSON_FILE;
// patches
static std::vector<PatchData> patches;
static bool local_patches_initialized;
void config_load();
void config_save();
void append_patches(
std::string &patches_json,
bool apply_patches = false,
std::function<bool(const PatchData&)> filter = std::function<bool(const PatchData&)>(),
std::string pe_identifier_for_patch = "");
void show_patch_tooltip(const PatchData& patch);
bool is_game_id_wildcard_matched(const std::string& id_from_config);
};
PatchStatus is_patch_active(PatchData &patch);
bool apply_patch(PatchData &patch, bool active);
int64_t parse_little_endian_int(uint8_t* bytes, size_t size);
void int_to_little_endian_bytes(int64_t value, uint8_t* bytes, size_t size);
std::vector<uint8_t>* find_in_dll_map(
const std::string& dll_name, size_t offset, size_t size);
std::vector<uint8_t>* find_in_dll_map_org(
const std::string& dll_name, size_t offset, size_t size);
bool restore_bytes_from_dll_map_org(
uint8_t* destination, const std::string& dll_name, size_t offset, size_t size);
void create_dll_backup(
std::vector<std::string>& written_list, const std::filesystem::path& dll_path);
std::string fix_up_dll_name(const std::string& dll_name);
uint8_t* get_dll_offset_for_patch_apply(
const std::string& dll_name, const uint64_t data_offset, const size_t size_in_bytes);
uint64_t parse_json_data_offset(
const std::string &patch_name, const rapidjson::Value &value);
void print_auto_apply_status(PatchData &patch);
}
+17
View File
@@ -0,0 +1,17 @@
> [!NOTE]
> Before submitting code changes...
> * Please do note that this is a GPL v3.0 open source project.
> * Please read the [CONTRIBUTING](https://github.com/spice2x/spice2x.github.io/blob/main/CONTRIBUTING.md) guide.
> * Maintainers reserve the right to reject or modify your submission without reason.
> * No new compiler warnings must be introduced. Check the CI build results.
>
> Feel free to remove this section after you have read it.
## Link to GitHub Issue or related Pull Request, if one exists
#0
## Description of change
*what does this code do?*
## Testing
*how was the code tested?*
-20
View File
@@ -1,20 +0,0 @@
#pragma once
#include "device.h"
#include "rawinput.h"
namespace rawinput::touch {
// settings
extern bool DISABLED;
extern bool INVERTED;
bool is_touchscreen(Device *device);
void enable(Device *device);
void disable(Device *device);
void update_input(Device *device);
void update_timeouts(Device *device);
void update_timeouts(RawInputManager *manager);
bool is_enabled(RawInputManager *manager);
void display_update();
}
+26
View File
@@ -0,0 +1,26 @@
.vscode/*
# JetBrains IDEs
.idea
.idea_modules
*.iws
# CMake
cmake-build*
# user config
build_all.local.sh
build_docker.local.sh
# output from build script
bin/*
dist/*
external/cv2pdb/*
.ccache/*
# Visual Studio
.vs
out
CMakeSettings.json
+190 -43
View File
@@ -109,6 +109,12 @@ elseif(CMAKE_CXX_COMPILER_ID STREQUAL "Clang")
# RapidJSON does this # RapidJSON does this
add_compile_definitions(_SILENCE_CXX17_ITERATOR_BASE_CLASS_DEPRECATION_WARNING) add_compile_definitions(_SILENCE_CXX17_ITERATOR_BASE_CLASS_DEPRECATION_WARNING)
add_compile_options("-Wno-nontrivial-memcall") # imgui, rapidjson
add_compile_options("-Wno-deprecated-builtins") # robin_hood
add_compile_options("-Wno-deprecated-declarations") # rapidjson
add_compile_options("-Wno-inconsistent-missing-override") # headsocket
add_compile_options("-Wno-microsoft-exception-spec") # gcc/clang have different noexcept specifiers on d3d9 COM interfaces
# warnings # warnings
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wunknown-warning-option") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wunknown-warning-option")
@@ -118,6 +124,8 @@ elseif(CMAKE_CXX_COMPILER_ID STREQUAL "Clang")
set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -static") set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -static")
else() else()
# warnings as errors
set(CMAKE_COMPILE_WARNING_AS_ERROR ON)
# warnings # warnings
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wextra") # enable stuff set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wextra") # enable stuff
@@ -227,6 +235,13 @@ add_compile_definitions(
_WIN32_IE=0x0400 _WIN32_IE=0x0400
) )
# SPICE_XP: set by build_all.sh for the WinXP-compat toolchains. Gates out
# the DX11 overlay backend
option(SPICE_XP "Build for the WinXP-compat toolchain" OFF)
if(SPICE_XP)
add_compile_definitions(SPICE_XP=1)
endif()
# acioemu log # acioemu log
#set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DACIOEMU_LOG") #set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DACIOEMU_LOG")
@@ -258,19 +273,7 @@ set_source_files_properties(cfg/manifest.rc PROPERTIES LANGUAGE RC)
set_source_files_properties(cfg/icon.rc PROPERTIES LANGUAGE RC) set_source_files_properties(cfg/icon.rc PROPERTIES LANGUAGE RC)
set_source_files_properties(cfg/Win32D.rc PROPERTIES LANGUAGE RC) set_source_files_properties(cfg/Win32D.rc PROPERTIES LANGUAGE RC)
set_source_files_properties(build/manifest64.rc PROPERTIES LANGUAGE RC) set_source_files_properties(build/manifest64.rc PROPERTIES LANGUAGE RC)
set_source_files_properties(build/blob.rc PROPERTIES LANGUAGE RC) set_source_files_properties(stubs/manifest.rc PROPERTIES LANGUAGE RC)
add_custom_command(
OUTPUT build/dsdmo.i686.dll
COMMAND sh -c "wget -qO- 'https://web.archive.org/web/20260212204718if_/https://download.zip.dll-files.com/d01cab97ad497201c4ad99eb6e4182b3/dsdmo.zip?token=f7cVie5KOSz-hnFUYC37Ow&expires=1770974229' | bsdtar -xvf - -C '${CMAKE_SOURCE_DIR}/build' -s '/dsdmo.dll/dsdmo.i686.dll/' dsdmo.dll"
COMMENT "Fetching dsdmo.dll blob (i686)"
VERBATIM
)
add_custom_command(
OUTPUT build/dsdmo.x86_64.dll
COMMAND sh -c "wget -qO- 'https://web.archive.org/web/20260212203516if_/https://download.zip.dll-files.com/2627c69b89807078f5fdf63ee104dddf/dsdmo.zip?token=1rLcDAA2LFA6YRyinX0GXg&expires=1770973374' | bsdtar -xvf - -C '${CMAKE_SOURCE_DIR}/build' -s '/dsdmo.dll/dsdmo.x86_64.dll/' dsdmo.dll"
COMMENT "Fetching dsdmo.dll blob (x86_64)"
VERBATIM
)
# sources # sources
######### #########
@@ -357,6 +360,7 @@ set(SOURCE_FILES ${SOURCE_FILES}
cfg/configurator.cpp cfg/configurator.cpp
cfg/configurator_wnd.cpp cfg/configurator_wnd.cpp
cfg/screen_resize.cpp cfg/screen_resize.cpp
cfg/controller_presets.cpp
# easrv # easrv
easrv/easrv.cpp easrv/easrv.cpp
@@ -386,6 +390,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
@@ -403,14 +408,19 @@ set(SOURCE_FILES ${SOURCE_FILES}
games/iidx/mf_wrappers.cpp games/iidx/mf_wrappers.cpp
games/sdvx/bi2x_hook.cpp games/sdvx/bi2x_hook.cpp
games/sdvx/sdvx.cpp games/sdvx/sdvx.cpp
games/sdvx/sdvx_live2d.cpp
games/sdvx/io.cpp games/sdvx/io.cpp
games/sdvx/camera.cpp games/sdvx/camera.cpp
games/jb/bi2x_hook.cpp
games/jb/jb.cpp games/jb/jb.cpp
games/jb/jb_touch.cpp
games/jb/io.cpp games/jb/io.cpp
games/nost/nost.cpp games/nost/nost.cpp
games/nost/io.cpp games/nost/io.cpp
games/nost/poke.cpp games/nost/poke.cpp
games/nost/touch_mode.cpp
games/gitadora/gitadora.cpp games/gitadora/gitadora.cpp
games/gitadora/asio.cpp
games/gitadora/io.cpp games/gitadora/io.cpp
games/gitadora/handle.cpp games/gitadora/handle.cpp
games/gitadora/j32d.cpp games/gitadora/j32d.cpp
@@ -424,6 +434,7 @@ set(SOURCE_FILES ${SOURCE_FILES}
games/rb/rb.cpp games/rb/rb.cpp
games/rb/io.cpp games/rb/io.cpp
games/rb/touch.cpp games/rb/touch.cpp
games/rb/touch_debug.cpp
games/bs/bs.cpp games/bs/bs.cpp
games/bs/io.cpp games/bs/io.cpp
games/rf3d/rf3d.cpp games/rf3d/rf3d.cpp
@@ -488,16 +499,24 @@ set(SOURCE_FILES ${SOURCE_FILES}
# hooks # hooks
hooks/audio/acm.cpp hooks/audio/acm.cpp
hooks/audio/audio.cpp hooks/audio/audio.cpp
hooks/audio/asio_driver_scan.cpp
hooks/audio/asio_proxy.cpp
hooks/audio/buffer.cpp hooks/audio/buffer.cpp
hooks/audio/mme.cpp hooks/audio/mme.cpp
hooks/audio/util.cpp hooks/audio/util.cpp
hooks/audio/xact.cpp
hooks/audio/backends/dsound/dsound_backend.cpp hooks/audio/backends/dsound/dsound_backend.cpp
hooks/audio/backends/mmdevice/audio_endpoint_volume.cpp hooks/audio/backends/mmdevice/audio_endpoint_volume.cpp
hooks/audio/backends/mmdevice/device.cpp hooks/audio/backends/mmdevice/device.cpp
hooks/audio/backends/mmdevice/device_collection.cpp hooks/audio/backends/mmdevice/device_collection.cpp
hooks/audio/backends/mmdevice/device_enumerator.cpp hooks/audio/backends/mmdevice/device_enumerator.cpp
hooks/audio/backends/mmdevice/null_device.cpp
hooks/audio/backends/mmdevice/null_discard_backend.cpp
hooks/audio/backends/wasapi/audio_client.cpp hooks/audio/backends/wasapi/audio_client.cpp
hooks/audio/backends/wasapi/audio_render_client.cpp hooks/audio/backends/wasapi/audio_render_client.cpp
hooks/audio/backends/wasapi/downmix.cpp
hooks/audio/backends/wasapi/resample.cpp
hooks/audio/backends/wasapi/shared.cpp
hooks/audio/backends/wasapi/dummy_audio_client.cpp hooks/audio/backends/wasapi/dummy_audio_client.cpp
hooks/audio/backends/wasapi/dummy_audio_clock.cpp hooks/audio/backends/wasapi/dummy_audio_clock.cpp
hooks/audio/backends/wasapi/dummy_audio_render_client.cpp hooks/audio/backends/wasapi/dummy_audio_render_client.cpp
@@ -506,30 +525,35 @@ set(SOURCE_FILES ${SOURCE_FILES}
hooks/audio/backends/wasapi/util.cpp hooks/audio/backends/wasapi/util.cpp
hooks/audio/implementations/asio.cpp hooks/audio/implementations/asio.cpp
hooks/audio/implementations/wave_out.cpp hooks/audio/implementations/wave_out.cpp
hooks/audio/implementations/none.cpp
hooks/audio/implementations/pipewire.cpp
hooks/develhook.cpp
hooks/sndbhook.cpp
hooks/wmischook.cpp
hooks/avshook.cpp hooks/avshook.cpp
hooks/cfgmgr32hook.cpp hooks/cfgmgr32hook.cpp
hooks/debughook.cpp hooks/debughook.cpp
hooks/devicehook.cpp hooks/devicehook.cpp
hooks/graphics/graphics.cpp hooks/graphics/graphics.cpp
hooks/graphics/graphics_windowed.cpp hooks/graphics/graphics_windowed.cpp
hooks/graphics/nvapi_impl.cpp
hooks/graphics/nvapi_hook.cpp hooks/graphics/nvapi_hook.cpp
hooks/graphics/nvenc_hook.cpp hooks/graphics/nvenc_hook.cpp
hooks/graphics/backends/d3d9/d3d9_backend.cpp hooks/graphics/backends/d3d9/d3d9_backend.cpp
hooks/graphics/backends/d3d9/d3d9_device.cpp hooks/graphics/backends/d3d9/d3d9_device.cpp
hooks/graphics/backends/d3d9/d3d9_gfdm.cpp
hooks/graphics/backends/d3d9/d3d9_live2d.cpp
hooks/graphics/backends/d3d9/d3d9_fake_swapchain.cpp hooks/graphics/backends/d3d9/d3d9_fake_swapchain.cpp
hooks/graphics/backends/d3d9/d3d9_swapchain.cpp hooks/graphics/backends/d3d9/d3d9_swapchain.cpp
hooks/graphics/backends/d3d9/d3d9_texture.cpp hooks/graphics/backends/d3d9/d3d9_texture.cpp
hooks/graphics/backends/d3d11/d3d11_backend.cpp
hooks/graphics/backends/d3d11/d3d11_swapchain.cpp
hooks/graphics/backends/d3d11/d3d11_factory.cpp
hooks/graphics/backends/d3d11/d3d11_vtable_capture.cpp
hooks/graphics/backends/d3d11/d3d11_screenshot.cpp
hooks/input/dinput8/fake_backend.cpp hooks/input/dinput8/fake_backend.cpp
hooks/input/dinput8/fake_device.cpp hooks/input/dinput8/fake_device.cpp
hooks/input/dinput8/hook.cpp hooks/input/dinput8/hook.cpp
hooks/lang.cpp hooks/lang.cpp
hooks/libraryhook.cpp hooks/libraryhook.cpp
hooks/networkhook.cpp hooks/networkhook.cpp
hooks/icmphook_net.cpp
hooks/icmphook_iphlpapi.cpp
hooks/powrprof.cpp hooks/powrprof.cpp
#hooks/rom.cpp #hooks/rom.cpp
hooks/setupapihook.cpp hooks/setupapihook.cpp
@@ -552,7 +576,6 @@ set(SOURCE_FILES ${SOURCE_FILES}
misc/device.cpp misc/device.cpp
misc/eamuse.cpp misc/eamuse.cpp
misc/extdev.cpp misc/extdev.cpp
misc/nativetouchhook.cpp
misc/sciunit.cpp misc/sciunit.cpp
misc/sde.cpp misc/sde.cpp
misc/wintouchemu.cpp misc/wintouchemu.cpp
@@ -563,6 +586,7 @@ set(SOURCE_FILES ${SOURCE_FILES}
# overlay # overlay
overlay/overlay.cpp overlay/overlay.cpp
overlay/notifications.cpp
overlay/window.cpp overlay/window.cpp
overlay/imgui/extensions.cpp overlay/imgui/extensions.cpp
overlay/imgui/impl_spice.cpp overlay/imgui/impl_spice.cpp
@@ -575,6 +599,7 @@ set(SOURCE_FILES ${SOURCE_FILES}
overlay/windows/screen_resize.cpp overlay/windows/screen_resize.cpp
overlay/windows/sdvx_sub.cpp overlay/windows/sdvx_sub.cpp
overlay/windows/config.cpp overlay/windows/config.cpp
overlay/windows/controller_presets.cpp
overlay/windows/control.cpp overlay/windows/control.cpp
overlay/windows/eadev.cpp overlay/windows/eadev.cpp
overlay/windows/fps.cpp overlay/windows/fps.cpp
@@ -588,11 +613,27 @@ set(SOURCE_FILES ${SOURCE_FILES}
overlay/windows/keypad.cpp overlay/windows/keypad.cpp
overlay/windows/log.cpp overlay/windows/log.cpp
overlay/windows/midi.cpp overlay/windows/midi.cpp
overlay/windows/nostalgia_touch_piano.cpp
overlay/windows/obs.cpp
overlay/windows/obs_websocket.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
# patcher
patcher/config.cpp
patcher/lifecycle.cpp
patcher/loader.cpp
patcher/loader_group.cpp
patcher/runtime.cpp
# external
external/easywsclient/easywsclient.cpp
# rawinput # rawinput
rawinput/rawinput.cpp rawinput/rawinput.cpp
rawinput/rawinput_handles.cpp
rawinput/midi.cpp
rawinput/sextet.cpp rawinput/sextet.cpp
rawinput/piuio.cpp rawinput/piuio.cpp
rawinput/touch.cpp rawinput/touch.cpp
@@ -603,6 +644,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
@@ -610,12 +653,21 @@ set(SOURCE_FILES ${SOURCE_FILES}
reader/structuredmessage.cpp reader/structuredmessage.cpp
reader/crypt.cpp reader/crypt.cpp
# sdk
sdk/sdk.cpp
# stubs # stubs
stubs/stubs.cpp stubs/stubs.cpp
# touch # touch
touch/gdi_overlay.cpp
touch/native/inject.cpp
touch/native/inject_mouse.cpp
touch/native/inject_synthetic.cpp
touch/native/nativetouchhook.cpp
touch/native/transform.cpp
touch/touch.cpp touch/touch.cpp
touch/touch_indicators.cpp touch/touch_gestures.cpp
touch/win7.cpp touch/win7.cpp
touch/win8.cpp touch/win8.cpp
@@ -636,6 +688,7 @@ set(SOURCE_FILES ${SOURCE_FILES}
util/time.cpp util/time.cpp
util/cpuutils.cpp util/cpuutils.cpp
util/netutils.cpp util/netutils.cpp
util/precise_timer.cpp
util/sysutils.cpp util/sysutils.cpp
util/lz77.cpp util/lz77.cpp
util/tapeled.cpp util/tapeled.cpp
@@ -647,35 +700,74 @@ set(SOURCE_FILES ${SOURCE_FILES}
source_group(TREE ${CMAKE_CURRENT_SOURCE_DIR} PREFIX "Source Files" FILES ${SOURCE_FILES}) source_group(TREE ${CMAKE_CURRENT_SOURCE_DIR} PREFIX "Source Files" FILES ${SOURCE_FILES})
# guard against statically importing DLLs that must always be loaded dynamically:
# * DLLs users override via the modules directory (e.g. DXVK's d3d9.dll) - a
# static import loads the system copy at startup and preempts the override
# (issue #779).
# * Media Foundation DLLs (mf/mfplat/mfreadwrite) - a static import breaks
# Unity games.
# the check runs objdump on each produced binary and fails the build if any
# forbidden DLL is imported.
set(SPICE_FORBIDDEN_STATIC_IMPORTS
d3d8.dll d3d9.dll d3d10core.dll d3d11.dll dxgi.dll opengl32.dll
mf.dll mfplat.dll mfreadwrite.dll)
function(spice_guard_dll_imports target)
if(MSVC)
# objdump-based parsing assumes a GNU/LLVM toolchain
return()
endif()
add_custom_command(TARGET ${target} POST_BUILD
COMMAND ${CMAKE_COMMAND}
-DOBJDUMP=${CMAKE_OBJDUMP}
-DTARGET_FILE=$<TARGET_FILE:${target}>
"-DFORBIDDEN=${SPICE_FORBIDDEN_STATIC_IMPORTS}"
-P ${CMAKE_CURRENT_SOURCE_DIR}/cmake/check_no_static_dll_imports.cmake
VERBATIM
COMMENT "Checking ${target} for forbidden static DLL imports")
endfunction()
# spice.exe / spice_laa.exe shared objects
###########################################
# spice.exe and spice_laa.exe are compiled identically; the only difference is
# the large-address-aware bit, which is set at link time. Compile the sources
# once into a shared OBJECT library so spice_laa.exe only costs an extra link
# instead of a full recompile.
add_library(spicetools_spice_objs OBJECT ${SOURCE_FILES})
target_link_libraries(spicetools_spice_objs
PUBLIC d3d9 ws2_32 version comctl32 shlwapi iphlpapi hid secur32 setupapi psapi winmm winhttp
PRIVATE fmt::fmt-header-only discord-rpc imgui hash-library minhook imm32 dwmapi CpuFeatures::cpu_features)
target_link_libraries(spicetools_spice_objs PUBLIC winscard)
if(NOT MSVC)
set_target_properties(spicetools_spice_objs PROPERTIES COMPILE_FLAGS "-m32")
endif()
# spice.exe # spice.exe
########### ###########
set(RESOURCE_FILES build/manifest.manifest build/manifest.rc build/icon.rc cfg/Win32D.rc build/blob.rc build/dsdmo.i686.dll) set(RESOURCE_FILES build/manifest.manifest build/manifest.rc build/icon.rc cfg/Win32D.rc)
add_executable(spicetools_spice ${SOURCE_FILES} ${RESOURCE_FILES}) add_executable(spicetools_spice ${RESOURCE_FILES})
target_link_libraries(spicetools_spice target_link_libraries(spicetools_spice PRIVATE spicetools_spice_objs)
PUBLIC d3d9 ws2_32 version comctl32 shlwapi iphlpapi hid secur32 setupapi psapi winmm winhttp
PRIVATE fmt::fmt-header-only discord-rpc imgui hash-library minhook imm32 dwmapi CpuFeatures::cpu_features)
set_target_properties(spicetools_spice PROPERTIES PREFIX "") set_target_properties(spicetools_spice PROPERTIES PREFIX "")
set_target_properties(spicetools_spice PROPERTIES OUTPUT_NAME "spice") set_target_properties(spicetools_spice PROPERTIES OUTPUT_NAME "spice")
IF(NOT MSVC) IF(NOT MSVC)
set_target_properties(spicetools_spice PROPERTIES COMPILE_FLAGS "-m32" LINK_FLAGS "-m32") set_target_properties(spicetools_spice PROPERTIES LINK_FLAGS "-m32")
endif() endif()
# spice_laa.exe # spice_laa.exe
########### ###########
set(RESOURCE_FILES build/manifest.manifest build/manifest.rc build/icon.rc cfg/Win32D.rc build/blob.rc build/dsdmo.i686.dll) set(RESOURCE_FILES build/manifest.manifest build/manifest.rc build/icon.rc cfg/Win32D.rc)
add_executable(spicetools_spice_laa ${SOURCE_FILES} ${RESOURCE_FILES}) add_executable(spicetools_spice_laa ${RESOURCE_FILES})
target_link_libraries(spicetools_spice_laa target_link_libraries(spicetools_spice_laa PRIVATE spicetools_spice_objs)
PUBLIC d3d9 ws2_32 version comctl32 shlwapi iphlpapi hid secur32 setupapi psapi winmm winhttp
PRIVATE fmt::fmt-header-only discord-rpc imgui hash-library minhook imm32 dwmapi CpuFeatures::cpu_features)
set_target_properties(spicetools_spice_laa PROPERTIES PREFIX "") set_target_properties(spicetools_spice_laa PROPERTIES PREFIX "")
set_target_properties(spicetools_spice_laa PROPERTIES OUTPUT_NAME "spice_laa") set_target_properties(spicetools_spice_laa PROPERTIES OUTPUT_NAME "spice_laa")
target_compile_definitions(spicetools_spice_laa PRIVATE SPICE32_LARGE_ADDRESS_AWARE=1)
IF(NOT MSVC) IF(NOT MSVC)
set_target_properties(spicetools_spice_laa PROPERTIES COMPILE_FLAGS "-m32" LINK_FLAGS "-m32 -Wl,--large-address-aware") set_target_properties(spicetools_spice_laa PROPERTIES LINK_FLAGS "-m32 -Wl,--large-address-aware")
endif() endif()
# spice_linux.exe # spice_linux.exe
@@ -697,17 +789,21 @@ endif()
# spice64.exe # spice64.exe
############# #############
set(RESOURCE_FILES build/manifest.manifest build/manifest64.rc build/icon.rc cfg/Win32D.rc build/blob.rc build/dsdmo.x86_64.dll) set(RESOURCE_FILES build/manifest.manifest build/manifest64.rc build/icon.rc cfg/Win32D.rc)
add_executable(spicetools_spice64 ${SOURCE_FILES} ${RESOURCE_FILES}) add_executable(spicetools_spice64 ${SOURCE_FILES} ${RESOURCE_FILES})
# do NOT link against: mf, mfplat, mfreadwrite; otherwise unity games will break # do NOT link against: mf, mfplat, mfreadwrite; otherwise unity games will break
target_link_libraries(spicetools_spice64 target_link_libraries(spicetools_spice64
PUBLIC d3d9 ws2_32 version comctl32 shlwapi iphlpapi hid secur32 setupapi psapi winmm winhttp mfuuid strmiids dxva2 PUBLIC d3d9 ws2_32 version comctl32 shlwapi iphlpapi hid secur32 setupapi psapi winmm winhttp mfuuid strmiids dxva2
PRIVATE fmt::fmt-header-only discord-rpc imgui hash-library minhook imm32 dwmapi CpuFeatures::cpu_features) PRIVATE fmt::fmt-header-only discord-rpc imgui hash-library minhook imm32 dwmapi CpuFeatures::cpu_features)
target_link_libraries(spicetools_spice64 PUBLIC winscard)
set_target_properties(spicetools_spice64 PROPERTIES PREFIX "") set_target_properties(spicetools_spice64 PROPERTIES PREFIX "")
set_target_properties(spicetools_spice64 PROPERTIES OUTPUT_NAME "spice64") set_target_properties(spicetools_spice64 PROPERTIES OUTPUT_NAME "spice64")
target_compile_definitions(spicetools_spice64 PRIVATE SPICE64=1) target_compile_definitions(spicetools_spice64 PRIVATE SPICE64=1)
if(NOT SPICE_XP)
target_link_libraries(spicetools_spice64 PUBLIC d3d11 dxgi)
target_compile_definitions(spicetools_spice64 PRIVATE SPICE_D3D11=1)
endif()
IF(NOT MSVC) IF(NOT MSVC)
set_target_properties(spicetools_spice64 PROPERTIES COMPILE_FLAGS "-m64" LINK_FLAGS "-m64") set_target_properties(spicetools_spice64 PROPERTIES COMPILE_FLAGS "-m64" LINK_FLAGS "-m64")
@@ -727,6 +823,9 @@ set_target_properties(spicetools_spice64_linux PROPERTIES PREFIX "")
set_target_properties(spicetools_spice64_linux PROPERTIES OUTPUT_NAME "spice64_linux") set_target_properties(spicetools_spice64_linux PROPERTIES OUTPUT_NAME "spice64_linux")
target_compile_definitions(spicetools_spice64_linux PRIVATE SPICE64=1) target_compile_definitions(spicetools_spice64_linux PRIVATE SPICE64=1)
target_compile_definitions(spicetools_spice64_linux PRIVATE NO_SCARD=1 PRIVATE SPICE_LINUX=1) target_compile_definitions(spicetools_spice64_linux PRIVATE NO_SCARD=1 PRIVATE SPICE_LINUX=1)
# spice64_linux is never built under the WinXP toolchain, so dx11 is always on.
target_link_libraries(spicetools_spice64_linux PUBLIC d3d11 dxgi)
target_compile_definitions(spicetools_spice64_linux PRIVATE SPICE_D3D11=1)
IF(NOT MSVC) IF(NOT MSVC)
set_target_properties(spicetools_spice64_linux PROPERTIES COMPILE_FLAGS "-m64" LINK_FLAGS "-m64") set_target_properties(spicetools_spice64_linux PROPERTIES COMPILE_FLAGS "-m64" LINK_FLAGS "-m64")
@@ -739,8 +838,9 @@ set(SOURCE_FILES ${SOURCE_FILES} launcher/options.h launcher/options.cpp)
set(RESOURCE_FILES cfg/manifest.manifest cfg/manifest.rc cfg/icon.rc cfg/Win32D.rc) set(RESOURCE_FILES cfg/manifest.manifest cfg/manifest.rc cfg/icon.rc cfg/Win32D.rc)
add_executable(spicetools_cfg WIN32 ${SOURCE_FILES} ${RESOURCE_FILES}) add_executable(spicetools_cfg WIN32 ${SOURCE_FILES} ${RESOURCE_FILES})
target_link_libraries(spicetools_cfg target_link_libraries(spicetools_cfg
PUBLIC ws2_32 version comctl32 shlwapi iphlpapi hid secur32 setupapi psapi winmm winhttp strmiids PUBLIC d3d9 ws2_32 version comctl32 shlwapi iphlpapi hid secur32 setupapi psapi winmm winhttp strmiids
PRIVATE fmt::fmt-header-only discord-rpc imgui hash-library minhook imm32 dwmapi CpuFeatures::cpu_features) PRIVATE fmt::fmt-header-only discord-rpc imgui hash-library minhook imm32 dwmapi CpuFeatures::cpu_features)
target_link_libraries(spicetools_cfg PUBLIC winscard)
set_target_properties(spicetools_cfg PROPERTIES PREFIX "") set_target_properties(spicetools_cfg PROPERTIES PREFIX "")
set_target_properties(spicetools_cfg PROPERTIES OUTPUT_NAME "spicecfg") set_target_properties(spicetools_cfg PROPERTIES OUTPUT_NAME "spicecfg")
target_compile_definitions(spicetools_cfg PRIVATE SPICETOOLS_SPICECFG_STANDALONE=1) target_compile_definitions(spicetools_cfg PRIVATE SPICETOOLS_SPICECFG_STANDALONE=1)
@@ -756,7 +856,7 @@ set(SOURCE_FILES ${SOURCE_FILES} launcher/options.h launcher/options.cpp)
set(RESOURCE_FILES cfg/manifest.manifest cfg/manifest.rc cfg/icon.rc cfg/Win32D.rc) set(RESOURCE_FILES cfg/manifest.manifest cfg/manifest.rc cfg/icon.rc cfg/Win32D.rc)
add_executable(spicetools_cfg_linux WIN32 ${SOURCE_FILES} ${RESOURCE_FILES}) add_executable(spicetools_cfg_linux WIN32 ${SOURCE_FILES} ${RESOURCE_FILES})
target_link_libraries(spicetools_cfg_linux target_link_libraries(spicetools_cfg_linux
PUBLIC ws2_32 version comctl32 shlwapi iphlpapi hid secur32 setupapi psapi winmm winhttp strmiids PUBLIC d3d9 ws2_32 version comctl32 shlwapi iphlpapi hid secur32 setupapi psapi winmm winhttp strmiids
PRIVATE fmt::fmt-header-only discord-rpc imgui hash-library minhook imm32 dwmapi CpuFeatures::cpu_features) PRIVATE fmt::fmt-header-only discord-rpc imgui hash-library minhook imm32 dwmapi CpuFeatures::cpu_features)
set_target_properties(spicetools_cfg_linux PROPERTIES PREFIX "") set_target_properties(spicetools_cfg_linux PROPERTIES PREFIX "")
set_target_properties(spicetools_cfg_linux PROPERTIES OUTPUT_NAME "spicecfg_linux") set_target_properties(spicetools_cfg_linux PROPERTIES OUTPUT_NAME "spicecfg_linux")
@@ -818,7 +918,8 @@ endif()
# nvcuda.dll # nvcuda.dll
set(SOURCE_FILES stubs/nvcuda.cpp) set(SOURCE_FILES stubs/nvcuda.cpp)
add_library(spicetools_stubs_nvcuda SHARED ${SOURCE_FILES} stubs/nvcuda.def) set(RESOURCE_FILES stubs/manifest.rc)
add_library(spicetools_stubs_nvcuda SHARED ${SOURCE_FILES} ${RESOURCE_FILES} stubs/nvcuda.def)
set_target_properties(spicetools_stubs_nvcuda PROPERTIES PREFIX "") set_target_properties(spicetools_stubs_nvcuda PROPERTIES PREFIX "")
set_target_properties(spicetools_stubs_nvcuda PROPERTIES OUTPUT_NAME "nvcuda") set_target_properties(spicetools_stubs_nvcuda PROPERTIES OUTPUT_NAME "nvcuda")
@@ -828,7 +929,8 @@ endif()
# nvcuvid.dll # nvcuvid.dll
set(SOURCE_FILES stubs/nvcuvid.cpp) set(SOURCE_FILES stubs/nvcuvid.cpp)
add_library(spicetools_stubs_nvcuvid SHARED ${SOURCE_FILES} stubs/nvcuvid.def) set(RESOURCE_FILES stubs/manifest.rc)
add_library(spicetools_stubs_nvcuvid SHARED ${SOURCE_FILES} ${RESOURCE_FILES} stubs/nvcuvid.def)
set_target_properties(spicetools_stubs_nvcuvid PROPERTIES PREFIX "") set_target_properties(spicetools_stubs_nvcuvid PROPERTIES PREFIX "")
set_target_properties(spicetools_stubs_nvcuvid PROPERTIES OUTPUT_NAME "nvcuvid") set_target_properties(spicetools_stubs_nvcuvid PROPERTIES OUTPUT_NAME "nvcuvid")
@@ -838,7 +940,8 @@ endif()
# nvEncodeAPI64.dll # nvEncodeAPI64.dll
set(SOURCE_FILES stubs/nvEncodeAPI64.cpp) set(SOURCE_FILES stubs/nvEncodeAPI64.cpp)
add_library(spicetools_stubs_nvEncodeAPI64 SHARED ${SOURCE_FILES} stubs/nvEncodeAPI64.def) set(RESOURCE_FILES stubs/manifest.rc)
add_library(spicetools_stubs_nvEncodeAPI64 SHARED ${SOURCE_FILES} ${RESOURCE_FILES} stubs/nvEncodeAPI64.def)
set_target_properties(spicetools_stubs_nvEncodeAPI64 PROPERTIES PREFIX "") set_target_properties(spicetools_stubs_nvEncodeAPI64 PROPERTIES PREFIX "")
set_target_properties(spicetools_stubs_nvEncodeAPI64 PROPERTIES OUTPUT_NAME "nvEncodeAPI64") set_target_properties(spicetools_stubs_nvEncodeAPI64 PROPERTIES OUTPUT_NAME "nvEncodeAPI64")
@@ -848,7 +951,8 @@ endif()
# cpusbxpkm.dll (32 bit) # cpusbxpkm.dll (32 bit)
set(SOURCE_FILES stubs/cpusbxpkm.cpp) set(SOURCE_FILES stubs/cpusbxpkm.cpp)
add_library(spicetools_stubs_cpusbxpkm SHARED ${SOURCE_FILES} stubs/cpusbxpkm.def) set(RESOURCE_FILES stubs/manifest.rc)
add_library(spicetools_stubs_cpusbxpkm SHARED ${SOURCE_FILES} ${RESOURCE_FILES} stubs/cpusbxpkm.def)
set_target_properties(spicetools_stubs_cpusbxpkm PROPERTIES PREFIX "") set_target_properties(spicetools_stubs_cpusbxpkm PROPERTIES PREFIX "")
set_target_properties(spicetools_stubs_cpusbxpkm PROPERTIES OUTPUT_NAME "cpusbxpkm") set_target_properties(spicetools_stubs_cpusbxpkm PROPERTIES OUTPUT_NAME "cpusbxpkm")
@@ -856,6 +960,36 @@ if(NOT MSVC)
set_target_properties(spicetools_stubs_cpusbxpkm PROPERTIES COMPILE_FLAGS "-m32" LINK_FLAGS "-m32") set_target_properties(spicetools_stubs_cpusbxpkm PROPERTIES COMPILE_FLAGS "-m32" LINK_FLAGS "-m32")
endif() endif()
# sdk_sample_v0_flat_c.dll (32 bit)
set(SOURCE_FILES sdk/sample/v0/flat_c/v0_flat_c.c)
add_library(spicetools_sdk_sample_v0_flat_c_32 SHARED ${SOURCE_FILES} ${RESOURCE_FILES} sdk/sample/v0/flat_c/v0_flat_c.def)
set_target_properties(spicetools_sdk_sample_v0_flat_c_32 PROPERTIES PREFIX "")
set_target_properties(spicetools_sdk_sample_v0_flat_c_32 PROPERTIES OUTPUT_NAME "sdk_sample_v0_flat_c")
if(NOT MSVC)
set_target_properties(spicetools_sdk_sample_v0_flat_c_32 PROPERTIES COMPILE_FLAGS "-m32" LINK_FLAGS "-m32")
endif()
# sdk_sample_v0_flat_c.dll (64 bit)
set(SOURCE_FILES sdk/sample/v0/flat_c/v0_flat_c.c)
add_library(spicetools_sdk_sample_v0_flat_c_64 SHARED ${SOURCE_FILES} ${RESOURCE_FILES} sdk/sample/v0/flat_c/v0_flat_c.def)
set_target_properties(spicetools_sdk_sample_v0_flat_c_64 PROPERTIES PREFIX "")
set_target_properties(spicetools_sdk_sample_v0_flat_c_64 PROPERTIES OUTPUT_NAME "sdk_sample_v0_flat_c")
if(NOT MSVC)
set_target_properties(spicetools_sdk_sample_v0_flat_c_64 PROPERTIES COMPILE_FLAGS "-m64" LINK_FLAGS "-m64")
endif()
# sdk_sample_v0_cpp.dll (64 bit)
set(SOURCE_FILES sdk/sample/v0/cpp/v0_cpp.cpp)
add_library(spicetools_sdk_sample_v0_cpp_64 SHARED ${SOURCE_FILES} ${RESOURCE_FILES} sdk/sample/v0/cpp/v0_cpp.def)
set_target_properties(spicetools_sdk_sample_v0_cpp_64 PROPERTIES PREFIX "")
set_target_properties(spicetools_sdk_sample_v0_cpp_64 PROPERTIES OUTPUT_NAME "sdk_sample_v0_cpp")
if(NOT MSVC)
set_target_properties(spicetools_sdk_sample_v0_cpp_64 PROPERTIES COMPILE_FLAGS "-m64" LINK_FLAGS "-m64")
endif()
# output directories # output directories
#################### ####################
@@ -865,15 +999,28 @@ set_target_properties(spicetools_cfg spicetools_cfg_linux
RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/spicetools") RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/spicetools")
# output 32bit # output 32bit
set_target_properties(spicetools_spice spicetools_spice_laa spicetools_spice_linux spicetools_stubs_kbt spicetools_stubs_kld spicetools_stubs_cpusbxpkm set_target_properties(spicetools_spice spicetools_spice_laa spicetools_spice_linux spicetools_stubs_kbt spicetools_stubs_kld spicetools_stubs_cpusbxpkm spicetools_sdk_sample_v0_flat_c_32
PROPERTIES PROPERTIES
ARCHIVE_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/archive32" ARCHIVE_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/archive32"
LIBRARY_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/spicetools/32" LIBRARY_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/spicetools/32"
RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/spicetools/32") RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/spicetools/32")
# output 64bit # output 64bit
set_target_properties(spicetools_spice64 spicetools_spice64_linux spicetools_stubs_kbt64 spicetools_stubs_kld64 spicetools_stubs_nvcuda spicetools_stubs_nvcuvid spicetools_stubs_nvEncodeAPI64 set_target_properties(spicetools_spice64 spicetools_spice64_linux spicetools_stubs_kbt64 spicetools_stubs_kld64 spicetools_stubs_nvcuda spicetools_stubs_nvcuvid spicetools_stubs_nvEncodeAPI64 spicetools_sdk_sample_v0_flat_c_64 spicetools_sdk_sample_v0_cpp_64
PROPERTIES PROPERTIES
ARCHIVE_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/archive64" ARCHIVE_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/archive64"
LIBRARY_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/spicetools/64" LIBRARY_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/spicetools/64"
RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/spicetools/64") RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/spicetools/64")
# forbidden static DLL import guard
###################################
# apply the check to every executable and DLL produced by this project.
get_property(spice_all_targets DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} PROPERTY BUILDSYSTEM_TARGETS)
foreach(spice_target IN LISTS spice_all_targets)
get_target_property(spice_target_type ${spice_target} TYPE)
if(spice_target_type STREQUAL "EXECUTABLE"
OR spice_target_type STREQUAL "SHARED_LIBRARY"
OR spice_target_type STREQUAL "MODULE_LIBRARY")
spice_guard_dll_imports(${spice_target})
endif()
endforeach()
+674
View File
@@ -0,0 +1,674 @@
GNU GENERAL PUBLIC LICENSE
Version 3, 29 June 2007
Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
Preamble
The GNU General Public License is a free, copyleft license for
software and other kinds of works.
The licenses for most software and other practical works are designed
to take away your freedom to share and change the works. By contrast,
the GNU General Public License is intended to guarantee your freedom to
share and change all versions of a program--to make sure it remains free
software for all its users. We, the Free Software Foundation, use the
GNU General Public License for most of our software; it applies also to
any other work released this way by its authors. You can apply it to
your programs, too.
When we speak of free software, we are referring to freedom, not
price. Our General Public Licenses are designed to make sure that you
have the freedom to distribute copies of free software (and charge for
them if you wish), that you receive source code or can get it if you
want it, that you can change the software or use pieces of it in new
free programs, and that you know you can do these things.
To protect your rights, we need to prevent others from denying you
these rights or asking you to surrender the rights. Therefore, you have
certain responsibilities if you distribute copies of the software, or if
you modify it: responsibilities to respect the freedom of others.
For example, if you distribute copies of such a program, whether
gratis or for a fee, you must pass on to the recipients the same
freedoms that you received. You must make sure that they, too, receive
or can get the source code. And you must show them these terms so they
know their rights.
Developers that use the GNU GPL protect your rights with two steps:
(1) assert copyright on the software, and (2) offer you this License
giving you legal permission to copy, distribute and/or modify it.
For the developers' and authors' protection, the GPL clearly explains
that there is no warranty for this free software. For both users' and
authors' sake, the GPL requires that modified versions be marked as
changed, so that their problems will not be attributed erroneously to
authors of previous versions.
Some devices are designed to deny users access to install or run
modified versions of the software inside them, although the manufacturer
can do so. This is fundamentally incompatible with the aim of
protecting users' freedom to change the software. The systematic
pattern of such abuse occurs in the area of products for individuals to
use, which is precisely where it is most unacceptable. Therefore, we
have designed this version of the GPL to prohibit the practice for those
products. If such problems arise substantially in other domains, we
stand ready to extend this provision to those domains in future versions
of the GPL, as needed to protect the freedom of users.
Finally, every program is threatened constantly by software patents.
States should not allow patents to restrict development and use of
software on general-purpose computers, but in those that do, we wish to
avoid the special danger that patents applied to a free program could
make it effectively proprietary. To prevent this, the GPL assures that
patents cannot be used to render the program non-free.
The precise terms and conditions for copying, distribution and
modification follow.
TERMS AND CONDITIONS
0. Definitions.
"This License" refers to version 3 of the GNU General Public License.
"Copyright" also means copyright-like laws that apply to other kinds of
works, such as semiconductor masks.
"The Program" refers to any copyrightable work licensed under this
License. Each licensee is addressed as "you". "Licensees" and
"recipients" may be individuals or organizations.
To "modify" a work means to copy from or adapt all or part of the work
in a fashion requiring copyright permission, other than the making of an
exact copy. The resulting work is called a "modified version" of the
earlier work or a work "based on" the earlier work.
A "covered work" means either the unmodified Program or a work based
on the Program.
To "propagate" a work means to do anything with it that, without
permission, would make you directly or secondarily liable for
infringement under applicable copyright law, except executing it on a
computer or modifying a private copy. Propagation includes copying,
distribution (with or without modification), making available to the
public, and in some countries other activities as well.
To "convey" a work means any kind of propagation that enables other
parties to make or receive copies. Mere interaction with a user through
a computer network, with no transfer of a copy, is not conveying.
An interactive user interface displays "Appropriate Legal Notices"
to the extent that it includes a convenient and prominently visible
feature that (1) displays an appropriate copyright notice, and (2)
tells the user that there is no warranty for the work (except to the
extent that warranties are provided), that licensees may convey the
work under this License, and how to view a copy of this License. If
the interface presents a list of user commands or options, such as a
menu, a prominent item in the list meets this criterion.
1. Source Code.
The "source code" for a work means the preferred form of the work
for making modifications to it. "Object code" means any non-source
form of a work.
A "Standard Interface" means an interface that either is an official
standard defined by a recognized standards body, or, in the case of
interfaces specified for a particular programming language, one that
is widely used among developers working in that language.
The "System Libraries" of an executable work include anything, other
than the work as a whole, that (a) is included in the normal form of
packaging a Major Component, but which is not part of that Major
Component, and (b) serves only to enable use of the work with that
Major Component, or to implement a Standard Interface for which an
implementation is available to the public in source code form. A
"Major Component", in this context, means a major essential component
(kernel, window system, and so on) of the specific operating system
(if any) on which the executable work runs, or a compiler used to
produce the work, or an object code interpreter used to run it.
The "Corresponding Source" for a work in object code form means all
the source code needed to generate, install, and (for an executable
work) run the object code and to modify the work, including scripts to
control those activities. However, it does not include the work's
System Libraries, or general-purpose tools or generally available free
programs which are used unmodified in performing those activities but
which are not part of the work. For example, Corresponding Source
includes interface definition files associated with source files for
the work, and the source code for shared libraries and dynamically
linked subprograms that the work is specifically designed to require,
such as by intimate data communication or control flow between those
subprograms and other parts of the work.
The Corresponding Source need not include anything that users
can regenerate automatically from other parts of the Corresponding
Source.
The Corresponding Source for a work in source code form is that
same work.
2. Basic Permissions.
All rights granted under this License are granted for the term of
copyright on the Program, and are irrevocable provided the stated
conditions are met. This License explicitly affirms your unlimited
permission to run the unmodified Program. The output from running a
covered work is covered by this License only if the output, given its
content, constitutes a covered work. This License acknowledges your
rights of fair use or other equivalent, as provided by copyright law.
You may make, run and propagate covered works that you do not
convey, without conditions so long as your license otherwise remains
in force. You may convey covered works to others for the sole purpose
of having them make modifications exclusively for you, or provide you
with facilities for running those works, provided that you comply with
the terms of this License in conveying all material for which you do
not control copyright. Those thus making or running the covered works
for you must do so exclusively on your behalf, under your direction
and control, on terms that prohibit them from making any copies of
your copyrighted material outside their relationship with you.
Conveying under any other circumstances is permitted solely under
the conditions stated below. Sublicensing is not allowed; section 10
makes it unnecessary.
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
No covered work shall be deemed part of an effective technological
measure under any applicable law fulfilling obligations under article
11 of the WIPO copyright treaty adopted on 20 December 1996, or
similar laws prohibiting or restricting circumvention of such
measures.
When you convey a covered work, you waive any legal power to forbid
circumvention of technological measures to the extent such circumvention
is effected by exercising rights under this License with respect to
the covered work, and you disclaim any intention to limit operation or
modification of the work as a means of enforcing, against the work's
users, your or third parties' legal rights to forbid circumvention of
technological measures.
4. Conveying Verbatim Copies.
You may convey verbatim copies of the Program's source code as you
receive it, in any medium, provided that you conspicuously and
appropriately publish on each copy an appropriate copyright notice;
keep intact all notices stating that this License and any
non-permissive terms added in accord with section 7 apply to the code;
keep intact all notices of the absence of any warranty; and give all
recipients a copy of this License along with the Program.
You may charge any price or no price for each copy that you convey,
and you may offer support or warranty protection for a fee.
5. Conveying Modified Source Versions.
You may convey a work based on the Program, or the modifications to
produce it from the Program, in the form of source code under the
terms of section 4, provided that you also meet all of these conditions:
a) The work must carry prominent notices stating that you modified
it, and giving a relevant date.
b) The work must carry prominent notices stating that it is
released under this License and any conditions added under section
7. This requirement modifies the requirement in section 4 to
"keep intact all notices".
c) You must license the entire work, as a whole, under this
License to anyone who comes into possession of a copy. This
License will therefore apply, along with any applicable section 7
additional terms, to the whole of the work, and all its parts,
regardless of how they are packaged. This License gives no
permission to license the work in any other way, but it does not
invalidate such permission if you have separately received it.
d) If the work has interactive user interfaces, each must display
Appropriate Legal Notices; however, if the Program has interactive
interfaces that do not display Appropriate Legal Notices, your
work need not make them do so.
A compilation of a covered work with other separate and independent
works, which are not by their nature extensions of the covered work,
and which are not combined with it such as to form a larger program,
in or on a volume of a storage or distribution medium, is called an
"aggregate" if the compilation and its resulting copyright are not
used to limit the access or legal rights of the compilation's users
beyond what the individual works permit. Inclusion of a covered work
in an aggregate does not cause this License to apply to the other
parts of the aggregate.
6. Conveying Non-Source Forms.
You may convey a covered work in object code form under the terms
of sections 4 and 5, provided that you also convey the
machine-readable Corresponding Source under the terms of this License,
in one of these ways:
a) Convey the object code in, or embodied in, a physical product
(including a physical distribution medium), accompanied by the
Corresponding Source fixed on a durable physical medium
customarily used for software interchange.
b) Convey the object code in, or embodied in, a physical product
(including a physical distribution medium), accompanied by a
written offer, valid for at least three years and valid for as
long as you offer spare parts or customer support for that product
model, to give anyone who possesses the object code either (1) a
copy of the Corresponding Source for all the software in the
product that is covered by this License, on a durable physical
medium customarily used for software interchange, for a price no
more than your reasonable cost of physically performing this
conveying of source, or (2) access to copy the
Corresponding Source from a network server at no charge.
c) Convey individual copies of the object code with a copy of the
written offer to provide the Corresponding Source. This
alternative is allowed only occasionally and noncommercially, and
only if you received the object code with such an offer, in accord
with subsection 6b.
d) Convey the object code by offering access from a designated
place (gratis or for a charge), and offer equivalent access to the
Corresponding Source in the same way through the same place at no
further charge. You need not require recipients to copy the
Corresponding Source along with the object code. If the place to
copy the object code is a network server, the Corresponding Source
may be on a different server (operated by you or a third party)
that supports equivalent copying facilities, provided you maintain
clear directions next to the object code saying where to find the
Corresponding Source. Regardless of what server hosts the
Corresponding Source, you remain obligated to ensure that it is
available for as long as needed to satisfy these requirements.
e) Convey the object code using peer-to-peer transmission, provided
you inform other peers where the object code and Corresponding
Source of the work are being offered to the general public at no
charge under subsection 6d.
A separable portion of the object code, whose source code is excluded
from the Corresponding Source as a System Library, need not be
included in conveying the object code work.
A "User Product" is either (1) a "consumer product", which means any
tangible personal property which is normally used for personal, family,
or household purposes, or (2) anything designed or sold for incorporation
into a dwelling. In determining whether a product is a consumer product,
doubtful cases shall be resolved in favor of coverage. For a particular
product received by a particular user, "normally used" refers to a
typical or common use of that class of product, regardless of the status
of the particular user or of the way in which the particular user
actually uses, or expects or is expected to use, the product. A product
is a consumer product regardless of whether the product has substantial
commercial, industrial or non-consumer uses, unless such uses represent
the only significant mode of use of the product.
"Installation Information" for a User Product means any methods,
procedures, authorization keys, or other information required to install
and execute modified versions of a covered work in that User Product from
a modified version of its Corresponding Source. The information must
suffice to ensure that the continued functioning of the modified object
code is in no case prevented or interfered with solely because
modification has been made.
If you convey an object code work under this section in, or with, or
specifically for use in, a User Product, and the conveying occurs as
part of a transaction in which the right of possession and use of the
User Product is transferred to the recipient in perpetuity or for a
fixed term (regardless of how the transaction is characterized), the
Corresponding Source conveyed under this section must be accompanied
by the Installation Information. But this requirement does not apply
if neither you nor any third party retains the ability to install
modified object code on the User Product (for example, the work has
been installed in ROM).
The requirement to provide Installation Information does not include a
requirement to continue to provide support service, warranty, or updates
for a work that has been modified or installed by the recipient, or for
the User Product in which it has been modified or installed. Access to a
network may be denied when the modification itself materially and
adversely affects the operation of the network or violates the rules and
protocols for communication across the network.
Corresponding Source conveyed, and Installation Information provided,
in accord with this section must be in a format that is publicly
documented (and with an implementation available to the public in
source code form), and must require no special password or key for
unpacking, reading or copying.
7. Additional Terms.
"Additional permissions" are terms that supplement the terms of this
License by making exceptions from one or more of its conditions.
Additional permissions that are applicable to the entire Program shall
be treated as though they were included in this License, to the extent
that they are valid under applicable law. If additional permissions
apply only to part of the Program, that part may be used separately
under those permissions, but the entire Program remains governed by
this License without regard to the additional permissions.
When you convey a copy of a covered work, you may at your option
remove any additional permissions from that copy, or from any part of
it. (Additional permissions may be written to require their own
removal in certain cases when you modify the work.) You may place
additional permissions on material, added by you to a covered work,
for which you have or can give appropriate copyright permission.
Notwithstanding any other provision of this License, for material you
add to a covered work, you may (if authorized by the copyright holders of
that material) supplement the terms of this License with terms:
a) Disclaiming warranty or limiting liability differently from the
terms of sections 15 and 16 of this License; or
b) Requiring preservation of specified reasonable legal notices or
author attributions in that material or in the Appropriate Legal
Notices displayed by works containing it; or
c) Prohibiting misrepresentation of the origin of that material, or
requiring that modified versions of such material be marked in
reasonable ways as different from the original version; or
d) Limiting the use for publicity purposes of names of licensors or
authors of the material; or
e) Declining to grant rights under trademark law for use of some
trade names, trademarks, or service marks; or
f) Requiring indemnification of licensors and authors of that
material by anyone who conveys the material (or modified versions of
it) with contractual assumptions of liability to the recipient, for
any liability that these contractual assumptions directly impose on
those licensors and authors.
All other non-permissive additional terms are considered "further
restrictions" within the meaning of section 10. If the Program as you
received it, or any part of it, contains a notice stating that it is
governed by this License along with a term that is a further
restriction, you may remove that term. If a license document contains
a further restriction but permits relicensing or conveying under this
License, you may add to a covered work material governed by the terms
of that license document, provided that the further restriction does
not survive such relicensing or conveying.
If you add terms to a covered work in accord with this section, you
must place, in the relevant source files, a statement of the
additional terms that apply to those files, or a notice indicating
where to find the applicable terms.
Additional terms, permissive or non-permissive, may be stated in the
form of a separately written license, or stated as exceptions;
the above requirements apply either way.
8. Termination.
You may not propagate or modify a covered work except as expressly
provided under this License. Any attempt otherwise to propagate or
modify it is void, and will automatically terminate your rights under
this License (including any patent licenses granted under the third
paragraph of section 11).
However, if you cease all violation of this License, then your
license from a particular copyright holder is reinstated (a)
provisionally, unless and until the copyright holder explicitly and
finally terminates your license, and (b) permanently, if the copyright
holder fails to notify you of the violation by some reasonable means
prior to 60 days after the cessation.
Moreover, your license from a particular copyright holder is
reinstated permanently if the copyright holder notifies you of the
violation by some reasonable means, this is the first time you have
received notice of violation of this License (for any work) from that
copyright holder, and you cure the violation prior to 30 days after
your receipt of the notice.
Termination of your rights under this section does not terminate the
licenses of parties who have received copies or rights from you under
this License. If your rights have been terminated and not permanently
reinstated, you do not qualify to receive new licenses for the same
material under section 10.
9. Acceptance Not Required for Having Copies.
You are not required to accept this License in order to receive or
run a copy of the Program. Ancillary propagation of a covered work
occurring solely as a consequence of using peer-to-peer transmission
to receive a copy likewise does not require acceptance. However,
nothing other than this License grants you permission to propagate or
modify any covered work. These actions infringe copyright if you do
not accept this License. Therefore, by modifying or propagating a
covered work, you indicate your acceptance of this License to do so.
10. Automatic Licensing of Downstream Recipients.
Each time you convey a covered work, the recipient automatically
receives a license from the original licensors, to run, modify and
propagate that work, subject to this License. You are not responsible
for enforcing compliance by third parties with this License.
An "entity transaction" is a transaction transferring control of an
organization, or substantially all assets of one, or subdividing an
organization, or merging organizations. If propagation of a covered
work results from an entity transaction, each party to that
transaction who receives a copy of the work also receives whatever
licenses to the work the party's predecessor in interest had or could
give under the previous paragraph, plus a right to possession of the
Corresponding Source of the work from the predecessor in interest, if
the predecessor has it or can get it with reasonable efforts.
You may not impose any further restrictions on the exercise of the
rights granted or affirmed under this License. For example, you may
not impose a license fee, royalty, or other charge for exercise of
rights granted under this License, and you may not initiate litigation
(including a cross-claim or counterclaim in a lawsuit) alleging that
any patent claim is infringed by making, using, selling, offering for
sale, or importing the Program or any portion of it.
11. Patents.
A "contributor" is a copyright holder who authorizes use under this
License of the Program or a work on which the Program is based. The
work thus licensed is called the contributor's "contributor version".
A contributor's "essential patent claims" are all patent claims
owned or controlled by the contributor, whether already acquired or
hereafter acquired, that would be infringed by some manner, permitted
by this License, of making, using, or selling its contributor version,
but do not include claims that would be infringed only as a
consequence of further modification of the contributor version. For
purposes of this definition, "control" includes the right to grant
patent sublicenses in a manner consistent with the requirements of
this License.
Each contributor grants you a non-exclusive, worldwide, royalty-free
patent license under the contributor's essential patent claims, to
make, use, sell, offer for sale, import and otherwise run, modify and
propagate the contents of its contributor version.
In the following three paragraphs, a "patent license" is any express
agreement or commitment, however denominated, not to enforce a patent
(such as an express permission to practice a patent or covenant not to
sue for patent infringement). To "grant" such a patent license to a
party means to make such an agreement or commitment not to enforce a
patent against the party.
If you convey a covered work, knowingly relying on a patent license,
and the Corresponding Source of the work is not available for anyone
to copy, free of charge and under the terms of this License, through a
publicly available network server or other readily accessible means,
then you must either (1) cause the Corresponding Source to be so
available, or (2) arrange to deprive yourself of the benefit of the
patent license for this particular work, or (3) arrange, in a manner
consistent with the requirements of this License, to extend the patent
license to downstream recipients. "Knowingly relying" means you have
actual knowledge that, but for the patent license, your conveying the
covered work in a country, or your recipient's use of the covered work
in a country, would infringe one or more identifiable patents in that
country that you have reason to believe are valid.
If, pursuant to or in connection with a single transaction or
arrangement, you convey, or propagate by procuring conveyance of, a
covered work, and grant a patent license to some of the parties
receiving the covered work authorizing them to use, propagate, modify
or convey a specific copy of the covered work, then the patent license
you grant is automatically extended to all recipients of the covered
work and works based on it.
A patent license is "discriminatory" if it does not include within
the scope of its coverage, prohibits the exercise of, or is
conditioned on the non-exercise of one or more of the rights that are
specifically granted under this License. You may not convey a covered
work if you are a party to an arrangement with a third party that is
in the business of distributing software, under which you make payment
to the third party based on the extent of your activity of conveying
the work, and under which the third party grants, to any of the
parties who would receive the covered work from you, a discriminatory
patent license (a) in connection with copies of the covered work
conveyed by you (or copies made from those copies), or (b) primarily
for and in connection with specific products or compilations that
contain the covered work, unless you entered into that arrangement,
or that patent license was granted, prior to 28 March 2007.
Nothing in this License shall be construed as excluding or limiting
any implied license or other defenses to infringement that may
otherwise be available to you under applicable patent law.
12. No Surrender of Others' Freedom.
If conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License. If you cannot convey a
covered work so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you may
not convey it at all. For example, if you agree to terms that obligate you
to collect a royalty for further conveying from those to whom you convey
the Program, the only way you could satisfy both those terms and this
License would be to refrain entirely from conveying the Program.
13. Use with the GNU Affero General Public License.
Notwithstanding any other provision of this License, you have
permission to link or combine any covered work with a work licensed
under version 3 of the GNU Affero General Public License into a single
combined work, and to convey the resulting work. The terms of this
License will continue to apply to the part which is the covered work,
but the special requirements of the GNU Affero General Public License,
section 13, concerning interaction through a network will apply to the
combination as such.
14. Revised Versions of this License.
The Free Software Foundation may publish revised and/or new versions of
the GNU General Public License from time to time. Such new versions will
be similar in spirit to the present version, but may differ in detail to
address new problems or concerns.
Each version is given a distinguishing version number. If the
Program specifies that a certain numbered version of the GNU General
Public License "or any later version" applies to it, you have the
option of following the terms and conditions either of that numbered
version or of any later version published by the Free Software
Foundation. If the Program does not specify a version number of the
GNU General Public License, you may choose any version ever published
by the Free Software Foundation.
If the Program specifies that a proxy can decide which future
versions of the GNU General Public License can be used, that proxy's
public statement of acceptance of a version permanently authorizes you
to choose that version for the Program.
Later license versions may give you additional or different
permissions. However, no additional obligations are imposed on any
author or copyright holder as a result of your choosing to follow a
later version.
15. Disclaimer of Warranty.
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
16. Limitation of Liability.
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
SUCH DAMAGES.
17. Interpretation of Sections 15 and 16.
If the disclaimer of warranty and limitation of liability provided
above cannot be given local legal effect according to their terms,
reviewing courts shall apply local law that most closely approximates
an absolute waiver of all civil liability in connection with the
Program, unless a warranty or assumption of liability accompanies a
copy of the Program in return for a fee.
END OF TERMS AND CONDITIONS
How to Apply These Terms to Your New Programs
If you develop a new program, and you want it to be of the greatest
possible use to the public, the best way to achieve this is to make it
free software which everyone can redistribute and change under these terms.
To do so, attach the following notices to the program. It is safest
to attach them to the start of each source file to most effectively
state the exclusion of warranty; and each file should have at least
the "copyright" line and a pointer to where the full notice is found.
<one line to give the program's name and a brief idea of what it does.>
Copyright (C) <year> <name of author>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
Also add information on how to contact you by electronic and paper mail.
If the program does terminal interaction, make it output a short
notice like this when it starts in an interactive mode:
<program> Copyright (C) <year> <name of author>
This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
This is free software, and you are welcome to redistribute it
under certain conditions; type `show c' for details.
The hypothetical commands `show w' and `show c' should show the appropriate
parts of the General Public License. Of course, your program's commands
might be different; for a GUI interface, you would use an "about box".
You should also get your employer (if you work as a programmer) or school,
if any, to sign a "copyright disclaimer" for the program, if necessary.
For more information on this, and how to apply and follow the GNU GPL, see
<https://www.gnu.org/licenses/>.
The GNU General Public License does not permit incorporating your program
into proprietary programs. If your program is a subroutine library, you
may consider it more useful to permit linking proprietary applications with
the library. If this is what you want to do, use the GNU Lesser General
Public License instead of this License. But first, please read
<https://www.gnu.org/licenses/why-not-lgpl.html>.
+308
View File
@@ -0,0 +1,308 @@
SpiceTools
==========
This is a loader for various arcade games developed by 573.
The project is using CMake as it's build system, with a custom build
script for making packages ready for distribution and to keep it easy
for people not knowing how to use CMake.
## Building/Distribution
We're currently using Arch Linux for building the binaries.
You'll need:
- MinGW-64 packages (can be found in the AUR)
- bash
- git
- zip
- upx (optional)
For any other GNU/Linux distributions (or Windows lol), you're on your
own.
To build the project, run:
$ ./build_all.sh
## Build Configuration
You can tweak some settings at the beginning of the build script. You
might want to modify the paths of the toolchains if yours differ. It's
also possible to disable UPX compression and source distribution for
example.
If you're not using an IDE and want to run the build script from command
line manually each time you make a change, you can set CLEAN_BUILD to 0
so it will only compile what's needed. When modifying the resources you
should build from scratch, since CMake isn't able to track changes of
those files (e.g. changelog.txt, licenses.txt).
You can put your custom build script under "build_all.local.sh" if you
don't want git to track the changes you made to the build settings.
## Debug Build
To get proper stackstraces, you need to build with the DEBUG setting
enabled. This will set the CMake build type to debug and the script
will try to make use of cv2pdb.
Check external/cv2pdb/README.md for details on how to set it up.
It is required to use cv2pdb to generate PDB output, because at time of
writing, MinGW still didn't support Microsoft's proprietary file format.
## API
SpiceTools is providing a TCP/JSON based API. You can enable it with the
`-api [PORT]` option. It's recommended to also set a password with
`-apipass [PASS]`.
The protocol is meant to be simple, easy to use and with few overhead.
To connect to the API, just open a TCP connection to the host computer
at the specified port.
To make a request you write the UTF-8 encoded JSON contents to the
socket. To mark the end of the request, you need to terminate the JSON
string with `0x00`. The server returns its answer on the same way,
UTF-8 encoded JSON terminated with NULL.
To save space, by default the JSONs are without whitespace. To enable
pretty printing, use `-apipretty`.
If you want to test the API server without running a game, you can
run it headless via `-apidebug`.
If a password is specified, both request and response are encrypted
using RC4 with the key being the password encoded in UTF-8.
While only providing weak security when the password is static,
it's simple to implement and requires no authentication protocol.
The password is also able to change dynamically, see
`control.session_refresh()` for details.
If you don't want to worry about all the details, you can just use one
of the supplied libraries (check /api/resources). There's also a little
remote control GUI available for python.
### WebSocket
SpiceTools opens a WebSocket server on the specified port plus one.
That means if the API is on 1337, the WebSocket server will be on 1338.
The protocol is the very same as for the normal API, but instead of
directly sending the data over TCP you need to send binary datapackets.
The included dart spiceapi library also has a WebSocket implementation.
### Example Call
This example inserts the a card into P1's reader slot.
If you need more examples, you can check the example code.
#### Request
```JSON
{
"id": 1,
"module": "card",
"function": "insert",
"params": [0, "E004010000000000"]
}
```
#### Response
```JSON
{
"id": 1,
"errors": [],
"data": []
}
```
### Modules
For the sake of simplifying this documentation, I will describe functions
as if they were called via a normal programming language. That means,
besides `module`/`function` needing to be set accordingly, the `params` field
must contain all the parameters described below. Some of them are optional.
The returning data will always be in the `data` field of the response.
Errors will be reported as strings in the `errors` field. Sometimes, you can
receive more than one error. The error messages may be changed/improved in the
future, so you shouldn't rely on them. If the error list is empty, the function
executed successfully.
The `id` of the response will always match the `id` of the request. The API
server itself doesn't expect any logic of your ID choices, however you can use
them to make sure your answers aren't arriving out of order. Currently it
doesn't matter since the TCP protocol doesn't allow for out of order data,
however this may change when/if support for UDP is being introduced. The only
restriction is that the ID has to be a valid 64-bit unsigned integer.
#### Card
- insert(index: uint, card_id: hex)
- inserts a card which gets read by the emulated card readers for the game
- index has to be either 0 (for P1) or 1 (for P2)
- card_id has to be a valid 16-character hex string
#### Coin
- get()
- returns the amount of unprocessed coins in queue
- not equal to the amount of coins shown ingame since the game may drain it
- set(amount: int)
- sets the amount of coins in queue for the game to process
- insert(amount: int)
- adds the amount to the coins in queue
- amount is optional and defaults to 1
- blocker_get()
- returns the current coin blocker state (false: open, true: closed)
#### Info
- avs()
- returns a dict including the AVS model, dest, spec, rev and ext
- launcher()
- returns a dict including the version, compile date/time, system time and
the arguments used to start the process
- memory()
- returns a dict including total, total used and bytes used by the process
for both physical and virtual RAM
#### Keypads
For all functions in this module, the keypad parameter must be
either 0 (for P1) or 1 (for P2). Accepted keypad characters are "0" to "9" for
the single digit numbers, "A" for the double zero and "D" for the decimal key.
- write(keypad: uint, input: str)
- writes all characters in input sequentially to the keypad
- this should be used for things like PIN input
- set(keypad: uint, key: char, ...)
- clears all overrides, then applies each given key as override
- if a key is overridden, it shows up as pressed in-game
- get(keypad: uint)
- returns all pressed keys of the specified keypad
#### Analogs/Buttons/Lights
All of those three modules have equally named methods for you to call.
- read()
- returns an array of state objects containing name, state and a bool
- the bool indicates if the object is active (e.g. the button was overridden)
- write([name: str, state: float], ...)
- applies the states as an override value
- the device binding via the config will be ignored while an override is set
- if an override value is set, the object will be marked as active
- write_reset([name: str], ...)
- removes the override value from the objects specified by name
- if no names were passed, all overrides will be removed
##### Additional API for lights
- read(name: string, ...)
- same as read(), but you can specify light names
- write_reset(name: str, ...)
- same as write_reset(), but it accepts a flat list of strings
#### Touch
- read()
- returns an array of state objects containing id, x and y
- write([id: uint, x: int, y: int], ...)
- adds the given touch points having an unknown ID
- overrides old touch points when the ID matches
- write_reset(id: uint, ...)
- removes the touch points matching one of the given IDs
#### Control
This module only functions if a password is being used to communicate,
with the single exception of session_refresh().
- raise(signal: str)
- raises a signal to the current process
- signal is a string and must be one of the following values:
"SIGABRT", "SIGFPE", "SIGILL", "SIGINT", "SIGSEGV", "SIGTERM"
- the signal will be raised while the message is being processed
- exit(code: int)
- code is optional and defaults to 0
- the process will end while the message is being processed
- restart()
- spawns a new instance with the same arguments as passed to the main executable
- session_refresh()
- generates a new secure password and applies it after the response
- can be used to increase the security by using the password as some kind of
session token
- recommended to call directly after connecting and once in a while for
persistent connections
- shutdown()
- tries to force shutdown the computer
- reboot()
- tries to force reboot the computer
#### Memory
This module only functions if a password is being used to communicate.
All offsets are specified in file offsets of the corresponding `dll_name`,
which also means that your hex edits are applicable directly.
- write(dll_name: str, data: hex, offset: uint)
- writes the bytes in data to the specified offset
- read(dll_name: str, offset: uint, size: uint)
- reads bytes starting from offset and returns it
- signature(dll_name: str, signature: hex, replacement: hex, offset: uint,
usage: uint)
- tries to find the signature in the module's memory
- unknown bytes are masked out via "??" (e.g. "75??90????74")
- masking out bytes works for both the signature and the replacement
- offset is the byte difference between the offset of the found signature and
the offset where the replacement data gets written to
- usage is the number of the result being used for replacement, starting at 0
- returns the offset where the replacement data gets written to
- the replacement string can be empty, so you can only get the address of the
signature while not actually replacing anything
#### IIDX
- ticker_get()
- returns a string of the characters displayed on the 16 segment display
- ticker_set(text: str)
- sets the contents of the 16 segment display and disables writes from game
- ticker_reset()
- re-enables writes from game
- tapeled_get(name: str, ...)
- returns a list containing a dict of the current tape LED states. The dict keys are:
- `Stage Left`
- `Stage Right`
- `Cabinet Left`
- `Cabinet Right`
- `Control Panel Under`
- `Ceiling Left`
- `Title Left`
- `Title Right`
- `Ceiling Right`
- `Touch Panel Left`
- `Touch Panel Right`
- `Side Panel Left Inner`
- `Side Panel Left Outer`
- `Side Panel Left`
- `Side Panel Right Outer`
- `Side Panel Right Inner`
- `Side Panel Right`
#### LCD
- info()
- returns information about the serial LCD controller some games use
#### Resize
- image_resize_enable(enable: bool)
- enables or disables image resize state
- image_resize_set_scene(scene: int)
- sets the active scene for image resize state; set to 0 to disable resize
## Native wrapper libraries
Spicetools provides wrapper libraries in: Arduino, C++, Dart, and Python.
Python is the only one that is fully spec compliant.
Other libraries may be missing features and contain bugs; please feel free to
contribute code to fill the gaps if you work on a project using these libraries.
## Spice SDK (for DLL hooks / plugins)
As an alternative to Spice API, Spice SDK is available.
This is a flat C, header-only library that can be included in your DLL. Once
initialized, you can call directly into helper routines provided by Spicetools
executable, making low-latency and high-throughput interactions possible.
Check the documentation on the wiki for more details.
## License
Unless otherwise noted, all files are licensed under the GPLv3.
See the LICENSE file for the full license text.
Files not originating from this project should be placed in "external",
they each have their own license.
###### What that means for you
- If you want to distribute the binaries, you'll have to include the
sources, otherwise you're violating the GPL.
- If you want to merge some of this project's code into similar
software, that software has to be put under the GPL as well.
You'll probably have to modify a lot anyways, so you can just use the
source as some kind of documentation.
@@ -4,6 +4,7 @@
#include "launcher/launcher.h" #include "launcher/launcher.h"
#include "misc/wintouchemu.h" #include "misc/wintouchemu.h"
#include "rawinput/rawinput.h" #include "rawinput/rawinput.h"
#include "touch/native/nativetouchhook.h"
// static stuff // static stuff
static int ACIO_WARMUP = 0; static int ACIO_WARMUP = 0;
@@ -140,8 +141,9 @@ static int __cdecl ac_io_update(int a1) {
// flush device output // flush device output
RI_MGR->devices_flush_output(); RI_MGR->devices_flush_output();
// update wintouchemu // update touch emulation
wintouchemu::update(); wintouchemu::update();
nativetouch::refresh_contact_lifetime();
return 1; return 1;
} }

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